Port forwarding now works in SSH 2 as well as SSH 1.
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index e120c15..f6828dd 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -95,6 +95,8 @@
 #define SSH2_MSG_USERAUTH_BANNER                  53   /* 0x35 */
 #define SSH2_MSG_USERAUTH_PK_OK                   60   /* 0x3c */
 #define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ        60   /* 0x3c */
+#define SSH2_MSG_USERAUTH_INFO_REQUEST            60   /* 0x3c */
+#define SSH2_MSG_USERAUTH_INFO_RESPONSE           61   /* 0x3d */
 #define SSH2_MSG_GLOBAL_REQUEST                   80   /* 0x50 */
 #define SSH2_MSG_REQUEST_SUCCESS                  81   /* 0x51 */
 #define SSH2_MSG_REQUEST_FAILURE                  82   /* 0x52 */
@@ -196,6 +198,12 @@ extern void x11_close(Socket);
 extern void x11_send(Socket, char *, int);
 extern void x11_invent_auth(char *, int, char *, int);
 
+extern char *pfd_newconnect(Socket * s, char *hostname, int port, void *c);
+extern char *pfd_addforward(char *desthost, int destport, int port);
+extern void pfd_close(Socket s);
+extern void pfd_send(Socket s, char *data, int len);
+extern void pfd_confirm(Socket s);
+
 /*
  * Ciphers for SSH2. We miss out single-DES because it isn't
  * supported; also 3DES and Blowfish are both done differently from
@@ -263,6 +271,8 @@ enum {                                     /* channel types */
     CHAN_MAINSESSION,
     CHAN_X11,
     CHAN_AGENT,
