When we disconnect because we have no supported authentication
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index 92fadd0..43d642d 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -13,6 +13,7 @@
 #include "tree234.h"
 #include "ssh.h"
 #ifndef NO_GSSAPI
+#include "sshgssc.h"
 #include "sshgss.h"
 #endif
 
@@ -194,6 +195,7 @@ static const char *const ssh2_disconnect_reasons[] = {
 #define BUG_SSH2_REKEY                           64
 #define BUG_SSH2_PK_SESSIONID                   128
 #define BUG_SSH2_MAXPKT                                256
+#define BUG_CHOKES_ON_SSH2_IGNORE               512
 
 /*
  * Codes for terminal modes.
@@ -588,6 +590,17 @@ struct ssh_channel {
      * A channel is completely finished with when all four bits are set.
      */
     int closes;
+
+    /*
+     * This flag indicates that a close is pending on the outgoing
+     * side of the channel: that is, wherever we're getting the data
+     * for this channel has sent us some data followed by EOF. We
+     * can't actually close the channel until we've finished sending
+     * the data, so we set this flag instead to remind us to
+     * initiate the closing process once our buffer is clear.
+     */
+    int pending_close;
+
     /*
      * True if this channel is causing the underlying connection to be
      * throttled.
@@ -2011,7 +2024,8 @@ static void ssh2_pkt_defer_noqueue(Ssh ssh, struct Packet *pkt, int noignore)
 {
     int len;
     if (ssh->cscipher != NULL && (ssh->cscipher->flags & SSH_CIPHER_IS_CBC) &&
-       ssh->deferred_len == 0 && !noignore) {
+       ssh->deferred_len == 0 && !noignore &&
+       !(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) {
        /*
         * Interpose an SSH_MSG_IGNORE to ensure that user data don't
         * get encrypted with a known IV.
@@ -2141,7 +2155,8 @@ static void ssh2_pkt_send_with_padding(Ssh ssh, struct Packet *pkt,
         * unavailable, we don't do this trick at all, because we
         * gain nothing by it.)
         */
-       if (ssh->cscipher) {
+       if (ssh->cscipher &&
+           !(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) {
            int stringlen, i;
 
            stringlen = (256 - ssh->deferred_len);
@@ -2508,6 +2523,15 @@ static void ssh_detect_bugs(Ssh ssh, char *vstring)
        ssh->remote_bugs |= BUG_SSH2_MAXPKT;
        logevent("We believe remote version ignores SSH-2 maximum packet size");
     }
+
+    if (ssh->cfg.sshbug_ignore2 == FORCE_ON) {
+       /*
+        * Servers that don't support SSH2_MSG_IGNORE. Currently,
+        * none detected automatically.
+        */
+       ssh->remote_bugs |= BUG_CHOKES_ON_SSH2_IGNORE;
+       logevent("We believe remote version has SSH-2 ignore bug");
+    }
 }
 
 /*
@@ -2853,6 +2877,8 @@ static int ssh_do_close(Ssh ssh, int notify_exit)
            del234(ssh->portfwds, pf); /* moving next one to index 0 */
            free_portfwd(pf);
        }
+       freetree234(ssh->portfwds);
+       ssh->portfwds = NULL;
     }
 
     return ret;
@@ -4150,14 +4176,42 @@ void sshfwd_close(struct ssh_channel *c)
            if (ssh->version == 1) {
                send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
                            PKT_END);
