+ crWaitUntil(!ispkt);
+ while (inlen--)
+ switch (c = *in++) {
+ case 10:
+ case 13:
+ password[pos] = 0;
+ pos = -1;
+ break;
+ case 8:
+ case 127:
+ if (pos > 0)
+ pos--;
+ break;
+ case 21:
+ case 27:
+ pos = 0;
+ break;
+ case 3:
+ case 4:
+ cleanup_exit(0);
+ break;
+ default:
+ if (pos < sizeof(password)-1)
+ password[pos++] = c;
+ break;
+ }
+ }
+ c_write_str("\r\n");
+ }
+
+ tryauth:
+ if (pwpkt_type == SSH1_CMSG_AUTH_RSA) {
+ /*
+ * Try public key authentication with the specified
+ * key file.
+ */
+ static struct RSAKey pubkey;
+ static Bignum challenge, response;
+ static int i;
+ static unsigned char buffer[32];
+
+ tried_publickey = 1;
+ i = loadrsakey(cfg.keyfile, &pubkey, password);
+ if (i == 0) {
+ c_write_str("Couldn't load private key from ");
+ c_write_str(cfg.keyfile);
+ c_write_str(".\r\n");
+ continue; /* go and try password */
+ }
+ if (i == -1) {
+ c_write_str("Wrong passphrase.\r\n");
+ tried_publickey = 0;
+ continue; /* try again */
+ }
+
+ /*
+ * Send a public key attempt.
+ */
+ send_packet(SSH1_CMSG_AUTH_RSA,
+ PKT_BIGNUM, pubkey.modulus, PKT_END);
+
+ crWaitUntil(ispkt);
+ if (pktin.type == SSH1_SMSG_FAILURE) {
+ c_write_str("Server refused our public key.\r\n");
+ continue; /* go and try password */
+ }
+ if (pktin.type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
+ bombout(("Bizarre response to offer of public key"));
+ crReturn(0);
+ }
+ ssh1_read_bignum(pktin.body, &challenge);
+ response = rsadecrypt(challenge, &pubkey);
+ freebn(pubkey.private_exponent); /* burn the evidence */
+
+ for (i = 0; i < 32; i++) {
+ buffer[i] = bignum_byte(response, 31 - i);
+ }
+
+ MD5Init(&md5c);
+ MD5Update(&md5c, buffer, 32);
+ MD5Update(&md5c, session_id, 16);
+ MD5Final(buffer, &md5c);
+
+ send_packet(SSH1_CMSG_AUTH_RSA_RESPONSE,
+ PKT_DATA, buffer, 16, PKT_END);
+
+ crWaitUntil(ispkt);
+ if (pktin.type == SSH1_SMSG_FAILURE) {
+ if (flags & FLAG_VERBOSE)
+ c_write_str
+ ("Failed to authenticate with our public key.\r\n");
+ continue; /* go and try password */
+ } else if (pktin.type != SSH1_SMSG_SUCCESS) {
+ bombout(
+ ("Bizarre response to RSA authentication response"));
+ crReturn(0);
+ }
+
+ break; /* we're through! */
+ } else {
+ if (pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
+ /*
+ * Defence against traffic analysis: we send a
+ * whole bunch of packets containing strings of
+ * different lengths. One of these strings is the
+ * password, in a SSH1_CMSG_AUTH_PASSWORD packet.
+ * The others are all random data in
+ * SSH1_MSG_IGNORE packets. This way a passive
+ * listener can't tell which is the password, and
+ * hence can't deduce the password length.
+ *
+ * Anybody with a password length greater than 16
+ * bytes is going to have enough entropy in their
+ * password that a listener won't find it _that_
+ * much help to know how long it is. So what we'll
+ * do is:
+ *
+ * - if password length < 16, we send 15 packets
+ * containing string lengths 1 through 15
+ *
+ * - otherwise, we let N be the nearest multiple
+ * of 8 below the password length, and send 8
+ * packets containing string lengths N through
+ * N+7. This won't obscure the order of
+ * magnitude of the password length, but it will
+ * introduce a bit of extra uncertainty.
+ *
+ * A few servers (the old 1.2.18 through 1.2.22)
+ * can't deal with SSH1_MSG_IGNORE. For these
+ * servers, we need an alternative defence. We make
+ * use of the fact that the password is interpreted
+ * as a C string: so we can append a NUL, then some
+ * random data.
+ *
+ * One server (a Cisco one) can deal with neither
+ * SSH1_MSG_IGNORE _nor_ a padded password string.
+ * For this server we are left with no defences
+ * against password length sniffing.
+ */
+ if (!(ssh_remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) {
+ /*
+ * The server can deal with SSH1_MSG_IGNORE, so
+ * we can use the primary defence.
+ */
+ int bottom, top, pwlen, i;
+ char *randomstr;
+
+ pwlen = strlen(password);
+ if (pwlen < 16) {
+ bottom = 0; /* zero length passwords are OK! :-) */
+ top = 15;
+ } else {
+ bottom = pwlen & ~7;
+ top = bottom + 7;
+ }
+
+ assert(pwlen >= bottom && pwlen <= top);
+
+ randomstr = smalloc(top + 1);
+
+ for (i = bottom; i <= top; i++) {
+ if (i == pwlen)
+ defer_packet(pwpkt_type, PKT_STR, password,
+ PKT_END);
+ else {
+ for (j = 0; j < i; j++) {
+ do {
+ randomstr[j] = random_byte();
+ } while (randomstr[j] == '\0');
+ }
+ randomstr[i] = '\0';
+ defer_packet(SSH1_MSG_IGNORE,
+ PKT_STR, randomstr, PKT_END);
+ }
+ }
+ logevent("Sending password with camouflage packets");
+ ssh_pkt_defersend();
+ }
+ else if (!(ssh_remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
+ /*
+ * The server can't deal with SSH1_MSG_IGNORE
+ * but can deal with padded passwords, so we
+ * can use the secondary defence.
+ */
+ char string[64];
+ char *s;
+ int len;
+
+ len = strlen(password);
+ if (len < sizeof(string)) {
+ s = string;
+ strcpy(string, password);
+ len++; /* cover the zero byte */
+ while (len < sizeof(string)) {
+ string[len++] = (char) random_byte();
+ }
+ } else {
+ s = password;
+ }
+ logevent("Sending length-padded password");
+ send_packet(pwpkt_type, PKT_INT, len,
+ PKT_DATA, s, len, PKT_END);
+ } else {
+ /*
+ * The server has _both_
+ * BUG_CHOKES_ON_SSH1_IGNORE and
+ * BUG_NEEDS_SSH1_PLAIN_PASSWORD. There is
+ * therefore nothing we can do.
+ */
+ int len;
+ len = strlen(password);
+ logevent("Sending unpadded password");
+ send_packet(pwpkt_type, PKT_INT, len,
+ PKT_DATA, password, len, PKT_END);
+ }
+ } else {
+ send_packet(pwpkt_type, PKT_STR, password, PKT_END);
+ }
+ }
+ logevent("Sent password");
+ memset(password, 0, strlen(password));
+ crWaitUntil(ispkt);
+ if (pktin.type == SSH1_SMSG_FAILURE) {
+ if (flags & FLAG_VERBOSE)
+ c_write_str("Access denied\r\n");
+ logevent("Authentication refused");
+ } else if (pktin.type == SSH1_MSG_DISCONNECT) {
+ logevent("Received disconnect request");
+ ssh_state = SSH_STATE_CLOSED;
+ crReturn(1);
+ } else if (pktin.type != SSH1_SMSG_SUCCESS) {
+ bombout(("Strange packet received, type %d", pktin.type));
+ crReturn(0);
+ }
+ }
+
+ logevent("Authentication successful");
+
+ crFinish(1);
+}
+
+void sshfwd_close(struct ssh_channel *c)
+{
+ if (c && !c->closes) {
+ /*
+ * If the channel's remoteid is -1, we have sent
+ * CHANNEL_OPEN for this channel, but it hasn't even been
+ * acknowledged by the server. So we must set a close flag
+ * on it now, and then when the server acks the channel
+ * open, we can close it then.
+ */
+ if (c->remoteid != -1) {
+ if (ssh_version == 1) {
+ send_packet(SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
+ PKT_END);
+ } else {
+ ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
+ ssh2_pkt_adduint32(c->remoteid);
+ ssh2_pkt_send();
+ }
+ }
+ c->closes = 1;
+ if (c->type == CHAN_X11) {
+ c->u.x11.s = NULL;
+ logevent("Forwarded X11 connection terminated");
+ } else if (c->type == CHAN_SOCKDATA ||
+ c->type == CHAN_SOCKDATA_DORMANT) {
+ c->u.pfd.s = NULL;
+ logevent("Forwarded port closed");
+ }
+ }
+}
+
+int sshfwd_write(struct ssh_channel *c, char *buf, int len)
+{
+ if (ssh_version == 1) {
+ send_packet(SSH1_MSG_CHANNEL_DATA,
+ PKT_INT, c->remoteid,
+ PKT_INT, len, PKT_DATA, buf, len, PKT_END);
+ /*
+ * In SSH1 we can return 0 here - implying that forwarded
+ * connections are never individually throttled - because
+ * the only circumstance that can cause throttling will be
+ * the whole SSH connection backing up, in which case
+ * _everything_ will be throttled as a whole.
+ */
+ return 0;
+ } else {
+ ssh2_add_channel_data(c, buf, len);
+ return ssh2_try_send(c);
+ }
+}
+
+void sshfwd_unthrottle(struct ssh_channel *c, int bufsize)
+{
+ if (ssh_version == 1) {
+ if (c->v.v1.throttling && bufsize < SSH1_BUFFER_LIMIT) {
+ c->v.v1.throttling = 0;
+ ssh1_throttle(-1);
+ }
+ } else {
+ ssh2_set_window(c, OUR_V2_WINSIZE - bufsize);
+ }
+}
+
+static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
+{
+ crBegin;
+
+ random_init();
+
+ while (!do_ssh1_login(in, inlen, ispkt)) {
+ crReturnV;
+ }
+ if (ssh_state == SSH_STATE_CLOSED)
+ crReturnV;
+
+ if (cfg.agentfwd && agent_exists()) {
+ logevent("Requesting agent forwarding");
+ send_packet(SSH1_CMSG_AGENT_REQUEST_FORWARDING, PKT_END);
+ do {
+ crReturnV;
+ } while (!ispkt);
+ if (pktin.type != SSH1_SMSG_SUCCESS
+ && pktin.type != SSH1_SMSG_FAILURE) {
+ bombout(("Protocol confusion"));
+ crReturnV;
+ } else if (pktin.type == SSH1_SMSG_FAILURE) {
+ logevent("Agent forwarding refused");
+ } else {
+ logevent("Agent forwarding enabled");
+ ssh_agentfwd_enabled = TRUE;
+ }
+ }
+
+ if (cfg.x11_forward) {
+ char proto[20], data[64];
+ logevent("Requesting X11 forwarding");
+ x11_invent_auth(proto, sizeof(proto), data, sizeof(data));
+ if (ssh1_local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) {
+ send_packet(SSH1_CMSG_X11_REQUEST_FORWARDING,
+ PKT_STR, proto, PKT_STR, data,
+ PKT_INT, 0, PKT_END);
+ } else {
+ send_packet(SSH1_CMSG_X11_REQUEST_FORWARDING,
+ PKT_STR, proto, PKT_STR, data, PKT_END);
+ }
+ do {
+ crReturnV;
+ } while (!ispkt);
+ if (pktin.type != SSH1_SMSG_SUCCESS
+ && pktin.type != SSH1_SMSG_FAILURE) {
+ bombout(("Protocol confusion"));
+ crReturnV;
+ } else if (pktin.type == SSH1_SMSG_FAILURE) {
+ logevent("X11 forwarding refused");
+ } else {
+ logevent("X11 forwarding enabled");
+ ssh_X11_fwd_enabled = TRUE;
+ }
+ }
+
+ {
+ char type;
+ static char *e;
+ int n;
+ int sport,dport,sserv,dserv;
+ char sports[256], dports[256], host[256];
+ char buf[1024];
+ struct servent *se;
+
+ ssh_rportfwds = newtree234(ssh_rportcmp_ssh1);
+ /* Add port forwardings. */
+ e = cfg.portfwd;
+ while (*e) {
+ type = *e++;
+ n = 0;
+ while (*e && *e != '\t')
+ sports[n++] = *e++;
+ sports[n] = 0;
+ if (*e == '\t')
+ e++;
+ n = 0;
+ while (*e && *e != ':')
+ host[n++] = *e++;
+ host[n] = 0;
+ if (*e == ':')
+ e++;
+ n = 0;
+ while (*e)
+ dports[n++] = *e++;
+ dports[n] = 0;
+ e++;
+ dport = atoi(dports);
+ dserv = 0;
+ if (dport == 0) {
+ dserv = 1;
+ se = getservbyname(dports, NULL);
+ if (se != NULL) {
+ dport = ntohs(se->s_port);
+ } else {
+ sprintf(buf,
+ "Service lookup failed for destination port \"%s\"",
+ dports);
+ logevent(buf);
+ }
+ }
+ sport = atoi(sports);
+ sserv = 0;
+ if (sport == 0) {
+ sserv = 1;
+ se = getservbyname(sports, NULL);
+ if (se != NULL) {
+ sport = ntohs(se->s_port);
+ } else {
+ sprintf(buf,
+ "Service lookup failed for source port \"%s\"",
+ sports);
+ logevent(buf);
+ }
+ }
+ if (sport && dport) {
+ if (type == 'L') {
+ pfd_addforward(host, dport, sport);
+ sprintf(buf, "Local port %.*s%.*s%d%.*s forwarding to"
+ " %s:%.*s%.*s%d%.*s",
+ sserv ? strlen(sports) : 0, sports,
+ sserv, "(", sport, sserv, ")",
+ host,
+ dserv ? strlen(dports) : 0, dports,
+ dserv, "(", dport, dserv, ")");
+ logevent(buf);
+ } else {
+ struct ssh_rportfwd *pf;
+ pf = smalloc(sizeof(*pf));
+ strcpy(pf->dhost, host);
+ pf->dport = dport;
+ if (add234(ssh_rportfwds, pf) != pf) {
+ sprintf(buf,
+ "Duplicate remote port forwarding to %s:%d",
+ host, dport);
+ logevent(buf);
+ sfree(pf);
+ } else {
+ sprintf(buf, "Requesting remote port %.*s%.*s%d%.*s"
+ " forward to %s:%.*s%.*s%d%.*s",
+ sserv ? strlen(sports) : 0, sports,
+ sserv, "(", sport, sserv, ")",
+ host,
+ dserv ? strlen(dports) : 0, dports,
+ dserv, "(", dport, dserv, ")");
+ logevent(buf);
+ send_packet(SSH1_CMSG_PORT_FORWARD_REQUEST,
+ PKT_INT, sport,
+ PKT_STR, host,
+ PKT_INT, dport,
+ PKT_END);
+ do {
+ crReturnV;
+ } while (!ispkt);
+ if (pktin.type != SSH1_SMSG_SUCCESS
+ && pktin.type != SSH1_SMSG_FAILURE) {
+ bombout(("Protocol confusion"));
+ crReturnV;
+ } else if (pktin.type == SSH1_SMSG_FAILURE) {
+ c_write_str("Server refused port forwarding\r\n");
+ ssh_editing = ssh_echoing = 1;
+ }
+ logevent("Remote port forwarding enabled");
+ }
+ }
+ }
+ }
+ }
+
+ if (!cfg.nopty) {
+ send_packet(SSH1_CMSG_REQUEST_PTY,
+ PKT_STR, cfg.termtype,
+ PKT_INT, rows, PKT_INT, cols,
+ PKT_INT, 0, PKT_INT, 0, PKT_CHAR, 0, PKT_END);
+ ssh_state = SSH_STATE_INTERMED;
+ do {
+ crReturnV;
+ } while (!ispkt);
+ if (pktin.type != SSH1_SMSG_SUCCESS
+ && pktin.type != SSH1_SMSG_FAILURE) {
+ bombout(("Protocol confusion"));
+ crReturnV;
+ } else if (pktin.type == SSH1_SMSG_FAILURE) {
+ c_write_str("Server refused to allocate pty\r\n");
+ ssh_editing = ssh_echoing = 1;
+ }
+ logevent("Allocated pty");
+ } else {
+ ssh_editing = ssh_echoing = 1;
+ }
+
+ if (cfg.compression) {
+ send_packet(SSH1_CMSG_REQUEST_COMPRESSION, PKT_INT, 6, PKT_END);
+ do {
+ crReturnV;
+ } while (!ispkt);
+ if (pktin.type != SSH1_SMSG_SUCCESS
+ && pktin.type != SSH1_SMSG_FAILURE) {
+ bombout(("Protocol confusion"));
+ crReturnV;
+ } else if (pktin.type == SSH1_SMSG_FAILURE) {
+ c_write_str("Server refused to compress\r\n");
+ }
+ logevent("Started compression");
+ ssh1_compressing = TRUE;
+ zlib_compress_init();
+ zlib_decompress_init();
+ }
+
+ /*
+ * Start the shell or command.
+ *
+ * Special case: if the first-choice command is an SSH2
+ * subsystem (hence not usable here) and the second choice
+ * exists, we fall straight back to that.
+ */
+ {
+ char *cmd = cfg.remote_cmd_ptr;
+
+ if (cfg.ssh_subsys && cfg.remote_cmd_ptr2) {
+ cmd = cfg.remote_cmd_ptr2;
+ ssh_fallback_cmd = TRUE;
+ }
+ if (*cmd)
+ send_packet(SSH1_CMSG_EXEC_CMD, PKT_STR, cmd, PKT_END);
+ else
+ send_packet(SSH1_CMSG_EXEC_SHELL, PKT_END);
+ logevent("Started session");
+ }
+
+ ssh_state = SSH_STATE_SESSION;
+ if (size_needed)
+ ssh_size();
+ if (eof_needed)
+ ssh_special(TS_EOF);
+
+ ldisc_send(NULL, 0, 0); /* cause ldisc to notice changes */
+ ssh_send_ok = 1;
+ ssh_channels = newtree234(ssh_channelcmp);
+ while (1) {
+ crReturnV;
+ if (ispkt) {
+ if (pktin.type == SSH1_SMSG_STDOUT_DATA ||
+ pktin.type == SSH1_SMSG_STDERR_DATA) {
+ long len = GET_32BIT(pktin.body);
+ int bufsize =
+ from_backend(pktin.type == SSH1_SMSG_STDERR_DATA,
+ pktin.body + 4, len);
+ if (!ssh1_stdout_throttling && bufsize > SSH1_BUFFER_LIMIT) {
+ ssh1_stdout_throttling = 1;
+ ssh1_throttle(+1);
+ }
+ } else if (pktin.type == SSH1_MSG_DISCONNECT) {
+ ssh_state = SSH_STATE_CLOSED;
+ logevent("Received disconnect request");
+ crReturnV;
+ } else if (pktin.type == SSH1_SMSG_X11_OPEN) {
+ /* Remote side is trying to open a channel to talk to our
+ * X-Server. Give them back a local channel number. */
+ struct ssh_channel *c;
+
+ logevent("Received X11 connect request");
+ /* Refuse if X11 forwarding is disabled. */
+ if (!ssh_X11_fwd_enabled) {
+ send_packet(SSH1_MSG_CHANNEL_OPEN_FAILURE,
+ PKT_INT, GET_32BIT(pktin.body), PKT_END);
+ logevent("Rejected X11 connect request");
+ } else {
+ c = smalloc(sizeof(struct ssh_channel));
+
+ if (x11_init(&c->u.x11.s, cfg.x11_display, c) != NULL) {
+ logevent("opening X11 forward connection failed");
+ sfree(c);
+ send_packet(SSH1_MSG_CHANNEL_OPEN_FAILURE,
+ PKT_INT, GET_32BIT(pktin.body),
+ PKT_END);
+ } else {
+ logevent
+ ("opening X11 forward connection succeeded");
+ c->remoteid = GET_32BIT(pktin.body);
+ c->localid = alloc_channel_id();
+ c->closes = 0;
+ c->v.v1.throttling = 0;
+ c->type = CHAN_X11; /* identify channel type */
+ add234(ssh_channels, c);
+ send_packet(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
+ PKT_INT, c->remoteid, PKT_INT,
+ c->localid, PKT_END);
+ logevent("Opened X11 forward channel");
+ }
+ }
+ } else if (pktin.type == SSH1_SMSG_AGENT_OPEN) {
+ /* Remote side is trying to open a channel to talk to our
+ * agent. Give them back a local channel number. */
+ struct ssh_channel *c;
+
+ /* Refuse if agent forwarding is disabled. */
+ if (!ssh_agentfwd_enabled) {
+ send_packet(SSH1_MSG_CHANNEL_OPEN_FAILURE,
+ PKT_INT, GET_32BIT(pktin.body), PKT_END);
+ } else {
+ c = smalloc(sizeof(struct ssh_channel));
+ c->remoteid = GET_32BIT(pktin.body);
+ c->localid = alloc_channel_id();
+ c->closes = 0;
+ c->v.v1.throttling = 0;
+ c->type = CHAN_AGENT; /* identify channel type */
+ c->u.a.lensofar = 0;
+ add234(ssh_channels, c);
+ send_packet(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
+ PKT_INT, c->remoteid, PKT_INT, c->localid,
+ PKT_END);
+ }
+ } else if (pktin.type == SSH1_MSG_PORT_OPEN) {
+ /* Remote side is trying to open a channel to talk to a
+ * forwarded port. Give them back a local channel number. */
+ struct ssh_channel *c;
+ struct ssh_rportfwd pf;
+ int hostsize, port;
+ char host[256], buf[1024];
+ char *p, *h, *e;
+ c = smalloc(sizeof(struct ssh_channel));
+
+ hostsize = GET_32BIT(pktin.body+4);
+ for(h = host, p = pktin.body+8; hostsize != 0; hostsize--) {
+ if (h+1 < host+sizeof(host))
+ *h++ = *p;
+ p++;
+ }
+ *h = 0;
+ port = GET_32BIT(p);
+
+ strcpy(pf.dhost, host);
+ pf.dport = port;
+
+ if (find234(ssh_rportfwds, &pf, NULL) == NULL) {
+ sprintf(buf, "Rejected remote port open request for %s:%d",
+ host, port);
+ logevent(buf);
+ send_packet(SSH1_MSG_CHANNEL_OPEN_FAILURE,
+ PKT_INT, GET_32BIT(pktin.body), PKT_END);
+ } else {
+ sprintf(buf, "Received remote port open request for %s:%d",
+ host, port);
+ logevent(buf);
+ e = pfd_newconnect(&c->u.pfd.s, host, port, c);
+ if (e != NULL) {
+ char buf[256];
+ sprintf(buf, "Port open failed: %s", e);
+ logevent(buf);
+ sfree(c);
+ send_packet(SSH1_MSG_CHANNEL_OPEN_FAILURE,
+ PKT_INT, GET_32BIT(pktin.body),
+ PKT_END);
+ } else {
+ c->remoteid = GET_32BIT(pktin.body);
+ c->localid = alloc_channel_id();
+ c->closes = 0;
+ c->v.v1.throttling = 0;
+ c->type = CHAN_SOCKDATA; /* identify channel type */
+ add234(ssh_channels, c);
+ send_packet(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
+ PKT_INT, c->remoteid, PKT_INT,
+ c->localid, PKT_END);
+ logevent("Forwarded port opened successfully");
+ }
+ }
+
+ } else if (pktin.type == SSH1_MSG_CHANNEL_OPEN_CONFIRMATION) {
+ unsigned int remoteid = GET_32BIT(pktin.body);
+ unsigned int localid = GET_32BIT(pktin.body+4);
+ struct ssh_channel *c;
+
+ c = find234(ssh_channels, &remoteid, ssh_channelfind);
+ if (c && c->type == CHAN_SOCKDATA_DORMANT) {
+ c->remoteid = localid;
+ c->type = CHAN_SOCKDATA;
+ c->v.v1.throttling = 0;
+ pfd_confirm(c->u.pfd.s);
+ }
+
+ if (c && c->closes) {
+ /*
+ * We have a pending close on this channel,
+ * which we decided on before the server acked
+ * the channel open. So now we know the
+ * remoteid, we can close it again.
+ */
+ send_packet(SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
+ PKT_END);
+ }
+
+ } else if (pktin.type == SSH1_MSG_CHANNEL_OPEN_FAILURE) {
+ unsigned int remoteid = GET_32BIT(pktin.body);
+ unsigned int localid = GET_32BIT(pktin.body+4);
+ struct ssh_channel *c;
+
+ c = find234(ssh_channels, &remoteid, ssh_channelfind);
+ if (c && c->type == CHAN_SOCKDATA_DORMANT) {
+ logevent("Forwarded connection refused by server");
+ pfd_close(c->u.pfd.s);
+ del234(ssh_channels, c);
+ sfree(c);
+ }
+
+ } else if (pktin.type == SSH1_MSG_CHANNEL_CLOSE ||
+ pktin.type == SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION) {
+ /* Remote side closes a channel. */
+ unsigned i = GET_32BIT(pktin.body);
+ struct ssh_channel *c;
+ c = find234(ssh_channels, &i, ssh_channelfind);
+ if (c) {
+ int closetype;
+ closetype =
+ (pktin.type == SSH1_MSG_CHANNEL_CLOSE ? 1 : 2);
+ if (!(c->closes & closetype))
+ send_packet(pktin.type, PKT_INT, c->remoteid,
+ PKT_END);
+ if ((c->closes == 0) && (c->type == CHAN_X11)) {
+ logevent("Forwarded X11 connection terminated");
+ assert(c->u.x11.s != NULL);
+ x11_close(c->u.x11.s);
+ c->u.x11.s = NULL;
+ }
+ if ((c->closes == 0) && (c->type == CHAN_SOCKDATA)) {
+ logevent("Forwarded port closed");
+ assert(c->u.pfd.s != NULL);
+ pfd_close(c->u.pfd.s);
+ c->u.pfd.s = NULL;
+ }
+ c->closes |= closetype;
+ if (c->closes == 3) {
+ del234(ssh_channels, c);
+ sfree(c);
+ }
+ }
+ } else if (pktin.type == SSH1_MSG_CHANNEL_DATA) {
+ /* Data sent down one of our channels. */
+ int i = GET_32BIT(pktin.body);
+ int len = GET_32BIT(pktin.body + 4);
+ unsigned char *p = pktin.body + 8;
+ struct ssh_channel *c;
+ c = find234(ssh_channels, &i, ssh_channelfind);
+ if (c) {
+ int bufsize;
+ 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) {
+ int l = min(4 - c->u.a.lensofar, 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 = smalloc(c->u.a.totallen);
+ memcpy(c->u.a.message, c->u.a.msglen, 4);
+ }
+ if (c->u.a.lensofar >= 4 && len > 0) {
+ int l =
+ min(c->u.a.totallen - c->u.a.lensofar,
+ 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, *sentreply;
+ int replylen;
+ agent_query(c->u.a.message,
+ c->u.a.totallen, &reply,
+ &replylen);
+ if (reply)
+ sentreply = reply;
+ else {
+ /* Fake SSH_AGENT_FAILURE. */
+ sentreply = "\0\0\0\1\5";
+ replylen = 5;
+ }
+ send_packet(SSH1_MSG_CHANNEL_DATA,
+ PKT_INT, c->remoteid,
+ PKT_INT, replylen,
+ PKT_DATA, sentreply, replylen,
+ PKT_END);
+ if (reply)
+ sfree(reply);
+ sfree(c->u.a.message);
+ c->u.a.lensofar = 0;
+ }
+ }
+ bufsize = 0; /* agent channels never back up */
+ break;
+ }
+ if (!c->v.v1.throttling && bufsize > SSH1_BUFFER_LIMIT) {
+ c->v.v1.throttling = 1;
+ ssh1_throttle(+1);
+ }
+ }
+ } else 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 if (pktin.type == SSH1_SMSG_EXIT_STATUS) {
+ char buf[100];
+ ssh_exitcode = GET_32BIT(pktin.body);
+ sprintf(buf, "Server sent command exit status %d",
+ ssh_exitcode);
+ logevent(buf);
+ send_packet(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_state = SSH_STATE_CLOSED;
+ crReturnV;
+ } else {
+ bombout(("Strange packet received: type %d", pktin.type));
+ crReturnV;
+ }
+ } else {
+ while (inlen > 0) {
+ int len = min(inlen, 512);
+ send_packet(SSH1_CMSG_STDIN_DATA,
+ PKT_INT, len, PKT_DATA, in, len, PKT_END);
+ in += len;
+ inlen -= len;
+ }
+ }
+ }
+
+ crFinishV;
+}
+
+/*
+ * Utility routine for decoding comma-separated strings in KEXINIT.
+ */
+static int in_commasep_string(char *needle, char *haystack, int haylen)
+{
+ int 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 */
+ }
+}
+
+/*
+ * SSH2 key creation method.
+ */
+static void ssh2_mkkey(Bignum K, char *H, char *sessid, char chr,
+ char *keyspace)
+{
+ SHA_State s;
+ /* First 20 bytes. */
+ SHA_Init(&s);
+ sha_mpint(&s, K);
+ SHA_Bytes(&s, H, 20);
+ SHA_Bytes(&s, &chr, 1);
+ SHA_Bytes(&s, sessid, 20);
+ SHA_Final(&s, keyspace);
+ /* Next 20 bytes. */
+ SHA_Init(&s);
+ sha_mpint(&s, K);
+ SHA_Bytes(&s, H, 20);
+ SHA_Bytes(&s, keyspace, 20);
+ SHA_Final(&s, keyspace + 20);
+}
+
+/*
+ * Handle the SSH2 transport layer.
+ */
+static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
+{
+ static int i, j, len, nbits, pbits, warn;
+ static char *str;
+ static Bignum p, g, e, f, K;
+ static int kex_init_value, kex_reply_value;
+ static const struct ssh_mac **maclist;
+ static int nmacs;
+ static const struct ssh2_cipher *cscipher_tobe = NULL;
+ static const struct ssh2_cipher *sccipher_tobe = NULL;
+ static const struct ssh_mac *csmac_tobe = NULL;
+ static const struct ssh_mac *scmac_tobe = NULL;
+ static const struct ssh_compress *cscomp_tobe = NULL;
+ static const struct ssh_compress *sccomp_tobe = NULL;
+ static char *hostkeydata, *sigdata, *keystr, *fingerprint;
+ static int hostkeylen, siglen;
+ static void *hkey; /* actual host key */
+ static unsigned char exchange_hash[20];
+ static unsigned char keyspace[40];
+ static int n_preferred_ciphers;
+ static const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
+ static const struct ssh_compress *preferred_comp;
+ static int cipherstr_started;
+ static int first_kex;
+
+ crBegin;
+ random_init();
+ first_kex = 1;
+
+ /*
+ * Set up the preferred ciphers. (NULL => warn below here)
+ */
+ n_preferred_ciphers = 0;
+ for (i = 0; i < CIPHER_MAX; i++) {
+ switch (cfg.ssh_cipherlist[i]) {
+ case CIPHER_BLOWFISH:
+ preferred_ciphers[n_preferred_ciphers] = &ssh2_blowfish;
+ n_preferred_ciphers++;
+ break;
+ case CIPHER_DES:
+ if (cfg.ssh2_des_cbc) {
+ preferred_ciphers[n_preferred_ciphers] = &ssh2_des;
+ n_preferred_ciphers++;
+ }
+ break;
+ case CIPHER_3DES:
+ preferred_ciphers[n_preferred_ciphers] = &ssh2_3des;
+ n_preferred_ciphers++;
+ break;
+ case CIPHER_AES:
+ preferred_ciphers[n_preferred_ciphers] = &ssh2_aes;
+ n_preferred_ciphers++;
+ break;
+ case CIPHER_WARN:
+ /* Flag for later. Don't bother if it's the last in
+ * the list. */
+ if (i < CIPHER_MAX - 1) {
+ preferred_ciphers[n_preferred_ciphers] = NULL;
+ n_preferred_ciphers++;
+ }
+ break;
+ }
+ }
+
+ /*
+ * Set up preferred compression.
+ */
+ if (cfg.compression)
+ preferred_comp = &ssh_zlib;
+ else
+ preferred_comp = &ssh_comp_none;
+
+ /*
+ * Be prepared to work around the buggy MAC problem.
+ */
+ if (cfg.buggymac || (ssh_remote_bugs & BUG_SSH2_HMAC))
+ maclist = buggymacs, nmacs = lenof(buggymacs);
+ else
+ maclist = macs, nmacs = lenof(macs);
+
+ begin_key_exchange:
+ /*
+ * Construct and send our key exchange packet.
+ */
+ ssh2_pkt_init(SSH2_MSG_KEXINIT);
+ for (i = 0; i < 16; i++)
+ ssh2_pkt_addbyte((unsigned char) random_byte());
+ /* List key exchange algorithms. */
+ ssh2_pkt_addstring_start();
+ for (i = 0; i < lenof(kex_algs); i++) {
+ ssh2_pkt_addstring_str(kex_algs[i]->name);
+ if (i < lenof(kex_algs) - 1)
+ ssh2_pkt_addstring_str(",");
+ }
+ /* List server host key algorithms. */
+ ssh2_pkt_addstring_start();
+ for (i = 0; i < lenof(hostkey_algs); i++) {
+ ssh2_pkt_addstring_str(hostkey_algs[i]->name);
+ if (i < lenof(hostkey_algs) - 1)
+ ssh2_pkt_addstring_str(",");
+ }
+ /* List client->server encryption algorithms. */
+ ssh2_pkt_addstring_start();
+ cipherstr_started = 0;
+ for (i = 0; i < n_preferred_ciphers; i++) {
+ const struct ssh2_ciphers *c = preferred_ciphers[i];
+ if (!c) continue; /* warning flag */
+ for (j = 0; j < c->nciphers; j++) {
+ if (cipherstr_started)
+ ssh2_pkt_addstring_str(",");
+ ssh2_pkt_addstring_str(c->list[j]->name);
+ cipherstr_started = 1;
+ }
+ }
+ /* List server->client encryption algorithms. */
+ ssh2_pkt_addstring_start();
+ cipherstr_started = 0;
+ for (i = 0; i < n_preferred_ciphers; i++) {
+ const struct ssh2_ciphers *c = preferred_ciphers[i];
+ if (!c) continue; /* warning flag */
+ for (j = 0; j < c->nciphers; j++) {
+ if (cipherstr_started)
+ ssh2_pkt_addstring_str(",");
+ ssh2_pkt_addstring_str(c->list[j]->name);
+ cipherstr_started = 1;
+ }
+ }
+ /* List client->server MAC algorithms. */
+ ssh2_pkt_addstring_start();
+ for (i = 0; i < nmacs; i++) {
+ ssh2_pkt_addstring_str(maclist[i]->name);
+ if (i < nmacs - 1)
+ ssh2_pkt_addstring_str(",");
+ }
+ /* List server->client MAC algorithms. */
+ ssh2_pkt_addstring_start();
+ for (i = 0; i < nmacs; i++) {
+ ssh2_pkt_addstring_str(maclist[i]->name);
+ if (i < nmacs - 1)
+ ssh2_pkt_addstring_str(",");
+ }
+ /* List client->server compression algorithms. */
+ ssh2_pkt_addstring_start();
+ for (i = 0; i < lenof(compressions) + 1; i++) {
+ const struct ssh_compress *c =
+ i == 0 ? preferred_comp : compressions[i - 1];
+ ssh2_pkt_addstring_str(c->name);
+ if (i < lenof(compressions))
+ ssh2_pkt_addstring_str(",");
+ }
+ /* List server->client compression algorithms. */
+ ssh2_pkt_addstring_start();
+ for (i = 0; i < lenof(compressions) + 1; i++) {
+ const struct ssh_compress *c =
+ i == 0 ? preferred_comp : compressions[i - 1];
+ ssh2_pkt_addstring_str(c->name);
+ if (i < lenof(compressions))
+ ssh2_pkt_addstring_str(",");
+ }
+ /* List client->server languages. Empty list. */
+ ssh2_pkt_addstring_start();
+ /* List server->client languages. Empty list. */
+ ssh2_pkt_addstring_start();
+ /* First KEX packet does _not_ follow, because we're not that brave. */
+ ssh2_pkt_addbool(FALSE);
+ /* Reserved. */
+ ssh2_pkt_adduint32(0);
+
+ exhash = exhashbase;
+ sha_string(&exhash, pktout.data + 5, pktout.length - 5);
+
+ ssh2_pkt_send();
+
+ if (!ispkt)
+ crWaitUntil(ispkt);
+ sha_string(&exhash, pktin.data + 5, pktin.length - 5);
+
+ /*
+ * Now examine the other side's KEXINIT to see what we're up
+ * to.
+ */
+ if (pktin.type != SSH2_MSG_KEXINIT) {
+ bombout(("expected key exchange packet from server"));
+ crReturn(0);
+ }
+ kex = NULL;
+ hostkey = NULL;
+ cscipher_tobe = NULL;
+ sccipher_tobe = NULL;
+ csmac_tobe = NULL;
+ scmac_tobe = NULL;
+ cscomp_tobe = NULL;
+ sccomp_tobe = NULL;
+ pktin.savedpos += 16; /* skip garbage cookie */
+ ssh2_pkt_getstring(&str, &len); /* key exchange algorithms */
+ for (i = 0; i < lenof(kex_algs); i++) {
+ if (in_commasep_string(kex_algs[i]->name, str, len)) {
+ kex = kex_algs[i];
+ break;
+ }
+ }
+ ssh2_pkt_getstring(&str, &len); /* host key algorithms */
+ for (i = 0; i < lenof(hostkey_algs); i++) {
+ if (in_commasep_string(hostkey_algs[i]->name, str, len)) {
+ hostkey = hostkey_algs[i];
+ break;
+ }
+ }
+ ssh2_pkt_getstring(&str, &len); /* client->server cipher */
+ warn = 0;
+ for (i = 0; i < n_preferred_ciphers; i++) {
+ const struct ssh2_ciphers *c = preferred_ciphers[i];
+ if (!c) {
+ warn = 1;
+ } else {
+ for (j = 0; j < c->nciphers; j++) {
+ if (in_commasep_string(c->list[j]->name, str, len)) {
+ cscipher_tobe = c->list[j];