+ /*
+ * Check the MAC.
+ */
+ if (scmac
+ && !scmac->verify(pktin.data, st->len + 4,
+ st->incoming_sequence)) {
+ bombout(("Incorrect MAC received on packet"));
+ crReturn(0);
+ }
+ st->incoming_sequence++; /* whether or not we MACed */
+
+ /*
+ * Decompress packet payload.
+ */
+ {
+ unsigned char *newpayload;
+ int newlen;
+ if (sccomp && sccomp->decompress(pktin.data + 5, pktin.length - 5,
+ &newpayload, &newlen)) {
+ if (pktin.maxlen < newlen + 5) {
+ pktin.maxlen = newlen + 5;
+ pktin.data =
+ (pktin.data ==
+ NULL ? smalloc(pktin.maxlen +
+ APIEXTRA) : srealloc(pktin.data,
+ pktin.maxlen +
+ APIEXTRA));
+ if (!pktin.data)
+ fatalbox("Out of memory");
+ }
+ pktin.length = 5 + newlen;
+ memcpy(pktin.data + 5, newpayload, newlen);
+ sfree(newpayload);
+ }
+ }
+
+ pktin.savedpos = 6;
+ pktin.type = pktin.data[5];
+
+ log_packet(PKT_INCOMING, pktin.type, ssh2_pkt_type(pktin.type),
+ pktin.data+6, pktin.length-6);
+
+ switch (pktin.type) {
+ /*
+ * These packets we must handle instantly.
+ */
+ case SSH2_MSG_DISCONNECT:
+ {
+ /* log reason code in disconnect message */
+ char buf[256];
+ int reason = GET_32BIT(pktin.data + 6);
+ unsigned msglen = GET_32BIT(pktin.data + 10);
+ unsigned nowlen;
+ if (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) {
+ sprintf(buf, "Received disconnect message (%s)",
+ ssh2_disconnect_reasons[reason]);
+ } else {
+ sprintf(buf, "Received disconnect message (unknown type %d)",
+ reason);
+ }
+ logevent(buf);
+ strcpy(buf, "Disconnection message text: ");
+ nowlen = strlen(buf);
+ if (msglen > sizeof(buf) - nowlen - 1)
+ msglen = sizeof(buf) - nowlen - 1;
+ memcpy(buf + nowlen, pktin.data + 14, msglen);
+ buf[nowlen + msglen] = '\0';
+ 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",
+ buf+nowlen));
+ crReturn(0);
+ }
+ break;
+ case SSH2_MSG_IGNORE:
+ goto next_packet;
+ case SSH2_MSG_DEBUG:
+ {
+ /* log the debug message */
+ char buf[512];
+ /* int display = pktin.body[6]; */
+ int stringlen = GET_32BIT(pktin.data+7);
+ int prefix;
+ strcpy(buf, "Remote debug message: ");
+ prefix = strlen(buf);
+ if (stringlen > sizeof(buf)-prefix-1)
+ stringlen = sizeof(buf)-prefix-1;
+ memcpy(buf + prefix, pktin.data + 11, stringlen);
+ buf[prefix + stringlen] = '\0';
+ logevent(buf);
+ }
+ goto next_packet; /* FIXME: print the debug message */
+
+ /*
+ * These packets we need do nothing about here.
+ */
+ case SSH2_MSG_UNIMPLEMENTED:
+ case SSH2_MSG_SERVICE_REQUEST:
+ case SSH2_MSG_SERVICE_ACCEPT:
+ case SSH2_MSG_KEXINIT:
+ case SSH2_MSG_NEWKEYS:
+ case SSH2_MSG_KEXDH_INIT:
+ case SSH2_MSG_KEXDH_REPLY:
+ /* case SSH2_MSG_KEX_DH_GEX_REQUEST: duplicate case value */
+ /* case SSH2_MSG_KEX_DH_GEX_GROUP: duplicate case value */
+ case SSH2_MSG_KEX_DH_GEX_INIT:
+ case SSH2_MSG_KEX_DH_GEX_REPLY:
+ case SSH2_MSG_USERAUTH_REQUEST:
+ case SSH2_MSG_USERAUTH_FAILURE:
+ case SSH2_MSG_USERAUTH_SUCCESS:
+ case SSH2_MSG_USERAUTH_BANNER:
+ case SSH2_MSG_USERAUTH_PK_OK:
+ /* case SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: duplicate case value */
+ /* case SSH2_MSG_USERAUTH_INFO_REQUEST: duplicate case value */
+ case SSH2_MSG_USERAUTH_INFO_RESPONSE:
+ case SSH2_MSG_GLOBAL_REQUEST:
+ case SSH2_MSG_REQUEST_SUCCESS:
+ case SSH2_MSG_REQUEST_FAILURE:
+ case SSH2_MSG_CHANNEL_OPEN:
+ case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
+ case SSH2_MSG_CHANNEL_OPEN_FAILURE:
+ case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
+ case SSH2_MSG_CHANNEL_DATA:
+ case SSH2_MSG_CHANNEL_EXTENDED_DATA:
+ case SSH2_MSG_CHANNEL_EOF:
+ case SSH2_MSG_CHANNEL_CLOSE:
+ case SSH2_MSG_CHANNEL_REQUEST:
+ case SSH2_MSG_CHANNEL_SUCCESS:
+ case SSH2_MSG_CHANNEL_FAILURE:
+ break;
+
+ /*
+ * For anything else we send SSH2_MSG_UNIMPLEMENTED.
+ */
+ default:
+ ssh2_pkt_init(SSH2_MSG_UNIMPLEMENTED);
+ ssh2_pkt_adduint32(st->incoming_sequence - 1);
+ ssh2_pkt_send();
+ break;
+ }
+
+ crFinish(0);
+}
+
+static void ssh1_pktout_size(int len)
+{
+ int pad, biglen;
+
+ len += 5; /* type and CRC */
+ pad = 8 - (len % 8);
+ biglen = len + pad;
+
+ pktout.length = len - 5;
+ if (pktout.maxlen < biglen) {
+ pktout.maxlen = biglen;
+#ifdef MSCRYPTOAPI
+ /* Allocate enough buffer space for extra block
+ * for MS CryptEncrypt() */
+ pktout.data = (pktout.data == NULL ? smalloc(biglen + 12) :
+ srealloc(pktout.data, biglen + 12));
+#else
+ pktout.data = (pktout.data == NULL ? smalloc(biglen + 4) :
+ srealloc(pktout.data, biglen + 4));
+#endif
+ if (!pktout.data)
+ fatalbox("Out of memory");
+ }
+ pktout.body = pktout.data + 4 + pad + 1;
+}
+
+static void s_wrpkt_start(int type, int len)
+{
+ ssh1_pktout_size(len);
+ pktout.type = type;
+}
+
+static int s_wrpkt_prepare(void)
+{
+ int pad, len, biglen, i;
+ unsigned long crc;
+
+ pktout.body[-1] = pktout.type;
+
+ log_packet(PKT_OUTGOING, pktout.type, ssh1_pkt_type(pktout.type),
+ pktout.body, pktout.length);
+
+ if (ssh1_compressing) {
+ unsigned char *compblk;
+ int complen;
+ zlib_compress_block(pktout.body - 1, pktout.length + 1,
+ &compblk, &complen);
+ ssh1_pktout_size(complen - 1);
+ memcpy(pktout.body - 1, compblk, complen);
+ sfree(compblk);
+ }
+
+ len = pktout.length + 5; /* type and CRC */
+ pad = 8 - (len % 8);
+ biglen = len + pad;
+
+ for (i = 0; i < pad; i++)
+ pktout.data[i + 4] = random_byte();
+ crc = crc32(pktout.data + 4, biglen - 4);
+ PUT_32BIT(pktout.data + biglen, crc);
+ PUT_32BIT(pktout.data, len);
+
+ if (cipher)
+ cipher->encrypt(pktout.data + 4, biglen);
+
+ return biglen + 4;
+}
+
+static void s_wrpkt(void)
+{
+ int len, backlog;
+ len = s_wrpkt_prepare();
+ backlog = sk_write(s, pktout.data, len);
+ if (backlog > SSH_MAX_BACKLOG)
+ ssh_throttle_all(1, backlog);
+}
+
+static void s_wrpkt_defer(void)
+{
+ int len;
+ len = s_wrpkt_prepare();
+ if (deferred_len + len > deferred_size) {
+ deferred_size = deferred_len + len + 128;
+ deferred_send_data = srealloc(deferred_send_data, deferred_size);
+ }
+ memcpy(deferred_send_data + deferred_len, pktout.data, len);
+ deferred_len += len;
+}
+
+/*
+ * Construct a packet with the specified contents.
+ */
+static void construct_packet(int pkttype, va_list ap1, va_list ap2)
+{
+ unsigned char *p, *argp, argchar;
+ unsigned long argint;
+ int pktlen, argtype, arglen;
+ Bignum bn;
+
+ pktlen = 0;
+ while ((argtype = va_arg(ap1, int)) != PKT_END) {
+ switch (argtype) {
+ case PKT_INT:
+ (void) va_arg(ap1, int);
+ pktlen += 4;
+ break;
+ case PKT_CHAR:
+ (void) va_arg(ap1, char);
+ pktlen++;
+ break;
+ case PKT_DATA:
+ (void) va_arg(ap1, unsigned char *);
+ arglen = va_arg(ap1, int);
+ pktlen += arglen;
+ break;
+ case PKT_STR:
+ argp = va_arg(ap1, unsigned char *);
+ arglen = strlen(argp);
+ pktlen += 4 + arglen;
+ break;
+ case PKT_BIGNUM:
+ bn = va_arg(ap1, Bignum);
+ pktlen += ssh1_bignum_length(bn);
+ break;
+ default:
+ assert(0);
+ }
+ }
+
+ s_wrpkt_start(pkttype, pktlen);
+ p = pktout.body;
+
+ while ((argtype = va_arg(ap2, int)) != PKT_END) {
+ switch (argtype) {
+ case PKT_INT:
+ argint = va_arg(ap2, int);
+ PUT_32BIT(p, argint);
+ p += 4;
+ break;
+ case PKT_CHAR:
+ argchar = va_arg(ap2, unsigned char);
+ *p = argchar;
+ p++;
+ break;
+ case PKT_DATA:
+ argp = va_arg(ap2, unsigned char *);
+ arglen = va_arg(ap2, int);
+ memcpy(p, argp, arglen);
+ p += arglen;
+ break;
+ case PKT_STR:
+ argp = va_arg(ap2, unsigned char *);
+ arglen = strlen(argp);
+ PUT_32BIT(p, arglen);
+ memcpy(p + 4, argp, arglen);
+ p += 4 + arglen;
+ break;
+ case PKT_BIGNUM:
+ bn = va_arg(ap2, Bignum);
+ p += ssh1_write_bignum(p, bn);
+ break;
+ }
+ }
+}
+
+static void send_packet(int pkttype, ...)
+{
+ va_list ap1, ap2;
+ va_start(ap1, pkttype);
+ va_start(ap2, pkttype);
+ construct_packet(pkttype, ap1, ap2);
+ s_wrpkt();
+}
+
+static void defer_packet(int pkttype, ...)
+{
+ va_list ap1, ap2;
+ va_start(ap1, pkttype);
+ va_start(ap2, pkttype);
+ construct_packet(pkttype, ap1, ap2);
+ s_wrpkt_defer();
+}
+
+static int ssh_versioncmp(char *a, char *b)
+{
+ char *ae, *be;
+ unsigned long av, bv;
+
+ av = strtoul(a, &ae, 10);
+ bv = strtoul(b, &be, 10);
+ if (av != bv)
+ return (av < bv ? -1 : +1);
+ if (*ae == '.')
+ ae++;
+ if (*be == '.')
+ be++;
+ av = strtoul(ae, &ae, 10);
+ bv = strtoul(be, &be, 10);
+ if (av != bv)
+ return (av < bv ? -1 : +1);
+ return 0;
+}
+
+
+/*
+ * Utility routines for putting an SSH-protocol `string' and
+ * `uint32' into a SHA state.
+ */
+#include <stdio.h>
+static void sha_string(SHA_State * s, void *str, int len)
+{
+ unsigned char lenblk[4];
+ PUT_32BIT(lenblk, len);
+ SHA_Bytes(s, lenblk, 4);
+ SHA_Bytes(s, str, len);
+}
+
+static void sha_uint32(SHA_State * s, unsigned i)
+{
+ unsigned char intblk[4];
+ PUT_32BIT(intblk, i);
+ SHA_Bytes(s, intblk, 4);
+}
+
+/*
+ * SSH2 packet construction functions.
+ */
+static void ssh2_pkt_ensure(int length)
+{
+ if (pktout.maxlen < length) {
+ pktout.maxlen = length + 256;
+ pktout.data =
+ (pktout.data ==
+ NULL ? smalloc(pktout.maxlen +
+ APIEXTRA) : srealloc(pktout.data,
+ pktout.maxlen +
+ APIEXTRA));
+ if (!pktout.data)
+ fatalbox("Out of memory");
+ }
+}
+static void ssh2_pkt_adddata(void *data, int len)
+{
+ pktout.length += len;
+ ssh2_pkt_ensure(pktout.length);
+ memcpy(pktout.data + pktout.length - len, data, len);
+}
+static void ssh2_pkt_addbyte(unsigned char byte)
+{
+ ssh2_pkt_adddata(&byte, 1);
+}
+static void ssh2_pkt_init(int pkt_type)
+{
+ pktout.length = 5;
+ ssh2_pkt_addbyte((unsigned char) pkt_type);
+}
+static void ssh2_pkt_addbool(unsigned char value)
+{
+ ssh2_pkt_adddata(&value, 1);
+}
+static void ssh2_pkt_adduint32(unsigned long value)
+{
+ unsigned char x[4];
+ PUT_32BIT(x, value);
+ ssh2_pkt_adddata(x, 4);
+}
+static void ssh2_pkt_addstring_start(void)
+{
+ ssh2_pkt_adduint32(0);
+ pktout.savedpos = pktout.length;
+}
+static void ssh2_pkt_addstring_str(char *data)
+{
+ ssh2_pkt_adddata(data, strlen(data));
+ PUT_32BIT(pktout.data + pktout.savedpos - 4,
+ pktout.length - pktout.savedpos);
+}
+static void ssh2_pkt_addstring_data(char *data, int len)
+{
+ ssh2_pkt_adddata(data, len);
+ PUT_32BIT(pktout.data + pktout.savedpos - 4,
+ pktout.length - pktout.savedpos);
+}
+static void ssh2_pkt_addstring(char *data)
+{
+ ssh2_pkt_addstring_start();
+ ssh2_pkt_addstring_str(data);
+}
+static char *ssh2_mpint_fmt(Bignum b, int *len)
+{
+ unsigned char *p;
+ int i, n = (bignum_bitcount(b) + 7) / 8;
+ p = smalloc(n + 1);
+ if (!p)
+ fatalbox("out of memory");
+ p[0] = 0;
+ for (i = 1; i <= n; i++)
+ p[i] = bignum_byte(b, n - i);
+ i = 0;
+ while (i <= n && p[i] == 0 && (p[i + 1] & 0x80) == 0)
+ i++;
+ memmove(p, p + i, n + 1 - i);
+ *len = n + 1 - i;
+ return p;
+}
+static void ssh2_pkt_addmp(Bignum b)
+{
+ unsigned char *p;
+ int len;
+ p = ssh2_mpint_fmt(b, &len);
+ ssh2_pkt_addstring_start();
+ ssh2_pkt_addstring_data(p, len);
+ sfree(p);
+}
+
+/*
+ * Construct an SSH2 final-form packet: compress it, encrypt it,
+ * put the MAC on it. Final packet, ready to be sent, is stored in
+ * pktout.data. Total length is returned.
+ */
+static int ssh2_pkt_construct(void)
+{
+ int cipherblk, maclen, padding, i;
+ static unsigned long outgoing_sequence = 0;
+
+ log_packet(PKT_OUTGOING, pktout.data[5], ssh2_pkt_type(pktout.data[5]),
+ pktout.data + 6, pktout.length - 6);
+
+ /*
+ * Compress packet payload.
+ */
+ {
+ unsigned char *newpayload;
+ int newlen;
+ if (cscomp && cscomp->compress(pktout.data + 5, pktout.length - 5,
+ &newpayload, &newlen)) {
+ pktout.length = 5;
+ ssh2_pkt_adddata(newpayload, newlen);
+ sfree(newpayload);
+ }
+ }
+
+ /*
+ * Add padding. At least four bytes, and must also bring total
+ * length (minus MAC) up to a multiple of the block size.
+ */
+ cipherblk = cscipher ? cscipher->blksize : 8; /* block size */
+ cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */
+ padding = 4;
+ padding +=
+ (cipherblk - (pktout.length + padding) % cipherblk) % cipherblk;
+ maclen = csmac ? csmac->len : 0;
+ ssh2_pkt_ensure(pktout.length + padding + maclen);
+ pktout.data[4] = padding;
+ for (i = 0; i < padding; i++)
+ pktout.data[pktout.length + i] = random_byte();
+ PUT_32BIT(pktout.data, pktout.length + padding - 4);
+ if (csmac)
+ csmac->generate(pktout.data, pktout.length + padding,
+ outgoing_sequence);
+ outgoing_sequence++; /* whether or not we MACed */
+
+ if (cscipher)
+ cscipher->encrypt(pktout.data, pktout.length + padding);
+
+ /* Ready-to-send packet starts at pktout.data. We return length. */
+ return pktout.length + padding + maclen;
+}
+
+/*
+ * Construct and send an SSH2 packet immediately.
+ */
+static void ssh2_pkt_send(void)
+{
+ int len;
+ int backlog;
+ len = ssh2_pkt_construct();
+ backlog = sk_write(s, pktout.data, len);
+ if (backlog > SSH_MAX_BACKLOG)
+ ssh_throttle_all(1, backlog);
+}
+
+/*
+ * Construct an SSH2 packet and add it to a deferred data block.
+ * Useful for sending multiple packets in a single sk_write() call,
+ * to prevent a traffic-analysing listener from being able to work
+ * out the length of any particular packet (such as the password
+ * packet).
+ *
+ * Note that because SSH2 sequence-numbers its packets, this can
+ * NOT be used as an m4-style `defer' allowing packets to be
+ * constructed in one order and sent in another.
+ */
+static void ssh2_pkt_defer(void)
+{
+ int len = ssh2_pkt_construct();
+ if (deferred_len + len > deferred_size) {
+ deferred_size = deferred_len + len + 128;
+ deferred_send_data = srealloc(deferred_send_data, deferred_size);
+ }
+ memcpy(deferred_send_data + deferred_len, pktout.data, len);
+ deferred_len += len;
+}
+
+/*
+ * Send the whole deferred data block constructed by
+ * ssh2_pkt_defer() or SSH1's defer_packet().
+ */
+static void ssh_pkt_defersend(void)
+{
+ int backlog;
+ backlog = sk_write(s, deferred_send_data, deferred_len);
+ deferred_len = deferred_size = 0;
+ sfree(deferred_send_data);
+ deferred_send_data = NULL;
+ if (backlog > SSH_MAX_BACKLOG)
+ ssh_throttle_all(1, backlog);
+}
+
+#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
+
+static void sha_mpint(SHA_State * s, Bignum b)
+{
+ unsigned char *p;
+ int len;
+ p = ssh2_mpint_fmt(b, &len);
+ sha_string(s, p, len);
+ sfree(p);
+}
+
+/*
+ * SSH2 packet decode functions.
+ */
+static unsigned long ssh2_pkt_getuint32(void)
+{
+ unsigned long value;
+ if (pktin.length - pktin.savedpos < 4)
+ return 0; /* arrgh, no way to decline (FIXME?) */
+ value = GET_32BIT(pktin.data + pktin.savedpos);
+ pktin.savedpos += 4;
+ return value;
+}
+static int ssh2_pkt_getbool(void)
+{
+ unsigned long value;
+ if (pktin.length - pktin.savedpos < 1)
+ return 0; /* arrgh, no way to decline (FIXME?) */
+ value = pktin.data[pktin.savedpos] != 0;
+ pktin.savedpos++;
+ return value;
+}
+static void ssh2_pkt_getstring(char **p, int *length)
+{
+ *p = NULL;
+ *length = 0;
+ if (pktin.length - pktin.savedpos < 4)
+ return;
+ *length = GET_32BIT(pktin.data + pktin.savedpos);
+ pktin.savedpos += 4;
+ if (pktin.length - pktin.savedpos < *length)
+ return;
+ *p = pktin.data + pktin.savedpos;
+ pktin.savedpos += *length;
+}
+static Bignum ssh2_pkt_getmp(void)
+{
+ char *p;
+ int length;
+ Bignum b;
+
+ ssh2_pkt_getstring(&p, &length);
+ if (!p)
+ return NULL;
+ if (p[0] & 0x80) {
+ bombout(("internal error: Can't handle negative mpints"));
+ return NULL;
+ }
+ b = bignum_from_bytes(p, length);
+ return b;
+}
+
+/*
+ * Helper function to add an SSH2 signature blob to a packet.
+ * Expects to be shown the public key blob as well as the signature
+ * blob. Normally works just like ssh2_pkt_addstring, but will
+ * fiddle with the signature packet if necessary for
+ * BUG_SSH2_RSA_PADDING.
+ */
+static void ssh2_add_sigblob(void *pkblob_v, int pkblob_len,
+ void *sigblob_v, int sigblob_len)
+{
+ unsigned char *pkblob = (unsigned char *)pkblob_v;
+ unsigned char *sigblob = (unsigned char *)sigblob_v;
+
+ /* 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) &&
+ (GET_32BIT(pkblob) == 7 && !memcmp(pkblob+4, "ssh-rsa", 7))) {
+ int pos, len, siglen;
+
+ /*
+ * Find the byte length of the modulus.
+ */
+
+ pos = 4+7; /* skip over "ssh-rsa" */
+ pos += 4 + GET_32BIT(pkblob+pos); /* skip over exponent */
+ len = GET_32BIT(pkblob+pos); /* find length of modulus */
+ pos += 4; /* find modulus itself */
+ while (len > 0 && pkblob[pos] == 0)
+ len--, pos++;
+ /* debug(("modulus length is %d\n", len)); */
+
+ /*
+ * Now find the signature integer.
+ */
+ pos = 4+7; /* skip over "ssh-rsa" */
+ siglen = GET_32BIT(sigblob+pos);
+ /* debug(("signature length is %d\n", siglen)); */
+
+ if (len != siglen) {
+ unsigned char newlen[4];
+ ssh2_pkt_addstring_start();
+ ssh2_pkt_addstring_data(sigblob, pos);
+ /* dmemdump(sigblob, pos); */
+ pos += 4; /* point to start of actual sig */
+ PUT_32BIT(newlen, len);
+ ssh2_pkt_addstring_data(newlen, 4);
+ /* dmemdump(newlen, 4); */
+ newlen[0] = 0;
+ while (len-- > siglen) {
+ ssh2_pkt_addstring_data(newlen, 1);
+ /* dmemdump(newlen, 1); */
+ }
+ ssh2_pkt_addstring_data(sigblob+pos, siglen);
+ /* dmemdump(sigblob+pos, siglen); */
+ return;
+ }
+
+ /* Otherwise fall through and do it the easy way. */
+ }
+
+ ssh2_pkt_addstring_start();
+ ssh2_pkt_addstring_data(sigblob, sigblob_len);
+}
+
+/*
+ * Examine the remote side's version string and compare it against
+ * a list of known buggy implementations.
+ */
+static void ssh_detect_bugs(char *vstring)
+{
+ char *imp; /* pointer to implementation part */
+ imp = vstring;
+ imp += strcspn(imp, "-");
+ if (*imp) imp++;
+ imp += strcspn(imp, "-");
+ if (*imp) imp++;
+
+ ssh_remote_bugs = 0;
+
+ if (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") ||
+ !strcmp(imp, "1.2.20") || !strcmp(imp, "1.2.21") ||
+ !strcmp(imp, "1.2.22") || !strcmp(imp, "Cisco-1.25")) {
+ /*
+ * These versions don't support SSH1_MSG_IGNORE, so we have
+ * to use a different defence against password length
+ * sniffing.
+ */
+ ssh_remote_bugs |= BUG_CHOKES_ON_SSH1_IGNORE;
+ logevent("We believe remote version has SSH1 ignore bug");
+ }
+
+ if (!strcmp(imp, "Cisco-1.25")) {
+ /*
+ * These versions need a plain password sent; they can't
+ * handle having a null and a random length of data after
+ * the password.
+ */
+ ssh_remote_bugs |= BUG_NEEDS_SSH1_PLAIN_PASSWORD;
+ logevent("We believe remote version needs a plain SSH1 password");
+ }
+
+ if (!strcmp(imp, "Cisco-1.25")) {
+ /*
+ * These versions apparently have no clue whatever about
+ * RSA authentication and will panic and die if they see
+ * an AUTH_RSA message.
+ */
+ ssh_remote_bugs |= BUG_CHOKES_ON_RSA;
+ logevent("We believe remote version can't handle RSA authentication");
+ }
+
+ if (!strncmp(imp, "2.1.0", 5) || !strncmp(imp, "2.0.", 4) ||
+ !strncmp(imp, "2.2.0", 5) || !strncmp(imp, "2.3.0", 5) ||
+ !strncmp(imp, "2.1 ", 4)) {
+ /*
+ * These versions have the HMAC bug.
+ */
+ ssh_remote_bugs |= BUG_SSH2_HMAC;
+ logevent("We believe remote version has SSH2 HMAC bug");
+ }
+
+ if ((!strncmp(imp, "OpenSSH_2.", 10) && imp[10]>='5' && imp[10]<='9') ||
+ (!strncmp(imp, "OpenSSH_3.", 10) && imp[10]>='0' && imp[10]<='2')) {
+ /*
+ * These versions have the SSH2 RSA padding bug.
+ */
+ ssh_remote_bugs |= BUG_SSH2_RSA_PADDING;
+ logevent("We believe remote version has SSH2 RSA padding bug");
+ }
+}
+
+static int do_ssh_init(unsigned char c)
+{
+ static int vslen;
+ static char version[10];
+ static char *vstring;
+ static int vstrsize;
+ static char *vlog;
+ static int i;
+
+ crBegin;
+
+ /* Search for the string "SSH-" in the input. */
+ i = 0;
+ while (1) {
+ static const int transS[] = { 1, 2, 2, 1 };
+ static const int transH[] = { 0, 0, 3, 0 };
+ static const int transminus[] = { 0, 0, 0, -1 };
+ if (c == 'S')
+ i = transS[i];
+ else if (c == 'H')
+ i = transH[i];
+ else if (c == '-')
+ i = transminus[i];
+ else
+ i = 0;
+ if (i < 0)
+ break;
+ crReturn(1); /* get another character */
+ }
+
+ vstrsize = 16;
+ vstring = smalloc(vstrsize);
+ strcpy(vstring, "SSH-");
+ vslen = 4;
+ i = 0;
+ while (1) {
+ crReturn(1); /* get another char */
+ if (vslen >= vstrsize - 1) {
+ vstrsize += 16;
+ vstring = srealloc(vstring, vstrsize);
+ }
+ vstring[vslen++] = c;
+ if (i >= 0) {
+ if (c == '-') {
+ version[i] = '\0';
+ i = -1;
+ } else if (i < sizeof(version) - 1)
+ version[i++] = c;
+ } else if (c == '\n')
+ break;
+ }
+
+ ssh_agentfwd_enabled = FALSE;
+ rdpkt2_state.incoming_sequence = 0;
+
+ vstring[vslen] = 0;
+ vlog = smalloc(20 + vslen);
+ vstring[strcspn (vstring, "\r\n")] = '\0'; /* remove end-of-line chars */
+ sprintf(vlog, "Server version: %s", vstring);
+ logevent(vlog);
+ ssh_detect_bugs(vstring);
+ sfree(vlog);
+
+ /*
+ * Server version "1.99" means we can choose whether we use v1
+ * or v2 protocol. Choice is based on cfg.sshprot.
+ */
+ if (ssh_versioncmp(version, cfg.sshprot == 1 ? "2.0" : "1.99") >= 0) {
+ /*
+ * This is a v2 server. Begin v2 protocol.
+ */
+ char verstring[80], vlog[100];
+ sprintf(verstring, "SSH-2.0-%s", sshver);
+ SHA_Init(&exhashbase);
+ /*
+ * Hash our version string and their version string.
+ */
+ sha_string(&exhashbase, verstring, strlen(verstring));
+ sha_string(&exhashbase, vstring, strcspn(vstring, "\r\n"));
+ sprintf(vlog, "We claim version: %s", verstring);
+ logevent(vlog);
+ strcat(verstring, "\n");
+ logevent("Using SSH protocol version 2");
+ sk_write(s, verstring, strlen(verstring));
+ ssh_protocol = ssh2_protocol;
+ ssh_version = 2;
+ s_rdpkt = ssh2_rdpkt;
+ } else {
+ /*
+ * This is a v1 server. Begin v1 protocol.
+ */
+ char verstring[80], vlog[100];
+ sprintf(verstring, "SSH-%s-%s",
+ (ssh_versioncmp(version, "1.5") <= 0 ? version : "1.5"),
+ sshver);
+ sprintf(vlog, "We claim version: %s", verstring);
+ logevent(vlog);
+ strcat(verstring, "\n");
+
+ if (cfg.sshprot == 3) {
+ bombout(("SSH protocol version 2 required by user but not provided by server"));
+ crReturn(0);
+ }
+
+ logevent("Using SSH protocol version 1");
+ sk_write(s, verstring, strlen(verstring));
+ ssh_protocol = ssh1_protocol;
+ ssh_version = 1;
+ s_rdpkt = ssh1_rdpkt;
+ }
+ ssh_state = SSH_STATE_BEFORE_SIZE;
+
+ sfree(vstring);
+
+ crFinish(0);
+}
+
+static void ssh_gotdata(unsigned char *data, int datalen)
+{
+ crBegin;
+
+ /*
+ * To begin with, feed the characters one by one to the
+ * protocol initialisation / selection function do_ssh_init().
+ * When that returns 0, we're done with the initial greeting
+ * exchange and can move on to packet discipline.
+ */
+ while (1) {
+ int ret;
+ if (datalen == 0)
+ crReturnV; /* more data please */
+ ret = do_ssh_init(*data);
+ data++;
+ datalen--;
+ if (ret == 0)
+ break;
+ }
+
+ /*
+ * We emerge from that loop when the initial negotiation is
+ * over and we have selected an s_rdpkt function. Now pass
+ * everything to s_rdpkt, and then pass the resulting packets
+ * to the proper protocol handler.
+ */
+ if (datalen == 0)
+ crReturnV;
+ while (1) {
+ while (datalen > 0) {
+ if (s_rdpkt(&data, &datalen) == 0) {
+ if (ssh_state == SSH_STATE_CLOSED) {
+ return;
+ }
+ ssh_protocol(NULL, 0, 1);
+ if (ssh_state == SSH_STATE_CLOSED) {
+ return;
+ }
+ }
+ }
+ crReturnV;
+ }
+ crFinishV;
+}
+
+static int ssh_closing(Plug plug, char *error_msg, int error_code,
+ int calling_back)
+{
+ ssh_state = SSH_STATE_CLOSED;
+ if (s) {
+ sk_close(s);
+ s = NULL;
+ }
+ if (error_msg) {
+ /* A socket error has occurred. */
+ logevent(error_msg);
+ connection_fatal(error_msg);
+ } else {
+ /* Otherwise, the remote side closed the connection normally. */
+ }
+ return 0;
+}
+
+static int ssh_receive(Plug plug, int urgent, char *data, int len)
+{
+ ssh_gotdata(data, len);
+ if (ssh_state == SSH_STATE_CLOSED) {
+ if (s) {
+ sk_close(s);
+ s = NULL;
+ }
+ return 0;
+ }
+ return 1;
+}
+
+static void ssh_sent(Plug plug, int bufsize)
+{
+ /*
+ * If the send backlog on the SSH socket itself clears, we
+ * should unthrottle the whole world if it was throttled.
+ */
+ if (bufsize < SSH_MAX_BACKLOG)
+ ssh_throttle_all(0, bufsize);
+}
+
+/*
+ * Connect to specified host and port.
+ * Returns an error message, or NULL on success.
+ * Also places the canonical host name into `realhost'. It must be
+ * freed by the caller.
+ */
+static char *connect_to_host(char *host, int port, char **realhost, int nodelay)
+{
+ static struct plug_function_table fn_table = {
+ ssh_closing,
+ ssh_receive,
+ ssh_sent,
+ NULL
+ }, *fn_table_ptr = &fn_table;
+
+ SockAddr addr;
+ char *err;
+#ifdef FWHACK
+ char *FWhost;
+ int FWport;
+#endif
+
+ savedhost = smalloc(1 + strlen(host));
+ if (!savedhost)
+ fatalbox("Out of memory");
+ strcpy(savedhost, host);
+
+ if (port < 0)
+ port = 22; /* default ssh port */
+ savedport = port;
+
+#ifdef FWHACK
+ FWhost = host;
+ FWport = port;
+ host = FWSTR;
+ port = 23;
+#endif
+
+ /*
+ * Try to find host.
+ */
+ {
+ char buf[200];
+ sprintf(buf, "Looking up host \"%.170s\"", host);
+ logevent(buf);
+ }
+ addr = sk_namelookup(host, realhost);
+ if ((err = sk_addr_error(addr)))
+ return err;
+
+#ifdef FWHACK
+ *realhost = strdup(FWhost);
+#endif
+
+ /*
+ * Open socket.
+ */
+ {
+ char buf[200], addrbuf[100];
+ sk_getaddr(addr, addrbuf, 100);
+ sprintf(buf, "Connecting to %.100s port %d", addrbuf, port);
+ logevent(buf);
+ }
+ s = new_connection(addr, *realhost, port, 0, 1, nodelay, &fn_table_ptr);
+ if ((err = sk_socket_error(s))) {
+ s = NULL;
+ return err;
+ }
+
+#ifdef FWHACK
+ sk_write(s, "connect ", 8);
+ sk_write(s, FWhost, strlen(FWhost));
+ {
+ char buf[20];
+ sprintf(buf, " %d\n", FWport);
+ sk_write(s, buf, strlen(buf));
+ }
+#endif
+
+ return NULL;
+}
+
+/*
+ * Throttle or unthrottle the SSH connection.
+ */
+static void ssh1_throttle(int adjust)
+{
+ int old_count = ssh1_throttle_count;
+ ssh1_throttle_count += adjust;
+ assert(ssh1_throttle_count >= 0);
+ if (ssh1_throttle_count && !old_count) {
+ sk_set_frozen(s, 1);
+ } else if (!ssh1_throttle_count && old_count) {
+ sk_set_frozen(s, 0);
+ }
+}
+
+/*
+ * Throttle or unthrottle _all_ local data streams (for when sends
+ * on the SSH connection itself back up).
+ */
+static void ssh_throttle_all(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++) {
+ switch (c->type) {
+ case CHAN_MAINSESSION:
+ /*
+ * This is treated separately, outside the switch.
+ */
+ break;
+ case CHAN_X11:
+ x11_override_throttle(c->u.x11.s, enable);
+ break;
+ case CHAN_AGENT:
+ /* Agent channels require no buffer management. */
+ break;
+ case CHAN_SOCKDATA:
+ pfd_override_throttle(c->u.x11.s, enable);
+ break;
+ }
+ }
+}
+
+/*
+ * Handle the key exchange and user authentication phases.
+ */
+static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
+{
+ int i, j;
+ static int len;
+ static unsigned char *rsabuf, *keystr1, *keystr2;
+ unsigned char cookie[8];
+ struct RSAKey servkey, hostkey;
+ struct MD5Context md5c;
+ static unsigned long supported_ciphers_mask, supported_auths_mask;
+ static int tried_publickey, tried_agent;
+ static int tis_auth_refused, ccard_auth_refused;
+ static unsigned char session_id[16];
+ static int cipher_type;
+ static char username[100];
+ static void *publickey_blob;
+ int publickey_bloblen;
+
+ crBegin;
+
+ if (!ispkt)
+ crWaitUntil(ispkt);
+
+ if (pktin.type != SSH1_SMSG_PUBLIC_KEY) {
+ bombout(("Public key packet not received"));
+ crReturn(0);
+ }
+
+ logevent("Received public keys");
+
+ memcpy(cookie, pktin.body, 8);
+
+ i = makekey(pktin.body + 8, &servkey, &keystr1, 0);
+ j = makekey(pktin.body + 8 + i, &hostkey, &keystr2, 0);
+
+ /*
+ * Log the host key fingerprint.
+ */
+ {
+ char logmsg[80];
+ logevent("Host key fingerprint is:");
+ strcpy(logmsg, " ");
+ hostkey.comment = NULL;
+ rsa_fingerprint(logmsg + strlen(logmsg),
+ sizeof(logmsg) - strlen(logmsg), &hostkey);
+ logevent(logmsg);
+ }
+
+ ssh1_remote_protoflags = GET_32BIT(pktin.body + 8 + i + j);
+ supported_ciphers_mask = GET_32BIT(pktin.body + 12 + i + j);
+ supported_auths_mask = GET_32BIT(pktin.body + 16 + i + j);
+
+ ssh1_local_protoflags =
+ ssh1_remote_protoflags & SSH1_PROTOFLAGS_SUPPORTED;
+ ssh1_local_protoflags |= SSH1_PROTOFLAG_SCREEN_NUMBER;
+
+ MD5Init(&md5c);
+ MD5Update(&md5c, keystr2, hostkey.bytes);
+ MD5Update(&md5c, keystr1, servkey.bytes);
+ MD5Update(&md5c, pktin.body, 8);
+ MD5Final(session_id, &md5c);
+
+ for (i = 0; i < 32; i++)
+ session_key[i] = random_byte();
+
+ len = (hostkey.bytes > servkey.bytes ? hostkey.bytes : servkey.bytes);
+
+ rsabuf = smalloc(len);
+ if (!rsabuf)
+ fatalbox("Out of memory");
+
+ /*
+ * Verify the host key.
+ */
+ {
+ /*
+ * First format the key into a string.
+ */
+ int len = rsastr_len(&hostkey);
+ char fingerprint[100];
+ char *keystr = smalloc(len);
+ if (!keystr)
+ fatalbox("Out of memory");
+ rsastr_fmt(keystr, &hostkey);
+ rsa_fingerprint(fingerprint, sizeof(fingerprint), &hostkey);
+ verify_ssh_host_key(savedhost, savedport, "rsa", keystr,
+ fingerprint);
+ sfree(keystr);
+ }
+
+ for (i = 0; i < 32; i++) {
+ rsabuf[i] = session_key[i];
+ if (i < 16)
+ rsabuf[i] ^= session_id[i];
+ }
+
+ if (hostkey.bytes > servkey.bytes) {
+ rsaencrypt(rsabuf, 32, &servkey);
+ rsaencrypt(rsabuf, servkey.bytes, &hostkey);
+ } else {
+ rsaencrypt(rsabuf, 32, &hostkey);
+ rsaencrypt(rsabuf, hostkey.bytes, &servkey);
+ }
+
+ logevent("Encrypted session key");
+
+ {
+ int cipher_chosen = 0, warn = 0;
+ char *cipher_string = NULL;
+ for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) {
+ int next_cipher = cfg.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 SSH1, skipping");
+ } else {
+ switch (next_cipher) {
+ case CIPHER_3DES: cipher_type = SSH_CIPHER_3DES;
+ cipher_string = "3DES"; break;
+ case CIPHER_BLOWFISH: cipher_type = SSH_CIPHER_BLOWFISH;
+ cipher_string = "Blowfish"; break;
+ case CIPHER_DES: cipher_type = SSH_CIPHER_DES;
+ cipher_string = "single-DES"; break;
+ }
+ if (supported_ciphers_mask & (1 << cipher_type))
+ cipher_chosen = 1;
+ }
+ }
+ if (!cipher_chosen) {
+ if ((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"));
+ crReturn(0);
+ }
+
+ /* Warn about chosen cipher if necessary. */
+ if (warn)
+ askcipher(cipher_string, 0);
+ }
+
+ switch (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;
+ }
+
+ send_packet(SSH1_CMSG_SESSION_KEY,
+ PKT_CHAR, cipher_type,
+ PKT_DATA, cookie, 8,
+ PKT_CHAR, (len * 8) >> 8, PKT_CHAR, (len * 8) & 0xFF,
+ PKT_DATA, rsabuf, len,
+ PKT_INT, ssh1_local_protoflags, PKT_END);
+
+ logevent("Trying to enable encryption...");
+
+ sfree(rsabuf);
+
+ cipher = cipher_type == SSH_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 :
+ cipher_type == SSH_CIPHER_DES ? &ssh_des : &ssh_3des;
+ cipher->sesskey(session_key);
+
+ crWaitUntil(ispkt);
+
+ if (pktin.type != SSH1_SMSG_SUCCESS) {
+ bombout(("Encryption not successfully enabled"));
+ crReturn(0);
+ }
+
+ logevent("Successfully started encryption");
+
+ fflush(stdout);
+ {
+ static int pos = 0;
+ static char c;
+ if ((flags & FLAG_INTERACTIVE) && !*cfg.username) {
+ if (ssh_get_line && !ssh_getline_pw_only) {
+ if (!ssh_get_line("login as: ",
+ username, sizeof(username), FALSE)) {
+ /*
+ * get_line failed to get a username.
+ * Terminate.
+ */
+ logevent("No username provided. Abandoning session.");
+ ssh_state = SSH_STATE_CLOSED;
+ crReturn(1);
+ }
+ } else {
+ c_write_str("login as: ");
+ ssh_send_ok = 1;
+ while (pos >= 0) {
+ crWaitUntil(!ispkt);
+ while (inlen--)
+ switch (c = *in++) {
+ case 10:
+ case 13:
+ username[pos] = 0;
+ pos = -1;
+ break;
+ case 8:
+ case 127:
+ if (pos > 0) {
+ c_write_str("\b \b");
+ pos--;
+ }
+ break;
+ case 21:
+ case 27:
+ while (pos > 0) {
+ c_write_str("\b \b");
+ pos--;
+ }
+ break;
+ case 3:
+ case 4:
+ cleanup_exit(0);
+ break;
+ default:
+ if (((c >= ' ' && c <= '~') ||
+ ((unsigned char) c >= 160))
+ && pos < sizeof(username)-1) {
+ username[pos++] = c;
+ c_write(&c, 1);
+ }
+ break;
+ }
+ }
+ c_write_str("\r\n");
+ username[strcspn(username, "\n\r")] = '\0';
+ }
+ } else {
+ strncpy(username, cfg.username, 99);
+ username[99] = '\0';
+ }
+
+ send_packet(SSH1_CMSG_USER, PKT_STR, username, PKT_END);
+ {
+ char userlog[22 + sizeof(username)];
+ sprintf(userlog, "Sent username \"%s\"", username);
+ logevent(userlog);
+ if (flags & FLAG_INTERACTIVE &&
+ (!((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)))) {
+ strcat(userlog, "\r\n");
+ c_write_str(userlog);
+ }
+ }
+ }
+
+ crWaitUntil(ispkt);
+
+ if ((ssh_remote_bugs & BUG_CHOKES_ON_RSA)) {
+ /* We must not attempt PK auth. Pretend we've already tried it. */
+ tried_publickey = tried_agent = 1;
+ } else {
+ tried_publickey = tried_agent = 0;
+ }
+ tis_auth_refused = ccard_auth_refused = 0;
+ /* Load the public half of cfg.keyfile so we notice if it's in Pageant */
+ if (*cfg.keyfile) {
+ if (!rsakey_pubblob(cfg.keyfile, &publickey_blob, &publickey_bloblen))
+ publickey_blob = NULL;
+ } else
+ publickey_blob = NULL;
+
+ while (pktin.type == SSH1_SMSG_FAILURE) {
+ static char password[100];
+ static char prompt[200];
+ static int pos;