From 2ca0070f891c464d4274bdc1c69709fd6b694bd7 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Mon, 24 Sep 2018 18:28:16 +0100 Subject: [PATCH] Move most of ssh.c out into separate source files. I've tried to separate out as many individually coherent changes from this work as I could into their own commits, but here's where I run out and have to commit the rest of this major refactoring as a big-bang change. Most of ssh.c is now no longer in ssh.c: all five of the main coroutines that handle layers of the SSH-1 and SSH-2 protocols now each have their own source file to live in, and a lot of the supporting functions have moved into the appropriate one of those too. The new abstraction is a vtable called 'PacketProtocolLayer', which has an input and output packet queue. Each layer's main coroutine is invoked from the method ssh_ppl_process_queue(), which is usually (though not exclusively) triggered automatically when things are pushed on the input queue. In SSH-2, the base layer is the transport protocol, and it contains a pair of subsidiary queues by which it passes some of its packets to the higher SSH-2 layers - first userauth and then connection, which are peers at the same level, with the former abdicating in favour of the latter at the appropriate moment. SSH-1 is simpler: the whole login phase of the protocol (crypto setup and authentication) is all in one module, and since SSH-1 has no repeat key exchange, that setup layer abdicates in favour of the connection phase when it's done. ssh.c itself is now about a tenth of its old size (which all by itself is cause for celebration!). Its main job is to set up all the layers, hook them up to each other and to the BPP, and to funnel data back and forth between that collection of modules and external things such as the network and the terminal. Once it's set up a collection of packet protocol layers, it communicates with them partly by calling methods of the base layer (and if that's ssh2transport then it will delegate some functionality to the corresponding methods of its higher layer), and partly by talking directly to the connection layer no matter where it is in the stack by means of the separate ConnectionLayer vtable which I introduced in commit 8001dd4cb, and to which I've now added quite a few extra methods replacing services that used to be internal function calls within ssh.c. (One effect of this is that the SSH-1 and SSH-2 channel storage is now no longer shared - there are distinct struct types ssh1_channel and ssh2_channel. That means a bit more code duplication, but on the plus side, a lot fewer confusing conditionals in the middle of half-shared functions, and less risk of a piece of SSH-1 escaping into SSH-2 or vice versa, which I remember has happened at least once in the past.) The bulk of this commit introduces the five new source files, their common header sshppl.h and some shared supporting routines in sshcommon.c, and rewrites nearly all of ssh.c itself. But it also includes a couple of other changes that I couldn't separate easily enough: Firstly, there's a new handling for socket EOF, in which ssh.c sets an 'input_eof' flag in the BPP, and that responds by checking a flag that tells it whether to report the EOF as an error or not. (This is the main reason for those new BPP_READ / BPP_WAITFOR macros - they can check the EOF flag every time the coroutine is resumed.) Secondly, the error reporting itself is changed around again. I'd expected to put some data fields in the public PacketProtocolLayer structure that it could set to report errors in the same way as the BPPs have been doing, but in the end, I decided propagating all those data fields around was a pain and that even the BPPs shouldn't have been doing it that way. So I've reverted to a system where everything calls back to functions in ssh.c itself to report any connection- ending condition. But there's a new family of those functions, categorising the possible such conditions by semantics, and each one has a different set of detailed effects (e.g. how rudely to close the network connection, what exit status should be passed back to the whole application, whether to send a disconnect message and/or display a GUI error box). I don't expect this to be immediately perfect: of course, the code has been through a big upheaval, new bugs are expected, and I haven't been able to do a full job of testing (e.g. I haven't tested every auth or kex method). But I've checked that it _basically_ works - both SSH protocols, all the different kinds of forwarding channel, more than one auth method, Windows and Linux, connection sharing - and I think it's now at the point where the easiest way to find further bugs is to let it out into the wild and see what users can spot. --- Recipe | 1 + defs.h | 1 + ssh.c | 9632 +++------------------------------------------- ssh.h | 56 +- ssh1bpp.c | 35 +- ssh1connection.c | 1211 ++++++ ssh1login.c | 1206 ++++++ ssh2bpp-bare.c | 25 +- ssh2bpp.c | 36 +- ssh2connection.c | 2501 ++++++++++++ ssh2transport.c | 2967 ++++++++++++++ ssh2userauth.c | 1673 ++++++++ sshbpp.h | 8 +- sshcommon.c | 102 + sshgss.h | 12 + sshppl.h | 144 + sshverstring.c | 21 +- 17 files changed, 10421 insertions(+), 9210 deletions(-) create mode 100644 ssh1connection.c create mode 100644 ssh1login.c create mode 100644 ssh2connection.c create mode 100644 ssh2transport.c create mode 100644 ssh2userauth.c create mode 100644 sshppl.h 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; }