diff --git a/Recipe b/Recipe index 87fc0b67..10366fe0 100644 --- a/Recipe +++ b/Recipe @@ -251,6 +251,7 @@ 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 diff --git a/defs.h b/defs.h index 61ada0b3..28020964 100644 --- a/defs.h +++ b/defs.h @@ -95,6 +95,7 @@ typedef struct ptrlen { typedef struct logblank_t logblank_t; typedef struct BinaryPacketProtocol BinaryPacketProtocol; +typedef struct PacketProtocolLayer PacketProtocolLayer; /* Do a compile-time type-check of 'to_check' (without evaluating it), * as a side effect of returning the value 'to_return'. Note that diff --git a/ssh.c b/ssh.c index 94dbb064..033c236a 100644 --- a/ssh.c +++ b/ssh.c @@ -17,6 +17,7 @@ #include "ssh.h" #include "sshcr.h" #include "sshbpp.h" +#include "sshppl.h" #include "sshchan.h" #ifndef NO_GSSAPI #include "sshgssc.h" @@ -28,353 +29,13 @@ #define GSS_CTXT_MAYFAIL (1<<3) /* Context may expire during handshake */ #endif -static const char *const ssh2_disconnect_reasons[] = { - NULL, - "host not allowed to connect", - "protocol error", - "key exchange failed", - "host authentication failed", - "MAC error", - "compression error", - "service not available", - "protocol version not supported", - "host key not verifiable", - "connection lost", - "by application", - "too many connections", - "auth cancelled by user", - "no more auth methods available", - "illegal user name", -}; - -#define DH_MIN_SIZE 1024 -#define DH_MAX_SIZE 8192 - -/* Safely convert rekey_time to unsigned long minutes */ -static unsigned long rekey_mins(int rekey_time, unsigned long def) -{ - if (rekey_time < 0 || rekey_time > MAX_TICK_MINS) - rekey_time = def; - return (unsigned long)rekey_time; -} - -/* Enumeration values for fields in SSH-1 packets */ -enum { - PKT_END, PKT_INT, PKT_CHAR, PKT_DATA, PKT_STR, PKT_BIGNUM, -}; - -static void ssh2_pkt_send(Ssh, struct PktOut *); -static void do_ssh1_login(void *vctx); -static void do_ssh2_userauth(void *vctx); -static void ssh2_connection_setup(Ssh ssh); -static void do_ssh2_connection(void *vctx); -static void ssh_channel_init(struct ssh_channel *c); -static struct ssh_channel *ssh_channel_msg(Ssh ssh, PktIn *pktin); -static void ssh_channel_got_eof(struct ssh_channel *c); -static void ssh2_channel_check_close(struct ssh_channel *c); -static void ssh_channel_close_local(struct ssh_channel *c, char const *reason); -static void ssh_channel_destroy(struct ssh_channel *c); -static void ssh_channel_unthrottle(struct ssh_channel *c, int bufsize); -static void ssh2_general_packet_processing(Ssh ssh, PktIn *pktin); -static void ssh1_login_input(Ssh ssh); -static void ssh2_userauth_input(Ssh ssh); -static void ssh2_connection_input(Ssh ssh); - -struct ssh_signkey_with_user_pref_id { - const ssh_keyalg *alg; - int id; -}; -const static struct ssh_signkey_with_user_pref_id hostkey_algs[] = { - { &ssh_ecdsa_ed25519, HK_ED25519 }, - { &ssh_ecdsa_nistp256, HK_ECDSA }, - { &ssh_ecdsa_nistp384, HK_ECDSA }, - { &ssh_ecdsa_nistp521, HK_ECDSA }, - { &ssh_dss, HK_DSA }, - { &ssh_rsa, HK_RSA }, -}; - -const static struct ssh2_macalg *const macs[] = { - &ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5 -}; -const static struct ssh2_macalg *const buggymacs[] = { - &ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5 -}; - -static ssh_compressor *ssh_comp_none_init(void) -{ - return NULL; -} -static void ssh_comp_none_cleanup(ssh_compressor *handle) -{ -} -static ssh_decompressor *ssh_decomp_none_init(void) -{ - return NULL; -} -static void ssh_decomp_none_cleanup(ssh_decompressor *handle) -{ -} -static void ssh_comp_none_block(ssh_compressor *handle, - unsigned char *block, int len, - unsigned char **outblock, int *outlen, - int minlen) -{ -} -static int ssh_decomp_none_block(ssh_decompressor *handle, - unsigned char *block, int len, - unsigned char **outblock, int *outlen) -{ - return 0; -} -const static struct ssh_compression_alg ssh_comp_none = { - "none", NULL, - ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block, - ssh_decomp_none_init, ssh_decomp_none_cleanup, ssh_decomp_none_block, - NULL -}; -const static struct ssh_compression_alg *const compressions[] = { - &ssh_zlib, &ssh_comp_none -}; - -typedef void (*handler_fn_t)(Ssh ssh, PktIn *pktin); -typedef void (*chandler_fn_t)(Ssh ssh, PktIn *pktin, void *ctx); -typedef void (*cchandler_fn_t)(struct ssh_channel *, PktIn *, void *); - -/* - * Each channel has a queue of outstanding CHANNEL_REQUESTS and their - * handlers. - */ -struct outstanding_channel_request { - cchandler_fn_t handler; - void *ctx; - struct outstanding_channel_request *next; -}; - -/* - * 2-3-4 tree storing channels. - */ -struct ssh_channel { - Ssh ssh; /* pointer back to main context */ - unsigned remoteid, localid; - int type; - /* True if we opened this channel but server hasn't confirmed. */ - int halfopen; - /* - * In SSH-1, this value contains four bits: - * - * 1 We have sent SSH1_MSG_CHANNEL_CLOSE. - * 2 We have sent SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION. - * 4 We have received SSH1_MSG_CHANNEL_CLOSE. - * 8 We have received SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION. - * - * A channel is completely finished with when all four bits are set. - * - * In SSH-2, the four bits mean: - * - * 1 We have sent SSH2_MSG_CHANNEL_EOF. - * 2 We have sent SSH2_MSG_CHANNEL_CLOSE. - * 4 We have received SSH2_MSG_CHANNEL_EOF. - * 8 We have received SSH2_MSG_CHANNEL_CLOSE. - * - * A channel is completely finished with when we have both sent - * and received CLOSE. - * - * The symbolic constants below use the SSH-2 terminology, which - * is a bit confusing in SSH-1, but we have to use _something_. - */ -#define CLOSES_SENT_EOF 1 -#define CLOSES_SENT_CLOSE 2 -#define CLOSES_RCVD_EOF 4 -#define CLOSES_RCVD_CLOSE 8 - int closes; - - /* - * This flag indicates that an EOF is pending on the outgoing side - * of the channel: that is, wherever we're getting the data for - * this channel has sent us some data followed by EOF. We can't - * actually send the EOF until we've finished sending the data, so - * we set this flag instead to remind us to do so once our buffer - * is clear. - */ - int pending_eof; - - /* - * True if this channel is causing the underlying connection to be - * throttled. - */ - int throttling_conn; - - /* - * True if we currently have backed-up data on the direction of - * this channel pointing out of the SSH connection, and therefore - * would prefer the 'Channel' implementation not to read further - * local input if possible. - */ - int throttled_by_backlog; - - union { - struct ssh2_data_channel { - bufchain outbuffer; - unsigned remwindow, remmaxpkt; - /* locwindow is signed so we can cope with excess data. */ - int locwindow, locmaxwin; - /* - * remlocwin is the amount of local window that we think - * the remote end had available to it after it sent the - * last data packet or window adjust ack. - */ - int remlocwin; - /* - * These store the list of channel requests that haven't - * been acked. - */ - struct outstanding_channel_request *chanreq_head, *chanreq_tail; - enum { THROTTLED, UNTHROTTLING, UNTHROTTLED } throttle_state; - } v2; - } v; - - ssh_sharing_connstate *sharectx; /* sharing context, if this is a - * downstream channel */ - Channel *chan; /* handle the client side of this channel, if not */ - SshChannel sc; /* entry point for chan to talk back to */ -}; - -static int sshchannel_write(SshChannel *c, const void *buf, int len); -static void sshchannel_write_eof(SshChannel *c); -static void sshchannel_unclean_close(SshChannel *c, const char *err); -static void sshchannel_unthrottle(SshChannel *c, int bufsize); -static Conf *sshchannel_get_conf(SshChannel *c); -static void sshchannel_window_override_removed(SshChannel *c); -static void sshchannel_x11_sharing_handover( - SshChannel *c, ssh_sharing_connstate *share_cs, share_channel *share_chan, - const char *peer_addr, int peer_port, int endian, - int protomajor, int protominor, const void *initial_data, int initial_len); - -const struct SshChannelVtable sshchannel_vtable = { - sshchannel_write, - sshchannel_write_eof, - sshchannel_unclean_close, - sshchannel_unthrottle, - sshchannel_get_conf, - sshchannel_window_override_removed, - sshchannel_x11_sharing_handover, -}; - -/* - * 2-3-4 tree storing remote->local port forwardings. SSH-1 and SSH-2 - * use this structure in different ways, reflecting SSH-2's - * altogether saner approach to port forwarding. - * - * In SSH-1, you arrange a remote forwarding by sending the server - * the remote port number, and the local destination host:port. - * When a connection comes in, the server sends you back that - * host:port pair, and you connect to it. This is a ready-made - * security hole if you're not on the ball: a malicious server - * could send you back _any_ host:port pair, so if you trustingly - * connect to the address it gives you then you've just opened the - * entire inside of your corporate network just by connecting - * through it to a dodgy SSH server. Hence, we must store a list of - * host:port pairs we _are_ trying to forward to, and reject a - * connection request from the server if it's not in the list. - * - * In SSH-2, each side of the connection minds its own business and - * doesn't send unnecessary information to the other. You arrange a - * remote forwarding by sending the server just the remote port - * number. When a connection comes in, the server tells you which - * of its ports was connected to; and _you_ have to remember what - * local host:port pair went with that port number. - * - * Hence, in SSH-1 this structure is indexed by destination - * host:port pair, whereas in SSH-2 it is indexed by source port. - */ -struct ssh_portfwd; /* forward declaration */ - -struct ssh_rportfwd { - unsigned sport, dport; - char *shost, *dhost; - int addressfamily; - char *log_description; /* name of remote listening port, for logging */ - ssh_sharing_connstate *share_ctx; - PortFwdRecord *pfr; -}; - -static void free_rportfwd(struct ssh_rportfwd *pf) -{ - if (pf) { - sfree(pf->log_description); - sfree(pf->shost); - sfree(pf->dhost); - sfree(pf); - } -} - -static void ssh1_protocol_setup(Ssh ssh); -static void ssh2_protocol_setup(Ssh ssh); -static void ssh2_bare_connection_protocol_setup(Ssh ssh); -static void ssh_size(Backend *be, int width, int height); -static void ssh_special(Backend *be, SessionSpecialCode, int arg); -static int ssh2_try_send(struct ssh_channel *c); -static int ssh_send_channel_data(struct ssh_channel *c, - const char *buf, int len); -static void ssh_throttle_all(Ssh ssh, int enable, int bufsize); -static void ssh2_set_window(struct ssh_channel *c, int newwin); -static int ssh_sendbuffer(Backend *be); -static int ssh_do_close(Ssh ssh, int notify_exit); -static void ssh2_timer(void *ctx, unsigned long now); -static int ssh2_timer_update(Ssh ssh, unsigned long rekey_time); -#ifndef NO_GSSAPI -static void ssh2_gss_update(Ssh ssh, int definitely_rekeying); -static PktOut *ssh2_gss_authpacket(Ssh ssh, Ssh_gss_ctx gss_ctx, - const char *authtype); -#endif -static void ssh2_msg_unexpected(Ssh ssh, PktIn *pktin); - -struct queued_handler; -struct queued_handler { - int msg1, msg2; - chandler_fn_t handler; - void *ctx; - struct queued_handler *next; -}; - -/* - * Enumeration of high-level classes of reason why we might need to do - * a repeat key exchange. The full detailed reason in human-readable - * form for the Event Log is kept in ssh->rekey_reason, but - * ssh->rekey_class is a variable with this enum type which is used to - * discriminate between classes of reason that the code needs to treat - * differently. - * - * RK_NONE == 0 is the value indicating that no rekey is currently - * needed at all. RK_INITIAL indicates that we haven't even done the - * _first_ key exchange yet. RK_NORMAL is the usual case. - * RK_GSS_UPDATE indicates that we're rekeying because we've just got - * new GSSAPI credentials (hence there's no point in doing a - * preliminary check for new GSS creds, because we already know the - * answer); RK_POST_USERAUTH indicates that _if_ we're going to need a - * post-userauth immediate rekey for any reason, this is the moment to - * do it. - * - * So RK_POST_USERAUTH only tells the transport layer to _consider_ - * rekeying, not to definitely do it. Also, that one enum value is - * special in that do_ssh2_transport fills in the reason text after it - * decides whether it needs a rekey at all. In the other cases, - * rekey_reason is set up at the same time as rekey_class. - */ -enum RekeyClass { - RK_NONE = 0, - RK_INITIAL, - RK_NORMAL, - RK_POST_USERAUTH, - RK_GSS_UPDATE -}; - struct ssh_tag { - char *v_c, *v_s; - struct ssh_version_receiver version_receiver; - ssh_hash *exhash; - Socket s; + Frontend *frontend; + Conf *conf; + + struct ssh_version_receiver version_receiver; + int remote_bugs; const Plug_vtable *plugvt; Backend backend; @@ -382,510 +43,99 @@ struct ssh_tag { Ldisc *ldisc; LogContext *logctx; - unsigned char session_key[32]; - int v1_remote_protoflags; - int v1_local_protoflags; - int agentfwd_enabled; - int X11_fwd_enabled; - int remote_bugs; - const struct ssh_kex *kex; - const ssh_keyalg *hostkey_alg; - char *hostkey_str; /* string representation, for easy checking in rekeys */ - unsigned char v2_session_id[SSH2_KEX_MAX_HASH_LEN]; - int v2_session_id_len; - int v2_cbc_ignore_workaround; - int v2_out_cipherblksize; - struct dh_ctx *dh_ctx; + /* The last list returned from get_specials. */ + SessionSpecial *specials; int bare_connection; - int attempting_connshare; ssh_sharing_state *connshare; + int attempting_connshare; + + struct ssh_connection_shared_gss_state gss_state; char *savedhost; int savedport; - int send_ok; - int echoing, editing; + char *fullhostname; - int session_started; - Frontend *frontend; - - int ospeed, ispeed; /* temporaries */ - int term_width, term_height; - - tree234 *channels; /* indexed by local id */ - struct ssh_channel *mainchan; /* primary session channel */ - int ncmode; /* is primary channel direct-tcpip? */ - int exitcode; - int close_expected; - int clean_exit; - int disconnect_message_seen; - - tree234 *rportfwds; - PortFwdManager *portfwdmgr; - - ConnectionLayer cl; - - enum { - SSH_STATE_PREPACKET, - SSH_STATE_BEFORE_SIZE, - SSH_STATE_INTERMED, - SSH_STATE_SESSION, - SSH_STATE_CLOSED - } state; - - int size_needed, eof_needed; - int sent_console_eof; - int got_pty; /* affects EOF behaviour on main channel */ - - PktOutQueue outq; - int queueing; - - /* - * Gross hack: pscp will try to start SFTP but fall back to - * scp1 if that fails. This variable is the means by which - * scp.c can reach into the SSH code and find out which one it - * got. - */ int fallback_cmd; - - bufchain banner; /* accumulates banners during do_ssh2_userauth */ - - struct X11Display *x11disp; - struct X11FakeAuth *x11auth; - tree234 *x11authtree; + int exitcode; int version; int conn_throttle_count; int overall_bufsize; int throttled_all; - int v1_stdout_throttling; - - int do_ssh1_connection_crstate; - - void *do_ssh_init_state; - void *do_ssh1_login_state; - void *do_ssh2_transport_state; - void *do_ssh2_userauth_state; - void *do_ssh2_connection_state; - void *do_ssh_connection_init_state; - - bufchain incoming_data; - struct IdempotentCallback incoming_data_consumer; - int incoming_data_seen_eof; - char *incoming_data_eof_message; - - struct IdempotentCallback incoming_pkt_consumer; - - PktInQueue pq_ssh1_login; - struct IdempotentCallback ssh1_login_icb; - - PktInQueue pq_ssh1_connection; - struct IdempotentCallback ssh1_connection_icb; - - PktInQueue pq_ssh2_transport; - struct IdempotentCallback ssh2_transport_icb; - - PktInQueue pq_ssh2_userauth; - struct IdempotentCallback ssh2_userauth_icb; - - PktInQueue pq_ssh2_connection; - struct IdempotentCallback ssh2_connection_icb; - - bufchain user_input; - struct IdempotentCallback user_input_consumer; - - bufchain outgoing_data; - struct IdempotentCallback outgoing_data_sender; - - const char *rekey_reason; - enum RekeyClass rekey_class; - - PacketLogSettings pls; - BinaryPacketProtocol *bpp; - - void (*general_packet_processing)(Ssh ssh, PktIn *pkt); - void (*current_user_input_fn) (Ssh ssh); - - /* - * We maintain our own copy of a Conf structure here. That way, - * when we're passed a new one for reconfiguration, we can check - * the differences and potentially reconfigure port forwardings - * etc in mid-session. - */ - Conf *conf; - - /* - * Dynamically allocated username string created during SSH - * login. Stored in here rather than in the coroutine state so - * that it'll be reliably freed if we shut down the SSH session - * at some unexpected moment. - */ - char *username; - - /* - * Used to transfer data back from async callbacks. - */ - void *agent_response; - int agent_response_len; - int user_response; - - /* - * The SSH connection can be set as `frozen', meaning we are - * not currently accepting incoming data from the network. - */ int frozen; - /* - * Dispatch table for packet types that we may have to deal - * with at any time. - */ - handler_fn_t packet_dispatch[SSH_MAX_MSG]; + /* in case we find these out before we have a ConnectionLayer to tell */ + int term_width, term_height; - /* - * Queues of one-off handler functions for success/failure - * indications from a request. - */ - struct queued_handler *qhead, *qtail; - handler_fn_t q_saved_handler1, q_saved_handler2; + bufchain in_raw, out_raw, user_input; + int send_outgoing_eof; + IdempotentCallback ic_out_raw; - /* - * This module deals with sending keepalives. - */ - Pinger pinger; - - /* - * Track incoming and outgoing data sizes and time, for - * size-based rekeys. - */ - unsigned long max_data_size; + PacketLogSettings pls; struct DataTransferStats stats; - int kex_in_progress; - unsigned long next_rekey, last_rekey; - const char *deferred_rekey_reason; + + BinaryPacketProtocol *bpp; /* - * Fully qualified host name, which we need if doing GSSAPI. + * base_layer identifies the bottommost packet protocol layer, the + * one connected directly to the BPP's packet queues. Any + * operation that needs to talk to all layers (e.g. free, or + * get_specials) will do it by talking to this, which will + * recursively propagate it if necessary. */ - char *fullhostname; - -#ifndef NO_GSSAPI - /* - * GSSAPI libraries for this session. We need them at key exchange - * and userauth time. - * - * And the gss_ctx we setup at initial key exchange will be used - * during gssapi-keyex userauth time as well. - */ - struct ssh_gss_liblist *gsslibs; - struct ssh_gss_library *gsslib; - int gss_status; - time_t gss_cred_expiry; /* Re-delegate if newer */ - unsigned long gss_ctxt_lifetime; /* Re-delegate when short */ - Ssh_gss_name gss_srv_name; /* Cached for KEXGSS */ - Ssh_gss_ctx gss_ctx; /* Saved for gssapi-keyex */ - tree234 *transient_hostkey_cache; -#endif - int gss_kex_used; /* outside ifdef; always FALSE if NO_GSSAPI */ + PacketProtocolLayer *base_layer; /* - * The last list returned from get_specials. + * The ConnectionLayer vtable from our connection layer. */ - SessionSpecial *specials; + ConnectionLayer *cl; /* - * List of host key algorithms for which we _don't_ have a stored - * host key. These are indices into the main hostkey_algs[] array + * session_started is FALSE until we initialise the main protocol + * layers. So it distinguishes between base_layer==NULL meaning + * that the SSH protocol hasn't been set up _yet_, and + * base_layer==NULL meaning the SSH protocol has run and finished. + * It's also used to mark the point where we stop counting proxy + * command diagnostics as pre-session-startup. */ - int uncert_hostkeys[lenof(hostkey_algs)]; - int n_uncert_hostkeys; + int session_started; - /* - * Flag indicating that the current rekey is intended to finish - * with a newly cross-certified host key. - */ - int cross_certifying; - - /* - * Any asynchronous query to our SSH agent that we might have in - * flight from the main authentication loop. (Queries from - * agent-forwarding channels live in their channel structure.) - */ - agent_pending_query *auth_agent_query; + Pinger pinger; int need_random_unref; }; -static const char *ssh_pkt_type(Ssh ssh, int type) -{ - if (ssh->version == 1) - return ssh1_pkt_type(type); - else - return ssh2_pkt_type(ssh->pls.kctx, ssh->pls.actx, type); -} +#define ssh_logevent(params) ( \ + logevent_and_free((ssh)->frontend, dupprintf params)) + +static void ssh_shutdown(Ssh ssh); +static void ssh_throttle_all(Ssh ssh, int enable, int bufsize); +static void ssh_bpp_output_raw_data_callback(void *vctx); Frontend *ssh_get_frontend(Ssh ssh) { return ssh->frontend; } -#define logevent(s) logevent(ssh->frontend, s) - -/* logevent, only printf-formatted. */ -static void logeventf(Ssh ssh, const char *fmt, ...) +static void ssh_connect_bpp(Ssh ssh) { - va_list ap; - char *buf; - - va_start(ap, fmt); - buf = dupvprintf(fmt, ap); - va_end(ap); - logevent(buf); - sfree(buf); + ssh->bpp->ssh = ssh; + ssh->bpp->in_raw = &ssh->in_raw; + ssh->bpp->out_raw = &ssh->out_raw; + ssh->bpp->out_raw->ic = &ssh->ic_out_raw; + ssh->bpp->pls = &ssh->pls; + ssh->bpp->logctx = ssh->logctx; + ssh->bpp->remote_bugs = ssh->remote_bugs; } -static void bomb_out(Ssh ssh, char *text) +static void ssh_connect_ppl(Ssh ssh, PacketProtocolLayer *ppl) { - ssh_do_close(ssh, FALSE); - logevent(text); - connection_fatal(ssh->frontend, "%s", text); - sfree(text); -} - -#define bombout(msg) bomb_out(ssh, dupprintf msg) - -static int ssh_channelcmp(void *av, void *bv) -{ - struct ssh_channel *a = (struct ssh_channel *) av; - struct ssh_channel *b = (struct ssh_channel *) bv; - if (a->localid < b->localid) - return -1; - if (a->localid > b->localid) - return +1; - return 0; -} -static int ssh_channelfind(void *av, void *bv) -{ - unsigned *a = (unsigned *) av; - struct ssh_channel *b = (struct ssh_channel *) bv; - if (*a < b->localid) - return -1; - if (*a > b->localid) - return +1; - return 0; -} - -static int ssh_rportcmp_ssh1(void *av, void *bv) -{ - struct ssh_rportfwd *a = (struct ssh_rportfwd *) av; - struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv; - int i; - if ( (i = strcmp(a->dhost, b->dhost)) != 0) - return i < 0 ? -1 : +1; - if (a->dport > b->dport) - return +1; - if (a->dport < b->dport) - return -1; - return 0; -} - -static int ssh_rportcmp_ssh2(void *av, void *bv) -{ - struct ssh_rportfwd *a = (struct ssh_rportfwd *) av; - struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv; - int i; - if ( (i = strcmp(a->shost, b->shost)) != 0) - return i < 0 ? -1 : +1; - if (a->sport > b->sport) - return +1; - if (a->sport < b->sport) - return -1; - return 0; -} - -static void c_write(Ssh ssh, const void *buf, int len) -{ - from_backend(ssh->frontend, 1, buf, len); -} - -static void c_write_str(Ssh ssh, const char *buf) -{ - c_write(ssh, buf, strlen(buf)); -} - -static int s_write(Ssh ssh, const void *data, int len) -{ - if (len && ssh->logctx) - log_packet(ssh->logctx, PKT_OUTGOING, -1, NULL, data, len, - 0, NULL, NULL, 0, NULL); - if (!ssh->s) - return 0; - return sk_write(ssh->s, data, len); -} - -static void ssh_pkt_write(Ssh ssh, PktOut *pkt) -{ - pq_push(&ssh->bpp->out_pq, pkt); -} - -/* - * Either queue or send a packet, depending on whether queueing is - * set. - */ -static void ssh2_pkt_send(Ssh ssh, PktOut *pkt) -{ - if (ssh->queueing) { - pq_push(&ssh->outq, pkt); - } else { - ssh_pkt_write(ssh, pkt); - } -} - -static void ssh_send_outgoing_data(void *ctx) -{ - Ssh ssh = (Ssh)ctx; - - while (bufchain_size(&ssh->outgoing_data) > 0) { - void *data; - int len, backlog; - - bufchain_prefix(&ssh->outgoing_data, &data, &len); - backlog = s_write(ssh, data, len); - bufchain_consume(&ssh->outgoing_data, len); - - if (ssh->version == 2 && !ssh->kex_in_progress && - ssh->state != SSH_STATE_PREPACKET && - !ssh->bare_connection && !ssh->stats.out.running) { - ssh->rekey_reason = "too much data sent"; - ssh->rekey_class = RK_NORMAL; - queue_idempotent_callback(&ssh->ssh2_transport_icb); - } - - if (backlog > SSH_MAX_BACKLOG) { - ssh_throttle_all(ssh, 1, backlog); - return; - } - } -} - -/* - * Send all queued SSH-2 packets. - */ -static void ssh2_pkt_queuesend(Ssh ssh) -{ - PktOut *pkt; - - assert(!ssh->queueing); - - while ((pkt = pq_pop(&ssh->outq)) != NULL) - ssh_pkt_write(ssh, pkt); -} - -#if 0 -void bndebug(char *string, Bignum b) -{ - unsigned char *p; - int i, len; - p = ssh2_mpint_fmt(b, &len); - debug(("%s", string)); - for (i = 0; i < len; i++) - debug((" %02x", p[i])); - debug(("\n")); - sfree(p); -} -#endif - -/* - * Helper function to add an SSH-2 signature blob to a packet. Expects - * to be shown the public key blob as well as the signature blob. - * Normally just appends the sig blob unmodified as a string, except - * that it optionally breaks it open and fiddle with it to work around - * BUG_SSH2_RSA_PADDING. - */ -static void ssh2_add_sigblob(Ssh ssh, PktOut *pkt, - const void *pkblob, int pkblob_len, - const void *sigblob, int sigblob_len) -{ - BinarySource pk[1], sig[1]; - BinarySource_BARE_INIT(pk, pkblob, pkblob_len); - BinarySource_BARE_INIT(sig, sigblob, sigblob_len); - - /* dmemdump(pkblob, pkblob_len); */ - /* dmemdump(sigblob, sigblob_len); */ - - /* - * See if this is in fact an ssh-rsa signature and a buggy - * server; otherwise we can just do this the easy way. - */ - if ((ssh->remote_bugs & BUG_SSH2_RSA_PADDING) && - ptrlen_eq_string(get_string(pk), "ssh-rsa") && - ptrlen_eq_string(get_string(sig), "ssh-rsa")) { - ptrlen mod_mp, sig_mp; - size_t sig_prefix_len; - - /* - * Find the modulus and signature integers. - */ - get_string(pk); /* skip over exponent */ - mod_mp = get_string(pk); /* remember modulus */ - sig_prefix_len = sig->pos; - sig_mp = get_string(sig); - if (get_err(pk) || get_err(sig)) - goto give_up; - - /* - * Find the byte length of the modulus, not counting leading - * zeroes. - */ - while (mod_mp.len > 0 && *(const char *)mod_mp.ptr == 0) { - mod_mp.len--; - mod_mp.ptr = (const char *)mod_mp.ptr + 1; - } - - /* debug(("modulus length is %d\n", len)); */ - /* debug(("signature length is %d\n", siglen)); */ - - if (mod_mp.len != sig_mp.len) { - strbuf *substr = strbuf_new(); - put_data(substr, sigblob, sig_prefix_len); - put_uint32(substr, mod_mp.len); - put_padding(substr, mod_mp.len - sig_mp.len, 0); - put_data(substr, sig_mp.ptr, sig_mp.len); - put_stringsb(pkt, substr); - return; - } - - /* Otherwise fall through and do it the easy way. We also come - * here as a fallback if we discover above that the key blob - * is misformatted in some way. */ - give_up:; - } - - put_string(pkt, sigblob, sigblob_len); -} - -static void ssh_feed_to_bpp(Ssh ssh) -{ - assert(ssh->bpp); - ssh_bpp_handle_input(ssh->bpp); - - if (ssh->bpp->error) { - bomb_out(ssh, ssh->bpp->error); /* also frees the error string */ - return; - } - - if (ssh->bpp->seen_disconnect) { - /* - * If we've seen a DISCONNECT message, we should unset the - * close_expected flag, because now we _do_ expect the server - * to close the network connection afterwards. That way, the - * more informative connection_fatal message for the - * disconnect itself won't fight with 'Server unexpectedly - * closed network connection'. - */ - ssh->clean_exit = FALSE; - ssh->close_expected = TRUE; - ssh->disconnect_message_seen = TRUE; - } + ppl->bpp = ssh->bpp; + ppl->user_input = &ssh->user_input; + ppl->frontend = ssh->frontend; + ppl->ssh = ssh; + ppl->remote_bugs = ssh->remote_bugs; } static void ssh_got_ssh_version(struct ssh_version_receiver *rcv, @@ -893,22 +143,10 @@ static void ssh_got_ssh_version(struct ssh_version_receiver *rcv, { Ssh ssh = FROMFIELD(rcv, struct ssh_tag, version_receiver); BinaryPacketProtocol *old_bpp; + PacketProtocolLayer *connection_layer; - /* - * This is as good a time as any to stop printing proxy-command - * diagnostics in the terminal window, on the assumption that the - * proxy command must by now have made a sensible connection and - * the real session output will start shortly. - */ ssh->session_started = TRUE; - /* - * Queue an outgoing-data run: if the version string has been sent - * late rather than early, it'll still be sitting on our output - * raw data queue. - */ - queue_idempotent_callback(&ssh->outgoing_data_sender); - /* * We don't support choosing a major protocol version dynamically, * so this should always be the same value we set up in @@ -921,162 +159,328 @@ static void ssh_got_ssh_version(struct ssh_version_receiver *rcv, if (!ssh->bare_connection) { if (ssh->version == 2) { - /* - * Retrieve both version strings from the old BPP before - * we free it. - */ - ssh->v_s = dupstr(ssh_verstring_get_remote(old_bpp)); - ssh->v_c = dupstr(ssh_verstring_get_local(old_bpp)); + PacketProtocolLayer *userauth_layer, *transport_child_layer; /* - * Initialise SSH-2 protocol. + * We use the 'simple' variant of the SSH protocol if + * we're asked to, except not if we're also doing + * connection-sharing (either tunnelling our packets over + * an upstream or expecting to be tunnelled over + * ourselves), since then the assumption that we have only + * one channel to worry about is not true after all. */ - ssh2_protocol_setup(ssh); - ssh->general_packet_processing = ssh2_general_packet_processing; - ssh->current_user_input_fn = NULL; + int is_simple = + (conf_get_int(ssh->conf, CONF_ssh_simple) && !ssh->connshare); + + ssh->bpp = ssh2_bpp_new(&ssh->stats); + ssh_connect_bpp(ssh); + +#ifndef NO_GSSAPI + /* Load and pick the highest GSS library on the preference + * list. */ + if (!ssh->gss_state.libs) + ssh->gss_state.libs = ssh_gss_setup(ssh->conf); + ssh->gss_state.lib = NULL; + if (ssh->gss_state.libs->nlibraries > 0) { + int i, j; + for (i = 0; i < ngsslibs; i++) { + int want_id = conf_get_int_int(ssh->conf, + CONF_ssh_gsslist, i); + for (j = 0; j < ssh->gss_state.libs->nlibraries; j++) + if (ssh->gss_state.libs->libraries[j].id == want_id) { + ssh->gss_state.lib = + &ssh->gss_state.libs->libraries[j]; + goto got_gsslib; /* double break */ + } + } + got_gsslib: + /* + * We always expect to have found something in + * the above loop: we only came here if there + * was at least one viable GSS library, and the + * preference list should always mention + * everything and only change the order. + */ + assert(ssh->gss_state.lib); + } +#endif + + connection_layer = ssh2_connection_new( + ssh, ssh->connshare, is_simple, ssh->conf, + ssh_verstring_get_remote(old_bpp), &ssh->cl); + ssh_connect_ppl(ssh, connection_layer); + + if (conf_get_int(ssh->conf, CONF_ssh_no_userauth)) { + userauth_layer = NULL; + transport_child_layer = connection_layer; + } else { + char *username = get_remote_username(ssh->conf); + + userauth_layer = ssh2_userauth_new( + connection_layer, ssh->savedhost, ssh->fullhostname, + conf_get_filename(ssh->conf, CONF_keyfile), + conf_get_int(ssh->conf, CONF_tryagent), username, + conf_get_int(ssh->conf, CONF_change_username), + conf_get_int(ssh->conf, CONF_try_ki_auth), + conf_get_int(ssh->conf, CONF_try_gssapi_auth), + conf_get_int(ssh->conf, CONF_try_gssapi_kex), + conf_get_int(ssh->conf, CONF_gssapifwd), + &ssh->gss_state); + ssh_connect_ppl(ssh, userauth_layer); + transport_child_layer = userauth_layer; + + sfree(username); + } + + ssh->base_layer = ssh2_transport_new( + ssh->conf, ssh->savedhost, ssh->savedport, + ssh->fullhostname, + ssh_verstring_get_local(old_bpp), + ssh_verstring_get_remote(old_bpp), + &ssh->gss_state, + &ssh->stats, transport_child_layer); + ssh_connect_ppl(ssh, ssh->base_layer); + + if (userauth_layer) + ssh2_userauth_set_transport_layer(userauth_layer, + ssh->base_layer); + } else { - /* - * Initialise SSH-1 protocol. - */ - ssh1_protocol_setup(ssh); - ssh->current_user_input_fn = ssh1_login_input; - } - if (ssh->version == 2) - queue_idempotent_callback(&ssh->ssh2_transport_icb); + ssh->bpp = ssh1_bpp_new(); + ssh_connect_bpp(ssh); + + connection_layer = ssh1_connection_new(ssh, ssh->conf, &ssh->cl); + ssh_connect_ppl(ssh, connection_layer); + + ssh->base_layer = ssh1_login_new( + ssh->conf, ssh->savedhost, ssh->savedport, connection_layer); + ssh_connect_ppl(ssh, ssh->base_layer); + + } } else { - assert(ssh->version == 2); /* can't do SSH-1 bare connection! */ - logeventf(ssh, "Using bare ssh-connection protocol"); - - ssh2_bare_connection_protocol_setup(ssh); - ssh->current_user_input_fn = ssh2_connection_input; + ssh->bpp = ssh2_bare_bpp_new(); + ssh_connect_bpp(ssh); + connection_layer = ssh2_connection_new( + ssh, NULL, FALSE, ssh->conf, ssh_verstring_get_remote(old_bpp), + &ssh->cl); + ssh_connect_ppl(ssh, connection_layer); + ssh->base_layer = connection_layer; } - ssh->bpp->out_raw = &ssh->outgoing_data; - ssh->bpp->out_raw->ic = &ssh->outgoing_data_sender; - ssh->bpp->in_raw = &ssh->incoming_data; - ssh->bpp->in_pq.pqb.ic = &ssh->incoming_pkt_consumer; - ssh->bpp->pls = &ssh->pls; - ssh->bpp->logctx = ssh->logctx; - ssh->bpp->remote_bugs = ssh->remote_bugs; - - queue_idempotent_callback(&ssh->bpp->ic_in_raw); - queue_idempotent_callback(&ssh->user_input_consumer); + /* Connect the base layer - whichever it is - to the BPP, and set + * up its selfptr. */ + ssh->base_layer->selfptr = &ssh->base_layer; + ssh_ppl_setup_queues(ssh->base_layer, &ssh->bpp->in_pq, &ssh->bpp->out_pq); update_specials_menu(ssh->frontend); - ssh->state = SSH_STATE_BEFORE_SIZE; ssh->pinger = pinger_new(ssh->conf, &ssh->backend); - if (ssh->bare_connection) { - /* - * Get connection protocol under way. - */ - do_ssh2_connection(ssh); - } + queue_idempotent_callback(&ssh->bpp->ic_in_raw); + ssh_ppl_process_queue(ssh->base_layer); + + /* Pass in the initial terminal size, if we knew it already. */ + ssh_terminal_size(ssh->cl, ssh->term_width, ssh->term_height); + + ssh_bpp_free(old_bpp); } -static void ssh_process_incoming_data(void *ctx) +static void ssh_bpp_output_raw_data_callback(void *vctx) { - Ssh ssh = (Ssh)ctx; + Ssh ssh = (Ssh)vctx; - if (ssh->state == SSH_STATE_CLOSED) + if (!ssh->s) return; - if (!ssh->frozen) - ssh_feed_to_bpp(ssh); + while (bufchain_size(&ssh->out_raw) > 0) { + void *data; + int len, backlog; - if (ssh->state == SSH_STATE_CLOSED) /* yes, check _again_ */ - return; + bufchain_prefix(&ssh->out_raw, &data, &len); - if (ssh->incoming_data_seen_eof) { - int need_notify = ssh_do_close(ssh, FALSE); - const char *error_msg = ssh->incoming_data_eof_message; + if (ssh->logctx) + log_packet(ssh->logctx, PKT_OUTGOING, -1, NULL, data, len, + 0, NULL, NULL, 0, NULL); + backlog = sk_write(ssh->s, data, len); - if (!error_msg) { - if (!ssh->close_expected) - error_msg = "Server unexpectedly closed network connection"; - else - error_msg = "Server closed network connection"; + bufchain_consume(&ssh->out_raw, len); + + if (backlog > SSH_MAX_BACKLOG) { + ssh_throttle_all(ssh, 1, backlog); + return; } - - if (ssh->close_expected && ssh->clean_exit && ssh->exitcode < 0) - ssh->exitcode = 0; - - if (need_notify) - notify_remote_exit(ssh->frontend); - - if (error_msg) - logevent(error_msg); - if ((!ssh->close_expected || !ssh->clean_exit) && - !ssh->disconnect_message_seen) - connection_fatal(ssh->frontend, "%s", error_msg); } + + if (ssh->send_outgoing_eof) + sk_write_eof(ssh->s); } -static void ssh_process_incoming_pkts(void *ctx) +static void ssh_shutdown_internal(Ssh ssh) { - Ssh ssh = (Ssh)ctx; - PktIn *pktin; - - while ((pktin = pq_pop(&ssh->bpp->in_pq)) != NULL) { - if (ssh->general_packet_processing) - ssh->general_packet_processing(ssh, pktin); - ssh->packet_dispatch[pktin->type](ssh, pktin); - } -} - -static void ssh_process_user_input(void *ctx) -{ - Ssh ssh = (Ssh)ctx; - if (ssh->current_user_input_fn) - ssh->current_user_input_fn(ssh); -} - -static int ssh_do_close(Ssh ssh, int notify_exit) -{ - int ret = 0; - struct ssh_channel *c; - - ssh->state = SSH_STATE_CLOSED; expire_timer_context(ssh); - if (ssh->s) { - sk_close(ssh->s); - ssh->s = NULL; - if (notify_exit) - notify_remote_exit(ssh->frontend); - else - ret = 1; - } - /* - * Now we must shut down any port- and X-forwarded channels going - * through this connection. - */ - if (ssh->channels) { - while (NULL != (c = index234(ssh->channels, 0))) { - ssh_channel_close_local(c, NULL); - del234(ssh->channels, c); /* moving next one to index 0 */ - if (ssh->version == 2) - bufchain_clear(&c->v.v2.outbuffer); - sfree(c); - } - } - /* - * Go through port-forwardings, and close any associated - * listening sockets. - */ - portfwdmgr_close_all(ssh->portfwdmgr); - /* - * Also stop attempting to connection-share. - */ if (ssh->connshare) { sharestate_free(ssh->connshare); ssh->connshare = NULL; } - return ret; + if (ssh->pinger) { + pinger_free(ssh->pinger); + ssh->pinger = NULL; + } + + /* + * We only need to free the base PPL, which will free the others + * (if any) transitively. + */ + if (ssh->base_layer) { + ssh_ppl_free(ssh->base_layer); + ssh->base_layer = NULL; + } + + ssh->cl = NULL; +} + +static void ssh_shutdown(Ssh ssh) +{ + ssh_shutdown_internal(ssh); + + if (ssh->bpp) { + ssh_bpp_free(ssh->bpp); + ssh->bpp = NULL; + } + + if (ssh->s) { + sk_close(ssh->s); + ssh->s = NULL; + } + + bufchain_clear(&ssh->in_raw); + bufchain_clear(&ssh->out_raw); + bufchain_clear(&ssh->user_input); +} + +static void ssh_initiate_connection_close(Ssh ssh) +{ + /* Wind up everything above the BPP. */ + ssh_shutdown_internal(ssh); + + /* Force any remaining queued SSH packets through the BPP, and + * schedule sending of EOF on the network socket after them. */ + ssh_bpp_handle_output(ssh->bpp); + ssh->send_outgoing_eof = TRUE; + + /* Now we expect the other end to close the connection too in + * response, so arrange that we'll receive notification of that + * via ssh_remote_eof. */ + ssh->bpp->expect_close = TRUE; +} + +#define GET_FORMATTED_MSG \ + char *msg; \ + va_list ap; \ + va_start(ap, fmt); \ + msg = dupvprintf(fmt, ap); \ + va_end(ap); + +void ssh_remote_error(Ssh ssh, const char *fmt, ...) +{ + if (ssh->base_layer) { + GET_FORMATTED_MSG; + + /* Error messages sent by the remote don't count as clean exits */ + ssh->exitcode = 128; + + /* Close the socket immediately, since the server has already + * closed its end (or is about to). */ + ssh_shutdown(ssh); + + logevent(ssh->frontend, msg); + connection_fatal(ssh->frontend, "%s", msg); + sfree(msg); + } +} + +void ssh_remote_eof(Ssh ssh, const char *fmt, ...) +{ + if (ssh->base_layer) { + GET_FORMATTED_MSG; + + /* EOF from the remote, if we were expecting it, does count as + * a clean exit */ + ssh->exitcode = 0; + + /* Close the socket immediately, since the server has already + * closed its end. */ + ssh_shutdown(ssh); + + logevent(ssh->frontend, msg); + sfree(msg); + notify_remote_exit(ssh->frontend); + } else { + /* This is responding to EOF after we've already seen some + * other reason for terminating the session. */ + ssh_shutdown(ssh); + } +} + +void ssh_proto_error(Ssh ssh, const char *fmt, ...) +{ + if (ssh->base_layer) { + GET_FORMATTED_MSG; + + ssh->exitcode = 128; + + ssh_bpp_queue_disconnect(ssh->bpp, msg, + SSH2_DISCONNECT_PROTOCOL_ERROR); + ssh_initiate_connection_close(ssh); + + logevent(ssh->frontend, msg); + connection_fatal(ssh->frontend, "%s", msg); + sfree(msg); + } +} + +void ssh_sw_abort(Ssh ssh, const char *fmt, ...) +{ + if (ssh->base_layer) { + GET_FORMATTED_MSG; + + ssh->exitcode = 128; + + ssh_initiate_connection_close(ssh); + + logevent(ssh->frontend, msg); + connection_fatal(ssh->frontend, "%s", msg); + sfree(msg); + + notify_remote_exit(ssh->frontend); + } +} + +void ssh_user_close(Ssh ssh, const char *fmt, ...) +{ + if (ssh->base_layer) { + GET_FORMATTED_MSG; + + /* Closing the connection due to user action, even if the + * action is the user aborting during authentication prompts, + * does count as a clean exit - except that this is also how + * we signal ordinary session termination, in which case we + * should use the exit status already sent from the main + * session (if any). */ + if (ssh->exitcode < 0) + ssh->exitcode = 0; + + ssh_initiate_connection_close(ssh); + + logevent(ssh->frontend, msg); + sfree(msg); + + notify_remote_exit(ssh->frontend); + } } static void ssh_socket_log(Plug plug, int type, SockAddr addr, int port, @@ -1103,9 +507,10 @@ static void ssh_closing(Plug plug, const char *error_msg, int error_code, int calling_back) { Ssh ssh = FROMFIELD(plug, struct ssh_tag, plugvt); - ssh->incoming_data_seen_eof = TRUE; - ssh->incoming_data_eof_message = dupstr(error_msg); - queue_idempotent_callback(&ssh->incoming_data_consumer); + if (ssh->bpp) { + ssh->bpp->input_eof = TRUE; + queue_idempotent_callback(&ssh->bpp->ic_in_raw); + } } static void ssh_receive(Plug plug, int urgent, char *data, int len) @@ -1117,12 +522,9 @@ static void ssh_receive(Plug plug, int urgent, char *data, int len) log_packet(ssh->logctx, PKT_INCOMING, -1, NULL, data, len, 0, NULL, NULL, 0, NULL); - bufchain_add(&ssh->incoming_data, data, len); - queue_idempotent_callback(&ssh->incoming_data_consumer); - - if (ssh->state == SSH_STATE_CLOSED) { - ssh_do_close(ssh, TRUE); - } + bufchain_add(&ssh->in_raw, data, len); + if (!ssh->frozen && ssh->bpp) + queue_idempotent_callback(&ssh->bpp->ic_in_raw); } static void ssh_sent(Plug plug, int bufsize) @@ -1130,12 +532,13 @@ static void ssh_sent(Plug plug, int bufsize) Ssh ssh = FROMFIELD(plug, struct ssh_tag, plugvt); /* * If the send backlog on the SSH socket itself clears, we should - * unthrottle the whole world if it was throttled, and also resume - * sending our bufchain of queued wire data. + * 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) { ssh_throttle_all(ssh, 0, bufsize); - queue_idempotent_callback(&ssh->outgoing_data_sender); + queue_idempotent_callback(&ssh->ic_out_raw); } } @@ -1232,9 +635,6 @@ static const char *connect_to_host(Ssh ssh, const char *host, int port, ssh->s = ssh_connection_sharing_init( ssh->savedhost, ssh->savedport, ssh->conf, ssh->frontend, &ssh->plugvt, &ssh->connshare); - if (ssh->connshare) - ssh_connshare_provide_connlayer(ssh->connshare, &ssh->cl); - ssh->attempting_connshare = FALSE; if (ssh->s != NULL) { /* @@ -1249,7 +649,9 @@ static const char *connect_to_host(Ssh ssh, const char *host, int port, * in the console window that we're a sharing downstream, * to avoid confusing users as to why this session doesn't * behave in quite the usual way. */ - c_write_str(ssh,"Reusing a shared connection to this server.\r\n"); + const char *msg = + "Reusing a shared connection to this server.\r\n"; + from_backend(ssh->frontend, TRUE, msg, strlen(msg)); } } else { /* @@ -1294,20 +696,15 @@ static const char *connect_to_host(Ssh ssh, const char *host, int port, /* * Set up the initial BPP that will do the version string - * exchange. + * exchange, and get it started so that it can send the outgoing + * version string early if it wants to. */ ssh->version_receiver.got_ssh_version = ssh_got_ssh_version; ssh->bpp = ssh_verstring_new( ssh->conf, ssh->frontend, ssh->bare_connection, ssh->version == 1 ? "1.5" : "2.0", &ssh->version_receiver); - ssh->bpp->out_raw = &ssh->outgoing_data; - ssh->bpp->in_raw = &ssh->incoming_data; - /* - * And call its handle_input method right now, in case it wants to - * send the outgoing version string immediately. - */ - ssh_bpp_handle_input(ssh->bpp); - queue_idempotent_callback(&ssh->outgoing_data_sender); + ssh_connect_bpp(ssh); + queue_idempotent_callback(&ssh->bpp->ic_in_raw); /* * loghost, if configured, overrides realhost. @@ -1323,7 +720,7 @@ static const char *connect_to_host(Ssh ssh, const char *host, int port, /* * Throttle or unthrottle the SSH connection. */ -static void ssh_throttle_conn(Ssh ssh, int adjust) +void ssh_throttle_conn(Ssh ssh, int adjust) { int old_count = ssh->conn_throttle_count; int frozen; @@ -1341,24 +738,15 @@ static void ssh_throttle_conn(Ssh ssh, int adjust) ssh->frozen = frozen; - if (ssh->s) + if (ssh->s) { sk_set_frozen(ssh->s, frozen); -} -static void ssh_channel_check_throttle(struct ssh_channel *c) -{ - /* - * We don't want this channel to read further input if this - * particular channel has a backed-up SSH window, or if the - * outgoing side of the whole SSH connection is currently - * throttled, or if this channel already has an outgoing EOF - * either sent or pending. - */ - chan_set_input_wanted(c->chan, - !c->throttled_by_backlog && - !c->ssh->throttled_all && - !c->pending_eof && - !(c->closes & CLOSES_SENT_EOF)); + /* + * Now process any SSH connection data that was stashed in our + * queue while we were frozen. + */ + queue_idempotent_callback(&ssh->bpp->ic_in_raw); + } } /* @@ -1367,7576 +755,12 @@ static void ssh_channel_check_throttle(struct ssh_channel *c) */ static void ssh_throttle_all(Ssh ssh, int enable, int bufsize) { - int i; - struct ssh_channel *c; - if (enable == ssh->throttled_all) return; ssh->throttled_all = enable; ssh->overall_bufsize = bufsize; - if (!ssh->channels) - return; - for (i = 0; NULL != (c = index234(ssh->channels, i)); i++) - ssh_channel_check_throttle(c); -} -static void ssh_agent_callback(void *sshv, void *reply, int replylen) -{ - Ssh ssh = (Ssh) sshv; - - ssh->auth_agent_query = NULL; - - ssh->agent_response = reply; - ssh->agent_response_len = replylen; - - if (ssh->version == 1) - do_ssh1_login(ssh); - else - do_ssh2_userauth(ssh); -} - -static void ssh_dialog_callback(void *sshv, int ret) -{ - Ssh ssh = (Ssh) sshv; - - ssh->user_response = ret; - - if (ssh->version == 1) - do_ssh1_login(ssh); - else - queue_idempotent_callback(&ssh->ssh2_transport_icb); - - /* - * This may have unfrozen the SSH connection. - */ - if (!ssh->frozen) - queue_idempotent_callback(&ssh->incoming_data_consumer); -} - -/* - * Client-initiated disconnection. Send a DISCONNECT if `wire_reason' - * non-NULL, otherwise just close the connection. `client_reason' == NULL - * => log `wire_reason'. - */ -static void ssh_disconnect(Ssh ssh, const char *client_reason, - const char *wire_reason, - int code, int clean_exit) -{ - char *error; - if (!client_reason) - client_reason = wire_reason; - if (client_reason) - error = dupprintf("Disconnected: %s", client_reason); - else - error = dupstr("Disconnected"); - if (wire_reason) - ssh_bpp_queue_disconnect(ssh->bpp, wire_reason, code); - ssh->close_expected = TRUE; - ssh->clean_exit = clean_exit; - ssh_closing(&ssh->plugvt, error, 0, 0); - sfree(error); -} - -static void ssh1_coro_wrapper_initial(Ssh ssh, PktIn *pktin); -static void ssh1_coro_wrapper_session(Ssh ssh, PktIn *pktin); -static void ssh1_connection_input(Ssh ssh); - -/* - * Handle the key exchange and user authentication phases. - */ -static void do_ssh1_login(void *vctx) -{ - Ssh ssh = (Ssh)vctx; - PktIn *pktin; - PktOut *pkt; - - int i, j, ret; - ptrlen pl; - struct MD5Context md5c; - struct do_ssh1_login_state { - int crLine; - int len; - unsigned char *rsabuf; - unsigned long supported_ciphers_mask, supported_auths_mask; - int tried_publickey, tried_agent; - int tis_auth_refused, ccard_auth_refused; - unsigned char cookie[8]; - unsigned char session_id[16]; - int cipher_type; - strbuf *publickey_blob; - char *publickey_comment; - int privatekey_available, privatekey_encrypted; - prompts_t *cur_prompt; - int userpass_ret; - char c; - int pwpkt_type; - unsigned char *agent_response; - BinarySource asrc[1]; /* response from SSH agent */ - int keyi, nkeys; - int authed; - struct RSAKey key; - Bignum challenge; - ptrlen comment; - int dlgret; - Filename *keyfile; - struct RSAKey servkey, hostkey; - }; - crState(do_ssh1_login_state); - - crBeginState; - - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh1_login)) != NULL); - - if (pktin->type != SSH1_SMSG_PUBLIC_KEY) { - bombout(("Public key packet not received")); - crStopV; - } - - logevent("Received public keys"); - - pl = get_data(pktin, 8); - memcpy(s->cookie, pl.ptr, pl.len); - - get_rsa_ssh1_pub(pktin, &s->servkey, RSA_SSH1_EXPONENT_FIRST); - get_rsa_ssh1_pub(pktin, &s->hostkey, RSA_SSH1_EXPONENT_FIRST); - - s->hostkey.comment = NULL; /* avoid confusing rsa_ssh1_fingerprint */ - - /* - * Log the host key fingerprint. - */ - if (!get_err(pktin)) { - char *fingerprint = rsa_ssh1_fingerprint(&s->hostkey); - logevent("Host key fingerprint is:"); - logeventf(ssh, " %s", fingerprint); - sfree(fingerprint); - } - - ssh->v1_remote_protoflags = get_uint32(pktin); - s->supported_ciphers_mask = get_uint32(pktin); - s->supported_auths_mask = get_uint32(pktin); - - if (get_err(pktin)) { - bombout(("Bad SSH-1 public key packet")); - crStopV; - } - - if ((ssh->remote_bugs & BUG_CHOKES_ON_RSA)) - s->supported_auths_mask &= ~(1 << SSH1_AUTH_RSA); - - ssh->v1_local_protoflags = - ssh->v1_remote_protoflags & SSH1_PROTOFLAGS_SUPPORTED; - ssh->v1_local_protoflags |= SSH1_PROTOFLAG_SCREEN_NUMBER; - - MD5Init(&md5c); - { - int i; - for (i = (bignum_bitcount(s->hostkey.modulus) + 7) / 8; i-- ;) - put_byte(&md5c, bignum_byte(s->hostkey.modulus, i)); - for (i = (bignum_bitcount(s->servkey.modulus) + 7) / 8; i-- ;) - put_byte(&md5c, bignum_byte(s->servkey.modulus, i)); - } - put_data(&md5c, s->cookie, 8); - MD5Final(s->session_id, &md5c); - - for (i = 0; i < 32; i++) - ssh->session_key[i] = random_byte(); - - /* - * Verify that the `bits' and `bytes' parameters match. - */ - if (s->hostkey.bits > s->hostkey.bytes * 8 || - s->servkey.bits > s->servkey.bytes * 8) { - bombout(("SSH-1 public keys were badly formatted")); - crStopV; - } - - s->len = (s->hostkey.bytes > s->servkey.bytes ? - s->hostkey.bytes : s->servkey.bytes); - - s->rsabuf = snewn(s->len, unsigned char); - - /* - * Verify the host key. - */ - { - /* - * First format the key into a string. - */ - int len = rsastr_len(&s->hostkey); - char *fingerprint; - char *keystr = snewn(len, char); - rsastr_fmt(keystr, &s->hostkey); - fingerprint = rsa_ssh1_fingerprint(&s->hostkey); - - /* First check against manually configured host keys. */ - s->dlgret = verify_ssh_manual_host_key(ssh->conf, fingerprint, NULL); - sfree(fingerprint); - if (s->dlgret == 0) { /* did not match */ - bombout(("Host key did not appear in manually configured list")); - sfree(keystr); - crStopV; - } else if (s->dlgret < 0) { /* none configured; use standard handling */ - s->dlgret = verify_ssh_host_key(ssh->frontend, - ssh->savedhost, ssh->savedport, - "rsa", keystr, fingerprint, - ssh_dialog_callback, ssh); - sfree(keystr); -#ifdef FUZZING - s->dlgret = 1; -#endif - if (s->dlgret < 0) { - ssh->user_response = -1; - crWaitUntilV(ssh->user_response >= 0); - s->dlgret = ssh->user_response; - } - - if (s->dlgret == 0) { - ssh_disconnect(ssh, "User aborted at host key verification", - NULL, 0, TRUE); - crStopV; - } - } else { - sfree(keystr); - } - } - - for (i = 0; i < 32; i++) { - s->rsabuf[i] = ssh->session_key[i]; - if (i < 16) - s->rsabuf[i] ^= s->session_id[i]; - } - - if (s->hostkey.bytes > s->servkey.bytes) { - ret = rsa_ssh1_encrypt(s->rsabuf, 32, &s->servkey); - if (ret) - ret = rsa_ssh1_encrypt(s->rsabuf, s->servkey.bytes, &s->hostkey); - } else { - ret = rsa_ssh1_encrypt(s->rsabuf, 32, &s->hostkey); - if (ret) - ret = rsa_ssh1_encrypt(s->rsabuf, s->hostkey.bytes, &s->servkey); - } - if (!ret) { - bombout(("SSH-1 public key encryptions failed due to bad formatting")); - crStopV; - } - - logevent("Encrypted session key"); - - { - int cipher_chosen = 0, warn = 0; - const char *cipher_string = NULL; - int i; - for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) { - int next_cipher = conf_get_int_int(ssh->conf, - CONF_ssh_cipherlist, i); - if (next_cipher == CIPHER_WARN) { - /* If/when we choose a cipher, warn about it */ - warn = 1; - } else if (next_cipher == CIPHER_AES) { - /* XXX Probably don't need to mention this. */ - logevent("AES not supported in SSH-1, skipping"); - } else { - switch (next_cipher) { - case CIPHER_3DES: s->cipher_type = SSH_CIPHER_3DES; - cipher_string = "3DES"; break; - case CIPHER_BLOWFISH: s->cipher_type = SSH_CIPHER_BLOWFISH; - cipher_string = "Blowfish"; break; - case CIPHER_DES: s->cipher_type = SSH_CIPHER_DES; - cipher_string = "single-DES"; break; - } - if (s->supported_ciphers_mask & (1 << s->cipher_type)) - cipher_chosen = 1; - } - } - if (!cipher_chosen) { - if ((s->supported_ciphers_mask & (1 << SSH_CIPHER_3DES)) == 0) - bombout(("Server violates SSH-1 protocol by not " - "supporting 3DES encryption")); - else - /* shouldn't happen */ - bombout(("No supported ciphers found")); - crStopV; - } - - /* Warn about chosen cipher if necessary. */ - if (warn) { - s->dlgret = askalg(ssh->frontend, "cipher", cipher_string, - ssh_dialog_callback, ssh); - if (s->dlgret < 0) { - ssh->user_response = -1; - crWaitUntilV(ssh->user_response >= 0); - s->dlgret = ssh->user_response; - } - if (s->dlgret == 0) { - ssh_disconnect(ssh, "User aborted at cipher warning", NULL, - 0, TRUE); - crStopV; - } - } - } - - switch (s->cipher_type) { - case SSH_CIPHER_3DES: - logevent("Using 3DES encryption"); - break; - case SSH_CIPHER_DES: - logevent("Using single-DES encryption"); - break; - case SSH_CIPHER_BLOWFISH: - logevent("Using Blowfish encryption"); - break; - } - - pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_CMSG_SESSION_KEY); - put_byte(pkt, s->cipher_type); - put_data(pkt, s->cookie, 8); - put_uint16(pkt, s->len * 8); - put_data(pkt, s->rsabuf, s->len); - put_uint32(pkt, ssh->v1_local_protoflags); - ssh_pkt_write(ssh, pkt); - - logevent("Trying to enable encryption..."); - - sfree(s->rsabuf); - - /* - * Force the BPP to synchronously marshal all packets up to and - * including the SESSION_KEY into wire format, before we turn on - * crypto. - */ - ssh_bpp_handle_output(ssh->bpp); - - { - 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(ssh->bpp, cipher, ssh->session_key); - logeventf(ssh, "Initialised %s encryption", cipher->text_name); - } - - if (s->servkey.modulus) { - sfree(s->servkey.modulus); - s->servkey.modulus = NULL; - } - if (s->servkey.exponent) { - sfree(s->servkey.exponent); - s->servkey.exponent = NULL; - } - if (s->hostkey.modulus) { - sfree(s->hostkey.modulus); - s->hostkey.modulus = NULL; - } - if (s->hostkey.exponent) { - sfree(s->hostkey.exponent); - s->hostkey.exponent = NULL; - } - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh1_login)) != NULL); - - if (pktin->type != SSH1_SMSG_SUCCESS) { - bombout(("Encryption not successfully enabled")); - crStopV; - } - - logevent("Successfully started encryption"); - - fflush(stdout); /* FIXME eh? */ - if ((ssh->username = get_remote_username(ssh->conf)) == NULL) { - s->cur_prompt = new_prompts(ssh->frontend); - s->cur_prompt->to_server = TRUE; - s->cur_prompt->name = dupstr("SSH login name"); - add_prompt(s->cur_prompt, dupstr("login as: "), TRUE); - s->userpass_ret = get_userpass_input(s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(&ssh->user_input) > 0) - s->userpass_ret = get_userpass_input( - s->cur_prompt, &ssh->user_input); - - if (s->userpass_ret >= 0) - break; - - ssh->send_ok = 1; - crReturnV; - ssh->send_ok = 0; - } - if (!s->userpass_ret) { - /* - * Failed to get a username. Terminate. - */ - free_prompts(s->cur_prompt); - ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE); - crStopV; - } - ssh->username = dupstr(s->cur_prompt->prompts[0]->result); - free_prompts(s->cur_prompt); - } - - pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_CMSG_USER); - put_stringz(pkt, ssh->username); - ssh_pkt_write(ssh, pkt); - - { - char *userlog = dupprintf("Sent username \"%s\"", ssh->username); - logevent(userlog); - if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) { - c_write_str(ssh, userlog); - c_write_str(ssh, "\r\n"); - } - sfree(userlog); - } - - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh1_login)) != NULL); - - if ((s->supported_auths_mask & (1 << SSH1_AUTH_RSA)) == 0) { - /* We must not attempt PK auth. Pretend we've already tried it. */ - s->tried_publickey = s->tried_agent = 1; - } else { - s->tried_publickey = s->tried_agent = 0; - } - s->tis_auth_refused = s->ccard_auth_refused = 0; - /* - * Load the public half of any configured keyfile for later use. - */ - s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile); - if (!filename_is_null(s->keyfile)) { - int keytype; - logeventf(ssh, "Reading key file \"%.150s\"", - filename_to_str(s->keyfile)); - keytype = key_type(s->keyfile); - if (keytype == SSH_KEYTYPE_SSH1 || - keytype == SSH_KEYTYPE_SSH1_PUBLIC) { - const char *error; - s->publickey_blob = strbuf_new(); - if (rsa_ssh1_loadpub(s->keyfile, - BinarySink_UPCAST(s->publickey_blob), - &s->publickey_comment, &error)) { - s->privatekey_available = (keytype == SSH_KEYTYPE_SSH1); - if (!s->privatekey_available) - logeventf(ssh, "Key file contains public key only"); - s->privatekey_encrypted = rsa_ssh1_encrypted(s->keyfile, NULL); - } else { - char *msgbuf; - logeventf(ssh, "Unable to load key (%s)", error); - msgbuf = dupprintf("Unable to load key file " - "\"%.150s\" (%s)\r\n", - filename_to_str(s->keyfile), - error); - c_write_str(ssh, msgbuf); - sfree(msgbuf); - strbuf_free(s->publickey_blob); - s->publickey_blob = NULL; - } - } else { - char *msgbuf; - logeventf(ssh, "Unable to use this key file (%s)", - key_type_to_str(keytype)); - msgbuf = dupprintf("Unable to use key file \"%.150s\"" - " (%s)\r\n", - filename_to_str(s->keyfile), - key_type_to_str(keytype)); - c_write_str(ssh, msgbuf); - sfree(msgbuf); - s->publickey_blob = NULL; - } - } else - s->publickey_blob = NULL; - - while (pktin->type == SSH1_SMSG_FAILURE) { - s->pwpkt_type = SSH1_CMSG_AUTH_PASSWORD; - - if (conf_get_int(ssh->conf, CONF_tryagent) && agent_exists() && !s->tried_agent) { - /* - * Attempt RSA authentication using Pageant. - */ - void *r; - int rlen; - strbuf *request; - - s->authed = FALSE; - s->tried_agent = 1; - logevent("Pageant is running. Requesting keys."); - - /* Request the keys held by the agent. */ - request = strbuf_new_for_agent_query(); - put_byte(request, SSH1_AGENTC_REQUEST_RSA_IDENTITIES); - ssh->auth_agent_query = agent_query( - request, &r, &rlen, ssh_agent_callback, ssh); - strbuf_free(request); - if (ssh->auth_agent_query) { - ssh->agent_response = NULL; - crWaitUntilV(ssh->agent_response); - r = ssh->agent_response; - rlen = ssh->agent_response_len; - } - s->agent_response = r; - BinarySource_BARE_INIT(s->asrc, r, rlen); - get_uint32(s->asrc); /* skip length field */ - if (get_byte(s->asrc) == SSH1_AGENT_RSA_IDENTITIES_ANSWER) { - s->nkeys = toint(get_uint32(s->asrc)); - if (s->nkeys < 0) { - logeventf(ssh, "Pageant reported negative key count %d", - s->nkeys); - s->nkeys = 0; - } - logeventf(ssh, "Pageant has %d SSH-1 keys", s->nkeys); - for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) { - size_t start, end; - start = s->asrc->pos; - get_rsa_ssh1_pub(s->asrc, &s->key, - RSA_SSH1_EXPONENT_FIRST); - end = s->asrc->pos; - s->comment = get_string(s->asrc); - if (get_err(s->asrc)) { - logevent("Pageant key list packet was truncated"); - break; - } - if (s->publickey_blob) { - ptrlen keystr = make_ptrlen( - (const char *)s->asrc->data + start, end - start); - - if (keystr.len == s->publickey_blob->len && - !memcmp(keystr.ptr, s->publickey_blob->s, - s->publickey_blob->len)) { - logeventf(ssh, "Pageant key #%d matches " - "configured key file", s->keyi); - s->tried_publickey = 1; - } else - /* Skip non-configured key */ - continue; - } - logeventf(ssh, "Trying Pageant key #%d", s->keyi); - pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_CMSG_AUTH_RSA); - put_mp_ssh1(pkt, s->key.modulus); - ssh_pkt_write(ssh, pkt); - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh1_login)) - != NULL); - if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { - logevent("Key refused"); - continue; - } - logevent("Received RSA challenge"); - s->challenge = get_mp_ssh1(pktin); - if (get_err(pktin)) { - freebn(s->challenge); - bombout(("Server's RSA challenge was badly formatted")); - crStopV; - } - - { - strbuf *agentreq; - char *ret; - void *vret; - int retlen; - - agentreq = strbuf_new_for_agent_query(); - put_byte(agentreq, SSH1_AGENTC_RSA_CHALLENGE); - put_uint32(agentreq, bignum_bitcount(s->key.modulus)); - put_mp_ssh1(agentreq, s->key.exponent); - put_mp_ssh1(agentreq, s->key.modulus); - put_mp_ssh1(agentreq, s->challenge); - put_data(agentreq, s->session_id, 16); - put_uint32(agentreq, 1); /* response format */ - ssh->auth_agent_query = agent_query( - agentreq, &vret, &retlen, - ssh_agent_callback, ssh); - strbuf_free(agentreq); - - if (ssh->auth_agent_query) { - ssh->agent_response = NULL; - crWaitUntilV(ssh->agent_response); - vret = ssh->agent_response; - retlen = ssh->agent_response_len; - } - - ret = vret; - if (ret) { - if (ret[4] == SSH1_AGENT_RSA_RESPONSE) { - logevent("Sending Pageant's response"); - pkt = ssh_bpp_new_pktout(ssh->bpp, - SSH1_CMSG_AUTH_RSA_RESPONSE); - put_data(pkt, ret + 5, 16); - ssh_pkt_write(ssh, pkt); - sfree(ret); - crMaybeWaitUntilV( - (pktin = pq_pop(&ssh->pq_ssh1_login)) - != NULL); - if (pktin->type == SSH1_SMSG_SUCCESS) { - logevent - ("Pageant's response accepted"); - if (flags & FLAG_VERBOSE) { - c_write_str(ssh, "Authenticated using" - " RSA key \""); - c_write(ssh, s->comment.ptr, - s->comment.len); - c_write_str(ssh, "\" from agent\r\n"); - } - s->authed = TRUE; - } else - logevent - ("Pageant's response not accepted"); - } else { - logevent - ("Pageant failed to answer challenge"); - sfree(ret); - } - } else { - logevent("No reply received from Pageant"); - } - } - freebn(s->key.exponent); - freebn(s->key.modulus); - freebn(s->challenge); - if (s->authed) - break; - } - sfree(s->agent_response); - if (s->publickey_blob && !s->tried_publickey) - logevent("Configured key file not in Pageant"); - } else { - logevent("Failed to get reply from Pageant"); - } - if (s->authed) - break; - } - if (s->publickey_blob && s->privatekey_available && - !s->tried_publickey) { - /* - * Try public key authentication with the specified - * key file. - */ - int got_passphrase; /* need not be kept over crReturn */ - if (flags & FLAG_VERBOSE) - c_write_str(ssh, "Trying public key authentication.\r\n"); - s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile); - logeventf(ssh, "Trying public key \"%s\"", - filename_to_str(s->keyfile)); - s->tried_publickey = 1; - got_passphrase = FALSE; - while (!got_passphrase) { - /* - * Get a passphrase, if necessary. - */ - char *passphrase = NULL; /* only written after crReturn */ - const char *error; - if (!s->privatekey_encrypted) { - if (flags & FLAG_VERBOSE) - c_write_str(ssh, "No passphrase required.\r\n"); - passphrase = NULL; - } else { - s->cur_prompt = new_prompts(ssh->frontend); - s->cur_prompt->to_server = FALSE; - s->cur_prompt->name = dupstr("SSH key passphrase"); - add_prompt(s->cur_prompt, - dupprintf("Passphrase for key \"%.100s\": ", - s->publickey_comment), FALSE); - s->userpass_ret = get_userpass_input(s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(&ssh->user_input) > 0) - s->userpass_ret = get_userpass_input( - s->cur_prompt, &ssh->user_input); - - if (s->userpass_ret >= 0) - break; - - ssh->send_ok = 1; - crReturnV; - ssh->send_ok = 0; - } - if (!s->userpass_ret) { - /* Failed to get a passphrase. Terminate. */ - free_prompts(s->cur_prompt); - ssh_disconnect(ssh, NULL, "Unable to authenticate", - 0, TRUE); - crStopV; - } - passphrase = dupstr(s->cur_prompt->prompts[0]->result); - free_prompts(s->cur_prompt); - } - /* - * Try decrypting key with passphrase. - */ - s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile); - ret = rsa_ssh1_loadkey( - s->keyfile, &s->key, passphrase, &error); - if (passphrase) { - smemclr(passphrase, strlen(passphrase)); - sfree(passphrase); - } - if (ret == 1) { - /* Correct passphrase. */ - got_passphrase = TRUE; - } else if (ret == 0) { - c_write_str(ssh, "Couldn't load private key from "); - c_write_str(ssh, filename_to_str(s->keyfile)); - c_write_str(ssh, " ("); - c_write_str(ssh, error); - c_write_str(ssh, ").\r\n"); - got_passphrase = FALSE; - break; /* go and try something else */ - } else if (ret == -1) { - c_write_str(ssh, "Wrong passphrase.\r\n"); /* FIXME */ - got_passphrase = FALSE; - /* and try again */ - } else { - assert(0 && "unexpected return from rsa_ssh1_loadkey()"); - got_passphrase = FALSE; /* placate optimisers */ - } - } - - if (got_passphrase) { - - /* - * Send a public key attempt. - */ - pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_CMSG_AUTH_RSA); - put_mp_ssh1(pkt, s->key.modulus); - ssh_pkt_write(ssh, pkt); - - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh1_login)) - != NULL); - if (pktin->type == SSH1_SMSG_FAILURE) { - c_write_str(ssh, "Server refused our public key.\r\n"); - continue; /* go and try something else */ - } - if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { - bombout(("Bizarre response to offer of public key")); - crStopV; - } - - { - int i; - unsigned char buffer[32]; - Bignum challenge, response; - - challenge = get_mp_ssh1(pktin); - if (get_err(pktin)) { - freebn(challenge); - bombout(("Server's RSA challenge was badly formatted")); - crStopV; - } - response = rsa_ssh1_decrypt(challenge, &s->key); - freebn(s->key.private_exponent);/* burn the evidence */ - - for (i = 0; i < 32; i++) { - buffer[i] = bignum_byte(response, 31 - i); - } - - MD5Init(&md5c); - put_data(&md5c, buffer, 32); - put_data(&md5c, s->session_id, 16); - MD5Final(buffer, &md5c); - - pkt = ssh_bpp_new_pktout( - ssh->bpp, SSH1_CMSG_AUTH_RSA_RESPONSE); - put_data(pkt, buffer, 16); - ssh_pkt_write(ssh, pkt); - - freebn(challenge); - freebn(response); - } - - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh1_login)) - != NULL); - if (pktin->type == SSH1_SMSG_FAILURE) { - if (flags & FLAG_VERBOSE) - c_write_str(ssh, "Failed to authenticate with" - " our public key.\r\n"); - continue; /* go and try something else */ - } else if (pktin->type != SSH1_SMSG_SUCCESS) { - bombout(("Bizarre response to RSA authentication response")); - crStopV; - } - - break; /* we're through! */ - } - - } - - /* - * Otherwise, try various forms of password-like authentication. - */ - s->cur_prompt = new_prompts(ssh->frontend); - - if (conf_get_int(ssh->conf, CONF_try_tis_auth) && - (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) && - !s->tis_auth_refused) { - s->pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE; - logevent("Requested TIS authentication"); - pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_CMSG_AUTH_TIS); - ssh_pkt_write(ssh, pkt); - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh1_login)) != NULL); - if (pktin->type != SSH1_SMSG_AUTH_TIS_CHALLENGE) { - logevent("TIS authentication declined"); - if (flags & FLAG_INTERACTIVE) - c_write_str(ssh, "TIS authentication refused.\r\n"); - s->tis_auth_refused = 1; - continue; - } else { - ptrlen challenge; - char *instr_suf, *prompt; - - challenge = get_string(pktin); - if (get_err(pktin)) { - bombout(("TIS challenge packet was badly formed")); - crStopV; - } - logevent("Received TIS challenge"); - s->cur_prompt->to_server = TRUE; - s->cur_prompt->name = dupstr("SSH TIS authentication"); - /* Prompt heuristic comes from OpenSSH */ - if (memchr(challenge.ptr, '\n', challenge.len)) { - instr_suf = dupstr(""); - prompt = mkstr(challenge); - } else { - instr_suf = mkstr(challenge); - prompt = dupstr("Response: "); - } - s->cur_prompt->instruction = - dupprintf("Using TIS authentication.%s%s", - (*instr_suf) ? "\n" : "", - instr_suf); - s->cur_prompt->instr_reqd = TRUE; - add_prompt(s->cur_prompt, prompt, FALSE); - sfree(instr_suf); - } - } - if (conf_get_int(ssh->conf, CONF_try_tis_auth) && - (s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) && - !s->ccard_auth_refused) { - s->pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE; - logevent("Requested CryptoCard authentication"); - pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_CMSG_AUTH_CCARD); - ssh_pkt_write(ssh, pkt); - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh1_login)) != NULL); - if (pktin->type != SSH1_SMSG_AUTH_CCARD_CHALLENGE) { - logevent("CryptoCard authentication declined"); - c_write_str(ssh, "CryptoCard authentication refused.\r\n"); - s->ccard_auth_refused = 1; - continue; - } else { - ptrlen challenge; - char *instr_suf, *prompt; - - challenge = get_string(pktin); - if (get_err(pktin)) { - bombout(("CryptoCard challenge packet was badly formed")); - crStopV; - } - logevent("Received CryptoCard challenge"); - s->cur_prompt->to_server = TRUE; - s->cur_prompt->name = dupstr("SSH CryptoCard authentication"); - s->cur_prompt->name_reqd = FALSE; - /* Prompt heuristic comes from OpenSSH */ - if (memchr(challenge.ptr, '\n', challenge.len)) { - instr_suf = dupstr(""); - prompt = mkstr(challenge); - } else { - instr_suf = mkstr(challenge); - prompt = dupstr("Response: "); - } - s->cur_prompt->instruction = - dupprintf("Using CryptoCard authentication.%s%s", - (*instr_suf) ? "\n" : "", - instr_suf); - s->cur_prompt->instr_reqd = TRUE; - add_prompt(s->cur_prompt, prompt, FALSE); - sfree(instr_suf); - } - } - if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { - if ((s->supported_auths_mask & (1 << SSH1_AUTH_PASSWORD)) == 0) { - bombout(("No supported authentication methods available")); - crStopV; - } - s->cur_prompt->to_server = TRUE; - s->cur_prompt->name = dupstr("SSH password"); - add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ", - ssh->username, ssh->savedhost), - FALSE); - } - - /* - * Show password prompt, having first obtained it via a TIS - * or CryptoCard exchange if we're doing TIS or CryptoCard - * authentication. - */ - s->userpass_ret = get_userpass_input(s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(&ssh->user_input) > 0) - s->userpass_ret = get_userpass_input( - s->cur_prompt, &ssh->user_input); - - if (s->userpass_ret >= 0) - break; - - ssh->send_ok = 1; - crReturnV; - ssh->send_ok = 0; - } - if (!s->userpass_ret) { - /* - * Failed to get a password (for example - * because one was supplied on the command line - * which has already failed to work). Terminate. - */ - free_prompts(s->cur_prompt); - ssh_disconnect(ssh, NULL, "Unable to authenticate", 0, TRUE); - crStopV; - } - - if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { - /* - * Defence against traffic analysis: we send a - * whole bunch of packets containing strings of - * different lengths. One of these strings is the - * password, in a SSH1_CMSG_AUTH_PASSWORD packet. - * The others are all random data in - * SSH1_MSG_IGNORE packets. This way a passive - * listener can't tell which is the password, and - * hence can't deduce the password length. - * - * Anybody with a password length greater than 16 - * bytes is going to have enough entropy in their - * password that a listener won't find it _that_ - * much help to know how long it is. So what we'll - * do is: - * - * - if password length < 16, we send 15 packets - * containing string lengths 1 through 15 - * - * - otherwise, we let N be the nearest multiple - * of 8 below the password length, and send 8 - * packets containing string lengths N through - * N+7. This won't obscure the order of - * magnitude of the password length, but it will - * introduce a bit of extra uncertainty. - * - * A few servers can't deal with SSH1_MSG_IGNORE, at - * least in this context. For these servers, we need - * an alternative defence. We make use of the fact - * that the password is interpreted as a C string: - * so we can append a NUL, then some random data. - * - * A few servers can deal with neither SSH1_MSG_IGNORE - * here _nor_ a padded password string. - * For these servers we are left with no defences - * against password length sniffing. - */ - if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) && - !(ssh->remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) { - /* - * The server can deal with SSH1_MSG_IGNORE, so - * we can use the primary defence. - */ - int bottom, top, pwlen, i; - - pwlen = strlen(s->cur_prompt->prompts[0]->result); - if (pwlen < 16) { - bottom = 0; /* zero length passwords are OK! :-) */ - top = 15; - } else { - bottom = pwlen & ~7; - top = bottom + 7; - } - - assert(pwlen >= bottom && pwlen <= top); - - for (i = bottom; i <= top; i++) { - if (i == pwlen) { - pkt = ssh_bpp_new_pktout(ssh->bpp, s->pwpkt_type); - put_stringz(pkt, s->cur_prompt->prompts[0]->result); - ssh_pkt_write(ssh, pkt); - } else { - strbuf *random_data = strbuf_new(); - for (j = 0; j < i; j++) - put_byte(random_data, random_byte()); - - pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_MSG_IGNORE); - put_stringsb(pkt, random_data); - ssh_pkt_write(ssh, pkt); - } - } - logevent("Sending password with camouflage packets"); - } - else if (!(ssh->remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) { - /* - * The server can't deal with SSH1_MSG_IGNORE - * but can deal with padded passwords, so we - * can use the secondary defence. - */ - strbuf *padded_pw = strbuf_new(); - - logevent("Sending length-padded password"); - pkt = ssh_bpp_new_pktout(ssh->bpp, s->pwpkt_type); - put_asciz(padded_pw, s->cur_prompt->prompts[0]->result); - do { - put_byte(padded_pw, random_byte()); - } while (padded_pw->len % 64 != 0); - put_stringsb(pkt, padded_pw); - ssh_pkt_write(ssh, pkt); - } else { - /* - * The server is believed unable to cope with - * any of our password camouflage methods. - */ - logevent("Sending unpadded password"); - pkt = ssh_bpp_new_pktout(ssh->bpp, s->pwpkt_type); - put_stringz(pkt, s->cur_prompt->prompts[0]->result); - ssh_pkt_write(ssh, pkt); - } - } else { - pkt = ssh_bpp_new_pktout(ssh->bpp, s->pwpkt_type); - put_stringz(pkt, s->cur_prompt->prompts[0]->result); - ssh_pkt_write(ssh, pkt); - } - logevent("Sent password"); - free_prompts(s->cur_prompt); - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh1_login)) != NULL); - if (pktin->type == SSH1_SMSG_FAILURE) { - if (flags & FLAG_VERBOSE) - c_write_str(ssh, "Access denied\r\n"); - logevent("Authentication refused"); - } else if (pktin->type != SSH1_SMSG_SUCCESS) { - bombout(("Strange packet received, type %d", pktin->type)); - crStopV; - } - } - - /* Clear up */ - if (s->publickey_blob) { - strbuf_free(s->publickey_blob); - sfree(s->publickey_comment); - } - - logevent("Authentication successful"); - - /* Set up for the next phase */ - { - int i; - for (i = 0; i < SSH_MAX_MSG; i++) - if (ssh->packet_dispatch[i] == ssh1_coro_wrapper_initial) - ssh->packet_dispatch[i] = ssh1_coro_wrapper_session; - ssh->current_user_input_fn = ssh1_connection_input; - queue_idempotent_callback(&ssh->user_input_consumer); - } - - crFinishV; -} - -static void ssh_channel_try_eof(struct ssh_channel *c) -{ - Ssh ssh = c->ssh; - PktOut *pktout; - assert(c->pending_eof); /* precondition for calling us */ - if (c->halfopen) - return; /* can't close: not even opened yet */ - if (ssh->version == 2 && bufchain_size(&c->v.v2.outbuffer) > 0) - return; /* can't send EOF: pending outgoing data */ - - c->pending_eof = FALSE; /* we're about to send it */ - if (ssh->version == 1) { - pktout = ssh_bpp_new_pktout(ssh->bpp, SSH1_MSG_CHANNEL_CLOSE); - put_uint32(pktout, c->remoteid); - ssh_pkt_write(ssh, pktout); - c->closes |= CLOSES_SENT_EOF; - } else { - pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_CHANNEL_EOF); - put_uint32(pktout, c->remoteid); - ssh2_pkt_send(ssh, pktout); - c->closes |= CLOSES_SENT_EOF; - ssh2_channel_check_close(c); - } -} - -static Conf *sshchannel_get_conf(SshChannel *sc) -{ - struct ssh_channel *c = FROMFIELD(sc, struct ssh_channel, sc); - Ssh ssh = c->ssh; - return ssh->conf; -} - -static void sshchannel_write_eof(SshChannel *sc) -{ - struct ssh_channel *c = FROMFIELD(sc, struct ssh_channel, sc); - Ssh ssh = c->ssh; - - if (ssh->state == SSH_STATE_CLOSED) - return; - - if (c->closes & CLOSES_SENT_EOF) - return; - - c->pending_eof = TRUE; - ssh_channel_try_eof(c); -} - -static void sshchannel_unclean_close(SshChannel *sc, const char *err) -{ - struct ssh_channel *c = FROMFIELD(sc, struct ssh_channel, sc); - Ssh ssh = c->ssh; - char *reason; - - if (ssh->state == SSH_STATE_CLOSED) - return; - - reason = dupprintf("due to local error: %s", err); - ssh_channel_close_local(c, reason); - sfree(reason); - c->pending_eof = FALSE; /* this will confuse a zombie channel */ - - ssh2_channel_check_close(c); -} - -static int sshchannel_write(SshChannel *sc, const void *buf, int len) -{ - struct ssh_channel *c = FROMFIELD(sc, struct ssh_channel, sc); - Ssh ssh = c->ssh; - - if (ssh->state == SSH_STATE_CLOSED) - return 0; - - return ssh_send_channel_data(c, buf, len); -} - -static void sshchannel_unthrottle(SshChannel *sc, int bufsize) -{ - struct ssh_channel *c = FROMFIELD(sc, struct ssh_channel, sc); - Ssh ssh = c->ssh; - - if (ssh->state == SSH_STATE_CLOSED) - return; - - ssh_channel_unthrottle(c, bufsize); -} - -static void ssh_queueing_handler(Ssh ssh, PktIn *pktin) -{ - struct queued_handler *qh = ssh->qhead; - - assert(qh != NULL); - - assert(pktin->type == qh->msg1 || pktin->type == qh->msg2); - - if (qh->msg1 > 0) { - assert(ssh->packet_dispatch[qh->msg1] == ssh_queueing_handler); - ssh->packet_dispatch[qh->msg1] = ssh->q_saved_handler1; - } - if (qh->msg2 > 0) { - assert(ssh->packet_dispatch[qh->msg2] == ssh_queueing_handler); - ssh->packet_dispatch[qh->msg2] = ssh->q_saved_handler2; - } - - if (qh->next) { - ssh->qhead = qh->next; - - if (ssh->qhead->msg1 > 0) { - ssh->q_saved_handler1 = ssh->packet_dispatch[ssh->qhead->msg1]; - ssh->packet_dispatch[ssh->qhead->msg1] = ssh_queueing_handler; - } - if (ssh->qhead->msg2 > 0) { - ssh->q_saved_handler2 = ssh->packet_dispatch[ssh->qhead->msg2]; - ssh->packet_dispatch[ssh->qhead->msg2] = ssh_queueing_handler; - } - } else { - ssh->qhead = ssh->qtail = NULL; - } - - qh->handler(ssh, pktin, qh->ctx); - - sfree(qh); -} - -static void ssh_queue_handler(Ssh ssh, int msg1, int msg2, - chandler_fn_t handler, void *ctx) -{ - struct queued_handler *qh; - - qh = snew(struct queued_handler); - qh->msg1 = msg1; - qh->msg2 = msg2; - qh->handler = handler; - qh->ctx = ctx; - qh->next = NULL; - - if (ssh->qtail == NULL) { - ssh->qhead = qh; - - if (qh->msg1 > 0) { - ssh->q_saved_handler1 = ssh->packet_dispatch[ssh->qhead->msg1]; - ssh->packet_dispatch[qh->msg1] = ssh_queueing_handler; - } - if (qh->msg2 > 0) { - ssh->q_saved_handler2 = ssh->packet_dispatch[ssh->qhead->msg2]; - ssh->packet_dispatch[qh->msg2] = ssh_queueing_handler; - } - } else { - ssh->qtail->next = qh; - } - ssh->qtail = qh; -} - -static void ssh_rportfwd_succfail(Ssh ssh, PktIn *pktin, void *ctx) -{ - struct ssh_rportfwd *rpf = (struct ssh_rportfwd *)ctx; - - if (pktin->type == (ssh->version == 1 ? SSH1_SMSG_SUCCESS : - SSH2_MSG_REQUEST_SUCCESS)) { - logeventf(ssh, "Remote port forwarding from %s enabled", - rpf->log_description); - } else { - logeventf(ssh, "Remote port forwarding from %s refused", - rpf->log_description); - - struct ssh_rportfwd *realpf = del234(ssh->rportfwds, rpf); - assert(realpf == rpf); - portfwdmgr_close(ssh->portfwdmgr, rpf->pfr); - free_rportfwd(rpf); - } -} - -/* Many of these vtable methods have the same names as wrapper macros - * in ssh.h, so parenthesise the names to inhibit macro expansion */ - -static struct ssh_rportfwd *(ssh_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); -static void (ssh_rportfwd_remove)( - ConnectionLayer *cl, struct ssh_rportfwd *rpf); -static SshChannel *(ssh_lportfwd_open)( - ConnectionLayer *cl, const char *hostname, int port, - const char *org, Channel *chan); -static struct X11FakeAuth *(ssh_add_sharing_x11_display)( - ConnectionLayer *cl, int authtype, ssh_sharing_connstate *share_cs, - share_channel *share_chan); -static void (ssh_remove_sharing_x11_display)(ConnectionLayer *cl, - struct X11FakeAuth *auth); -static void (ssh_send_packet_from_downstream)( - ConnectionLayer *cl, unsigned id, int type, - const void *pkt, int pktlen, const char *additional_log_text); -static unsigned (ssh_alloc_sharing_channel)( - ConnectionLayer *cl, ssh_sharing_connstate *connstate); -static void (ssh_delete_sharing_channel)( - ConnectionLayer *cl, unsigned localid); -static void (ssh_sharing_queue_global_request)( - ConnectionLayer *cl, ssh_sharing_connstate *share_ctx); -static int (ssh_agent_forwarding_permitted)(ConnectionLayer *cl); - -const struct ConnectionLayerVtable ssh_connlayer_vtable = { - ssh_rportfwd_alloc, - ssh_rportfwd_remove, - ssh_lportfwd_open, - ssh_add_sharing_x11_display, - ssh_remove_sharing_x11_display, - ssh_send_packet_from_downstream, - ssh_alloc_sharing_channel, - ssh_delete_sharing_channel, - ssh_sharing_queue_global_request, - ssh_agent_forwarding_permitted, -}; - -static struct ssh_rportfwd *(ssh_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) -{ - Ssh ssh = FROMFIELD(cl, struct ssh_tag, cl); - - /* - * Ensure the remote port forwardings tree exists. - */ - if (!ssh->rportfwds) { - if (ssh->version == 1) - ssh->rportfwds = newtree234(ssh_rportcmp_ssh1); - else - ssh->rportfwds = newtree234(ssh_rportcmp_ssh2); - } - - struct ssh_rportfwd *rpf = snew(struct ssh_rportfwd); - - rpf->shost = dupstr(shost); - rpf->sport = sport; - rpf->dhost = dupstr(dhost); - rpf->dport = dport; - rpf->addressfamily = addressfamily; - rpf->log_description = dupstr(log_description); - rpf->pfr = pfr; - rpf->share_ctx = share_ctx; - - if (add234(ssh->rportfwds, rpf) != rpf) { - free_rportfwd(rpf); - return NULL; - } - - if (!rpf->share_ctx) { - PktOut *pktout; - - if (ssh->version == 1) { - pktout = ssh_bpp_new_pktout( - ssh->bpp, SSH1_CMSG_PORT_FORWARD_REQUEST); - put_uint32(pktout, rpf->sport); - put_stringz(pktout, rpf->dhost); - put_uint32(pktout, rpf->dport); - ssh_pkt_write(ssh, pktout); - ssh_queue_handler(ssh, SSH1_SMSG_SUCCESS, - SSH1_SMSG_FAILURE, - ssh_rportfwd_succfail, rpf); - } else { - pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_GLOBAL_REQUEST); - put_stringz(pktout, "tcpip-forward"); - put_bool(pktout, 1); /* want reply */ - put_stringz(pktout, rpf->shost); - put_uint32(pktout, rpf->sport); - ssh2_pkt_send(ssh, pktout); - - ssh_queue_handler(ssh, SSH2_MSG_REQUEST_SUCCESS, - SSH2_MSG_REQUEST_FAILURE, - ssh_rportfwd_succfail, rpf); - } - } - - return rpf; -} - -static void (ssh_rportfwd_remove)( - ConnectionLayer *cl, struct ssh_rportfwd *rpf) -{ - Ssh ssh = FROMFIELD(cl, struct ssh_tag, cl); - if (ssh->version == 1) { - /* - * We cannot cancel listening ports on the server side in - * SSH-1! There's no message to support it. - */ - } else if (rpf->share_ctx) { - /* - * We don't manufacture a cancel-tcpip-forward message for - * remote port forwardings being removed on behalf of a - * downstream; we just pass through the one the downstream - * sent to us. - */ - } else { - PktOut *pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_GLOBAL_REQUEST); - put_stringz(pktout, "cancel-tcpip-forward"); - put_bool(pktout, 0); /* _don't_ want reply */ - put_stringz(pktout, rpf->shost); - put_uint32(pktout, rpf->sport); - ssh2_pkt_send(ssh, pktout); - } - - struct ssh_rportfwd *realpf = del234(ssh->rportfwds, rpf); - assert(realpf == rpf); - free_rportfwd(rpf); -} - -static void ssh_sharing_global_request_response(Ssh ssh, PktIn *pktin, - void *ctx) -{ - share_got_pkt_from_server(ctx, pktin->type, - BinarySource_UPCAST(pktin)->data, - BinarySource_UPCAST(pktin)->len); -} - -static void (ssh_sharing_queue_global_request)( - ConnectionLayer *cl, ssh_sharing_connstate *share_ctx) -{ - Ssh ssh = FROMFIELD(cl, struct ssh_tag, cl); - ssh_queue_handler(ssh, SSH2_MSG_REQUEST_SUCCESS, SSH2_MSG_REQUEST_FAILURE, - ssh_sharing_global_request_response, share_ctx); -} - -static void ssh1_smsg_stdout_stderr_data(Ssh ssh, PktIn *pktin) -{ - ptrlen string; - int bufsize; - - string = get_string(pktin); - if (get_err(pktin)) { - bombout(("Incoming terminal data packet was badly formed")); - return; - } - - bufsize = from_backend(ssh->frontend, pktin->type == SSH1_SMSG_STDERR_DATA, - string.ptr, string.len); - if (!ssh->v1_stdout_throttling && bufsize > SSH1_BUFFER_LIMIT) { - ssh->v1_stdout_throttling = 1; - ssh_throttle_conn(ssh, +1); - } -} - -static void ssh1_smsg_x11_open(Ssh ssh, PktIn *pktin) -{ - /* Remote side is trying to open a channel to talk to our - * X-Server. Give them back a local channel number. */ - struct ssh_channel *c; - PktOut *pkt; - int remoteid = get_uint32(pktin); - - logevent("Received X11 connect request"); - /* Refuse if X11 forwarding is disabled. */ - if (!ssh->X11_fwd_enabled) { - pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE); - put_uint32(pkt, remoteid); - ssh_pkt_write(ssh, pkt); - logevent("Rejected X11 connect request"); - } else { - c = snew(struct ssh_channel); - c->ssh = ssh; - - ssh_channel_init(c); - c->chan = x11_new_channel(ssh->x11authtree, &c->sc, NULL, -1, FALSE); - c->remoteid = remoteid; - c->halfopen = FALSE; - pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION); - put_uint32(pkt, c->remoteid); - put_uint32(pkt, c->localid); - ssh_pkt_write(ssh, pkt); - logevent("Opened X11 forward channel"); - } -} - -static void ssh1_smsg_agent_open(Ssh ssh, PktIn *pktin) -{ - /* Remote side is trying to open a channel to talk to our - * agent. Give them back a local channel number. */ - struct ssh_channel *c; - PktOut *pkt; - int remoteid = toint(get_uint32(pktin)); - - /* Refuse if agent forwarding is disabled. */ - if (!ssh->agentfwd_enabled) { - pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE); - put_uint32(pkt, remoteid); - ssh_pkt_write(ssh, pkt); - } else { - c = snew(struct ssh_channel); - c->ssh = ssh; - ssh_channel_init(c); - c->remoteid = remoteid; - c->halfopen = FALSE; - c->chan = agentf_new(&c->sc); - pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION); - put_uint32(pkt, c->remoteid); - put_uint32(pkt, c->localid); - ssh_pkt_write(ssh, pkt); - } -} - -static void ssh1_msg_port_open(Ssh ssh, PktIn *pktin) -{ - /* Remote side is trying to open a channel to talk to a - * forwarded port. Give them back a local channel number. */ - struct ssh_rportfwd pf, *pfp; - PktOut *pkt; - int remoteid; - int port; - ptrlen host; - char *err; - - remoteid = toint(get_uint32(pktin)); - host = get_string(pktin); - port = toint(get_uint32(pktin)); - - pf.dhost = mkstr(host); - pf.dport = port; - pfp = find234(ssh->rportfwds, &pf, NULL); - - if (pfp == NULL) { - logeventf(ssh, "Rejected remote port open request for %s:%d", - pf.dhost, port); - pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE); - put_uint32(pkt, remoteid); - ssh_pkt_write(ssh, pkt); - } else { - struct ssh_channel *c = snew(struct ssh_channel); - c->ssh = ssh; - - logeventf(ssh, "Received remote port open request for %s:%d", - pf.dhost, port); - err = portfwdmgr_connect(ssh->portfwdmgr, &c->chan, pf.dhost, port, - &c->sc, pfp->addressfamily); - if (err != NULL) { - logeventf(ssh, "Port open failed: %s", err); - sfree(err); - sfree(c); - pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE); - put_uint32(pkt, remoteid); - ssh_pkt_write(ssh, pkt); - } else { - ssh_channel_init(c); - c->remoteid = remoteid; - c->halfopen = FALSE; - pkt = ssh_bpp_new_pktout( - ssh->bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION); - put_uint32(pkt, c->remoteid); - put_uint32(pkt, c->localid); - ssh_pkt_write(ssh, pkt); - logevent("Forwarded port opened successfully"); - } - } - - sfree(pf.dhost); -} - -static void ssh1_msg_channel_open_confirmation(Ssh ssh, PktIn *pktin) -{ - struct ssh_channel *c; - - c = ssh_channel_msg(ssh, pktin); - chan_open_confirmation(c->chan); - c->remoteid = get_uint32(pktin); - c->halfopen = FALSE; - c->throttling_conn = 0; - - if (c && c->pending_eof) { - /* - * We have a pending close on this channel, - * which we decided on before the server acked - * the channel open. So now we know the - * remoteid, we can close it again. - */ - ssh_channel_try_eof(c); - } -} - -static void ssh1_msg_channel_open_failure(Ssh ssh, PktIn *pktin) -{ - struct ssh_channel *c; - - c = ssh_channel_msg(ssh, pktin); - chan_open_failed(c->chan, NULL); - chan_free(c->chan); - - del234(ssh->channels, c); - sfree(c); -} - -static void ssh1_msg_channel_close(Ssh ssh, PktIn *pktin) -{ - /* Remote side closes a channel. */ - struct ssh_channel *c; - - c = ssh_channel_msg(ssh, pktin); - if (c) { - - if (pktin->type == SSH1_MSG_CHANNEL_CLOSE) { - /* - * Received CHANNEL_CLOSE, which we translate into - * outgoing EOF. - */ - ssh_channel_got_eof(c); - } - - if (pktin->type == SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION && - !(c->closes & CLOSES_RCVD_CLOSE)) { - - if (!(c->closes & CLOSES_SENT_EOF)) { - bombout(("Received CHANNEL_CLOSE_CONFIRMATION for channel %u" - " for which we never sent CHANNEL_CLOSE\n", - c->localid)); - } - - c->closes |= CLOSES_RCVD_CLOSE; - } - - if (!((CLOSES_SENT_EOF | CLOSES_RCVD_EOF) & ~c->closes) && - !(c->closes & CLOSES_SENT_CLOSE)) { - PktOut *pkt = ssh_bpp_new_pktout( - ssh->bpp, SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION); - put_uint32(pkt, c->remoteid); - ssh_pkt_write(ssh, pkt); - c->closes |= CLOSES_SENT_CLOSE; - } - - if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) - ssh_channel_destroy(c); - } -} - -static int ssh_channel_data(struct ssh_channel *c, int is_stderr, - const void *data, int length) -{ - if (c->chan) - chan_send(c->chan, is_stderr, data, length); - return 0; -} - -static void ssh1_msg_channel_data(Ssh ssh, PktIn *pktin) -{ - /* Data sent down one of our channels. */ - ptrlen data; - struct ssh_channel *c; - - c = ssh_channel_msg(ssh, pktin); - data = get_string(pktin); - - if (c) { - int bufsize = ssh_channel_data(c, FALSE, data.ptr, data.len); - if (!c->throttling_conn && bufsize > SSH1_BUFFER_LIMIT) { - c->throttling_conn = 1; - ssh_throttle_conn(ssh, +1); - } - } -} - -static void ssh1_smsg_exit_status(Ssh ssh, PktIn *pktin) -{ - PktOut *pkt; - ssh->exitcode = get_uint32(pktin); - logeventf(ssh, "Server sent command exit status %d", ssh->exitcode); - pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_CMSG_EXIT_CONFIRMATION); - ssh_pkt_write(ssh, pkt); - /* - * In case `helpful' firewalls or proxies tack - * extra human-readable text on the end of the - * session which we might mistake for another - * encrypted packet, we close the session once - * we've sent EXIT_CONFIRMATION. - */ - ssh_disconnect(ssh, NULL, NULL, 0, TRUE); -} - -static int (ssh_agent_forwarding_permitted)(ConnectionLayer *cl) -{ - Ssh ssh = FROMFIELD(cl, struct ssh_tag, cl); - return conf_get_int(ssh->conf, CONF_agentfwd) && agent_exists(); -} - -static void do_ssh1_connection(void *vctx) -{ - Ssh ssh = (Ssh)vctx; - PktIn *pktin; - PktOut *pkt; - - crBegin(ssh->do_ssh1_connection_crstate); - - ssh->packet_dispatch[SSH1_SMSG_STDOUT_DATA] = - ssh->packet_dispatch[SSH1_SMSG_STDERR_DATA] = - ssh1_smsg_stdout_stderr_data; - - ssh->packet_dispatch[SSH1_MSG_CHANNEL_OPEN_CONFIRMATION] = - ssh1_msg_channel_open_confirmation; - ssh->packet_dispatch[SSH1_MSG_CHANNEL_OPEN_FAILURE] = - ssh1_msg_channel_open_failure; - ssh->packet_dispatch[SSH1_MSG_CHANNEL_CLOSE] = - ssh->packet_dispatch[SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION] = - ssh1_msg_channel_close; - ssh->packet_dispatch[SSH1_MSG_CHANNEL_DATA] = ssh1_msg_channel_data; - ssh->packet_dispatch[SSH1_SMSG_EXIT_STATUS] = ssh1_smsg_exit_status; - - if (ssh_agent_forwarding_permitted(&ssh->cl)) { - logevent("Requesting agent forwarding"); - pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_CMSG_AGENT_REQUEST_FORWARDING); - ssh_pkt_write(ssh, pkt); - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh1_connection)) != NULL); - if (pktin->type != SSH1_SMSG_SUCCESS - && pktin->type != SSH1_SMSG_FAILURE) { - bombout(("Protocol confusion")); - crStopV; - } else if (pktin->type == SSH1_SMSG_FAILURE) { - logevent("Agent forwarding refused"); - } else { - logevent("Agent forwarding enabled"); - ssh->agentfwd_enabled = TRUE; - ssh->packet_dispatch[SSH1_SMSG_AGENT_OPEN] = ssh1_smsg_agent_open; - } - } - - if (conf_get_int(ssh->conf, CONF_x11_forward)) { - ssh->x11disp = - x11_setup_display(conf_get_str(ssh->conf, CONF_x11_display), - ssh->conf); - if (!ssh->x11disp) { - /* FIXME: return an error message from x11_setup_display */ - logevent("X11 forwarding not enabled: unable to" - " initialise X display"); - } else { - ssh->x11auth = x11_invent_fake_auth - (ssh->x11authtree, conf_get_int(ssh->conf, CONF_x11_auth)); - ssh->x11auth->disp = ssh->x11disp; - - logevent("Requesting X11 forwarding"); - pkt = ssh_bpp_new_pktout( - ssh->bpp, SSH1_CMSG_X11_REQUEST_FORWARDING); - put_stringz(pkt, ssh->x11auth->protoname); - put_stringz(pkt, ssh->x11auth->datastring); - if (ssh->v1_local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) - put_uint32(pkt, ssh->x11disp->screennum); - ssh_pkt_write(ssh, pkt); - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh1_connection)) - != NULL); - if (pktin->type != SSH1_SMSG_SUCCESS - && pktin->type != SSH1_SMSG_FAILURE) { - bombout(("Protocol confusion")); - crStopV; - } else if (pktin->type == SSH1_SMSG_FAILURE) { - logevent("X11 forwarding refused"); - } else { - logevent("X11 forwarding enabled"); - ssh->X11_fwd_enabled = TRUE; - ssh->packet_dispatch[SSH1_SMSG_X11_OPEN] = ssh1_smsg_x11_open; - } - } - } - - portfwdmgr_config(ssh->portfwdmgr, ssh->conf); - ssh->packet_dispatch[SSH1_MSG_PORT_OPEN] = ssh1_msg_port_open; - - if (!conf_get_int(ssh->conf, CONF_nopty)) { - PktOut *pkt; - /* Unpick the terminal-speed string. */ - /* XXX perhaps we should allow no speeds to be sent. */ - ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */ - sscanf(conf_get_str(ssh->conf, CONF_termspeed), "%d,%d", &ssh->ospeed, &ssh->ispeed); - /* Send the pty request. */ - pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_CMSG_REQUEST_PTY); - put_stringz(pkt, conf_get_str(ssh->conf, CONF_termtype)); - put_uint32(pkt, ssh->term_height); - put_uint32(pkt, ssh->term_width); - put_uint32(pkt, 0); /* width in pixels */ - put_uint32(pkt, 0); /* height in pixels */ - write_ttymodes_to_packet_from_conf( - BinarySink_UPCAST(pkt), ssh->frontend, ssh->conf, - 1, ssh->ospeed, ssh->ispeed); - ssh_pkt_write(ssh, pkt); - ssh->state = SSH_STATE_INTERMED; - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh1_connection)) != NULL); - if (pktin->type != SSH1_SMSG_SUCCESS - && pktin->type != SSH1_SMSG_FAILURE) { - bombout(("Protocol confusion")); - crStopV; - } else if (pktin->type == SSH1_SMSG_FAILURE) { - c_write_str(ssh, "Server refused to allocate pty\r\n"); - ssh->editing = ssh->echoing = 1; - } else { - logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)", - ssh->ospeed, ssh->ispeed); - ssh->got_pty = TRUE; - } - } else { - ssh->editing = ssh->echoing = 1; - } - - if (conf_get_int(ssh->conf, CONF_compression)) { - pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_CMSG_REQUEST_COMPRESSION); - put_uint32(pkt, 6); /* gzip compression level */ - ssh_pkt_write(ssh, pkt); - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh1_connection)) != NULL); - if (pktin->type != SSH1_SMSG_SUCCESS - && pktin->type != SSH1_SMSG_FAILURE) { - bombout(("Protocol confusion")); - crStopV; - } else if (pktin->type == SSH1_SMSG_FAILURE) { - c_write_str(ssh, "Server refused to compress\r\n"); - } else { - /* - * We don't have to actually do anything here: the SSH-1 - * BPP will take care of automatically starting the - * compression, by recognising our outgoing request packet - * and the success response. (Horrible, but it's the - * easiest way to avoid race conditions if other packets - * cross in transit.) - */ - logevent("Started zlib (RFC1950) compression"); - } - } - - /* - * Start the shell or command. - * - * Special case: if the first-choice command is an SSH-2 - * subsystem (hence not usable here) and the second choice - * exists, we fall straight back to that. - */ - { - char *cmd = conf_get_str(ssh->conf, CONF_remote_cmd); - - if (conf_get_int(ssh->conf, CONF_ssh_subsys) && - conf_get_str(ssh->conf, CONF_remote_cmd2)) { - cmd = conf_get_str(ssh->conf, CONF_remote_cmd2); - ssh->fallback_cmd = TRUE; - } - if (*cmd) { - pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_CMSG_EXEC_CMD); - put_stringz(pkt, cmd); - ssh_pkt_write(ssh, pkt); - } else { - pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_CMSG_EXEC_SHELL); - ssh_pkt_write(ssh, pkt); - } - logevent("Started session"); - } - - ssh->state = SSH_STATE_SESSION; - if (ssh->size_needed) - backend_size(&ssh->backend, ssh->term_width, ssh->term_height); - if (ssh->eof_needed) - backend_special(&ssh->backend, SS_EOF, 0); - - if (ssh->ldisc) - ldisc_echoedit_update(ssh->ldisc); /* cause ldisc to notice changes */ - ssh->send_ok = 1; - ssh->channels = newtree234(ssh_channelcmp); - while (1) { - - /* - * By this point, most incoming packets are already being - * handled by the dispatch table, and we need only pay - * attention to the unusual ones. - */ - - while ((pktin = pq_pop(&ssh->pq_ssh1_connection)) != NULL) { - if (pktin->type == SSH1_SMSG_SUCCESS) { - /* may be from EXEC_SHELL on some servers */ - } else if (pktin->type == SSH1_SMSG_FAILURE) { - /* may be from EXEC_SHELL on some servers - * if no pty is available or in other odd cases. Ignore */ - } else { - bombout(("Strange packet received: type %d", pktin->type)); - crStopV; - } - } - while (bufchain_size(&ssh->user_input) > 0) { - void *data; - int len; - bufchain_prefix(&ssh->user_input, &data, &len); - if (len > 512) - len = 512; - pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_CMSG_STDIN_DATA); - put_string(pkt, data, len); - ssh_pkt_write(ssh, pkt); - bufchain_consume(&ssh->user_input, len); - } - crReturnV; - } - - crFinishV; -} - -/* - * Handle the top-level SSH-2 protocol. - */ -static void ssh1_msg_debug(Ssh ssh, PktIn *pktin) -{ - ptrlen msg = get_string(pktin); - logeventf(ssh, "Remote debug message: %.*s", PTRLEN_PRINTF(msg)); -} - -static void ssh1_msg_disconnect(Ssh ssh, PktIn *pktin) -{ - ptrlen msg = get_string(pktin); - bombout(("Server sent disconnect message:\n\"%.*s\"", PTRLEN_PRINTF(msg))); -} - -static void ssh_msg_ignore(Ssh ssh, PktIn *pktin) -{ - /* Do nothing, because we're ignoring it! Duhh. */ -} - -static void ssh1_login_input(Ssh ssh) -{ - do_ssh1_login(ssh); -} - -static void ssh1_connection_input(Ssh ssh) -{ - do_ssh1_connection(ssh); -} - -static void ssh1_coro_wrapper_initial(Ssh ssh, PktIn *pktin) -{ - pq_push(&ssh->pq_ssh1_login, pktin); - queue_idempotent_callback(&ssh->ssh1_login_icb); -} - -static void ssh1_coro_wrapper_session(Ssh ssh, PktIn *pktin) -{ - pq_push(&ssh->pq_ssh1_connection, pktin); - queue_idempotent_callback(&ssh->ssh1_connection_icb); -} - -static void ssh1_protocol_setup(Ssh ssh) -{ - int i; - - ssh->bpp = ssh1_bpp_new(); - - /* - * Most messages are handled by the main protocol routine. - */ - for (i = 0; i < SSH_MAX_MSG; i++) - ssh->packet_dispatch[i] = ssh1_coro_wrapper_initial; - - /* - * These special message types we install handlers for. - */ - ssh->packet_dispatch[SSH1_MSG_DISCONNECT] = ssh1_msg_disconnect; - ssh->packet_dispatch[SSH1_MSG_IGNORE] = ssh_msg_ignore; - ssh->packet_dispatch[SSH1_MSG_DEBUG] = ssh1_msg_debug; -} - -/* - * SSH-2 key derivation (RFC 4253 section 7.2). - */ -static void ssh2_mkkey(Ssh ssh, strbuf *out, Bignum K, unsigned char *H, - char chr, int keylen) -{ - const struct ssh_hashalg *h = ssh->kex->hash; - int keylen_padded; - unsigned char *key; - ssh_hash *s, *s2; - - if (keylen == 0) - return; - - /* - * Round the requested amount of key material up to a multiple of - * the length of the hash we're using to make it. This makes life - * simpler because then we can just write each hash output block - * straight into the output buffer without fiddling about - * truncating the last one. Since it's going into a strbuf, and - * strbufs are always smemclr()ed on free, there's no need to - * worry about leaving extra potentially-sensitive data in memory - * that the caller didn't ask for. - */ - keylen_padded = ((keylen + h->hlen - 1) / h->hlen) * h->hlen; - - out->len = 0; - key = strbuf_append(out, keylen_padded); - - /* First hlen bytes. */ - s = ssh_hash_new(h); - if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY)) - put_mp_ssh2(s, K); - put_data(s, H, h->hlen); - put_byte(s, chr); - put_data(s, ssh->v2_session_id, ssh->v2_session_id_len); - ssh_hash_final(s, key); - - /* Subsequent blocks of hlen bytes. */ - if (keylen_padded > h->hlen) { - int offset; - - s = ssh_hash_new(h); - if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY)) - put_mp_ssh2(s, K); - put_data(s, H, h->hlen); - - for (offset = h->hlen; offset < keylen_padded; offset += h->hlen) { - put_data(s, key + offset - h->hlen, h->hlen); - s2 = ssh_hash_copy(s); - ssh_hash_final(s2, key + offset); - } - - ssh_hash_free(s); - } -} - -/* - * Structure for constructing KEXINIT algorithm lists. - */ -#define MAXKEXLIST 16 -struct kexinit_algorithm { - const char *name; - union { - struct { - const struct ssh_kex *kex; - int warn; - } kex; - struct { - const ssh_keyalg *hostkey; - int warn; - } hk; - struct { - const struct ssh2_cipheralg *cipher; - int warn; - } cipher; - struct { - const struct ssh2_macalg *mac; - int etm; - } mac; - const struct ssh_compression_alg *comp; - } u; -}; - -/* - * Find a slot in a KEXINIT algorithm list to use for a new algorithm. - * If the algorithm is already in the list, return a pointer to its - * entry, otherwise return an entry from the end of the list. - * This assumes that every time a particular name is passed in, it - * comes from the same string constant. If this isn't true, this - * function may need to be rewritten to use strcmp() instead. - */ -static struct kexinit_algorithm *ssh2_kexinit_addalg(struct kexinit_algorithm - *list, const char *name) -{ - int i; - - for (i = 0; i < MAXKEXLIST; i++) - if (list[i].name == NULL || list[i].name == name) { - list[i].name = name; - return &list[i]; - } - assert(!"No space in KEXINIT list"); - return NULL; -} - -#ifndef NO_GSSAPI -/* - * Data structure managing host keys in sessions based on GSSAPI KEX. - * - * In a session we started with a GSSAPI key exchange, the concept of - * 'host key' has completely different lifetime and security semantics - * from the usual ones. Per RFC 4462 section 2.1, we assume that any - * host key delivered to us in the course of a GSSAPI key exchange is - * _solely_ there to use as a transient fallback within the same - * session, if at the time of a subsequent rekey the GSS credentials - * are temporarily invalid and so a non-GSS KEX method has to be used. - * - * In particular, in a GSS-based SSH deployment, host keys may not - * even _be_ persistent identities for the server; it would be - * legitimate for a server to generate a fresh one routinely if it - * wanted to, like SSH-1 server keys. - * - * So, in this mode, we never touch the persistent host key cache at - * all, either to check keys against it _or_ to store keys in it. - * Instead, we maintain an in-memory cache of host keys that have been - * mentioned in GSS key exchanges within this particular session, and - * we permit precisely those host keys in non-GSS rekeys. - */ -struct ssh_transient_hostkey_cache_entry { - const ssh_keyalg *alg; - strbuf *pub_blob; -}; - -static int ssh_transient_hostkey_cache_cmp(void *av, void *bv) -{ - const struct ssh_transient_hostkey_cache_entry - *a = (const struct ssh_transient_hostkey_cache_entry *)av, - *b = (const struct ssh_transient_hostkey_cache_entry *)bv; - return strcmp(a->alg->ssh_id, b->alg->ssh_id); -} - -static int ssh_transient_hostkey_cache_find(void *av, void *bv) -{ - const ssh_keyalg *aalg = (const ssh_keyalg *)av; - const struct ssh_transient_hostkey_cache_entry - *b = (const struct ssh_transient_hostkey_cache_entry *)bv; - return strcmp(aalg->ssh_id, b->alg->ssh_id); -} - -static void ssh_init_transient_hostkey_store(Ssh ssh) -{ - ssh->transient_hostkey_cache = - newtree234(ssh_transient_hostkey_cache_cmp); -} - -static void ssh_cleanup_transient_hostkey_store(Ssh ssh) -{ - struct ssh_transient_hostkey_cache_entry *ent; - while ((ent = delpos234(ssh->transient_hostkey_cache, 0)) != NULL) { - strbuf_free(ent->pub_blob); - sfree(ent); - } - freetree234(ssh->transient_hostkey_cache); -} - -static void ssh_store_transient_hostkey(Ssh ssh, ssh_key *key) -{ - struct ssh_transient_hostkey_cache_entry *ent, *retd; - - if ((ent = find234(ssh->transient_hostkey_cache, (void *)ssh_key_alg(key), - ssh_transient_hostkey_cache_find)) != NULL) { - strbuf_free(ent->pub_blob); - sfree(ent); - } - - ent = snew(struct ssh_transient_hostkey_cache_entry); - ent->alg = ssh_key_alg(key); - ent->pub_blob = strbuf_new(); - ssh_key_public_blob(key, BinarySink_UPCAST(ent->pub_blob)); - retd = add234(ssh->transient_hostkey_cache, ent); - assert(retd == ent); -} - -static int ssh_verify_transient_hostkey(Ssh ssh, ssh_key *key) -{ - struct ssh_transient_hostkey_cache_entry *ent; - int toret = FALSE; - - if ((ent = find234(ssh->transient_hostkey_cache, (void *)ssh_key_alg(key), - ssh_transient_hostkey_cache_find)) != NULL) { - strbuf *this_blob = strbuf_new(); - ssh_key_public_blob(key, BinarySink_UPCAST(this_blob)); - - if (this_blob->len == ent->pub_blob->len && - !memcmp(this_blob->s, ent->pub_blob->s, - this_blob->len)) - toret = TRUE; - - strbuf_free(this_blob); - } - - return toret; -} - -static int ssh_have_transient_hostkey(Ssh ssh, const ssh_keyalg *alg) -{ - struct ssh_transient_hostkey_cache_entry *ent = - find234(ssh->transient_hostkey_cache, (void *)alg, - ssh_transient_hostkey_cache_find); - return ent != NULL; -} - -static int ssh_have_any_transient_hostkey(Ssh ssh) -{ - return count234(ssh->transient_hostkey_cache) > 0; -} - -#endif /* NO_GSSAPI */ - -/* - * Handle the SSH-2 transport layer. - */ -static void do_ssh2_transport(void *vctx) -{ - Ssh ssh = (Ssh)vctx; - PktIn *pktin; - - enum kexlist { - KEXLIST_KEX, KEXLIST_HOSTKEY, KEXLIST_CSCIPHER, KEXLIST_SCCIPHER, - KEXLIST_CSMAC, KEXLIST_SCMAC, KEXLIST_CSCOMP, KEXLIST_SCCOMP, - NKEXLIST - }; - const char * kexlist_descr[NKEXLIST] = { - "key exchange algorithm", "host key algorithm", - "client-to-server cipher", "server-to-client cipher", - "client-to-server MAC", "server-to-client MAC", - "client-to-server compression method", - "server-to-client compression method" }; - struct do_ssh2_transport_state { - int crLine; - int nbits, pbits, warn_kex, warn_hk, warn_cscipher, warn_sccipher; - Bignum p, g, e, f, K; - void *our_kexinit; - int our_kexinitlen; - int kex_init_value, kex_reply_value; - const struct ssh2_macalg *const *maclist; - int nmacs; - struct { - const struct ssh2_cipheralg *cipher; - const struct ssh2_macalg *mac; - int etm_mode; - const struct ssh_compression_alg *comp; - } in, out; - ptrlen hostkeydata, sigdata; - char *keystr, *fingerprint; - ssh_key *hkey; /* actual host key */ - struct RSAKey *rsakey; /* for RSA kex */ - struct ec_key *eckey; /* for ECDH kex */ - unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN]; - int n_preferred_kex; - int can_gssapi_keyex; - int need_gss_transient_hostkey; - int warned_about_no_gss_transient_hostkey; - const struct ssh_kexes *preferred_kex[KEX_MAX + 1]; /* +1 for GSSAPI */ - int n_preferred_hk; - int preferred_hk[HK_MAX]; - int n_preferred_ciphers; - const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX]; - const struct ssh_compression_alg *preferred_comp; - int userauth_succeeded; /* for delayed compression */ - int pending_compression; - int got_session_id; - PktOut *pktout; - int dlgret; - int guessok; - int ignorepkt; - struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST]; -#ifndef NO_GSSAPI - Ssh_gss_buf gss_buf; - Ssh_gss_buf gss_rcvtok, gss_sndtok; - Ssh_gss_stat gss_stat; - Ssh_gss_ctx gss_ctx; - Ssh_gss_buf mic; - int init_token_sent; - int complete_rcvd; - int gss_delegate; - time_t gss_cred_expiry; -#endif - }; - crState(do_ssh2_transport_state); - - assert(!ssh->bare_connection); - assert(ssh->version == 2); - - crBeginState; - - s->in.cipher = s->out.cipher = NULL; - s->in.mac = s->out.mac = NULL; - s->in.comp = s->out.comp = NULL; - - s->got_session_id = FALSE; - s->userauth_succeeded = FALSE; - s->pending_compression = FALSE; - s->need_gss_transient_hostkey = FALSE; - s->warned_about_no_gss_transient_hostkey = FALSE; - - /* - * Be prepared to work around the buggy MAC problem. - */ - if (ssh->remote_bugs & BUG_SSH2_HMAC) - s->maclist = buggymacs, s->nmacs = lenof(buggymacs); - else - s->maclist = macs, s->nmacs = lenof(macs); - - begin_key_exchange: - -#ifndef NO_GSSAPI - if (s->need_gss_transient_hostkey) { - /* - * This flag indicates a special case in which we must not do - * GSS key exchange even if we could. (See comments below, - * where the flag was set on the previous key exchange.) - */ - s->can_gssapi_keyex = FALSE; - } else if (conf_get_int(ssh->conf, CONF_try_gssapi_kex)) { - /* - * We always check if we have GSS creds before we come up with - * the kex algorithm list, otherwise future rekeys will fail - * when creds expire. To make this so, this code section must - * follow the begin_key_exchange label above, otherwise this - * section would execute just once per-connection. - * - * Update GSS state unless the reason we're here is that a - * timer just checked the GSS state and decided that we should - * rekey to update delegated credentials. In that case, the - * state is "fresh". - */ - if (ssh->rekey_class != RK_GSS_UPDATE) - ssh2_gss_update(ssh, TRUE); - - /* Do GSSAPI KEX when capable */ - s->can_gssapi_keyex = ssh->gss_status & GSS_KEX_CAPABLE; - - /* - * But not when failure is likely. [ GSS implementations may - * attempt (and fail) to use a ticket that is almost expired - * when retrieved from the ccache that actually expires by the - * time the server receives it. ] - * - * Note: The first time always try KEXGSS if we can, failures - * will be very rare, and disabling the initial GSS KEX is - * worse. Some day GSS libraries will ignore cached tickets - * whose lifetime is critically short, and will instead use - * fresh ones. - */ - if (!s->got_session_id && (ssh->gss_status & GSS_CTXT_MAYFAIL) != 0) - s->can_gssapi_keyex = 0; - s->gss_delegate = conf_get_int(ssh->conf, CONF_gssapifwd); - } else { - s->can_gssapi_keyex = FALSE; - } -#endif - - ssh->pls.kctx = SSH2_PKTCTX_NOKEX; - { - int i, j, k, warn; - struct kexinit_algorithm *alg; - - /* - * Set up the preferred key exchange. (NULL => warn below here) - */ - s->n_preferred_kex = 0; - if (s->can_gssapi_keyex) - s->preferred_kex[s->n_preferred_kex++] = &ssh_gssk5_sha1_kex; - for (i = 0; i < KEX_MAX; i++) { - switch (conf_get_int_int(ssh->conf, CONF_ssh_kexlist, i)) { - case KEX_DHGEX: - s->preferred_kex[s->n_preferred_kex++] = - &ssh_diffiehellman_gex; - break; - case KEX_DHGROUP14: - s->preferred_kex[s->n_preferred_kex++] = - &ssh_diffiehellman_group14; - break; - case KEX_DHGROUP1: - s->preferred_kex[s->n_preferred_kex++] = - &ssh_diffiehellman_group1; - break; - case KEX_RSA: - s->preferred_kex[s->n_preferred_kex++] = - &ssh_rsa_kex; - break; - case KEX_ECDH: - s->preferred_kex[s->n_preferred_kex++] = - &ssh_ecdh_kex; - break; - case KEX_WARN: - /* Flag for later. Don't bother if it's the last in - * the list. */ - if (i < KEX_MAX - 1) { - s->preferred_kex[s->n_preferred_kex++] = NULL; - } - break; - } - } - - /* - * Set up the preferred host key types. These are just the ids - * in the enum in putty.h, so 'warn below here' is indicated - * by HK_WARN. - */ - s->n_preferred_hk = 0; - for (i = 0; i < HK_MAX; i++) { - int id = conf_get_int_int(ssh->conf, CONF_ssh_hklist, i); - /* As above, don't bother with HK_WARN if it's last in the - * list */ - if (id != HK_WARN || i < HK_MAX - 1) - s->preferred_hk[s->n_preferred_hk++] = id; - } - - /* - * Set up the preferred ciphers. (NULL => warn below here) - */ - s->n_preferred_ciphers = 0; - for (i = 0; i < CIPHER_MAX; i++) { - switch (conf_get_int_int(ssh->conf, CONF_ssh_cipherlist, i)) { - case CIPHER_BLOWFISH: - s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_blowfish; - break; - case CIPHER_DES: - if (conf_get_int(ssh->conf, CONF_ssh2_des_cbc)) { - s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_des; - } - break; - case CIPHER_3DES: - s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_3des; - break; - case CIPHER_AES: - s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_aes; - break; - case CIPHER_ARCFOUR: - s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_arcfour; - break; - case CIPHER_CHACHA20: - s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_ccp; - break; - case CIPHER_WARN: - /* Flag for later. Don't bother if it's the last in - * the list. */ - if (i < CIPHER_MAX - 1) { - s->preferred_ciphers[s->n_preferred_ciphers++] = NULL; - } - break; - } - } - - /* - * Set up preferred compression. - */ - if (conf_get_int(ssh->conf, CONF_compression)) - s->preferred_comp = &ssh_zlib; - else - s->preferred_comp = &ssh_comp_none; - - /* - * Enable queueing of outgoing auth- or connection-layer - * packets while we are in the middle of a key exchange. - */ - ssh->queueing = TRUE; - - /* - * Flag that KEX is in progress. - */ - ssh->kex_in_progress = TRUE; - - for (i = 0; i < NKEXLIST; i++) - for (j = 0; j < MAXKEXLIST; j++) - s->kexlists[i][j].name = NULL; - /* List key exchange algorithms. */ - warn = FALSE; - for (i = 0; i < s->n_preferred_kex; i++) { - const struct ssh_kexes *k = s->preferred_kex[i]; - if (!k) warn = TRUE; - else for (j = 0; j < k->nkexes; j++) { - alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_KEX], - k->list[j]->name); - alg->u.kex.kex = k->list[j]; - alg->u.kex.warn = warn; - } - } - /* List server host key algorithms. */ - if (!s->got_session_id) { - /* - * In the first key exchange, we list all the algorithms - * we're prepared to cope with, but prefer those algorithms - * for which we have a host key for this host. - * - * If the host key algorithm is below the warning - * threshold, we warn even if we did already have a key - * for it, on the basis that if the user has just - * reconfigured that host key type to be warned about, - * they surely _do_ want to be alerted that a server - * they're actually connecting to is using it. - */ - warn = FALSE; - for (i = 0; i < s->n_preferred_hk; i++) { - if (s->preferred_hk[i] == HK_WARN) - warn = TRUE; - for (j = 0; j < lenof(hostkey_algs); j++) { - if (hostkey_algs[j].id != s->preferred_hk[i]) - continue; - if (have_ssh_host_key(ssh->savedhost, ssh->savedport, - hostkey_algs[j].alg->cache_id)) { - alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY], - hostkey_algs[j].alg->ssh_id); - alg->u.hk.hostkey = hostkey_algs[j].alg; - alg->u.hk.warn = warn; - } - } - } - warn = FALSE; - for (i = 0; i < s->n_preferred_hk; i++) { - if (s->preferred_hk[i] == HK_WARN) - warn = TRUE; - for (j = 0; j < lenof(hostkey_algs); j++) { - if (hostkey_algs[j].id != s->preferred_hk[i]) - continue; - alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY], - hostkey_algs[j].alg->ssh_id); - alg->u.hk.hostkey = hostkey_algs[j].alg; - alg->u.hk.warn = warn; - } - } -#ifndef NO_GSSAPI - } else if (ssh->gss_kex_used && !s->need_gss_transient_hostkey) { - /* - * If we've previously done a GSSAPI KEX, then we list - * precisely the algorithms for which a previous GSS key - * exchange has delivered us a host key, because we expect - * one of exactly those keys to be used in any subsequent - * non-GSS-based rekey. - * - * An exception is if this is the key exchange we - * triggered for the purposes of populating that cache - - * in which case the cache will currently be empty, which - * isn't helpful! - */ - warn = FALSE; - for (i = 0; i < s->n_preferred_hk; i++) { - if (s->preferred_hk[i] == HK_WARN) - warn = TRUE; - for (j = 0; j < lenof(hostkey_algs); j++) { - if (hostkey_algs[j].id != s->preferred_hk[i]) - continue; - if (ssh_have_transient_hostkey(ssh, hostkey_algs[j].alg)) { - alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY], - hostkey_algs[j].alg->ssh_id); - alg->u.hk.hostkey = hostkey_algs[j].alg; - alg->u.hk.warn = warn; - } - } - } -#endif - } else { - /* - * In subsequent key exchanges, we list only the kex - * algorithm that was selected in the first key exchange, - * so that we keep getting the same host key and hence - * don't have to interrupt the user's session to ask for - * reverification. - */ - assert(ssh->kex); - alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY], - ssh->hostkey_alg->ssh_id); - alg->u.hk.hostkey = ssh->hostkey_alg; - alg->u.hk.warn = FALSE; - } - if (s->can_gssapi_keyex) { - alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY], "null"); - alg->u.hk.hostkey = NULL; - } - /* List encryption algorithms (client->server then server->client). */ - for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) { - warn = FALSE; -#ifdef FUZZING - alg = ssh2_kexinit_addalg(s->kexlists[k], "none"); - alg->u.cipher.cipher = NULL; - alg->u.cipher.warn = warn; -#endif /* FUZZING */ - for (i = 0; i < s->n_preferred_ciphers; i++) { - const struct ssh2_ciphers *c = s->preferred_ciphers[i]; - if (!c) warn = TRUE; - else for (j = 0; j < c->nciphers; j++) { - alg = ssh2_kexinit_addalg(s->kexlists[k], - c->list[j]->name); - alg->u.cipher.cipher = c->list[j]; - alg->u.cipher.warn = warn; - } - } - } - /* List MAC algorithms (client->server then server->client). */ - for (j = KEXLIST_CSMAC; j <= KEXLIST_SCMAC; j++) { -#ifdef FUZZING - alg = ssh2_kexinit_addalg(s->kexlists[j], "none"); - alg->u.mac.mac = NULL; - alg->u.mac.etm = FALSE; -#endif /* FUZZING */ - for (i = 0; i < s->nmacs; i++) { - alg = ssh2_kexinit_addalg(s->kexlists[j], s->maclist[i]->name); - alg->u.mac.mac = s->maclist[i]; - alg->u.mac.etm = FALSE; - } - for (i = 0; i < s->nmacs; i++) - /* For each MAC, there may also be an ETM version, - * which we list second. */ - if (s->maclist[i]->etm_name) { - alg = ssh2_kexinit_addalg(s->kexlists[j], - s->maclist[i]->etm_name); - alg->u.mac.mac = s->maclist[i]; - alg->u.mac.etm = TRUE; - } - } - /* List client->server compression algorithms, - * then server->client compression algorithms. (We use the - * same set twice.) */ - for (j = KEXLIST_CSCOMP; j <= KEXLIST_SCCOMP; j++) { - assert(lenof(compressions) > 1); - /* Prefer non-delayed versions */ - alg = ssh2_kexinit_addalg(s->kexlists[j], s->preferred_comp->name); - alg->u.comp = s->preferred_comp; - /* We don't even list delayed versions of algorithms until - * they're allowed to be used, to avoid a race. See the end of - * this function. */ - if (s->userauth_succeeded && s->preferred_comp->delayed_name) { - alg = ssh2_kexinit_addalg(s->kexlists[j], - s->preferred_comp->delayed_name); - alg->u.comp = s->preferred_comp; - } - for (i = 0; i < lenof(compressions); i++) { - const struct ssh_compression_alg *c = compressions[i]; - alg = ssh2_kexinit_addalg(s->kexlists[j], c->name); - alg->u.comp = c; - if (s->userauth_succeeded && c->delayed_name) { - alg = ssh2_kexinit_addalg(s->kexlists[j], c->delayed_name); - alg->u.comp = c; - } - } - } - /* - * Construct and send our key exchange packet. - */ - s->pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_KEXINIT); - for (i = 0; i < 16; i++) - put_byte(s->pktout, (unsigned char) random_byte()); - for (i = 0; i < NKEXLIST; i++) { - strbuf *list = strbuf_new(); - for (j = 0; j < MAXKEXLIST; j++) { - if (s->kexlists[i][j].name == NULL) break; - add_to_commasep(list, s->kexlists[i][j].name); - } - put_stringsb(s->pktout, list); - } - /* List client->server languages. Empty list. */ - put_stringz(s->pktout, ""); - /* List server->client languages. Empty list. */ - put_stringz(s->pktout, ""); - /* First KEX packet does _not_ follow, because we're not that brave. */ - put_bool(s->pktout, FALSE); - /* Reserved. */ - put_uint32(s->pktout, 0); - } - - s->our_kexinitlen = s->pktout->length - 5; - s->our_kexinit = snewn(s->our_kexinitlen, unsigned char); - memcpy(s->our_kexinit, s->pktout->data + 5, s->our_kexinitlen); - - ssh_pkt_write(ssh, s->pktout); - - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_transport)) != NULL); - - /* - * Now examine the other side's KEXINIT to see what we're up - * to. - */ - { - ptrlen str; - int i, j; - - if (pktin->type != SSH2_MSG_KEXINIT) { - bombout(("expected key exchange packet from server")); - crStopV; - } - ssh->kex = NULL; - ssh->hostkey_alg = NULL; - s->in.cipher = s->out.cipher = NULL; - s->in.mac = s->out.mac = NULL; - s->in.comp = s->out.comp = NULL; - s->warn_kex = s->warn_hk = FALSE; - s->warn_cscipher = s->warn_sccipher = FALSE; - - get_data(pktin, 16); /* skip garbage cookie */ - - s->guessok = FALSE; - for (i = 0; i < NKEXLIST; i++) { - str = get_string(pktin); - if (get_err(pktin)) { - bombout(("KEXINIT packet was incomplete")); - crStopV; - } - - /* If we've already selected a cipher which requires a - * particular MAC, then just select that, and don't even - * bother looking through the server's KEXINIT string for - * MACs. */ - if (i == KEXLIST_CSMAC && s->out.cipher && - s->out.cipher->required_mac) { - s->out.mac = s->out.cipher->required_mac; - s->out.etm_mode = !!(s->out.mac->etm_name); - goto matched; - } - if (i == KEXLIST_SCMAC && s->in.cipher && - s->in.cipher->required_mac) { - s->in.mac = s->in.cipher->required_mac; - s->in.etm_mode = !!(s->in.mac->etm_name); - goto matched; - } - - for (j = 0; j < MAXKEXLIST; j++) { - struct kexinit_algorithm *alg = &s->kexlists[i][j]; - if (alg->name == NULL) break; - if (in_commasep_string(alg->name, str.ptr, str.len)) { - /* We've found a matching algorithm. */ - if (i == KEXLIST_KEX || i == KEXLIST_HOSTKEY) { - /* Check if we might need to ignore first kex pkt */ - if (j != 0 || - !first_in_commasep_string(alg->name, - str.ptr, str.len)) - s->guessok = FALSE; - } - if (i == KEXLIST_KEX) { - ssh->kex = alg->u.kex.kex; - s->warn_kex = alg->u.kex.warn; - } else if (i == KEXLIST_HOSTKEY) { - /* - * Ignore an unexpected/inappropriate offer of "null", - * we offer "null" when we're willing to use GSS KEX, - * but it is only acceptable when GSSKEX is actually - * selected. - */ - if (alg->u.hk.hostkey == NULL && - ssh->kex->main_type != KEXTYPE_GSS) - continue; - ssh->hostkey_alg = alg->u.hk.hostkey; - s->warn_hk = alg->u.hk.warn; - } else if (i == KEXLIST_CSCIPHER) { - s->out.cipher = alg->u.cipher.cipher; - s->warn_cscipher = alg->u.cipher.warn; - } else if (i == KEXLIST_SCCIPHER) { - s->in.cipher = alg->u.cipher.cipher; - s->warn_sccipher = alg->u.cipher.warn; - } else if (i == KEXLIST_CSMAC) { - s->out.mac = alg->u.mac.mac; - s->out.etm_mode = alg->u.mac.etm; - } else if (i == KEXLIST_SCMAC) { - s->in.mac = alg->u.mac.mac; - s->in.etm_mode = alg->u.mac.etm; - } else if (i == KEXLIST_CSCOMP) { - s->out.comp = alg->u.comp; - } else if (i == KEXLIST_SCCOMP) { - s->in.comp = alg->u.comp; - } - goto matched; - } - if ((i == KEXLIST_CSCOMP || i == KEXLIST_SCCOMP) && - in_commasep_string(alg->u.comp->delayed_name, - str.ptr, str.len) && - !s->userauth_succeeded) - s->pending_compression = TRUE; /* try this later */ - } - bombout(("Couldn't agree a %s (available: %.*s)", - kexlist_descr[i], PTRLEN_PRINTF(str))); - crStopV; - matched:; - - if (i == KEXLIST_HOSTKEY && - !ssh->gss_kex_used && - ssh->kex->main_type != KEXTYPE_GSS) { - int j; - - /* - * In addition to deciding which host key we're - * actually going to use, we should make a list of the - * host keys offered by the server which we _don't_ - * have cached. These will be offered as cross- - * certification options by ssh_get_specials. - * - * We also count the key we're currently using for KEX - * as one we've already got, because by the time this - * menu becomes visible, it will be. - */ - ssh->n_uncert_hostkeys = 0; - - for (j = 0; j < lenof(hostkey_algs); j++) { - if (hostkey_algs[j].alg != ssh->hostkey_alg && - in_commasep_string(hostkey_algs[j].alg->ssh_id, - str.ptr, str.len) && - !have_ssh_host_key(ssh->savedhost, ssh->savedport, - hostkey_algs[j].alg->cache_id)) { - ssh->uncert_hostkeys[ssh->n_uncert_hostkeys++] = j; - } - } - } - } - - if (s->pending_compression) { - logevent("Server supports delayed compression; " - "will try this later"); - } - get_string(pktin); /* client->server language */ - get_string(pktin); /* server->client language */ - s->ignorepkt = get_bool(pktin) && !s->guessok; - - ssh->exhash = ssh_hash_new(ssh->kex->hash); - put_stringz(ssh->exhash, ssh->v_c); - put_stringz(ssh->exhash, ssh->v_s); - put_string(ssh->exhash, s->our_kexinit, s->our_kexinitlen); - sfree(s->our_kexinit); - /* Include the type byte in the hash of server's KEXINIT */ - put_string(ssh->exhash, - (const char *)BinarySource_UPCAST(pktin)->data - 1, - BinarySource_UPCAST(pktin)->len + 1); - - if (s->warn_kex) { - s->dlgret = askalg(ssh->frontend, "key-exchange algorithm", - ssh->kex->name, - ssh_dialog_callback, ssh); - if (s->dlgret < 0) { - ssh->user_response = -1; - crWaitUntilV(ssh->user_response >= 0); - s->dlgret = ssh->user_response; - } - if (s->dlgret == 0) { - ssh_disconnect(ssh, "User aborted at kex warning", NULL, - 0, TRUE); - crStopV; - } - } - - if (s->warn_hk) { - int j, k; - char *betteralgs; - - /* - * Change warning box wording depending on why we chose a - * warning-level host key algorithm. If it's because - * that's all we have *cached*, use the askhk mechanism, - * and list the host keys we could usefully cross-certify. - * Otherwise, use askalg for the standard wording. - */ - betteralgs = NULL; - for (j = 0; j < ssh->n_uncert_hostkeys; j++) { - const struct ssh_signkey_with_user_pref_id *hktype = - &hostkey_algs[ssh->uncert_hostkeys[j]]; - int better = FALSE; - for (k = 0; k < HK_MAX; k++) { - int id = conf_get_int_int(ssh->conf, CONF_ssh_hklist, k); - if (id == HK_WARN) { - break; - } else if (id == hktype->id) { - better = TRUE; - break; - } - } - if (better) { - if (betteralgs) { - char *old_ba = betteralgs; - betteralgs = dupcat(betteralgs, ",", - hktype->alg->ssh_id, - (const char *)NULL); - sfree(old_ba); - } else { - betteralgs = dupstr(hktype->alg->ssh_id); - } - } - } - if (betteralgs) { - s->dlgret = askhk(ssh->frontend, ssh->hostkey_alg->ssh_id, - betteralgs, ssh_dialog_callback, ssh); - sfree(betteralgs); - } else { - s->dlgret = askalg(ssh->frontend, "host key type", - ssh->hostkey_alg->ssh_id, - ssh_dialog_callback, ssh); - } - if (s->dlgret < 0) { - ssh->user_response = -1; - crWaitUntilV(ssh->user_response >= 0); - s->dlgret = ssh->user_response; - } - if (s->dlgret == 0) { - ssh_disconnect(ssh, "User aborted at host key warning", NULL, - 0, TRUE); - crStopV; - } - } - - if (s->warn_cscipher) { - s->dlgret = askalg(ssh->frontend, - "client-to-server cipher", - s->out.cipher->name, - ssh_dialog_callback, ssh); - if (s->dlgret < 0) { - ssh->user_response = -1; - crWaitUntilV(ssh->user_response >= 0); - s->dlgret = ssh->user_response; - } - if (s->dlgret == 0) { - ssh_disconnect(ssh, "User aborted at cipher warning", NULL, - 0, TRUE); - crStopV; - } - } - - if (s->warn_sccipher) { - s->dlgret = askalg(ssh->frontend, - "server-to-client cipher", - s->in.cipher->name, - ssh_dialog_callback, ssh); - if (s->dlgret < 0) { - ssh->user_response = -1; - crWaitUntilV(ssh->user_response >= 0); - s->dlgret = ssh->user_response; - } - if (s->dlgret == 0) { - ssh_disconnect(ssh, "User aborted at cipher warning", NULL, - 0, TRUE); - crStopV; - } - } - - if (s->ignorepkt) /* first_kex_packet_follows */ - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_transport)) != NULL); - } - - if (ssh->kex->main_type == KEXTYPE_DH) { - /* - * Work out the number of bits of key we will need from the - * key exchange. We start with the maximum key length of - * either cipher... - */ - { - int csbits, scbits; - - csbits = s->out.cipher ? s->out.cipher->real_keybits : 0; - scbits = s->in.cipher ? s->in.cipher->real_keybits : 0; - s->nbits = (csbits > scbits ? csbits : scbits); - } - /* The keys only have hlen-bit entropy, since they're based on - * a hash. So cap the key size at hlen bits. */ - if (s->nbits > ssh->kex->hash->hlen * 8) - s->nbits = ssh->kex->hash->hlen * 8; - - /* - * If we're doing Diffie-Hellman group exchange, start by - * requesting a group. - */ - if (dh_is_gex(ssh->kex)) { - logevent("Doing Diffie-Hellman group exchange"); - ssh->pls.kctx = SSH2_PKTCTX_DHGEX; - /* - * Work out how big a DH group we will need to allow that - * much data. - */ - s->pbits = 512 << ((s->nbits - 1) / 64); - if (s->pbits < DH_MIN_SIZE) - s->pbits = DH_MIN_SIZE; - if (s->pbits > DH_MAX_SIZE) - s->pbits = DH_MAX_SIZE; - if ((ssh->remote_bugs & BUG_SSH2_OLDGEX)) { - s->pktout = ssh_bpp_new_pktout( - ssh->bpp, SSH2_MSG_KEX_DH_GEX_REQUEST_OLD); - put_uint32(s->pktout, s->pbits); - } else { - s->pktout = ssh_bpp_new_pktout( - ssh->bpp, SSH2_MSG_KEX_DH_GEX_REQUEST); - put_uint32(s->pktout, DH_MIN_SIZE); - put_uint32(s->pktout, s->pbits); - put_uint32(s->pktout, DH_MAX_SIZE); - } - ssh_pkt_write(ssh, s->pktout); - - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_transport)) != NULL); - if (pktin->type != SSH2_MSG_KEX_DH_GEX_GROUP) { - bombout(("expected key exchange group packet from server")); - crStopV; - } - s->p = get_mp_ssh2(pktin); - s->g = get_mp_ssh2(pktin); - if (get_err(pktin)) { - freebn(s->p); - freebn(s->g); - bombout(("unable to read mp-ints from incoming group packet")); - crStopV; - } - ssh->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; - } else { - ssh->pls.kctx = SSH2_PKTCTX_DHGROUP; - ssh->dh_ctx = dh_setup_group(ssh->kex); - s->kex_init_value = SSH2_MSG_KEXDH_INIT; - s->kex_reply_value = SSH2_MSG_KEXDH_REPLY; - logeventf(ssh, "Using Diffie-Hellman with standard group \"%s\"", - ssh->kex->groupname); - } - - logeventf(ssh, "Doing Diffie-Hellman key exchange with hash %s", - ssh->kex->hash->text_name); - /* - * Now generate and send e for Diffie-Hellman. - */ - set_busy_status(ssh->frontend, BUSY_CPU); /* this can take a while */ - s->e = dh_create_e(ssh->dh_ctx, s->nbits * 2); - s->pktout = ssh_bpp_new_pktout(ssh->bpp, s->kex_init_value); - put_mp_ssh2(s->pktout, s->e); - ssh_pkt_write(ssh, s->pktout); - - set_busy_status(ssh->frontend, BUSY_WAITING); /* wait for server */ - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_transport)) != NULL); - if (pktin->type != s->kex_reply_value) { - bombout(("expected key exchange reply packet from server")); - crStopV; - } - set_busy_status(ssh->frontend, BUSY_CPU); /* cogitate */ - s->hostkeydata = get_string(pktin); - s->hkey = ssh_key_new_pub(ssh->hostkey_alg, s->hostkeydata); - s->f = get_mp_ssh2(pktin); - s->sigdata = get_string(pktin); - if (get_err(pktin)) { - bombout(("unable to parse key exchange reply packet")); - crStopV; - } - - { - const char *err = dh_validate_f(ssh->dh_ctx, s->f); - if (err) { - bombout(("key exchange reply failed validation: %s", err)); - crStopV; - } - } - s->K = dh_find_K(ssh->dh_ctx, s->f); - - /* We assume everything from now on will be quick, and it might - * involve user interaction. */ - set_busy_status(ssh->frontend, BUSY_NOT); - - put_stringpl(ssh->exhash, s->hostkeydata); - if (dh_is_gex(ssh->kex)) { - if (!(ssh->remote_bugs & BUG_SSH2_OLDGEX)) - put_uint32(ssh->exhash, DH_MIN_SIZE); - put_uint32(ssh->exhash, s->pbits); - if (!(ssh->remote_bugs & BUG_SSH2_OLDGEX)) - put_uint32(ssh->exhash, DH_MAX_SIZE); - put_mp_ssh2(ssh->exhash, s->p); - put_mp_ssh2(ssh->exhash, s->g); - } - put_mp_ssh2(ssh->exhash, s->e); - put_mp_ssh2(ssh->exhash, s->f); - - dh_cleanup(ssh->dh_ctx); - freebn(s->f); - if (dh_is_gex(ssh->kex)) { - freebn(s->g); - freebn(s->p); - } - } else if (ssh->kex->main_type == KEXTYPE_ECDH) { - - logeventf(ssh, "Doing ECDH key exchange with curve %s and hash %s", - ssh_ecdhkex_curve_textname(ssh->kex), - ssh->kex->hash->text_name); - ssh->pls.kctx = SSH2_PKTCTX_ECDHKEX; - - s->eckey = ssh_ecdhkex_newkey(ssh->kex); - if (!s->eckey) { - bombout(("Unable to generate key for ECDH")); - crStopV; - } - - s->pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_KEX_ECDH_INIT); - { - strbuf *pubpoint = strbuf_new(); - ssh_ecdhkex_getpublic(s->eckey, BinarySink_UPCAST(pubpoint)); - put_stringsb(s->pktout, pubpoint); - } - - ssh_pkt_write(ssh, s->pktout); - - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_transport)) != NULL); - if (pktin->type != SSH2_MSG_KEX_ECDH_REPLY) { - ssh_ecdhkex_freekey(s->eckey); - bombout(("expected ECDH reply packet from server")); - crStopV; - } - - s->hostkeydata = get_string(pktin); - put_stringpl(ssh->exhash, s->hostkeydata); - s->hkey = ssh_key_new_pub(ssh->hostkey_alg, s->hostkeydata); - - { - strbuf *pubpoint = strbuf_new(); - ssh_ecdhkex_getpublic(s->eckey, BinarySink_UPCAST(pubpoint)); - put_string(ssh->exhash, pubpoint->u, pubpoint->len); - strbuf_free(pubpoint); - } - - { - ptrlen keydata = get_string(pktin); - put_stringpl(ssh->exhash, keydata); - s->K = ssh_ecdhkex_getkey(s->eckey, keydata.ptr, keydata.len); - if (!get_err(pktin) && !s->K) { - ssh_ecdhkex_freekey(s->eckey); - bombout(("point received in ECDH was not valid")); - crStopV; - } - } - - s->sigdata = get_string(pktin); - if (get_err(pktin)) { - bombout(("unable to parse key exchange reply packet")); - crStopV; - } - - ssh_ecdhkex_freekey(s->eckey); -#ifndef NO_GSSAPI - } else if (ssh->kex->main_type == KEXTYPE_GSS) { - ptrlen data; - - ssh->pls.kctx = SSH2_PKTCTX_GSSKEX; - s->init_token_sent = 0; - s->complete_rcvd = 0; - s->hkey = NULL; - s->fingerprint = NULL; - s->keystr = NULL; - - /* - * Work out the number of bits of key we will need from the - * key exchange. We start with the maximum key length of - * either cipher... - * - * This is rote from the KEXTYPE_DH section above. - */ - { - int csbits, scbits; - - csbits = s->out.cipher->real_keybits; - scbits = s->in.cipher->real_keybits; - s->nbits = (csbits > scbits ? csbits : scbits); - } - /* The keys only have hlen-bit entropy, since they're based on - * a hash. So cap the key size at hlen bits. */ - if (s->nbits > ssh->kex->hash->hlen * 8) - s->nbits = ssh->kex->hash->hlen * 8; - - if (dh_is_gex(ssh->kex)) { - /* - * Work out how big a DH group we will need to allow that - * much data. - */ - s->pbits = 512 << ((s->nbits - 1) / 64); - logeventf(ssh, "Doing GSSAPI (with Kerberos V5) Diffie-Hellman " - "group exchange, with minimum %d bits", s->pbits); - s->pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_KEXGSS_GROUPREQ); - put_uint32(s->pktout, s->pbits); /* min */ - put_uint32(s->pktout, s->pbits); /* preferred */ - put_uint32(s->pktout, s->pbits * 2); /* max */ - ssh_pkt_write(ssh, s->pktout); - - crMaybeWaitUntilV( - (pktin = pq_pop(&ssh->pq_ssh2_transport)) != NULL); - if (pktin->type != SSH2_MSG_KEXGSS_GROUP) { - bombout(("expected key exchange group packet from server")); - crStopV; - } - s->p = get_mp_ssh2(pktin); - s->g = get_mp_ssh2(pktin); - if (get_err(pktin)) { - bombout(("unable to read mp-ints from incoming group packet")); - crStopV; - } - ssh->dh_ctx = dh_setup_gex(s->p, s->g); - } else { - ssh->dh_ctx = dh_setup_group(ssh->kex); - logeventf(ssh, "Using GSSAPI (with Kerberos V5) Diffie-Hellman with standard group \"%s\"", - ssh->kex->groupname); - } - - logeventf(ssh, "Doing GSSAPI (with Kerberos V5) Diffie-Hellman key exchange with hash %s", - ssh->kex->hash->text_name); - /* Now generate e for Diffie-Hellman. */ - set_busy_status(ssh->frontend, BUSY_CPU); /* this can take a while */ - s->e = dh_create_e(ssh->dh_ctx, s->nbits * 2); - - if (ssh->gsslib->gsslogmsg) - logevent(ssh->gsslib->gsslogmsg); - - /* initial tokens are empty */ - SSH_GSS_CLEAR_BUF(&s->gss_rcvtok); - SSH_GSS_CLEAR_BUF(&s->gss_sndtok); - SSH_GSS_CLEAR_BUF(&s->mic); - s->gss_stat = ssh->gsslib->acquire_cred(ssh->gsslib, &s->gss_ctx, - &s->gss_cred_expiry); - if (s->gss_stat != SSH_GSS_OK) { - bombout(("GSSAPI key exchange failed to initialize")); - crStopV; - } - - /* now enter the loop */ - assert(ssh->gss_srv_name); - do { - /* - * When acquire_cred yields no useful expiration, go with the - * service ticket expiration. - */ - s->gss_stat = ssh->gsslib->init_sec_context - (ssh->gsslib, - &s->gss_ctx, - ssh->gss_srv_name, - s->gss_delegate, - &s->gss_rcvtok, - &s->gss_sndtok, - (s->gss_cred_expiry == GSS_NO_EXPIRATION ? - &s->gss_cred_expiry : NULL), - NULL); - SSH_GSS_CLEAR_BUF(&s->gss_rcvtok); - - if (s->gss_stat == SSH_GSS_S_COMPLETE && s->complete_rcvd) - break; /* MIC is verified after the loop */ - - if (s->gss_stat != SSH_GSS_S_COMPLETE && - s->gss_stat != SSH_GSS_S_CONTINUE_NEEDED) { - if (ssh->gsslib->display_status(ssh->gsslib, s->gss_ctx, - &s->gss_buf) == SSH_GSS_OK) { - bombout(("GSSAPI key exchange failed to initialize" - " context: %s", (char *)s->gss_buf.value)); - sfree(s->gss_buf.value); - crStopV; - } else { - bombout(("GSSAPI key exchange failed to initialize" - " context")); - crStopV; - } - } - assert(s->gss_stat == SSH_GSS_S_COMPLETE || - s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED); - - if (!s->init_token_sent) { - s->init_token_sent = 1; - s->pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_KEXGSS_INIT); - if (s->gss_sndtok.length == 0) { - bombout(("GSSAPI key exchange failed:" - " no initial context token")); - crStopV; - } - put_string(s->pktout, - s->gss_sndtok.value, s->gss_sndtok.length); - put_mp_ssh2(s->pktout, s->e); - ssh_pkt_write(ssh, s->pktout); - ssh->gsslib->free_tok(ssh->gsslib, &s->gss_sndtok); - logevent("GSSAPI key exchange initialised"); - } else if (s->gss_sndtok.length != 0) { - s->pktout = ssh_bpp_new_pktout( - ssh->bpp, SSH2_MSG_KEXGSS_CONTINUE); - put_string(s->pktout, - s->gss_sndtok.value, s->gss_sndtok.length); - ssh_pkt_write(ssh, s->pktout); - ssh->gsslib->free_tok(ssh->gsslib, &s->gss_sndtok); - } - - if (s->gss_stat == SSH_GSS_S_COMPLETE && s->complete_rcvd) - break; - - wait_for_gss_token: - crMaybeWaitUntilV( - (pktin = pq_pop(&ssh->pq_ssh2_transport)) != NULL); - switch (pktin->type) { - case SSH2_MSG_KEXGSS_CONTINUE: - data = get_string(pktin); - s->gss_rcvtok.value = (char *)data.ptr; - s->gss_rcvtok.length = data.len; - continue; - case SSH2_MSG_KEXGSS_COMPLETE: - s->complete_rcvd = 1; - s->f = get_mp_ssh2(pktin); - data = get_string(pktin); - s->mic.value = (char *)data.ptr; - s->mic.length = data.len; - /* Save expiration time of cred when delegating */ - if (s->gss_delegate && s->gss_cred_expiry != GSS_NO_EXPIRATION) - ssh->gss_cred_expiry = s->gss_cred_expiry; - /* If there's a final token we loop to consume it */ - if (get_bool(pktin)) { - data = get_string(pktin); - s->gss_rcvtok.value = (char *)data.ptr; - s->gss_rcvtok.length = data.len; - continue; - } - break; - case SSH2_MSG_KEXGSS_HOSTKEY: - s->hostkeydata = get_string(pktin); - if (ssh->hostkey_alg) { - s->hkey = ssh_key_new_pub(ssh->hostkey_alg, - s->hostkeydata); - put_string(ssh->exhash, - s->hostkeydata.ptr, s->hostkeydata.len); - } - /* - * Can't loop as we have no token to pass to - * init_sec_context. - */ - goto wait_for_gss_token; - case SSH2_MSG_KEXGSS_ERROR: - /* - * We have no use for the server's major and minor - * status. The minor status is really only - * meaningful to the server, and with luck the major - * status means something to us (but not really all - * that much). The string is more meaningful, and - * hopefully the server sends any error tokens, as - * that will produce the most useful information for - * us. - */ - get_uint32(pktin); /* server's major status */ - get_uint32(pktin); /* server's minor status */ - data = get_string(pktin); - logeventf(ssh, "GSSAPI key exchange failed; " - "server's message: %.*s", PTRLEN_PRINTF(data)); - /* Language tag, but we have no use for it */ - get_string(pktin); - /* - * Wait for an error token, if there is one, or the - * server's disconnect. The error token, if there - * is one, must follow the SSH2_MSG_KEXGSS_ERROR - * message, per the RFC. - */ - goto wait_for_gss_token; - default: - bombout(("unexpected message type during gss kex")); - crStopV; - break; - } - } while (s->gss_rcvtok.length || - s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED || - !s->complete_rcvd); - - s->K = dh_find_K(ssh->dh_ctx, s->f); - - /* We assume everything from now on will be quick, and it might - * involve user interaction. */ - set_busy_status(ssh->frontend, BUSY_NOT); - - if (!s->hkey) - put_stringz(ssh->exhash, ""); - if (dh_is_gex(ssh->kex)) { - /* min, preferred, max */ - put_uint32(ssh->exhash, s->pbits); - put_uint32(ssh->exhash, s->pbits); - put_uint32(ssh->exhash, s->pbits * 2); - - put_mp_ssh2(ssh->exhash, s->p); - put_mp_ssh2(ssh->exhash, s->g); - } - put_mp_ssh2(ssh->exhash, s->e); - put_mp_ssh2(ssh->exhash, s->f); - - /* - * MIC verification is done below, after we compute the hash - * used as the MIC input. - */ - - dh_cleanup(ssh->dh_ctx); - freebn(s->f); - if (dh_is_gex(ssh->kex)) { - freebn(s->g); - freebn(s->p); - } -#endif - } else { - ptrlen rsakeydata; - - assert(ssh->kex->main_type == KEXTYPE_RSA); - logeventf(ssh, "Doing RSA key exchange with hash %s", - ssh->kex->hash->text_name); - ssh->pls.kctx = SSH2_PKTCTX_RSAKEX; - /* - * RSA key exchange. First expect a KEXRSA_PUBKEY packet - * from the server. - */ - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_transport)) != NULL); - if (pktin->type != SSH2_MSG_KEXRSA_PUBKEY) { - bombout(("expected RSA public key packet from server")); - crStopV; - } - - s->hostkeydata = get_string(pktin); - put_stringpl(ssh->exhash, s->hostkeydata); - s->hkey = ssh_key_new_pub(ssh->hostkey_alg, s->hostkeydata); - - rsakeydata = get_string(pktin); - - s->rsakey = ssh_rsakex_newkey(rsakeydata.ptr, rsakeydata.len); - if (!s->rsakey) { - bombout(("unable to parse RSA public key from server")); - crStopV; - } - - put_stringpl(ssh->exhash, rsakeydata); - - /* - * Next, set up a shared secret K, of precisely KLEN - - * 2*HLEN - 49 bits, where KLEN is the bit length of the - * RSA key modulus and HLEN is the bit length of the hash - * we're using. - */ - { - int klen = ssh_rsakex_klen(s->rsakey); - int nbits = klen - (2*ssh->kex->hash->hlen*8 + 49); - int i, byte = 0; - strbuf *buf; - unsigned char *outstr; - int outstrlen; - - s->K = bn_power_2(nbits - 1); - - for (i = 0; i < nbits; i++) { - if ((i & 7) == 0) { - byte = random_byte(); - } - bignum_set_bit(s->K, i, (byte >> (i & 7)) & 1); - } - - /* - * Encode this as an mpint. - */ - buf = strbuf_new(); - put_mp_ssh2(buf, s->K); - - /* - * Encrypt it with the given RSA key. - */ - outstrlen = (klen + 7) / 8; - outstr = snewn(outstrlen, unsigned char); - ssh_rsakex_encrypt(ssh->kex->hash, buf->u, buf->len, - outstr, outstrlen, s->rsakey); - - /* - * And send it off in a return packet. - */ - s->pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_KEXRSA_SECRET); - put_string(s->pktout, outstr, outstrlen); - ssh_pkt_write(ssh, s->pktout); - - put_string(ssh->exhash, outstr, outstrlen); - - strbuf_free(buf); - sfree(outstr); - } - - ssh_rsakex_freekey(s->rsakey); - - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_transport)) != NULL); - if (pktin->type != SSH2_MSG_KEXRSA_DONE) { - bombout(("expected signature packet from server")); - crStopV; - } - - s->sigdata = get_string(pktin); - if (get_err(pktin)) { - bombout(("unable to parse signature packet")); - crStopV; - } - } - - put_mp_ssh2(ssh->exhash, s->K); - assert(ssh_hash_alg(ssh->exhash)->hlen <= sizeof(s->exchange_hash)); - ssh_hash_final(ssh->exhash, s->exchange_hash); - -#ifndef NO_GSSAPI - if (ssh->kex->main_type == KEXTYPE_GSS) { - Ssh_gss_buf gss_buf; - SSH_GSS_CLEAR_BUF(&s->gss_buf); - - gss_buf.value = s->exchange_hash; - gss_buf.length = ssh->kex->hash->hlen; - s->gss_stat = ssh->gsslib->verify_mic(ssh->gsslib, s->gss_ctx, &gss_buf, &s->mic); - if (s->gss_stat != SSH_GSS_OK) { - if (ssh->gsslib->display_status(ssh->gsslib, s->gss_ctx, - &s->gss_buf) == SSH_GSS_OK) { - bombout(("GSSAPI Key Exchange MIC was not valid: %s", - (char *)s->gss_buf.value)); - sfree(s->gss_buf.value); - } else { - bombout(("GSSAPI Key Exchange MIC was not valid")); - } - crStopV; - } - - ssh->gss_kex_used = TRUE; - - /*- - * If this the first KEX, save the GSS context for "gssapi-keyex" - * authentication. - * - * http://tools.ietf.org/html/rfc4462#section-4 - * - * This method may be used only if the initial key exchange was - * performed using a GSS-API-based key exchange method defined in - * accordance with Section 2. The GSS-API context used with this - * method is always that established during an initial GSS-API-based - * key exchange. Any context established during key exchange for the - * purpose of rekeying MUST NOT be used with this method. - */ - if (!s->got_session_id) { - ssh->gss_ctx = s->gss_ctx; - } else { - ssh->gsslib->release_cred(ssh->gsslib, &s->gss_ctx); - } - logeventf(ssh, "GSSAPI Key Exchange complete!"); - } -#endif - - ssh->dh_ctx = NULL; - -#if 0 - debug(("Exchange hash is:\n")); - dmemdump(s->exchange_hash, ssh->kex->hash->hlen); -#endif - - /* In GSS keyex there's no hostkey signature to verify */ - if (ssh->kex->main_type != KEXTYPE_GSS) { - if (!s->hkey) { - bombout(("Server's host key is invalid")); - crStopV; - } - - if (!ssh_key_verify( - s->hkey, s->sigdata, - make_ptrlen(s->exchange_hash, ssh->kex->hash->hlen))) { -#ifndef FUZZING - bombout(("Server's host key did not match the signature " - "supplied")); - crStopV; -#endif - } - } - - s->keystr = (s->hkey ? ssh_key_cache_str(s->hkey) : NULL); -#ifndef NO_GSSAPI - if (ssh->gss_kex_used) { - /* - * In a GSS-based session, check the host key (if any) against - * the transient host key cache. See comment above, at the - * definition of ssh_transient_hostkey_cache_entry. - */ - if (ssh->kex->main_type == KEXTYPE_GSS) { - - /* - * We've just done a GSS key exchange. If it gave us a - * host key, store it. - */ - if (s->hkey) { - s->fingerprint = ssh2_fingerprint(s->hkey); - logevent("GSS kex provided fallback host key:"); - logevent(s->fingerprint); - sfree(s->fingerprint); - s->fingerprint = NULL; - ssh_store_transient_hostkey(ssh, s->hkey); - } else if (!ssh_have_any_transient_hostkey(ssh)) { - /* - * But if it didn't, then we currently have no - * fallback host key to use in subsequent non-GSS - * rekeys. So we should immediately trigger a non-GSS - * rekey of our own, to set one up, before the session - * keys have been used for anything else. - * - * This is similar to the cross-certification done at - * user request in the permanent host key cache, but - * here we do it automatically, once, at session - * startup, and only add the key to the transient - * cache. - */ - if (ssh->hostkey_alg) { - s->need_gss_transient_hostkey = TRUE; - } else { - /* - * If we negotiated the "null" host key algorithm - * in the key exchange, that's an indication that - * no host key at all is available from the server - * (both because we listed "null" last, and - * because RFC 4462 section 5 says that a server - * MUST NOT offer "null" as a host key algorithm - * unless that is the only algorithm it provides - * at all). - * - * In that case we actually _can't_ perform a - * non-GSSAPI key exchange, so it's pointless to - * attempt one proactively. This is also likely to - * cause trouble later if a rekey is required at a - * moment whne GSS credentials are not available, - * but someone setting up a server in this - * configuration presumably accepts that as a - * consequence. - */ - if (!s->warned_about_no_gss_transient_hostkey) { - logevent("No fallback host key available"); - s->warned_about_no_gss_transient_hostkey = TRUE; - } - } - } - } else { - /* - * We've just done a fallback key exchange, so make - * sure the host key it used is in the cache of keys - * we previously received in GSS kexes. - * - * An exception is if this was the non-GSS key exchange we - * triggered on purpose to populate the transient cache. - */ - assert(s->hkey); /* only KEXTYPE_GSS lets this be null */ - s->fingerprint = ssh2_fingerprint(s->hkey); - - if (s->need_gss_transient_hostkey) { - logevent("Post-GSS rekey provided fallback host key:"); - logevent(s->fingerprint); - ssh_store_transient_hostkey(ssh, s->hkey); - s->need_gss_transient_hostkey = FALSE; - } else if (!ssh_verify_transient_hostkey(ssh, s->hkey)) { - logevent("Non-GSS rekey after initial GSS kex " - "used host key:"); - logevent(s->fingerprint); - bombout(("Host key was not previously sent via GSS kex")); - } - - sfree(s->fingerprint); - s->fingerprint = NULL; - } - } else -#endif /* NO_GSSAPI */ - if (!s->got_session_id) { - /* - * Make a note of any other host key formats that are available. - */ - { - int i, j, nkeys = 0; - char *list = NULL; - for (i = 0; i < lenof(hostkey_algs); i++) { - if (hostkey_algs[i].alg == ssh->hostkey_alg) - continue; - - for (j = 0; j < ssh->n_uncert_hostkeys; j++) - if (ssh->uncert_hostkeys[j] == i) - break; - - if (j < ssh->n_uncert_hostkeys) { - char *newlist; - if (list) - newlist = dupprintf("%s/%s", list, - hostkey_algs[i].alg->ssh_id); - else - newlist = dupprintf("%s", hostkey_algs[i].alg->ssh_id); - sfree(list); - list = newlist; - nkeys++; - } - } - if (list) { - logeventf(ssh, - "Server also has %s host key%s, but we " - "don't know %s", list, - nkeys > 1 ? "s" : "", - nkeys > 1 ? "any of them" : "it"); - sfree(list); - } - } - - /* - * Authenticate remote host: verify host key. (We've already - * checked the signature of the exchange hash.) - */ - s->fingerprint = ssh2_fingerprint(s->hkey); - logevent("Host key fingerprint is:"); - logevent(s->fingerprint); - /* First check against manually configured host keys. */ - s->dlgret = verify_ssh_manual_host_key(ssh->conf, - s->fingerprint, s->hkey); - if (s->dlgret == 0) { /* did not match */ - bombout(("Host key did not appear in manually configured list")); - crStopV; - } else if (s->dlgret < 0) { /* none configured; use standard handling */ - s->dlgret = verify_ssh_host_key(ssh->frontend, - ssh->savedhost, ssh->savedport, - ssh_key_cache_id(s->hkey), - s->keystr, s->fingerprint, - ssh_dialog_callback, ssh); -#ifdef FUZZING - s->dlgret = 1; -#endif - if (s->dlgret < 0) { - ssh->user_response = -1; - crWaitUntilV(ssh->user_response >= 0); - s->dlgret = ssh->user_response; - } - if (s->dlgret == 0) { - ssh_disconnect(ssh, "Aborted at host key verification", NULL, - 0, TRUE); - crStopV; - } - } - sfree(s->fingerprint); - /* - * Save this host key, to check against the one presented in - * subsequent rekeys. - */ - ssh->hostkey_str = s->keystr; - s->keystr = NULL; - } else if (ssh->cross_certifying) { - s->fingerprint = ssh2_fingerprint(s->hkey); - logevent("Storing additional host key for this host:"); - logevent(s->fingerprint); - sfree(s->fingerprint); - store_host_key(ssh->savedhost, ssh->savedport, - ssh_key_cache_id(s->hkey), s->keystr); - ssh->cross_certifying = FALSE; - /* - * Don't forget to store the new key as the one we'll be - * re-checking in future normal rekeys. - */ - ssh->hostkey_str = s->keystr; - s->keystr = NULL; - } else { - /* - * In a rekey, we never present an interactive host key - * verification request to the user. Instead, we simply - * enforce that the key we're seeing this time is identical to - * the one we saw before. - */ - if (strcmp(ssh->hostkey_str, s->keystr)) { -#ifndef FUZZING - bombout(("Host key was different in repeat key exchange")); - crStopV; -#endif - } - } - sfree(s->keystr); - if (s->hkey) { - ssh_key_free(s->hkey); - s->hkey = NULL; - } - - /* - * The exchange hash from the very first key exchange is also - * the session id, used in session key construction and - * authentication. - */ - if (!s->got_session_id) { - assert(sizeof(s->exchange_hash) <= sizeof(ssh->v2_session_id)); - memcpy(ssh->v2_session_id, s->exchange_hash, - sizeof(s->exchange_hash)); - ssh->v2_session_id_len = ssh->kex->hash->hlen; - assert(ssh->v2_session_id_len <= sizeof(ssh->v2_session_id)); - s->got_session_id = TRUE; - } - - /* - * Send SSH2_MSG_NEWKEYS. - */ - s->pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_NEWKEYS); - ssh_pkt_write(ssh, s->pktout); - /* Start counting down the outgoing-data limit for these cipher keys. */ - ssh->stats.out.running = TRUE; - ssh->stats.out.remaining = ssh->max_data_size; - - /* - * Force the BPP to synchronously marshal all packets up to and - * including that NEWKEYS into wire format, before we switch over - * to new crypto. - */ - ssh_bpp_handle_output(ssh->bpp); - - /* - * We've sent client NEWKEYS, so create and initialise - * client-to-server session keys. - */ - { - strbuf *cipher_key = strbuf_new(); - strbuf *cipher_iv = strbuf_new(); - strbuf *mac_key = strbuf_new(); - - if (s->out.cipher) { - ssh2_mkkey(ssh, cipher_iv, s->K, s->exchange_hash, 'A', - s->out.cipher->blksize); - ssh2_mkkey(ssh, cipher_key, s->K, s->exchange_hash, 'C', - s->out.cipher->padded_keybytes); - } - if (s->out.mac) { - ssh2_mkkey(ssh, mac_key, s->K, s->exchange_hash, 'E', - s->out.mac->keylen); - } - - ssh2_bpp_new_outgoing_crypto( - ssh->bpp, - s->out.cipher, cipher_key->u, cipher_iv->u, - s->out.mac, s->out.etm_mode, mac_key->u, - s->out.comp); - - /* - * Remember some details we'll need later for making other - * policy decisions based on the crypto we've just - * initialised. - */ - ssh->v2_cbc_ignore_workaround = ( - s->out.cipher && - (s->out.cipher->flags & SSH_CIPHER_IS_CBC) && - !(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)); - ssh->v2_out_cipherblksize = s->out.cipher->blksize; - - strbuf_free(cipher_key); - strbuf_free(cipher_iv); - strbuf_free(mac_key); - } - - if (s->out.cipher) - logeventf(ssh, "Initialised %.200s client->server encryption", - s->out.cipher->text_name); - if (s->out.mac) - logeventf(ssh, "Initialised %.200s client->server" - " MAC algorithm%s%s", - s->out.mac->text_name, - s->out.etm_mode ? " (in ETM mode)" : "", - (s->out.cipher->required_mac ? - " (required by cipher)" : "")); - if (s->out.comp->text_name) - logeventf(ssh, "Initialised %s compression", - s->out.comp->text_name); - - /* - * Now our end of the key exchange is complete, we can send all - * our queued higher-layer packets. - */ - ssh->queueing = FALSE; - ssh2_pkt_queuesend(ssh); - - /* - * Expect SSH2_MSG_NEWKEYS from server. - */ - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_transport)) != NULL); - if (pktin->type != SSH2_MSG_NEWKEYS) { - bombout(("expected new-keys packet from server")); - crStopV; - } - /* Start counting down the incoming-data limit for these cipher keys. */ - ssh->stats.in.running = TRUE; - ssh->stats.in.remaining = ssh->max_data_size; - - /* - * We've seen server NEWKEYS, so create and initialise - * server-to-client session keys. - */ - { - strbuf *cipher_key = strbuf_new(); - strbuf *cipher_iv = strbuf_new(); - strbuf *mac_key = strbuf_new(); - - if (s->in.cipher) { - ssh2_mkkey(ssh, cipher_iv, s->K, s->exchange_hash, 'B', - s->in.cipher->blksize); - ssh2_mkkey(ssh, cipher_key, s->K, s->exchange_hash, 'D', - s->in.cipher->padded_keybytes); - } - if (s->in.mac) { - ssh2_mkkey(ssh, mac_key, s->K, s->exchange_hash, 'F', - s->in.mac->keylen); - } - - ssh2_bpp_new_incoming_crypto( - ssh->bpp, - s->in.cipher, cipher_key->u, cipher_iv->u, - s->in.mac, s->in.etm_mode, mac_key->u, - s->in.comp); - - strbuf_free(cipher_key); - strbuf_free(cipher_iv); - strbuf_free(mac_key); - } - - if (s->in.cipher) - logeventf(ssh, "Initialised %.200s server->client encryption", - s->in.cipher->text_name); - if (s->in.mac) - logeventf(ssh, "Initialised %.200s server->client" - " MAC algorithm%s%s", - s->in.mac->text_name, - s->in.etm_mode ? " (in ETM mode)" : "", - (s->in.cipher->required_mac ? - " (required by cipher)" : "")); - if (s->in.comp->text_name) - logeventf(ssh, "Initialised %s decompression", - s->in.comp->text_name); - - /* - * Free shared secret. - */ - freebn(s->K); - - /* - * Update the specials menu to list the remaining uncertified host - * keys. - */ - update_specials_menu(ssh->frontend); - - /* - * Key exchange is over. Loop straight back round if we have a - * deferred rekey reason. - */ - if (ssh->deferred_rekey_reason) { - logevent(ssh->deferred_rekey_reason); - pktin = NULL; - ssh->deferred_rekey_reason = NULL; - goto begin_key_exchange; - } - - /* - * Otherwise, schedule a timer for our next rekey. - */ - ssh->kex_in_progress = FALSE; - ssh->last_rekey = GETTICKCOUNT(); - (void) ssh2_timer_update(ssh, 0); - - /* - * Now we're encrypting. Get the next-layer protocol started if it - * hasn't already, and then sit here waiting for reasons to go - * back to the start and do a repeat key exchange. One of those - * reasons is that we receive KEXINIT from the other end; the - * other is if we find ssh->rekey_reason is non-NULL, i.e. we've - * decided to initiate a rekey ourselves for some reason. - */ - ssh->rekey_class = RK_NONE; - while (!pq_peek(&ssh->pq_ssh2_transport) && !ssh->rekey_class) { - wait_for_rekey: - if (!ssh->current_user_input_fn) { - /* - * Allow userauth to initialise itself. - */ - do_ssh2_userauth(ssh); - ssh->current_user_input_fn = ssh2_userauth_input; - } - crReturnV; - } - if ((pktin = pq_pop(&ssh->pq_ssh2_transport)) != NULL) { - if (pktin->type != SSH2_MSG_KEXINIT) { - bombout(("unexpected key exchange packet, type %d", pktin->type)); - crStopV; - } - logevent("Server initiated key re-exchange"); - } else { - if (ssh->rekey_class == RK_POST_USERAUTH) { - /* - * userauth has seen a USERAUTH_SUCCEEDED. For a couple of - * reasons, this may be the moment to do an immediate - * rekey with different parameters. - * - * One is to turn on delayed compression. We do this by a - * rekey to work around a protocol design bug: - * draft-miller-secsh-compression-delayed-00 says that you - * negotiate delayed compression in the first key - * exchange, and both sides start compressing when the - * server has sent USERAUTH_SUCCESS. This has a race - * condition -- the server can't know when the client has - * seen it, and thus which incoming packets it should - * treat as compressed. - * - * Instead, we do the initial key exchange without - * offering the delayed methods, but note if the server - * offers them; when we get here, if a delayed method was - * available that was higher on our list than what we got, - * we initiate a rekey in which we _do_ list the delayed - * methods (and hopefully get it as a result). Subsequent - * rekeys will do the same. - * - * Another reason for a rekey at this point is if we've - * done a GSS key exchange and don't have anything in our - * transient hostkey cache, in which case we should make - * an attempt to populate the cache now. - */ - assert(!s->userauth_succeeded); /* should only happen once */ - s->userauth_succeeded = TRUE; - if (s->pending_compression) { - ssh->rekey_reason = "enabling delayed compression"; - } else if (s->need_gss_transient_hostkey) { - ssh->rekey_reason = "populating transient host key cache"; - } else { - /* Can't see any point rekeying. */ - goto wait_for_rekey; /* this is utterly horrid */ - } - /* else fall through to rekey... */ - - s->pending_compression = FALSE; - } - /* - * Now we've decided to rekey. - * - * Special case: if the server bug is set that doesn't - * allow rekeying, we give a different log message and - * continue waiting. (If such a server _initiates_ a rekey, - * we process it anyway!) - */ - if ((ssh->remote_bugs & BUG_SSH2_REKEY)) { - logeventf(ssh, "Server bug prevents key re-exchange (%s)", - ssh->rekey_reason); - /* Reset the counters, so that at least this message doesn't - * hit the event log _too_ often. */ - ssh->stats.in.running = ssh->stats.out.running = TRUE; - ssh->stats.in.remaining = ssh->stats.out.remaining = - ssh->max_data_size; - (void) ssh2_timer_update(ssh, 0); - goto wait_for_rekey; /* this is still utterly horrid */ - } else { - logeventf(ssh, "Initiating key re-exchange (%s)", - ssh->rekey_reason); - } - } - goto begin_key_exchange; - - crFinishV; -} - -/* - * Send data on an SSH channel. In SSH-2, this involves buffering it - * first. - */ -static int ssh_send_channel_data(struct ssh_channel *c, const char *buf, - int len) -{ - assert(!(c->closes & CLOSES_SENT_EOF)); - - if (c->ssh->version == 2) { - bufchain_add(&c->v.v2.outbuffer, buf, len); - return ssh2_try_send(c); - } else { - PktOut *pkt = ssh_bpp_new_pktout(c->ssh->bpp, SSH1_MSG_CHANNEL_DATA); - put_uint32(pkt, c->remoteid); - put_string(pkt, buf, len); - ssh_pkt_write(c->ssh, pkt); - /* - * In SSH-1 we can return 0 here - implying that channels are - * never individually throttled - because the only - * circumstance that can cause throttling will be the whole - * SSH connection backing up, in which case _everything_ will - * be throttled as a whole. - */ - return 0; - } -} - -/* - * Attempt to send data on an SSH-2 channel. - */ -static int ssh2_try_send(struct ssh_channel *c) -{ - Ssh ssh = c->ssh; - PktOut *pktout; - int ret; - - while (c->v.v2.remwindow > 0 && bufchain_size(&c->v.v2.outbuffer) > 0) { - int len; - void *data; - bufchain_prefix(&c->v.v2.outbuffer, &data, &len); - if ((unsigned)len > c->v.v2.remwindow) - len = c->v.v2.remwindow; - if ((unsigned)len > c->v.v2.remmaxpkt) - len = c->v.v2.remmaxpkt; - pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_CHANNEL_DATA); - put_uint32(pktout, c->remoteid); - put_string(pktout, data, len); - ssh2_pkt_send(ssh, pktout); - if (!ssh->s) /* a network error might have closed the socket */ - break; - bufchain_consume(&c->v.v2.outbuffer, len); - c->v.v2.remwindow -= len; - } - - /* - * After having sent as much data as we can, return the amount - * still buffered. - */ - ret = bufchain_size(&c->v.v2.outbuffer); - - /* - * And if there's no data pending but we need to send an EOF, send - * it. - */ - if (!ret && c->pending_eof) - ssh_channel_try_eof(c); - - return ret; -} - -static void ssh2_try_send_and_unthrottle(Ssh ssh, struct ssh_channel *c) -{ - int bufsize; - if (c->closes & CLOSES_SENT_EOF) - return; /* don't send on channels we've EOFed */ - bufsize = ssh2_try_send(c); - if (bufsize == 0) { - c->throttled_by_backlog = FALSE; - ssh_channel_check_throttle(c); - } -} - -static int ssh_is_simple(Ssh ssh) -{ - /* - * We use the 'simple' variant of the SSH protocol if we're asked - * to, except not if we're also doing connection-sharing (either - * tunnelling our packets over an upstream or expecting to be - * tunnelled over ourselves), since then the assumption that we - * have only one channel to worry about is not true after all. - */ - return (conf_get_int(ssh->conf, CONF_ssh_simple) && - !ssh->bare_connection && !ssh->connshare); -} - -/* - * Set up most of a new ssh_channel. Nulls out sharectx, but leaves - * chan untouched (since it will sometimes have been filled in before - * calling this). - */ -static void ssh_channel_init(struct ssh_channel *c) -{ - Ssh ssh = c->ssh; - c->localid = alloc_channel_id(ssh->channels, struct ssh_channel); - c->closes = 0; - c->pending_eof = FALSE; - c->throttling_conn = FALSE; - c->sharectx = NULL; - if (ssh->version == 2) { - c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin = - ssh_is_simple(ssh) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE; - c->v.v2.chanreq_head = NULL; - c->v.v2.throttle_state = UNTHROTTLED; - bufchain_init(&c->v.v2.outbuffer); - } - c->sc.vt = &sshchannel_vtable; - add234(ssh->channels, c); -} - -/* - * Construct the common parts of a CHANNEL_OPEN. - */ -static PktOut *ssh2_chanopen_init(struct ssh_channel *c, - const char *type) -{ - PktOut *pktout; - - pktout = ssh_bpp_new_pktout(c->ssh->bpp, SSH2_MSG_CHANNEL_OPEN); - put_stringz(pktout, type); - put_uint32(pktout, c->localid); - put_uint32(pktout, c->v.v2.locwindow);/* our window size */ - put_uint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */ - return pktout; -} - -/* - * CHANNEL_FAILURE doesn't come with any indication of what message - * caused it, so we have to keep track of the outstanding - * CHANNEL_REQUESTs ourselves. - */ -static void ssh2_queue_chanreq_handler(struct ssh_channel *c, - cchandler_fn_t handler, void *ctx) -{ - struct outstanding_channel_request *ocr = - snew(struct outstanding_channel_request); - - assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE))); - ocr->handler = handler; - ocr->ctx = ctx; - ocr->next = NULL; - if (!c->v.v2.chanreq_head) - c->v.v2.chanreq_head = ocr; - else - c->v.v2.chanreq_tail->next = ocr; - c->v.v2.chanreq_tail = ocr; -} - -/* - * Construct the common parts of a CHANNEL_REQUEST. If handler is not - * NULL then a reply will be requested and the handler will be called - * when it arrives. The returned packet is ready to have any - * request-specific data added and be sent. Note that if a handler is - * provided, it's essential that the request actually be sent. - * - * The handler will usually be passed the response packet in pktin. If - * pktin is NULL, this means that no reply will ever be forthcoming - * (e.g. because the entire connection is being destroyed, or because - * the server initiated channel closure before we saw the response) - * and the handler should free any storage it's holding. - */ -static PktOut *ssh2_chanreq_init(struct ssh_channel *c, - const char *type, - cchandler_fn_t handler, void *ctx) -{ - PktOut *pktout; - - assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE))); - pktout = ssh_bpp_new_pktout(c->ssh->bpp, SSH2_MSG_CHANNEL_REQUEST); - put_uint32(pktout, c->remoteid); - put_stringz(pktout, type); - put_bool(pktout, handler != NULL); - if (handler != NULL) - ssh2_queue_chanreq_handler(c, handler, ctx); - return pktout; -} - -static void ssh_channel_unthrottle(struct ssh_channel *c, int bufsize) -{ - Ssh ssh = c->ssh; - int buflimit; - - if (ssh->version == 1) { - buflimit = SSH1_BUFFER_LIMIT; - } else { - if (ssh_is_simple(ssh)) - buflimit = 0; - else - buflimit = c->v.v2.locmaxwin; - if (bufsize < buflimit) - ssh2_set_window(c, buflimit - bufsize); - } - if (c->throttling_conn && bufsize <= buflimit) { - c->throttling_conn = 0; - ssh_throttle_conn(ssh, -1); - } -} - -/* - * Potentially enlarge the window on an SSH-2 channel. - */ -static void ssh2_handle_winadj_response(struct ssh_channel *, PktIn *, - void *); -static void ssh2_set_window(struct ssh_channel *c, int newwin) -{ - Ssh ssh = c->ssh; - - /* - * Never send WINDOW_ADJUST for a channel that the remote side has - * already sent EOF on; there's no point, since it won't be - * sending any more data anyway. Ditto if _we've_ already sent - * CLOSE. - */ - if (c->closes & (CLOSES_RCVD_EOF | CLOSES_SENT_CLOSE)) - return; - - /* - * If the client-side Channel is in an initial setup phase with a - * fixed window size, e.g. for an X11 channel when we're still - * waiting to see its initial auth and may yet hand it off to a - * downstream, don't send any WINDOW_ADJUST either. - */ - if (c->chan->initial_fixed_window_size) - return; - - /* - * If the remote end has a habit of ignoring maxpkt, limit the - * window so that it has no choice (assuming it doesn't ignore the - * window as well). - */ - if ((ssh->remote_bugs & BUG_SSH2_MAXPKT) && newwin > OUR_V2_MAXPKT) - newwin = OUR_V2_MAXPKT; - - /* - * Only send a WINDOW_ADJUST if there's significantly more window - * available than the other end thinks there is. This saves us - * sending a WINDOW_ADJUST for every character in a shell session. - * - * "Significant" is arbitrarily defined as half the window size. - */ - if (newwin / 2 >= c->v.v2.locwindow) { - PktOut *pktout; - unsigned *up; - - /* - * In order to keep track of how much window the client - * actually has available, we'd like it to acknowledge each - * WINDOW_ADJUST. We can't do that directly, so we accompany - * it with a CHANNEL_REQUEST that has to be acknowledged. - * - * This is only necessary if we're opening the window wide. - * If we're not, then throughput is being constrained by - * something other than the maximum window size anyway. - */ - if (newwin == c->v.v2.locmaxwin && - !(ssh->remote_bugs & BUG_CHOKES_ON_WINADJ)) { - up = snew(unsigned); - *up = newwin - c->v.v2.locwindow; - pktout = ssh2_chanreq_init(c, "winadj@putty.projects.tartarus.org", - ssh2_handle_winadj_response, up); - ssh2_pkt_send(ssh, pktout); - - if (c->v.v2.throttle_state != UNTHROTTLED) - c->v.v2.throttle_state = UNTHROTTLING; - } else { - /* Pretend the WINDOW_ADJUST was acked immediately. */ - c->v.v2.remlocwin = newwin; - c->v.v2.throttle_state = THROTTLED; - } - pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_CHANNEL_WINDOW_ADJUST); - put_uint32(pktout, c->remoteid); - put_uint32(pktout, newwin - c->v.v2.locwindow); - ssh2_pkt_send(ssh, pktout); - c->v.v2.locwindow = newwin; - } -} - -/* - * Find the channel associated with a message. If there's no channel, - * or it's not properly open, make a noise about it and return NULL. - * If the channel is shared, pass the message on to downstream and - * also return NULL (meaning the caller should ignore this message). - */ -static struct ssh_channel *ssh_channel_msg(Ssh ssh, PktIn *pktin) -{ - unsigned localid = get_uint32(pktin); - struct ssh_channel *c; - int halfopen_ok; - - /* Is this message OK on a half-open connection? */ - if (ssh->version == 1) - halfopen_ok = (pktin->type == SSH1_MSG_CHANNEL_OPEN_CONFIRMATION || - pktin->type == SSH1_MSG_CHANNEL_OPEN_FAILURE); - else - halfopen_ok = (pktin->type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION || - pktin->type == SSH2_MSG_CHANNEL_OPEN_FAILURE); - c = find234(ssh->channels, &localid, ssh_channelfind); - if (c && c->sharectx) { - share_got_pkt_from_server(c->sharectx, pktin->type, - BinarySource_UPCAST(pktin)->data, - BinarySource_UPCAST(pktin)->len); - return NULL; - } - if (!c || c->halfopen != halfopen_ok) { - char *buf = dupprintf("Received %s for %s channel %u", - ssh_pkt_type(ssh, pktin->type), - !c ? "nonexistent" : - c->halfopen ? "half-open" : "open", - localid); - ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE); - sfree(buf); - return NULL; - } - return c; -} - -static void ssh2_handle_winadj_response(struct ssh_channel *c, - PktIn *pktin, void *ctx) -{ - unsigned *sizep = ctx; - - /* - * Winadj responses should always be failures. However, at least - * one server ("boks_sshd") is known to return SUCCESS for channel - * requests it's never heard of, such as "winadj@putty". Raised - * with foxt.com as bug 090916-090424, but for the sake of a quiet - * life, we don't worry about what kind of response we got. - */ - - c->v.v2.remlocwin += *sizep; - sfree(sizep); - /* - * winadj messages are only sent when the window is fully open, so - * if we get an ack of one, we know any pending unthrottle is - * complete. - */ - if (c->v.v2.throttle_state == UNTHROTTLING) - c->v.v2.throttle_state = UNTHROTTLED; -} - -static void ssh2_msg_channel_response(Ssh ssh, PktIn *pktin) -{ - struct ssh_channel *c = ssh_channel_msg(ssh, pktin); - struct outstanding_channel_request *ocr; - - if (!c) return; - ocr = c->v.v2.chanreq_head; - if (!ocr) { - ssh2_msg_unexpected(ssh, pktin); - return; - } - ocr->handler(c, pktin, ocr->ctx); - if (ssh->state == SSH_STATE_CLOSED) - return; /* in case the handler called bomb_out(), which some can */ - c->v.v2.chanreq_head = ocr->next; - sfree(ocr); - /* - * We may now initiate channel-closing procedures, if that - * CHANNEL_REQUEST was the last thing outstanding before we send - * CHANNEL_CLOSE. - */ - ssh2_channel_check_close(c); -} - -static void ssh2_msg_channel_window_adjust(Ssh ssh, PktIn *pktin) -{ - struct ssh_channel *c; - c = ssh_channel_msg(ssh, pktin); - if (!c) - return; - if (!(c->closes & CLOSES_SENT_EOF)) { - c->v.v2.remwindow += get_uint32(pktin); - ssh2_try_send_and_unthrottle(ssh, c); - } -} - -static void ssh2_msg_channel_data(Ssh ssh, PktIn *pktin) -{ - ptrlen data; - unsigned ext_type = 0; /* 0 means not extended */ - struct ssh_channel *c; - c = ssh_channel_msg(ssh, pktin); - if (!c) - return; - if (pktin->type == SSH2_MSG_CHANNEL_EXTENDED_DATA) - ext_type = get_uint32(pktin); - data = get_string(pktin); - if (!get_err(pktin)) { - int bufsize; - c->v.v2.locwindow -= data.len; - c->v.v2.remlocwin -= data.len; - if (ext_type != 0 && ext_type != SSH2_EXTENDED_DATA_STDERR) - data.len = 0; /* Don't do anything with unknown extended data. */ - bufsize = ssh_channel_data(c, ext_type == SSH2_EXTENDED_DATA_STDERR, - data.ptr, data.len); - /* - * If it looks like the remote end hit the end of its window, - * and we didn't want it to do that, think about using a - * larger window. - */ - if (c->v.v2.remlocwin <= 0 && c->v.v2.throttle_state == UNTHROTTLED && - c->v.v2.locmaxwin < 0x40000000) - c->v.v2.locmaxwin += OUR_V2_WINSIZE; - /* - * If we are not buffering too much data, - * enlarge the window again at the remote side. - * If we are buffering too much, we may still - * need to adjust the window if the server's - * sent excess data. - */ - if (bufsize < c->v.v2.locmaxwin) - ssh2_set_window(c, c->v.v2.locmaxwin - bufsize); - /* - * If we're either buffering way too much data, or if we're - * buffering anything at all and we're in "simple" mode, - * throttle the whole channel. - */ - if ((bufsize > c->v.v2.locmaxwin || (ssh_is_simple(ssh) && bufsize>0)) - && !c->throttling_conn) { - c->throttling_conn = 1; - ssh_throttle_conn(ssh, +1); - } - } -} - -static void ssh_check_termination(Ssh ssh) -{ - if (ssh->version == 2 && - !conf_get_int(ssh->conf, CONF_ssh_no_shell) && - (ssh->channels && count234(ssh->channels) == 0) && - !(ssh->connshare && share_ndownstreams(ssh->connshare) > 0)) { - /* - * We used to send SSH_MSG_DISCONNECT here, because I'd - * believed that _every_ conforming SSH-2 connection had to - * end with a disconnect being sent by at least one side; - * apparently I was wrong and it's perfectly OK to - * unceremoniously slam the connection shut when you're done, - * and indeed OpenSSH feels this is more polite than sending a - * DISCONNECT. So now we don't. - */ - ssh_disconnect(ssh, "All channels closed", NULL, 0, TRUE); - } -} - -/* - * Close any local socket and free any local resources associated with - * a channel. This converts the channel into a zombie. - */ -static void ssh_channel_close_local(struct ssh_channel *c, char const *reason) -{ - Ssh ssh = c->ssh; - const char *msg = NULL; - - if (c->sharectx) - return; - - msg = chan_log_close_msg(c->chan); - chan_free(c->chan); - c->chan = zombiechan_new(); - - if (msg != NULL) { - if (reason != NULL) - logeventf(ssh, "%s %s", msg, reason); - else - logevent(msg); - } -} - -static void ssh_channel_destroy(struct ssh_channel *c) -{ - Ssh ssh = c->ssh; - - ssh_channel_close_local(c, NULL); - - del234(ssh->channels, c); - if (ssh->version == 2) { - bufchain_clear(&c->v.v2.outbuffer); - assert(c->v.v2.chanreq_head == NULL); - } - sfree(c); - - /* - * If that was the last channel left open, we might need to - * terminate. - */ - ssh_check_termination(ssh); -} - -static void ssh2_channel_check_close(struct ssh_channel *c) -{ - Ssh ssh = c->ssh; - PktOut *pktout; - - assert(ssh->version == 2); - if (c->halfopen) { - /* - * If we've sent out our own CHANNEL_OPEN but not yet seen - * either OPEN_CONFIRMATION or OPEN_FAILURE in response, then - * it's too early to be sending close messages of any kind. - */ - return; - } - - if ((!((CLOSES_SENT_EOF | CLOSES_RCVD_EOF) & ~c->closes) || - chan_want_close(c->chan, (c->closes & CLOSES_SENT_EOF), - (c->closes & CLOSES_RCVD_EOF))) && - !c->v.v2.chanreq_head && - !(c->closes & CLOSES_SENT_CLOSE)) { - /* - * We have both sent and received EOF (or the channel is a - * zombie), and we have no outstanding channel requests, which - * means the channel is in final wind-up. But we haven't sent - * CLOSE, so let's do so now. - */ - pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_CHANNEL_CLOSE); - put_uint32(pktout, c->remoteid); - ssh2_pkt_send(ssh, pktout); - c->closes |= CLOSES_SENT_EOF | CLOSES_SENT_CLOSE; - } - - if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) { - assert(c->v.v2.chanreq_head == NULL); - /* - * We have both sent and received CLOSE, which means we're - * completely done with the channel. - */ - ssh_channel_destroy(c); - } -} - -static void ssh_channel_got_eof(struct ssh_channel *c) -{ - if (c->closes & CLOSES_RCVD_EOF) - return; /* already seen EOF */ - c->closes |= CLOSES_RCVD_EOF; - - chan_send_eof(c->chan); -} - -static void ssh2_msg_channel_eof(Ssh ssh, PktIn *pktin) -{ - struct ssh_channel *c; - - c = ssh_channel_msg(ssh, pktin); - if (!c) - return; - ssh_channel_got_eof(c); - ssh2_channel_check_close(c); -} - -static void ssh2_msg_channel_close(Ssh ssh, PktIn *pktin) -{ - struct ssh_channel *c; - - c = ssh_channel_msg(ssh, pktin); - if (!c) - return; - - /* - * When we receive CLOSE on a channel, we assume it comes with an - * implied EOF if we haven't seen EOF yet. - */ - ssh_channel_got_eof(c); - - if (!(ssh->remote_bugs & BUG_SENDS_LATE_REQUEST_REPLY)) { - /* - * It also means we stop expecting to see replies to any - * outstanding channel requests, so clean those up too. - * (ssh_chanreq_init will enforce by assertion that we don't - * subsequently put anything back on this list.) - */ - while (c->v.v2.chanreq_head) { - struct outstanding_channel_request *ocr = c->v.v2.chanreq_head; - ocr->handler(c, NULL, ocr->ctx); - c->v.v2.chanreq_head = ocr->next; - sfree(ocr); - } - } - - /* - * And we also send an outgoing EOF, if we haven't already, on the - * assumption that CLOSE is a pretty forceful announcement that - * the remote side is doing away with the entire channel. (If it - * had wanted to send us EOF and continue receiving data from us, - * it would have just sent CHANNEL_EOF.) - */ - if (!(c->closes & CLOSES_SENT_EOF)) { - /* - * Abandon any buffered data we still wanted to send to this - * channel. Receiving a CHANNEL_CLOSE is an indication that - * the server really wants to get on and _destroy_ this - * channel, and it isn't going to send us any further - * WINDOW_ADJUSTs to permit us to send pending stuff. - */ - bufchain_clear(&c->v.v2.outbuffer); - - /* - * Send outgoing EOF. - */ - sshfwd_write_eof(&c->sc); - - /* - * Make sure we don't read any more from whatever our local - * data source is for this channel. (This will pick up on the - * changes made by sshfwd_write_eof.) - */ - ssh_channel_check_throttle(c); - } - - /* - * Now process the actual close. - */ - if (!(c->closes & CLOSES_RCVD_CLOSE)) { - c->closes |= CLOSES_RCVD_CLOSE; - ssh2_channel_check_close(c); - } -} - -static void ssh2_msg_channel_open_confirmation(Ssh ssh, PktIn *pktin) -{ - struct ssh_channel *c; - - c = ssh_channel_msg(ssh, pktin); - if (!c) - return; - assert(c->halfopen); /* ssh_channel_msg will have enforced this */ - c->remoteid = get_uint32(pktin); - c->halfopen = FALSE; - c->v.v2.remwindow = get_uint32(pktin); - c->v.v2.remmaxpkt = get_uint32(pktin); - - chan_open_confirmation(c->chan); - - /* - * Now that the channel is fully open, it's possible in principle - * to immediately close it. Check whether it wants us to! - * - * This can occur if a local socket error occurred between us - * sending out CHANNEL_OPEN and receiving OPEN_CONFIRMATION. If - * that happens, all we can do is immediately initiate close - * proceedings now that we know the server's id to put in the - * close message. We'll have handled that in this code by having - * already turned c->chan into a zombie, so its want_close method - * (which ssh2_channel_check_close will consult) will already be - * returning TRUE. - */ - ssh2_channel_check_close(c); - - if (c->pending_eof) - ssh_channel_try_eof(c); /* in case we had a pending EOF */ -} - -static char *ssh2_channel_open_failure_error_text(PktIn *pktin) -{ - static const char *const reasons[] = { - NULL, - "Administratively prohibited", - "Connect failed", - "Unknown channel type", - "Resource shortage", - }; - unsigned reason_code; - const char *reason_code_string; - char reason_code_buf[256]; - ptrlen reason; - - reason_code = get_uint32(pktin); - if (reason_code < lenof(reasons) && reasons[reason_code]) { - reason_code_string = reasons[reason_code]; - } else { - reason_code_string = reason_code_buf; - sprintf(reason_code_buf, "unknown reason code %#x", reason_code); - } - - reason = get_string(pktin); - - return dupprintf("%s [%.*s]", reason_code_string, PTRLEN_PRINTF(reason)); -} - -static void ssh2_msg_channel_open_failure(Ssh ssh, PktIn *pktin) -{ - struct ssh_channel *c; - - c = ssh_channel_msg(ssh, pktin); - if (!c) - return; - assert(c->halfopen); /* ssh_channel_msg will have enforced this */ - - { - char *errtext = ssh2_channel_open_failure_error_text(pktin); - chan_open_failed(c->chan, errtext); - sfree(errtext); - } - chan_free(c->chan); - - del234(ssh->channels, c); - sfree(c); -} - -static void ssh2_msg_channel_request(Ssh ssh, PktIn *pktin) -{ - ptrlen type; - int want_reply; - int reply = SSH2_MSG_CHANNEL_FAILURE; /* default */ - struct ssh_channel *c; - PktOut *pktout; - - c = ssh_channel_msg(ssh, pktin); - if (!c) - return; - type = get_string(pktin); - want_reply = get_bool(pktin); - - if (c->closes & CLOSES_SENT_CLOSE) { - /* - * We don't reply to channel requests after we've sent - * CHANNEL_CLOSE for the channel, because our reply might - * cross in the network with the other side's CHANNEL_CLOSE - * and arrive after they have wound the channel up completely. - */ - want_reply = FALSE; - } - - /* - * Having got the channel number, we now look at - * the request type string to see if it's something - * we recognise. - */ - if (c == ssh->mainchan) { - /* - * We recognise "exit-status" and "exit-signal" on - * the primary channel. - */ - if (ptrlen_eq_string(type, "exit-status")) { - - ssh->exitcode = get_uint32(pktin); - logeventf(ssh, "Server sent command exit status %d", - ssh->exitcode); - reply = SSH2_MSG_CHANNEL_SUCCESS; - - } else if (ptrlen_eq_string(type, "exit-signal")) { - char *fmt_sig = NULL, *fmt_msg = NULL; - ptrlen errmsg; - int core = FALSE; - int format, exitcode; - - /* ICK: older versions of OpenSSH (e.g. 3.4p1) - * provide an `int' for the signal, despite its - * having been a `string' in the drafts of RFC 4254 since at - * least 2001. (Fixed in session.c 1.147.) Try to - * infer which we can safely parse it as. */ - - size_t startpos = BinarySource_UPCAST(pktin)->pos; - - for (format = 0; format < 2; format++) { - BinarySource_UPCAST(pktin)->pos = startpos; - BinarySource_UPCAST(pktin)->err = BSE_NO_ERROR; - - if (format == 0) { /* standard string-based format */ - ptrlen signame = get_string(pktin); - fmt_sig = dupprintf(" \"%.*s\"", PTRLEN_PRINTF(signame)); - - /* - * Really hideous method of translating the - * signal description back into a locally - * meaningful number. - */ - - if (0) - ; -#define TRANSLATE_SIGNAL(s) \ - else if (ptrlen_eq_string(signame, #s)) \ - exitcode = 128 + SIG ## s -#ifdef SIGABRT - TRANSLATE_SIGNAL(ABRT); -#endif -#ifdef SIGALRM - TRANSLATE_SIGNAL(ALRM); -#endif -#ifdef SIGFPE - TRANSLATE_SIGNAL(FPE); -#endif -#ifdef SIGHUP - TRANSLATE_SIGNAL(HUP); -#endif -#ifdef SIGILL - TRANSLATE_SIGNAL(ILL); -#endif -#ifdef SIGINT - TRANSLATE_SIGNAL(INT); -#endif -#ifdef SIGKILL - TRANSLATE_SIGNAL(KILL); -#endif -#ifdef SIGPIPE - TRANSLATE_SIGNAL(PIPE); -#endif -#ifdef SIGQUIT - TRANSLATE_SIGNAL(QUIT); -#endif -#ifdef SIGSEGV - TRANSLATE_SIGNAL(SEGV); -#endif -#ifdef SIGTERM - TRANSLATE_SIGNAL(TERM); -#endif -#ifdef SIGUSR1 - TRANSLATE_SIGNAL(USR1); -#endif -#ifdef SIGUSR2 - TRANSLATE_SIGNAL(USR2); -#endif -#undef TRANSLATE_SIGNAL - else - exitcode = 128; - } else { /* nonstandard integer format */ - unsigned signum = get_uint32(pktin); - fmt_sig = dupprintf(" %u", signum); - exitcode = 128 + signum; - } - - core = get_bool(pktin); - errmsg = get_string(pktin); /* error message */ - get_string(pktin); /* language tag */ - if (!get_err(pktin) && get_avail(pktin) == 0) - break; /* successful parse */ - - sfree(fmt_sig); - } - - if (format == 2) { - fmt_sig = NULL; - exitcode = 128; - } - - ssh->exitcode = exitcode; - if (errmsg.len) { - fmt_msg = dupprintf(" (\"%.*s\")", PTRLEN_PRINTF(errmsg)); - } - - logeventf(ssh, "Server exited on signal%s%s%s", - fmt_sig ? fmt_sig : "", - core ? " (core dumped)" : "", - fmt_msg ? fmt_msg : ""); - sfree(fmt_sig); - sfree(fmt_msg); - reply = SSH2_MSG_CHANNEL_SUCCESS; - } - } else { - /* - * This is a channel request we don't know - * about, so we now either ignore the request - * or respond with CHANNEL_FAILURE, depending - * on want_reply. - */ - reply = SSH2_MSG_CHANNEL_FAILURE; - } - if (want_reply) { - pktout = ssh_bpp_new_pktout(ssh->bpp, reply); - put_uint32(pktout, c->remoteid); - ssh2_pkt_send(ssh, pktout); - } -} - -static void ssh2_msg_global_request(Ssh ssh, PktIn *pktin) -{ - int want_reply; - PktOut *pktout; - - get_string(pktin); /* ignore request type (see below) */ - want_reply = get_bool(pktin); - - /* - * We currently don't support any global requests - * at all, so we either ignore the request or - * respond with REQUEST_FAILURE, depending on - * want_reply. - */ - if (want_reply) { - pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_REQUEST_FAILURE); - ssh2_pkt_send(ssh, pktout); - } -} - -static struct X11FakeAuth *(ssh_add_sharing_x11_display)( - ConnectionLayer *cl, int authtype, ssh_sharing_connstate *share_cs, - share_channel *share_chan) -{ - Ssh ssh = FROMFIELD(cl, struct ssh_tag, cl); - struct X11FakeAuth *auth; - - /* - * Make up a new set of fake X11 auth data, and add it to the tree - * of currently valid ones with an indication of the sharing - * context that it's relevant to. - */ - auth = x11_invent_fake_auth(ssh->x11authtree, authtype); - auth->share_cs = share_cs; - auth->share_chan = share_chan; - - return auth; -} - -static void (ssh_remove_sharing_x11_display)( - ConnectionLayer *cl, struct X11FakeAuth *auth) -{ - Ssh ssh = FROMFIELD(cl, struct ssh_tag, cl); - del234(ssh->x11authtree, auth); - x11_free_fake_auth(auth); -} - -static void ssh2_msg_channel_open(Ssh ssh, PktIn *pktin) -{ - ptrlen type; - int peerport; - const char *error = NULL; - struct ssh_channel *c; - unsigned remid, winsize, pktsize; - PktOut *pktout; - - type = get_string(pktin); - c = snew(struct ssh_channel); - c->ssh = ssh; - - remid = get_uint32(pktin); - winsize = get_uint32(pktin); - pktsize = get_uint32(pktin); - - if (ptrlen_eq_string(type, "x11")) { - char *addrstr = mkstr(get_string(pktin)); - peerport = get_uint32(pktin); - - logeventf(ssh, "Received X11 connect request from %s:%d", - addrstr, peerport); - - if (!ssh->X11_fwd_enabled && !ssh->connshare) - error = "X11 forwarding is not enabled"; - else { - c->chan = x11_new_channel(ssh->x11authtree, &c->sc, - addrstr, peerport, - ssh->connshare != NULL); - logevent("Opened X11 forward channel"); - } - - sfree(addrstr); - } else if (ptrlen_eq_string(type, "forwarded-tcpip")) { - struct ssh_rportfwd pf, *realpf; - ptrlen peeraddr; - - pf.shost = mkstr(get_string(pktin)); - pf.sport = get_uint32(pktin); - peeraddr = get_string(pktin); - peerport = get_uint32(pktin); - realpf = find234(ssh->rportfwds, &pf, NULL); - logeventf(ssh, "Received remote port %s:%d open request " - "from %.*s:%d", pf.shost, pf.sport, - PTRLEN_PRINTF(peeraddr), peerport); - sfree(pf.shost); - - if (realpf == NULL) { - error = "Remote port is not recognised"; - } else { - char *err; - - if (realpf->share_ctx) { - /* - * This port forwarding is on behalf of a - * connection-sharing downstream, so abandon our own - * channel-open procedure and just pass the message on - * to sshshare.c. - */ - share_got_pkt_from_server(realpf->share_ctx, pktin->type, - BinarySource_UPCAST(pktin)->data, - BinarySource_UPCAST(pktin)->len); - sfree(c); - return; - } - - err = portfwdmgr_connect( - ssh->portfwdmgr, &c->chan, realpf->dhost, realpf->dport, - &c->sc, realpf->addressfamily); - logeventf(ssh, "Attempting to forward remote port to " - "%s:%d", realpf->dhost, realpf->dport); - if (err != NULL) { - logeventf(ssh, "Port open failed: %s", err); - sfree(err); - error = "Port open failed"; - } else { - logevent("Forwarded port opened successfully"); - } - } - } else if (ptrlen_eq_string(type, "auth-agent@openssh.com")) { - if (!ssh->agentfwd_enabled) - error = "Agent forwarding is not enabled"; - else - c->chan = agentf_new(&c->sc); - } else { - error = "Unsupported channel type requested"; - } - - c->remoteid = remid; - c->halfopen = FALSE; - if (error) { - pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_CHANNEL_OPEN_FAILURE); - put_uint32(pktout, c->remoteid); - put_uint32(pktout, SSH2_OPEN_CONNECT_FAILED); - put_stringz(pktout, error); - put_stringz(pktout, "en"); /* language tag */ - ssh2_pkt_send(ssh, pktout); - logeventf(ssh, "Rejected channel open: %s", error); - sfree(c); - } else { - ssh_channel_init(c); - c->v.v2.remwindow = winsize; - c->v.v2.remmaxpkt = pktsize; - if (c->chan->initial_fixed_window_size) { - c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin = - c->chan->initial_fixed_window_size; - } - pktout = ssh_bpp_new_pktout( - ssh->bpp, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); - put_uint32(pktout, c->remoteid); - put_uint32(pktout, c->localid); - put_uint32(pktout, c->v.v2.locwindow); - put_uint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */ - ssh2_pkt_send(ssh, pktout); - } -} - -static void sshchannel_x11_sharing_handover( - SshChannel *sc, ssh_sharing_connstate *share_cs, share_channel *share_chan, - const char *peer_addr, int peer_port, int endian, - int protomajor, int protominor, const void *initial_data, int initial_len) -{ - struct ssh_channel *c = FROMFIELD(sc, struct ssh_channel, sc); - - /* - * This function is called when we've just discovered that an X - * forwarding channel on which we'd been handling the initial auth - * ourselves turns out to be destined for a connection-sharing - * downstream. So we turn the channel into a sharing one, meaning - * that we completely stop tracking windows and buffering data and - * just pass more or less unmodified SSH messages back and forth. - */ - c->sharectx = share_cs; - share_setup_x11_channel(share_cs, share_chan, - c->localid, c->remoteid, c->v.v2.remwindow, - c->v.v2.remmaxpkt, c->v.v2.locwindow, - peer_addr, peer_port, endian, - protomajor, protominor, - initial_data, initial_len); - chan_free(c->chan); - c->chan = NULL; -} - -static void sshchannel_window_override_removed(SshChannel *sc) -{ - struct ssh_channel *c = FROMFIELD(sc, struct ssh_channel, sc); - - /* - * This function is called when a client-side Channel has just - * stopped requiring an initial fixed-size window. - */ - assert(!c->chan->initial_fixed_window_size); - if (c->ssh->version == 2) - ssh2_set_window( - c, ssh_is_simple(c->ssh) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE); -} - -/* - * Buffer banner messages for later display at some convenient point, - * if we're going to display them. - */ -static void ssh2_msg_userauth_banner(Ssh ssh, PktIn *pktin) -{ - /* Arbitrary limit to prevent unbounded inflation of buffer */ - if (conf_get_int(ssh->conf, CONF_ssh_show_banner) && - bufchain_size(&ssh->banner) <= 131072) { - ptrlen banner = get_string(pktin); - if (banner.len) - sanitise_term_data(&ssh->banner, banner.ptr, banner.len); - } -} - -static void ssh2_setup_x11(struct ssh_channel *c, PktIn *pktin, - void *ctx) -{ - struct ssh2_setup_x11_state { - int crLine; - }; - Ssh ssh = c->ssh; - PktOut *pktout; - crStateP(ssh2_setup_x11_state, ctx); - - crBeginState; - - logevent("Requesting X11 forwarding"); - pktout = ssh2_chanreq_init(ssh->mainchan, "x11-req", - ssh2_setup_x11, s); - put_bool(pktout, 0); /* many connections */ - put_stringz(pktout, ssh->x11auth->protoname); - put_stringz(pktout, ssh->x11auth->datastring); - put_uint32(pktout, ssh->x11disp->screennum); - ssh2_pkt_send(ssh, pktout); - - /* Wait to be called back with either a response packet, or NULL - * meaning clean up and free our data */ - crReturnV; - - if (pktin) { - if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) { - logevent("X11 forwarding enabled"); - ssh->X11_fwd_enabled = TRUE; - } else - logevent("X11 forwarding refused"); - } - - crFinishFreeV; -} - -static void ssh2_setup_agent(struct ssh_channel *c, PktIn *pktin, - void *ctx) -{ - struct ssh2_setup_agent_state { - int crLine; - }; - Ssh ssh = c->ssh; - PktOut *pktout; - crStateP(ssh2_setup_agent_state, ctx); - - crBeginState; - - logevent("Requesting OpenSSH-style agent forwarding"); - pktout = ssh2_chanreq_init(ssh->mainchan, "auth-agent-req@openssh.com", - ssh2_setup_agent, s); - ssh2_pkt_send(ssh, pktout); - - /* Wait to be called back with either a response packet, or NULL - * meaning clean up and free our data */ - crReturnV; - - if (pktin) { - if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) { - logevent("Agent forwarding enabled"); - ssh->agentfwd_enabled = TRUE; - } else - logevent("Agent forwarding refused"); - } - - crFinishFreeV; -} - -static void ssh2_setup_pty(struct ssh_channel *c, PktIn *pktin, - void *ctx) -{ - struct ssh2_setup_pty_state { - int crLine; - }; - Ssh ssh = c->ssh; - PktOut *pktout; - crStateP(ssh2_setup_pty_state, ctx); - - crBeginState; - - /* Unpick the terminal-speed string. */ - /* XXX perhaps we should allow no speeds to be sent. */ - ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */ - sscanf(conf_get_str(ssh->conf, CONF_termspeed), "%d,%d", &ssh->ospeed, &ssh->ispeed); - /* Build the pty request. */ - pktout = ssh2_chanreq_init(ssh->mainchan, "pty-req", - ssh2_setup_pty, s); - put_stringz(pktout, conf_get_str(ssh->conf, CONF_termtype)); - put_uint32(pktout, ssh->term_width); - put_uint32(pktout, ssh->term_height); - put_uint32(pktout, 0); /* pixel width */ - put_uint32(pktout, 0); /* pixel height */ - { - strbuf *modebuf = strbuf_new(); - write_ttymodes_to_packet_from_conf( - BinarySink_UPCAST(modebuf), ssh->frontend, ssh->conf, - 2, ssh->ospeed, ssh->ispeed); - put_stringsb(pktout, modebuf); - } - ssh2_pkt_send(ssh, pktout); - ssh->state = SSH_STATE_INTERMED; - - /* Wait to be called back with either a response packet, or NULL - * meaning clean up and free our data */ - crReturnV; - - if (pktin) { - if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) { - logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)", - ssh->ospeed, ssh->ispeed); - ssh->got_pty = TRUE; - } else { - c_write_str(ssh, "Server refused to allocate pty\r\n"); - ssh->editing = ssh->echoing = 1; - } - } - - crFinishFreeV; -} - -static void ssh2_setup_env(struct ssh_channel *c, PktIn *pktin, - void *ctx) -{ - struct ssh2_setup_env_state { - int crLine; - int num_env, env_left, env_ok; - }; - Ssh ssh = c->ssh; - PktOut *pktout; - crStateP(ssh2_setup_env_state, ctx); - - crBeginState; - - /* - * Send environment variables. - * - * Simplest thing here is to send all the requests at once, and - * then wait for a whole bunch of successes or failures. - */ - s->num_env = 0; - { - char *key, *val; - - for (val = conf_get_str_strs(ssh->conf, CONF_environmt, NULL, &key); - val != NULL; - val = conf_get_str_strs(ssh->conf, CONF_environmt, key, &key)) { - pktout = ssh2_chanreq_init(ssh->mainchan, "env", ssh2_setup_env, s); - put_stringz(pktout, key); - put_stringz(pktout, val); - ssh2_pkt_send(ssh, pktout); - - s->num_env++; - } - if (s->num_env) - logeventf(ssh, "Sent %d environment variables", s->num_env); - } - - if (s->num_env) { - s->env_ok = 0; - s->env_left = s->num_env; - - while (s->env_left > 0) { - /* Wait to be called back with either a response packet, - * or NULL meaning clean up and free our data */ - crReturnV; - if (!pktin) goto out; - if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) - s->env_ok++; - s->env_left--; - } - - if (s->env_ok == s->num_env) { - logevent("All environment variables successfully set"); - } else if (s->env_ok == 0) { - logevent("All environment variables refused"); - c_write_str(ssh, "Server refused to set environment variables\r\n"); - } else { - logeventf(ssh, "%d environment variables refused", - s->num_env - s->env_ok); - c_write_str(ssh, "Server refused to set all environment variables\r\n"); - } - } - out:; - crFinishFreeV; -} - -/* - * Handle the SSH-2 userauth layer. - */ -static void ssh2_msg_userauth(Ssh ssh, PktIn *pktin) -{ - pq_push(&ssh->pq_ssh2_userauth, pktin); - if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) { - /* - * The very instant we see this message, the connection - * protocol has officially started, which means we must - * install the dispatch-table entries for all the - * connection-layer messages. In particular, we must do this - * _before_ we return to the loop in ssh_process_incoming_pkts - * that's processing the currently queued packets through the - * dispatch table, because if (say) an SSH_MSG_GLOBAL_REQUEST - * is already pending in in_pq, we can't afford to delay - * installing its dispatch table entry until after that queue - * run is done. - */ - ssh2_connection_setup(ssh); - } - queue_idempotent_callback(&ssh->ssh2_userauth_icb); -} - -static void do_ssh2_userauth(void *vctx) -{ - Ssh ssh = (Ssh)vctx; - PktIn *pktin; - - struct do_ssh2_userauth_state { - int crLine; - enum { - AUTH_TYPE_NONE, - AUTH_TYPE_PUBLICKEY, - AUTH_TYPE_PUBLICKEY_OFFER_LOUD, - AUTH_TYPE_PUBLICKEY_OFFER_QUIET, - AUTH_TYPE_PASSWORD, - AUTH_TYPE_GSSAPI, /* always QUIET */ - AUTH_TYPE_KEYBOARD_INTERACTIVE, - AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET - } type; - int done_service_req; - int need_pw, can_pubkey, can_passwd, can_keyb_inter; - int userpass_ret; - int tried_pubkey_config, done_agent; -#ifndef NO_GSSAPI - int can_gssapi; - int can_gssapi_keyex_auth; - int tried_gssapi; - int tried_gssapi_keyex_auth; - time_t gss_cred_expiry; -#endif - int kbd_inter_refused; - int we_are_in, userauth_success; - prompts_t *cur_prompt; - int num_prompts; - char *username; - char *password; - int got_username; - strbuf *publickey_blob; - int privatekey_available, privatekey_encrypted; - char *publickey_algorithm; - char *publickey_comment; - unsigned char *agent_response; - BinarySource asrc[1]; /* for reading SSH agent response */ - size_t pkblob_pos_in_agent; - int keyi, nkeys; - ptrlen pk, alg, comment; - int len; - PktOut *pktout; - Filename *keyfile; -#ifndef NO_GSSAPI - Ssh_gss_ctx gss_ctx; - Ssh_gss_buf gss_buf; - Ssh_gss_buf gss_rcvtok, gss_sndtok; - Ssh_gss_stat gss_stat; -#endif - }; - crState(do_ssh2_userauth_state); - - crBeginState; - - /* Register as a handler for all the messages this coroutine handles. */ - ssh->packet_dispatch[SSH2_MSG_SERVICE_ACCEPT] = ssh2_msg_userauth; - ssh->packet_dispatch[SSH2_MSG_USERAUTH_REQUEST] = ssh2_msg_userauth; - ssh->packet_dispatch[SSH2_MSG_USERAUTH_FAILURE] = ssh2_msg_userauth; - ssh->packet_dispatch[SSH2_MSG_USERAUTH_SUCCESS] = ssh2_msg_userauth; - ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = ssh2_msg_userauth; - ssh->packet_dispatch[SSH2_MSG_USERAUTH_PK_OK] = ssh2_msg_userauth; - /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ] = ssh2_msg_userauth; duplicate case value */ - /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_REQUEST] = ssh2_msg_userauth; duplicate case value */ - ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_RESPONSE] = ssh2_msg_userauth; - - s->done_service_req = FALSE; - s->we_are_in = s->userauth_success = FALSE; - s->agent_response = NULL; -#ifndef NO_GSSAPI - s->tried_gssapi = FALSE; - s->tried_gssapi_keyex_auth = FALSE; -#endif - - if (!conf_get_int(ssh->conf, CONF_ssh_no_userauth)) { - /* - * Request userauth protocol, and await a response to it. - */ - s->pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_SERVICE_REQUEST); - put_stringz(s->pktout, "ssh-userauth"); - ssh2_pkt_send(ssh, s->pktout); - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_userauth)) != NULL); - if (pktin->type == SSH2_MSG_SERVICE_ACCEPT) - s->done_service_req = TRUE; - } - if (!s->done_service_req) { - /* - * Request connection protocol directly, without authentication. - */ - s->pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_SERVICE_REQUEST); - put_stringz(s->pktout, "ssh-connection"); - ssh2_pkt_send(ssh, s->pktout); - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_userauth)) != NULL); - if (pktin->type == SSH2_MSG_SERVICE_ACCEPT) { - s->we_are_in = TRUE; /* no auth required */ - } else { - bombout(("Server refused service request")); - crStopV; - } - } - - /* Arrange to be able to deal with any BANNERs that come in. - * (We do this now as packets may come in during the next bit.) */ - bufchain_init(&ssh->banner); - ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = - ssh2_msg_userauth_banner; - - /* - * Misc one-time setup for authentication. - */ - s->publickey_blob = NULL; - if (!s->we_are_in) { - - /* - * Load the public half of any configured public key file - * for later use. - */ - s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile); - if (!filename_is_null(s->keyfile)) { - int keytype; - logeventf(ssh, "Reading key file \"%.150s\"", - filename_to_str(s->keyfile)); - keytype = key_type(s->keyfile); - if (keytype == SSH_KEYTYPE_SSH2 || - keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 || - keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) { - const char *error; - s->publickey_blob = strbuf_new(); - if (ssh2_userkey_loadpub(s->keyfile, - &s->publickey_algorithm, - BinarySink_UPCAST(s->publickey_blob), - &s->publickey_comment, &error)) { - s->privatekey_available = (keytype == SSH_KEYTYPE_SSH2); - if (!s->privatekey_available) - logeventf(ssh, "Key file contains public key only"); - s->privatekey_encrypted = - ssh2_userkey_encrypted(s->keyfile, NULL); - } else { - char *msgbuf; - logeventf(ssh, "Unable to load key (%s)", - error); - msgbuf = dupprintf("Unable to load key file " - "\"%.150s\" (%s)\r\n", - filename_to_str(s->keyfile), - error); - c_write_str(ssh, msgbuf); - sfree(msgbuf); - strbuf_free(s->publickey_blob); - s->publickey_blob = NULL; - } - } else { - char *msgbuf; - logeventf(ssh, "Unable to use this key file (%s)", - key_type_to_str(keytype)); - msgbuf = dupprintf("Unable to use key file \"%.150s\"" - " (%s)\r\n", - filename_to_str(s->keyfile), - key_type_to_str(keytype)); - c_write_str(ssh, msgbuf); - sfree(msgbuf); - s->publickey_blob = NULL; - } - } - - /* - * Find out about any keys Pageant has (but if there's a - * public key configured, filter out all others). - */ - s->nkeys = 0; - s->agent_response = NULL; - s->pkblob_pos_in_agent = 0; - if (conf_get_int(ssh->conf, CONF_tryagent) && agent_exists()) { - void *r; - int rlen; - strbuf *agent_request; - - logevent("Pageant is running. Requesting keys."); - - /* Request the keys held by the agent. */ - agent_request = strbuf_new_for_agent_query(); - put_byte(agent_request, SSH2_AGENTC_REQUEST_IDENTITIES); - ssh->auth_agent_query = agent_query( - agent_request, &r, &rlen, ssh_agent_callback, ssh); - strbuf_free(agent_request); - - if (ssh->auth_agent_query) { - ssh->agent_response = NULL; - crWaitUntilV(ssh->agent_response); - r = ssh->agent_response; - rlen = ssh->agent_response_len; - } - s->agent_response = r; - BinarySource_BARE_INIT(s->asrc, r, rlen); - get_uint32(s->asrc); /* skip length field */ - if (get_byte(s->asrc) == SSH2_AGENT_IDENTITIES_ANSWER) { - int keyi; - - s->nkeys = toint(get_uint32(s->asrc)); - - /* - * Vet the Pageant response to ensure that the key - * count and blob lengths make sense. - */ - if (s->nkeys < 0) { - logeventf(ssh, "Pageant response contained a negative" - " key count %d", s->nkeys); - s->nkeys = 0; - goto done_agent_query; - } else { - logeventf(ssh, "Pageant has %d SSH-2 keys", s->nkeys); - - /* See if configured key is in agent. */ - for (keyi = 0; keyi < s->nkeys; keyi++) { - size_t pos = s->asrc->pos; - ptrlen blob = get_string(s->asrc); - get_string(s->asrc); /* skip comment */ - if (get_err(s->asrc)) { - logeventf(ssh, "Pageant response was truncated"); - s->nkeys = 0; - goto done_agent_query; - } - - if (s->publickey_blob && - blob.len == s->publickey_blob->len && - !memcmp(blob.ptr, s->publickey_blob->s, - s->publickey_blob->len)) { - logeventf(ssh, "Pageant key #%d matches " - "configured key file", keyi); - s->keyi = keyi; - s->pkblob_pos_in_agent = pos; - break; - } - } - if (s->publickey_blob && !s->pkblob_pos_in_agent) { - logevent("Configured key file not in Pageant"); - s->nkeys = 0; - } - } - } else { - logevent("Failed to get reply from Pageant"); - } - done_agent_query:; - } - - } - - /* - * We repeat this whole loop, including the username prompt, - * until we manage a successful authentication. If the user - * types the wrong _password_, they can be sent back to the - * beginning to try another username, if this is configured on. - * (If they specify a username in the config, they are never - * asked, even if they do give a wrong password.) - * - * I think this best serves the needs of - * - * - the people who have no configuration, no keys, and just - * want to try repeated (username,password) pairs until they - * type both correctly - * - * - people who have keys and configuration but occasionally - * need to fall back to passwords - * - * - people with a key held in Pageant, who might not have - * logged in to a particular machine before; so they want to - * type a username, and then _either_ their key will be - * accepted, _or_ they will type a password. If they mistype - * the username they will want to be able to get back and - * retype it! - */ - s->got_username = FALSE; - while (!s->we_are_in) { - /* - * Get a username. - */ - if (s->got_username && !conf_get_int(ssh->conf, CONF_change_username)) { - /* - * We got a username last time round this loop, and - * with change_username turned off we don't try to get - * it again. - */ - } else if ((ssh->username = get_remote_username(ssh->conf)) == NULL) { - s->cur_prompt = new_prompts(ssh->frontend); - s->cur_prompt->to_server = TRUE; - s->cur_prompt->name = dupstr("SSH login name"); - add_prompt(s->cur_prompt, dupstr("login as: "), TRUE); - s->userpass_ret = get_userpass_input(s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(&ssh->user_input) > 0) - s->userpass_ret = get_userpass_input( - s->cur_prompt, &ssh->user_input); - - if (s->userpass_ret >= 0) - break; - - ssh->send_ok = 1; - crReturnV; - ssh->send_ok = 0; - } - if (!s->userpass_ret) { - /* - * get_userpass_input() failed to get a username. - * Terminate. - */ - free_prompts(s->cur_prompt); - ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE); - crStopV; - } - ssh->username = dupstr(s->cur_prompt->prompts[0]->result); - free_prompts(s->cur_prompt); - } else { - char *stuff; - if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) { - stuff = dupprintf("Using username \"%s\".\r\n", ssh->username); - c_write_str(ssh, stuff); - sfree(stuff); - } - } - s->got_username = TRUE; - - /* - * Send an authentication request using method "none": (a) - * just in case it succeeds, and (b) so that we know what - * authentication methods we can usefully try next. - */ - ssh->pls.actx = SSH2_PKTCTX_NOAUTH; - - s->pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, ssh->username); - put_stringz(s->pktout, "ssh-connection");/* service requested */ - put_stringz(s->pktout, "none"); /* method */ - ssh2_pkt_send(ssh, s->pktout); - s->type = AUTH_TYPE_NONE; - s->we_are_in = FALSE; - - s->tried_pubkey_config = FALSE; - s->kbd_inter_refused = FALSE; - - /* Reset agent request state. */ - s->done_agent = FALSE; - if (s->agent_response) { - if (s->pkblob_pos_in_agent) { - s->asrc->pos = s->pkblob_pos_in_agent; - } else { - s->asrc->pos = 9; /* skip length + type + key count */ - s->keyi = 0; - } - } - - while (1) { - ptrlen methods; - - methods.ptr = ""; - methods.len = 0; - - /* - * Wait for the result of the last authentication request. - */ - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_userauth)) != NULL); - - /* - * Now is a convenient point to spew any banner material - * that we've accumulated. (This should ensure that when - * we exit the auth loop, we haven't any left to deal - * with.) - */ - { - int size = bufchain_size(&ssh->banner); - /* - * Don't show the banner if we're operating in - * non-verbose non-interactive mode. (It's probably - * a script, which means nobody will read the - * banner _anyway_, and moreover the printing of - * the banner will screw up processing on the - * output of (say) plink.) - * - * The banner data has been sanitised already by this - * point, so we can safely send it straight to the - * output channel. - */ - if (size && (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE))) { - char *banner = snewn(size, char); - bufchain_fetch(&ssh->banner, banner, size); - c_write(ssh, banner, size); - sfree(banner); - } - bufchain_clear(&ssh->banner); - } - if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) { - logevent("Access granted"); - s->we_are_in = s->userauth_success = TRUE; - break; - } - - if (pktin->type != SSH2_MSG_USERAUTH_FAILURE && s->type != AUTH_TYPE_GSSAPI) { - bombout(("Strange packet received during authentication: " - "type %d", pktin->type)); - crStopV; - } - - /* - * OK, we're now sitting on a USERAUTH_FAILURE message, so - * we can look at the string in it and know what we can - * helpfully try next. - */ - if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) { - methods = get_string(pktin); - if (!get_bool(pktin)) { - /* - * We have received an unequivocal Access - * Denied. This can translate to a variety of - * messages, or no message at all. - * - * For forms of authentication which are attempted - * implicitly, by which I mean without printing - * anything in the window indicating that we're - * trying them, we should never print 'Access - * denied'. - * - * If we do print a message saying that we're - * attempting some kind of authentication, it's OK - * to print a followup message saying it failed - - * but the message may sometimes be more specific - * than simply 'Access denied'. - * - * Additionally, if we'd just tried password - * authentication, we should break out of this - * whole loop so as to go back to the username - * prompt (iff we're configured to allow - * username change attempts). - */ - if (s->type == AUTH_TYPE_NONE) { - /* do nothing */ - } else if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD || - s->type == AUTH_TYPE_PUBLICKEY_OFFER_QUIET) { - if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD) - c_write_str(ssh, "Server refused our key\r\n"); - logevent("Server refused our key"); - } else if (s->type == AUTH_TYPE_PUBLICKEY) { - /* This _shouldn't_ happen except by a - * protocol bug causing client and server to - * disagree on what is a correct signature. */ - c_write_str(ssh, "Server refused public-key signature" - " despite accepting key!\r\n"); - logevent("Server refused public-key signature" - " despite accepting key!"); - } else if (s->type==AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET) { - /* quiet, so no c_write */ - logevent("Server refused keyboard-interactive authentication"); - } else if (s->type==AUTH_TYPE_GSSAPI) { - /* always quiet, so no c_write */ - /* also, the code down in the GSSAPI block has - * already logged this in the Event Log */ - } else if (s->type == AUTH_TYPE_KEYBOARD_INTERACTIVE) { - logevent("Keyboard-interactive authentication failed"); - c_write_str(ssh, "Access denied\r\n"); - } else { - assert(s->type == AUTH_TYPE_PASSWORD); - logevent("Password authentication failed"); - c_write_str(ssh, "Access denied\r\n"); - - if (conf_get_int(ssh->conf, CONF_change_username)) { - /* XXX perhaps we should allow - * keyboard-interactive to do this too? */ - s->we_are_in = FALSE; - break; - } - } - } else { - c_write_str(ssh, "Further authentication required\r\n"); - logevent("Further authentication required"); - } - - s->can_pubkey = - in_commasep_string("publickey", methods.ptr, methods.len); - s->can_passwd = - in_commasep_string("password", methods.ptr, methods.len); - s->can_keyb_inter = - conf_get_int(ssh->conf, CONF_try_ki_auth) && - in_commasep_string("keyboard-interactive", - methods.ptr, methods.len); -#ifndef NO_GSSAPI - s->can_gssapi = - conf_get_int(ssh->conf, CONF_try_gssapi_auth) && - in_commasep_string("gssapi-with-mic", - methods.ptr, methods.len) && - ssh->gsslibs->nlibraries > 0; - s->can_gssapi_keyex_auth = - conf_get_int(ssh->conf, CONF_try_gssapi_kex) && - in_commasep_string("gssapi-keyex", - methods.ptr, methods.len) && - ssh->gsslibs->nlibraries > 0 && - ssh->gss_ctx; -#endif - } - - ssh->pls.actx = SSH2_PKTCTX_NOAUTH; - -#ifndef NO_GSSAPI - if (s->can_gssapi_keyex_auth && !s->tried_gssapi_keyex_auth) { - - /* gssapi-keyex authentication */ - - s->type = AUTH_TYPE_GSSAPI; - s->tried_gssapi_keyex_auth = TRUE; - ssh->pls.actx = SSH2_PKTCTX_GSSAPI; - - if (ssh->gsslib->gsslogmsg) - logevent(ssh->gsslib->gsslogmsg); - - logeventf(ssh, "Trying gssapi-keyex..."); - s->pktout = - ssh2_gss_authpacket(ssh, ssh->gss_ctx, "gssapi-keyex"); - ssh2_pkt_send(ssh, s->pktout); - ssh->gsslib->release_cred(ssh->gsslib, &ssh->gss_ctx); - ssh->gss_ctx = NULL; - - continue; - } else -#endif /* NO_GSSAPI */ - - if (s->can_pubkey && !s->done_agent && s->nkeys) { - - /* - * Attempt public-key authentication using a key from Pageant. - */ - - ssh->pls.actx = SSH2_PKTCTX_PUBLICKEY; - - logeventf(ssh, "Trying Pageant key #%d", s->keyi); - - /* Unpack key from agent response */ - s->pk = get_string(s->asrc); - s->comment = get_string(s->asrc); - { - BinarySource src[1]; - BinarySource_BARE_INIT(src, s->pk.ptr, s->pk.len); - s->alg = get_string(src); - } - - /* See if server will accept it */ - s->pktout = ssh_bpp_new_pktout( - ssh->bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, ssh->username); - put_stringz(s->pktout, "ssh-connection"); - /* service requested */ - put_stringz(s->pktout, "publickey"); - /* method */ - put_bool(s->pktout, FALSE); /* no signature included */ - put_stringpl(s->pktout, s->alg); - put_stringpl(s->pktout, s->pk); - ssh2_pkt_send(ssh, s->pktout); - s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET; - - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_userauth)) != NULL); - if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) { - - /* Offer of key refused, presumably via - * USERAUTH_FAILURE. Requeue for the next iteration. */ - pq_push_front(&ssh->pq_ssh2_userauth, pktin); - - } else { - strbuf *agentreq, *sigdata; - void *r; - int rlen; - - if (flags & FLAG_VERBOSE) { - c_write_str(ssh, "Authenticating with " - "public key \""); - c_write(ssh, s->comment.ptr, s->comment.len); - c_write_str(ssh, "\" from agent\r\n"); - } - - /* - * Server is willing to accept the key. - * Construct a SIGN_REQUEST. - */ - s->pktout = ssh_bpp_new_pktout( - ssh->bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, ssh->username); - put_stringz(s->pktout, "ssh-connection"); - /* service requested */ - put_stringz(s->pktout, "publickey"); - /* method */ - put_bool(s->pktout, TRUE); /* signature included */ - put_stringpl(s->pktout, s->alg); - put_stringpl(s->pktout, s->pk); - - /* Ask agent for signature. */ - agentreq = strbuf_new_for_agent_query(); - put_byte(agentreq, SSH2_AGENTC_SIGN_REQUEST); - put_stringpl(agentreq, s->pk); - /* Now the data to be signed... */ - sigdata = strbuf_new(); - if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID) { - put_data(sigdata, ssh->v2_session_id, - ssh->v2_session_id_len); - } else { - put_string(sigdata, ssh->v2_session_id, - ssh->v2_session_id_len); - } - put_data(sigdata, s->pktout->data + 5, - s->pktout->length - 5); - put_stringsb(agentreq, sigdata); - /* And finally the (zero) flags word. */ - put_uint32(agentreq, 0); - ssh->auth_agent_query = agent_query( - agentreq, &r, &rlen, ssh_agent_callback, ssh); - strbuf_free(agentreq); - - if (ssh->auth_agent_query) { - ssh->agent_response = NULL; - crWaitUntilV(ssh->agent_response); - r = ssh->agent_response; - rlen = ssh->agent_response_len; - } - if (r) { - ptrlen sigblob; - BinarySource src[1]; - BinarySource_BARE_INIT(src, r, rlen); - get_uint32(src); /* skip length field */ - if (get_byte(src) == SSH2_AGENT_SIGN_RESPONSE && - (sigblob = get_string(src), !get_err(src))) { - logevent("Sending Pageant's response"); - ssh2_add_sigblob(ssh, s->pktout, - s->pk.ptr, s->pk.len, - sigblob.ptr, sigblob.len); - ssh2_pkt_send(ssh, s->pktout); - s->type = AUTH_TYPE_PUBLICKEY; - } else { - /* FIXME: less drastic response */ - bombout(("Pageant failed to answer challenge")); - crStopV; - } - } - } - - /* Do we have any keys left to try? */ - if (s->pkblob_pos_in_agent) { - s->done_agent = TRUE; - s->tried_pubkey_config = TRUE; - } else { - s->keyi++; - if (s->keyi >= s->nkeys) - s->done_agent = TRUE; - } - - } else if (s->can_pubkey && s->publickey_blob && - s->privatekey_available && !s->tried_pubkey_config) { - - struct ssh2_userkey *key; /* not live over crReturn */ - char *passphrase; /* not live over crReturn */ - - ssh->pls.actx = SSH2_PKTCTX_PUBLICKEY; - - s->tried_pubkey_config = TRUE; - - /* - * Try the public key supplied in the configuration. - * - * First, offer the public blob to see if the server is - * willing to accept it. - */ - s->pktout = ssh_bpp_new_pktout( - ssh->bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, ssh->username); - put_stringz(s->pktout, "ssh-connection"); - /* service requested */ - put_stringz(s->pktout, "publickey"); /* method */ - put_bool(s->pktout, FALSE); - /* no signature included */ - put_stringz(s->pktout, s->publickey_algorithm); - put_string(s->pktout, s->publickey_blob->s, - s->publickey_blob->len); - ssh2_pkt_send(ssh, s->pktout); - logevent("Offered public key"); - - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_userauth)) != NULL); - if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) { - /* Key refused. Give up. */ - pq_push_front(&ssh->pq_ssh2_userauth, pktin); - s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD; - continue; /* process this new message */ - } - logevent("Offer of public key accepted"); - - /* - * Actually attempt a serious authentication using - * the key. - */ - if (flags & FLAG_VERBOSE) { - c_write_str(ssh, "Authenticating with public key \""); - c_write_str(ssh, s->publickey_comment); - c_write_str(ssh, "\"\r\n"); - } - key = NULL; - while (!key) { - const char *error; /* not live over crReturn */ - if (s->privatekey_encrypted) { - /* - * Get a passphrase from the user. - */ - s->cur_prompt = new_prompts(ssh->frontend); - s->cur_prompt->to_server = FALSE; - s->cur_prompt->name = dupstr("SSH key passphrase"); - add_prompt(s->cur_prompt, - dupprintf("Passphrase for key \"%.100s\": ", - s->publickey_comment), - FALSE); - s->userpass_ret = get_userpass_input( - s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(&ssh->user_input) > 0) - s->userpass_ret = get_userpass_input( - s->cur_prompt, &ssh->user_input); - - if (s->userpass_ret >= 0) - break; - - ssh->send_ok = 1; - crReturnV; - ssh->send_ok = 0; - } - if (!s->userpass_ret) { - /* Failed to get a passphrase. Terminate. */ - free_prompts(s->cur_prompt); - ssh_disconnect(ssh, NULL, - "Unable to authenticate", - SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, - TRUE); - crStopV; - } - passphrase = - dupstr(s->cur_prompt->prompts[0]->result); - free_prompts(s->cur_prompt); - } else { - passphrase = NULL; /* no passphrase needed */ - } - - /* - * Try decrypting the key. - */ - s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile); - key = ssh2_load_userkey(s->keyfile, passphrase, &error); - if (passphrase) { - /* burn the evidence */ - smemclr(passphrase, strlen(passphrase)); - sfree(passphrase); - } - if (key == SSH2_WRONG_PASSPHRASE || key == NULL) { - if (passphrase && - (key == SSH2_WRONG_PASSPHRASE)) { - c_write_str(ssh, "Wrong passphrase\r\n"); - key = NULL; - /* and loop again */ - } else { - c_write_str(ssh, "Unable to load private key ("); - c_write_str(ssh, error); - c_write_str(ssh, ")\r\n"); - key = NULL; - break; /* try something else */ - } - } - } - - if (key) { - strbuf *pkblob, *sigdata, *sigblob; - - /* - * We have loaded the private key and the server - * has announced that it's willing to accept it. - * Hallelujah. Generate a signature and send it. - */ - s->pktout = ssh_bpp_new_pktout( - ssh->bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, ssh->username); - put_stringz(s->pktout, "ssh-connection"); - /* service requested */ - put_stringz(s->pktout, "publickey"); /* method */ - put_bool(s->pktout, TRUE); /* signature follows */ - put_stringz(s->pktout, ssh_key_ssh_id(key->key)); - pkblob = strbuf_new(); - ssh_key_public_blob(key->key, BinarySink_UPCAST(pkblob)); - put_string(s->pktout, pkblob->s, pkblob->len); - - /* - * The data to be signed is: - * - * string session-id - * - * followed by everything so far placed in the - * outgoing packet. - */ - sigdata = strbuf_new(); - if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID) { - put_data(sigdata, ssh->v2_session_id, - ssh->v2_session_id_len); - } else { - put_string(sigdata, ssh->v2_session_id, - ssh->v2_session_id_len); - } - put_data(sigdata, s->pktout->data + 5, - s->pktout->length - 5); - sigblob = strbuf_new(); - ssh_key_sign(key->key, sigdata->s, sigdata->len, - BinarySink_UPCAST(sigblob)); - strbuf_free(sigdata); - ssh2_add_sigblob(ssh, s->pktout, pkblob->s, pkblob->len, - sigblob->s, sigblob->len); - strbuf_free(pkblob); - strbuf_free(sigblob); - - ssh2_pkt_send(ssh, s->pktout); - logevent("Sent public key signature"); - s->type = AUTH_TYPE_PUBLICKEY; - ssh_key_free(key->key); - sfree(key->comment); - sfree(key); - } - -#ifndef NO_GSSAPI - } else if (s->can_gssapi && !s->tried_gssapi) { - - /* gssapi-with-mic authentication */ - - ptrlen data; - - s->type = AUTH_TYPE_GSSAPI; - s->tried_gssapi = TRUE; - ssh->pls.actx = SSH2_PKTCTX_GSSAPI; - - if (ssh->gsslib->gsslogmsg) - logevent(ssh->gsslib->gsslogmsg); - - /* Sending USERAUTH_REQUEST with "gssapi-with-mic" method */ - logeventf(ssh, "Trying gssapi-with-mic..."); - s->pktout = ssh_bpp_new_pktout( - ssh->bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, ssh->username); - put_stringz(s->pktout, "ssh-connection"); - put_stringz(s->pktout, "gssapi-with-mic"); - logevent("Attempting GSSAPI authentication"); - - /* add mechanism info */ - ssh->gsslib->indicate_mech(ssh->gsslib, &s->gss_buf); - - /* number of GSSAPI mechanisms */ - put_uint32(s->pktout, 1); - - /* length of OID + 2 */ - put_uint32(s->pktout, s->gss_buf.length + 2); - put_byte(s->pktout, SSH2_GSS_OIDTYPE); - - /* length of OID */ - put_byte(s->pktout, s->gss_buf.length); - - put_data(s->pktout, s->gss_buf.value, s->gss_buf.length); - ssh2_pkt_send(ssh, s->pktout); - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_userauth)) != NULL); - if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_RESPONSE) { - logevent("GSSAPI authentication request refused"); - pq_push_front(&ssh->pq_ssh2_userauth, pktin); - continue; - } - - /* check returned packet ... */ - - data = get_string(pktin); - s->gss_rcvtok.value = (char *)data.ptr; - s->gss_rcvtok.length = data.len; - if (s->gss_rcvtok.length != s->gss_buf.length + 2 || - ((char *)s->gss_rcvtok.value)[0] != SSH2_GSS_OIDTYPE || - ((char *)s->gss_rcvtok.value)[1] != s->gss_buf.length || - memcmp((char *)s->gss_rcvtok.value + 2, - s->gss_buf.value,s->gss_buf.length) ) { - logevent("GSSAPI authentication - wrong response from server"); - continue; - } - - /* Import server name if not cached from KEX */ - if (ssh->gss_srv_name == GSS_C_NO_NAME) { - s->gss_stat = ssh->gsslib->import_name(ssh->gsslib, - ssh->fullhostname, - &ssh->gss_srv_name); - if (s->gss_stat != SSH_GSS_OK) { - if (s->gss_stat == SSH_GSS_BAD_HOST_NAME) - logevent("GSSAPI import name failed -" - " Bad service name"); - else - logevent("GSSAPI import name failed"); - continue; - } - } - - /* Allocate our gss_ctx */ - s->gss_stat = ssh->gsslib->acquire_cred(ssh->gsslib, - &s->gss_ctx, NULL); - if (s->gss_stat != SSH_GSS_OK) { - logevent("GSSAPI authentication failed to get credentials"); - continue; - } - - /* initial tokens are empty */ - SSH_GSS_CLEAR_BUF(&s->gss_rcvtok); - SSH_GSS_CLEAR_BUF(&s->gss_sndtok); - - /* now enter the loop */ - do { - /* - * When acquire_cred yields no useful expiration, go with - * the service ticket expiration. - */ - s->gss_stat = ssh->gsslib->init_sec_context - (ssh->gsslib, - &s->gss_ctx, - ssh->gss_srv_name, - conf_get_int(ssh->conf, CONF_gssapifwd), - &s->gss_rcvtok, - &s->gss_sndtok, - NULL, - NULL); - - if (s->gss_stat!=SSH_GSS_S_COMPLETE && - s->gss_stat!=SSH_GSS_S_CONTINUE_NEEDED) { - logevent("GSSAPI authentication initialisation failed"); - - if (ssh->gsslib->display_status(ssh->gsslib, - s->gss_ctx, &s->gss_buf) == SSH_GSS_OK) { - logevent(s->gss_buf.value); - sfree(s->gss_buf.value); - } - - pq_push_front(&ssh->pq_ssh2_userauth, pktin); - break; - } - logevent("GSSAPI authentication initialised"); - - /* - * Client and server now exchange tokens until GSSAPI - * no longer says CONTINUE_NEEDED - */ - if (s->gss_sndtok.length != 0) { - s->pktout = - ssh_bpp_new_pktout( - ssh->bpp, SSH2_MSG_USERAUTH_GSSAPI_TOKEN); - put_string(s->pktout, - s->gss_sndtok.value, s->gss_sndtok.length); - ssh2_pkt_send(ssh, s->pktout); - ssh->gsslib->free_tok(ssh->gsslib, &s->gss_sndtok); - } - - if (s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED) { - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_userauth)) != NULL); - if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_TOKEN) { - logevent("GSSAPI authentication -" - " bad server response"); - s->gss_stat = SSH_GSS_FAILURE; - pq_push_front(&ssh->pq_ssh2_userauth, pktin); - break; - } - data = get_string(pktin); - s->gss_rcvtok.value = (char *)data.ptr; - s->gss_rcvtok.length = data.len; - } - } while (s-> gss_stat == SSH_GSS_S_CONTINUE_NEEDED); - - if (s->gss_stat != SSH_GSS_OK) { - ssh->gsslib->release_cred(ssh->gsslib, &s->gss_ctx); - continue; - } - logevent("GSSAPI authentication loop finished OK"); - - /* Now send the MIC */ - - s->pktout = - ssh2_gss_authpacket(ssh, s->gss_ctx, "gssapi-with-mic"); - ssh2_pkt_send(ssh, s->pktout); - - ssh->gsslib->release_cred(ssh->gsslib, &s->gss_ctx); - continue; -#endif - } else if (s->can_keyb_inter && !s->kbd_inter_refused) { - - /* - * Keyboard-interactive authentication. - */ - - s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; - - ssh->pls.actx = SSH2_PKTCTX_KBDINTER; - - s->pktout = ssh_bpp_new_pktout( - ssh->bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, ssh->username); - put_stringz(s->pktout, "ssh-connection"); - /* service requested */ - put_stringz(s->pktout, "keyboard-interactive"); - /* method */ - put_stringz(s->pktout, ""); /* lang */ - put_stringz(s->pktout, ""); /* submethods */ - ssh2_pkt_send(ssh, s->pktout); - - logevent("Attempting keyboard-interactive authentication"); - - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_userauth)) != NULL); - if (pktin->type != SSH2_MSG_USERAUTH_INFO_REQUEST) { - /* Server is not willing to do keyboard-interactive - * at all (or, bizarrely but legally, accepts the - * user without actually issuing any prompts). - * Give up on it entirely. */ - pq_push_front(&ssh->pq_ssh2_userauth, pktin); - s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET; - s->kbd_inter_refused = TRUE; /* don't try it again */ - continue; - } - - /* - * Loop while the server continues to send INFO_REQUESTs. - */ - while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { - - ptrlen name, inst; - int i; - - /* - * We've got a fresh USERAUTH_INFO_REQUEST. - * Get the preamble and start building a prompt. - */ - name = get_string(pktin); - inst = get_string(pktin); - get_string(pktin); /* skip language tag */ - s->cur_prompt = new_prompts(ssh->frontend); - s->cur_prompt->to_server = TRUE; - - /* - * Get any prompt(s) from the packet. - */ - s->num_prompts = get_uint32(pktin); - for (i = 0; i < s->num_prompts; i++) { - ptrlen prompt; - int echo; - static char noprompt[] = - ": "; - - prompt = get_string(pktin); - echo = get_bool(pktin); - if (!prompt.len) { - prompt.ptr = noprompt; - prompt.len = lenof(noprompt)-1; - } - add_prompt(s->cur_prompt, mkstr(prompt), echo); - } - - if (name.len) { - /* FIXME: better prefix to distinguish from - * local prompts? */ - s->cur_prompt->name = - dupprintf("SSH server: %.*s", PTRLEN_PRINTF(name)); - s->cur_prompt->name_reqd = TRUE; - } else { - s->cur_prompt->name = - dupstr("SSH server authentication"); - s->cur_prompt->name_reqd = FALSE; - } - /* We add a prefix to try to make it clear that a prompt - * has come from the server. - * FIXME: ugly to print "Using..." in prompt _every_ - * time round. Can this be done more subtly? */ - /* Special case: for reasons best known to themselves, - * some servers send k-i requests with no prompts and - * nothing to display. Keep quiet in this case. */ - if (s->num_prompts || name.len || inst.len) { - s->cur_prompt->instruction = - dupprintf("Using keyboard-interactive " - "authentication.%s%.*s", - inst.len ? "\n" : "", - PTRLEN_PRINTF(inst)); - s->cur_prompt->instr_reqd = TRUE; - } else { - s->cur_prompt->instr_reqd = FALSE; - } - - /* - * Display any instructions, and get the user's - * response(s). - */ - s->userpass_ret = get_userpass_input(s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(&ssh->user_input) > 0) - s->userpass_ret = get_userpass_input( - s->cur_prompt, &ssh->user_input); - - if (s->userpass_ret >= 0) - break; - - ssh->send_ok = 1; - crReturnV; - ssh->send_ok = 0; - } - if (!s->userpass_ret) { - /* - * Failed to get responses. Terminate. - */ - free_prompts(s->cur_prompt); - ssh_disconnect(ssh, NULL, "Unable to authenticate", - SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, - TRUE); - crStopV; - } - - /* - * Send the response(s) to the server, padding - * them to disguise their true length. - */ - s->pktout = ssh_bpp_new_pktout( - ssh->bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE); - put_uint32(s->pktout, s->num_prompts); - for (i=0; i < s->num_prompts; i++) { - put_stringz(s->pktout, - s->cur_prompt->prompts[i]->result); - } - s->pktout->minlen = 256; - ssh2_pkt_send(ssh, s->pktout); - - /* - * Free the prompts structure from this iteration. - * If there's another, a new one will be allocated - * when we return to the top of this while loop. - */ - free_prompts(s->cur_prompt); - - /* - * Get the next packet in case it's another - * INFO_REQUEST. - */ - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_userauth)) != NULL); - - } - - /* - * We should have SUCCESS or FAILURE now. - */ - pq_push_front(&ssh->pq_ssh2_userauth, pktin); - - } else if (s->can_passwd) { - - /* - * Plain old password authentication. - */ - int changereq_first_time; /* not live over crReturn */ - - ssh->pls.actx = SSH2_PKTCTX_PASSWORD; - - s->cur_prompt = new_prompts(ssh->frontend); - s->cur_prompt->to_server = TRUE; - s->cur_prompt->name = dupstr("SSH password"); - add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ", - ssh->username, - ssh->savedhost), - FALSE); - - s->userpass_ret = get_userpass_input(s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(&ssh->user_input) > 0) - s->userpass_ret = get_userpass_input( - s->cur_prompt, &ssh->user_input); - - if (s->userpass_ret >= 0) - break; - - ssh->send_ok = 1; - crReturnV; - ssh->send_ok = 0; - } - if (!s->userpass_ret) { - /* - * Failed to get responses. Terminate. - */ - free_prompts(s->cur_prompt); - ssh_disconnect(ssh, NULL, "Unable to authenticate", - SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, - TRUE); - crStopV; - } - /* - * Squirrel away the password. (We may need it later if - * asked to change it.) - */ - s->password = dupstr(s->cur_prompt->prompts[0]->result); - free_prompts(s->cur_prompt); - - /* - * Send the password packet. - * - * We pad out the password packet to 256 bytes to make - * it harder for an attacker to find the length of the - * user's password. - * - * Anyone using a password longer than 256 bytes - * probably doesn't have much to worry about from - * people who find out how long their password is! - */ - s->pktout = ssh_bpp_new_pktout( - ssh->bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, ssh->username); - put_stringz(s->pktout, "ssh-connection"); - /* service requested */ - put_stringz(s->pktout, "password"); - put_bool(s->pktout, FALSE); - put_stringz(s->pktout, s->password); - s->pktout->minlen = 256; - ssh2_pkt_send(ssh, s->pktout); - logevent("Sent password"); - s->type = AUTH_TYPE_PASSWORD; - - /* - * Wait for next packet, in case it's a password change - * request. - */ - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_userauth)) != NULL); - changereq_first_time = TRUE; - - while (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) { - - /* - * We're being asked for a new password - * (perhaps not for the first time). - * Loop until the server accepts it. - */ - - int got_new = FALSE; /* not live over crReturn */ - ptrlen prompt; /* not live over crReturn */ - - { - const char *msg; - if (changereq_first_time) - msg = "Server requested password change"; - else - msg = "Server rejected new password"; - logevent(msg); - c_write_str(ssh, msg); - c_write_str(ssh, "\r\n"); - } - - prompt = get_string(pktin); - - s->cur_prompt = new_prompts(ssh->frontend); - s->cur_prompt->to_server = TRUE; - s->cur_prompt->name = dupstr("New SSH password"); - s->cur_prompt->instruction = mkstr(prompt); - s->cur_prompt->instr_reqd = TRUE; - /* - * There's no explicit requirement in the protocol - * for the "old" passwords in the original and - * password-change messages to be the same, and - * apparently some Cisco kit supports password change - * by the user entering a blank password originally - * and the real password subsequently, so, - * reluctantly, we prompt for the old password again. - * - * (On the other hand, some servers don't even bother - * to check this field.) - */ - add_prompt(s->cur_prompt, - dupstr("Current password (blank for previously entered password): "), - FALSE); - add_prompt(s->cur_prompt, dupstr("Enter new password: "), - FALSE); - add_prompt(s->cur_prompt, dupstr("Confirm new password: "), - FALSE); - - /* - * Loop until the user manages to enter the same - * password twice. - */ - while (!got_new) { - s->userpass_ret = get_userpass_input( - s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(&ssh->user_input) > 0) - s->userpass_ret = get_userpass_input( - s->cur_prompt, &ssh->user_input); - - if (s->userpass_ret >= 0) - break; - - ssh->send_ok = 1; - crReturnV; - ssh->send_ok = 0; - } - if (!s->userpass_ret) { - /* - * Failed to get responses. Terminate. - */ - /* burn the evidence */ - free_prompts(s->cur_prompt); - smemclr(s->password, strlen(s->password)); - sfree(s->password); - ssh_disconnect(ssh, NULL, "Unable to authenticate", - SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, - TRUE); - crStopV; - } - - /* - * If the user specified a new original password - * (IYSWIM), overwrite any previously specified - * one. - * (A side effect is that the user doesn't have to - * re-enter it if they louse up the new password.) - */ - if (s->cur_prompt->prompts[0]->result[0]) { - smemclr(s->password, strlen(s->password)); - /* burn the evidence */ - sfree(s->password); - s->password = - dupstr(s->cur_prompt->prompts[0]->result); - } - - /* - * Check the two new passwords match. - */ - got_new = (strcmp(s->cur_prompt->prompts[1]->result, - s->cur_prompt->prompts[2]->result) - == 0); - if (!got_new) - /* They don't. Silly user. */ - c_write_str(ssh, "Passwords do not match\r\n"); - - } - - /* - * Send the new password (along with the old one). - * (see above for padding rationale) - */ - s->pktout = ssh_bpp_new_pktout( - ssh->bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, ssh->username); - put_stringz(s->pktout, "ssh-connection"); - /* service requested */ - put_stringz(s->pktout, "password"); - put_bool(s->pktout, TRUE); - put_stringz(s->pktout, s->password); - put_stringz(s->pktout, - s->cur_prompt->prompts[1]->result); - free_prompts(s->cur_prompt); - s->pktout->minlen = 256; - ssh2_pkt_send(ssh, s->pktout); - logevent("Sent new password"); - - /* - * Now see what the server has to say about it. - * (If it's CHANGEREQ again, it's not happy with the - * new password.) - */ - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_userauth)) != NULL); - changereq_first_time = FALSE; - - } - - /* - * We need to reexamine the current pktin at the top - * of the loop. Either: - * - we weren't asked to change password at all, in - * which case it's a SUCCESS or FAILURE with the - * usual meaning - * - we sent a new password, and the server was - * either OK with it (SUCCESS or FAILURE w/partial - * success) or unhappy with the _old_ password - * (FAILURE w/o partial success) - * In any of these cases, we go back to the top of - * the loop and start again. - */ - pq_push_front(&ssh->pq_ssh2_userauth, pktin); - - /* - * We don't need the old password any more, in any - * case. Burn the evidence. - */ - smemclr(s->password, strlen(s->password)); - sfree(s->password); - - } else { - char *str = dupprintf( - "No supported authentication methods available" - " (server sent: %.*s)", PTRLEN_PRINTF(methods)); - - ssh_disconnect(ssh, str, - "No supported authentication methods available", - SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, - FALSE); - sfree(str); - - crStopV; - - } - - } - } - ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = NULL; - - /* Clear up various bits and pieces from authentication. */ - if (s->publickey_blob) { - sfree(s->publickey_algorithm); - strbuf_free(s->publickey_blob); - sfree(s->publickey_comment); - } - if (s->agent_response) - sfree(s->agent_response); - - if (s->userauth_success) { - /* - * We've just received USERAUTH_SUCCESS, and we haven't sent - * any packets since. Signal the transport layer to consider - * doing an immediate rekey, if it has any reason to want to. - * - * (Relying on we_are_in is not sufficient. One of the reasons - * to do a post-userauth rekey is OpenSSH delayed compression; - * draft-miller-secsh-compression-delayed is quite clear that - * that triggers on USERAUTH_SUCCESS specifically, and - * we_are_in can become set for other reasons.) - */ - ssh->rekey_reason = NULL; /* will be filled in later */ - ssh->rekey_class = RK_POST_USERAUTH; - queue_idempotent_callback(&ssh->ssh2_transport_icb); - } - - /* - * Finally, hand over to the connection layer. - */ - do_ssh2_connection(ssh); - ssh->current_user_input_fn = ssh2_connection_input; - queue_idempotent_callback(&ssh->user_input_consumer); - - crFinishV; -} - -static void ssh2_userauth_input(Ssh ssh) -{ - do_ssh2_userauth(ssh); -} - -/* - * Handle the SSH-2 connection layer. - */ -static void ssh2_msg_connection(Ssh ssh, PktIn *pktin) -{ - pq_push(&ssh->pq_ssh2_connection, pktin); - queue_idempotent_callback(&ssh->ssh2_connection_icb); -} - -static void ssh2_response_connection(struct ssh_channel *c, - PktIn *pktin, void *ctx) -{ - if (pktin) - ssh2_msg_connection(c->ssh, pktin); -} - -static void ssh2_connection_setup(Ssh ssh) -{ - /* - * Initially, most connection-protocol messages go to the function - * that queues them for handling by the main do_ssh2_connection - * coroutine. - */ - ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = ssh2_msg_connection; - ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = ssh2_msg_connection; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = ssh2_msg_connection; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = - ssh2_msg_connection; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = ssh2_msg_connection; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = ssh2_msg_connection; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = ssh2_msg_connection; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_connection; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_connection; - - /* - * But a couple of them are easier to pass to special handler - * functions right from the start. - */ - ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = - ssh2_msg_channel_window_adjust; - ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = - ssh2_msg_global_request; - - /* - * Ensure our channels tree exists. - */ - if (!ssh->channels) - ssh->channels = newtree234(ssh_channelcmp); -} - -typedef struct mainchan { - Ssh ssh; - SshChannel *sc; - - Channel chan; -} mainchan; - -static void mainchan_free(Channel *chan); -static void mainchan_open_confirmation(Channel *chan); -static void mainchan_open_failure(Channel *chan, const char *errtext); -static int mainchan_send(Channel *chan, int is_stderr, const void *, int); -static void mainchan_send_eof(Channel *chan); -static void mainchan_set_input_wanted(Channel *chan, int wanted); -static char *mainchan_log_close_msg(Channel *chan); - -static const struct ChannelVtable mainchan_channelvt = { - mainchan_free, - mainchan_open_confirmation, - mainchan_open_failure, - mainchan_send, - mainchan_send_eof, - mainchan_set_input_wanted, - mainchan_log_close_msg, - chan_no_eager_close, -}; - -static mainchan *mainchan_new(Ssh ssh) -{ - mainchan *mc = snew(mainchan); - mc->ssh = ssh; - mc->sc = NULL; - mc->chan.vt = &mainchan_channelvt; - mc->chan.initial_fixed_window_size = 0; - return mc; -} - -static void mainchan_free(Channel *chan) -{ - assert(chan->vt == &mainchan_channelvt); - mainchan *mc = FROMFIELD(chan, mainchan, chan); - mc->ssh->mainchan = NULL; - sfree(mc); -} - -static void mainchan_open_confirmation(Channel *chan) -{ - assert(FALSE && "OPEN_CONFIRMATION for main channel should be " - "handled by connection layer setup"); -} - -static void mainchan_open_failure(Channel *chan, const char *errtext) -{ - assert(FALSE && "OPEN_FAILURE for main channel should be " - "handled by connection layer setup"); -} - -static int mainchan_send(Channel *chan, int is_stderr, - const void *data, int length) -{ - assert(chan->vt == &mainchan_channelvt); - mainchan *mc = FROMFIELD(chan, mainchan, chan); - return from_backend(mc->ssh->frontend, is_stderr, data, length); -} - -static void mainchan_send_eof(Channel *chan) -{ - assert(chan->vt == &mainchan_channelvt); - mainchan *mc = FROMFIELD(chan, mainchan, chan); - - if (!mc->ssh->sent_console_eof && - (from_backend_eof(mc->ssh->frontend) || mc->ssh->got_pty)) { - /* - * Either from_backend_eof told us that the front end wants us - * to close the outgoing side of the connection as soon as we - * see EOF from the far end, or else we've unilaterally - * decided to do that because we've allocated a remote pty and - * hence EOF isn't a particularly meaningful concept. - */ - sshfwd_write_eof(mc->sc); - } - mc->ssh->sent_console_eof = TRUE; -} - -static void mainchan_set_input_wanted(Channel *chan, int wanted) -{ - assert(chan->vt == &mainchan_channelvt); - mainchan *mc = FROMFIELD(chan, mainchan, chan); - - /* - * This is the main channel of the SSH session, i.e. the one tied - * to the standard input (or GUI) of the primary SSH client user - * interface. So ssh->send_ok is how we control whether we're - * reading from that input. - */ - mc->ssh->send_ok = wanted; -} - -static char *mainchan_log_close_msg(Channel *chan) -{ - return dupstr("Main session channel closed"); -} - -static void do_ssh2_connection(void *vctx) -{ - Ssh ssh = (Ssh)vctx; - PktIn *pktin; - - struct do_ssh2_connection_state { - int crLine; - PktOut *pktout; - }; - - crState(do_ssh2_connection_state); - - crBeginState; - - /* - * Initialise our dispatch table entries. Normally this will have - * been done as a side effect of USERAUTH_SUCCESS, but in some - * cases, it might not (e.g. if we bypassed userauth, or if we're - * running the bare-connection protocol). - */ - ssh2_connection_setup(ssh); - - /* - * Create the main session channel. - */ - if (conf_get_int(ssh->conf, CONF_ssh_no_shell)) { - ssh->mainchan = NULL; - } else { - mainchan *mc = mainchan_new(ssh); - - if (*conf_get_str(ssh->conf, CONF_ssh_nc_host)) { - /* - * Just start a direct-tcpip channel and use it as the main - * channel. - */ - mc->sc = ssh_lportfwd_open - (&ssh->cl, conf_get_str(ssh->conf, CONF_ssh_nc_host), - conf_get_int(ssh->conf, CONF_ssh_nc_port), - "main channel", &mc->chan); - ssh->mainchan = FROMFIELD(mc->sc, struct ssh_channel, sc); - ssh->ncmode = TRUE; - } else { - ssh->mainchan = snew(struct ssh_channel); - mc->sc = &ssh->mainchan->sc; - ssh->mainchan->ssh = ssh; - ssh_channel_init(ssh->mainchan); - ssh->mainchan->chan = &mc->chan; - s->pktout = ssh2_chanopen_init(ssh->mainchan, "session"); - logevent("Opening session as main channel"); - ssh2_pkt_send(ssh, s->pktout); - ssh->ncmode = FALSE; - } - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_connection)) != NULL); - if (pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION && - pktin->type != SSH2_MSG_CHANNEL_OPEN_FAILURE) { - bombout(("Server sent strange packet %d in response to main " - "channel open request", pktin->type)); - crStopV; - } - if (get_uint32(pktin) != ssh->mainchan->localid) { - bombout(("Server's response to main channel open cited wrong" - " channel number")); - crStopV; - } - if (pktin->type == SSH2_MSG_CHANNEL_OPEN_FAILURE) { - char *errtext = ssh2_channel_open_failure_error_text(pktin); - bombout(("Server refused to open main channel: %s", errtext)); - sfree(errtext); - crStopV; - } - - ssh->mainchan->remoteid = get_uint32(pktin); - ssh->mainchan->halfopen = FALSE; - ssh->mainchan->v.v2.remwindow = get_uint32(pktin); - ssh->mainchan->v.v2.remmaxpkt = get_uint32(pktin); - update_specials_menu(ssh->frontend); - logevent("Opened main channel"); - } - - /* - * Now we have a channel, make dispatch table entries for - * general channel-based messages. - */ - ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = - ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = - ssh2_msg_channel_data; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_channel_eof; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_channel_close; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = - ssh2_msg_channel_open_confirmation; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = - ssh2_msg_channel_open_failure; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = - ssh2_msg_channel_request; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = - ssh2_msg_channel_open; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_channel_response; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_channel_response; - - /* - * Put our current pending packet queue back to the front of - * the main pq, and then schedule a callback to re-process those - * packets (if any). That way, anything already in our queue that - * matches any of the table entries we've just modified will go to - * the right handler function, and won't come here to confuse us. - */ - if (pq_peek(&ssh->pq_ssh2_connection)) { - pq_concatenate(&ssh->bpp->in_pq, - &ssh->pq_ssh2_connection, &ssh->bpp->in_pq); - queue_idempotent_callback(ssh->bpp->in_pq.pqb.ic); - } - - /* - * Now the connection protocol is properly up and running, with - * all those dispatch table entries, so it's safe to let - * downstreams start trying to open extra channels through us. - */ - if (ssh->connshare) - share_activate(ssh->connshare, ssh->v_s); - - if (ssh->mainchan && ssh_is_simple(ssh)) { - /* - * This message indicates to the server that we promise - * not to try to run any other channel in parallel with - * this one, so it's safe for it to advertise a very large - * window and leave the flow control to TCP. - */ - s->pktout = ssh2_chanreq_init(ssh->mainchan, - "simple@putty.projects.tartarus.org", - NULL, NULL); - ssh2_pkt_send(ssh, s->pktout); - } - - /* - * Enable port forwardings. - */ - portfwdmgr_config(ssh->portfwdmgr, ssh->conf); - - if (ssh->mainchan && !ssh->ncmode) { - /* - * Send the CHANNEL_REQUESTS for the main session channel. - * Each one is handled by its own little asynchronous - * co-routine. - */ - - /* Potentially enable X11 forwarding. */ - if (conf_get_int(ssh->conf, CONF_x11_forward)) { - ssh->x11disp = - x11_setup_display(conf_get_str(ssh->conf, CONF_x11_display), - ssh->conf); - if (!ssh->x11disp) { - /* FIXME: return an error message from x11_setup_display */ - logevent("X11 forwarding not enabled: unable to" - " initialise X display"); - } else { - ssh->x11auth = x11_invent_fake_auth - (ssh->x11authtree, conf_get_int(ssh->conf, CONF_x11_auth)); - ssh->x11auth->disp = ssh->x11disp; - - ssh2_setup_x11(ssh->mainchan, NULL, NULL); - } - } - - /* Potentially enable agent forwarding. */ - if (ssh_agent_forwarding_permitted(&ssh->cl)) - ssh2_setup_agent(ssh->mainchan, NULL, NULL); - - /* Now allocate a pty for the session. */ - if (!conf_get_int(ssh->conf, CONF_nopty)) - ssh2_setup_pty(ssh->mainchan, NULL, NULL); - - /* Send environment variables. */ - ssh2_setup_env(ssh->mainchan, NULL, NULL); - - /* - * Start a shell or a remote command. We may have to attempt - * this twice if the config data has provided a second choice - * of command. - */ - while (1) { - int subsys; - char *cmd; - - if (ssh->fallback_cmd) { - subsys = conf_get_int(ssh->conf, CONF_ssh_subsys2); - cmd = conf_get_str(ssh->conf, CONF_remote_cmd2); - } else { - subsys = conf_get_int(ssh->conf, CONF_ssh_subsys); - cmd = conf_get_str(ssh->conf, CONF_remote_cmd); - } - - if (subsys) { - s->pktout = ssh2_chanreq_init(ssh->mainchan, "subsystem", - ssh2_response_connection, NULL); - put_stringz(s->pktout, cmd); - } else if (*cmd) { - s->pktout = ssh2_chanreq_init(ssh->mainchan, "exec", - ssh2_response_connection, NULL); - put_stringz(s->pktout, cmd); - } else { - s->pktout = ssh2_chanreq_init(ssh->mainchan, "shell", - ssh2_response_connection, NULL); - } - ssh2_pkt_send(ssh, s->pktout); - - crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_connection)) != NULL); - - if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) { - if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) { - bombout(("Unexpected response to shell/command request:" - " packet type %d", pktin->type)); - crStopV; - } - /* - * We failed to start the command. If this is the - * fallback command, we really are finished; if it's - * not, and if the fallback command exists, try falling - * back to it before complaining. - */ - if (!ssh->fallback_cmd && - *conf_get_str(ssh->conf, CONF_remote_cmd2)) { - logevent("Primary command failed; attempting fallback"); - ssh->fallback_cmd = TRUE; - continue; - } - bombout(("Server refused to start a shell/command")); - crStopV; - } else { - logevent("Started a shell/command"); - } - break; - } - } else { - ssh->editing = ssh->echoing = TRUE; - } - - ssh->state = SSH_STATE_SESSION; - if (ssh->size_needed) - backend_size(&ssh->backend, ssh->term_width, ssh->term_height); - if (ssh->eof_needed) - backend_special(&ssh->backend, SS_EOF, 0); - - /* - * Transfer data! - */ - if (ssh->ldisc) - ldisc_echoedit_update(ssh->ldisc); /* cause ldisc to notice changes */ - if (ssh->mainchan) - ssh->send_ok = 1; - while (1) { - if ((pktin = pq_pop(&ssh->pq_ssh2_connection)) != NULL) { - - /* - * _All_ the connection-layer packets we expect to - * receive are now handled by the dispatch table. - * Anything that reaches here must be bogus. - */ - - bombout(("Strange packet received: type %d", pktin->type)); - crStopV; - } - while (ssh->mainchan && bufchain_size(&ssh->user_input) > 0) { - /* - * Add user input to the main channel's buffer. - */ - void *data; - int len; - bufchain_prefix(&ssh->user_input, &data, &len); - ssh_send_channel_data(ssh->mainchan, data, len); - bufchain_consume(&ssh->user_input, len); - } - crReturnV; - } - - crFinishV; -} - -static void ssh2_connection_input(Ssh ssh) -{ - do_ssh2_connection(ssh); -} - -/* - * Handlers for SSH-2 messages that might arrive at any moment. - */ -static void ssh2_msg_disconnect(Ssh ssh, PktIn *pktin) -{ - /* log reason code in disconnect message */ - char *buf; - ptrlen msg; - int reason; - - reason = get_uint32(pktin); - msg = get_string(pktin); - - if (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) { - buf = dupprintf("Received disconnect message (%s)", - ssh2_disconnect_reasons[reason]); - } else { - buf = dupprintf("Received disconnect message (unknown" - " type %d)", reason); - } - logevent(buf); - sfree(buf); - buf = dupprintf("Disconnection message text: %.*s", PTRLEN_PRINTF(msg)); - logevent(buf); - bombout(("Server sent disconnect message\ntype %d (%s):\n\"%.*s\"", - reason, - (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ? - ssh2_disconnect_reasons[reason] : "unknown", PTRLEN_PRINTF(msg))); - sfree(buf); -} - -static void ssh2_msg_debug(Ssh ssh, PktIn *pktin) -{ - /* log the debug message */ - ptrlen msg; - - /* XXX maybe we should actually take notice of the return value */ - get_bool(pktin); - msg = get_string(pktin); - - logeventf(ssh, "Remote debug message: %.*s", PTRLEN_PRINTF(msg)); -} - -static void ssh2_msg_transport(Ssh ssh, PktIn *pktin) -{ - pq_push(&ssh->pq_ssh2_transport, pktin); - queue_idempotent_callback(&ssh->ssh2_transport_icb); -} - -/* - * Called if we receive a packet that isn't allowed by the protocol. - * This only applies to packets whose meaning PuTTY understands. - * Entirely unknown packets are handled below. - */ -static void ssh2_msg_unexpected(Ssh ssh, PktIn *pktin) -{ - char *buf = dupprintf("Server protocol violation: unexpected %s packet", - ssh2_pkt_type(ssh->pls.kctx, ssh->pls.actx, - pktin->type)); - ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE); - sfree(buf); -} - -/* - * Handle the top-level SSH-2 protocol. - */ -static void ssh2_protocol_setup(Ssh ssh) -{ - int i; - - ssh->bpp = ssh2_bpp_new(&ssh->stats); - -#ifndef NO_GSSAPI - /* Load and pick the highest GSS library on the preference list. */ - if (!ssh->gsslibs) - ssh->gsslibs = ssh_gss_setup(ssh->conf); - ssh->gsslib = NULL; - if (ssh->gsslibs->nlibraries > 0) { - int i, j; - for (i = 0; i < ngsslibs; i++) { - int want_id = conf_get_int_int(ssh->conf, - CONF_ssh_gsslist, i); - for (j = 0; j < ssh->gsslibs->nlibraries; j++) - if (ssh->gsslibs->libraries[j].id == want_id) { - ssh->gsslib = &ssh->gsslibs->libraries[j]; - goto got_gsslib; /* double break */ - } - } - got_gsslib: - /* - * We always expect to have found something in - * the above loop: we only came here if there - * was at least one viable GSS library, and the - * preference list should always mention - * everything and only change the order. - */ - assert(ssh->gsslib); - } -#endif - - /* - * All message types we don't set explicitly will dispatch to - * ssh2_msg_unexpected. - */ - for (i = 0; i < SSH_MAX_MSG; i++) - ssh->packet_dispatch[i] = ssh2_msg_unexpected; - - /* - * Initially, we only accept transport messages (and a few generic - * ones). do_ssh2_userauth and do_ssh2_connection will each add - * more when they start. - */ - ssh->packet_dispatch[SSH2_MSG_KEXINIT] = ssh2_msg_transport; - ssh->packet_dispatch[SSH2_MSG_NEWKEYS] = ssh2_msg_transport; - ssh->packet_dispatch[SSH2_MSG_KEXDH_INIT] = ssh2_msg_transport; - ssh->packet_dispatch[SSH2_MSG_KEXDH_REPLY] = ssh2_msg_transport; - /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REQUEST] = ssh2_msg_transport; duplicate case value */ - /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_GROUP] = ssh2_msg_transport; duplicate case value */ - ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_INIT] = ssh2_msg_transport; - ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REPLY] = ssh2_msg_transport; - ssh->packet_dispatch[SSH2_MSG_KEXGSS_GROUP] = ssh2_msg_transport; - - /* - * These messages have a special handler from the start. - */ - ssh->packet_dispatch[SSH2_MSG_DISCONNECT] = ssh2_msg_disconnect; - ssh->packet_dispatch[SSH2_MSG_IGNORE] = ssh_msg_ignore; /* shared with SSH-1 */ - ssh->packet_dispatch[SSH2_MSG_DEBUG] = ssh2_msg_debug; -} - -static void ssh2_bare_connection_protocol_setup(Ssh ssh) -{ - int i; - - ssh->bpp = ssh2_bare_bpp_new(); - - /* - * Everything defaults to ssh2_msg_unexpected for the moment; - * do_ssh2_connection will fill things in properly. But we do - * handle a couple of messages from the transport protocol which - * aren't related to key exchange (UNIMPLEMENTED, IGNORE, DEBUG, - * DISCONNECT). - */ - for (i = 0; i < SSH_MAX_MSG; i++) - ssh->packet_dispatch[i] = ssh2_msg_unexpected; - - ssh->packet_dispatch[SSH2_MSG_DISCONNECT] = ssh2_msg_disconnect; - ssh->packet_dispatch[SSH2_MSG_IGNORE] = ssh_msg_ignore; - ssh->packet_dispatch[SSH2_MSG_DEBUG] = ssh2_msg_debug; -} - -#ifndef NO_GSSAPI -static PktOut *ssh2_gss_authpacket(Ssh ssh, Ssh_gss_ctx gss_ctx, - const char *authtype) -{ - strbuf *sb; - PktOut *p; - Ssh_gss_buf buf; - Ssh_gss_buf mic; - - /* - * The mic is computed over the session id + intended - * USERAUTH_REQUEST packet. - */ - sb = strbuf_new(); - put_string(sb, ssh->v2_session_id, ssh->v2_session_id_len); - put_byte(sb, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(sb, ssh->username); - put_stringz(sb, "ssh-connection"); - put_stringz(sb, authtype); - - /* Compute the mic */ - buf.value = sb->s; - buf.length = sb->len; - ssh->gsslib->get_mic(ssh->gsslib, gss_ctx, &buf, &mic); - strbuf_free(sb); - - /* Now we can build the real packet */ - if (strcmp(authtype, "gssapi-with-mic") == 0) { - p = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_USERAUTH_GSSAPI_MIC); - } else { - p = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(p, ssh->username); - put_stringz(p, "ssh-connection"); - put_stringz(p, authtype); - } - put_string(p, mic.value, mic.length); - - return p; -} - -/* - * This is called at the beginning of each SSH rekey to determine whether we are - * GSS capable, and if we did GSS key exchange, and are delegating credentials, - * it is also called periodically to determine whether we should rekey in order - * to delegate (more) fresh credentials. This is called "credential cascading". - * - * On Windows, with SSPI, we may not get the credential expiration, as Windows - * automatically renews from cached passwords, so the credential effectively - * never expires. Since we still want to cascade when the local TGT is updated, - * we use the expiration of a newly obtained context as a proxy for the - * expiration of the TGT. - */ -static void ssh2_gss_update(Ssh ssh, int definitely_rekeying) -{ - int gss_stat; - time_t gss_cred_expiry; - unsigned long mins; - Ssh_gss_buf gss_sndtok; - Ssh_gss_buf gss_rcvtok; - Ssh_gss_ctx gss_ctx; - - ssh->gss_status = 0; - - /* - * Nothing to do if no GSSAPI libraries are configured or GSSAPI auth is not - * enabled. - */ - if (ssh->gsslibs->nlibraries == 0) - return; - if (!conf_get_int(ssh->conf, CONF_try_gssapi_auth) && - !conf_get_int(ssh->conf, CONF_try_gssapi_kex)) - return; - - /* Import server name and cache it */ - if (ssh->gss_srv_name == GSS_C_NO_NAME) { - gss_stat = ssh->gsslib->import_name(ssh->gsslib, - ssh->fullhostname, - &ssh->gss_srv_name); - if (gss_stat != SSH_GSS_OK) { - if (gss_stat == SSH_GSS_BAD_HOST_NAME) - logevent("GSSAPI import name failed -" - " Bad service name; won't use GSS key exchange"); - else - logevent("GSSAPI import name failed;" - " won't use GSS key exchange"); - return; - } - } - - /* - * Do we (still) have credentials? Capture the credential expiration when - * available - */ - gss_stat = ssh->gsslib->acquire_cred(ssh->gsslib, - &gss_ctx, - &gss_cred_expiry); - if (gss_stat != SSH_GSS_OK) - return; - - SSH_GSS_CLEAR_BUF(&gss_sndtok); - SSH_GSS_CLEAR_BUF(&gss_rcvtok); - - /* - * When acquire_cred yields no useful expiration, get a proxy for the cred - * expiration from the context expiration. - */ - gss_stat = ssh->gsslib->init_sec_context( - ssh->gsslib, &gss_ctx, ssh->gss_srv_name, - 0 /* don't delegate */, &gss_rcvtok, &gss_sndtok, - (gss_cred_expiry == GSS_NO_EXPIRATION ? &gss_cred_expiry : NULL), - &ssh->gss_ctxt_lifetime); - - /* This context was for testing only. */ - if (gss_ctx) - ssh->gsslib->release_cred(ssh->gsslib, &gss_ctx); - - if (gss_stat != SSH_GSS_OK && - gss_stat != SSH_GSS_S_CONTINUE_NEEDED) { - /* - * No point in verbosely interrupting the user to tell them we - * couldn't get GSS credentials, if this was only a check - * between key exchanges to see if fresh ones were available. - * When we do do a rekey, this message (if displayed) will - * appear among the standard rekey blurb, but when we're not, - * it shouldn't pop up all the time regardless. - */ - if (definitely_rekeying) - logeventf(ssh, "No GSSAPI security context available"); - - return; - } - - if (gss_sndtok.length) - ssh->gsslib->free_tok(ssh->gsslib, &gss_sndtok); - - ssh->gss_status |= GSS_KEX_CAPABLE; - - /* - * When rekeying to cascade, avoding doing this too close to the context - * expiration time, since the key exchange might fail. - */ - if (ssh->gss_ctxt_lifetime < MIN_CTXT_LIFETIME) - ssh->gss_status |= GSS_CTXT_MAYFAIL; - - /* - * If we're not delegating credentials, rekeying is not used to refresh - * them. We must avoid setting GSS_CRED_UPDATED or GSS_CTXT_EXPIRES when - * credential delegation is disabled. - */ - if (conf_get_int(ssh->conf, CONF_gssapifwd) == 0) - return; - - if (ssh->gss_cred_expiry != GSS_NO_EXPIRATION && - difftime(gss_cred_expiry, ssh->gss_cred_expiry) > 0) - ssh->gss_status |= GSS_CRED_UPDATED; - - mins = conf_get_int(ssh->conf, CONF_gssapirekey); - mins = rekey_mins(mins, GSS_DEF_REKEY_MINS); - if (mins > 0 && ssh->gss_ctxt_lifetime <= mins * 60) - ssh->gss_status |= GSS_CTXT_EXPIRES; -} -#endif - -/* - * The rekey_time is zero except when re-configuring. - * - * We either schedule the next timer and return 0, or return 1 to run the - * callback now, which will call us again to re-schedule on completion. - */ -static int ssh2_timer_update(Ssh ssh, unsigned long rekey_time) -{ - unsigned long mins; - unsigned long ticks; - - mins = conf_get_int(ssh->conf, CONF_ssh_rekey_time); - mins = rekey_mins(mins, 60); - ticks = mins * 60 * TICKSPERSEC; - - /* Handle change from previous setting */ - if (rekey_time != 0 && rekey_time != mins) { - unsigned long next; - unsigned long now = GETTICKCOUNT(); - - mins = rekey_time; - ticks = mins * 60 * TICKSPERSEC; - next = ssh->last_rekey + ticks; - - /* If overdue, caller will rekey synchronously now */ - if (now - ssh->last_rekey > ticks) - return 1; - ticks = next - now; - } - -#ifndef NO_GSSAPI - if (ssh->gss_kex_used) { - /* - * If we've used GSSAPI key exchange, then we should - * periodically check whether we need to do another one to - * pass new credentials to the server. - */ - unsigned long gssmins; - - /* Check cascade conditions more frequently if configured */ - gssmins = conf_get_int(ssh->conf, CONF_gssapirekey); - gssmins = rekey_mins(gssmins, GSS_DEF_REKEY_MINS); - if (gssmins > 0) { - if (gssmins < mins) - ticks = (mins = gssmins) * 60 * TICKSPERSEC; - - if ((ssh->gss_status & GSS_KEX_CAPABLE) != 0) { - /* - * Run next timer even sooner if it would otherwise be too close - * to the context expiration time - */ - if ((ssh->gss_status & GSS_CTXT_EXPIRES) == 0 && - ssh->gss_ctxt_lifetime - mins * 60 < 2 * MIN_CTXT_LIFETIME) - ticks -= 2 * MIN_CTXT_LIFETIME * TICKSPERSEC; - } - } - } -#endif - - /* Schedule the next timer */ - ssh->next_rekey = schedule_timer(ticks, ssh2_timer, ssh); - return 0; -} - -static void ssh2_timer(void *ctx, unsigned long now) -{ - Ssh ssh = (Ssh)ctx; - unsigned long mins; - unsigned long ticks; - - if (ssh->state == SSH_STATE_CLOSED || - ssh->kex_in_progress || - ssh->bare_connection || - now != ssh->next_rekey) - return; - - mins = conf_get_int(ssh->conf, CONF_ssh_rekey_time); - mins = rekey_mins(mins, 60); - if (mins == 0) - return; - - /* Rekey if enough time has elapsed */ - ticks = mins * 60 * TICKSPERSEC; - if (now - ssh->last_rekey > ticks - 30*TICKSPERSEC) { - ssh->rekey_reason = "timeout"; - ssh->rekey_class = RK_NORMAL; - queue_idempotent_callback(&ssh->ssh2_transport_icb); - return; - } - -#ifndef NO_GSSAPI - /* - * Rekey now if we have a new cred or context expires this cycle, but not if - * this is unsafe. - */ - if (conf_get_int(ssh->conf, CONF_gssapirekey)) { - ssh2_gss_update(ssh, FALSE); - if ((ssh->gss_status & GSS_KEX_CAPABLE) != 0 && - (ssh->gss_status & GSS_CTXT_MAYFAIL) == 0 && - (ssh->gss_status & (GSS_CRED_UPDATED|GSS_CTXT_EXPIRES)) != 0) { - ssh->rekey_reason = "GSS credentials updated"; - ssh->rekey_class = RK_GSS_UPDATE; - queue_idempotent_callback(&ssh->ssh2_transport_icb); - return; - } - } -#endif - - /* Try again later. */ - (void) ssh2_timer_update(ssh, 0); -} - -static void ssh2_general_packet_processing(Ssh ssh, PktIn *pktin) -{ - if (!ssh->kex_in_progress && ssh->max_data_size != 0 && - ssh->state != SSH_STATE_PREPACKET && !ssh->stats.in.running) { - ssh->rekey_reason = "too much data received"; - ssh->rekey_class = RK_NORMAL; - queue_idempotent_callback(&ssh->ssh2_transport_icb); - } + ssh_throttle_all_channels(ssh->cl, enable); } static void ssh_cache_conf_values(Ssh ssh) @@ -8959,143 +783,23 @@ static const char *ssh_init(Frontend *frontend, Backend **backend_handle, Ssh ssh; ssh = snew(struct ssh_tag); + memset(ssh, 0, sizeof(struct ssh_tag)); + ssh->conf = conf_copy(conf); ssh_cache_conf_values(ssh); - ssh->version = 0; /* when not ready yet */ - ssh->s = NULL; - ssh->kex = NULL; - ssh->dh_ctx = NULL; - ssh->hostkey_alg = NULL; - ssh->hostkey_str = NULL; ssh->exitcode = -1; - ssh->close_expected = FALSE; - ssh->clean_exit = FALSE; - ssh->disconnect_message_seen = FALSE; - ssh->state = SSH_STATE_PREPACKET; - ssh->size_needed = FALSE; - ssh->eof_needed = FALSE; - ssh->ldisc = NULL; - ssh->logctx = NULL; - ssh->fallback_cmd = 0; ssh->pls.kctx = SSH2_PKTCTX_NOKEX; ssh->pls.actx = SSH2_PKTCTX_NOAUTH; - ssh->x11disp = NULL; - ssh->x11auth = NULL; - ssh->x11authtree = newtree234(x11_authcmp); - ssh->v2_cbc_ignore_workaround = FALSE; - ssh->bpp = NULL; - ssh->do_ssh1_connection_crstate = 0; - ssh->do_ssh_init_state = NULL; - ssh->do_ssh_connection_init_state = NULL; - ssh->do_ssh1_login_state = NULL; - ssh->do_ssh2_transport_state = NULL; - ssh->do_ssh2_userauth_state = NULL; - ssh->do_ssh2_connection_state = NULL; - bufchain_init(&ssh->incoming_data); - ssh->incoming_data_seen_eof = FALSE; - ssh->incoming_data_eof_message = NULL; - ssh->incoming_data_consumer.fn = ssh_process_incoming_data; - ssh->incoming_data_consumer.ctx = ssh; - ssh->incoming_data_consumer.queued = FALSE; - ssh->incoming_pkt_consumer.fn = ssh_process_incoming_pkts; - ssh->incoming_pkt_consumer.ctx = ssh; - ssh->incoming_pkt_consumer.queued = FALSE; - pq_in_init(&ssh->pq_ssh1_login); - ssh->ssh1_login_icb.fn = do_ssh1_login; - ssh->ssh1_login_icb.ctx = ssh; - ssh->ssh1_login_icb.queued = FALSE; - pq_in_init(&ssh->pq_ssh1_connection); - ssh->ssh1_connection_icb.fn = do_ssh1_connection; - ssh->ssh1_connection_icb.ctx = ssh; - ssh->ssh1_connection_icb.queued = FALSE; - pq_in_init(&ssh->pq_ssh2_transport); - ssh->ssh2_transport_icb.fn = do_ssh2_transport; - ssh->ssh2_transport_icb.ctx = ssh; - ssh->ssh2_transport_icb.queued = FALSE; - pq_in_init(&ssh->pq_ssh2_userauth); - ssh->ssh2_userauth_icb.fn = do_ssh2_userauth; - ssh->ssh2_userauth_icb.ctx = ssh; - ssh->ssh2_userauth_icb.queued = FALSE; - pq_in_init(&ssh->pq_ssh2_connection); - ssh->ssh2_connection_icb.fn = do_ssh2_connection; - ssh->ssh2_connection_icb.ctx = ssh; - ssh->ssh2_connection_icb.queued = FALSE; + bufchain_init(&ssh->in_raw); + bufchain_init(&ssh->out_raw); bufchain_init(&ssh->user_input); - ssh->user_input_consumer.fn = ssh_process_user_input; - ssh->user_input_consumer.ctx = ssh; - ssh->user_input_consumer.queued = FALSE; - bufchain_init(&ssh->outgoing_data); - ssh->outgoing_data_sender.fn = ssh_send_outgoing_data; - ssh->outgoing_data_sender.ctx = ssh; - ssh->outgoing_data_sender.queued = FALSE; - ssh->current_user_input_fn = NULL; - ssh->rekey_reason = NULL; - ssh->rekey_class = RK_INITIAL; - ssh->v_c = NULL; - ssh->v_s = NULL; - ssh->mainchan = NULL; - ssh->throttled_all = 0; - ssh->v1_stdout_throttling = 0; - pq_out_init(&ssh->outq); - ssh->queueing = FALSE; - ssh->qhead = ssh->qtail = NULL; - ssh->deferred_rekey_reason = NULL; - ssh->frozen = FALSE; - ssh->username = NULL; - ssh->sent_console_eof = FALSE; - ssh->got_pty = FALSE; - ssh->bare_connection = FALSE; - ssh->X11_fwd_enabled = FALSE; - ssh->connshare = NULL; - ssh->attempting_connshare = FALSE; - ssh->session_started = FALSE; - ssh->specials = NULL; - ssh->n_uncert_hostkeys = 0; - ssh->cross_certifying = FALSE; - -#ifndef NO_GSSAPI - ssh->gss_cred_expiry = GSS_NO_EXPIRATION; - ssh->gss_srv_name = GSS_C_NO_NAME; - ssh->gss_ctx = NULL; - ssh_init_transient_hostkey_store(ssh); -#endif - ssh->gss_kex_used = FALSE; + ssh->ic_out_raw.fn = ssh_bpp_output_raw_data_callback; + ssh->ic_out_raw.ctx = ssh; ssh->backend.vt = &ssh_backend; *backend_handle = &ssh->backend; ssh->frontend = frontend; - ssh->term_width = conf_get_int(ssh->conf, CONF_width); - ssh->term_height = conf_get_int(ssh->conf, CONF_height); - - ssh->cl.vt = &ssh_connlayer_vtable; - ssh->cl.frontend = ssh->frontend; - - ssh->channels = NULL; - ssh->rportfwds = NULL; - ssh->portfwdmgr = portfwdmgr_new(&ssh->cl); - - ssh->send_ok = 0; - ssh->editing = 0; - ssh->echoing = 0; - ssh->conn_throttle_count = 0; - ssh->overall_bufsize = 0; - ssh->fallback_cmd = 0; - - ssh->general_packet_processing = NULL; - - ssh->pinger = NULL; - - memset(&ssh->stats, 0, sizeof(ssh->stats)); - ssh->max_data_size = parse_blocksize(conf_get_str(ssh->conf, - CONF_ssh_rekey_data)); - ssh->kex_in_progress = FALSE; - - ssh->auth_agent_query = NULL; - -#ifndef NO_GSSAPI - ssh->gsslibs = NULL; -#endif random_ref(); /* do this now - may be needed by sharing setup code */ ssh->need_random_unref = TRUE; @@ -9117,102 +821,28 @@ static const char *ssh_init(Frontend *frontend, Backend **backend_handle, static void ssh_free(Backend *be) { Ssh ssh = FROMFIELD(be, struct ssh_tag, backend); - struct ssh_channel *c; - struct ssh_rportfwd *pf; - struct X11FakeAuth *auth; int need_random_unref; - if (ssh->dh_ctx) - dh_cleanup(ssh->dh_ctx); - sfree(ssh->savedhost); + ssh_shutdown(ssh); - pq_out_clear(&ssh->outq); - - while (ssh->qhead) { - struct queued_handler *qh = ssh->qhead; - ssh->qhead = qh->next; - sfree(qh); - } - ssh->qhead = ssh->qtail = NULL; - - if (ssh->channels) { - while ((c = delpos234(ssh->channels, 0)) != NULL) { - ssh_channel_close_local(c, NULL); - if (ssh->version == 2) { - struct outstanding_channel_request *ocr, *nocr; - ocr = c->v.v2.chanreq_head; - while (ocr) { - ocr->handler(c, NULL, ocr->ctx); - nocr = ocr->next; - sfree(ocr); - ocr = nocr; - } - bufchain_clear(&c->v.v2.outbuffer); - } - sfree(c); - } - freetree234(ssh->channels); - ssh->channels = NULL; - } - - if (ssh->connshare) { - sharestate_free(ssh->connshare); - ssh->connshare = NULL; - } - - if (ssh->rportfwds) { - while ((pf = delpos234(ssh->rportfwds, 0)) != NULL) - free_rportfwd(pf); - freetree234(ssh->rportfwds); - ssh->rportfwds = NULL; - } - portfwdmgr_free(ssh->portfwdmgr); - if (ssh->x11disp) - x11_free_display(ssh->x11disp); - while ((auth = delpos234(ssh->x11authtree, 0)) != NULL) - x11_free_fake_auth(auth); - if (ssh->bpp) - ssh_bpp_free(ssh->bpp); - freetree234(ssh->x11authtree); - sfree(ssh->do_ssh_init_state); - sfree(ssh->do_ssh1_login_state); - sfree(ssh->do_ssh2_transport_state); - sfree(ssh->do_ssh2_userauth_state); - sfree(ssh->do_ssh2_connection_state); - bufchain_clear(&ssh->incoming_data); - bufchain_clear(&ssh->outgoing_data); - sfree(ssh->incoming_data_eof_message); - pq_in_clear(&ssh->pq_ssh1_login); - pq_in_clear(&ssh->pq_ssh1_connection); - pq_in_clear(&ssh->pq_ssh2_transport); - pq_in_clear(&ssh->pq_ssh2_userauth); - pq_in_clear(&ssh->pq_ssh2_connection); - bufchain_clear(&ssh->user_input); - sfree(ssh->v_c); - sfree(ssh->v_s); - sfree(ssh->fullhostname); - sfree(ssh->hostkey_str); - sfree(ssh->specials); - if (ssh->s) - ssh_do_close(ssh, TRUE); - expire_timer_context(ssh); - if (ssh->pinger) - pinger_free(ssh->pinger); - sfree(ssh->username); conf_free(ssh->conf); - - if (ssh->auth_agent_query) - agent_cancel_query(ssh->auth_agent_query); + if (ssh->connshare) + sharestate_free(ssh->connshare); + sfree(ssh->savedhost); + sfree(ssh->fullhostname); + sfree(ssh->specials); #ifndef NO_GSSAPI - if (ssh->gss_srv_name) - ssh->gsslib->release_name(ssh->gsslib, &ssh->gss_srv_name); - if (ssh->gss_ctx != NULL) - ssh->gsslib->release_cred(ssh->gsslib, &ssh->gss_ctx); - if (ssh->gsslibs) - ssh_gss_cleanup(ssh->gsslibs); - ssh_cleanup_transient_hostkey_store(ssh); + if (ssh->gss_state.srv_name) + ssh->gss_state.lib->release_name( + ssh->gss_state.lib, &ssh->gss_state.srv_name); + if (ssh->gss_state.ctx != NULL) + ssh->gss_state.lib->release_cred( + ssh->gss_state.lib, &ssh->gss_state.ctx); + if (ssh->gss_state.libs) + ssh_gss_cleanup(ssh->gss_state.libs); #endif + need_random_unref = ssh->need_random_unref; sfree(ssh); @@ -9226,72 +856,15 @@ static void ssh_free(Backend *be) static void ssh_reconfig(Backend *be, Conf *conf) { Ssh ssh = FROMFIELD(be, struct ssh_tag, backend); - const char *rekeying = NULL; - int rekey_mandatory = FALSE; - unsigned long old_max_data_size; - int i, rekey_time; - pinger_reconfig(ssh->pinger, ssh->conf, conf); - portfwdmgr_config(ssh->portfwdmgr, conf); + if (ssh->pinger) + pinger_reconfig(ssh->pinger, ssh->conf, conf); - rekey_time = conf_get_int(conf, CONF_ssh_rekey_time); - if (ssh2_timer_update(ssh, rekey_mins(rekey_time, 60))) - rekeying = "timeout shortened"; - - old_max_data_size = ssh->max_data_size; - ssh->max_data_size = parse_blocksize(conf_get_str(ssh->conf, - CONF_ssh_rekey_data)); - if (old_max_data_size != ssh->max_data_size && - ssh->max_data_size != 0) { - if (ssh->max_data_size < old_max_data_size) { - unsigned long diff = old_max_data_size - ssh->max_data_size; - - /* Intentionally use bitwise OR instead of logical, so - * that we decrement both counters even if the first one - * runs out */ - if ((DTS_CONSUME(&ssh->stats, out, diff) != 0) | - (DTS_CONSUME(&ssh->stats, in, diff) != 0)) - rekeying = "data limit lowered"; - } else { - unsigned long diff = ssh->max_data_size - old_max_data_size; - if (ssh->stats.out.running) - ssh->stats.out.remaining += diff; - if (ssh->stats.in.running) - ssh->stats.in.remaining += diff; - } - } - - if (conf_get_int(ssh->conf, CONF_compression) != - conf_get_int(conf, CONF_compression)) { - rekeying = "compression setting changed"; - rekey_mandatory = TRUE; - } - - for (i = 0; i < CIPHER_MAX; i++) - if (conf_get_int_int(ssh->conf, CONF_ssh_cipherlist, i) != - conf_get_int_int(conf, CONF_ssh_cipherlist, i)) { - rekeying = "cipher settings changed"; - rekey_mandatory = TRUE; - } - if (conf_get_int(ssh->conf, CONF_ssh2_des_cbc) != - conf_get_int(conf, CONF_ssh2_des_cbc)) { - rekeying = "cipher settings changed"; - rekey_mandatory = TRUE; - } + ssh_ppl_reconfigure(ssh->base_layer, conf); conf_free(ssh->conf); ssh->conf = conf_copy(conf); ssh_cache_conf_values(ssh); - - if (!ssh->bare_connection && rekeying) { - if (!ssh->kex_in_progress) { - ssh->rekey_reason = rekeying; - ssh->rekey_class = RK_NORMAL; - queue_idempotent_callback(&ssh->ssh2_transport_icb); - } else if (rekey_mandatory) { - ssh->deferred_rekey_reason = rekeying; - } - } } /* @@ -9305,7 +878,8 @@ static int ssh_send(Backend *be, const char *buf, int len) return 0; bufchain_add(&ssh->user_input, buf, len); - queue_idempotent_callback(&ssh->user_input_consumer); + if (ssh->base_layer) + ssh_ppl_got_user_input(ssh->base_layer); return backend_sendbuffer(&ssh->backend); } @@ -9316,30 +890,23 @@ static int ssh_send(Backend *be, const char *buf, int len) static int ssh_sendbuffer(Backend *be) { Ssh ssh = FROMFIELD(be, struct ssh_tag, backend); - int override_value; + int backlog; - if (ssh == NULL || ssh->s == NULL) + if (!ssh || !ssh->s || !ssh->cl) return 0; + backlog = ssh_stdin_backlog(ssh->cl); + + /* FIXME: also include sizes of pqs */ + /* * If the SSH socket itself has backed up, add the total backup * size on that to any individual buffer on the stdin channel. */ - override_value = 0; if (ssh->throttled_all) - override_value = ssh->overall_bufsize; + backlog += ssh->overall_bufsize; - if (ssh->version == 1) { - return override_value; - } else if (ssh->version == 2) { - if (!ssh->mainchan) - return override_value; - else - return (override_value + - bufchain_size(&ssh->mainchan->v.v2.outbuffer)); - } - - return 0; + return backlog; } /* @@ -9348,40 +915,34 @@ static int ssh_sendbuffer(Backend *be) static void ssh_size(Backend *be, int width, int height) { Ssh ssh = FROMFIELD(be, struct ssh_tag, backend); - PktOut *pktout; ssh->term_width = width; ssh->term_height = height; + if (ssh->cl) + ssh_terminal_size(ssh->cl, ssh->term_width, ssh->term_height); +} - switch (ssh->state) { - case SSH_STATE_BEFORE_SIZE: - case SSH_STATE_PREPACKET: - case SSH_STATE_CLOSED: - break; /* do nothing */ - case SSH_STATE_INTERMED: - ssh->size_needed = TRUE; /* buffer for later */ - break; - case SSH_STATE_SESSION: - if (!conf_get_int(ssh->conf, CONF_nopty)) { - if (ssh->version == 1) { - pktout = ssh_bpp_new_pktout(ssh->bpp, SSH1_CMSG_WINDOW_SIZE); - put_uint32(pktout, ssh->term_height); - put_uint32(pktout, ssh->term_width); - put_uint32(pktout, 0); - put_uint32(pktout, 0); - ssh_pkt_write(ssh, pktout); - } else if (ssh->mainchan) { - pktout = ssh2_chanreq_init(ssh->mainchan, "window-change", - NULL, NULL); - put_uint32(pktout, ssh->term_width); - put_uint32(pktout, ssh->term_height); - put_uint32(pktout, 0); - put_uint32(pktout, 0); - ssh2_pkt_send(ssh, pktout); - } - } - break; +struct ssh_add_special_ctx { + SessionSpecial *specials; + int nspecials, specials_size; +}; + +static void ssh_add_special(void *vctx, const char *text, + SessionSpecialCode code, int arg) +{ + struct ssh_add_special_ctx *ctx = (struct ssh_add_special_ctx *)vctx; + SessionSpecial *spec; + + if (ctx->nspecials >= ctx->specials_size) { + ctx->specials_size = ctx->nspecials * 5 / 4 + 32; + ctx->specials = sresize(ctx->specials, ctx->specials_size, + SessionSpecial); } + + spec = &ctx->specials[ctx->nspecials++]; + spec->name = text; + spec->code = code; + spec->arg = arg; } /* @@ -9390,244 +951,43 @@ static void ssh_size(Backend *be, int width, int height) */ static const SessionSpecial *ssh_get_specials(Backend *be) { - static const SessionSpecial ssh1_ignore_special[] = { - {"IGNORE message", SS_NOP} - }; - static const SessionSpecial ssh2_ignore_special[] = { - {"IGNORE message", SS_NOP}, - }; - static const SessionSpecial ssh2_rekey_special[] = { - {"Repeat key exchange", SS_REKEY}, - }; - static const SessionSpecial ssh2_session_specials[] = { - {NULL, SS_SEP}, - {"Break", SS_BRK}, - /* These are the signal names defined by RFC 4254. - * They include all the ISO C signals, but are a subset of the POSIX - * required signals. */ - {"SIGINT (Interrupt)", SS_SIGINT}, - {"SIGTERM (Terminate)", SS_SIGTERM}, - {"SIGKILL (Kill)", SS_SIGKILL}, - {"SIGQUIT (Quit)", SS_SIGQUIT}, - {"SIGHUP (Hangup)", SS_SIGHUP}, - {"More signals", SS_SUBMENU}, - {"SIGABRT", SS_SIGABRT}, {"SIGALRM", SS_SIGALRM}, - {"SIGFPE", SS_SIGFPE}, {"SIGILL", SS_SIGILL}, - {"SIGPIPE", SS_SIGPIPE}, {"SIGSEGV", SS_SIGSEGV}, - {"SIGUSR1", SS_SIGUSR1}, {"SIGUSR2", SS_SIGUSR2}, - {NULL, SS_EXITMENU} - }; - static const SessionSpecial specials_end[] = { - {NULL, SS_EXITMENU} - }; - - SessionSpecial *specials = NULL; - int nspecials = 0, specialsize = 0; - Ssh ssh = FROMFIELD(be, struct ssh_tag, backend); - sfree(ssh->specials); + /* + * Ask all our active protocol layers what specials they've got, + * and amalgamate the list into one combined one. + */ -#define ADD_SPECIALS(name) do \ - { \ - int len = lenof(name); \ - if (nspecials + len > specialsize) { \ - specialsize = (nspecials + len) * 5 / 4 + 32; \ - specials = sresize(specials, specialsize, SessionSpecial); \ - } \ - memcpy(specials+nspecials, name, len*sizeof(SessionSpecial)); \ - nspecials += len; \ - } while (0) + struct ssh_add_special_ctx ctx; - if (ssh->version == 1) { - /* Don't bother offering IGNORE if we've decided the remote - * won't cope with it, since we wouldn't bother sending it if - * asked anyway. */ - if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) - ADD_SPECIALS(ssh1_ignore_special); - } else if (ssh->version == 2) { - if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) - ADD_SPECIALS(ssh2_ignore_special); - if (!(ssh->remote_bugs & BUG_SSH2_REKEY) && !ssh->bare_connection) - ADD_SPECIALS(ssh2_rekey_special); - if (ssh->mainchan) - ADD_SPECIALS(ssh2_session_specials); + ctx.specials = NULL; + ctx.nspecials = ctx.specials_size = 0; - if (ssh->n_uncert_hostkeys) { - static const SessionSpecial uncert_start[] = { - {NULL, SS_SEP}, - {"Cache new host key type", SS_SUBMENU}, - }; - static const SessionSpecial uncert_end[] = { - {NULL, SS_EXITMENU}, - }; - int i; + if (ssh->base_layer) + ssh_ppl_get_specials(ssh->base_layer, ssh_add_special, &ctx); - ADD_SPECIALS(uncert_start); - for (i = 0; i < ssh->n_uncert_hostkeys; i++) { - SessionSpecial uncert[1]; - const ssh_keyalg *alg = - hostkey_algs[ssh->uncert_hostkeys[i]].alg; - uncert[0].name = alg->ssh_id; - uncert[0].code = SS_XCERT; - uncert[0].arg = ssh->uncert_hostkeys[i]; - ADD_SPECIALS(uncert); - } - ADD_SPECIALS(uncert_end); - } - } /* else we're not ready yet */ + if (!ssh->specials) + return NULL; - if (nspecials) - ADD_SPECIALS(specials_end); - - ssh->specials = specials; - - if (nspecials) { - return specials; - } else { - return NULL; + if (ctx.specials) { + /* If the list is non-empty, terminate it with a SS_EXITMENU. */ + ssh_add_special(&ctx, NULL, SS_EXITMENU, 0); } -#undef ADD_SPECIALS + + sfree(ssh->specials); + ssh->specials = ctx.specials; + return ssh->specials; } /* - * Send special codes. SS_EOF is useful for `plink', so you - * can send an EOF and collect resulting output (e.g. `plink - * hostname sort'). + * Send special codes. */ static void ssh_special(Backend *be, SessionSpecialCode code, int arg) { Ssh ssh = FROMFIELD(be, struct ssh_tag, backend); - PktOut *pktout; - if (code == SS_EOF) { - if (ssh->state != SSH_STATE_SESSION) { - /* - * Buffer the EOF in case we are pre-SESSION, so we can - * send it as soon as we reach SESSION. - */ - if (code == SS_EOF) - ssh->eof_needed = TRUE; - return; - } - if (ssh->version == 1) { - pktout = ssh_bpp_new_pktout(ssh->bpp, SSH1_CMSG_EOF); - ssh_pkt_write(ssh, pktout); - } else if (ssh->mainchan) { - sshfwd_write_eof(&ssh->mainchan->sc); - ssh->send_ok = 0; /* now stop trying to read from stdin */ - } - logevent("Sent EOF message"); - } else if (code == SS_PING || code == SS_NOP) { - if (ssh->state == SSH_STATE_CLOSED - || ssh->state == SSH_STATE_PREPACKET) return; - if (ssh->version == 1) { - if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) { - pktout = ssh_bpp_new_pktout(ssh->bpp, SSH1_MSG_IGNORE); - put_stringz(pktout, ""); - ssh_pkt_write(ssh, pktout); - } - } else { - if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) { - pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_IGNORE); - put_stringz(pktout, ""); - ssh_pkt_write(ssh, pktout); - } - } - } else if (code == SS_REKEY) { - if (!ssh->kex_in_progress && !ssh->bare_connection && - ssh->version == 2) { - ssh->rekey_reason = "at user request"; - ssh->rekey_class = RK_NORMAL; - queue_idempotent_callback(&ssh->ssh2_transport_icb); - } - } else if (code == SS_XCERT) { - ssh->hostkey_alg = hostkey_algs[arg].alg; - ssh->cross_certifying = TRUE; - if (!ssh->kex_in_progress && !ssh->bare_connection && - ssh->version == 2) { - ssh->rekey_reason = "cross-certifying new host key"; - ssh->rekey_class = RK_NORMAL; - queue_idempotent_callback(&ssh->ssh2_transport_icb); - } - } else if (code == SS_BRK) { - if (ssh->state == SSH_STATE_CLOSED - || ssh->state == SSH_STATE_PREPACKET) return; - if (ssh->version == 1) { - logevent("Unable to send BREAK signal in SSH-1"); - } else if (ssh->mainchan) { - pktout = ssh2_chanreq_init(ssh->mainchan, "break", NULL, NULL); - put_uint32(pktout, 0); /* default break length */ - ssh2_pkt_send(ssh, pktout); - } - } else { - /* Is is a POSIX signal? */ - const char *signame = NULL; - if (code == SS_SIGABRT) signame = "ABRT"; - if (code == SS_SIGALRM) signame = "ALRM"; - if (code == SS_SIGFPE) signame = "FPE"; - if (code == SS_SIGHUP) signame = "HUP"; - if (code == SS_SIGILL) signame = "ILL"; - if (code == SS_SIGINT) signame = "INT"; - if (code == SS_SIGKILL) signame = "KILL"; - if (code == SS_SIGPIPE) signame = "PIPE"; - if (code == SS_SIGQUIT) signame = "QUIT"; - if (code == SS_SIGSEGV) signame = "SEGV"; - if (code == SS_SIGTERM) signame = "TERM"; - if (code == SS_SIGUSR1) signame = "USR1"; - if (code == SS_SIGUSR2) signame = "USR2"; - /* The SSH-2 protocol does in principle support arbitrary named - * signals, including signame@domain, but we don't support those. */ - if (signame) { - /* It's a signal. */ - if (ssh->version == 2 && ssh->mainchan) { - pktout = ssh2_chanreq_init(ssh->mainchan, "signal", NULL, NULL); - put_stringz(pktout, signame); - ssh2_pkt_send(ssh, pktout); - logeventf(ssh, "Sent signal SIG%s", signame); - } - } else { - /* Never heard of it. Do nothing */ - } - } -} - -static unsigned (ssh_alloc_sharing_channel)( - ConnectionLayer *cl, ssh_sharing_connstate *connstate) -{ - Ssh ssh = FROMFIELD(cl, struct ssh_tag, cl); - struct ssh_channel *c; - c = snew(struct ssh_channel); - - c->ssh = ssh; - ssh_channel_init(c); - c->chan = NULL; - c->sharectx = connstate; - return c->localid; -} - -static void (ssh_delete_sharing_channel)(ConnectionLayer *cl, unsigned localid) -{ - Ssh ssh = FROMFIELD(cl, struct ssh_tag, cl); - struct ssh_channel *c; - - c = find234(ssh->channels, &localid, ssh_channelfind); - if (c) - ssh_channel_destroy(c); -} - -static void (ssh_send_packet_from_downstream)( - ConnectionLayer *cl, unsigned id, int type, - const void *data, int datalen, const char *additional_log_text) -{ - Ssh ssh = FROMFIELD(cl, struct ssh_tag, cl); - PktOut *pkt; - - pkt = ssh_bpp_new_pktout(ssh->bpp, type); - pkt->downstream_id = id; - pkt->additional_log_text = additional_log_text; - put_data(pkt, data, datalen); - ssh2_pkt_send(ssh, pkt); + if (ssh->base_layer) + ssh_ppl_special_cmd(ssh->base_layer, code, arg); } /* @@ -9638,69 +998,7 @@ static void ssh_unthrottle(Backend *be, int bufsize) { Ssh ssh = FROMFIELD(be, struct ssh_tag, backend); - if (ssh->version == 1) { - if (ssh->v1_stdout_throttling && bufsize < SSH1_BUFFER_LIMIT) { - ssh->v1_stdout_throttling = 0; - ssh_throttle_conn(ssh, -1); - } - } else { - if (ssh->mainchan) - ssh_channel_unthrottle(ssh->mainchan, bufsize); - } - - /* - * Now process any SSH connection data that was stashed in our - * queue while we were frozen. - */ - queue_idempotent_callback(&ssh->incoming_data_consumer); -} - -static SshChannel *(ssh_lportfwd_open)( - ConnectionLayer *cl, const char *hostname, int port, - const char *org, Channel *chan) -{ - Ssh ssh = FROMFIELD(cl, struct ssh_tag, cl); - struct ssh_channel *c = snew(struct ssh_channel); - PktOut *pktout; - - c->ssh = ssh; - ssh_channel_init(c); - c->halfopen = TRUE; - c->chan = chan; - - logeventf(ssh, "Opening connection to %s:%d for %s", hostname, port, org); - - if (ssh->version == 1) { - pktout = ssh_bpp_new_pktout(ssh->bpp, SSH1_MSG_PORT_OPEN); - put_uint32(pktout, c->localid); - put_stringz(pktout, hostname); - put_uint32(pktout, port); - /* originator string would go here, but we didn't specify - * SSH_PROTOFLAG_HOST_IN_FWD_OPEN */ - ssh_pkt_write(ssh, pktout); - } else { - pktout = ssh2_chanopen_init(c, "direct-tcpip"); - { - char *trimmed_host = host_strduptrim(hostname); - put_stringz(pktout, trimmed_host); - sfree(trimmed_host); - } - put_uint32(pktout, port); - /* - * We make up values for the originator data; partly it's - * too much hassle to keep track, and partly I'm not - * convinced the server should be told details like that - * about my local network configuration. - * The "originator IP address" is syntactically a numeric - * IP address, and some servers (e.g., Tectia) get upset - * if it doesn't match this syntax. - */ - put_stringz(pktout, "0.0.0.0"); - put_uint32(pktout, 0); - ssh2_pkt_send(ssh, pktout); - } - - return &c->sc; + ssh_stdout_unthrottle(ssh->cl, bufsize); } static int ssh_connected(Backend *be) @@ -9712,17 +1010,21 @@ static int ssh_connected(Backend *be) static int ssh_sendok(Backend *be) { Ssh ssh = FROMFIELD(be, struct ssh_tag, backend); - return ssh->send_ok; + return ssh->base_layer && ssh_ppl_want_user_input(ssh->base_layer); +} + +void ssh_ldisc_update(Ssh ssh) +{ + /* Called when the connection layer wants to propagate an update + * to the line discipline options */ + if (ssh->ldisc) + ldisc_echoedit_update(ssh->ldisc); } static int ssh_ldisc(Backend *be, int option) { Ssh ssh = FROMFIELD(be, struct ssh_tag, backend); - if (option == LD_ECHO) - return ssh->echoing; - if (option == LD_EDIT) - return ssh->editing; - return FALSE; + return ssh->cl ? ssh_ldisc_option(ssh->cl, option) : FALSE; } static void ssh_provide_ldisc(Backend *be, Ldisc *ldisc) @@ -9737,10 +1039,15 @@ static void ssh_provide_logctx(Backend *be, LogContext *logctx) ssh->logctx = logctx; } +void ssh_got_exitcode(Ssh ssh, int exitcode) +{ + ssh->exitcode = exitcode; +} + static int ssh_return_exitcode(Backend *be) { Ssh ssh = FROMFIELD(be, struct ssh_tag, backend); - if (ssh->s != NULL) + if (ssh->s && (!ssh->session_started || ssh->base_layer)) return -1; else return (ssh->exitcode >= 0 ? ssh->exitcode : INT_MAX); @@ -9773,6 +1080,11 @@ extern int ssh_fallback_cmd(Backend *be) return ssh->fallback_cmd; } +void ssh_got_fallback_cmd(Ssh ssh) +{ + ssh->fallback_cmd = TRUE; +} + const struct Backend_vtable ssh_backend = { ssh_init, ssh_free, diff --git a/ssh.h b/ssh.h index e9db3219..797b40c9 100644 --- a/ssh.h +++ b/ssh.h @@ -183,7 +183,18 @@ void share_setup_x11_channel(ssh_sharing_connstate *cs, share_channel *chan, int protomajor, int protominor, const void *initial_data, int initial_len); -struct ssh_rportfwd; +/* Structure definition centralised here because the SSH-1 and SSH-2 + * connection layers both use it. But the client module (portfwd.c) + * should not try to look inside here. */ +struct ssh_rportfwd { + unsigned sport, dport; + char *shost, *dhost; + int addressfamily; + char *log_description; /* name of remote listening port, for logging */ + ssh_sharing_connstate *share_ctx; + PortFwdRecord *pfr; +}; +void free_rportfwd(struct ssh_rportfwd *rpf); struct ConnectionLayerVtable { /* Allocate and free remote-to-local port forwardings, called by @@ -227,6 +238,25 @@ struct ConnectionLayerVtable { /* Query whether the connection layer is doing agent forwarding */ int (*agent_forwarding_permitted)(ConnectionLayer *cl); + + /* Set the size of the main terminal window (if any) */ + void (*terminal_size)(ConnectionLayer *cl, int width, int height); + + /* Indicate that the backlog on standard output has cleared */ + void (*stdout_unthrottle)(ConnectionLayer *cl, int bufsize); + + /* Query the size of the backlog on standard _input_ */ + int (*stdin_backlog)(ConnectionLayer *cl); + + /* Tell the connection layer that the SSH connection itself has + * backed up, so it should tell all currently open channels to + * cease reading from their local input sources if they can. (Or + * tell it that that state of affairs has gone away again.) */ + void (*throttle_all_channels)(ConnectionLayer *cl, int throttled); + + /* Ask the connection layer about its current preference for + * line-discipline options. */ + int (*ldisc_option)(ConnectionLayer *cl, int option); }; struct ConnectionLayer { @@ -253,6 +283,13 @@ struct ConnectionLayer { ((cl)->vt->sharing_queue_global_request(cl, cs)) #define ssh_agent_forwarding_permitted(cl) \ ((cl)->vt->agent_forwarding_permitted(cl)) +#define ssh_terminal_size(cl, w, h) ((cl)->vt->terminal_size(cl, w, h)) +#define ssh_stdout_unthrottle(cl, bufsize) \ + ((cl)->vt->stdout_unthrottle(cl, bufsize)) +#define ssh_stdin_backlog(cl) ((cl)->vt->stdin_backlog(cl)) +#define ssh_throttle_all_channels(cl, throttled) \ + ((cl)->vt->throttle_all_channels(cl, throttled)) +#define ssh_ldisc_option(cl, option) ((cl)->vt->ldisc_option(cl, option)) /* Exports from portfwd.c */ PortFwdManager *portfwdmgr_new(ConnectionLayer *cl); @@ -266,6 +303,19 @@ char *portfwdmgr_connect(PortFwdManager *mgr, Channel **chan_ret, Frontend *ssh_get_frontend(Ssh ssh); +/* Communications back to ssh.c from connection layers */ +void ssh_throttle_conn(Ssh ssh, int adjust); +void ssh_got_exitcode(Ssh ssh, int status); +void ssh_ldisc_update(Ssh ssh); +void ssh_got_fallback_cmd(Ssh ssh); + +/* Functions to abort the connection, for various reasons. */ +void ssh_remote_error(Ssh ssh, const char *fmt, ...); +void ssh_remote_eof(Ssh ssh, const char *fmt, ...); +void ssh_proto_error(Ssh ssh, const char *fmt, ...); +void ssh_sw_abort(Ssh ssh, const char *fmt, ...); +void ssh_user_close(Ssh ssh, const char *fmt, ...); + #define SSH_CIPHER_IDEA 1 #define SSH_CIPHER_DES 2 #define SSH_CIPHER_3DES 3 @@ -1241,10 +1291,6 @@ enum { #undef DEF_ENUM_UNIVERSAL #undef DEF_ENUM_CONTEXTUAL -/* Given that virtual packet types exist, this is how big the dispatch - * table has to be */ -#define SSH_MAX_MSG 0x101 - /* * SSH-1 agent messages. */ diff --git a/ssh1bpp.c b/ssh1bpp.c index 4b775929..e57676cc 100644 --- a/ssh1bpp.c +++ b/ssh1bpp.c @@ -88,8 +88,11 @@ void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp, #define BPP_READ(ptr, len) do \ { \ - crMaybeWaitUntilV(bufchain_try_fetch_consume( \ + crMaybeWaitUntilV(s->bpp.input_eof || \ + bufchain_try_fetch_consume( \ s->bpp.in_raw, ptr, len)); \ + if (s->bpp.input_eof) \ + goto eof; \ } while (0) static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp) @@ -109,9 +112,9 @@ static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp) } if (s->len < 0 || s->len > 262144) { /* SSH1.5-mandated max size */ - s->bpp.error = dupprintf( - "Extremely large packet length from server suggests" - " data stream corruption"); + ssh_sw_abort(s->bpp.ssh, + "Extremely large packet length from server suggests" + " data stream corruption"); crStopV; } @@ -134,8 +137,8 @@ static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp) if (s->cipher && detect_attack(s->crcda_ctx, s->data, s->biglen, NULL)) { - s->bpp.error = dupprintf( - "Network attack (CRC compensation) detected!"); + ssh_sw_abort(s->bpp.ssh, + "Network attack (CRC compensation) detected!"); crStopV; } @@ -145,8 +148,7 @@ static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp) s->realcrc = crc32_compute(s->data, s->biglen - 4); s->gotcrc = GET_32BIT(s->data + s->biglen - 4); if (s->gotcrc != s->realcrc) { - s->bpp.error = dupprintf( - "Incorrect CRC received on packet"); + ssh_sw_abort(s->bpp.ssh, "Incorrect CRC received on packet"); crStopV; } @@ -156,8 +158,8 @@ static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp) if (!ssh_decompressor_decompress( s->decompctx, s->data + s->pad, s->length + 1, &decompblk, &decomplen)) { - s->bpp.error = dupprintf( - "Zlib decompression encountered invalid data"); + ssh_sw_abort(s->bpp.ssh, + "Zlib decompression encountered invalid data"); crStopV; } @@ -204,10 +206,6 @@ static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp) s->pktin = NULL; switch (type) { - case SSH1_MSG_DISCONNECT: - s->bpp.seen_disconnect = TRUE; - break; - case SSH1_SMSG_SUCCESS: case SSH1_SMSG_FAILURE: if (s->pending_compression_request) { @@ -239,6 +237,15 @@ static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp) } } } + + eof: + if (!s->bpp.expect_close) { + ssh_remote_error(s->bpp.ssh, + "Server unexpectedly closed network connection"); + } else { + ssh_remote_eof(s->bpp.ssh, "Server closed network connection"); + } + crFinishV; } diff --git a/ssh1connection.c b/ssh1connection.c new file mode 100644 index 00000000..cf1616d6 --- /dev/null +++ b/ssh1connection.c @@ -0,0 +1,1211 @@ +/* + * Packet protocol layer for the SSH-1 'connection protocol', i.e. + * everything after authentication finishes. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshchan.h" +#include "sshcr.h" + +struct ssh1_channel; + +struct outstanding_succfail; + +struct ssh1_connection_state { + int crState; + + Ssh ssh; + + Conf *conf; + int local_protoflags; + + tree234 *channels; /* indexed by local id */ + + int got_pty; + int echoedit; + int ospeed, ispeed; + int stdout_throttling; + int session_ready; + int session_eof_pending, session_eof_sent, session_terminated; + int term_width, term_height, term_width_orig, term_height_orig; + + int X11_fwd_enabled; + struct X11Display *x11disp; + struct X11FakeAuth *x11auth; + tree234 *x11authtree; + + int agent_fwd_enabled; + + tree234 *rportfwds; + PortFwdManager *portfwdmgr; + int portfwdmgr_configured; + + int finished_setup; + + /* + * These store the list of requests that we're waiting for + * SSH_SMSG_{SUCCESS,FAILURE} replies to. (Those messages don't + * come with any indication of what they're in response to, so we + * have to keep track of the queue ourselves.) + */ + struct outstanding_succfail *succfail_head, *succfail_tail; + + ConnectionLayer cl; + PacketProtocolLayer ppl; +}; + +static int ssh1_rportfwd_cmp(void *av, void *bv) +{ + struct ssh_rportfwd *a = (struct ssh_rportfwd *) av; + struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv; + int i; + if ( (i = strcmp(a->dhost, b->dhost)) != 0) + return i < 0 ? -1 : +1; + if (a->dport > b->dport) + return +1; + if (a->dport < b->dport) + return -1; + return 0; +} + +static void ssh1_connection_free(PacketProtocolLayer *); +static void ssh1_connection_process_queue(PacketProtocolLayer *); +static void ssh1_connection_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg); +static int ssh1_connection_want_user_input(PacketProtocolLayer *ppl); +static void ssh1_connection_got_user_input(PacketProtocolLayer *ppl); +static void ssh1_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf); + +static const struct PacketProtocolLayerVtable ssh1_connection_vtable = { + ssh1_connection_free, + ssh1_connection_process_queue, + ssh1_common_get_specials, + ssh1_connection_special_cmd, + ssh1_connection_want_user_input, + ssh1_connection_got_user_input, + ssh1_connection_reconfigure, + NULL /* no layer names in SSH-1 */, +}; + +static 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); +static void ssh1_rportfwd_remove( + ConnectionLayer *cl, struct ssh_rportfwd *rpf); +static SshChannel *ssh1_lportfwd_open( + ConnectionLayer *cl, const char *hostname, int port, + const char *org, Channel *chan); +static int ssh1_agent_forwarding_permitted(ConnectionLayer *cl); +static void ssh1_terminal_size(ConnectionLayer *cl, int width, int height); +static void ssh1_stdout_unthrottle(ConnectionLayer *cl, int bufsize); +static int ssh1_stdin_backlog(ConnectionLayer *cl); +static void ssh1_throttle_all_channels(ConnectionLayer *cl, int throttled); +static int ssh1_ldisc_option(ConnectionLayer *cl, int option); + +static const struct ConnectionLayerVtable ssh1_connlayer_vtable = { + ssh1_rportfwd_alloc, + ssh1_rportfwd_remove, + ssh1_lportfwd_open, + NULL /* add_sharing_x11_display */, + NULL /* remove_sharing_x11_display */, + NULL /* send_packet_from_downstream */, + NULL /* alloc_sharing_channel */, + NULL /* delete_sharing_channel */, + NULL /* sharing_queue_global_request */, + ssh1_agent_forwarding_permitted, + ssh1_terminal_size, + ssh1_stdout_unthrottle, + ssh1_stdin_backlog, + ssh1_throttle_all_channels, + ssh1_ldisc_option, +}; + +struct ssh1_channel { + struct ssh1_connection_state *connlayer; + + unsigned remoteid, localid; + int type; + /* True if we opened this channel but server hasn't confirmed. */ + int halfopen; + + /* Bitmap of whether we've sent/received CHANNEL_CLOSE and + * CHANNEL_CLOSE_CONFIRMATION. */ +#define CLOSES_SENT_CLOSE 1 +#define CLOSES_SENT_CLOSECONF 2 +#define CLOSES_RCVD_CLOSE 4 +#define CLOSES_RCVD_CLOSECONF 8 + int closes; + + /* + * This flag indicates that an EOF is pending on the outgoing side + * of the channel: that is, wherever we're getting the data for + * this channel has sent us some data followed by EOF. We can't + * actually send the EOF until we've finished sending the data, so + * we set this flag instead to remind us to do so once our buffer + * is clear. + */ + int pending_eof; + + /* + * True if this channel is causing the underlying connection to be + * throttled. + */ + int throttling_conn; + + /* + * True if we currently have backed-up data on the direction of + * this channel pointing out of the SSH connection, and therefore + * would prefer the 'Channel' implementation not to read further + * local input if possible. + */ + int throttled_by_backlog; + + Channel *chan; /* handle the client side of this channel, if not */ + SshChannel sc; /* entry point for chan to talk back to */ +}; + +static int ssh1channel_write(SshChannel *c, const void *buf, int len); +static void ssh1channel_write_eof(SshChannel *c); +static void ssh1channel_unclean_close(SshChannel *c, const char *err); +static void ssh1channel_unthrottle(SshChannel *c, int bufsize); +static Conf *ssh1channel_get_conf(SshChannel *c); + +static const struct SshChannelVtable ssh1channel_vtable = { + ssh1channel_write, + ssh1channel_write_eof, + ssh1channel_unclean_close, + ssh1channel_unthrottle, + ssh1channel_get_conf, + NULL /* window_override_removed is only used by SSH-2 sharing */, + NULL /* x11_sharing_handover, likewise */, +}; + +static void ssh1_channel_init(struct ssh1_channel *c); +static void ssh1_channel_try_eof(struct ssh1_channel *c); +static void ssh1_channel_close_local(struct ssh1_channel *c, + const char *reason); +static void ssh1_channel_destroy(struct ssh1_channel *c); +static void ssh1_channel_check_close(struct ssh1_channel *c); + +static int ssh1_check_termination(struct ssh1_connection_state *s); + +typedef void (*sf_handler_fn_t)(struct ssh1_connection_state *s, + PktIn *pktin, void *ctx); +struct outstanding_succfail { + sf_handler_fn_t handler; + void *ctx; + struct outstanding_succfail *next; +}; +static void ssh1_queue_succfail_handler( + struct ssh1_connection_state *s, sf_handler_fn_t handler, void *ctx) +{ + struct outstanding_succfail *osf = + snew(struct outstanding_succfail); + osf->handler = handler; + osf->ctx = ctx; + if (s->succfail_tail) + s->succfail_tail->next = osf; + else + s->succfail_head = osf; + s->succfail_tail = osf; +} + +static int ssh1_channelcmp(void *av, void *bv) +{ + const struct ssh1_channel *a = (const struct ssh1_channel *) av; + const struct ssh1_channel *b = (const struct ssh1_channel *) bv; + if (a->localid < b->localid) + return -1; + if (a->localid > b->localid) + return +1; + return 0; +} + +static int ssh1_channelfind(void *av, void *bv) +{ + const unsigned *a = (const unsigned *) av; + const struct ssh1_channel *b = (const struct ssh1_channel *) bv; + if (*a < b->localid) + return -1; + if (*a > b->localid) + return +1; + return 0; +} + +static void ssh1_channel_free(struct ssh1_channel *c) +{ + if (c->chan) + chan_free(c->chan); + sfree(c); +} + +PacketProtocolLayer *ssh1_connection_new( + Ssh ssh, Conf *conf, ConnectionLayer **cl_out) +{ + struct ssh1_connection_state *s = snew(struct ssh1_connection_state); + memset(s, 0, sizeof(*s)); + s->ppl.vt = &ssh1_connection_vtable; + + s->conf = conf_copy(conf); + + s->channels = newtree234(ssh1_channelcmp); + + s->x11authtree = newtree234(x11_authcmp); + + /* Need to get the frontend for s->cl now, because we won't be + * helpfully notified when a copy is written into s->ppl by our + * owner. */ + s->cl.vt = &ssh1_connlayer_vtable; + s->cl.frontend = ssh_get_frontend(ssh); + + s->portfwdmgr = portfwdmgr_new(&s->cl); + s->rportfwds = newtree234(ssh1_rportfwd_cmp); + + *cl_out = &s->cl; + return &s->ppl; +} + +static void ssh1_connection_free(PacketProtocolLayer *ppl) +{ + struct ssh1_connection_state *s = + FROMFIELD(ppl, struct ssh1_connection_state, ppl); + struct X11FakeAuth *auth; + struct ssh1_channel *c; + struct ssh_rportfwd *rpf; + + conf_free(s->conf); + + while ((c = delpos234(s->channels, 0)) != NULL) + ssh1_channel_free(c); + freetree234(s->channels); + + if (s->x11disp) + x11_free_display(s->x11disp); + while ((auth = delpos234(s->x11authtree, 0)) != NULL) + x11_free_fake_auth(auth); + freetree234(s->x11authtree); + + while ((rpf = delpos234(s->rportfwds, 0)) != NULL) + free_rportfwd(rpf); + freetree234(s->rportfwds); + portfwdmgr_free(s->portfwdmgr); + + sfree(s); +} + +void ssh1_connection_set_local_protoflags(PacketProtocolLayer *ppl, int flags) +{ + assert(ppl->vt == &ssh1_connection_vtable); + struct ssh1_connection_state *s = + FROMFIELD(ppl, struct ssh1_connection_state, ppl); + s->local_protoflags = flags; +} + +static int ssh1_connection_filter_queue(struct ssh1_connection_state *s) +{ + PktIn *pktin; + PktOut *pktout; + ptrlen data, host; + struct ssh1_channel *c; + unsigned localid, remid; + int port, expect_halfopen; + struct ssh_rportfwd pf, *pfp; + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + + /* Cross-reference to ssh1login.c to handle the common packets + * between login and connection: DISCONNECT, DEBUG and IGNORE. */ + extern int ssh1_common_filter_queue(PacketProtocolLayer *ppl); + + while (1) { + if (ssh1_common_filter_queue(&s->ppl)) + return TRUE; + if ((pktin = pq_peek(s->ppl.in_pq)) == NULL) + return FALSE; + + switch (pktin->type) { + case SSH1_SMSG_SUCCESS: + case SSH1_SMSG_FAILURE: + if (!s->finished_setup) { + /* During initial setup, these messages are not + * filtered out, but go back to the main coroutine. */ + return FALSE; + } + + if (!s->succfail_head) { + ssh_remote_error(s->ppl.ssh, + "Received %s with no outstanding request", + ssh1_pkt_type(pktin->type)); + return TRUE; + } + + s->succfail_head->handler(s, pktin, s->succfail_head->ctx); + { + struct outstanding_succfail *tmp = s->succfail_head; + s->succfail_head = s->succfail_head->next; + sfree(tmp); + } + + pq_pop(s->ppl.in_pq); + break; + + case SSH1_SMSG_X11_OPEN: + remid = get_uint32(pktin); + + /* Refuse if X11 forwarding is disabled. */ + if (!s->X11_fwd_enabled) { + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE); + put_uint32(pktout, remid); + pq_push(s->ppl.out_pq, pktout); + ppl_logevent(("Rejected X11 connect request")); + } else { + c = snew(struct ssh1_channel); + c->connlayer = s; + ssh1_channel_init(c); + c->remoteid = remid; + c->chan = x11_new_channel(s->x11authtree, &c->sc, + NULL, -1, FALSE); + 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(("Opened X11 forward channel")); + } + + pq_pop(s->ppl.in_pq); + break; + + case SSH1_SMSG_AGENT_OPEN: + remid = get_uint32(pktin); + + /* Refuse if agent forwarding is disabled. */ + if (!s->agent_fwd_enabled) { + 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 { + c = snew(struct ssh1_channel); + c->connlayer = s; + ssh1_channel_init(c); + c->remoteid = remid; + c->chan = agentf_new(&c->sc); + 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); + } + + pq_pop(s->ppl.in_pq); + break; + + case SSH1_MSG_PORT_OPEN: + remid = get_uint32(pktin); + host = get_string(pktin); + port = toint(get_uint32(pktin)); + + pf.dhost = mkstr(host); + pf.dport = port; + pfp = find234(s->rportfwds, &pf, NULL); + + if (!pfp) { + ppl_logevent(("Rejected remote port open request for %s:%d", + pf.dhost, port)); + 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 { + char *err; + + c = snew(struct ssh1_channel); + c->connlayer = s; + ppl_logevent(("Received remote port open request for %s:%d", + pf.dhost, port)); + err = portfwdmgr_connect( + s->portfwdmgr, &c->chan, pf.dhost, port, + &c->sc, pfp->addressfamily); + + 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")); + } + } + + sfree(pf.dhost); + + pq_pop(s->ppl.in_pq); + break; + + case SSH1_MSG_CHANNEL_DATA: + case SSH1_MSG_CHANNEL_OPEN_CONFIRMATION: + case SSH1_MSG_CHANNEL_OPEN_FAILURE: + case SSH1_MSG_CHANNEL_CLOSE: + case SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION: + /* + * Common preliminary code for all the messages from the + * server that cite one of our channel ids: look up that + * channel id, check it exists, and if it's for a sharing + * downstream, pass it on. + */ + localid = get_uint32(pktin); + c = find234(s->channels, &localid, ssh1_channelfind); + + expect_halfopen = ( + pktin->type == SSH1_MSG_CHANNEL_OPEN_CONFIRMATION || + pktin->type == SSH1_MSG_CHANNEL_OPEN_FAILURE); + + if (!c || c->halfopen != expect_halfopen) { + ssh_remote_error( + s->ppl.ssh, "Received %s for %s channel %u", + ssh1_pkt_type(pktin->type), + !c ? "nonexistent" : c->halfopen ? "half-open" : "open", + localid); + return TRUE; + } + + switch (pktin->type) { + case SSH1_MSG_CHANNEL_OPEN_CONFIRMATION: + assert(c->halfopen); + c->remoteid = get_uint32(pktin); + c->halfopen = FALSE; + c->throttling_conn = FALSE; + + chan_open_confirmation(c->chan); + + /* + * Now that the channel is fully open, it's possible + * in principle to immediately close it. Check whether + * it wants us to! + * + * This can occur if a local socket error occurred + * between us sending out CHANNEL_OPEN and receiving + * OPEN_CONFIRMATION. If that happens, all we can do + * is immediately initiate close proceedings now that + * we know the server's id to put in the close + * message. We'll have handled that in this code by + * having already turned c->chan into a zombie, so its + * want_close method (which ssh1_channel_check_close + * will consult) will already be returning TRUE. + */ + ssh1_channel_check_close(c); + + if (c->pending_eof) + ssh1_channel_try_eof(c); /* in case we had a pending EOF */ + break; + + case SSH1_MSG_CHANNEL_OPEN_FAILURE: + assert(c->halfopen); + + chan_open_failed(c->chan, NULL); + chan_free(c->chan); + + del234(s->channels, c); + ssh1_channel_free(c); + break; + + case SSH1_MSG_CHANNEL_DATA: + data = get_string(pktin); + if (!get_err(pktin)) { + int bufsize = chan_send( + c->chan, FALSE, data.ptr, data.len); + + if (!c->throttling_conn && bufsize > SSH1_BUFFER_LIMIT) { + c->throttling_conn = TRUE; + ssh_throttle_conn(s->ppl.ssh, +1); + } + } + break; + + case SSH1_MSG_CHANNEL_CLOSE: + if (!(c->closes & CLOSES_RCVD_CLOSE)) { + c->closes |= CLOSES_RCVD_CLOSE; + chan_send_eof(c->chan); + ssh1_channel_check_close(c); + } + break; + + case SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION: + if (!(c->closes & CLOSES_RCVD_CLOSECONF)) { + if (!(c->closes & CLOSES_SENT_CLOSE)) { + ssh_remote_error( + s->ppl.ssh, + "Received CHANNEL_CLOSE_CONFIRMATION for channel" + " %u for which we never sent CHANNEL_CLOSE\n", + c->localid); + return TRUE; + } + + c->closes |= CLOSES_RCVD_CLOSECONF; + ssh1_channel_check_close(c); + } + break; + } + + pq_pop(s->ppl.in_pq); + break; + + case SSH1_SMSG_STDOUT_DATA: + case SSH1_SMSG_STDERR_DATA: + data = get_string(pktin); + if (!get_err(pktin)) { + int bufsize = from_backend( + s->ppl.frontend, pktin->type == SSH1_SMSG_STDERR_DATA, + data.ptr, data.len); + if (!s->stdout_throttling && bufsize > SSH1_BUFFER_LIMIT) { + s->stdout_throttling = 1; + ssh_throttle_conn(s->ppl.ssh, +1); + } + } + + pq_pop(s->ppl.in_pq); + break; + + case SSH1_SMSG_EXIT_STATUS: + { + int exitcode = get_uint32(pktin); + ppl_logevent(("Server sent command exit status %d", exitcode)); + ssh_got_exitcode(s->ppl.ssh, exitcode); + + s->session_terminated = TRUE; + if (ssh1_check_termination(s)) + return TRUE; + } + pq_pop(s->ppl.in_pq); + break; + + default: + return FALSE; + } + } +} + +static PktIn *ssh1_connection_pop(struct ssh1_connection_state *s) +{ + ssh1_connection_filter_queue(s); + return pq_pop(s->ppl.in_pq); +} + +static void ssh1_connection_process_queue(PacketProtocolLayer *ppl) +{ + struct ssh1_connection_state *s = + FROMFIELD(ppl, struct ssh1_connection_state, ppl); + PktIn *pktin; + PktOut *pktout; + + if (ssh1_connection_filter_queue(s)) /* no matter why we were called */ + return; + + crBegin(s->crState); + + if (ssh_agent_forwarding_permitted(&s->cl)) { + ppl_logevent(("Requesting agent forwarding")); + pktout = ssh_bpp_new_pktout(s->ppl.bpp, + SSH1_CMSG_AGENT_REQUEST_FORWARDING); + pq_push(s->ppl.out_pq, pktout); + crMaybeWaitUntilV((pktin = ssh1_connection_pop(s)) != NULL); + if (pktin->type == SSH1_SMSG_SUCCESS) { + ppl_logevent(("Agent forwarding enabled")); + s->agent_fwd_enabled = TRUE; + } else if (pktin->type == SSH1_SMSG_FAILURE) { + ppl_logevent(("Agent forwarding refused")); + } else { + ssh_proto_error(s->ppl.ssh, "Unexpected packet received" + " in response to agent forwarding request, " + "type %d (%s)", pktin->type, + ssh1_pkt_type(pktin->type)); + return; + } + } + + if (conf_get_int(s->conf, CONF_x11_forward)) { + s->x11disp = + x11_setup_display(conf_get_str(s->conf, CONF_x11_display), + s->conf); + if (!s->x11disp) { + /* FIXME: return an error message from x11_setup_display */ + ppl_logevent(("X11 forwarding not enabled: unable to" + " initialise X display")); + } else { + s->x11auth = x11_invent_fake_auth + (s->x11authtree, conf_get_int(s->conf, CONF_x11_auth)); + s->x11auth->disp = s->x11disp; + + ppl_logevent(("Requesting X11 forwarding")); + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_CMSG_X11_REQUEST_FORWARDING); + put_stringz(pktout, s->x11auth->protoname); + put_stringz(pktout, s->x11auth->datastring); + if (s->local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) + put_uint32(pktout, s->x11disp->screennum); + pq_push(s->ppl.out_pq, pktout); + crMaybeWaitUntilV((pktin = ssh1_connection_pop(s)) != NULL); + if (pktin->type == SSH1_SMSG_SUCCESS) { + ppl_logevent(("X11 forwarding enabled")); + s->X11_fwd_enabled = TRUE; + } else if (pktin->type == SSH1_SMSG_FAILURE) { + ppl_logevent(("X11 forwarding refused")); + } else { + ssh_proto_error(s->ppl.ssh, "Unexpected packet received" + " in response to X11 forwarding request, " + "type %d (%s)", pktin->type, + ssh1_pkt_type(pktin->type)); + return; + } + } + } + + portfwdmgr_config(s->portfwdmgr, s->conf); + s->portfwdmgr_configured = TRUE; + + if (!conf_get_int(s->conf, CONF_nopty)) { + /* Unpick the terminal-speed string. */ + /* XXX perhaps we should allow no speeds to be sent. */ + s->ospeed = 38400; s->ispeed = 38400; /* last-resort defaults */ + sscanf(conf_get_str(s->conf, CONF_termspeed), "%d,%d", + &s->ospeed, &s->ispeed); + /* Send the pty request. */ + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_REQUEST_PTY); + put_stringz(pktout, conf_get_str(s->conf, CONF_termtype)); + put_uint32(pktout, s->term_height); + put_uint32(pktout, s->term_width); + s->term_width_orig = s->term_width; + s->term_height_orig = s->term_height; + put_uint32(pktout, 0); /* width in pixels */ + put_uint32(pktout, 0); /* height in pixels */ + write_ttymodes_to_packet_from_conf( + BinarySink_UPCAST(pktout), s->ppl.frontend, s->conf, + 1, s->ospeed, s->ispeed); + pq_push(s->ppl.out_pq, pktout); + crMaybeWaitUntilV((pktin = ssh1_connection_pop(s)) != NULL); + if (pktin->type == SSH1_SMSG_SUCCESS) { + ppl_logevent(("Allocated pty (ospeed %dbps, ispeed %dbps)", + s->ospeed, s->ispeed)); + s->got_pty = TRUE; + } else if (pktin->type == SSH1_SMSG_FAILURE) { + ppl_printf(("Server refused to allocate pty\r\n")); + s->echoedit = TRUE; + } else { + ssh_proto_error(s->ppl.ssh, "Unexpected packet received" + " in response to pty request, " + "type %d (%s)", pktin->type, + ssh1_pkt_type(pktin->type)); + crStopV; + } + } else { + s->echoedit = TRUE; + } + + /* + * Start the shell or command. + * + * Special case: if the first-choice command is an SSH-2 + * subsystem (hence not usable here) and the second choice + * exists, we fall straight back to that. + */ + { + char *cmd = conf_get_str(s->conf, CONF_remote_cmd); + + if (conf_get_int(s->conf, CONF_ssh_subsys) && + conf_get_str(s->conf, CONF_remote_cmd2)) { + cmd = conf_get_str(s->conf, CONF_remote_cmd2); + ssh_got_fallback_cmd(s->ppl.ssh); + } + if (*cmd) { + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EXEC_CMD); + put_stringz(pktout, cmd); + } else { + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EXEC_SHELL); + } + pq_push(s->ppl.out_pq, pktout); + ppl_logevent(("Started session")); + } + + s->session_ready = TRUE; + + /* If an EOF or a window-size change arrived before we were ready + * to handle either one, handle them now. */ + if (s->session_eof_pending) { + ssh_ppl_special_cmd(&s->ppl, SS_EOF, 0); + s->session_eof_pending = FALSE; + } + if (s->term_width_orig != s->term_width || + s->term_height_orig != s->term_height) + ssh_terminal_size(&s->cl, s->term_width, s->term_height); + + ssh_ldisc_update(s->ppl.ssh); + s->finished_setup = TRUE; + + while (1) { + + /* + * By this point, most incoming packets are already being + * handled by filter_queue, and we need only pay attention to + * the unusual ones. + */ + + if ((pktin = ssh1_connection_pop(s)) != NULL) { + ssh_proto_error(s->ppl.ssh, "Unexpected packet received, " + "type %d (%s)", pktin->type, + ssh1_pkt_type(pktin->type)); + return; + } + while (bufchain_size(s->ppl.user_input) > 0) { + void *data; + int len; + bufchain_prefix(s->ppl.user_input, &data, &len); + if (len > 512) + len = 512; + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_STDIN_DATA); + put_string(pktout, data, len); + pq_push(s->ppl.out_pq, pktout); + bufchain_consume(s->ppl.user_input, len); + } + crReturnV; + } + + crFinishV; +} + +static void ssh1_channel_check_close(struct ssh1_channel *c) +{ + struct ssh1_connection_state *s = c->connlayer; + PktOut *pktout; + + if (c->halfopen) { + /* + * If we've sent out our own CHANNEL_OPEN but not yet seen + * either OPEN_CONFIRMATION or OPEN_FAILURE in response, then + * it's too early to be sending close messages of any kind. + */ + return; + } + + if ((!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes) || + chan_want_close(c->chan, (c->closes & CLOSES_SENT_CLOSE), + (c->closes & CLOSES_RCVD_CLOSE))) && + !(c->closes & CLOSES_SENT_CLOSECONF)) { + /* + * We have both sent and received CLOSE (or the channel type + * doesn't need us to), which means the channel is in final + * wind-up. Send CLOSE and/or CLOSE_CONFIRMATION, whichever we + * haven't sent yet. + */ + if (!(c->closes & CLOSES_SENT_CLOSE)) { + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE); + put_uint32(pktout, c->remoteid); + pq_push(s->ppl.out_pq, pktout); + c->closes |= CLOSES_SENT_CLOSE; + } + if (c->closes & CLOSES_RCVD_CLOSE) { + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION); + put_uint32(pktout, c->remoteid); + pq_push(s->ppl.out_pq, pktout); + c->closes |= CLOSES_SENT_CLOSECONF; + } + } + + if (!((CLOSES_SENT_CLOSECONF | CLOSES_RCVD_CLOSECONF) & ~c->closes)) { + /* + * We have both sent and received CLOSE_CONFIRMATION, which + * means we're completely done with the channel. + */ + ssh1_channel_destroy(c); + } +} + +static void ssh1_channel_try_eof(struct ssh1_channel *c) +{ + struct ssh1_connection_state *s = c->connlayer; + PktOut *pktout; + assert(c->pending_eof); /* precondition for calling us */ + if (c->halfopen) + return; /* can't close: not even opened yet */ + + c->pending_eof = FALSE; /* we're about to send it */ + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE); + put_uint32(pktout, c->remoteid); + pq_push(s->ppl.out_pq, pktout); + c->closes |= CLOSES_SENT_CLOSE; + + ssh1_channel_check_close(c); +} + +/* + * Close any local socket and free any local resources associated with + * a channel. This converts the channel into a zombie. + */ +static void ssh1_channel_close_local(struct ssh1_channel *c, + const char *reason) +{ + struct ssh1_connection_state *s = c->connlayer; + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + const char *msg = chan_log_close_msg(c->chan); + + if (msg != NULL) + ppl_logevent(("%s%s%s", msg, reason ? " " : "", reason ? reason : "")); + + chan_free(c->chan); + c->chan = zombiechan_new(); +} + +static void ssh1_check_termination_callback(void *vctx) +{ + struct ssh1_connection_state *s = (struct ssh1_connection_state *)vctx; + ssh1_check_termination(s); +} + +static void ssh1_channel_destroy(struct ssh1_channel *c) +{ + struct ssh1_connection_state *s = c->connlayer; + + ssh1_channel_close_local(c, NULL); + del234(s->channels, c); + ssh1_channel_free(c); + + /* + * If that was the last channel left open, we might need to + * terminate. But we'll be a bit cautious, by doing that in a + * toplevel callback, just in case anything on the current call + * stack objects to this entire PPL being freed. + */ + queue_toplevel_callback(ssh1_check_termination_callback, s); +} + +static int ssh1_check_termination(struct ssh1_connection_state *s) +{ + /* + * Decide whether we should terminate the SSH connection now. + * Called after a channel goes away, or when the main session + * returns SSH1_SMSG_EXIT_STATUS; we terminate when none of either + * is left. + */ + if (s->session_terminated && count234(s->channels) == 0) { + PktOut *pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_CMSG_EXIT_CONFIRMATION); + pq_push(s->ppl.out_pq, pktout); + + ssh_user_close(s->ppl.ssh, "Session finished"); + return TRUE; + } + + return FALSE; +} + +/* + * Set up most of a new ssh1_channel. Leaves chan untouched (since it + * will sometimes have been filled in before calling this). + */ +static void ssh1_channel_init(struct ssh1_channel *c) +{ + struct ssh1_connection_state *s = c->connlayer; + c->closes = 0; + c->pending_eof = FALSE; + c->throttling_conn = FALSE; + c->sc.vt = &ssh1channel_vtable; + c->localid = alloc_channel_id(s->channels, struct ssh1_channel); + add234(s->channels, c); +} + +static Conf *ssh1channel_get_conf(SshChannel *sc) +{ + struct ssh1_channel *c = FROMFIELD(sc, struct ssh1_channel, sc); + struct ssh1_connection_state *s = c->connlayer; + return s->conf; +} + +static void ssh1channel_write_eof(SshChannel *sc) +{ + struct ssh1_channel *c = FROMFIELD(sc, struct ssh1_channel, sc); + + if (c->closes & CLOSES_SENT_CLOSE) + return; + + c->pending_eof = TRUE; + ssh1_channel_try_eof(c); +} + +static void ssh1channel_unclean_close(SshChannel *sc, const char *err) +{ + struct ssh1_channel *c = FROMFIELD(sc, struct ssh1_channel, sc); + char *reason; + + reason = dupprintf("due to local error: %s", err); + ssh1_channel_close_local(c, reason); + sfree(reason); + c->pending_eof = FALSE; /* this will confuse a zombie channel */ + + ssh1_channel_check_close(c); +} + +static void ssh1channel_unthrottle(SshChannel *sc, int bufsize) +{ + struct ssh1_channel *c = FROMFIELD(sc, struct ssh1_channel, sc); + struct ssh1_connection_state *s = c->connlayer; + + if (c->throttling_conn && bufsize <= SSH1_BUFFER_LIMIT) { + c->throttling_conn = 0; + ssh_throttle_conn(s->ppl.ssh, -1); + } +} + +static int ssh1channel_write(SshChannel *sc, const void *buf, int len) +{ + struct ssh1_channel *c = FROMFIELD(sc, struct ssh1_channel, sc); + struct ssh1_connection_state *s = c->connlayer; + + assert(!(c->closes & CLOSES_SENT_CLOSE)); + + PktOut *pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_DATA); + put_uint32(pktout, c->remoteid); + put_string(pktout, buf, len); + pq_push(s->ppl.out_pq, pktout); + + /* + * In SSH-1 we can return 0 here - implying that channels are + * never individually throttled - because the only circumstance + * that can cause throttling will be the whole SSH connection + * backing up, in which case _everything_ will be throttled as a + * whole. + */ + return 0; +} + +static SshChannel *ssh1_lportfwd_open( + ConnectionLayer *cl, const char *hostname, int port, + const char *org, Channel *chan) +{ + struct ssh1_connection_state *s = + FROMFIELD(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(("Opening connection to %s:%d for %s", hostname, port, org)); + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_PORT_OPEN); + put_uint32(pktout, c->localid); + put_stringz(pktout, hostname); + put_uint32(pktout, port); + /* originator string would go here, but we didn't specify + * SSH_PROTOFLAG_HOST_IN_FWD_OPEN */ + pq_push(s->ppl.out_pq, pktout); + + return &c->sc; +} + +static void ssh1_rportfwd_response(struct ssh1_connection_state *s, + PktIn *pktin, void *ctx) +{ + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + struct ssh_rportfwd *rpf = (struct ssh_rportfwd *)ctx; + + if (pktin->type == SSH1_SMSG_SUCCESS) { + ppl_logevent(("Remote port forwarding from %s enabled", + rpf->log_description)); + } else { + ppl_logevent(("Remote port forwarding from %s refused", + rpf->log_description)); + + struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf); + assert(realpf == rpf); + portfwdmgr_close(s->portfwdmgr, rpf->pfr); + free_rportfwd(rpf); + } +} + +static 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) +{ + struct ssh1_connection_state *s = + FROMFIELD(cl, struct ssh1_connection_state, cl); + struct ssh_rportfwd *rpf = snew(struct ssh_rportfwd); + + rpf->shost = dupstr(shost); + rpf->sport = sport; + rpf->dhost = dupstr(dhost); + rpf->dport = dport; + rpf->addressfamily = addressfamily; + rpf->log_description = dupstr(log_description); + rpf->pfr = pfr; + + if (add234(s->rportfwds, rpf) != rpf) { + free_rportfwd(rpf); + return NULL; + } + + PktOut *pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_CMSG_PORT_FORWARD_REQUEST); + put_uint32(pktout, rpf->sport); + put_stringz(pktout, rpf->dhost); + put_uint32(pktout, rpf->dport); + pq_push(s->ppl.out_pq, pktout); + + ssh1_queue_succfail_handler(s, ssh1_rportfwd_response, rpf); + + return rpf; +} + +static void ssh1_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf) +{ + /* + * We cannot cancel listening ports on the server side in SSH-1! + * There's no message to support it. + */ +} + +static int ssh1_agent_forwarding_permitted(ConnectionLayer *cl) +{ + struct ssh1_connection_state *s = + FROMFIELD(cl, struct ssh1_connection_state, cl); + return conf_get_int(s->conf, CONF_agentfwd) && agent_exists(); +} + +static void ssh1_connection_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg) +{ + struct ssh1_connection_state *s = + FROMFIELD(ppl, struct ssh1_connection_state, ppl); + PktOut *pktout; + + if (code == SS_PING || code == SS_NOP) { + if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) { + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE); + put_stringz(pktout, ""); + pq_push(s->ppl.out_pq, pktout); + } + } else if (code == SS_EOF) { + if (!s->session_ready) { + /* + * Buffer the EOF to send as soon as the main session is + * fully set up. + */ + s->session_eof_pending = TRUE; + } else if (!s->session_eof_sent) { + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EOF); + pq_push(s->ppl.out_pq, pktout); + ppl_logevent(("Sent EOF message")); + s->session_eof_sent = TRUE; + } + } +} + +static void ssh1_terminal_size(ConnectionLayer *cl, int width, int height) +{ + struct ssh1_connection_state *s = + FROMFIELD(cl, struct ssh1_connection_state, cl); + + s->term_width = width; + s->term_height = height; + + if (s->session_ready) { + PktOut *pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_WINDOW_SIZE); + put_uint32(pktout, s->term_height); + put_uint32(pktout, s->term_width); + put_uint32(pktout, 0); + put_uint32(pktout, 0); + pq_push(s->ppl.out_pq, pktout); + } +} + +static void ssh1_stdout_unthrottle(ConnectionLayer *cl, int bufsize) +{ + struct ssh1_connection_state *s = + FROMFIELD(cl, struct ssh1_connection_state, cl); + + if (s->stdout_throttling && bufsize < SSH1_BUFFER_LIMIT) { + s->stdout_throttling = 0; + ssh_throttle_conn(s->ppl.ssh, -1); + } +} + +static int ssh1_stdin_backlog(ConnectionLayer *cl) +{ + return 0; +} + +static void ssh1_throttle_all_channels(ConnectionLayer *cl, int throttled) +{ + struct ssh1_connection_state *s = + FROMFIELD(cl, struct ssh1_connection_state, cl); + struct ssh1_channel *c; + int i; + + for (i = 0; NULL != (c = index234(s->channels, i)); i++) + chan_set_input_wanted(c->chan, !throttled); +} + +static int ssh1_ldisc_option(ConnectionLayer *cl, int option) +{ + struct ssh1_connection_state *s = + FROMFIELD(cl, struct ssh1_connection_state, cl); + + /* We always return the same value for LD_ECHO and LD_EDIT */ + return s->echoedit; +} + +static int ssh1_connection_want_user_input(PacketProtocolLayer *ppl) +{ + struct ssh1_connection_state *s = + FROMFIELD(ppl, struct ssh1_connection_state, ppl); + return s->session_ready && !s->session_eof_sent; +} + +static void ssh1_connection_got_user_input(PacketProtocolLayer *ppl) +{ + struct ssh1_connection_state *s = + FROMFIELD(ppl, struct ssh1_connection_state, ppl); + if (s->session_ready && !s->session_eof_sent) + queue_idempotent_callback(&s->ppl.ic_process_queue); +} + +static void ssh1_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf) +{ + struct ssh1_connection_state *s = + FROMFIELD(ppl, struct ssh1_connection_state, ppl); + + conf_free(s->conf); + s->conf = conf_copy(conf); + + if (s->portfwdmgr_configured) + portfwdmgr_config(s->portfwdmgr, s->conf); +} diff --git a/ssh1login.c b/ssh1login.c new file mode 100644 index 00000000..f676a6eb --- /dev/null +++ b/ssh1login.c @@ -0,0 +1,1206 @@ +/* + * Packet protocol layer for the SSH-1 login phase (combining what + * SSH-2 would think of as key exchange and user authentication). + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshcr.h" + +struct ssh1_login_state { + int crState; + + PacketProtocolLayer *successor_layer; + + Conf *conf; + + char *savedhost; + int savedport; + int try_agent_auth; + + int remote_protoflags; + int local_protoflags; + unsigned char session_key[32]; + char *username; + agent_pending_query *auth_agent_query; + + int len; + unsigned char *rsabuf; + unsigned long supported_ciphers_mask, supported_auths_mask; + int tried_publickey, tried_agent; + int tis_auth_refused, ccard_auth_refused; + unsigned char cookie[8]; + unsigned char session_id[16]; + int cipher_type; + strbuf *publickey_blob; + char *publickey_comment; + int privatekey_available, privatekey_encrypted; + prompts_t *cur_prompt; + int userpass_ret; + char c; + int pwpkt_type; + void *agent_response_to_free; + ptrlen agent_response; + BinarySource asrc[1]; /* response from SSH agent */ + int keyi, nkeys; + int authed; + struct RSAKey key; + Bignum challenge; + ptrlen comment; + int dlgret; + Filename *keyfile; + struct RSAKey servkey, hostkey; + int want_user_input; + + PacketProtocolLayer ppl; +}; + +static void ssh1_login_free(PacketProtocolLayer *); +static void ssh1_login_process_queue(PacketProtocolLayer *); +static void ssh1_login_dialog_callback(void *, int); +static void ssh1_login_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg); +static int ssh1_login_want_user_input(PacketProtocolLayer *ppl); +static void ssh1_login_got_user_input(PacketProtocolLayer *ppl); +static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf); + +static const struct PacketProtocolLayerVtable ssh1_login_vtable = { + ssh1_login_free, + ssh1_login_process_queue, + ssh1_common_get_specials, + ssh1_login_special_cmd, + ssh1_login_want_user_input, + ssh1_login_got_user_input, + ssh1_login_reconfigure, + NULL /* no layer names in SSH-1 */, +}; + +static void ssh1_login_agent_query(struct ssh1_login_state *s, strbuf *req); +static void ssh1_login_agent_callback(void *loginv, void *reply, int replylen); + +PacketProtocolLayer *ssh1_login_new( + Conf *conf, const char *host, int port, + PacketProtocolLayer *successor_layer) +{ + struct ssh1_login_state *s = snew(struct ssh1_login_state); + memset(s, 0, sizeof(*s)); + s->ppl.vt = &ssh1_login_vtable; + + s->conf = conf_copy(conf); + s->savedhost = dupstr(host); + s->savedport = port; + s->successor_layer = successor_layer; + return &s->ppl; +} + +static void ssh1_login_free(PacketProtocolLayer *ppl) +{ + struct ssh1_login_state *s = FROMFIELD(ppl, struct ssh1_login_state, ppl); + + if (s->successor_layer) + ssh_ppl_free(s->successor_layer); + + conf_free(s->conf); + sfree(s->savedhost); + sfree(s->rsabuf); + sfree(s->username); + if (s->publickey_blob) + strbuf_free(s->publickey_blob); + sfree(s->publickey_comment); + if (s->cur_prompt) + free_prompts(s->cur_prompt); + sfree(s->agent_response_to_free); + if (s->auth_agent_query) + agent_cancel_query(s->auth_agent_query); + sfree(s); +} + +int ssh1_common_filter_queue(PacketProtocolLayer *ppl) +{ + PktIn *pktin; + ptrlen msg; + + while ((pktin = pq_peek(ppl->in_pq)) != NULL) { + switch (pktin->type) { + case SSH1_MSG_DISCONNECT: + msg = get_string(pktin); + ssh_remote_error(ppl->ssh, + "Server sent disconnect message:\n\"%.*s\"", + PTRLEN_PRINTF(msg)); + return TRUE; /* indicate that we've been freed */ + + case SSH1_MSG_DEBUG: + msg = get_string(pktin); + ppl_logevent(("Remote debug message: %.*s", PTRLEN_PRINTF(msg))); + pq_pop(ppl->in_pq); + break; + + case SSH1_MSG_IGNORE: + /* Do nothing, because we're ignoring it! Duhh. */ + pq_pop(ppl->in_pq); + break; + + default: + return FALSE; + } + } + + return FALSE; +} + +static int ssh1_login_filter_queue(struct ssh1_login_state *s) +{ + return ssh1_common_filter_queue(&s->ppl); +} + +static PktIn *ssh1_login_pop(struct ssh1_login_state *s) +{ + if (ssh1_login_filter_queue(s)) + return NULL; + return pq_pop(s->ppl.in_pq); +} + +static void ssh1_login_process_queue(PacketProtocolLayer *ppl) +{ + struct ssh1_login_state *s = FROMFIELD(ppl, struct ssh1_login_state, ppl); + PktIn *pktin; + PktOut *pkt; + 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_filter_queue(s)) + return; + + crBegin(s->crState); + + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); + + if (pktin->type != SSH1_SMSG_PUBLIC_KEY) { + ssh_proto_error(s->ppl.ssh, "Public key packet not received"); + return; + } + + ppl_logevent(("Received public keys")); + + { + ptrlen pl = get_data(pktin, 8); + memcpy(s->cookie, pl.ptr, pl.len); + } + + get_rsa_ssh1_pub(pktin, &s->servkey, RSA_SSH1_EXPONENT_FIRST); + get_rsa_ssh1_pub(pktin, &s->hostkey, RSA_SSH1_EXPONENT_FIRST); + + s->hostkey.comment = NULL; /* avoid confusing rsa_ssh1_fingerprint */ + + /* + * Log the host key fingerprint. + */ + if (!get_err(pktin)) { + char *fingerprint = rsa_ssh1_fingerprint(&s->hostkey); + ppl_logevent(("Host key fingerprint is:")); + ppl_logevent((" %s", fingerprint)); + sfree(fingerprint); + } + + s->remote_protoflags = get_uint32(pktin); + s->supported_ciphers_mask = get_uint32(pktin); + s->supported_auths_mask = get_uint32(pktin); + + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, "Bad SSH-1 public key packet"); + return; + } + + if ((s->ppl.remote_bugs & BUG_CHOKES_ON_RSA)) + s->supported_auths_mask &= ~(1 << SSH1_AUTH_RSA); + + s->local_protoflags = + s->remote_protoflags & SSH1_PROTOFLAGS_SUPPORTED; + s->local_protoflags |= SSH1_PROTOFLAG_SCREEN_NUMBER; + + { + struct MD5Context md5c; + + MD5Init(&md5c); + for (i = (bignum_bitcount(s->hostkey.modulus) + 7) / 8; i-- ;) + put_byte(&md5c, bignum_byte(s->hostkey.modulus, i)); + for (i = (bignum_bitcount(s->servkey.modulus) + 7) / 8; i-- ;) + put_byte(&md5c, bignum_byte(s->servkey.modulus, i)); + put_data(&md5c, s->cookie, 8); + MD5Final(s->session_id, &md5c); + } + + for (i = 0; i < 32; i++) + s->session_key[i] = random_byte(); + + /* + * Verify that the `bits' and `bytes' parameters match. + */ + if (s->hostkey.bits > s->hostkey.bytes * 8 || + s->servkey.bits > s->servkey.bytes * 8) { + ssh_proto_error(s->ppl.ssh, "SSH-1 public keys were badly formatted"); + return; + } + + s->len = (s->hostkey.bytes > s->servkey.bytes ? + s->hostkey.bytes : s->servkey.bytes); + + s->rsabuf = snewn(s->len, unsigned char); + + /* + * Verify the host key. + */ + { + /* + * First format the key into a string. + */ + int len = rsastr_len(&s->hostkey); + char *fingerprint; + char *keystr = snewn(len, char); + rsastr_fmt(keystr, &s->hostkey); + fingerprint = rsa_ssh1_fingerprint(&s->hostkey); + + /* First check against manually configured host keys. */ + s->dlgret = verify_ssh_manual_host_key(s->conf, fingerprint, NULL); + sfree(fingerprint); + if (s->dlgret == 0) { /* did not match */ + sfree(keystr); + ssh_proto_error(s->ppl.ssh, "Host key did not appear in manually " + "configured list"); + return; + } else if (s->dlgret < 0) { /* none configured; use standard handling */ + s->dlgret = verify_ssh_host_key( + s->ppl.frontend, s->savedhost, s->savedport, + "rsa", keystr, fingerprint, ssh1_login_dialog_callback, s); + sfree(keystr); +#ifdef FUZZING + s->dlgret = 1; +#endif + crMaybeWaitUntilV(s->dlgret >= 0); + + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, + "User aborted at host key verification"); + return; + } + } else { + sfree(keystr); + } + } + + for (i = 0; i < 32; i++) { + s->rsabuf[i] = s->session_key[i]; + if (i < 16) + s->rsabuf[i] ^= s->session_id[i]; + } + + { + struct RSAKey *smaller = (s->hostkey.bytes > s->servkey.bytes ? + &s->servkey : &s->hostkey); + struct RSAKey *larger = (s->hostkey.bytes > s->servkey.bytes ? + &s->hostkey : &s->servkey); + + if (!rsa_ssh1_encrypt(s->rsabuf, 32, smaller) || + !rsa_ssh1_encrypt(s->rsabuf, smaller->bytes, larger)) { + ssh_proto_error(s->ppl.ssh, "SSH-1 public key encryptions failed " + "due to bad formatting"); + return; + } + } + + ppl_logevent(("Encrypted session key")); + + { + int cipher_chosen = 0, warn = 0; + const char *cipher_string = NULL; + int i; + for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) { + int next_cipher = conf_get_int_int( + s->conf, CONF_ssh_cipherlist, i); + if (next_cipher == CIPHER_WARN) { + /* If/when we choose a cipher, warn about it */ + warn = 1; + } else if (next_cipher == CIPHER_AES) { + /* XXX Probably don't need to mention this. */ + ppl_logevent(("AES not supported in SSH-1, skipping")); + } else { + switch (next_cipher) { + case CIPHER_3DES: s->cipher_type = SSH_CIPHER_3DES; + cipher_string = "3DES"; break; + case CIPHER_BLOWFISH: s->cipher_type = SSH_CIPHER_BLOWFISH; + cipher_string = "Blowfish"; break; + case CIPHER_DES: s->cipher_type = SSH_CIPHER_DES; + cipher_string = "single-DES"; break; + } + if (s->supported_ciphers_mask & (1 << s->cipher_type)) + cipher_chosen = 1; + } + } + if (!cipher_chosen) { + if ((s->supported_ciphers_mask & (1 << SSH_CIPHER_3DES)) == 0) { + ssh_proto_error(s->ppl.ssh, "Server violates SSH-1 protocol " + "by not supporting 3DES encryption"); + } else { + /* shouldn't happen */ + ssh_sw_abort(s->ppl.ssh, "No supported ciphers found"); + } + return; + } + + /* Warn about chosen cipher if necessary. */ + if (warn) { + s->dlgret = askalg(s->ppl.frontend, "cipher", cipher_string, + ssh1_login_dialog_callback, s); + crMaybeWaitUntilV(s->dlgret >= 0); + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, "User aborted at cipher warning"); + return; + } + } + } + + switch (s->cipher_type) { + case SSH_CIPHER_3DES: + ppl_logevent(("Using 3DES encryption")); + break; + case SSH_CIPHER_DES: + ppl_logevent(("Using single-DES encryption")); + break; + case SSH_CIPHER_BLOWFISH: + ppl_logevent(("Using Blowfish encryption")); + break; + } + + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_SESSION_KEY); + put_byte(pkt, s->cipher_type); + put_data(pkt, s->cookie, 8); + put_uint16(pkt, s->len * 8); + put_data(pkt, s->rsabuf, s->len); + put_uint32(pkt, s->local_protoflags); + pq_push(s->ppl.out_pq, pkt); + + ppl_logevent(("Trying to enable encryption...")); + + sfree(s->rsabuf); + s->rsabuf = NULL; + + /* + * Force the BPP to synchronously marshal all packets up to and + * including the SESSION_KEY into wire format, before we turn on + * crypto. + */ + ssh_bpp_handle_output(s->ppl.bpp); + + { + 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); + ppl_logevent(("Initialised %s encryption", cipher->text_name)); + } + + if (s->servkey.modulus) { + sfree(s->servkey.modulus); + s->servkey.modulus = NULL; + } + if (s->servkey.exponent) { + sfree(s->servkey.exponent); + s->servkey.exponent = NULL; + } + if (s->hostkey.modulus) { + sfree(s->hostkey.modulus); + s->hostkey.modulus = NULL; + } + if (s->hostkey.exponent) { + sfree(s->hostkey.exponent); + s->hostkey.exponent = NULL; + } + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); + + if (pktin->type != SSH1_SMSG_SUCCESS) { + ssh_proto_error(s->ppl.ssh, "Encryption not successfully enabled"); + return; + } + + ppl_logevent(("Successfully started encryption")); + + if ((s->username = get_remote_username(s->conf)) == NULL) { + s->cur_prompt = new_prompts(s->ppl.frontend); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH login name"); + add_prompt(s->cur_prompt, dupstr("login as: "), TRUE); + s->userpass_ret = get_userpass_input(s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = get_userpass_input( + s->cur_prompt, s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = TRUE; + crReturnV; + s->want_user_input = FALSE; + } + if (!s->userpass_ret) { + /* + * Failed to get a username. Terminate. + */ + ssh_user_close(s->ppl.ssh, "No username provided"); + return; + } + s->username = dupstr(s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); + s->cur_prompt = NULL; + } + + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_USER); + put_stringz(pkt, s->username); + pq_push(s->ppl.out_pq, pkt); + + ppl_logevent(("Sent username \"%s\"", s->username)); + if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) + ppl_printf(("Sent username \"%s\"\r\n", s->username)); + + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); + + if (!(s->supported_auths_mask & (1 << SSH1_AUTH_RSA))) { + /* We must not attempt PK auth. Pretend we've already tried it. */ + s->tried_publickey = s->tried_agent = TRUE; + } else { + s->tried_publickey = s->tried_agent = FALSE; + } + s->tis_auth_refused = s->ccard_auth_refused = FALSE; + + /* + * Load the public half of any configured keyfile for later use. + */ + s->keyfile = conf_get_filename(s->conf, CONF_keyfile); + if (!filename_is_null(s->keyfile)) { + int keytype; + ppl_logevent(("Reading key file \"%.150s\"", + filename_to_str(s->keyfile))); + keytype = key_type(s->keyfile); + if (keytype == SSH_KEYTYPE_SSH1 || + keytype == SSH_KEYTYPE_SSH1_PUBLIC) { + const char *error; + s->publickey_blob = strbuf_new(); + if (rsa_ssh1_loadpub(s->keyfile, + BinarySink_UPCAST(s->publickey_blob), + &s->publickey_comment, &error)) { + s->privatekey_available = (keytype == SSH_KEYTYPE_SSH1); + if (!s->privatekey_available) + ppl_logevent(("Key file contains public key only")); + s->privatekey_encrypted = rsa_ssh1_encrypted(s->keyfile, NULL); + } else { + ppl_logevent(("Unable to load key (%s)", error)); + ppl_printf(("Unable to load key file \"%s\" (%s)\r\n", + filename_to_str(s->keyfile), error)); + + strbuf_free(s->publickey_blob); + s->publickey_blob = NULL; + } + } else { + ppl_logevent(("Unable to use this key file (%s)", + key_type_to_str(keytype))); + ppl_printf(("Unable to use key file \"%s\" (%s)\r\n", + filename_to_str(s->keyfile), + key_type_to_str(keytype))); + } + } + + /* Check whether we're configured to try Pageant, and also whether + * it's available. */ + s->try_agent_auth = (conf_get_int(s->conf, CONF_tryagent) && + agent_exists()); + + while (pktin->type == SSH1_SMSG_FAILURE) { + s->pwpkt_type = SSH1_CMSG_AUTH_PASSWORD; + + if (s->try_agent_auth && !s->tried_agent) { + /* + * Attempt RSA authentication using Pageant. + */ + s->authed = FALSE; + s->tried_agent = 1; + ppl_logevent(("Pageant is running. Requesting keys.")); + + /* Request the keys held by the agent. */ + { + strbuf *request = strbuf_new_for_agent_query(); + put_byte(request, SSH1_AGENTC_REQUEST_RSA_IDENTITIES); + ssh1_login_agent_query(s, request); + strbuf_free(request); + crMaybeWaitUntilV(!s->auth_agent_query); + } + BinarySource_BARE_INIT( + s->asrc, s->agent_response.ptr, s->agent_response.len); + + get_uint32(s->asrc); /* skip length field */ + if (get_byte(s->asrc) == SSH1_AGENT_RSA_IDENTITIES_ANSWER) { + s->nkeys = toint(get_uint32(s->asrc)); + if (s->nkeys < 0) { + ppl_logevent(("Pageant reported negative key count %d", + s->nkeys)); + s->nkeys = 0; + } + ppl_logevent(("Pageant has %d SSH-1 keys", s->nkeys)); + for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) { + size_t start, end; + start = s->asrc->pos; + get_rsa_ssh1_pub(s->asrc, &s->key, + RSA_SSH1_EXPONENT_FIRST); + end = s->asrc->pos; + s->comment = get_string(s->asrc); + if (get_err(s->asrc)) { + ppl_logevent(("Pageant key list packet was truncated")); + break; + } + if (s->publickey_blob) { + ptrlen keystr = make_ptrlen( + (const char *)s->asrc->data + start, end - start); + + if (keystr.len == s->publickey_blob->len && + !memcmp(keystr.ptr, s->publickey_blob->s, + s->publickey_blob->len)) { + ppl_logevent(("Pageant key #%d matches " + "configured key file", s->keyi)); + s->tried_publickey = 1; + } else + /* Skip non-configured key */ + continue; + } + ppl_logevent(("Trying Pageant key #%d", s->keyi)); + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA); + put_mp_ssh1(pkt, s->key.modulus); + pq_push(s->ppl.out_pq, pkt); + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) + != NULL); + if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { + ppl_logevent(("Key refused")); + continue; + } + ppl_logevent(("Received RSA challenge")); + s->challenge = get_mp_ssh1(pktin); + if (get_err(pktin)) { + freebn(s->challenge); + ssh_proto_error(s->ppl.ssh, "Server's RSA challenge " + "was badly formatted"); + return; + } + + { + strbuf *agentreq; + const char *ret; + + agentreq = strbuf_new_for_agent_query(); + put_byte(agentreq, SSH1_AGENTC_RSA_CHALLENGE); + put_uint32(agentreq, bignum_bitcount(s->key.modulus)); + put_mp_ssh1(agentreq, s->key.exponent); + put_mp_ssh1(agentreq, s->key.modulus); + put_mp_ssh1(agentreq, s->challenge); + put_data(agentreq, s->session_id, 16); + put_uint32(agentreq, 1); /* response format */ + ssh1_login_agent_query(s, agentreq); + strbuf_free(agentreq); + crMaybeWaitUntilV(!s->auth_agent_query); + + ret = s->agent_response.ptr; + if (ret) { + if (s->agent_response.len >= 5+16 && + ret[4] == SSH1_AGENT_RSA_RESPONSE) { + ppl_logevent(("Sending Pageant's response")); + pkt = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE); + put_data(pkt, ret + 5, 16); + pq_push(s->ppl.out_pq, pkt); + sfree((char *)ret); + crMaybeWaitUntilV( + (pktin = ssh1_login_pop(s)) + != NULL); + if (pktin->type == SSH1_SMSG_SUCCESS) { + ppl_logevent(("Pageant's response " + "accepted")); + if (flags & FLAG_VERBOSE) { + ppl_printf(("Authenticated using RSA " + "key \"%.*s\" from " + "agent\r\n", PTRLEN_PRINTF( + s->comment))); + } + s->authed = TRUE; + } else + ppl_logevent(("Pageant's response not " + "accepted")); + } else { + ppl_logevent(("Pageant failed to answer " + "challenge")); + sfree((char *)ret); + } + } else { + ppl_logevent(("No reply received from Pageant")); + } + } + freebn(s->key.exponent); + freebn(s->key.modulus); + freebn(s->challenge); + if (s->authed) + break; + } + sfree(s->agent_response_to_free); + s->agent_response_to_free = NULL; + if (s->publickey_blob && !s->tried_publickey) + ppl_logevent(("Configured key file not in Pageant")); + } else { + ppl_logevent(("Failed to get reply from Pageant")); + } + if (s->authed) + break; + } + if (s->publickey_blob && s->privatekey_available && + !s->tried_publickey) { + /* + * Try public key authentication with the specified + * key file. + */ + int got_passphrase; /* need not be kept over crReturn */ + if (flags & FLAG_VERBOSE) + ppl_printf(("Trying public key authentication.\r\n")); + ppl_logevent(("Trying public key \"%s\"", + filename_to_str(s->keyfile))); + s->tried_publickey = 1; + got_passphrase = FALSE; + while (!got_passphrase) { + /* + * Get a passphrase, if necessary. + */ + int retd; + char *passphrase = NULL; /* only written after crReturn */ + const char *error; + if (!s->privatekey_encrypted) { + if (flags & FLAG_VERBOSE) + ppl_printf(("No passphrase required.\r\n")); + passphrase = NULL; + } else { + s->cur_prompt = new_prompts(s->ppl.frontend); + s->cur_prompt->to_server = FALSE; + s->cur_prompt->name = dupstr("SSH key passphrase"); + add_prompt(s->cur_prompt, + dupprintf("Passphrase for key \"%.100s\": ", + s->publickey_comment), FALSE); + s->userpass_ret = get_userpass_input(s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = get_userpass_input( + s->cur_prompt, s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = TRUE; + crReturnV; + s->want_user_input = FALSE; + } + if (!s->userpass_ret) { + /* Failed to get a passphrase. Terminate. */ + ssh_sw_abort(s->ppl.ssh, "Unable to authenticate"); + return; + } + passphrase = dupstr(s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); + s->cur_prompt = NULL; + } + /* + * Try decrypting key with passphrase. + */ + retd = rsa_ssh1_loadkey( + s->keyfile, &s->key, passphrase, &error); + if (passphrase) { + smemclr(passphrase, strlen(passphrase)); + sfree(passphrase); + } + if (retd == 1) { + /* Correct passphrase. */ + got_passphrase = TRUE; + } else if (retd == 0) { + ppl_printf(("Couldn't load private key from %s (%s).\r\n", + filename_to_str(s->keyfile), error)); + got_passphrase = FALSE; + break; /* go and try something else */ + } else if (retd == -1) { + ppl_printf(("Wrong passphrase.\r\n")); + got_passphrase = FALSE; + /* and try again */ + } else { + assert(0 && "unexpected return from rsa_ssh1_loadkey()"); + got_passphrase = FALSE; /* placate optimisers */ + } + } + + if (got_passphrase) { + + /* + * Send a public key attempt. + */ + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA); + put_mp_ssh1(pkt, s->key.modulus); + pq_push(s->ppl.out_pq, pkt); + + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) + != NULL); + if (pktin->type == SSH1_SMSG_FAILURE) { + ppl_printf(("Server refused our public key.\r\n")); + continue; /* go and try something else */ + } + if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet" + " in response to offer of public key, " + "type %d (%s)", pktin->type, + ssh1_pkt_type(pktin->type)); + return; + } + + { + int i; + unsigned char buffer[32]; + Bignum challenge, response; + + challenge = get_mp_ssh1(pktin); + if (get_err(pktin)) { + freebn(challenge); + ssh_proto_error(s->ppl.ssh, "Server's RSA challenge " + "was badly formatted"); + return; + } + response = rsa_ssh1_decrypt(challenge, &s->key); + freebn(s->key.private_exponent);/* burn the evidence */ + + for (i = 0; i < 32; i++) { + buffer[i] = bignum_byte(response, 31 - i); + } + + { + struct MD5Context md5c; + MD5Init(&md5c); + put_data(&md5c, buffer, 32); + put_data(&md5c, s->session_id, 16); + MD5Final(buffer, &md5c); + } + + pkt = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE); + put_data(pkt, buffer, 16); + pq_push(s->ppl.out_pq, pkt); + + freebn(challenge); + freebn(response); + } + + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) + != NULL); + if (pktin->type == SSH1_SMSG_FAILURE) { + if (flags & FLAG_VERBOSE) + ppl_printf(("Failed to authenticate with" + " our public key.\r\n")); + continue; /* go and try something else */ + } else if (pktin->type != SSH1_SMSG_SUCCESS) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet" + " in response to RSA authentication, " + "type %d (%s)", pktin->type, + ssh1_pkt_type(pktin->type)); + return; + } + + break; /* we're through! */ + } + + } + + /* + * Otherwise, try various forms of password-like authentication. + */ + s->cur_prompt = new_prompts(s->ppl.frontend); + + if (conf_get_int(s->conf, CONF_try_tis_auth) && + (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) && + !s->tis_auth_refused) { + s->pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE; + ppl_logevent(("Requested TIS authentication")); + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_TIS); + pq_push(s->ppl.out_pq, pkt); + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); + if (pktin->type == SSH1_SMSG_FAILURE) { + ppl_logevent(("TIS authentication declined")); + if (flags & FLAG_INTERACTIVE) + ppl_printf(("TIS authentication refused.\r\n")); + s->tis_auth_refused = 1; + continue; + } else if (pktin->type == SSH1_SMSG_AUTH_TIS_CHALLENGE) { + ptrlen challenge; + char *instr_suf, *prompt; + + challenge = get_string(pktin); + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, "TIS challenge packet was " + "badly formed"); + return; + } + ppl_logevent(("Received TIS challenge")); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH TIS authentication"); + /* Prompt heuristic comes from OpenSSH */ + if (memchr(challenge.ptr, '\n', challenge.len)) { + instr_suf = dupstr(""); + prompt = mkstr(challenge); + } else { + instr_suf = mkstr(challenge); + prompt = dupstr("Response: "); + } + s->cur_prompt->instruction = + dupprintf("Using TIS authentication.%s%s", + (*instr_suf) ? "\n" : "", + instr_suf); + s->cur_prompt->instr_reqd = TRUE; + add_prompt(s->cur_prompt, prompt, FALSE); + sfree(instr_suf); + } else { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet" + " in response to TIS authentication, " + "type %d (%s)", pktin->type, + ssh1_pkt_type(pktin->type)); + return; + } + } + if (conf_get_int(s->conf, CONF_try_tis_auth) && + (s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) && + !s->ccard_auth_refused) { + s->pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE; + ppl_logevent(("Requested CryptoCard authentication")); + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_CCARD); + pq_push(s->ppl.out_pq, pkt); + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); + if (pktin->type == SSH1_SMSG_FAILURE) { + ppl_logevent(("CryptoCard authentication declined")); + ppl_printf(("CryptoCard authentication refused.\r\n")); + s->ccard_auth_refused = 1; + continue; + } else if (pktin->type == SSH1_SMSG_AUTH_CCARD_CHALLENGE) { + ptrlen challenge; + char *instr_suf, *prompt; + + challenge = get_string(pktin); + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, "CryptoCard challenge packet " + "was badly formed"); + return; + } + ppl_logevent(("Received CryptoCard challenge")); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH CryptoCard authentication"); + s->cur_prompt->name_reqd = FALSE; + /* Prompt heuristic comes from OpenSSH */ + if (memchr(challenge.ptr, '\n', challenge.len)) { + instr_suf = dupstr(""); + prompt = mkstr(challenge); + } else { + instr_suf = mkstr(challenge); + prompt = dupstr("Response: "); + } + s->cur_prompt->instruction = + dupprintf("Using CryptoCard authentication.%s%s", + (*instr_suf) ? "\n" : "", + instr_suf); + s->cur_prompt->instr_reqd = TRUE; + add_prompt(s->cur_prompt, prompt, FALSE); + sfree(instr_suf); + } else { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet" + " in response to TIS authentication, " + "type %d (%s)", pktin->type, + ssh1_pkt_type(pktin->type)); + return; + } + } + if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { + if ((s->supported_auths_mask & (1 << SSH1_AUTH_PASSWORD)) == 0) { + ssh_sw_abort(s->ppl.ssh, "No supported authentication methods " + "available"); + return; + } + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH password"); + add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ", + s->username, s->savedhost), + FALSE); + } + + /* + * Show password prompt, having first obtained it via a TIS + * or CryptoCard exchange if we're doing TIS or CryptoCard + * authentication. + */ + s->userpass_ret = get_userpass_input(s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = get_userpass_input( + s->cur_prompt, s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = TRUE; + crReturnV; + s->want_user_input = FALSE; + } + if (!s->userpass_ret) { + /* + * Failed to get a password (for example + * because one was supplied on the command line + * which has already failed to work). Terminate. + */ + ssh_sw_abort(s->ppl.ssh, "Unable to authenticate"); + return; + } + + if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { + /* + * Defence against traffic analysis: we send a + * whole bunch of packets containing strings of + * different lengths. One of these strings is the + * password, in a SSH1_CMSG_AUTH_PASSWORD packet. + * The others are all random data in + * SSH1_MSG_IGNORE packets. This way a passive + * listener can't tell which is the password, and + * hence can't deduce the password length. + * + * Anybody with a password length greater than 16 + * bytes is going to have enough entropy in their + * password that a listener won't find it _that_ + * much help to know how long it is. So what we'll + * do is: + * + * - if password length < 16, we send 15 packets + * containing string lengths 1 through 15 + * + * - otherwise, we let N be the nearest multiple + * of 8 below the password length, and send 8 + * packets containing string lengths N through + * N+7. This won't obscure the order of + * magnitude of the password length, but it will + * introduce a bit of extra uncertainty. + * + * A few servers can't deal with SSH1_MSG_IGNORE, at + * least in this context. For these servers, we need + * an alternative defence. We make use of the fact + * that the password is interpreted as a C string: + * so we can append a NUL, then some random data. + * + * A few servers can deal with neither SSH1_MSG_IGNORE + * here _nor_ a padded password string. + * For these servers we are left with no defences + * against password length sniffing. + */ + if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) && + !(s->ppl.remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) { + /* + * The server can deal with SSH1_MSG_IGNORE, so + * we can use the primary defence. + */ + int bottom, top, pwlen, i; + + pwlen = strlen(s->cur_prompt->prompts[0]->result); + if (pwlen < 16) { + bottom = 0; /* zero length passwords are OK! :-) */ + top = 15; + } else { + bottom = pwlen & ~7; + top = bottom + 7; + } + + assert(pwlen >= bottom && pwlen <= top); + + for (i = bottom; i <= top; i++) { + if (i == pwlen) { + pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); + put_stringz(pkt, s->cur_prompt->prompts[0]->result); + pq_push(s->ppl.out_pq, pkt); + } else { + int j; + strbuf *random_data = strbuf_new(); + for (j = 0; j < i; j++) + put_byte(random_data, random_byte()); + + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE); + put_stringsb(pkt, random_data); + pq_push(s->ppl.out_pq, pkt); + } + } + ppl_logevent(("Sending password with camouflage packets")); + } + else if (!(s->ppl.remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) { + /* + * The server can't deal with SSH1_MSG_IGNORE + * but can deal with padded passwords, so we + * can use the secondary defence. + */ + strbuf *padded_pw = strbuf_new(); + + ppl_logevent(("Sending length-padded password")); + pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); + put_asciz(padded_pw, s->cur_prompt->prompts[0]->result); + do { + put_byte(padded_pw, random_byte()); + } while (padded_pw->len % 64 != 0); + put_stringsb(pkt, padded_pw); + pq_push(s->ppl.out_pq, pkt); + } else { + /* + * The server is believed unable to cope with + * any of our password camouflage methods. + */ + ppl_logevent(("Sending unpadded password")); + pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); + put_stringz(pkt, s->cur_prompt->prompts[0]->result); + pq_push(s->ppl.out_pq, pkt); + } + } else { + pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); + put_stringz(pkt, s->cur_prompt->prompts[0]->result); + pq_push(s->ppl.out_pq, pkt); + } + ppl_logevent(("Sent password")); + free_prompts(s->cur_prompt); + s->cur_prompt = NULL; + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); + if (pktin->type == SSH1_SMSG_FAILURE) { + if (flags & FLAG_VERBOSE) + ppl_printf(("Access denied\r\n")); + ppl_logevent(("Authentication refused")); + } else if (pktin->type != SSH1_SMSG_SUCCESS) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet" + " in response to password authentication, type %d " + "(%s)", pktin->type, ssh1_pkt_type(pktin->type)); + return; + } + } + + ppl_logevent(("Authentication successful")); + + if (conf_get_int(s->conf, CONF_compression)) { + ppl_logevent(("Requesting compression")); + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_REQUEST_COMPRESSION); + put_uint32(pkt, 6); /* gzip compression level */ + pq_push(s->ppl.out_pq, pkt); + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); + if (pktin->type == SSH1_SMSG_SUCCESS) { + /* + * We don't have to actually do anything here: the SSH-1 + * BPP will take care of automatically starting the + * compression, by recognising our outgoing request packet + * and the success response. (Horrible, but it's the + * easiest way to avoid race conditions if other packets + * cross in transit.) + */ + ppl_logevent(("Started zlib (RFC1950) compression")); + } else if (pktin->type == SSH1_SMSG_FAILURE) { + ppl_logevent(("Server refused to enable compression")); + ppl_printf(("Server refused to compress\r\n")); + } else { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet" + " in response to compression request, type %d " + "(%s)", pktin->type, ssh1_pkt_type(pktin->type)); + return; + } + } + + ssh1_connection_set_local_protoflags( + s->successor_layer, s->local_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; +} + +static void ssh1_login_dialog_callback(void *loginv, int ret) +{ + struct ssh1_login_state *s = (struct ssh1_login_state *)loginv; + s->dlgret = ret; + ssh_ppl_process_queue(&s->ppl); +} + +static void ssh1_login_agent_query(struct ssh1_login_state *s, strbuf *req) +{ + void *response; + int response_len; + + sfree(s->agent_response_to_free); + s->agent_response_to_free = NULL; + + s->auth_agent_query = agent_query(req, &response, &response_len, + ssh1_login_agent_callback, s); + if (!s->auth_agent_query) + ssh1_login_agent_callback(s, response, response_len); +} + +static void ssh1_login_agent_callback(void *loginv, void *reply, int replylen) +{ + struct ssh1_login_state *s = (struct ssh1_login_state *)loginv; + + s->auth_agent_query = NULL; + s->agent_response_to_free = reply; + s->agent_response = make_ptrlen(reply, replylen); + + queue_idempotent_callback(&s->ppl.ic_process_queue); +} + +static void ssh1_login_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg) +{ + struct ssh1_login_state *s = + FROMFIELD(ppl, struct ssh1_login_state, ppl); + PktOut *pktout; + + if (code == SS_PING || code == SS_NOP) { + if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) { + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE); + put_stringz(pktout, ""); + pq_push(s->ppl.out_pq, pktout); + } + } +} + +static int ssh1_login_want_user_input(PacketProtocolLayer *ppl) +{ + struct ssh1_login_state *s = + FROMFIELD(ppl, struct ssh1_login_state, ppl); + return s->want_user_input; +} + +static void ssh1_login_got_user_input(PacketProtocolLayer *ppl) +{ + struct ssh1_login_state *s = + FROMFIELD(ppl, struct ssh1_login_state, ppl); + if (s->want_user_input) + queue_idempotent_callback(&s->ppl.ic_process_queue); +} + +static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf) +{ + struct ssh1_login_state *s = + FROMFIELD(ppl, struct ssh1_login_state, ppl); + ssh_ppl_reconfigure(s->successor_layer, conf); +} diff --git a/ssh2bpp-bare.c b/ssh2bpp-bare.c index 5b58a3c7..49689c3b 100644 --- a/ssh2bpp-bare.c +++ b/ssh2bpp-bare.c @@ -52,8 +52,11 @@ static void ssh2_bare_bpp_free(BinaryPacketProtocol *bpp) #define BPP_READ(ptr, len) do \ { \ - crMaybeWaitUntilV(bufchain_try_fetch_consume( \ + crMaybeWaitUntilV(s->bpp.input_eof || \ + bufchain_try_fetch_consume( \ s->bpp.in_raw, ptr, len)); \ + if (s->bpp.input_eof) \ + goto eof; \ } while (0) static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp) @@ -72,7 +75,7 @@ static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp) } if (s->packetlen <= 0 || s->packetlen >= (long)OUR_V2_PACKETLIMIT) { - s->bpp.error = dupstr("Invalid packet length received"); + ssh_sw_abort(s->bpp.ssh, "Invalid packet length received"); crStopV; } @@ -123,15 +126,17 @@ static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp) } pq_push(&s->bpp.in_pq, s->pktin); - - { - int type = s->pktin->type; - s->pktin = NULL; - - if (type == SSH2_MSG_DISCONNECT) - s->bpp.seen_disconnect = TRUE; - } + s->pktin = NULL; } + + eof: + if (!s->bpp.expect_close) { + ssh_remote_error(s->bpp.ssh, + "Server unexpectedly closed network connection"); + } else { + ssh_remote_eof(s->bpp.ssh, "Server closed network connection"); + } + crFinishV; } diff --git a/ssh2bpp.c b/ssh2bpp.c index b476f33b..8ddb49f7 100644 --- a/ssh2bpp.c +++ b/ssh2bpp.c @@ -168,8 +168,11 @@ void ssh2_bpp_new_incoming_crypto( #define BPP_READ(ptr, len) do \ { \ - crMaybeWaitUntilV(bufchain_try_fetch_consume( \ + crMaybeWaitUntilV(s->bpp.input_eof || \ + bufchain_try_fetch_consume( \ s->bpp.in_raw, ptr, len)); \ + if (s->bpp.input_eof) \ + goto eof; \ } while (0) static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp) @@ -246,8 +249,8 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp) s->packetlen-4)) break; if (s->packetlen >= (long)OUR_V2_PACKETLIMIT) { - s->bpp.error = dupprintf( - "No valid incoming packet found"); + ssh_sw_abort(s->bpp.ssh, + "No valid incoming packet found"); crStopV; } } @@ -293,8 +296,8 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp) */ if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT || s->len % s->cipherblk != 0) { - s->bpp.error = dupprintf( - "Incoming packet length field was garbled"); + ssh_sw_abort(s->bpp.ssh, + "Incoming packet length field was garbled"); crStopV; } @@ -323,7 +326,7 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp) */ if (s->in.mac && !ssh2_mac_verify( s->in.mac, s->data, s->len + 4, s->in.sequence)) { - s->bpp.error = dupprintf("Incorrect MAC received on packet"); + ssh_sw_abort(s->bpp.ssh, "Incorrect MAC received on packet"); crStopV; } @@ -358,8 +361,8 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp) */ if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT || (s->len + 4) % s->cipherblk != 0) { - s->bpp.error = dupprintf( - "Incoming packet was garbled on decryption"); + ssh_sw_abort(s->bpp.ssh, + "Incoming packet was garbled on decryption"); crStopV; } @@ -396,15 +399,15 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp) */ if (s->in.mac && !ssh2_mac_verify( s->in.mac, s->data, s->len + 4, s->in.sequence)) { - s->bpp.error = dupprintf("Incorrect MAC received on packet"); + ssh_sw_abort(s->bpp.ssh, "Incorrect MAC received on packet"); crStopV; } } /* Get and sanity-check the amount of random padding. */ s->pad = s->data[4]; if (s->pad < 4 || s->len - s->pad < 1) { - s->bpp.error = dupprintf( - "Invalid padding length on received packet"); + ssh_sw_abort(s->bpp.ssh, + "Invalid padding length on received packet"); crStopV; } /* @@ -492,8 +495,6 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp) int type = s->pktin->type; s->pktin = NULL; - if (type == SSH2_MSG_DISCONNECT) - s->bpp.seen_disconnect = TRUE; if (type == SSH2_MSG_NEWKEYS) { /* * Mild layer violation: in this situation we must @@ -506,6 +507,15 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp) } } } + + eof: + if (!s->bpp.expect_close) { + ssh_remote_error(s->bpp.ssh, + "Server unexpectedly closed network connection"); + } else { + ssh_remote_eof(s->bpp.ssh, "Server closed network connection"); + } + crFinishV; } diff --git a/ssh2connection.c b/ssh2connection.c new file mode 100644 index 00000000..38566dc0 --- /dev/null +++ b/ssh2connection.c @@ -0,0 +1,2501 @@ +/* + * Packet protocol layer for the SSH-2 connection protocol (RFC 4254). + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshchan.h" +#include "sshcr.h" + +struct ssh2_channel; + +typedef enum MainChanType { + MAINCHAN_DIRECT_TCPIP, MAINCHAN_SESSION, MAINCHAN_NONE +} MainChanType; + +struct outstanding_global_request; + +struct ssh2_connection_state { + int crState; + + Ssh ssh; + + ssh_sharing_state *connshare; + char *peer_verstring; + + struct ssh2_channel *mainchan; /* primary session channel */ + MainChanType mctype; + char *mainchan_open_error; + int mainchan_ready; + int echoedit; + int mainchan_eof_pending, mainchan_eof_sent; + int session_attempt, session_status; + int term_width, term_height, term_width_orig, term_height_orig; + int want_user_input; + + int ssh_is_simple; + + Conf *conf; + + tree234 *channels; /* indexed by local id */ + int all_channels_throttled; + + int X11_fwd_enabled; + struct X11Display *x11disp; + struct X11FakeAuth *x11auth; + tree234 *x11authtree; + + int got_pty; + int agent_fwd_enabled; + + tree234 *rportfwds; + PortFwdManager *portfwdmgr; + int portfwdmgr_configured; + + /* + * These store the list of global requests that we're waiting for + * replies to. (REQUEST_FAILURE doesn't come with any indication + * of what message caused it, so we have to keep track of the + * queue ourselves.) + */ + struct outstanding_global_request *globreq_head, *globreq_tail; + + ConnectionLayer cl; + PacketProtocolLayer ppl; +}; + +static int ssh2_rportfwd_cmp(void *av, void *bv) +{ + struct ssh_rportfwd *a = (struct ssh_rportfwd *) av; + struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv; + int i; + if ( (i = strcmp(a->shost, b->shost)) != 0) + return i < 0 ? -1 : +1; + if (a->sport > b->sport) + return +1; + if (a->sport < b->sport) + return -1; + return 0; +} + +static void ssh2_connection_free(PacketProtocolLayer *); +static void ssh2_connection_process_queue(PacketProtocolLayer *); +static int ssh2_connection_get_specials( + PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx); +static void ssh2_connection_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg); +static int ssh2_connection_want_user_input(PacketProtocolLayer *ppl); +static void ssh2_connection_got_user_input(PacketProtocolLayer *ppl); +static void ssh2_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf); + +static const struct PacketProtocolLayerVtable ssh2_connection_vtable = { + ssh2_connection_free, + ssh2_connection_process_queue, + ssh2_connection_get_specials, + ssh2_connection_special_cmd, + ssh2_connection_want_user_input, + ssh2_connection_got_user_input, + ssh2_connection_reconfigure, + "ssh-connection", +}; + +static 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); +static void ssh2_rportfwd_remove( + ConnectionLayer *cl, struct ssh_rportfwd *rpf); +static SshChannel *ssh2_lportfwd_open( + ConnectionLayer *cl, const char *hostname, int port, + const char *org, Channel *chan); +static struct X11FakeAuth *ssh2_add_sharing_x11_display( + ConnectionLayer *cl, int authtype, ssh_sharing_connstate *share_cs, + share_channel *share_chan); +static void ssh2_remove_sharing_x11_display(ConnectionLayer *cl, + struct X11FakeAuth *auth); +static void ssh2_send_packet_from_downstream( + ConnectionLayer *cl, unsigned id, int type, + const void *pkt, int pktlen, const char *additional_log_text); +static unsigned ssh2_alloc_sharing_channel( + ConnectionLayer *cl, ssh_sharing_connstate *connstate); +static void ssh2_delete_sharing_channel( + ConnectionLayer *cl, unsigned localid); +static void ssh2_sharing_queue_global_request( + ConnectionLayer *cl, ssh_sharing_connstate *share_ctx); +static int ssh2_agent_forwarding_permitted(ConnectionLayer *cl); +static void ssh2_terminal_size(ConnectionLayer *cl, int width, int height); +static void ssh2_stdout_unthrottle(ConnectionLayer *cl, int bufsize); +static int ssh2_stdin_backlog(ConnectionLayer *cl); +static void ssh2_throttle_all_channels(ConnectionLayer *cl, int throttled); +static int ssh2_ldisc_option(ConnectionLayer *cl, int option); + +static const struct ConnectionLayerVtable ssh2_connlayer_vtable = { + ssh2_rportfwd_alloc, + ssh2_rportfwd_remove, + ssh2_lportfwd_open, + ssh2_add_sharing_x11_display, + ssh2_remove_sharing_x11_display, + ssh2_send_packet_from_downstream, + ssh2_alloc_sharing_channel, + ssh2_delete_sharing_channel, + ssh2_sharing_queue_global_request, + ssh2_agent_forwarding_permitted, + ssh2_terminal_size, + ssh2_stdout_unthrottle, + ssh2_stdin_backlog, + ssh2_throttle_all_channels, + ssh2_ldisc_option, +}; + +static char *ssh2_channel_open_failure_error_text(PktIn *pktin) +{ + static const char *const reasons[] = { + NULL, + "Administratively prohibited", + "Connect failed", + "Unknown channel type", + "Resource shortage", + }; + unsigned reason_code; + const char *reason_code_string; + char reason_code_buf[256]; + ptrlen reason; + + reason_code = get_uint32(pktin); + if (reason_code < lenof(reasons) && reasons[reason_code]) { + reason_code_string = reasons[reason_code]; + } else { + reason_code_string = reason_code_buf; + sprintf(reason_code_buf, "unknown reason code %#x", reason_code); + } + + reason = get_string(pktin); + + return dupprintf("%s [%.*s]", reason_code_string, PTRLEN_PRINTF(reason)); +} + +struct outstanding_channel_request; +struct outstanding_global_request; + +struct ssh2_channel { + struct ssh2_connection_state *connlayer; + + unsigned remoteid, localid; + int type; + /* True if we opened this channel but server hasn't confirmed. */ + int halfopen; + + /* Bitmap of whether we've sent/received CHANNEL_EOF and + * CHANNEL_CLOSE. */ +#define CLOSES_SENT_EOF 1 +#define CLOSES_SENT_CLOSE 2 +#define CLOSES_RCVD_EOF 4 +#define CLOSES_RCVD_CLOSE 8 + int closes; + + /* + * This flag indicates that an EOF is pending on the outgoing side + * of the channel: that is, wherever we're getting the data for + * this channel has sent us some data followed by EOF. We can't + * actually send the EOF until we've finished sending the data, so + * we set this flag instead to remind us to do so once our buffer + * is clear. + */ + int pending_eof; + + /* + * True if this channel is causing the underlying connection to be + * throttled. + */ + int throttling_conn; + + /* + * True if we currently have backed-up data on the direction of + * this channel pointing out of the SSH connection, and therefore + * would prefer the 'Channel' implementation not to read further + * local input if possible. + */ + int throttled_by_backlog; + + bufchain outbuffer; + unsigned remwindow, remmaxpkt; + /* locwindow is signed so we can cope with excess data. */ + int locwindow, locmaxwin; + /* + * remlocwin is the amount of local window that we think + * the remote end had available to it after it sent the + * last data packet or window adjust ack. + */ + int remlocwin; + + /* + * These store the list of channel requests that we're waiting for + * replies to. (CHANNEL_FAILURE doesn't come with any indication + * of what message caused it, so we have to keep track of the + * queue ourselves.) + */ + struct outstanding_channel_request *chanreq_head, *chanreq_tail; + + enum { THROTTLED, UNTHROTTLING, UNTHROTTLED } throttle_state; + + ssh_sharing_connstate *sharectx; /* sharing context, if this is a + * downstream channel */ + Channel *chan; /* handle the client side of this channel, if not */ + SshChannel sc; /* entry point for chan to talk back to */ +}; + +static int ssh2channel_write(SshChannel *c, const void *buf, int len); +static void ssh2channel_write_eof(SshChannel *c); +static void ssh2channel_unclean_close(SshChannel *c, const char *err); +static void ssh2channel_unthrottle(SshChannel *c, int bufsize); +static Conf *ssh2channel_get_conf(SshChannel *c); +static void ssh2channel_window_override_removed(SshChannel *c); +static void ssh2channel_x11_sharing_handover( + SshChannel *c, ssh_sharing_connstate *share_cs, share_channel *share_chan, + const char *peer_addr, int peer_port, int endian, + int protomajor, int protominor, const void *initial_data, int initial_len); + +static const struct SshChannelVtable ssh2channel_vtable = { + ssh2channel_write, + ssh2channel_write_eof, + ssh2channel_unclean_close, + ssh2channel_unthrottle, + ssh2channel_get_conf, + ssh2channel_window_override_removed, + ssh2channel_x11_sharing_handover, +}; + +typedef void (*cr_handler_fn_t)(struct ssh2_channel *, PktIn *, void *); + +static void ssh2_channel_init(struct ssh2_channel *c); +static PktOut *ssh2_chanopen_init(struct ssh2_channel *c, const char *type); +static PktOut *ssh2_chanreq_init(struct ssh2_channel *c, const char *type, + cr_handler_fn_t handler, void *ctx); +static void ssh2_channel_check_close(struct ssh2_channel *c); +static void ssh2_channel_try_eof(struct ssh2_channel *c); +static void ssh2_set_window(struct ssh2_channel *c, int newwin); +static int ssh2_try_send(struct ssh2_channel *c); +static void ssh2_try_send_and_unthrottle(struct ssh2_channel *c); +static void ssh2_channel_check_throttle(struct ssh2_channel *c); +static void ssh2_channel_close_local(struct ssh2_channel *c, + const char *reason); +static void ssh2_channel_destroy(struct ssh2_channel *c); + +static void ssh2_check_termination(struct ssh2_connection_state *s); + +typedef void (*gr_handler_fn_t)(struct ssh2_connection_state *s, + PktIn *pktin, void *ctx); +struct outstanding_global_request { + gr_handler_fn_t handler; + void *ctx; + struct outstanding_global_request *next; +}; +static void ssh2_queue_global_request_handler( + struct ssh2_connection_state *s, gr_handler_fn_t handler, void *ctx) +{ + struct outstanding_global_request *ogr = + snew(struct outstanding_global_request); + ogr->handler = handler; + ogr->ctx = ctx; + if (s->globreq_tail) + s->globreq_tail->next = ogr; + else + s->globreq_head = ogr; + s->globreq_tail = ogr; +} + +typedef struct mainchan { + struct ssh2_connection_state *connlayer; + SshChannel *sc; + + Channel chan; +} mainchan; +static mainchan *mainchan_new(struct ssh2_connection_state *s); +static void ssh2_setup_x11(struct ssh2_channel *c, PktIn *pktin, void *ctx); +static void ssh2_setup_agent(struct ssh2_channel *c, PktIn *pktin, void *ctx); +static void ssh2_setup_pty(struct ssh2_channel *c, PktIn *pktin, void *ctx); +static void ssh2_setup_env(struct ssh2_channel *c, PktIn *pktin, void *ctx); +static void ssh2_response_session(struct ssh2_channel *c, PktIn *, void *); + +static int ssh2_channelcmp(void *av, void *bv) +{ + const struct ssh2_channel *a = (const struct ssh2_channel *) av; + const struct ssh2_channel *b = (const struct ssh2_channel *) bv; + if (a->localid < b->localid) + return -1; + if (a->localid > b->localid) + return +1; + return 0; +} + +static int ssh2_channelfind(void *av, void *bv) +{ + const unsigned *a = (const unsigned *) av; + const struct ssh2_channel *b = (const struct ssh2_channel *) bv; + if (*a < b->localid) + return -1; + if (*a > b->localid) + return +1; + return 0; +} + +/* + * Each channel has a queue of outstanding CHANNEL_REQUESTS and their + * handlers. + */ +struct outstanding_channel_request { + cr_handler_fn_t handler; + void *ctx; + struct outstanding_channel_request *next; +}; + +static void ssh2_channel_free(struct ssh2_channel *c) +{ + bufchain_clear(&c->outbuffer); + while (c->chanreq_head) { + struct outstanding_channel_request *chanreq = c->chanreq_head; + c->chanreq_head = c->chanreq_head->next; + sfree(chanreq); + } + if (c->chan) + chan_free(c->chan); + sfree(c); +} + +PacketProtocolLayer *ssh2_connection_new( + Ssh ssh, ssh_sharing_state *connshare, int is_simple, + Conf *conf, const char *peer_verstring, ConnectionLayer **cl_out) +{ + struct ssh2_connection_state *s = snew(struct ssh2_connection_state); + memset(s, 0, sizeof(*s)); + s->ppl.vt = &ssh2_connection_vtable; + + s->conf = conf_copy(conf); + + s->ssh_is_simple = is_simple; + + s->connshare = connshare; + s->peer_verstring = dupstr(peer_verstring); + + s->channels = newtree234(ssh2_channelcmp); + + s->x11authtree = newtree234(x11_authcmp); + + /* Need to get the frontend for s->cl now, because we won't be + * helpfully notified when a copy is written into s->ppl by our + * owner. */ + s->cl.vt = &ssh2_connlayer_vtable; + s->cl.frontend = ssh_get_frontend(ssh); + + s->portfwdmgr = portfwdmgr_new(&s->cl); + s->rportfwds = newtree234(ssh2_rportfwd_cmp); + + *cl_out = &s->cl; + if (s->connshare) + ssh_connshare_provide_connlayer(s->connshare, &s->cl); + + return &s->ppl; +} + +static void ssh2_connection_free(PacketProtocolLayer *ppl) +{ + struct ssh2_connection_state *s = + FROMFIELD(ppl, struct ssh2_connection_state, ppl); + struct X11FakeAuth *auth; + struct ssh2_channel *c; + struct ssh_rportfwd *rpf; + + sfree(s->peer_verstring); + + conf_free(s->conf); + + sfree(s->mainchan_open_error); + + while ((c = delpos234(s->channels, 0)) != NULL) + ssh2_channel_free(c); + freetree234(s->channels); + + if (s->x11disp) + x11_free_display(s->x11disp); + while ((auth = delpos234(s->x11authtree, 0)) != NULL) + x11_free_fake_auth(auth); + freetree234(s->x11authtree); + + while ((rpf = delpos234(s->rportfwds, 0)) != NULL) + free_rportfwd(rpf); + freetree234(s->rportfwds); + portfwdmgr_free(s->portfwdmgr); + + sfree(s); +} + +static int ssh2_connection_filter_queue(struct ssh2_connection_state *s) +{ + PktIn *pktin; + PktOut *pktout; + ptrlen type, data; + struct ssh2_channel *c; + struct outstanding_channel_request *ocr; + unsigned localid, remid, winsize, pktsize, ext_type; + int want_reply, reply_type, expect_halfopen; + const char *error; + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + + /* Cross-reference to ssh2transport.c to handle the common packets + * between login and connection: DISCONNECT, DEBUG and IGNORE. If + * we have an instance of ssh2transport below us, then those + * messages won't come here anyway, but they could if we're + * running in bare ssh2-connection mode. */ + extern int ssh2_common_filter_queue(PacketProtocolLayer *ppl); + + while (1) { + if (ssh2_common_filter_queue(&s->ppl)) + return TRUE; + if ((pktin = pq_peek(s->ppl.in_pq)) == NULL) + return FALSE; + + switch (pktin->type) { + case SSH2_MSG_GLOBAL_REQUEST: + /* type = */ get_string(pktin); + want_reply = get_bool(pktin); + + /* + * 'reply_type' is the message type we'll send in + * response, if want_reply is set. Initialise it to the + * default value of REQUEST_FAILURE, for any request we + * don't recognise and handle below. + */ + reply_type = SSH2_MSG_REQUEST_FAILURE; + + /* + * We currently don't support any incoming global requests + * at all. Here's where to insert some code to handle + * them, if and when we do. + */ + + if (want_reply) { + pktout = ssh_bpp_new_pktout(s->ppl.bpp, reply_type); + pq_push(s->ppl.out_pq, pktout); + } + pq_pop(s->ppl.in_pq); + break; + + case SSH2_MSG_REQUEST_SUCCESS: + case SSH2_MSG_REQUEST_FAILURE: + if (!s->globreq_head) { + ssh_proto_error( + s->ppl.ssh, + "Received %s with no outstanding global request", + ssh2_pkt_type(s->ppl.bpp->pls->kctx, s->ppl.bpp->pls->actx, + pktin->type)); + return TRUE; + } + + s->globreq_head->handler(s, pktin, s->globreq_head->ctx); + { + struct outstanding_global_request *tmp = s->globreq_head; + s->globreq_head = s->globreq_head->next; + sfree(tmp); + } + + pq_pop(s->ppl.in_pq); + break; + + case SSH2_MSG_CHANNEL_OPEN: + error = NULL; + + type = get_string(pktin); + c = snew(struct ssh2_channel); + c->connlayer = s; + + remid = get_uint32(pktin); + winsize = get_uint32(pktin); + pktsize = get_uint32(pktin); + + if (ptrlen_eq_string(type, "x11")) { + char *addrstr = mkstr(get_string(pktin)); + int peerport = get_uint32(pktin); + + ppl_logevent(("Received X11 connect request from %s:%d", + addrstr, peerport)); + + if (!s->X11_fwd_enabled && !s->connshare) { + error = "X11 forwarding is not enabled"; + } else { + c->chan = x11_new_channel( + s->x11authtree, &c->sc, addrstr, peerport, + s->connshare != NULL); + ppl_logevent(("Opened X11 forward channel")); + } + + sfree(addrstr); + } else if (ptrlen_eq_string(type, "forwarded-tcpip")) { + struct ssh_rportfwd pf, *realpf; + ptrlen peeraddr; + int peerport; + + pf.shost = mkstr(get_string(pktin)); + pf.sport = get_uint32(pktin); + peeraddr = get_string(pktin); + peerport = get_uint32(pktin); + realpf = find234(s->rportfwds, &pf, NULL); + ppl_logevent(("Received remote port %s:%d open request " + "from %.*s:%d", pf.shost, pf.sport, + PTRLEN_PRINTF(peeraddr), peerport)); + sfree(pf.shost); + + if (realpf == NULL) { + error = "Remote port is not recognised"; + } else { + char *err; + + if (realpf->share_ctx) { + /* + * This port forwarding is on behalf of a + * connection-sharing downstream, so abandon our own + * channel-open procedure and just pass the message on + * to sshshare.c. + */ + share_got_pkt_from_server( + realpf->share_ctx, pktin->type, + BinarySource_UPCAST(pktin)->data, + BinarySource_UPCAST(pktin)->len); + sfree(c); + break; + } + + err = portfwdmgr_connect( + s->portfwdmgr, &c->chan, realpf->dhost, realpf->dport, + &c->sc, realpf->addressfamily); + ppl_logevent(("Attempting to forward remote port to " + "%s:%d", realpf->dhost, realpf->dport)); + if (err != NULL) { + ppl_logevent(("Port open failed: %s", err)); + sfree(err); + error = "Port open failed"; + } else { + ppl_logevent(("Forwarded port opened successfully")); + } + } + } else if (ptrlen_eq_string(type, "auth-agent@openssh.com")) { + if (!s->agent_fwd_enabled) + error = "Agent forwarding is not enabled"; + else + c->chan = agentf_new(&c->sc); + } else { + error = "Unsupported channel type requested"; + } + + c->remoteid = remid; + c->halfopen = FALSE; + if (error) { + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_CHANNEL_OPEN_FAILURE); + put_uint32(pktout, c->remoteid); + put_uint32(pktout, SSH2_OPEN_CONNECT_FAILED); + put_stringz(pktout, error); + put_stringz(pktout, "en"); /* language tag */ + pq_push(s->ppl.out_pq, pktout); + ppl_logevent(("Rejected channel open: %s", error)); + sfree(c); + } else { + ssh2_channel_init(c); + c->remwindow = winsize; + c->remmaxpkt = pktsize; + if (c->chan->initial_fixed_window_size) { + c->locwindow = c->locmaxwin = c->remlocwin = + c->chan->initial_fixed_window_size; + } + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); + put_uint32(pktout, c->remoteid); + put_uint32(pktout, c->localid); + put_uint32(pktout, c->locwindow); + put_uint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */ + pq_push(s->ppl.out_pq, pktout); + } + + pq_pop(s->ppl.in_pq); + break; + + case SSH2_MSG_CHANNEL_DATA: + case SSH2_MSG_CHANNEL_WINDOW_ADJUST: + case SSH2_MSG_CHANNEL_REQUEST: + case SSH2_MSG_CHANNEL_EOF: + case SSH2_MSG_CHANNEL_CLOSE: + case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: + case SSH2_MSG_CHANNEL_OPEN_FAILURE: + case SSH2_MSG_CHANNEL_SUCCESS: + case SSH2_MSG_CHANNEL_FAILURE: + /* + * Common preliminary code for all the messages from the + * server that cite one of our channel ids: look up that + * channel id, check it exists, and if it's for a sharing + * downstream, pass it on. + */ + localid = get_uint32(pktin); + c = find234(s->channels, &localid, ssh2_channelfind); + + if (c && c->sharectx) { + share_got_pkt_from_server(c->sharectx, pktin->type, + BinarySource_UPCAST(pktin)->data, + BinarySource_UPCAST(pktin)->len); + pq_pop(s->ppl.in_pq); + break; + } + + expect_halfopen = ( + pktin->type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION || + pktin->type == SSH2_MSG_CHANNEL_OPEN_FAILURE); + + if (!c || c->halfopen != expect_halfopen) { + ssh_proto_error(s->ppl.ssh, + "Received %s for %s channel %u", + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type), + (!c ? "nonexistent" : + c->halfopen ? "half-open" : "open"), + localid); + return TRUE; + } + + switch (pktin->type) { + case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: + assert(c->halfopen); + c->remoteid = get_uint32(pktin); + c->halfopen = FALSE; + c->remwindow = get_uint32(pktin); + c->remmaxpkt = get_uint32(pktin); + + chan_open_confirmation(c->chan); + + /* + * Now that the channel is fully open, it's possible + * in principle to immediately close it. Check whether + * it wants us to! + * + * This can occur if a local socket error occurred + * between us sending out CHANNEL_OPEN and receiving + * OPEN_CONFIRMATION. If that happens, all we can do + * is immediately initiate close proceedings now that + * we know the server's id to put in the close + * message. We'll have handled that in this code by + * having already turned c->chan into a zombie, so its + * want_close method (which ssh2_channel_check_close + * will consult) will already be returning TRUE. + */ + ssh2_channel_check_close(c); + + if (c->pending_eof) + ssh2_channel_try_eof(c); /* in case we had a pending EOF */ + break; + + case SSH2_MSG_CHANNEL_OPEN_FAILURE: + assert(c->halfopen); + + { + char *err = ssh2_channel_open_failure_error_text(pktin); + chan_open_failed(c->chan, err); + sfree(err); + } + chan_free(c->chan); + + del234(s->channels, c); + ssh2_channel_free(c); + + break; + + case SSH2_MSG_CHANNEL_DATA: + case SSH2_MSG_CHANNEL_EXTENDED_DATA: + ext_type = (pktin->type == SSH2_MSG_CHANNEL_DATA ? 0 : + get_uint32(pktin)); + data = get_string(pktin); + if (!get_err(pktin)) { + int bufsize; + c->locwindow -= data.len; + c->remlocwin -= data.len; + if (ext_type != 0 && ext_type != SSH2_EXTENDED_DATA_STDERR) + data.len = 0; /* ignore unknown extended data */ + bufsize = chan_send( + c->chan, ext_type == SSH2_EXTENDED_DATA_STDERR, + data.ptr, data.len); + + /* + * If it looks like the remote end hit the end of + * its window, and we didn't want it to do that, + * think about using a larger window. + */ + if (c->remlocwin <= 0 && + c->throttle_state == UNTHROTTLED && + c->locmaxwin < 0x40000000) + c->locmaxwin += OUR_V2_WINSIZE; + + /* + * If we are not buffering too much data, enlarge + * the window again at the remote side. If we are + * buffering too much, we may still need to adjust + * the window if the server's sent excess data. + */ + if (bufsize < c->locmaxwin) + ssh2_set_window(c, c->locmaxwin - bufsize); + + /* + * If we're either buffering way too much data, or + * if we're buffering anything at all and we're in + * "simple" mode, throttle the whole channel. + */ + if ((bufsize > c->locmaxwin || + (s->ssh_is_simple && bufsize>0)) && + !c->throttling_conn) { + c->throttling_conn = TRUE; + ssh_throttle_conn(s->ppl.ssh, +1); + } + } + break; + + case SSH2_MSG_CHANNEL_WINDOW_ADJUST: + if (!(c->closes & CLOSES_SENT_EOF)) { + c->remwindow += get_uint32(pktin); + ssh2_try_send_and_unthrottle(c); + } + break; + + case SSH2_MSG_CHANNEL_REQUEST: + type = get_string(pktin); + want_reply = get_bool(pktin); + + /* + * 'reply_type' is the message type we'll send in + * response, if want_reply is set. Initialise it to + * the default value of CHANNEL_FAILURE, for any + * request we don't recognise and handle below. + */ + reply_type = SSH2_MSG_CHANNEL_FAILURE; + + if (c->closes & CLOSES_SENT_CLOSE) { + /* + * We don't reply to channel requests after we've + * sent CHANNEL_CLOSE for the channel, because our + * reply might cross in the network with the other + * side's CHANNEL_CLOSE and arrive after they have + * wound the channel up completely. + */ + want_reply = FALSE; + } + + /* + * Having got the channel number, we now look at the + * request type string to see if it's something we + * recognise. + */ + if (c == s->mainchan) { + int exitcode; + + /* + * We recognise "exit-status" and "exit-signal" on + * the primary channel. + */ + if (ptrlen_eq_string(type, "exit-status")) { + exitcode = toint(get_uint32(pktin)); + ssh_got_exitcode(s->ppl.ssh, exitcode); + ppl_logevent(("Server sent command exit status %d", + exitcode)); + reply_type = SSH2_MSG_CHANNEL_SUCCESS; + } else if (ptrlen_eq_string(type, "exit-signal")) { + char *fmt_sig = NULL, *fmt_msg = NULL; + ptrlen errmsg; + int core = FALSE; + int format; + + /* + * ICK: older versions of OpenSSH (e.g. 3.4p1) + * provide an `int' for the signal, despite + * its having been a `string' in the drafts of + * RFC 4254 since at least 2001. (Fixed in + * session.c 1.147.) Try to infer which we can + * safely parse it as. + */ + + size_t startpos = BinarySource_UPCAST(pktin)->pos; + + for (format = 0; format < 2; format++) { + BinarySource_UPCAST(pktin)->pos = startpos; + BinarySource_UPCAST(pktin)->err = BSE_NO_ERROR; + + if (format == 0) { + /* standard string-based format */ + ptrlen signame = get_string(pktin); + fmt_sig = dupprintf(" \"%.*s\"", + PTRLEN_PRINTF(signame)); + + /* + * Really hideous method of translating the + * signal description back into a locally + * meaningful number. + */ + + if (0) + ; +#define TRANSLATE_SIGNAL(s) \ + else if (ptrlen_eq_string(signame, #s)) \ + exitcode = 128 + SIG ## s +#ifdef SIGABRT + TRANSLATE_SIGNAL(ABRT); +#endif +#ifdef SIGALRM + TRANSLATE_SIGNAL(ALRM); +#endif +#ifdef SIGFPE + TRANSLATE_SIGNAL(FPE); +#endif +#ifdef SIGHUP + TRANSLATE_SIGNAL(HUP); +#endif +#ifdef SIGILL + TRANSLATE_SIGNAL(ILL); +#endif +#ifdef SIGINT + TRANSLATE_SIGNAL(INT); +#endif +#ifdef SIGKILL + TRANSLATE_SIGNAL(KILL); +#endif +#ifdef SIGPIPE + TRANSLATE_SIGNAL(PIPE); +#endif +#ifdef SIGQUIT + TRANSLATE_SIGNAL(QUIT); +#endif +#ifdef SIGSEGV + TRANSLATE_SIGNAL(SEGV); +#endif +#ifdef SIGTERM + TRANSLATE_SIGNAL(TERM); +#endif +#ifdef SIGUSR1 + TRANSLATE_SIGNAL(USR1); +#endif +#ifdef SIGUSR2 + TRANSLATE_SIGNAL(USR2); +#endif +#undef TRANSLATE_SIGNAL + else + exitcode = 128; + } else { + /* nonstandard integer format */ + unsigned signum = get_uint32(pktin); + fmt_sig = dupprintf(" %u", signum); + exitcode = 128 + signum; + } + + core = get_bool(pktin); + errmsg = get_string(pktin); /* error message */ + get_string(pktin); /* language tag */ + if (!get_err(pktin) && get_avail(pktin) == 0) + break; /* successful parse */ + + sfree(fmt_sig); + } + + if (format == 2) { + fmt_sig = NULL; + exitcode = 128; + } + + if (errmsg.len) { + fmt_msg = dupprintf(" (\"%.*s\")", + PTRLEN_PRINTF(errmsg)); + } + + ssh_got_exitcode(s->ppl.ssh, exitcode); + ppl_logevent(("Server exited on signal%s%s%s", + fmt_sig ? fmt_sig : "", + core ? " (core dumped)" : "", + fmt_msg ? fmt_msg : "")); + sfree(fmt_sig); + sfree(fmt_msg); + reply_type = SSH2_MSG_CHANNEL_SUCCESS; + } + } + if (want_reply) { + pktout = ssh_bpp_new_pktout(s->ppl.bpp, reply_type); + put_uint32(pktout, c->remoteid); + pq_push(s->ppl.out_pq, pktout); + } + break; + + case SSH2_MSG_CHANNEL_SUCCESS: + case SSH2_MSG_CHANNEL_FAILURE: + ocr = c->chanreq_head; + if (!ocr) { + ssh_proto_error( + s->ppl.ssh, + "Received %s for channel %d with no outstanding " + "channel request", + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, pktin->type)); + return TRUE; + } + ocr->handler(c, pktin, ocr->ctx); + c->chanreq_head = ocr->next; + sfree(ocr); + /* + * We may now initiate channel-closing procedures, if + * that CHANNEL_REQUEST was the last thing outstanding + * before we send CHANNEL_CLOSE. + */ + ssh2_channel_check_close(c); + break; + + case SSH2_MSG_CHANNEL_EOF: + if (!(c->closes & CLOSES_RCVD_EOF)) { + c->closes |= CLOSES_RCVD_EOF; + chan_send_eof(c->chan); + ssh2_channel_check_close(c); + } + break; + + case SSH2_MSG_CHANNEL_CLOSE: + /* + * When we receive CLOSE on a channel, we assume it + * comes with an implied EOF if we haven't seen EOF + * yet. + */ + if (!(c->closes & CLOSES_RCVD_EOF)) { + c->closes |= CLOSES_RCVD_EOF; + chan_send_eof(c->chan); + } + + if (!(s->ppl.remote_bugs & BUG_SENDS_LATE_REQUEST_REPLY)) { + /* + * It also means we stop expecting to see replies + * to any outstanding channel requests, so clean + * those up too. (ssh_chanreq_init will enforce by + * assertion that we don't subsequently put + * anything back on this list.) + */ + while (c->chanreq_head) { + struct outstanding_channel_request *ocr = + c->chanreq_head; + ocr->handler(c, NULL, ocr->ctx); + c->chanreq_head = ocr->next; + sfree(ocr); + } + } + + /* + * And we also send an outgoing EOF, if we haven't + * already, on the assumption that CLOSE is a pretty + * forceful announcement that the remote side is doing + * away with the entire channel. (If it had wanted to + * send us EOF and continue receiving data from us, it + * would have just sent CHANNEL_EOF.) + */ + if (!(c->closes & CLOSES_SENT_EOF)) { + /* + * Abandon any buffered data we still wanted to + * send to this channel. Receiving a CHANNEL_CLOSE + * is an indication that the server really wants + * to get on and _destroy_ this channel, and it + * isn't going to send us any further + * WINDOW_ADJUSTs to permit us to send pending + * stuff. + */ + bufchain_clear(&c->outbuffer); + + /* + * Send outgoing EOF. + */ + sshfwd_write_eof(&c->sc); + + /* + * Make sure we don't read any more from whatever + * our local data source is for this channel. + * (This will pick up on the changes made by + * sshfwd_write_eof.) + */ + ssh2_channel_check_throttle(c); + } + + /* + * Now process the actual close. + */ + if (!(c->closes & CLOSES_RCVD_CLOSE)) { + c->closes |= CLOSES_RCVD_CLOSE; + ssh2_channel_check_close(c); + } + + break; + } + + pq_pop(s->ppl.in_pq); + break; + + default: + return FALSE; + } + } +} + +static void ssh2_handle_winadj_response(struct ssh2_channel *c, + PktIn *pktin, void *ctx) +{ + unsigned *sizep = ctx; + + /* + * Winadj responses should always be failures. However, at least + * one server ("boks_sshd") is known to return SUCCESS for channel + * requests it's never heard of, such as "winadj@putty". Raised + * with foxt.com as bug 090916-090424, but for the sake of a quiet + * life, we don't worry about what kind of response we got. + */ + + c->remlocwin += *sizep; + sfree(sizep); + /* + * winadj messages are only sent when the window is fully open, so + * if we get an ack of one, we know any pending unthrottle is + * complete. + */ + if (c->throttle_state == UNTHROTTLING) + c->throttle_state = UNTHROTTLED; +} + +static void ssh2_set_window(struct ssh2_channel *c, int newwin) +{ + struct ssh2_connection_state *s = c->connlayer; + + /* + * Never send WINDOW_ADJUST for a channel that the remote side has + * already sent EOF on; there's no point, since it won't be + * sending any more data anyway. Ditto if _we've_ already sent + * CLOSE. + */ + if (c->closes & (CLOSES_RCVD_EOF | CLOSES_SENT_CLOSE)) + return; + + /* + * If the client-side Channel is in an initial setup phase with a + * fixed window size, e.g. for an X11 channel when we're still + * waiting to see its initial auth and may yet hand it off to a + * downstream, don't send any WINDOW_ADJUST either. + */ + if (c->chan->initial_fixed_window_size) + return; + + /* + * If the remote end has a habit of ignoring maxpkt, limit the + * window so that it has no choice (assuming it doesn't ignore the + * window as well). + */ + if ((s->ppl.remote_bugs & BUG_SSH2_MAXPKT) && newwin > OUR_V2_MAXPKT) + newwin = OUR_V2_MAXPKT; + + /* + * Only send a WINDOW_ADJUST if there's significantly more window + * available than the other end thinks there is. This saves us + * sending a WINDOW_ADJUST for every character in a shell session. + * + * "Significant" is arbitrarily defined as half the window size. + */ + if (newwin / 2 >= c->locwindow) { + PktOut *pktout; + unsigned *up; + + /* + * In order to keep track of how much window the client + * actually has available, we'd like it to acknowledge each + * WINDOW_ADJUST. We can't do that directly, so we accompany + * it with a CHANNEL_REQUEST that has to be acknowledged. + * + * This is only necessary if we're opening the window wide. + * If we're not, then throughput is being constrained by + * something other than the maximum window size anyway. + */ + if (newwin == c->locmaxwin && + !(s->ppl.remote_bugs & BUG_CHOKES_ON_WINADJ)) { + up = snew(unsigned); + *up = newwin - c->locwindow; + pktout = ssh2_chanreq_init(c, "winadj@putty.projects.tartarus.org", + ssh2_handle_winadj_response, up); + pq_push(s->ppl.out_pq, pktout); + + if (c->throttle_state != UNTHROTTLED) + c->throttle_state = UNTHROTTLING; + } else { + /* Pretend the WINDOW_ADJUST was acked immediately. */ + c->remlocwin = newwin; + c->throttle_state = THROTTLED; + } + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_WINDOW_ADJUST); + put_uint32(pktout, c->remoteid); + put_uint32(pktout, newwin - c->locwindow); + pq_push(s->ppl.out_pq, pktout); + c->locwindow = newwin; + } +} + +static PktIn *ssh2_connection_pop(struct ssh2_connection_state *s) +{ + ssh2_connection_filter_queue(s); + return pq_pop(s->ppl.in_pq); +} + +static void ssh2_connection_process_queue(PacketProtocolLayer *ppl) +{ + struct ssh2_connection_state *s = + FROMFIELD(ppl, struct ssh2_connection_state, ppl); + PktIn *pktin; + PktOut *pktout; + + if (ssh2_connection_filter_queue(s)) /* no matter why we were called */ + return; + + crBegin(s->crState); + + /* + * Create the main session channel, if any. + */ + if (conf_get_int(s->conf, CONF_ssh_no_shell)) { + s->mctype = MAINCHAN_NONE; + } else if (*conf_get_str(s->conf, CONF_ssh_nc_host)) { + s->mctype = MAINCHAN_DIRECT_TCPIP; + } else { + s->mctype = MAINCHAN_SESSION; + } + + if (s->mctype != MAINCHAN_NONE) { + mainchan *mc = mainchan_new(s); + + switch (s->mctype) { + case MAINCHAN_NONE: + assert(0 && "Unreachable"); + break; + + case MAINCHAN_SESSION: + s->mainchan = snew(struct ssh2_channel); + mc->sc = &s->mainchan->sc; + s->mainchan->connlayer = s; + ssh2_channel_init(s->mainchan); + s->mainchan->chan = &mc->chan; + s->mainchan->halfopen = TRUE; + pktout = ssh2_chanopen_init(s->mainchan, "session"); + ppl_logevent(("Opening session as main channel")); + pq_push(s->ppl.out_pq, pktout); + break; + + case MAINCHAN_DIRECT_TCPIP: + mc->sc = ssh_lportfwd_open( + &s->cl, conf_get_str(s->conf, CONF_ssh_nc_host), + conf_get_int(s->conf, CONF_ssh_nc_port), + "main channel", &mc->chan); + s->mainchan = FROMFIELD(mc->sc, struct ssh2_channel, sc); + break; + } + + /* + * Wait until that channel has been successfully opened (or + * not). + */ + crMaybeWaitUntilV(!s->mainchan || !s->mainchan->halfopen); + if (!s->mainchan) { + ssh_sw_abort(s->ppl.ssh, "Server refused to open main channel: %s", + s->mainchan_open_error); + return; + } + } + + /* + * Now the connection protocol is properly up and running, with + * all those dispatch table entries, so it's safe to let + * downstreams start trying to open extra channels through us. + */ + if (s->connshare) + share_activate(s->connshare, s->peer_verstring); + + if (s->mainchan && s->ssh_is_simple) { + /* + * This message indicates to the server that we promise + * not to try to run any other channel in parallel with + * this one, so it's safe for it to advertise a very large + * window and leave the flow control to TCP. + */ + pktout = ssh2_chanreq_init( + s->mainchan, "simple@putty.projects.tartarus.org", NULL, NULL); + pq_push(s->ppl.out_pq, pktout); + } + + /* + * Enable port forwardings. + */ + portfwdmgr_config(s->portfwdmgr, s->conf); + s->portfwdmgr_configured = TRUE; + + if (s->mainchan && s->mctype == MAINCHAN_SESSION) { + /* + * Send the CHANNEL_REQUESTS for the main session channel. + * Each one is handled by its own little asynchronous + * co-routine. + */ + + /* Potentially enable X11 forwarding. */ + if (conf_get_int(s->conf, CONF_x11_forward)) { + s->x11disp = x11_setup_display( + conf_get_str(s->conf, CONF_x11_display), s->conf); + if (!s->x11disp) { + /* FIXME: return an error message from x11_setup_display */ + ppl_logevent(("X11 forwarding not enabled: unable to" + " initialise X display")); + } else { + s->x11auth = x11_invent_fake_auth( + s->x11authtree, conf_get_int(s->conf, CONF_x11_auth)); + s->x11auth->disp = s->x11disp; + + ssh2_setup_x11(s->mainchan, NULL, NULL); + } + } + + /* Potentially enable agent forwarding. */ + if (ssh_agent_forwarding_permitted(&s->cl)) + ssh2_setup_agent(s->mainchan, NULL, NULL); + + /* Now allocate a pty for the session. */ + if (!conf_get_int(s->conf, CONF_nopty)) + ssh2_setup_pty(s->mainchan, NULL, NULL); + + /* Send environment variables. */ + ssh2_setup_env(s->mainchan, NULL, NULL); + + /* + * Start a shell or a remote command. We may have to attempt + * this twice if the config data has provided a second choice + * of command. + */ + for (s->session_attempt = 0; s->session_attempt < 2; + s->session_attempt++) { + int subsys; + char *cmd; + + if (s->session_attempt == 0) { + subsys = conf_get_int(s->conf, CONF_ssh_subsys); + cmd = conf_get_str(s->conf, CONF_remote_cmd); + } else { + subsys = conf_get_int(s->conf, CONF_ssh_subsys2); + cmd = conf_get_str(s->conf, CONF_remote_cmd2); + if (!*cmd) + break; + ppl_logevent(("Primary command failed; attempting fallback")); + } + + if (subsys) { + pktout = ssh2_chanreq_init(s->mainchan, "subsystem", + ssh2_response_session, s); + put_stringz(pktout, cmd); + } else if (*cmd) { + pktout = ssh2_chanreq_init(s->mainchan, "exec", + ssh2_response_session, s); + put_stringz(pktout, cmd); + } else { + pktout = ssh2_chanreq_init(s->mainchan, "shell", + ssh2_response_session, s); + } + pq_push(s->ppl.out_pq, pktout); + s->session_status = 0; + + /* Wait for success or failure message to be passed to + * ssh2_response_session, which will set session_status to + * +1 for success or -1 for failure */ + crMaybeWaitUntilV(s->session_status != 0); + + if (s->session_status > 0) { + if (s->session_attempt == 1) + ssh_got_fallback_cmd(s->ppl.ssh); + ppl_logevent(("Started a shell/command")); + break; + } + } + + if (s->session_status < 0) { + /* + * We failed to start either the primary or the fallback + * command. + */ + ssh_sw_abort(s->ppl.ssh, + "Server refused to start a shell/command"); + return; + } + } else { + s->echoedit = TRUE; + } + + s->mainchan_ready = TRUE; + if (s->mainchan) + s->want_user_input = TRUE; + + /* If an EOF or a window-size change arrived before we were ready + * to handle either one, handle them now. */ + if (s->mainchan_eof_pending) + ssh_ppl_special_cmd(&s->ppl, SS_EOF, 0); + if (s->term_width_orig != s->term_width || + s->term_height_orig != s->term_height) + ssh_terminal_size(&s->cl, s->term_width, s->term_height); + + ssh_ldisc_update(s->ppl.ssh); + + /* + * Transfer data! + */ + + while (1) { + if ((pktin = ssh2_connection_pop(s)) != NULL) { + + /* + * _All_ the connection-layer packets we expect to + * receive are now handled by the dispatch table. + * Anything that reaches here must be bogus. + */ + + ssh_proto_error(s->ppl.ssh, "Received unexpected connection-layer " + "packet, type %d (%s)", pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return; + } + crReturnV; + } + + crFinishV; +} + +static void ssh2_channel_check_close(struct ssh2_channel *c) +{ + struct ssh2_connection_state *s = c->connlayer; + PktOut *pktout; + + if (c->halfopen) { + /* + * If we've sent out our own CHANNEL_OPEN but not yet seen + * either OPEN_CONFIRMATION or OPEN_FAILURE in response, then + * it's too early to be sending close messages of any kind. + */ + return; + } + + if ((!((CLOSES_SENT_EOF | CLOSES_RCVD_EOF) & ~c->closes) || + chan_want_close(c->chan, (c->closes & CLOSES_SENT_EOF), + (c->closes & CLOSES_RCVD_EOF))) && + !c->chanreq_head && + !(c->closes & CLOSES_SENT_CLOSE)) { + /* + * We have both sent and received EOF (or the channel is a + * zombie), and we have no outstanding channel requests, which + * means the channel is in final wind-up. But we haven't sent + * CLOSE, so let's do so now. + */ + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_CLOSE); + put_uint32(pktout, c->remoteid); + pq_push(s->ppl.out_pq, pktout); + c->closes |= CLOSES_SENT_EOF | CLOSES_SENT_CLOSE; + } + + if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) { + assert(c->chanreq_head == NULL); + /* + * We have both sent and received CLOSE, which means we're + * completely done with the channel. + */ + ssh2_channel_destroy(c); + } +} + +static void ssh2_channel_try_eof(struct ssh2_channel *c) +{ + struct ssh2_connection_state *s = c->connlayer; + PktOut *pktout; + assert(c->pending_eof); /* precondition for calling us */ + if (c->halfopen) + return; /* can't close: not even opened yet */ + if (bufchain_size(&c->outbuffer) > 0) + return; /* can't send EOF: pending outgoing data */ + + c->pending_eof = FALSE; /* we're about to send it */ + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_EOF); + put_uint32(pktout, c->remoteid); + pq_push(s->ppl.out_pq, pktout); + c->closes |= CLOSES_SENT_EOF; + ssh2_channel_check_close(c); +} + +/* + * Attempt to send data on an SSH-2 channel. + */ +static int ssh2_try_send(struct ssh2_channel *c) +{ + struct ssh2_connection_state *s = c->connlayer; + PktOut *pktout; + int bufsize; + + while (c->remwindow > 0 && bufchain_size(&c->outbuffer) > 0) { + int len; + void *data; + bufchain_prefix(&c->outbuffer, &data, &len); + if ((unsigned)len > c->remwindow) + len = c->remwindow; + if ((unsigned)len > c->remmaxpkt) + len = c->remmaxpkt; + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_DATA); + put_uint32(pktout, c->remoteid); + put_string(pktout, data, len); + pq_push(s->ppl.out_pq, pktout); + bufchain_consume(&c->outbuffer, len); + c->remwindow -= len; + } + + /* + * After having sent as much data as we can, return the amount + * still buffered. + */ + bufsize = bufchain_size(&c->outbuffer); + + /* + * And if there's no data pending but we need to send an EOF, send + * it. + */ + if (!bufsize && c->pending_eof) + ssh2_channel_try_eof(c); + + return bufsize; +} + +static void ssh2_try_send_and_unthrottle(struct ssh2_channel *c) +{ + int bufsize; + if (c->closes & CLOSES_SENT_EOF) + return; /* don't send on channels we've EOFed */ + bufsize = ssh2_try_send(c); + if (bufsize == 0) { + c->throttled_by_backlog = FALSE; + ssh2_channel_check_throttle(c); + } +} + +static void ssh2_channel_check_throttle(struct ssh2_channel *c) +{ + /* + * We don't want this channel to read further input if this + * particular channel has a backed-up SSH window, or if the + * outgoing side of the whole SSH connection is currently + * throttled, or if this channel already has an outgoing EOF + * either sent or pending. + */ + chan_set_input_wanted(c->chan, + !c->throttled_by_backlog && + !c->connlayer->all_channels_throttled && + !c->pending_eof && + !(c->closes & CLOSES_SENT_EOF)); +} + +/* + * Close any local socket and free any local resources associated with + * a channel. This converts the channel into a zombie. + */ +static void ssh2_channel_close_local(struct ssh2_channel *c, + const char *reason) +{ + struct ssh2_connection_state *s = c->connlayer; + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + char *msg = NULL; + + if (c->sharectx) + return; + + msg = chan_log_close_msg(c->chan); + + if (msg) + ppl_logevent(("%s%s%s", msg, reason ? " " : "", reason ? reason : "")); + + sfree(msg); + + chan_free(c->chan); + c->chan = zombiechan_new(); +} + +static void ssh2_check_termination_callback(void *vctx) +{ + struct ssh2_connection_state *s = (struct ssh2_connection_state *)vctx; + ssh2_check_termination(s); +} + +static void ssh2_channel_destroy(struct ssh2_channel *c) +{ + struct ssh2_connection_state *s = c->connlayer; + + assert(c->chanreq_head == NULL); + + ssh2_channel_close_local(c, NULL); + del234(s->channels, c); + ssh2_channel_free(c); + + /* + * If that was the last channel left open, we might need to + * terminate. But we'll be a bit cautious, by doing that in a + * toplevel callback, just in case anything on the current call + * stack objects to this entire PPL being freed. + */ + queue_toplevel_callback(ssh2_check_termination_callback, s); +} + +static void ssh2_check_termination(struct ssh2_connection_state *s) +{ + /* + * Decide whether we should terminate the SSH connection now. + * Called after a channel or a downstream goes away. The general + * policy is that we terminate when none of either is left. + */ + + if (s->mctype == MAINCHAN_NONE) { + /* + * Exception: in ssh_no_shell mode we persist even in the + * absence of any channels (because our purpose is probably to + * be a background port forwarder). + */ + return; + } + + if (count234(s->channels) == 0 && + !(s->connshare && share_ndownstreams(s->connshare) > 0)) { + /* + * We used to send SSH_MSG_DISCONNECT here, because I'd + * believed that _every_ conforming SSH-2 connection had to + * end with a disconnect being sent by at least one side; + * apparently I was wrong and it's perfectly OK to + * unceremoniously slam the connection shut when you're done, + * and indeed OpenSSH feels this is more polite than sending a + * DISCONNECT. So now we don't. + */ + ssh_user_close(s->ppl.ssh, "All channels closed"); + return; + } +} + +static void ssh2_setup_x11(struct ssh2_channel *c, PktIn *pktin, void *ctx) +{ + struct ssh2_setup_x11_state { + int crLine; + }; + struct ssh2_connection_state *cs = c->connlayer; + PacketProtocolLayer *ppl = &cs->ppl; /* for ppl_logevent */ + PktOut *pktout; + crStateP(ssh2_setup_x11_state, ctx); + + crBeginState; + + ppl_logevent(("Requesting X11 forwarding")); + pktout = ssh2_chanreq_init(cs->mainchan, "x11-req", ssh2_setup_x11, s); + put_bool(pktout, 0); /* many connections */ + put_stringz(pktout, cs->x11auth->protoname); + put_stringz(pktout, cs->x11auth->datastring); + put_uint32(pktout, cs->x11disp->screennum); + pq_push(cs->ppl.out_pq, pktout); + + /* Wait to be called back with either a response packet, or NULL + * meaning clean up and free our data */ + crReturnV; + + if (pktin) { + if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) { + ppl_logevent(("X11 forwarding enabled")); + cs->X11_fwd_enabled = TRUE; + } else + ppl_logevent(("X11 forwarding refused")); + } + + crFinishFreeV; +} + +static void ssh2_setup_agent(struct ssh2_channel *c, PktIn *pktin, void *ctx) +{ + struct ssh2_setup_agent_state { + int crLine; + }; + struct ssh2_connection_state *cs = c->connlayer; + PacketProtocolLayer *ppl = &cs->ppl; /* for ppl_logevent */ + PktOut *pktout; + crStateP(ssh2_setup_agent_state, ctx); + + crBeginState; + + ppl_logevent(("Requesting OpenSSH-style agent forwarding")); + pktout = ssh2_chanreq_init(cs->mainchan, "auth-agent-req@openssh.com", + ssh2_setup_agent, s); + pq_push(cs->ppl.out_pq, pktout); + + /* Wait to be called back with either a response packet, or NULL + * meaning clean up and free our data */ + crReturnV; + + if (pktin) { + if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) { + ppl_logevent(("Agent forwarding enabled")); + cs->agent_fwd_enabled = TRUE; + } else + ppl_logevent(("Agent forwarding refused")); + } + + crFinishFreeV; +} + +static void ssh2_setup_pty(struct ssh2_channel *c, PktIn *pktin, void *ctx) +{ + struct ssh2_setup_pty_state { + int crLine; + int ospeed, ispeed; + }; + struct ssh2_connection_state *cs = c->connlayer; + PacketProtocolLayer *ppl = &cs->ppl; /* for ppl_logevent, ppl_printf */ + PktOut *pktout; + crStateP(ssh2_setup_pty_state, ctx); + + crBeginState; + + /* Unpick the terminal-speed string. */ + s->ospeed = 38400; s->ispeed = 38400; /* last-resort defaults */ + sscanf(conf_get_str(cs->conf, CONF_termspeed), "%d,%d", + &s->ospeed, &s->ispeed); + /* Build the pty request. */ + pktout = ssh2_chanreq_init(cs->mainchan, "pty-req", ssh2_setup_pty, s); + put_stringz(pktout, conf_get_str(cs->conf, CONF_termtype)); + put_uint32(pktout, cs->term_width); + put_uint32(pktout, cs->term_height); + cs->term_width_orig = cs->term_width; + cs->term_height_orig = cs->term_height; + put_uint32(pktout, 0); /* pixel width */ + put_uint32(pktout, 0); /* pixel height */ + { + strbuf *modebuf = strbuf_new(); + write_ttymodes_to_packet_from_conf( + BinarySink_UPCAST(modebuf), cs->ppl.frontend, cs->conf, + 2, s->ospeed, s->ispeed); + put_stringsb(pktout, modebuf); + } + pq_push(cs->ppl.out_pq, pktout); + + /* Wait to be called back with either a response packet, or NULL + * meaning clean up and free our data */ + crReturnV; + + if (pktin) { + if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) { + ppl_logevent(("Allocated pty (ospeed %dbps, ispeed %dbps)", + s->ospeed, s->ispeed)); + cs->got_pty = TRUE; + } else { + ppl_printf(("Server refused to allocate pty\r\n")); + cs->echoedit = TRUE; + } + } + + crFinishFreeV; +} + +static void ssh2_setup_env(struct ssh2_channel *c, PktIn *pktin, void *ctx) +{ + struct ssh2_setup_env_state { + int crLine; + int num_env, env_left, env_ok; + }; + struct ssh2_connection_state *cs = c->connlayer; + PacketProtocolLayer *ppl = &cs->ppl; /* for ppl_logevent, ppl_printf */ + PktOut *pktout; + crStateP(ssh2_setup_env_state, ctx); + + crBeginState; + + /* + * Send environment variables. + * + * Simplest thing here is to send all the requests at once, and + * then wait for a whole bunch of successes or failures. + */ + s->num_env = 0; + { + char *key, *val; + + for (val = conf_get_str_strs(cs->conf, CONF_environmt, NULL, &key); + val != NULL; + val = conf_get_str_strs(cs->conf, CONF_environmt, key, &key)) { + pktout = ssh2_chanreq_init(cs->mainchan, "env", ssh2_setup_env, s); + put_stringz(pktout, key); + put_stringz(pktout, val); + pq_push(cs->ppl.out_pq, pktout); + + s->num_env++; + } + if (s->num_env) + ppl_logevent(("Sent %d environment variables", s->num_env)); + } + + if (s->num_env) { + s->env_ok = 0; + s->env_left = s->num_env; + + while (s->env_left > 0) { + /* Wait to be called back with either a response packet, + * or NULL meaning clean up and free our data */ + crReturnV; + if (!pktin) goto out; + if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) + s->env_ok++; + s->env_left--; + } + + if (s->env_ok == s->num_env) { + ppl_logevent(("All environment variables successfully set")); + } else if (s->env_ok == 0) { + ppl_logevent(("All environment variables refused")); + ppl_printf(("Server refused to set environment variables\r\n")); + } else { + ppl_logevent(("%d environment variables refused", + s->num_env - s->env_ok)); + ppl_printf(("Server refused to set all environment " + "variables\r\n")); + } + } + out:; + crFinishFreeV; +} + +static void ssh2_response_session(struct ssh2_channel *c, PktIn *pktin, + void *ctx) +{ + struct ssh2_connection_state *s = c->connlayer; + s->session_status = (pktin->type == SSH2_MSG_CHANNEL_SUCCESS ? +1 : -1); +} + +/* + * Set up most of a new ssh2_channel. Nulls out sharectx, but leaves + * chan untouched (since it will sometimes have been filled in before + * calling this). + */ +static void ssh2_channel_init(struct ssh2_channel *c) +{ + struct ssh2_connection_state *s = c->connlayer; + c->closes = 0; + c->pending_eof = FALSE; + c->throttling_conn = FALSE; + c->sharectx = NULL; + c->locwindow = c->locmaxwin = c->remlocwin = + s->ssh_is_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE; + c->chanreq_head = NULL; + c->throttle_state = UNTHROTTLED; + bufchain_init(&c->outbuffer); + c->sc.vt = &ssh2channel_vtable; + c->localid = alloc_channel_id(s->channels, struct ssh2_channel); + add234(s->channels, c); +} + +/* + * Construct the common parts of a CHANNEL_OPEN. + */ +static PktOut *ssh2_chanopen_init(struct ssh2_channel *c, const char *type) +{ + struct ssh2_connection_state *s = c->connlayer; + PktOut *pktout; + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_OPEN); + put_stringz(pktout, type); + put_uint32(pktout, c->localid); + put_uint32(pktout, c->locwindow); /* our window size */ + put_uint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */ + return pktout; +} + +/* + * Construct the common parts of a CHANNEL_REQUEST. If handler is not + * NULL then a reply will be requested and the handler will be called + * when it arrives. The returned packet is ready to have any + * request-specific data added and be sent. Note that if a handler is + * provided, it's essential that the request actually be sent. + * + * The handler will usually be passed the response packet in pktin. If + * pktin is NULL, this means that no reply will ever be forthcoming + * (e.g. because the entire connection is being destroyed, or because + * the server initiated channel closure before we saw the response) + * and the handler should free any storage it's holding. + */ +static PktOut *ssh2_chanreq_init(struct ssh2_channel *c, const char *type, + cr_handler_fn_t handler, void *ctx) +{ + struct ssh2_connection_state *s = c->connlayer; + PktOut *pktout; + + assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE))); + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_REQUEST); + put_uint32(pktout, c->remoteid); + put_stringz(pktout, type); + put_bool(pktout, handler != NULL); + if (handler != NULL) { + struct outstanding_channel_request *ocr = + snew(struct outstanding_channel_request); + + ocr->handler = handler; + ocr->ctx = ctx; + ocr->next = NULL; + if (!c->chanreq_head) + c->chanreq_head = ocr; + else + c->chanreq_tail->next = ocr; + c->chanreq_tail = ocr; + } + return pktout; +} + +static Conf *ssh2channel_get_conf(SshChannel *sc) +{ + struct ssh2_channel *c = FROMFIELD(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + return s->conf; +} + +static void ssh2channel_write_eof(SshChannel *sc) +{ + struct ssh2_channel *c = FROMFIELD(sc, struct ssh2_channel, sc); + + if (c->closes & CLOSES_SENT_EOF) + return; + + c->pending_eof = TRUE; + ssh2_channel_try_eof(c); +} + +static void ssh2channel_unclean_close(SshChannel *sc, const char *err) +{ + struct ssh2_channel *c = FROMFIELD(sc, struct ssh2_channel, sc); + char *reason; + + reason = dupprintf("due to local error: %s", err); + ssh2_channel_close_local(c, reason); + sfree(reason); + c->pending_eof = FALSE; /* this will confuse a zombie channel */ + + ssh2_channel_check_close(c); +} + +static void ssh2channel_unthrottle(SshChannel *sc, int bufsize) +{ + struct ssh2_channel *c = FROMFIELD(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + int buflimit; + + buflimit = s->ssh_is_simple ? 0 : c->locmaxwin; + if (bufsize < buflimit) + ssh2_set_window(c, buflimit - bufsize); + + if (c->throttling_conn && bufsize <= buflimit) { + c->throttling_conn = 0; + ssh_throttle_conn(s->ppl.ssh, -1); + } +} + +static int ssh2channel_write(SshChannel *sc, const void *buf, int len) +{ + struct ssh2_channel *c = FROMFIELD(sc, struct ssh2_channel, sc); + assert(!(c->closes & CLOSES_SENT_EOF)); + bufchain_add(&c->outbuffer, buf, len); + return ssh2_try_send(c); +} + +static void ssh2channel_x11_sharing_handover( + SshChannel *sc, ssh_sharing_connstate *share_cs, share_channel *share_chan, + const char *peer_addr, int peer_port, int endian, + int protomajor, int protominor, const void *initial_data, int initial_len) +{ + struct ssh2_channel *c = FROMFIELD(sc, struct ssh2_channel, sc); + /* + * This function is called when we've just discovered that an X + * forwarding channel on which we'd been handling the initial auth + * ourselves turns out to be destined for a connection-sharing + * downstream. So we turn the channel into a sharing one, meaning + * that we completely stop tracking windows and buffering data and + * just pass more or less unmodified SSH messages back and forth. + */ + c->sharectx = share_cs; + share_setup_x11_channel(share_cs, share_chan, + c->localid, c->remoteid, c->remwindow, + c->remmaxpkt, c->locwindow, + peer_addr, peer_port, endian, + protomajor, protominor, + initial_data, initial_len); + chan_free(c->chan); + c->chan = NULL; +} + +static void ssh2channel_window_override_removed(SshChannel *sc) +{ + struct ssh2_channel *c = FROMFIELD(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + + /* + * This function is called when a client-side Channel has just + * stopped requiring an initial fixed-size window. + */ + assert(!c->chan->initial_fixed_window_size); + ssh2_set_window(c, s->ssh_is_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE); +} + +static SshChannel *ssh2_lportfwd_open( + ConnectionLayer *cl, const char *hostname, int port, + const char *org, Channel *chan) +{ + struct ssh2_connection_state *s = + FROMFIELD(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(("Opening connection to %s:%d for %s", hostname, port, org)); + + pktout = ssh2_chanopen_init(c, "direct-tcpip"); + { + char *trimmed_host = host_strduptrim(hostname); + put_stringz(pktout, trimmed_host); + sfree(trimmed_host); + } + put_uint32(pktout, port); + /* + * We make up values for the originator data; partly it's too much + * hassle to keep track, and partly I'm not convinced the server + * should be told details like that about my local network + * configuration. The "originator IP address" is syntactically a + * numeric IP address, and some servers (e.g., Tectia) get upset + * if it doesn't match this syntax. + */ + put_stringz(pktout, "0.0.0.0"); + put_uint32(pktout, 0); + pq_push(s->ppl.out_pq, pktout); + + return &c->sc; +} + +static void ssh2_rportfwd_globreq_response(struct ssh2_connection_state *s, + PktIn *pktin, void *ctx) +{ + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + struct ssh_rportfwd *rpf = (struct ssh_rportfwd *)ctx; + + if (pktin->type == SSH2_MSG_REQUEST_SUCCESS) { + ppl_logevent(("Remote port forwarding from %s enabled", + rpf->log_description)); + } else { + ppl_logevent(("Remote port forwarding from %s refused", + rpf->log_description)); + + struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf); + assert(realpf == rpf); + portfwdmgr_close(s->portfwdmgr, rpf->pfr); + free_rportfwd(rpf); + } +} + +static 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) +{ + struct ssh2_connection_state *s = + FROMFIELD(cl, struct ssh2_connection_state, cl); + struct ssh_rportfwd *rpf = snew(struct ssh_rportfwd); + + rpf->shost = dupstr(shost); + rpf->sport = sport; + rpf->dhost = dupstr(dhost); + rpf->dport = dport; + rpf->addressfamily = addressfamily; + rpf->log_description = dupstr(log_description); + rpf->pfr = pfr; + rpf->share_ctx = share_ctx; + + if (add234(s->rportfwds, rpf) != rpf) { + free_rportfwd(rpf); + return NULL; + } + + if (!rpf->share_ctx) { + PktOut *pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_GLOBAL_REQUEST); + put_stringz(pktout, "tcpip-forward"); + put_bool(pktout, 1); /* want reply */ + put_stringz(pktout, rpf->shost); + put_uint32(pktout, rpf->sport); + pq_push(s->ppl.out_pq, pktout); + + ssh2_queue_global_request_handler( + s, ssh2_rportfwd_globreq_response, rpf); + } + + return rpf; +} + +static void ssh2_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf) +{ + struct ssh2_connection_state *s = + FROMFIELD(cl, struct ssh2_connection_state, cl); + + if (rpf->share_ctx) { + /* + * We don't manufacture a cancel-tcpip-forward message for + * remote port forwardings being removed on behalf of a + * downstream; we just pass through the one the downstream + * sent to us. + */ + } else { + PktOut *pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_GLOBAL_REQUEST); + put_stringz(pktout, "cancel-tcpip-forward"); + put_bool(pktout, 0); /* _don't_ want reply */ + put_stringz(pktout, rpf->shost); + put_uint32(pktout, rpf->sport); + pq_push(s->ppl.out_pq, pktout); + } + + struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf); + assert(realpf == rpf); + free_rportfwd(rpf); +} + +static void ssh2_sharing_globreq_response( + struct ssh2_connection_state *s, PktIn *pktin, void *ctx) +{ + ssh_sharing_connstate *cs = (ssh_sharing_connstate *)ctx; + share_got_pkt_from_server(cs, pktin->type, + BinarySource_UPCAST(pktin)->data, + BinarySource_UPCAST(pktin)->len); +} + +static void ssh2_sharing_queue_global_request( + ConnectionLayer *cl, ssh_sharing_connstate *cs) +{ + struct ssh2_connection_state *s = + FROMFIELD(cl, struct ssh2_connection_state, cl); + ssh2_queue_global_request_handler(s, ssh2_sharing_globreq_response, cs); +} + +static struct X11FakeAuth *ssh2_add_sharing_x11_display( + ConnectionLayer *cl, int authtype, ssh_sharing_connstate *share_cs, + share_channel *share_chan) +{ + struct ssh2_connection_state *s = + FROMFIELD(cl, struct ssh2_connection_state, cl); + struct X11FakeAuth *auth; + + /* + * Make up a new set of fake X11 auth data, and add it to the tree + * of currently valid ones with an indication of the sharing + * context that it's relevant to. + */ + auth = x11_invent_fake_auth(s->x11authtree, authtype); + auth->share_cs = share_cs; + auth->share_chan = share_chan; + + return auth; +} + +static void ssh2_remove_sharing_x11_display( + ConnectionLayer *cl, struct X11FakeAuth *auth) +{ + struct ssh2_connection_state *s = + FROMFIELD(cl, struct ssh2_connection_state, cl); + del234(s->x11authtree, auth); + x11_free_fake_auth(auth); +} + +static unsigned ssh2_alloc_sharing_channel( + ConnectionLayer *cl, ssh_sharing_connstate *connstate) +{ + struct ssh2_connection_state *s = + FROMFIELD(cl, struct ssh2_connection_state, cl); + struct ssh2_channel *c = snew(struct ssh2_channel); + + c->connlayer = s; + ssh2_channel_init(c); + c->chan = NULL; + c->sharectx = connstate; + return c->localid; +} + +static void ssh2_delete_sharing_channel(ConnectionLayer *cl, unsigned localid) +{ + struct ssh2_connection_state *s = + FROMFIELD(cl, struct ssh2_connection_state, cl); + struct ssh2_channel *c = find234(s->channels, &localid, ssh2_channelfind); + if (c) + ssh2_channel_destroy(c); +} + +static void ssh2_send_packet_from_downstream( + ConnectionLayer *cl, unsigned id, int type, + const void *data, int datalen, const char *additional_log_text) +{ + struct ssh2_connection_state *s = + FROMFIELD(cl, struct ssh2_connection_state, cl); + PktOut *pkt = ssh_bpp_new_pktout(s->ppl.bpp, type); + pkt->downstream_id = id; + pkt->additional_log_text = additional_log_text; + put_data(pkt, data, datalen); + pq_push(s->ppl.out_pq, pkt); +} + +static int ssh2_agent_forwarding_permitted(ConnectionLayer *cl) +{ + struct ssh2_connection_state *s = + FROMFIELD(cl, struct ssh2_connection_state, cl); + return conf_get_int(s->conf, CONF_agentfwd) && agent_exists(); +} + +static void mainchan_free(Channel *chan); +static void mainchan_open_confirmation(Channel *chan); +static void mainchan_open_failure(Channel *chan, const char *errtext); +static int mainchan_send(Channel *chan, int is_stderr, const void *, int); +static void mainchan_send_eof(Channel *chan); +static void mainchan_set_input_wanted(Channel *chan, int wanted); +static char *mainchan_log_close_msg(Channel *chan); + +static const struct ChannelVtable mainchan_channelvt = { + mainchan_free, + mainchan_open_confirmation, + mainchan_open_failure, + mainchan_send, + mainchan_send_eof, + mainchan_set_input_wanted, + mainchan_log_close_msg, + chan_no_eager_close, +}; + +static mainchan *mainchan_new(struct ssh2_connection_state *s) +{ + mainchan *mc = snew(mainchan); + mc->connlayer = s; + mc->sc = NULL; + mc->chan.vt = &mainchan_channelvt; + mc->chan.initial_fixed_window_size = 0; + return mc; +} + +static void mainchan_free(Channel *chan) +{ + assert(chan->vt == &mainchan_channelvt); + mainchan *mc = FROMFIELD(chan, mainchan, chan); + struct ssh2_connection_state *s = mc->connlayer; + s->mainchan = NULL; + sfree(mc); +} + +static void mainchan_open_confirmation(Channel *chan) +{ + mainchan *mc = FROMFIELD(chan, mainchan, chan); + struct ssh2_connection_state *s = mc->connlayer; + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + + update_specials_menu(s->ppl.frontend); + ppl_logevent(("Opened main channel")); +} + +static void mainchan_open_failure(Channel *chan, const char *errtext) +{ + assert(chan->vt == &mainchan_channelvt); + mainchan *mc = FROMFIELD(chan, mainchan, chan); + struct ssh2_connection_state *s = mc->connlayer; + + /* + * Record the failure reason we're given, and let the main + * coroutine handle closing the SSH session. + */ + s->mainchan_open_error = dupstr(errtext); + queue_idempotent_callback(&s->ppl.ic_process_queue); +} + +static int mainchan_send(Channel *chan, int is_stderr, + const void *data, int length) +{ + assert(chan->vt == &mainchan_channelvt); + mainchan *mc = FROMFIELD(chan, mainchan, chan); + struct ssh2_connection_state *s = mc->connlayer; + return from_backend(s->ppl.frontend, is_stderr, data, length); +} + +static void mainchan_send_eof(Channel *chan) +{ + assert(chan->vt == &mainchan_channelvt); + mainchan *mc = FROMFIELD(chan, mainchan, chan); + struct ssh2_connection_state *s = mc->connlayer; + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + + if (!s->mainchan_eof_sent && + (from_backend_eof(s->ppl.frontend) || s->got_pty)) { + /* + * Either from_backend_eof told us that the front end wants us + * to close the outgoing side of the connection as soon as we + * see EOF from the far end, or else we've unilaterally + * decided to do that because we've allocated a remote pty and + * hence EOF isn't a particularly meaningful concept. + */ + sshfwd_write_eof(mc->sc); + ppl_logevent(("Sent EOF message")); + } + s->mainchan_eof_sent = TRUE; + s->want_user_input = FALSE; /* now stop reading from stdin */ +} + +static void mainchan_set_input_wanted(Channel *chan, int wanted) +{ + assert(chan->vt == &mainchan_channelvt); + mainchan *mc = FROMFIELD(chan, mainchan, chan); + struct ssh2_connection_state *s = mc->connlayer; + + /* + * This is the main channel of the SSH session, i.e. the one tied + * to the standard input (or GUI) of the primary SSH client user + * interface. So ssh->send_ok is how we control whether we're + * reading from that input. + */ + s->want_user_input = wanted; +} + +static char *mainchan_log_close_msg(Channel *chan) +{ + return dupstr("Main session channel closed"); +} + +/* + * List of signal names defined by RFC 4254. These include all the ISO + * C signals, but are a subset of the POSIX required signals. + * + * The list macro takes parameters MAIN and SUB, which is an arbitrary + * UI decision to expose the signals we think users are most likely to + * want, with extra descriptive text, and relegate the less probable + * ones to a submenu for people who know what they're doing. + */ +#define SIGNAL_LIST(MAIN, SUB) \ + MAIN(INT, "Interrupt") \ + MAIN(TERM, "Terminate") \ + MAIN(KILL, "Kill") \ + MAIN(QUIT, "Quit") \ + MAIN(HUP, "Hangup") \ + SUB(ABRT) \ + SUB(ALRM) \ + SUB(FPE) \ + SUB(ILL) \ + SUB(PIPE) \ + SUB(SEGV) \ + SUB(USR1) \ + SUB(USR2) \ + /* end of list */ + +static int ssh2_connection_get_specials( + PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) +{ + struct ssh2_connection_state *s = + FROMFIELD(ppl, struct ssh2_connection_state, ppl); + int toret = FALSE; + + if (s->mainchan) { + add_special(ctx, "Break", SS_BRK, 0); + + #define ADD_MAIN(name, desc) \ + add_special(ctx, "SIG" #name " (" desc ")", SS_SIG ## name, 0); + #define ADD_SUB(name) \ + add_special(ctx, "SIG" #name, SS_SIG ## name, 0); + + #define NO_ADD_SUB(name) + #define NO_ADD_MAIN(name, desc) + + SIGNAL_LIST(ADD_MAIN, NO_ADD_SUB); + add_special(ctx, "More signals", SS_SUBMENU, 0); + SIGNAL_LIST(NO_ADD_MAIN, ADD_SUB); + add_special(ctx, NULL, SS_EXITMENU, 0); + + #undef ADD_MAIN + #undef ADD_SUB + #undef NO_ADD_MAIN + #undef NO_ADD_SUB + + toret = TRUE; + } + + /* + * Don't bother offering IGNORE if we've decided the remote + * won't cope with it, since we wouldn't bother sending it if + * asked anyway. + */ + if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) { + if (toret) + add_special(ctx, NULL, SS_SEP, 0); + + add_special(ctx, "IGNORE message", SS_NOP, 0); + toret = TRUE; + } + + return toret; +} + +static const char *ssh_signal_lookup(SessionSpecialCode code) +{ + #define CHECK_SUB(name) \ + if (code == SS_SIG ## name) return #name; + #define CHECK_MAIN(name, desc) CHECK_SUB(name) + + SIGNAL_LIST(CHECK_MAIN, CHECK_SUB); + return NULL; + + #undef CHECK_MAIN + #undef CHECK_SUB +} + +static void ssh2_connection_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg) +{ + struct ssh2_connection_state *s = + FROMFIELD(ppl, struct ssh2_connection_state, ppl); + PktOut *pktout; + const char *signame; + + if (code == SS_PING || code == SS_NOP) { + if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) { + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_IGNORE); + put_stringz(pktout, ""); + pq_push(s->ppl.out_pq, pktout); + } + } else if (code == SS_EOF) { + if (!s->mainchan_ready) { + /* + * Buffer the EOF to send as soon as the main channel is + * fully set up. + */ + s->mainchan_eof_pending = TRUE; + } else if (s->mainchan && !s->mainchan_eof_sent) { + sshfwd_write_eof(&s->mainchan->sc); + } + } else if (code == SS_BRK) { + if (s->mainchan) { + pktout = ssh2_chanreq_init(s->mainchan, "break", NULL, NULL); + put_uint32(pktout, 0); /* default break length */ + pq_push(s->ppl.out_pq, pktout); + } + } else if ((signame = ssh_signal_lookup(code)) != NULL) { + /* It's a signal. */ + if (s->mainchan) { + pktout = ssh2_chanreq_init(s->mainchan, "signal", NULL, NULL); + put_stringz(pktout, signame); + pq_push(s->ppl.out_pq, pktout); + ppl_logevent(("Sent signal SIG%s", signame)); + } + } +} + +static void ssh2_terminal_size(ConnectionLayer *cl, int width, int height) +{ + struct ssh2_connection_state *s = + FROMFIELD(cl, struct ssh2_connection_state, cl); + + s->term_width = width; + s->term_height = height; + + if (s->mainchan_ready) { + PktOut *pktout = ssh2_chanreq_init( + s->mainchan, "window-change", NULL, NULL); + put_uint32(pktout, s->term_width); + put_uint32(pktout, s->term_height); + put_uint32(pktout, 0); + put_uint32(pktout, 0); + pq_push(s->ppl.out_pq, pktout); + } +} + +static void ssh2_stdout_unthrottle(ConnectionLayer *cl, int bufsize) +{ + struct ssh2_connection_state *s = + FROMFIELD(cl, struct ssh2_connection_state, cl); + + if (s->mainchan) + ssh2channel_unthrottle(&s->mainchan->sc, bufsize); +} + +static int ssh2_stdin_backlog(ConnectionLayer *cl) +{ + struct ssh2_connection_state *s = + FROMFIELD(cl, struct ssh2_connection_state, cl); + + return s->mainchan ? bufchain_size(&s->mainchan->outbuffer) : 0; +} + +static void ssh2_throttle_all_channels(ConnectionLayer *cl, int throttled) +{ + struct ssh2_connection_state *s = + FROMFIELD(cl, struct ssh2_connection_state, cl); + struct ssh2_channel *c; + int i; + + s->all_channels_throttled = throttled; + + for (i = 0; NULL != (c = index234(s->channels, i)); i++) + ssh2_channel_check_throttle(c); +} + +static int ssh2_ldisc_option(ConnectionLayer *cl, int option) +{ + struct ssh2_connection_state *s = + FROMFIELD(cl, struct ssh2_connection_state, cl); + + /* We always return the same value for LD_ECHO and LD_EDIT */ + return s->echoedit; +} + +static int ssh2_connection_want_user_input(PacketProtocolLayer *ppl) +{ + struct ssh2_connection_state *s = + FROMFIELD(ppl, struct ssh2_connection_state, ppl); + return s->want_user_input; +} + +static void ssh2_connection_got_user_input(PacketProtocolLayer *ppl) +{ + struct ssh2_connection_state *s = + FROMFIELD(ppl, struct ssh2_connection_state, ppl); + + while (s->mainchan && bufchain_size(s->ppl.user_input) > 0) { + /* + * Add user input to the main channel's buffer. + */ + void *data; + int len; + bufchain_prefix(s->ppl.user_input, &data, &len); + sshfwd_write(&s->mainchan->sc, data, len); + bufchain_consume(s->ppl.user_input, len); + } +} + +static void ssh2_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf) +{ + struct ssh2_connection_state *s = + FROMFIELD(ppl, struct ssh2_connection_state, ppl); + + conf_free(s->conf); + s->conf = conf_copy(conf); + + if (s->portfwdmgr_configured) + portfwdmgr_config(s->portfwdmgr, s->conf); +} diff --git a/ssh2transport.c b/ssh2transport.c new file mode 100644 index 00000000..1dcd4a8c --- /dev/null +++ b/ssh2transport.c @@ -0,0 +1,2967 @@ +/* + * Packet protocol layer 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" + +#ifndef NO_GSSAPI +#include "sshgssc.h" +#include "sshgss.h" +#define MIN_CTXT_LIFETIME 5 /* Avoid rekey with short lifetime (seconds) */ +#define GSS_KEX_CAPABLE (1<<0) /* Can do GSS KEX */ +#define GSS_CRED_UPDATED (1<<1) /* Cred updated since previous delegation */ +#define GSS_CTXT_EXPIRES (1<<2) /* Context expires before next timer */ +#define GSS_CTXT_MAYFAIL (1<<3) /* Context may expire during handshake */ +#endif + +#define DH_MIN_SIZE 1024 +#define DH_MAX_SIZE 8192 + +enum kexlist { + KEXLIST_KEX, KEXLIST_HOSTKEY, KEXLIST_CSCIPHER, KEXLIST_SCCIPHER, + KEXLIST_CSMAC, KEXLIST_SCMAC, KEXLIST_CSCOMP, KEXLIST_SCCOMP, + NKEXLIST +}; +#define MAXKEXLIST 16 +struct kexinit_algorithm { + const char *name; + union { + struct { + const struct ssh_kex *kex; + int warn; + } kex; + struct { + const ssh_keyalg *hostkey; + int warn; + } hk; + struct { + const struct ssh2_cipheralg *cipher; + int warn; + } cipher; + struct { + const struct ssh2_macalg *mac; + int etm; + } mac; + const struct ssh_compression_alg *comp; + } u; +}; + +struct ssh_signkey_with_user_pref_id { + const ssh_keyalg *alg; + int id; +}; +const static struct ssh_signkey_with_user_pref_id hostkey_algs[] = { + { &ssh_ecdsa_ed25519, HK_ED25519 }, + { &ssh_ecdsa_nistp256, HK_ECDSA }, + { &ssh_ecdsa_nistp384, HK_ECDSA }, + { &ssh_ecdsa_nistp521, HK_ECDSA }, + { &ssh_dss, HK_DSA }, + { &ssh_rsa, HK_RSA }, +}; + +const static struct ssh2_macalg *const macs[] = { + &ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5 +}; +const static struct ssh2_macalg *const buggymacs[] = { + &ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5 +}; + +static ssh_compressor *ssh_comp_none_init(void) +{ + return NULL; +} +static void ssh_comp_none_cleanup(ssh_compressor *handle) +{ +} +static ssh_decompressor *ssh_decomp_none_init(void) +{ + return NULL; +} +static void ssh_decomp_none_cleanup(ssh_decompressor *handle) +{ +} +static void ssh_comp_none_block(ssh_compressor *handle, + unsigned char *block, int len, + unsigned char **outblock, int *outlen, + int minlen) +{ +} +static int ssh_decomp_none_block(ssh_decompressor *handle, + unsigned char *block, int len, + unsigned char **outblock, int *outlen) +{ + return 0; +} +const static struct ssh_compression_alg ssh_comp_none = { + "none", NULL, + ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block, + ssh_decomp_none_init, ssh_decomp_none_cleanup, ssh_decomp_none_block, + NULL +}; +const static struct ssh_compression_alg *const compressions[] = { + &ssh_zlib, &ssh_comp_none +}; + +/* + * Enumeration of high-level classes of reason why we might need to do + * a repeat key exchange. A full detailed reason in human-readable + * string form for the Event Log is also provided, but this enum type + * is used to discriminate between classes of reason that the code + * needs to treat differently. + * + * RK_NONE == 0 is the value indicating that no rekey is currently + * needed at all. RK_INITIAL indicates that we haven't even done the + * _first_ key exchange yet. RK_SERVER indicates that we're rekeying + * because the server asked for it, not because we decided it + * ourselves. RK_NORMAL is the usual case. RK_GSS_UPDATE indicates + * that we're rekeying because we've just got new GSSAPI credentials + * (hence there's no point in doing a preliminary check for new GSS + * creds, because we already know the answer); RK_POST_USERAUTH + * indicates that _if_ we're going to need a post-userauth immediate + * rekey for any reason, this is the moment to do it. + * + * So RK_POST_USERAUTH only tells the transport layer to _consider_ + * rekeying, not to definitely do it. Also, that one enum value is + * special in that the user-readable reason text is passed in to the + * transport layer as NULL, whereas fills in the reason text after it + * decides whether it needs a rekey at all. In the other cases, + * rekey_reason is passed in to the at the same time as rekey_class. + */ +typedef enum RekeyClass { + RK_NONE = 0, + RK_INITIAL, + RK_SERVER, + RK_NORMAL, + RK_POST_USERAUTH, + RK_GSS_UPDATE +} RekeyClass; + +struct ssh2_transport_state { + int crState; + + PacketProtocolLayer *higher_layer; + PktInQueue pq_in_higher; + PktOutQueue pq_out_higher; + IdempotentCallback ic_pq_out_higher; + + Conf *conf; + char *savedhost; + int savedport; + const char *rekey_reason; + enum RekeyClass rekey_class; + + unsigned long max_data_size; + + const struct ssh_kex *kex_alg; + const ssh_keyalg *hostkey_alg; + char *hostkey_str; /* string representation, for easy checking in rekeys */ + unsigned char session_id[SSH2_KEX_MAX_HASH_LEN]; + int session_id_len; + struct dh_ctx *dh_ctx; + ssh_hash *exhash; + + struct DataTransferStats *stats; + + char *client_greeting, *server_greeting; + + int kex_in_progress; + unsigned long next_rekey, last_rekey; + const char *deferred_rekey_reason; + int higher_layer_ok; + + /* + * Fully qualified host name, which we need if doing GSSAPI. + */ + char *fullhostname; + + /* shgss is outside the ifdef on purpose to keep APIs simple. If + * NO_GSSAPI is not defined, then it's just an opaque structure + * tag and the pointer will be NULL. */ + struct ssh_connection_shared_gss_state *shgss; +#ifndef NO_GSSAPI + int gss_status; + time_t gss_cred_expiry; /* Re-delegate if newer */ + unsigned long gss_ctxt_lifetime; /* Re-delegate when short */ + tree234 *transient_hostkey_cache; +#endif + + int gss_kex_used; + + int nbits, pbits, warn_kex, warn_hk, warn_cscipher, warn_sccipher; + Bignum p, g, e, f, K; + void *our_kexinit; + int our_kexinitlen; + int kex_init_value, kex_reply_value; + const struct ssh2_macalg *const *maclist; + int nmacs; + struct { + const struct ssh2_cipheralg *cipher; + const struct ssh2_macalg *mac; + int etm_mode; + const struct ssh_compression_alg *comp; + } in, out; + ptrlen hostkeydata, sigdata; + char *keystr, *fingerprint; + ssh_key *hkey; /* actual host key */ + struct RSAKey *rsa_kex_key; /* for RSA kex */ + struct ec_key *ecdh_key; /* for ECDH kex */ + unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN]; + int n_preferred_kex; + int can_gssapi_keyex; + int need_gss_transient_hostkey; + int warned_about_no_gss_transient_hostkey; + const struct ssh_kexes *preferred_kex[KEX_MAX + 1]; /* +1 for GSSAPI */ + int n_preferred_hk; + int preferred_hk[HK_MAX]; + int n_preferred_ciphers; + const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX]; + const struct ssh_compression_alg *preferred_comp; + int userauth_succeeded; /* for delayed compression */ + int pending_compression; + int got_session_id; + int dlgret; + int guessok; + int ignorepkt; + struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST]; +#ifndef NO_GSSAPI + Ssh_gss_buf gss_buf; + Ssh_gss_buf gss_rcvtok, gss_sndtok; + Ssh_gss_stat gss_stat; + Ssh_gss_buf mic; + int init_token_sent; + int complete_rcvd; + int gss_delegate; +#endif + + /* + * List of host key algorithms for which we _don't_ have a stored + * host key. These are indices into the main hostkey_algs[] array + */ + int uncert_hostkeys[lenof(hostkey_algs)]; + int n_uncert_hostkeys; + + /* + * Flag indicating that the current rekey is intended to finish + * with a newly cross-certified host key. + */ + int cross_certifying; + + PacketProtocolLayer ppl; +}; + +static void ssh2_transport_free(PacketProtocolLayer *); +static void ssh2_transport_process_queue(PacketProtocolLayer *); +static int ssh2_transport_get_specials( + PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx); +static void ssh2_transport_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg); +static int ssh2_transport_want_user_input(PacketProtocolLayer *ppl); +static void ssh2_transport_got_user_input(PacketProtocolLayer *ppl); +static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf); + +static void ssh2_transport_dialog_callback(void *, int); +static void ssh2_transport_set_max_data_size(struct ssh2_transport_state *s); +static unsigned long sanitise_rekey_time(int rekey_time, unsigned long def); +static void ssh2_transport_higher_layer_packet_callback(void *context); + +static const struct PacketProtocolLayerVtable ssh2_transport_vtable = { + ssh2_transport_free, + ssh2_transport_process_queue, + ssh2_transport_get_specials, + ssh2_transport_special_cmd, + ssh2_transport_want_user_input, + ssh2_transport_got_user_input, + ssh2_transport_reconfigure, + NULL, /* no protocol name for this layer */ +}; + +#ifndef NO_GSSAPI +static void ssh2_transport_gss_update(struct ssh2_transport_state *s, + int definitely_rekeying); +static void ssh_init_transient_hostkey_store(struct ssh2_transport_state *); +static void ssh_cleanup_transient_hostkey_store(struct ssh2_transport_state *); +static void ssh_store_transient_hostkey( + struct ssh2_transport_state *s, ssh_key *key); +static int ssh_verify_transient_hostkey( + struct ssh2_transport_state *s, ssh_key *key); +static int ssh_have_transient_hostkey( + struct ssh2_transport_state *s, const ssh_keyalg *alg); +static int ssh_have_any_transient_hostkey( + struct ssh2_transport_state *s); +#endif + +static int ssh2_transport_timer_update(struct ssh2_transport_state *s, + unsigned long rekey_time); + +static const char *const kexlist_descr[NKEXLIST] = { + "key exchange algorithm", + "host key algorithm", + "client-to-server cipher", + "server-to-client cipher", + "client-to-server MAC", + "server-to-client MAC", + "client-to-server compression method", + "server-to-client compression method" +}; + +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 ssh2_transport_state *s = snew(struct ssh2_transport_state); + memset(s, 0, sizeof(*s)); + s->ppl.vt = &ssh2_transport_vtable; + + s->conf = conf_copy(conf); + s->savedhost = dupstr(host); + s->savedport = port; + s->fullhostname = dupstr(fullhostname); + s->shgss = shgss; + s->client_greeting = dupstr(client_greeting); + s->server_greeting = dupstr(server_greeting); + s->stats = stats; + + pq_in_init(&s->pq_in_higher); + pq_out_init(&s->pq_out_higher); + s->pq_out_higher.pqb.ic = &s->ic_pq_out_higher; + s->ic_pq_out_higher.fn = ssh2_transport_higher_layer_packet_callback; + s->ic_pq_out_higher.ctx = &s->ppl; + + s->higher_layer = higher_layer; + s->higher_layer->selfptr = &s->higher_layer; + ssh_ppl_setup_queues(s->higher_layer, &s->pq_in_higher, &s->pq_out_higher); + +#ifndef NO_GSSAPI + s->gss_cred_expiry = GSS_NO_EXPIRATION; + s->shgss->srv_name = GSS_C_NO_NAME; + s->shgss->ctx = NULL; + ssh_init_transient_hostkey_store(s); +#endif + s->gss_kex_used = FALSE; + + ssh2_transport_set_max_data_size(s); + + return &s->ppl; +} + +static void ssh2_transport_free(PacketProtocolLayer *ppl) +{ + struct ssh2_transport_state *s = + FROMFIELD(ppl, struct ssh2_transport_state, ppl); + + /* + * As our last act before being freed, move any outgoing packets + * off our higher layer's output queue on to our own output queue. + * We might be being freed while the SSH connection is still alive + * (because we're initiating shutdown from our end), in which case + * we don't want those last few packets to get lost. + * + * (If our owner were to have already destroyed our output pq + * before wanting to free us, then it would have to reset our + * publicly visible out_pq field to NULL to inhibit this attempt. + * But that's not how I expect the shutdown sequence to go in + * practice.) + */ + if (s->ppl.out_pq) + pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher); + + conf_free(s->conf); + + ssh_ppl_free(s->higher_layer); + + pq_in_clear(&s->pq_in_higher); + pq_out_clear(&s->pq_out_higher); + + sfree(s->savedhost); + sfree(s->fullhostname); + sfree(s->client_greeting); + sfree(s->server_greeting); + sfree(s->keystr); + sfree(s->hostkey_str); + sfree(s->fingerprint); + if (s->hkey) { + ssh_key_free(s->hkey); + s->hkey = NULL; + } + if (s->e) freebn(s->e); + if (s->f) freebn(s->f); + if (s->p) freebn(s->p); + if (s->g) freebn(s->g); + if (s->K) freebn(s->K); + if (s->dh_ctx) + dh_cleanup(s->dh_ctx); + if (s->rsa_kex_key) + ssh_rsakex_freekey(s->rsa_kex_key); + if (s->ecdh_key) + ssh_ecdhkex_freekey(s->ecdh_key); + if (s->exhash) + ssh_hash_free(s->exhash); +#ifndef NO_GSSAPI + ssh_cleanup_transient_hostkey_store(s); +#endif + sfree(s); +} + +/* + * SSH-2 key derivation (RFC 4253 section 7.2). + */ +static void ssh2_mkkey( + struct ssh2_transport_state *s, strbuf *out, + Bignum K, unsigned char *H, char chr, int keylen) +{ + int hlen = s->kex_alg->hash->hlen; + int keylen_padded; + unsigned char *key; + ssh_hash *h; + + if (keylen == 0) + return; + + /* + * Round the requested amount of key material up to a multiple of + * the length of the hash we're using to make it. This makes life + * simpler because then we can just write each hash output block + * straight into the output buffer without fiddling about + * truncating the last one. Since it's going into a strbuf, and + * strbufs are always smemclr()ed on free, there's no need to + * worry about leaving extra potentially-sensitive data in memory + * that the caller didn't ask for. + */ + keylen_padded = ((keylen + hlen - 1) / hlen) * hlen; + + out->len = 0; + key = strbuf_append(out, keylen_padded); + + /* First hlen bytes. */ + h = ssh_hash_new(s->kex_alg->hash); + if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY)) + put_mp_ssh2(h, K); + put_data(h, H, hlen); + put_byte(h, chr); + put_data(h, s->session_id, s->session_id_len); + ssh_hash_final(h, key); + + /* Subsequent blocks of hlen bytes. */ + if (keylen_padded > hlen) { + int offset; + + h = ssh_hash_new(s->kex_alg->hash); + if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY)) + put_mp_ssh2(h, K); + put_data(h, H, hlen); + + for (offset = hlen; offset < keylen_padded; offset += hlen) { + put_data(h, key + offset - hlen, hlen); + ssh_hash *h2 = ssh_hash_copy(h); + ssh_hash_final(h2, key + offset); + } + + ssh_hash_free(h); + } +} + +/* + * Find a slot in a KEXINIT algorithm list to use for a new algorithm. + * If the algorithm is already in the list, return a pointer to its + * entry, otherwise return an entry from the end of the list. + * This assumes that every time a particular name is passed in, it + * comes from the same string constant. If this isn't true, this + * function may need to be rewritten to use strcmp() instead. + */ +static struct kexinit_algorithm *ssh2_kexinit_addalg(struct kexinit_algorithm + *list, const char *name) +{ + int i; + + for (i = 0; i < MAXKEXLIST; i++) + if (list[i].name == NULL || list[i].name == name) { + list[i].name = name; + return &list[i]; + } + assert(!"No space in KEXINIT list"); + return NULL; +} + +int ssh2_common_filter_queue(PacketProtocolLayer *ppl) +{ + static const char *const ssh2_disconnect_reasons[] = { + NULL, + "host not allowed to connect", + "protocol error", + "key exchange failed", + "host authentication failed", + "MAC error", + "compression error", + "service not available", + "protocol version not supported", + "host key not verifiable", + "connection lost", + "by application", + "too many connections", + "auth cancelled by user", + "no more auth methods available", + "illegal user name", + }; + + PktIn *pktin; + ptrlen msg; + int reason; + + while ((pktin = pq_peek(ppl->in_pq)) != NULL) { + switch (pktin->type) { + case SSH2_MSG_DISCONNECT: + reason = get_uint32(pktin); + msg = get_string(pktin); + + ssh_remote_error( + ppl->ssh, "Server sent disconnect message\n" + "type %d (%s):\n\"%.*s\"", reason, + ((reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ? + ssh2_disconnect_reasons[reason] : "unknown"), + PTRLEN_PRINTF(msg)); + return TRUE; /* indicate that we've been freed */ + + case SSH2_MSG_DEBUG: + /* XXX maybe we should actually take notice of the return value */ + get_bool(pktin); + msg = get_string(pktin); + ppl_logevent(("Remote debug message: %.*s", PTRLEN_PRINTF(msg))); + pq_pop(ppl->in_pq); + break; + + case SSH2_MSG_IGNORE: + /* Do nothing, because we're ignoring it! Duhh. */ + pq_pop(ppl->in_pq); + break; + + default: + return FALSE; + } + } + + return FALSE; +} + +static int ssh2_transport_filter_queue(struct ssh2_transport_state *s) +{ + PktIn *pktin; + + while (1) { + if (ssh2_common_filter_queue(&s->ppl)) + return TRUE; + if ((pktin = pq_peek(s->ppl.in_pq)) == NULL) + return FALSE; + + /* Pass on packets to the next layer if they're outside + * the range reserved for the transport protocol. */ + if (pktin->type >= 50) { + /* ... except that we shouldn't tolerate higher-layer + * packets coming from the server before we've seen + * the first NEWKEYS. */ + if (!s->higher_layer_ok) { + ssh_proto_error(s->ppl.ssh, "Received premature higher-" + "layer packet, type %d (%s)", pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return TRUE; + } + + pq_pop(s->ppl.in_pq); + pq_push(&s->pq_in_higher, pktin); + } else { + /* Anything else is a transport-layer packet that the main + * process_queue coroutine should handle. */ + return FALSE; + } + } +} + +static PktIn *ssh2_transport_pop(struct ssh2_transport_state *s) +{ + ssh2_transport_filter_queue(s); + return pq_pop(s->ppl.in_pq); +} + +static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) +{ + struct ssh2_transport_state *s = + FROMFIELD(ppl, struct ssh2_transport_state, ppl); + PktIn *pktin; + PktOut *pktout; + + /* 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. */ + ssh2_transport_filter_queue(s); + + crBegin(s->crState); + + s->in.cipher = s->out.cipher = NULL; + s->in.mac = s->out.mac = NULL; + s->in.comp = s->out.comp = NULL; + + s->got_session_id = FALSE; + s->userauth_succeeded = FALSE; + s->pending_compression = FALSE; + s->need_gss_transient_hostkey = FALSE; + s->warned_about_no_gss_transient_hostkey = FALSE; + + /* + * Be prepared to work around the buggy MAC problem. + */ + if (s->ppl.remote_bugs & BUG_SSH2_HMAC) + s->maclist = buggymacs, s->nmacs = lenof(buggymacs); + else + s->maclist = macs, s->nmacs = lenof(macs); + + begin_key_exchange: + +#ifndef NO_GSSAPI + if (s->need_gss_transient_hostkey) { + /* + * This flag indicates a special case in which we must not do + * GSS key exchange even if we could. (See comments below, + * where the flag was set on the previous key exchange.) + */ + s->can_gssapi_keyex = FALSE; + } else if (conf_get_int(s->conf, CONF_try_gssapi_kex)) { + /* + * We always check if we have GSS creds before we come up with + * the kex algorithm list, otherwise future rekeys will fail + * when creds expire. To make this so, this code section must + * follow the begin_key_exchange label above, otherwise this + * section would execute just once per-connection. + * + * Update GSS state unless the reason we're here is that a + * timer just checked the GSS state and decided that we should + * rekey to update delegated credentials. In that case, the + * state is "fresh". + */ + if (s->rekey_class != RK_GSS_UPDATE) + ssh2_transport_gss_update(s, TRUE); + + /* Do GSSAPI KEX when capable */ + s->can_gssapi_keyex = s->gss_status & GSS_KEX_CAPABLE; + + /* + * But not when failure is likely. [ GSS implementations may + * attempt (and fail) to use a ticket that is almost expired + * when retrieved from the ccache that actually expires by the + * time the server receives it. ] + * + * Note: The first time always try KEXGSS if we can, failures + * will be very rare, and disabling the initial GSS KEX is + * worse. Some day GSS libraries will ignore cached tickets + * whose lifetime is critically short, and will instead use + * fresh ones. + */ + if (!s->got_session_id && (s->gss_status & GSS_CTXT_MAYFAIL) != 0) + s->can_gssapi_keyex = 0; + s->gss_delegate = conf_get_int(s->conf, CONF_gssapifwd); + } else { + s->can_gssapi_keyex = FALSE; + } +#endif + + s->ppl.bpp->pls->kctx = SSH2_PKTCTX_NOKEX; + { + int i, j, k, warn; + struct kexinit_algorithm *alg; + + /* + * Set up the preferred key exchange. (NULL => warn below here) + */ + s->n_preferred_kex = 0; + if (s->can_gssapi_keyex) + s->preferred_kex[s->n_preferred_kex++] = &ssh_gssk5_sha1_kex; + for (i = 0; i < KEX_MAX; i++) { + switch (conf_get_int_int(s->conf, CONF_ssh_kexlist, i)) { + case KEX_DHGEX: + s->preferred_kex[s->n_preferred_kex++] = + &ssh_diffiehellman_gex; + break; + case KEX_DHGROUP14: + s->preferred_kex[s->n_preferred_kex++] = + &ssh_diffiehellman_group14; + break; + case KEX_DHGROUP1: + s->preferred_kex[s->n_preferred_kex++] = + &ssh_diffiehellman_group1; + break; + case KEX_RSA: + s->preferred_kex[s->n_preferred_kex++] = + &ssh_rsa_kex; + break; + case KEX_ECDH: + s->preferred_kex[s->n_preferred_kex++] = + &ssh_ecdh_kex; + break; + case KEX_WARN: + /* Flag for later. Don't bother if it's the last in + * the list. */ + if (i < KEX_MAX - 1) { + s->preferred_kex[s->n_preferred_kex++] = NULL; + } + break; + } + } + + /* + * Set up the preferred host key types. These are just the ids + * in the enum in putty.h, so 'warn below here' is indicated + * by HK_WARN. + */ + s->n_preferred_hk = 0; + for (i = 0; i < HK_MAX; i++) { + int id = conf_get_int_int(s->conf, CONF_ssh_hklist, i); + /* As above, don't bother with HK_WARN if it's last in the + * list */ + if (id != HK_WARN || i < HK_MAX - 1) + s->preferred_hk[s->n_preferred_hk++] = id; + } + + /* + * Set up the preferred ciphers. (NULL => warn below here) + */ + s->n_preferred_ciphers = 0; + for (i = 0; i < CIPHER_MAX; i++) { + switch (conf_get_int_int(s->conf, CONF_ssh_cipherlist, i)) { + case CIPHER_BLOWFISH: + s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_blowfish; + break; + case CIPHER_DES: + if (conf_get_int(s->conf, CONF_ssh2_des_cbc)) + s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_des; + break; + case CIPHER_3DES: + s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_3des; + break; + case CIPHER_AES: + s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_aes; + break; + case CIPHER_ARCFOUR: + s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_arcfour; + break; + case CIPHER_CHACHA20: + s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_ccp; + break; + case CIPHER_WARN: + /* Flag for later. Don't bother if it's the last in + * the list. */ + if (i < CIPHER_MAX - 1) { + s->preferred_ciphers[s->n_preferred_ciphers++] = NULL; + } + break; + } + } + + /* + * Set up preferred compression. + */ + if (conf_get_int(s->conf, CONF_compression)) + s->preferred_comp = &ssh_zlib; + else + s->preferred_comp = &ssh_comp_none; + + /* + * Flag that KEX is in progress. + */ + s->kex_in_progress = TRUE; + + for (i = 0; i < NKEXLIST; i++) + for (j = 0; j < MAXKEXLIST; j++) + s->kexlists[i][j].name = NULL; + /* List key exchange algorithms. */ + warn = FALSE; + for (i = 0; i < s->n_preferred_kex; i++) { + const struct ssh_kexes *k = s->preferred_kex[i]; + if (!k) warn = TRUE; + else for (j = 0; j < k->nkexes; j++) { + alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_KEX], + k->list[j]->name); + alg->u.kex.kex = k->list[j]; + alg->u.kex.warn = warn; + } + } + /* List server host key algorithms. */ + if (!s->got_session_id) { + /* + * In the first key exchange, we list all the algorithms + * we're prepared to cope with, but prefer those algorithms + * for which we have a host key for this host. + * + * If the host key algorithm is below the warning + * threshold, we warn even if we did already have a key + * for it, on the basis that if the user has just + * reconfigured that host key type to be warned about, + * they surely _do_ want to be alerted that a server + * they're actually connecting to is using it. + */ + warn = FALSE; + for (i = 0; i < s->n_preferred_hk; i++) { + if (s->preferred_hk[i] == HK_WARN) + warn = TRUE; + for (j = 0; j < lenof(hostkey_algs); j++) { + if (hostkey_algs[j].id != s->preferred_hk[i]) + continue; + if (have_ssh_host_key(s->savedhost, s->savedport, + hostkey_algs[j].alg->cache_id)) { + alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY], + hostkey_algs[j].alg->ssh_id); + alg->u.hk.hostkey = hostkey_algs[j].alg; + alg->u.hk.warn = warn; + } + } + } + warn = FALSE; + for (i = 0; i < s->n_preferred_hk; i++) { + if (s->preferred_hk[i] == HK_WARN) + warn = TRUE; + for (j = 0; j < lenof(hostkey_algs); j++) { + if (hostkey_algs[j].id != s->preferred_hk[i]) + continue; + alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY], + hostkey_algs[j].alg->ssh_id); + alg->u.hk.hostkey = hostkey_algs[j].alg; + alg->u.hk.warn = warn; + } + } +#ifndef NO_GSSAPI + } else if (s->gss_kex_used && !s->need_gss_transient_hostkey) { + /* + * If we've previously done a GSSAPI KEX, then we list + * precisely the algorithms for which a previous GSS key + * exchange has delivered us a host key, because we expect + * one of exactly those keys to be used in any subsequent + * non-GSS-based rekey. + * + * An exception is if this is the key exchange we + * triggered for the purposes of populating that cache - + * in which case the cache will currently be empty, which + * isn't helpful! + */ + warn = FALSE; + for (i = 0; i < s->n_preferred_hk; i++) { + if (s->preferred_hk[i] == HK_WARN) + warn = TRUE; + for (j = 0; j < lenof(hostkey_algs); j++) { + if (hostkey_algs[j].id != s->preferred_hk[i]) + continue; + if (ssh_have_transient_hostkey(s, hostkey_algs[j].alg)) { + alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY], + hostkey_algs[j].alg->ssh_id); + alg->u.hk.hostkey = hostkey_algs[j].alg; + alg->u.hk.warn = warn; + } + } + } +#endif + } else { + /* + * In subsequent key exchanges, we list only the kex + * algorithm that was selected in the first key exchange, + * so that we keep getting the same host key and hence + * don't have to interrupt the user's session to ask for + * reverification. + */ + assert(s->kex_alg); + alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY], + s->hostkey_alg->ssh_id); + alg->u.hk.hostkey = s->hostkey_alg; + alg->u.hk.warn = FALSE; + } + if (s->can_gssapi_keyex) { + alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY], "null"); + alg->u.hk.hostkey = NULL; + } + /* List encryption algorithms (client->server then server->client). */ + for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) { + warn = FALSE; +#ifdef FUZZING + alg = ssh2_kexinit_addalg(s->kexlists[k], "none"); + alg->u.cipher.cipher = NULL; + alg->u.cipher.warn = warn; +#endif /* FUZZING */ + for (i = 0; i < s->n_preferred_ciphers; i++) { + const struct ssh2_ciphers *c = s->preferred_ciphers[i]; + if (!c) warn = TRUE; + else for (j = 0; j < c->nciphers; j++) { + alg = ssh2_kexinit_addalg(s->kexlists[k], + c->list[j]->name); + alg->u.cipher.cipher = c->list[j]; + alg->u.cipher.warn = warn; + } + } + } + /* List MAC algorithms (client->server then server->client). */ + for (j = KEXLIST_CSMAC; j <= KEXLIST_SCMAC; j++) { +#ifdef FUZZING + alg = ssh2_kexinit_addalg(s->kexlists[j], "none"); + alg->u.mac.mac = NULL; + alg->u.mac.etm = FALSE; +#endif /* FUZZING */ + for (i = 0; i < s->nmacs; i++) { + alg = ssh2_kexinit_addalg(s->kexlists[j], s->maclist[i]->name); + alg->u.mac.mac = s->maclist[i]; + alg->u.mac.etm = FALSE; + } + for (i = 0; i < s->nmacs; i++) + /* For each MAC, there may also be an ETM version, + * which we list second. */ + if (s->maclist[i]->etm_name) { + alg = ssh2_kexinit_addalg(s->kexlists[j], + s->maclist[i]->etm_name); + alg->u.mac.mac = s->maclist[i]; + alg->u.mac.etm = TRUE; + } + } + /* List client->server compression algorithms, + * then server->client compression algorithms. (We use the + * same set twice.) */ + for (j = KEXLIST_CSCOMP; j <= KEXLIST_SCCOMP; j++) { + assert(lenof(compressions) > 1); + /* Prefer non-delayed versions */ + alg = ssh2_kexinit_addalg(s->kexlists[j], s->preferred_comp->name); + alg->u.comp = s->preferred_comp; + /* We don't even list delayed versions of algorithms until + * they're allowed to be used, to avoid a race. See the end of + * this function. */ + if (s->userauth_succeeded && s->preferred_comp->delayed_name) { + alg = ssh2_kexinit_addalg(s->kexlists[j], + s->preferred_comp->delayed_name); + alg->u.comp = s->preferred_comp; + } + for (i = 0; i < lenof(compressions); i++) { + const struct ssh_compression_alg *c = compressions[i]; + alg = ssh2_kexinit_addalg(s->kexlists[j], c->name); + alg->u.comp = c; + if (s->userauth_succeeded && c->delayed_name) { + alg = ssh2_kexinit_addalg(s->kexlists[j], c->delayed_name); + alg->u.comp = c; + } + } + } + /* + * Construct and send our key exchange packet. + */ + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT); + for (i = 0; i < 16; i++) + put_byte(pktout, (unsigned char) random_byte()); + for (i = 0; i < NKEXLIST; i++) { + strbuf *list = strbuf_new(); + for (j = 0; j < MAXKEXLIST; j++) { + if (s->kexlists[i][j].name == NULL) break; + add_to_commasep(list, s->kexlists[i][j].name); + } + put_stringsb(pktout, list); + } + /* List client->server languages. Empty list. */ + put_stringz(pktout, ""); + /* List server->client languages. Empty list. */ + put_stringz(pktout, ""); + /* First KEX packet does _not_ follow, because we're not that brave. */ + put_bool(pktout, FALSE); + /* Reserved. */ + put_uint32(pktout, 0); + } + + s->our_kexinitlen = pktout->length - 5; + s->our_kexinit = snewn(s->our_kexinitlen, unsigned char); + memcpy(s->our_kexinit, pktout->data + 5, s->our_kexinitlen); + + pq_push(s->ppl.out_pq, pktout); + + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + + /* + * Now examine the other side's KEXINIT to see what we're up + * to. + */ + { + ptrlen str; + int i, j; + + if (pktin->type != SSH2_MSG_KEXINIT) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting KEXINIT, type %d (%s)", pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, pktin->type)); + return; + } + s->kex_alg = NULL; + s->hostkey_alg = NULL; + s->in.cipher = s->out.cipher = NULL; + s->in.mac = s->out.mac = NULL; + s->in.comp = s->out.comp = NULL; + s->warn_kex = s->warn_hk = FALSE; + s->warn_cscipher = s->warn_sccipher = FALSE; + + get_data(pktin, 16); /* skip garbage cookie */ + + s->guessok = FALSE; + for (i = 0; i < NKEXLIST; i++) { + str = get_string(pktin); + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, "KEXINIT packet was incomplete"); + return; + } + + /* If we've already selected a cipher which requires a + * particular MAC, then just select that, and don't even + * bother looking through the server's KEXINIT string for + * MACs. */ + if (i == KEXLIST_CSMAC && s->out.cipher && + s->out.cipher->required_mac) { + s->out.mac = s->out.cipher->required_mac; + s->out.etm_mode = !!(s->out.mac->etm_name); + goto matched; + } + if (i == KEXLIST_SCMAC && s->in.cipher && + s->in.cipher->required_mac) { + s->in.mac = s->in.cipher->required_mac; + s->in.etm_mode = !!(s->in.mac->etm_name); + goto matched; + } + + for (j = 0; j < MAXKEXLIST; j++) { + struct kexinit_algorithm *alg = &s->kexlists[i][j]; + if (alg->name == NULL) break; + if (in_commasep_string(alg->name, str.ptr, str.len)) { + /* We've found a matching algorithm. */ + if (i == KEXLIST_KEX || i == KEXLIST_HOSTKEY) { + /* Check if we might need to ignore first kex pkt */ + if (j != 0 || + !first_in_commasep_string(alg->name, + str.ptr, str.len)) + s->guessok = FALSE; + } + if (i == KEXLIST_KEX) { + s->kex_alg = alg->u.kex.kex; + s->warn_kex = alg->u.kex.warn; + } else if (i == KEXLIST_HOSTKEY) { + /* + * Ignore an unexpected/inappropriate offer of "null", + * we offer "null" when we're willing to use GSS KEX, + * but it is only acceptable when GSSKEX is actually + * selected. + */ + if (alg->u.hk.hostkey == NULL && + s->kex_alg->main_type != KEXTYPE_GSS) + continue; + s->hostkey_alg = alg->u.hk.hostkey; + s->warn_hk = alg->u.hk.warn; + } else if (i == KEXLIST_CSCIPHER) { + s->out.cipher = alg->u.cipher.cipher; + s->warn_cscipher = alg->u.cipher.warn; + } else if (i == KEXLIST_SCCIPHER) { + s->in.cipher = alg->u.cipher.cipher; + s->warn_sccipher = alg->u.cipher.warn; + } else if (i == KEXLIST_CSMAC) { + s->out.mac = alg->u.mac.mac; + s->out.etm_mode = alg->u.mac.etm; + } else if (i == KEXLIST_SCMAC) { + s->in.mac = alg->u.mac.mac; + s->in.etm_mode = alg->u.mac.etm; + } else if (i == KEXLIST_CSCOMP) { + s->out.comp = alg->u.comp; + } else if (i == KEXLIST_SCCOMP) { + s->in.comp = alg->u.comp; + } + goto matched; + } + + /* Set a flag if there's a delayed compression option + * available for a compression method that we just + * failed to select the immediate version of. */ + s->pending_compression = ( + (i == KEXLIST_CSCOMP || i == KEXLIST_SCCOMP) && + in_commasep_string(alg->u.comp->delayed_name, + str.ptr, str.len) && + !s->userauth_succeeded); + } + ssh_sw_abort(s->ppl.ssh, "Couldn't agree a %s (available: %.*s)", + kexlist_descr[i], PTRLEN_PRINTF(str)); + return; + matched:; + + if (i == KEXLIST_HOSTKEY && + !s->gss_kex_used && + s->kex_alg->main_type != KEXTYPE_GSS) { + int j; + + /* + * In addition to deciding which host key we're + * actually going to use, we should make a list of the + * host keys offered by the server which we _don't_ + * have cached. These will be offered as cross- + * certification options by ssh_get_specials. + * + * We also count the key we're currently using for KEX + * as one we've already got, because by the time this + * menu becomes visible, it will be. + */ + s->n_uncert_hostkeys = 0; + + for (j = 0; j < lenof(hostkey_algs); j++) { + if (hostkey_algs[j].alg != s->hostkey_alg && + in_commasep_string(hostkey_algs[j].alg->ssh_id, + str.ptr, str.len) && + !have_ssh_host_key(s->savedhost, s->savedport, + hostkey_algs[j].alg->cache_id)) { + s->uncert_hostkeys[s->n_uncert_hostkeys++] = j; + } + } + } + } + + if (s->pending_compression) { + ppl_logevent(("Server supports delayed compression; " + "will try this later")); + } + get_string(pktin); /* client->server language */ + get_string(pktin); /* server->client language */ + s->ignorepkt = get_bool(pktin) && !s->guessok; + + s->exhash = ssh_hash_new(s->kex_alg->hash); + put_stringz(s->exhash, s->client_greeting); + put_stringz(s->exhash, s->server_greeting); + put_string(s->exhash, s->our_kexinit, s->our_kexinitlen); + sfree(s->our_kexinit); + /* Include the type byte in the hash of server's KEXINIT */ + put_string(s->exhash, + (const char *)BinarySource_UPCAST(pktin)->data - 1, + BinarySource_UPCAST(pktin)->len + 1); + + if (s->warn_kex) { + s->dlgret = askalg(s->ppl.frontend, "key-exchange algorithm", + s->kex_alg->name, + ssh2_transport_dialog_callback, s); + crMaybeWaitUntilV(s->dlgret >= 0); + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, "User aborted at kex warning"); + return; + } + } + + if (s->warn_hk) { + int j, k; + char *betteralgs; + + /* + * Change warning box wording depending on why we chose a + * warning-level host key algorithm. If it's because + * that's all we have *cached*, use the askhk mechanism, + * and list the host keys we could usefully cross-certify. + * Otherwise, use askalg for the standard wording. + */ + betteralgs = NULL; + for (j = 0; j < s->n_uncert_hostkeys; j++) { + const struct ssh_signkey_with_user_pref_id *hktype = + &hostkey_algs[s->uncert_hostkeys[j]]; + int better = FALSE; + for (k = 0; k < HK_MAX; k++) { + int id = conf_get_int_int(s->conf, CONF_ssh_hklist, k); + if (id == HK_WARN) { + break; + } else if (id == hktype->id) { + better = TRUE; + break; + } + } + if (better) { + if (betteralgs) { + char *old_ba = betteralgs; + betteralgs = dupcat(betteralgs, ",", + hktype->alg->ssh_id, + (const char *)NULL); + sfree(old_ba); + } else { + betteralgs = dupstr(hktype->alg->ssh_id); + } + } + } + if (betteralgs) { + s->dlgret = askhk( + s->ppl.frontend, s->hostkey_alg->ssh_id, betteralgs, + ssh2_transport_dialog_callback, s); + sfree(betteralgs); + } else { + s->dlgret = askalg(s->ppl.frontend, "host key type", + s->hostkey_alg->ssh_id, + ssh2_transport_dialog_callback, s); + } + crMaybeWaitUntilV(s->dlgret >= 0); + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, "User aborted at host key warning"); + return; + } + } + + if (s->warn_cscipher) { + s->dlgret = askalg(s->ppl.frontend, + "client-to-server cipher", + s->out.cipher->name, + ssh2_transport_dialog_callback, s); + crMaybeWaitUntilV(s->dlgret >= 0); + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, "User aborted at cipher warning"); + return; + } + } + + if (s->warn_sccipher) { + s->dlgret = askalg(s->ppl.frontend, + "server-to-client cipher", + s->in.cipher->name, + ssh2_transport_dialog_callback, s); + crMaybeWaitUntilV(s->dlgret >= 0); + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, "User aborted at cipher warning"); + return; + } + } + + if (s->ignorepkt) /* first_kex_packet_follows */ + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + } + + if (s->kex_alg->main_type == KEXTYPE_DH) { + /* + * Work out the number of bits of key we will need from the + * key exchange. We start with the maximum key length of + * either cipher... + */ + { + int csbits, scbits; + + csbits = s->out.cipher ? s->out.cipher->real_keybits : 0; + scbits = s->in.cipher ? s->in.cipher->real_keybits : 0; + s->nbits = (csbits > scbits ? csbits : scbits); + } + /* The keys only have hlen-bit entropy, since they're based on + * a hash. So cap the key size at hlen bits. */ + if (s->nbits > s->kex_alg->hash->hlen * 8) + s->nbits = s->kex_alg->hash->hlen * 8; + + /* + * If we're doing Diffie-Hellman group exchange, start by + * requesting a group. + */ + if (dh_is_gex(s->kex_alg)) { + ppl_logevent(("Doing Diffie-Hellman group exchange")); + s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGEX; + /* + * Work out how big a DH group we will need to allow that + * much data. + */ + s->pbits = 512 << ((s->nbits - 1) / 64); + if (s->pbits < DH_MIN_SIZE) + s->pbits = DH_MIN_SIZE; + if (s->pbits > DH_MAX_SIZE) + s->pbits = DH_MAX_SIZE; + if ((s->ppl.remote_bugs & BUG_SSH2_OLDGEX)) { + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_REQUEST_OLD); + put_uint32(pktout, s->pbits); + } else { + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_REQUEST); + put_uint32(pktout, DH_MIN_SIZE); + put_uint32(pktout, s->pbits); + put_uint32(pktout, DH_MAX_SIZE); + } + pq_push(s->ppl.out_pq, pktout); + + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_KEX_DH_GEX_GROUP) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting Diffie-Hellman group, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return; + } + s->p = get_mp_ssh2(pktin); + s->g = get_mp_ssh2(pktin); + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, + "Unable to parse Diffie-Hellman group packet"); + return; + } + 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; + } 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)); + /* + * Now generate and send e for Diffie-Hellman. + */ + set_busy_status(s->ppl.frontend, BUSY_CPU); /* this can take a while */ + s->e = dh_create_e(s->dh_ctx, s->nbits * 2); + pktout = ssh_bpp_new_pktout(s->ppl.bpp, s->kex_init_value); + put_mp_ssh2(pktout, s->e); + pq_push(s->ppl.out_pq, pktout); + + set_busy_status(s->ppl.frontend, BUSY_WAITING); /* wait for server */ + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != s->kex_reply_value) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting Diffie-Hellman reply, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return; + } + set_busy_status(s->ppl.frontend, BUSY_CPU); /* cogitate */ + s->hostkeydata = get_string(pktin); + s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata); + s->f = get_mp_ssh2(pktin); + s->sigdata = get_string(pktin); + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, + "Unable to parse Diffie-Hellman reply packet"); + return; + } + + { + const char *err = dh_validate_f(s->dh_ctx, s->f); + if (err) { + ssh_proto_error(s->ppl.ssh, "Diffie-Hellman reply failed " + "validation: %s", err); + return; + } + } + s->K = dh_find_K(s->dh_ctx, s->f); + + /* We assume everything from now on will be quick, and it might + * involve user interaction. */ + set_busy_status(s->ppl.frontend, BUSY_NOT); + + put_stringpl(s->exhash, s->hostkeydata); + if (dh_is_gex(s->kex_alg)) { + if (!(s->ppl.remote_bugs & BUG_SSH2_OLDGEX)) + put_uint32(s->exhash, DH_MIN_SIZE); + put_uint32(s->exhash, s->pbits); + if (!(s->ppl.remote_bugs & BUG_SSH2_OLDGEX)) + put_uint32(s->exhash, DH_MAX_SIZE); + put_mp_ssh2(s->exhash, s->p); + put_mp_ssh2(s->exhash, s->g); + } + put_mp_ssh2(s->exhash, s->e); + put_mp_ssh2(s->exhash, s->f); + + dh_cleanup(s->dh_ctx); + s->dh_ctx = NULL; + freebn(s->f); s->f = NULL; + freebn(s->e); s->e = 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; + } + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_ECDH_INIT); + { + strbuf *pubpoint = strbuf_new(); + ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); + put_stringsb(pktout, pubpoint); + } + + pq_push(s->ppl.out_pq, pktout); + + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_KEX_ECDH_REPLY) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting ECDH reply, type %d (%s)", pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return; + } + + s->hostkeydata = get_string(pktin); + put_stringpl(s->exhash, s->hostkeydata); + s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata); + + { + strbuf *pubpoint = strbuf_new(); + ssh_ecdhkex_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); + put_string(s->exhash, pubpoint->u, pubpoint->len); + strbuf_free(pubpoint); + } + + { + 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 reply"); + return; + } + } + + s->sigdata = get_string(pktin); + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, "Unable to parse ECDH reply packet"); + return; + } + + ssh_ecdhkex_freekey(s->ecdh_key); + s->ecdh_key = NULL; +#ifndef NO_GSSAPI + } else if (s->kex_alg->main_type == KEXTYPE_GSS) { + ptrlen data; + + s->ppl.bpp->pls->kctx = SSH2_PKTCTX_GSSKEX; + s->init_token_sent = 0; + s->complete_rcvd = 0; + s->hkey = NULL; + s->fingerprint = NULL; + s->keystr = NULL; + + /* + * Work out the number of bits of key we will need from the + * key exchange. We start with the maximum key length of + * either cipher... + * + * This is rote from the KEXTYPE_DH section above. + */ + { + int csbits, scbits; + + csbits = s->out.cipher->real_keybits; + scbits = s->in.cipher->real_keybits; + s->nbits = (csbits > scbits ? csbits : scbits); + } + /* The keys only have hlen-bit entropy, since they're based on + * a hash. So cap the key size at hlen bits. */ + if (s->nbits > s->kex_alg->hash->hlen * 8) + s->nbits = s->kex_alg->hash->hlen * 8; + + if (dh_is_gex(s->kex_alg)) { + /* + * Work out how big a DH group we will need to allow that + * much data. + */ + s->pbits = 512 << ((s->nbits - 1) / 64); + ppl_logevent(("Doing GSSAPI (with Kerberos V5) Diffie-Hellman " + "group exchange, with minimum %d bits", s->pbits)); + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXGSS_GROUPREQ); + put_uint32(pktout, s->pbits); /* min */ + put_uint32(pktout, s->pbits); /* preferred */ + put_uint32(pktout, s->pbits * 2); /* max */ + pq_push(s->ppl.out_pq, pktout); + + crMaybeWaitUntilV( + (pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_KEXGSS_GROUP) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting Diffie-Hellman group, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return; + } + s->p = get_mp_ssh2(pktin); + s->g = get_mp_ssh2(pktin); + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, + "Unable to parse Diffie-Hellman group packet"); + return; + } + s->dh_ctx = dh_setup_gex(s->p, s->g); + } else { + s->dh_ctx = dh_setup_group(s->kex_alg); + ppl_logevent(("Using GSSAPI (with Kerberos V5) Diffie-Hellman with" + " standard group \"%s\"", s->kex_alg->groupname)); + } + + ppl_logevent(("Doing GSSAPI (with Kerberos V5) Diffie-Hellman key " + "exchange with hash %s", s->kex_alg->hash->text_name)); + /* Now generate e for Diffie-Hellman. */ + set_busy_status(s->ppl.frontend, BUSY_CPU); /* this can take a while */ + s->e = dh_create_e(s->dh_ctx, s->nbits * 2); + + if (s->shgss->lib->gsslogmsg) + ppl_logevent(("%s", s->shgss->lib->gsslogmsg)); + + /* initial tokens are empty */ + SSH_GSS_CLEAR_BUF(&s->gss_rcvtok); + SSH_GSS_CLEAR_BUF(&s->gss_sndtok); + SSH_GSS_CLEAR_BUF(&s->mic); + s->gss_stat = s->shgss->lib->acquire_cred( + s->shgss->lib, &s->shgss->ctx, &s->gss_cred_expiry); + if (s->gss_stat != SSH_GSS_OK) { + ssh_sw_abort(s->ppl.ssh, + "GSSAPI key exchange failed to initialise"); + return; + } + + /* now enter the loop */ + assert(s->shgss->srv_name); + do { + /* + * When acquire_cred yields no useful expiration, go with the + * service ticket expiration. + */ + s->gss_stat = s->shgss->lib->init_sec_context( + s->shgss->lib, &s->shgss->ctx, s->shgss->srv_name, + s->gss_delegate, &s->gss_rcvtok, &s->gss_sndtok, + (s->gss_cred_expiry == GSS_NO_EXPIRATION ? + &s->gss_cred_expiry : NULL), NULL); + SSH_GSS_CLEAR_BUF(&s->gss_rcvtok); + + if (s->gss_stat == SSH_GSS_S_COMPLETE && s->complete_rcvd) + break; /* MIC is verified after the loop */ + + if (s->gss_stat != SSH_GSS_S_COMPLETE && + s->gss_stat != SSH_GSS_S_CONTINUE_NEEDED) { + if (s->shgss->lib->display_status( + s->shgss->lib, s->shgss->ctx, + &s->gss_buf) == SSH_GSS_OK) { + char *err = s->gss_buf.value; + ssh_sw_abort(s->ppl.ssh, + "GSSAPI key exchange failed to initialise " + "context: %s", err); + sfree(err); + return; + } + } + assert(s->gss_stat == SSH_GSS_S_COMPLETE || + s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED); + + if (!s->init_token_sent) { + s->init_token_sent = 1; + pktout = ssh_bpp_new_pktout(s->ppl.bpp, + SSH2_MSG_KEXGSS_INIT); + if (s->gss_sndtok.length == 0) { + ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange failed: " + "no initial context token"); + return; + } + put_string(pktout, + s->gss_sndtok.value, s->gss_sndtok.length); + put_mp_ssh2(pktout, s->e); + pq_push(s->ppl.out_pq, pktout); + s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok); + ppl_logevent(("GSSAPI key exchange initialised")); + } else if (s->gss_sndtok.length != 0) { + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_KEXGSS_CONTINUE); + put_string(pktout, + s->gss_sndtok.value, s->gss_sndtok.length); + pq_push(s->ppl.out_pq, pktout); + s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok); + } + + if (s->gss_stat == SSH_GSS_S_COMPLETE && s->complete_rcvd) + break; + + wait_for_gss_token: + crMaybeWaitUntilV( + (pktin = ssh2_transport_pop(s)) != NULL); + switch (pktin->type) { + case SSH2_MSG_KEXGSS_CONTINUE: + data = get_string(pktin); + s->gss_rcvtok.value = (char *)data.ptr; + s->gss_rcvtok.length = data.len; + continue; + case SSH2_MSG_KEXGSS_COMPLETE: + s->complete_rcvd = 1; + s->f = get_mp_ssh2(pktin); + data = get_string(pktin); + s->mic.value = (char *)data.ptr; + s->mic.length = data.len; + /* Save expiration time of cred when delegating */ + if (s->gss_delegate && s->gss_cred_expiry != GSS_NO_EXPIRATION) + s->gss_cred_expiry = s->gss_cred_expiry; + /* If there's a final token we loop to consume it */ + if (get_bool(pktin)) { + data = get_string(pktin); + s->gss_rcvtok.value = (char *)data.ptr; + s->gss_rcvtok.length = data.len; + continue; + } + break; + case SSH2_MSG_KEXGSS_HOSTKEY: + s->hostkeydata = get_string(pktin); + if (s->hostkey_alg) { + s->hkey = ssh_key_new_pub(s->hostkey_alg, + s->hostkeydata); + put_string(s->exhash, + s->hostkeydata.ptr, s->hostkeydata.len); + } + /* + * Can't loop as we have no token to pass to + * init_sec_context. + */ + goto wait_for_gss_token; + case SSH2_MSG_KEXGSS_ERROR: + /* + * We have no use for the server's major and minor + * status. The minor status is really only + * meaningful to the server, and with luck the major + * status means something to us (but not really all + * that much). The string is more meaningful, and + * hopefully the server sends any error tokens, as + * that will produce the most useful information for + * us. + */ + get_uint32(pktin); /* server's major status */ + get_uint32(pktin); /* server's minor status */ + data = get_string(pktin); + ppl_logevent(("GSSAPI key exchange failed; " + "server's message: %.*s", PTRLEN_PRINTF(data))); + /* Language tag, but we have no use for it */ + get_string(pktin); + /* + * Wait for an error token, if there is one, or the + * server's disconnect. The error token, if there + * is one, must follow the SSH2_MSG_KEXGSS_ERROR + * message, per the RFC. + */ + goto wait_for_gss_token; + default: + ssh_proto_error(s->ppl.ssh, "Received unexpected packet " + "during GSSAPI key exchange, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return; + } + } while (s->gss_rcvtok.length || + s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED || + !s->complete_rcvd); + + s->K = dh_find_K(s->dh_ctx, s->f); + + /* We assume everything from now on will be quick, and it might + * involve user interaction. */ + set_busy_status(s->ppl.frontend, BUSY_NOT); + + if (!s->hkey) + put_stringz(s->exhash, ""); + if (dh_is_gex(s->kex_alg)) { + /* min, preferred, max */ + put_uint32(s->exhash, s->pbits); + put_uint32(s->exhash, s->pbits); + put_uint32(s->exhash, s->pbits * 2); + + put_mp_ssh2(s->exhash, s->p); + put_mp_ssh2(s->exhash, s->g); + } + put_mp_ssh2(s->exhash, s->e); + put_mp_ssh2(s->exhash, s->f); + + /* + * MIC verification is done below, after we compute the hash + * used as the MIC input. + */ + + dh_cleanup(s->dh_ctx); + s->dh_ctx = NULL; + freebn(s->f); s->f = NULL; + freebn(s->e); s->e = NULL; + if (dh_is_gex(s->kex_alg)) { + freebn(s->g); s->g = NULL; + freebn(s->p); s->p = NULL; + } +#endif + } else { + ptrlen rsakeydata; + + 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; + /* + * RSA key exchange. First expect a KEXRSA_PUBKEY packet + * from the server. + */ + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_KEXRSA_PUBKEY) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting RSA public key, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return; + } + + s->hostkeydata = get_string(pktin); + put_stringpl(s->exhash, s->hostkeydata); + s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata); + + rsakeydata = get_string(pktin); + + s->rsa_kex_key = ssh_rsakex_newkey(rsakeydata.ptr, rsakeydata.len); + if (!s->rsa_kex_key) { + ssh_proto_error(s->ppl.ssh, + "Unable to parse RSA public key packet"); + return; + } + + put_stringpl(s->exhash, rsakeydata); + + /* + * Next, set up a shared secret K, of precisely KLEN - + * 2*HLEN - 49 bits, where KLEN is the bit length of the + * RSA key modulus and HLEN is the bit length of the hash + * we're using. + */ + { + int klen = ssh_rsakex_klen(s->rsa_kex_key); + int nbits = klen - (2*s->kex_alg->hash->hlen*8 + 49); + int i, byte = 0; + strbuf *buf; + unsigned char *outstr; + int outstrlen; + + s->K = bn_power_2(nbits - 1); + + for (i = 0; i < nbits; i++) { + if ((i & 7) == 0) { + byte = random_byte(); + } + bignum_set_bit(s->K, i, (byte >> (i & 7)) & 1); + } + + /* + * Encode this as an mpint. + */ + buf = strbuf_new(); + put_mp_ssh2(buf, s->K); + + /* + * Encrypt it with the given RSA key. + */ + outstrlen = (klen + 7) / 8; + outstr = snewn(outstrlen, unsigned char); + ssh_rsakex_encrypt(s->kex_alg->hash, buf->u, buf->len, + outstr, outstrlen, s->rsa_kex_key); + + /* + * And send it off in a return packet. + */ + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXRSA_SECRET); + put_string(pktout, outstr, outstrlen); + pq_push(s->ppl.out_pq, pktout); + + put_string(s->exhash, outstr, outstrlen); + + strbuf_free(buf); + sfree(outstr); + } + + ssh_rsakex_freekey(s->rsa_kex_key); + s->rsa_kex_key = NULL; + + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_KEXRSA_DONE) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting RSA kex signature, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return; + } + + s->sigdata = get_string(pktin); + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, "Unable to parse RSA kex signature"); + return; + } + } + + put_mp_ssh2(s->exhash, s->K); + assert(ssh_hash_alg(s->exhash)->hlen <= sizeof(s->exchange_hash)); + ssh_hash_final(s->exhash, s->exchange_hash); + s->exhash = NULL; + +#ifndef NO_GSSAPI + if (s->kex_alg->main_type == KEXTYPE_GSS) { + Ssh_gss_buf gss_buf; + SSH_GSS_CLEAR_BUF(&s->gss_buf); + + gss_buf.value = s->exchange_hash; + gss_buf.length = s->kex_alg->hash->hlen; + s->gss_stat = s->shgss->lib->verify_mic( + s->shgss->lib, s->shgss->ctx, &gss_buf, &s->mic); + if (s->gss_stat != SSH_GSS_OK) { + if (s->shgss->lib->display_status( + s->shgss->lib, s->shgss->ctx, &s->gss_buf) == SSH_GSS_OK) { + char *err = s->gss_buf.value; + ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange MIC was " + "not valid: %s", err); + sfree(err); + } else { + ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange MIC was " + "not valid"); + } + return; + } + + s->gss_kex_used = TRUE; + + /*- + * If this the first KEX, save the GSS context for "gssapi-keyex" + * authentication. + * + * http://tools.ietf.org/html/rfc4462#section-4 + * + * This method may be used only if the initial key exchange was + * performed using a GSS-API-based key exchange method defined in + * accordance with Section 2. The GSS-API context used with this + * method is always that established during an initial GSS-API-based + * key exchange. Any context established during key exchange for the + * purpose of rekeying MUST NOT be used with this method. + */ + if (s->got_session_id) { + s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx); + } + ppl_logevent(("GSSAPI Key Exchange complete!")); + } +#endif + + s->dh_ctx = NULL; + +#if 0 + debug(("Exchange hash is:\n")); + dmemdump(s->exchange_hash, s->kex_alg->hash->hlen); +#endif + + /* In GSS keyex there's no hostkey signature to verify */ + if (s->kex_alg->main_type != KEXTYPE_GSS) { + if (!s->hkey) { + ssh_proto_error(s->ppl.ssh, "Server's host key is invalid"); + return; + } + + if (!ssh_key_verify( + s->hkey, s->sigdata, + make_ptrlen(s->exchange_hash, s->kex_alg->hash->hlen))) { +#ifndef FUZZING + ssh_proto_error(s->ppl.ssh, "Signature from server's host key " + "is invalid"); + return; +#endif + } + } + + s->keystr = (s->hkey ? ssh_key_cache_str(s->hkey) : NULL); +#ifndef NO_GSSAPI + if (s->gss_kex_used) { + /* + * In a GSS-based session, check the host key (if any) against + * the transient host key cache. See comment above, at the + * definition of ssh_transient_hostkey_cache_entry. + */ + if (s->kex_alg->main_type == KEXTYPE_GSS) { + + /* + * We've just done a GSS key exchange. If it gave us a + * host key, store it. + */ + if (s->hkey) { + s->fingerprint = ssh2_fingerprint(s->hkey); + ppl_logevent(("GSS kex provided fallback host key:")); + ppl_logevent(("%s", s->fingerprint)); + sfree(s->fingerprint); + s->fingerprint = NULL; + ssh_store_transient_hostkey(s, s->hkey); + } else if (!ssh_have_any_transient_hostkey(s)) { + /* + * But if it didn't, then we currently have no + * fallback host key to use in subsequent non-GSS + * rekeys. So we should immediately trigger a non-GSS + * rekey of our own, to set one up, before the session + * keys have been used for anything else. + * + * This is similar to the cross-certification done at + * user request in the permanent host key cache, but + * here we do it automatically, once, at session + * startup, and only add the key to the transient + * cache. + */ + if (s->hostkey_alg) { + s->need_gss_transient_hostkey = TRUE; + } else { + /* + * If we negotiated the "null" host key algorithm + * in the key exchange, that's an indication that + * no host key at all is available from the server + * (both because we listed "null" last, and + * because RFC 4462 section 5 says that a server + * MUST NOT offer "null" as a host key algorithm + * unless that is the only algorithm it provides + * at all). + * + * In that case we actually _can't_ perform a + * non-GSSAPI key exchange, so it's pointless to + * attempt one proactively. This is also likely to + * cause trouble later if a rekey is required at a + * moment whne GSS credentials are not available, + * but someone setting up a server in this + * configuration presumably accepts that as a + * consequence. + */ + if (!s->warned_about_no_gss_transient_hostkey) { + ppl_logevent(("No fallback host key available")); + s->warned_about_no_gss_transient_hostkey = TRUE; + } + } + } + } else { + /* + * We've just done a fallback key exchange, so make + * sure the host key it used is in the cache of keys + * we previously received in GSS kexes. + * + * An exception is if this was the non-GSS key exchange we + * triggered on purpose to populate the transient cache. + */ + assert(s->hkey); /* only KEXTYPE_GSS lets this be null */ + s->fingerprint = ssh2_fingerprint(s->hkey); + + if (s->need_gss_transient_hostkey) { + ppl_logevent(("Post-GSS rekey provided fallback host key:")); + ppl_logevent(("%s", s->fingerprint)); + ssh_store_transient_hostkey(s, s->hkey); + s->need_gss_transient_hostkey = FALSE; + } else if (!ssh_verify_transient_hostkey(s, s->hkey)) { + ppl_logevent(("Non-GSS rekey after initial GSS kex " + "used host key:")); + ppl_logevent(("%s", s->fingerprint)); + ssh_sw_abort(s->ppl.ssh, "Server's host key did not match any " + "used in previous GSS kex"); + return; + } + + sfree(s->fingerprint); + s->fingerprint = NULL; + } + } else +#endif /* NO_GSSAPI */ + if (!s->got_session_id) { + /* + * Make a note of any other host key formats that are available. + */ + { + int i, j, nkeys = 0; + char *list = NULL; + for (i = 0; i < lenof(hostkey_algs); i++) { + if (hostkey_algs[i].alg == s->hostkey_alg) + continue; + + for (j = 0; j < s->n_uncert_hostkeys; j++) + if (s->uncert_hostkeys[j] == i) + break; + + if (j < s->n_uncert_hostkeys) { + char *newlist; + if (list) + newlist = dupprintf("%s/%s", list, + hostkey_algs[i].alg->ssh_id); + else + newlist = dupprintf("%s", hostkey_algs[i].alg->ssh_id); + sfree(list); + list = newlist; + nkeys++; + } + } + if (list) { + ppl_logevent(("Server also has %s host key%s, but we " + "don't know %s", list, + nkeys > 1 ? "s" : "", + nkeys > 1 ? "any of them" : "it")); + sfree(list); + } + } + + /* + * Authenticate remote host: verify host key. (We've already + * checked the signature of the exchange hash.) + */ + s->fingerprint = ssh2_fingerprint(s->hkey); + ppl_logevent(("Host key fingerprint is:")); + ppl_logevent(("%s", s->fingerprint)); + /* First check against manually configured host keys. */ + s->dlgret = verify_ssh_manual_host_key( + s->conf, s->fingerprint, s->hkey); + if (s->dlgret == 0) { /* did not match */ + ssh_sw_abort(s->ppl.ssh, "Host key did not appear in manually " + "configured list"); + return; + } else if (s->dlgret < 0) { /* none configured; use standard handling */ + s->dlgret = verify_ssh_host_key(s->ppl.frontend, + s->savedhost, s->savedport, + ssh_key_cache_id(s->hkey), + s->keystr, s->fingerprint, + ssh2_transport_dialog_callback, s); +#ifdef FUZZING + s->dlgret = 1; +#endif + crMaybeWaitUntilV(s->dlgret >= 0); + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, + "User aborted at host key verification"); + return; + } + } + sfree(s->fingerprint); + s->fingerprint = NULL; + /* + * Save this host key, to check against the one presented in + * subsequent rekeys. + */ + s->hostkey_str = s->keystr; + s->keystr = NULL; + } else if (s->cross_certifying) { + s->fingerprint = ssh2_fingerprint(s->hkey); + ppl_logevent(("Storing additional host key for this host:")); + ppl_logevent(("%s", s->fingerprint)); + sfree(s->fingerprint); + s->fingerprint = NULL; + store_host_key(s->savedhost, s->savedport, + ssh_key_cache_id(s->hkey), s->keystr); + s->cross_certifying = FALSE; + /* + * Don't forget to store the new key as the one we'll be + * re-checking in future normal rekeys. + */ + s->hostkey_str = s->keystr; + s->keystr = NULL; + } else { + /* + * In a rekey, we never present an interactive host key + * verification request to the user. Instead, we simply + * enforce that the key we're seeing this time is identical to + * the one we saw before. + */ + if (strcmp(s->hostkey_str, s->keystr)) { +#ifndef FUZZING + ssh_sw_abort(s->ppl.ssh, + "Host key was different in repeat key exchange"); + return; +#endif + } + } + sfree(s->keystr); + s->keystr = NULL; + if (s->hkey) { + ssh_key_free(s->hkey); + s->hkey = NULL; + } + + /* + * The exchange hash from the very first key exchange is also + * the session id, used in session key construction and + * authentication. + */ + if (!s->got_session_id) { + assert(sizeof(s->exchange_hash) <= sizeof(s->session_id)); + memcpy(s->session_id, s->exchange_hash, sizeof(s->exchange_hash)); + s->session_id_len = s->kex_alg->hash->hlen; + assert(s->session_id_len <= sizeof(s->session_id)); + s->got_session_id = TRUE; + } + + /* + * Send SSH2_MSG_NEWKEYS. + */ + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_NEWKEYS); + pq_push(s->ppl.out_pq, pktout); + /* Start counting down the outgoing-data limit for these cipher keys. */ + s->stats->out.running = TRUE; + s->stats->out.remaining = s->max_data_size; + + /* + * Force the BPP to synchronously marshal all packets up to and + * including that NEWKEYS into wire format, before we switch over + * to new crypto. + */ + ssh_bpp_handle_output(s->ppl.bpp); + + /* + * We've sent client NEWKEYS, so create and initialise + * client-to-server session keys. + */ + { + strbuf *cipher_key = strbuf_new(); + strbuf *cipher_iv = strbuf_new(); + 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', + 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_bpp_new_outgoing_crypto( + s->ppl.bpp, + s->out.cipher, cipher_key->u, cipher_iv->u, + s->out.mac, s->out.etm_mode, mac_key->u, + s->out.comp); + + strbuf_free(cipher_key); + strbuf_free(cipher_iv); + strbuf_free(mac_key); + } + + if (s->out.cipher) + ppl_logevent(("Initialised %.200s client->server encryption", + s->out.cipher->text_name)); + if (s->out.mac) + ppl_logevent(("Initialised %.200s client->server" + " MAC algorithm%s%s", + s->out.mac->text_name, + s->out.etm_mode ? " (in ETM mode)" : "", + (s->out.cipher->required_mac ? + " (required by cipher)" : ""))); + if (s->out.comp->text_name) + ppl_logevent(("Initialised %s compression", + s->out.comp->text_name)); + + /* + * Now our end of the key exchange is complete, we can send all + * our queued higher-layer packets. Transfer the whole of the next + * layer's outgoing queue on to our own. + */ + pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher); + + /* + * Expect SSH2_MSG_NEWKEYS from server. + */ + crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_NEWKEYS) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting SSH_MSG_NEWKEYS, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return; + } + /* Start counting down the incoming-data limit for these cipher keys. */ + s->stats->in.running = TRUE; + s->stats->in.remaining = s->max_data_size; + + /* + * We've seen server NEWKEYS, so create and initialise + * server-to-client session keys. + */ + { + strbuf *cipher_key = strbuf_new(); + strbuf *cipher_iv = strbuf_new(); + 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', + 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_bpp_new_incoming_crypto( + s->ppl.bpp, + s->in.cipher, cipher_key->u, cipher_iv->u, + s->in.mac, s->in.etm_mode, mac_key->u, + s->in.comp); + + strbuf_free(cipher_key); + strbuf_free(cipher_iv); + strbuf_free(mac_key); + } + + if (s->in.cipher) + ppl_logevent(("Initialised %.200s server->client encryption", + s->in.cipher->text_name)); + if (s->in.mac) + ppl_logevent(("Initialised %.200s server->client MAC algorithm%s%s", + s->in.mac->text_name, + s->in.etm_mode ? " (in ETM mode)" : "", + (s->in.cipher->required_mac ? + " (required by cipher)" : ""))); + if (s->in.comp->text_name) + ppl_logevent(("Initialised %s decompression", + s->in.comp->text_name)); + + /* + * Free shared secret. + */ + freebn(s->K); s->K = NULL; + + /* + * Update the specials menu to list the remaining uncertified host + * keys. + */ + update_specials_menu(s->ppl.frontend); + + /* + * Key exchange is over. Loop straight back round if we have a + * deferred rekey reason. + */ + if (s->deferred_rekey_reason) { + ppl_logevent(("%s", s->deferred_rekey_reason)); + pktin = NULL; + s->deferred_rekey_reason = NULL; + goto begin_key_exchange; + } + + /* + * Otherwise, schedule a timer for our next rekey. + */ + s->kex_in_progress = FALSE; + s->last_rekey = GETTICKCOUNT(); + (void) ssh2_transport_timer_update(s, 0); + + /* + * Now we're encrypting. Get the next-layer protocol started if it + * hasn't already, and then sit here waiting for reasons to go + * back to the start and do a repeat key exchange. One of those + * reasons is that we receive KEXINIT from the other end; the + * other is if we find rekey_reason is non-NULL, i.e. we've + * 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; + } + + s->higher_layer_ok = TRUE; + queue_idempotent_callback(&s->higher_layer->ic_process_queue); + } + + s->rekey_class = RK_NONE; + do { + crReturnV; + + /* Pass through outgoing packets from the higher layer. */ + pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher); + + /* Wait for either a KEXINIT, or something setting + * s->rekey_class. This call to ssh2_transport_pop also has + * the side effect of transferring incoming packets _to_ the + * higher layer (via filter_queue). */ + if ((pktin = ssh2_transport_pop(s)) != NULL) { + if (pktin->type != SSH2_MSG_KEXINIT) { + ssh_proto_error(s->ppl.ssh, "Received unexpected transport-" + "layer packet outside a key exchange, " + "type %d (%s)", pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return; + } + pq_push_front(s->ppl.in_pq, pktin); + ppl_logevent(("Server initiated key re-exchange")); + s->rekey_class = RK_SERVER; + } + + if (s->rekey_class == RK_POST_USERAUTH) { + /* + * userauth has seen a USERAUTH_SUCCEEDED. For a couple of + * reasons, this may be the moment to do an immediate + * rekey with different parameters. But it may not; so + * here we turn that rekey class into either RK_NONE or + * RK_NORMAL. + * + * One is to turn on delayed compression. We do this by a + * rekey to work around a protocol design bug: + * draft-miller-secsh-compression-delayed-00 says that you + * negotiate delayed compression in the first key + * exchange, and both sides start compressing when the + * server has sent USERAUTH_SUCCESS. This has a race + * condition -- the server can't know when the client has + * seen it, and thus which incoming packets it should + * treat as compressed. + * + * Instead, we do the initial key exchange without + * offering the delayed methods, but note if the server + * offers them; when we get here, if a delayed method was + * available that was higher on our list than what we got, + * we initiate a rekey in which we _do_ list the delayed + * methods (and hopefully get it as a result). Subsequent + * rekeys will do the same. + * + * Another reason for a rekey at this point is if we've + * done a GSS key exchange and don't have anything in our + * transient hostkey cache, in which case we should make + * an attempt to populate the cache now. + */ + assert(!s->userauth_succeeded); /* should only happen once */ + s->userauth_succeeded = TRUE; + if (s->pending_compression) { + s->rekey_reason = "enabling delayed compression"; + s->rekey_class = RK_NORMAL; + } else if (s->need_gss_transient_hostkey) { + s->rekey_reason = "populating transient host key cache"; + s->rekey_class = RK_NORMAL; + } else { + /* No need to rekey at this time. */ + s->rekey_class = RK_NONE; + } + } + + if (!s->rekey_class) { + /* If we don't yet have any other reason to rekey, check + * if we've hit our data limit in either direction. */ + if (!s->stats->in.running) { + s->rekey_reason = "too much data received"; + s->rekey_class = RK_NORMAL; + } else if (!s->stats->out.running) { + s->rekey_reason = "too much data sent"; + s->rekey_class = RK_NORMAL; + } + } + + if (s->rekey_class != RK_NONE && s->rekey_class != RK_SERVER) { + /* + * Special case: if the server bug is set that doesn't + * allow rekeying, we give a different log message and + * continue waiting. (If such a server _initiates_ a + * rekey, we process it anyway!) + */ + if ((s->ppl.remote_bugs & BUG_SSH2_REKEY)) { + ppl_logevent(("Server bug prevents key re-exchange (%s)", + s->rekey_reason)); + /* Reset the counters, so that at least this message doesn't + * hit the event log _too_ often. */ + s->stats->in.running = s->stats->out.running = TRUE; + s->stats->in.remaining = s->stats->out.remaining = + s->max_data_size; + (void) ssh2_transport_timer_update(s, 0); + s->rekey_class = RK_NONE; + } else { + ppl_logevent(("Initiating key re-exchange (%s)", + s->rekey_reason)); + } + } + } while (s->rekey_reason == RK_NONE); + + /* Once we exit the above loop, we really are rekeying. */ + goto begin_key_exchange; + + crFinishV; +} + +static void ssh2_transport_higher_layer_packet_callback(void *context) +{ + PacketProtocolLayer *ppl = (PacketProtocolLayer *)context; + ssh_ppl_process_queue(ppl); +} + +static void ssh2_transport_timer(void *ctx, unsigned long now) +{ + struct ssh2_transport_state *s = (struct ssh2_transport_state *)ctx; + unsigned long mins; + unsigned long ticks; + + if (s->kex_in_progress || now != s->next_rekey) + return; + + mins = sanitise_rekey_time(conf_get_int(s->conf, CONF_ssh_rekey_time), 60); + if (mins == 0) + return; + + /* Rekey if enough time has elapsed */ + ticks = mins * 60 * TICKSPERSEC; + if (now - s->last_rekey > ticks - 30*TICKSPERSEC) { + s->rekey_reason = "timeout"; + s->rekey_class = RK_NORMAL; + queue_idempotent_callback(&s->ppl.ic_process_queue); + return; + } + +#ifndef NO_GSSAPI + /* + * Rekey now if we have a new cred or context expires this cycle, + * but not if this is unsafe. + */ + if (conf_get_int(s->conf, CONF_gssapirekey)) { + ssh2_transport_gss_update(s, FALSE); + if ((s->gss_status & GSS_KEX_CAPABLE) != 0 && + (s->gss_status & GSS_CTXT_MAYFAIL) == 0 && + (s->gss_status & (GSS_CRED_UPDATED|GSS_CTXT_EXPIRES)) != 0) { + s->rekey_reason = "GSS credentials updated"; + s->rekey_class = RK_GSS_UPDATE; + queue_idempotent_callback(&s->ppl.ic_process_queue); + return; + } + } +#endif + + /* Try again later. */ + (void) ssh2_transport_timer_update(s, 0); +} + +/* + * The rekey_time is zero except when re-configuring. + * + * We either schedule the next timer and return 0, or return 1 to run the + * callback now, which will call us again to re-schedule on completion. + */ +static int ssh2_transport_timer_update(struct ssh2_transport_state *s, + unsigned long rekey_time) +{ + unsigned long mins; + unsigned long ticks; + + mins = sanitise_rekey_time(conf_get_int(s->conf, CONF_ssh_rekey_time), 60); + ticks = mins * 60 * TICKSPERSEC; + + /* Handle change from previous setting */ + if (rekey_time != 0 && rekey_time != mins) { + unsigned long next; + unsigned long now = GETTICKCOUNT(); + + mins = rekey_time; + ticks = mins * 60 * TICKSPERSEC; + next = s->last_rekey + ticks; + + /* If overdue, caller will rekey synchronously now */ + if (now - s->last_rekey > ticks) + return 1; + ticks = next - now; + } + +#ifndef NO_GSSAPI + if (s->gss_kex_used) { + /* + * If we've used GSSAPI key exchange, then we should + * periodically check whether we need to do another one to + * pass new credentials to the server. + */ + unsigned long gssmins; + + /* Check cascade conditions more frequently if configured */ + gssmins = sanitise_rekey_time( + conf_get_int(s->conf, CONF_gssapirekey), GSS_DEF_REKEY_MINS); + if (gssmins > 0) { + if (gssmins < mins) + ticks = (mins = gssmins) * 60 * TICKSPERSEC; + + if ((s->gss_status & GSS_KEX_CAPABLE) != 0) { + /* + * Run next timer even sooner if it would otherwise be + * too close to the context expiration time + */ + if ((s->gss_status & GSS_CTXT_EXPIRES) == 0 && + s->gss_ctxt_lifetime - mins * 60 < 2 * MIN_CTXT_LIFETIME) + ticks -= 2 * MIN_CTXT_LIFETIME * TICKSPERSEC; + } + } + } +#endif + + /* Schedule the next timer */ + s->next_rekey = schedule_timer(ticks, ssh2_transport_timer, s); + return 0; +} + +static void ssh2_transport_dialog_callback(void *loginv, int ret) +{ + struct ssh2_transport_state *s = (struct ssh2_transport_state *)loginv; + s->dlgret = ret; + ssh_ppl_process_queue(&s->ppl); +} + +#ifndef NO_GSSAPI +/* + * This is called at the beginning of each SSH rekey to determine + * whether we are GSS capable, and if we did GSS key exchange, and are + * delegating credentials, it is also called periodically to determine + * whether we should rekey in order to delegate (more) fresh + * credentials. This is called "credential cascading". + * + * On Windows, with SSPI, we may not get the credential expiration, as + * Windows automatically renews from cached passwords, so the + * credential effectively never expires. Since we still want to + * cascade when the local TGT is updated, we use the expiration of a + * newly obtained context as a proxy for the expiration of the TGT. + */ +static void ssh2_transport_gss_update(struct ssh2_transport_state *s, + int definitely_rekeying) +{ + PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ + int gss_stat; + time_t gss_cred_expiry; + unsigned long mins; + Ssh_gss_buf gss_sndtok; + Ssh_gss_buf gss_rcvtok; + Ssh_gss_ctx gss_ctx; + + s->gss_status = 0; + + /* + * Nothing to do if no GSSAPI libraries are configured or GSSAPI + * auth is not enabled. + */ + if (s->shgss->libs->nlibraries == 0) + return; + if (!conf_get_int(s->conf, CONF_try_gssapi_auth) && + !conf_get_int(s->conf, CONF_try_gssapi_kex)) + return; + + /* Import server name and cache it */ + if (s->shgss->srv_name == GSS_C_NO_NAME) { + gss_stat = s->shgss->lib->import_name( + s->shgss->lib, s->fullhostname, &s->shgss->srv_name); + if (gss_stat != SSH_GSS_OK) { + if (gss_stat == SSH_GSS_BAD_HOST_NAME) + ppl_logevent(("GSSAPI import name failed - Bad service name;" + " won't use GSS key exchange")); + else + ppl_logevent(("GSSAPI import name failed;" + " won't use GSS key exchange")); + return; + } + } + + /* + * Do we (still) have credentials? Capture the credential + * expiration when available + */ + gss_stat = s->shgss->lib->acquire_cred( + s->shgss->lib, &gss_ctx, &gss_cred_expiry); + if (gss_stat != SSH_GSS_OK) + return; + + SSH_GSS_CLEAR_BUF(&gss_sndtok); + SSH_GSS_CLEAR_BUF(&gss_rcvtok); + + /* + * When acquire_cred yields no useful expiration, get a proxy for + * the cred expiration from the context expiration. + */ + gss_stat = s->shgss->lib->init_sec_context( + s->shgss->lib, &gss_ctx, s->shgss->srv_name, + 0 /* don't delegate */, &gss_rcvtok, &gss_sndtok, + (gss_cred_expiry == GSS_NO_EXPIRATION ? &gss_cred_expiry : NULL), + &s->gss_ctxt_lifetime); + + /* This context was for testing only. */ + if (gss_ctx) + s->shgss->lib->release_cred(s->shgss->lib, &gss_ctx); + + if (gss_stat != SSH_GSS_OK && + gss_stat != SSH_GSS_S_CONTINUE_NEEDED) { + /* + * No point in verbosely interrupting the user to tell them we + * couldn't get GSS credentials, if this was only a check + * between key exchanges to see if fresh ones were available. + * When we do do a rekey, this message (if displayed) will + * appear among the standard rekey blurb, but when we're not, + * it shouldn't pop up all the time regardless. + */ + if (definitely_rekeying) + ppl_logevent(("No GSSAPI security context available")); + + return; + } + + if (gss_sndtok.length) + s->shgss->lib->free_tok(s->shgss->lib, &gss_sndtok); + + s->gss_status |= GSS_KEX_CAPABLE; + + /* + * When rekeying to cascade, avoding doing this too close to the + * context expiration time, since the key exchange might fail. + */ + if (s->gss_ctxt_lifetime < MIN_CTXT_LIFETIME) + s->gss_status |= GSS_CTXT_MAYFAIL; + + /* + * If we're not delegating credentials, rekeying is not used to + * refresh them. We must avoid setting GSS_CRED_UPDATED or + * GSS_CTXT_EXPIRES when credential delegation is disabled. + */ + if (conf_get_int(s->conf, CONF_gssapifwd) == 0) + return; + + if (s->gss_cred_expiry != GSS_NO_EXPIRATION && + difftime(gss_cred_expiry, s->gss_cred_expiry) > 0) + s->gss_status |= GSS_CRED_UPDATED; + + mins = sanitise_rekey_time( + conf_get_int(s->conf, CONF_gssapirekey), GSS_DEF_REKEY_MINS); + if (mins > 0 && s->gss_ctxt_lifetime <= mins * 60) + s->gss_status |= GSS_CTXT_EXPIRES; +} + +/* + * Data structure managing host keys in sessions based on GSSAPI KEX. + * + * In a session we started with a GSSAPI key exchange, the concept of + * 'host key' has completely different lifetime and security semantics + * from the usual ones. Per RFC 4462 section 2.1, we assume that any + * host key delivered to us in the course of a GSSAPI key exchange is + * _solely_ there to use as a transient fallback within the same + * session, if at the time of a subsequent rekey the GSS credentials + * are temporarily invalid and so a non-GSS KEX method has to be used. + * + * In particular, in a GSS-based SSH deployment, host keys may not + * even _be_ persistent identities for the server; it would be + * legitimate for a server to generate a fresh one routinely if it + * wanted to, like SSH-1 server keys. + * + * So, in this mode, we never touch the persistent host key cache at + * all, either to check keys against it _or_ to store keys in it. + * Instead, we maintain an in-memory cache of host keys that have been + * mentioned in GSS key exchanges within this particular session, and + * we permit precisely those host keys in non-GSS rekeys. + */ +struct ssh_transient_hostkey_cache_entry { + const ssh_keyalg *alg; + strbuf *pub_blob; +}; + +static int ssh_transient_hostkey_cache_cmp(void *av, void *bv) +{ + const struct ssh_transient_hostkey_cache_entry + *a = (const struct ssh_transient_hostkey_cache_entry *)av, + *b = (const struct ssh_transient_hostkey_cache_entry *)bv; + return strcmp(a->alg->ssh_id, b->alg->ssh_id); +} + +static int ssh_transient_hostkey_cache_find(void *av, void *bv) +{ + const ssh_keyalg *aalg = (const ssh_keyalg *)av; + const struct ssh_transient_hostkey_cache_entry + *b = (const struct ssh_transient_hostkey_cache_entry *)bv; + return strcmp(aalg->ssh_id, b->alg->ssh_id); +} + +static void ssh_init_transient_hostkey_store( + struct ssh2_transport_state *s) +{ + s->transient_hostkey_cache = + newtree234(ssh_transient_hostkey_cache_cmp); +} + +static void ssh_cleanup_transient_hostkey_store( + struct ssh2_transport_state *s) +{ + struct ssh_transient_hostkey_cache_entry *ent; + while ((ent = delpos234(s->transient_hostkey_cache, 0)) != NULL) { + strbuf_free(ent->pub_blob); + sfree(ent); + } + freetree234(s->transient_hostkey_cache); +} + +static void ssh_store_transient_hostkey( + struct ssh2_transport_state *s, ssh_key *key) +{ + struct ssh_transient_hostkey_cache_entry *ent, *retd; + + if ((ent = find234(s->transient_hostkey_cache, (void *)ssh_key_alg(key), + ssh_transient_hostkey_cache_find)) != NULL) { + strbuf_free(ent->pub_blob); + sfree(ent); + } + + ent = snew(struct ssh_transient_hostkey_cache_entry); + ent->alg = ssh_key_alg(key); + ent->pub_blob = strbuf_new(); + ssh_key_public_blob(key, BinarySink_UPCAST(ent->pub_blob)); + retd = add234(s->transient_hostkey_cache, ent); + assert(retd == ent); +} + +static int ssh_verify_transient_hostkey( + struct ssh2_transport_state *s, ssh_key *key) +{ + struct ssh_transient_hostkey_cache_entry *ent; + int toret = FALSE; + + if ((ent = find234(s->transient_hostkey_cache, (void *)ssh_key_alg(key), + ssh_transient_hostkey_cache_find)) != NULL) { + strbuf *this_blob = strbuf_new(); + ssh_key_public_blob(key, BinarySink_UPCAST(this_blob)); + + if (this_blob->len == ent->pub_blob->len && + !memcmp(this_blob->s, ent->pub_blob->s, + this_blob->len)) + toret = TRUE; + + strbuf_free(this_blob); + } + + return toret; +} + +static int ssh_have_transient_hostkey( + struct ssh2_transport_state *s, const ssh_keyalg *alg) +{ + struct ssh_transient_hostkey_cache_entry *ent = + find234(s->transient_hostkey_cache, (void *)alg, + ssh_transient_hostkey_cache_find); + return ent != NULL; +} + +static int ssh_have_any_transient_hostkey( + struct ssh2_transport_state *s) +{ + return count234(s->transient_hostkey_cache) > 0; +} + +ptrlen ssh2_transport_get_session_id(PacketProtocolLayer *ppl) +{ + struct ssh2_transport_state *s; + + assert(ppl->vt == &ssh2_transport_vtable); + s = FROMFIELD(ppl, struct ssh2_transport_state, ppl); + + assert(s->got_session_id); + return make_ptrlen(s->session_id, s->session_id_len); +} + +void ssh2_transport_notify_auth_done(PacketProtocolLayer *ppl) +{ + struct ssh2_transport_state *s; + + assert(ppl->vt == &ssh2_transport_vtable); + s = FROMFIELD(ppl, struct ssh2_transport_state, ppl); + + s->rekey_reason = NULL; /* will be filled in later */ + s->rekey_class = RK_POST_USERAUTH; + queue_idempotent_callback(&s->ppl.ic_process_queue); +} + +#endif /* NO_GSSAPI */ + +static int ssh2_transport_get_specials( + PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) +{ + struct ssh2_transport_state *s = + FROMFIELD(ppl, struct ssh2_transport_state, ppl); + int need_separator = FALSE; + int toret; + + if (ssh_ppl_get_specials(s->higher_layer, add_special, ctx)) { + need_separator = TRUE; + toret = TRUE; + } + + /* + * Don't bother offering rekey-based specials if we've decided the + * remote won't cope with it, since we wouldn't bother sending it + * if asked anyway. + */ + if (!(s->ppl.remote_bugs & BUG_SSH2_REKEY)) { + if (need_separator) { + add_special(ctx, NULL, SS_SEP, 0); + need_separator = FALSE; + } + + add_special(ctx, "Repeat key exchange", SS_REKEY, 0); + toret = TRUE; + + if (s->n_uncert_hostkeys) { + int i; + + add_special(ctx, NULL, SS_SEP, 0); + add_special(ctx, "Cache new host key type", SS_SUBMENU, 0); + for (i = 0; i < s->n_uncert_hostkeys; i++) { + const ssh_keyalg *alg = + hostkey_algs[s->uncert_hostkeys[i]].alg; + + add_special(ctx, alg->ssh_id, SS_XCERT, s->uncert_hostkeys[i]); + } + add_special(ctx, NULL, SS_EXITMENU, 0); + } + } + + return toret; +} + +static void ssh2_transport_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg) +{ + struct ssh2_transport_state *s = + FROMFIELD(ppl, struct ssh2_transport_state, ppl); + + if (code == SS_REKEY) { + if (!s->kex_in_progress) { + s->rekey_reason = "at user request"; + s->rekey_class = RK_NORMAL; + queue_idempotent_callback(&s->ppl.ic_process_queue); + } + } else if (code == SS_XCERT) { + if (!s->kex_in_progress) { + s->hostkey_alg = hostkey_algs[arg].alg; + s->cross_certifying = TRUE; + s->rekey_reason = "cross-certifying new host key"; + s->rekey_class = RK_NORMAL; + queue_idempotent_callback(&s->ppl.ic_process_queue); + } + } else { + /* Send everything else to the next layer up. This includes + * SS_PING/SS_NOP, which we _could_ handle here - but it's + * better to put them in the connection layer, so they'll + * still work in bare connection mode. */ + ssh_ppl_special_cmd(s->higher_layer, code, arg); + } +} + +/* Safely convert rekey_time to unsigned long minutes */ +static unsigned long sanitise_rekey_time(int rekey_time, unsigned long def) +{ + if (rekey_time < 0 || rekey_time > MAX_TICK_MINS) + rekey_time = def; + return (unsigned long)rekey_time; +} + +static void ssh2_transport_set_max_data_size(struct ssh2_transport_state *s) +{ + s->max_data_size = parse_blocksize( + conf_get_str(s->conf, CONF_ssh_rekey_data)); +} + +static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf) +{ + struct ssh2_transport_state *s; + const char *rekey_reason = NULL; + int rekey_mandatory = FALSE; + unsigned long old_max_data_size, rekey_time; + int i; + + assert(ppl->vt == &ssh2_transport_vtable); + s = FROMFIELD(ppl, struct ssh2_transport_state, ppl); + + rekey_time = sanitise_rekey_time( + conf_get_int(conf, CONF_ssh_rekey_time), 60); + if (ssh2_transport_timer_update(s, rekey_time)) + rekey_reason = "timeout shortened"; + + old_max_data_size = s->max_data_size; + ssh2_transport_set_max_data_size(s); + if (old_max_data_size != s->max_data_size && + s->max_data_size != 0) { + if (s->max_data_size < old_max_data_size) { + unsigned long diff = old_max_data_size - s->max_data_size; + + /* Intentionally use bitwise OR instead of logical, so + * that we decrement both counters even if the first one + * runs out */ + if ((DTS_CONSUME(s->stats, out, diff) != 0) | + (DTS_CONSUME(s->stats, in, diff) != 0)) + rekey_reason = "data limit lowered"; + } else { + unsigned long diff = s->max_data_size - old_max_data_size; + if (s->stats->out.running) + s->stats->out.remaining += diff; + if (s->stats->in.running) + s->stats->in.remaining += diff; + } + } + + if (conf_get_int(s->conf, CONF_compression) != + conf_get_int(conf, CONF_compression)) { + rekey_reason = "compression setting changed"; + rekey_mandatory = TRUE; + } + + for (i = 0; i < CIPHER_MAX; i++) + if (conf_get_int_int(s->conf, CONF_ssh_cipherlist, i) != + conf_get_int_int(conf, CONF_ssh_cipherlist, i)) { + rekey_reason = "cipher settings changed"; + rekey_mandatory = TRUE; + } + if (conf_get_int(s->conf, CONF_ssh2_des_cbc) != + conf_get_int(conf, CONF_ssh2_des_cbc)) { + rekey_reason = "cipher settings changed"; + rekey_mandatory = TRUE; + } + + conf_free(s->conf); + s->conf = conf_copy(conf); + + if (rekey_reason) { + if (!s->kex_in_progress) { + s->rekey_reason = rekey_reason; + s->rekey_class = RK_NORMAL; + queue_idempotent_callback(&s->ppl.ic_process_queue); + } else if (rekey_mandatory) { + s->deferred_rekey_reason = rekey_reason; + } + } + + /* Also pass the configuration along to our higher layer */ + ssh_ppl_reconfigure(s->higher_layer, conf); +} + +static int ssh2_transport_want_user_input(PacketProtocolLayer *ppl) +{ + struct ssh2_transport_state *s = + FROMFIELD(ppl, struct ssh2_transport_state, ppl); + + /* Just delegate this to the higher layer */ + return ssh_ppl_want_user_input(s->higher_layer); +} + +static void ssh2_transport_got_user_input(PacketProtocolLayer *ppl) +{ + struct ssh2_transport_state *s = + FROMFIELD(ppl, struct ssh2_transport_state, ppl); + + /* Just delegate this to the higher layer */ + ssh_ppl_got_user_input(s->higher_layer); +} diff --git a/ssh2userauth.c b/ssh2userauth.c new file mode 100644 index 00000000..1e5a1207 --- /dev/null +++ b/ssh2userauth.c @@ -0,0 +1,1673 @@ +/* + * Packet protocol layer for the SSH-2 userauth protocol (RFC 4252). + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshcr.h" + +#ifndef NO_GSSAPI +#include "sshgssc.h" +#include "sshgss.h" +#endif + +#define BANNER_LIMIT 131072 + +struct ssh2_userauth_state { + int crState; + + PacketProtocolLayer *transport_layer, *successor_layer; + Filename *keyfile; + int tryagent, change_username; + char *hostname, *fullhostname; + char *default_username; + int try_ki_auth, try_gssapi_auth, try_gssapi_kex_auth, gssapi_fwd; + + ptrlen session_id; + enum { + AUTH_TYPE_NONE, + AUTH_TYPE_PUBLICKEY, + AUTH_TYPE_PUBLICKEY_OFFER_LOUD, + AUTH_TYPE_PUBLICKEY_OFFER_QUIET, + AUTH_TYPE_PASSWORD, + AUTH_TYPE_GSSAPI, /* always QUIET */ + AUTH_TYPE_KEYBOARD_INTERACTIVE, + AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET + } type; + int done_service_req; + int need_pw, can_pubkey, can_passwd, can_keyb_inter; + int userpass_ret; + int tried_pubkey_config, done_agent; + struct ssh_connection_shared_gss_state *shgss; +#ifndef NO_GSSAPI + int can_gssapi; + int can_gssapi_keyex_auth; + int tried_gssapi; + int tried_gssapi_keyex_auth; + time_t gss_cred_expiry; + Ssh_gss_buf gss_buf; + Ssh_gss_buf gss_rcvtok, gss_sndtok; + Ssh_gss_stat gss_stat; +#endif + int kbd_inter_refused; + prompts_t *cur_prompt; + int num_prompts; + char *username; + char *password; + int got_username; + strbuf *publickey_blob; + int privatekey_available, privatekey_encrypted; + char *publickey_algorithm; + char *publickey_comment; + void *agent_response_to_free; + ptrlen agent_response; + BinarySource asrc[1]; /* for reading SSH agent response */ + size_t pkblob_pos_in_agent; + int keyi, nkeys; + ptrlen pk, alg, comment; + int len; + PktOut *pktout; + int want_user_input; + + agent_pending_query *auth_agent_query; + bufchain banner; + + PacketProtocolLayer ppl; +}; + +static void ssh2_userauth_free(PacketProtocolLayer *); +static void ssh2_userauth_process_queue(PacketProtocolLayer *); +static int ssh2_userauth_get_specials( + PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx); +static void ssh2_userauth_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg); +static int ssh2_userauth_want_user_input(PacketProtocolLayer *ppl); +static void ssh2_userauth_got_user_input(PacketProtocolLayer *ppl); +static void ssh2_userauth_reconfigure(PacketProtocolLayer *ppl, Conf *conf); + +static void ssh2_userauth_agent_query(struct ssh2_userauth_state *, strbuf *); +static void ssh2_userauth_agent_callback(void *, void *, int); +static void ssh2_userauth_add_sigblob( + struct ssh2_userauth_state *s, PktOut *pkt, ptrlen pkblob, ptrlen sigblob); +static void ssh2_userauth_add_session_id( + struct ssh2_userauth_state *s, strbuf *sigdata); +#ifndef NO_GSSAPI +static PktOut *ssh2_userauth_gss_packet( + struct ssh2_userauth_state *s, const char *authtype); +#endif + +static const struct PacketProtocolLayerVtable ssh2_userauth_vtable = { + ssh2_userauth_free, + ssh2_userauth_process_queue, + ssh2_userauth_get_specials, + ssh2_userauth_special_cmd, + ssh2_userauth_want_user_input, + ssh2_userauth_got_user_input, + ssh2_userauth_reconfigure, + "ssh-userauth", +}; + +PacketProtocolLayer *ssh2_userauth_new( + PacketProtocolLayer *successor_layer, + const char *hostname, const char *fullhostname, + Filename *keyfile, int tryagent, + const char *default_username, int change_username, + int try_ki_auth, + int try_gssapi_auth, int try_gssapi_kex_auth, + int gssapi_fwd, struct ssh_connection_shared_gss_state *shgss) +{ + struct ssh2_userauth_state *s = snew(struct ssh2_userauth_state); + memset(s, 0, sizeof(*s)); + s->ppl.vt = &ssh2_userauth_vtable; + + s->successor_layer = successor_layer; + s->hostname = dupstr(hostname); + s->fullhostname = dupstr(fullhostname); + s->keyfile = filename_copy(keyfile); + s->tryagent = tryagent; + s->default_username = dupstr(default_username); + s->change_username = change_username; + s->try_ki_auth = try_ki_auth; + s->try_gssapi_auth = try_gssapi_auth; + s->try_gssapi_kex_auth = try_gssapi_kex_auth; + s->gssapi_fwd = gssapi_fwd; + s->shgss = shgss; + bufchain_init(&s->banner); + + return &s->ppl; +} + +void ssh2_userauth_set_transport_layer(PacketProtocolLayer *userauth, + PacketProtocolLayer *transport) +{ + struct ssh2_userauth_state *s = + FROMFIELD(userauth, struct ssh2_userauth_state, ppl); + s->transport_layer = transport; +} + +static void ssh2_userauth_free(PacketProtocolLayer *ppl) +{ + struct ssh2_userauth_state *s = + FROMFIELD(ppl, struct ssh2_userauth_state, ppl); + bufchain_clear(&s->banner); + + if (s->successor_layer) + ssh_ppl_free(s->successor_layer); + + sfree(s->agent_response_to_free); + if (s->auth_agent_query) + agent_cancel_query(s->auth_agent_query); + filename_free(s->keyfile); + sfree(s->default_username); + sfree(s->hostname); + sfree(s->fullhostname); + sfree(s); +} + +static void ssh2_userauth_filter_queue(struct ssh2_userauth_state *s) +{ + PktIn *pktin; + ptrlen string; + + while ((pktin = pq_peek(s->ppl.in_pq)) != NULL) { + switch (pktin->type) { + case SSH2_MSG_USERAUTH_BANNER: + string = get_string(pktin); + if (string.len > BANNER_LIMIT - bufchain_size(&s->banner)) + string.len = BANNER_LIMIT - bufchain_size(&s->banner); + sanitise_term_data(&s->banner, string.ptr, string.len); + pq_pop(s->ppl.in_pq); + break; + + default: + return; + } + } +} + +static PktIn *ssh2_userauth_pop(struct ssh2_userauth_state *s) +{ + ssh2_userauth_filter_queue(s); + return pq_pop(s->ppl.in_pq); +} + +static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) +{ + struct ssh2_userauth_state *s = + FROMFIELD(ppl, struct ssh2_userauth_state, ppl); + PktIn *pktin; + + ssh2_userauth_filter_queue(s); /* no matter why we were called */ + + crBegin(s->crState); + + s->done_service_req = FALSE; +#ifndef NO_GSSAPI + s->tried_gssapi = FALSE; + s->tried_gssapi_keyex_auth = FALSE; +#endif + + /* + * Misc one-time setup for authentication. + */ + s->publickey_blob = NULL; + s->session_id = ssh2_transport_get_session_id(s->transport_layer); + + /* + * Load the public half of any configured public key file for + * later use. + */ + if (!filename_is_null(s->keyfile)) { + int keytype; + ppl_logevent(("Reading key file \"%.150s\"", + filename_to_str(s->keyfile))); + keytype = key_type(s->keyfile); + if (keytype == SSH_KEYTYPE_SSH2 || + keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 || + keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) { + const char *error; + s->publickey_blob = strbuf_new(); + if (ssh2_userkey_loadpub(s->keyfile, + &s->publickey_algorithm, + BinarySink_UPCAST(s->publickey_blob), + &s->publickey_comment, &error)) { + s->privatekey_available = (keytype == SSH_KEYTYPE_SSH2); + if (!s->privatekey_available) + ppl_logevent(("Key file contains public key only")); + s->privatekey_encrypted = + ssh2_userkey_encrypted(s->keyfile, NULL); + } else { + ppl_logevent(("Unable to load key (%s)", error)); + ppl_printf(("Unable to load key file \"%s\" (%s)\r\n", + filename_to_str(s->keyfile), error)); + strbuf_free(s->publickey_blob); + s->publickey_blob = NULL; + } + } else { + ppl_logevent(("Unable to use this key file (%s)", + key_type_to_str(keytype))); + ppl_printf(("Unable to use key file \"%s\" (%s)\r\n", + filename_to_str(s->keyfile), + key_type_to_str(keytype))); + s->publickey_blob = NULL; + } + } + + /* + * Find out about any keys Pageant has (but if there's a public + * key configured, filter out all others). + */ + s->nkeys = 0; + s->pkblob_pos_in_agent = 0; + if (s->tryagent && agent_exists()) { + ppl_logevent(("Pageant is running. Requesting keys.")); + + /* Request the keys held by the agent. */ + { + strbuf *request = strbuf_new_for_agent_query(); + put_byte(request, SSH2_AGENTC_REQUEST_IDENTITIES); + ssh2_userauth_agent_query(s, request); + strbuf_free(request); + crWaitUntilV(!s->auth_agent_query); + } + BinarySource_BARE_INIT( + s->asrc, s->agent_response.ptr, s->agent_response.len); + + get_uint32(s->asrc); /* skip length field */ + if (get_byte(s->asrc) == SSH2_AGENT_IDENTITIES_ANSWER) { + int keyi; + + s->nkeys = toint(get_uint32(s->asrc)); + + /* + * Vet the Pageant response to ensure that the key count + * and blob lengths make sense. + */ + if (s->nkeys < 0) { + ppl_logevent(("Pageant response contained a negative" + " key count %d", s->nkeys)); + s->nkeys = 0; + goto done_agent_query; + } else { + ppl_logevent(("Pageant has %d SSH-2 keys", s->nkeys)); + + /* See if configured key is in agent. */ + for (keyi = 0; keyi < s->nkeys; keyi++) { + size_t pos = s->asrc->pos; + ptrlen blob = get_string(s->asrc); + get_string(s->asrc); /* skip comment */ + if (get_err(s->asrc)) { + ppl_logevent(("Pageant response was truncated")); + s->nkeys = 0; + goto done_agent_query; + } + + if (s->publickey_blob && + blob.len == s->publickey_blob->len && + !memcmp(blob.ptr, s->publickey_blob->s, + s->publickey_blob->len)) { + ppl_logevent(("Pageant key #%d matches " + "configured key file", keyi)); + s->keyi = keyi; + s->pkblob_pos_in_agent = pos; + break; + } + } + if (s->publickey_blob && !s->pkblob_pos_in_agent) { + ppl_logevent(("Configured key file not in Pageant")); + s->nkeys = 0; + } + } + } else { + ppl_logevent(("Failed to get reply from Pageant")); + } + done_agent_query:; + } + + /* + * We repeat this whole loop, including the username prompt, + * until we manage a successful authentication. If the user + * types the wrong _password_, they can be sent back to the + * beginning to try another username, if this is configured on. + * (If they specify a username in the config, they are never + * asked, even if they do give a wrong password.) + * + * I think this best serves the needs of + * + * - the people who have no configuration, no keys, and just + * want to try repeated (username,password) pairs until they + * type both correctly + * + * - people who have keys and configuration but occasionally + * need to fall back to passwords + * + * - people with a key held in Pageant, who might not have + * logged in to a particular machine before; so they want to + * type a username, and then _either_ their key will be + * accepted, _or_ they will type a password. If they mistype + * the username they will want to be able to get back and + * retype it! + */ + s->got_username = FALSE; + while (1) { + /* + * Get a username. + */ + if (s->got_username && s->change_username) { + /* + * We got a username last time round this loop, and + * with change_username turned off we don't try to get + * it again. + */ + } else if ((s->username = s->default_username) == NULL) { + s->cur_prompt = new_prompts(s->ppl.frontend); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH login name"); + add_prompt(s->cur_prompt, dupstr("login as: "), TRUE); + s->userpass_ret = get_userpass_input(s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = get_userpass_input( + s->cur_prompt, s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = TRUE; + crReturnV; + s->want_user_input = FALSE; + } + if (!s->userpass_ret) { + /* + * get_userpass_input() failed to get a username. + * Terminate. + */ + free_prompts(s->cur_prompt); + ssh_user_close(s->ppl.ssh, "No username provided"); + return; + } + s->username = dupstr(s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); + } else { + if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) + ppl_printf(("Using username \"%s\".\r\n", s->username)); + } + s->got_username = TRUE; + + /* + * Send an authentication request using method "none": (a) + * just in case it succeeds, and (b) so that we know what + * authentication methods we can usefully try next. + */ + s->ppl.bpp->pls->actx = SSH2_PKTCTX_NOAUTH; + + s->pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); + put_stringz(s->pktout, s->username); + put_stringz(s->pktout, "ssh-connection");/* service requested */ + put_stringz(s->pktout, "none"); /* method */ + pq_push(s->ppl.out_pq, s->pktout); + s->type = AUTH_TYPE_NONE; + + s->tried_pubkey_config = FALSE; + s->kbd_inter_refused = FALSE; + + /* Reset agent request state. */ + s->done_agent = FALSE; + if (s->agent_response.ptr) { + if (s->pkblob_pos_in_agent) { + s->asrc->pos = s->pkblob_pos_in_agent; + } else { + s->asrc->pos = 9; /* skip length + type + key count */ + s->keyi = 0; + } + } + + while (1) { + ptrlen methods; + + methods.ptr = ""; + methods.len = 0; + + /* + * Wait for the result of the last authentication request. + */ + crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); + + /* + * Now is a convenient point to spew any banner material + * that we've accumulated. (This should ensure that when + * we exit the auth loop, we haven't any left to deal + * with.) + */ + { + /* + * Don't show the banner if we're operating in + * non-verbose non-interactive mode. (It's probably + * a script, which means nobody will read the + * banner _anyway_, and moreover the printing of + * the banner will screw up processing on the + * output of (say) plink.) + * + * The banner data has been sanitised already by this + * point, so we can safely pass it straight to + * from_backend. + */ + if (bufchain_size(&s->banner) && + (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE))) { + while (bufchain_size(&s->banner) > 0) { + void *data; + int len; + bufchain_prefix(&s->banner, &data, &len); + from_backend(s->ppl.frontend, TRUE, data, len); + bufchain_consume(&s->banner, len); + } + } + bufchain_clear(&s->banner); + } + if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) { + ppl_logevent(("Access granted")); + goto userauth_success; + } + + if (pktin->type != SSH2_MSG_USERAUTH_FAILURE && + s->type != AUTH_TYPE_GSSAPI) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet " + "in response to authentication request, " + "type %d (%s)", pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, + pktin->type)); + return; + } + + /* + * OK, we're now sitting on a USERAUTH_FAILURE message, so + * we can look at the string in it and know what we can + * helpfully try next. + */ + if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) { + methods = get_string(pktin); + if (!get_bool(pktin)) { + /* + * We have received an unequivocal Access + * Denied. This can translate to a variety of + * messages, or no message at all. + * + * For forms of authentication which are attempted + * implicitly, by which I mean without printing + * anything in the window indicating that we're + * trying them, we should never print 'Access + * denied'. + * + * If we do print a message saying that we're + * attempting some kind of authentication, it's OK + * to print a followup message saying it failed - + * but the message may sometimes be more specific + * than simply 'Access denied'. + * + * Additionally, if we'd just tried password + * authentication, we should break out of this + * whole loop so as to go back to the username + * prompt (iff we're configured to allow + * username change attempts). + */ + if (s->type == AUTH_TYPE_NONE) { + /* do nothing */ + } else if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD || + s->type == AUTH_TYPE_PUBLICKEY_OFFER_QUIET) { + if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD) + ppl_printf(("Server refused our key\r\n")); + ppl_logevent(("Server refused our key")); + } else if (s->type == AUTH_TYPE_PUBLICKEY) { + /* This _shouldn't_ happen except by a + * protocol bug causing client and server to + * disagree on what is a correct signature. */ + ppl_printf(("Server refused public-key signature" + " despite accepting key!\r\n")); + ppl_logevent(("Server refused public-key signature" + " despite accepting key!")); + } else if (s->type==AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET) { + /* quiet, so no ppl_printf */ + ppl_logevent(("Server refused keyboard-interactive " + "authentication")); + } else if (s->type==AUTH_TYPE_GSSAPI) { + /* always quiet, so no ppl_printf */ + /* also, the code down in the GSSAPI block has + * already logged this in the Event Log */ + } else if (s->type == AUTH_TYPE_KEYBOARD_INTERACTIVE) { + ppl_logevent(("Keyboard-interactive authentication " + "failed")); + ppl_printf(("Access denied\r\n")); + } else { + assert(s->type == AUTH_TYPE_PASSWORD); + ppl_logevent(("Password authentication failed")); + ppl_printf(("Access denied\r\n")); + + if (s->change_username) { + /* XXX perhaps we should allow + * keyboard-interactive to do this too? */ + goto try_new_username; + } + } + } else { + ppl_printf(("Further authentication required\r\n")); + ppl_logevent(("Further authentication required")); + } + + s->can_pubkey = + in_commasep_string("publickey", methods.ptr, methods.len); + s->can_passwd = + in_commasep_string("password", methods.ptr, methods.len); + s->can_keyb_inter = + s->try_ki_auth && + in_commasep_string("keyboard-interactive", + methods.ptr, methods.len); +#ifndef NO_GSSAPI + s->can_gssapi = + s->try_gssapi_auth && + in_commasep_string("gssapi-with-mic", + methods.ptr, methods.len) && + s->shgss->libs->nlibraries > 0; + s->can_gssapi_keyex_auth = + s->try_gssapi_kex_auth && + in_commasep_string("gssapi-keyex", + methods.ptr, methods.len) && + s->shgss->libs->nlibraries > 0 && + s->shgss->ctx; +#endif + } + + s->ppl.bpp->pls->actx = SSH2_PKTCTX_NOAUTH; + +#ifndef NO_GSSAPI + if (s->can_gssapi_keyex_auth && !s->tried_gssapi_keyex_auth) { + + /* gssapi-keyex authentication */ + + s->type = AUTH_TYPE_GSSAPI; + s->tried_gssapi_keyex_auth = TRUE; + s->ppl.bpp->pls->actx = SSH2_PKTCTX_GSSAPI; + + if (s->shgss->lib->gsslogmsg) + ppl_logevent(("%s", s->shgss->lib->gsslogmsg)); + + ppl_logevent(("Trying gssapi-keyex...")); + s->pktout = ssh2_userauth_gss_packet(s, "gssapi-keyex"); + pq_push(s->ppl.out_pq, s->pktout); + s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx); + s->shgss->ctx = NULL; + + continue; + } else +#endif /* NO_GSSAPI */ + + if (s->can_pubkey && !s->done_agent && s->nkeys) { + + /* + * Attempt public-key authentication using a key from Pageant. + */ + + s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY; + + ppl_logevent(("Trying Pageant key #%d", s->keyi)); + + /* Unpack key from agent response */ + s->pk = get_string(s->asrc); + s->comment = get_string(s->asrc); + { + BinarySource src[1]; + BinarySource_BARE_INIT(src, s->pk.ptr, s->pk.len); + s->alg = get_string(src); + } + + /* See if server will accept it */ + s->pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); + put_stringz(s->pktout, s->username); + put_stringz(s->pktout, "ssh-connection"); + /* service requested */ + put_stringz(s->pktout, "publickey"); + /* method */ + put_bool(s->pktout, FALSE); /* no signature included */ + put_stringpl(s->pktout, s->alg); + put_stringpl(s->pktout, s->pk); + pq_push(s->ppl.out_pq, s->pktout); + s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET; + + crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) { + + /* Offer of key refused, presumably via + * USERAUTH_FAILURE. Requeue for the next iteration. */ + pq_push_front(s->ppl.in_pq, pktin); + + } else { + strbuf *agentreq, *sigdata; + + if (flags & FLAG_VERBOSE) + ppl_printf(("Authenticating with public key " + "\"%.*s\" from agent\r\n", + PTRLEN_PRINTF(s->comment))); + + /* + * Server is willing to accept the key. + * Construct a SIGN_REQUEST. + */ + s->pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); + put_stringz(s->pktout, s->username); + put_stringz(s->pktout, "ssh-connection"); + /* service requested */ + put_stringz(s->pktout, "publickey"); + /* method */ + put_bool(s->pktout, TRUE); /* signature included */ + put_stringpl(s->pktout, s->alg); + put_stringpl(s->pktout, s->pk); + + /* Ask agent for signature. */ + agentreq = strbuf_new_for_agent_query(); + put_byte(agentreq, SSH2_AGENTC_SIGN_REQUEST); + put_stringpl(agentreq, s->pk); + /* Now the data to be signed... */ + sigdata = strbuf_new(); + ssh2_userauth_add_session_id(s, sigdata); + put_data(sigdata, s->pktout->data + 5, + s->pktout->length - 5); + put_stringsb(agentreq, sigdata); + /* And finally the (zero) flags word. */ + put_uint32(agentreq, 0); + ssh2_userauth_agent_query(s, agentreq); + strbuf_free(agentreq); + crWaitUntilV(!s->auth_agent_query); + + if (s->agent_response.ptr) { + ptrlen sigblob; + BinarySource src[1]; + BinarySource_BARE_INIT(src, s->agent_response.ptr, + s->agent_response.len); + get_uint32(src); /* skip length field */ + if (get_byte(src) == SSH2_AGENT_SIGN_RESPONSE && + (sigblob = get_string(src), !get_err(src))) { + ppl_logevent(("Sending Pageant's response")); + ssh2_userauth_add_sigblob(s, s->pktout, + s->pk, sigblob); + pq_push(s->ppl.out_pq, s->pktout); + s->type = AUTH_TYPE_PUBLICKEY; + } else { + /* FIXME: less drastic response */ + ssh_sw_abort(s->ppl.ssh, "Pageant failed to " + "provide a signature"); + return; + } + } + } + + /* Do we have any keys left to try? */ + if (s->pkblob_pos_in_agent) { + s->done_agent = TRUE; + s->tried_pubkey_config = TRUE; + } else { + s->keyi++; + if (s->keyi >= s->nkeys) + s->done_agent = TRUE; + } + + } else if (s->can_pubkey && s->publickey_blob && + s->privatekey_available && !s->tried_pubkey_config) { + + struct ssh2_userkey *key; /* not live over crReturn */ + char *passphrase; /* not live over crReturn */ + + s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY; + + s->tried_pubkey_config = TRUE; + + /* + * Try the public key supplied in the configuration. + * + * First, offer the public blob to see if the server is + * willing to accept it. + */ + s->pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); + put_stringz(s->pktout, s->username); + put_stringz(s->pktout, "ssh-connection"); + /* service requested */ + put_stringz(s->pktout, "publickey"); /* method */ + put_bool(s->pktout, FALSE); + /* no signature included */ + put_stringz(s->pktout, s->publickey_algorithm); + put_string(s->pktout, s->publickey_blob->s, + s->publickey_blob->len); + pq_push(s->ppl.out_pq, s->pktout); + ppl_logevent(("Offered public key")); + + crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) { + /* Key refused. Give up. */ + pq_push_front(s->ppl.in_pq, pktin); + s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD; + continue; /* process this new message */ + } + ppl_logevent(("Offer of public key accepted")); + + /* + * Actually attempt a serious authentication using + * the key. + */ + if (flags & FLAG_VERBOSE) + ppl_printf(("Authenticating with public key \"%s\"\r\n", + s->publickey_comment)); + + key = NULL; + while (!key) { + const char *error; /* not live over crReturn */ + if (s->privatekey_encrypted) { + /* + * Get a passphrase from the user. + */ + s->cur_prompt = new_prompts(s->ppl.frontend); + s->cur_prompt->to_server = FALSE; + s->cur_prompt->name = dupstr("SSH key passphrase"); + add_prompt(s->cur_prompt, + dupprintf("Passphrase for key \"%.100s\": ", + s->publickey_comment), + FALSE); + s->userpass_ret = get_userpass_input( + s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = get_userpass_input( + s->cur_prompt, s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = TRUE; + crReturnV; + s->want_user_input = FALSE; + } + if (!s->userpass_ret) { + /* Failed to get a passphrase. Terminate. */ + free_prompts(s->cur_prompt); + ssh_bpp_queue_disconnect( + s->ppl.bpp, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); + ssh_user_close(s->ppl.ssh, "User aborted at " + "passphrase prompt"); + return; + } + passphrase = + dupstr(s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); + } else { + passphrase = NULL; /* no passphrase needed */ + } + + /* + * Try decrypting the key. + */ + key = ssh2_load_userkey(s->keyfile, passphrase, &error); + if (passphrase) { + /* burn the evidence */ + smemclr(passphrase, strlen(passphrase)); + sfree(passphrase); + } + if (key == SSH2_WRONG_PASSPHRASE || key == NULL) { + if (passphrase && + (key == SSH2_WRONG_PASSPHRASE)) { + ppl_printf(("Wrong passphrase\r\n")); + key = NULL; + /* and loop again */ + } else { + ppl_printf(("Unable to load private key (%s)\r\n", + error)); + key = NULL; + break; /* try something else */ + } + } + } + + if (key) { + strbuf *pkblob, *sigdata, *sigblob; + + /* + * We have loaded the private key and the server + * has announced that it's willing to accept it. + * Hallelujah. Generate a signature and send it. + */ + s->pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); + put_stringz(s->pktout, s->username); + put_stringz(s->pktout, "ssh-connection"); + /* service requested */ + put_stringz(s->pktout, "publickey"); /* method */ + put_bool(s->pktout, TRUE); /* signature follows */ + put_stringz(s->pktout, ssh_key_ssh_id(key->key)); + pkblob = strbuf_new(); + ssh_key_public_blob(key->key, BinarySink_UPCAST(pkblob)); + put_string(s->pktout, pkblob->s, pkblob->len); + + /* + * The data to be signed is: + * + * string session-id + * + * followed by everything so far placed in the + * outgoing packet. + */ + sigdata = strbuf_new(); + ssh2_userauth_add_session_id(s, sigdata); + put_data(sigdata, s->pktout->data + 5, + s->pktout->length - 5); + sigblob = strbuf_new(); + ssh_key_sign(key->key, sigdata->s, sigdata->len, + BinarySink_UPCAST(sigblob)); + strbuf_free(sigdata); + ssh2_userauth_add_sigblob( + s, s->pktout, make_ptrlen(pkblob->s, pkblob->len), + make_ptrlen(sigblob->s, sigblob->len)); + strbuf_free(pkblob); + strbuf_free(sigblob); + + pq_push(s->ppl.out_pq, s->pktout); + ppl_logevent(("Sent public key signature")); + s->type = AUTH_TYPE_PUBLICKEY; + ssh_key_free(key->key); + sfree(key->comment); + sfree(key); + } + +#ifndef NO_GSSAPI + } else if (s->can_gssapi && !s->tried_gssapi) { + + /* gssapi-with-mic authentication */ + + ptrlen data; + + s->type = AUTH_TYPE_GSSAPI; + s->tried_gssapi = TRUE; + s->ppl.bpp->pls->actx = SSH2_PKTCTX_GSSAPI; + + if (s->shgss->lib->gsslogmsg) + ppl_logevent(("%s", s->shgss->lib->gsslogmsg)); + + /* Sending USERAUTH_REQUEST with "gssapi-with-mic" method */ + ppl_logevent(("Trying gssapi-with-mic...")); + s->pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); + put_stringz(s->pktout, s->username); + put_stringz(s->pktout, "ssh-connection"); + put_stringz(s->pktout, "gssapi-with-mic"); + ppl_logevent(("Attempting GSSAPI authentication")); + + /* add mechanism info */ + s->shgss->lib->indicate_mech(s->shgss->lib, &s->gss_buf); + + /* number of GSSAPI mechanisms */ + put_uint32(s->pktout, 1); + + /* length of OID + 2 */ + put_uint32(s->pktout, s->gss_buf.length + 2); + put_byte(s->pktout, SSH2_GSS_OIDTYPE); + + /* length of OID */ + put_byte(s->pktout, s->gss_buf.length); + + put_data(s->pktout, s->gss_buf.value, s->gss_buf.length); + pq_push(s->ppl.out_pq, s->pktout); + crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_RESPONSE) { + ppl_logevent(("GSSAPI authentication request refused")); + pq_push_front(s->ppl.in_pq, pktin); + continue; + } + + /* check returned packet ... */ + + data = get_string(pktin); + s->gss_rcvtok.value = (char *)data.ptr; + s->gss_rcvtok.length = data.len; + if (s->gss_rcvtok.length != s->gss_buf.length + 2 || + ((char *)s->gss_rcvtok.value)[0] != SSH2_GSS_OIDTYPE || + ((char *)s->gss_rcvtok.value)[1] != s->gss_buf.length || + memcmp((char *)s->gss_rcvtok.value + 2, + s->gss_buf.value,s->gss_buf.length) ) { + ppl_logevent(("GSSAPI authentication - wrong response " + "from server")); + continue; + } + + /* Import server name if not cached from KEX */ + if (s->shgss->srv_name == GSS_C_NO_NAME) { + s->gss_stat = s->shgss->lib->import_name( + s->shgss->lib, s->fullhostname, &s->shgss->srv_name); + if (s->gss_stat != SSH_GSS_OK) { + if (s->gss_stat == SSH_GSS_BAD_HOST_NAME) + ppl_logevent(("GSSAPI import name failed -" + " Bad service name")); + else + ppl_logevent(("GSSAPI import name failed")); + continue; + } + } + + /* Allocate our gss_ctx */ + s->gss_stat = s->shgss->lib->acquire_cred( + s->shgss->lib, &s->shgss->ctx, NULL); + if (s->gss_stat != SSH_GSS_OK) { + ppl_logevent(("GSSAPI authentication failed to get " + "credentials")); + continue; + } + + /* initial tokens are empty */ + SSH_GSS_CLEAR_BUF(&s->gss_rcvtok); + SSH_GSS_CLEAR_BUF(&s->gss_sndtok); + + /* now enter the loop */ + do { + /* + * When acquire_cred yields no useful expiration, go with + * the service ticket expiration. + */ + s->gss_stat = s->shgss->lib->init_sec_context + (s->shgss->lib, + &s->shgss->ctx, + s->shgss->srv_name, + s->gssapi_fwd, + &s->gss_rcvtok, + &s->gss_sndtok, + NULL, + NULL); + + if (s->gss_stat!=SSH_GSS_S_COMPLETE && + s->gss_stat!=SSH_GSS_S_CONTINUE_NEEDED) { + ppl_logevent(("GSSAPI authentication initialisation " + "failed")); + + if (s->shgss->lib->display_status(s->shgss->lib, + s->shgss->ctx, &s->gss_buf) == SSH_GSS_OK) { + ppl_logevent(("%s", (char *)s->gss_buf.value)); + sfree(s->gss_buf.value); + } + + pq_push_front(s->ppl.in_pq, pktin); + break; + } + ppl_logevent(("GSSAPI authentication initialised")); + + /* + * Client and server now exchange tokens until GSSAPI + * no longer says CONTINUE_NEEDED + */ + if (s->gss_sndtok.length != 0) { + s->pktout = + ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_GSSAPI_TOKEN); + put_string(s->pktout, + s->gss_sndtok.value, s->gss_sndtok.length); + pq_push(s->ppl.out_pq, s->pktout); + s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok); + } + + if (s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED) { + crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_TOKEN) { + ppl_logevent(("GSSAPI authentication -" + " bad server response")); + s->gss_stat = SSH_GSS_FAILURE; + pq_push_front(s->ppl.in_pq, pktin); + break; + } + data = get_string(pktin); + s->gss_rcvtok.value = (char *)data.ptr; + s->gss_rcvtok.length = data.len; + } + } while (s-> gss_stat == SSH_GSS_S_CONTINUE_NEEDED); + + if (s->gss_stat != SSH_GSS_OK) { + s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx); + continue; + } + ppl_logevent(("GSSAPI authentication loop finished OK")); + + /* Now send the MIC */ + + s->pktout = ssh2_userauth_gss_packet(s, "gssapi-with-mic"); + pq_push(s->ppl.out_pq, s->pktout); + + s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx); + continue; +#endif + } else if (s->can_keyb_inter && !s->kbd_inter_refused) { + + /* + * Keyboard-interactive authentication. + */ + + s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; + + s->ppl.bpp->pls->actx = SSH2_PKTCTX_KBDINTER; + + s->pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); + put_stringz(s->pktout, s->username); + put_stringz(s->pktout, "ssh-connection"); + /* service requested */ + put_stringz(s->pktout, "keyboard-interactive"); + /* method */ + put_stringz(s->pktout, ""); /* lang */ + put_stringz(s->pktout, ""); /* submethods */ + pq_push(s->ppl.out_pq, s->pktout); + + ppl_logevent(("Attempting keyboard-interactive authentication")); + + crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_USERAUTH_INFO_REQUEST) { + /* Server is not willing to do keyboard-interactive + * at all (or, bizarrely but legally, accepts the + * user without actually issuing any prompts). + * Give up on it entirely. */ + pq_push_front(s->ppl.in_pq, pktin); + s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET; + s->kbd_inter_refused = TRUE; /* don't try it again */ + continue; + } + + /* + * Loop while the server continues to send INFO_REQUESTs. + */ + while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { + + ptrlen name, inst; + int i; + + /* + * We've got a fresh USERAUTH_INFO_REQUEST. + * Get the preamble and start building a prompt. + */ + name = get_string(pktin); + inst = get_string(pktin); + get_string(pktin); /* skip language tag */ + s->cur_prompt = new_prompts(s->ppl.frontend); + s->cur_prompt->to_server = TRUE; + + /* + * Get any prompt(s) from the packet. + */ + s->num_prompts = get_uint32(pktin); + for (i = 0; i < s->num_prompts; i++) { + ptrlen prompt; + int echo; + static char noprompt[] = + ": "; + + prompt = get_string(pktin); + echo = get_bool(pktin); + if (!prompt.len) { + prompt.ptr = noprompt; + prompt.len = lenof(noprompt)-1; + } + add_prompt(s->cur_prompt, mkstr(prompt), echo); + } + + if (name.len) { + /* FIXME: better prefix to distinguish from + * local prompts? */ + s->cur_prompt->name = + dupprintf("SSH server: %.*s", PTRLEN_PRINTF(name)); + s->cur_prompt->name_reqd = TRUE; + } else { + s->cur_prompt->name = + dupstr("SSH server authentication"); + s->cur_prompt->name_reqd = FALSE; + } + /* We add a prefix to try to make it clear that a prompt + * has come from the server. + * FIXME: ugly to print "Using..." in prompt _every_ + * time round. Can this be done more subtly? */ + /* Special case: for reasons best known to themselves, + * some servers send k-i requests with no prompts and + * nothing to display. Keep quiet in this case. */ + if (s->num_prompts || name.len || inst.len) { + s->cur_prompt->instruction = + dupprintf("Using keyboard-interactive " + "authentication.%s%.*s", + inst.len ? "\n" : "", + PTRLEN_PRINTF(inst)); + s->cur_prompt->instr_reqd = TRUE; + } else { + s->cur_prompt->instr_reqd = FALSE; + } + + /* + * Display any instructions, and get the user's + * response(s). + */ + s->userpass_ret = get_userpass_input(s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = get_userpass_input( + s->cur_prompt, s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = TRUE; + crReturnV; + s->want_user_input = FALSE; + } + if (!s->userpass_ret) { + /* + * Failed to get responses. Terminate. + */ + free_prompts(s->cur_prompt); + ssh_bpp_queue_disconnect( + s->ppl.bpp, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); + ssh_user_close(s->ppl.ssh, "User aborted during " + "keyboard-interactive authentication"); + return; + } + + /* + * Send the response(s) to the server. + */ + s->pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE); + put_uint32(s->pktout, s->num_prompts); + for (i=0; i < s->num_prompts; i++) { + put_stringz(s->pktout, + s->cur_prompt->prompts[i]->result); + } + s->pktout->minlen = 256; + pq_push(s->ppl.out_pq, s->pktout); + + /* + * Free the prompts structure from this iteration. + * If there's another, a new one will be allocated + * when we return to the top of this while loop. + */ + free_prompts(s->cur_prompt); + + /* + * Get the next packet in case it's another + * INFO_REQUEST. + */ + crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); + + } + + /* + * We should have SUCCESS or FAILURE now. + */ + pq_push_front(s->ppl.in_pq, pktin); + + } else if (s->can_passwd) { + + /* + * Plain old password authentication. + */ + int changereq_first_time; /* not live over crReturn */ + + s->ppl.bpp->pls->actx = SSH2_PKTCTX_PASSWORD; + + s->cur_prompt = new_prompts(s->ppl.frontend); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH password"); + add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ", + s->username, s->hostname), + FALSE); + + s->userpass_ret = get_userpass_input(s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = get_userpass_input( + s->cur_prompt, s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = TRUE; + crReturnV; + s->want_user_input = FALSE; + } + if (!s->userpass_ret) { + /* + * Failed to get responses. Terminate. + */ + free_prompts(s->cur_prompt); + ssh_bpp_queue_disconnect( + s->ppl.bpp, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); + ssh_user_close(s->ppl.ssh, "User aborted during password " + "authentication"); + return; + } + /* + * Squirrel away the password. (We may need it later if + * asked to change it.) + */ + s->password = dupstr(s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); + + /* + * Send the password packet. + * + * We pad out the password packet to 256 bytes to make + * it harder for an attacker to find the length of the + * user's password. + * + * Anyone using a password longer than 256 bytes + * probably doesn't have much to worry about from + * people who find out how long their password is! + */ + s->pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); + put_stringz(s->pktout, s->username); + put_stringz(s->pktout, "ssh-connection"); + /* service requested */ + put_stringz(s->pktout, "password"); + put_bool(s->pktout, FALSE); + put_stringz(s->pktout, s->password); + s->pktout->minlen = 256; + pq_push(s->ppl.out_pq, s->pktout); + ppl_logevent(("Sent password")); + s->type = AUTH_TYPE_PASSWORD; + + /* + * Wait for next packet, in case it's a password change + * request. + */ + crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); + changereq_first_time = TRUE; + + while (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) { + + /* + * We're being asked for a new password + * (perhaps not for the first time). + * Loop until the server accepts it. + */ + + int got_new = FALSE; /* not live over crReturn */ + ptrlen prompt; /* not live over crReturn */ + + { + const char *msg; + if (changereq_first_time) + msg = "Server requested password change"; + else + msg = "Server rejected new password"; + ppl_logevent(("%s", msg)); + ppl_printf(("%s\r\n", msg)); + } + + prompt = get_string(pktin); + + s->cur_prompt = new_prompts(s->ppl.frontend); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("New SSH password"); + s->cur_prompt->instruction = mkstr(prompt); + s->cur_prompt->instr_reqd = TRUE; + /* + * There's no explicit requirement in the protocol + * for the "old" passwords in the original and + * password-change messages to be the same, and + * apparently some Cisco kit supports password change + * by the user entering a blank password originally + * and the real password subsequently, so, + * reluctantly, we prompt for the old password again. + * + * (On the other hand, some servers don't even bother + * to check this field.) + */ + add_prompt(s->cur_prompt, + dupstr("Current password (blank for previously entered password): "), + FALSE); + add_prompt(s->cur_prompt, dupstr("Enter new password: "), + FALSE); + add_prompt(s->cur_prompt, dupstr("Confirm new password: "), + FALSE); + + /* + * Loop until the user manages to enter the same + * password twice. + */ + while (!got_new) { + s->userpass_ret = get_userpass_input( + s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = get_userpass_input( + s->cur_prompt, s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = TRUE; + crReturnV; + s->want_user_input = FALSE; + } + if (!s->userpass_ret) { + /* + * Failed to get responses. Terminate. + */ + /* burn the evidence */ + free_prompts(s->cur_prompt); + smemclr(s->password, strlen(s->password)); + sfree(s->password); + ssh_bpp_queue_disconnect( + s->ppl.bpp, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); + ssh_user_close(s->ppl.ssh, "User aborted during " + "password changing"); + return; + } + + /* + * If the user specified a new original password + * (IYSWIM), overwrite any previously specified + * one. + * (A side effect is that the user doesn't have to + * re-enter it if they louse up the new password.) + */ + if (s->cur_prompt->prompts[0]->result[0]) { + smemclr(s->password, strlen(s->password)); + /* burn the evidence */ + sfree(s->password); + s->password = + dupstr(s->cur_prompt->prompts[0]->result); + } + + /* + * Check the two new passwords match. + */ + got_new = (strcmp(s->cur_prompt->prompts[1]->result, + s->cur_prompt->prompts[2]->result) + == 0); + if (!got_new) + /* They don't. Silly user. */ + ppl_printf(("Passwords do not match\r\n")); + + } + + /* + * Send the new password (along with the old one). + * (see above for padding rationale) + */ + s->pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); + put_stringz(s->pktout, s->username); + put_stringz(s->pktout, "ssh-connection"); + /* service requested */ + put_stringz(s->pktout, "password"); + put_bool(s->pktout, TRUE); + put_stringz(s->pktout, s->password); + put_stringz(s->pktout, + s->cur_prompt->prompts[1]->result); + free_prompts(s->cur_prompt); + s->pktout->minlen = 256; + pq_push(s->ppl.out_pq, s->pktout); + ppl_logevent(("Sent new password")); + + /* + * Now see what the server has to say about it. + * (If it's CHANGEREQ again, it's not happy with the + * new password.) + */ + crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); + changereq_first_time = FALSE; + + } + + /* + * We need to reexamine the current pktin at the top + * of the loop. Either: + * - we weren't asked to change password at all, in + * which case it's a SUCCESS or FAILURE with the + * usual meaning + * - we sent a new password, and the server was + * either OK with it (SUCCESS or FAILURE w/partial + * success) or unhappy with the _old_ password + * (FAILURE w/o partial success) + * In any of these cases, we go back to the top of + * the loop and start again. + */ + pq_push_front(s->ppl.in_pq, pktin); + + /* + * We don't need the old password any more, in any + * case. Burn the evidence. + */ + smemclr(s->password, strlen(s->password)); + sfree(s->password); + + } else { + ssh_bpp_queue_disconnect( + s->ppl.bpp, + "No supported authentication methods available", + SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE); + ssh_sw_abort(s->ppl.ssh, "No supported authentication methods " + "available (server sent: %.*s)", + PTRLEN_PRINTF(methods)); + return; + } + + } + try_new_username:; + } + + userauth_success: + /* + * We've just received USERAUTH_SUCCESS, and we haven't sent + * any packets since. Signal the transport layer to consider + * doing an immediate rekey, if it has any reason to want to. + */ + ssh2_transport_notify_auth_done(s->transport_layer); + + /* + * 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; +} + +static void ssh2_userauth_add_session_id( + struct ssh2_userauth_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_agent_query( + struct ssh2_userauth_state *s, strbuf *req) +{ + void *response; + int response_len; + + sfree(s->agent_response_to_free); + s->agent_response_to_free = NULL; + + s->auth_agent_query = agent_query(req, &response, &response_len, + ssh2_userauth_agent_callback, s); + if (!s->auth_agent_query) + ssh2_userauth_agent_callback(s, response, response_len); +} + +static void ssh2_userauth_agent_callback(void *uav, void *reply, int replylen) +{ + struct ssh2_userauth_state *s = (struct ssh2_userauth_state *)uav; + + s->auth_agent_query = NULL; + s->agent_response_to_free = reply; + s->agent_response = make_ptrlen(reply, replylen); + + queue_idempotent_callback(&s->ppl.ic_process_queue); +} + +/* + * Helper function to add an SSH-2 signature blob to a packet. Expects + * to be shown the public key blob as well as the signature blob. + * Normally just appends the sig blob unmodified as a string, except + * that it optionally breaks it open and fiddle with it to work around + * BUG_SSH2_RSA_PADDING. + */ +static void ssh2_userauth_add_sigblob( + struct ssh2_userauth_state *s, PktOut *pkt, ptrlen pkblob, ptrlen sigblob) +{ + BinarySource pk[1], sig[1]; + BinarySource_BARE_INIT(pk, pkblob.ptr, pkblob.len); + BinarySource_BARE_INIT(sig, sigblob.ptr, sigblob.len); + + /* dmemdump(pkblob, pkblob_len); */ + /* dmemdump(sigblob, sigblob_len); */ + + /* + * See if this is in fact an ssh-rsa signature and a buggy + * server; otherwise we can just do this the easy way. + */ + if ((s->ppl.remote_bugs & BUG_SSH2_RSA_PADDING) && + ptrlen_eq_string(get_string(pk), "ssh-rsa") && + ptrlen_eq_string(get_string(sig), "ssh-rsa")) { + ptrlen mod_mp, sig_mp; + size_t sig_prefix_len; + + /* + * Find the modulus and signature integers. + */ + get_string(pk); /* skip over exponent */ + mod_mp = get_string(pk); /* remember modulus */ + sig_prefix_len = sig->pos; + sig_mp = get_string(sig); + if (get_err(pk) || get_err(sig)) + goto give_up; + + /* + * Find the byte length of the modulus, not counting leading + * zeroes. + */ + while (mod_mp.len > 0 && *(const char *)mod_mp.ptr == 0) { + mod_mp.len--; + mod_mp.ptr = (const char *)mod_mp.ptr + 1; + } + + /* debug(("modulus length is %d\n", len)); */ + /* debug(("signature length is %d\n", siglen)); */ + + if (mod_mp.len != sig_mp.len) { + strbuf *substr = strbuf_new(); + put_data(substr, sigblob.ptr, sig_prefix_len); + put_uint32(substr, mod_mp.len); + put_padding(substr, mod_mp.len - sig_mp.len, 0); + put_data(substr, sig_mp.ptr, sig_mp.len); + put_stringsb(pkt, substr); + return; + } + + /* Otherwise fall through and do it the easy way. We also come + * here as a fallback if we discover above that the key blob + * is misformatted in some way. */ + give_up:; + } + + put_stringpl(pkt, sigblob); +} + +#ifndef NO_GSSAPI +static PktOut *ssh2_userauth_gss_packet( + struct ssh2_userauth_state *s, const char *authtype) +{ + strbuf *sb; + PktOut *p; + Ssh_gss_buf buf; + Ssh_gss_buf mic; + + /* + * The mic is computed over the session id + intended + * USERAUTH_REQUEST packet. + */ + sb = strbuf_new(); + put_stringpl(sb, s->session_id); + put_byte(sb, SSH2_MSG_USERAUTH_REQUEST); + put_stringz(sb, s->username); + put_stringz(sb, "ssh-connection"); + put_stringz(sb, authtype); + + /* Compute the mic */ + buf.value = sb->s; + buf.length = sb->len; + s->shgss->lib->get_mic(s->shgss->lib, s->shgss->ctx, &buf, &mic); + strbuf_free(sb); + + /* Now we can build the real packet */ + if (strcmp(authtype, "gssapi-with-mic") == 0) { + p = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_GSSAPI_MIC); + } else { + p = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); + put_stringz(p, s->username); + put_stringz(p, "ssh-connection"); + put_stringz(p, authtype); + } + put_string(p, mic.value, mic.length); + + return p; +} +#endif + +static int ssh2_userauth_get_specials( + PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) +{ + /* No specials provided by this layer. */ + return FALSE; +} + +static void ssh2_userauth_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg) +{ + /* No specials provided by this layer. */ +} + +static int ssh2_userauth_want_user_input(PacketProtocolLayer *ppl) +{ + struct ssh2_userauth_state *s = + FROMFIELD(ppl, struct ssh2_userauth_state, ppl); + return s->want_user_input; +} + +static void ssh2_userauth_got_user_input(PacketProtocolLayer *ppl) +{ + struct ssh2_userauth_state *s = + FROMFIELD(ppl, struct ssh2_userauth_state, ppl); + if (s->want_user_input) + queue_idempotent_callback(&s->ppl.ic_process_queue); +} + +static void ssh2_userauth_reconfigure(PacketProtocolLayer *ppl, Conf *conf) +{ + struct ssh2_userauth_state *s = + FROMFIELD(ppl, struct ssh2_userauth_state, ppl); + ssh_ppl_reconfigure(s->successor_layer, conf); +} diff --git a/sshbpp.h b/sshbpp.h index b3eb9985..831145e6 100644 --- a/sshbpp.h +++ b/sshbpp.h @@ -17,6 +17,7 @@ struct BinaryPacketProtocolVtable { struct BinaryPacketProtocol { const struct BinaryPacketProtocolVtable *vt; bufchain *in_raw, *out_raw; + int input_eof; /* set this if in_raw will never be added to again */ PktInQueue in_pq; PktOutQueue out_pq; PacketLogSettings *pls; @@ -34,8 +35,11 @@ struct BinaryPacketProtocol { int remote_bugs; - int seen_disconnect; - char *error; + /* Set this if remote connection closure should not generate an + * error message (either because it's not to be treated as an + * error at all, or because some other error message has already + * been emitted). */ + int expect_close; }; #define ssh_bpp_handle_input(bpp) ((bpp)->vt->handle_input(bpp)) diff --git a/sshcommon.c b/sshcommon.c index 5a2270b4..ac4a6715 100644 --- a/sshcommon.c +++ b/sshcommon.c @@ -9,6 +9,7 @@ #include "putty.h" #include "ssh.h" #include "sshbpp.h" +#include "sshppl.h" #include "sshchan.h" /* ---------------------------------------------------------------------- @@ -630,6 +631,72 @@ const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type) #undef TRANSLATE_KEX #undef TRANSLATE_AUTH +/* ---------------------------------------------------------------------- + * Common helper function for clients and implementations of + * PacketProtocolLayer. + */ + +void ssh_logevent_and_free(void *frontend, char *message) +{ + logevent(frontend, message); + sfree(message); +} + +void ssh_ppl_replace(PacketProtocolLayer *old, PacketProtocolLayer *new) +{ + new->bpp = old->bpp; + ssh_ppl_setup_queues(new, old->in_pq, old->out_pq); + new->selfptr = old->selfptr; + new->user_input = old->user_input; + new->frontend = old->frontend; + new->ssh = old->ssh; + + *new->selfptr = new; + ssh_ppl_free(old); + + /* The new layer might need to be the first one that sends a + * packet, so trigger a call to its main coroutine immediately. If + * it doesn't need to go first, the worst that will do is return + * straight away. */ + queue_idempotent_callback(&new->ic_process_queue); +} + +void ssh_ppl_free(PacketProtocolLayer *ppl) +{ + delete_callbacks_for_context(ppl); + ppl->vt->free(ppl); +} + +static void ssh_ppl_ic_process_queue_callback(void *context) +{ + PacketProtocolLayer *ppl = (PacketProtocolLayer *)context; + ssh_ppl_process_queue(ppl); +} + +void ssh_ppl_setup_queues(PacketProtocolLayer *ppl, + PktInQueue *inq, PktOutQueue *outq) +{ + ppl->in_pq = inq; + ppl->out_pq = outq; + ppl->in_pq->pqb.ic = &ppl->ic_process_queue; + ppl->ic_process_queue.fn = ssh_ppl_ic_process_queue_callback; + ppl->ic_process_queue.ctx = ppl; + + /* If there's already something on the input queue, it will want + * handling immediately. */ + if (pq_peek(ppl->in_pq)) + queue_idempotent_callback(&ppl->ic_process_queue); +} + +void ssh_ppl_user_output_string_and_free(PacketProtocolLayer *ppl, char *text) +{ + /* Messages sent via this function are from the SSH layer, not + * from the server-side process, so they always have the stderr + * flag set. */ + from_backend(ppl->frontend, TRUE, text, strlen(text)); + sfree(text); +} + /* ---------------------------------------------------------------------- * Common helper functions for clients and implementations of * BinaryPacketProtocol. @@ -651,6 +718,7 @@ void ssh_bpp_common_setup(BinaryPacketProtocol *bpp) { pq_in_init(&bpp->in_pq); pq_out_init(&bpp->out_pq); + bpp->input_eof = FALSE; bpp->ic_in_raw.fn = ssh_bpp_input_raw_data_callback; bpp->ic_in_raw.ctx = bpp; bpp->ic_out_pq.fn = ssh_bpp_output_packet_callback; @@ -764,3 +832,37 @@ int verify_ssh_manual_host_key( return 0; } + +/* ---------------------------------------------------------------------- + * Common get_specials function for the two SSH-1 layers. + */ + +int ssh1_common_get_specials( + PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) +{ + /* + * Don't bother offering IGNORE if we've decided the remote + * won't cope with it, since we wouldn't bother sending it if + * asked anyway. + */ + if (!(ppl->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) { + add_special(ctx, "IGNORE message", SS_NOP, 0); + return TRUE; + } + + return FALSE; +} + +/* ---------------------------------------------------------------------- + * Other miscellaneous utility functions. + */ + +void free_rportfwd(struct ssh_rportfwd *rpf) +{ + if (rpf) { + sfree(rpf->log_description); + sfree(rpf->shost); + sfree(rpf->dhost); + sfree(rpf); + } +} diff --git a/sshgss.h b/sshgss.h index 7b2ed323..b1c04a35 100644 --- a/sshgss.h +++ b/sshgss.h @@ -200,6 +200,18 @@ struct ssh_gss_library { void *handle; }; +/* + * State that has to be shared between all GSSAPI-using parts of the + * same SSH connection, in particular between GSS key exchange and the + * subsequent trivial userauth method that reuses its output. + */ +struct ssh_connection_shared_gss_state { + struct ssh_gss_liblist *libs; + struct ssh_gss_library *lib; + Ssh_gss_name srv_name; + Ssh_gss_ctx ctx; +}; + #endif /* NO_GSSAPI */ #endif /*PUTTY_SSHGSS_H*/ diff --git a/sshppl.h b/sshppl.h new file mode 100644 index 00000000..29944df3 --- /dev/null +++ b/sshppl.h @@ -0,0 +1,144 @@ +/* + * Abstraction of the various layers of SSH packet-level protocol, + * general enough to take in all three of the main SSH-2 layers and + * both of the SSH-1 phases. + */ + +#ifndef PUTTY_SSHPPL_H +#define PUTTY_SSHPPL_H + +typedef void (*packet_handler_fn_t)(PacketProtocolLayer *ppl, PktIn *pktin); +typedef void (*add_special_fn_t)( + void *ctx, const char *text, SessionSpecialCode code, int arg); + +struct PacketProtocolLayerVtable { + void (*free)(PacketProtocolLayer *); + void (*process_queue)(PacketProtocolLayer *ppl); + int (*get_specials)( + PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx); + void (*special_cmd)( + PacketProtocolLayer *ppl, SessionSpecialCode code, int arg); + int (*want_user_input)(PacketProtocolLayer *ppl); + void (*got_user_input)(PacketProtocolLayer *ppl); + void (*reconfigure)(PacketProtocolLayer *ppl, Conf *conf); + + /* Protocol-level name of this layer. */ + const char *name; +}; + +struct PacketProtocolLayer { + const struct PacketProtocolLayerVtable *vt; + + /* Link to the underlying SSH BPP. */ + BinaryPacketProtocol *bpp; + + /* Queue from which the layer receives its input packets, and one + * to put its output packets on. */ + PktInQueue *in_pq; + PktOutQueue *out_pq; + + /* Idempotent callback that in_pq will be linked to, causing a + * call to the process_queue method. in_pq points to this, so it + * will be automatically triggered by pushing things on the + * layer's input queue, but it can also be triggered on purpose. */ + IdempotentCallback ic_process_queue; + + /* Owner's pointer to this layer. Permits a layer to unilaterally + * abdicate in favour of a replacement, by overwriting this + * pointer and then freeing itself. */ + PacketProtocolLayer **selfptr; + + /* Bufchain of keyboard input from the user, for login prompts and + * similar. */ + bufchain *user_input; + + /* Logging and error-reporting facilities. */ + void *frontend; /* for logevent, dialog boxes etc */ + Ssh ssh; /* for session termination + assorted connection-layer ops */ + + /* Known bugs in the remote implementation. */ + unsigned remote_bugs; +}; + +#define ssh_ppl_process_queue(ppl) ((ppl)->vt->process_queue(ppl)) +#define ssh_ppl_get_specials(ppl, add, ctx) \ + ((ppl)->vt->get_specials(ppl, add, ctx)) +#define ssh_ppl_special_cmd(ppl, code, arg) \ + ((ppl)->vt->special_cmd(ppl, code, arg)) +#define ssh_ppl_want_user_input(ppl) ((ppl)->vt->want_user_input(ppl)) +#define ssh_ppl_got_user_input(ppl) ((ppl)->vt->got_user_input(ppl)) +#define ssh_ppl_reconfigure(ppl, conf) ((ppl)->vt->reconfigure(ppl, conf)) + +/* ssh_ppl_free is more than just a macro wrapper on the vtable; it + * does centralised parts of the freeing too. */ +void ssh_ppl_free(PacketProtocolLayer *ppl); + +/* Helper routine to point a PPL at its input and output queues. Also + * sets up the IdempotentCallback on the input queue to trigger a call + * to process_queue whenever packets are added to it. */ +void ssh_ppl_setup_queues(PacketProtocolLayer *ppl, + PktInQueue *inq, PktOutQueue *outq); + +/* Routine a PPL can call to abdicate in favour of a replacement, by + * overwriting ppl->selfptr. Has the side effect of freeing 'old', so + * if 'old' actually called this (which is likely) then it should + * avoid dereferencing itself on return from this function! */ +void ssh_ppl_replace(PacketProtocolLayer *old, PacketProtocolLayer *new); + +PacketProtocolLayer *ssh1_login_new( + Conf *conf, const char *host, int port, + PacketProtocolLayer *successor_layer); +PacketProtocolLayer *ssh1_connection_new( + Ssh ssh, Conf *conf, ConnectionLayer **cl_out); + +struct DataTransferStats; +struct ssh_connection_shared_gss_state; +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); +PacketProtocolLayer *ssh2_userauth_new( + PacketProtocolLayer *successor_layer, + const char *hostname, const char *fullhostname, + Filename *keyfile, int tryagent, + const char *default_username, int change_username, + int try_ki_auth, + int try_gssapi_auth, int try_gssapi_kex_auth, + int gssapi_fwd, struct ssh_connection_shared_gss_state *shgss); +PacketProtocolLayer *ssh2_connection_new( + Ssh ssh, ssh_sharing_state *connshare, int is_simple, + Conf *conf, const char *peer_verstring, ConnectionLayer **cl_out); + +/* Can't put this in the userauth constructor without having a + * dependency loop at setup time (transport and userauth can't _both_ + * be constructed second and given a pointer to the other). */ +void ssh2_userauth_set_transport_layer(PacketProtocolLayer *userauth, + PacketProtocolLayer *transport); + +/* Convenience macro for protocol layers to send formatted strings to + * the Event Log. Assumes a function parameter called 'ppl' is in + * scope, and takes a double pair of parens because it passes a whole + * argument list to dupprintf. */ +#define ppl_logevent(params) ( \ + logevent_and_free((ppl)->frontend, dupprintf params)) + +/* Convenience macro for protocol layers to send formatted strings to + * the terminal. Also expects 'ppl' to be in scope and takes double + * parens. */ +#define ppl_printf(params) \ + ssh_ppl_user_output_string_and_free(ppl, dupprintf params) +void ssh_ppl_user_output_string_and_free(PacketProtocolLayer *ppl, char *text); + +/* Methods for userauth to communicate back to the transport layer */ +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); + +/* Shared get_specials method between the two ssh1 layers */ +int ssh1_common_get_specials(PacketProtocolLayer *, add_special_fn_t, void *); + +#endif /* PUTTY_SSHPPL_H */ diff --git a/sshverstring.c b/sshverstring.c index ffead60c..612c3194 100644 --- a/sshverstring.c +++ b/sshverstring.c @@ -204,7 +204,10 @@ static void ssh_verstring_send(struct ssh_verstring_state *s) #define BPP_WAITFOR(minlen) do \ { \ crMaybeWaitUntilV( \ + s->bpp.input_eof || \ bufchain_size(s->bpp.in_raw) >= (minlen)); \ + if (s->bpp.input_eof) \ + goto eof; \ } while (0) void ssh_verstring_handle_input(BinaryPacketProtocol *bpp) @@ -359,13 +362,14 @@ void ssh_verstring_handle_input(BinaryPacketProtocol *bpp) * Unable to agree on a major protocol version at all. */ if (!ssh_version_includes_v2(s->our_protoversion)) { - s->bpp.error = dupstr( - "SSH protocol version 1 required by our configuration " - "but not provided by remote"); + ssh_sw_abort(s->bpp.ssh, + "SSH protocol version 1 required by our " + "configuration but not provided by remote"); } else { - s->bpp.error = dupstr( - "SSH protocol version 2 required by our configuration " - "but remote only provides (old, insecure) SSH-1"); + ssh_sw_abort(s->bpp.ssh, + "SSH protocol version 2 required by our " + "configuration but remote only provides " + "(old, insecure) SSH-1"); } crStopV; } @@ -387,6 +391,11 @@ void ssh_verstring_handle_input(BinaryPacketProtocol *bpp) * done. */ s->receiver->got_ssh_version(s->receiver, s->major_protoversion); + return; + + eof: + ssh_remote_error(s->bpp.ssh, + "Server unexpectedly closed network connection"); crFinishV; }