+ c = find234(ssh->channels, &i, ssh_channelfind);
+ if (c) {
+ int bufsize = 0;
+ switch (c->type) {
+ case CHAN_X11:
+ bufsize = x11_send(c->u.x11.s, p, len);
+ break;
+ case CHAN_SOCKDATA:
+ bufsize = pfd_send(c->u.pfd.s, p, len);
+ break;
+ case CHAN_AGENT:
+ /* Data for an agent message. Buffer it. */
+ while (len > 0) {
+ if (c->u.a.lensofar < 4) {
+ unsigned int l = min(4 - c->u.a.lensofar, (unsigned)len);
+ memcpy(c->u.a.msglen + c->u.a.lensofar, p,
+ l);
+ p += l;
+ len -= l;
+ c->u.a.lensofar += l;
+ }
+ if (c->u.a.lensofar == 4) {
+ c->u.a.totallen =
+ 4 + GET_32BIT(c->u.a.msglen);
+ c->u.a.message = snewn(c->u.a.totallen,
+ unsigned char);
+ memcpy(c->u.a.message, c->u.a.msglen, 4);
+ }
+ if (c->u.a.lensofar >= 4 && len > 0) {
+ unsigned int l =
+ min(c->u.a.totallen - c->u.a.lensofar,
+ (unsigned)len);
+ memcpy(c->u.a.message + c->u.a.lensofar, p,
+ l);
+ p += l;
+ len -= l;
+ c->u.a.lensofar += l;
+ }
+ if (c->u.a.lensofar == c->u.a.totallen) {
+ void *reply;
+ int replylen;
+ if (agent_query(c->u.a.message,
+ c->u.a.totallen,
+ &reply, &replylen,
+ ssh_agentf_callback, c))
+ ssh_agentf_callback(c, reply, replylen);
+ sfree(c->u.a.message);
+ c->u.a.lensofar = 0;
+ }
+ }
+ bufsize = 0; /* agent channels never back up */
+ break;
+ }
+ 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, struct Packet *pktin)
+{
+ ssh->exitcode = ssh_pkt_getuint32(pktin);
+ logeventf(ssh, "Server sent command exit status %d", ssh->exitcode);
+ send_packet(ssh, SSH1_CMSG_EXIT_CONFIRMATION, PKT_END);
+ /*
+ * 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);
+}
+
+/* Helper function to deal with sending tty modes for REQUEST_PTY */
+static void ssh1_send_ttymode(void *data, char *mode, char *val)
+{
+ struct Packet *pktout = (struct Packet *)data;
+ int i = 0;
+ unsigned int arg = 0;
+ while (strcmp(mode, ssh_ttymodes[i].mode) != 0) i++;
+ if (i == lenof(ssh_ttymodes)) return;
+ switch (ssh_ttymodes[i].type) {
+ case TTY_OP_CHAR:
+ arg = ssh_tty_parse_specchar(val);
+ break;
+ case TTY_OP_BOOL:
+ arg = ssh_tty_parse_boolean(val);
+ break;
+ }
+ ssh2_pkt_addbyte(pktout, ssh_ttymodes[i].opcode);
+ ssh2_pkt_addbyte(pktout, arg);
+}
+
+
+static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen,
+ struct Packet *pktin)
+{
+ 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 (conf_get_int(ssh->conf, CONF_agentfwd) && agent_exists()) {
+ logevent("Requesting agent forwarding");
+ send_packet(ssh, SSH1_CMSG_AGENT_REQUEST_FORWARDING, PKT_END);
+ do {
+ crReturnV;
+ } while (!pktin);
+ 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),
+ conf_get_int(ssh->conf, CONF_x11_auth), ssh->conf))) {
+ logevent("Requesting X11 forwarding");
+ /*
+ * Note that while we blank the X authentication data here, we don't
+ * take any special action to blank the start of an X11 channel,
+ * so using MIT-MAGIC-COOKIE-1 and actually opening an X connection
+ * without having session blanking enabled is likely to leak your
+ * cookie into the log.
+ */
+ if (ssh->v1_local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) {
+ send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING,
+ PKT_STR, ssh->x11disp->remoteauthprotoname,
+ PKTT_PASSWORD,
+ PKT_STR, ssh->x11disp->remoteauthdatastring,
+ PKTT_OTHER,
+ PKT_INT, ssh->x11disp->screennum,
+ PKT_END);
+ } else {
+ send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING,
+ PKT_STR, ssh->x11disp->remoteauthprotoname,
+ PKTT_PASSWORD,
+ PKT_STR, ssh->x11disp->remoteauthdatastring,
+ PKTT_OTHER,
+ PKT_END);
+ }
+ do {
+ crReturnV;
+ } while (!pktin);
+ 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;
+ }
+ }
+
+ ssh_setup_portfwd(ssh, ssh->conf);
+ ssh->packet_dispatch[SSH1_MSG_PORT_OPEN] = ssh1_msg_port_open;
+
+ if (!conf_get_int(ssh->conf, CONF_nopty)) {
+ struct Packet *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 = ssh1_pkt_init(SSH1_CMSG_REQUEST_PTY);
+ ssh_pkt_addstring(pkt, conf_get_str(ssh->conf, CONF_termtype));
+ ssh_pkt_adduint32(pkt, ssh->term_height);
+ ssh_pkt_adduint32(pkt, ssh->term_width);
+ ssh_pkt_adduint32(pkt, 0); /* width in pixels */
+ ssh_pkt_adduint32(pkt, 0); /* height in pixels */
+ parse_ttymodes(ssh, ssh1_send_ttymode, (void *)pkt);
+ ssh_pkt_addbyte(pkt, SSH1_TTY_OP_ISPEED);
+ ssh_pkt_adduint32(pkt, ssh->ispeed);
+ ssh_pkt_addbyte(pkt, SSH1_TTY_OP_OSPEED);
+ ssh_pkt_adduint32(pkt, ssh->ospeed);
+ ssh_pkt_addbyte(pkt, SSH_TTY_OP_END);
+ s_wrpkt(ssh, pkt);
+ ssh->state = SSH_STATE_INTERMED;
+ do {
+ crReturnV;
+ } while (!pktin);
+ 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)) {
+ send_packet(ssh, SSH1_CMSG_REQUEST_COMPRESSION, PKT_INT, 6, PKT_END);
+ do {
+ crReturnV;
+ } while (!pktin);
+ 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");
+ }
+ logevent("Started compression");
+ ssh->v1_compressing = TRUE;
+ ssh->cs_comp_ctx = zlib_compress_init();
+ logevent("Initialised zlib (RFC1950) compression");
+ ssh->sc_comp_ctx = zlib_decompress_init();
+ logevent("Initialised zlib (RFC1950) decompression");
+ }
+
+ /*
+ * 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)
+ send_packet(ssh, SSH1_CMSG_EXEC_CMD, PKT_STR, cmd, PKT_END);
+ else
+ send_packet(ssh, SSH1_CMSG_EXEC_SHELL, PKT_END);
+ logevent("Started session");
+ }
+
+ ssh->state = SSH_STATE_SESSION;
+ if (ssh->size_needed)
+ ssh_size(ssh, ssh->term_width, ssh->term_height);
+ if (ssh->eof_needed)
+ ssh_special(ssh, TS_EOF);
+
+ if (ssh->ldisc)
+ ldisc_send(ssh->ldisc, NULL, 0, 0);/* 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.
+ */
+
+ crReturnV;
+ if (pktin) {
+ 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;
+ }
+ } else {
+ while (inlen > 0) {
+ int len = min(inlen, 512);
+ send_packet(ssh, SSH1_CMSG_STDIN_DATA,
+ PKT_INT, len, PKTT_DATA, PKT_DATA, in, len,
+ PKTT_OTHER, PKT_END);
+ in += len;
+ inlen -= len;
+ }
+ }
+ }
+
+ crFinishV;
+}
+
+/*
+ * Handle the top-level SSH-2 protocol.
+ */
+static void ssh1_msg_debug(Ssh ssh, struct Packet *pktin)
+{
+ char *msg;
+ int msglen;
+
+ ssh_pkt_getstring(pktin, &msg, &msglen);
+ logeventf(ssh, "Remote debug message: %.*s", msglen, msg);
+}
+
+static void ssh1_msg_disconnect(Ssh ssh, struct Packet *pktin)
+{
+ /* log reason code in disconnect message */
+ char *msg;
+ int msglen;
+
+ ssh_pkt_getstring(pktin, &msg, &msglen);
+ bombout(("Server sent disconnect message:\n\"%.*s\"", msglen, msg));
+}
+
+static void ssh_msg_ignore(Ssh ssh, struct Packet *pktin)
+{
+ /* Do nothing, because we're ignoring it! Duhh. */
+}
+
+static void ssh1_protocol_setup(Ssh ssh)
+{
+ int i;
+
+ /*
+ * Most messages are handled by the coroutines.
+ */
+ for (i = 0; i < 256; i++)
+ ssh->packet_dispatch[i] = NULL;
+
+ /*
+ * 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;
+}
+
+static void ssh1_protocol(Ssh ssh, void *vin, int inlen,
+ struct Packet *pktin)
+{
+ unsigned char *in=(unsigned char*)vin;
+ if (ssh->state == SSH_STATE_CLOSED)
+ return;
+
+ if (pktin && ssh->packet_dispatch[pktin->type]) {
+ ssh->packet_dispatch[pktin->type](ssh, pktin);
+ return;
+ }
+
+ if (!ssh->protocol_initial_phase_done) {
+ if (do_ssh1_login(ssh, in, inlen, pktin))
+ ssh->protocol_initial_phase_done = TRUE;
+ else
+ return;
+ }
+
+ do_ssh1_connection(ssh, in, inlen, pktin);
+}
+
+/*
+ * Utility routine for decoding comma-separated strings in KEXINIT.
+ */
+static int in_commasep_string(char *needle, char *haystack, int haylen)
+{
+ int needlen;
+ if (!needle || !haystack) /* protect against null pointers */
+ return 0;
+ needlen = strlen(needle);
+ while (1) {
+ /*
+ * Is it at the start of the string?
+ */
+ if (haylen >= needlen && /* haystack is long enough */
+ !memcmp(needle, haystack, needlen) && /* initial match */
+ (haylen == needlen || haystack[needlen] == ',')
+ /* either , or EOS follows */
+ )
+ return 1;
+ /*
+ * If not, search for the next comma and resume after that.
+ * If no comma found, terminate.
+ */
+ while (haylen > 0 && *haystack != ',')
+ haylen--, haystack++;
+ if (haylen == 0)
+ return 0;
+ haylen--, haystack++; /* skip over comma itself */
+ }
+}
+
+/*
+ * Similar routine for checking whether we have the first string in a list.
+ */
+static int first_in_commasep_string(char *needle, char *haystack, int haylen)
+{
+ int needlen;
+ if (!needle || !haystack) /* protect against null pointers */
+ return 0;
+ needlen = strlen(needle);
+ /*
+ * Is it at the start of the string?
+ */
+ if (haylen >= needlen && /* haystack is long enough */
+ !memcmp(needle, haystack, needlen) && /* initial match */
+ (haylen == needlen || haystack[needlen] == ',')
+ /* either , or EOS follows */
+ )
+ return 1;
+ return 0;
+}
+
+
+/*
+ * SSH-2 key creation method.
+ * (Currently assumes 2 lots of any hash are sufficient to generate
+ * keys/IVs for any cipher/MAC. SSH2_MKKEY_ITERS documents this assumption.)
+ */
+#define SSH2_MKKEY_ITERS (2)
+static void ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H, char chr,
+ unsigned char *keyspace)
+{
+ const struct ssh_hash *h = ssh->kex->hash;
+ void *s;
+ /* First hlen bytes. */
+ s = h->init();
+ if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY))
+ hash_mpint(h, s, K);
+ h->bytes(s, H, h->hlen);
+ h->bytes(s, &chr, 1);
+ h->bytes(s, ssh->v2_session_id, ssh->v2_session_id_len);
+ h->final(s, keyspace);
+ /* Next hlen bytes. */
+ s = h->init();
+ if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY))
+ hash_mpint(h, s, K);
+ h->bytes(s, H, h->hlen);
+ h->bytes(s, keyspace, h->hlen);
+ h->final(s, keyspace + h->hlen);
+}
+
+/*
+ * Handle the SSH-2 transport layer.
+ */
+static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
+ struct Packet *pktin)
+{
+ unsigned char *in = (unsigned char *)vin;
+ struct do_ssh2_transport_state {
+ int nbits, pbits, warn_kex, 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 ssh_mac **maclist;
+ int nmacs;
+ const struct ssh2_cipher *cscipher_tobe;
+ const struct ssh2_cipher *sccipher_tobe;
+ const struct ssh_mac *csmac_tobe;
+ const struct ssh_mac *scmac_tobe;
+ const struct ssh_compress *cscomp_tobe;
+ const struct ssh_compress *sccomp_tobe;
+ char *hostkeydata, *sigdata, *rsakeydata, *keystr, *fingerprint;
+ int hostkeylen, siglen, rsakeylen;
+ void *hkey; /* actual host key */
+ void *rsakey; /* for RSA kex */
+ unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN];
+ int n_preferred_kex;
+ const struct ssh_kexes *preferred_kex[KEX_MAX];
+ int n_preferred_ciphers;
+ const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
+ const struct ssh_compress *preferred_comp;
+ int userauth_succeeded; /* for delayed compression */
+ int pending_compression;
+ int got_session_id, activated_authconn;
+ struct Packet *pktout;
+ int dlgret;
+ int guessok;
+ int ignorepkt;
+ };
+ crState(do_ssh2_transport_state);
+
+ crBegin(ssh->do_ssh2_transport_crstate);
+
+ s->cscipher_tobe = s->sccipher_tobe = NULL;
+ s->csmac_tobe = s->scmac_tobe = NULL;
+ s->cscomp_tobe = s->sccomp_tobe = NULL;
+
+ s->got_session_id = s->activated_authconn = FALSE;
+ s->userauth_succeeded = FALSE;
+ s->pending_compression = 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:
+ ssh->pkt_kctx = SSH2_PKTCTX_NOKEX;
+ {
+ int i, j, commalist_started;
+
+ /*
+ * Set up the preferred key exchange. (NULL => warn below here)
+ */
+ s->n_preferred_kex = 0;
+ 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_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 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_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;
+
+ /*
+ * Construct and send our key exchange packet.
+ */
+ s->pktout = ssh2_pkt_init(SSH2_MSG_KEXINIT);
+ for (i = 0; i < 16; i++)
+ ssh2_pkt_addbyte(s->pktout, (unsigned char) random_byte());
+ /* List key exchange algorithms. */
+ ssh2_pkt_addstring_start(s->pktout);
+ commalist_started = 0;
+ for (i = 0; i < s->n_preferred_kex; i++) {
+ const struct ssh_kexes *k = s->preferred_kex[i];
+ if (!k) continue; /* warning flag */
+ for (j = 0; j < k->nkexes; j++) {
+ if (commalist_started)
+ ssh2_pkt_addstring_str(s->pktout, ",");
+ ssh2_pkt_addstring_str(s->pktout, k->list[j]->name);
+ commalist_started = 1;
+ }
+ }
+ /* List server host key algorithms. */
+ ssh2_pkt_addstring_start(s->pktout);
+ for (i = 0; i < lenof(hostkey_algs); i++) {
+ ssh2_pkt_addstring_str(s->pktout, hostkey_algs[i]->name);
+ if (i < lenof(hostkey_algs) - 1)
+ ssh2_pkt_addstring_str(s->pktout, ",");
+ }
+ /* List client->server encryption algorithms. */
+ ssh2_pkt_addstring_start(s->pktout);
+ commalist_started = 0;
+ for (i = 0; i < s->n_preferred_ciphers; i++) {
+ const struct ssh2_ciphers *c = s->preferred_ciphers[i];
+ if (!c) continue; /* warning flag */
+ for (j = 0; j < c->nciphers; j++) {
+ if (commalist_started)
+ ssh2_pkt_addstring_str(s->pktout, ",");
+ ssh2_pkt_addstring_str(s->pktout, c->list[j]->name);
+ commalist_started = 1;
+ }
+ }
+ /* List server->client encryption algorithms. */
+ ssh2_pkt_addstring_start(s->pktout);
+ commalist_started = 0;
+ for (i = 0; i < s->n_preferred_ciphers; i++) {
+ const struct ssh2_ciphers *c = s->preferred_ciphers[i];
+ if (!c) continue; /* warning flag */
+ for (j = 0; j < c->nciphers; j++) {
+ if (commalist_started)
+ ssh2_pkt_addstring_str(s->pktout, ",");
+ ssh2_pkt_addstring_str(s->pktout, c->list[j]->name);
+ commalist_started = 1;
+ }
+ }
+ /* List client->server MAC algorithms. */
+ ssh2_pkt_addstring_start(s->pktout);
+ for (i = 0; i < s->nmacs; i++) {
+ ssh2_pkt_addstring_str(s->pktout, s->maclist[i]->name);
+ if (i < s->nmacs - 1)
+ ssh2_pkt_addstring_str(s->pktout, ",");
+ }
+ /* List server->client MAC algorithms. */
+ ssh2_pkt_addstring_start(s->pktout);
+ for (i = 0; i < s->nmacs; i++) {
+ ssh2_pkt_addstring_str(s->pktout, s->maclist[i]->name);
+ if (i < s->nmacs - 1)
+ ssh2_pkt_addstring_str(s->pktout, ",");
+ }
+ /* List client->server compression algorithms,
+ * then server->client compression algorithms. (We use the
+ * same set twice.) */
+ for (j = 0; j < 2; j++) {
+ ssh2_pkt_addstring_start(s->pktout);
+ assert(lenof(compressions) > 1);
+ /* Prefer non-delayed versions */
+ ssh2_pkt_addstring_str(s->pktout, s->preferred_comp->name);
+ /* 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) {
+ ssh2_pkt_addstring_str(s->pktout, ",");
+ ssh2_pkt_addstring_str(s->pktout,
+ s->preferred_comp->delayed_name);
+ }
+ for (i = 0; i < lenof(compressions); i++) {
+ const struct ssh_compress *c = compressions[i];
+ if (c != s->preferred_comp) {
+ ssh2_pkt_addstring_str(s->pktout, ",");
+ ssh2_pkt_addstring_str(s->pktout, c->name);
+ if (s->userauth_succeeded && c->delayed_name) {
+ ssh2_pkt_addstring_str(s->pktout, ",");
+ ssh2_pkt_addstring_str(s->pktout, c->delayed_name);
+ }
+ }
+ }
+ }
+ /* List client->server languages. Empty list. */
+ ssh2_pkt_addstring_start(s->pktout);
+ /* List server->client languages. Empty list. */
+ ssh2_pkt_addstring_start(s->pktout);
+ /* First KEX packet does _not_ follow, because we're not that brave. */
+ ssh2_pkt_addbool(s->pktout, FALSE);
+ /* Reserved. */
+ ssh2_pkt_adduint32(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);
+
+ ssh2_pkt_send_noqueue(ssh, s->pktout);
+
+ if (!pktin)
+ crWaitUntil(pktin);
+
+ /*
+ * Now examine the other side's KEXINIT to see what we're up
+ * to.
+ */
+ {
+ char *str, *preferred;
+ int i, j, len;
+
+ if (pktin->type != SSH2_MSG_KEXINIT) {
+ bombout(("expected key exchange packet from server"));
+ crStop(0);
+ }
+ ssh->kex = NULL;
+ ssh->hostkey = NULL;
+ s->cscipher_tobe = NULL;
+ s->sccipher_tobe = NULL;
+ s->csmac_tobe = NULL;
+ s->scmac_tobe = NULL;
+ s->cscomp_tobe = NULL;
+ s->sccomp_tobe = NULL;
+ s->warn_kex = s->warn_cscipher = s->warn_sccipher = FALSE;
+
+ pktin->savedpos += 16; /* skip garbage cookie */
+ ssh_pkt_getstring(pktin, &str, &len); /* key exchange algorithms */
+
+ preferred = NULL;
+ for (i = 0; i < s->n_preferred_kex; i++) {
+ const struct ssh_kexes *k = s->preferred_kex[i];
+ if (!k) {
+ s->warn_kex = TRUE;
+ } else {
+ for (j = 0; j < k->nkexes; j++) {
+ if (!preferred) preferred = k->list[j]->name;
+ if (in_commasep_string(k->list[j]->name, str, len)) {
+ ssh->kex = k->list[j];
+ break;
+ }
+ }
+ }
+ if (ssh->kex)
+ break;
+ }
+ if (!ssh->kex) {
+ bombout(("Couldn't agree a key exchange algorithm (available: %s)",
+ str ? str : "(null)"));
+ crStop(0);
+ }
+ /*
+ * Note that the server's guess is considered wrong if it doesn't match
+ * the first algorithm in our list, even if it's still the algorithm
+ * we end up using.
+ */
+ s->guessok = first_in_commasep_string(preferred, str, len);
+ ssh_pkt_getstring(pktin, &str, &len); /* host key algorithms */
+ for (i = 0; i < lenof(hostkey_algs); i++) {
+ if (in_commasep_string(hostkey_algs[i]->name, str, len)) {
+ ssh->hostkey = hostkey_algs[i];
+ break;
+ }
+ }
+ s->guessok = s->guessok &&
+ first_in_commasep_string(hostkey_algs[0]->name, str, len);
+ ssh_pkt_getstring(pktin, &str, &len); /* client->server cipher */
+ for (i = 0; i < s->n_preferred_ciphers; i++) {
+ const struct ssh2_ciphers *c = s->preferred_ciphers[i];
+ if (!c) {
+ s->warn_cscipher = TRUE;
+ } else {
+ for (j = 0; j < c->nciphers; j++) {
+ if (in_commasep_string(c->list[j]->name, str, len)) {
+ s->cscipher_tobe = c->list[j];
+ break;
+ }
+ }
+ }
+ if (s->cscipher_tobe)
+ break;
+ }
+ if (!s->cscipher_tobe) {
+ bombout(("Couldn't agree a client-to-server cipher (available: %s)",
+ str ? str : "(null)"));
+ crStop(0);
+ }
+
+ ssh_pkt_getstring(pktin, &str, &len); /* server->client cipher */
+ for (i = 0; i < s->n_preferred_ciphers; i++) {
+ const struct ssh2_ciphers *c = s->preferred_ciphers[i];
+ if (!c) {
+ s->warn_sccipher = TRUE;
+ } else {
+ for (j = 0; j < c->nciphers; j++) {
+ if (in_commasep_string(c->list[j]->name, str, len)) {
+ s->sccipher_tobe = c->list[j];
+ break;
+ }
+ }
+ }
+ if (s->sccipher_tobe)
+ break;
+ }
+ if (!s->sccipher_tobe) {
+ bombout(("Couldn't agree a server-to-client cipher (available: %s)",
+ str ? str : "(null)"));
+ crStop(0);
+ }
+
+ ssh_pkt_getstring(pktin, &str, &len); /* client->server mac */
+ for (i = 0; i < s->nmacs; i++) {
+ if (in_commasep_string(s->maclist[i]->name, str, len)) {
+ s->csmac_tobe = s->maclist[i];
+ break;
+ }
+ }
+ ssh_pkt_getstring(pktin, &str, &len); /* server->client mac */
+ for (i = 0; i < s->nmacs; i++) {
+ if (in_commasep_string(s->maclist[i]->name, str, len)) {
+ s->scmac_tobe = s->maclist[i];
+ break;
+ }
+ }
+ ssh_pkt_getstring(pktin, &str, &len); /* client->server compression */
+ for (i = 0; i < lenof(compressions) + 1; i++) {
+ const struct ssh_compress *c =
+ i == 0 ? s->preferred_comp : compressions[i - 1];
+ if (in_commasep_string(c->name, str, len)) {
+ s->cscomp_tobe = c;
+ break;
+ } else if (in_commasep_string(c->delayed_name, str, len)) {
+ if (s->userauth_succeeded) {
+ s->cscomp_tobe = c;
+ break;
+ } else {
+ s->pending_compression = TRUE; /* try this later */
+ }
+ }
+ }
+ ssh_pkt_getstring(pktin, &str, &len); /* server->client compression */
+ for (i = 0; i < lenof(compressions) + 1; i++) {
+ const struct ssh_compress *c =
+ i == 0 ? s->preferred_comp : compressions[i - 1];
+ if (in_commasep_string(c->name, str, len)) {
+ s->sccomp_tobe = c;
+ break;
+ } else if (in_commasep_string(c->delayed_name, str, len)) {
+ if (s->userauth_succeeded) {
+ s->sccomp_tobe = c;
+ break;
+ } else {
+ s->pending_compression = TRUE; /* try this later */
+ }
+ }
+ }
+ if (s->pending_compression) {
+ logevent("Server supports delayed compression; "
+ "will try this later");
+ }
+ ssh_pkt_getstring(pktin, &str, &len); /* client->server language */
+ ssh_pkt_getstring(pktin, &str, &len); /* server->client language */
+ s->ignorepkt = ssh2_pkt_getbool(pktin) && !s->guessok;
+
+ if (s->warn_kex) {
+ ssh_set_frozen(ssh, 1);
+ s->dlgret = askalg(ssh->frontend, "key-exchange algorithm",
+ ssh->kex->name,
+ ssh_dialog_callback, ssh);
+ if (s->dlgret < 0) {
+ do {
+ crReturn(0);
+ if (pktin) {
+ bombout(("Unexpected data from server while"
+ " waiting for user response"));
+ crStop(0);
+ }
+ } while (pktin || inlen > 0);
+ s->dlgret = ssh->user_response;
+ }
+ ssh_set_frozen(ssh, 0);
+ if (s->dlgret == 0) {
+ ssh_disconnect(ssh, "User aborted at kex warning", NULL,
+ 0, TRUE);
+ crStop(0);
+ }
+ }
+
+ if (s->warn_cscipher) {
+ ssh_set_frozen(ssh, 1);
+ s->dlgret = askalg(ssh->frontend,
+ "client-to-server cipher",
+ s->cscipher_tobe->name,
+ ssh_dialog_callback, ssh);
+ if (s->dlgret < 0) {
+ do {
+ crReturn(0);
+ if (pktin) {
+ bombout(("Unexpected data from server while"
+ " waiting for user response"));
+ crStop(0);
+ }
+ } while (pktin || inlen > 0);
+ s->dlgret = ssh->user_response;
+ }
+ ssh_set_frozen(ssh, 0);
+ if (s->dlgret == 0) {
+ ssh_disconnect(ssh, "User aborted at cipher warning", NULL,
+ 0, TRUE);
+ crStop(0);
+ }
+ }
+
+ if (s->warn_sccipher) {
+ ssh_set_frozen(ssh, 1);
+ s->dlgret = askalg(ssh->frontend,
+ "server-to-client cipher",
+ s->sccipher_tobe->name,
+ ssh_dialog_callback, ssh);
+ if (s->dlgret < 0) {
+ do {
+ crReturn(0);
+ if (pktin) {
+ bombout(("Unexpected data from server while"
+ " waiting for user response"));
+ crStop(0);
+ }
+ } while (pktin || inlen > 0);
+ s->dlgret = ssh->user_response;
+ }
+ ssh_set_frozen(ssh, 0);
+ if (s->dlgret == 0) {
+ ssh_disconnect(ssh, "User aborted at cipher warning", NULL,
+ 0, TRUE);
+ crStop(0);
+ }
+ }
+
+ ssh->exhash = ssh->kex->hash->init();
+ hash_string(ssh->kex->hash, ssh->exhash, ssh->v_c, strlen(ssh->v_c));
+ hash_string(ssh->kex->hash, ssh->exhash, ssh->v_s, strlen(ssh->v_s));
+ hash_string(ssh->kex->hash, ssh->exhash,
+ s->our_kexinit, s->our_kexinitlen);
+ sfree(s->our_kexinit);
+ if (pktin->length > 5)
+ hash_string(ssh->kex->hash, ssh->exhash,
+ pktin->data + 5, pktin->length - 5);
+
+ if (s->ignorepkt) /* first_kex_packet_follows */
+ crWaitUntil(pktin); /* Ignore packet */
+ }
+
+ 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->cscipher_tobe->keylen;
+ scbits = s->sccipher_tobe->keylen;
+ 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 (!ssh->kex->pdata) {
+ logevent("Doing Diffie-Hellman group exchange");
+ ssh->pkt_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);
+ s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_DH_GEX_REQUEST);
+ ssh2_pkt_adduint32(s->pktout, s->pbits);
+ ssh2_pkt_send_noqueue(ssh, s->pktout);
+
+ crWaitUntil(pktin);
+ if (pktin->type != SSH2_MSG_KEX_DH_GEX_GROUP) {
+ bombout(("expected key exchange group packet from server"));
+ crStop(0);
+ }
+ s->p = ssh2_pkt_getmp(pktin);
+ s->g = ssh2_pkt_getmp(pktin);
+ if (!s->p || !s->g) {
+ bombout(("unable to read mp-ints from incoming group packet"));
+ crStop(0);
+ }
+ ssh->kex_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->pkt_kctx = SSH2_PKTCTX_DHGROUP;
+ ssh->kex_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->kex_ctx, s->nbits * 2);
+ s->pktout = ssh2_pkt_init(s->kex_init_value);
+ ssh2_pkt_addmp(s->pktout, s->e);
+ ssh2_pkt_send_noqueue(ssh, s->pktout);
+
+ set_busy_status(ssh->frontend, BUSY_WAITING); /* wait for server */
+ crWaitUntil(pktin);
+ if (pktin->type != s->kex_reply_value) {
+ bombout(("expected key exchange reply packet from server"));
+ crStop(0);
+ }
+ set_busy_status(ssh->frontend, BUSY_CPU); /* cogitate */
+ ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen);
+ s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen);
+ s->f = ssh2_pkt_getmp(pktin);
+ if (!s->f) {
+ bombout(("unable to parse key exchange reply packet"));
+ crStop(0);
+ }
+ ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen);
+
+ s->K = dh_find_K(ssh->kex_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);
+
+ hash_string(ssh->kex->hash, ssh->exhash, s->hostkeydata, s->hostkeylen);
+ if (!ssh->kex->pdata) {
+ hash_uint32(ssh->kex->hash, ssh->exhash, s->pbits);
+ hash_mpint(ssh->kex->hash, ssh->exhash, s->p);
+ hash_mpint(ssh->kex->hash, ssh->exhash, s->g);
+ }
+ hash_mpint(ssh->kex->hash, ssh->exhash, s->e);
+ hash_mpint(ssh->kex->hash, ssh->exhash, s->f);
+
+ dh_cleanup(ssh->kex_ctx);
+ freebn(s->f);
+ if (!ssh->kex->pdata) {
+ freebn(s->g);
+ freebn(s->p);
+ }
+ } else {
+ logeventf(ssh, "Doing RSA key exchange with hash %s",
+ ssh->kex->hash->text_name);
+ ssh->pkt_kctx = SSH2_PKTCTX_RSAKEX;
+ /*
+ * RSA key exchange. First expect a KEXRSA_PUBKEY packet
+ * from the server.
+ */
+ crWaitUntil(pktin);
+ if (pktin->type != SSH2_MSG_KEXRSA_PUBKEY) {
+ bombout(("expected RSA public key packet from server"));
+ crStop(0);
+ }
+
+ ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen);
+ hash_string(ssh->kex->hash, ssh->exhash,
+ s->hostkeydata, s->hostkeylen);
+ s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen);
+
+ {
+ char *keydata;
+ ssh_pkt_getstring(pktin, &keydata, &s->rsakeylen);
+ s->rsakeydata = snewn(s->rsakeylen, char);
+ memcpy(s->rsakeydata, keydata, s->rsakeylen);
+ }
+
+ s->rsakey = ssh_rsakex_newkey(s->rsakeydata, s->rsakeylen);
+ if (!s->rsakey) {
+ sfree(s->rsakeydata);
+ bombout(("unable to parse RSA public key from server"));
+ crStop(0);
+ }
+
+ hash_string(ssh->kex->hash, ssh->exhash, s->rsakeydata, s->rsakeylen);
+
+ /*
+ * 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;
+ unsigned char *kstr1, *kstr2, *outstr;
+ int kstr1len, kstr2len, 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.
+ */
+ kstr1 = ssh2_mpint_fmt(s->K, &kstr1len);
+ kstr2 = snewn(kstr2len = 4 + kstr1len, unsigned char);
+ PUT_32BIT(kstr2, kstr1len);
+ memcpy(kstr2 + 4, kstr1, kstr1len);
+
+ /*
+ * Encrypt it with the given RSA key.
+ */
+ outstrlen = (klen + 7) / 8;
+ outstr = snewn(outstrlen, unsigned char);
+ ssh_rsakex_encrypt(ssh->kex->hash, kstr2, kstr2len,
+ outstr, outstrlen, s->rsakey);
+
+ /*
+ * And send it off in a return packet.
+ */
+ s->pktout = ssh2_pkt_init(SSH2_MSG_KEXRSA_SECRET);
+ ssh2_pkt_addstring_start(s->pktout);
+ ssh2_pkt_addstring_data(s->pktout, (char *)outstr, outstrlen);
+ ssh2_pkt_send_noqueue(ssh, s->pktout);
+
+ hash_string(ssh->kex->hash, ssh->exhash, outstr, outstrlen);
+
+ sfree(kstr2);
+ sfree(kstr1);
+ sfree(outstr);
+ }
+
+ ssh_rsakex_freekey(s->rsakey);
+
+ crWaitUntil(pktin);
+ if (pktin->type != SSH2_MSG_KEXRSA_DONE) {
+ sfree(s->rsakeydata);
+ bombout(("expected signature packet from server"));
+ crStop(0);
+ }
+
+ ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen);
+
+ sfree(s->rsakeydata);
+ }
+
+ hash_mpint(ssh->kex->hash, ssh->exhash, s->K);
+ assert(ssh->kex->hash->hlen <= sizeof(s->exchange_hash));
+ ssh->kex->hash->final(ssh->exhash, s->exchange_hash);
+
+ ssh->kex_ctx = NULL;
+
+#if 0
+ debug(("Exchange hash is:\n"));
+ dmemdump(s->exchange_hash, ssh->kex->hash->hlen);
+#endif
+
+ if (!s->hkey ||
+ !ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen,
+ (char *)s->exchange_hash,
+ ssh->kex->hash->hlen)) {
+ bombout(("Server's host key did not match the signature supplied"));
+ crStop(0);
+ }
+
+ /*
+ * Authenticate remote host: verify host key. (We've already
+ * checked the signature of the exchange hash.)
+ */
+ s->keystr = ssh->hostkey->fmtkey(s->hkey);
+ s->fingerprint = ssh->hostkey->fingerprint(s->hkey);
+ ssh_set_frozen(ssh, 1);
+ s->dlgret = verify_ssh_host_key(ssh->frontend,
+ ssh->savedhost, ssh->savedport,
+ ssh->hostkey->keytype, s->keystr,
+ s->fingerprint,
+ ssh_dialog_callback, ssh);
+ if (s->dlgret < 0) {
+ do {
+ crReturn(0);
+ if (pktin) {
+ bombout(("Unexpected data from server while waiting"
+ " for user host key response"));
+ crStop(0);
+ }
+ } while (pktin || inlen > 0);
+ s->dlgret = ssh->user_response;
+ }
+ ssh_set_frozen(ssh, 0);
+ if (s->dlgret == 0) {
+ ssh_disconnect(ssh, "User aborted at host key verification", NULL,
+ 0, TRUE);
+ crStop(0);
+ }
+ if (!s->got_session_id) { /* don't bother logging this in rekeys */
+ logevent("Host key fingerprint is:");
+ logevent(s->fingerprint);
+ }
+ sfree(s->fingerprint);
+ sfree(s->keystr);
+ ssh->hostkey->freekey(s->hkey);
+
+ /*
+ * 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 = ssh2_pkt_init(SSH2_MSG_NEWKEYS);
+ ssh2_pkt_send_noqueue(ssh, s->pktout);
+ ssh->outgoing_data_size = 0; /* start counting from here */
+
+ /*
+ * We've sent client NEWKEYS, so create and initialise
+ * client-to-server session keys.
+ */
+ if (ssh->cs_cipher_ctx)
+ ssh->cscipher->free_context(ssh->cs_cipher_ctx);
+ ssh->cscipher = s->cscipher_tobe;
+ ssh->cs_cipher_ctx = ssh->cscipher->make_context();
+
+ if (ssh->cs_mac_ctx)
+ ssh->csmac->free_context(ssh->cs_mac_ctx);
+ ssh->csmac = s->csmac_tobe;
+ ssh->cs_mac_ctx = ssh->csmac->make_context();
+
+ if (ssh->cs_comp_ctx)
+ ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx);
+ ssh->cscomp = s->cscomp_tobe;
+ ssh->cs_comp_ctx = ssh->cscomp->compress_init();
+
+ /*
+ * Set IVs on client-to-server keys. Here we use the exchange
+ * hash from the _first_ key exchange.
+ */
+ {
+ unsigned char keyspace[SSH2_KEX_MAX_HASH_LEN * SSH2_MKKEY_ITERS];
+ assert(sizeof(keyspace) >= ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
+ ssh2_mkkey(ssh,s->K,s->exchange_hash,'C',keyspace);
+ assert((ssh->cscipher->keylen+7) / 8 <=
+ ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
+ ssh->cscipher->setkey(ssh->cs_cipher_ctx, keyspace);
+ ssh2_mkkey(ssh,s->K,s->exchange_hash,'A',keyspace);
+ assert(ssh->cscipher->blksize <=
+ ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
+ ssh->cscipher->setiv(ssh->cs_cipher_ctx, keyspace);
+ ssh2_mkkey(ssh,s->K,s->exchange_hash,'E',keyspace);
+ assert(ssh->csmac->len <=
+ ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
+ ssh->csmac->setkey(ssh->cs_mac_ctx, keyspace);
+ memset(keyspace, 0, sizeof(keyspace));
+ }
+
+ logeventf(ssh, "Initialised %.200s client->server encryption",
+ ssh->cscipher->text_name);
+ logeventf(ssh, "Initialised %.200s client->server MAC algorithm",
+ ssh->csmac->text_name);
+ if (ssh->cscomp->text_name)
+ logeventf(ssh, "Initialised %s compression",
+ ssh->cscomp->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.
+ */
+ crWaitUntil(pktin);
+ if (pktin->type != SSH2_MSG_NEWKEYS) {
+ bombout(("expected new-keys packet from server"));
+ crStop(0);
+ }
+ ssh->incoming_data_size = 0; /* start counting from here */
+
+ /*
+ * We've seen server NEWKEYS, so create and initialise
+ * server-to-client session keys.
+ */
+ if (ssh->sc_cipher_ctx)
+ ssh->sccipher->free_context(ssh->sc_cipher_ctx);
+ ssh->sccipher = s->sccipher_tobe;
+ ssh->sc_cipher_ctx = ssh->sccipher->make_context();
+
+ if (ssh->sc_mac_ctx)
+ ssh->scmac->free_context(ssh->sc_mac_ctx);
+ ssh->scmac = s->scmac_tobe;
+ ssh->sc_mac_ctx = ssh->scmac->make_context();
+
+ if (ssh->sc_comp_ctx)
+ ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx);
+ ssh->sccomp = s->sccomp_tobe;
+ ssh->sc_comp_ctx = ssh->sccomp->decompress_init();
+
+ /*
+ * Set IVs on server-to-client keys. Here we use the exchange
+ * hash from the _first_ key exchange.
+ */
+ {
+ unsigned char keyspace[SSH2_KEX_MAX_HASH_LEN * SSH2_MKKEY_ITERS];
+ assert(sizeof(keyspace) >= ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
+ ssh2_mkkey(ssh,s->K,s->exchange_hash,'D',keyspace);
+ assert((ssh->sccipher->keylen+7) / 8 <=
+ ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
+ ssh->sccipher->setkey(ssh->sc_cipher_ctx, keyspace);
+ ssh2_mkkey(ssh,s->K,s->exchange_hash,'B',keyspace);
+ assert(ssh->sccipher->blksize <=
+ ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
+ ssh->sccipher->setiv(ssh->sc_cipher_ctx, keyspace);
+ ssh2_mkkey(ssh,s->K,s->exchange_hash,'F',keyspace);
+ assert(ssh->scmac->len <=
+ ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
+ ssh->scmac->setkey(ssh->sc_mac_ctx, keyspace);
+ memset(keyspace, 0, sizeof(keyspace));
+ }
+ logeventf(ssh, "Initialised %.200s server->client encryption",
+ ssh->sccipher->text_name);
+ logeventf(ssh, "Initialised %.200s server->client MAC algorithm",
+ ssh->scmac->text_name);
+ if (ssh->sccomp->text_name)
+ logeventf(ssh, "Initialised %s decompression",
+ ssh->sccomp->text_name);
+
+ /*
+ * Free shared secret.
+ */
+ freebn(s->K);
+
+ /*
+ * 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();
+ if (conf_get_int(ssh->conf, CONF_ssh_rekey_time) != 0)
+ ssh->next_rekey = schedule_timer(conf_get_int(ssh->conf, CONF_ssh_rekey_time)*60*TICKSPERSEC,
+ ssh2_timer, ssh);
+
+ /*
+ * If this is the first key exchange phase, we must pass the
+ * SSH2_MSG_NEWKEYS packet to the next layer, not because it
+ * wants to see it but because it will need time to initialise
+ * itself before it sees an actual packet. In subsequent key
+ * exchange phases, we don't pass SSH2_MSG_NEWKEYS on, because
+ * it would only confuse the layer above.
+ */
+ if (s->activated_authconn) {
+ crReturn(0);
+ }
+ s->activated_authconn = TRUE;
+
+ /*
+ * Now we're encrypting. Begin returning 1 to the protocol main
+ * function so that other things can run on top of the
+ * transport. If we ever see a KEXINIT, we must go back to the
+ * start.
+ *
+ * We _also_ go back to the start if we see pktin==NULL and
+ * inlen negative, because this is a special signal meaning
+ * `initiate client-driven rekey', and `in' contains a message
+ * giving the reason for the rekey.
+ *
+ * inlen==-1 means always initiate a rekey;
+ * inlen==-2 means that userauth has completed successfully and
+ * we should consider rekeying (for delayed compression).
+ */
+ while (!((pktin && pktin->type == SSH2_MSG_KEXINIT) ||
+ (!pktin && inlen < 0))) {
+ wait_for_rekey:
+ crReturn(1);
+ }
+ if (pktin) {
+ logevent("Server initiated key re-exchange");
+ } else {
+ if (inlen == -2) {
+ /*
+ * authconn has seen a USERAUTH_SUCCEEDED. Time to enable
+ * delayed compression, if it's available.
+ *
+ * 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.
+ */
+ assert(!s->userauth_succeeded); /* should only happen once */
+ s->userauth_succeeded = TRUE;
+ if (!s->pending_compression)
+ /* 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)",
+ (char *)in);
+ /* Reset the counters, so that at least this message doesn't
+ * hit the event log _too_ often. */
+ ssh->outgoing_data_size = 0;
+ ssh->incoming_data_size = 0;
+ if (conf_get_int(ssh->conf, CONF_ssh_rekey_time) != 0) {
+ ssh->next_rekey =
+ schedule_timer(conf_get_int(ssh->conf, CONF_ssh_rekey_time)*60*TICKSPERSEC,
+ ssh2_timer, ssh);
+ }
+ goto wait_for_rekey; /* this is still utterly horrid */
+ } else {
+ logeventf(ssh, "Initiating key re-exchange (%s)", (char *)in);
+ }
+ }
+ goto begin_key_exchange;
+
+ crFinish(1);
+}
+
+/*
+ * Add data to an SSH-2 channel output buffer.
+ */
+static void ssh2_add_channel_data(struct ssh_channel *c, char *buf,
+ int len)
+{
+ bufchain_add(&c->v.v2.outbuffer, buf, len);
+}
+
+/*
+ * Attempt to send data on an SSH-2 channel.
+ */
+static int ssh2_try_send(struct ssh_channel *c)
+{
+ Ssh ssh = c->ssh;
+ struct Packet *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 = ssh2_pkt_init(SSH2_MSG_CHANNEL_DATA);
+ ssh2_pkt_adduint32(pktout, c->remoteid);
+ ssh2_pkt_addstring_start(pktout);
+ dont_log_data(ssh, pktout, PKTLOG_OMIT);
+ ssh2_pkt_addstring_data(pktout, data, len);
+ end_log_omission(ssh, pktout);
+ ssh2_pkt_send(ssh, pktout);
+ 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) {
+ switch (c->type) {
+ case CHAN_MAINSESSION:
+ /* stdin need not receive an unthrottle
+ * notification since it will be polled */
+ break;
+ case CHAN_X11:
+ x11_unthrottle(c->u.x11.s);
+ break;
+ case CHAN_AGENT:
+ /* agent sockets are request/response and need no
+ * buffer management */
+ break;
+ case CHAN_SOCKDATA:
+ pfd_unthrottle(c->u.pfd.s);
+ break;
+ }
+ }
+}
+
+/*
+ * Set up most of a new ssh_channel for SSH-2.
+ */
+static void ssh2_channel_init(struct ssh_channel *c)
+{
+ Ssh ssh = c->ssh;
+ c->localid = alloc_channel_id(ssh);
+ c->closes = 0;
+ c->pending_eof = FALSE;
+ c->throttling_conn = FALSE;
+ c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin =
+ conf_get_int(ssh->conf, CONF_ssh_simple) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE;
+ c->v.v2.winadj_head = c->v.v2.winadj_tail = NULL;
+ c->v.v2.throttle_state = UNTHROTTLED;
+ bufchain_init(&c->v.v2.outbuffer);
+}
+
+/*
+ * Potentially enlarge the window on an SSH-2 channel.
+ */
+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 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) {
+ struct Packet *pktout;
+ struct winadj *wa;
+
+ /*
+ * 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.
+ *
+ * We also only send this if the main channel has finished its
+ * initial CHANNEL_REQUESTs and installed the default
+ * CHANNEL_FAILURE handler, so as not to risk giving it
+ * unexpected CHANNEL_FAILUREs.
+ */
+ if (newwin == c->v.v2.locmaxwin &&
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE]) {
+ pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
+ ssh2_pkt_adduint32(pktout, c->remoteid);
+ ssh2_pkt_addstring(pktout, "winadj@putty.projects.tartarus.org");
+ ssh2_pkt_addbool(pktout, TRUE);
+ ssh2_pkt_send(ssh, 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.
+ */
+ wa = snew(struct winadj);
+ wa->size = newwin - c->v.v2.locwindow;
+ wa->next = NULL;
+ if (!c->v.v2.winadj_head)
+ c->v.v2.winadj_head = wa;
+ else
+ c->v.v2.winadj_tail->next = wa;
+ c->v.v2.winadj_tail = wa;
+ 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 = ssh2_pkt_init(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+ ssh2_pkt_adduint32(pktout, c->remoteid);
+ ssh2_pkt_adduint32(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.
+ */
+static struct ssh_channel *ssh2_channel_msg(Ssh ssh, struct Packet *pktin)
+{
+ unsigned localid = ssh_pkt_getuint32(pktin);
+ struct ssh_channel *c;
+
+ c = find234(ssh->channels, &localid, ssh_channelfind);
+ if (!c ||
+ (c->halfopen && pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION &&
+ pktin->type != SSH2_MSG_CHANNEL_OPEN_FAILURE)) {
+ char *buf = dupprintf("Received %s for %s channel %u",
+ ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx,
+ pktin->type),
+ c ? "half-open" : "nonexistent", localid);
+ ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
+ sfree(buf);
+ return NULL;
+ }
+ return c;
+}
+
+static int ssh2_handle_winadj_response(struct ssh_channel *c)
+{
+ struct winadj *wa = c->v.v2.winadj_head;
+ if (!wa)
+ return FALSE;
+ c->v.v2.winadj_head = wa->next;
+ c->v.v2.remlocwin += wa->size;
+ sfree(wa);
+ /*
+ * 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;
+ /*
+ * We may now initiate channel-closing procedures, if that winadj
+ * was the last thing outstanding before we send CHANNEL_CLOSE.
+ */
+ ssh2_channel_check_close(c);
+ return TRUE;
+}
+
+static void ssh2_msg_channel_success(Ssh ssh, struct Packet *pktin)
+{
+ /*
+ * This should never get called. All channel requests are either
+ * sent with want_reply false, are sent before this handler gets
+ * installed, or are "winadj@putty" requests, which servers should
+ * never respond to with success.
+ *
+ * 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 handle it just the same as the
+ * expected FAILURE.
+ */
+ struct ssh_channel *c;
+
+ c = ssh2_channel_msg(ssh, pktin);
+ if (!c)
+ return;
+ if (!ssh2_handle_winadj_response(c))
+ ssh_disconnect(ssh, NULL,
+ "Received unsolicited SSH_MSG_CHANNEL_SUCCESS",
+ SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
+}
+
+static void ssh2_msg_channel_failure(Ssh ssh, struct Packet *pktin)
+{
+ /*
+ * The only time this should get called is for "winadj@putty"
+ * messages sent above. All other channel requests are either
+ * sent with want_reply false or are sent before this handler gets
+ * installed.
+ */
+ struct ssh_channel *c;
+
+ c = ssh2_channel_msg(ssh, pktin);
+ if (!c)
+ return;
+ if (!ssh2_handle_winadj_response(c))
+ ssh_disconnect(ssh, NULL,
+ "Received unsolicited SSH_MSG_CHANNEL_FAILURE",
+ SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
+}
+
+static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin)
+{
+ struct ssh_channel *c;
+ c = ssh2_channel_msg(ssh, pktin);
+ if (!c)
+ return;
+ if (!(c->closes & CLOSES_SENT_EOF)) {
+ c->v.v2.remwindow += ssh_pkt_getuint32(pktin);
+ ssh2_try_send_and_unthrottle(ssh, c);
+ }
+}
+
+static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
+{
+ char *data;
+ int length;
+ struct ssh_channel *c;
+ c = ssh2_channel_msg(ssh, pktin);
+ if (!c)
+ return;
+ if (pktin->type == SSH2_MSG_CHANNEL_EXTENDED_DATA &&
+ ssh_pkt_getuint32(pktin) != SSH2_EXTENDED_DATA_STDERR)
+ return; /* extended but not stderr */
+ ssh_pkt_getstring(pktin, &data, &length);
+ if (data) {
+ int bufsize = 0;
+ c->v.v2.locwindow -= length;
+ c->v.v2.remlocwin -= length;
+ switch (c->type) {
+ case CHAN_MAINSESSION:
+ bufsize =
+ from_backend(ssh->frontend, pktin->type ==
+ SSH2_MSG_CHANNEL_EXTENDED_DATA,
+ data, length);
+ break;
+ case CHAN_X11:
+ bufsize = x11_send(c->u.x11.s, data, length);
+ break;
+ case CHAN_SOCKDATA:
+ bufsize = pfd_send(c->u.pfd.s, data, length);
+ break;
+ case CHAN_AGENT:
+ while (length > 0) {
+ if (c->u.a.lensofar < 4) {
+ unsigned int l = min(4 - c->u.a.lensofar,
+ (unsigned)length);
+ memcpy(c->u.a.msglen + c->u.a.lensofar,
+ data, l);
+ data += l;
+ length -= l;
+ c->u.a.lensofar += l;
+ }
+ if (c->u.a.lensofar == 4) {
+ c->u.a.totallen =
+ 4 + GET_32BIT(c->u.a.msglen);
+ c->u.a.message = snewn(c->u.a.totallen,
+ unsigned char);
+ memcpy(c->u.a.message, c->u.a.msglen, 4);
+ }
+ if (c->u.a.lensofar >= 4 && length > 0) {
+ unsigned int l =
+ min(c->u.a.totallen - c->u.a.lensofar,
+ (unsigned)length);
+ memcpy(c->u.a.message + c->u.a.lensofar,
+ data, l);
+ data += l;
+ length -= l;
+ c->u.a.lensofar += l;
+ }
+ if (c->u.a.lensofar == c->u.a.totallen) {
+ void *reply;
+ int replylen;
+ if (agent_query(c->u.a.message,
+ c->u.a.totallen,
+ &reply, &replylen,
+ ssh_agentf_callback, c))
+ ssh_agentf_callback(c, reply, replylen);
+ sfree(c->u.a.message);
+ c->u.a.message = NULL;
+ c->u.a.lensofar = 0;
+ }
+ }
+ bufsize = 0;
+ break;
+ }
+ /*
+ * 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.
+ */
+ ssh2_set_window(c, bufsize < c->v.v2.locmaxwin ?
+ c->v.v2.locmaxwin - bufsize : 0);
+ /*
+ * 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 ||
+ (conf_get_int(ssh->conf, CONF_ssh_simple) && bufsize > 0)) &&
+ !c->throttling_conn) {
+ c->throttling_conn = 1;
+ ssh_throttle_conn(ssh, +1);
+ }
+ }
+}
+
+static void ssh_channel_destroy(struct ssh_channel *c)
+{
+ Ssh ssh = c->ssh;
+
+ switch (c->type) {
+ case CHAN_MAINSESSION:
+ ssh->mainchan = NULL;
+ update_specials_menu(ssh->frontend);
+ break;
+ case CHAN_X11:
+ if (c->u.x11.s != NULL)
+ x11_close(c->u.x11.s);
+ logevent("Forwarded X11 connection terminated");
+ break;
+ case CHAN_AGENT:
+ sfree(c->u.a.message);
+ break;
+ case CHAN_SOCKDATA:
+ if (c->u.pfd.s != NULL)
+ pfd_close(c->u.pfd.s);
+ logevent("Forwarded port closed");
+ break;
+ }
+
+ del234(ssh->channels, c);
+ if (ssh->version == 2)
+ bufchain_clear(&c->v.v2.outbuffer);
+ sfree(c);
+
+ /*
+ * See if that was the last channel left open.
+ * (This is only our termination condition if we're
+ * not running in -N mode.)
+ */
+ if (ssh->version == 2 &&
+ !conf_get_int(ssh->conf, CONF_ssh_no_shell) &&
+ count234(ssh->channels) == 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);
+ }
+}
+
+static void ssh2_channel_check_close(struct ssh_channel *c)
+{
+ Ssh ssh = c->ssh;
+ struct Packet *pktout;
+
+ if ((c->closes & (CLOSES_SENT_EOF | CLOSES_RCVD_EOF | CLOSES_SENT_CLOSE))
+ == (CLOSES_SENT_EOF | CLOSES_RCVD_EOF) && !c->v.v2.winadj_head) {
+ /*
+ * We have both sent and received EOF, and we have no
+ * outstanding winadj channel requests, which means the
+ * channel is in final wind-up. But we haven't sent CLOSE, so
+ * let's do so now.
+ */
+ pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
+ ssh2_pkt_adduint32(pktout, c->remoteid);
+ ssh2_pkt_send(ssh, pktout);
+ c->closes |= CLOSES_SENT_CLOSE;
+ }
+
+ if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) {
+ /*
+ * We have both sent and received CLOSE, which means we're
+ * completely done with the channel.
+ */
+ ssh_channel_destroy(c);
+ }
+}
+
+static void ssh2_channel_got_eof(struct ssh_channel *c)
+{
+ if (c->closes & CLOSES_RCVD_EOF)
+ return; /* already seen EOF */
+ c->closes |= CLOSES_RCVD_EOF;
+
+ if (c->type == CHAN_X11) {
+ x11_send_eof(c->u.x11.s);
+ } else if (c->type == CHAN_AGENT) {
+ /* Manufacture an outgoing EOF in response to the incoming one. */
+ sshfwd_write_eof(c);
+ } else if (c->type == CHAN_SOCKDATA) {
+ pfd_send_eof(c->u.pfd.s);
+ } else if (c->type == CHAN_MAINSESSION) {
+ Ssh ssh = c->ssh;
+
+ if (!ssh->sent_console_eof &&
+ (from_backend_eof(ssh->frontend) || 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(c);
+ }
+ ssh->sent_console_eof = TRUE;
+ }
+
+ ssh2_channel_check_close(c);
+}
+
+static void ssh2_msg_channel_eof(Ssh ssh, struct Packet *pktin)
+{
+ struct ssh_channel *c;
+
+ c = ssh2_channel_msg(ssh, pktin);
+ if (!c)
+ return;
+ ssh2_channel_got_eof(c);
+}
+
+static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin)
+{
+ struct ssh_channel *c;
+
+ c = ssh2_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.
+ */
+ ssh2_channel_got_eof(c);
+
+ /*
+ * 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)) {
+ /*
+ * Make sure we don't read any more from whatever our local
+ * data source is for this channel.
+ */
+ switch (c->type) {
+ case CHAN_MAINSESSION:
+ ssh->send_ok = 0; /* stop trying to read from stdin */
+ break;
+ case CHAN_X11:
+ x11_override_throttle(c->u.x11.s, 1);
+ break;
+ case CHAN_SOCKDATA:
+ pfd_override_throttle(c->u.pfd.s, 1);
+ break;
+ }
+
+ /*
+ * Send outgoing EOF.
+ */
+ sshfwd_write_eof(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, struct Packet *pktin)
+{
+ struct ssh_channel *c;
+
+ c = ssh2_channel_msg(ssh, pktin);
+ if (!c)
+ return;
+ if (c->type != CHAN_SOCKDATA_DORMANT)
+ return; /* dunno why they're confirming this */
+ c->remoteid = ssh_pkt_getuint32(pktin);
+ c->halfopen = FALSE;
+ c->type = CHAN_SOCKDATA;
+ c->v.v2.remwindow = ssh_pkt_getuint32(pktin);
+ c->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin);
+ if (c->u.pfd.s)
+ pfd_confirm(c->u.pfd.s);
+ if (c->pending_eof)
+ ssh_channel_try_eof(c);
+}
+
+static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin)
+{
+ static const char *const reasons[] = {
+ "<unknown reason code>",
+ "Administratively prohibited",
+ "Connect failed",
+ "Unknown channel type",
+ "Resource shortage",
+ };
+ unsigned reason_code;
+ char *reason_string;
+ int reason_length;
+ struct ssh_channel *c;
+ c = ssh2_channel_msg(ssh, pktin);
+ if (!c)
+ return;
+ if (c->type != CHAN_SOCKDATA_DORMANT)
+ return; /* dunno why they're failing this */
+
+ reason_code = ssh_pkt_getuint32(pktin);
+ if (reason_code >= lenof(reasons))
+ reason_code = 0; /* ensure reasons[reason_code] in range */
+ ssh_pkt_getstring(pktin, &reason_string, &reason_length);
+ logeventf(ssh, "Forwarded connection refused by server: %s [%.*s]",
+ reasons[reason_code], reason_length, reason_string);
+
+ pfd_close(c->u.pfd.s);
+
+ del234(ssh->channels, c);
+ sfree(c);
+}
+
+static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin)
+{
+ char *type;
+ int typelen, want_reply;
+ int reply = SSH2_MSG_CHANNEL_FAILURE; /* default */
+ struct ssh_channel *c;
+ struct Packet *pktout;
+
+ c = ssh2_channel_msg(ssh, pktin);
+ if (!c)
+ return;
+ ssh_pkt_getstring(pktin, &type, &typelen);
+ want_reply = ssh2_pkt_getbool(pktin);
+
+ /*
+ * 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 (typelen == 11 &&
+ !memcmp(type, "exit-status", 11)) {
+
+ ssh->exitcode = ssh_pkt_getuint32(pktin);
+ logeventf(ssh, "Server sent command exit status %d",
+ ssh->exitcode);
+ reply = SSH2_MSG_CHANNEL_SUCCESS;
+
+ } else if (typelen == 11 &&
+ !memcmp(type, "exit-signal", 11)) {
+
+ int is_plausible = TRUE, is_int = FALSE;
+ char *fmt_sig = "", *fmt_msg = "";
+ char *msg;
+ int msglen = 0, core = FALSE;
+ /* 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. */
+ {
+ unsigned char *p = pktin->body +
+ pktin->savedpos;
+ long len = pktin->length - pktin->savedpos;
+ unsigned long num = GET_32BIT(p); /* what is it? */
+ /* If it's 0, it hardly matters; assume string */
+ if (num == 0) {
+ is_int = FALSE;
+ } else {
+ int maybe_int = FALSE, maybe_str = FALSE;
+#define CHECK_HYPOTHESIS(offset, result) \
+ do { \
+ long q = offset; \
+ if (q >= 0 && q+4 <= len) { \
+ q = q + 4 + GET_32BIT(p+q); \
+ if (q >= 0 && q+4 <= len && \
+ ((q = q + 4 + GET_32BIT(p+q))!= 0) && q == len) \
+ result = TRUE; \
+ } \
+ } while(0)
+ CHECK_HYPOTHESIS(4+1, maybe_int);
+ CHECK_HYPOTHESIS(4+num+1, maybe_str);
+#undef CHECK_HYPOTHESIS
+ if (maybe_int && !maybe_str)
+ is_int = TRUE;
+ else if (!maybe_int && maybe_str)
+ is_int = FALSE;
+ else
+ /* Crikey. Either or neither. Panic. */
+ is_plausible = FALSE;
+ }
+ }
+ ssh->exitcode = 128; /* means `unknown signal' */
+ if (is_plausible) {
+ if (is_int) {
+ /* Old non-standard OpenSSH. */
+ int signum = ssh_pkt_getuint32(pktin);
+ fmt_sig = dupprintf(" %d", signum);
+ ssh->exitcode = 128 + signum;
+ } else {
+ /* As per RFC 4254. */
+ char *sig;
+ int siglen;
+ ssh_pkt_getstring(pktin, &sig, &siglen);
+ /* Signal name isn't supposed to be blank, but
+ * let's cope gracefully if it is. */
+ if (siglen) {
+ fmt_sig = dupprintf(" \"%.*s\"",
+ siglen, sig);
+ }
+
+ /*
+ * Really hideous method of translating the
+ * signal description back into a locally
+ * meaningful number.
+ */
+
+ if (0)
+ ;
+#define TRANSLATE_SIGNAL(s) \
+ else if (siglen == lenof(#s)-1 && !memcmp(sig, #s, siglen)) \
+ ssh->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
+ ssh->exitcode = 128;
+ }
+ core = ssh2_pkt_getbool(pktin);
+ ssh_pkt_getstring(pktin, &msg, &msglen);
+ if (msglen) {
+ fmt_msg = dupprintf(" (\"%.*s\")", msglen, msg);
+ }
+ /* ignore lang tag */
+ } /* else don't attempt to parse */
+ logeventf(ssh, "Server exited on signal%s%s%s",
+ fmt_sig, core ? " (core dumped)" : "",
+ fmt_msg);
+ if (*fmt_sig) sfree(fmt_sig);
+ if (*fmt_msg) 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 = ssh2_pkt_init(reply);
+ ssh2_pkt_adduint32(pktout, c->remoteid);
+ ssh2_pkt_send(ssh, pktout);
+ }
+}
+
+static void ssh2_msg_global_request(Ssh ssh, struct Packet *pktin)
+{
+ char *type;
+ int typelen, want_reply;
+ struct Packet *pktout;
+
+ ssh_pkt_getstring(pktin, &type, &typelen);
+ want_reply = ssh2_pkt_getbool(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 = ssh2_pkt_init(SSH2_MSG_REQUEST_FAILURE);
+ ssh2_pkt_send(ssh, pktout);
+ }
+}
+
+static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
+{
+ char *type;
+ int typelen;
+ char *peeraddr;
+ int peeraddrlen;
+ int peerport;
+ char *error = NULL;
+ struct ssh_channel *c;
+ unsigned remid, winsize, pktsize;
+ struct Packet *pktout;
+
+ ssh_pkt_getstring(pktin, &type, &typelen);
+ c = snew(struct ssh_channel);
+ c->ssh = ssh;
+
+ remid = ssh_pkt_getuint32(pktin);
+ winsize = ssh_pkt_getuint32(pktin);
+ pktsize = ssh_pkt_getuint32(pktin);
+
+ if (typelen == 3 && !memcmp(type, "x11", 3)) {
+ char *addrstr;
+ const char *x11err;
+
+ ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen);
+ addrstr = snewn(peeraddrlen+1, char);
+ memcpy(addrstr, peeraddr, peeraddrlen);
+ addrstr[peeraddrlen] = '\0';
+ peerport = ssh_pkt_getuint32(pktin);
+
+ logeventf(ssh, "Received X11 connect request from %s:%d",
+ addrstr, peerport);
+
+ if (!ssh->X11_fwd_enabled)
+ error = "X11 forwarding is not enabled";
+ else if ((x11err = x11_init(&c->u.x11.s, ssh->x11disp, c,
+ addrstr, peerport, ssh->conf)) != NULL) {
+ logeventf(ssh, "Local X11 connection failed: %s", x11err);
+ error = "Unable to open an X11 connection";
+ } else {
+ logevent("Opening X11 forward connection succeeded");
+ c->type = CHAN_X11;
+ }
+
+ sfree(addrstr);
+ } else if (typelen == 15 &&
+ !memcmp(type, "forwarded-tcpip", 15)) {
+ struct ssh_rportfwd pf, *realpf;
+ char *dummy;
+ int dummylen;
+ ssh_pkt_getstring(pktin, &dummy, &dummylen);/* skip address */
+ pf.sport = ssh_pkt_getuint32(pktin);
+ ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen);
+ peerport = ssh_pkt_getuint32(pktin);
+ realpf = find234(ssh->rportfwds, &pf, NULL);
+ logeventf(ssh, "Received remote port %d open request "
+ "from %s:%d", pf.sport, peeraddr, peerport);
+ if (realpf == NULL) {
+ error = "Remote port is not recognised";
+ } else {
+ const char *e = pfd_newconnect(&c->u.pfd.s,
+ realpf->dhost,
+ realpf->dport, c,
+ ssh->conf,
+ realpf->pfrec->addressfamily);
+ logeventf(ssh, "Attempting to forward remote port to "
+ "%s:%d", realpf->dhost, realpf->dport);
+ if (e != NULL) {
+ logeventf(ssh, "Port open failed: %s", e);
+ error = "Port open failed";
+ } else {
+ logevent("Forwarded port opened successfully");
+ c->type = CHAN_SOCKDATA;
+ }
+ }
+ } else if (typelen == 22 &&
+ !memcmp(type, "auth-agent@openssh.com", 22)) {
+ if (!ssh->agentfwd_enabled)
+ error = "Agent forwarding is not enabled";
+ else {
+ c->type = CHAN_AGENT; /* identify channel type */
+ c->u.a.lensofar = 0;
+ }
+ } else {
+ error = "Unsupported channel type requested";
+ }
+
+ c->remoteid = remid;
+ c->halfopen = FALSE;
+ if (error) {
+ pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_FAILURE);
+ ssh2_pkt_adduint32(pktout, c->remoteid);
+ ssh2_pkt_adduint32(pktout, SSH2_OPEN_CONNECT_FAILED);
+ ssh2_pkt_addstring(pktout, error);
+ ssh2_pkt_addstring(pktout, "en"); /* language tag */
+ ssh2_pkt_send(ssh, pktout);
+ logeventf(ssh, "Rejected channel open: %s", error);
+ sfree(c);
+ } else {
+ ssh2_channel_init(c);
+ c->v.v2.remwindow = winsize;
+ c->v.v2.remmaxpkt = pktsize;
+ add234(ssh->channels, c);
+ pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
+ ssh2_pkt_adduint32(pktout, c->remoteid);
+ ssh2_pkt_adduint32(pktout, c->localid);
+ ssh2_pkt_adduint32(pktout, c->v.v2.locwindow);
+ ssh2_pkt_adduint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */
+ ssh2_pkt_send(ssh, pktout);
+ }
+}
+
+/*
+ * 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, struct Packet *pktin)
+{
+ /* Arbitrary limit to prevent unbounded inflation of buffer */
+ if (conf_get_int(ssh->conf, CONF_ssh_show_banner) &&
+ bufchain_size(&ssh->banner) <= 131072) {
+ char *banner = NULL;
+ int size = 0;
+ ssh_pkt_getstring(pktin, &banner, &size);
+ if (banner)
+ bufchain_add(&ssh->banner, banner, size);
+ }
+}
+
+/* Helper function to deal with sending tty modes for "pty-req" */
+static void ssh2_send_ttymode(void *data, char *mode, char *val)
+{
+ struct Packet *pktout = (struct Packet *)data;
+ int i = 0;
+ unsigned int arg = 0;
+ while (strcmp(mode, ssh_ttymodes[i].mode) != 0) i++;
+ if (i == lenof(ssh_ttymodes)) return;
+ switch (ssh_ttymodes[i].type) {
+ case TTY_OP_CHAR:
+ arg = ssh_tty_parse_specchar(val);
+ break;
+ case TTY_OP_BOOL:
+ arg = ssh_tty_parse_boolean(val);
+ break;
+ }
+ ssh2_pkt_addbyte(pktout, ssh_ttymodes[i].opcode);
+ ssh2_pkt_adduint32(pktout, arg);
+}
+
+/*
+ * Handle the SSH-2 userauth and connection layers.
+ */
+static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
+ struct Packet *pktin)
+{
+ struct do_ssh2_authconn_state {
+ 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 gotit, need_pw, can_pubkey, can_passwd, can_keyb_inter;
+ int tried_pubkey_config, done_agent;
+#ifndef NO_GSSAPI
+ int can_gssapi;
+ int tried_gssapi;
+#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;
+ void *publickey_blob;
+ int publickey_bloblen;
+ int publickey_encrypted;
+ char *publickey_algorithm;
+ char *publickey_comment;
+ unsigned char agent_request[5], *agent_response, *agentp;
+ int agent_responselen;
+ unsigned char *pkblob_in_agent;
+ int keyi, nkeys;
+ char *pkblob, *alg, *commentp;
+ int pklen, alglen, commentlen;
+ int siglen, retlen, len;
+ char *q, *agentreq, *ret;
+ int try_send;
+ int num_env, env_left, env_ok;
+ struct Packet *pktout;
+ Filename *keyfile;
+#ifndef NO_GSSAPI
+ struct ssh_gss_library *gsslib;
+ Ssh_gss_ctx gss_ctx;
+ Ssh_gss_buf gss_buf;
+ Ssh_gss_buf gss_rcvtok, gss_sndtok;
+ Ssh_gss_name gss_srv_name;
+ Ssh_gss_stat gss_stat;
+#endif
+ };
+ crState(do_ssh2_authconn_state);
+
+ crBegin(ssh->do_ssh2_authconn_crstate);
+
+ s->done_service_req = FALSE;
+ s->we_are_in = s->userauth_success = FALSE;
+#ifndef NO_GSSAPI
+ s->tried_gssapi = FALSE;
+#endif
+
+ if (!conf_get_int(ssh->conf, CONF_ssh_no_userauth)) {
+ /*
+ * Request userauth protocol, and await a response to it.
+ */
+ s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST);
+ ssh2_pkt_addstring(s->pktout, "ssh-userauth");
+ ssh2_pkt_send(ssh, s->pktout);
+ crWaitUntilV(pktin);
+ 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 = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST);
+ ssh2_pkt_addstring(s->pktout, "ssh-connection");
+ ssh2_pkt_send(ssh, s->pktout);
+ crWaitUntilV(pktin);
+ 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 private key file \"%.150s\"",
+ filename_to_str(s->keyfile));
+ keytype = key_type(s->keyfile);
+ if (keytype == SSH_KEYTYPE_SSH2) {
+ const char *error;
+ s->publickey_blob =
+ ssh2_userkey_loadpub(s->keyfile,
+ &s->publickey_algorithm,
+ &s->publickey_bloblen,
+ &s->publickey_comment, &error);
+ if (s->publickey_blob) {
+ s->publickey_encrypted =
+ ssh2_userkey_encrypted(s->keyfile, NULL);
+ } else {
+ char *msgbuf;
+ logeventf(ssh, "Unable to load private key (%s)",
+ error);
+ msgbuf = dupprintf("Unable to load private key file "
+ "\"%.150s\" (%s)\r\n",
+ filename_to_str(s->keyfile),
+ error);
+ c_write_str(ssh, msgbuf);
+ sfree(msgbuf);
+ }
+ } 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_in_agent = NULL;
+ if (conf_get_int(ssh->conf, CONF_tryagent) && agent_exists()) {
+
+ void *r;
+
+ logevent("Pageant is running. Requesting keys.");
+
+ /* Request the keys held by the agent. */
+ PUT_32BIT(s->agent_request, 1);
+ s->agent_request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
+ if (!agent_query(s->agent_request, 5, &r, &s->agent_responselen,
+ ssh_agent_callback, ssh)) {
+ do {
+ crReturnV;
+ if (pktin) {
+ bombout(("Unexpected data from server while"
+ " waiting for agent response"));
+ crStopV;
+ }
+ } while (pktin || inlen > 0);
+ r = ssh->agent_response;
+ s->agent_responselen = ssh->agent_response_len;
+ }
+ s->agent_response = (unsigned char *) r;
+ if (s->agent_response && s->agent_responselen >= 5 &&
+ s->agent_response[4] == SSH2_AGENT_IDENTITIES_ANSWER) {
+ int keyi;
+ unsigned char *p;
+ p = s->agent_response + 5;
+ s->nkeys = GET_32BIT(p);
+ p += 4;
+ logeventf(ssh, "Pageant has %d SSH-2 keys", s->nkeys);
+ if (s->publickey_blob) {
+ /* See if configured key is in agent. */
+ for (keyi = 0; keyi < s->nkeys; keyi++) {
+ s->pklen = GET_32BIT(p);
+ if (s->pklen == s->publickey_bloblen &&
+ !memcmp(p+4, s->publickey_blob,
+ s->publickey_bloblen)) {
+ logeventf(ssh, "Pageant key #%d matches "
+ "configured key file", keyi);
+ s->keyi = keyi;
+ s->pkblob_in_agent = p;
+ break;
+ }
+ p += 4 + s->pklen;
+ p += GET_32BIT(p) + 4; /* comment */
+ }
+ if (!s->pkblob_in_agent) {
+ logevent("Configured key file not in Pageant");
+ s->nkeys = 0;
+ }
+ }
+ } else {
+ logevent("Failed to get reply from Pageant");
+ }
+ }
+
+ }
+
+ /*
+ * 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