From 96f1fb9456255c5ce982a168b6b35ae448dfbbf4 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 16 Feb 2020 12:27:14 +0000 Subject: [PATCH] New application: 'psusan', the PROT_SSHCONN server end. In the previous commit I introduced the ability for PuTTY to talk to a server speaking the bare ssh-connection protocol, and listed several applications for that ability. But none of those applications is any use without a server that speaks the same protocol. Until now, the only such server has been the Unix-domain socket presented by an upstream connection-sharing PuTTY - and we already had a way to connect to that. So here's the missing piece: by reusing code that already existed for the testing SSH server Uppity, I've created a program that will speak the bare ssh-connection protocol on its standard I/O channels. If you want to get a shell session over any of the transports I mentioned in the last commit, this is the program you need to run at the far end of it. I have yet to write the documentation, but just in case I forget, the name stands for 'Pseudo Ssh for Untappable, Separately Authenticated Networks'. --- .gitignore | 1 + Recipe | 6 +- sshserver.c | 20 +++- sshserver.h | 1 + unix/uxpsusan.c | 305 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 328 insertions(+), 5 deletions(-) create mode 100644 unix/uxpsusan.c diff --git a/.gitignore b/.gitignore index 6d24ae57..a124edac 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ /pterm /puttyapp /ptermapp +/psusan /osxlaunch /uppity /unix/PuTTY.app diff --git a/Recipe b/Recipe index 5058dde2..fdf988f2 100644 --- a/Recipe +++ b/Recipe @@ -268,8 +268,8 @@ SSHCOMMON = sshcommon sshutils sshprng sshrand SSHCRYPTO + sshmac marshal nullplug + sshgssc pgssapi wildcard ssh1censor ssh2censor ssh2bpp + ssh2transport ssh2transhk ssh2connection portfwd x11fwd - + ssh1connection ssh1bpp -SSH = SSHCOMMON ssh ssh2bpp-bare + + ssh1connection ssh1bpp ssh2bpp-bare +SSH = SSHCOMMON ssh + ssh1login ssh2userauth + pinger + sshshare aqsync agentf @@ -405,6 +405,8 @@ testzlib : [UT] testzlib sshzlib utils marshal memory uppity : [UT] uxserver SSHSERVER UXMISC uxsignal uxnoise uxgss uxnogtk + uxpty uxsftpserver ux_x11 uxagentsock procnet uxcliloop +psusan : [UT] uxpsusan SSHSERVER UXMISC uxsignal uxnoise nogss uxnogtk + + uxpty uxsftpserver ux_x11 uxagentsock procnet uxcliloop # ---------------------------------------------------------------------- # On Windows, provide a means of removing local test binaries that we diff --git a/sshserver.c b/sshserver.c index 46f68659..97dd9403 100644 --- a/sshserver.c +++ b/sshserver.c @@ -282,7 +282,9 @@ void ssh_server_start(Plug *plug, Socket *socket) server *srv = container_of(plug, server, plug); const char *our_protoversion; - if (srv->hostkey1 && srv->nhostkeys) { + if (srv->ssc->bare_connection) { + our_protoversion = "2.0"; /* SSH-2 only */ + } else if (srv->hostkey1 && srv->nhostkeys) { our_protoversion = "1.99"; /* offer both SSH-1 and SSH-2 */ } else if (srv->hostkey1) { our_protoversion = "1.5"; /* SSH-1 only */ @@ -297,7 +299,7 @@ void ssh_server_start(Plug *plug, Socket *socket) srv->ic_out_raw.ctx = srv; srv->version_receiver.got_ssh_version = server_got_ssh_version; srv->bpp = ssh_verstring_new( - srv->conf, srv->logctx, false /* bare_connection */, + srv->conf, srv->logctx, srv->ssc->bare_connection, our_protoversion, &srv->version_receiver, true, "Uppity"); server_connect_bpp(srv); @@ -492,7 +494,19 @@ static void server_got_ssh_version(struct ssh_version_receiver *rcv, old_bpp = srv->bpp; srv->remote_bugs = ssh_verstring_get_bugs(old_bpp); - if (major_version == 2) { + if (srv->ssc->bare_connection) { + srv->bpp = ssh2_bare_bpp_new(srv->logctx); + server_connect_bpp(srv); + + connection_layer = ssh2_connection_new( + &srv->ssh, NULL, false, srv->conf, + ssh_verstring_get_local(old_bpp), &srv->cl); + ssh2connection_server_configure(connection_layer, + srv->sftpserver_vt, srv->ssc); + server_connect_ppl(srv, connection_layer); + + srv->base_layer = connection_layer; + } else if (major_version == 2) { PacketProtocolLayer *userauth_layer, *transport_child_layer; srv->bpp = ssh2_bpp_new(srv->logctx, &srv->stats, true); diff --git a/sshserver.h b/sshserver.h index 129d6fec..588636b3 100644 --- a/sshserver.h +++ b/sshserver.h @@ -16,6 +16,7 @@ struct SshServerConfig { unsigned long ssh1_cipher_mask; bool ssh1_allow_compression; + bool bare_connection; }; Plug *ssh_server_plug( diff --git a/unix/uxpsusan.c b/unix/uxpsusan.c new file mode 100644 index 00000000..71b51695 --- /dev/null +++ b/unix/uxpsusan.c @@ -0,0 +1,305 @@ +/* + * 'psusan': Pseudo Ssh for Untappable, Separately Authenticated Networks + * + * This is a standalone application that speaks on its standard I/O + * the server end of the bare ssh-connection protocol used by PuTTY's + * connection sharing. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "mpint.h" +#include "ssh.h" +#include "sshserver.h" + +const char *const appname = "psusan"; + +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; } + +static bool verbose; + +struct server_instance { + unsigned id; + LogPolicy logpolicy; +}; + +static void log_to_stderr(unsigned id, const char *msg) +{ + 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 = { + server_eventlog, + server_askappend, + server_logging_error, + null_lp_verbose_no, +}; + +static void show_help(FILE *fp) +{ + fputs("usage: psusan [options]\n" + "options: --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 finished = false; +void server_instance_terminated(LogPolicy *lp) +{ + struct server_instance *inst = container_of( + lp, struct server_instance, logpolicy); + + finished = true; + + sfree(inst); +} + +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; +}; + +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); +} + +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) +{ + SshServerConfig ssc; + + Conf *conf = make_ssh_server_conf(); + + memset(&ssc, 0, sizeof(ssc)); + + 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 (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 { + 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; + + struct server_instance *inst; + Plug *plug = server_conn_plug(&scfg, &inst); + ssh_server_start(plug, make_fd_socket(0, 1, -1, plug)); + + cli_main_loop(cliloop_no_pw_setup, cliloop_no_pw_check, + cliloop_always_continue, NULL); + + return 0; +}