SSH port forwarding is now configurable in mid-session. After doing
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Tue, 28 Dec 2004 14:07:05 +0000 (14:07 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Tue, 28 Dec 2004 14:07:05 +0000 (14:07 +0000)
Change Settings, the port forwarding setup function is run again,
and tags all existing port forwardings as `do not keep'. Then it
iterates through the config in the normal way; when it encounters a
port forwarding which is already in the tree, it tags it `keep'
rather than setting it up from scratch. Finally, it goes through the
tree and removes any that haven't been labelled `keep'. Hence,
editing the list of forwardings in Change Settings has the effect of
cancelling any forwardings you remove, and adding any new ones.

The SSH panel now appears in the reconfig box, and is empty apart
from a message explaining that it has to be there for subpanels of
it to exist. Better wording for this message would be welcome.

git-svn-id: svn://svn.tartarus.org/sgt/putty@5030 cda61777-01e9-0310-a592-d414129be87e

config.c
doc/config.but
portfwd.c
ssh.c
ssh.h

index 6511502..d57a6db 100644 (file)
--- a/config.c
+++ b/config.c
@@ -1516,10 +1516,11 @@ void setup_config_box(struct controlbox *b, struct sesslist *sesslist,
     }
 
     /*
-     * All the SSH stuff is omitted in PuTTYtel.
+     * All the SSH stuff is omitted in PuTTYtel, or in a reconfig
+     * when we're not doing SSH.
      */
 
-    if (!midsession && backends[3].name != NULL) {
+    if (backends[3].name != NULL && (!midsession || protocol == PROT_SSH)) {
 
        /*
         * The Connection/SSH panel.
@@ -1527,128 +1528,141 @@ void setup_config_box(struct controlbox *b, struct sesslist *sesslist,
        ctrl_settitle(b, "Connection/SSH",
                      "Options controlling SSH connections");
 
-       s = ctrl_getset(b, "Connection/SSH", "data",
-                       "Data to send to the server");
-       ctrl_editbox(s, "Remote command:", 'r', 100,
-                    HELPCTX(ssh_command),
-                    dlg_stdeditbox_handler, I(offsetof(Config,remote_cmd)),
-                    I(sizeof(((Config *)0)->remote_cmd)));
-
-       s = ctrl_getset(b, "Connection/SSH", "protocol", "Protocol options");
-       ctrl_checkbox(s, "Don't allocate a pseudo-terminal", 'p',
-                     HELPCTX(ssh_nopty),
-                     dlg_stdcheckbox_handler,
-                     I(offsetof(Config,nopty)));
-       ctrl_checkbox(s, "Don't start a shell or command at all", 'n',
-                     HELPCTX(ssh_noshell),
-                     dlg_stdcheckbox_handler,
-                     I(offsetof(Config,ssh_no_shell)));
-       ctrl_checkbox(s, "Enable compression", 'e',
-                     HELPCTX(ssh_compress),
-                     dlg_stdcheckbox_handler,
-                     I(offsetof(Config,compression)));
-       ctrl_radiobuttons(s, "Preferred SSH protocol version:", NO_SHORTCUT, 4,
-                         HELPCTX(ssh_protocol),
-                         dlg_stdradiobutton_handler,
-                         I(offsetof(Config, sshprot)),
-                         "1 only", 'l', I(0),
-                         "1", '1', I(1),
-                         "2", '2', I(2),
-                         "2 only", 'y', I(3), NULL);
-
-       s = ctrl_getset(b, "Connection/SSH", "encryption", "Encryption options");
-       c = ctrl_draglist(s, "Encryption cipher selection policy:", 's',
+       if (midsession) {
+           s = ctrl_getset(b, "Connection/SSH", "disclaimer", NULL);
+           ctrl_text(s, "Nothing on this panel may be reconfigured in mid-"
+                     "session; it is only here so that sub-panels of it can "
+                     "exist without looking strange.", HELPCTX(no_help));
+       }
+
+       if (!midsession) {
+
+           s = ctrl_getset(b, "Connection/SSH", "data",
+                           "Data to send to the server");
+           ctrl_editbox(s, "Remote command:", 'r', 100,
+                        HELPCTX(ssh_command),
+                        dlg_stdeditbox_handler, I(offsetof(Config,remote_cmd)),
+                        I(sizeof(((Config *)0)->remote_cmd)));
+
+           s = ctrl_getset(b, "Connection/SSH", "protocol", "Protocol options");
+           ctrl_checkbox(s, "Don't allocate a pseudo-terminal", 'p',
+                         HELPCTX(ssh_nopty),
+                         dlg_stdcheckbox_handler,
+                         I(offsetof(Config,nopty)));
+           ctrl_checkbox(s, "Don't start a shell or command at all", 'n',
+                         HELPCTX(ssh_noshell),
+                         dlg_stdcheckbox_handler,
+                         I(offsetof(Config,ssh_no_shell)));
+           ctrl_checkbox(s, "Enable compression", 'e',
+                         HELPCTX(ssh_compress),
+                         dlg_stdcheckbox_handler,
+                         I(offsetof(Config,compression)));
+           ctrl_radiobuttons(s, "Preferred SSH protocol version:", NO_SHORTCUT, 4,
+                             HELPCTX(ssh_protocol),
+                             dlg_stdradiobutton_handler,
+                             I(offsetof(Config, sshprot)),
+                             "1 only", 'l', I(0),
+                             "1", '1', I(1),
+                             "2", '2', I(2),
+                             "2 only", 'y', I(3), NULL);
+
+           s = ctrl_getset(b, "Connection/SSH", "encryption", "Encryption options");
+           c = ctrl_draglist(s, "Encryption cipher selection policy:", 's',
+                             HELPCTX(ssh_ciphers),
+                             cipherlist_handler, P(NULL));
+           c->listbox.height = 6;
+
+           ctrl_checkbox(s, "Enable legacy use of single-DES in SSH 2", 'i',
                          HELPCTX(ssh_ciphers),
-                         cipherlist_handler, P(NULL));
-       c->listbox.height = 6;
-       
-       ctrl_checkbox(s, "Enable legacy use of single-DES in SSH 2", 'i',
-                     HELPCTX(ssh_ciphers),
-                     dlg_stdcheckbox_handler,
-                     I(offsetof(Config,ssh2_des_cbc)));
+                         dlg_stdcheckbox_handler,
+                         I(offsetof(Config,ssh2_des_cbc)));
 
-       /*
-        * The Connection/SSH/Kex panel.
-        */
-       ctrl_settitle(b, "Connection/SSH/Kex",
-                     "Options controlling SSH key exchange");
-
-       s = ctrl_getset(b, "Connection/SSH/Kex", "main", 
-                       "Key exchange algorithm options");
-       c = ctrl_draglist(s, "Algorithm selection policy", 's',
-                         HELPCTX(ssh_kexlist),
-                         kexlist_handler, P(NULL));
-       c->listbox.height = 5;
-
-       s = ctrl_getset(b, "Connection/SSH/Kex", "repeat",
-                       "Options controlling key re-exchange");
-
-       /* FIXME: these could usefully be configured mid-session in SSH-2.
-        *        (So could cipher/compression/kex, now we have rekey.) */
-       ctrl_editbox(s, "Max minutes before rekey (0 for no limit)", 't', 20,
-                    HELPCTX(ssh_kex_repeat),
-                    dlg_stdeditbox_handler,
-                    I(offsetof(Config,ssh_rekey_time)),
-                    I(-1));
-       ctrl_editbox(s, "Max data before rekey (0 for no limit)", 'd', 20,
-                    HELPCTX(ssh_kex_repeat),
-                    dlg_stdeditbox_handler,
-                    I(offsetof(Config,ssh_rekey_data)),
-                    I(16));
-       ctrl_text(s, "(Use 1M for 1 megabyte, 1G for 1 gigabyte etc)",
-                 HELPCTX(ssh_kex_repeat));
+           /*
+            * The Connection/SSH/Kex panel.
+            */
+           ctrl_settitle(b, "Connection/SSH/Kex",
+                         "Options controlling SSH key exchange");
+
+           s = ctrl_getset(b, "Connection/SSH/Kex", "main",
+                           "Key exchange algorithm options");
+           c = ctrl_draglist(s, "Algorithm selection policy", 's',
+                             HELPCTX(ssh_kexlist),
+                             kexlist_handler, P(NULL));
+           c->listbox.height = 5;
+
+           s = ctrl_getset(b, "Connection/SSH/Kex", "repeat",
+                           "Options controlling key re-exchange");
+
+           /* FIXME: these could usefully be configured mid-session in SSH-2.
+            *        (So could cipher/compression/kex, now we have rekey.) */
+           ctrl_editbox(s, "Max minutes before rekey (0 for no limit)", 't', 20,
+                        HELPCTX(ssh_kex_repeat),
+                        dlg_stdeditbox_handler,
+                        I(offsetof(Config,ssh_rekey_time)),
+                        I(-1));
+           ctrl_editbox(s, "Max data before rekey (0 for no limit)", 'd', 20,
+                        HELPCTX(ssh_kex_repeat),
+                        dlg_stdeditbox_handler,
+                        I(offsetof(Config,ssh_rekey_data)),
+                        I(16));
+           ctrl_text(s, "(Use 1M for 1 megabyte, 1G for 1 gigabyte etc)",
+                     HELPCTX(ssh_kex_repeat));
 
-       /*
-        * The Connection/SSH/Auth panel.
-        */
-       ctrl_settitle(b, "Connection/SSH/Auth",
-                     "Options controlling SSH authentication");
+           /*
+            * The Connection/SSH/Auth panel.
+            */
+           ctrl_settitle(b, "Connection/SSH/Auth",
+                         "Options controlling SSH authentication");
 
-       s = ctrl_getset(b, "Connection/SSH/Auth", "methods",
-                       "Authentication methods");
-       ctrl_checkbox(s, "Attempt TIS or CryptoCard auth (SSH1)", 'm',
-                     HELPCTX(ssh_auth_tis),
-                     dlg_stdcheckbox_handler,
-                     I(offsetof(Config,try_tis_auth)));
-       ctrl_checkbox(s, "Attempt \"keyboard-interactive\" auth (SSH2)",
-                     'i', HELPCTX(ssh_auth_ki),
-                     dlg_stdcheckbox_handler,
-                     I(offsetof(Config,try_ki_auth)));
-
-       s = ctrl_getset(b, "Connection/SSH/Auth", "params",
-                       "Authentication parameters");
-       ctrl_checkbox(s, "Allow agent forwarding", 'f',
-                     HELPCTX(ssh_auth_agentfwd),
-                     dlg_stdcheckbox_handler, I(offsetof(Config,agentfwd)));
-       ctrl_checkbox(s, "Allow attempted changes of username in SSH2", 'u',
-                     HELPCTX(ssh_auth_changeuser),
-                     dlg_stdcheckbox_handler,
-                     I(offsetof(Config,change_username)));
-       ctrl_filesel(s, "Private key file for authentication:", 'k',
-                    FILTER_KEY_FILES, FALSE, "Select private key file",
-                    HELPCTX(ssh_auth_privkey),
-                    dlg_stdfilesel_handler, I(offsetof(Config, keyfile)));
+           s = ctrl_getset(b, "Connection/SSH/Auth", "methods",
+                           "Authentication methods");
+           ctrl_checkbox(s, "Attempt TIS or CryptoCard auth (SSH1)", 'm',
+                         HELPCTX(ssh_auth_tis),
+                         dlg_stdcheckbox_handler,
+                         I(offsetof(Config,try_tis_auth)));
+           ctrl_checkbox(s, "Attempt \"keyboard-interactive\" auth (SSH2)",
+                         'i', HELPCTX(ssh_auth_ki),
+                         dlg_stdcheckbox_handler,
+                         I(offsetof(Config,try_ki_auth)));
+
+           s = ctrl_getset(b, "Connection/SSH/Auth", "params",
+                           "Authentication parameters");
+           ctrl_checkbox(s, "Allow agent forwarding", 'f',
+                         HELPCTX(ssh_auth_agentfwd),
+                         dlg_stdcheckbox_handler, I(offsetof(Config,agentfwd)));
+           ctrl_checkbox(s, "Allow attempted changes of username in SSH2", 'u',
+                         HELPCTX(ssh_auth_changeuser),
+                         dlg_stdcheckbox_handler,
+                         I(offsetof(Config,change_username)));
+           ctrl_filesel(s, "Private key file for authentication:", 'k',
+                        FILTER_KEY_FILES, FALSE, "Select private key file",
+                        HELPCTX(ssh_auth_privkey),
+                        dlg_stdfilesel_handler, I(offsetof(Config, keyfile)));
+       }
 
        /*
-        * The Connection/SSH/Tunnels panel.
+        * The Connection/SSH/Tunnels panel. Some of this _is_
+        * still available in mid-session.
         */
        ctrl_settitle(b, "Connection/SSH/Tunnels",
                      "Options controlling SSH tunnelling");
 
-       s = ctrl_getset(b, "Connection/SSH/Tunnels", "x11", "X11 forwarding");
-       ctrl_checkbox(s, "Enable X11 forwarding", 'e',
-                     HELPCTX(ssh_tunnels_x11),
-                     dlg_stdcheckbox_handler,I(offsetof(Config,x11_forward)));
-       ctrl_editbox(s, "X display location", 'x', 50,
-                    HELPCTX(ssh_tunnels_x11),
-                    dlg_stdeditbox_handler, I(offsetof(Config,x11_display)),
-                    I(sizeof(((Config *)0)->x11_display)));
-       ctrl_radiobuttons(s, "Remote X11 authentication protocol", 'u', 2,
-                         HELPCTX(ssh_tunnels_x11auth),
-                         dlg_stdradiobutton_handler,
-                         I(offsetof(Config, x11_auth)),
-                         "MIT-Magic-Cookie-1", I(X11_MIT),
-                         "XDM-Authorization-1", I(X11_XDM), NULL);
+       if (!midsession) {
+           s = ctrl_getset(b, "Connection/SSH/Tunnels", "x11", "X11 forwarding");
+           ctrl_checkbox(s, "Enable X11 forwarding", 'e',
+                         HELPCTX(ssh_tunnels_x11),
+                         dlg_stdcheckbox_handler,I(offsetof(Config,x11_forward)));
+           ctrl_editbox(s, "X display location", 'x', 50,
+                        HELPCTX(ssh_tunnels_x11),
+                        dlg_stdeditbox_handler, I(offsetof(Config,x11_display)),
+                        I(sizeof(((Config *)0)->x11_display)));
+           ctrl_radiobuttons(s, "Remote X11 authentication protocol", 'u', 2,
+                             HELPCTX(ssh_tunnels_x11auth),
+                             dlg_stdradiobutton_handler,
+                             I(offsetof(Config, x11_auth)),
+                             "MIT-Magic-Cookie-1", I(X11_MIT),
+                             "XDM-Authorization-1", I(X11_XDM), NULL);
+       }
 
        s = ctrl_getset(b, "Connection/SSH/Tunnels", "portfwd",
                        "Port forwarding");
@@ -1706,34 +1720,36 @@ void setup_config_box(struct controlbox *b, struct sesslist *sesslist,
        ctrl_tabdelay(s, pfd->addbutton);
        ctrl_columns(s, 1, 100);
 
-       /*
-        * The Connection/SSH/Bugs panel.
-        */
-       ctrl_settitle(b, "Connection/SSH/Bugs",
-                     "Workarounds for SSH server bugs");
-
-       s = ctrl_getset(b, "Connection/SSH/Bugs", "main",
-                       "Detection of known bugs in SSH servers");
-       ctrl_droplist(s, "Chokes on SSH1 ignore messages", 'i', 20,
-                     HELPCTX(ssh_bugs_ignore1),
-                     sshbug_handler, I(offsetof(Config,sshbug_ignore1)));
-       ctrl_droplist(s, "Refuses all SSH1 password camouflage", 's', 20,
-                     HELPCTX(ssh_bugs_plainpw1),
-                     sshbug_handler, I(offsetof(Config,sshbug_plainpw1)));
-       ctrl_droplist(s, "Chokes on SSH1 RSA authentication", 'r', 20,
-                     HELPCTX(ssh_bugs_rsa1),
-                     sshbug_handler, I(offsetof(Config,sshbug_rsa1)));
-       ctrl_droplist(s, "Miscomputes SSH2 HMAC keys", 'm', 20,
-                     HELPCTX(ssh_bugs_hmac2),
-                     sshbug_handler, I(offsetof(Config,sshbug_hmac2)));
-       ctrl_droplist(s, "Miscomputes SSH2 encryption keys", 'e', 20,
-                     HELPCTX(ssh_bugs_derivekey2),
-                     sshbug_handler, I(offsetof(Config,sshbug_derivekey2)));
-       ctrl_droplist(s, "Requires padding on SSH2 RSA signatures", 'p', 20,
-                     HELPCTX(ssh_bugs_rsapad2),
-                     sshbug_handler, I(offsetof(Config,sshbug_rsapad2)));
-       ctrl_droplist(s, "Misuses the session ID in PK auth", 'n', 20,
-                     HELPCTX(ssh_bugs_pksessid2),
-                     sshbug_handler, I(offsetof(Config,sshbug_pksessid2)));
+       if (!midsession) {
+           /*
+            * The Connection/SSH/Bugs panel.
+            */
+           ctrl_settitle(b, "Connection/SSH/Bugs",
+                         "Workarounds for SSH server bugs");
+
+           s = ctrl_getset(b, "Connection/SSH/Bugs", "main",
+                           "Detection of known bugs in SSH servers");
+           ctrl_droplist(s, "Chokes on SSH1 ignore messages", 'i', 20,
+                         HELPCTX(ssh_bugs_ignore1),
+                         sshbug_handler, I(offsetof(Config,sshbug_ignore1)));
+           ctrl_droplist(s, "Refuses all SSH1 password camouflage", 's', 20,
+                         HELPCTX(ssh_bugs_plainpw1),
+                         sshbug_handler, I(offsetof(Config,sshbug_plainpw1)));
+           ctrl_droplist(s, "Chokes on SSH1 RSA authentication", 'r', 20,
+                         HELPCTX(ssh_bugs_rsa1),
+                         sshbug_handler, I(offsetof(Config,sshbug_rsa1)));
+           ctrl_droplist(s, "Miscomputes SSH2 HMAC keys", 'm', 20,
+                         HELPCTX(ssh_bugs_hmac2),
+                         sshbug_handler, I(offsetof(Config,sshbug_hmac2)));
+           ctrl_droplist(s, "Miscomputes SSH2 encryption keys", 'e', 20,
+                         HELPCTX(ssh_bugs_derivekey2),
+                         sshbug_handler, I(offsetof(Config,sshbug_derivekey2)));
+           ctrl_droplist(s, "Requires padding on SSH2 RSA signatures", 'p', 20,
+                         HELPCTX(ssh_bugs_rsapad2),
+                         sshbug_handler, I(offsetof(Config,sshbug_rsapad2)));
+           ctrl_droplist(s, "Misuses the session ID in PK auth", 'n', 20,
+                         HELPCTX(ssh_bugs_pksessid2),
+                         sshbug_handler, I(offsetof(Config,sshbug_pksessid2)));
+       }
     }
 }
index f125817..ed6600c 100644 (file)
@@ -2399,6 +2399,26 @@ address to listen on, by specifying (for instance) \c{127.0.0.5:79}.
 See \k{using-port-forwarding} for more information on how this
 works and its restrictions.
 
+You can modify the currently active set of port forwardings in
+mid-session using \q{Change Settings}. If you delete a local or
+dynamic port forwarding in mid-session, PuTTY will stop listening
+for connections on that port, so it can be re-used by another
+program. If you delete a remote port forwarding, note that:
+
+\b The SSHv1 protocol contains no mechanism for asking the server to
+stop listening on a remote port.
+
+\b The SSHv2 protocol does contain such a mechanism, but not all SSH
+servers support it. (In particular, OpenSSH does not support it in
+any version earlier than 3.9.)
+
+If you ask to delete a remote port forwarding and PuTTY cannot make
+the server actually stop listening on the port, it will instead just
+start refusing incoming connections on that port. Therefore,
+although the port cannot be reused by another program, you can at
+least be reasonably sure that server-side programs can no longer
+access the service at your end of the port forwarding.
+
 \S{config-ssh-portfwd-localhost} Controlling the visibility of
 forwarded ports
 
index b95e5ac..e6934d4 100644 (file)
--- a/portfwd.c
+++ b/portfwd.c
@@ -462,7 +462,8 @@ static int pfd_accepting(Plug p, OSSocket sock)
  sets up a listener on the local machine on (srcaddr:)port
  */
 const char *pfd_addforward(char *desthost, int destport, char *srcaddr,
-                          int port, void *backhandle, const Config *cfg)
+                          int port, void *backhandle, const Config *cfg,
+                          void **sockdata)
 {
     static const struct plug_function_table fn_table = {
        pfd_closing,
@@ -501,6 +502,8 @@ const char *pfd_addforward(char *desthost, int destport, char *srcaddr,
 
     sk_set_private_ptr(s, pr);
 
+    *sockdata = (void *)s;
+
     return NULL;
 }
 
@@ -519,6 +522,14 @@ void pfd_close(Socket s)
     sk_close(s);
 }
 
+/*
+ * Terminate a listener.
+ */
+void pfd_terminate(void *sv)
+{
+    pfd_close((Socket)sv);
+}
+
 void pfd_unthrottle(Socket s)
 {
     struct PFwdPrivate *pr;
diff --git a/ssh.c b/ssh.c
index dc58727..e619b59 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -497,11 +497,34 @@ struct ssh_channel {
  * Hence, in SSH 1 this structure is indexed by destination
  * host:port pair, whereas in SSH 2 it is indexed by source port.
  */
+struct ssh_portfwd; /* forward declaration */
+
 struct ssh_rportfwd {
     unsigned sport, dport;
     char dhost[256];
     char *sportdesc;
+    struct ssh_portfwd *pfrec;
 };
+#define free_rportfwd(pf) ( \
+    ((pf) ? (sfree((pf)->sportdesc)) : (void)0 ), sfree(pf) )
+
+/*
+ * Separately to the rportfwd tree (which is for looking up port
+ * open requests from the server), a tree of _these_ structures is
+ * used to keep track of all the currently open port forwardings,
+ * so that we can reconfigure in mid-session if the user requests
+ * it.
+ */
+struct ssh_portfwd {
+    int keep;
+    int type;
+    unsigned sport, dport;
+    char *saddr, *daddr;
+    struct ssh_rportfwd *remote;
+    void *local;
+};
+#define free_portfwd(pf) ( \
+    ((pf) ? (sfree((pf)->saddr), sfree((pf)->daddr)) : (void)0 ), sfree(pf) )
 
 struct Packet {
     long length;
@@ -616,7 +639,7 @@ struct ssh_tag {
     struct ssh_channel *mainchan;      /* primary session channel */
     int exitcode;
 
-    tree234 *rportfwds;
+    tree234 *rportfwds, *portfwds;
 
     enum {
        SSH_STATE_PREPACKET,
@@ -816,6 +839,47 @@ static int ssh_rportcmp_ssh2(void *av, void *bv)
     return 0;
 }
 
+/*
+ * Special form of strcmp which can cope with NULL inputs. NULL is
+ * defined to sort before even the empty string.
+ */
+static int nullstrcmp(const char *a, const char *b)
+{
+    if (a == NULL && b == NULL)
+       return 0;
+    if (a == NULL)
+       return -1;
+    if (b == NULL)
+       return +1;
+    return strcmp(a, b);
+}
+
+static int ssh_portcmp(void *av, void *bv)
+{
+    struct ssh_portfwd *a = (struct ssh_portfwd *) av;
+    struct ssh_portfwd *b = (struct ssh_portfwd *) bv;
+    int i;
+    if (a->type > b->type)
+       return +1;
+    if (a->type < b->type)
+       return -1;
+    if ( (i = nullstrcmp(a->saddr, b->saddr)) != 0)
+       return i < 0 ? -1 : +1;
+    if (a->sport > b->sport)
+       return +1;
+    if (a->sport < b->sport)
+       return -1;
+    if (a->type != 'D') {
+       if ( (i = nullstrcmp(a->daddr, b->daddr)) != 0)
+           return i < 0 ? -1 : +1;
+       if (a->dport > b->dport)
+           return +1;
+       if (a->dport < b->dport)
+           return -1;
+    }
+    return 0;
+}
+
 static int alloc_channel_id(Ssh ssh)
 {
     const unsigned CHANNEL_NUMBER_OFFSET = 256;
@@ -3596,8 +3660,7 @@ static void ssh_rportfwd_succfail(Ssh ssh, struct Packet *pktin, void *ctx)
 
        rpf = del234(ssh->rportfwds, pf);
        assert(rpf == pf);
-       sfree(pf->sportdesc);
-       sfree(pf);
+       free_rportfwd(pf);
     }
 }
 
@@ -3611,6 +3674,21 @@ static void ssh_setup_portfwd(Ssh ssh, const Config *cfg)
 
     portfwd_strptr = cfg->portfwd;
 
+    if (!ssh->portfwds) {
+       ssh->portfwds = newtree234(ssh_portcmp);
+    } else {
+       /*
+        * Go through the existing port forwardings and tag them
+        * with keep==FALSE. Any that we want to keep will be
+        * re-enabled as we go through the configuration and find
+        * out which bits are the same as they were before.
+        */
+       struct ssh_portfwd *epf;
+       int i;
+       for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++)
+           epf->keep = FALSE;
+    }
+
     while (*portfwd_strptr) {
        type = *portfwd_strptr++;
        saddr[0] = '\0';
@@ -3680,13 +3758,33 @@ static void ssh_setup_portfwd(Ssh ssh, const Config *cfg)
        }
        if (sport && dport) {
            /* Set up a description of the source port. */
-           static char *sportdesc;
+           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, ")");
-           if (type == 'L') {
+
+           pfrec = snew(struct ssh_portfwd);
+           pfrec->type = type;
+           pfrec->saddr = *saddr ? dupstr(saddr) : NULL;
+           pfrec->sport = sport;
+           pfrec->daddr = dupstr(host);
+           pfrec->dport = dport;
+           pfrec->local = NULL;
+           pfrec->remote = NULL;
+
+           epfrec = add234(ssh->portfwds, pfrec);
+           if (epfrec != pfrec) {
+               /*
+                * We already have a port forwarding with precisely
+                * these parameters. Hence, no need to do anything;
+                * simply tag the existing one as `keep'.
+                */
+               epfrec->keep = TRUE;
+               free_portfwd(pfrec);
+           } else if (type == 'L') {
                /* Verbose description of the destination port */
                char *dportdesc = dupprintf("%s:%.*s%.*s%d%.*s",
                                            host,
@@ -3694,7 +3792,8 @@ static void ssh_setup_portfwd(Ssh ssh, const Config *cfg)
                                            dserv, "(", dport, dserv, ")");
                const char *err = pfd_addforward(host, dport,
                                                 *saddr ? saddr : NULL,
-                                                sport, ssh, &ssh->cfg);
+                                                sport, ssh, &ssh->cfg,
+                                                &pfrec->local);
                if (err) {
                    logeventf(ssh, "Local port %s forward to %s"
                              " failed: %s", sportdesc, dportdesc, err);
@@ -3706,7 +3805,8 @@ static void ssh_setup_portfwd(Ssh ssh, const Config *cfg)
            } else if (type == 'D') {
                const char *err = pfd_addforward(NULL, -1,
                                                 *saddr ? saddr : NULL,
-                                                sport, ssh, &ssh->cfg);
+                                                sport, ssh, &ssh->cfg,
+                                                &pfrec->local);
                if (err) {
                    logeventf(ssh, "Local port %s SOCKS dynamic forward"
                              " setup failed: %s", sportdesc, err);
@@ -3744,6 +3844,8 @@ static void ssh_setup_portfwd(Ssh ssh, const Config *cfg)
 
                    pf->sportdesc = sportdesc;
                    sportdesc = NULL;
+                   pfrec->remote = pf;
+                   pf->pfrec = pfrec;
 
                    if (ssh->version == 1) {
                        send_packet(ssh, SSH1_CMSG_PORT_FORWARD_REQUEST,
@@ -3778,6 +3880,78 @@ static void ssh_setup_portfwd(Ssh ssh, const Config *cfg)
            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)
@@ -7281,17 +7455,12 @@ static void ssh_free(void *handle)
 
 /*
  * Reconfigure the SSH backend.
- * 
- * Currently, this function does nothing very useful. In future,
- * however, we could do some handy things with it. For example, we
- * could make the port forwarding configurer active in the Change
- * Settings box, and this routine could close down existing
- * forwardings and open up new ones in response to changes.
  */
 static void ssh_reconfig(void *handle, Config *cfg)
 {
     Ssh ssh = (Ssh) handle;
     pinger_reconfig(ssh->pinger, &ssh->cfg, cfg);
+    ssh_setup_portfwd(ssh, cfg);
     ssh->cfg = *cfg;                  /* STRUCTURE COPY */
 }
 
diff --git a/ssh.h b/ssh.h
index 2685090..cd0f883 100644 (file)
--- a/ssh.h
+++ b/ssh.h
@@ -274,8 +274,9 @@ extern const char *pfd_newconnect(Socket * s, char *hostname, int port,
 /* desthost == NULL indicates dynamic (SOCKS) port forwarding */
 extern const char *pfd_addforward(char *desthost, int destport, char *srcaddr,
                                  int port, void *backhandle,
-                                 const Config *cfg);
+                                 const Config *cfg, void **sockdata);
 extern void pfd_close(Socket s);
+extern void pfd_terminate(void *sockdata);
 extern int pfd_send(Socket s, char *data, int len);
 extern void pfd_confirm(Socket s);
 extern void pfd_unthrottle(Socket s);