Been meaning to do this for years: introduce a configuration option
[u/mdw/putty] / config.c
index 39da6c2..4f01c16 100644 (file)
--- a/config.c
+++ b/config.c
 
 #define PRINTER_DISABLED_STRING "None (printing disabled)"
 
-static void protocolbuttons_handler(union control *ctrl, void *dlg,
+#define HOST_BOX_TITLE "Host Name (or IP address)"
+#define PORT_BOX_TITLE "Port"
+
+static void config_host_handler(union control *ctrl, void *dlg,
+                               void *data, int event)
+{
+    Config *cfg = (Config *)data;
+
+    /*
+     * This function works just like the standard edit box handler,
+     * only it has to choose the control's label and text from two
+     * different places depending on the protocol.
+     */
+    if (event == EVENT_REFRESH) {
+       if (cfg->protocol == PROT_SERIAL) {
+           /*
+            * This label text is carefully chosen to contain an n,
+            * since that's the shortcut for the host name control.
+            */
+           dlg_label_change(ctrl, dlg, "Serial line");
+           dlg_editbox_set(ctrl, dlg, cfg->serline);
+       } else {
+           dlg_label_change(ctrl, dlg, HOST_BOX_TITLE);
+           dlg_editbox_set(ctrl, dlg, cfg->host);
+       }
+    } else if (event == EVENT_VALCHANGE) {
+       if (cfg->protocol == PROT_SERIAL)
+           dlg_editbox_get(ctrl, dlg, cfg->serline, lenof(cfg->serline));
+       else
+           dlg_editbox_get(ctrl, dlg, cfg->host, lenof(cfg->host));
+    }
+}
+
+static void config_port_handler(union control *ctrl, void *dlg,
+                               void *data, int event)
+{
+    Config *cfg = (Config *)data;
+    char buf[80];
+
+    /*
+     * This function works just like the standard edit box handler,
+     * only it has to choose the control's label and text from two
+     * different places depending on the protocol.
+     */
+    if (event == EVENT_REFRESH) {
+       if (cfg->protocol == PROT_SERIAL) {
+           /*
+            * This label text is carefully chosen to contain a p,
+            * since that's the shortcut for the port control.
+            */
+           dlg_label_change(ctrl, dlg, "Speed");
+           sprintf(buf, "%d", cfg->serspeed);
+       } else {
+           dlg_label_change(ctrl, dlg, PORT_BOX_TITLE);
+           sprintf(buf, "%d", cfg->port);
+       }
+       dlg_editbox_set(ctrl, dlg, buf);
+    } else if (event == EVENT_VALCHANGE) {
+       dlg_editbox_get(ctrl, dlg, buf, lenof(buf));
+       if (cfg->protocol == PROT_SERIAL)
+           cfg->serspeed = atoi(buf);
+       else
+           cfg->port = atoi(buf);
+    }
+}
+
+struct hostport {
+    union control *host, *port;
+};
+
+/*
+ * We export this function so that platform-specific config
+ * routines can use it to conveniently identify the protocol radio
+ * buttons in order to add to them.
+ */
+void config_protocolbuttons_handler(union control *ctrl, void *dlg,
                                    void *data, int event)
 {
-    int button, defport;
+    int button;
     Config *cfg = (Config *)data;
+    struct hostport *hp = (struct hostport *)ctrl->radio.context.p;
+
     /*
      * This function works just like the standard radio-button
      * handler, except that it also has to change the setting of
-     * the port box. We expect the context parameter to point at
-     * the `union control' structure for the port box.
+     * the port box, and refresh both host and port boxes when. We
+     * expect the context parameter to point at a hostport
+     * structure giving the `union control's for both.
      */
     if (event == EVENT_REFRESH) {
        for (button = 0; button < ctrl->radio.nbuttons; button++)
@@ -36,17 +114,23 @@ static void protocolbuttons_handler(union control *ctrl, void *dlg,
        assert(button >= 0 && button < ctrl->radio.nbuttons);
        cfg->protocol = ctrl->radio.buttondata[button].i;
        if (oldproto != cfg->protocol) {
-           defport = -1;
-           switch (cfg->protocol) {
-             case PROT_SSH: defport = 22; break;
-             case PROT_TELNET: defport = 23; break;
-             case PROT_RLOGIN: defport = 513; break;
-           }
-           if (defport > 0 && cfg->port != defport) {
-               cfg->port = defport;
-               dlg_refresh((union control *)ctrl->radio.context.p, dlg);
-           }
+           Backend *ob = backend_from_proto(oldproto);
+           Backend *nb = backend_from_proto(cfg->protocol);
+           assert(ob);
+           assert(nb);
+           /* Iff the user hasn't changed the port from the protocol
+            * default (if any), update it with the new protocol's
+            * default.
+            * (XXX: this isn't perfect; a default can become permanent
+            * by going via the serial backend. However, it helps with
+            * the common case of tabbing through the controls in order
+            * and setting a non-default port.) */
+           if (cfg->port == ob->default_port &&
+               cfg->port > 0 && nb->default_port > 0)
+               cfg->port = nb->default_port;
        }
+       dlg_refresh(hp->host, dlg);
+       dlg_refresh(hp->port, dlg);
     }
 }
 
@@ -163,6 +247,7 @@ static void kexlist_handler(union control *ctrl, void *dlg,
            { "Diffie-Hellman group 1",         KEX_DHGROUP1 },
            { "Diffie-Hellman group 14",        KEX_DHGROUP14 },
            { "Diffie-Hellman group exchange",  KEX_DHGEX },
+           { "RSA-based key exchange",         KEX_RSA },
            { "-- warn below here --",          KEX_WARN }
        };
 
@@ -290,7 +375,7 @@ struct sessionsaver_data {
  */
 static int load_selected_session(struct sessionsaver_data *ssd,
                                 char *savedsession,
-                                void *dlg, Config *cfg)
+                                void *dlg, Config *cfg, int *maybe_launch)
 {
     int i = dlg_listbox_index(ssd->listbox, dlg);
     int isdef;
@@ -299,13 +384,17 @@ static int load_selected_session(struct sessionsaver_data *ssd,
        return 0;
     }
     isdef = !strcmp(ssd->sesslist.sessions[i], "Default Settings");
-    load_settings(ssd->sesslist.sessions[i], !isdef, cfg);
+    load_settings(ssd->sesslist.sessions[i], cfg);
     if (!isdef) {
        strncpy(savedsession, ssd->sesslist.sessions[i],
                SAVEDSESSION_LEN);
        savedsession[SAVEDSESSION_LEN-1] = '\0';
+       if (maybe_launch)
+           *maybe_launch = TRUE;
     } else {
        savedsession[0] = '\0';
+       if (maybe_launch)
+           *maybe_launch = FALSE;
     }
     dlg_refresh(NULL, dlg);
     /* Restore the selection, which might have been clobbered by
@@ -371,6 +460,7 @@ static void sessionsaver_handler(union control *ctrl, void *dlg,
            dlg_listbox_select(ssd->listbox, dlg, top);
        }
     } else if (event == EVENT_ACTION) {
+       int mbl = FALSE;
        if (!ssd->midsession &&
            (ctrl == ssd->listbox ||
             (ssd->loadbutton && ctrl == ssd->loadbutton))) {
@@ -381,8 +471,8 @@ static void sessionsaver_handler(union control *ctrl, void *dlg,
             * double-click on the list box _and_ that session
             * contains a hostname.
             */
-           if (load_selected_session(ssd, savedsession, dlg, cfg) &&
-               (ctrl == ssd->listbox && cfg->host[0])) {
+           if (load_selected_session(ssd, savedsession, dlg, cfg, &mbl) &&
+               (mbl && ctrl == ssd->listbox && cfg_launchable(cfg))) {
                dlg_end(dlg, 1);       /* it's all over, and succeeded */
            }
        } else if (ctrl == ssd->savebutton) {
@@ -403,7 +493,7 @@ static void sessionsaver_handler(union control *ctrl, void *dlg,
                }
            }
             {
-                char *errmsg = save_settings(savedsession, !isdef, cfg);
+                char *errmsg = save_settings(savedsession, cfg);
                 if (errmsg) {
                     dlg_error_msg(dlg, errmsg);
                     sfree(errmsg);
@@ -437,14 +527,17 @@ static void sessionsaver_handler(union control *ctrl, void *dlg,
             * there was a session selected in that which had a
             * valid host name in it, then load it and go.
             */
-           if (dlg_last_focused(ctrl, dlg) == ssd->listbox && !*cfg->host) {
+           if (dlg_last_focused(ctrl, dlg) == ssd->listbox &&
+               !cfg_launchable(cfg)) {
                Config cfg2;
-               if (!load_selected_session(ssd, savedsession, dlg, &cfg2)) {
+               int mbl = FALSE;
+               if (!load_selected_session(ssd, savedsession, dlg,
+                                          &cfg2, &mbl)) {
                    dlg_beep(dlg);
                    return;
                }
                /* If at this point we have a valid session, go! */
-               if (*cfg2.host) {
+               if (mbl && cfg_launchable(&cfg2)) {
                    *cfg = cfg2;       /* structure copy */
                    cfg->remote_cmd_ptr = NULL;
                    dlg_end(dlg, 1);
@@ -457,7 +550,7 @@ static void sessionsaver_handler(union control *ctrl, void *dlg,
             * Otherwise, do the normal thing: if we have a valid
             * session, get going.
             */
-           if (*cfg->host) {
+           if (cfg_launchable(cfg)) {
                dlg_end(dlg, 1);
            } else
                dlg_beep(dlg);
@@ -564,7 +657,9 @@ static void colour_handler(union control *ctrl, void *dlg,
            int i, cval;
 
            dlg_editbox_get(ctrl, dlg, buf, lenof(buf));
-           cval = atoi(buf) & 255;
+           cval = atoi(buf);
+           if (cval > 255) cval = 255;
+           if (cval < 0)   cval = 0;
 
            i = dlg_listbox_index(cd->listbox, dlg);
            if (i >= 0) {
@@ -696,7 +791,29 @@ static void ttymodes_handler(union control *ctrl, void *dlg,
            char *p = cfg->ttymodes;
            int i = 0, len = lenof(cfg->ttymodes);
            while (*p) {
+               int multisel = dlg_listbox_index(td->listbox, dlg) < 0;
                if (dlg_listbox_issel(td->listbox, dlg, i)) {
+                   if (!multisel) {
+                       /* Populate controls with entry we're about to
+                        * delete, for ease of editing.
+                        * (If multiple entries were selected, don't
+                        * touch the controls.) */
+                       char *val = strchr(p, '\t');
+                       if (val) {
+                           int ind = 0;
+                           val++;
+                           while (ttymodes[ind]) {
+                               if (strlen(ttymodes[ind]) == val-p-1 &&
+                                   !strncmp(ttymodes[ind], p, val-p-1))
+                                   break;
+                               ind++;
+                           }
+                           dlg_listbox_select(td->modelist, dlg, ind);
+                           dlg_radiobutton_set(td->valradio, dlg,
+                                               (*val == 'V'));
+                           dlg_editbox_set(td->valbox, dlg, val+1);
+                       }
+                   }
                    memmove(p, p+strlen(p)+1, len - (strlen(p)+1));
                    i++;
                    continue;
@@ -770,7 +887,7 @@ static void environ_handler(union control *ctrl, void *dlg,
            if (i < 0) {
                dlg_beep(dlg);
            } else {
-               char *p, *q;
+               char *p, *q, *str;
 
                dlg_listbox_del(ed->listbox, dlg, i);
                p = cfg->environmt;
@@ -785,8 +902,20 @@ static void environ_handler(union control *ctrl, void *dlg,
                q = p;
                if (!*p)
                    goto disaster;
-               while (*p)
-                   p++;
+               /* Populate controls with the entry we're about to delete
+                * for ease of editing */
+               str = p;
+               p = strchr(p, '\t');
+               if (!p)
+                   goto disaster;
+               *p = '\0';
+               dlg_editbox_set(ed->varbox, dlg, str);
+               p++;
+               str = p;
+               dlg_editbox_set(ed->valbox, dlg, str);
+               p = strchr(p, '\0');
+               if (!p)
+                   goto disaster;
                p++;
                while (*p) {
                    while (*p)
@@ -899,7 +1028,8 @@ static void portfwd_handler(union control *ctrl, void *dlg,
            if (i < 0)
                dlg_beep(dlg);
            else {
-               char *p, *q;
+               char *p, *q, *src, *dst;
+               char dir;
 
                dlg_listbox_del(pfd->listbox, dlg, i);
                p = cfg->portfwd;
@@ -914,8 +1044,44 @@ static void portfwd_handler(union control *ctrl, void *dlg,
                q = p;
                if (!*p)
                    goto disaster2;
-               while (*p)
+               /* Populate the controls with the entry we're about to
+                * delete, for ease of editing. */
+               {
+                   static const char *const afs = "A46";
+                   char *afp = strchr(afs, *p);
+#ifndef NO_IPV6
+                   int idx = afp ? afp-afs : 0;
+#endif
+                   if (afp)
+                       p++;
+#ifndef NO_IPV6
+                   dlg_radiobutton_set(pfd->addressfamily, dlg, idx);
+#endif
+               }
+               {
+                   static const char *const dirs = "LRD";
+                   dir = *p;
+                   dlg_radiobutton_set(pfd->direction, dlg,
+                                       strchr(dirs, dir) - dirs);
+               }
+               p++;
+               if (dir != 'D') {
+                   src = p;
+                   p = strchr(p, '\t');
+                   if (!p)
+                       goto disaster2;
+                   *p = '\0';
                    p++;
+                   dst = p;
+               } else {
+                   src = p;
+                   dst = "";
+               }
+               p = strchr(p, '\0');
+               if (!p)
+                   goto disaster2;
+               dlg_editbox_set(pfd->sourcebox, dlg, src);
+               dlg_editbox_set(pfd->destbox, dlg, dst);
                p++;
                while (*p) {
                    while (*p)
@@ -975,31 +1141,36 @@ void setup_config_box(struct controlbox *b, int midsession,
     sfree(str);
 
     if (!midsession) {
+       struct hostport *hp = (struct hostport *)
+           ctrl_alloc(b, sizeof(struct hostport));
+
        s = ctrl_getset(b, "Session", "hostport",
-                       "Specify your connection by host name or IP address");
+                       "Specify the destination you want to connect to");
        ctrl_columns(s, 2, 75, 25);
-       c = ctrl_editbox(s, "Host Name (or IP address)", 'n', 100,
+       c = ctrl_editbox(s, HOST_BOX_TITLE, 'n', 100,
                         HELPCTX(session_hostname),
-                        dlg_stdeditbox_handler, I(offsetof(Config,host)),
-                        I(sizeof(((Config *)0)->host)));
+                        config_host_handler, I(0), I(0));
        c->generic.column = 0;
-       c = ctrl_editbox(s, "Port", 'p', 100, HELPCTX(session_hostname),
-                        dlg_stdeditbox_handler,
-                        I(offsetof(Config,port)), I(-1));
+       hp->host = c;
+       c = ctrl_editbox(s, PORT_BOX_TITLE, 'p', 100,
+                        HELPCTX(session_hostname),
+                        config_port_handler, I(0), I(0));
        c->generic.column = 1;
+       hp->port = c;
        ctrl_columns(s, 1, 100);
-       if (backends[3].name == NULL) {
-           ctrl_radiobuttons(s, "Protocol:", NO_SHORTCUT, 3,
+
+       if (!backend_from_proto(PROT_SSH)) {
+           ctrl_radiobuttons(s, "Connection type:", NO_SHORTCUT, 3,
                              HELPCTX(session_hostname),
-                             protocolbuttons_handler, P(c),
+                             config_protocolbuttons_handler, P(hp),
                              "Raw", 'r', I(PROT_RAW),
                              "Telnet", 't', I(PROT_TELNET),
                              "Rlogin", 'i', I(PROT_RLOGIN),
                              NULL);
        } else {
-           ctrl_radiobuttons(s, "Protocol:", NO_SHORTCUT, 4,
+           ctrl_radiobuttons(s, "Connection type:", NO_SHORTCUT, 4,
                              HELPCTX(session_hostname),
-                             protocolbuttons_handler, P(c),
+                             config_protocolbuttons_handler, P(hp),
                              "Raw", 'r', I(PROT_RAW),
                              "Telnet", 't', I(PROT_TELNET),
                              "Rlogin", 'i', I(PROT_RLOGIN),
@@ -1077,20 +1248,24 @@ void setup_config_box(struct controlbox *b, int midsession,
      * logging can sensibly be available.
      */
     {
-       char *sshlogname;
+       char *sshlogname, *sshrawlogname;
        if ((midsession && protocol == PROT_SSH) ||
-           (!midsession && backends[3].name != NULL))
-           sshlogname = "Log SSH packet data";
-       else
-           sshlogname = NULL;         /* this will disable the button */
-       ctrl_radiobuttons(s, "Session logging:", NO_SHORTCUT, 1,
+           (!midsession && backend_from_proto(PROT_SSH))) {
+           sshlogname = "SSH packets";
+           sshrawlogname = "SSH packets and raw data";
+        } else {
+           sshlogname = NULL;         /* this will disable both buttons */
+           sshrawlogname = NULL;      /* this will just placate optimisers */
+        }
+       ctrl_radiobuttons(s, "Session logging:", NO_SHORTCUT, 2,
                          HELPCTX(logging_main),
                          loggingbuttons_handler,
                          I(offsetof(Config, logtype)),
-                         "Logging turned off completely", 't', I(LGTYP_NONE),
-                         "Log printable output only", 'p', I(LGTYP_ASCII),
-                         "Log all session output", 'l', I(LGTYP_DEBUG),
+                         "None", 't', I(LGTYP_NONE),
+                         "Printable output", 'p', I(LGTYP_ASCII),
+                         "All session output", 'l', I(LGTYP_DEBUG),
                          sshlogname, 's', I(LGTYP_PACKETS),
+                         sshrawlogname, 'r', I(LGTYP_SSHRAW),
                          NULL);
     }
     ctrl_filesel(s, "Log file name:", 'f',
@@ -1111,7 +1286,7 @@ void setup_config_box(struct controlbox *b, int midsession,
                 dlg_stdcheckbox_handler, I(offsetof(Config,logflush)));
 
     if ((midsession && protocol == PROT_SSH) ||
-       (!midsession && backends[3].name != NULL)) {
+       (!midsession && backend_from_proto(PROT_SSH))) {
        s = ctrl_getset(b, "Session/Logging", "ssh",
                        "Options specific to SSH packet logging");
        ctrl_checkbox(s, "Omit known password fields", 'k',
@@ -1137,6 +1312,9 @@ void setup_config_box(struct controlbox *b, int midsession,
     ctrl_checkbox(s, "Implicit CR in every LF", 'r',
                  HELPCTX(terminal_lfhascr),
                  dlg_stdcheckbox_handler, I(offsetof(Config,lfhascr)));
+    ctrl_checkbox(s, "Implicit LF in every CR", 'f',
+                 HELPCTX(terminal_crhaslf),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,crhaslf)));
     ctrl_checkbox(s, "Use background colour to erase screen", 'e',
                  HELPCTX(terminal_bce),
                  dlg_stdcheckbox_handler, I(offsetof(Config,bce)));
@@ -1265,9 +1443,13 @@ void setup_config_box(struct controlbox *b, int midsession,
                  HELPCTX(features_retitle),
                  dlg_stdcheckbox_handler,
                  I(offsetof(Config,no_remote_wintitle)));
-    ctrl_checkbox(s, "Disable remote window title querying (SECURITY)",
-                 'q', HELPCTX(features_qtitle), dlg_stdcheckbox_handler,
-                 I(offsetof(Config,no_remote_qtitle)));
+    ctrl_radiobuttons(s, "Response to remote title query (SECURITY):", 'q', 3,
+                     HELPCTX(features_qtitle),
+                     dlg_stdradiobutton_handler,
+                     I(offsetof(Config,remote_qtitle_action)),
+                     "None", I(TITLE_NONE),
+                     "Empty string", I(TITLE_EMPTY),
+                     "Window title", I(TITLE_REAL), NULL);
     ctrl_checkbox(s, "Disable destructive backspace on server sending ^?",'b',
                  HELPCTX(features_dbackspace),
                  dlg_stdcheckbox_handler, I(offsetof(Config,no_dbackspace)));
@@ -1290,13 +1472,13 @@ void setup_config_box(struct controlbox *b, int midsession,
 
     s = ctrl_getset(b, "Window", "size", "Set the size of the window");
     ctrl_columns(s, 2, 50, 50);
-    c = ctrl_editbox(s, "Rows", 'r', 100,
-                    HELPCTX(window_size),
-                    dlg_stdeditbox_handler, I(offsetof(Config,height)),I(-1));
-    c->generic.column = 0;
     c = ctrl_editbox(s, "Columns", 'm', 100,
                     HELPCTX(window_size),
                     dlg_stdeditbox_handler, I(offsetof(Config,width)), I(-1));
+    c->generic.column = 0;
+    c = ctrl_editbox(s, "Rows", 'r', 100,
+                    HELPCTX(window_size),
+                    dlg_stdeditbox_handler, I(offsetof(Config,height)),I(-1));
     c->generic.column = 1;
     ctrl_columns(s, 1, 100);
 
@@ -1539,6 +1721,14 @@ void setup_config_box(struct controlbox *b, int midsession,
                          "IPv6", '6', I(ADDRTYPE_IPV6),
                          NULL);
 #endif
+
+           s = ctrl_getset(b, "Connection", "identity",
+                           "Logical name of remote host");
+           ctrl_editbox(s, "Logical name of remote host (e.g. for SSH key lookup):",
+                        'm', 100,
+                        HELPCTX(connection_loghost),
+                        dlg_stdeditbox_handler, I(offsetof(Config,loghost)),
+                        I(sizeof(((Config *)0)->loghost)));
        }
 
        /*
@@ -1726,7 +1916,7 @@ void setup_config_box(struct controlbox *b, int midsession,
      * when we're not doing SSH.
      */
 
-    if (backends[3].name != NULL && (!midsession || protocol == PROT_SSH)) {
+    if (backend_from_proto(PROT_SSH) && (!midsession || protocol == PROT_SSH)) {
 
        /*
         * The Connection/SSH panel.
@@ -1841,6 +2031,10 @@ void setup_config_box(struct controlbox *b, int midsession,
 
            s = ctrl_getset(b, "Connection/SSH/Auth", "methods",
                            "Authentication methods");
+           ctrl_checkbox(s, "Attempt authentication using Pageant", 'p',
+                         HELPCTX(ssh_auth_pageant),
+                         dlg_stdcheckbox_handler,
+                         I(offsetof(Config,tryagent)));
            ctrl_checkbox(s, "Attempt TIS or CryptoCard auth (SSH-1)", 'm',
                          HELPCTX(ssh_auth_tis),
                          dlg_stdcheckbox_handler,
@@ -2059,6 +2253,9 @@ void setup_config_box(struct controlbox *b, int midsession,
            ctrl_droplist(s, "Handles SSH-2 key re-exchange badly", 'k', 20,
                          HELPCTX(ssh_bugs_rekey2),
                          sshbug_handler, I(offsetof(Config,sshbug_rekey2)));
+           ctrl_droplist(s, "Ignores SSH-2 maximum packet size", 'x', 20,
+                         HELPCTX(ssh_bugs_maxpkt2),
+                         sshbug_handler, I(offsetof(Config,sshbug_maxpkt2)));
        }
     }
 }