SSH port forwarding is now configurable in mid-session. After doing
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index 126e6ff..e619b59 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -494,15 +494,37 @@ struct ssh_channel {
  * of its ports was connected to; and _you_ have to remember what
  * local host:port pair went with that port number.
  * 
- * Hence: in SSH 1 this structure stores host:port pairs we intend
- * to allow connections to, and is indexed by those host:port
- * pairs. In SSH 2 it stores a mapping from source port to
- * destination host:port pair, and is indexed by source port.
+ * Hence, in SSH 1 this structure is indexed by destination
+ * host:port pair, whereas in SSH 2 it is indexed by source port.
  */
+struct ssh_portfwd; /* forward declaration */
+
 struct ssh_rportfwd {
     unsigned sport, dport;
     char dhost[256];
+    char *sportdesc;
+    struct ssh_portfwd *pfrec;
+};
+#define free_rportfwd(pf) ( \
+    ((pf) ? (sfree((pf)->sportdesc)) : (void)0 ), sfree(pf) )
+
+/*
+ * Separately to the rportfwd tree (which is for looking up port
+ * open requests from the server), a tree of _these_ structures is
+ * used to keep track of all the currently open port forwardings,
+ * so that we can reconfigure in mid-session if the user requests
+ * it.
+ */
+struct ssh_portfwd {
+    int keep;
+    int type;
+    unsigned sport, dport;
+    char *saddr, *daddr;
+    struct ssh_rportfwd *remote;
+    void *local;
 };