+    CHAN_SOCKDATA,
+    CHAN_SOCKDATA_DORMANT             /* one the remote hasn't confirmed */
 };
 
 /*
@@ -286,9 +296,46 @@ struct ssh_channel {
        struct ssh_x11_channel {
            Socket s;
        } x11;
+       struct ssh_pfd_channel {
+           Socket s;
+       } pfd;
     } u;
 };
 
+/*
+ * 2-3-4 tree storing remote->local port forwardings. SSH 1 and SSH
+ * 2 use this structure in different ways, reflecting SSH 2's
+ * altogether saner approach to port forwarding.
+ * 
+ * In SSH 1, you arrange a remote forwarding by sending the server
+ * the remote port number, and the local destination host:port.
+ * When a connection comes in, the server sends you back that
+ * host:port pair, and you connect to it. This is a ready-made
+ * security hole if you're not on the ball: a malicious server
+ * could send you back _any_ host:port pair, so if you trustingly
+ * connect to the address it gives you then you've just opened the
+ * entire inside of your corporate network just by connecting
+ * through it to a dodgy SSH server. Hence, we must store a list of
+ * host:port pairs we _are_ trying to forward to, and reject a
+ * connection request from the server if it's not in the list.
+ * 
+ * In SSH 2, each side of the connection minds its own business and
+ * doesn't send unnecessary information to the other. You arrange a
+ * remote forwarding by sending the server just the remote port
+ * number. When a connection comes in, the server tells you which
+ * 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.
+ */
+struct ssh_rportfwd {
+    unsigned sport, dport;
+    char dhost[256];
+};
+
 struct Packet {
     long length;
     int type;
@@ -330,6 +377,8 @@ static int ssh_echoing, ssh_editing;
 static tree234 *ssh_channels;         /* indexed by local id */
 static struct ssh_channel *mainchan;   /* primary session channel */
 
+static tree234 *ssh_rportfwds;
+
 static enum {
     SSH_STATE_PREPACKET,
     SSH_STATE_BEFORE_SIZE,
@@ -393,6 +442,32 @@ static int ssh_channelfind(void *av, void *bv)
     return 0;
 }
 
+static int ssh_rportcmp_ssh1(void *av, void *bv)
+{
+    struct ssh_rportfwd *a = (struct ssh_rportfwd *) av;
+    struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv;
+    int i;
+    if ( (i = strcmp(a->dhost, b->dhost)) != 0)
+       return i < 0 ? -1 : +1;
+    if (a->dport > b->dport)
+       return +1;
+    if (a->dport < b->dport)
+       return -1;
+    return 0;
+}
+
+static int ssh_rportcmp_ssh2(void *av, void *bv)
+{
+    struct ssh_rportfwd *a = (struct ssh_rportfwd *) av;
+    struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv;
+    int i;
+    if (a->sport > b->sport)
+       return +1;
+    if (a->sport < b->sport)
+       return -1;
+    return 0;
+}
+
 static int alloc_channel_id(void)
 {
     const unsigned CHANNEL_NUMBER_OFFSET = 256;
@@ -1322,7 +1397,7 @@ static void ssh_detect_bugs(char *vstring)
 
 static int do_ssh_init(unsigned char c)
 {
-    static char vslen;
+    static int vslen;
     static char version[10];
     static char *vstring;
     static int vstrsize;
@@ -1478,8 +1553,10 @@ static int ssh_closing(Plug plug, char *error_msg, int error_code,
                       int calling_back)
 {
     ssh_state = SSH_STATE_CLOSED;
-    sk_close(s);
-    s = NULL;
+    if (s) {
+        sk_close(s);
+        s = NULL;
+    }
     if (error_msg) {
        /* A socket error has occurred. */
        connection_fatal(error_msg);
@@ -1574,15 +1651,16 @@ static char *connect_to_host(char *host, int port, char **realhost)
  */
 static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
 {
-    int i, j, len;
-    unsigned char *rsabuf, *keystr1, *keystr2;
+    int i, j;
+    static int len;
+    static unsigned char *rsabuf, *keystr1, *keystr2;
     unsigned char cookie[8];
     struct RSAKey servkey, hostkey;
     struct MD5Context md5c;
     static unsigned long supported_ciphers_mask, supported_auths_mask;
     static int tried_publickey;
     static unsigned char session_id[16];
-    int cipher_type;
+    static int cipher_type;
     static char username[100];
 
     crBegin;
@@ -1783,7 +1861,8 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                            break;
                          default:
                            if (((c >= ' ' && c <= '~') ||
-                                ((unsigned char) c >= 160)) && pos < 40) {
+                                ((unsigned char) c >= 160))
+                               && pos < sizeof(username)-1) {
                                username[pos++] = c;
                                c_write(&c, 1);
                            }
@@ -1883,6 +1962,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                    ssh1_read_bignum(pktin.body, &challenge);
                    {
                        char *agentreq, *q, *ret;
+                       void *vret;
                        int len, retlen;
                        len = 1 + 4;   /* message type, bit count */
                        len += ssh1_bignum_length(key.exponent);
@@ -1902,7 +1982,8 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                        memcpy(q, session_id, 16);
                        q += 16;
                        PUT_32BIT(q, 1);        /* response format */
-                       agent_query(agentreq, len + 4, &ret, &retlen);
+                       agent_query(agentreq, len + 4, &vret, &retlen);
+                       ret = vret;
                        sfree(agentreq);
                        if (ret) {
                            if (ret[4] == SSH1_AGENT_RSA_RESPONSE) {
@@ -2018,7 +2099,10 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                 * because one was supplied on the command line
                 * which has already failed to work). Terminate.
                 */
-               logevent("No more passwords to try");
+               send_packet(SSH1_MSG_DISCONNECT,
+                           PKT_STR, "No more passwords available to try",
+                           PKT_END);
+               connection_fatal("Unable to authenticate");
                ssh_state = SSH_STATE_CLOSED;
                crReturn(1);
            }
@@ -2050,9 +2134,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                        exit(0);
                        break;
                      default:
-                       if (((c >= ' ' && c <= '~') ||
-                            ((unsigned char) c >= 160))
-                           && pos < sizeof(password))
+                       if (pos < sizeof(password)-1)
                            password[pos++] = c;
                        break;
                    }
@@ -2256,7 +2338,10 @@ void sshfwd_close(struct ssh_channel *c)
        c->closes = 1;
        if (c->type == CHAN_X11) {
            c->u.x11.s = NULL;
-           logevent("X11 connection terminated");
+           logevent("Forwarded X11 connection terminated");
+       } else if (c->type == CHAN_SOCKDATA) {
+           c->u.pfd.s = NULL;
+           logevent("Forwarded port closed");
        }
     }
 }
@@ -2330,6 +2415,69 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
        }
     }
 
+    {
+       char type, *e;
+       int n;
+       int sport,dport;
+       char sports[256], dports[256], host[256];
+       char buf[1024];
+
+       ssh_rportfwds = newtree234(ssh_rportcmp_ssh1);
+        /* Add port forwardings. */
+       e = cfg.portfwd;
+       while (*e) {
+           type = *e++;
+           n = 0;
+           while (*e && *e != '\t')
+               sports[n++] = *e++;
+           sports[n] = 0;
+           if (*e == '\t')
+               e++;
+           n = 0;
+           while (*e && *e != ':')
+               host[n++] = *e++;
+           host[n] = 0;
+           if (*e == ':')
+               e++;
+           n = 0;
+           while (*e)
+               dports[n++] = *e++;
+           dports[n] = 0;
+           e++;
+           dport = atoi(dports);
+           sport = atoi(sports);
+           if (sport && dport) {
+               if (type == 'L') {
+                   pfd_addforward(host, dport, sport);
+                   sprintf(buf, "Local port %d forwarding to %s:%d",
+                           sport, host, dport);
+                   logevent(buf);
+               } else {
+                   struct ssh_rportfwd *pf;
+                   pf = smalloc(sizeof(*pf));
+                   strcpy(pf->dhost, host);
+                   pf->dport = dport;
+                   if (add234(ssh_rportfwds, pf) != pf) {
+                       sprintf(buf, 
+                               "Duplicate remote port forwarding to %s:%s",
+                               host, dport);
+                       logevent(buf);
+                       sfree(pf);
+                   } else {
+                       sprintf(buf, "Requesting remote port %d forward to %s:%d",
+                               sport, host, dport);
+                       logevent(buf);
+                       send_packet(SSH1_CMSG_PORT_FORWARD_REQUEST,
+                                   PKT_INT, sport,
+                                   PKT_STR, host,
+                                   PKT_INT, dport,
+                                   PKT_END);
+                   }
+               }
+           }
+       }
+    }
+
     if (!cfg.nopty) {
        send_packet(SSH1_CMSG_REQUEST_PTY,
                    PKT_STR, cfg.termtype,
@@ -2453,6 +2601,72 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
                                PKT_INT, c->remoteid, PKT_INT, c->localid,
                                PKT_END);
                }
+           } else if (pktin.type == SSH1_MSG_PORT_OPEN) {
+               /* Remote side is trying to open a channel to talk to a
+                * forwarded port. Give them back a local channel number. */
+               struct ssh_channel *c;
+               struct ssh_rportfwd pf;
+               int hostsize, port;
+               char host[256], buf[1024];
+               char *p, *h, *e;
+               c = smalloc(sizeof(struct ssh_channel));
+
+               hostsize = GET_32BIT(pktin.body+4);
+               for(h = host, p = pktin.body+8; hostsize != 0; hostsize--) {
+                   if (h+1 < host+sizeof(host))
+                       *h++ = *p;
+                   *p++;
+               }
+               *h = 0;
+               port = GET_32BIT(p);
+
+               strcpy(pf.dhost, host);
+               pf.dport = port;
+
+               if (find234(ssh_rportfwds, &pf, NULL) == NULL) {
+                   sprintf(buf, "Rejected remote port open request for %s:%d",
+                           host, port);
+                   logevent(buf);
+                    send_packet(SSH1_MSG_CHANNEL_OPEN_FAILURE,
+                                PKT_INT, GET_32BIT(pktin.body), PKT_END);
+               } else {
+                   sprintf(buf, "Received remote port open request for %s:%d",
+                           host, port);
+                   logevent(buf);
+                   e = pfd_newconnect(&c->u.pfd.s, host, port, c);
+                   if (e != NULL) {
+                       char buf[256];
+                       sprintf(buf, "Port open failed: %s", e);
+                       logevent(buf);
+                       sfree(c);
+                       send_packet(SSH1_MSG_CHANNEL_OPEN_FAILURE,
+                                   PKT_INT, GET_32BIT(pktin.body),
+                                   PKT_END);
+                   } else {
+                       c->remoteid = GET_32BIT(pktin.body);
+                       c->localid = alloc_channel_id();
+                       c->closes = 0;
+                       c->type = CHAN_SOCKDATA;        /* identify channel type */
+                       add234(ssh_channels, c);
+                       send_packet(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
+                                   PKT_INT, c->remoteid, PKT_INT,
+                                   c->localid, PKT_END);
+                       logevent("Forwarded port opened successfully");
+                   }
+               }
+
+           } else if (pktin.type == SSH1_MSG_CHANNEL_OPEN_CONFIRMATION) {
+               unsigned int remoteid = GET_32BIT(pktin.body);
+               unsigned int localid = GET_32BIT(pktin.body+4);
+               struct ssh_channel *c;
+
+               c = find234(ssh_channels, &remoteid, ssh_channelfind);
+               if (c && c->type == CHAN_SOCKDATA_DORMANT) {
+                   c->remoteid = localid;
+                   c->type = CHAN_SOCKDATA;
+                   pfd_confirm(c->u.pfd.s);
+               }
+
            } else if (pktin.type == SSH1_MSG_CHANNEL_CLOSE ||
                       pktin.type == SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION) {
                /* Remote side closes a channel. */
@@ -2467,11 +2681,17 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
                        send_packet(pktin.type, PKT_INT, c->remoteid,
                                    PKT_END);
                    if ((c->closes == 0) && (c->type == CHAN_X11)) {
-                       logevent("X11 connection closed");
+                       logevent("Forwarded X11 connection terminated");
                        assert(c->u.x11.s != NULL);
                        x11_close(c->u.x11.s);
                        c->u.x11.s = NULL;
                    }
+                   if ((c->closes == 0) && (c->type == CHAN_SOCKDATA)) {
+                       logevent("Forwarded port closed");
+                       assert(c->u.pfd.s != NULL);
+                       pfd_close(c->u.pfd.s);
+                       c->u.pfd.s = NULL;
+                   }
                    c->closes |= closetype;
                    if (c->closes == 3) {
                        del234(ssh_channels, c);
@@ -2490,6 +2710,9 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
                      case CHAN_X11:
                        x11_send(c->u.x11.s, p, len);
                        break;
+                     case CHAN_SOCKDATA:
+                       pfd_send(c->u.pfd.s, p, len);
+                       break;
                      case CHAN_AGENT:
                        /* Data for an agent message. Buffer it. */
                        while (len > 0) {
@@ -2948,8 +3171,9 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
 #endif
 
     hkey = hostkey->newkey(hostkeydata, hostkeylen);
-    if (!hostkey->verifysig(hkey, sigdata, siglen, exchange_hash, 20)) {
-       bombout(("Server failed host key check"));
+    if (!hkey ||
+       !hostkey->verifysig(hkey, sigdata, siglen, exchange_hash, 20)) {
+       bombout(("Server's host key did not match the signature supplied"));
        crReturn(0);
     }
 
@@ -3083,22 +3307,23 @@ static void ssh2_try_send(struct ssh_channel *c)
  */
 static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
 {
-    static unsigned long remote_winsize;
-    static unsigned long remote_maxpkt;
     static enum {
        AUTH_INVALID, AUTH_PUBLICKEY_AGENT, AUTH_PUBLICKEY_FILE,
-       AUTH_PASSWORD
+       AUTH_PASSWORD,
+        AUTH_KEYBOARD_INTERACTIVE
     } method;
     static enum {
        AUTH_TYPE_NONE,
        AUTH_TYPE_PUBLICKEY,
        AUTH_TYPE_PUBLICKEY_OFFER_LOUD,
        AUTH_TYPE_PUBLICKEY_OFFER_QUIET,
-       AUTH_TYPE_PASSWORD
+       AUTH_TYPE_PASSWORD,
+       AUTH_TYPE_KEYBOARD_INTERACTIVE
     } type;
-    static int gotit, need_pw, can_pubkey, can_passwd;
-    static int tried_pubkey_config, tried_agent;
+    static int gotit, need_pw, can_pubkey, can_passwd, can_keyb_inter;
+    static int tried_pubkey_config, tried_agent, tried_keyb_inter;
     static int we_are_in;
+    static int num_prompts, echo;
     static char username[100];
     static char pwprompt[200];
     static char password[100];
@@ -3194,7 +3419,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                            break;
                          default:
                            if (((c >= ' ' && c <= '~') ||
-                                ((unsigned char) c >= 160)) && pos < 40) {
+                                ((unsigned char) c >= 160))
+                               && pos < sizeof(username)-1) {
                                username[pos++] = c;
                                c_write(&c, 1);
                            }
@@ -3325,10 +3551,58 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    in_commasep_string("publickey", methods, methlen);
                can_passwd =
                    in_commasep_string("password", methods, methlen);
+               can_passwd =
+                   in_commasep_string("password", methods, methlen);
+               can_keyb_inter = 
+                   in_commasep_string("keyboard-interactive", methods, methlen);
            }
 
            method = 0;
 
+           if (!method && can_keyb_inter && !tried_keyb_inter) {
+               method = AUTH_KEYBOARD_INTERACTIVE;
+               type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
+               tried_keyb_inter = TRUE;
+
+               ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
+               ssh2_pkt_addstring(username);
+               ssh2_pkt_addstring("ssh-connection");   /* service requested */
+               ssh2_pkt_addstring("keyboard-interactive");     /* method */
+               ssh2_pkt_addstring(""); /* lang */
+               ssh2_pkt_addstring("");
+               ssh2_pkt_send();
+
+               crWaitUntilV(ispkt);
+               if (pktin.type != SSH2_MSG_USERAUTH_INFO_REQUEST) {
+                   if (pktin.type == SSH2_MSG_USERAUTH_FAILURE)
+                       gotit = TRUE;
+                   logevent("Keyboard-interactive authentication refused");
+                   type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
+                   continue;
+               }
+
+               /* We've got packet with that "interactive" info
+                  dump banners, and set its prompt as ours */
+               {
+                   char *name, *inst, *lang, *prompt;
+                   int name_len, inst_len, lang_len, prompt_len;
+                   ssh2_pkt_getstring(&name, &name_len);
+                   ssh2_pkt_getstring(&inst, &inst_len);
+                   ssh2_pkt_getstring(&lang, &lang_len);
+                   if (name_len > 0)
+                       c_write_untrusted(name, name_len);
+                   if (inst_len > 0)
+                       c_write_untrusted(inst, inst_len);
+                   num_prompts = ssh2_pkt_getuint32();
+
+                   ssh2_pkt_getstring(&prompt, &prompt_len);
+                   strncpy(pwprompt, prompt, sizeof(pwprompt));
+                   need_pw = TRUE;
+
+                   echo = ssh2_pkt_getbool();
+               }
+           }
+
            if (!method && can_pubkey && agent_exists() && !tried_agent) {
                /*
                 * Attempt public-key authentication using Pageant.
@@ -3363,6 +3637,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                        static int pklen, alglen, commentlen;
                        static int siglen, retlen, len;
                        static char *q, *agentreq, *ret;
+                       void *vret;
 
                        {
                            char buf[64];
@@ -3441,7 +3716,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                        q += pktout.length - 5;
                        /* And finally the (zero) flags word. */
                        PUT_32BIT(q, 0);
-                       agent_query(agentreq, len + 4, &ret, &retlen);
+                       agent_query(agentreq, len + 4, &vret, &retlen);
+                       ret = vret;
                        sfree(agentreq);
                        if (ret) {
                            if (ret[4] == SSH2_AGENT_SIGN_RESPONSE) {
@@ -3537,7 +3813,13 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                         * command line which has already failed to
                         * work). Terminate.
                         */
-                       logevent("No more passwords to try");
+                       ssh2_pkt_init(SSH2_MSG_DISCONNECT);
+                       ssh2_pkt_adduint32(SSH2_DISCONNECT_BY_APPLICATION);
+                       ssh2_pkt_addstring
+                           ("No more passwords available to try");
+                       ssh2_pkt_addstring("en");       /* language tag */
+                       ssh2_pkt_send();
+                       connection_fatal("Unable to authenticate");
                        ssh_state = SSH_STATE_CLOSED;
                        crReturnV;
                    }
@@ -3545,7 +3827,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    static int pos = 0;
                    static char c;
 
-                   c_write_str(pwprompt);
+                   c_write_untrusted(pwprompt, strlen(pwprompt));
                    ssh_send_ok = 1;
 
                    pos = 0;
@@ -3573,9 +3855,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                                exit(0);
                                break;
                              default:
-                               if (((c >= ' ' && c <= '~') ||
-                                    ((unsigned char) c >= 160))
-                                   && pos < 40)
+                               if (pos < sizeof(password)-1)
                                    password[pos++] = c;
                                break;
                            }
@@ -3708,6 +3988,13 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                ssh_pkt_defersend();
                logevent("Sent password");
                type = AUTH_TYPE_PASSWORD;
+           } else if (method == AUTH_KEYBOARD_INTERACTIVE) {
+                ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE);
+                ssh2_pkt_adduint32(num_prompts);
+                ssh2_pkt_addstring(password);
+                memset(password, 0, sizeof(password));
+                ssh2_pkt_send();
+               type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
            } else {
                c_write_str
                    ("No supported authentication methods left to try!\r\n");
@@ -3805,6 +4092,97 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
     }
 
     /*
+     * Enable port forwardings.
+     */
+    {
+       static char *e;                /* preserve across crReturn */
+       char type;
+       int n;
+       int sport,dport;
+       char sports[256], dports[256], host[256];
+       char buf[1024];
+
+       ssh_rportfwds = newtree234(ssh_rportcmp_ssh2);
+        /* Add port forwardings. */
+       e = cfg.portfwd;
+       while (*e) {
+           type = *e++;
+           n = 0;
+           while (*e && *e != '\t')
+               sports[n++] = *e++;
+           sports[n] = 0;
+           if (*e == '\t')
+               e++;
+           n = 0;
+           while (*e && *e != ':')
+               host[n++] = *e++;
+           host[n] = 0;
+           if (*e == ':')
+               e++;
+           n = 0;
+           while (*e)
+               dports[n++] = *e++;
+           dports[n] = 0;
+           e++;
+           dport = atoi(dports);
+           sport = atoi(sports);
+           if (sport && dport) {
+               if (type == 'L') {
+                   pfd_addforward(host, dport, sport);
+                   sprintf(buf, "Local port %d forwarding to %s:%d",
+                           sport, host, dport);
+                   logevent(buf);
+               } else {
+                   struct ssh_rportfwd *pf;
+                   pf = smalloc(sizeof(*pf));
+                   strcpy(pf->dhost, host);
+                   pf->dport = dport;
+                   pf->sport = sport;
+                   if (add234(ssh_rportfwds, pf) != pf) {
+                       sprintf(buf, 
+                               "Duplicate remote port forwarding to %s:%s",
+                               host, dport);
+                       logevent(buf);
+                       sfree(pf);
+                   } else {
+                       sprintf(buf, "Requesting remote port %d (forwarded to %s:%d)",
+                               sport, host, dport);
+                       logevent(buf);
+                       ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST);
+                       ssh2_pkt_addstring("tcpip-forward");
+                       ssh2_pkt_addbool(1);/* want reply */
+                       ssh2_pkt_addstring("127.0.0.1");
+                       ssh2_pkt_adduint32(sport);
+                       ssh2_pkt_send();
+
+                       do {
+                           crWaitUntilV(ispkt);
+                           if (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
+                               unsigned i = ssh2_pkt_getuint32();
+                               struct ssh_channel *c;
+                               c = find234(ssh_channels, &i, ssh_channelfind);
+                               if (!c)
+                                   continue;/* nonexistent channel */
+                               c->v2.remwindow += ssh2_pkt_getuint32();
+                           }
+                       } while (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+
+                       if (pktin.type != SSH2_MSG_REQUEST_SUCCESS) {
+                           if (pktin.type != SSH2_MSG_REQUEST_FAILURE) {
+                               bombout(("Server got confused by port forwarding request"));
+                               crReturnV;
+                           }
+                           logevent("Server refused this port forwarding");
+                       } else {
+                           logevent("Remote port forwarding enabled");
+                       }
+                   }
+               }
+           }
+       }
+    }
+
+    /*
      * Potentially enable agent forwarding.
      */
     if (cfg.agentfwd && agent_exists()) {
@@ -3963,6 +4341,9 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                      case CHAN_X11:
                        x11_send(c->u.x11.s, data, length);
                        break;
+                     case CHAN_SOCKDATA:
+                       pfd_send(c->u.pfd.s, data, length);
+                       break;
                      case CHAN_AGENT:
                        while (length > 0) {
                            if (c->u.a.lensofar < 4) {
@@ -4046,6 +4427,9 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    sshfwd_close(c);
                } else if (c->type == CHAN_AGENT) {
                    sshfwd_close(c);
+               } else if (c->type == CHAN_SOCKDATA) {
+                   pfd_close(c->u.pfd.s);
+                   sshfwd_close(c);
                }
            } else if (pktin.type == SSH2_MSG_CHANNEL_CLOSE) {
                unsigned i = ssh2_pkt_getuint32();
@@ -4067,6 +4451,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    break;
                  case CHAN_AGENT:
                    break;
+                 case CHAN_SOCKDATA:
+                   break;
                }
                del234(ssh_channels, c);
                sfree(c->v2.outbuffer);
@@ -4094,14 +4480,35 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    continue;          /* nonexistent channel */
                c->v2.remwindow += ssh2_pkt_getuint32();
                try_send = TRUE;
+           } else if (pktin.type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
+               unsigned i = ssh2_pkt_getuint32();
+               struct ssh_channel *c;
+               c = find234(ssh_channels, &i, ssh_channelfind);
+               if (!c)
+                   continue;          /* nonexistent channel */
+               if (c->type != CHAN_SOCKDATA_DORMANT)
+                   continue;          /* dunno why they're confirming this */
+               c->remoteid = ssh2_pkt_getuint32();
+               c->type = CHAN_SOCKDATA;
+               c->closes = 0;
+               c->v2.remwindow = ssh2_pkt_getuint32();
+               c->v2.remmaxpkt = ssh2_pkt_getuint32();
+               c->v2.outbuffer = NULL;
+               c->v2.outbuflen = c->v2.outbufsize = 0;
+               pfd_confirm(c->u.pfd.s);
            } else if (pktin.type == SSH2_MSG_CHANNEL_OPEN) {
                char *type;
                int typelen;
                char *error = NULL;
                struct ssh_channel *c;
+               unsigned remid, winsize, pktsize;
                ssh2_pkt_getstring(&type, &typelen);
                c = smalloc(sizeof(struct ssh_channel));
 
+               remid = ssh2_pkt_getuint32();
+               winsize = ssh2_pkt_getuint32();
+               pktsize = ssh2_pkt_getuint32();
+
                if (typelen == 3 && !memcmp(type, "x11", 3)) {
                    if (!ssh_X11_fwd_enabled)
                        error = "X11 forwarding is not enabled";
@@ -4111,6 +4518,32 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    } else {
                        c->type = CHAN_X11;
                    }
+               } else if (typelen == 15 &&
+                          !memcmp(type, "forwarded-tcpip", 15)) {
+                   struct ssh_rportfwd pf, *realpf;
+                   char *dummy;
+                   int dummylen;
+                   ssh2_pkt_getstring(&dummy, &dummylen);/* skip address */
+                   pf.sport = ssh2_pkt_getuint32();
+                   realpf = find234(ssh_rportfwds, &pf, NULL);
+                   if (realpf == NULL) {
+                       error = "Remote port is not recognised";
+                   } else {
+                       char *e = pfd_newconnect(&c->u.pfd.s, realpf->dhost,
+                                                realpf->dport, c);
+                       char buf[1024];
+                       sprintf(buf, "Received remote port open request for %s:%d",
+                               realpf->dhost, realpf->dport);
+                       logevent(buf);
+                       if (e != NULL) {
+                           sprintf(buf, "Port open failed: %s", e);
+                           logevent(buf);
+                           error = "Port open failed";
+                       } else {
+                           logevent("Forwarded port opened successfully");
+                           c->type = CHAN_SOCKDATA;
+                       }
+                   }
                } else if (typelen == 22 &&
                           !memcmp(type, "auth-agent@openssh.com", 3)) {
                    if (!ssh_agentfwd_enabled)
@@ -4123,7 +4556,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                    error = "Unsupported channel type requested";
                }
 
-               c->remoteid = ssh2_pkt_getuint32();
+               c->remoteid = remid;
                if (error) {
                    ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_FAILURE);
                    ssh2_pkt_adduint32(c->remoteid);
@@ -4135,8 +4568,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                } else {
                    c->localid = alloc_channel_id();
                    c->closes = 0;
-                   c->v2.remwindow = ssh2_pkt_getuint32();
-                   c->v2.remmaxpkt = ssh2_pkt_getuint32();
+                   c->v2.remwindow = winsize;
+                   c->v2.remmaxpkt = pktsize;
                    c->v2.outbuffer = NULL;
                    c->v2.outbuflen = c->v2.outbufsize = 0;
                    add234(ssh_channels, c);
@@ -4293,6 +4726,58 @@ static void ssh_special(Telnet_Special code)
     }
 }
 
