2020-02-16 12:27:14 +00:00
|
|
|
/*
|
|
|
|
* 'psusan': Pseudo Ssh for Untappable, Separately Authenticated Networks
|
|
|
|
*
|
|
|
|
* This is a standalone application that speaks on its standard I/O
|
2020-12-13 12:30:19 +00:00
|
|
|
* (or a listening Unix-domain socket) the server end of the bare
|
|
|
|
* ssh-connection protocol used by PuTTY's connection sharing.
|
|
|
|
*
|
|
|
|
* The idea of this tool is that you can use it to communicate across
|
|
|
|
* any 8-bit-clean data channel between two inconveniently separated
|
|
|
|
* domains, provided the channel is already (as the name suggests)
|
|
|
|
* adequately secured against eavesdropping and modification and
|
|
|
|
* already authenticated as the right user.
|
|
|
|
*
|
|
|
|
* If you're sitting at one end of such a channel and want to type
|
|
|
|
* commands into the other end, the most obvious thing to do is to run
|
|
|
|
* a terminal session directly over it. But if you run psusan at one
|
|
|
|
* end, and a PuTTY (or compatible) client at the other end, then you
|
|
|
|
* not only get a single terminal session: you get all the other SSH
|
|
|
|
* amenities, like the ability to spawn extra terminal sessions,
|
|
|
|
* forward ports or X11 connections, even forward an SSH agent.
|
|
|
|
*
|
|
|
|
* There are a surprising number of channels of that kind; see the man
|
|
|
|
* page for some examples.
|
2020-02-16 12:27:14 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <signal.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <termios.h>
|
|
|
|
#include <pwd.h>
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <sys/time.h>
|
|
|
|
|
|
|
|
#include "putty.h"
|
|
|
|
#include "mpint.h"
|
|
|
|
#include "ssh.h"
|
2021-04-22 16:58:40 +00:00
|
|
|
#include "ssh/server.h"
|
2020-02-16 12:27:14 +00:00
|
|
|
|
|
|
|
void modalfatalbox(const char *p, ...)
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
fprintf(stderr, "FATAL ERROR: ");
|
|
|
|
va_start(ap, p);
|
|
|
|
vfprintf(stderr, p, ap);
|
|
|
|
va_end(ap);
|
|
|
|
fputc('\n', stderr);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
void nonfatal(const char *p, ...)
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
fprintf(stderr, "ERROR: ");
|
|
|
|
va_start(ap, p);
|
|
|
|
vfprintf(stderr, p, ap);
|
|
|
|
va_end(ap);
|
|
|
|
fputc('\n', stderr);
|
|
|
|
}
|
|
|
|
|
|
|
|
char *platform_default_s(const char *name)
|
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool platform_default_b(const char *name, bool def)
|
|
|
|
{
|
|
|
|
return def;
|
|
|
|
}
|
|
|
|
|
|
|
|
int platform_default_i(const char *name, int def)
|
|
|
|
{
|
|
|
|
return def;
|
|
|
|
}
|
|
|
|
|
|
|
|
FontSpec *platform_default_fontspec(const char *name)
|
|
|
|
{
|
2023-02-18 13:43:50 +00:00
|
|
|
return fontspec_new_default();
|
2020-02-16 12:27:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Filename *platform_default_filename(const char *name)
|
|
|
|
{
|
|
|
|
return filename_from_str("");
|
|
|
|
}
|
|
|
|
|
|
|
|
char *x_get_default(const char *key)
|
|
|
|
{
|
|
|
|
return NULL; /* this is a stub */
|
|
|
|
}
|
|
|
|
|
|
|
|
void old_keyfile_warning(void) { }
|
|
|
|
|
|
|
|
void timer_change_notify(unsigned long next)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
char *platform_get_x_display(void) { return NULL; }
|
|
|
|
|
2020-11-04 21:50:47 +00:00
|
|
|
void make_unix_sftp_filehandle_key(void *vdata, size_t size)
|
|
|
|
{
|
|
|
|
/* psusan runs without a random number generator, so we can't make
|
|
|
|
* this up by random_read. Fortunately, psusan is also
|
|
|
|
* non-adversarial, so it's safe to generate this trivially. */
|
|
|
|
unsigned char *data = (unsigned char *)vdata;
|
|
|
|
for (size_t i = 0; i < size; i++)
|
|
|
|
data[i] = (unsigned)rand() / ((unsigned)RAND_MAX / 256);
|
|
|
|
}
|
|
|
|
|
2020-02-16 12:27:14 +00:00
|
|
|
static bool verbose;
|
|
|
|
|
|
|
|
struct server_instance {
|
|
|
|
unsigned id;
|
|
|
|
LogPolicy logpolicy;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void log_to_stderr(unsigned id, const char *msg)
|
|
|
|
{
|
2020-12-13 11:54:45 +00:00
|
|
|
if (!verbose)
|
|
|
|
return;
|
2020-02-16 12:27:14 +00:00
|
|
|
if (id != (unsigned)-1)
|
|
|
|
fprintf(stderr, "#%u: ", id);
|
|
|
|
fputs(msg, stderr);
|
|
|
|
fputc('\n', stderr);
|
|
|
|
fflush(stderr);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void server_eventlog(LogPolicy *lp, const char *event)
|
|
|
|
{
|
|
|
|
struct server_instance *inst = container_of(
|
|
|
|
lp, struct server_instance, logpolicy);
|
|
|
|
if (verbose)
|
|
|
|
log_to_stderr(inst->id, event);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void server_logging_error(LogPolicy *lp, const char *event)
|
|
|
|
{
|
|
|
|
struct server_instance *inst = container_of(
|
|
|
|
lp, struct server_instance, logpolicy);
|
|
|
|
log_to_stderr(inst->id, event); /* unconditional */
|
|
|
|
}
|
|
|
|
|
|
|
|
static int server_askappend(
|
|
|
|
LogPolicy *lp, Filename *filename,
|
|
|
|
void (*callback)(void *ctx, int result), void *ctx)
|
|
|
|
{
|
|
|
|
return 2; /* always overwrite (FIXME: could make this a cmdline option) */
|
|
|
|
}
|
|
|
|
|
|
|
|
static const LogPolicyVtable server_logpolicy_vt = {
|
Change vtable defs to use C99 designated initialisers.
This is a sweeping change applied across the whole code base by a spot
of Emacs Lisp. Now, everywhere I declare a vtable filled with function
pointers (and the occasional const data member), all the members of
the vtable structure are initialised by name using the '.fieldname =
value' syntax introduced in C99.
We were already using this syntax for a handful of things in the new
key-generation progress report system, so it's not new to the code
base as a whole.
The advantage is that now, when a vtable only declares a subset of the
available fields, I can initialise the rest to NULL or zero just by
leaving them out. This is most dramatic in a couple of the outlying
vtables in things like psocks (which has a ConnectionLayerVtable
containing only one non-NULL method), but less dramatically, it means
that the new 'flags' field in BackendVtable can be completely left out
of every backend definition except for the SUPDUP one which defines it
to a nonzero value. Similarly, the test_for_upstream method only used
by SSH doesn't have to be mentioned in the rest of the backends;
network Plugs for listening sockets don't have to explicitly null out
'receive' and 'sent', and vice versa for 'accepting', and so on.
While I'm at it, I've normalised the declarations so they don't use
the unnecessarily verbose 'struct' keyword. Also a handful of them
weren't const; now they are.
2020-03-10 21:06:29 +00:00
|
|
|
.eventlog = server_eventlog,
|
|
|
|
.askappend = server_askappend,
|
|
|
|
.logging_error = server_logging_error,
|
|
|
|
.verbose = null_lp_verbose_no,
|
2020-02-16 12:27:14 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static void show_help(FILE *fp)
|
|
|
|
{
|
|
|
|
fputs("usage: psusan [options]\n"
|
2020-12-13 11:54:45 +00:00
|
|
|
"options: --listen SOCKETPATH listen for connections on a Unix-domain socket\n"
|
|
|
|
" --listen-once (with --listen) stop after one connection\n"
|
|
|
|
" --verbose print log messages to standard error\n"
|
|
|
|
" --sessiondir DIR cwd for session subprocess (default $HOME)\n"
|
2020-02-16 12:27:14 +00:00
|
|
|
" --sshlog FILE write ssh-connection packet log to FILE\n"
|
|
|
|
" --sshrawlog FILE write packets and raw data log to FILE\n"
|
|
|
|
"also: psusan --help show this text\n"
|
|
|
|
" psusan --version show version information\n", fp);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void show_version_and_exit(void)
|
|
|
|
{
|
|
|
|
char *buildinfo_text = buildinfo("\n");
|
|
|
|
printf("%s: %s\n%s\n", appname, ver, buildinfo_text);
|
|
|
|
sfree(buildinfo_text);
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
const bool buildinfo_gtk_relevant = false;
|
|
|
|
|
2020-12-13 11:54:45 +00:00
|
|
|
static bool listening = false, listen_once = false;
|
2020-02-16 12:27:14 +00:00
|
|
|
static bool finished = false;
|
|
|
|
void server_instance_terminated(LogPolicy *lp)
|
|
|
|
{
|
|
|
|
struct server_instance *inst = container_of(
|
|
|
|
lp, struct server_instance, logpolicy);
|
|
|
|
|
2020-12-13 11:54:45 +00:00
|
|
|
if (listening && !listen_once) {
|
|
|
|
log_to_stderr(inst->id, "connection terminated");
|
|
|
|
} else {
|
|
|
|
finished = true;
|
|
|
|
}
|
2020-02-16 12:27:14 +00:00
|
|
|
|
|
|
|
sfree(inst);
|
|
|
|
}
|
|
|
|
|
2020-09-29 06:47:29 +00:00
|
|
|
bool psusan_continue(void *ctx, bool fd, bool cb)
|
|
|
|
{
|
|
|
|
return !finished;
|
|
|
|
}
|
|
|
|
|
2020-02-16 12:27:14 +00:00
|
|
|
static bool longoptarg(const char *arg, const char *expected,
|
|
|
|
const char **val, int *argcp, char ***argvp)
|
|
|
|
{
|
|
|
|
int len = strlen(expected);
|
|
|
|
if (memcmp(arg, expected, len))
|
|
|
|
return false;
|
|
|
|
if (arg[len] == '=') {
|
|
|
|
*val = arg + len + 1;
|
|
|
|
return true;
|
|
|
|
} else if (arg[len] == '\0') {
|
|
|
|
if (--*argcp > 0) {
|
|
|
|
*val = *++*argvp;
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
fprintf(stderr, "%s: option %s expects an argument\n",
|
|
|
|
appname, expected);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool longoptnoarg(const char *arg, const char *expected)
|
|
|
|
{
|
|
|
|
int len = strlen(expected);
|
|
|
|
if (memcmp(arg, expected, len))
|
|
|
|
return false;
|
|
|
|
if (arg[len] == '=') {
|
|
|
|
fprintf(stderr, "%s: option %s expects no argument\n",
|
|
|
|
appname, expected);
|
|
|
|
exit(1);
|
|
|
|
} else if (arg[len] == '\0') {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct server_config {
|
|
|
|
Conf *conf;
|
|
|
|
const SshServerConfig *ssc;
|
2020-12-13 11:54:45 +00:00
|
|
|
|
2020-02-16 12:27:14 +00:00
|
|
|
unsigned next_id;
|
2020-12-13 11:54:45 +00:00
|
|
|
|
|
|
|
Socket *listening_socket;
|
|
|
|
Plug listening_plug;
|
2020-02-16 12:27:14 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static Plug *server_conn_plug(
|
|
|
|
struct server_config *cfg, struct server_instance **inst_out)
|
|
|
|
{
|
|
|
|
struct server_instance *inst = snew(struct server_instance);
|
|
|
|
|
|
|
|
memset(inst, 0, sizeof(*inst));
|
|
|
|
|
|
|
|
inst->id = cfg->next_id++;
|
|
|
|
inst->logpolicy.vt = &server_logpolicy_vt;
|
|
|
|
|
|
|
|
if (inst_out)
|
|
|
|
*inst_out = inst;
|
|
|
|
|
|
|
|
return ssh_server_plug(
|
|
|
|
cfg->conf, cfg->ssc, NULL, 0, NULL, NULL,
|
|
|
|
&inst->logpolicy, &unix_live_sftpserver_vt);
|
|
|
|
}
|
|
|
|
|
2024-06-26 07:29:39 +00:00
|
|
|
static void server_log(Plug *plug, Socket *s, PlugLogType type, SockAddr *addr,
|
|
|
|
int port, const char *error_msg, int error_code)
|
2020-12-13 11:54:45 +00:00
|
|
|
{
|
|
|
|
log_to_stderr(-1, error_msg);
|
|
|
|
}
|
|
|
|
|
New API for plug_closing() with a custom type enum.
Passing an operating-system-specific error code to plug_closing(),
such as errno or GetLastError(), was always a bit weird, given that it
generally had to be handled by cross-platform receiving code in
backends. I had the platform.h implementations #define any error
values that the cross-platform code would have to handle specially,
but that's still not a great system, because it also doesn't leave
freedom to invent error representations of my own that don't
correspond to any OS code. (For example, the ones I just removed from
proxy.h.)
So now, the OS error code is gone from the plug_closing API, and in
its place is a custom enumeration of closure types: normal, error, and
the special case BROKEN_PIPE which is the only OS error code we have
so far needed to handle specially. (All others just mean 'abandon the
connection and print the textual message'.)
Having already centralised the handling of OS error codes in the
previous commit, we've now got a convenient place to add any further
type codes for errors needing special handling: each of Unix
plug_closing_errno(), Windows plug_closing_system_error(), and Windows
plug_closing_winsock_error() can easily grow extra special cases if
need be, and each one will only have to live in one place.
2021-11-06 13:28:32 +00:00
|
|
|
static void server_closing(Plug *plug, PlugCloseType type,
|
|
|
|
const char *error_msg)
|
2020-12-13 11:54:45 +00:00
|
|
|
{
|
New API for plug_closing() with a custom type enum.
Passing an operating-system-specific error code to plug_closing(),
such as errno or GetLastError(), was always a bit weird, given that it
generally had to be handled by cross-platform receiving code in
backends. I had the platform.h implementations #define any error
values that the cross-platform code would have to handle specially,
but that's still not a great system, because it also doesn't leave
freedom to invent error representations of my own that don't
correspond to any OS code. (For example, the ones I just removed from
proxy.h.)
So now, the OS error code is gone from the plug_closing API, and in
its place is a custom enumeration of closure types: normal, error, and
the special case BROKEN_PIPE which is the only OS error code we have
so far needed to handle specially. (All others just mean 'abandon the
connection and print the textual message'.)
Having already centralised the handling of OS error codes in the
previous commit, we've now got a convenient place to add any further
type codes for errors needing special handling: each of Unix
plug_closing_errno(), Windows plug_closing_system_error(), and Windows
plug_closing_winsock_error() can easily grow extra special cases if
need be, and each one will only have to live in one place.
2021-11-06 13:28:32 +00:00
|
|
|
if (type != PLUGCLOSE_NORMAL)
|
|
|
|
log_to_stderr(-1, error_msg);
|
2020-12-13 11:54:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int server_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
|
|
|
|
{
|
|
|
|
struct server_config *cfg = container_of(
|
|
|
|
p, struct server_config, listening_plug);
|
|
|
|
Socket *s;
|
|
|
|
const char *err;
|
|
|
|
|
|
|
|
struct server_instance *inst;
|
|
|
|
|
|
|
|
if (listen_once) {
|
|
|
|
if (!cfg->listening_socket) /* in case of rapid double-accept */
|
|
|
|
return 1;
|
|
|
|
sk_close(cfg->listening_socket);
|
|
|
|
cfg->listening_socket = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
Plug *plug = server_conn_plug(cfg, &inst);
|
|
|
|
s = constructor(ctx, plug);
|
|
|
|
if ((err = sk_socket_error(s)) != NULL)
|
|
|
|
return 1;
|
|
|
|
|
2024-06-26 05:35:40 +00:00
|
|
|
SocketEndpointInfo *pi = sk_peer_info(s);
|
2020-12-13 11:54:45 +00:00
|
|
|
|
|
|
|
char *msg = dupprintf("new connection from %s", pi->log_text);
|
|
|
|
log_to_stderr(inst->id, msg);
|
|
|
|
sfree(msg);
|
2024-06-26 05:35:40 +00:00
|
|
|
sk_free_endpoint_info(pi);
|
2020-12-13 11:54:45 +00:00
|
|
|
|
|
|
|
sk_set_frozen(s, false);
|
|
|
|
ssh_server_start(plug, s);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const PlugVtable server_plugvt = {
|
|
|
|
.log = server_log,
|
|
|
|
.closing = server_closing,
|
|
|
|
.accepting = server_accepting,
|
|
|
|
};
|
|
|
|
|
2020-02-16 12:27:14 +00:00
|
|
|
unsigned auth_methods(AuthPolicy *ap)
|
|
|
|
{ return 0; }
|
|
|
|
bool auth_none(AuthPolicy *ap, ptrlen username)
|
|
|
|
{ return false; }
|
|
|
|
int auth_password(AuthPolicy *ap, ptrlen username, ptrlen password,
|
|
|
|
ptrlen *new_password_opt)
|
|
|
|
{ return 0; }
|
|
|
|
bool auth_publickey(AuthPolicy *ap, ptrlen username, ptrlen public_blob)
|
|
|
|
{ return false; }
|
|
|
|
RSAKey *auth_publickey_ssh1(
|
|
|
|
AuthPolicy *ap, ptrlen username, mp_int *rsa_modulus)
|
|
|
|
{ return NULL; }
|
|
|
|
AuthKbdInt *auth_kbdint_prompts(AuthPolicy *ap, ptrlen username)
|
|
|
|
{ return NULL; }
|
|
|
|
int auth_kbdint_responses(AuthPolicy *ap, const ptrlen *responses)
|
|
|
|
{ return -1; }
|
|
|
|
char *auth_ssh1int_challenge(AuthPolicy *ap, unsigned method, ptrlen username)
|
|
|
|
{ return NULL; }
|
|
|
|
bool auth_ssh1int_response(AuthPolicy *ap, ptrlen response)
|
|
|
|
{ return false; }
|
|
|
|
bool auth_successful(AuthPolicy *ap, ptrlen username, unsigned method)
|
|
|
|
{ return false; }
|
|
|
|
|
|
|
|
int main(int argc, char **argv)
|
|
|
|
{
|
2020-12-13 11:54:45 +00:00
|
|
|
const char *listen_socket = NULL;
|
|
|
|
|
2020-02-16 12:27:14 +00:00
|
|
|
SshServerConfig ssc;
|
|
|
|
|
|
|
|
Conf *conf = make_ssh_server_conf();
|
|
|
|
|
|
|
|
memset(&ssc, 0, sizeof(ssc));
|
|
|
|
|
2020-12-13 11:09:51 +00:00
|
|
|
ssc.application_name = "PSUSAN";
|
2020-02-16 12:27:14 +00:00
|
|
|
ssc.session_starting_dir = getenv("HOME");
|
|
|
|
ssc.bare_connection = true;
|
|
|
|
|
|
|
|
while (--argc > 0) {
|
|
|
|
const char *arg = *++argv;
|
|
|
|
const char *val;
|
|
|
|
|
|
|
|
if (longoptnoarg(arg, "--help")) {
|
|
|
|
show_help(stdout);
|
|
|
|
exit(0);
|
|
|
|
} else if (longoptnoarg(arg, "--version")) {
|
|
|
|
show_version_and_exit();
|
2020-12-13 11:54:45 +00:00
|
|
|
} else if (longoptnoarg(arg, "--verbose") || !strcmp(arg, "-v")) {
|
|
|
|
verbose = true;
|
2020-02-16 12:27:14 +00:00
|
|
|
} else if (longoptarg(arg, "--sessiondir", &val, &argc, &argv)) {
|
|
|
|
ssc.session_starting_dir = val;
|
|
|
|
} else if (longoptarg(arg, "--sshlog", &val, &argc, &argv) ||
|
|
|
|
longoptarg(arg, "-sshlog", &val, &argc, &argv)) {
|
|
|
|
Filename *logfile = filename_from_str(val);
|
|
|
|
conf_set_filename(conf, CONF_logfilename, logfile);
|
|
|
|
filename_free(logfile);
|
|
|
|
conf_set_int(conf, CONF_logtype, LGTYP_PACKETS);
|
|
|
|
conf_set_int(conf, CONF_logxfovr, LGXF_OVR);
|
|
|
|
} else if (longoptarg(arg, "--sshrawlog", &val, &argc, &argv) ||
|
|
|
|
longoptarg(arg, "-sshrawlog", &val, &argc, &argv)) {
|
|
|
|
Filename *logfile = filename_from_str(val);
|
|
|
|
conf_set_filename(conf, CONF_logfilename, logfile);
|
|
|
|
filename_free(logfile);
|
|
|
|
conf_set_int(conf, CONF_logtype, LGTYP_SSHRAW);
|
|
|
|
conf_set_int(conf, CONF_logxfovr, LGXF_OVR);
|
2020-12-13 11:54:45 +00:00
|
|
|
} else if (longoptarg(arg, "--listen", &val, &argc, &argv)) {
|
|
|
|
listen_socket = val;
|
|
|
|
} else if (!strcmp(arg, "--listen-once")) {
|
|
|
|
listen_once = true;
|
2020-02-16 12:27:14 +00:00
|
|
|
} else {
|
|
|
|
fprintf(stderr, "%s: unrecognised option '%s'\n", appname, arg);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sk_init();
|
|
|
|
uxsel_init();
|
|
|
|
|
|
|
|
struct server_config scfg;
|
|
|
|
scfg.conf = conf;
|
|
|
|
scfg.ssc = &ssc;
|
|
|
|
scfg.next_id = 0;
|
|
|
|
|
2020-12-13 11:54:45 +00:00
|
|
|
if (listen_socket) {
|
|
|
|
listening = true;
|
|
|
|
scfg.listening_plug.vt = &server_plugvt;
|
|
|
|
SockAddr *addr = unix_sock_addr(listen_socket);
|
|
|
|
scfg.listening_socket = new_unix_listener(addr, &scfg.listening_plug);
|
|
|
|
char *msg = dupprintf("listening on Unix socket %s", listen_socket);
|
|
|
|
log_to_stderr(-1, msg);
|
|
|
|
sfree(msg);
|
|
|
|
} else {
|
|
|
|
struct server_instance *inst;
|
|
|
|
Plug *plug = server_conn_plug(&scfg, &inst);
|
2021-09-13 13:34:46 +00:00
|
|
|
ssh_server_start(plug, make_fd_socket(0, 1, -1, NULL, 0, plug));
|
2020-12-13 11:54:45 +00:00
|
|
|
log_to_stderr(inst->id, "running directly on stdio");
|
|
|
|
}
|
2020-02-16 12:27:14 +00:00
|
|
|
|
|
|
|
cli_main_loop(cliloop_no_pw_setup, cliloop_no_pw_check,
|
2020-09-29 06:47:29 +00:00
|
|
|
psusan_continue, NULL);
|
2020-02-16 12:27:14 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|