+#define free_portfwd(pf) ( \
+    ((pf) ? (sfree((pf)->saddr), sfree((pf)->daddr)) : (void)0 ), sfree(pf) )
 
 struct Packet {
     long length;
@@ -561,6 +583,15 @@ struct rdpkt2_state_tag {
 };
 
 typedef void (*handler_fn_t)(Ssh ssh, struct Packet *pktin);
+typedef void (*chandler_fn_t)(Ssh ssh, struct Packet *pktin, void *ctx);
+
+struct queued_handler;
+struct queued_handler {
+    int msg1, msg2;
+    chandler_fn_t handler;
+    void *ctx;
+    struct queued_handler *next;
+};
 
 struct ssh_tag {
     const struct plug_function_table *fn;
@@ -608,7 +639,7 @@ struct ssh_tag {
     struct ssh_channel *mainchan;      /* primary session channel */
     int exitcode;
 
-    tree234 *rportfwds;
+    tree234 *rportfwds, *portfwds;
 
     enum {
        SSH_STATE_PREPACKET,
@@ -642,7 +673,6 @@ struct ssh_tag {
     int userpass_input_bufpos;
     int userpass_input_echo;
 
-    char *portfwd_strptr;
     int pkt_ctx;
 
     void *x11auth;
@@ -699,6 +729,12 @@ struct ssh_tag {
     handler_fn_t packet_dispatch[256];
 
     /*
+     * Queues of one-off handler functions for success/failure
+     * indications from a request.
+     */
+    struct queued_handler *qhead, *qtail;
+
+    /*
      * This module deals with sending keepalives.
      */
     Pinger pinger;
@@ -708,13 +744,11 @@ struct ssh_tag {
      * size-based rekeys.
      */
     unsigned long incoming_data_size, outgoing_data_size, deferred_data_size;
+    unsigned long max_data_size;
     int kex_in_progress;
     long next_rekey;
 };
 
-#define MAX_DATA_BEFORE_REKEY (0x40000000UL)
-#define REKEY_TIMEOUT (3600 * TICKSPERSEC)
-
 #define logevent(s) logevent(ssh->frontend, s)
 
 /* logevent, only printf-formatted. */
@@ -805,6 +839,47 @@ static int ssh_rportcmp_ssh2(void *av, void *bv)
     return 0;
 }
 
+/*
+ * Special form of strcmp which can cope with NULL inputs. NULL is
+ * defined to sort before even the empty string.
+ */
+static int nullstrcmp(const char *a, const char *b)
+{
+    if (a == NULL && b == NULL)
+       return 0;
+    if (a == NULL)
+       return -1;
+    if (b == NULL)
+       return +1;
+    return strcmp(a, b);
+}
+
+static int ssh_portcmp(void *av, void *bv)
+{
+    struct ssh_portfwd *a = (struct ssh_portfwd *) av;
+    struct ssh_portfwd *b = (struct ssh_portfwd *) bv;
+    int i;
+    if (a->type > b->type)
+       return +1;
+    if (a->type < b->type)
+       return -1;
+    if ( (i = nullstrcmp(a->saddr, b->saddr)) != 0)
+       return i < 0 ? -1 : +1;
+    if (a->sport > b->sport)
+       return +1;
+    if (a->sport < b->sport)
+       return -1;
+    if (a->type != 'D') {
+       if ( (i = nullstrcmp(a->daddr, b->daddr)) != 0)
+           return i < 0 ? -1 : +1;
+       if (a->dport > b->dport)
+           return +1;
+       if (a->dport < b->dport)
+           return -1;
+    }
+    return 0;
+}
+
 static int alloc_channel_id(Ssh ssh)
 {
     const unsigned CHANNEL_NUMBER_OFFSET = 256;
@@ -1653,7 +1728,8 @@ static void ssh2_pkt_send_noqueue(Ssh ssh, struct Packet *pkt)
 
     ssh->outgoing_data_size += pkt->encrypted_len;
     if (!ssh->kex_in_progress &&
-       ssh->outgoing_data_size > MAX_DATA_BEFORE_REKEY)
+       ssh->max_data_size != 0 &&
+       ssh->outgoing_data_size > ssh->max_data_size)
        do_ssh2_transport(ssh, "Initiating key re-exchange "
                          "(too much data sent)", -1, NULL);
 
@@ -1743,7 +1819,8 @@ static void ssh_pkt_defersend(Ssh ssh)
 
     ssh->outgoing_data_size += ssh->deferred_data_size;
     if (!ssh->kex_in_progress &&
-       ssh->outgoing_data_size > MAX_DATA_BEFORE_REKEY)
+       ssh->max_data_size != 0 &&
+       ssh->outgoing_data_size > ssh->max_data_size)
        do_ssh2_transport(ssh, "Initiating key re-exchange "
                          "(too much data sent)", -1, NULL);
     ssh->deferred_data_size = 0;
@@ -3502,6 +3579,381 @@ void sshfwd_unthrottle(struct ssh_channel *c, int bufsize)
     }
 }
 
+static void ssh_queueing_handler(Ssh ssh, struct Packet *pktin)
+{
+    struct queued_handler *qh = ssh->qhead;
+
+    assert(qh != NULL);
+
+    assert(pktin->type == qh->msg1 || pktin->type == qh->msg2);
+
+    if (qh->msg1 > 0) {
+       assert(ssh->packet_dispatch[qh->msg1] == ssh_queueing_handler);
+       ssh->packet_dispatch[qh->msg1] = NULL;
+    }
+    if (qh->msg2 > 0) {
+       assert(ssh->packet_dispatch[qh->msg2] == ssh_queueing_handler);
+       ssh->packet_dispatch[qh->msg2] = NULL;
+    }
+
+    if (qh->next) {
+       ssh->qhead = qh->next;
+
+       if (ssh->qhead->msg1 > 0) {
+           assert(ssh->packet_dispatch[ssh->qhead->msg1] == NULL);
+           ssh->packet_dispatch[ssh->qhead->msg1] = ssh_queueing_handler;
+       }
+       if (ssh->qhead->msg2 > 0) {
+           assert(ssh->packet_dispatch[ssh->qhead->msg2] == NULL);
+           ssh->packet_dispatch[ssh->qhead->msg2] = ssh_queueing_handler;
+       }
+    } else {
+       ssh->qhead = ssh->qtail = NULL;
+       ssh->packet_dispatch[pktin->type] = NULL;
+    }
+
+    qh->handler(ssh, pktin, qh->ctx);
+
+    sfree(qh);
+}
+
+static void ssh_queue_handler(Ssh ssh, int msg1, int msg2,
+                             chandler_fn_t handler, void *ctx)
+{
+    struct queued_handler *qh;
+
+    qh = snew(struct queued_handler);
+    qh->msg1 = msg1;
+    qh->msg2 = msg2;
+    qh->handler = handler;
+    qh->ctx = ctx;
+    qh->next = NULL;
+
+    if (ssh->qtail == NULL) {
+       ssh->qhead = qh;
+
+       if (qh->msg1 > 0) {
+           assert(ssh->packet_dispatch[qh->msg1] == NULL);
+           ssh->packet_dispatch[qh->msg1] = ssh_queueing_handler;
+       }
+       if (qh->msg2 > 0) {
+           assert(ssh->packet_dispatch[qh->msg2] == NULL);
+           ssh->packet_dispatch[qh->msg2] = ssh_queueing_handler;
+       }
+    } else {
+       ssh->qtail->next = qh;
+    }
+    ssh->qtail = qh;
+}
+
+static void ssh_rportfwd_succfail(Ssh ssh, struct Packet *pktin, void *ctx)
+{
+    struct ssh_rportfwd *rpf, *pf = (struct ssh_rportfwd *)ctx;
+
+    if (pktin->type == (ssh->version == 1 ? SSH1_SMSG_SUCCESS :
+                       SSH2_MSG_REQUEST_SUCCESS)) {
+       logeventf(ssh, "Remote port forwarding from %s enabled",
+                 pf->sportdesc);
+    } else {
+       logeventf(ssh, "Remote port forwarding from %s refused",
+                 pf->sportdesc);
+
+       rpf = del234(ssh->rportfwds, pf);
+       assert(rpf == pf);
+       free_rportfwd(pf);
+    }
+}
+
+static void ssh_setup_portfwd(Ssh ssh, const Config *cfg)
+{
+    char type;
+    int n;
+    int sport,dport,sserv,dserv;
+    char sports[256], dports[256], saddr[256], host[256];
+    const char *portfwd_strptr;
+
+    portfwd_strptr = cfg->portfwd;
+
+    if (!ssh->portfwds) {
+       ssh->portfwds = newtree234(ssh_portcmp);
+    } else {
+       /*
+        * Go through the existing port forwardings and tag them
+        * with keep==FALSE. Any that we want to keep will be
+        * re-enabled as we go through the configuration and find
+        * out which bits are the same as they were before.
+        */
+       struct ssh_portfwd *epf;
+       int i;
+       for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++)
+           epf->keep = FALSE;
+    }
+
+    while (*portfwd_strptr) {
+       type = *portfwd_strptr++;
+       saddr[0] = '\0';
+       n = 0;
+       while (*portfwd_strptr && *portfwd_strptr != '\t') {
+           if (*portfwd_strptr == ':') {
+               /*
+                * We've seen a colon in the middle of the
+                * source port number. This means that
+                * everything we've seen until now is the
+                * source _address_, so we'll move it into
+                * saddr and start sports from the beginning
+                * again.
+                */
+               portfwd_strptr++;
+               sports[n] = '\0';
+               if (ssh->version == 1 && type == 'R') {
+                   logeventf(ssh, "SSH1 cannot handle remote source address "
+                             "spec \"%s\"; ignoring", sports);
+               } else
+                   strcpy(saddr, sports);
+               n = 0;
+           }
+           if (n < 255) sports[n++] = *portfwd_strptr++;
+       }
+       sports[n] = 0;
+       if (type != 'D') {
+           if (*portfwd_strptr == '\t')
+               portfwd_strptr++;
+           n = 0;
+           while (*portfwd_strptr && *portfwd_strptr != ':') {
+               if (n < 255) host[n++] = *portfwd_strptr++;
+           }
+           host[n] = 0;
+           if (*portfwd_strptr == ':')
+               portfwd_strptr++;
+           n = 0;
+           while (*portfwd_strptr) {
+               if (n < 255) dports[n++] = *portfwd_strptr++;
+           }
+           dports[n] = 0;
+           portfwd_strptr++;
+           dport = atoi(dports);
+           dserv = 0;
+           if (dport == 0) {
+               dserv = 1;
+               dport = net_service_lookup(dports);
+               if (!dport) {
+                   logeventf(ssh, "Service lookup failed for destination"
+                             " port \"%s\"", dports);
+               }
+           }
+       } else {
+           while (*portfwd_strptr) portfwd_strptr++;
+           dport = dserv = -1;
+           portfwd_strptr++;          /* eat the NUL and move to next one */
+       }
+       sport = atoi(sports);
+       sserv = 0;
+       if (sport == 0) {
+           sserv = 1;
+           sport = net_service_lookup(sports);
+           if (!sport) {
+               logeventf(ssh, "Service lookup failed for source"
+                         " port \"%s\"", sports);
+           }
+       }
+       if (sport && dport) {
+           /* Set up a description of the source port. */
+           struct ssh_portfwd *pfrec, *epfrec;
+           char *sportdesc;
+           sportdesc = dupprintf("%.*s%.*s%.*s%.*s%d%.*s",
+                                 (int)(*saddr?strlen(saddr):0), *saddr?saddr:NULL,
+                                 (int)(*saddr?1:0), ":",
+                                 (int)(sserv ? strlen(sports) : 0), sports,
+                                 sserv, "(", sport, sserv, ")");
+
+           pfrec = snew(struct ssh_portfwd);
+           pfrec->type = type;
+           pfrec->saddr = *saddr ? dupstr(saddr) : NULL;
+           pfrec->sport = sport;
+           pfrec->daddr = dupstr(host);
+           pfrec->dport = dport;
+           pfrec->local = NULL;
+           pfrec->remote = NULL;
+
+           epfrec = add234(ssh->portfwds, pfrec);
+           if (epfrec != pfrec) {
+               /*
+                * We already have a port forwarding with precisely
+                * these parameters. Hence, no need to do anything;
+                * simply tag the existing one as `keep'.
+                */
+               epfrec->keep = TRUE;
+               free_portfwd(pfrec);
+           } else if (type == 'L') {
+               /* Verbose description of the destination port */
+               char *dportdesc = dupprintf("%s:%.*s%.*s%d%.*s",
+                                           host,
+                                           (int)(dserv ? strlen(dports) : 0), dports,
+                                           dserv, "(", dport, dserv, ")");
+               const char *err = pfd_addforward(host, dport,
+                                                *saddr ? saddr : NULL,
+                                                sport, ssh, &ssh->cfg,
+                                                &pfrec->local);
+               if (err) {
+                   logeventf(ssh, "Local port %s forward to %s"
+                             " failed: %s", sportdesc, dportdesc, err);
+               } else {
+                   logeventf(ssh, "Local port %s forwarding to %s",
+                             sportdesc, dportdesc);
+               }
+               sfree(dportdesc);
+           } else if (type == 'D') {
+               const char *err = pfd_addforward(NULL, -1,
+                                                *saddr ? saddr : NULL,
+                                                sport, ssh, &ssh->cfg,
+                                                &pfrec->local);
+               if (err) {
+                   logeventf(ssh, "Local port %s SOCKS dynamic forward"
+                             " setup failed: %s", sportdesc, err);
+               } else {
+                   logeventf(ssh, "Local port %s doing SOCKS"
+                             " dynamic forwarding", sportdesc);
+               }
+           } else {
+               struct ssh_rportfwd *pf;
+
+               /*
+                * Ensure the remote port forwardings tree exists.
+                */
+               if (!ssh->rportfwds) {
+                   if (ssh->version == 1)
+                       ssh->rportfwds = newtree234(ssh_rportcmp_ssh1);
+                   else
+                       ssh->rportfwds = newtree234(ssh_rportcmp_ssh2);
+               }
+
+               pf = snew(struct ssh_rportfwd);
+               strcpy(pf->dhost, host);
+               pf->dport = dport;
+               pf->sport = sport;
+               if (add234(ssh->rportfwds, pf) != pf) {
+                   logeventf(ssh, "Duplicate remote port forwarding to %s:%d",
+                             host, dport);
+                   sfree(pf);
+               } else {
+                   logeventf(ssh, "Requesting remote port %s"
+                             " forward to %s:%.*s%.*s%d%.*s",
+                             sportdesc, host,
+                             (int)(dserv ? strlen(dports) : 0), dports,
+                             dserv, "(", dport, dserv, ")");
+
+                   pf->sportdesc = sportdesc;
+                   sportdesc = NULL;
+                   pfrec->remote = pf;
+                   pf->pfrec = pfrec;
+
+                   if (ssh->version == 1) {
+                       send_packet(ssh, SSH1_CMSG_PORT_FORWARD_REQUEST,
+                                   PKT_INT, sport,
+                                   PKT_STR, host,
+                                   PKT_INT, dport,
+                                   PKT_END);
+                       ssh_queue_handler(ssh, SSH1_SMSG_SUCCESS,
+                                         SSH1_SMSG_FAILURE,
+                                         ssh_rportfwd_succfail, pf);
+                   } else {
+                       struct Packet *pktout;
+                       pktout = ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST);
+                       ssh2_pkt_addstring(pktout, "tcpip-forward");
+                       ssh2_pkt_addbool(pktout, 1);/* want reply */
+                       if (*saddr) {
+                           ssh2_pkt_addstring(pktout, saddr);
+                       } else if (ssh->cfg.rport_acceptall) {
+                           ssh2_pkt_addstring(pktout, "0.0.0.0");
+                       } else {
+                           ssh2_pkt_addstring(pktout, "127.0.0.1");
+                       }
+                       ssh2_pkt_adduint32(pktout, sport);
+                       ssh2_pkt_send(ssh, pktout);
+
+                       ssh_queue_handler(ssh, SSH2_MSG_REQUEST_SUCCESS,
+                                         SSH2_MSG_REQUEST_FAILURE,
+                                         ssh_rportfwd_succfail, pf);
+                   }
+               }
+           }
+           sfree(sportdesc);
+       }
+    }
+
+    /*
+     * Now go through and destroy any port forwardings which were
+     * not re-enabled.
+     */
+    {
+       struct ssh_portfwd *epf;
+       int i;
+       for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++)
+           if (!epf->keep) {
+               char *message;
+
+               message = dupprintf("%s port forwarding from %s%s%d",
+                                   epf->type == 'L' ? "local" :
+                                   epf->type == 'R' ? "remote" : "dynamic",
+                                   epf->saddr ? epf->saddr : "",
+                                   epf->saddr ? ":" : "",
+                                   epf->sport);
+
+               if (epf->type != 'D') {
+                   char *msg2 = dupprintf("%s to %s:%d", message,
+                                          epf->daddr, epf->dport);
+                   sfree(message);
+                   message = msg2;
+               }
+
+               logeventf(ssh, "Cancelling %s", message);
+               sfree(message);
+
+               if (epf->remote) {
+                   struct ssh_rportfwd *rpf = epf->remote;
+                   struct Packet *pktout;
+
+                   /*
+                    * Cancel the port forwarding at the server
+                    * end.
+                    */
+                   if (ssh->version == 1) {
+                       /*
+                        * We cannot cancel listening ports on the
+                        * server side in SSH1! There's no message
+                        * to support it. Instead, we simply remove
+                        * the rportfwd record from the local end
+                        * so that any connections the server tries
+                        * to make on it are rejected.
+                        */
+                   } else {
+                       pktout = ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST);
+                       ssh2_pkt_addstring(pktout, "cancel-tcpip-forward");
+                       ssh2_pkt_addbool(pktout, 0);/* _don't_ want reply */
+                       if (epf->saddr) {
+                           ssh2_pkt_addstring(pktout, epf->saddr);
+                       } else if (ssh->cfg.rport_acceptall) {
+                           ssh2_pkt_addstring(pktout, "0.0.0.0");
+                       } else {
+                           ssh2_pkt_addstring(pktout, "127.0.0.1");
+                       }
+                       ssh2_pkt_adduint32(pktout, epf->sport);
+                       ssh2_pkt_send(ssh, pktout);
+                   }
+
+                   del234(ssh->rportfwds, rpf);
+                   free_rportfwd(rpf);
+               } else if (epf->local) {
+                   pfd_terminate(epf->local);
+               }
+
+               delpos234(ssh->portfwds, i);
+               free_portfwd(epf);
+               i--;                   /* so we don't skip one in the list */
+           }
+    }
+}
+
 static void ssh1_smsg_stdout_stderr_data(Ssh ssh, struct Packet *pktin)
 {
     char *string;
@@ -3885,164 +4337,8 @@ static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen,
        }
     }
 
