diff options
Diffstat (limited to 'unix/psusan.c')
-rw-r--r-- | unix/psusan.c | 421 |
1 files changed, 421 insertions, 0 deletions
diff --git a/unix/psusan.c b/unix/psusan.c new file mode 100644 index 00000000..1d5816d5 --- /dev/null +++ b/unix/psusan.c @@ -0,0 +1,421 @@ +/* + * 'psusan': Pseudo Ssh for Untappable, Separately Authenticated Networks + * + * This is a standalone application that speaks on its standard I/O + * (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. + */ + +#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" +#include "ssh/server.h" + +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) +{ + return fontspec_new(""); +} + +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; } + +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); +} + +static bool verbose; + +struct server_instance { + unsigned id; + LogPolicy logpolicy; +}; + +static void log_to_stderr(unsigned id, const char *msg) +{ + if (!verbose) + return; + 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 = { + .eventlog = server_eventlog, + .askappend = server_askappend, + .logging_error = server_logging_error, + .verbose = null_lp_verbose_no, +}; + +static void show_help(FILE *fp) +{ + fputs("usage: psusan [options]\n" + "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" + " --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; + +static bool listening = false, listen_once = false; +static bool finished = false; +void server_instance_terminated(LogPolicy *lp) +{ + struct server_instance *inst = container_of( + lp, struct server_instance, logpolicy); + + if (listening && !listen_once) { + log_to_stderr(inst->id, "connection terminated"); + } else { + finished = true; + } + + sfree(inst); +} + +bool psusan_continue(void *ctx, bool fd, bool cb) +{ + return !finished; +} + +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; + + unsigned next_id; + + Socket *listening_socket; + Plug listening_plug; +}; + +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); +} + +static void server_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, + const char *error_msg, int error_code) +{ + log_to_stderr(-1, error_msg); +} + +static void server_closing(Plug *plug, PlugCloseType type, + const char *error_msg) +{ + if (type != PLUGCLOSE_NORMAL) + log_to_stderr(-1, error_msg); +} + +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; + + SocketPeerInfo *pi = sk_peer_info(s); + + char *msg = dupprintf("new connection from %s", pi->log_text); + log_to_stderr(inst->id, msg); + sfree(msg); + sk_free_peer_info(pi); + + 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, +}; + +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) +{ + const char *listen_socket = NULL; + + SshServerConfig ssc; + + Conf *conf = make_ssh_server_conf(); + + memset(&ssc, 0, sizeof(ssc)); + + ssc.application_name = "PSUSAN"; + 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(); + } else if (longoptnoarg(arg, "--verbose") || !strcmp(arg, "-v")) { + verbose = true; + } 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); + } else if (longoptarg(arg, "--listen", &val, &argc, &argv)) { + listen_socket = val; + } else if (!strcmp(arg, "--listen-once")) { + listen_once = true; + } 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; + + 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); + ssh_server_start(plug, make_fd_socket(0, 1, -1, NULL, 0, plug)); + log_to_stderr(inst->id, "running directly on stdio"); + } + + cli_main_loop(cliloop_no_pw_setup, cliloop_no_pw_check, + psusan_continue, NULL); + + return 0; +} |