+               c->closes = 1;                 /* sent MSG_CLOSE */
            } else {
-               struct Packet *pktout;
-               pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
-               ssh2_pkt_adduint32(pktout, c->remoteid);
-               ssh2_pkt_send(ssh, pktout);
+               int bytes_to_send = bufchain_size(&c->v.v2.outbuffer);
+               if (bytes_to_send > 0) {
+                   /*
+                    * If we still have unsent data in our outgoing
+                    * buffer for this channel, we can't actually
+                    * initiate a close operation yet or that data
+                    * will be lost. Instead, set the pending_close
+                    * flag so that when we do clear the buffer
+                    * we'll start closing the channel.
+                    */
+                   char logmsg[160] = {'\0'};
+                   sprintf(
+                           logmsg,
+                           "Forwarded port pending to be closed : "
+                           "%d bytes remaining",
+                           bytes_to_send);
+                   logevent(logmsg);
+
+                   c->pending_close = TRUE;
+               } else {
+                   /*
+                    * No locally buffered data, so we can send the
+                    * close message immediately.
+                    */
+                   struct Packet *pktout;
+                   pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
+                   ssh2_pkt_adduint32(pktout, c->remoteid);
+                   ssh2_pkt_send(ssh, pktout);
+                   c->closes = 1;                     /* sent MSG_CLOSE */
+                   logevent("Nothing left to send, closing channel");
+               }
            }
        }
-       c->closes = 1;                 /* sent MSG_CLOSE */
+
        if (c->type == CHAN_X11) {
            c->u.x11.s = NULL;
            logevent("Forwarded X11 connection terminated");
@@ -4296,6 +4350,7 @@ static void ssh_rportfwd_succfail(Ssh ssh, struct Packet *pktin, void *ctx)
 
        rpf = del234(ssh->rportfwds, pf);
        assert(rpf == pf);
+       pf->pfrec->remote = NULL;
        free_rportfwd(pf);
     }
 }
@@ -4427,12 +4482,19 @@ static void ssh_setup_portfwd(Ssh ssh, const Config *cfg)
 
            epfrec = add234(ssh->portfwds, pfrec);
            if (epfrec != pfrec) {
+               if (epfrec->status == DESTROY) {
+                   /*
+                    * We already have a port forwarding up and running
+                    * with precisely these parameters. Hence, no need
+                    * to do anything; simply re-tag the existing one
+                    * as KEEP.
+                    */
+                   epfrec->status = KEEP;
+               }
                /*
-                * We already have a port forwarding with precisely
-                * these parameters. Hence, no need to do anything;
-                * simply tag the existing one as KEEP.
+                * Anything else indicates that there was a duplicate
+                * in our input, which we'll silently ignore.
                 */
-               epfrec->status = KEEP;
                free_portfwd(pfrec);
            } else {
                pfrec->status = CREATE;
@@ -4465,6 +4527,8 @@ static void ssh_setup_portfwd(Ssh ssh, const Config *cfg)
            logeventf(ssh, "Cancelling %s", message);
            sfree(message);
 
+           /* epf->remote or epf->local may be NULL if setting up a
+            * forwarding failed. */
            if (epf->remote) {
                struct ssh_rportfwd *rpf = epf->remote;
                struct Packet *pktout;
@@ -4674,6 +4738,7 @@ static void ssh1_smsg_x11_open(Ssh ssh, struct Packet *pktin)
            c->halfopen = FALSE;
            c->localid = alloc_channel_id(ssh);
            c->closes = 0;
+           c->pending_close = FALSE;
            c->throttling_conn = 0;
            c->type = CHAN_X11; /* identify channel type */
            add234(ssh->channels, c);
@@ -4703,6 +4768,7 @@ static void ssh1_smsg_agent_open(Ssh ssh, struct Packet *pktin)
        c->halfopen = FALSE;
        c->localid = alloc_channel_id(ssh);
        c->closes = 0;
+       c->pending_close = FALSE;
        c->throttling_conn = 0;
        c->type = CHAN_AGENT;   /* identify channel type */
        c->u.a.lensofar = 0;
@@ -4757,6 +4823,7 @@ static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin)
            c->halfopen = FALSE;
            c->localid = alloc_channel_id(ssh);
            c->closes = 0;
+           c->pending_close = FALSE;
            c->throttling_conn = 0;
            c->type = CHAN_SOCKDATA;    /* identify channel type */
            add234(ssh->channels, c);
@@ -6323,7 +6390,7 @@ static int ssh2_try_send(struct ssh_channel *c)
     return bufchain_size(&c->v.v2.outbuffer);
 }
 