-    {
-       char type;
-       int n;
-       int sport,dport,sserv,dserv;
-       char sports[256], dports[256], saddr[256], host[256];
-
-       ssh->rportfwds = newtree234(ssh_rportcmp_ssh1);
-        /* Add port forwardings. */
-       ssh->portfwd_strptr = ssh->cfg.portfwd;
-       while (*ssh->portfwd_strptr) {
-           type = *ssh->portfwd_strptr++;
-           saddr[0] = '\0';
-           n = 0;
-           while (*ssh->portfwd_strptr && *ssh->portfwd_strptr != '\t') {
-               if (*ssh->portfwd_strptr == ':') {
-                   /*
-                    * We've seen a colon in the middle of the
-                    * source port number. This means that
-                    * everything we've seen until now is the
-                    * source _address_, so we'll move it into
-                    * saddr and start sports from the beginning
-                    * again.
-                    */
-                   ssh->portfwd_strptr++;
-                   sports[n] = '\0';
-                   strcpy(saddr, sports);
-                   n = 0;
-               }
-               if (n < 255) sports[n++] = *ssh->portfwd_strptr++;
-           }
-           sports[n] = 0;
-           if (type != 'D') {
-               if (*ssh->portfwd_strptr == '\t')
-                   ssh->portfwd_strptr++;
-               n = 0;
-               while (*ssh->portfwd_strptr && *ssh->portfwd_strptr != ':') {
-                   if (n < 255) host[n++] = *ssh->portfwd_strptr++;
-               }
-               host[n] = 0;
-               if (*ssh->portfwd_strptr == ':')
-                   ssh->portfwd_strptr++;
-               n = 0;
-               while (*ssh->portfwd_strptr) {
-                   if (n < 255) dports[n++] = *ssh->portfwd_strptr++;
-               }
-               dports[n] = 0;
-               ssh->portfwd_strptr++;
-               dport = atoi(dports);
-               dserv = 0;
-               if (dport == 0) {
-                   dserv = 1;
-                   dport = net_service_lookup(dports);
-                   if (!dport) {
-                       logeventf(ssh, "Service lookup failed for"
-                                 " destination port \"%s\"", dports);
-                   }
-               }
-           } else {
-               while (*ssh->portfwd_strptr) ssh->portfwd_strptr++;
-               dport = dserv = -1;
-               ssh->portfwd_strptr++; /* eat the NUL and move to next one */
-           }
-           sport = atoi(sports);
-           sserv = 0;
-           if (sport == 0) {
-               sserv = 1;
-               sport = net_service_lookup(sports);
-               if (!sport) {
-                   logeventf(ssh, "Service lookup failed for source"
-                             " port \"%s\"", sports);
-               }
-           }
-           if (sport && dport) {
-               /* Set up a description of the source port. */
-               static char *sportdesc;
-               sportdesc = dupprintf("%.*s%.*s%.*s%.*s%d%.*s",
-                       (int)(*saddr?strlen(saddr):0), *saddr?saddr:NULL,
-                       (int)(*saddr?1:0), ":",
-                       (int)(sserv ? strlen(sports) : 0), sports,
-                       sserv, "(", sport, sserv, ")");
-               if (type == 'L') {
-                   /* Verbose description of the destination port */
-                   char *dportdesc = dupprintf("%s:%.*s%.*s%d%.*s",
-                           host,
-                           (int)(dserv ? strlen(dports) : 0), dports,
-                           dserv, "(", dport, dserv, ")");
-                   const char *err = pfd_addforward(host, dport,
-                                                    *saddr ? saddr : NULL,
-                                                    sport, ssh, &ssh->cfg);
-                   if (err) {
-                       logeventf(ssh, "Local port %s forward to %s"
-                                 " failed: %s", sportdesc, dportdesc, err);
-                   } else {
-                       logeventf(ssh, "Local port %s forwarding to %s",
-                                 sportdesc, dportdesc);
-                   }
-                   sfree(dportdesc);
-               } else if (type == 'D') {
-                   const char *err = pfd_addforward(NULL, -1,
-                                                    *saddr ? saddr : NULL,
-                                                    sport, ssh, &ssh->cfg);
-                   if (err) {
-                       logeventf(ssh, "Local port %s SOCKS dynamic forward"
-                                 " setup failed: %s", sportdesc, err);
-                   } else {
-                       logeventf(ssh, "Local port %s doing SOCKS"
-                                 " dynamic forwarding", sportdesc);
-                   }
-               } else {
-                   struct ssh_rportfwd *pf;
-                   pf = snew(struct ssh_rportfwd);
-                   strcpy(pf->dhost, host);
-                   pf->dport = dport;
-                   if (*saddr) {
-                       logeventf(ssh,
-                                 "SSH1 cannot handle source address spec \"%s:%d\"; ignoring",
-                                 saddr, sport);
-                   }
-                   if (add234(ssh->rportfwds, pf) != pf) {
-                       logeventf(ssh, 
-                                 "Duplicate remote port forwarding to %s:%d",
-                                 host, dport);
-                       sfree(pf);
-                   } else {
-                       logeventf(ssh, "Requesting remote port %.*s%.*s%d%.*s"
-                                 " forward to %s:%.*s%.*s%d%.*s",
-                                 (int)(sserv ? strlen(sports) : 0), sports,
-                                 sserv, "(", sport, sserv, ")",
-                                 host,
-                                 (int)(dserv ? strlen(dports) : 0), dports,
-                                 dserv, "(", dport, dserv, ")");
-                       send_packet(ssh, SSH1_CMSG_PORT_FORWARD_REQUEST,
-                                   PKT_INT, sport,
-                                   PKT_STR, host,
-                                   PKT_INT, dport,
-                                   PKT_END);
-                       do {
-                           crReturnV;
-                       } while (!pktin);
-                       if (pktin->type != SSH1_SMSG_SUCCESS
-                           && pktin->type != SSH1_SMSG_FAILURE) {
-                           bombout(("Protocol confusion"));
-                           crStopV;
-                       } else if (pktin->type == SSH1_SMSG_FAILURE) {
-                           c_write_str(ssh, "Server refused port"
-                                       " forwarding\r\n");
-                           logevent("Server refused this port forwarding");
-                       } else {
-                           logevent("Remote port forwarding enabled");
-                           ssh->packet_dispatch[SSH1_MSG_PORT_OPEN] =
-                               ssh1_msg_port_open;
-                       }
-                   }
-               }
-               sfree(sportdesc);
-           }
-       }
-    }
+    ssh_setup_portfwd(ssh, &ssh->cfg);
+    ssh->packet_dispatch[SSH1_MSG_PORT_OPEN] = ssh1_msg_port_open;
 
     if (!ssh->cfg.nopty) {
        /* Unpick the terminal-speed string. */
@@ -4915,8 +5211,10 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen,
      * Key exchange is over. Schedule a timer for our next rekey.
      */
     ssh->kex_in_progress = FALSE;
-    ssh->next_rekey = schedule_timer(REKEY_TIMEOUT, ssh2_timer, ssh);
-
+    if (ssh->cfg.ssh_rekey_time != 0)
+       ssh->next_rekey = schedule_timer(ssh->cfg.ssh_rekey_time*60*TICKSPERSEC,
+                                        ssh2_timer, ssh);
+    
     /*
      * If this is the first key exchange phase, we must pass the
      * SSH2_MSG_NEWKEYS packet to the next layer, not because it
@@ -6524,163 +6822,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
     /*
      * Enable port forwardings.
      */
-    {
-       char type;
-       int n;
-       int sport,dport,sserv,dserv;
-       char sports[256], dports[256], saddr[256], host[256];
-
-       ssh->rportfwds = newtree234(ssh_rportcmp_ssh2);
-        /* Add port forwardings. */
-       ssh->portfwd_strptr = ssh->cfg.portfwd;
-       while (*ssh->portfwd_strptr) {
-           type = *ssh->portfwd_strptr++;
-           saddr[0] = '\0';
-           n = 0;
-           while (*ssh->portfwd_strptr && *ssh->portfwd_strptr != '\t') {
-               if (*ssh->portfwd_strptr == ':') {
-                   /*
-                    * We've seen a colon in the middle of the
-                    * source port number. This means that
-                    * everything we've seen until now is the
-                    * source _address_, so we'll move it into
-                    * saddr and start sports from the beginning
-                    * again.
-                    */
-                   ssh->portfwd_strptr++;
-                   sports[n] = '\0';
-                   strcpy(saddr, sports);
-                   n = 0;
-               }
-               if (n < 255) sports[n++] = *ssh->portfwd_strptr++;
-           }
-           sports[n] = 0;
-           if (type != 'D') {
-               if (*ssh->portfwd_strptr == '\t')
-                   ssh->portfwd_strptr++;
-               n = 0;
-               while (*ssh->portfwd_strptr && *ssh->portfwd_strptr != ':') {
-                   if (n < 255) host[n++] = *ssh->portfwd_strptr++;
-               }
-               host[n] = 0;
-               if (*ssh->portfwd_strptr == ':')
-                   ssh->portfwd_strptr++;
-               n = 0;
-               while (*ssh->portfwd_strptr) {
-                   if (n < 255) dports[n++] = *ssh->portfwd_strptr++;
-               }
-               dports[n] = 0;
-               ssh->portfwd_strptr++;
-               dport = atoi(dports);
-               dserv = 0;
-               if (dport == 0) {
-                   dserv = 1;
-                   dport = net_service_lookup(dports);
-                   if (!dport) {
-                       logeventf(ssh, "Service lookup failed for destination"
-                                 " port \"%s\"", dports);
-                   }
-               }
-           } else {
-               while (*ssh->portfwd_strptr) ssh->portfwd_strptr++;
-               dport = dserv = -1;
-               ssh->portfwd_strptr++; /* eat the NUL and move to next one */
-           }
-           sport = atoi(sports);
-           sserv = 0;
-           if (sport == 0) {
-               sserv = 1;
-               sport = net_service_lookup(sports);
-               if (!sport) {
-                   logeventf(ssh, "Service lookup failed for source"
-                             " port \"%s\"", sports);
-               }
-           }
-           if (sport && dport) {
-               /* Set up a description of the source port. */
-               static char *sportdesc;
-               sportdesc = dupprintf("%.*s%.*s%.*s%.*s%d%.*s",
-                       (int)(*saddr?strlen(saddr):0), *saddr?saddr:NULL,
-                       (int)(*saddr?1:0), ":",
-                       (int)(sserv ? strlen(sports) : 0), sports,
-                       sserv, "(", sport, sserv, ")");
-               if (type == 'L') {
-                   /* Verbose description of the destination port */
-                   char *dportdesc = dupprintf("%s:%.*s%.*s%d%.*s",
-                           host,
-                           (int)(dserv ? strlen(dports) : 0), dports,
-                           dserv, "(", dport, dserv, ")");
-                   const char *err = pfd_addforward(host, dport,
-                                                    *saddr ? saddr : NULL,
-                                                    sport, ssh, &ssh->cfg);
-                   if (err) {
-                       logeventf(ssh, "Local port %s forward to %s"
-                                 " failed: %s", sportdesc, dportdesc, err);
-                   } else {
-                       logeventf(ssh, "Local port %s forwarding to %s",
-                                 sportdesc, dportdesc);
-                   }
-                   sfree(dportdesc);
-               } else if (type == 'D') {
-                   const char *err = pfd_addforward(NULL, -1,
-                                                    *saddr ? saddr : NULL,
-                                                    sport, ssh, &ssh->cfg);
-                   if (err) {
-                       logeventf(ssh, "Local port %s SOCKS dynamic forward"
-                                 " setup failed: %s", sportdesc, err);
-                   } else {
-                       logeventf(ssh, "Local port %s doing SOCKS"
-                                 " dynamic forwarding", sportdesc);
-                   }
-               } else {
-                   struct ssh_rportfwd *pf;
-                   pf = snew(struct ssh_rportfwd);
-                   strcpy(pf->dhost, host);
-                   pf->dport = dport;
-                   pf->sport = sport;
-                   if (add234(ssh->rportfwds, pf) != pf) {
-                       logeventf(ssh, "Duplicate remote port forwarding"
-                                 " to %s:%d", host, dport);
-                       sfree(pf);
-                   } else {
-                       logeventf(ssh, "Requesting remote port %s"
-                                 " forward to %s:%.*s%.*s%d%.*s",
-                                 sportdesc,
-                                 host,
-                                 (int)(dserv ? strlen(dports) : 0), dports,
-                                 dserv, "(", dport, dserv, ")");
-                       s->pktout = ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST);
-                       ssh2_pkt_addstring(s->pktout, "tcpip-forward");
-                       ssh2_pkt_addbool(s->pktout, 1);/* want reply */
-                       if (*saddr) {
-                           ssh2_pkt_addstring(s->pktout, saddr);
-                       } else if (ssh->cfg.rport_acceptall) {
-                           ssh2_pkt_addstring(s->pktout, "0.0.0.0");
-                       } else {
-                           ssh2_pkt_addstring(s->pktout, "127.0.0.1");
-                       }
-                       ssh2_pkt_adduint32(s->pktout, sport);
-                       ssh2_pkt_send(ssh, s->pktout);
-
-                       crWaitUntilV(pktin);
-
-                       if (pktin->type != SSH2_MSG_REQUEST_SUCCESS) {
-                           if (pktin->type != SSH2_MSG_REQUEST_FAILURE) {
-                               bombout(("Unexpected response to port "
-                                        "forwarding request: packet type %d",
-                                        pktin->type));
-                               crStopV;
-                           }
-                           logevent("Server refused this port forwarding");
-                       } else {
-                           logevent("Remote port forwarding enabled");
-                       }
-                   }
-               }
-               sfree(sportdesc);
-           }
-       }
-    }
+    ssh_setup_portfwd(ssh, &ssh->cfg);
 
     /*
      * Potentially enable agent forwarding.
@@ -7087,7 +7229,8 @@ static void ssh2_protocol(Ssh ssh, unsigned char *in, int inlen,
     if (pktin) {
        ssh->incoming_data_size += pktin->encrypted_len;
        if (!ssh->kex_in_progress &&
-           ssh->incoming_data_size > MAX_DATA_BEFORE_REKEY)
+           ssh->max_data_size != 0 &&
+           ssh->incoming_data_size > ssh->max_data_size)
            do_ssh2_transport(ssh, "Initiating key re-exchange "
                              "(too much data received)", -1, NULL);
     }
@@ -7179,6 +7322,7 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
     ssh->queue = NULL;
     ssh->queuelen = ssh->queuesize = 0;
     ssh->queueing = FALSE;
+    ssh->qhead = ssh->qtail = NULL;
 
     *backend_handle = ssh;
 
@@ -7209,6 +7353,7 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
 
     ssh->incoming_data_size = ssh->outgoing_data_size =
        ssh->deferred_data_size = 0L;
+    ssh->max_data_size = parse_blocksize(ssh->cfg.ssh_rekey_data);
     ssh->kex_in_progress = FALSE;
 
     p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive);
@@ -7256,6 +7401,13 @@ static void ssh_free(void *handle)
        ssh_free_packet(ssh->queue[ssh->queuelen]);
     sfree(ssh->queue);
 
+    while (ssh->qhead) {
+       struct queued_handler *qh = ssh->qhead;
+       ssh->qhead = qh->next;
+       sfree(ssh->qhead);
+    }
+    ssh->qhead = ssh->qtail = NULL;
+
     if (ssh->channels) {
        while ((c = delpos234(ssh->channels, 0)) != NULL) {
            switch (c->type) {
@@ -7303,17 +7455,12 @@ static void ssh_free(void *handle)
 
 /*
  * Reconfigure the SSH backend.
- * 
- * Currently, this function does nothing very useful. In future,
- * however, we could do some handy things with it. For example, we
- * could make the port forwarding configurer active in the Change
- * Settings box, and this routine could close down existing
- * forwardings and open up new ones in response to changes.
  */
 static void ssh_reconfig(void *handle, Config *cfg)
 {
     Ssh ssh = (Ssh) handle;
     pinger_reconfig(ssh->pinger, &ssh->cfg, cfg);
+    ssh_setup_portfwd(ssh, cfg);
     ssh->cfg = *cfg;                  /* STRUCTURE COPY */
 }
 
