Add a new back-end function to return the exit code of the remote
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index 2163e21..e0fed88 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -15,9 +15,6 @@
 #define TRUE 1
 #endif
 
-/* uncomment this for packet level debugging */
-/* #define DUMP_PACKETS */
-
 #define logevent(s) { logevent(s); \
                       if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)) \
                       { fprintf(stderr, "%s\n", s); fflush(stderr); } }
 #define SSH2_MSG_CHANNEL_SUCCESS                  99   /* 0x63 */
 #define SSH2_MSG_CHANNEL_FAILURE                  100  /* 0x64 */
 
+/*
+ * Packet type contexts, so that ssh2_pkt_type can correctly decode
+ * the ambiguous type numbers back into the correct type strings.
+ */
+#define SSH2_PKTCTX_DHGROUP1         0x0001
+#define SSH2_PKTCTX_DHGEX            0x0002
+#define SSH2_PKTCTX_PUBLICKEY        0x0010
+#define SSH2_PKTCTX_PASSWORD         0x0020
+#define SSH2_PKTCTX_KBDINTER         0x0040
+#define SSH2_PKTCTX_AUTH_MASK        0x00F0
+
 #define SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1  /* 0x1 */
 #define SSH2_DISCONNECT_PROTOCOL_ERROR            2    /* 0x2 */
 #define SSH2_DISCONNECT_KEY_EXCHANGE_FAILED       3    /* 0x3 */
