From 443ad75a81f3ddeb54417a90639663b59731e038 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 31 Mar 2019 09:27:10 +0100 Subject: [PATCH] Uppity: add a --listen mode, protected by /proc/net/tcp. Uppity is not secure enough to listen on a TCP port as if it was a normal SSH server. Until now, I've been using it by means of a local proxy command, i.e. PuTTY invokes Uppity in the same way it might invoke 'plink -nc'. This rigorously prevents any hostile user from connecting to my utterly insecure test server, but it's a thundering inconvenience as soon as you want to attach a debugger to the Uppity process itself - you have to stick a gdbserver somewhere in the middle of your already complicated shell pipeline, and then find a way to connect back to it from a gdb in a terminal window. So I've added an option to make Uppity listen on a TCP port in the normal way - but it's protected using that /proc/net/tcp trick I just added in the previous commit. --- Recipe | 2 +- sshserver.c | 4 +- sshserver.h | 2 +- unix/uxserver.c | 156 +++++++++++++++++++++++++++++++++++++++++------- 4 files changed, 139 insertions(+), 25 deletions(-) diff --git a/Recipe b/Recipe index 987110e1..31770fe7 100644 --- a/Recipe +++ b/Recipe @@ -397,7 +397,7 @@ testsc : [UT] testsc SSHCRYPTO marshal utils memory tree234 wildcard testzlib : [UT] testzlib sshzlib utils marshal memory uppity : [UT] uxserver SSHSERVER UXMISC uxsignal uxnoise uxgss uxnogtk - + uxpty uxsftpserver ux_x11 uxagentsock + + uxpty uxsftpserver ux_x11 uxagentsock procnet # ---------------------------------------------------------------------- # On Windows, provide a means of removing local test binaries that we diff --git a/sshserver.c b/sshserver.c index 700d793b..b2a2e24e 100644 --- a/sshserver.c +++ b/sshserver.c @@ -41,6 +41,7 @@ struct server { int nhostkeys; RSAKey *hostkey1; AuthPolicy *authpolicy; + LogPolicy *logpolicy; const SftpServerVtable *sftpserver_vt; Seat seat; @@ -242,6 +243,7 @@ Plug *ssh_server_plug( srv->hostkeys = hostkeys; srv->hostkey1 = hostkey1; srv->authpolicy = authpolicy; + srv->logpolicy = logpolicy; srv->sftpserver_vt = sftpserver_vt; srv->seat.vt = &server_seat_vt; @@ -310,7 +312,7 @@ static void ssh_server_free_callback(void *vsrv) sfree(srv); - server_instance_terminated(); + server_instance_terminated(srv->logpolicy); } static void server_connect_bpp(server *srv) diff --git a/sshserver.h b/sshserver.h index 3496d6ca..a78b3eca 100644 --- a/sshserver.h +++ b/sshserver.h @@ -18,7 +18,7 @@ Plug *ssh_server_plug( const SftpServerVtable *sftpserver_vt); void ssh_server_start(Plug *plug, Socket *socket); -void server_instance_terminated(void); +void server_instance_terminated(LogPolicy *logpolicy); void platform_logevent(const char *msg); #define AUTHMETHODS(X) \ diff --git a/unix/uxserver.c b/unix/uxserver.c index a8c72d0c..df26fa9e 100644 --- a/unix/uxserver.c +++ b/unix/uxserver.c @@ -112,19 +112,15 @@ char *platform_get_x_display(void) { return NULL; } static bool verbose; -static void log_to_stderr(const char *msg) -{ - /* - * FIXME: in multi-connection proper-socket mode, prefix this with - * a connection id of some kind. We'll surely pass this in to - * sshserver.c by way of constructing a distinct LogPolicy per - * instance and making its containing structure contain the id - - * but we'll also have to arrange for those LogPolicy structs to - * be freed when the server instance terminates. - * - * For now, though, we only have one server instance, so no need. - */ +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); @@ -132,13 +128,17 @@ static void log_to_stderr(const char *msg) 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(event); + log_to_stderr(inst->id, event); } static void server_logging_error(LogPolicy *lp, const char *event) { - log_to_stderr(event); /* unconditional */ + struct server_instance *inst = container_of( + lp, struct server_instance, logpolicy); + log_to_stderr(inst->id, event); /* unconditional */ } static int server_askappend( @@ -153,7 +153,6 @@ static const LogPolicyVtable server_logpolicy_vt = { server_askappend, server_logging_error, }; -LogPolicy server_logpolicy[1] = {{ &server_logpolicy_vt }}; struct AuthPolicy_ssh1_pubkey { RSAKey key; @@ -324,11 +323,20 @@ static void show_version_and_exit(void) const bool buildinfo_gtk_relevant = false; +static bool listening = false; static bool finished = false; -void server_instance_terminated(void) +void server_instance_terminated(LogPolicy *lp) { - /* FIXME: change policy here if we're running in a listening loop */ - finished = true; + struct server_instance *inst = container_of( + lp, struct server_instance, logpolicy); + + if (listening) { + log_to_stderr(inst->id, "connection terminated"); + } else { + finished = true; + } + + sfree(inst); } static bool longoptarg(const char *arg, const char *expected, @@ -368,6 +376,95 @@ static bool longoptnoarg(const char *arg, const char *expected) return false; } +struct server_config { + Conf *conf; + const SshServerConfig *ssc; + + ssh_key **hostkeys; + int nhostkeys; + + RSAKey *hostkey1; + + AuthPolicy *ap; + + unsigned next_id; + + 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); + + 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, cfg->hostkeys, cfg->nhostkeys, cfg->hostkey1, + cfg->ap, &inst->logpolicy, &unix_live_sftpserver_vt); +} + +static void server_log(Plug *plug, int type, SockAddr *addr, int port, + const char *error_msg, int error_code) +{ + log_to_stderr((unsigned)-1, error_msg); +} + +static void server_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ + log_to_stderr((unsigned)-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; + + unsigned old_next_id = cfg->next_id; + + 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); + + if (!sk_peer_trusted(s)) { + fprintf(stderr, "rejected connection from %s (untrustworthy peer)\n", + pi->log_text); + sk_free_peer_info(pi); + sk_close(s); + cfg->next_id = old_next_id; + return 1; + } + + 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 = { + server_log, + server_closing, + NULL, /* recv */ + NULL, /* send */ + server_accepting +}; + int main(int argc, char **argv) { int *fdlist; @@ -375,6 +472,7 @@ int main(int argc, char **argv) int i, fdstate; size_t fdsize; unsigned long now; + int listen_port = -1; ssh_key **hostkeys = NULL; size_t nhostkeys = 0, hostkeysize = 0; @@ -414,6 +512,8 @@ int main(int argc, char **argv) show_version_and_exit(); } else if (longoptnoarg(arg, "--verbose") || !strcmp(arg, "-v")) { verbose = true; + } else if (longoptarg(arg, "--listen", &val, &argc, &argv)) { + listen_port = atoi(val); } else if (longoptarg(arg, "--hostkey", &val, &argc, &argv)) { Filename *keyfile; int keytype; @@ -599,10 +699,22 @@ int main(int argc, char **argv) sk_init(); uxsel_init(); - { - Plug *plug = ssh_server_plug( - conf, &ssc, hostkeys, nhostkeys, hostkey1, &ap, server_logpolicy, - &unix_live_sftpserver_vt); + struct server_config scfg; + scfg.conf = conf; + scfg.ssc = &ssc; + scfg.hostkeys = hostkeys; + scfg.nhostkeys = nhostkeys; + scfg.hostkey1 = hostkey1; + scfg.ap = ≈ + scfg.next_id = 0; + + if (listen_port >= 0) { + listening = true; + scfg.listening_plug.vt = &server_plugvt; + sk_newlistener(NULL, listen_port, &scfg.listening_plug, + true, ADDRTYPE_UNSPEC); + } else { + Plug *plug = server_conn_plug(&scfg, NULL); ssh_server_start(plug, make_fd_socket(0, 1, -1, plug)); }