+void *new_sock_channel(Socket s)
+{
+    struct ssh_channel *c;
+    c = smalloc(sizeof(struct ssh_channel));
+
+    if (c) {
+       c->remoteid = -1;              /* to be set when open confirmed */
+       c->localid = alloc_channel_id();
+       c->closes = 0;
+       c->type = CHAN_SOCKDATA_DORMANT;/* identify channel type */
+       c->u.pfd.s = s;
+       add234(ssh_channels, c);
+    }
+    return c;
+}
+
+void ssh_send_port_open(void *channel, char *hostname, int port, char *org)
+{
+    struct ssh_channel *c = (struct ssh_channel *)channel;
+    char buf[1024];
+
+    sprintf(buf, "Opening forwarded connection to %.512s:%d", hostname, port);
+    logevent(buf);
+
+    if (ssh_version == 1) {
+       send_packet(SSH1_MSG_PORT_OPEN,
+                   PKT_INT, c->localid,
+                   PKT_STR, hostname,
+                   PKT_INT, port,
+                   //PKT_STR, <org:orgport>,
+                   PKT_END);
+    } else {
+       ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
+       ssh2_pkt_addstring("direct-tcpip");
+       ssh2_pkt_adduint32(c->localid);
+       ssh2_pkt_adduint32(0x8000UL);      /* our window size */
+       ssh2_pkt_adduint32(0x4000UL);      /* our max pkt size */
+       ssh2_pkt_addstring(hostname);
+       ssh2_pkt_adduint32(port);
+       /*
+        * We make up values for the originator data; partly it's
+        * too much hassle to keep track, and partly I'm not
+        * convinced the server should be told details like that
+        * about my local network configuration.
+        */
+       ssh2_pkt_addstring("client-side-connection");
+       ssh2_pkt_adduint32(0);
+       ssh2_pkt_send();
+    }
+}
+
+
 static Socket ssh_socket(void)
 {
     return s;