-static void ssh2_try_send_and_unthrottle(struct ssh_channel *c)
+static void ssh2_try_send_and_unthrottle(Ssh ssh, struct ssh_channel *c)
 {
     int bufsize;
     if (c->closes)
@@ -6347,6 +6414,19 @@ static void ssh2_try_send_and_unthrottle(struct ssh_channel *c)
            break;
        }
     }
+
+    /*
+     * If we've emptied the channel's output buffer and there's a
+     * pending close event, start the channel-closing procedure.
+     */
+    if (c->pending_close && bufchain_size(&c->v.v2.outbuffer) == 0) {
+       struct Packet *pktout;
+       pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
+       ssh2_pkt_adduint32(pktout, c->remoteid);
+       ssh2_pkt_send(ssh, pktout);
+       c->closes = 1;
+       c->pending_close = FALSE;
+    }
 }
 
 /*
@@ -6357,6 +6437,7 @@ static void ssh2_channel_init(struct ssh_channel *c)
     Ssh ssh = c->ssh;
     c->localid = alloc_channel_id(ssh);
     c->closes = 0;
+    c->pending_close = FALSE;
     c->throttling_conn = FALSE;
     c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin =
        ssh->cfg.ssh_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE;
@@ -6540,7 +6621,7 @@ static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin)
        return;
     if (!c->closes) {
        c->v.v2.remwindow += ssh_pkt_getuint32(pktin);
-       ssh2_try_send_and_unthrottle(c);
+       ssh2_try_send_and_unthrottle(ssh, c);
     }
 }
 
@@ -6661,11 +6742,13 @@ static void ssh2_msg_channel_eof(Ssh ssh, struct Packet *pktin)
         * wrap up and close the channel ourselves.
         */
        x11_close(c->u.x11.s);
+       c->u.x11.s = NULL;
        sshfwd_close(c);
     } else if (c->type == CHAN_AGENT) {
        sshfwd_close(c);
     } else if (c->type == CHAN_SOCKDATA) {
        pfd_close(c->u.pfd.s);
+       c->u.pfd.s = NULL;
        sshfwd_close(c);
     }
 }
@@ -7186,6 +7269,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        int num_env, env_left, env_ok;
        struct Packet *pktout;
 #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;
