Integrate unfix.org's IPv6 patches up to level 10, with rather a lot
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index 8c552b1..170253b 100644 (file)
--- a/ssh.c
+++ b/ssh.c
  * 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_DHGROUP          0x0001
 #define SSH2_PKTCTX_DHGEX            0x0002
 #define SSH2_PKTCTX_PUBLICKEY        0x0010
 #define SSH2_PKTCTX_PASSWORD         0x0020
@@ -162,7 +162,7 @@ static const char *const ssh2_disconnect_reasons[] = {
 #define BUG_CHOKES_ON_RSA                        8
 #define BUG_SSH2_RSA_PADDING                    16
 #define BUG_SSH2_DERIVEKEY                       32
-#define BUG_SSH2_DH_GEX                          64
+/* 64 was BUG_SSH2_DH_GEX, now spare */
 #define BUG_SSH2_PK_SESSIONID                   128
 
 #define translate(x) if (type == x) return #x
@@ -222,8 +222,8 @@ static char *ssh2_pkt_type(int pkt_ctx, int type)
     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_KEXDH_INIT, SSH2_PKTCTX_DHGROUP);
+    translatec(SSH2_MSG_KEXDH_REPLY, SSH2_PKTCTX_DHGROUP);
     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);
@@ -360,11 +360,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
 #define SSH_MAX_BACKLOG 32768
 #define OUR_V2_WINSIZE 16384
 
-const static struct ssh_kex *kex_algs[] = {
-    &ssh_diffiehellman_gex,
-    &ssh_diffiehellman
-};
-
 const static struct ssh_signkey *hostkey_algs[] = { &ssh_rsa, &ssh_dss };
 
 static void *nullmac_make_context(void)
@@ -499,15 +494,38 @@ 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;
+    int addressfamily;
+    void *local;
 };
+#define free_portfwd(pf) ( \
+    ((pf) ? (sfree((pf)->saddr), sfree((pf)->daddr)) : (void)0 ), sfree(pf) )
 
 struct Packet {
     long length;
@@ -566,6 +584,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;
@@ -613,7 +640,7 @@ struct ssh_tag {
     struct ssh_channel *mainchan;      /* primary session channel */
     int exitcode;
 
-    tree234 *rportfwds;
+    tree234 *rportfwds, *portfwds;
 
     enum {
        SSH_STATE_PREPACKET,
@@ -647,7 +674,6 @@ struct ssh_tag {
     int userpass_input_bufpos;
     int userpass_input_echo;
 
-    char *portfwd_strptr;
     int pkt_ctx;
 
     void *x11auth;
@@ -704,6 +730,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;
@@ -713,13 +745,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;
+    long next_rekey, last_rekey;
 };
 
-#define MAX_DATA_BEFORE_REKEY (0x40000000UL)
-#define REKEY_TIMEOUT (3600 * TICKSPERSEC)
-
 #define logevent(s) logevent(ssh->frontend, s)
 
 /* logevent, only printf-formatted. */
@@ -810,6 +840,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;
@@ -1658,7 +1729,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);
 
@@ -1748,7 +1820,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;
@@ -2057,14 +2130,6 @@ static void ssh_detect_bugs(Ssh ssh, char *vstring)
        ssh->remote_bugs |= BUG_SSH2_PK_SESSIONID;
        logevent("We believe remote version has SSH2 public-key-session-ID bug");
     }
-
-    if (ssh->cfg.sshbug_dhgex2 == FORCE_ON) {
-       /*
-        * User specified the SSH2 DH GEX bug.
-        */
-       ssh->remote_bugs |= BUG_SSH2_DH_GEX;
-       logevent("We believe remote version has SSH2 DH group exchange bug");
-    }
 }
 
 /*
@@ -2385,8 +2450,11 @@ static const char *connect_to_host(Ssh ssh, char *host, int port,
     /*
      * Try to find host.
      */