@@ -7412,14 +7559,17 @@ static void ssh_size(void *handle, int width, int height)
  */
 static const struct telnet_special *ssh_get_specials(void *handle)
 {
-    static const struct telnet_special ignore_special[] = {
+    static const struct telnet_special ssh1_ignore_special[] = {
+       {"IGNORE message", TS_NOP}
+    };
+    static const struct telnet_special ssh2_transport_specials[] = {
        {"IGNORE message", TS_NOP},
        {"Repeat key exchange", TS_REKEY},
     };
     static const struct telnet_special ssh2_session_specials[] = {
        {NULL, TS_SEP},
        {"Break", TS_BRK},
-       /* These are the signal names defined by draft-ietf-secsh-connect-19.
+       /* These are the signal names defined by draft-ietf-secsh-connect-23.
         * They include all the ISO C signals, but are a subset of the POSIX
         * required signals. */
        {"SIGINT (Interrupt)", TS_SIGINT},
@@ -7437,7 +7587,8 @@ static const struct telnet_special *ssh_get_specials(void *handle)
     static const struct telnet_special specials_end[] = {
        {NULL, TS_EXITMENU}
     };
-    static struct telnet_special ssh_specials[lenof(ignore_special) +
+    /* XXX review this length for any changes: */
+    static struct telnet_special ssh_specials[lenof(ssh2_transport_specials) +
                                              lenof(ssh2_session_specials) +
                                              lenof(specials_end)];
     Ssh ssh = (Ssh) handle;
@@ -7454,9 +7605,9 @@ static const struct telnet_special *ssh_get_specials(void *handle)
         * won't cope with it, since we wouldn't bother sending it if
         * asked anyway. */
        if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE))
-           ADD_SPECIALS(ignore_special);
+           ADD_SPECIALS(ssh1_ignore_special);
     } else if (ssh->version == 2) {
-       ADD_SPECIALS(ignore_special);
+       ADD_SPECIALS(ssh2_transport_specials);
        if (ssh->mainchan)
            ADD_SPECIALS(ssh2_session_specials);
     } /* else we're not ready yet */