@@ -7454,6 +7538,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        }
 
        while (1) {
+           char *methods = NULL;
+           int methlen = 0;
+
            /*
             * Wait for the result of the last authentication request.
             */
@@ -7503,8 +7590,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
             * helpfully try next.
             */
            if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) {
-               char *methods;
-               int methlen;
                ssh_pkt_getstring(pktin, &methods, &methlen);
                if (!ssh2_pkt_getbool(pktin)) {
                    /*
@@ -7561,9 +7646,10 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                s->can_keyb_inter = ssh->cfg.try_ki_auth &&
                    in_commasep_string("keyboard-interactive", methods, methlen);
 #ifndef NO_GSSAPI              
+               ssh_gss_init();
                s->can_gssapi = ssh->cfg.try_gssapi_auth &&
-                 in_commasep_string("gssapi-with-mic", methods, methlen) &&
-                 ssh_gss_init();
+                   in_commasep_string("gssapi-with-mic", methods, methlen) &&
+                   n_ssh_gss_libraries > 0;
 #endif
            }
 
@@ -7906,6 +7992,35 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                s->gotit = TRUE;
                ssh->pkt_actx = SSH2_PKTCTX_GSSAPI;
 
+               /*
+                * Pick the highest GSS library on the preference
+                * list.
+                */
+               {
+                   int i, j;
+                   s->gsslib = NULL;
+                   for (i = 0; i < ngsslibs; i++) {
+                       int want_id = ssh->cfg.ssh_gsslist[i];
+                       for (j = 0; j < n_ssh_gss_libraries; j++)
+                           if (ssh_gss_libraries[j].id == want_id) {
+                               s->gsslib = &ssh_gss_libraries[j];
+                               goto got_gsslib;   /* double break */
+                           }
+                   }
+                   got_gsslib:
+                   /*
+                    * We always expect to have found something in
+                    * the above loop: we only came here if there
+                    * was at least one viable GSS library, and the
+                    * preference list should always mention
+                    * everything and only change the order.
+                    */
+                   assert(s->gsslib);
+               }
+
+               if (s->gsslib->gsslogmsg)
+                   logevent(s->gsslib->gsslogmsg);
+
                /* Sending USERAUTH_REQUEST with "gssapi-with-mic" method */
                s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
                ssh2_pkt_addstring(s->pktout, s->username);
@@ -7913,7 +8028,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                ssh2_pkt_addstring(s->pktout, "gssapi-with-mic");
 
                /* add mechanism info */
-               ssh_gss_indicate_mech(&s->gss_buf);
+               s->gsslib->indicate_mech(s->gsslib, &s->gss_buf);
 
                /* number of GSSAPI mechanisms */
                ssh2_pkt_adduint32(s->pktout,1);
@@ -7949,8 +8064,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                }
 
                /* now start running */
-               s->gss_stat = ssh_gss_import_name(ssh->fullhostname,
-                                                 &s->gss_srv_name);
+               s->gss_stat = s->gsslib->import_name(s->gsslib,
+                                                    ssh->fullhostname,
+                                                    &s->gss_srv_name);
                if (s->gss_stat != SSH_GSS_OK) {
                    if (s->gss_stat == SSH_GSS_BAD_HOST_NAME)
                        logevent("GSSAPI import name failed - Bad service name");
@@ -7960,11 +8076,11 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                }
 
                /* fetch TGT into GSS engine */
-               s->gss_stat = ssh_gss_acquire_cred(&s->gss_ctx);
+               s->gss_stat = s->gsslib->acquire_cred(s->gsslib, &s->gss_ctx);
 
                if (s->gss_stat != SSH_GSS_OK) {
                    logevent("GSSAPI authentication failed to get credentials");
-                   ssh_gss_release_name(&s->gss_srv_name);
+                   s->gsslib->release_name(s->gsslib, &s->gss_srv_name);
                    continue;
                }
 
@@ -7974,17 +8090,20 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
 
                /* now enter the loop */
                do {
-                   s->gss_stat = ssh_gss_init_sec_context(&s->gss_ctx,
-                                                          s->gss_srv_name,
-                                                          ssh->cfg.gssapifwd,
-                                                          &s->gss_rcvtok,
-                                                          &s->gss_sndtok);
+                   s->gss_stat = s->gsslib->init_sec_context
+                       (s->gsslib,
+                        &s->gss_ctx,
+                        s->gss_srv_name,
+                        ssh->cfg.gssapifwd,
+                        &s->gss_rcvtok,
+                        &s->gss_sndtok);
 
                    if (s->gss_stat!=SSH_GSS_S_COMPLETE &&
                        s->gss_stat!=SSH_GSS_S_CONTINUE_NEEDED) {
                        logevent("GSSAPI authentication initialisation failed");
 
-                       if (ssh_gss_display_status(s->gss_ctx,&s->gss_buf) == SSH_GSS_OK) {
+                       if (s->gsslib->display_status(s->gsslib, s->gss_ctx,
+                                                     &s->gss_buf) == SSH_GSS_OK) {
                            logevent(s->gss_buf.value);
                            sfree(s->gss_buf.value);
                        }
@@ -8001,7 +8120,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                        ssh_pkt_addstring_start(s->pktout);
                        ssh_pkt_addstring_data(s->pktout,s->gss_sndtok.value,s->gss_sndtok.length);
                        ssh2_pkt_send(ssh, s->pktout);
-                       ssh_gss_free_tok(&s->gss_sndtok);
+                       s->gsslib->free_tok(s->gsslib, &s->gss_sndtok);
                    }
 
                    if (s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED) {
@@ -8018,8 +8137,8 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                } while (s-> gss_stat == SSH_GSS_S_CONTINUE_NEEDED);
 
                if (s->gss_stat != SSH_GSS_OK) {
-                   ssh_gss_release_name(&s->gss_srv_name);
-                   ssh_gss_release_cred(&s->gss_ctx);
+                   s->gsslib->release_name(s->gsslib, &s->gss_srv_name);
+                   s->gsslib->release_cred(s->gsslib, &s->gss_ctx);
                    continue;
                }
                logevent("GSSAPI authentication loop finished OK");
@@ -8038,17 +8157,17 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                s->gss_buf.value = (char *)s->pktout->data + micoffset;
                s->gss_buf.length = s->pktout->length - micoffset;
 
-               ssh_gss_get_mic(s->gss_ctx, &s->gss_buf, &mic);
+               s->gsslib->get_mic(s->gsslib, s->gss_ctx, &s->gss_buf, &mic);
                s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_GSSAPI_MIC);
                ssh_pkt_addstring_start(s->pktout);
                ssh_pkt_addstring_data(s->pktout, mic.value, mic.length);
                ssh2_pkt_send(ssh, s->pktout);
-               ssh_gss_free_mic(&mic);
+               s->gsslib->free_mic(s->gsslib, &mic);
 
                s->gotit = FALSE;
 
-               ssh_gss_release_name(&s->gss_srv_name);
-               ssh_gss_release_cred(&s->gss_ctx);
+               s->gsslib->release_name(s->gsslib, &s->gss_srv_name);
+               s->gsslib->release_cred(s->gsslib, &s->gss_ctx);
                continue;
 #endif
            } else if (s->can_keyb_inter && !s->kbd_inter_refused) {
@@ -8103,23 +8222,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                    ssh_pkt_getstring(pktin, &lang, &lang_len);
                    s->cur_prompt = new_prompts(ssh->frontend);
                    s->cur_prompt->to_server = TRUE;
-                   if (name_len) {
-                       /* FIXME: better prefix to distinguish from
-                        * local prompts? */
-                       s->cur_prompt->name =
-                           dupprintf("SSH server: %.*s", name_len, name);
-                       s->cur_prompt->name_reqd = TRUE;
-                   } else {
-                       s->cur_prompt->name =
-                           dupstr("SSH server authentication");
-                       s->cur_prompt->name_reqd = FALSE;
-                   }
-                   /* FIXME: ugly to print "Using..." in prompt _every_
-                    * time round. Can this be done more subtly? */
-                   s->cur_prompt->instruction =
-                       dupprintf("Using keyboard-interactive authentication.%s%.*s",
-                                 inst_len ? "\n" : "", inst_len, inst);
-                   s->cur_prompt->instr_reqd = TRUE;
 
                    /*
                     * Get any prompt(s) from the packet.
@@ -8143,6 +8245,33 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                                   echo, SSH_MAX_PASSWORD_LEN);
                    }
 
+                   if (name_len) {
+                       /* FIXME: better prefix to distinguish from
+                        * local prompts? */
+                       s->cur_prompt->name =
+                           dupprintf("SSH server: %.*s", name_len, name);
+                       s->cur_prompt->name_reqd = TRUE;
+                   } else {
+                       s->cur_prompt->name =
+                           dupstr("SSH server authentication");
+                       s->cur_prompt->name_reqd = FALSE;
+                   }
+                   /* We add a prefix to try to make it clear that a prompt
+                    * has come from the server.
+                    * FIXME: ugly to print "Using..." in prompt _every_
+                    * time round. Can this be done more subtly? */
+                   /* Special case: for reasons best known to themselves,
+                    * some servers send k-i requests with no prompts and
+                    * nothing to display. Keep quiet in this case. */
+                   if (s->num_prompts || name_len || inst_len) {
+                       s->cur_prompt->instruction =
+                           dupprintf("Using keyboard-interactive authentication.%s%.*s",
+                                     inst_len ? "\n" : "", inst_len, inst);
+                       s->cur_prompt->instr_reqd = TRUE;
+                   } else {
+                       s->cur_prompt->instr_reqd = FALSE;
+                   }
+
                    /*
                      * Display any instructions, and get the user's
                      * response(s).
@@ -8424,11 +8553,16 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                sfree(s->password);
 
            } else {
+               char *str = dupprintf("No supported authentication methods available"
+                                     " (server sent: %.*s)",
+                                     methlen, methods);
 
-               ssh_disconnect(ssh, NULL,
+               ssh_disconnect(ssh, str,
                               "No supported authentication methods available",
                               SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
                               FALSE);
+               sfree(str);
+
                crStopV;
 
            }
@@ -8560,7 +8694,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
     ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] =
        ssh2_msg_channel_open;
 
-    if (ssh->cfg.ssh_simple) {
+    if (ssh->mainchan && ssh->cfg.ssh_simple) {
        /*
         * This message indicates to the server that we promise
         * not to try to run any other channel in parallel with
@@ -8867,7 +9001,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
             * Try to send data on all channels if we can.
             */
            for (i = 0; NULL != (c = index234(ssh->channels, i)); i++)
-               ssh2_try_send_and_unthrottle(c);
+               ssh2_try_send_and_unthrottle(ssh, c);
        }
     }
 