-    logeventf(ssh, "Looking up host \"%s\"", host);
-    addr = name_lookup(host, port, realhost, &ssh->cfg);
+    logeventf(ssh, "Looking up host \"%s\"%s", host,
+             (ssh->cfg.addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" :
+              (ssh->cfg.addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : "")));
+    addr = name_lookup(host, port, realhost, &ssh->cfg,
+                      ssh->cfg.addressfamily);
     if ((err = sk_addr_error(addr)) != NULL) {
        sk_addr_free(addr);
        return err;
@@ -2760,7 +2828,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
 
        /* Warn about chosen cipher if necessary. */
        if (warn)
-           askcipher(ssh->frontend, cipher_string, 0);
+           askalg(ssh->frontend, "cipher", cipher_string);
     }
 
     switch (s->cipher_type) {
@@ -3515,6 +3583,399 @@ 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 address_family, 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) {
+       address_family = 'A';
+       type = 'L';
+       while (*portfwd_strptr && *portfwd_strptr != '\t') {
+           if (*portfwd_strptr == 'A' ||
+               *portfwd_strptr == '4' ||
+               *portfwd_strptr == '6')
+               address_family = *portfwd_strptr;
+           else if (*portfwd_strptr == 'L' ||
+               *portfwd_strptr == 'R' ||
+               *portfwd_strptr == 'D')
+               type = *portfwd_strptr;
+           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;
+           pfrec->addressfamily = (address_family == '4' ? ADDRTYPE_IPV4 :
+                                   address_family == '6' ? ADDRTYPE_IPV6 :
+                                   ADDRTYPE_UNSPEC);
+
+           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,
+                                                pfrec->addressfamily);
+
+               logeventf(ssh, "Local %sport %s forward to %s%s%s",
+                         pfrec->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " :
+                         pfrec->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "",
+                         sportdesc, dportdesc,
+                         err ? " failed: " : "", err);
+
+               sfree(dportdesc);
+           } else if (type == 'D') {
+               const char *err = pfd_addforward(NULL, -1,
+                                                *saddr ? saddr : NULL,
+                                                sport, ssh, &ssh->cfg,
+                                                &pfrec->local,
+                                                pfrec->addressfamily);
+
+                       logeventf(ssh, "Local %sport %s SOCKS dynamic forward%s%s",
+                                 pfrec->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " :
+                                 pfrec->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "",
+                                 sportdesc,
+                                 err ? " setup failed: " : "", err);
+           } 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;
@@ -3606,7 +4067,7 @@ static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin)
     /* 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;
+    struct ssh_rportfwd pf, *pfp;
     int remoteid;
     int hostsize, port;
     char *host, buf[1024];
@@ -3623,8 +4084,9 @@ static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin)
     memcpy(pf.dhost, host, hostsize);
     pf.dhost[hostsize] = '\0';
     pf.dport = port;
+    pfp = find234(ssh->rportfwds, &pf, NULL);
 
-    if (find234(ssh->rportfwds, &pf, NULL) == NULL) {
+    if (pfp == NULL) {
        sprintf(buf, "Rejected remote port open request for %s:%d",
                pf.dhost, port);
        logevent(buf);
@@ -3635,7 +4097,7 @@ static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin)
                pf.dhost, port);
        logevent(buf);
        e = pfd_newconnect(&c->u.pfd.s, pf.dhost, port,
-                          c, &ssh->cfg);
+                          c, &ssh->cfg, pfp->pfrec->addressfamily);
        if (e != NULL) {
            char buf[256];
            sprintf(buf, "Port open failed: %s", e);
@@ -3746,7 +4208,7 @@ static void ssh1_msg_channel_data(Ssh ssh, struct Packet *pktin)
     /* Data sent down one of our channels. */
     int i = ssh_pkt_getuint32(pktin);
     char *p;
-    int len;
+    unsigned int len;
     struct ssh_channel *c;
 
     ssh_pkt_getstring(pktin, &p, &len);
@@ -3765,7 +4227,7 @@ static void ssh1_msg_channel_data(Ssh ssh, struct Packet *pktin)
            /* 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);
+                   unsigned int l = min(4 - c->u.a.lensofar, len);
                    memcpy(c->u.a.msglen + c->u.a.lensofar, p,
                           l);
                    p += l;
@@ -3780,7 +4242,7 @@ static void ssh1_msg_channel_data(Ssh ssh, struct Packet *pktin)
                    memcpy(c->u.a.message, c->u.a.msglen, 4);
                }
                if (c->u.a.lensofar >= 4 && len > 0) {
-                   int l =
+                   unsigned int l =
                        min(c->u.a.totallen - c->u.a.lensofar,
                            len);
                    memcpy(c->u.a.message + c->u.a.lensofar, p,
@@ -3898,164 +4360,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. */
@@ -4320,6 +4626,8 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen,
        int hostkeylen, siglen;
        void *hkey;                    /* actual host key */
        unsigned char exchange_hash[20];
+       int n_preferred_kex;
+       const struct ssh_kex *preferred_kex[KEX_MAX];
        int n_preferred_ciphers;
        const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
        const struct ssh_compress *preferred_comp;