@@ -161,6 +169,97 @@ static const char *const ssh2_disconnect_reasons[] = {
 #define BUG_SSH2_HMAC                             2
 #define BUG_NEEDS_SSH1_PLAIN_PASSWORD            4
 
+static int ssh_pkt_ctx = 0;
+
+#define translate(x) if (type == x) return #x
+#define translatec(x,ctx) if (type == x && (ssh_pkt_ctx & ctx)) return #x
+char *ssh1_pkt_type(int type)
+{
+    translate(SSH1_MSG_DISCONNECT);
+    translate(SSH1_SMSG_PUBLIC_KEY);
+    translate(SSH1_CMSG_SESSION_KEY);
+    translate(SSH1_CMSG_USER);
+    translate(SSH1_CMSG_AUTH_RSA);
+    translate(SSH1_SMSG_AUTH_RSA_CHALLENGE);
+    translate(SSH1_CMSG_AUTH_RSA_RESPONSE);
+    translate(SSH1_CMSG_AUTH_PASSWORD);
+    translate(SSH1_CMSG_REQUEST_PTY);
+    translate(SSH1_CMSG_WINDOW_SIZE);
+    translate(SSH1_CMSG_EXEC_SHELL);
+    translate(SSH1_CMSG_EXEC_CMD);
+    translate(SSH1_SMSG_SUCCESS);
+    translate(SSH1_SMSG_FAILURE);
+    translate(SSH1_CMSG_STDIN_DATA);
+    translate(SSH1_SMSG_STDOUT_DATA);
+    translate(SSH1_SMSG_STDERR_DATA);
+    translate(SSH1_CMSG_EOF);
+    translate(SSH1_SMSG_EXIT_STATUS);
+    translate(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION);
+    translate(SSH1_MSG_CHANNEL_OPEN_FAILURE);
+    translate(SSH1_MSG_CHANNEL_DATA);
+    translate(SSH1_MSG_CHANNEL_CLOSE);
+    translate(SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION);
+    translate(SSH1_SMSG_X11_OPEN);
+    translate(SSH1_CMSG_PORT_FORWARD_REQUEST);
+    translate(SSH1_MSG_PORT_OPEN);
+    translate(SSH1_CMSG_AGENT_REQUEST_FORWARDING);
+    translate(SSH1_SMSG_AGENT_OPEN);
+    translate(SSH1_MSG_IGNORE);
+    translate(SSH1_CMSG_EXIT_CONFIRMATION);
+    translate(SSH1_CMSG_X11_REQUEST_FORWARDING);
+    translate(SSH1_CMSG_AUTH_RHOSTS_RSA);
+    translate(SSH1_MSG_DEBUG);
+    translate(SSH1_CMSG_REQUEST_COMPRESSION);
+    translate(SSH1_CMSG_AUTH_TIS);
+    translate(SSH1_SMSG_AUTH_TIS_CHALLENGE);
+    translate(SSH1_CMSG_AUTH_TIS_RESPONSE);
+    translate(SSH1_CMSG_AUTH_CCARD);
+    translate(SSH1_SMSG_AUTH_CCARD_CHALLENGE);
+    translate(SSH1_CMSG_AUTH_CCARD_RESPONSE);
+    return "unknown";
+}
+char *ssh2_pkt_type(int type)
+{
+    translate(SSH2_MSG_DISCONNECT);
+    translate(SSH2_MSG_IGNORE);
+    translate(SSH2_MSG_UNIMPLEMENTED);
+    translate(SSH2_MSG_DEBUG);
+    translate(SSH2_MSG_SERVICE_REQUEST);
+    translate(SSH2_MSG_SERVICE_ACCEPT);
+    translate(SSH2_MSG_KEXINIT);
+    translate(SSH2_MSG_NEWKEYS);
+    translatec(SSH2_MSG_KEXDH_INIT, SSH2_PKTCTX_DHGROUP1);
+    translatec(SSH2_MSG_KEXDH_REPLY, SSH2_PKTCTX_DHGROUP1);
+    translatec(SSH2_MSG_KEX_DH_GEX_REQUEST, SSH2_PKTCTX_DHGEX);
+    translatec(SSH2_MSG_KEX_DH_GEX_GROUP, SSH2_PKTCTX_DHGEX);
+    translatec(SSH2_MSG_KEX_DH_GEX_INIT, SSH2_PKTCTX_DHGEX);
+    translatec(SSH2_MSG_KEX_DH_GEX_REPLY, SSH2_PKTCTX_DHGEX);
+    translate(SSH2_MSG_USERAUTH_REQUEST);
+    translate(SSH2_MSG_USERAUTH_FAILURE);
+    translate(SSH2_MSG_USERAUTH_SUCCESS);
+    translate(SSH2_MSG_USERAUTH_BANNER);
+    translatec(SSH2_MSG_USERAUTH_PK_OK, SSH2_PKTCTX_PUBLICKEY);
+    translatec(SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, SSH2_PKTCTX_PASSWORD);
+    translatec(SSH2_MSG_USERAUTH_INFO_REQUEST, SSH2_PKTCTX_KBDINTER);
+    translatec(SSH2_MSG_USERAUTH_INFO_RESPONSE, SSH2_PKTCTX_KBDINTER);
+    translate(SSH2_MSG_GLOBAL_REQUEST);
+    translate(SSH2_MSG_REQUEST_SUCCESS);
+    translate(SSH2_MSG_REQUEST_FAILURE);
+    translate(SSH2_MSG_CHANNEL_OPEN);
+    translate(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
+    translate(SSH2_MSG_CHANNEL_OPEN_FAILURE);
+    translate(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+    translate(SSH2_MSG_CHANNEL_DATA);
+    translate(SSH2_MSG_CHANNEL_EXTENDED_DATA);
+    translate(SSH2_MSG_CHANNEL_EOF);
+    translate(SSH2_MSG_CHANNEL_CLOSE);
+    translate(SSH2_MSG_CHANNEL_REQUEST);
+    translate(SSH2_MSG_CHANNEL_SUCCESS);
+    translate(SSH2_MSG_CHANNEL_FAILURE);
+    return "unknown";
+}
+#undef translate
+#undef translatec
 
 #define GET_32BIT(cp) \
     (((unsigned long)(unsigned char)(cp)[0] << 24) | \
@@ -235,16 +334,6 @@ extern void pfd_override_throttle(Socket s, int enable);
 #define SSH_MAX_BACKLOG 32768
 #define OUR_V2_WINSIZE 16384
 
-/*
- * Ciphers for SSH2.
- */
-const static struct ssh2_ciphers *ciphers[] = {
-    &ssh2_aes,
-    &ssh2_blowfish,
-    &ssh2_3des,
-    &ssh2_des,
-};
-
 const static struct ssh_kex *kex_algs[] = {
     &ssh_diffiehellman_gex,
     &ssh_diffiehellman
@@ -410,6 +499,7 @@ static int ssh_echoing, ssh_editing;
 
 static tree234 *ssh_channels;         /* indexed by local id */
 static struct ssh_channel *mainchan;   /* primary session channel */
+static int ssh_exitcode = -1;
 
 static tree234 *ssh_rportfwds;
 
@@ -636,10 +726,6 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen)
 
     if (cipher)
        cipher->decrypt(pktin.data, st->biglen);
-#ifdef DUMP_PACKETS
-    debug(("Got packet len=%d pad=%d\n", st->len, st->pad));
-    dmemdump(pktin.data, st->biglen);
-#endif
 
     st->realcrc = crc32(pktin.data, st->biglen - 4);
     st->gotcrc = GET_32BIT(pktin.data + st->biglen - 4);
@@ -653,10 +739,6 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen)
     if (ssh1_compressing) {
        unsigned char *decompblk;
        int decomplen;
-#ifdef DUMP_PACKETS
-       debug(("Packet payload pre-decompression:\n"));
-       dmemdump(pktin.body - 1, pktin.length + 1);
-#endif
        zlib_decompress_block(pktin.body - 1, pktin.length + 1,
                              &decompblk, &decomplen);
 
@@ -671,12 +753,13 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen)
        memcpy(pktin.body - 1, decompblk, decomplen);
        sfree(decompblk);
        pktin.length = decomplen - 1;
-#ifdef DUMP_PACKETS
-       debug(("Packet payload post-decompression:\n"));
-       dmemdump(pktin.body - 1, pktin.length + 1);
-#endif
     }
 
+    pktin.type = pktin.body[-1];
+
+    log_packet(PKT_INCOMING, pktin.type, ssh1_pkt_type(pktin.type),
+              pktin.body, pktin.length);
+
     if (pktin.type == SSH1_SMSG_STDOUT_DATA ||
        pktin.type == SSH1_SMSG_STDERR_DATA ||
        pktin.type == SSH1_MSG_DEBUG ||
@@ -689,8 +772,6 @@ static int ssh1_rdpkt(unsigned char **data, int *datalen)
        }
     }
 
-    pktin.type = pktin.body[-1];
-
     if (pktin.type == SSH1_MSG_DEBUG) {
        /* log debug message */
        char buf[80];
@@ -817,11 +898,6 @@ static int ssh2_rdpkt(unsigned char **data, int *datalen)
        sccipher->decrypt(pktin.data + st->cipherblk,
                          st->packetlen - st->cipherblk);
 
-#ifdef DUMP_PACKETS
-    debug(("Got packet len=%d pad=%d\n", st->len, st->pad));
-    dmemdump(pktin.data, st->packetlen);
-#endif
-
     /*
      * Check the MAC.
      */
@@ -854,11 +930,6 @@ static int ssh2_rdpkt(unsigned char **data, int *datalen)
            }
            pktin.length = 5 + newlen;
            memcpy(pktin.data + 5, newpayload, newlen);
-#ifdef DUMP_PACKETS
-           debug(("Post-decompression payload:\n"));
-           dmemdump(pktin.data + 5, newlen);
-#endif
-
            sfree(newpayload);
        }
     }