@@ -9221,7 +9355,7 @@ static void ssh_free(void *handle)
 
     if (ssh->rportfwds) {
        while ((pf = delpos234(ssh->rportfwds, 0)) != NULL)
-           sfree(pf);
+           free_rportfwd(pf);
        freetree234(ssh->rportfwds);
        ssh->rportfwds = NULL;
     }
@@ -9405,8 +9539,10 @@ static const struct telnet_special *ssh_get_specials(void *handle)
     static const struct telnet_special ssh1_ignore_special[] = {
        {"IGNORE message", TS_NOP}
     };
-    static const struct telnet_special ssh2_transport_specials[] = {
+    static const struct telnet_special ssh2_ignore_special[] = {
        {"IGNORE message", TS_NOP},
+    };
+    static const struct telnet_special ssh2_rekey_special[] = {
        {"Repeat key exchange", TS_REKEY},
     };
     static const struct telnet_special ssh2_session_specials[] = {
@@ -9431,7 +9567,8 @@ static const struct telnet_special *ssh_get_specials(void *handle)
        {NULL, TS_EXITMENU}
     };
     /* XXX review this length for any changes: */
-    static struct telnet_special ssh_specials[lenof(ssh2_transport_specials) +
+    static struct telnet_special ssh_specials[lenof(ssh2_ignore_special) +
+                                             lenof(ssh2_rekey_special) +
                                              lenof(ssh2_session_specials) +
                                              lenof(specials_end)];
     Ssh ssh = (Ssh) handle;
@@ -9450,7 +9587,10 @@ static const struct telnet_special *ssh_get_specials(void *handle)
        if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE))
            ADD_SPECIALS(ssh1_ignore_special);
     } else if (ssh->version == 2) {
-       ADD_SPECIALS(ssh2_transport_specials);
+       if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE))
+           ADD_SPECIALS(ssh2_ignore_special);
+       if (!(ssh->remote_bugs & BUG_SSH2_REKEY))
+           ADD_SPECIALS(ssh2_rekey_special);
        if (ssh->mainchan)
            ADD_SPECIALS(ssh2_session_specials);
     } /* else we're not ready yet */
@@ -9500,9 +9640,11 @@ static void ssh_special(void *handle, Telnet_Special code)
            if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE))
                send_packet(ssh, SSH1_MSG_IGNORE, PKT_STR, "", PKT_END);
        } else {
-           pktout = ssh2_pkt_init(SSH2_MSG_IGNORE);
-           ssh2_pkt_addstring_start(pktout);
-           ssh2_pkt_send_noqueue(ssh, pktout);
+           if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) {
+               pktout = ssh2_pkt_init(SSH2_MSG_IGNORE);
+               ssh2_pkt_addstring_start(pktout);
+               ssh2_pkt_send_noqueue(ssh, pktout);
+           }
        }
     } else if (code == TS_REKEY) {
        if (!ssh->kex_in_progress && ssh->version == 2) {