@@ -4339,6 +4647,37 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen,
     {
        int i;
        /*
+        * Set up the preferred key exchange. (NULL => warn below here)
+        */
+       s->n_preferred_kex = 0;
+       for (i = 0; i < KEX_MAX; i++) {
+           switch (ssh->cfg.ssh_kexlist[i]) {
+             case KEX_DHGEX:
+               s->preferred_kex[s->n_preferred_kex++] =
+                   &ssh_diffiehellman_gex;
+               break;
+             case KEX_DHGROUP14:
+               s->preferred_kex[s->n_preferred_kex++] =
+                   &ssh_diffiehellman_group14;
+               break;
+             case KEX_DHGROUP1:
+               s->preferred_kex[s->n_preferred_kex++] =
+                   &ssh_diffiehellman_group1;
+               break;
+             case CIPHER_WARN:
+               /* Flag for later. Don't bother if it's the last in
+                * the list. */
+               if (i < KEX_MAX - 1) {
+                   s->preferred_kex[s->n_preferred_kex++] = NULL;
+               }
+               break;
+           }
+       }
+    }
+
+    {
+       int i;
+       /*
         * Set up the preferred ciphers. (NULL => warn below here)
         */
        s->n_preferred_ciphers = 0;
@@ -4387,7 +4726,7 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen,
 
   begin_key_exchange:
     {
-       int i, j, cipherstr_started;
+       int i, j, commalist_started;
 
        /*
         * Enable queueing of outgoing auth- or connection-layer
@@ -4408,13 +4747,14 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen,
            ssh2_pkt_addbyte(s->pktout, (unsigned char) random_byte());
        /* List key exchange algorithms. */
        ssh2_pkt_addstring_start(s->pktout);
-       for (i = 0; i < lenof(kex_algs); i++) {
-           if (kex_algs[i] == &ssh_diffiehellman_gex &&
-               (ssh->remote_bugs & BUG_SSH2_DH_GEX))
-               continue;
-           ssh2_pkt_addstring_str(s->pktout, kex_algs[i]->name);
-           if (i < lenof(kex_algs) - 1)
+       commalist_started = 0;
+       for (i = 0; i < s->n_preferred_kex; i++) {
+           const struct ssh_kex *k = s->preferred_kex[i];
+           if (!k) continue;          /* warning flag */
+           if (commalist_started)
                ssh2_pkt_addstring_str(s->pktout, ",");
+           ssh2_pkt_addstring_str(s->pktout, s->preferred_kex[i]->name);
+           commalist_started = 1;
        }
        /* List server host key algorithms. */
        ssh2_pkt_addstring_start(s->pktout);
@@ -4425,28 +4765,28 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen,
        }
        /* List client->server encryption algorithms. */
        ssh2_pkt_addstring_start(s->pktout);
-       cipherstr_started = 0;
+       commalist_started = 0;
        for (i = 0; i < s->n_preferred_ciphers; i++) {
            const struct ssh2_ciphers *c = s->preferred_ciphers[i];
            if (!c) continue;          /* warning flag */
            for (j = 0; j < c->nciphers; j++) {
-               if (cipherstr_started)
+               if (commalist_started)
                    ssh2_pkt_addstring_str(s->pktout, ",");
                ssh2_pkt_addstring_str(s->pktout, c->list[j]->name);
-               cipherstr_started = 1;
+               commalist_started = 1;
            }
        }
        /* List server->client encryption algorithms. */
        ssh2_pkt_addstring_start(s->pktout);
-       cipherstr_started = 0;
+       commalist_started = 0;
        for (i = 0; i < s->n_preferred_ciphers; i++) {
            const struct ssh2_ciphers *c = s->preferred_ciphers[i];
            if (!c) continue; /* warning flag */
            for (j = 0; j < c->nciphers; j++) {
-               if (cipherstr_started)
+               if (commalist_started)
                    ssh2_pkt_addstring_str(s->pktout, ",");
                ssh2_pkt_addstring_str(s->pktout, c->list[j]->name);
-               cipherstr_started = 1;
+               commalist_started = 1;
            }
        }
        /* List client->server MAC algorithms. */
@@ -4527,15 +4867,26 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen,
        s->sccomp_tobe = NULL;
        pktin->savedpos += 16;          /* skip garbage cookie */
        ssh_pkt_getstring(pktin, &str, &len);    /* key exchange algorithms */
-       for (i = 0; i < lenof(kex_algs); i++) {
-           if (kex_algs[i] == &ssh_diffiehellman_gex &&
-               (ssh->remote_bugs & BUG_SSH2_DH_GEX))
-               continue;
-           if (in_commasep_string(kex_algs[i]->name, str, len)) {
-               ssh->kex = kex_algs[i];
+       s->warn = 0;
+       for (i = 0; i < s->n_preferred_kex; i++) {
+           const struct ssh_kex *k = s->preferred_kex[i];
+           if (!k) {
+               s->warn = 1;
+           } else if (in_commasep_string(k->name, str, len)) {
+               ssh->kex = k;
+           }
+           if (ssh->kex) {
+               if (s->warn)
+                   askalg(ssh->frontend, "key-exchange algorithm",
+                          ssh->kex->name);
                break;
            }
        }
+       if (!ssh->kex) {
+           bombout(("Couldn't agree a key exchange algorithm (available: %s)",
+                    str ? str : "(null)"));
+           crStop(0);
+       }
        ssh_pkt_getstring(pktin, &str, &len);    /* host key algorithms */
        for (i = 0; i < lenof(hostkey_algs); i++) {
            if (in_commasep_string(hostkey_algs[i]->name, str, len)) {
@@ -4559,7 +4910,8 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen,
            }
            if (s->cscipher_tobe) {
                if (s->warn)
-                   askcipher(ssh->frontend, s->cscipher_tobe->name, 1);
+                   askalg(ssh->frontend, "client-to-server cipher",
+                          s->cscipher_tobe->name);
                break;
            }
        }
@@ -4585,7 +4937,8 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen,
            }
            if (s->sccipher_tobe) {
                if (s->warn)
-                   askcipher(ssh->frontend, s->sccipher_tobe->name, 2);
+                   askalg(ssh->frontend, "server-to-client cipher",
+                          s->sccipher_tobe->name);
                break;
            }
        }
@@ -4650,7 +5003,7 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen,
      * If we're doing Diffie-Hellman group exchange, start by
      * requesting a group.
      */
-    if (ssh->kex == &ssh_diffiehellman_gex) {
+    if (!ssh->kex->pdata) {
        logevent("Doing Diffie-Hellman group exchange");
        ssh->pkt_ctx |= SSH2_PKTCTX_DHGEX;
        /*
@@ -4673,14 +5026,16 @@ static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen,
            bombout(("unable to read mp-ints from incoming group packet"));
            crStop(0);
        }
-       ssh->kex_ctx = dh_setup_group(s->p, s->g);
+       ssh->kex_ctx = dh_setup_gex(s->p, s->g);
        s->kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT;
        s->kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY;
     } else {
-       ssh->pkt_ctx |= SSH2_PKTCTX_DHGROUP1;
-       ssh->kex_ctx = dh_setup_group1();
+       ssh->pkt_ctx |= SSH2_PKTCTX_DHGROUP;
+       ssh->kex_ctx = dh_setup_group(ssh->kex);
        s->kex_init_value = SSH2_MSG_KEXDH_INIT;
        s->kex_reply_value = SSH2_MSG_KEXDH_REPLY;
+       logeventf(ssh, "Using Diffie-Hellman with standard group \"%s\"",
+                 ssh->kex->groupname);
     }
 
     logevent("Doing Diffie-Hellman key exchange");
@@ -4879,8 +5234,11 @@ 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);
-
+    ssh->last_rekey = GETTICKCOUNT();
+    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
@@ -5000,7 +5358,7 @@ static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin)
 static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
 {
     char *data;
-    int length;
+    unsigned int length;
     unsigned i = ssh_pkt_getuint32(pktin);
     struct ssh_channel *c;
     c = find234(ssh->channels, &i, ssh_channelfind);
@@ -5029,7 +5387,7 @@ static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
          case CHAN_AGENT:
            while (length > 0) {
                if (c->u.a.lensofar < 4) {
-                   int l = min(4 - c->u.a.lensofar, length);
+                   unsigned int l = min(4 - c->u.a.lensofar, length);
                    memcpy(c->u.a.msglen + c->u.a.lensofar,
                           data, l);
                    data += l;
@@ -5044,7 +5402,7 @@ static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
                    memcpy(c->u.a.message, c->u.a.msglen, 4);
                }
                if (c->u.a.lensofar >= 4 && length > 0) {
-                   int l =
+                   unsigned int l =
                        min(c->u.a.totallen - c->u.a.lensofar,
                            length);
                    memcpy(c->u.a.message + c->u.a.lensofar,
@@ -5464,7 +5822,8 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
            const char *e = pfd_newconnect(&c->u.pfd.s,
                                           realpf->dhost,
                                           realpf->dport, c,
-                                          &ssh->cfg);
+                                          &ssh->cfg,
+                                          realpf->pfrec->addressfamily);
            logeventf(ssh, "Attempting to forward remote port to "
                      "%s:%d", realpf->dhost, realpf->dport);
            if (e != NULL) {
@@ -6488,163 +6847,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.
@@ -7035,7 +7238,7 @@ static void ssh2_timer(void *ctx, long now)
 {
     Ssh ssh = (Ssh)ctx;
 
-    if (!ssh->kex_in_progress &&
+    if (!ssh->kex_in_progress && ssh->cfg.ssh_rekey_time != 0 &&
        now - ssh->next_rekey >= 0) {
        do_ssh2_transport(ssh, "Initiating key re-exchange (timeout)",
                          -1, NULL);
@@ -7051,7 +7254,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);
     }
@@ -7143,6 +7347,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;
 
@@ -7157,6 +7362,7 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
 
     ssh->channels = NULL;
     ssh->rportfwds = NULL;
+    ssh->portfwds = NULL;
 
     ssh->send_ok = 0;
     ssh->editing = 0;
@@ -7173,6 +7379,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);
@@ -7220,6 +7427,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) {
@@ -7258,26 +7472,50 @@ static void ssh_free(void *handle)
     if (ssh->s)
        ssh_do_close(ssh);
     expire_timer_context(ssh);
-    sfree(ssh);
     if (ssh->pinger)
        pinger_free(ssh->pinger);
+    sfree(ssh);
 
     random_unref();
 }
 
 /*
  * 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;
+    char *rekeying = NULL;
+    unsigned long old_max_data_size;
+
     pinger_reconfig(ssh->pinger, &ssh->cfg, cfg);
+    ssh_setup_portfwd(ssh, cfg);
+
+    if (ssh->cfg.ssh_rekey_time != cfg->ssh_rekey_time &&
+       cfg->ssh_rekey_time != 0) {
+       long new_next = ssh->last_rekey + cfg->ssh_rekey_time*60*TICKSPERSEC;
+       long now = GETTICKCOUNT();
+
+       if (new_next - now < 0) {
+           rekeying = "Initiating key re-exchange (timeout shortened)";
+       } else {
+           ssh->next_rekey = schedule_timer(new_next - now, ssh2_timer, ssh);
+       }
+    }
+
+    old_max_data_size = ssh->max_data_size;
+    ssh->max_data_size = parse_blocksize(cfg->ssh_rekey_data);
+    if (old_max_data_size != ssh->max_data_size &&
+       ssh->max_data_size != 0) {
+       if (ssh->outgoing_data_size > ssh->max_data_size ||
+           ssh->incoming_data_size > ssh->max_data_size)
+           rekeying = "Initiating key re-exchange (data limit lowered)";
+    }
+
+    if (rekeying && !ssh->kex_in_progress) {
+       do_ssh2_transport(ssh, rekeying, -1, NULL);
+    }
+
     ssh->cfg = *cfg;                  /* STRUCTURE COPY */
 }
 
@@ -7376,14 +7614,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},
@@ -7401,7 +7642,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;
@@ -7418,9 +7660,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 */
@@ -7576,7 +7818,7 @@ void ssh_send_port_open(void *channel, char *hostname, int port, char *org)
                    PKT_INT, c->localid,
                    PKT_STR, hostname,
                    PKT_INT, port,
-                   //PKT_STR, <org:orgport>,
+                   /* PKT_STR, <org:orgport>, */
                    PKT_END);
     } else {
        pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
@@ -7643,6 +7885,16 @@ static int ssh_return_exitcode(void *handle)
 }
 
 /*
+ * cfg_info for SSH is the currently running version of the
+ * protocol. (1 for 1; 2 for 2; 0 for not-decided-yet.)
+ */
+static int ssh_cfg_info(void *handle)
+{
+    Ssh ssh = (Ssh) handle;
+    return ssh->version;
+}
+
+/*
  * Gross hack: pscp will try to start SFTP but fall back to scp1 if
  * that fails. This variable is the means by which scp.c can reach
  * into the SSH code and find out which one it got.
@@ -7669,5 +7921,6 @@ Backend ssh_backend = {
     ssh_provide_ldisc,
     ssh_provide_logctx,
     ssh_unthrottle,
+    ssh_cfg_info,
     22
 };