@@ -866,6 +937,9 @@ static int ssh2_rdpkt(unsigned char **data, int *datalen)
     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);
+
     if (pktin.type == SSH2_MSG_IGNORE || pktin.type == SSH2_MSG_DEBUG)
        goto next_packet;              /* FIXME: print DEBUG message */
 
@@ -940,13 +1014,12 @@ static int s_wrpkt_prepare(void)
 
     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;
-#ifdef DUMP_PACKETS
-       debug(("Packet payload pre-compression:\n"));
-       dmemdump(pktout.body - 1, pktout.length + 1);
-#endif
        zlib_compress_block(pktout.body - 1, pktout.length + 1,
                            &compblk, &complen);
        ssh1_pktout_size(complen - 1);
@@ -964,10 +1037,6 @@ static int s_wrpkt_prepare(void)
     PUT_32BIT(pktout.data + biglen, crc);
     PUT_32BIT(pktout.data, len);
 
-#ifdef DUMP_PACKETS
-    debug(("Sending packet len=%d\n", biglen + 4));
-    dmemdump(pktout.data, biglen + 4);
-#endif
     if (cipher)
        cipher->encrypt(pktout.data + 4, biglen);
 
@@ -1231,18 +1300,15 @@ 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;
-#ifdef DUMP_PACKETS
-       if (cscomp && cscomp != &ssh_comp_none) {
-           debug(("Pre-compression payload:\n"));
-           dmemdump(pktout.data + 5, pktout.length - 5);
-       }
-#endif
        if (cscomp && cscomp->compress(pktout.data + 5, pktout.length - 5,
                                       &newpayload, &newlen)) {
            pktout.length = 5;
@@ -1271,11 +1337,6 @@ static int ssh2_pkt_construct(void)
                        outgoing_sequence);
     outgoing_sequence++;              /* whether or not we MACed */
 
