diff --git a/.gitignore b/.gitignore index c04f119b..890f8b23 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ /puttyapp /ptermapp /osxlaunch +/uppity /unix/PuTTY.app /unix/Pterm.app /fuzzterm diff --git a/Recipe b/Recipe index b819c546..72b899b6 100644 --- a/Recipe +++ b/Recipe @@ -250,14 +250,18 @@ GTKMAIN = gtkmain cmdline NONSSH = telnet raw rlogin ldisc pinger # SSH back end (putty, plink, pscp, psftp). -SSH = ssh sshcommon ssh1bpp ssh2bpp ssh2bpp-bare ssh1censor ssh2censor - + ssh1login ssh1connection ssh2transport ssh2userauth ssh2connection - + sshverstring sshcrc sshdes sshmd5 sshrsa sshrand sshsha sshblowf - + sshdh sshcrcda sshpubk sshzlib sshdss x11fwd portfwd - + sshaes sshccp sshsh256 sshsh512 sshbn wildcard pinger ssharcf - + sshgssc pgssapi sshshare sshecc aqsync marshal nullplug agentf - + sshmac mainchan ssh2transhk ssh2kex-client ssh2connection-client - + ssh1connection-client +SSHCOMMON = sshcommon sshrand + + sshverstring sshcrc sshdes sshmd5 sshrsa sshsha sshblowf + + sshdh sshcrcda sshpubk sshzlib sshdss ssharcf + + sshaes sshccp sshsh256 sshsh512 sshbn sshmac marshal nullplug + + sshgssc pgssapi sshecc wildcard ssh1censor ssh2censor ssh2bpp + + ssh2transport ssh2transhk ssh2connection portfwd x11fwd + + ssh1connection ssh1bpp +SSH = SSHCOMMON ssh ssh2bpp-bare + + ssh1login ssh2userauth + + pinger + + sshshare aqsync agentf + + mainchan ssh2kex-client ssh2connection-client ssh1connection-client WINSSH = SSH winnoise wincapi winpgntc wingss winshare winnps winnpc + winhsock errsock UXSSH = SSH uxnoise uxagentc uxgss uxshare @@ -268,11 +272,19 @@ SFTP = sftp int64 logging cmdline # Miscellaneous objects appearing in all the utilities, or all the # network ones, or the Unix or Windows subsets of those in turn. MISC = misc marshal -MISCNET = timing callback MISC version settings tree234 proxy CONF be_misc +MISCNETCOMMON = timing callback MISC version tree234 CONF +MISCNET = MISCNETCOMMON be_misc settings proxy WINMISC = MISCNET winstore winnet winhandl cmdline windefs winmisc winproxy + wintime winhsock errsock winsecur winucs miscucs -UXMISC = MISCNET uxstore uxsel uxnet uxpeer uxmisc time - + uxproxy uxfdsock errsock +UXMISCCOMMON = MISCNETCOMMON uxstore uxsel uxnet uxpeer uxmisc time + + uxfdsock errsock +UXMISC = MISCNET UXMISCCOMMON uxproxy + +# SSH server. +SSHSERVER = SSHCOMMON sshserver settings be_none logging ssh2kex-server + + ssh2userauth-server sshrsag sshprime ssh2connection-server + + sesschan int64 proxy cproxy ssh1login-server + + ssh1connection-server # import.c and dependencies, for PuTTYgen-like utilities that have to # load foreign key files. @@ -364,6 +376,9 @@ fuzzterm : [UT] UXTERM CHARSET misc version uxmisc uxucs fuzzterm time settings testbn : [UT] testbn sshbn MISC version CONF tree234 uxmisc uxnogtk testbn : [C] testbn sshbn MISC version CONF tree234 winmisc LIBS +uppity : [UT] uxserver SSHSERVER UXMISC uxsignal uxnoise uxgss uxnogtk + + uxpty ux_x11 uxagentsock + # ---------------------------------------------------------------------- # On Windows, provide a means of removing local test binaries that we # aren't going to actually ship. (I prefer this to not building them diff --git a/portfwd.c b/portfwd.c index 87c6dae3..af4b541d 100644 --- a/portfwd.c +++ b/portfwd.c @@ -1050,6 +1050,72 @@ void portfwdmgr_config(PortFwdManager *mgr, Conf *conf) } } +int portfwdmgr_listen(PortFwdManager *mgr, const char *host, int port, + const char *keyhost, int keyport, Conf *conf) +{ + PortFwdRecord *pfr; + + pfr = snew(PortFwdRecord); + pfr->type = 'L'; + pfr->saddr = host ? dupstr(host) : NULL; + pfr->daddr = keyhost ? dupstr(keyhost) : NULL; + pfr->sserv = pfr->dserv = NULL; + pfr->sport = port; + pfr->dport = keyport; + pfr->local = NULL; + pfr->remote = NULL; + pfr->addressfamily = ADDRTYPE_UNSPEC; + + PortFwdRecord *existing = add234(mgr->forwardings, pfr); + if (existing != pfr) { + /* + * We had this record already. Return failure. + */ + pfr_free(pfr); + return FALSE; + } + + char *err = pfl_listen(keyhost, keyport, host, port, + mgr->cl, conf, &pfr->local, pfr->addressfamily); + logeventf(mgr->cl->logctx, + "%s on port %s:%d to forward to client%s%s", + err ? "Failed to listen" : "Listening", host, port, + err ? ": " : "", err ? err : ""); + if (err) { + sfree(err); + del234(mgr->forwardings, pfr); + pfr_free(pfr); + return FALSE; + } + + return TRUE; +} + +int portfwdmgr_unlisten(PortFwdManager *mgr, const char *host, int port) +{ + PortFwdRecord pfr_key; + + pfr_key.type = 'L'; + /* Safe to cast the const away here, because it will only be used + * by pfr_cmp, which won't write to the string */ + pfr_key.saddr = pfr_key.daddr = (char *)host; + pfr_key.sserv = pfr_key.dserv = NULL; + pfr_key.sport = pfr_key.dport = port; + pfr_key.local = NULL; + pfr_key.remote = NULL; + pfr_key.addressfamily = ADDRTYPE_UNSPEC; + + PortFwdRecord *pfr = del234(mgr->forwardings, &pfr_key); + + if (!pfr) + return FALSE; + + logeventf(mgr->cl->logctx, "Closing listening port %s:%d", host, port); + + pfr_free(pfr); + return TRUE; +} + /* * Called when receiving a PORT OPEN from the server to make a * connection to a destination host. diff --git a/sesschan.c b/sesschan.c new file mode 100644 index 00000000..577c5feb --- /dev/null +++ b/sesschan.c @@ -0,0 +1,559 @@ +/* + * Implement the "session" channel type for the SSH server. + */ + +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sshchan.h" +#include "sshserver.h" + +typedef struct sesschan { + SshChannel *c; + + LogContext *parent_logctx, *child_logctx; + Conf *conf; + + LogPolicy logpolicy; + Seat seat; + + int want_pty; + struct ssh_ttymodes ttymodes; + int wc, hc, wp, hp; + strbuf *termtype; + + int ignoring_input; + int seen_eof, seen_exit; + + Plug xfwd_plug; + int n_x11_sockets; + Socket *x11_sockets[MAX_X11_SOCKETS]; + + Plug agentfwd_plug; + Socket *agentfwd_socket; + + Backend *backend; + + bufchain subsys_input; + + Channel chan; +} sesschan; + +static void sesschan_free(Channel *chan); +static int sesschan_send(Channel *chan, int is_stderr, const void *, int); +static void sesschan_send_eof(Channel *chan); +static char *sesschan_log_close_msg(Channel *chan); +static int sesschan_want_close(Channel *, int, int); +static void sesschan_set_input_wanted(Channel *chan, int wanted); +static int sesschan_run_shell(Channel *chan); +static int sesschan_run_command(Channel *chan, ptrlen command); +static int sesschan_run_subsystem(Channel *chan, ptrlen subsys); +static int sesschan_enable_x11_forwarding( + Channel *chan, int oneshot, ptrlen authproto, ptrlen authdata, + unsigned screen_number); +static int sesschan_enable_agent_forwarding(Channel *chan); +static int sesschan_allocate_pty( + Channel *chan, ptrlen termtype, unsigned width, unsigned height, + unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes); +static int sesschan_set_env(Channel *chan, ptrlen var, ptrlen value); +static int sesschan_send_break(Channel *chan, unsigned length); +static int sesschan_send_signal(Channel *chan, ptrlen signame); +static int sesschan_change_window_size( + Channel *chan, unsigned width, unsigned height, + unsigned pixwidth, unsigned pixheight); + +static const struct ChannelVtable sesschan_channelvt = { + sesschan_free, + chan_remotely_opened_confirmation, + chan_remotely_opened_failure, + sesschan_send, + sesschan_send_eof, + sesschan_set_input_wanted, + sesschan_log_close_msg, + sesschan_want_close, + chan_no_exit_status, + chan_no_exit_signal, + chan_no_exit_signal_numeric, + sesschan_run_shell, + sesschan_run_command, + sesschan_run_subsystem, + sesschan_enable_x11_forwarding, + sesschan_enable_agent_forwarding, + sesschan_allocate_pty, + sesschan_set_env, + sesschan_send_break, + sesschan_send_signal, + sesschan_change_window_size, + chan_no_request_response, +}; + +static void sesschan_eventlog(LogPolicy *lp, const char *event) {} +static void sesschan_logging_error(LogPolicy *lp, const char *event) {} +static int sesschan_askappend( + LogPolicy *lp, Filename *filename, + void (*callback)(void *ctx, int result), void *ctx) { return 2; } + +static const LogPolicyVtable sesschan_logpolicy_vt = { + sesschan_eventlog, + sesschan_askappend, + sesschan_logging_error, +}; + +static int sesschan_seat_output(Seat *, int is_stderr, const void *, int); +static int sesschan_seat_eof(Seat *); +static void sesschan_notify_remote_exit(Seat *seat); +static void sesschan_connection_fatal(Seat *seat, const char *message); +static int sesschan_get_window_pixel_size(Seat *seat, int *width, int *height); + +static const SeatVtable sesschan_seat_vt = { + sesschan_seat_output, + sesschan_seat_eof, + nullseat_get_userpass_input, + sesschan_notify_remote_exit, + sesschan_connection_fatal, + nullseat_update_specials_menu, + nullseat_get_ttymode, + nullseat_set_busy_status, + nullseat_verify_ssh_host_key, + nullseat_confirm_weak_crypto_primitive, + nullseat_confirm_weak_cached_hostkey, + nullseat_is_never_utf8, + nullseat_echoedit_update, + nullseat_get_x_display, + nullseat_get_windowid, + sesschan_get_window_pixel_size, +}; + +Channel *sesschan_new(SshChannel *c, LogContext *logctx) +{ + sesschan *sess = snew(sesschan); + memset(sess, 0, sizeof(sesschan)); + + sess->c = c; + sess->chan.vt = &sesschan_channelvt; + sess->chan.initial_fixed_window_size = 0; + sess->parent_logctx = logctx; + + /* Start with a completely default Conf */ + sess->conf = conf_new(); + load_open_settings(NULL, sess->conf); + + /* Set close-on-exit = TRUE to suppress uxpty.c's "[pterm: process + * terminated with status x]" message */ + conf_set_int(sess->conf, CONF_close_on_exit, FORCE_ON); + + sess->seat.vt = &sesschan_seat_vt; + sess->logpolicy.vt = &sesschan_logpolicy_vt; + sess->child_logctx = log_init(&sess->logpolicy, sess->conf); + + bufchain_init(&sess->subsys_input); + + return &sess->chan; +} + +static void sesschan_free(Channel *chan) +{ + sesschan *sess = container_of(chan, sesschan, chan); + int i; + + delete_callbacks_for_context(sess); + conf_free(sess->conf); + if (sess->backend) + backend_free(sess->backend); + bufchain_clear(&sess->subsys_input); + for (i = 0; i < sess->n_x11_sockets; i++) + sk_close(sess->x11_sockets[i]); + if (sess->agentfwd_socket) + sk_close(sess->agentfwd_socket); + + sfree(sess); +} + +static int sesschan_send(Channel *chan, int is_stderr, + const void *data, int length) +{ + sesschan *sess = container_of(chan, sesschan, chan); + + if (!sess->backend || sess->ignoring_input) + return 0; + + return backend_send(sess->backend, data, length); +} + +static void sesschan_send_eof(Channel *chan) +{ + sesschan *sess = container_of(chan, sesschan, chan); + if (sess->backend) + backend_special(sess->backend, SS_EOF, 0); +} + +static char *sesschan_log_close_msg(Channel *chan) +{ + return dupstr("Session channel closed"); +} + +static void sesschan_set_input_wanted(Channel *chan, int wanted) +{ + /* I don't think we need to do anything here */ +} + +static void sesschan_start_backend(sesschan *sess, const char *cmd) +{ + sess->backend = pty_backend_create( + &sess->seat, sess->child_logctx, sess->conf, NULL, cmd, + sess->ttymodes, !sess->want_pty); + backend_size(sess->backend, sess->wc, sess->hc); +} + +int sesschan_run_shell(Channel *chan) +{ + sesschan *sess = container_of(chan, sesschan, chan); + + if (sess->backend) + return FALSE; + + sesschan_start_backend(sess, NULL); + return TRUE; +} + +int sesschan_run_command(Channel *chan, ptrlen command) +{ + sesschan *sess = container_of(chan, sesschan, chan); + + if (sess->backend) + return FALSE; + + char *command_str = mkstr(command); + sesschan_start_backend(sess, command_str); + sfree(command_str); + + return TRUE; +} + +int sesschan_run_subsystem(Channel *chan, ptrlen subsys) +{ + return FALSE; +} + +static void fwd_log(Plug *plug, int type, SockAddr *addr, int port, + const char *error_msg, int error_code) +{ /* don't expect any weirdnesses from a listening socket */ } +static void fwd_closing(Plug *plug, const char *error_msg, int error_code, + int calling_back) +{ /* not here, either */ } + +static int xfwd_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) +{ + sesschan *sess = container_of(p, sesschan, xfwd_plug); + Plug *plug; + Channel *chan; + Socket *s; + SocketPeerInfo *pi; + const char *err; + + chan = portfwd_raw_new(sess->c->cl, &plug); + s = constructor(ctx, plug); + if ((err = sk_socket_error(s)) != NULL) { + portfwd_raw_free(chan); + return TRUE; + } + pi = sk_peer_info(s); + portfwd_raw_setup(chan, s, ssh_serverside_x11_open(sess->c->cl, chan, pi)); + sk_free_peer_info(pi); + + return FALSE; +} + +static const PlugVtable xfwd_plugvt = { + fwd_log, + fwd_closing, + NULL, /* recv */ + NULL, /* send */ + xfwd_accepting, +}; + +int sesschan_enable_x11_forwarding( + Channel *chan, int oneshot, ptrlen authproto, ptrlen authdata_hex, + unsigned screen_number) +{ + sesschan *sess = container_of(chan, sesschan, chan); + strbuf *authdata_bin; + size_t i; + char screensuffix[32]; + + if (oneshot) + return FALSE; /* not supported */ + + snprintf(screensuffix, sizeof(screensuffix), ".%u", screen_number); + + /* + * Decode the authorisation data from ASCII hex into binary. + */ + if (authdata_hex.len % 2) + return FALSE; /* expected an even number of digits */ + authdata_bin = strbuf_new(); + for (i = 0; i < authdata_hex.len; i += 2) { + const unsigned char *hex = authdata_hex.ptr; + char hexbuf[3]; + + if (!isxdigit(hex[i]) || !isxdigit(hex[i+1])) { + strbuf_free(authdata_bin); + return FALSE; /* not hex */ + } + + hexbuf[0] = hex[i]; + hexbuf[1] = hex[i+1]; + hexbuf[2] = '\0'; + put_byte(authdata_bin, strtoul(hexbuf, NULL, 16)); + } + + sess->xfwd_plug.vt = &xfwd_plugvt; + + sess->n_x11_sockets = platform_make_x11_server( + &sess->xfwd_plug, appname, 10, screensuffix, + authproto, ptrlen_from_strbuf(authdata_bin), + sess->x11_sockets, sess->conf); + + strbuf_free(authdata_bin); + return sess->n_x11_sockets != 0; +} + +static int agentfwd_accepting( + Plug *p, accept_fn_t constructor, accept_ctx_t ctx) +{ + sesschan *sess = container_of(p, sesschan, agentfwd_plug); + Plug *plug; + Channel *chan; + Socket *s; + const char *err; + + chan = portfwd_raw_new(sess->c->cl, &plug); + s = constructor(ctx, plug); + if ((err = sk_socket_error(s)) != NULL) { + portfwd_raw_free(chan); + return TRUE; + } + portfwd_raw_setup(chan, s, ssh_serverside_agent_open(sess->c->cl, chan)); + + return FALSE; +} + +static const PlugVtable agentfwd_plugvt = { + fwd_log, + fwd_closing, + NULL, /* recv */ + NULL, /* send */ + agentfwd_accepting, +}; + +int sesschan_enable_agent_forwarding(Channel *chan) +{ + sesschan *sess = container_of(chan, sesschan, chan); + char *error, *socketname, *dir_prefix; + + dir_prefix = dupprintf("/tmp/%s-agentfwd", appname); + + sess->agentfwd_plug.vt = &agentfwd_plugvt; + sess->agentfwd_socket = platform_make_agent_socket( + &sess->agentfwd_plug, dir_prefix, &error, &socketname); + + sfree(dir_prefix); + + if (sess->agentfwd_socket) { + conf_set_str_str(sess->conf, CONF_environmt, + "SSH_AUTH_SOCK", socketname); + } + + sfree(error); + sfree(socketname); + + return sess->agentfwd_socket != NULL; +} + +int sesschan_allocate_pty( + Channel *chan, ptrlen termtype, unsigned width, unsigned height, + unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes) +{ + sesschan *sess = container_of(chan, sesschan, chan); + char *s; + + if (sess->want_pty) + return FALSE; + + s = mkstr(termtype); + conf_set_str(sess->conf, CONF_termtype, s); + sfree(s); + + sess->want_pty = TRUE; + sess->ttymodes = modes; + sess->wc = width; + sess->hc = height; + sess->wp = pixwidth; + sess->hp = pixheight; + + return TRUE; +} + +int sesschan_set_env(Channel *chan, ptrlen var, ptrlen value) +{ + sesschan *sess = container_of(chan, sesschan, chan); + + char *svar = mkstr(var), *svalue = mkstr(value); + conf_set_str_str(sess->conf, CONF_environmt, svar, svalue); + sfree(svar); + sfree(svalue); + + return TRUE; +} + +int sesschan_send_break(Channel *chan, unsigned length) +{ + sesschan *sess = container_of(chan, sesschan, chan); + + if (sess->backend) { + /* We ignore the break length. We could pass it through as the + * 'arg' parameter, and have uxpty.c collect it and pass it on + * to tcsendbreak, but since tcsendbreak in turn assigns + * implementation-defined semantics to _its_ duration + * parameter, this all just sounds too difficult. */ + backend_special(sess->backend, SS_BRK, 0); + return TRUE; + } + return FALSE; +} + +int sesschan_send_signal(Channel *chan, ptrlen signame) +{ + sesschan *sess = container_of(chan, sesschan, chan); + + /* Start with a code that definitely isn't a signal (or indeed a + * special command at all), to indicate 'nothing matched'. */ + SessionSpecialCode code = SS_EXITMENU; + + #define SIGNAL_SUB(name) \ + if (ptrlen_eq_string(signame, #name)) code = SS_SIG ## name; + #define SIGNAL_MAIN(name, text) SIGNAL_SUB(name) + #include "sshsignals.h" + #undef SIGNAL_MAIN + #undef SIGNAL_SUB + + if (code == SS_EXITMENU) + return FALSE; + + backend_special(sess->backend, code, 0); + return TRUE; +} + +int sesschan_change_window_size( + Channel *chan, unsigned width, unsigned height, + unsigned pixwidth, unsigned pixheight) +{ + sesschan *sess = container_of(chan, sesschan, chan); + + if (!sess->want_pty) + return FALSE; + + sess->wc = width; + sess->hc = height; + sess->wp = pixwidth; + sess->hp = pixheight; + + if (sess->backend) + backend_size(sess->backend, sess->wc, sess->hc); + + return TRUE; +} + +static int sesschan_seat_output( + Seat *seat, int is_stderr, const void *data, int len) +{ + sesschan *sess = container_of(seat, sesschan, seat); + return sshfwd_write_ext(sess->c, is_stderr, data, len); +} + +static void sesschan_check_close_callback(void *vctx) +{ + sesschan *sess = (sesschan *)vctx; + + /* + * Once we've seen incoming EOF from the backend (aka EIO from the + * pty master) and also passed on the process's exit status, we + * should proactively initiate closure of the session channel. + */ + if (sess->seen_eof && sess->seen_exit) + sshfwd_initiate_close(sess->c, NULL); +} + +static int sesschan_want_close(Channel *chan, int seen_eof, int rcvd_eof) +{ + sesschan *sess = container_of(chan, sesschan, chan); + + /* + * Similarly to above, we don't want to initiate channel closure + * until we've sent the process's exit status, _even_ if EOF of + * the actual data stream has happened in both directions. + */ + return (sess->seen_eof && sess->seen_exit); +} + +static int sesschan_seat_eof(Seat *seat) +{ + sesschan *sess = container_of(seat, sesschan, seat); + + sshfwd_write_eof(sess->c); + sess->seen_eof = TRUE; + + queue_toplevel_callback(sesschan_check_close_callback, sess); + return TRUE; +} + +static void sesschan_notify_remote_exit(Seat *seat) +{ + sesschan *sess = container_of(seat, sesschan, seat); + ptrlen signame; + char *sigmsg; + + if (!sess->backend) + return; + + signame = pty_backend_exit_signame(sess->backend, &sigmsg); + if (signame.len) { + if (!sigmsg) + sigmsg = dupstr(""); + + sshfwd_send_exit_signal( + sess->c, signame, FALSE, ptrlen_from_asciz(sigmsg)); + + sfree(sigmsg); + } else { + sshfwd_send_exit_status(sess->c, backend_exitcode(sess->backend)); + } + + sess->seen_exit = TRUE; + queue_toplevel_callback(sesschan_check_close_callback, sess); +} + +static void sesschan_connection_fatal(Seat *seat, const char *message) +{ + sesschan *sess = container_of(seat, sesschan, seat); + + /* Closest translation I can think of */ + sshfwd_send_exit_signal( + sess->c, PTRLEN_LITERAL("HUP"), FALSE, ptrlen_from_asciz(message)); + + sess->ignoring_input = TRUE; +} + +static int sesschan_get_window_pixel_size(Seat *seat, int *width, int *height) +{ + sesschan *sess = container_of(seat, sesschan, seat); + + *width = sess->wp; + *height = sess->hp; + + return TRUE; +} diff --git a/ssh.c b/ssh.c index 1a881c80..251634f1 100644 --- a/ssh.c +++ b/ssh.c @@ -180,7 +180,7 @@ static void ssh_got_ssh_version(struct ssh_version_receiver *rcv, int is_simple = (conf_get_int(ssh->conf, CONF_ssh_simple) && !ssh->connshare); - ssh->bpp = ssh2_bpp_new(ssh->logctx, &ssh->stats); + ssh->bpp = ssh2_bpp_new(ssh->logctx, &ssh->stats, FALSE); ssh_connect_bpp(ssh); #ifndef NO_GSSAPI @@ -246,7 +246,7 @@ static void ssh_got_ssh_version(struct ssh_version_receiver *rcv, ssh_verstring_get_local(old_bpp), ssh_verstring_get_remote(old_bpp), &ssh->gss_state, - &ssh->stats, transport_child_layer); + &ssh->stats, transport_child_layer, FALSE); ssh_connect_ppl(ssh, ssh->base_layer); if (userauth_layer) @@ -717,7 +717,8 @@ static const char *connect_to_host(Ssh *ssh, const char *host, int port, ssh->version_receiver.got_ssh_version = ssh_got_ssh_version; ssh->bpp = ssh_verstring_new( ssh->conf, ssh->logctx, ssh->bare_connection, - ssh->version == 1 ? "1.5" : "2.0", &ssh->version_receiver, "PuTTY"); + ssh->version == 1 ? "1.5" : "2.0", &ssh->version_receiver, + FALSE, "PuTTY"); ssh_connect_bpp(ssh); queue_idempotent_callback(&ssh->bpp->ic_in_raw); diff --git a/ssh.h b/ssh.h index 0014a502..439eebb1 100644 --- a/ssh.h +++ b/ssh.h @@ -222,6 +222,12 @@ struct ConnectionLayerVtable { /* Initiate opening of a 'session'-type channel */ SshChannel *(*session_open)(ConnectionLayer *cl, Channel *chan); + /* Open outgoing channels for X and agent forwarding. (Used in the + * SSH server.) */ + SshChannel *(*serverside_x11_open)(ConnectionLayer *cl, Channel *chan, + const SocketPeerInfo *pi); + SshChannel *(*serverside_agent_open)(ConnectionLayer *cl, Channel *chan); + /* Add an X11 display for ordinary X forwarding */ struct X11FakeAuth *(*add_x11_display)( ConnectionLayer *cl, int authtype, struct X11Display *x11disp); @@ -300,6 +306,10 @@ struct ConnectionLayer { #define ssh_rportfwd_remove(cl, rpf) ((cl)->vt->rportfwd_remove(cl, rpf)) #define ssh_lportfwd_open(cl, h, p, desc, pi, chan) \ ((cl)->vt->lportfwd_open(cl, h, p, desc, pi, chan)) +#define ssh_serverside_x11_open(cl, chan, pi) \ + ((cl)->vt->serverside_x11_open(cl, chan, pi)) +#define ssh_serverside_agent_open(cl, chan) \ + ((cl)->vt->serverside_agent_open(cl, chan)) #define ssh_session_open(cl, chan) \ ((cl)->vt->session_open(cl, chan)) #define ssh_add_x11_display(cl, auth, disp) \ @@ -333,6 +343,8 @@ struct ConnectionLayer { #define ssh_enable_agent_fwd(cl) ((cl)->vt->enable_agent_fwd(cl)) #define ssh_set_wants_user_input(cl, wanted) \ ((cl)->vt->set_wants_user_input(cl, wanted)) +#define ssh_setup_server_x_forwarding(cl, conf, ap, ad, scr) \ + ((cl)->vt->setup_server_x_forwarding(cl, conf, ap, ad, scr)) /* Exports from portfwd.c */ PortFwdManager *portfwdmgr_new(ConnectionLayer *cl); @@ -343,6 +355,12 @@ void portfwdmgr_close_all(PortFwdManager *mgr); char *portfwdmgr_connect(PortFwdManager *mgr, Channel **chan_ret, char *hostname, int port, SshChannel *c, int addressfamily); +int portfwdmgr_listen(PortFwdManager *mgr, const char *host, int port, + const char *keyhost, int keyport, Conf *conf); +int portfwdmgr_unlisten(PortFwdManager *mgr, const char *host, int port); +Channel *portfwd_raw_new(ConnectionLayer *cl, Plug **plug); +void portfwd_raw_free(Channel *pfchan); +void portfwd_raw_setup(Channel *pfchan, Socket *s, SshChannel *sc); Socket *platform_make_agent_socket(Plug *plug, const char *dirprefix, char **error, char **name); diff --git a/ssh1bpp.c b/ssh1bpp.c index 2f306dd0..59fe5104 100644 --- a/ssh1bpp.c +++ b/ssh1bpp.c @@ -89,6 +89,21 @@ void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp, } } +void ssh1_bpp_start_compression(BinaryPacketProtocol *bpp) +{ + struct ssh1_bpp_state *s; + assert(bpp->vt == &ssh1_bpp_vtable); + s = container_of(bpp, struct ssh1_bpp_state, bpp); + + assert(!s->compctx); + assert(!s->decompctx); + + s->compctx = ssh_compressor_new(&ssh_zlib); + s->decompctx = ssh_decompressor_new(&ssh_zlib); + + bpp_logevent(("Started zlib (RFC1950) compression")); +} + #define BPP_READ(ptr, len) do \ { \ crMaybeWaitUntilV(s->bpp.input_eof || \ @@ -221,13 +236,7 @@ static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp) * If the response was positive, start * compression. */ - assert(!s->compctx); - assert(!s->decompctx); - - s->compctx = ssh_compressor_new(&ssh_zlib); - s->decompctx = ssh_decompressor_new(&ssh_zlib); - - bpp_logevent(("Started zlib (RFC1950) compression")); + ssh1_bpp_start_compression(&s->bpp); } /* diff --git a/ssh1connection-client.c b/ssh1connection-client.c index 3018b68d..db2d5b23 100644 --- a/ssh1connection-client.c +++ b/ssh1connection-client.c @@ -516,3 +516,16 @@ struct ssh_rportfwd *ssh1_rportfwd_alloc( return rpf; } + +SshChannel *ssh1_serverside_x11_open( + ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi) +{ + assert(FALSE && "Should never be called in the client"); + return NULL; +} + +SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan) +{ + assert(FALSE && "Should never be called in the client"); + return NULL; +} diff --git a/ssh1connection-server.c b/ssh1connection-server.c new file mode 100644 index 00000000..01ea6f8d --- /dev/null +++ b/ssh1connection-server.c @@ -0,0 +1,355 @@ +/* + * Server-specific parts of the SSH-1 connection layer. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshchan.h" +#include "sshcr.h" +#include "ssh1connection.h" +#include "sshserver.h" + +static int ssh1sesschan_write(SshChannel *c, int is_stderr, const void *, int); +static void ssh1sesschan_write_eof(SshChannel *c); +static void ssh1sesschan_initiate_close(SshChannel *c, const char *err); +static void ssh1sesschan_send_exit_status(SshChannel *c, int status); +static void ssh1sesschan_send_exit_signal( + SshChannel *c, ptrlen signame, int core_dumped, ptrlen msg); + +static const struct SshChannelVtable ssh1sesschan_vtable = { + ssh1sesschan_write, + ssh1sesschan_write_eof, + ssh1sesschan_initiate_close, + NULL /* unthrottle */, + NULL /* get_conf */, + NULL /* window_override_removed is only used by SSH-2 sharing */, + NULL /* x11_sharing_handover, likewise */, + ssh1sesschan_send_exit_status, + ssh1sesschan_send_exit_signal, + NULL /* send_exit_signal_numeric */, + NULL /* request_x11_forwarding */, + NULL /* request_agent_forwarding */, + NULL /* request_pty */, + NULL /* send_env_var */, + NULL /* start_shell */, + NULL /* start_command */, + NULL /* start_subsystem */, + NULL /* send_serial_break */, + NULL /* send_signal */, + NULL /* send_terminal_size_change */, + NULL /* hint_channel_is_simple */, +}; + +void ssh1_connection_direction_specific_setup( + struct ssh1_connection_state *s) +{ + if (!s->mainchan_chan) { + s->mainchan_sc.vt = &ssh1sesschan_vtable; + s->mainchan_sc.cl = &s->cl; + s->mainchan_chan = sesschan_new(&s->mainchan_sc, s->ppl.logctx); + } +} + +int ssh1_handle_direction_specific_packet( + struct ssh1_connection_state *s, PktIn *pktin) +{ + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + PktOut *pktout; + struct ssh1_channel *c; + unsigned remid; + ptrlen host, cmd, data; + char *host_str, *err; + int port, listenport, success; + + switch (pktin->type) { + case SSH1_CMSG_EXEC_SHELL: + if (s->finished_setup) + goto unexpected_setup_packet; + + ppl_logevent(("Client requested a shell")); + chan_run_shell(s->mainchan_chan); + s->finished_setup = TRUE; + return TRUE; + + case SSH1_CMSG_EXEC_CMD: + if (s->finished_setup) + goto unexpected_setup_packet; + + cmd = get_string(pktin); + ppl_logevent(("Client sent command '%.*s'", PTRLEN_PRINTF(cmd))); + chan_run_command(s->mainchan_chan, cmd); + s->finished_setup = TRUE; + return TRUE; + + case SSH1_CMSG_REQUEST_COMPRESSION: + if (s->compressing) { + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_FAILURE); + pq_push(s->ppl.out_pq, pktout); + } else { + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_SUCCESS); + pq_push(s->ppl.out_pq, pktout); + /* Synchronous run of output formatting, to ensure that + * success packet is converted into wire format before we + * start compressing */ + ssh_bpp_handle_output(s->ppl.bpp); + /* And now ensure that the _next_ packet will be the first + * compressed one. */ + ssh1_bpp_start_compression(s->ppl.bpp); + s->compressing = TRUE; + } + + return TRUE; + + case SSH1_CMSG_REQUEST_PTY: + if (s->finished_setup) + goto unexpected_setup_packet; + { + ptrlen termtype = get_string(pktin); + unsigned height = get_uint32(pktin); + unsigned width = get_uint32(pktin); + unsigned pixwidth = get_uint32(pktin); + unsigned pixheight = get_uint32(pktin); + struct ssh_ttymodes modes = read_ttymodes_from_packet( + BinarySource_UPCAST(pktin), 1); + + if (get_err(pktin)) { + ppl_logevent(("Unable to decode pty request packet")); + success = FALSE; + } else if (!chan_allocate_pty( + s->mainchan_chan, termtype, width, height, + pixwidth, pixheight, modes)) { + ppl_logevent(("Unable to allocate a pty")); + success = FALSE; + } else { + success = TRUE; + } + } + + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE)); + pq_push(s->ppl.out_pq, pktout); + return TRUE; + + case SSH1_CMSG_PORT_FORWARD_REQUEST: + if (s->finished_setup) + goto unexpected_setup_packet; + + listenport = toint(get_uint32(pktin)); + host = get_string(pktin); + port = toint(get_uint32(pktin)); + + ppl_logevent(("Client requested port %d forward to %.*s:%d", + listenport, PTRLEN_PRINTF(host), port)); + + host_str = mkstr(host); + success = portfwdmgr_listen( + s->portfwdmgr, NULL, listenport, host_str, port, s->conf); + sfree(host_str); + + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE)); + pq_push(s->ppl.out_pq, pktout); + return TRUE; + + case SSH1_CMSG_X11_REQUEST_FORWARDING: + if (s->finished_setup) + goto unexpected_setup_packet; + + { + ptrlen authproto = get_string(pktin); + ptrlen authdata = get_string(pktin); + unsigned screen_number = 0; + if (s->remote_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) + screen_number = get_uint32(pktin); + + success = chan_enable_x11_forwarding( + s->mainchan_chan, FALSE, authproto, authdata, screen_number); + } + + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE)); + pq_push(s->ppl.out_pq, pktout); + return TRUE; + + case SSH1_CMSG_AGENT_REQUEST_FORWARDING: + if (s->finished_setup) + goto unexpected_setup_packet; + + success = chan_enable_agent_forwarding(s->mainchan_chan); + + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE)); + pq_push(s->ppl.out_pq, pktout); + return TRUE; + + case SSH1_CMSG_STDIN_DATA: + data = get_string(pktin); + chan_send(s->mainchan_chan, FALSE, data.ptr, data.len); + return TRUE; + + case SSH1_CMSG_EOF: + chan_send_eof(s->mainchan_chan); + return TRUE; + + case SSH1_CMSG_WINDOW_SIZE: + return TRUE; + + case SSH1_MSG_PORT_OPEN: + remid = get_uint32(pktin); + host = get_string(pktin); + port = toint(get_uint32(pktin)); + + host_str = mkstr(host); + + ppl_logevent(("Received request to connect to port %s:%d", + host_str, port)); + c = snew(struct ssh1_channel); + c->connlayer = s; + err = portfwdmgr_connect( + s->portfwdmgr, &c->chan, host_str, port, + &c->sc, ADDRTYPE_UNSPEC); + + sfree(host_str); + + if (err) { + ppl_logevent(("Port open failed: %s", err)); + sfree(err); + ssh1_channel_free(c); + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE); + put_uint32(pktout, remid); + pq_push(s->ppl.out_pq, pktout); + } else { + ssh1_channel_init(c); + c->remoteid = remid; + c->halfopen = FALSE; + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION); + put_uint32(pktout, c->remoteid); + put_uint32(pktout, c->localid); + pq_push(s->ppl.out_pq, pktout); + ppl_logevent(("Forwarded port opened successfully")); + } + + return TRUE; + + default: + return FALSE; + } + + unexpected_setup_packet: + ssh_proto_error(s->ppl.ssh, "Received unexpected setup packet after the " + "setup phase, type %d (%s)", pktin->type, + ssh1_pkt_type(pktin->type)); + /* FIXME: ensure caller copes with us just having freed the whole layer */ + return TRUE; +} + +SshChannel *ssh1_session_open(ConnectionLayer *cl, Channel *chan) +{ + assert(FALSE && "Should never be called in the server"); +} + +struct ssh_rportfwd *ssh1_rportfwd_alloc( + ConnectionLayer *cl, + const char *shost, int sport, const char *dhost, int dport, + int addressfamily, const char *log_description, PortFwdRecord *pfr, + ssh_sharing_connstate *share_ctx) +{ + assert(FALSE && "Should never be called in the server"); + return NULL; +} + +static int ssh1sesschan_write(SshChannel *sc, int is_stderr, + const void *data, int len) +{ + struct ssh1_connection_state *s = + container_of(sc, struct ssh1_connection_state, mainchan_sc); + PktOut *pktout; + + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, + (is_stderr ? SSH1_SMSG_STDERR_DATA : SSH1_SMSG_STDOUT_DATA)); + put_string(pktout, data, len); + pq_push(s->ppl.out_pq, pktout); + + return 0; +} + +static void ssh1sesschan_write_eof(SshChannel *sc) +{ + /* SSH-1 can't represent server-side EOF */ + /* FIXME: some kind of check-termination system, whereby once this has been called _and_ we've had an exit status _and_ we've got no other channels open, we send the actual EXIT_STATUS message */ +} + +static void ssh1sesschan_initiate_close(SshChannel *sc, const char *err) +{ + /* SSH-1 relies on the client to close the connection in the end */ +} + +static void ssh1sesschan_send_exit_status(SshChannel *sc, int status) +{ + struct ssh1_connection_state *s = + container_of(sc, struct ssh1_connection_state, mainchan_sc); + PktOut *pktout; + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_EXIT_STATUS); + put_uint32(pktout, status); + pq_push(s->ppl.out_pq, pktout); +} + +static void ssh1sesschan_send_exit_signal( + SshChannel *sc, ptrlen signame, int core_dumped, ptrlen msg) +{ + /* SSH-1 has no separate representation for signals */ + ssh1sesschan_send_exit_status(sc, 128); +} + +SshChannel *ssh1_serverside_x11_open( + ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi) +{ + struct ssh1_connection_state *s = + container_of(cl, struct ssh1_connection_state, cl); + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + struct ssh1_channel *c = snew(struct ssh1_channel); + PktOut *pktout; + + c->connlayer = s; + ssh1_channel_init(c); + c->halfopen = TRUE; + c->chan = chan; + + ppl_logevent(("Forwarding X11 connection to client")); + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_X11_OPEN); + put_uint32(pktout, c->localid); + pq_push(s->ppl.out_pq, pktout); + + return &c->sc; +} + +SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan) +{ + struct ssh1_connection_state *s = + container_of(cl, struct ssh1_connection_state, cl); + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + struct ssh1_channel *c = snew(struct ssh1_channel); + PktOut *pktout; + + c->connlayer = s; + ssh1_channel_init(c); + c->halfopen = TRUE; + c->chan = chan; + + ppl_logevent(("Forwarding agent connection to client")); + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_AGENT_OPEN); + put_uint32(pktout, c->localid); + pq_push(s->ppl.out_pq, pktout); + + return &c->sc; +} diff --git a/ssh1connection.c b/ssh1connection.c index d7c9144d..a64ccb3d 100644 --- a/ssh1connection.c +++ b/ssh1connection.c @@ -69,6 +69,8 @@ static const struct ConnectionLayerVtable ssh1_connlayer_vtable = { ssh1_rportfwd_remove, ssh1_lportfwd_open, ssh1_session_open, + ssh1_serverside_x11_open, + ssh1_serverside_agent_open, ssh1_add_x11_display, NULL /* add_sharing_x11_display */, NULL /* remove_sharing_x11_display */, @@ -212,12 +214,14 @@ static void ssh1_connection_free(PacketProtocolLayer *ppl) sfree(s); } -void ssh1_connection_set_local_protoflags(PacketProtocolLayer *ppl, int flags) +void ssh1_connection_set_protoflags(PacketProtocolLayer *ppl, + int local, int remote) { assert(ppl->vt == &ssh1_connection_vtable); struct ssh1_connection_state *s = container_of(ppl, struct ssh1_connection_state, ppl); - s->local_protoflags = flags; + s->local_protoflags = local; + s->remote_protoflags = remote; } static int ssh1_connection_filter_queue(struct ssh1_connection_state *s) diff --git a/ssh1connection.h b/ssh1connection.h index c55a002d..7d5194da 100644 --- a/ssh1connection.h +++ b/ssh1connection.h @@ -8,7 +8,7 @@ struct ssh1_connection_state { Ssh *ssh; Conf *conf; - int local_protoflags; + int local_protoflags, remote_protoflags; tree234 *channels; /* indexed by local id */ @@ -49,6 +49,8 @@ struct ssh1_connection_state { */ struct outstanding_succfail *succfail_head, *succfail_tail; + int compressing; /* used in server mode only */ + ConnectionLayer cl; PacketProtocolLayer ppl; }; @@ -105,6 +107,9 @@ struct ssh_rportfwd *ssh1_rportfwd_alloc( const char *shost, int sport, const char *dhost, int dport, int addressfamily, const char *log_description, PortFwdRecord *pfr, ssh_sharing_connstate *share_ctx); +SshChannel *ssh1_serverside_x11_open( + ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi); +SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan); void ssh1_connection_direction_specific_setup( struct ssh1_connection_state *s); diff --git a/ssh1login-server.c b/ssh1login-server.c new file mode 100644 index 00000000..26f8f730 --- /dev/null +++ b/ssh1login-server.c @@ -0,0 +1,376 @@ +/* + * Packet protocol layer for the SSH-1 login phase, from the server side. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshcr.h" +#include "sshserver.h" + +struct ssh1_login_server_state { + int crState; + + PacketProtocolLayer *successor_layer; + + int remote_protoflags; + int local_protoflags; + unsigned long supported_ciphers_mask, supported_auths_mask; + unsigned cipher_type; + + unsigned char cookie[8]; + unsigned char session_key[32]; + unsigned char session_id[16]; + char *username_str; + ptrlen username; + + struct RSAKey *servkey, *hostkey; + int servkey_generated_here; + Bignum sesskey; + + AuthPolicy *authpolicy; + unsigned ap_methods, current_method; + unsigned char auth_rsa_expected_response[16]; + struct RSAKey *authkey; + int auth_successful; + + PacketProtocolLayer ppl; +}; + +static void ssh1_login_server_free(PacketProtocolLayer *); +static void ssh1_login_server_process_queue(PacketProtocolLayer *); + +static int ssh1_login_server_get_specials( + PacketProtocolLayer *ppl, add_special_fn_t add_special, + void *ctx) { return FALSE; } +static void ssh1_login_server_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg) {} +static int ssh1_login_server_want_user_input( + PacketProtocolLayer *ppl) { return FALSE; } +static void ssh1_login_server_got_user_input(PacketProtocolLayer *ppl) {} +static void ssh1_login_server_reconfigure( + PacketProtocolLayer *ppl, Conf *conf) {} + +static const struct PacketProtocolLayerVtable ssh1_login_server_vtable = { + ssh1_login_server_free, + ssh1_login_server_process_queue, + ssh1_login_server_get_specials, + ssh1_login_server_special_cmd, + ssh1_login_server_want_user_input, + ssh1_login_server_got_user_input, + ssh1_login_server_reconfigure, + NULL /* no layer names in SSH-1 */, +}; + +static void no_progress(void *param, int action, int phase, int iprogress) {} + +PacketProtocolLayer *ssh1_login_server_new( + PacketProtocolLayer *successor_layer, struct RSAKey *hostkey, + AuthPolicy *authpolicy) +{ + struct ssh1_login_server_state *s = snew(struct ssh1_login_server_state); + memset(s, 0, sizeof(*s)); + s->ppl.vt = &ssh1_login_server_vtable; + + s->hostkey = hostkey; + s->authpolicy = authpolicy; + + s->successor_layer = successor_layer; + return &s->ppl; +} + +static void ssh1_login_server_free(PacketProtocolLayer *ppl) +{ + struct ssh1_login_server_state *s = + container_of(ppl, struct ssh1_login_server_state, ppl); + + if (s->successor_layer) + ssh_ppl_free(s->successor_layer); + + if (s->servkey_generated_here && s->servkey) { + freersakey(s->servkey); + sfree(s->servkey); + } + + smemclr(s->session_key, sizeof(s->session_key)); + sfree(s->username_str); + + sfree(s); +} + +static int ssh1_login_server_filter_queue(struct ssh1_login_server_state *s) +{ + return ssh1_common_filter_queue(&s->ppl); +} + +static PktIn *ssh1_login_server_pop(struct ssh1_login_server_state *s) +{ + if (ssh1_login_server_filter_queue(s)) + return NULL; + return pq_pop(s->ppl.in_pq); +} + +static void ssh1_login_server_process_queue(PacketProtocolLayer *ppl) +{ + struct ssh1_login_server_state *s = + container_of(ppl, struct ssh1_login_server_state, ppl); + PktIn *pktin; + PktOut *pktout; + int i; + + /* Filter centrally handled messages off the front of the queue on + * every entry to this coroutine, no matter where we're resuming + * from, even if we're _not_ looping on pq_pop. That way we can + * still proactively handle those messages even if we're waiting + * for a user response. */ + if (ssh1_login_server_filter_queue(s)) + return; + + crBegin(s->crState); + + if (!s->servkey) { + int server_key_bits = s->hostkey->bytes - 256; + if (server_key_bits < 512) + server_key_bits = s->hostkey->bytes + 256; + s->servkey = snew(struct RSAKey); + rsa_generate(s->servkey, server_key_bits, no_progress, NULL); + s->servkey->comment = NULL; + s->servkey_generated_here = TRUE; + } + + s->local_protoflags = SSH1_PROTOFLAGS_SUPPORTED; + /* FIXME: ability to configure this to a subset */ + s->supported_ciphers_mask = ((1U << SSH_CIPHER_3DES) | + (1U << SSH_CIPHER_BLOWFISH) | + (1U << SSH_CIPHER_DES)); + s->supported_auths_mask = 0; + s->ap_methods = auth_methods(s->authpolicy); + if (s->ap_methods & AUTHMETHOD_PASSWORD) + s->supported_auths_mask |= (1U << SSH1_AUTH_PASSWORD); + if (s->ap_methods & AUTHMETHOD_PUBLICKEY) + s->supported_auths_mask |= (1U << SSH1_AUTH_RSA); + /* FIXME: TIS, CCARD */ + + for (i = 0; i < 8; i++) + s->cookie[i] = random_byte(); + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_PUBLIC_KEY); + put_data(pktout, s->cookie, 8); + rsa_ssh1_public_blob(BinarySink_UPCAST(pktout), + s->servkey, RSA_SSH1_EXPONENT_FIRST); + rsa_ssh1_public_blob(BinarySink_UPCAST(pktout), + s->hostkey, RSA_SSH1_EXPONENT_FIRST); + put_uint32(pktout, s->local_protoflags); + put_uint32(pktout, s->supported_ciphers_mask); + put_uint32(pktout, s->supported_auths_mask); + pq_push(s->ppl.out_pq, pktout); + + crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL); + if (pktin->type != SSH1_CMSG_SESSION_KEY) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet in response" + " to initial public key packet, type %d (%s)", + pktin->type, ssh1_pkt_type(pktin->type)); + return; + } + + { + ptrlen client_cookie; + s->cipher_type = get_byte(pktin); + client_cookie = get_data(pktin, 8); + s->sesskey = get_mp_ssh1(pktin); + s->remote_protoflags = get_uint32(pktin); + + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, "Unable to parse session key packet"); + return; + } + if (!ptrlen_eq_ptrlen(client_cookie, make_ptrlen(s->cookie, 8))) { + ssh_proto_error(s->ppl.ssh, + "Client sent incorrect anti-spoofing cookie"); + return; + } + } + if (s->cipher_type >= 32 || + !((s->supported_ciphers_mask >> s->cipher_type) & 1)) { + ssh_proto_error(s->ppl.ssh, "Client selected an unsupported cipher"); + return; + } + + { + struct RSAKey *smaller, *larger; + strbuf *data = strbuf_new(); + + if (bignum_bitcount(s->hostkey->modulus) > + bignum_bitcount(s->servkey->modulus)) { + larger = s->hostkey; + smaller = s->servkey; + } else { + smaller = s->hostkey; + larger = s->servkey; + } + + if (rsa_ssh1_decrypt_pkcs1(s->sesskey, larger, data)) { + freebn(s->sesskey); + s->sesskey = bignum_from_bytes(data->u, data->len); + data->len = 0; + if (rsa_ssh1_decrypt_pkcs1(s->sesskey, smaller, data) && + data->len == sizeof(s->session_key)) { + memcpy(s->session_key, data->u, sizeof(s->session_key)); + freebn(s->sesskey); + s->sesskey = NULL; /* indicates success */ + } + } + + strbuf_free(data); + } + if (s->sesskey) { + ssh_proto_error(s->ppl.ssh, "Failed to decrypt session key"); + return; + } + + ssh1_compute_session_id(s->session_id, s->cookie, s->hostkey, s->servkey); + + for (i = 0; i < 16; i++) + s->session_key[i] ^= s->session_id[i]; + + { + const struct ssh1_cipheralg *cipher = + (s->cipher_type == SSH_CIPHER_BLOWFISH ? &ssh1_blowfish : + s->cipher_type == SSH_CIPHER_DES ? &ssh1_des : &ssh1_3des); + ssh1_bpp_new_cipher(s->ppl.bpp, cipher, s->session_key); + } + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_SUCCESS); + pq_push(s->ppl.out_pq, pktout); + + crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL); + if (pktin->type != SSH1_CMSG_USER) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet while " + "expecting username, type %d (%s)", + pktin->type, ssh1_pkt_type(pktin->type)); + return; + } + s->username = get_string(pktin); + s->username.ptr = s->username_str = mkstr(s->username); + ppl_logevent(("Received username '%.*s'", PTRLEN_PRINTF(s->username))); + + s->auth_successful = auth_none(s->authpolicy, s->username); + while (1) { + /* Signal failed authentication */ + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_FAILURE); + pq_push(s->ppl.out_pq, pktout); + + crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL); + if (pktin->type == SSH1_CMSG_AUTH_PASSWORD) { + s->current_method = AUTHMETHOD_PASSWORD; + if (!(s->ap_methods & s->current_method)) + continue; + + ptrlen password = get_string(pktin); + + /* Tolerate historic traffic-analysis defence of NUL + + * garbage on the end of the binary password string */ + char *nul = memchr(password.ptr, '\0', password.len); + if (nul) + password.len = (const char *)nul - (const char *)password.ptr; + + if (auth_password(s->authpolicy, s->username, password)) + goto auth_success; + } else if (pktin->type == SSH1_CMSG_AUTH_RSA) { + s->current_method = AUTHMETHOD_PUBLICKEY; + if (!(s->ap_methods & s->current_method)) + continue; + + { + Bignum modulus = get_mp_ssh1(pktin); + s->authkey = auth_publickey_ssh1( + s->authpolicy, s->username, modulus); + freebn(modulus); + } + + if (!s->authkey) + continue; + + if (s->authkey->bytes < 32) { + ppl_logevent(("Auth key far too small")); + continue; + } + + { + unsigned char *rsabuf = + snewn(s->authkey->bytes, unsigned char); + struct MD5Context md5c; + + for (i = 0; i < 32; i++) + rsabuf[i] = random_byte(); + + MD5Init(&md5c); + put_data(&md5c, rsabuf, 32); + put_data(&md5c, s->session_id, 16); + MD5Final(s->auth_rsa_expected_response, &md5c); + + if (!rsa_ssh1_encrypt(rsabuf, 32, s->authkey)) { + sfree(rsabuf); + ppl_logevent(("Failed to encrypt auth challenge")); + continue; + } + + Bignum bn = bignum_from_bytes(rsabuf, s->authkey->bytes); + smemclr(rsabuf, s->authkey->bytes); + sfree(rsabuf); + + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_SMSG_AUTH_RSA_CHALLENGE); + put_mp_ssh1(pktout, bn); + pq_push(s->ppl.out_pq, pktout); + + freebn(bn); + } + + crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL); + if (pktin->type != SSH1_CMSG_AUTH_RSA_RESPONSE) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet in " + "response to RSA auth challenge, type %d (%s)", + pktin->type, ssh1_pkt_type(pktin->type)); + return; + } + + { + ptrlen response = get_data(pktin, 16); + ptrlen expected = make_ptrlen( + s->auth_rsa_expected_response, 16); + if (!ptrlen_eq_ptrlen(response, expected)) { + ppl_logevent(("Wrong response to auth challenge")); + continue; + } + } + + goto auth_success; + } + } + + auth_success: + if (!auth_successful(s->authpolicy, s->username, s->current_method)) { + ssh_sw_abort(s->ppl.ssh, "Multiple authentications required but SSH-1" + " cannot perform them"); + return; + } + + /* Signal successful authentication */ + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_SUCCESS); + pq_push(s->ppl.out_pq, pktout); + + ssh1_connection_set_protoflags( + s->successor_layer, s->local_protoflags, s->remote_protoflags); + { + PacketProtocolLayer *successor = s->successor_layer; + s->successor_layer = NULL; /* avoid freeing it ourself */ + ssh_ppl_replace(&s->ppl, successor); + return; /* we've just freed s, so avoid even touching s->crState */ + } + + crFinishV; +} diff --git a/ssh1login.c b/ssh1login.c index 0010b288..1d5b92b9 100644 --- a/ssh1login.c +++ b/ssh1login.c @@ -1086,8 +1086,8 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) } } - ssh1_connection_set_local_protoflags( - s->successor_layer, s->local_protoflags); + ssh1_connection_set_protoflags( + s->successor_layer, s->local_protoflags, s->remote_protoflags); { PacketProtocolLayer *successor = s->successor_layer; s->successor_layer = NULL; /* avoid freeing it ourself */ diff --git a/ssh2bpp.c b/ssh2bpp.c index 225fed28..f67b4ece 100644 --- a/ssh2bpp.c +++ b/ssh2bpp.c @@ -34,6 +34,7 @@ struct ssh2_bpp_state { ssh_decompressor *in_decomp; ssh_compressor *out_comp; + int is_server; int pending_newkeys; int pending_compression, seen_userauth_success; @@ -54,13 +55,14 @@ static const struct BinaryPacketProtocolVtable ssh2_bpp_vtable = { }; BinaryPacketProtocol *ssh2_bpp_new( - LogContext *logctx, struct DataTransferStats *stats) + LogContext *logctx, struct DataTransferStats *stats, int is_server) { struct ssh2_bpp_state *s = snew(struct ssh2_bpp_state); memset(s, 0, sizeof(*s)); s->bpp.vt = &ssh2_bpp_vtable; s->bpp.logctx = logctx; s->stats = stats; + s->is_server = is_server; ssh_bpp_common_setup(&s->bpp); return &s->bpp; } @@ -216,6 +218,10 @@ void ssh2_bpp_new_incoming_crypto( /* Clear the pending_newkeys flag, so that handle_input below will * start consuming the input data again. */ s->pending_newkeys = FALSE; + + /* And schedule a run of handle_input, in case there's already + * input data in the queue. */ + queue_idempotent_callback(&s->bpp.ic_in_raw); } int ssh2_bpp_rekey_inadvisable(BinaryPacketProtocol *bpp) @@ -227,6 +233,24 @@ int ssh2_bpp_rekey_inadvisable(BinaryPacketProtocol *bpp) return s->pending_compression; } +static void ssh2_bpp_enable_pending_compression(struct ssh2_bpp_state *s) +{ + BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */ + + if (s->in.pending_compression) { + s->in_decomp = ssh_decompressor_new(s->in.pending_compression); + bpp_logevent(("Initialised delayed %s decompression", + ssh_decompressor_alg(s->in_decomp)->text_name)); + s->in.pending_compression = NULL; + } + if (s->out.pending_compression) { + s->out_comp = ssh_compressor_new(s->out.pending_compression); + bpp_logevent(("Initialised delayed %s compression", + ssh_compressor_alg(s->out_comp)->text_name)); + s->out.pending_compression = NULL; + } +} + #define BPP_READ(ptr, len) do \ { \ crMaybeWaitUntilV(s->bpp.input_eof || \ @@ -571,29 +595,14 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp) continue; } - if (type == SSH2_MSG_USERAUTH_SUCCESS) { + if (type == SSH2_MSG_USERAUTH_SUCCESS && !s->is_server) { /* * Another one: if we were configured with OpenSSH's * deferred compression which is triggered on receipt * of USERAUTH_SUCCESS, then this is the moment to * turn on compression. */ - if (s->in.pending_compression) { - s->in_decomp = - ssh_decompressor_new(s->in.pending_compression); - bpp_logevent(("Initialised delayed %s decompression", - ssh_decompressor_alg( - s->in_decomp)->text_name)); - s->in.pending_compression = NULL; - } - if (s->out.pending_compression) { - s->out_comp = - ssh_compressor_new(s->out.pending_compression); - bpp_logevent(("Initialised delayed %s compression", - ssh_compressor_alg( - s->out_comp)->text_name)); - s->out.pending_compression = NULL; - } + ssh2_bpp_enable_pending_compression(s); /* * Whether or not we were doing delayed compression in @@ -869,13 +878,15 @@ static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp) } while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) { - if (userauth_range(pkt->type)) + int type = pkt->type; + + if (userauth_range(type)) n_userauth--; ssh2_bpp_format_packet(s, pkt); ssh_free_pktout(pkt); - if (n_userauth == 0 && s->out.pending_compression) { + if (n_userauth == 0 && s->out.pending_compression && !s->is_server) { /* * This is the last userauth packet in the queue, so * unless our side decides to send another one in future, @@ -885,6 +896,8 @@ static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp) */ s->pending_compression = TRUE; return; + } else if (type == SSH2_MSG_USERAUTH_SUCCESS && s->is_server) { + ssh2_bpp_enable_pending_compression(s); } } } diff --git a/ssh2connection-client.c b/ssh2connection-client.c index 5df69fd8..65fc6bc2 100644 --- a/ssh2connection-client.c +++ b/ssh2connection-client.c @@ -301,6 +301,17 @@ SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan) return &c->sc; } +SshChannel *ssh2_serverside_x11_open( + ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi) +{ + assert(FALSE && "Should never be called in the client"); +} + +SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan) +{ + assert(FALSE && "Should never be called in the client"); +} + static void ssh2_channel_response( struct ssh2_channel *c, PktIn *pkt, void *ctx) { diff --git a/ssh2connection-server.c b/ssh2connection-server.c new file mode 100644 index 00000000..da4b60cf --- /dev/null +++ b/ssh2connection-server.c @@ -0,0 +1,290 @@ +/* + * Server-specific parts of the SSH-2 connection layer. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshchan.h" +#include "sshcr.h" +#include "ssh2connection.h" +#include "sshserver.h" + +static ChanopenResult chan_open_session( + struct ssh2_connection_state *s, SshChannel *sc) +{ + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + + ppl_logevent(("Opened session channel")); + CHANOPEN_RETURN_SUCCESS(sesschan_new(sc, s->ppl.logctx)); +} + +static ChanopenResult chan_open_direct_tcpip( + struct ssh2_connection_state *s, SshChannel *sc, + ptrlen dstaddr, int dstport, ptrlen peeraddr, int peerport) +{ + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + Channel *ch; + char *dstaddr_str, *err; + + dstaddr_str = mkstr(dstaddr); + + ppl_logevent(("Received request to connect to port %s:%d (from %.*s:%d)", + dstaddr_str, dstport, PTRLEN_PRINTF(peeraddr), peerport)); + err = portfwdmgr_connect( + s->portfwdmgr, &ch, dstaddr_str, dstport, sc, ADDRTYPE_UNSPEC); + + sfree(dstaddr_str); + + if (err != NULL) { + ppl_logevent(("Port open failed: %s", err)); + sfree(err); + CHANOPEN_RETURN_FAILURE( + SSH2_OPEN_CONNECT_FAILED, ("Connection failed")); + } + + ppl_logevent(("Port opened successfully")); + CHANOPEN_RETURN_SUCCESS(ch); +} + +ChanopenResult ssh2_connection_parse_channel_open( + struct ssh2_connection_state *s, ptrlen type, + PktIn *pktin, SshChannel *sc) +{ + if (ptrlen_eq_string(type, "session")) { + return chan_open_session(s, sc); + } else if (ptrlen_eq_string(type, "direct-tcpip")) { + ptrlen dstaddr = get_string(pktin); + int dstport = toint(get_uint32(pktin)); + ptrlen peeraddr = get_string(pktin); + int peerport = toint(get_uint32(pktin)); + return chan_open_direct_tcpip( + s, sc, dstaddr, dstport, peeraddr, peerport); + } else { + CHANOPEN_RETURN_FAILURE( + SSH2_OPEN_UNKNOWN_CHANNEL_TYPE, + ("Unsupported channel type requested")); + } +} + +int ssh2_connection_parse_global_request( + struct ssh2_connection_state *s, ptrlen type, PktIn *pktin) +{ + if (ptrlen_eq_string(type, "tcpip-forward")) { + char *host = mkstr(get_string(pktin)); + unsigned port = get_uint32(pktin); + /* In SSH-2, the host/port we listen on are the same host/port + * we want reported back to us when a connection comes in, + * because that's what we tell the client */ + int toret = portfwdmgr_listen( + s->portfwdmgr, host, port, host, port, s->conf); + sfree(host); + return toret; + } else if (ptrlen_eq_string(type, "cancel-tcpip-forward")) { + char *host = mkstr(get_string(pktin)); + unsigned port = get_uint32(pktin); + int toret = portfwdmgr_unlisten(s->portfwdmgr, host, port); + sfree(host); + return toret; + } else { + /* Unrecognised request. */ + return FALSE; + } +} + +PktOut *ssh2_portfwd_chanopen( + struct ssh2_connection_state *s, struct ssh2_channel *c, + const char *hostname, int port, + const char *description, const SocketPeerInfo *pi) +{ + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + PktOut *pktout; + + /* + * In server mode, this function is called by portfwdmgr in + * response to PortListeners that were set up by calling + * portfwdmgr_listen, which means that the hostname and port + * parameters will identify the listening socket on which a + * connection just came in. + */ + + if (pi && pi->log_text) + ppl_logevent(("Forwarding connection to listening port %s:%d from %s", + hostname, port, pi->log_text)); + else + ppl_logevent(("Forwarding connection to listening port %s:%d", + hostname, port)); + + pktout = ssh2_chanopen_init(c, "forwarded-tcpip"); + put_stringz(pktout, hostname); + put_uint32(pktout, port); + put_stringz(pktout, (pi && pi->addr_text ? pi->addr_text : "0.0.0.0")); + put_uint32(pktout, (pi && pi->port >= 0 ? pi->port : 0)); + + return pktout; +} + +struct ssh_rportfwd *ssh2_rportfwd_alloc( + ConnectionLayer *cl, + const char *shost, int sport, const char *dhost, int dport, + int addressfamily, const char *log_description, PortFwdRecord *pfr, + ssh_sharing_connstate *share_ctx) +{ + assert(FALSE && "Should never be called in the server"); +} + +void ssh2_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf) +{ + assert(FALSE && "Should never be called in the server"); +} + +SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan) +{ + assert(FALSE && "Should never be called in the server"); +} + +SshChannel *ssh2_serverside_x11_open( + ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + struct ssh2_channel *c = snew(struct ssh2_channel); + PktOut *pktout; + + c->connlayer = s; + ssh2_channel_init(c); + c->halfopen = TRUE; + c->chan = chan; + + ppl_logevent(("Forwarding X11 channel to client")); + + pktout = ssh2_chanopen_init(c, "x11"); + put_stringz(pktout, (pi && pi->addr_text ? pi->addr_text : "0.0.0.0")); + put_uint32(pktout, (pi && pi->port >= 0 ? pi->port : 0)); + pq_push(s->ppl.out_pq, pktout); + + return &c->sc; +} + +SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + struct ssh2_channel *c = snew(struct ssh2_channel); + PktOut *pktout; + + c->connlayer = s; + ssh2_channel_init(c); + c->halfopen = TRUE; + c->chan = chan; + + ppl_logevent(("Forwarding SSH agent to client")); + + pktout = ssh2_chanopen_init(c, "auth-agent@openssh.com"); + pq_push(s->ppl.out_pq, pktout); + + return &c->sc; +} + +void ssh2channel_start_shell(SshChannel *sc, int want_reply) +{ + assert(FALSE && "Should never be called in the server"); +} + +void ssh2channel_start_command( + SshChannel *sc, int want_reply, const char *command) +{ + assert(FALSE && "Should never be called in the server"); +} + +int ssh2channel_start_subsystem( + SshChannel *sc, int want_reply, const char *subsystem) +{ + assert(FALSE && "Should never be called in the server"); +} + +void ssh2channel_send_exit_status(SshChannel *sc, int status) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + + PktOut *pktout = ssh2_chanreq_init(c, "exit-status", NULL, NULL); + put_uint32(pktout, status); + + pq_push(s->ppl.out_pq, pktout); +} + +void ssh2channel_send_exit_signal( + SshChannel *sc, ptrlen signame, int core_dumped, ptrlen msg) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + + PktOut *pktout = ssh2_chanreq_init(c, "exit-signal", NULL, NULL); + put_stringpl(pktout, signame); + put_bool(pktout, core_dumped); + put_stringpl(pktout, msg); + put_stringz(pktout, ""); /* language tag */ + + pq_push(s->ppl.out_pq, pktout); +} + +void ssh2channel_send_exit_signal_numeric( + SshChannel *sc, int signum, int core_dumped, ptrlen msg) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + + PktOut *pktout = ssh2_chanreq_init(c, "exit-signal", NULL, NULL); + put_uint32(pktout, signum); + put_bool(pktout, core_dumped); + put_stringpl(pktout, msg); + put_stringz(pktout, ""); /* language tag */ + + pq_push(s->ppl.out_pq, pktout); +} + +void ssh2channel_request_x11_forwarding( + SshChannel *sc, int want_reply, const char *authproto, + const char *authdata, int screen_number, int oneshot) +{ + assert(FALSE && "Should never be called in the server"); +} + +void ssh2channel_request_agent_forwarding(SshChannel *sc, int want_reply) +{ + assert(FALSE && "Should never be called in the server"); +} + +void ssh2channel_request_pty( + SshChannel *sc, int want_reply, Conf *conf, int w, int h) +{ + assert(FALSE && "Should never be called in the server"); +} + +int ssh2channel_send_env_var( + SshChannel *sc, int want_reply, const char *var, const char *value) +{ + assert(FALSE && "Should never be called in the server"); +} + +int ssh2channel_send_serial_break(SshChannel *sc, int want_reply, int length) +{ + assert(FALSE && "Should never be called in the server"); +} + +int ssh2channel_send_signal( + SshChannel *sc, int want_reply, const char *signame) +{ + assert(FALSE && "Should never be called in the server"); +} + +void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h) +{ + assert(FALSE && "Should never be called in the server"); +} diff --git a/ssh2connection.c b/ssh2connection.c index 9c27a715..fa079fec 100644 --- a/ssh2connection.c +++ b/ssh2connection.c @@ -69,6 +69,8 @@ static const struct ConnectionLayerVtable ssh2_connlayer_vtable = { ssh2_rportfwd_remove, ssh2_lportfwd_open, ssh2_session_open, + ssh2_serverside_x11_open, + ssh2_serverside_agent_open, ssh2_add_x11_display, ssh2_add_sharing_x11_display, ssh2_remove_sharing_x11_display, diff --git a/ssh2connection.h b/ssh2connection.h index 11e62419..a441127e 100644 --- a/ssh2connection.h +++ b/ssh2connection.h @@ -163,8 +163,10 @@ struct ssh_rportfwd *ssh2_rportfwd_alloc( ssh_sharing_connstate *share_ctx); void ssh2_rportfwd_remove( ConnectionLayer *cl, struct ssh_rportfwd *rpf); - SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan); +SshChannel *ssh2_serverside_x11_open( + ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi); +SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan); void ssh2channel_send_exit_status(SshChannel *c, int status); void ssh2channel_send_exit_signal( diff --git a/ssh2kex-server.c b/ssh2kex-server.c new file mode 100644 index 00000000..4b62d56d --- /dev/null +++ b/ssh2kex-server.c @@ -0,0 +1,289 @@ +/* + * Server side of key exchange for the SSH-2 transport protocol (RFC 4253). + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshcr.h" +#include "storage.h" +#include "ssh2transport.h" + +void ssh2_transport_provide_hostkeys(PacketProtocolLayer *ppl, + ssh_key *const *hostkeys, int nhostkeys) +{ + struct ssh2_transport_state *s = + container_of(ppl, struct ssh2_transport_state, ppl); + + s->hostkeys = hostkeys; + s->nhostkeys = nhostkeys; +} + +static strbuf *finalise_and_sign_exhash(struct ssh2_transport_state *s) +{ + strbuf *sb; + ssh2transport_finalise_exhash(s); + sb = strbuf_new(); + ssh_key_sign(s->hkey, s->exchange_hash, s->kex_alg->hash->hlen, + BinarySink_UPCAST(sb)); + return sb; +} + +static void no_progress(void *param, int action, int phase, int iprogress) +{ +} + +void ssh2kex_coroutine(struct ssh2_transport_state *s) +{ + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + PktIn *pktin; + PktOut *pktout; + + crBegin(s->crStateKex); + + { + int i; + for (i = 0; i < s->nhostkeys; i++) + if (ssh_key_alg(s->hostkeys[i]) == s->hostkey_alg) { + s->hkey = s->hostkeys[i]; + break; + } + assert(s->hkey); + } + + s->hostkeyblob->len = 0; + ssh_key_public_blob(s->hkey, BinarySink_UPCAST(s->hostkeyblob)); + s->hostkeydata = ptrlen_from_strbuf(s->hostkeyblob); + + put_stringpl(s->exhash, s->hostkeydata); + + if (s->kex_alg->main_type == KEXTYPE_DH) { + /* + * If we're doing Diffie-Hellman group exchange, start by + * waiting for the group request. + */ + if (dh_is_gex(s->kex_alg)) { + ppl_logevent(("Doing Diffie-Hellman group exchange")); + s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGEX; + + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_KEX_DH_GEX_REQUEST && + pktin->type != SSH2_MSG_KEX_DH_GEX_REQUEST_OLD) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting Diffie-Hellman group exchange " + "request, type %d (%s)", pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return; + } + + if (pktin->type != SSH2_MSG_KEX_DH_GEX_REQUEST_OLD) { + s->dh_got_size_bounds = TRUE; + s->dh_min_size = get_uint32(pktin); + s->pbits = get_uint32(pktin); + s->dh_max_size = get_uint32(pktin); + } else { + s->dh_got_size_bounds = FALSE; + s->pbits = get_uint32(pktin); + } + + /* + * This is a hopeless strategy for making a secure DH + * group! It's good enough for testing a client against, + * but not for serious use. + */ + s->p = primegen(s->pbits, 2, 2, NULL, 1, no_progress, NULL, 1); + s->g = bignum_from_long(2); + s->dh_ctx = dh_setup_gex(s->p, s->g); + s->kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT; + s->kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY; + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_GROUP); + put_mp_ssh2(pktout, s->p); + put_mp_ssh2(pktout, s->g); + pq_push(s->ppl.out_pq, pktout); + } else { + s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGROUP; + s->dh_ctx = dh_setup_group(s->kex_alg); + s->kex_init_value = SSH2_MSG_KEXDH_INIT; + s->kex_reply_value = SSH2_MSG_KEXDH_REPLY; + ppl_logevent(("Using Diffie-Hellman with standard group \"%s\"", + s->kex_alg->groupname)); + } + + ppl_logevent(("Doing Diffie-Hellman key exchange with hash %s", + s->kex_alg->hash->text_name)); + + /* + * Generate e for Diffie-Hellman. + */ + s->e = dh_create_e(s->dh_ctx, s->nbits * 2); + + /* + * Wait to receive f. + */ + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != s->kex_init_value) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting Diffie-Hellman initial packet, " + "type %d (%s)", pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return; + } + s->f = get_mp_ssh2(pktin); + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, + "Unable to parse Diffie-Hellman initial packet"); + return; + } + + { + const char *err = dh_validate_f(s->dh_ctx, s->f); + if (err) { + ssh_proto_error(s->ppl.ssh, "Diffie-Hellman initial packet " + "failed validation: %s", err); + return; + } + } + s->K = dh_find_K(s->dh_ctx, s->f); + + if (dh_is_gex(s->kex_alg)) { + if (s->dh_got_size_bounds) + put_uint32(s->exhash, s->dh_min_size); + put_uint32(s->exhash, s->pbits); + if (s->dh_got_size_bounds) + put_uint32(s->exhash, s->dh_max_size); + put_mp_ssh2(s->exhash, s->p); + put_mp_ssh2(s->exhash, s->g); + } + put_mp_ssh2(s->exhash, s->f); + put_mp_ssh2(s->exhash, s->e); + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, s->kex_reply_value); + put_stringpl(pktout, s->hostkeydata); + put_mp_ssh2(pktout, s->e); + put_stringsb(pktout, finalise_and_sign_exhash(s)); + pq_push(s->ppl.out_pq, pktout); + + dh_cleanup(s->dh_ctx); + s->dh_ctx = NULL; + freebn(s->f); s->f = NULL; + if (dh_is_gex(s->kex_alg)) { + freebn(s->g); s->g = NULL; + freebn(s->p); s->p = NULL; + } + } else if (s->kex_alg->main_type == KEXTYPE_ECDH) { + ppl_logevent(("Doing ECDH key exchange with curve %s and hash %s", + ssh_ecdhkex_curve_textname(s->kex_alg), + s->kex_alg->hash->text_name)); + s->ppl.bpp->pls->kctx = SSH2_PKTCTX_ECDHKEX; + + s->ecdh_key = ssh_ecdhkex_newkey(s->kex_alg); + if (!s->ecdh_key) { + ssh_sw_abort(s->ppl.ssh, "Unable to generate key for ECDH"); + return; + } + + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_KEX_ECDH_INIT) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting ECDH initial packet, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return; + } + + { + ptrlen keydata = get_string(pktin); + put_stringpl(s->exhash, keydata); + + s->K = ssh_ecdhkex_getkey(s->ecdh_key, keydata.ptr, keydata.len); + if (!get_err(pktin) && !s->K) { + ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve " + "point in ECDH initial packet"); + return; + } + } + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_ECDH_REPLY); + put_stringpl(pktout, s->hostkeydata); + { + strbuf *pubpoint = strbuf_new(); + ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); + put_string(s->exhash, pubpoint->u, pubpoint->len); + put_stringsb(pktout, pubpoint); + } + put_stringsb(pktout, finalise_and_sign_exhash(s)); + pq_push(s->ppl.out_pq, pktout); + + ssh_ecdhkex_freekey(s->ecdh_key); + s->ecdh_key = NULL; + } else if (s->kex_alg->main_type == KEXTYPE_GSS) { + ssh_sw_abort(s->ppl.ssh, "GSS key exchange not supported in server"); + } else { + assert(s->kex_alg->main_type == KEXTYPE_RSA); + ppl_logevent(("Doing RSA key exchange with hash %s", + s->kex_alg->hash->text_name)); + s->ppl.bpp->pls->kctx = SSH2_PKTCTX_RSAKEX; + + { + const struct ssh_rsa_kex_extra *extra = + (const struct ssh_rsa_kex_extra *)s->kex_alg->extra; + + s->rsa_kex_key = snew(struct RSAKey); + rsa_generate(s->rsa_kex_key, extra->minklen, no_progress, NULL); + s->rsa_kex_key->comment = NULL; + } + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXRSA_PUBKEY); + put_stringpl(pktout, s->hostkeydata); + { + strbuf *pubblob = strbuf_new(); + ssh_key_public_blob(&s->rsa_kex_key->sshk, + BinarySink_UPCAST(pubblob)); + put_string(s->exhash, pubblob->u, pubblob->len); + put_stringsb(pktout, pubblob); + } + pq_push(s->ppl.out_pq, pktout); + + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_KEXRSA_SECRET) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting RSA kex secret, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return; + } + + { + ptrlen encrypted_secret = get_string(pktin); + put_stringpl(s->exhash, encrypted_secret); + s->K = ssh_rsakex_decrypt( + s->kex_alg->hash, encrypted_secret, s->rsa_kex_key); + } + + if (!s->K) { + ssh_proto_error(s->ppl.ssh, "Unable to decrypt RSA kex secret"); + return; + } + + ssh_rsakex_freekey(s->rsa_kex_key); + s->rsa_kex_key = NULL; + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXRSA_DONE); + put_stringsb(pktout, finalise_and_sign_exhash(s)); + pq_push(s->ppl.out_pq, pktout); + } + + crFinishV; +} diff --git a/ssh2transport.c b/ssh2transport.c index 22960f01..7b04e5f7 100644 --- a/ssh2transport.c +++ b/ssh2transport.c @@ -108,8 +108,8 @@ PacketProtocolLayer *ssh2_transport_new( Conf *conf, const char *host, int port, const char *fullhostname, const char *client_greeting, const char *server_greeting, struct ssh_connection_shared_gss_state *shgss, - struct DataTransferStats *stats, - PacketProtocolLayer *higher_layer) + struct DataTransferStats *stats, PacketProtocolLayer *higher_layer, + int is_server) { struct ssh2_transport_state *s = snew(struct ssh2_transport_state); memset(s, 0, sizeof(*s)); @@ -123,6 +123,7 @@ PacketProtocolLayer *ssh2_transport_new( s->client_greeting = dupstr(client_greeting); s->server_greeting = dupstr(server_greeting); s->stats = stats; + s->hostkeyblob = strbuf_new(); pq_in_init(&s->pq_in_higher); pq_out_init(&s->pq_out_higher); @@ -142,8 +143,17 @@ PacketProtocolLayer *ssh2_transport_new( s->thc = ssh_transient_hostkey_cache_new(); s->gss_kex_used = FALSE; - s->client_kexinit = strbuf_new(); - s->server_kexinit = strbuf_new(); + s->outgoing_kexinit = strbuf_new(); + s->incoming_kexinit = strbuf_new(); + if (is_server) { + s->client_kexinit = s->incoming_kexinit; + s->server_kexinit = s->outgoing_kexinit; + s->out.mkkey_adjust = 1; + } else { + s->client_kexinit = s->outgoing_kexinit; + s->server_kexinit = s->incoming_kexinit; + s->in.mkkey_adjust = 1; + } ssh2_transport_set_max_data_size(s); @@ -184,8 +194,9 @@ static void ssh2_transport_free(PacketProtocolLayer *ppl) sfree(s->server_greeting); sfree(s->keystr); sfree(s->hostkey_str); + strbuf_free(s->hostkeyblob); sfree(s->fingerprint); - if (s->hkey) { + if (s->hkey && !s->hostkeys) { ssh_key_free(s->hkey); s->hkey = NULL; } @@ -201,8 +212,8 @@ static void ssh2_transport_free(PacketProtocolLayer *ppl) ssh_ecdhkex_freekey(s->ecdh_key); if (s->exhash) ssh_hash_free(s->exhash); - strbuf_free(s->client_kexinit); - strbuf_free(s->server_kexinit); + strbuf_free(s->outgoing_kexinit); + strbuf_free(s->incoming_kexinit); ssh_transient_hostkey_cache_free(s->thc); sfree(s); } @@ -395,6 +406,7 @@ static void ssh2_write_kexinit_lists( Conf *conf, int remote_bugs, const char *hk_host, int hk_port, const ssh_keyalg *hk_prev, ssh_transient_hostkey_cache *thc, + ssh_key *const *our_hostkeys, int our_nhostkeys, int first_time, int can_gssapi_keyex, int transient_hostkey_mode) { int i, j, k, warn; @@ -522,7 +534,18 @@ static void ssh2_write_kexinit_lists( } } /* List server host key algorithms. */ - if (first_time) { + if (our_hostkeys) { + /* + * In server mode, we just list the algorithms that match the + * host keys we actually have. + */ + for (i = 0; i < our_nhostkeys; i++) { + alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], + ssh_key_alg(our_hostkeys[i])->ssh_id); + alg->u.hk.hostkey = ssh_key_alg(our_hostkeys[i]); + alg->u.hk.warn = FALSE; + } + } else if (first_time) { /* * In the first key exchange, we list all the algorithms * we're prepared to cope with, but prefer those algorithms @@ -1016,28 +1039,29 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) * later. */ s->client_kexinit->len = 0; - put_byte(s->client_kexinit, SSH2_MSG_KEXINIT); + put_byte(s->outgoing_kexinit, SSH2_MSG_KEXINIT); { int i; for (i = 0; i < 16; i++) - put_byte(s->client_kexinit, (unsigned char) random_byte()); + put_byte(s->outgoing_kexinit, (unsigned char) random_byte()); } ssh2_write_kexinit_lists( - BinarySink_UPCAST(s->client_kexinit), s->kexlists, + BinarySink_UPCAST(s->outgoing_kexinit), s->kexlists, s->conf, s->ppl.remote_bugs, s->savedhost, s->savedport, s->hostkey_alg, s->thc, + s->hostkeys, s->nhostkeys, !s->got_session_id, s->can_gssapi_keyex, s->gss_kex_used && !s->need_gss_transient_hostkey); /* First KEX packet does _not_ follow, because we're not that brave. */ - put_bool(s->client_kexinit, FALSE); - put_uint32(s->client_kexinit, 0); /* reserved */ + put_bool(s->outgoing_kexinit, FALSE); + put_uint32(s->outgoing_kexinit, 0); /* reserved */ /* * Send our KEXINIT. */ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT); - put_data(pktout, s->client_kexinit->u + 1, - s->client_kexinit->len - 1); /* omit initial packet type byte */ + put_data(pktout, s->outgoing_kexinit->u + 1, + s->outgoing_kexinit->len - 1); /* omit initial packet type byte */ pq_push(s->ppl.out_pq, pktout); /* @@ -1056,9 +1080,9 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) s->ppl.bpp->pls->actx, pktin->type)); return; } - s->server_kexinit->len = 0; - put_byte(s->server_kexinit, SSH2_MSG_KEXINIT); - put_data(s->server_kexinit, get_ptr(pktin), get_avail(pktin)); + s->incoming_kexinit->len = 0; + put_byte(s->incoming_kexinit, SSH2_MSG_KEXINIT); + put_data(s->incoming_kexinit, get_ptr(pktin), get_avail(pktin)); /* * Work through the two KEXINIT packets in parallel to find the @@ -1241,8 +1265,8 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) ssh_bpp_handle_output(s->ppl.bpp); /* - * We've sent client NEWKEYS, so create and initialise - * client-to-server session keys. + * We've sent outgoing NEWKEYS, so create and initialise outgoing + * session keys. */ { strbuf *cipher_key = strbuf_new(); @@ -1250,14 +1274,15 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) strbuf *mac_key = strbuf_new(); if (s->out.cipher) { - ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash, 'A', - s->out.cipher->blksize); - ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash, 'C', + ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash, + 'A' + s->out.mkkey_adjust, s->out.cipher->blksize); + ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash, + 'C' + s->out.mkkey_adjust, s->out.cipher->padded_keybytes); } if (s->out.mac) { - ssh2_mkkey(s, mac_key, s->K, s->exchange_hash, 'E', - s->out.mac->keylen); + ssh2_mkkey(s, mac_key, s->K, s->exchange_hash, + 'E' + s->out.mkkey_adjust, s->out.mac->keylen); } ssh2_bpp_new_outgoing_crypto( @@ -1296,8 +1321,8 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) s->stats->in.remaining = s->max_data_size; /* - * We've seen server NEWKEYS, so create and initialise - * server-to-client session keys. + * We've seen incoming NEWKEYS, so create and initialise + * incoming session keys. */ { strbuf *cipher_key = strbuf_new(); @@ -1305,14 +1330,15 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) strbuf *mac_key = strbuf_new(); if (s->in.cipher) { - ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash, 'B', - s->in.cipher->blksize); - ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash, 'D', + ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash, + 'A' + s->in.mkkey_adjust, s->in.cipher->blksize); + ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash, + 'C' + s->in.mkkey_adjust, s->in.cipher->padded_keybytes); } if (s->in.mac) { - ssh2_mkkey(s, mac_key, s->K, s->exchange_hash, 'F', - s->in.mac->keylen); + ssh2_mkkey(s, mac_key, s->K, s->exchange_hash, + 'E' + s->in.mkkey_adjust, s->in.mac->keylen); } ssh2_bpp_new_incoming_crypto( @@ -1364,14 +1390,43 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) * decided to initiate a rekey ourselves for some reason. */ if (!s->higher_layer_ok) { - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_SERVICE_REQUEST); - put_stringz(pktout, s->higher_layer->vt->name); - pq_push(s->ppl.out_pq, pktout); - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_SERVICE_ACCEPT) { - ssh_sw_abort(s->ppl.ssh, "Server refused request to start " - "'%s' protocol", s->higher_layer->vt->name); - return; + if (!s->hostkeys) { + /* We're the client, so send SERVICE_REQUEST. */ + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_SERVICE_REQUEST); + put_stringz(pktout, s->higher_layer->vt->name); + pq_push(s->ppl.out_pq, pktout); + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_SERVICE_ACCEPT) { + ssh_sw_abort(s->ppl.ssh, "Server refused request to start " + "'%s' protocol", s->higher_layer->vt->name); + return; + } + } else { + ptrlen service_name; + + /* We're the server, so expect SERVICE_REQUEST. */ + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_SERVICE_REQUEST) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting SERVICE_REQUEST, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return; + } + service_name = get_string(pktin); + if (!ptrlen_eq_string(service_name, s->higher_layer->vt->name)) { + ssh_proto_error(s->ppl.ssh, "Client requested service " + "'%.*s' when we only support '%s'", + PTRLEN_PRINTF(service_name), + s->higher_layer->vt->name); + return; + } + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_SERVICE_ACCEPT); + put_stringz(pktout, s->higher_layer->vt->name); + pq_push(s->ppl.out_pq, pktout); } s->higher_layer_ok = TRUE; diff --git a/ssh2transport.h b/ssh2transport.h index 924ef38d..a86b6ee1 100644 --- a/ssh2transport.h +++ b/ssh2transport.h @@ -108,6 +108,7 @@ typedef struct transport_direction { int etm_mode; const struct ssh_compression_alg *comp; int comp_delayed; + int mkkey_adjust; } transport_direction; struct ssh2_transport_state { @@ -131,6 +132,7 @@ struct ssh2_transport_state { char *hostkey_str; /* string representation, for easy checking in rekeys */ unsigned char session_id[SSH2_KEX_MAX_HASH_LEN]; int session_id_len; + int dh_min_size, dh_max_size, dh_got_size_bounds; struct dh_ctx *dh_ctx; ssh_hash *exhash; @@ -163,10 +165,12 @@ struct ssh2_transport_state { int nbits, pbits, warn_kex, warn_hk, warn_cscipher, warn_sccipher; Bignum p, g, e, f, K; - strbuf *client_kexinit, *server_kexinit; + strbuf *outgoing_kexinit, *incoming_kexinit; + strbuf *client_kexinit, *server_kexinit; /* aliases to the above */ int kex_init_value, kex_reply_value; transport_direction in, out; ptrlen hostkeydata, sigdata; + strbuf *hostkeyblob; char *keystr, *fingerprint; ssh_key *hkey; /* actual host key */ struct RSAKey *rsa_kex_key; /* for RSA kex */ @@ -203,6 +207,9 @@ struct ssh2_transport_state { */ int cross_certifying; + ssh_key *const *hostkeys; + int nhostkeys; + PacketProtocolLayer ppl; }; diff --git a/ssh2userauth-server.c b/ssh2userauth-server.c new file mode 100644 index 00000000..84f3d27c --- /dev/null +++ b/ssh2userauth-server.c @@ -0,0 +1,269 @@ +/* + * Packet protocol layer for the server side of the SSH-2 userauth + * protocol (RFC 4252). + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshcr.h" +#include "sshserver.h" + +#ifndef NO_GSSAPI +#include "sshgssc.h" +#include "sshgss.h" +#endif + +struct ssh2_userauth_server_state { + int crState; + + PacketProtocolLayer *transport_layer, *successor_layer; + ptrlen session_id; + + AuthPolicy *authpolicy; + + ptrlen username, service, method; + unsigned methods, this_method; + int partial_success; + + PacketProtocolLayer ppl; +}; + +static void ssh2_userauth_server_free(PacketProtocolLayer *); +static void ssh2_userauth_server_process_queue(PacketProtocolLayer *); + +static const struct PacketProtocolLayerVtable ssh2_userauth_server_vtable = { + ssh2_userauth_server_free, + ssh2_userauth_server_process_queue, + NULL /* get_specials */, + NULL /* special_cmd */, + NULL /* want_user_input */, + NULL /* got_user_input */, + NULL /* reconfigure */, + "ssh-userauth", +}; + +PacketProtocolLayer *ssh2_userauth_server_new( + PacketProtocolLayer *successor_layer, AuthPolicy *authpolicy) +{ + struct ssh2_userauth_server_state *s = + snew(struct ssh2_userauth_server_state); + memset(s, 0, sizeof(*s)); + s->ppl.vt = &ssh2_userauth_server_vtable; + + s->successor_layer = successor_layer; + s->authpolicy = authpolicy; + + return &s->ppl; +} + +void ssh2_userauth_server_set_transport_layer(PacketProtocolLayer *userauth, + PacketProtocolLayer *transport) +{ + struct ssh2_userauth_server_state *s = + container_of(userauth, struct ssh2_userauth_server_state, ppl); + s->transport_layer = transport; +} + +static void ssh2_userauth_server_free(PacketProtocolLayer *ppl) +{ + struct ssh2_userauth_server_state *s = + container_of(ppl, struct ssh2_userauth_server_state, ppl); + + if (s->successor_layer) + ssh_ppl_free(s->successor_layer); + + sfree(s); +} + +static PktIn *ssh2_userauth_server_pop(struct ssh2_userauth_server_state *s) +{ + return pq_pop(s->ppl.in_pq); +} + +static void ssh2_userauth_server_add_session_id( + struct ssh2_userauth_server_state *s, strbuf *sigdata) +{ + if (s->ppl.remote_bugs & BUG_SSH2_PK_SESSIONID) { + put_data(sigdata, s->session_id.ptr, s->session_id.len); + } else { + put_stringpl(sigdata, s->session_id); + } +} + +static void ssh2_userauth_server_process_queue(PacketProtocolLayer *ppl) +{ + struct ssh2_userauth_server_state *s = + container_of(ppl, struct ssh2_userauth_server_state, ppl); + PktIn *pktin; + PktOut *pktout; + + crBegin(s->crState); + + s->session_id = ssh2_transport_get_session_id(s->transport_layer); + + while (1) { + crMaybeWaitUntilV((pktin = ssh2_userauth_server_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_USERAUTH_REQUEST) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting USERAUTH_REQUEST, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, pktin->type)); + return; + } + + s->username = get_string(pktin); + s->service = get_string(pktin); + s->method = get_string(pktin); + + if (!ptrlen_eq_string(s->service, s->successor_layer->vt->name)) { + /* + * Unconditionally reject authentication for any service + * other than the one we're going to hand over to. + */ + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_FAILURE); + put_stringz(pktout, ""); + put_bool(pktout, FALSE); + pq_push(s->ppl.out_pq, pktout); + continue; + } + + s->methods = auth_methods(s->authpolicy); + s->partial_success = FALSE; + + if (ptrlen_eq_string(s->method, "none")) { + s->this_method = AUTHMETHOD_NONE; + if (!(s->methods & s->this_method)) + goto failure; + + if (!auth_none(s->authpolicy, s->username)) + goto failure; + } else if (ptrlen_eq_string(s->method, "password")) { + int changing; + ptrlen password; + + s->this_method = AUTHMETHOD_PASSWORD; + if (!(s->methods & s->this_method)) + goto failure; + + changing = get_bool(pktin); + if (changing) + goto failure; /* FIXME: not yet supported */ + + password = get_string(pktin); + + if (!auth_password(s->authpolicy, s->username, password)) + goto failure; + } else if (ptrlen_eq_string(s->method, "publickey")) { + int has_signature, success; + ptrlen algorithm, blob, signature; + const ssh_keyalg *keyalg; + ssh_key *key; + strbuf *sigdata; + + s->this_method = AUTHMETHOD_PUBLICKEY; + if (!(s->methods & s->this_method)) + goto failure; + + has_signature = get_bool(pktin); + algorithm = get_string(pktin); + blob = get_string(pktin); + + if (!auth_publickey(s->authpolicy, s->username, blob)) + goto failure; + + keyalg = find_pubkey_alg_len(algorithm); + if (!keyalg) + goto failure; + key = ssh_key_new_pub(keyalg, blob); + if (!key) + goto failure; + + if (!has_signature) { + ssh_key_free(key); + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_PK_OK); + put_stringpl(pktout, algorithm); + put_stringpl(pktout, blob); + pq_push(s->ppl.out_pq, pktout); + continue; /* skip USERAUTH_{SUCCESS,FAILURE} epilogue */ + } + + sigdata = strbuf_new(); + ssh2_userauth_server_add_session_id(s, sigdata); + put_byte(sigdata, SSH2_MSG_USERAUTH_REQUEST); + put_stringpl(sigdata, s->username); + put_stringpl(sigdata, s->service); + put_stringpl(sigdata, s->method); + put_bool(sigdata, has_signature); + put_stringpl(sigdata, algorithm); + put_stringpl(sigdata, blob); + + signature = get_string(pktin); + success = ssh_key_verify(key, signature, + ptrlen_from_strbuf(sigdata)); + ssh_key_free(key); + strbuf_free(sigdata); + + if (!success) + goto failure; + } else { + goto failure; + } + + /* + * If we get here, we've successfully completed this + * authentication step. + */ + if (auth_successful(s->authpolicy, s->username, s->this_method)) { + /* + * ... and it was the last one, so we're completely done. + */ + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_SUCCESS); + pq_push(s->ppl.out_pq, pktout); + break; + } else { + /* + * ... but another is required, so fall through to + * generation of USERAUTH_FAILURE, having first refreshed + * the bit mask of available methods. + */ + s->methods = auth_methods(s->authpolicy); + } + s->partial_success = TRUE; + + failure: + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_FAILURE); + { + strbuf *list = strbuf_new(); + if (s->methods & AUTHMETHOD_NONE) + add_to_commasep(list, "none"); + if (s->methods & AUTHMETHOD_PASSWORD) + add_to_commasep(list, "password"); + if (s->methods & AUTHMETHOD_PUBLICKEY) + add_to_commasep(list, "publickey"); + put_stringsb(pktout, list); + } + put_bool(pktout, s->partial_success); + pq_push(s->ppl.out_pq, pktout); + } + + /* + * Finally, hand over to our successor layer, and return + * immediately without reaching the crFinishV: ssh_ppl_replace + * will have freed us, so crFinishV's zeroing-out of crState would + * be a use-after-free bug. + */ + { + PacketProtocolLayer *successor = s->successor_layer; + s->successor_layer = NULL; /* avoid freeing it ourself */ + ssh_ppl_replace(&s->ppl, successor); + return; /* we've just freed s, so avoid even touching s->crState */ + } + + crFinishV; +} diff --git a/ssh2userauth.c b/ssh2userauth.c index 1d526d02..7cbb425e 100644 --- a/ssh2userauth.c +++ b/ssh2userauth.c @@ -1,5 +1,6 @@ /* - * Packet protocol layer for the SSH-2 userauth protocol (RFC 4252). + * Packet protocol layer for the client side of the SSH-2 userauth + * protocol (RFC 4252). */ #include diff --git a/sshbpp.h b/sshbpp.h index 264c37c4..04c1f5ed 100644 --- a/sshbpp.h +++ b/sshbpp.h @@ -56,6 +56,10 @@ BinaryPacketProtocol *ssh1_bpp_new(LogContext *logctx); void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp, const struct ssh1_cipheralg *cipher, const void *session_key); +/* This is only called from outside the BPP in server mode; in client + * mode the BPP detects compression start time automatically by + * snooping message types */ +void ssh1_bpp_start_compression(BinaryPacketProtocol *bpp); /* Helper routine which does common BPP initialisation, e.g. setting * up in_pq and out_pq, and initialising input_consumer. */ @@ -99,7 +103,7 @@ struct DataTransferStats { ((stats)->direction.remaining -= (size), FALSE)) BinaryPacketProtocol *ssh2_bpp_new( - LogContext *logctx, struct DataTransferStats *stats); + LogContext *logctx, struct DataTransferStats *stats, int is_server); void ssh2_bpp_new_outgoing_crypto( BinaryPacketProtocol *bpp, const struct ssh2_cipheralg *cipher, const void *ckey, const void *iv, @@ -137,7 +141,7 @@ struct ssh_version_receiver { BinaryPacketProtocol *ssh_verstring_new( Conf *conf, LogContext *logctx, int bare_connection_mode, const char *protoversion, struct ssh_version_receiver *rcv, - const char *impl_name); + int server_mode, const char *impl_name); const char *ssh_verstring_get_remote(BinaryPacketProtocol *); const char *ssh_verstring_get_local(BinaryPacketProtocol *); int ssh_verstring_get_bugs(BinaryPacketProtocol *); diff --git a/sshppl.h b/sshppl.h index fa50d639..a44ba02c 100644 --- a/sshppl.h +++ b/sshppl.h @@ -96,8 +96,8 @@ PacketProtocolLayer *ssh2_transport_new( Conf *conf, const char *host, int port, const char *fullhostname, const char *client_greeting, const char *server_greeting, struct ssh_connection_shared_gss_state *shgss, - struct DataTransferStats *stats, - PacketProtocolLayer *higher_layer); + struct DataTransferStats *stats, PacketProtocolLayer *higher_layer, + int is_server); PacketProtocolLayer *ssh2_userauth_new( PacketProtocolLayer *successor_layer, const char *hostname, const char *fullhostname, @@ -135,7 +135,8 @@ ptrlen ssh2_transport_get_session_id(PacketProtocolLayer *ssh2_transport_ptr); void ssh2_transport_notify_auth_done(PacketProtocolLayer *ssh2_transport_ptr); /* Methods for ssh1login to pass protocol flags to ssh1connection */ -void ssh1_connection_set_local_protoflags(PacketProtocolLayer *ppl, int flags); +void ssh1_connection_set_protoflags( + PacketProtocolLayer *ppl, int local, int remote); /* Shared get_specials method between the two ssh1 layers */ int ssh1_common_get_specials(PacketProtocolLayer *, add_special_fn_t, void *); @@ -146,4 +147,8 @@ void ssh1_compute_session_id( unsigned char *session_id, const unsigned char *cookie, struct RSAKey *hostkey, struct RSAKey *servkey); +/* Method used by the SSH server */ +void ssh2_transport_provide_hostkeys(PacketProtocolLayer *ssh2_transport_ptr, + ssh_key *const *hostkeys, int nhostkeys); + #endif /* PUTTY_SSHPPL_H */ diff --git a/sshserver.c b/sshserver.c new file mode 100644 index 00000000..eaa22ca1 --- /dev/null +++ b/sshserver.c @@ -0,0 +1,464 @@ +/* + * Top-level code for SSH server implementation. + */ + +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshserver.h" +#ifndef NO_GSSAPI +#include "sshgssc.h" +#include "sshgss.h" +#endif + +struct Ssh { int dummy; }; + +typedef struct server server; +struct server { + bufchain in_raw, out_raw; + IdempotentCallback ic_out_raw; + + bufchain dummy_user_input; /* we never put anything on this */ + + PacketLogSettings pls; + LogContext *logctx; + struct DataTransferStats stats; + + int remote_bugs; + + Socket *socket; + Plug plug; + int conn_throttle_count; + int frozen; + + Conf *conf; + ssh_key *const *hostkeys; + int nhostkeys; + struct RSAKey *hostkey1; + AuthPolicy *authpolicy; + + Seat seat; + Ssh ssh; + struct ssh_version_receiver version_receiver; + + BinaryPacketProtocol *bpp; + PacketProtocolLayer *base_layer; + ConnectionLayer *cl; + + struct ssh_connection_shared_gss_state gss_state; +}; + +static void ssh_server_free_callback(void *vsrv); +static void server_got_ssh_version(struct ssh_version_receiver *rcv, + int major_version); +static void server_connect_bpp(server *srv); +static void server_bpp_output_raw_data_callback(void *vctx); + +void share_activate(ssh_sharing_state *sharestate, + const char *server_verstring) {} +void ssh_connshare_provide_connlayer(ssh_sharing_state *sharestate, + ConnectionLayer *cl) {} +int share_ndownstreams(ssh_sharing_state *sharestate) { return 0; } +void share_got_pkt_from_server(ssh_sharing_connstate *cs, int type, + const void *vpkt, int pktlen) {} +void share_setup_x11_channel(ssh_sharing_connstate *cs, share_channel *chan, + unsigned upstream_id, unsigned server_id, + unsigned server_currwin, unsigned server_maxpkt, + unsigned client_adjusted_window, + const char *peer_addr, int peer_port, int endian, + int protomajor, int protominor, + const void *initial_data, int initial_len) {} +Channel *agentf_new(SshChannel *c) { return NULL; } +int agent_exists(void) { return FALSE; } +void ssh_got_exitcode(Ssh *ssh, int exitcode) {} + +mainchan *mainchan_new( + PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf, + int term_width, int term_height, int is_simple, SshChannel **sc_out) +{ return NULL; } +void mainchan_get_specials( + mainchan *mc, add_special_fn_t add_special, void *ctx) {} +void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg) {} +void mainchan_terminal_size(mainchan *mc, int width, int height) {} + +/* Seat functions to ensure we don't get choosy about crypto - as the + * server, it's not up to us to give user warnings */ +static int server_confirm_weak_crypto_primitive( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx) { return 1; } +static int server_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx) { return 1; } + +static const SeatVtable server_seat_vt = { + nullseat_output, + nullseat_eof, + nullseat_get_userpass_input, + nullseat_notify_remote_exit, + nullseat_connection_fatal, + nullseat_update_specials_menu, + nullseat_get_ttymode, + nullseat_set_busy_status, + nullseat_verify_ssh_host_key, + server_confirm_weak_crypto_primitive, + server_confirm_weak_cached_hostkey, + nullseat_is_never_utf8, + nullseat_echoedit_update, + nullseat_get_x_display, + nullseat_get_windowid, + nullseat_get_window_pixel_size, +}; + +static void server_socket_log(Plug *plug, int type, SockAddr *addr, int port, + const char *error_msg, int error_code) +{ + /* server *srv = container_of(plug, server, plug); */ + /* FIXME */ +} + +static void server_closing(Plug *plug, const char *error_msg, int error_code, + int calling_back) +{ + server *srv = container_of(plug, server, plug); + if (error_msg) { + ssh_remote_error(&srv->ssh, "Network error: %s", error_msg); + } else if (srv->bpp) { + srv->bpp->input_eof = TRUE; + queue_idempotent_callback(&srv->bpp->ic_in_raw); + } +} + +static void server_receive(Plug *plug, int urgent, char *data, int len) +{ + server *srv = container_of(plug, server, plug); + + /* Log raw data, if we're in that mode. */ + if (srv->logctx) + log_packet(srv->logctx, PKT_INCOMING, -1, NULL, data, len, + 0, NULL, NULL, 0, NULL); + + bufchain_add(&srv->in_raw, data, len); + if (!srv->frozen && srv->bpp) + queue_idempotent_callback(&srv->bpp->ic_in_raw); +} + +static void server_sent(Plug *plug, int bufsize) +{ +#ifdef FIXME + server *srv = container_of(plug, server, plug); + + /* + * If the send backlog on the SSH socket itself clears, we should + * unthrottle the whole world if it was throttled. Also trigger an + * extra call to the consumer of the BPP's output, to try to send + * some more data off its bufchain. + */ + if (bufsize < SSH_MAX_BACKLOG) { + srv_throttle_all(srv, 0, bufsize); + queue_idempotent_callback(&srv->ic_out_raw); + } +#endif +} + +LogContext *ssh_get_logctx(Ssh *ssh) +{ + server *srv = container_of(ssh, server, ssh); + return srv->logctx; +} + +void ssh_throttle_conn(Ssh *ssh, int adjust) +{ + server *srv = container_of(ssh, server, ssh); + int old_count = srv->conn_throttle_count; + int frozen; + + srv->conn_throttle_count += adjust; + assert(srv->conn_throttle_count >= 0); + + if (srv->conn_throttle_count && !old_count) { + frozen = TRUE; + } else if (!srv->conn_throttle_count && old_count) { + frozen = FALSE; + } else { + return; /* don't change current frozen state */ + } + + srv->frozen = frozen; + + if (srv->socket) { + sk_set_frozen(srv->socket, frozen); + + /* + * Now process any SSH connection data that was stashed in our + * queue while we were frozen. + */ + queue_idempotent_callback(&srv->bpp->ic_in_raw); + } +} + +static const PlugVtable ssh_server_plugvt = { + server_socket_log, + server_closing, + server_receive, + server_sent, + NULL +}; + +Plug *ssh_server_plug( + Conf *conf, ssh_key *const *hostkeys, int nhostkeys, + struct RSAKey *hostkey1, AuthPolicy *authpolicy, LogPolicy *logpolicy) +{ + server *srv = snew(server); + + memset(srv, 0, sizeof(server)); + + srv->plug.vt = &ssh_server_plugvt; + srv->conf = conf_copy(conf); + srv->logctx = log_init(logpolicy, conf); + conf_set_int(srv->conf, CONF_ssh_no_shell, TRUE); + srv->nhostkeys = nhostkeys; + srv->hostkeys = hostkeys; + srv->hostkey1 = hostkey1; + srv->authpolicy = authpolicy; + + srv->seat.vt = &server_seat_vt; + + bufchain_init(&srv->in_raw); + bufchain_init(&srv->out_raw); + bufchain_init(&srv->dummy_user_input); + + /* FIXME: replace with sensible */ + srv->gss_state.libs = snew(struct ssh_gss_liblist); + srv->gss_state.libs->nlibraries = 0; + + return &srv->plug; +} + +void ssh_server_start(Plug *plug, Socket *socket) +{ + server *srv = container_of(plug, server, plug); + const char *our_protoversion; + + 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 */ + } else { + assert(srv->nhostkeys); + our_protoversion = "2.0"; /* SSH-2 only */ + } + + srv->socket = socket; + + srv->ic_out_raw.fn = server_bpp_output_raw_data_callback; + 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 */, + our_protoversion, &srv->version_receiver, + TRUE, "Uppity"); + server_connect_bpp(srv); + queue_idempotent_callback(&srv->bpp->ic_in_raw); +} + +static void ssh_server_free_callback(void *vsrv) +{ + server *srv = (server *)vsrv; + + bufchain_clear(&srv->in_raw); + bufchain_clear(&srv->out_raw); + bufchain_clear(&srv->dummy_user_input); + + sk_close(srv->socket); + + if (srv->bpp) + ssh_bpp_free(srv->bpp); + + delete_callbacks_for_context(srv); + + conf_free(srv->conf); + log_free(srv->logctx); + + sfree(srv->gss_state.libs); /* FIXME: replace with sensible */ + + sfree(srv); + + server_instance_terminated(); +} + +static void server_connect_bpp(server *srv) +{ + srv->bpp->ssh = &srv->ssh; + srv->bpp->in_raw = &srv->in_raw; + srv->bpp->out_raw = &srv->out_raw; + srv->bpp->out_raw->ic = &srv->ic_out_raw; + srv->bpp->pls = &srv->pls; + srv->bpp->logctx = srv->logctx; + srv->bpp->remote_bugs = srv->remote_bugs; + /* Servers don't really have a notion of 'unexpected' connection + * closure. The client is free to close if it likes. */ + srv->bpp->expect_close = TRUE; +} + +static void server_connect_ppl(server *srv, PacketProtocolLayer *ppl) +{ + ppl->bpp = srv->bpp; + ppl->user_input = &srv->dummy_user_input; + ppl->logctx = srv->logctx; + ppl->ssh = &srv->ssh; + ppl->seat = &srv->seat; + ppl->remote_bugs = srv->remote_bugs; +} + +static void server_bpp_output_raw_data_callback(void *vctx) +{ + server *srv = (server *)vctx; + + if (!srv->socket) + return; + + while (bufchain_size(&srv->out_raw) > 0) { + void *data; + int len, backlog; + + bufchain_prefix(&srv->out_raw, &data, &len); + + if (srv->logctx) + log_packet(srv->logctx, PKT_OUTGOING, -1, NULL, data, len, + 0, NULL, NULL, 0, NULL); + backlog = sk_write(srv->socket, data, len); + + bufchain_consume(&srv->out_raw, len); + + if (backlog > SSH_MAX_BACKLOG) { +#ifdef FIXME + ssh_throttle_all(ssh, 1, backlog); +#endif + return; + } + } + +#ifdef FIXME + if (ssh->pending_close) { + sk_close(ssh->s); + ssh->s = NULL; + } +#endif +} + +#define LOG_FORMATTED_MSG(logctx, fmt) do \ + { \ + va_list ap; \ + va_start(ap, fmt); \ + logeventvf(logctx, fmt, ap); \ + va_end(ap); \ + } while (0) + +void ssh_remote_error(Ssh *ssh, const char *fmt, ...) +{ + server *srv = container_of(ssh, server, ssh); + LOG_FORMATTED_MSG(srv->logctx, fmt); + queue_toplevel_callback(ssh_server_free_callback, srv); +} + +void ssh_remote_eof(Ssh *ssh, const char *fmt, ...) +{ + server *srv = container_of(ssh, server, ssh); + LOG_FORMATTED_MSG(srv->logctx, fmt); + queue_toplevel_callback(ssh_server_free_callback, srv); +} + +void ssh_proto_error(Ssh *ssh, const char *fmt, ...) +{ + server *srv = container_of(ssh, server, ssh); + LOG_FORMATTED_MSG(srv->logctx, fmt); + queue_toplevel_callback(ssh_server_free_callback, srv); +} + +void ssh_sw_abort(Ssh *ssh, const char *fmt, ...) +{ + server *srv = container_of(ssh, server, ssh); + LOG_FORMATTED_MSG(srv->logctx, fmt); + queue_toplevel_callback(ssh_server_free_callback, srv); +} + +void ssh_user_close(Ssh *ssh, const char *fmt, ...) +{ + server *srv = container_of(ssh, server, ssh); + LOG_FORMATTED_MSG(srv->logctx, fmt); + queue_toplevel_callback(ssh_server_free_callback, srv); +} + +static void server_got_ssh_version(struct ssh_version_receiver *rcv, + int major_version) +{ + server *srv = container_of(rcv, server, version_receiver); + BinaryPacketProtocol *old_bpp; + PacketProtocolLayer *connection_layer; + + old_bpp = srv->bpp; + srv->remote_bugs = ssh_verstring_get_bugs(old_bpp); + + if (major_version == 2) { + PacketProtocolLayer *userauth_layer, *transport_child_layer; + + srv->bpp = ssh2_bpp_new(srv->logctx, &srv->stats, TRUE); + server_connect_bpp(srv); + + connection_layer = ssh2_connection_new( + &srv->ssh, NULL, FALSE, srv->conf, + ssh_verstring_get_local(old_bpp), &srv->cl); + server_connect_ppl(srv, connection_layer); + + if (conf_get_int(srv->conf, CONF_ssh_no_userauth)) { + userauth_layer = NULL; + transport_child_layer = connection_layer; + } else { + userauth_layer = ssh2_userauth_server_new( + connection_layer, srv->authpolicy); + server_connect_ppl(srv, userauth_layer); + transport_child_layer = userauth_layer; + } + + srv->base_layer = ssh2_transport_new( + srv->conf, NULL, 0, NULL, + ssh_verstring_get_remote(old_bpp), + ssh_verstring_get_local(old_bpp), + &srv->gss_state, &srv->stats, transport_child_layer, TRUE); + ssh2_transport_provide_hostkeys( + srv->base_layer, srv->hostkeys, srv->nhostkeys); + if (userauth_layer) + ssh2_userauth_server_set_transport_layer( + userauth_layer, srv->base_layer); + server_connect_ppl(srv, srv->base_layer); + + } else { + srv->bpp = ssh1_bpp_new(srv->logctx); + server_connect_bpp(srv); + + connection_layer = ssh1_connection_new(&srv->ssh, srv->conf, &srv->cl); + server_connect_ppl(srv, connection_layer); + + srv->base_layer = ssh1_login_server_new( + connection_layer, srv->hostkey1, srv->authpolicy); + server_connect_ppl(srv, srv->base_layer); + } + + /* Connect the base layer - whichever it is - to the BPP, and set + * up its selfptr. */ + srv->base_layer->selfptr = &srv->base_layer; + ssh_ppl_setup_queues(srv->base_layer, &srv->bpp->in_pq, &srv->bpp->out_pq); + +#ifdef FIXME // we probably will want one of these, in the end + srv->pinger = pinger_new(srv->conf, &srv->backend); +#endif + + queue_idempotent_callback(&srv->bpp->ic_in_raw); + ssh_ppl_process_queue(srv->base_layer); + + ssh_bpp_free(old_bpp); +} diff --git a/sshserver.h b/sshserver.h new file mode 100644 index 00000000..e4d76df8 --- /dev/null +++ b/sshserver.h @@ -0,0 +1,65 @@ +typedef struct AuthPolicy AuthPolicy; + +Plug *ssh_server_plug( + Conf *conf, ssh_key *const *hostkeys, int nhostkeys, + struct RSAKey *hostkey1, AuthPolicy *authpolicy, LogPolicy *logpolicy); +void ssh_server_start(Plug *plug, Socket *socket); + +void server_instance_terminated(void); +void platform_logevent(const char *msg); + +#define AUTHMETHODS(X) \ + X(NONE) \ + X(PASSWORD) \ + X(PUBLICKEY) \ + /* end of list */ + +#define AUTHMETHOD_BIT_INDEX(name) AUTHMETHOD_BIT_INDEX_##name, +enum { AUTHMETHODS(AUTHMETHOD_BIT_INDEX) AUTHMETHOD_BIT_INDEX_dummy }; +#define AUTHMETHOD_BIT_VALUE(name) \ + AUTHMETHOD_##name = 1 << AUTHMETHOD_BIT_INDEX_##name, +enum { AUTHMETHODS(AUTHMETHOD_BIT_VALUE) AUTHMETHOD_BIT_VALUE_dummy }; + +unsigned auth_methods(AuthPolicy *); +int auth_none(AuthPolicy *, ptrlen username); +int auth_password(AuthPolicy *, ptrlen username, ptrlen password); +int auth_publickey(AuthPolicy *, ptrlen username, ptrlen public_blob); +/* auth_publickey_ssh1 must return the whole public key given the modulus, + * because the SSH-1 client never transmits the exponent over the wire. + * The key remains owned by the AuthPolicy. */ +struct RSAKey *auth_publickey_ssh1( + AuthPolicy *ap, ptrlen username, Bignum rsa_modulus); +/* auth_successful returns FALSE if further authentication is needed */ +int auth_successful(AuthPolicy *, ptrlen username, unsigned method); + +PacketProtocolLayer *ssh2_userauth_server_new( + PacketProtocolLayer *successor_layer, AuthPolicy *authpolicy); +void ssh2_userauth_server_set_transport_layer( + PacketProtocolLayer *userauth, PacketProtocolLayer *transport); + +PacketProtocolLayer *ssh1_login_server_new( + PacketProtocolLayer *successor_layer, struct RSAKey *hostkey, + AuthPolicy *authpolicy); + +Channel *sesschan_new(SshChannel *c, LogContext *logctx); + +Backend *pty_backend_create( + Seat *seat, LogContext *logctx, Conf *conf, char **argv, const char *cmd, + struct ssh_ttymodes ttymodes, int pipes_instead_of_pty); +ptrlen pty_backend_exit_signame(Backend *be, char **aux_msg); + +/* + * Establish a listening X server. Return value is the _number_ of + * Sockets that it established pointing at the given Plug. (0 + * indicates complete failure.) The socket pointers themselves are + * written into sockets[], up to a possible total of MAX_X11_SOCKETS. + * + * The supplied Conf has necessary environment variables written into + * it. (And is also used to open the port listeners, though that + * shouldn't affect anything.) + */ +#define MAX_X11_SOCKETS 2 +int platform_make_x11_server(Plug *plug, const char *progname, int mindisp, + const char *screen_number_suffix, + ptrlen authproto, ptrlen authdata, + Socket **sockets, Conf *conf); diff --git a/sshverstring.c b/sshverstring.c index f11c35ea..758c9e15 100644 --- a/sshverstring.c +++ b/sshverstring.c @@ -61,7 +61,7 @@ static int ssh_version_includes_v2(const char *ver); BinaryPacketProtocol *ssh_verstring_new( Conf *conf, LogContext *logctx, int bare_connection_mode, const char *protoversion, struct ssh_version_receiver *rcv, - const char *impl_name) + int server_mode, const char *impl_name) { struct ssh_verstring_state *s = snew(struct ssh_verstring_state); @@ -98,8 +98,10 @@ BinaryPacketProtocol *ssh_verstring_new( * We send our version string early if we can. But if it includes * SSH-1, we can't, because we have to take the other end into * account too (see below). + * + * In server mode, we do send early. */ - s->send_early = !ssh_version_includes_v1(protoversion); + s->send_early = server_mode || !ssh_version_includes_v1(protoversion); s->bpp.vt = &ssh_verstring_vtable; ssh_bpp_common_setup(&s->bpp); diff --git a/unix/uxserver.c b/unix/uxserver.c new file mode 100644 index 00000000..4c1ca982 --- /dev/null +++ b/unix/uxserver.c @@ -0,0 +1,529 @@ +/* + * SSH server for Unix: main program. + * + * ====================================================================== + * + * This server is NOT SECURE! + * + * DO NOT DEPLOY IT IN A HOSTILE-FACING ENVIRONMENT! + * + * Its purpose is to speak the server end of everything PuTTY speaks + * on the client side, so that I can test that I haven't broken PuTTY + * when I reorganise its code, even things like RSA key exchange or + * chained auth methods which it's hard to find a server that speaks + * at all. + * + * It has no interaction with the OS's authentication system: the + * authentications it will accept are configurable by command-line + * option, and once you authenticate, it will run the connection + * protocol - including all subprocesses and shells - under the same + * Unix user id you started it under. + * + * It really is only suitable for testing the actual SSH protocol. + * Don't use it for anything more serious! + * + * ====================================================================== + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef HAVE_NO_SYS_SELECT_H +#include +#endif + +#define PUTTY_DO_GLOBALS /* actually _define_ globals */ +#include "putty.h" +#include "ssh.h" +#include "sshserver.h" + +const char *const appname = "uppity"; + +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; +} + +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 */ +} + +/* + * Our selects are synchronous, so these functions are empty stubs. + */ +uxsel_id *uxsel_input_add(int fd, int rwx) { return NULL; } +void uxsel_input_remove(uxsel_id *id) { } + +void old_keyfile_warning(void) { } + +void timer_change_notify(unsigned long next) +{ +} + +char *platform_get_x_display(void) { return NULL; } + +static int 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. + */ + + fputs(msg, stderr); + fputc('\n', stderr); + fflush(stderr); +} + +static void server_eventlog(LogPolicy *lp, const char *event) +{ + if (verbose) + log_to_stderr(event); +} + +static void server_logging_error(LogPolicy *lp, const char *event) +{ + log_to_stderr(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, +}; +LogPolicy server_logpolicy[1] = {{ &server_logpolicy_vt }}; + +struct AuthPolicy_ssh1_pubkey { + struct RSAKey key; + struct AuthPolicy_ssh1_pubkey *next; +}; +struct AuthPolicy_ssh2_pubkey { + ptrlen public_blob; + struct AuthPolicy_ssh2_pubkey *next; +}; + +struct AuthPolicy { + struct AuthPolicy_ssh1_pubkey *ssh1keys; + struct AuthPolicy_ssh2_pubkey *ssh2keys; +}; +unsigned auth_methods(AuthPolicy *ap) +{ + return AUTHMETHOD_PUBLICKEY | AUTHMETHOD_PASSWORD; +} +int auth_none(AuthPolicy *ap, ptrlen username) +{ + return FALSE; +} +int auth_password(AuthPolicy *ap, ptrlen username, ptrlen password) +{ + return ptrlen_eq_string(password, "weasel"); +} +int auth_publickey(AuthPolicy *ap, ptrlen username, ptrlen public_blob) +{ + struct AuthPolicy_ssh2_pubkey *iter; + for (iter = ap->ssh2keys; iter; iter = iter->next) { + if (ptrlen_eq_ptrlen(public_blob, iter->public_blob)) + return TRUE; + } + return FALSE; +} +struct RSAKey *auth_publickey_ssh1( + AuthPolicy *ap, ptrlen username, Bignum rsa_modulus) +{ + struct AuthPolicy_ssh1_pubkey *iter; + for (iter = ap->ssh1keys; iter; iter = iter->next) { + if (!bignum_cmp(rsa_modulus, iter->key.modulus)) + return &iter->key; + } + return NULL; +} +int auth_successful(AuthPolicy *ap, ptrlen username, unsigned method) +{ + return TRUE; +} + +static void show_help_and_exit(void) +{ + printf("usage: uppity [options]\n"); + printf("options: --hostkey KEY SSH host key (need at least one)\n"); + printf(" --userkey KEY public key" + " acceptable for user authentication\n"); + printf("also: uppity --help show this text\n"); + printf(" uppity --version show version information\n"); + exit(0); +} + +static void show_version_and_exit(void) +{ + char *buildinfo_text = buildinfo("\n"); + printf("uppity: %s\n%s\n", ver, buildinfo_text); + sfree(buildinfo_text); + exit(0); +} + +const int buildinfo_gtk_relevant = FALSE; + +static int finished = FALSE; +void server_instance_terminated(void) +{ + /* FIXME: change policy here if we're running in a listening loop */ + finished = TRUE; +} + +static int 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, "uppity: option %s expects an argument\n", + expected); + exit(1); + } + } + return FALSE; +} + +int main(int argc, char **argv) +{ + int *fdlist; + int fd; + int i, fdcount, fdsize, fdstate; + unsigned long now; + + ssh_key **hostkeys = NULL; + int nhostkeys = 0, hostkeysize = 0; + struct RSAKey *hostkey1 = NULL; + + AuthPolicy ap; + + Conf *conf = conf_new(); + load_open_settings(NULL, conf); + + ap.ssh1keys = NULL; + ap.ssh2keys = NULL; + + while (--argc > 0) { + const char *arg = *++argv; + const char *val; + + if (!strcmp(arg, "--help")) { + show_help_and_exit(); + } else if (!strcmp(arg, "--version")) { + show_version_and_exit(); + } else if (!strcmp(arg, "--verbose") || !strcmp(arg, "-v")) { + verbose = TRUE; + } else if (longoptarg(arg, "--hostkey", &val, &argc, &argv)) { + Filename *keyfile; + int keytype; + const char *error; + + keyfile = filename_from_str(val); + keytype = key_type(keyfile); + + if (keytype == SSH_KEYTYPE_SSH2) { + struct ssh2_userkey *uk; + ssh_key *key; + uk = ssh2_load_userkey(keyfile, NULL, &error); + filename_free(keyfile); + if (!uk || !uk->key) { + fprintf(stderr, "uppity: unable to load host key '%s': " + "%s\n", val, error); + exit(1); + } + key = uk->key; + sfree(uk->comment); + sfree(uk); + + for (i = 0; i < nhostkeys; i++) + if (ssh_key_alg(hostkeys[i]) == ssh_key_alg(key)) { + fprintf(stderr, "uppity: host key '%s' duplicates key " + "type %s\n", val, ssh_key_alg(key)->ssh_id); + exit(1); + } + + if (nhostkeys >= hostkeysize) { + hostkeysize = nhostkeys * 5 / 4 + 16; + hostkeys = sresize(hostkeys, hostkeysize, ssh_key *); + } + hostkeys[nhostkeys++] = key; + } else if (keytype == SSH_KEYTYPE_SSH1) { + if (hostkey1) { + fprintf(stderr, "uppity: host key '%s' is a redundant " + "SSH-1 host key\n", val); + exit(1); + } + hostkey1 = snew(struct RSAKey); + if (!rsa_ssh1_loadkey(keyfile, hostkey1, NULL, &error)) { + fprintf(stderr, "uppity: unable to load host key '%s': " + "%s\n", val, error); + exit(1); + } + } else { + fprintf(stderr, "uppity: '%s' is not loadable as a " + "private key (%s)", val, key_type_to_str(keytype)); + exit(1); + } + } else if (longoptarg(arg, "--userkey", &val, &argc, &argv)) { + Filename *keyfile; + int keytype; + const char *error; + + keyfile = filename_from_str(val); + keytype = key_type(keyfile); + + if (keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 || + keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) { + strbuf *sb = strbuf_new(); + struct AuthPolicy_ssh2_pubkey *node; + void *blob; + + if (!ssh2_userkey_loadpub(keyfile, NULL, BinarySink_UPCAST(sb), + NULL, &error)) { + fprintf(stderr, "uppity: unable to load user key '%s': " + "%s\n", val, error); + exit(1); + } + + node = snew_plus(struct AuthPolicy_ssh2_pubkey, sb->len); + blob = snew_plus_get_aux(node); + memcpy(blob, sb->u, sb->len); + node->public_blob = make_ptrlen(blob, sb->len); + + node->next = ap.ssh2keys; + ap.ssh2keys = node; + + strbuf_free(sb); + } else if (keytype == SSH_KEYTYPE_SSH1_PUBLIC) { + strbuf *sb = strbuf_new(); + BinarySource src[1]; + struct AuthPolicy_ssh1_pubkey *node; + + if (!rsa_ssh1_loadpub(keyfile, BinarySink_UPCAST(sb), + NULL, &error)) { + fprintf(stderr, "uppity: unable to load user key '%s': " + "%s\n", val, error); + exit(1); + } + + node = snew(struct AuthPolicy_ssh1_pubkey); + BinarySource_BARE_INIT(src, sb->u, sb->len); + get_rsa_ssh1_pub(src, &node->key, RSA_SSH1_EXPONENT_FIRST); + + node->next = ap.ssh1keys; + ap.ssh1keys = node; + + strbuf_free(sb); + } else { + fprintf(stderr, "uppity: '%s' is not loadable as a public key " + "(%s)\n", val, key_type_to_str(keytype)); + exit(1); + } + } 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, "uppity: unrecognised option '%s'\n", arg); + exit(1); + } + } + + if (nhostkeys == 0 && !hostkey1) { + fprintf(stderr, "uppity: specify at least one host key\n"); + exit(1); + } + + fdlist = NULL; + fdcount = fdsize = 0; + + random_ref(); + + /* + * Block SIGPIPE, so that we'll get EPIPE individually on + * particular network connections that go wrong. + */ + putty_signal(SIGPIPE, SIG_IGN); + + sk_init(); + uxsel_init(); + + { + Plug *plug = ssh_server_plug( + conf, hostkeys, nhostkeys, hostkey1, &ap, server_logpolicy); + ssh_server_start(plug, make_fd_socket(0, 1, -1, plug)); + } + + now = GETTICKCOUNT(); + + while (!finished) { + fd_set rset, wset, xset; + int maxfd; + int rwx; + int ret; + unsigned long next; + + FD_ZERO(&rset); + FD_ZERO(&wset); + FD_ZERO(&xset); + maxfd = 0; + + /* Count the currently active fds. */ + i = 0; + for (fd = first_fd(&fdstate, &rwx); fd >= 0; + fd = next_fd(&fdstate, &rwx)) i++; + + /* Expand the fdlist buffer if necessary. */ + if (i > fdsize) { + fdsize = i + 16; + fdlist = sresize(fdlist, fdsize, int); + } + + /* + * Add all currently open fds to the select sets, and store + * them in fdlist as well. + */ + fdcount = 0; + for (fd = first_fd(&fdstate, &rwx); fd >= 0; + fd = next_fd(&fdstate, &rwx)) { + fdlist[fdcount++] = fd; + if (rwx & 1) + FD_SET_MAX(fd, maxfd, rset); + if (rwx & 2) + FD_SET_MAX(fd, maxfd, wset); + if (rwx & 4) + FD_SET_MAX(fd, maxfd, xset); + } + + if (toplevel_callback_pending()) { + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + ret = select(maxfd, &rset, &wset, &xset, &tv); + } else if (run_timers(now, &next)) { + do { + unsigned long then; + long ticks; + struct timeval tv; + + then = now; + now = GETTICKCOUNT(); + if (now - then > next - then) + ticks = 0; + else + ticks = next - now; + tv.tv_sec = ticks / 1000; + tv.tv_usec = ticks % 1000 * 1000; + ret = select(maxfd, &rset, &wset, &xset, &tv); + if (ret == 0) + now = next; + else + now = GETTICKCOUNT(); + } while (ret < 0 && errno == EINTR); + } else { + ret = select(maxfd, &rset, &wset, &xset, NULL); + } + + if (ret < 0 && errno == EINTR) + continue; + + if (ret < 0) { + perror("select"); + exit(1); + } + + for (i = 0; i < fdcount; i++) { + fd = fdlist[i]; + /* + * We must process exceptional notifications before + * ordinary readability ones, or we may go straight + * past the urgent marker. + */ + if (FD_ISSET(fd, &xset)) + select_result(fd, 4); + if (FD_ISSET(fd, &rset)) + select_result(fd, 1); + if (FD_ISSET(fd, &wset)) + select_result(fd, 2); + } + + run_toplevel_callbacks(); + } +}