-#ifdef DUMP_PACKETS
-    debug(("Sending packet len=%d\n", pktout.length + padding));
-    dmemdump(pktout.data, pktout.length + padding);
-#endif
-
     if (cscipher)
        cscipher->encrypt(pktout.data, pktout.length + padding);
 
@@ -3050,6 +3111,11 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
                /* 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
@@ -3153,6 +3219,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     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;
@@ -3170,8 +3237,10 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
            n_preferred_ciphers++;
            break;
          case CIPHER_DES:
-           preferred_ciphers[n_preferred_ciphers] = &ssh2_des;
-           n_preferred_ciphers++;
+           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;
@@ -3231,24 +3300,28 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     }
     /* 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++) {
-           ssh2_pkt_addstring_str(c->list[j]->name);
-           if (i < n_preferred_ciphers || j < c->nciphers - 1)
+           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++) {
-           ssh2_pkt_addstring_str(c->list[j]->name);
-           if (i < n_preferred_ciphers || j < c->nciphers - 1)
+           if (cipherstr_started)
                ssh2_pkt_addstring_str(",");
+           ssh2_pkt_addstring_str(c->list[j]->name);
+           cipherstr_started = 1;
        }
     }
     /* List client->server MAC algorithms. */
@@ -3438,6 +3511,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
      */
     if (kex == &ssh_diffiehellman_gex) {
        logevent("Doing Diffie-Hellman group exchange");
+       ssh_pkt_ctx |= SSH2_PKTCTX_DHGEX;
        /*
         * Work out how big a DH group we will need to allow that
         * much data.
@@ -3458,6 +3532,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
        kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT;
        kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY;
     } else {
+       ssh_pkt_ctx |= SSH2_PKTCTX_DHGROUP1;
        dh_setup_group1();
        kex_init_value = SSH2_MSG_KEXDH_INIT;
        kex_reply_value = SSH2_MSG_KEXDH_REPLY;
@@ -3715,6 +3790,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
      *    the username they will want to be able to get back and
      *    retype it!
      */
+    username[0] = '\0';
     do {
        static int pos;
        static char c;
@@ -3723,7 +3799,13 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
         * Get a username.
         */
        pos = 0;
-       if ((flags & FLAG_INTERACTIVE) && !*cfg.username) {
+       if (*username && !cfg.change_username) {
+           /*
+            * We got a username last time round this loop, and
+            * with change_username turned off we don't try to get
+            * it again.
+            */
+       } else if ((flags & FLAG_INTERACTIVE) && !*cfg.username) {
            if (ssh_get_line) {
                if (!ssh_get_line("login as: ",
                                  username, sizeof(username), FALSE)) {
@@ -3794,6 +3876,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
         * just in case it succeeds, and (b) so that we know what
         * authentication methods we can usefully try next.
         */
+       ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+
        ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
        ssh2_pkt_addstring(username);
        ssh2_pkt_addstring("ssh-connection");   /* service requested */
@@ -3915,6 +3999,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
            }
 
            method = 0;
+           ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
 
            if (!method && can_pubkey && agent_exists() && !tried_agent) {
                /*
@@ -3926,6 +4011,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                static int authed = FALSE;
                void *r;
 
+               ssh_pkt_ctx |= SSH2_PKTCTX_PUBLICKEY;
+
                tried_agent = TRUE;
 
                logevent("Pageant is running. Requesting keys.");
@@ -4062,6 +4149,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
 
                tried_pubkey_config = TRUE;
 
+               ssh_pkt_ctx |= SSH2_PKTCTX_PUBLICKEY;
+
                /*
                 * Try the public key supplied in the configuration.
                 *
@@ -4114,6 +4203,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
                tried_keyb_inter = TRUE;
 
+               ssh_pkt_ctx |= SSH2_PKTCTX_KBDINTER;
+
                ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
                ssh2_pkt_addstring(username);
                ssh2_pkt_addstring("ssh-connection");   /* service requested */
@@ -4139,6 +4230,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
                tried_keyb_inter = TRUE;
 
+               ssh_pkt_ctx |= SSH2_PKTCTX_KBDINTER;
+
                /* We've got packet with that "interactive" info
                   dump banners, and set its prompt as ours */
                {
@@ -4165,6 +4258,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
 
            if (!method && can_passwd) {
                method = AUTH_PASSWORD;
+               ssh_pkt_ctx |= SSH2_PKTCTX_PASSWORD;
                sprintf(pwprompt, "%.90s@%.90s's password: ", username,
                        savedhost);
                need_pw = TRUE;
@@ -4519,7 +4613,10 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                        ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST);
                        ssh2_pkt_addstring("tcpip-forward");
                        ssh2_pkt_addbool(1);/* want reply */
-                       ssh2_pkt_addstring("127.0.0.1");
+                       if (cfg.rport_acceptall)
+                           ssh2_pkt_addstring("0.0.0.0");
+                       else
+                           ssh2_pkt_addstring("127.0.0.1");
                        ssh2_pkt_adduint32(sport);
                        ssh2_pkt_send();
 
@@ -4867,12 +4964,25 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                 * See if that was the last channel left open.
                 */
                if (count234(ssh_channels) == 0) {
+#if 0
+                    /*
+                     * We used to send SSH_MSG_DISCONNECT here,
+                     * because I'd believed that _every_ conforming
+                     * SSH2 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.
+                     */
                    logevent("All channels closed. Disconnecting");
                    ssh2_pkt_init(SSH2_MSG_DISCONNECT);
                    ssh2_pkt_adduint32(SSH2_DISCONNECT_BY_APPLICATION);
                    ssh2_pkt_addstring("All open channels closed");
                    ssh2_pkt_addstring("en");   /* language tag */
                    ssh2_pkt_send();
+#endif
                    ssh_state = SSH_STATE_CLOSED;
                    crReturnV;
                }
@@ -4957,14 +5067,35 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                }
 
                /*
-                * We don't recognise any form of channel request,
-                * so we now either ignore the request or respond
-                * with CHANNEL_FAILURE, depending on want_reply.
+                * Having got the channel number, we now look at
+                * the request type string to see if it's something
+                * we recognise.
                 */
-               if (want_reply) {
-                   ssh2_pkt_init(SSH2_MSG_CHANNEL_FAILURE);
-                   ssh2_pkt_adduint32(c->remoteid);
-                   ssh2_pkt_send();
+               if (typelen == 11 && !memcmp(type, "exit-status", 11) &&
+                   c == mainchan) {
+                   /* We recognise "exit-status" on the primary channel. */
+                   char buf[100];
+                   ssh_exitcode = ssh2_pkt_getuint32();
+                   sprintf(buf, "Server sent command exit status %d",
+                           ssh_exitcode);
+                   logevent(buf);
+                   if (want_reply) {
+                       ssh2_pkt_init(SSH2_MSG_CHANNEL_SUCCESS);
+                       ssh2_pkt_adduint32(c->remoteid);
+                       ssh2_pkt_send();
+                   }
+               } 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.
+                    */
+                   if (want_reply) {
+                       ssh2_pkt_init(SSH2_MSG_CHANNEL_FAILURE);
+                       ssh2_pkt_adduint32(c->remoteid);
+                       ssh2_pkt_send();
+                   }
                }
            } else if (pktin.type == SSH2_MSG_CHANNEL_OPEN) {
                char *type;
@@ -5339,6 +5470,11 @@ static int ssh_ldisc(int option)
     return FALSE;
 }
 
+static int ssh_return_exitcode(void)
+{
+    return ssh_exitcode;
+}
+
 Backend ssh_backend = {
     ssh_init,
     ssh_send,
@@ -5346,6 +5482,7 @@ Backend ssh_backend = {
     ssh_size,
     ssh_special,
     ssh_socket,
+    ssh_return_exitcode,
     ssh_sendok,
     ssh_ldisc,
     ssh_unthrottle,