]> asedeno.scripts.mit.edu Git - PuTTY.git/commitdiff
Merge branch 'master' of ssh://tartarus.org/putty
authorOwen Dunn <owen@greenend.org.uk>
Sun, 3 Apr 2016 14:09:59 +0000 (15:09 +0100)
committerOwen Dunn <owen@greenend.org.uk>
Sun, 3 Apr 2016 14:09:59 +0000 (15:09 +0100)
87 files changed:
.gitignore
Buildscr
README
Recipe
cgtest.c [new file with mode: 0644]
cmdgen.c
config.c
configure.ac
contrib/kh2reg.py
doc/config.but
doc/errors.but
doc/faq.but
doc/feedback.but
doc/gs.but
doc/index.but
doc/man-pag.but
doc/man-pg.but
doc/man-pl.but
doc/man-pscp.but
doc/man-psft.but
doc/man-ptel.but
doc/man-pter.but
doc/man-putt.but
doc/pageant.but
doc/pgpkeys.but
doc/pubkey.but
doc/using.but
icons/Makefile
icons/macicon.py
icons/mkicon.py
macosx/README.OSX [deleted file]
macosx/info.plist [deleted file]
macosx/osx.h [deleted file]
macosx/osxclass.h [deleted file]
macosx/osxctrls.m [deleted file]
macosx/osxdlg.m [deleted file]
macosx/osxmain.m [deleted file]
macosx/osxsel.m [deleted file]
macosx/osxwin.m [deleted file]
mkfiles.pl
pinger.c
pscp.c
psftp.c
psftp.h
putty.h
puttyps.h
settings.c
ssh.c
ssh.h
sshecc.c
testdata/bignumtests.txt [new file with mode: 0644]
testdata/vt100.txt
timing.c
unix/gtkapp.c [new file with mode: 0644]
unix/gtkcomm.c [new file with mode: 0644]
unix/gtkdlg.c
unix/gtkfont.c
unix/gtkmain.c [new file with mode: 0644]
unix/gtkmisc.c
unix/gtkwin.c
unix/osxlaunch.c [new file with mode: 0644]
unix/pterm.bundle [new file with mode: 0644]
unix/pterm.plist [new file with mode: 0644]
unix/putty.bundle [new file with mode: 0644]
unix/putty.plist [new file with mode: 0644]
unix/unix.h
unix/uxcons.c
unix/uxgen.c
unix/uxpgnt.c
unix/uxpterm.c
unix/uxpty.c
unix/uxputty.c
unix/uxsftp.c
unix/x11misc.c [new file with mode: 0644]
unix/x11misc.h [new file with mode: 0644]
windows/README-msi.txt [new file with mode: 0644]
windows/installer.wxs
windows/wincons.c
windows/windlg.c
windows/window.c
windows/winhelp.h
windows/winjump.c
windows/winpgen.c
windows/winpgnt.c
windows/winplink.c
windows/winsftp.c
windows/winshare.c

index 482a02f1723ecb492426eef7b9aa53563d6aae09..e9ffff305fbcf46887744dfc9767464e759eee23 100644 (file)
@@ -2,6 +2,7 @@
 *.pyc
 .dirstamp
 .deps
+.DS_Store
 /*.pdb
 /*.ilk
 /*.res
 /puttytel
 /puttygen
 /pterm
+/puttyapp
+/ptermapp
+/osxlaunch
+/unix/PuTTY.app
+/unix/Pterm.app
 /fuzzterm
 /testbn
+/cgtest
 /*.DSA
 /*.RSA
 /*.cnt
 /doc/*.hhk
 /doc/licence.but
 /doc/copy.but
+/icons/*.pam
 /icons/*.png
 /icons/*.ico
 /icons/*.icns
 /icons/*.xpm
 /icons/*.c
-/macosx/Makefile
-/macosx/*.app
-/macosx/puttygen
-/macosx/plink
-/macosx/psftp
-/macosx/pscp
 /testdata/bignum.txt
 /unix/Makefile.gtk
 /unix/Makefile.ux
 /windows/.bmake
 /windows/*.sln
 /windows/*.suo
+/windows/*.msi
+/windows/*.wixobj
+/windows/*.wixpdb
index 6e2bbc3b70481c21559063abe40455fde828492c..e530ad123046a37ac4d3e8bde6ce6f7456b55745 100644 (file)
--- a/Buildscr
+++ b/Buildscr
@@ -171,7 +171,7 @@ delegate windows
   in putty/doc with htmlhelp do/win hhc putty.hhp & type putty.chm >nul
 
   # Build the WiX MSI installer.
-  in putty/windows with wix do/win candle -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" installer.wxs && light -ext WixUIExtension -sval installer.wixobj
+  in putty/windows with wix do/win candle -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj
 
   # Build the old Inno Setup installer.
   in putty/windows with innosetup do/win iscc putty.iss
diff --git a/README b/README
index 61f40b5f90fcebef36fba46fda9961b369571c7d..10bd626156df6a2bfbd58db339d463d893a4b66d 100644 (file)
--- a/README
+++ b/README
@@ -17,19 +17,13 @@ For building on Windows:
    systems. Change into the `windows' subdirectory and type `nmake
    -f Makefile.vc' to build all the PuTTY binaries.
 
-   Last time we checked, PuTTY built with vanilla VC7, or VC6 with
-   an up-to-date Platform SDK. (It might still be possible to build
-   with vanilla VC6, but you'll certainly have to remove some
-   functionality with directives such as NO_IPV6.)
-
-   (We've also had reports of success building with the
-   OpenWatcom compiler -- www.openwatcom.org -- using Makefile.vc
-   with `wmake -ms -f makefile.vc' and NO_MULTIMON, although we
-   haven't tried this ourselves. Version 1.3 is reported to work.)
+   As of 2016, we successfully compiled PuTTY with both Visual Studio
+   7 (2003) and Visual Studio 14 (2015), so our guess is that it will
+   probably build with versions in between those as well.
 
  - Inside the windows/MSVC subdirectory are MS Visual Studio project
    files for doing GUI-based builds of the various PuTTY utilities.
-   These have been tested on Visual Studio 6.
+   These have been tested on Visual Studio 7 and 10.
 
    You should be able to build each PuTTY utility by loading the
    corresponding .dsp file in Visual Studio. For example,
@@ -72,10 +66,6 @@ For building on Unix:
    a normal Unix source archive but doesn't do so well at keeping the
    per-platform stuff in each platform's subdirectory; it's up to you.
 
-   Note that Unix PuTTY has mostly only been tested on Linux so far;
-   portability problems such as BSD-style ptys or different header file
-   requirements are expected.
-
  - unix/Makefile.gtk and unix/Makefile.ux are for non-autoconfigured
    builds. These makefiles expect you to change into the `unix'
    subdirectory, then run `make -f Makefile.gtk' or `make -f
@@ -83,14 +73,15 @@ For building on Unix:
    relies on Gtk, whereas Makefile.ux builds only the command-line
    utilities and has no Gtk dependence.
 
- - For the graphical utilities, Gtk+-1.2 and Gtk+-2.0 should both be
-   supported. If you have both installed, you can manually specify
-   which one you want by giving the option '--with-gtk=1' or
-   '--with-gtk=2' to the configure script. (2 is the default, of
-   course.) In the absence of either, the configure script will
-   automatically construct a Makefile which builds only the
-   command-line utilities; you can manually create this condition by
-   giving configure the option '--without-gtk'.
+ - For the graphical utilities, any of Gtk+-1.2, Gtk+-2.0, and Gtk+-3.0
+   should be supported. If you have more than one installed, you can
+   manually specify which one you want by giving the option
+   '--with-gtk=N' to the configure script where N is 1, 2, or 3.
+   (The default is the newest available, of course.) In the absence
+   of any Gtk version, the configure script will automatically
+   construct a Makefile which builds only the command-line utilities;
+   you can manually create this condition by giving configure the
+   option '--without-gtk'.
 
  - pterm would like to be setuid or setgid, as appropriate, to permit
    it to write records of user logins to /var/run/utmp and
diff --git a/Recipe b/Recipe
index a480e3cdc0ddecb80a462e5628c1a0889759b2b9..39ab078ae254dc63da0cb4819dae6f25d2213311 100644 (file)
--- a/Recipe
+++ b/Recipe
@@ -20,7 +20,6 @@
 !makefile gtk unix/Makefile.gtk
 !makefile unix unix/Makefile.ux
 !makefile am Makefile.am
-!makefile osx macosx/Makefile
 !makefile devcppproj windows/DEVCPP
 !makefile vstudio10 windows/VS2010
 !makefile vstudio12 windows/VS2012
@@ -28,7 +27,6 @@
 !srcdir charset/
 !srcdir windows/
 !srcdir unix/
-!srcdir macosx/
 
 # Help text added to the top of each Makefile, with /D converted
 # into -D as appropriate for the particular Makefile.
@@ -173,9 +171,6 @@ install:
 install-strip:
        $(MAKE) install INSTALL_PROGRAM="$(INSTALL_PROGRAM) -s"
 !end
-!begin osx vars
-CFLAGS += -DMACOSX
-!end
 
 # List the man pages for the automake makefile.
 !begin am
@@ -192,6 +187,18 @@ install-exec-local:
 endif
 !end
 
+# In automake makefile, build the OS X app bundle, if configured in
+# Quartz mode.
+!begin am
+if HAVE_QUARTZ
+noinst_SCRIPTS = unix/PuTTY.app unix/Pterm.app
+unix/PuTTY.app: unix/putty.bundle puttyapp osxlaunch
+       rm -rf $@ && gtk-mac-bundler $<
+unix/Pterm.app: unix/pterm.bundle ptermapp osxlaunch
+       rm -rf $@ && gtk-mac-bundler $<
+endif
+!end
+
 # Random symbols.
 !begin cygwin vars
 # _WIN32_IE is required to expose identifiers that only make sense on
@@ -218,7 +225,7 @@ GUITERM  = TERMINAL window windlg winctrls sizetip winucs winprint
 # Same thing on Unix.
 UXTERM   = TERMINAL uxcfg sercfg uxucs uxprint timing callback miscucs
 GTKTERM  = UXTERM gtkwin gtkcfg gtkdlg gtkfont gtkcols gtkmisc xkeysym
-OSXTERM  = UXTERM osxwin osxdlg osxctrls
+        + x11misc gtkcomm
 
 # Non-SSH back ends (putty, puttytel, plink).
 NONSSH   = telnet raw rlogin ldisc pinger
@@ -241,7 +248,6 @@ MISC     = timing callback misc version settings tree234 proxy conf be_misc
 WINMISC  = MISC winstore winnet winhandl cmdline windefs winmisc winproxy
          + wintime winhsock errsock winsecur
 UXMISC   = MISC uxstore uxsel uxnet uxpeer cmdline uxmisc uxproxy time
-OSXMISC  = MISC uxstore uxsel osxsel uxnet uxpeer uxmisc uxproxy time
 
 # import.c and dependencies, for PuTTYgen-like utilities that have to
 # load foreign key files.
@@ -290,25 +296,27 @@ puttygen : [G] winpgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version
          + sshrand winnoise sshsha winstore misc winctrls sshrsa sshdss winmisc
          + sshpubk sshaes sshsh256 sshsh512 IMPORT winutils puttygen.res
          + tree234 notiming winhelp winnojmp conf LIBS wintime sshecc
-         + sshecdsag
+         + sshecdsag winsecur
 
 pterm    : [X] GTKTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore
          + uxsignal CHARSET cmdline uxpterm version time xpmpterm xpmptcfg
-        + nogss
+        + nogss gtkmain
 putty    : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_ALL uxstore
          + uxsignal CHARSET uxputty NONSSH UXSSH UXMISC ux_x11 xpmputty
-         + xpmpucfg
+         + xpmpucfg gtkmain
 puttytel : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_NOSSH
         + uxstore uxsignal CHARSET uxputty NONSSH UXMISC xpmputty xpmpucfg
-        + nogss
+        + nogss gtkmain
 
 plink    : [U] uxplink uxcons NONSSH UXSSH U_BE_ALL logging UXMISC uxsignal
          + ux_x11 noterm
 
-puttygen : [U] cmdgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version
+PUTTYGEN_UNIX = sshrsag sshdssg sshprime sshdes sshbn sshmd5 version
          + sshrand uxnoise sshsha misc sshrsa sshdss uxcons uxstore uxmisc
          + sshpubk sshaes sshsh256 sshsh512 IMPORT puttygen.res time tree234
          + uxgen notiming conf sshecc sshecdsag
+puttygen : [U] cmdgen PUTTYGEN_UNIX
+cgtest   : [UT] cgtest PUTTYGEN_UNIX
 
 pscp     : [U] pscp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC
 psftp    : [U] psftp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC
@@ -318,8 +326,13 @@ pageant  : [X] uxpgnt uxagentc pageant sshrsa sshpubk sshdes sshbn sshmd5
         + conf uxsignal nocproxy nogss be_none x11fwd ux_x11 uxcons gtkask
         + gtkmisc UXMISC
 
-PuTTY    : [MX] osxmain OSXTERM OSXMISC CHARSET U_BE_ALL NONSSH UXSSH
-         + ux_x11 uxpty uxsignal testback putty.icns info.plist
+ptermapp : [XT] GTKTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore
+         + uxsignal CHARSET cmdline uxpterm version time xpmpterm xpmptcfg
+         + nogss gtkapp
+puttyapp : [XT] GTKTERM uxmisc misc ldisc settings uxsel U_BE_ALL uxstore
+         + uxsignal CHARSET uxputty NONSSH UXSSH UXMISC ux_x11 xpmputty
+         + xpmpucfg gtkapp
+osxlaunch : [UT] osxlaunch
 
 fuzzterm : [UT] UXTERM CHARSET misc uxmisc uxucs fuzzterm time settings
         + uxstore be_none
diff --git a/cgtest.c b/cgtest.c
new file mode 100644 (file)
index 0000000..095ce72
--- /dev/null
+++ b/cgtest.c
@@ -0,0 +1,6 @@
+/*
+ * cgtest.c: stub file to compile cmdgen.c in self-test mode
+ */
+
+#define TEST_CMDGEN
+#include "cmdgen.c"
index 12e1ac9e19e31822e5d16f8095e982c21f37990b..265e5adc1ac9e450927100a6da5a7a2bce7333d5 100644 (file)
--- a/cmdgen.c
+++ b/cmdgen.c
@@ -10,6 +10,8 @@
 #include <limits.h>
 #include <assert.h>
 #include <time.h>
+#include <errno.h>
+#include <string.h>
 
 #include "putty.h"
 #include "ssh.h"
@@ -17,7 +19,7 @@
 #ifdef TEST_CMDGEN
 /*
  * This section overrides some definitions below for test purposes.
- * When compiled with -DTEST_CMDGEN:
+ * When compiled with -DTEST_CMDGEN (as cgtest.c will do):
  * 
  *  - Calls to get_random_data() are replaced with the diagnostic
  *    function below (I #define the name so that I can still link
@@ -35,7 +37,7 @@
  *    run tests.
  */
 #define get_random_data get_random_data_diagnostic
-char *get_random_data(int len)
+char *get_random_data(int len, const char *device)
 {
     char *buf = snewn(len, char);
     memset(buf, 'x', len);
@@ -50,8 +52,7 @@ int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
     int ret = 1;
     for (i = 0; i < p->n_prompts; i++) {
        if (promptsgot < nprompts) {
-           assert(strlen(prompts[promptsgot]) < p->prompts[i]->result_len);
-           strcpy(p->prompts[i]->result, prompts[promptsgot++]);
+           p->prompts[i]->result = dupstr(prompts[promptsgot++]);
        } else {
            promptsgot++;           /* track number of requests anyway */
            ret = 0;
@@ -171,6 +172,12 @@ void help(void)
            "  -l    equivalent to `-O fingerprint'\n"
            "  -L    equivalent to `-O public-openssh'\n"
            "  -p    equivalent to `-O public'\n"
+           "  --old-passphrase file\n"
+           "        specify file containing old key passphrase\n"
+           "  --new-passphrase file\n"
+           "        specify file containing new key passphrase\n"
+           "  --random-device device\n"
+           "        specify device to read entropy from (e.g. /dev/urandom)\n"
            );
 }
 
@@ -193,6 +200,31 @@ static int move(char *from, char *to)
     return TRUE;
 }
 
+static char *readpassphrase(const char *filename)
+{
+    FILE *fp;
+    char *line;
+
+    fp = fopen(filename, "r");
+    if (!fp) {
+       fprintf(stderr, "puttygen: cannot open %s: %s\n",
+               filename, strerror(errno));
+       return NULL;
+    }
+    line = fgetline(fp);
+    if (line)
+       line[strcspn(line, "\r\n")] = '\0';
+    else if (ferror(fp))
+       fprintf(stderr, "puttygen: error reading from %s: %s\n",
+               filename, strerror(errno));
+    else       /* empty file */
+       line = dupstr("");
+    fclose(fp);
+    return line;
+}
+
+#define DEFAULT_RSADSA_BITS 2048
+
 int main(int argc, char **argv)
 {
     char *infile = NULL;
@@ -213,9 +245,10 @@ int main(int argc, char **argv)
     char *ssh2alg = NULL;
     const struct ssh_signkey *ssh2algf = NULL;
     int ssh2bloblen;
-    char *passphrase = NULL;
+    char *old_passphrase = NULL, *new_passphrase = NULL;
     int load_encrypted;
     progfn_t progressfn = is_interactive() ? progress_update : no_progress;
+    const char *random_device = NULL;
 
     /* ------------------------------------------------------------------
      * Parse the command line to figure out what we've been asked to do.
@@ -285,21 +318,41 @@ int main(int argc, char **argv)
                                 pgp_fingerprints();
                                 nogo = TRUE;
                             }
-                        }
-                       /*
-                        * For long options requiring an argument, add
-                        * code along the lines of
-                        * 
-                        * else if (!strcmp(opt, "-output")) {
-                        *     if (!val) {
-                        *         errs = TRUE;
-                         *         fprintf(stderr, "puttygen: option `-%s'"
-                         *                 " expects an argument\n", opt);
-                        *     } else
-                        *         ofile = val;
-                        * }
-                        */
-                       else {
+                       } else if (!strcmp(opt, "-old-passphrase")) {
+                           if (!val && argc > 1)
+                               --argc, val = *++argv;
+                           if (!val) {
+                               errs = TRUE;
+                               fprintf(stderr, "puttygen: option `-%s'"
+                                       " expects an argument\n", opt);
+                           } else {
+                               old_passphrase = readpassphrase(val);
+                               if (!old_passphrase)
+                                   errs = TRUE;
+                           }
+                       } else if (!strcmp(opt, "-new-passphrase")) {
+                           if (!val && argc > 1)
+                               --argc, val = *++argv;
+                           if (!val) {
+                               errs = TRUE;
+                               fprintf(stderr, "puttygen: option `-%s'"
+                                       " expects an argument\n", opt);
+                           } else {
+                               new_passphrase = readpassphrase(val);
+                               if (!new_passphrase)
+                                   errs = TRUE;
+                           }
+                       } else if (!strcmp(opt, "-random-device")) {
+                           if (!val && argc > 1)
+                               --argc, val = *++argv;
+                           if (!val) {
+                               errs = TRUE;
+                               fprintf(stderr, "puttygen: option `-%s'"
+                                       " expects an argument\n", opt);
+                           } else {
+                                random_device = val;
+                           }
+                       } else {
                            errs = TRUE;
                            fprintf(stderr,
                                    "puttygen: no such option `-%s'\n", opt);
@@ -449,7 +502,7 @@ int main(int argc, char **argv)
             bits = 256;
             break;
           default:
-            bits = 2048;
+            bits = DEFAULT_RSADSA_BITS;
             break;
         }
     }
@@ -464,6 +517,19 @@ int main(int argc, char **argv)
         errs = TRUE;
     }
 
+    if (keytype == RSA2 || keytype == RSA1 || keytype == DSA) {
+        if (bits < 256) {
+            fprintf(stderr, "puttygen: cannot generate %s keys shorter than"
+                    " 256 bits\n", (keytype == DSA ? "DSA" : "RSA"));
+            errs = TRUE;
+        } else if (bits < DEFAULT_RSADSA_BITS) {
+            fprintf(stderr, "puttygen: warning: %s keys shorter than"
+                    " %d bits are probably not secure\n",
+                    (keytype == DSA ? "DSA" : "RSA"), DEFAULT_RSADSA_BITS);
+            /* but this is just a warning, so proceed anyway */
+        }
+    }
+
     if (errs)
        return 1;
 
@@ -638,7 +704,7 @@ int main(int argc, char **argv)
            strftime(default_comment, 30, "rsa-key-%Y%m%d", &tm);
 
        random_ref();
-       entropy = get_random_data(bits / 8);
+       entropy = get_random_data(bits / 8, random_device);
        if (!entropy) {
            fprintf(stderr, "puttygen: failed to collect entropy, "
                    "could not generate key\n");
@@ -708,23 +774,25 @@ int main(int argc, char **argv)
         * If so, ask for a passphrase.
         */
        if (encrypted && load_encrypted) {
-           prompts_t *p = new_prompts(NULL);
-           int ret;
-           p->to_server = FALSE;
-           p->name = dupstr("SSH key passphrase");
-           add_prompt(p, dupstr("Enter passphrase to load key: "), FALSE);
-           ret = console_get_userpass_input(p, NULL, 0);
-           assert(ret >= 0);
-           if (!ret) {
-               free_prompts(p);
-               perror("puttygen: unable to read passphrase");
-               return 1;
-           } else {
-               passphrase = dupstr(p->prompts[0]->result);
-               free_prompts(p);
+           if (!old_passphrase) {
+               prompts_t *p = new_prompts(NULL);
+               int ret;
+               p->to_server = FALSE;
+               p->name = dupstr("SSH key passphrase");
+               add_prompt(p, dupstr("Enter passphrase to load key: "), FALSE);
+               ret = console_get_userpass_input(p, NULL, 0);
+               assert(ret >= 0);
+               if (!ret) {
+                   free_prompts(p);
+                   perror("puttygen: unable to read passphrase");
+                   return 1;
+               } else {
+                   old_passphrase = dupstr(p->prompts[0]->result);
+                   free_prompts(p);
+               }
            }
        } else {
-           passphrase = NULL;
+           old_passphrase = NULL;
        }
 
        switch (intype) {
@@ -763,7 +831,7 @@ int main(int argc, char **argv)
                ssh1key->q = NULL;
                ssh1key->iqmp = NULL;
            } else {
-               ret = loadrsakey(infilename, ssh1key, passphrase, &error);
+               ret = loadrsakey(infilename, ssh1key, old_passphrase, &error);
            }
            if (ret > 0)
                error = NULL;
@@ -788,7 +856,8 @@ int main(int argc, char **argv)
                 }
                 sfree(ssh2alg);
            } else {
-               ssh2key = ssh2_load_userkey(infilename, passphrase, &error);
+               ssh2key = ssh2_load_userkey(infilename, old_passphrase,
+                                           &error);
            }
            if ((ssh2key && ssh2key != SSH2_WRONG_PASSPHRASE) || ssh2blob)
                error = NULL;
@@ -803,7 +872,7 @@ int main(int argc, char **argv)
          case SSH_KEYTYPE_OPENSSH_PEM:
          case SSH_KEYTYPE_OPENSSH_NEW:
          case SSH_KEYTYPE_SSHCOM:
-           ssh2key = import_ssh2(infilename, intype, passphrase, &error);
+           ssh2key = import_ssh2(infilename, intype, old_passphrase, &error);
            if (ssh2key) {
                if (ssh2key != SSH2_WRONG_PASSPHRASE)
                    error = NULL;
@@ -839,11 +908,18 @@ int main(int argc, char **argv)
        }
     }
 
+    /*
+     * Unless we're changing the passphrase, the old one (if any) is a
+     * reasonable default.
+     */
+    if (!change_passphrase && old_passphrase && !new_passphrase)
+       new_passphrase = dupstr(old_passphrase);
+
     /*
      * Prompt for a new passphrase if we have been asked to, or if
      * we have just generated a key.
      */
-    if (change_passphrase || keytype != NOKEYGEN) {
+    if (!new_passphrase && (change_passphrase || keytype != NOKEYGEN)) {
        prompts_t *p = new_prompts(NULL);
        int ret;
 
@@ -863,18 +939,14 @@ int main(int argc, char **argv)
                fprintf(stderr, "puttygen: passphrases do not match\n");
                return 1;
            }
-           if (passphrase) {
-               smemclr(passphrase, strlen(passphrase));
-               sfree(passphrase);
-           }
-           passphrase = dupstr(p->prompts[0]->result);
+           new_passphrase = dupstr(p->prompts[0]->result);
            free_prompts(p);
-           if (!*passphrase) {
-               sfree(passphrase);
-               passphrase = NULL;
-           }
        }
     }
+    if (new_passphrase && !*new_passphrase) {
+       sfree(new_passphrase);
+       new_passphrase = NULL;
+    }
 
     /*
      * Write output.
@@ -895,14 +967,14 @@ int main(int argc, char **argv)
       case PRIVATE:
        if (sshver == 1) {
            assert(ssh1key);
-           ret = saversakey(outfilename, ssh1key, passphrase);
+           ret = saversakey(outfilename, ssh1key, new_passphrase);
            if (!ret) {
                fprintf(stderr, "puttygen: unable to save SSH-1 private key\n");
                return 1;
            }
        } else {
            assert(ssh2key);
-           ret = ssh2_save_userkey(outfilename, ssh2key, passphrase);
+           ret = ssh2_save_userkey(outfilename, ssh2key, new_passphrase);
            if (!ret) {
                fprintf(stderr, "puttygen: unable to save SSH-2 private key\n");
                return 1;
@@ -996,7 +1068,7 @@ int main(int argc, char **argv)
           default:
             assert(0 && "control flow goof");
         }
-       ret = export_ssh2(outfilename, real_outtype, ssh2key, passphrase);
+       ret = export_ssh2(outfilename, real_outtype, ssh2key, new_passphrase);
        if (!ret) {
            fprintf(stderr, "puttygen: unable to export key\n");
            return 1;
@@ -1008,9 +1080,13 @@ int main(int argc, char **argv)
        break;
     }
 
-    if (passphrase) {
-       smemclr(passphrase, strlen(passphrase));
-       sfree(passphrase);
+    if (old_passphrase) {
+       smemclr(old_passphrase, strlen(old_passphrase));
+       sfree(old_passphrase);
+    }
+    if (new_passphrase) {
+       smemclr(new_passphrase, strlen(new_passphrase));
+       sfree(new_passphrase);
     }
 
     if (ssh1key)
@@ -1131,7 +1207,7 @@ char *cleanup_fp(char *s)
     s += strspn(s, " \n\t");
     s += strcspn(s, " \n\t");
 
-    return dupprintf("%.*s", s - p, p);
+    return dupprintf("%.*s", (int)(s - p), p);
 }
 
 char *get_fp(char *filename)
index 53ad2f910e24a340a2e325a814a0296938be39c7..328f39e8a8450dbfbc3ef69ff589a448ab1fea47 100644 (file)
--- a/config.c
+++ b/config.c
@@ -466,6 +466,49 @@ static void kexlist_handler(union control *ctrl, void *dlg,
     }
 }
 
+static void hklist_handler(union control *ctrl, void *dlg,
+                            void *data, int event)
+{
+    Conf *conf = (Conf *)data;
+    if (event == EVENT_REFRESH) {
+        int i;
+
+        static const struct { const char *s; int k; } hks[] = {
+            { "Ed25519",               HK_ED25519 },
+            { "ECDSA",                 HK_ECDSA },
+            { "DSA",                   HK_DSA },
+            { "RSA",                   HK_RSA },
+            { "-- warn below here --", HK_WARN }
+        };
+
+        /* Set up the "host key preference" box. */
+        /* (hklist assumed to contain all algorithms) */
+        dlg_update_start(ctrl, dlg);
+        dlg_listbox_clear(ctrl, dlg);
+        for (i = 0; i < HK_MAX; i++) {
+            int k = conf_get_int_int(conf, CONF_ssh_hklist, i);
+            int j;
+            const char *kstr = NULL;
+            for (j = 0; j < lenof(hks); j++) {
+                if (hks[j].k == k) {
+                    kstr = hks[j].s;
+                    break;
+                }
+            }
+            dlg_listbox_addwithid(ctrl, dlg, kstr, k);
+        }
+        dlg_update_done(ctrl, dlg);
+
+    } else if (event == EVENT_VALCHANGE) {
+        int i;
+
+        /* Update array to match the list box. */
+        for (i=0; i < HK_MAX; i++)
+            conf_set_int_int(conf, CONF_ssh_hklist, i,
+                             dlg_listbox_getid(ctrl, dlg, i));
+    }
+}
+
 static void printerbox_handler(union control *ctrl, void *dlg,
                               void *data, int event)
 {
@@ -2205,14 +2248,12 @@ void setup_config_box(struct controlbox *b, int midsession,
        if (!midsession) {
            s = ctrl_getset(b, "Connection/SSH", "protocol", "Protocol options");
 
-           ctrl_radiobuttons(s, "Preferred SSH protocol version:", NO_SHORTCUT, 4,
+           ctrl_radiobuttons(s, "SSH protocol version:", NO_SHORTCUT, 2,
                              HELPCTX(ssh_protocol),
                              conf_radiobutton_handler,
                              I(CONF_sshprot),
-                             "1 only", 'l', I(0),
-                             "1", '1', I(1),
-                             "2", '2', I(2),
-                             "2 only", 'y', I(3), NULL);
+                             "2", '2', I(3),
+                             "1 (INSECURE)", '1', I(0), NULL);
        }
 
        /*
@@ -2249,13 +2290,28 @@ void setup_config_box(struct controlbox *b, int midsession,
                      HELPCTX(ssh_kex_repeat));
        }
 
+       /*
+        * The 'Connection/SSH/Host keys' panel.
+        */
+       if (protcfginfo != 1 && protcfginfo != -1) {
+           ctrl_settitle(b, "Connection/SSH/Host keys",
+                         "Options controlling SSH host keys");
+
+           s = ctrl_getset(b, "Connection/SSH/Host keys", "main",
+                           "Host key algorithm preference");
+           c = ctrl_draglist(s, "Algorithm selection policy:", 's',
+                             HELPCTX(ssh_hklist),
+                             hklist_handler, P(NULL));
+           c->listbox.height = 5;
+       }
+
        /*
         * Manual host key configuration is irrelevant mid-session,
         * as we enforce that the host key for rekeys is the
         * same as that used at the start of the session.
         */
        if (!midsession) {
-           s = ctrl_getset(b, "Connection/SSH/Kex", "hostkeys",
+           s = ctrl_getset(b, "Connection/SSH/Host keys", "hostkeys",
                            "Manually configure host keys for this connection");
 
             ctrl_columns(s, 2, 75, 25);
@@ -2321,14 +2377,14 @@ void setup_config_box(struct controlbox *b, int midsession,
                          "Options controlling SSH authentication");
 
            s = ctrl_getset(b, "Connection/SSH/Auth", "main", NULL);
-           ctrl_checkbox(s, "Bypass authentication entirely (SSH-2 only)", 'b',
-                         HELPCTX(ssh_auth_bypass),
-                         conf_checkbox_handler,
-                         I(CONF_ssh_no_userauth));
            ctrl_checkbox(s, "Display pre-authentication banner (SSH-2 only)",
                          'd', HELPCTX(ssh_auth_banner),
                          conf_checkbox_handler,
                          I(CONF_ssh_show_banner));
+           ctrl_checkbox(s, "Bypass authentication entirely (SSH-2 only)", 'b',
+                         HELPCTX(ssh_auth_bypass),
+                         conf_checkbox_handler,
+                         I(CONF_ssh_no_userauth));
 
            s = ctrl_getset(b, "Connection/SSH/Auth", "methods",
                            "Authentication methods");
index 82fae26c40d02b6fa644d29c69bc382ac73ae690..e951a286b2e884128d2b821b05ed612956052c21 100644 (file)
@@ -51,8 +51,11 @@ AC_ARG_WITH([gssapi],
 AC_ARG_WITH([quartz],
   [AS_HELP_STRING([--with-quartz],
                   [build for the MacOS Quartz GTK back end])],
-  [AC_DEFINE([OSX_GTK], [1], [Define if building with GTK for MacOS.])],
-  [])
+  [AC_DEFINE([OSX_GTK], [1], [Define if building with GTK for MacOS.])
+   with_quartz=yes],
+  [with_quartz=no])
+
+AM_CONDITIONAL([HAVE_QUARTZ],[test "x$with_quartz" = "xyes"])
 
 WITH_GSSAPI=
 AS_IF([test "x$with_gssapi" != xno],
index 7904d65eb702ec4d934712289e2375641da3e5d8..5f9463d0718256c87ee4a933616f5a9226571e3d 100755 (executable)
@@ -36,6 +36,11 @@ def strtolong(s):
     bytes = struct.unpack(">%luB" % len(s), s)
     return reduce ((lambda a, b: (long(a) << 8) + long(b)), bytes)
 
+def strtolong_le(s):
+    "Convert arbitrary-length little-endian binary data to a Python long"
+    bytes = reversed(struct.unpack(">%luB" % len(s), s))
+    return reduce ((lambda a, b: (long(a) << 8) + long(b)), bytes)
+
 def longtohex(n):
     """Convert long int to lower-case hex.
 
@@ -47,6 +52,11 @@ def longtohex(n):
     plain=string.lower(re.match(r"0x([0-9A-Fa-f]*)l?$", hex(n), re.I).group(1))
     return "0x" + plain
 
+def warn(s):
+    "Warning with file/line number"
+    sys.stderr.write("%s:%d: %s\n"
+                     % (fileinput.filename(), fileinput.filelineno(), s))
+
 output_type = 'windows'
 
 try:
@@ -68,8 +78,12 @@ class BlankInputLine(Exception):
     pass
 
 class UnknownKeyType(Exception):
-   def __init__(self, keytype):
-       self.keytype = keytype
+    def __init__(self, keytype):
+        self.keytype = keytype
+
+class KeyFormatError(Exception):
+    def __init__(self, msg):
+        self.msg = msg
 
 # Now process all known_hosts input.
 for line in fileinput.input(args):
@@ -87,7 +101,7 @@ for line in fileinput.input(args):
 
         # Common fields
         hostpat = fields[0]
-        magicnumbers = []   # placeholder
+        keyparams = []      # placeholder
         keytype = ""        # placeholder
 
         # Grotty heuristic to distinguish known_hosts from known_hosts2:
@@ -97,7 +111,7 @@ for line in fileinput.input(args):
             # Treat as SSH-1-type host key.
             # Format: hostpat bits10 exp10 mod10 comment...
             # (PuTTY doesn't store the number of bits.)
-            magicnumbers = map (long, fields[2:4])
+            keyparams = map (long, fields[2:4])
             keytype = "rsa"
 
         else:
@@ -118,25 +132,100 @@ for line in fileinput.input(args):
                 subfields.append(data)
                 blob = blob [struct.calcsize(sizefmt) + size : ]
 
-            # The first field is keytype again, and the rest we can treat as
-            # an opaque list of bignums (same numbers and order as stored
-            # by PuTTY). (currently embedded keytype is ignored entirely)
-            magicnumbers = map (strtolong, subfields[1:])
+            # The first field is keytype again.
+            if subfields[0] != sshkeytype:
+                raise KeyFormatError("""
+                    outer and embedded key types do not match: '%s', '%s'
+                    """ % (sshkeytype, subfields[1]))
+
+            # Translate key type string into something PuTTY can use, and
+            # munge the rest of the data.
+            if sshkeytype == "ssh-rsa":
+                keytype = "rsa2"
+                # The rest of the subfields we can treat as an opaque list
+                # of bignums (same numbers and order as stored by PuTTY).
+                keyparams = map (strtolong, subfields[1:])
+
+            elif sshkeytype == "ssh-dss":
+                keytype = "dss"
+                # Same again.
+                keyparams = map (strtolong, subfields[1:])
+
+            elif sshkeytype == "ecdsa-sha2-nistp256" \
+              or sshkeytype == "ecdsa-sha2-nistp384" \
+              or sshkeytype == "ecdsa-sha2-nistp521":
+                keytype = sshkeytype
+                # Have to parse this a bit.
+                if len(subfields) > 3:
+                    raise KeyFormatError("too many subfields in blob")
+                (curvename, Q) = subfields[1:]
+                # First is yet another copy of the key name.
+                if not re.match("ecdsa-sha2-" + re.escape(curvename),
+                                sshkeytype):
+                    raise KeyFormatError("key type mismatch ('%s' vs '%s')"
+                            % (sshkeytype, curvename))
+                # Second contains key material X and Y (hopefully).
+                # First a magic octet indicating point compression.
+                if struct.unpack("B", Q[0])[0] != 4:
+                    # No-one seems to use this.
+                    raise KeyFormatError("can't convert point-compressed ECDSA")
+                # Then two equal-length bignums (X and Y).
+                bnlen = len(Q)-1
+                if (bnlen % 1) != 0:
+                    raise KeyFormatError("odd-length X+Y")
+                bnlen = bnlen / 2
+                (x,y) = Q[1:bnlen+1], Q[bnlen+1:2*bnlen+1]
+                keyparams = [curvename] + map (strtolong, [x,y])
+
+            elif sshkeytype == "ssh-ed25519":
+                keytype = sshkeytype
+
+                if len(subfields) != 2:
+                    raise KeyFormatError("wrong number of subfields in blob")
+                if subfields[0] != sshkeytype:
+                    raise KeyFormatError("key type mismatch ('%s' vs '%s')"
+                            % (sshkeytype, subfields[0]))
+                # Key material y, with the top bit being repurposed as
+                # the expected parity of the associated x (point
+                # compression).
+                y = strtolong_le(subfields[1])
+                x_parity = y >> 255
+                y &= ~(1 << 255)
+
+                # Standard Ed25519 parameters.
+                p = 2**255 - 19
+                d = 0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3
+
+                # Recover x^2 = (y^2 - 1) / (d y^2 + 1).
+                #
+                # With no real time constraints here, it's easier to
+                # take the inverse of the denominator by raising it to
+                # the power p-2 (by Fermat's Little Theorem) than
+                # faffing about with the properly efficient Euclid
+                # method.
+                xx = (y*y - 1) * pow(d*y*y + 1, p-2, p) % p
+
+                # Take the square root, which may require trying twice.
+                x = pow(xx, (p+3)/8, p)
+                if pow(x, 2, p) != xx:
+                    x = x * pow(2, (p-1)/4, p) % p
+                    assert pow(x, 2, p) == xx
+
+                # Pick the square root of the correct parity.
+                if (x % 2) != x_parity:
+                    x = p - x
 
-            # Translate key type into something PuTTY can use.
-            if   sshkeytype == "ssh-rsa":   keytype = "rsa2"
-            elif sshkeytype == "ssh-dss":   keytype = "dss"
+                keyparams = [x, y]
             else:
                 raise UnknownKeyType(sshkeytype)
 
         # Now print out one line per host pattern, discarding wildcards.
         for host in string.split (hostpat, ','):
             if re.search (r"[*?!]", host):
-                sys.stderr.write("Skipping wildcard host pattern '%s'\n"
-                                 % host)
+                warn("skipping wildcard host pattern '%s'" % host)
                 continue
             elif re.match (r"\|", host):
-                sys.stderr.write("Skipping hashed hostname '%s'\n" % host)
+                warn("skipping hashed hostname '%s'" % host)
                 continue
             else:
                 m = re.match (r"\[([^]]*)\]:(\d*)$", host)
@@ -148,7 +237,11 @@ for line in fileinput.input(args):
                 # Slightly bizarre output key format: 'type@port:hostname'
                 # XXX: does PuTTY do anything useful with literal IP[v4]s?
                 key = keytype + ("@%d:%s" % (port, host))
-                value = string.join (map (longtohex, magicnumbers), ',')
+                # Most of these are numbers, but there's the occasional
+                # string that needs passing through
+                value = string.join (map (
+                    lambda x: x if isinstance(x, basestring) else longtohex(x),
+                    keyparams), ',')
                 if output_type == 'unix':
                     # Unix format.
                     sys.stdout.write('%s %s\n' % (key, value))
@@ -159,6 +252,8 @@ for line in fileinput.input(args):
                                      % (winmungestr(key), value))
 
     except UnknownKeyType, k:
-        sys.stderr.write("Unknown SSH key type '%s', skipping\n" % k.keytype)
+        warn("unknown SSH key type '%s', skipping" % k.keytype)
+    except KeyFormatError, k:
+        warn("trouble parsing key (%s), skipping" % k.msg)
     except BlankInputLine:
         pass
index 5107858c5f54d4f52c2b06927c21466738817108..94626ab0be64f65f954cd904b9f8bab53e9a4fe4 100644 (file)
@@ -1667,7 +1667,7 @@ Keepalives are only supported in Telnet and SSH; the Rlogin and Raw
 protocols offer no way of implementing them. (For an alternative, see
 \k{config-tcp-keepalives}.)
 
-Note that if you are using \i{SSH-1} and the server has a bug that makes
+Note that if you are using SSH-1 and the server has a bug that makes
 it unable to deal with SSH-1 ignore messages (see
 \k{config-ssh-bug-ignore1}), enabling keepalives will have no effect.
 
@@ -1747,7 +1747,7 @@ arbitrary port (say, \cw{localhost} port 10022) were forwarded to a
 second machine's SSH port (say, \cw{foovax} port 22), and then
 started a second PuTTY connecting to the forwarded port.
 
-In normal usage, the second PuTTY will access the host key cache
+In normal usage, the second PuTTY will access the \i{host key cache}
 under the host name and port it actually connected to (i.e.
 \cw{localhost} port 10022 in this example). Using the logical host
 name option, however, you can configure the second PuTTY to cache
@@ -1762,7 +1762,7 @@ you to reconfirm its host key. Conversely, if you expect to use the
 same local port number for port forwardings to lots of different
 servers, you probably didn't want any particular server's host key
 cached under that local port number. (For this latter case, you
-could also explicitly configure host keys in the relevant sessions;
+could instead explicitly configure host keys in the relevant sessions;
 see \k{config-ssh-kex-manual-hostkeys}.)
 
 If you just enter a host name for this option, PuTTY will cache the
@@ -2267,30 +2267,28 @@ client end. Likewise, data sent by PuTTY to the server is compressed
 first and the server decompresses it at the other end. This can help
 make the most of a low-\i{bandwidth} connection.
 
-\S{config-ssh-prot} \q{Preferred \i{SSH protocol version}}
+\S{config-ssh-prot} \q{\i{SSH protocol version}}
 
 \cfg{winhelp-topic}{ssh.protocol}
 
-This allows you to select whether you would prefer to use \i{SSH protocol
-version 1} or \I{SSH-2}version 2, and whether to permit falling back
-to the other version.
+This allows you to select whether to use \i{SSH protocol version 2}
+or the older \I{SSH-1}version 1.
 
-With the settings \q{1} and \q{2}, PuTTY will attempt to use protocol 1
-if the server you connect to does not offer protocol 2, and vice versa.
+You should normally leave this at the default of \q{2}. As well as
+having fewer features, the older SSH-1 protocol is no longer
+developed, has many known cryptographic weaknesses, and is generally
+not considered to be secure. PuTTY's protocol 1 implementation is
+provided mainly for compatibility, and is no longer being enhanced.
 
-If you select \q{1 only} or \q{2 only} here, PuTTY will only connect
-if the server you connect to offers the SSH protocol version you
-have specified.
+If a server offers both versions, prefer \q{2}. If you have some
+server or piece of equipment that only talks SSH-1, select \q{1}
+here, and do not treat the resulting connection as secure.
 
-You should normally leave this at the default, \q{2 only}. The older
-SSH-1 protocol is no longer developed, has many known cryptographic
-weaknesses, and is generally not considered to be secure. If you
-permit use of SSH-1 by selecting \q{2} instead of \q{2 only}, an
-active attacker can force downgrade to SSH-1 even if the server
-you're connecting to supports SSH-2.
-
-PuTTY's protocol 1 implementation is provided mainly for
-compatibility, and is no longer being enhanced.
+PuTTY will not automatically fall back to the other version of the
+protocol if the server turns out not to match your selection here;
+instead, it will put up an error message and abort the connection.
+This prevents an active attacker downgrading an intended SSH-2
+connection to SSH-1.
 
 \S{config-ssh-sharing} Sharing an SSH connection between PuTTY tools
 
@@ -2483,6 +2481,53 @@ when the SSH connection is idle, so they shouldn't cause the same
 problems.  The SSH-1 protocol, incidentally, has even weaker integrity
 protection than SSH-2 without rekeys.
 
+\H{config-ssh-hostkey} The Host Keys panel
+
+The Host Keys panel allows you to configure options related to SSH-2
+\i{host key management}.
+
+Host keys are used to prove the server's identity, and assure you that
+the server is not being spoofed (either by a man-in-the-middle attack
+or by completely replacing it on the network). See \k{gs-hostkey} for
+a basic introduction to host keys.
+
+This entire panel is only relevant to SSH protocol version 2; none of
+these settings affect SSH-1 at all.
+
+\S{config-ssh-hostkey-order} \ii{Host key type} selection
+
+\cfg{winhelp-topic}{ssh.hostkey.order}
+
+PuTTY supports a variety of SSH-2 host key types, and allows you to
+choose which one you prefer to use to identify the server.
+Configuration is similar to cipher selection (see
+\k{config-ssh-encryption}).
+
+PuTTY currently supports the following host key types:
+
+\b \q{Ed25519}: \i{Edwards-curve} \i{DSA} using a twisted Edwards
+curve with modulus \cw{2^255-19}.
+
+\b \q{ECDSA}: \i{elliptic curve} \i{DSA} using one of the
+NIST-standardised elliptic curves.
+
+\b \q{DSA}: straightforward \i{DSA} using modular exponentiation.
+
+\b \q{RSA}: the ordinary \i{RSA} algorithm.
+
+If PuTTY already has one or more host keys stored for the server,
+it will prefer to use one of those, even if the server has a key
+type that is higher in the preference order. You can add such a
+key to PuTTY's cache from within an existing session using the
+\q{Special Commands} menu; see \k{using-specials}.
+
+Otherwise, PuTTY will choose a key type based purely on the
+preference order you specify in the configuration.
+
+If the first key type PuTTY finds is below the \q{warn below here}
+line, you will see a warning box when you make the connection, similar
+to that for cipher selection (see \k{config-ssh-encryption}).
+
 \S{config-ssh-kex-manual-hostkeys} \ii{Manually configuring host keys}
 
 \cfg{winhelp-topic}{ssh.kex.manualhostkeys}
@@ -2531,8 +2576,8 @@ If this box contains at least one host key or fingerprint when PuTTY
 makes an SSH connection, then PuTTY's automated host key management is
 completely bypassed: the connection will be permitted if and only if
 the host key presented by the server is one of the keys listed in this
-box, and the host key store in the Registry will be neither read
-\e{nor written}.
+box, and the \I{host key cache}host key store in the Registry will be
+neither read \e{nor written}, unless you explicitly do so.
 
 If the box is empty (as it usually is), then PuTTY's automated host
 key management will work as normal.
@@ -2596,22 +2641,6 @@ recommended ciphers.
 The Auth panel allows you to configure \i{authentication} options for
 SSH sessions.
 
-\S{config-ssh-noauth} \q{Bypass authentication entirely}
-
-\cfg{winhelp-topic}{ssh.auth.bypass}
-
-In SSH-2, it is possible to establish a connection without using SSH's
-mechanisms to identify or authenticate oneself to the server. Some
-servers may prefer to handle authentication in the data channel, for
-instance, or may simply require no authentication whatsoever.
-
-By default, PuTTY assumes the server requires authentication (most
-do), and thus must provide a username. If you find you are getting
-unwanted username prompts, you could try checking this option.
-
-This option only affects SSH-2 connections. SSH-1 connections always
-require an authentication step.
-
 \S{config-ssh-banner} \q{Display pre-authentication banner}
 
 \cfg{winhelp-topic}{ssh.auth.banner}
@@ -2627,6 +2656,34 @@ prompting for a login name, due to the nature of the protocol design).
 By unchecking this option, display of the banner can be suppressed
 entirely.
 
+\S{config-ssh-noauth} \q{Bypass authentication entirely}
+
+\cfg{winhelp-topic}{ssh.auth.bypass}
+
+In SSH-2, it is in principle possible to establish a connection
+without using SSH's mechanisms to identify or prove who you are
+to the server. An SSH server could prefer to handle authentication
+in the data channel, for instance, or simply require no user
+authentication whatsoever.
+
+By default, PuTTY assumes the server requires authentication (we've
+never heard of one that doesn't), and thus must start this process
+with a username. If you find you are getting username prompts that
+you cannot answer, you could try enabling this option. However,
+most SSH servers will reject this.
+
+This is not the option you want if you have a username and just want
+PuTTY to remember it; for that see \k{config-username}.
+It's also probably not what if you're trying to set up passwordless
+login to a mainstream SSH server; depending on the server, you
+probably wanted public-key authentication (\k{pubkey})
+or perhaps GSSAPI authentication (\k{config-ssh-auth-gssapi}).
+(These are still forms of authentication, even if you don't have to
+interact with them.)
+
+This option only affects SSH-2 connections. SSH-1 connections always
+require an authentication step.
+
 \S{config-ssh-tryagent} \q{Attempt authentication using Pageant}
 
 \cfg{winhelp-topic}{ssh.auth.pageant}
@@ -2729,11 +2786,15 @@ private key in another format that you want to use with PuTTY, see
 \k{puttygen-conversions}.
 
 You can use the authentication agent \i{Pageant} so that you do not
-need to explicitly configure a key here; see \k{pageant}. If a file
-is specified here with Pageant running, PuTTY will first try asking
-Pageant to authenticate with that key, and ignore any other keys
-Pageant may have. If that fails, PuTTY will ask for a passphrase as
-normal.
+need to explicitly configure a key here; see \k{pageant}.
+
+If a private key file is specified here with Pageant running, PuTTY
+will first try asking Pageant to authenticate with that key, and
+ignore any other keys Pageant may have. If that fails, PuTTY will ask
+for a passphrase as normal. You can also specify a \e{public} key file
+in this case (in RFC 4716 or OpenSSH format), as that's sufficient to
+identify the key to Pageant, but of course if Pageant isn't present
+PuTTY can't fall back to using this file itself.
 
 \H{config-ssh-auth-gssapi} The \i{GSSAPI} panel
 
@@ -2744,7 +2805,7 @@ GSSAPI authentication. This is a mechanism which delegates the
 authentication exchange to a library elsewhere on the client
 machine, which in principle can authenticate in many different ways
 but in practice is usually used with the \i{Kerberos} \i{single sign-on}
-protocol.
+protocol to implement \i{passwordless login}.
 
 GSSAPI is only available in the SSH-2 protocol.
 
index bf9672d05a0c7d28a268aef0565dc1cba08c6e55..e221a2d912cce090c2518292fdf82dfd6027b9a3 100644 (file)
@@ -56,6 +56,25 @@ in the same way as you would if it was new.
 
 See \k{gs-hostkey} for more information on host keys.
 
+\H{errors-ssh-protocol} \q{SSH protocol version 2 required by our
+configuration but server only provides (old, insecure) SSH-1}
+
+By default, PuTTY only supports connecting to SSH servers that
+implement \i{SSH protocol version 2}. If you see this message, the
+server you're trying to connect to only supports the older SSH-1
+protocol.
+
+If the server genuinely only supports SSH-1, then you need to either
+change the \q{SSH protocol version} setting (see \k{config-ssh-prot}),
+or use the \c{-1} command-line option; in any case, you should not
+treat the resulting connection as secure.
+
+You might start seeing this message with new versions of PuTTY
+\#{XXX-REVIEW-BEFORE-RELEASE: (from 0.XX onwards)}
+where you didn't before, because it used to be possible to configure
+PuTTY to automatically fall back from SSH-2 to SSH-1. This is no
+longer supported, to prevent the possibility of a downgrade attack.
+
 \H{errors-cipher-warning} \q{The first cipher supported by the server is
 ... below the configured warning threshold}
 
index e2e2374207a62e23632c74de93cdaeaffa0c69a8..040d61e70acd4ce33aef6825afa5b957dc50a83f 100644 (file)
@@ -64,7 +64,11 @@ files into PuTTY's format.
 Yes. SSH-1 support has always been available in PuTTY.
 
 However, the SSH-1 protocol has many weaknesses and is no longer
-considered secure; it should be avoided if at all possible.
+considered secure; you should use SSH-2 instead if at all possible.
+
+\#{XXX-REVIEW-BEFORE-RELEASE:
+As of 0.68, PuTTY will no longer fall back to SSH-1 if the server
+doesn't appear to support SSH-2; you must explicitly ask for SSH-1. }
 
 \S{faq-localecho}{Question} Does PuTTY support \i{local echo}?
 
@@ -222,14 +226,14 @@ processors that are backward-compatible with that architecture.
 (We used to also provide executables for Windows for the Alpha
 processor, but stopped after 0.58 due to lack of interest.)
 
-In the development code, partial ports to the Mac OSes exist (see
+In the development code, a partial port to Mac OS exists (see
 \k{faq-mac-port}).
 
 Currently PuTTY does \e{not} run on Windows CE (see \k{faq-wince}).
 
 We do not have release-quality ports for any other systems at the
-present time. If anyone told you we had an EPOC port, or an iPaq port,
-or any other port of PuTTY, they were mistaken. We don't.
+present time. If anyone told you we had an Android port, or an iOS
+port, or any other port of PuTTY, they were mistaken. We don't.
 
 There are some third-party ports to various platforms, mentioned
 on the 
@@ -246,16 +250,12 @@ including the usual \c{configure}/\c{make}; see the file \c{README}
 in the source distribution. This should build you Unix
 ports of Plink, PuTTY itself, PuTTYgen, PSCP, PSFTP, and also
 \i\c{pterm} - an \cw{xterm}-type program which supports the same
-terminal emulation as PuTTY. We do not yet have a Unix port of
-Pageant.
+terminal emulation as PuTTY. \#{XXX-REVIEW-BEFORE-RELEASE:}
+We do not yet have a Unix port of Pageant.
 
 If you don't have \i{Gtk}, you should still be able to build the
 command-line tools.
 
-Note that Unix PuTTY has mostly only been tested on Linux so far;
-portability problems such as BSD-style ptys or different header file
-requirements are expected.
-
 \S{faq-unix-why}{Question} What's the point of the Unix port? Unix
 has OpenSSH.
 
@@ -301,23 +301,24 @@ you'll need the right kind of C compiler - modern versions of Visual
 C at least have stopped being backwards compatible to Win32s. Also,
 the last time we tried this it didn't work very well.
 
-If you're interested in running PuTTY under Windows 3.1, help and
-testing in this area would be very welcome!
-
 \S{faq-mac-port}{Question} Will there be a port to the \I{Mac OS}Mac?
 
-There are several answers to this question:
+We hope so!
 
-\b The Unix/Gtk port is already fully working under Mac OS X as an X11
-application.
+We attempted one around 2005, written as a native Cocoa application,
+but it turned out to be very slow to redraw its window for some reason
+we never got to the bottom of.
 
-\b A native (Cocoa) Mac OS X port has been started. It's just about
-usable, but is of nowhere near release quality yet, and is likely to
-behave in unexpected ways. Currently it's unlikely to be completed
-unless someone steps in to help.
+In 2015, after porting the GTK front end to work with GTK 3, we began
+another attempt based on making small changes to the GTK code and
+building it against the OS X Quartz version of GTK 3. This doesn't
+seem to have the window redrawing problem any more, so it's already
+got further than the last effort, but it is still substantially
+unfinished.
 
-\b A separate port to the classic Mac OS (pre-OSX) is also in
-progress; it too is not ready yet.
+If any OS X and/or GTK programming experts are keen to have a finished
+version of this, we urge them to help out with some of the remaining
+problems!
 
 \S{faq-epoc}{Question} Will there be a port to EPOC?
 
@@ -334,8 +335,7 @@ for various third-party ports.
 
 We have no plans to write such a port ourselves; none of us has an
 iPhone, and developing and publishing applications for it looks
-awkward and expensive. Such a port would probably depend upon the
-stalled Mac OS X port (see \k{faq-mac-port}).
+awkward and expensive.
 
 However, there is a third-party SSH client for the iPhone and
 iPod\_Touch called \W{http://www.instantcocoa.com/products/pTerm/}{pTerm},
@@ -1106,8 +1106,13 @@ The PuTTY policy changed because the developers were informed of
 ways to implement DSA which do not suffer nearly as badly from this
 weakness, and indeed which don't need to rely on random numbers at
 all. For this reason we now believe PuTTY's DSA implementation is
-probably OK. However, if you have the choice, we still recommend you
-use RSA instead.
+probably OK.
+
+The recently added elliptic-curve signature methods are also DSA-style
+algorithms, so they have this same weakness in principle. Our ECDSA
+implementation uses the same defence as DSA, while our Ed25519
+implementation uses the similar system (but different in details) that
+the Ed25519 spec mandates.
 
 \S{faq-virtuallock}{Question} Couldn't Pageant use
 \cw{VirtualLock()} to stop private keys being written to disk?
index d81d1fee82c60474001289604505c39f738b0428..e0854fc54311d930c41b2fb15c54a1d76009d16c 100644 (file)
@@ -50,6 +50,9 @@ the URL; that way, we don't have to download it unless we decide we
 actually need it, and only one of us needs to download it instead of
 it being automatically copied to all the developers.
 
+(If the file contains confidential information, then you could encrypt
+it with our Secure Contact Key; see \k{pgpkeys-pubkey} for details.)
+
 Some people like to send mail in MS Word format. Please \e{don't}
 send us bug reports, or any other mail, as a Word document. Word
 documents are roughly fifty times larger than writing the same
@@ -201,6 +204,20 @@ will explain what you need to know. \e{Then}, if you think the
 documentation could usefully have told you that, send us a bug
 report and explain how you think we should change it.
 
+\H{feedback-vulns} Reporting security vulnerabilities
+
+If you've found a security vulnerability in PuTTY, you might well want
+to notify us using an encrypted communications channel, to avoid
+disclosing information about the vulnerability before a fixed release
+is available.
+
+For this purpose, we provide a GPG key suitable for encryption: the
+Secure Contact Key. See \k{pgpkeys-pubkey} for details of this.
+
+(Of course, vulnerabilities are also bugs, so please do include as
+much information as possible about them, the same way you would with
+any other bug report.)
+
 \H{feedback-features} Requesting extra features 
 
 If you want to request a new feature in PuTTY, the very first things
index 5909c8a3eb404d9fe0c6df040bf6a58fbf8c48e6..56ab282a69155564bb9f6fa294e4e3538e38a6ae 100644 (file)
@@ -77,13 +77,13 @@ server and it sends you a different host key from the one you were
 expecting, PuTTY can warn you that the server may have been switched
 and that a spoofing attack might be in progress.
 
-PuTTY records the host key for each server you connect to, in the
-Windows \i{Registry}. Every time you connect to a server, it checks
-that the host key presented by the server is the same host key as it
-was the last time you connected. If it is not, you will see a
-warning, and you will have the chance to abandon your connection
-before you type any private information (such as a password) into
-it.
+PuTTY \I{host key cache}records the host key for each server you
+connect to, in the Windows \i{Registry}. Every time you connect to a
+server, it checks that the host key presented by the server is the
+same host key as it was the last time you connected. If it is not,
+you will see a warning, and you will have the chance to abandon your
+connection before you type any private information (such as a
+password) into it.
 
 However, when you connect to a server you have not connected to
 before, PuTTY has no way of telling whether the host key is the
@@ -97,11 +97,13 @@ network users are on the same side and spoofing attacks are
 unlikely, so you might choose to trust the key without checking it.
 If you are connecting across a hostile network (such as the
 Internet), you should check with your system administrator, perhaps
-by telephone or in person. (Some modern servers have more than one
+by telephone or in person. (Many servers have more than one
 host key. If the system administrator sends you more than one
 \I{host key fingerprint}fingerprint, you should make sure the one
 PuTTY shows you is on the list, but it doesn't matter which one it is.)
 
+See \k{config-ssh-hostkey} for advanced options for managing host keys.
+
 \# FIXME: this is all very fine but of course in practice the world
 doesn't work that way. Ask the team if they have any good ideas for
 changes to this section!
index 204430ce80396b6fad6855b99bb3da708b1e17ba..0375164b8a4ec8dd05bace41203b205c05ada48d 100644 (file)
@@ -59,7 +59,6 @@ from SSH and Telnet
 
 \IM{security hazard}{security risk} security hazard
 
-\IM{SSH-1}{SSH protocol version 1} SSH-1
 \IM{SSH-2}{SSH protocol version 2} SSH-2
 
 \IM{terminal window}{PuTTY window} terminal window
@@ -850,7 +849,9 @@ saved sessions from
 
 \IM{logical host name} logical host name
 \IM{logical host name} host name, logical
-\IM{logical host name} host key, caching policy
+
+\IM{host key cache}{host key management} host key management
+\IM{host key cache}{host key management} cache, of SSH host keys
 
 \IM{web browsers} web browser
 
index 15bc5bad1a4aac61ab3d294ab2abe3d6a993c7bc..098d33250925ae85823706b0c247a7c695202955 100644 (file)
@@ -4,7 +4,7 @@
 
 \S{pageant-manpage-name} NAME
 
-\cw{pageant} - SSH authentication agent for the PuTTY tools
+\cw{pageant} - PuTTY SSH authentication agent
 
 \S{pageant-manpage-synopsis} SYNOPSIS
 
@@ -37,6 +37,10 @@ that the server they are connecting to will accept.
 with an already-running agent to add or remove keys, list the keys, or
 extract their public half.
 
+The agent protocol used by \c{pageant} is compatible with the PuTTY
+tools and also with other implementations such as OpenSSH's SSH client
+and \e{ssh-agent(1)}.
+
 To run \c{pageant} as an agent, you must provide an option to tell it
 what its \e{lifetime} should be. Typically you would probably want
 Pageant to last for the duration of a login session, in which case you
@@ -71,9 +75,9 @@ extra command-line arguments, e.g.
 
 in which case Pageant will prompt for the keys' passphrases (if any)
 and start the agent with those keys already loaded. Passphrase prompts
-will use the controlling terminal if one is available, or the GUI if
-one of those is available. If neither is available, no passphrase
-prompting can be done.
+will use the controlling terminal if one is available, or failing that
+the GUI if one of those is available. If neither is available, no
+passphrase prompting can be done.
 
 To use Pageant to talk to an existing agent, you can add new keys
 using \cw{-a}, list the current set of keys' fingerprints and comments
@@ -155,7 +159,7 @@ before it manages to happen.
 \dt \cw{--debug}
 
 \dd Pageant will run in the foreground, without forking. It will print
-its enviroment variable setup commands on standard output, and then it
+its environment variable setup commands on standard output, and then it
 will log all agent activity to standard output as well. This is useful
 for debugging what Pageant itself is doing, or what another process is
 doing to it.
@@ -256,6 +260,14 @@ output.
 
 }
 
+\dt \cw{-s}, \cw{-c}
+
+\dd Force Pageant to output its environment setup commands in the
+style of POSIX / Bourne shells (\cw{-s}) or C shells (\cw{-c})
+respectively. If neither option is given, Pageant will guess based on
+whether the environment variable \cw{SHELL} has a value ending in
+\cq{csh}.
+
 \dt \cw{--help}
 
 \dd Print a brief summary of command-line options and terminate.
index 6ee37c9932ac80b491d87296a03146344adc5774..b6e4ef2c11bc1ea560a24491454b12d60998ae5e 100644 (file)
@@ -64,6 +64,13 @@ and \c{rsa1} (to generate SSH-1 keys).
 
 \dd Suppress the progress display when generating a new key.
 
+\dt \cw{\-\-old\-passphrase} \e{file}
+
+\dd Specify a file name; the first line will be read from this file
+(removing any trailing newline) and used as the old passphrase.
+\s{CAUTION:} If the passphrase is important, the file should be stored
+on a temporary filesystem or else securely erased after use.
+
 In the second phase, \c{puttygen} optionally alters properties of
 the key it has loaded or generated. The options to control this are:
 
@@ -156,6 +163,14 @@ fingerprint. Otherwise, the \c{\-o} option is required.
 
 \dd Synonym for \q{\cw{-O public}}.
 
+\dt \cw{\-\-new\-passphrase} \e{file}
+
+\dd Specify a file name; the first line will be read from this file
+(removing any trailing newline) and used as the new passphrase. If the
+file is empty then the saved key will be unencrypted. \s{CAUTION:} If
+the passphrase is important, the file should be stored on a temporary
+filesystem or else securely erased after use.
+
 The following options do not run PuTTYgen as normal, but print
 informational messages and then quit:
 
@@ -210,8 +225,3 @@ To add the OpenSSH-format public half of a key to your authorised
 keys file:
 
 \c puttygen -L mykey.ppk >> $HOME/.ssh/authorized_keys
-
-\S{puttygen-manpage-bugs} BUGS
-
-There's currently no way to supply passphrases in batch mode, or
-even just to specify that you don't want a passphrase at all.
index b597b83f923cb8370a11c7693b7ef6c6f1d993ef..bda8abd3d947ea97f4406c3805e0adc4ecab2781 100644 (file)
@@ -137,9 +137,15 @@ tunnel all their connections. Only works in SSH.
 
 \dd Enable SSH compression.
 
-\dt \cw{-i} \e{path}
+\dt \cw{-i} \e{keyfile}
 
-\dd Private key file for user authentication.
+\dd Private key file for user authentication. For SSH-2 keys, this key
+file must be in PuTTY's PPK format, not OpenSSH's format or anyone
+else's.
+
+\lcont{ If you are using an authentication agent, you can also specify
+a \e{public} key here (in RFC 4716 or OpenSSH format), to identify
+which of the agent's keys to use. }
 
 \dt \cw{\-hostkey} \e{key}
 
index 1b95d4d243f9c3af6015d7196e80dc24e5153c32..b225f22a127fdcda40be77a567cbe87c3e759bfb 100644 (file)
@@ -91,9 +91,15 @@ commands such as \q{\c{w}}).
 
 \dd Enable SSH compression.
 
-\dt \cw{-i} \e{path}
+\dt \cw{-i} \e{keyfile}
 
-\dd Private key file for user authentication.
+\dd Private key file for user authentication. For SSH-2 keys, this key
+file must be in PuTTY's PPK format, not OpenSSH's format or anyone
+else's.
+
+\lcont{ If you are using an authentication agent, you can also specify
+a \e{public} key here (in RFC 4716 or OpenSSH format), to identify
+which of the agent's keys to use. }
 
 \dt \cw{\-hostkey} \e{key}
 
index 0194779a7c26a269fee37f34ddcf78d679d571e6..961262b9987fdecb18e38a9b72f68c11d7ed8c86 100644 (file)
@@ -79,9 +79,15 @@ commands such as \q{\c{w}}).
 
 \dd Enable SSH compression.
 
-\dt \cw{-i} \e{path}
+\dt \cw{-i} \e{keyfile}
 
-\dd Private key file for user authentication.
+\dd Private key file for user authentication. For SSH-2 keys, this key
+file must be in PuTTY's PPK format, not OpenSSH's format or anyone
+else's.
+
+\lcont{ If you are using an authentication agent, you can also specify
+a \e{public} key here (in RFC 4716 or OpenSSH format), to identify
+which of the agent's keys to use. }
 
 \dt \cw{\-hostkey} \e{key}
 
index 07a756b112a54eff32701b40807a595652817955..4101c86750d6078de8a48719352b7fbf58887d8a 100644 (file)
@@ -31,6 +31,7 @@ Sorry.)
 \dt \cw{\-fn} \e{font-name}
 
 \dd Specify the font to use for normal text displayed in the terminal.
+For example, \cw{\-fn\_fixed}, \cw{\-fn\_"Monospace\_12"}.
 
 \dt \cw{\-fb} \e{font-name}
 
index 26d5842d06f42065aac1c14096a33a8ddf0093ce..fec97f11cd49cceb3eeae4b088f396ec7124b3be 100644 (file)
@@ -51,6 +51,7 @@ sets of defaults and choose between them.
 \dt \cw{\-fn} \e{font-name}
 
 \dd Specify the font to use for normal text displayed in the terminal.
+For example, \cw{\-fn\_fixed}, \cw{\-fn\_"Monospace\_12"}.
 
 \dt \cw{\-fb} \e{font-name}
 
index 67bcba83c0fa9338c42b8ce5edc4d880323a2f7e..708bae7f9d641c94e1525e2788a0cd68eef7ea8a 100644 (file)
@@ -30,6 +30,7 @@ Sorry.)
 \dt \cw{\-fn} \e{font-name}
 
 \dd Specify the font to use for normal text displayed in the terminal.
+For example, \cw{\-fn\_fixed}, \cw{\-fn\_"Monospace\_12"}.
 
 \dt \cw{\-fb} \e{font-name}
 
@@ -232,9 +233,13 @@ pseudo-terminal at the server end.
 
 \dt \cw{\-i} \e{keyfile}
 
-\dd Specify a private key file to use for user authentication. For SSH-2
-keys, this key file must be in PuTTY's format, not OpenSSH's or
-anyone else's.
+\dd Private key file for user authentication. For SSH-2 keys, this key
+file must be in PuTTY's PPK format, not OpenSSH's format or anyone
+else's.
+
+\lcont{ If you are using an authentication agent, you can also specify
+a \e{public} key here (in RFC 4716 or OpenSSH format), to identify
+which of the agent's keys to use. }
 
 \dt \cw{\-hostkey} \e{key}
 
index 8e7c5a7a0c2145170f723eeab8ca177ced17cb73..f25119dd1ead10691c1583db1efe877f58c7c2e0 100644 (file)
@@ -64,8 +64,8 @@ The large list box in the Pageant main window lists the private keys
 that are currently loaded into Pageant. The list might look
 something like this:
 
-\c ssh1    1024 22:c3:68:3b:09:41:36:c3:39:83:91:ae:71:b2:0f:04 k1
-\c ssh-rsa 1023 74:63:08:82:95:75:e1:7c:33:31:bb:cb:00:c0:89:8b k2
+\c ssh-rsa 2048 22:d6:69:c9:22:51:ac:cb:b9:15:67:47:f7:65:6d:d7 k1
+\c ssh-dss 2048 e4:6c:69:f3:4f:fc:cf:fc:96:c0:88:34:a7:1e:59:d7 k2
 
 For each key, the list box will tell you:
 
@@ -260,10 +260,10 @@ as long as they want.
 However, the sysadmin of the server machine can always pretend to be
 you \e{on that machine}. So if you forward your agent to a server
 machine, then the sysadmin of that machine can access the forwarded
-agent connection and request signatures from your private keys, and
-can therefore log in to other machines as you. They can only do this
-to a limited extent - when the agent forwarding disappears they lose
-the ability - but using Pageant doesn't actually \e{prevent} the
+agent connection and request signatures from any of your private keys,
+and can therefore log in to other machines as you. They can only do
+this to a limited extent - when the agent forwarding disappears they
+lose the ability - but using Pageant doesn't actually \e{prevent} the
 sysadmin (or hackers) on the server from doing this.
 
 Therefore, if you don't trust the sysadmin of a server machine, you
index e93ee5ecb4ab3a83156d57411dfab5c2664c4e61..9ec900665863ff5ef422b70475daef4fe7f8d595 100644 (file)
@@ -69,7 +69,7 @@ IDs listed below.
 
 \dd RSA, 2048-bit. Main key ID: \cw{2048R/8A0AF00B} (long version:
 \cw{2048R/C4FCAAD08A0AF00B}). Encryption subkey ID:
-\cw{2048R/50C2CF5C} (long version: \cw{2048R/9EB39CC150C2CF5C}.
+\cw{2048R/50C2CF5C} (long version: \cw{2048R/9EB39CC150C2CF5C}).
 Fingerprint:
 \cw{8A26\_250E\_763F\_E359\_75F3\_\_118F\_C4FC\_AAD0\_8A0A\_F00B}
 
index e2620bb515a6edb592ffe1b7097a38649e3b9d6c..f9f894f7980bc78e7ee581b7a1c68d354127688a 100644 (file)
@@ -131,22 +131,6 @@ key will be completely useless.
 The SSH-2 protocol supports more than one key type. The types
 supported by PuTTY are RSA, DSA, ECDSA, and Ed25519.
 
-The PuTTY developers \e{strongly} recommend you use RSA.
-\#{FIXME: ECDSA, Ed25519!}
-\I{security risk}\i{DSA} has an intrinsic weakness which makes it very
-easy to create a signature which contains enough information to give
-away the \e{private} key!
-This would allow an attacker to pretend to be you for any number of
-future sessions. PuTTY's implementation has taken very careful
-precautions to avoid this weakness, but we cannot be 100% certain we
-have managed it, and if you have the choice we strongly recommend
-using RSA keys instead.
-
-If you really need to connect to an SSH server which only supports
-DSA, then you probably have no choice but to use DSA. If you do use
-DSA, we recommend you do not use the same key to authenticate with
-more than one server.
-
 \S{puttygen-strength} Selecting the size (strength) of the key
 
 \cfg{winhelp-topic}{puttygen.bits}
@@ -391,7 +375,7 @@ OpenSSH; for newer key types like Ed25519, it will use the newer
 format as that is the only legal option. If you have some specific
 reason for wanting to use OpenSSH's newer format even for RSA, DSA,
 or ECDSA keys, you can choose \q{Export OpenSSH key (force new file
-format}.
+format)}.
 
 Note that since only SSH-2 keys come in different formats, the export
 options are not available if you have generated an SSH-1 key.
index 5448b81ad13cc8cf56e1119aa2baa555f191972e..fc5859cf97b60269ac557959cb480f021da53d7d 100644 (file)
@@ -121,6 +121,9 @@ and hit the Copy button to copy them to the \i{clipboard}. If you
 are reporting a bug, it's often useful to paste the contents of the
 Event Log into your bug report.
 
+(The Event Log is not the same as the facility to create a log file
+of your session; that's described in \k{using-logging}.)
+
 \S2{using-specials} \ii{Special commands}
 
 Depending on the protocol used for the current session, there may be
@@ -198,6 +201,29 @@ resets associated timers and counters). For more information about
 repeat key exchanges, see \k{config-ssh-kex-rekey}.
 }
 
+\b \I{host key cache}Cache new host key type
+
+\lcont{
+Only available in SSH-2. This submenu appears only if the server has
+host keys of a type that PuTTY doesn't already have cached, and so
+won't consider. Selecting a key here will allow PuTTY to use that key
+now and in future: PuTTY will do a fresh key-exchange with the selected
+key, and immediately add that key to its permanent cache (relying on
+the host key used at the start of the connection to cross-certify the
+new key). That key will be used for the rest of the current session;
+it may not actually be used for future sessions, depending on your
+preferences (see \k{config-ssh-hostkey-order}).
+
+Normally, PuTTY will carry on using a host key it already knows, even
+if the server offers key formats that PuTTY would otherwise prefer,
+to avoid host key prompts. As a result, if you've been using a server
+for some years, you may still be using an older key than a new user
+would use, due to server upgrades in the meantime. The SSH protocol
+unfortunately does not have organised facilities for host key migration
+and rollover, but this allows you to \I{host keys, upgrading}manually
+upgrade.
+}
+
 \b \I{Break, SSH special command}Break
 
 \lcont{
@@ -316,8 +342,9 @@ If you find that special characters (\i{accented characters}, for
 example, or \i{line-drawing characters}) are not being displayed
 correctly in your PuTTY session, it may be that PuTTY is interpreting
 the characters sent by the server according to the wrong \e{character
-set}. There are a lot of different character sets available, so it's
-entirely possible for this to happen.
+set}. There are a lot of different character sets available, and no
+good way for PuTTY to know which to use, so it's entirely possible
+for this to happen.
 
 If you click \q{Change Settings} and look at the \q{Translation}
 panel, you should see a large number of character sets which you can
@@ -873,9 +900,8 @@ The \c{-1} and \c{-2} options force PuTTY to use version \I{SSH-1}1
 or version \I{SSH-2}2 of the SSH protocol. These options are only
 meaningful if you are using SSH.
 
-These options are equivalent to selecting your preferred SSH
-protocol version as \q{1 only} or \q{2 only} in the SSH panel of the
-PuTTY configuration box (see \k{config-ssh-prot}).
+These options are equivalent to selecting the SSH protocol version in
+the SSH panel of the PuTTY configuration box (see \k{config-ssh-prot}).
 
 \S2{using-cmdline-ipversion} \i\c{-4} and \i\c{-6}: specify an
 \i{Internet protocol version}
@@ -894,6 +920,10 @@ The \c{-i} option allows you to specify the name of a private key
 file in \c{*.\i{PPK}} format which PuTTY will use to authenticate with the
 server. This option is only meaningful if you are using SSH.
 
+If you are using Pageant, you can also specify a \e{public} key file
+(in RFC 4716 or OpenSSH format) to identify a specific key file to use.
+(This won't work if you're not running Pageant, of course.)
+
 For general information on \i{public-key authentication}, see
 \k{pubkey}.
 
@@ -904,22 +934,22 @@ authentication} box in the Auth panel of the PuTTY configuration box
 \S2{using-cmdline-loghost} \i\c{-loghost}: specify a \i{logical host
 name}
 
-This option overrides PuTTY's normal SSH host key caching policy by
-telling it the name of the host you expect your connection to end up
-at (in cases where this differs from the location PuTTY thinks it's
-connecting to). It can be a plain host name, or a host name followed
-by a colon and a port number. See \k{config-loghost} for more detail
-on this.
+This option overrides PuTTY's normal SSH \I{host key cache}host key
+caching policy by telling it the name of the host you expect your
+connection to end up at (in cases where this differs from the location
+PuTTY thinks it's connecting to). It can be a plain host name, or a
+host name followed by a colon and a port number. See
+\k{config-loghost} for more detail on this.
 
 \S2{using-cmdline-hostkey} \i\c{-hostkey}: \I{manually configuring
 host keys}manually specify an expected host key
 
-This option overrides PuTTY's normal SSH host key caching policy by
-telling it exactly what host key to expect, which can be useful if the
-normal automatic host key store in the Registry is unavailable. The
-argument to this option should be either a host key fingerprint, or an
-SSH-2 public key blob. See \k{config-ssh-kex-manual-hostkeys} for more
-information.
+This option overrides PuTTY's normal SSH \I{host key cache}host key
+caching policy by telling it exactly what host key to expect, which
+can be useful if the normal automatic host key store in the Registry
+is unavailable. The argument to this option should be either a host key
+fingerprint, or an SSH-2 public key blob. See
+\k{config-ssh-kex-manual-hostkeys} for more information.
 
 You can specify this option more than once if you want to configure
 more than one key to be accepted.
index 5e845b27c88929b434ba81cb23cad7c291259e74..3bdba19c3e09219d8fbc50cbdfcbaeeae528af46 100644 (file)
@@ -5,13 +5,17 @@ SIZES = 16 32 48 128
 
 MODE = # override to -it on command line for opaque testing
 
-PNGS = $(foreach I,$(ICONS),$(foreach S,$(SIZES),$(I)-$(S).png))
-MONOPNGS = $(foreach I,$(ICONS),$(foreach S,$(SIZES),$(I)-$(S)-mono.png))
-TRUEPNGS = $(foreach I,$(ICONS),$(foreach S,$(SIZES),$(I)-$(S)-true.png))
+PAMS = $(foreach I,$(ICONS),$(foreach S,$(SIZES),$(I)-$(S).pam))
+MONOPAMS = $(foreach I,$(ICONS),$(foreach S,$(SIZES),$(I)-$(S)-mono.pam))
+TRUEPAMS = $(foreach I,$(ICONS),$(foreach S,$(SIZES),$(I)-$(S)-true.pam))
+
+PNGS = $(patsubst %.pam,%.png,$(PAMS))
+MONOPNGS = $(patsubst %.pam,%.png,$(MONOPAMS))
+TRUEPNGS = $(patsubst %.pam,%.png,$(TRUEPAMS))
 
 ICOS = putty.ico puttygen.ico pscp.ico pageant.ico pageants.ico puttycfg.ico \
        puttyins.ico
-ICNS = PuTTY.icns
+ICNS = PuTTY.icns Pterm.icns
 CICONS = xpmputty.c xpmpucfg.c xpmpterm.c xpmptcfg.c
 
 base: icos cicons
@@ -30,13 +34,16 @@ install: icos cicons
        cp $(ICOS) ../windows
        cp $(CICONS) ../unix
 
-$(PNGS): %.png: mkicon.py
+$(PAMS): %.pam: mkicon.py
        ./mkicon.py $(MODE) $(join $(subst -, ,$(basename $@)),_icon) $@
 
-$(MONOPNGS): %.png: mkicon.py
+$(PNGS) $(MONOPNGS) $(TRUEPNGS): %.png: %.pam
+       convert $< $@
+
+$(MONOPAMS): %.pam: mkicon.py
        ./mkicon.py -2 $(MODE) $(join $(subst -, ,$(subst -mono,,$(basename $@))),_icon) $@
 
-$(TRUEPNGS): %.png: mkicon.py
+$(TRUEPAMS): %.pam: mkicon.py
        ./mkicon.py -T $(MODE) $(join $(subst -, ,$(subst -true,,$(basename $@))),_icon) $@
 
 putty.ico: putty-16.png putty-32.png putty-48.png \
@@ -90,15 +97,25 @@ xpmpterm.c: pterm-16.png pterm-32.png pterm-48.png
 xpmptcfg.c: ptermcfg-16.png ptermcfg-32.png ptermcfg-48.png
        ./cicon.pl cfg_icon $^ > $@
 
-PuTTY.icns: putty-16-mono.png putty-16.png \
-           putty-32-mono.png putty-32.png \
-           putty-48-mono.png putty-48.png \
-           putty-128.png
-       ./macicon.py mono:putty-16-mono.png colour:putty-16.png \
-                    mono:putty-32-mono.png colour:putty-32.png \
-                    mono:putty-48-mono.png colour:putty-48.png \
-                                           colour:putty-128.png \
+PuTTY.icns: putty-16-mono.pam putty-16.pam \
+           putty-32-mono.pam putty-32.pam \
+           putty-48-mono.pam putty-48.pam \
+           putty-128.pam
+       ./macicon.py mono:putty-16-mono.pam colour:putty-16.pam \
+                    mono:putty-32-mono.pam colour:putty-32.pam \
+                    mono:putty-48-mono.pam colour:putty-48.pam \
+                                           colour:putty-128.pam \
+               output:$@
+
+Pterm.icns: pterm-16-mono.pam pterm-16.pam \
+           pterm-32-mono.pam pterm-32.pam \
+           pterm-48-mono.pam pterm-48.pam \
+           pterm-128.pam
+       ./macicon.py mono:pterm-16-mono.pam colour:pterm-16.pam \
+                    mono:pterm-32-mono.pam colour:pterm-32.pam \
+                    mono:pterm-48-mono.pam colour:pterm-48.pam \
+                                           colour:pterm-128.pam \
                output:$@
 
 clean:
-       rm -f *.png *.ico *.icns *.c
+       rm -f *.pam *.png *.ico *.icns *.c
index 9dfc87ffd817f48cf4deaea453dbfc2d04790cab..b7fed6b2c59f4af3bad59b23d1f1a439c5074d55 100755 (executable)
@@ -112,18 +112,31 @@ def make_colour_icon(size, rgba):
 # Load an image file from disk and turn it into a simple list of
 # 4-tuples giving 8-bit R,G,B,A values for each pixel.
 #
-# My icon-building makefile already depends on ImageMagick, so I use
-# identify and convert here in place of more sensible Python libraries
-# so as to add no build dependency that wasn't already needed.
+# To avoid adding any build dependency on ImageMagick or Python
+# imaging libraries, none of which comes as standard on OS X, I insist
+# here that the file is in RGBA .pam format (as mkicon.py will have
+# generated it).
 def load_rgba(filename):
-    size = subprocess.check_output(["identify", "-format", "%wx%h", filename])
-    width, height = map(int, size.split("x"))
-    assert width == height
-    data = subprocess.check_output(["convert", "-depth", "8",
-                                    filename, "rgba:-"])
-    assert len(data) == width*height*4
-    rgba = [map(ord, data[i:i+4]) for i in range(0, len(data), 4)]
-    return width, rgba
+    with open(filename) as f:
+        assert f.readline() == "P7\n"
+        for line in iter(f.readline, ''):
+            words = line.rstrip("\n").split()
+            if words[0] == "WIDTH":
+                width = int(words[1])
+            elif words[0] == "HEIGHT":
+                height = int(words[1])
+            elif words[0] == "DEPTH":
+                assert int(words[1]) == 4
+            elif words[0] == "TUPLTYPE":
+                assert words[1] == "RGB_ALPHA"
+            elif words[0] == "ENDHDR":
+                break
+
+        assert width == height
+        data = f.read()
+        assert len(data) == width*height*4
+        rgba = [map(ord, data[i:i+4]) for i in range(0, len(data), 4)]
+        return width, rgba
 
 data = ""
 
index 3e6d81c9352cfc38842bb5af542f2491d3bf8368..30f41b6a7f6c2587d0d101cfb1991410e6933175 100755 (executable)
@@ -893,17 +893,18 @@ def testrun(func, fname):
     for canvas in canvases:
         minx, miny, maxx, maxy = bbox(canvas)
         block.extend(render(canvas, minx-2, miny-2, minx-2+wid, maxy+2))
-    p = os.popen("convert -depth 8 -size %dx%d rgb:- %s" % (wid,ht,fname), "w")
-    assert len(block) == ht
-    for line in block:
-        assert len(line) == wid
-        for r, g, b, a in line:
-            # Composite on to orange.
-            r = int(round((r * a + 255 * (255-a)) / 255.0))
-            g = int(round((g * a + 128 * (255-a)) / 255.0))
-            b = int(round((b * a +   0 * (255-a)) / 255.0))
-            p.write("%c%c%c" % (r,g,b))
-    p.close()
+    with open(fname, "w") as f:
+        f.write(("P7\nWIDTH %d\nHEIGHT %d\nDEPTH 3\nMAXVAL 255\n" +
+                 "TUPLTYPE RGB\nENDHDR\n") % (wid, ht))
+        assert len(block) == ht
+        for line in block:
+            assert len(line) == wid
+            for r, g, b, a in line:
+                # Composite on to orange.
+                r = int(round((r * a + 255 * (255-a)) / 255.0))
+                g = int(round((g * a + 128 * (255-a)) / 255.0))
+                b = int(round((b * a +   0 * (255-a)) / 255.0))
+                f.write("%c%c%c" % (r,g,b))
 
 def drawicon(func, width, fname, orangebackground = 0):
     canvas = func(width / 32.0)
@@ -912,19 +913,20 @@ def drawicon(func, width, fname, orangebackground = 0):
     assert minx >= 0 and miny >= 0 and maxx <= width and maxy <= width
 
     block = render(canvas, 0, 0, width, width)
-    p = os.popen("convert -depth 8 -size %dx%d rgba:- %s" % (width,width,fname), "w")
-    assert len(block) == width
-    for line in block:
-        assert len(line) == width
-        for r, g, b, a in line:
-            if orangebackground:
-                # Composite on to orange.
-                r = int(round((r * a + 255 * (255-a)) / 255.0))
-                g = int(round((g * a + 128 * (255-a)) / 255.0))
-                b = int(round((b * a +   0 * (255-a)) / 255.0))
-                a = 255
-            p.write("%c%c%c%c" % (r,g,b,a))
-    p.close()
+    with open(fname, "w") as f:
+        f.write(("P7\nWIDTH %d\nHEIGHT %d\nDEPTH 4\nMAXVAL 255\n" +
+                 "TUPLTYPE RGB_ALPHA\nENDHDR\n") % (width, width))
+        assert len(block) == width
+        for line in block:
+            assert len(line) == width
+            for r, g, b, a in line:
+                if orangebackground:
+                    # Composite on to orange.
+                    r = int(round((r * a + 255 * (255-a)) / 255.0))
+                    g = int(round((g * a + 128 * (255-a)) / 255.0))
+                    b = int(round((b * a +   0 * (255-a)) / 255.0))
+                    a = 255
+                f.write("%c%c%c%c" % (r,g,b,a))
 
 args = sys.argv[1:]
 
diff --git a/macosx/README.OSX b/macosx/README.OSX
deleted file mode 100644 (file)
index 084671a..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-This directory contains a Mac OS X port of PuTTY/pterm, running as a
-native Aqua GUI application.
-
-THIS PORT IS CURRENTLY UNFINISHED AND EXPERIMENTAL. It is _not_
-considered to be of release quality, even if you've found it (and
-are reading this) in a PuTTY release source archive. You are welcome
-to try using it, but don't be surprised at unexpected behaviour. I'm
-not kidding.
-
-In particular, I have not yet decided where OS X PuTTY should store
-its configuration data. Options include storing it in ~/.putty to be
-compatible with Unix PuTTY, storing it wherever is compatible with
-Mac Classic PuTTY, storing it in a natively OS X location, or
-sorting out the `config-locations' wishlist item and doing all
-three. Therefore, if you start using this port and create a whole
-load of saved sessions, you should not be surprised if a future
-version of the port decides to look somewhere completely different
-for the data and therefore loses them all. If that happens, don't
-say you weren't warned!
-
-Other ways in which the port is currently unfinished include:
-
-Bit rot
--------
-
- - the conversion of the old fixed-size 'Config' structure to the
-   new dynamic 'Conf' was never applied to this directory
-
- - probably other things are out of date too; it would need some
-   work to make it compile again
-
-Missing terminal window features
---------------------------------
-
- - terminal display is horribly slow
-
- - fonts aren't configurable
-
- - several features are unimplemented in the terminal display:
-   underlining, non-solid-block cursors, double-width and
-   double-height line attributes, bold as font rather than as
-   colour, wide (CJK) characters, combining characters.
-
- - there's no scrollbar
-
- - terminal window resizing isn't implemented yet
-
- - proper window placement (cascading down and right from the
-   starting position, plus remembering previous window positions per
-   the Apple HIG) is not implemented
-
-Missing alert box features
---------------------------
-
- - warn-on-close isn't implemented
-
-Missing input features
-----------------------
-
- - use of Alt+numberpad to enter arbitrary numeric character codes
-   is not yet supported
-
- - there's no Meta key yet. (I'd like to at least have the
-   possibility of using Command rather than Option as the Meta key,
-   since the latter is necessary to send some characters, including
-   the rather important # on Apple UK keyboards; but trapping
-   Command-<key> and sending it to the window rather than the
-   application menu requires me to make a positive effort of some
-   sort and I haven't got round to it yet. For those Mac users who
-   consider their Command key sacrosanct, don't worry, this option
-   _will_ be configurable and _will_ be off by default.)
-
- - there's no specials menu
-
- - mouse activity isn't supported (neither cut-and-paste nor xterm
-   mouse tracking)
-
-Missing terminal emulation features
------------------------------------
-
- - currently no support for server-side window management requests
-   (i.e. escape sequences to minimise or maximise the window,
-   request or change its position and size, change its title etc)
-
- - window title is currently fixed
-
-Other missing features
-----------------------
-
- - no Event Log
-
- - no mid-session Change Settings
diff --git a/macosx/info.plist b/macosx/info.plist
deleted file mode 100644 (file)
index ce03a33..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-       <key>CFBundleIconFile</key>
-       <string>PuTTY.icns</string>
-</dict>
-</plist>
diff --git a/macosx/osx.h b/macosx/osx.h
deleted file mode 100644 (file)
index 165539f..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-#ifndef PUTTY_OSX_H
-#define PUTTY_OSX_H
-
-/*
- * Cocoa defines `FontSpec' itself, so we must change its name.
- * (Arrgh.)
- */
-#define FontSpec FontSpec_OSX_Proof
-
-/*
- * Define the various compatibility symbols to make uxpty.c compile
- * correctly on OS X.
- */
-#define BSD_PTYS
-#define OMIT_UTMP
-#define HAVE_NO_SETRESUID
-#define NOT_X_WINDOWS
-
-/*
- * OS X is largely just Unix, so we can include most of this
- * unchanged.
- */
-#include "unix.h"
-
-/*
- * Functions exported by osxsel.m. (Both of these functions are
- * expected to be called in the _main_ thread: the select subthread
- * is an implementation detail of osxsel.m and ideally should not
- * be visible at all outside it.)
- */
-void osxsel_init(void);                       /* call this to kick things off */
-void osxsel_process_results(void);     /* call this on receipt of a netevent */
-
-#endif
diff --git a/macosx/osxclass.h b/macosx/osxclass.h
deleted file mode 100644 (file)
index e79290d..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Header file for the Objective-C parts of Mac OS X PuTTY. This
- * file contains the class definitions, which would cause compile
- * failures in the pure C modules if they appeared in osx.h.
- */
-
-#ifndef PUTTY_OSXCLASS_H
-#define PUTTY_OSXCLASS_H
-
-#include "putty.h"
-
-/*
- * The application controller class, defined in osxmain.m.
- */
-@interface AppController : NSObject
-{
-    NSTimer *timer;
-}
-- (void)newSessionConfig:(id)sender;
-- (void)newTerminal:(id)sender;
-- (void)newSessionWithConfig:(id)cfg;
-- (void)setTimer:(long)next;
-@end
-extern AppController *controller;
-
-/*
- * The SessionWindow class, defined in osxwin.m.
- */
-
-struct alert_queue {
-    struct alert_queue *next;
-    NSAlert *alert;
-    void (*callback)(void *, int);
-    void *ctx;
-};
-
-@class SessionWindow;
-@class TerminalView;
-
-@interface SessionWindow : NSWindow
-{
-    Terminal *term;
-    TerminalView *termview;
-    struct unicode_data ucsdata;
-    void *logctx;
-    Config cfg;
-    void *ldisc;
-    Backend *back;
-    void *backhandle;
-    int exited;
-    /*
-     * The following two members relate to the currently active
-     * alert sheet, if any. They are NULL if there isn't one.
-     */
-    void (*alert_callback)(void *, int);
-    void *alert_ctx;
-    /* This queues future alerts that need to be shown. */
-    struct alert_queue *alert_qhead, *alert_qtail;
-}
-- (id)initWithConfig:(Config)cfg;
-- (void)drawStartFinish:(BOOL)start;
-- (void)setColour:(int)n r:(float)r g:(float)g b:(float)b;
-- (Config *)cfg;
-- (void)doText:(wchar_t *)text len:(int)len x:(int)x y:(int)y
-    attr:(unsigned long)attr lattr:(int)lattr;
-- (int)fromBackend:(const char *)data len:(int)len isStderr:(int)is_stderr;
-- (int)fromBackendUntrusted:(const char *)data len:(int)len;
-- (void)startAlert:(NSAlert *)alert
-    withCallback:(void (*)(void *, int))callback andCtx:(void *)ctx;
-- (void)endSession:(int)clean;
-- (void)notifyRemoteExit;
-- (Terminal *)term;
-@end
-
-/*
- * The ConfigWindow class, defined in osxdlg.m.
- */
-
-@class ConfigWindow;
-
-@interface ConfigWindow : NSWindow
-{
-    NSOutlineView *treeview;
-    struct controlbox *ctrlbox;
-    void *dv;
-    Config cfg;
-}
-- (id)initWithConfig:(Config)cfg;
-@end
-
-/*
- * Functions exported by osxctrls.m. (They have to go in this
- * header file and not osx.h, because some of them have Cocoa class
- * types in their prototypes.)
- */
-#define HSPACING 12                   /* needed in osxdlg.m and osxctrls.m */
-#define VSPACING 8
-
-void *fe_dlg_init(void *data, NSWindow *window, NSObject *target, SEL action);
-void fe_dlg_free(void *dv);
-void create_ctrls(void *dv, NSView *parent, struct controlset *s,
-                 int *minw, int *minh);
-int place_ctrls(void *dv, struct controlset *s, int leftx, int topy,
-               int width);            /* returns height used */
-void select_panel(void *dv, struct controlbox *b, const char *name);
-
-#endif /* PUTTY_OSXCLASS_H */
diff --git a/macosx/osxctrls.m b/macosx/osxctrls.m
deleted file mode 100644 (file)
index 27c2c52..0000000
+++ /dev/null
@@ -1,1781 +0,0 @@
-/*
- * osxctrls.m: OS X implementation of the dialog.h interface.
- */
-
-#import <Cocoa/Cocoa.h>
-#include "putty.h"
-#include "dialog.h"
-#include "osxclass.h"
-#include "tree234.h"
-
-/*
- * Still to be implemented:
- * 
- *  - file selectors (NSOpenPanel / NSSavePanel)
- * 
- *  - font selectors
- *  - colour selectors
- *     * both of these have a conceptual oddity in Cocoa that
- *      you're only supposed to have one per application. But I
- *      currently expect to be able to have multiple PuTTY config
- *      boxes on screen at once; what happens if you trigger the
- *      font selector in each one at the same time?
- *     * if it comes to that, the _font_ selector can probably be
- *      managed by other means: nobody is forcing me to implement
- *      a font selector using a `Change...' button. The portable
- *      dialog interface gives me the flexibility to do this how I
- *      want.
- *     * The colour selector interface, in its present form, is
- *      more interesting and _if_ a radical change of plan is
- *      required then it may stretch across the interface into the
- *      portable side.
- *     * Before I do anything rash I should start by looking at the
- *      Mac Classic port and see how it's done there, on the basis
- *      that Apple seem reasonably unlikely to have invented this
- *      crazy restriction specifically for OS X.
- * 
- *  - focus management
- *     * I tried using makeFirstResponder to give keyboard focus,
- *      but it appeared not to work. Try again, and work out how
- *      it should be done.
- *     * also look into tab order. Currently pressing Tab suggests
- *      that only edit boxes and list boxes can get the keyboard
- *      focus, and that buttons (in all their forms) are unable to
- *      be driven by the keyboard. Find out for sure.
- * 
- *  - dlg_error_msg
- *     * this may run into the usual aggro with modal dialog boxes.
- */
-
-/*
- * For Cocoa control layout, I need a two-stage process. In stage
- * one, I allocate all the controls and measure their natural
- * sizes, which allows me to compute the _minimum_ width and height
- * of a given section of dialog. Then, in stage two, I lay out the
- * dialog box as a whole, decide how much each section of the box
- * needs to receive, and assign it its final size.
- */
-
-/*
- * As yet unsolved issues [FIXME]:
- * 
- *  - Sometimes the height returned from create_ctrls and the
- *    height returned from place_ctrls differ. Find out why. It may
- *    be harmless (e.g. results of NSTextView being odd), but I
- *    want to know.
- * 
- *  - NSTextViews are indented a bit. It'd be nice to put their
- *    left margin at the same place as everything else's.
- * 
- *  - I don't yet know whether we even _can_ support tab order or
- *    keyboard shortcuts. If we can't, then fair enough, we can't.
- *    But if we can, we should.
- * 
- *  - I would _really_ like to know of a better way to correct
- *    NSButton's stupid size estimates than by subclassing it and
- *    overriding sizeToFit with hard-wired sensible values!
- * 
- *  - Speaking of stupid size estimates, the amount by which I'm
- *    adjusting a titled NSBox (currently equal to the point size
- *    of its title font) looks as if it isn't _quite_ enough.
- *    Figure out what the real amount should be and use it.
- * 
- *  - I don't understand why there's always a scrollbar displayed
- *    in each list box. I thought I told it to autohide scrollers?
- * 
- *  - Why do I have to fudge list box heights by adding one? (Might
- *    it be to do with the missing header view?)
- */
-
-/*
- * Subclass of NSButton which corrects the fact that the normal
- * one's sizeToFit method persistently returns 32 as its height,
- * which is simply a lie. I have yet to work out a better
- * alternative than hard-coding the real heights.
- */
-@interface MyButton : NSButton
-{
-    int minht;
-}
-@end
-@implementation MyButton
-- (id)initWithFrame:(NSRect)r
-{
-    self = [super initWithFrame:r];
-    minht = 25;
-    return self;
-}
-- (void)setButtonType:(NSButtonType)t
-{
-    if (t == NSRadioButton || t == NSSwitchButton)
-       minht = 18;
-    else
-       minht = 25;
-    [super setButtonType:t];
-}
-- (void)sizeToFit
-{
-    NSRect r;
-    [super sizeToFit];
-    r = [self frame];
-    r.size.height = minht;
-    [self setFrame:r];
-}
-@end
-
-/*
- * Class used as the data source for NSTableViews.
- */
-@interface MyTableSource : NSObject
-{
-    tree234 *tree;
-}
-- (id)init;
-- (void)add:(const char *)str withId:(int)id;
-- (int)getid:(int)index;
-- (void)swap:(int)index1 with:(int)index2;
-- (void)removestr:(int)index;
-- (void)clear;
-@end
-@implementation MyTableSource
-- (id)init
-{
-    self = [super init];
-    tree = newtree234(NULL);
-    return self;
-}
-- (void)dealloc
-{
-    char *p;
-    while ((p = delpos234(tree, 0)) != NULL)
-       sfree(p);
-    freetree234(tree);
-    [super dealloc];
-}
-- (void)add:(const char *)str withId:(int)id
-{
-    addpos234(tree, dupprintf("%d\t%s", id, str), count234(tree));
-}
-- (int)getid:(int)index
-{
-    char *p = index234(tree, index);
-    return atoi(p);
-}
-- (void)removestr:(int)index
-{
-    char *p = delpos234(tree, index);
-    sfree(p);
-}
-- (void)swap:(int)index1 with:(int)index2
-{
-    char *p1, *p2;
-
-    if (index1 > index2) {
-       int t = index1; index1 = index2; index2 = t;
-    }
-
-    /* delete later one first so it doesn't affect index of earlier one */
-    p2 = delpos234(tree, index2);
-    p1 = delpos234(tree, index1);
-
-    /* now insert earlier one before later one for the inverse reason */
-    addpos234(tree, p2, index1);
-    addpos234(tree, p1, index2);
-}
-- (void)clear
-{
-    char *p;
-    while ((p = delpos234(tree, 0)) != NULL)
-       sfree(p);
-}
-- (int)numberOfRowsInTableView:(NSTableView *)aTableView
-{
-    return count234(tree);
-}
-- (id)tableView:(NSTableView *)aTableView
-    objectValueForTableColumn:(NSTableColumn *)aTableColumn
-    row:(int)rowIndex
-{
-    int j = [[aTableColumn identifier] intValue];
-    char *p = index234(tree, rowIndex);
-
-    while (j >= 0) {
-       p += strcspn(p, "\t");
-       if (*p) p++;
-       j--;
-    }
-
-    return [NSString stringWithCString:p length:strcspn(p, "\t")];
-}
-@end
-
-/*
- * Object to receive messages from various control classes.
- */
-@class Receiver;
-
-struct fe_dlg {
-    NSWindow *window;
-    NSObject *target;
-    SEL action;
-    tree234 *byctrl;
-    tree234 *bywidget;
-    tree234 *boxes;
-    void *data;                               /* passed to portable side */
-    Receiver *rec;
-};
-
-@interface Receiver : NSObject
-{
-    struct fe_dlg *d;
-}
-- (id)initWithStruct:(struct fe_dlg *)aStruct;
-@end
-
-struct fe_ctrl {
-    union control *ctrl;
-    NSButton *button, *button2;
-    NSTextField *label, *editbox;
-    NSComboBox *combobox;
-    NSButton **radiobuttons;
-    NSTextView *textview;
-    NSPopUpButton *popupbutton;
-    NSTableView *tableview;
-    NSScrollView *scrollview;
-    int nradiobuttons;
-};
-
-static int fe_ctrl_cmp_by_ctrl(void *av, void *bv)
-{
-    struct fe_ctrl *a = (struct fe_ctrl *)av;
-    struct fe_ctrl *b = (struct fe_ctrl *)bv;
-
-    if (a->ctrl < b->ctrl)
-       return -1;
-    if (a->ctrl > b->ctrl)
-       return +1;
-    return 0;
-}
-
-static int fe_ctrl_find_by_ctrl(void *av, void *bv)
-{
-    union control *a = (union control *)av;
-    struct fe_ctrl *b = (struct fe_ctrl *)bv;
-
-    if (a < b->ctrl)
-       return -1;
-    if (a > b->ctrl)
-       return +1;
-    return 0;
-}
-
-struct fe_box {
-    struct controlset *s;
-    id box;
-};
-
-static int fe_boxcmp(void *av, void *bv)
-{
-    struct fe_box *a = (struct fe_box *)av;
-    struct fe_box *b = (struct fe_box *)bv;
-
-    if (a->s < b->s)
-       return -1;
-    if (a->s > b->s)
-       return +1;
-    return 0;
-}
-
-static int fe_boxfind(void *av, void *bv)
-{
-    struct controlset *a = (struct controlset *)av;
-    struct fe_box *b = (struct fe_box *)bv;
-
-    if (a < b->s)
-       return -1;
-    if (a > b->s)
-       return +1;
-    return 0;
-}
-
-struct fe_backwards {                 /* map Cocoa widgets back to fe_ctrls */
-    id widget;
-    struct fe_ctrl *c;
-};
-
-static int fe_backwards_cmp_by_widget(void *av, void *bv)
-{
-    struct fe_backwards *a = (struct fe_backwards *)av;
-    struct fe_backwards *b = (struct fe_backwards *)bv;
-
-    if (a->widget < b->widget)
-       return -1;
-    if (a->widget > b->widget)
-       return +1;
-    return 0;
-}
-
-static int fe_backwards_find_by_widget(void *av, void *bv)
-{
-    id a = (id)av;
-    struct fe_backwards *b = (struct fe_backwards *)bv;
-
-    if (a < b->widget)
-       return -1;
-    if (a > b->widget)
-       return +1;
-    return 0;
-}
-
-static struct fe_ctrl *fe_ctrl_new(union control *ctrl)
-{
-    struct fe_ctrl *c;
-
-    c = snew(struct fe_ctrl);
-    c->ctrl = ctrl;
-
-    c->button = c->button2 = nil;
-    c->label = nil;
-    c->editbox = nil;
-    c->combobox = nil;
-    c->textview = nil;
-    c->popupbutton = nil;
-    c->tableview = nil;
-    c->scrollview = nil;
-    c->radiobuttons = NULL;
-    c->nradiobuttons = 0;
-
-    return c;
-}
-
-static void fe_ctrl_free(struct fe_ctrl *c)
-{
-    sfree(c->radiobuttons);
-    sfree(c);
-}
-
-static struct fe_ctrl *fe_ctrl_byctrl(struct fe_dlg *d, union control *ctrl)
-{
-    return find234(d->byctrl, ctrl, fe_ctrl_find_by_ctrl);
-}
-
-static void add_box(struct fe_dlg *d, struct controlset *s, id box)
-{
-    struct fe_box *b = snew(struct fe_box);
-    b->box = box;
-    b->s = s;
-    add234(d->boxes, b);
-}
-
-static id find_box(struct fe_dlg *d, struct controlset *s)
-{
-    struct fe_box *b = find234(d->boxes, s, fe_boxfind);
-    return b ? b->box : NULL;
-}
-
-static void add_widget(struct fe_dlg *d, struct fe_ctrl *c, id widget)
-{
-    struct fe_backwards *b = snew(struct fe_backwards);
-    b->widget = widget;
-    b->c = c;
-    add234(d->bywidget, b);
-}
-
-static struct fe_ctrl *find_widget(struct fe_dlg *d, id widget)
-{
-    struct fe_backwards *b = find234(d->bywidget, widget,
-                                    fe_backwards_find_by_widget);
-    return b ? b->c : NULL;
-}
-
-void *fe_dlg_init(void *data, NSWindow *window, NSObject *target, SEL action)
-{
-    struct fe_dlg *d;
-
-    d = snew(struct fe_dlg);
-    d->window = window;
-    d->target = target;
-    d->action = action;
-    d->byctrl = newtree234(fe_ctrl_cmp_by_ctrl);
-    d->bywidget = newtree234(fe_backwards_cmp_by_widget);
-    d->boxes = newtree234(fe_boxcmp);
-    d->data = data;
-    d->rec = [[Receiver alloc] initWithStruct:d];
-
-    return d;
-}
-
-void fe_dlg_free(void *dv)
-{
-    struct fe_dlg *d = (struct fe_dlg *)dv;
-    struct fe_ctrl *c;
-    struct fe_box *b;
-
-    while ( (c = delpos234(d->byctrl, 0)) != NULL )
-       fe_ctrl_free(c);
-    freetree234(d->byctrl);
-
-    while ( (c = delpos234(d->bywidget, 0)) != NULL )
-       sfree(c);
-    freetree234(d->bywidget);
-
-    while ( (b = delpos234(d->boxes, 0)) != NULL )
-       sfree(b);
-    freetree234(d->boxes);
-
-    [d->rec release];
-
-    sfree(d);
-}
-
-@implementation Receiver
-- (id)initWithStruct:(struct fe_dlg *)aStruct
-{
-    self = [super init];
-    d = aStruct;
-    return self;
-}
-- (void)buttonPushed:(id)sender
-{
-    struct fe_ctrl *c = find_widget(d, sender);
-
-    assert(c && c->ctrl->generic.type == CTRL_BUTTON);
-    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_ACTION);
-}
-- (void)checkboxChanged:(id)sender
-{
-    struct fe_ctrl *c = find_widget(d, sender);
-
-    assert(c && c->ctrl->generic.type == CTRL_CHECKBOX);
-    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
-}
-- (void)radioChanged:(id)sender
-{
-    struct fe_ctrl *c = find_widget(d, sender);
-    int j;
-
-    assert(c && c->radiobuttons);
-    for (j = 0; j < c->nradiobuttons; j++)
-       if (sender != c->radiobuttons[j])
-           [c->radiobuttons[j] setState:NSOffState];
-    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
-}
-- (void)popupMenuSelected:(id)sender
-{
-    struct fe_ctrl *c = find_widget(d, sender);
-    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
-}
-- (void)controlTextDidChange:(NSNotification *)notification
-{
-    id widget = [notification object];
-    struct fe_ctrl *c = find_widget(d, widget);
-    assert(c && c->ctrl->generic.type == CTRL_EDITBOX);
-    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
-}
-- (void)controlTextDidEndEditing:(NSNotification *)notification
-{
-    id widget = [notification object];
-    struct fe_ctrl *c = find_widget(d, widget);
-    assert(c && c->ctrl->generic.type == CTRL_EDITBOX);
-    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_REFRESH);
-}
-- (void)tableViewSelectionDidChange:(NSNotification *)notification
-{
-    id widget = [notification object];
-    struct fe_ctrl *c = find_widget(d, widget);
-    assert(c && c->ctrl->generic.type == CTRL_LISTBOX);
-    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_SELCHANGE);
-}
-- (BOOL)tableView:(NSTableView *)aTableView
-    shouldEditTableColumn:(NSTableColumn *)aTableColumn
-    row:(int)rowIndex
-{
-    return NO;                        /* no editing permitted */
-}
-- (void)listDoubleClicked:(id)sender
-{
-    struct fe_ctrl *c = find_widget(d, sender);
-    assert(c && c->ctrl->generic.type == CTRL_LISTBOX);
-    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_ACTION);
-}
-- (void)dragListButton:(id)sender
-{
-    struct fe_ctrl *c = find_widget(d, sender);
-    int direction, row, nrows;
-    assert(c && c->ctrl->generic.type == CTRL_LISTBOX &&
-          c->ctrl->listbox.draglist);
-
-    if (sender == c->button)
-       direction = -1;                /* up */
-    else
-       direction = +1;                /* down */
-
-    row = [c->tableview selectedRow];
-    nrows = [c->tableview numberOfRows];
-
-    if (row + direction < 0 || row + direction >= nrows) {
-       NSBeep();
-       return;
-    }
-
-    [[c->tableview dataSource] swap:row with:row+direction];
-    [c->tableview reloadData];
-    [c->tableview selectRow:row+direction byExtendingSelection:NO];
-
-    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
-}
-@end
-
-void create_ctrls(void *dv, NSView *parent, struct controlset *s,
-                 int *minw, int *minh)
-{
-    struct fe_dlg *d = (struct fe_dlg *)dv;
-    int ccw[100];                     /* cumulative column widths */
-    int cypos[100];
-    int ncols;
-    int wmin = 0, hmin = 0;
-    int i, j, cw, ch;
-    NSRect rect;
-    NSFont *textviewfont = nil;
-    int boxh = 0, boxw = 0;
-
-    if (!s->boxname && s->boxtitle) {
-        /* This controlset is a panel title. */
-
-       NSTextField *tf;
-
-       tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
-       [tf setEditable:NO];
-       [tf setSelectable:NO];
-       [tf setBordered:NO];
-       [tf setDrawsBackground:NO];
-       [tf setStringValue:[NSString stringWithCString:s->boxtitle]];
-       [tf sizeToFit];
-       rect = [tf frame];
-       [parent addSubview:tf];
-
-       /*
-        * I'm going to store this NSTextField in the boxes tree,
-        * because I really can't face having a special tree234
-        * mapping controlsets to panel titles.
-        */
-       add_box(d, s, tf);
-
-       *minw = rect.size.width;
-       *minh = rect.size.height;
-
-       return;
-    }
-
-    if (*s->boxname) {
-       /*
-        * Create an NSBox to contain this subset of controls.
-        */
-       NSBox *box;
-       NSRect tmprect;
-
-       box = [[NSBox alloc] initWithFrame:NSMakeRect(0,0,1,1)];
-       if (s->boxtitle)
-           [box setTitle:[NSString stringWithCString:s->boxtitle]];
-       else
-           [box setTitlePosition:NSNoTitle];
-       add_box(d, s, box);
-       tmprect = [box frame];
-       [box setContentViewMargins:NSMakeSize(20,20)];
-       [box setFrameFromContentFrame:NSMakeRect(100,100,100,100)];
-       rect = [box frame];
-       [box setFrame:tmprect];
-       boxh = (int)(rect.size.height - 100);
-       boxw = (int)(rect.size.width - 100);
-       [parent addSubview:box];
-
-       if (s->boxtitle)
-           boxh += [[box titleFont] pointSize];
-
-       /*
-        * All subsequent controls will be placed within this box.
-        */
-       parent = box;
-    }
-
-    ncols = 1;
-    ccw[0] = 0;
-    ccw[1] = 100;
-    cypos[0] = 0;
-
-    /*
-     * Now iterate through the controls themselves, create them,
-     * and add their width and height to the overall width/height
-     * calculation.
-     */
-    for (i = 0; i < s->ncontrols; i++) {
-       union control *ctrl = s->ctrls[i];
-       struct fe_ctrl *c;
-       int colstart = COLUMN_START(ctrl->generic.column);
-       int colspan = COLUMN_SPAN(ctrl->generic.column);
-       int colend = colstart + colspan;
-       int ytop, wthis;
-
-        switch (ctrl->generic.type) {
-          case CTRL_COLUMNS:
-           for (j = 1; j < ncols; j++)
-               if (cypos[0] < cypos[j])
-                   cypos[0] = cypos[j];
-
-           assert(ctrl->columns.ncols < lenof(ccw));
-
-           ccw[0] = 0;
-           for (j = 0; j < ctrl->columns.ncols; j++) {
-               ccw[j+1] = ccw[j] + (ctrl->columns.percentages ?
-                                    ctrl->columns.percentages[j] : 100);
-               cypos[j] = cypos[0];
-           }
-
-           ncols = ctrl->columns.ncols;
-
-            continue;                  /* no actual control created */
-          case CTRL_TABDELAY:
-           /*
-            * I'm currently uncertain that we can implement tab
-            * order in OS X.
-            */
-            continue;                  /* no actual control created */
-       }
-
-       c = fe_ctrl_new(ctrl);
-       add234(d->byctrl, c);
-
-       cw = ch = 0;
-
-        switch (ctrl->generic.type) {
-          case CTRL_BUTTON:
-          case CTRL_CHECKBOX:
-           {
-               NSButton *b;
-
-               b = [[MyButton alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];
-               [b setBezelStyle:NSRoundedBezelStyle];
-               if (ctrl->generic.type == CTRL_CHECKBOX)
-                   [b setButtonType:NSSwitchButton];
-               [b setTitle:[NSString stringWithCString:ctrl->generic.label]];
-               if (ctrl->button.isdefault)
-                   [b setKeyEquivalent:@"\r"];
-               else if (ctrl->button.iscancel)
-                   [b setKeyEquivalent:@"\033"];
-               [b sizeToFit];
-               rect = [b frame];
-
-               [parent addSubview:b];
-
-               [b setTarget:d->rec];
-               if (ctrl->generic.type == CTRL_CHECKBOX)
-                   [b setAction:@selector(checkboxChanged:)];
-               else
-                   [b setAction:@selector(buttonPushed:)];
-               add_widget(d, c, b);
-
-               c->button = b;
-
-               cw = rect.size.width;
-               ch = rect.size.height;
-           }
-           break;
-         case CTRL_EDITBOX:
-           {
-               int editp = ctrl->editbox.percentwidth;
-               int labelp = editp == 100 ? 100 : 100 - editp;
-               NSTextField *tf;
-               NSComboBox *cb;
-
-               tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
-               [tf setEditable:NO];
-               [tf setSelectable:NO];
-               [tf setBordered:NO];
-               [tf setDrawsBackground:NO];
-               [tf setStringValue:[NSString
-                                   stringWithCString:ctrl->generic.label]];
-               [tf sizeToFit];
-               rect = [tf frame];
-               [parent addSubview:tf];
-               c->label = tf;
-
-               cw = rect.size.width * 100 / labelp;
-               ch = rect.size.height;
-
-               if (ctrl->editbox.has_list) {
-                   cb = [[NSComboBox alloc]
-                         initWithFrame:NSMakeRect(0,0,1,1)];
-                   [cb setStringValue:@"x"];
-                   [cb sizeToFit];
-                   rect = [cb frame];
-                   [parent addSubview:cb];
-                   c->combobox = cb;
-               } else {
-                   if (ctrl->editbox.password)
-                       tf = [NSSecureTextField alloc];
-                   else
-                       tf = [NSTextField alloc];
-
-                   tf = [tf initWithFrame:NSMakeRect(0,0,1,1)];
-                   [tf setEditable:YES];
-                   [tf setSelectable:YES];
-                   [tf setBordered:YES];
-                   [tf setStringValue:@"x"];
-                   [tf sizeToFit];
-                   rect = [tf frame];
-                   [parent addSubview:tf];
-                   c->editbox = tf;
-
-                   [tf setDelegate:d->rec];
-                   add_widget(d, c, tf);
-               }
-
-               if (editp == 100) {
-                   /* the edit box and its label are vertically separated */
-                   ch += VSPACING + rect.size.height;
-               } else {
-                   /* the edit box and its label are horizontally separated */
-                   if (ch < rect.size.height)
-                       ch = rect.size.height;
-               }
-
-               if (cw < rect.size.width * 100 / editp)
-                   cw = rect.size.width * 100 / editp;
-           }
-           break;
-         case CTRL_TEXT:
-           {
-               NSTextView *tv;
-               int testwid;
-
-               if (!textviewfont) {
-                   NSTextField *tf;
-                   tf = [[NSTextField alloc] init];
-                   textviewfont = [tf font];
-                   [tf release];
-               }
-
-               testwid = (ccw[colend] - ccw[colstart]) * 3;
-
-               tv = [[NSTextView alloc]
-                     initWithFrame:NSMakeRect(0,0,testwid,1)];
-               [tv setEditable:NO];
-               [tv setSelectable:NO];
-               //[tv setBordered:NO];
-               [tv setDrawsBackground:NO];
-               [tv setFont:textviewfont];
-               [tv setString:
-                [NSString stringWithCString:ctrl->generic.label]];
-               rect = [tv frame];
-               [tv sizeToFit];
-               [parent addSubview:tv];
-               c->textview = tv;
-
-               cw = rect.size.width;
-               ch = rect.size.height;
-           }
-           break;
-         case CTRL_RADIO:
-           {
-               NSTextField *tf;
-               int j;
-
-               if (ctrl->generic.label) {
-                   tf = [[NSTextField alloc]
-                         initWithFrame:NSMakeRect(0,0,1,1)];
-                   [tf setEditable:NO];
-                   [tf setSelectable:NO];
-                   [tf setBordered:NO];
-                   [tf setDrawsBackground:NO];
-                   [tf setStringValue:
-                    [NSString stringWithCString:ctrl->generic.label]];
-                   [tf sizeToFit];
-                   rect = [tf frame];
-                   [parent addSubview:tf];
-                   c->label = tf;
-
-                   cw = rect.size.width;
-                   ch = rect.size.height;
-               } else {
-                   cw = 0;
-                   ch = -VSPACING;    /* compensate for next advance */
-               }
-
-               c->nradiobuttons = ctrl->radio.nbuttons;
-               c->radiobuttons = snewn(ctrl->radio.nbuttons, NSButton *);
-
-               for (j = 0; j < ctrl->radio.nbuttons; j++) {
-                   NSButton *b;
-                   int ncols;
-
-                   b = [[MyButton alloc] initWithFrame:NSMakeRect(0,0,1,1)];
-                   [b setBezelStyle:NSRoundedBezelStyle];
-                   [b setButtonType:NSRadioButton];
-                   [b setTitle:[NSString
-                                stringWithCString:ctrl->radio.buttons[j]]];
-                   [b sizeToFit];
-                   rect = [b frame];
-                   [parent addSubview:b];
-
-                   c->radiobuttons[j] = b;
-
-                   [b setTarget:d->rec];
-                   [b setAction:@selector(radioChanged:)];
-                   add_widget(d, c, b);
-
-                   /*
-                    * Add to the height every time we place a
-                    * button in column 0.
-                    */
-                   if (j % ctrl->radio.ncolumns == 0) {
-                       ch += rect.size.height + VSPACING;
-                   }
-
-                   /*
-                    * Add to the width by working out how many
-                    * columns this button spans.
-                    */
-                   if (j == ctrl->radio.nbuttons - 1)
-                       ncols = (ctrl->radio.ncolumns -
-                                (j % ctrl->radio.ncolumns));
-                   else
-                       ncols = 1;
-
-                   if (cw < rect.size.width * ctrl->radio.ncolumns / ncols)
-                       cw = rect.size.width * ctrl->radio.ncolumns / ncols;
-               }
-           }
-           break;
-         case CTRL_FILESELECT:
-         case CTRL_FONTSELECT:
-           {
-               NSTextField *tf;
-               NSButton *b;
-               int kh;
-
-               tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
-               [tf setEditable:NO];
-               [tf setSelectable:NO];
-               [tf setBordered:NO];
-               [tf setDrawsBackground:NO];
-               [tf setStringValue:[NSString
-                                   stringWithCString:ctrl->generic.label]];
-               [tf sizeToFit];
-               rect = [tf frame];
-               [parent addSubview:tf];
-               c->label = tf;
-
-               cw = rect.size.width;
-               ch = rect.size.height;
-
-               tf = [NSTextField alloc];
-               tf = [tf initWithFrame:NSMakeRect(0,0,1,1)];
-               if (ctrl->generic.type == CTRL_FILESELECT) {
-                   [tf setEditable:YES];
-                   [tf setSelectable:YES];
-                   [tf setBordered:YES];
-               } else {
-                   [tf setEditable:NO];
-                   [tf setSelectable:NO];
-                   [tf setBordered:NO];
-                   [tf setDrawsBackground:NO];
-               }
-               [tf setStringValue:@"x"];
-               [tf sizeToFit];
-               rect = [tf frame];
-               [parent addSubview:tf];
-               c->editbox = tf;
-
-               kh = rect.size.height;
-               if (cw < rect.size.width * 4 / 3)
-                   cw = rect.size.width * 4 / 3;
-
-               b = [[MyButton alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];
-               [b setBezelStyle:NSRoundedBezelStyle];
-               if (ctrl->generic.type == CTRL_FILESELECT)
-                   [b setTitle:@"Browse..."];
-               else
-                   [b setTitle:@"Change..."];
-               // [b setKeyEquivalent:somethingorother];
-               // [b setTarget:somethingorother];
-               // [b setAction:somethingorother];
-               [b sizeToFit];
-               rect = [b frame];
-               [parent addSubview:b];
-
-               c->button = b;
-
-               if (kh < rect.size.height)
-                   kh = rect.size.height;
-               ch += VSPACING + kh;
-               if (cw < rect.size.width * 4)
-                   cw = rect.size.width * 4;
-           }
-           break;
-         case CTRL_LISTBOX:
-           {
-               int listp = ctrl->listbox.percentwidth;
-               int labelp = listp == 100 ? 100 : 100 - listp;
-               NSTextField *tf;
-               NSPopUpButton *pb;
-               NSTableView *tv;
-               NSScrollView *sv;
-
-               if (ctrl->generic.label) {
-                   tf = [[NSTextField alloc]
-                         initWithFrame:NSMakeRect(0,0,1,1)];
-                   [tf setEditable:NO];
-                   [tf setSelectable:NO];
-                   [tf setBordered:NO];
-                   [tf setDrawsBackground:NO];
-                   [tf setStringValue:
-                    [NSString stringWithCString:ctrl->generic.label]];
-                   [tf sizeToFit];
-                   rect = [tf frame];
-                   [parent addSubview:tf];
-                   c->label = tf;
-
-                   cw = rect.size.width;
-                   ch = rect.size.height;
-               } else {
-                   cw = 0;
-                   ch = -VSPACING;    /* compensate for next advance */
-               }
-
-               if (ctrl->listbox.height == 0) {
-                   pb = [[NSPopUpButton alloc]
-                         initWithFrame:NSMakeRect(0,0,1,1)];
-                   [pb sizeToFit];
-                   rect = [pb frame];
-                   [parent addSubview:pb];
-                   c->popupbutton = pb;
-
-                   [pb setTarget:d->rec];
-                   [pb setAction:@selector(popupMenuSelected:)];
-                   add_widget(d, c, pb);
-               } else {
-                   assert(listp == 100);
-                   if (ctrl->listbox.draglist) {
-                       int bi;
-
-                       listp = 75;
-
-                       for (bi = 0; bi < 2; bi++) {
-                           NSButton *b;
-                           b = [[MyButton alloc]
-                                initWithFrame:NSMakeRect(0, 0, 1, 1)];
-                           [b setBezelStyle:NSRoundedBezelStyle];
-                           if (bi == 0)
-                               [b setTitle:@"Up"];
-                           else
-                               [b setTitle:@"Down"];
-                           [b sizeToFit];
-                           rect = [b frame];
-                           [parent addSubview:b];
-
-                           if (bi == 0)
-                               c->button = b;
-                           else
-                               c->button2 = b;
-
-                           [b setTarget:d->rec];
-                           [b setAction:@selector(dragListButton:)];
-                           add_widget(d, c, b);
-
-                           if (cw < rect.size.width * 4)
-                               cw = rect.size.width * 4;
-                       }
-                   }
-
-                   sv = [[NSScrollView alloc] initWithFrame:
-                         NSMakeRect(20,20,10,10)];
-                   [sv setBorderType:NSLineBorder];
-                   tv = [[NSTableView alloc] initWithFrame:[sv frame]];
-                   [[tv headerView] setFrame:NSMakeRect(0,0,0,0)];
-                   [sv setDocumentView:tv];
-                   [parent addSubview:sv];
-                   [sv setHasVerticalScroller:YES];
-                   [sv setAutohidesScrollers:YES];
-                   [tv setAllowsColumnReordering:NO];
-                   [tv setAllowsColumnResizing:NO];
-                   [tv setAllowsMultipleSelection:ctrl->listbox.multisel];
-                   [tv setAllowsEmptySelection:YES];
-                   [tv setAllowsColumnSelection:YES];
-                   [tv setDataSource:[[MyTableSource alloc] init]];
-                   rect = [tv frame];
-                   /*
-                    * For some reason this consistently comes out
-                    * one short. Add one.
-                    */
-                   rect.size.height = (ctrl->listbox.height+1)*[tv rowHeight];
-                   [sv setFrame:rect];
-                   c->tableview = tv;
-                   c->scrollview = sv;
-
-                   [tv setDelegate:d->rec];
-                   [tv setTarget:d->rec];
-                   [tv setDoubleAction:@selector(listDoubleClicked:)];
-                   add_widget(d, c, tv);
-               }
-
-               if (c->tableview) {
-                   int ncols, *percentages;
-                   int hundred = 100;
-
-                   if (ctrl->listbox.ncols) {
-                       ncols = ctrl->listbox.ncols;
-                       percentages = ctrl->listbox.percentages;
-                   } else {
-                       ncols = 1;
-                       percentages = &hundred;
-                   }
-
-                   for (j = 0; j < ncols; j++) {
-                       NSTableColumn *col;
-
-                       col = [[NSTableColumn alloc] initWithIdentifier:
-                              [NSNumber numberWithInt:j]];
-                       [c->tableview addTableColumn:col];
-                   }
-               }
-
-               if (labelp == 100) {
-                   /* the list and its label are vertically separated */
-                   ch += VSPACING + rect.size.height;
-               } else {
-                   /* the list and its label are horizontally separated */
-                   if (ch < rect.size.height)
-                       ch = rect.size.height;
-               }
-
-               if (cw < rect.size.width * 100 / listp)
-                   cw = rect.size.width * 100 / listp;
-           }
-           break;
-       }
-
-       /*
-        * Update the width and height data for the control we've
-        * just created.
-        */
-       ytop = 0;
-
-       for (j = colstart; j < colend; j++) {
-           if (ytop < cypos[j])
-               ytop = cypos[j];
-       }
-
-       for (j = colstart; j < colend; j++)
-           cypos[j] = ytop + ch + VSPACING;
-
-       if (hmin < ytop + ch)
-           hmin = ytop + ch;
-
-       wthis = (cw + HSPACING) * 100 / (ccw[colend] - ccw[colstart]);
-       wthis -= HSPACING;
-
-       if (wmin < wthis)
-           wmin = wthis;
-    }
-
-    if (*s->boxname) {
-       /*
-        * Add a bit to the width and height for the box.
-        */
-       wmin += boxw;
-       hmin += boxh;
-    }
-
-    //printf("For controlset %s/%s, returning w=%d h=%d\n",
-    //       s->pathname, s->boxname, wmin, hmin);
-    *minw = wmin;
-    *minh = hmin;
-}
-
-int place_ctrls(void *dv, struct controlset *s, int leftx, int topy,
-               int width)
-{
-    struct fe_dlg *d = (struct fe_dlg *)dv;
-    int ccw[100];                     /* cumulative column widths */
-    int cypos[100];
-    int ncols;
-    int i, j, ret;
-    int boxh = 0, boxw = 0;
-
-    if (!s->boxname && s->boxtitle) {
-        /* Size and place the panel title. */
-
-       NSTextField *tf = find_box(d, s);
-       NSRect rect;
-
-       rect = [tf frame];
-       [tf setFrame:NSMakeRect(leftx, topy-rect.size.height,
-                               width, rect.size.height)];
-       return rect.size.height;
-    }
-
-    if (*s->boxname) {
-       NSRect rect, tmprect;
-       NSBox *box = find_box(d, s);
-
-       assert(box != NULL);
-       tmprect = [box frame];
-       [box setFrameFromContentFrame:NSMakeRect(100,100,100,100)];
-       rect = [box frame];
-       [box setFrame:tmprect];
-       boxw = rect.size.width - 100;
-       boxh = rect.size.height - 100;
-       if (s->boxtitle)
-           boxh += [[box titleFont] pointSize];
-       topy -= boxh;
-       width -= boxw;
-    }
-
-    ncols = 1;
-    ccw[0] = 0;
-    ccw[1] = 100;
-    cypos[0] = topy;
-    ret = 0;
-
-    /*
-     * Now iterate through the controls themselves, placing them
-     * appropriately.
-     */
-    for (i = 0; i < s->ncontrols; i++) {
-       union control *ctrl = s->ctrls[i];
-       struct fe_ctrl *c;
-       int colstart = COLUMN_START(ctrl->generic.column);
-       int colspan = COLUMN_SPAN(ctrl->generic.column);
-       int colend = colstart + colspan;
-       int xthis, ythis, wthis, ch;
-       NSRect rect;
-
-        switch (ctrl->generic.type) {
-          case CTRL_COLUMNS:
-           for (j = 1; j < ncols; j++)
-               if (cypos[0] > cypos[j])
-                   cypos[0] = cypos[j];
-
-           assert(ctrl->columns.ncols < lenof(ccw));
-
-           ccw[0] = 0;
-           for (j = 0; j < ctrl->columns.ncols; j++) {
-               ccw[j+1] = ccw[j] + (ctrl->columns.percentages ?
-                                    ctrl->columns.percentages[j] : 100);
-               cypos[j] = cypos[0];
-           }
-
-           ncols = ctrl->columns.ncols;
-
-            continue;                  /* no actual control created */
-          case CTRL_TABDELAY:
-            continue;                  /* nothing to do here, move along */
-       }
-
-       c = fe_ctrl_byctrl(d, ctrl);
-
-       ch = 0;
-       ythis = topy;
-
-       for (j = colstart; j < colend; j++) {
-           if (ythis > cypos[j])
-               ythis = cypos[j];
-       }
-
-       xthis = (width + HSPACING) * ccw[colstart] / 100;
-       wthis = (width + HSPACING) * ccw[colend] / 100 - HSPACING - xthis;
-       xthis += leftx;
-
-        switch (ctrl->generic.type) {
-          case CTRL_BUTTON:
-         case CTRL_CHECKBOX:
-           rect = [c->button frame];
-           [c->button setFrame:NSMakeRect(xthis,ythis-rect.size.height,wthis,
-                                          rect.size.height)];
-           ch = rect.size.height;
-           break;
-         case CTRL_EDITBOX:
-           {
-               int editp = ctrl->editbox.percentwidth;
-               int labelp = editp == 100 ? 100 : 100 - editp;
-               int lheight, theight, rheight, ynext, editw;
-               NSControl *edit = (c->editbox ? c->editbox : c->combobox);
-
-               rect = [c->label frame];
-               lheight = rect.size.height;
-               rect = [edit frame];
-               theight = rect.size.height;
-
-               if (editp == 100)
-                   rheight = lheight;
-               else
-                   rheight = (lheight < theight ? theight : lheight);
-
-               [c->label setFrame:
-                NSMakeRect(xthis, ythis-(rheight+lheight)/2,
-                           (wthis + HSPACING) * labelp / 100 - HSPACING,
-                           lheight)];
-               if (editp == 100) {
-                   ynext = ythis - rheight - VSPACING;
-                   rheight = theight;
-               } else {
-                   ynext = ythis;
-               }
-
-               editw = (wthis + HSPACING) * editp / 100 - HSPACING;
-
-               [edit setFrame:
-                NSMakeRect(xthis+wthis-editw, ynext-(rheight+theight)/2,
-                           editw, theight)];
-
-               ch = (ythis - ynext) + theight;
-           }
-           break;
-          case CTRL_TEXT:
-           [c->textview setFrame:NSMakeRect(xthis, 0, wthis, 1)];
-           [c->textview sizeToFit];
-           rect = [c->textview frame];
-           [c->textview setFrame:NSMakeRect(xthis, ythis-rect.size.height,
-                                            wthis, rect.size.height)];
-           ch = rect.size.height;
-           break;
-         case CTRL_RADIO:
-           {
-               int j, ynext;
-
-               if (c->label) {
-                   rect = [c->label frame];
-                   [c->label setFrame:NSMakeRect(xthis,ythis-rect.size.height,
-                                                 wthis,rect.size.height)];
-                   ynext = ythis - rect.size.height - VSPACING;
-               } else
-                   ynext = ythis;
-
-               for (j = 0; j < ctrl->radio.nbuttons; j++) {
-                   int col = j % ctrl->radio.ncolumns;
-                   int ncols;
-                   int lx,rx;
-
-                   if (j == ctrl->radio.nbuttons - 1)
-                       ncols = ctrl->radio.ncolumns - col;
-                   else
-                       ncols = 1;
-
-                   lx = (wthis + HSPACING) * col / ctrl->radio.ncolumns;
-                   rx = ((wthis + HSPACING) *
-                         (col+ncols) / ctrl->radio.ncolumns) - HSPACING;
-
-                   /*
-                    * Set the frame size.
-                    */
-                   rect = [c->radiobuttons[j] frame];
-                   [c->radiobuttons[j] setFrame:
-                    NSMakeRect(lx+xthis, ynext-rect.size.height,
-                               rx-lx, rect.size.height)];
-
-                   /*
-                    * Advance to next line if we're in the last
-                    * column.
-                    */
-                   if (col + ncols == ctrl->radio.ncolumns)
-                       ynext -= rect.size.height + VSPACING;
-               }
-               ch = (ythis - ynext) - VSPACING;
-           }
-           break;
-         case CTRL_FILESELECT:
-         case CTRL_FONTSELECT:
-           {
-               int ynext, eh, bh, th, mx;
-
-               rect = [c->label frame];
-               [c->label setFrame:NSMakeRect(xthis,ythis-rect.size.height,
-                                             wthis,rect.size.height)];
-               ynext = ythis - rect.size.height - VSPACING;
-
-               rect = [c->editbox frame];
-               eh = rect.size.height;
-               rect = [c->button frame];
-               bh = rect.size.height;
-               th = (eh > bh ? eh : bh);
-
-               mx = (wthis + HSPACING) * 3 / 4 - HSPACING;
-
-               [c->editbox setFrame:
-                NSMakeRect(xthis, ynext-(th+eh)/2, mx, eh)];
-               [c->button setFrame:
-                NSMakeRect(xthis+mx+HSPACING, ynext-(th+bh)/2,
-                           wthis-mx-HSPACING, bh)];
-
-               ch = (ythis - ynext) + th + VSPACING;
-           }
-           break;
-         case CTRL_LISTBOX:
-           {
-               int listp = ctrl->listbox.percentwidth;
-               int labelp = listp == 100 ? 100 : 100 - listp;
-               int lheight, theight, rheight, ynext, listw, xlist;
-               NSControl *list = (c->scrollview ? (id)c->scrollview :
-                                  (id)c->popupbutton);
-
-               if (ctrl->listbox.draglist) {
-                   assert(listp == 100);
-                   listp = 75;
-               }
-
-               rect = [list frame];
-               theight = rect.size.height;
-
-               if (c->label) {
-                   rect = [c->label frame];
-                   lheight = rect.size.height;
-
-                   if (labelp == 100)
-                       rheight = lheight;
-                   else
-                       rheight = (lheight < theight ? theight : lheight);
-
-                   [c->label setFrame:
-                    NSMakeRect(xthis, ythis-(rheight+lheight)/2,
-                               (wthis + HSPACING) * labelp / 100 - HSPACING,
-                               lheight)];
-                   if (labelp == 100) {
-                       ynext = ythis - rheight - VSPACING;
-                       rheight = theight;
-                   } else {
-                       ynext = ythis;
-                   }
-               } else {
-                   ynext = ythis;
-                   rheight = theight;
-               }
-
-               listw = (wthis + HSPACING) * listp / 100 - HSPACING;
-
-               if (labelp == 100)
-                   xlist = xthis;
-               else
-                   xlist = xthis+wthis-listw;
-
-               [list setFrame: NSMakeRect(xlist, ynext-(rheight+theight)/2,
-                                          listw, theight)];
-
-               /*
-                * Size the columns for the table view.
-                */
-               if (c->tableview) {
-                   int ncols, *percentages;
-                   int hundred = 100;
-                   int cpercent = 0, cpixels = 0;
-                   NSArray *cols;
-
-                   if (ctrl->listbox.ncols) {
-                       ncols = ctrl->listbox.ncols;
-                       percentages = ctrl->listbox.percentages;
-                   } else {
-                       ncols = 1;
-                       percentages = &hundred;
-                   }
-
-                   cols = [c->tableview tableColumns];
-
-                   for (j = 0; j < ncols; j++) {
-                       NSTableColumn *col = [cols objectAtIndex:j];
-                       int newcpixels;
-
-                       cpercent += percentages[j];
-                       newcpixels = listw * cpercent / 100;
-                       [col setWidth:newcpixels-cpixels];
-                       cpixels = newcpixels;
-                   }
-               }
-
-               ch = (ythis - ynext) + theight;
-
-               if (c->button) {
-                   int b2height, centre;
-                   int bx, bw;
-
-                   /*
-                    * Place the Up and Down buttons for a drag list.
-                    */
-                   assert(c->button2);
-
-                   rect = [c->button frame];
-                   b2height = VSPACING + 2 * rect.size.height;
-
-                   centre = ynext - rheight/2;
-
-                   bx = (wthis + HSPACING) * 3 / 4;
-                   bw = wthis - bx;
-                   bx += leftx;
-
-                   [c->button setFrame:
-                    NSMakeRect(bx, centre+b2height/2-rect.size.height,
-                               bw, rect.size.height)];
-                   [c->button2 setFrame:
-                    NSMakeRect(bx, centre-b2height/2,
-                               bw, rect.size.height)];
-               }
-           }
-           break;
-       }
-
-       for (j = colstart; j < colend; j++)
-           cypos[j] = ythis - ch - VSPACING;
-       if (ret < topy - (ythis - ch))
-           ret = topy - (ythis - ch);
-    }
-
-    if (*s->boxname) {
-       NSBox *box = find_box(d, s);
-       assert(box != NULL);
-       [box sizeToFit];
-
-       if (s->boxtitle) {
-           NSRect rect = [box frame];
-           rect.size.height += [[box titleFont] pointSize];
-           [box setFrame:rect];
-       }
-
-       ret += boxh;
-    }
-
-    //printf("For controlset %s/%s, returning ret=%d\n",
-    //       s->pathname, s->boxname, ret);
-    return ret;
-}
-
-void select_panel(void *dv, struct controlbox *b, const char *name)
-{
-    struct fe_dlg *d = (struct fe_dlg *)dv;
-    int i, j, hidden;
-    struct controlset *s;
-    union control *ctrl;
-    struct fe_ctrl *c;
-    NSBox *box;
-
-    for (i = 0; i < b->nctrlsets; i++) {
-       s = b->ctrlsets[i];
-
-       if (*s->pathname) {
-           hidden = !strcmp(s->pathname, name) ? NO : YES;
-
-           if ((box = find_box(d, s)) != NULL) {
-               [box setHidden:hidden];
-           } else {
-               for (j = 0; j < s->ncontrols; j++) {
-                   ctrl = s->ctrls[j];
-                   c = fe_ctrl_byctrl(d, ctrl);
-
-                   if (!c)
-                       continue;
-
-                   if (c->label)
-                       [c->label setHidden:hidden];
-                   if (c->button)
-                       [c->button setHidden:hidden];
-                   if (c->button2)
-                       [c->button2 setHidden:hidden];
-                   if (c->editbox)
-                       [c->editbox setHidden:hidden];
-                   if (c->combobox)
-                       [c->combobox setHidden:hidden];
-                   if (c->textview)
-                       [c->textview setHidden:hidden];
-                   if (c->tableview)
-                       [c->tableview setHidden:hidden];
-                   if (c->scrollview)
-                       [c->scrollview setHidden:hidden];
-                   if (c->popupbutton)
-                       [c->popupbutton setHidden:hidden];
-                   if (c->radiobuttons) {
-                       int j;
-                       for (j = 0; j < c->nradiobuttons; j++)
-                           [c->radiobuttons[j] setHidden:hidden];
-                   }
-                   break;
-               }
-           }
-       }
-    }
-}
-
-void dlg_radiobutton_set(union control *ctrl, void *dv, int whichbutton)
-{
-    struct fe_dlg *d = (struct fe_dlg *)dv;
-    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
-    int j;
-
-    assert(c->radiobuttons);
-    for (j = 0; j < c->nradiobuttons; j++)
-       [c->radiobuttons[j] setState:
-        (j == whichbutton ? NSOnState : NSOffState)];
-}
-
-int dlg_radiobutton_get(union control *ctrl, void *dv)
-{
-    struct fe_dlg *d = (struct fe_dlg *)dv;
-    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
-    int j;
-
-    assert(c->radiobuttons);
-    for (j = 0; j < c->nradiobuttons; j++)
-       if ([c->radiobuttons[j] state] == NSOnState)
-           return j;
-
-    return 0;                         /* should never reach here */
-}
-
-void dlg_checkbox_set(union control *ctrl, void *dv, int checked)
-{
-    struct fe_dlg *d = (struct fe_dlg *)dv;
-    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
-
-    assert(c->button);
-    [c->button setState:(checked ? NSOnState : NSOffState)];
-}
-
-int dlg_checkbox_get(union control *ctrl, void *dv)
-{
-    struct fe_dlg *d = (struct fe_dlg *)dv;
-    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
-
-    assert(c->button);
-    return ([c->button state] == NSOnState);
-}
-
-void dlg_editbox_set(union control *ctrl, void *dv, char const *text)
-{
-    struct fe_dlg *d = (struct fe_dlg *)dv;
-    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
-
-    if (c->editbox) {
-       [c->editbox setStringValue:[NSString stringWithCString:text]];
-    } else {
-       assert(c->combobox);
-       [c->combobox setStringValue:[NSString stringWithCString:text]];
-    }
-}
-
-void dlg_editbox_get(union control *ctrl, void *dv, char *buffer, int length)
-{
-    struct fe_dlg *d = (struct fe_dlg *)dv;
-    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
-    NSString *str;
-
-    if (c->editbox) {
-       str = [c->editbox stringValue];
-    } else {
-       assert(c->combobox);
-       str = [c->combobox stringValue];
-    }
-    if (!str)
-       str = @"";
-
-    /* The length parameter to this method doesn't include a trailing NUL */
-    [str getCString:buffer maxLength:length-1];
-}
-
-void dlg_listbox_clear(union control *ctrl, void *dv)
-{
-    struct fe_dlg *d = (struct fe_dlg *)dv;
-    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
-
-    if (c->tableview) {
-       [[c->tableview dataSource] clear];
-       [c->tableview reloadData];
-    } else {
-       [c->popupbutton removeAllItems];
-    }
-}
-
-void dlg_listbox_del(union control *ctrl, void *dv, int index)
-{
-    struct fe_dlg *d = (struct fe_dlg *)dv;
-    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
-
-    if (c->tableview) {
-       [[c->tableview dataSource] removestr:index];
-       [c->tableview reloadData];
-    } else {
-       [c->popupbutton removeItemAtIndex:index];
-    }
-}
-
-void dlg_listbox_addwithid(union control *ctrl, void *dv,
-                          char const *text, int id)
-{
-    struct fe_dlg *d = (struct fe_dlg *)dv;
-    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
-
-    if (c->tableview) {
-       [[c->tableview dataSource] add:text withId:id];
-       [c->tableview reloadData];
-    } else {
-       [c->popupbutton addItemWithTitle:[NSString stringWithCString:text]];
-       [[c->popupbutton lastItem] setTag:id];
-    }
-}
-
-void dlg_listbox_add(union control *ctrl, void *dv, char const *text)
-{
-    dlg_listbox_addwithid(ctrl, dv, text, -1);
-}
-
-int dlg_listbox_getid(union control *ctrl, void *dv, int index)
-{
-    struct fe_dlg *d = (struct fe_dlg *)dv;
-    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
-
-    if (c->tableview) {
-       return [[c->tableview dataSource] getid:index];
-    } else {
-       return [[c->popupbutton itemAtIndex:index] tag];
-    }
-}
-
-int dlg_listbox_index(union control *ctrl, void *dv)
-{
-    struct fe_dlg *d = (struct fe_dlg *)dv;
-    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
-
-    if (c->tableview) {
-       return [c->tableview selectedRow];
-    } else {
-       return [c->popupbutton indexOfSelectedItem];
-    }
-}
-
-int dlg_listbox_issel(union control *ctrl, void *dv, int index)
-{
-    struct fe_dlg *d = (struct fe_dlg *)dv;
-    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
-
-    if (c->tableview) {
-       return [c->tableview isRowSelected:index];
-    } else {
-       return [c->popupbutton indexOfSelectedItem] == index;
-    }
-}
-
-void dlg_listbox_select(union control *ctrl, void *dv, int index)
-{
-    struct fe_dlg *d = (struct fe_dlg *)dv;
-    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
-
-    if (c->tableview) {
-       [c->tableview selectRow:index byExtendingSelection:NO];
-    } else {
-       [c->popupbutton selectItemAtIndex:index];
-    }
-}
-
-void dlg_text_set(union control *ctrl, void *dv, char const *text)
-{
-    struct fe_dlg *d = (struct fe_dlg *)dv;
-    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
-
-    assert(c->textview);
-    [c->textview setString:[NSString stringWithCString:text]];
-}
-
-void dlg_label_change(union control *ctrl, void *dlg, char const *text)
-{
-    /*
-     * This function is currently only used by the config box to
-     * switch the labels on the host and port boxes between serial
-     * and network modes. Since OS X does not (yet?) have a serial
-     * back end, this function can safely do nothing for the
-     * moment.
-     */
-}
-
-void dlg_filesel_set(union control *ctrl, void *dv, Filename fn)
-{
-    /* FIXME */
-}
-
-void dlg_filesel_get(union control *ctrl, void *dv, Filename *fn)
-{
-    /* FIXME */
-}
-
-void dlg_fontsel_set(union control *ctrl, void *dv, FontSpec fn)
-{
-    /* FIXME */
-}
-
-void dlg_fontsel_get(union control *ctrl, void *dv, FontSpec *fn)
-{
-    /* FIXME */
-}
-
-void dlg_update_start(union control *ctrl, void *dv)
-{
-    /* FIXME */
-}
-
-void dlg_update_done(union control *ctrl, void *dv)
-{
-    /* FIXME */
-}
-
-void dlg_set_focus(union control *ctrl, void *dv)
-{
-    /* FIXME */
-}
-
-union control *dlg_last_focused(union control *ctrl, void *dv)
-{
-    return NULL; /* FIXME */
-}
-
-void dlg_beep(void *dv)
-{
-    NSBeep();
-}
-
-void dlg_error_msg(void *dv, const char *msg)
-{
-    /* FIXME */
-}
-
-void dlg_end(void *dv, int value)
-{
-    struct fe_dlg *d = (struct fe_dlg *)dv;
-    [d->target performSelector:d->action
-     withObject:[NSNumber numberWithInt:value]];
-}
-
-void dlg_coloursel_start(union control *ctrl, void *dv,
-                        int r, int g, int b)
-{
-    /* FIXME */
-}
-
-int dlg_coloursel_results(union control *ctrl, void *dv,
-                         int *r, int *g, int *b)
-{
-    return 0; /* FIXME */
-}
-
-void dlg_refresh(union control *ctrl, void *dv)
-{
-    struct fe_dlg *d = (struct fe_dlg *)dv;
-    struct fe_ctrl *c;
-
-    if (ctrl) {
-       if (ctrl->generic.handler != NULL)
-           ctrl->generic.handler(ctrl, d, d->data, EVENT_REFRESH);
-    } else {
-       int i;
-
-       for (i = 0; (c = index234(d->byctrl, i)) != NULL; i++) {
-           assert(c->ctrl != NULL);
-           if (c->ctrl->generic.handler != NULL)
-               c->ctrl->generic.handler(c->ctrl, d,
-                                        d->data, EVENT_REFRESH);
-       }
-    }
-}
diff --git a/macosx/osxdlg.m b/macosx/osxdlg.m
deleted file mode 100644 (file)
index 84a761f..0000000
+++ /dev/null
@@ -1,509 +0,0 @@
-/*
- * osxdlg.m: various PuTTY dialog boxes for OS X.
- */
-
-#import <Cocoa/Cocoa.h>
-#include "putty.h"
-#include "storage.h"
-#include "dialog.h"
-#include "osxclass.h"
-
-/*
- * The `ConfigWindow' class is used to start up a new PuTTY
- * session.
- */
-
-@class ConfigTree;
-@interface ConfigTree : NSObject
-{
-    NSString **paths;
-    int *levels;
-    int nitems, itemsize;
-}
-- (void)addPath:(char *)path;
-@end
-
-@implementation ConfigTree
-- (id)init
-{
-    self = [super init];
-    paths = NULL;
-    levels = NULL;
-    nitems = itemsize = 0;
-    return self;
-}
-- (void)addPath:(char *)path
-{
-    if (nitems >= itemsize) {
-       itemsize += 32;
-       paths = sresize(paths, itemsize, NSString *);
-       levels = sresize(levels, itemsize, int);
-    }
-    paths[nitems] = [[NSString stringWithCString:path] retain];
-    levels[nitems] = ctrl_path_elements(path) - 1;
-    nitems++;
-}
-- (void)dealloc
-{
-    int i;
-
-    for (i = 0; i < nitems; i++)
-       [paths[i] release];
-
-    sfree(paths);
-    sfree(levels);
-
-    [super dealloc];
-}
-- (id)iterateChildren:(int)index ofItem:(id)item count:(int *)count
-{
-    int i, plevel;
-
-    if (item) {
-       for (i = 0; i < nitems; i++)
-           if (paths[i] == item)
-               break;
-       assert(i < nitems);
-       plevel = levels[i];
-       i++;
-    } else {
-       i = 0;
-       plevel = -1;
-    }
-
-    if (count)
-       *count = 0;
-
-    while (index > 0) {
-       if (i >= nitems || levels[i] != plevel+1)
-           return nil;
-       if (count)
-           (*count)++;
-       do {
-           i++;
-       } while (i < nitems && levels[i] > plevel+1);
-       index--;
-    }
-
-    return paths[i];
-}
-- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
-{
-    return [self iterateChildren:index ofItem:item count:NULL];
-}
-- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
-{
-    int count = 0;
-    /* pass nitems+1 to ensure we run off the end */
-    [self iterateChildren:nitems+1 ofItem:item count:&count];
-    return count;
-}
-- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
-{
-    return [self outlineView:outlineView numberOfChildrenOfItem:item] > 0;
-}
-- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
-{
-    /*
-     * Trim off all path elements except the last one.
-     */
-    NSArray *components = [item componentsSeparatedByString:@"/"];
-    return [components objectAtIndex:[components count]-1];
-}
-@end
-
-@implementation ConfigWindow
-- (id)initWithConfig:(Config)aCfg
-{
-    NSScrollView *scrollview;
-    NSTableColumn *col;
-    ConfigTree *treedata;
-    int by = 0, mby = 0;
-    int wmin = 0;
-    int hmin = 0;
-    int panelht = 0;
-
-    ctrlbox = ctrl_new_box();
-    setup_config_box(ctrlbox, FALSE /*midsession*/, aCfg.protocol,
-                    0 /* protcfginfo */);
-    unix_setup_config_box(ctrlbox, FALSE /*midsession*/, aCfg.protocol);
-
-    cfg = aCfg;                               /* structure copy */
-
-    self = [super initWithContentRect:NSMakeRect(0,0,300,300)
-           styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask |
-                      NSClosableWindowMask)
-           backing:NSBackingStoreBuffered
-           defer:YES];
-    [self setTitle:@"PuTTY Configuration"];
-
-    [self setIgnoresMouseEvents:NO];
-
-    dv = fe_dlg_init(&cfg, self, self, @selector(configBoxFinished:));
-
-    scrollview = [[NSScrollView alloc] initWithFrame:NSMakeRect(20,20,10,10)];
-    treeview = [[NSOutlineView alloc] initWithFrame:[scrollview frame]];
-    [scrollview setBorderType:NSLineBorder];
-    [scrollview setDocumentView:treeview];
-    [[self contentView] addSubview:scrollview];
-    [scrollview setHasVerticalScroller:YES];
-    [scrollview setAutohidesScrollers:YES];
-    /* FIXME: the below is untested. Test it then remove this notice. */
-    [treeview setAllowsColumnReordering:NO];
-    [treeview setAllowsColumnResizing:NO];
-    [treeview setAllowsMultipleSelection:NO];
-    [treeview setAllowsEmptySelection:NO];
-    [treeview setAllowsColumnSelection:YES];
-
-    treedata = [[[ConfigTree alloc] init] retain];
-
-    col = [[NSTableColumn alloc] initWithIdentifier:nil];
-    [treeview addTableColumn:col];
-    [treeview setOutlineTableColumn:col];
-
-    [[treeview headerView] setFrame:NSMakeRect(0,0,0,0)];
-
-    /*
-     * Create the controls.
-     */
-    {
-       int i;
-       char *path = NULL;
-
-       for (i = 0; i < ctrlbox->nctrlsets; i++) {
-           struct controlset *s = ctrlbox->ctrlsets[i];
-           int mw, mh;
-
-           if (!*s->pathname) {
-
-               create_ctrls(dv, [self contentView], s, &mw, &mh);
-
-               by += 20 + mh;
-
-               if (wmin < mw + 40)
-                   wmin = mw + 40;
-           } else {
-               int j = path ? ctrl_path_compare(s->pathname, path) : 0;
-
-               if (j != INT_MAX) {    /* add to treeview, start new panel */
-                   char *c;
-
-                   /*
-                    * We expect never to find an implicit path
-                    * component. For example, we expect never to
-                    * see A/B/C followed by A/D/E, because that
-                    * would _implicitly_ create A/D. All our path
-                    * prefixes are expected to contain actual
-                    * controls and be selectable in the treeview;
-                    * so we would expect to see A/D _explicitly_
-                    * before encountering A/D/E.
-                    */
-                   assert(j == ctrl_path_elements(s->pathname) - 1);
-
-                   c = strrchr(s->pathname, '/');
-                   if (!c)
-                       c = s->pathname;
-                   else
-                       c++;
-
-                   [treedata addPath:s->pathname];
-                   path = s->pathname;
-
-                   panelht = 0;
-               }
-
-               create_ctrls(dv, [self contentView], s, &mw, &mh);
-               if (wmin < mw + 3*20+150)
-                   wmin = mw + 3*20+150;
-               panelht += mh + 20;
-               if (hmin < panelht - 20)
-                   hmin = panelht - 20;
-           }
-       }
-    }
-
-    {
-       int i;
-       NSRect r;
-
-       [treeview setDataSource:treedata];
-       for (i = [treeview numberOfRows]; i-- ;)
-           [treeview expandItem:[treeview itemAtRow:i] expandChildren:YES];
-
-       [treeview sizeToFit];
-       r = [treeview frame];
-       if (hmin < r.size.height)
-           hmin = r.size.height;
-    }
-
-    [self setContentSize:NSMakeSize(wmin, hmin+60+by)];
-    [scrollview setFrame:NSMakeRect(20, 40+by, 150, hmin)];
-    [treeview setDelegate:self];
-    mby = by;
-
-    /*
-     * Now place the controls.
-     */
-    {
-       int i;
-       char *path = NULL;
-       panelht = 0;
-
-       for (i = 0; i < ctrlbox->nctrlsets; i++) {
-           struct controlset *s = ctrlbox->ctrlsets[i];
-
-           if (!*s->pathname) {
-               by -= VSPACING + place_ctrls(dv, s, 20, by, wmin-40);
-           } else {
-               if (!path || strcmp(s->pathname, path))
-                   panelht = 0;
-
-               panelht += VSPACING + place_ctrls(dv, s, 2*20+150,
-                                                 40+mby+hmin-panelht,
-                                                 wmin - (3*20+150));
-
-               path = s->pathname;
-           }
-       }
-    }
-
-    select_panel(dv, ctrlbox, [[treeview itemAtRow:0] cString]);
-
-    [treeview reloadData];
-
-    dlg_refresh(NULL, dv);
-
-    [self center];                    /* :-) */
-
-    return self;
-}
-- (void)configBoxFinished:(id)object
-{
-    int ret = [object intValue];       /* it'll be an NSNumber */
-    if (ret) {
-       [controller performSelectorOnMainThread:
-        @selector(newSessionWithConfig:)
-        withObject:[NSData dataWithBytes:&cfg length:sizeof(cfg)]
-        waitUntilDone:NO];
-    }
-    [self close];
-}
-- (void)outlineViewSelectionDidChange:(NSNotification *)notification
-{
-    const char *path = [[treeview itemAtRow:[treeview selectedRow]] cString];
-    select_panel(dv, ctrlbox, path);
-}
-- (BOOL)outlineView:(NSOutlineView *)outlineView
-    shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
-{
-    return NO;                        /* no editing! */
-}
-@end
-
-/* ----------------------------------------------------------------------
- * Various special-purpose dialog boxes.
- */
-
-struct appendstate {
-    void (*callback)(void *ctx, int result);
-    void *ctx;
-};
-
-static void askappend_callback(void *ctx, int result)
-{
-    struct appendstate *state = (struct appendstate *)ctx;
-
-    state->callback(state->ctx, (result == NSAlertFirstButtonReturn ? 2 :
-                                result == NSAlertSecondButtonReturn ? 1 : 0));
-    sfree(state);
-}
-
-int askappend(void *frontend, Filename filename,
-             void (*callback)(void *ctx, int result), void *ctx)
-{
-    static const char msgtemplate[] =
-       "The session log file \"%s\" already exists. "
-       "You can overwrite it with a new session log, "
-       "append your session log to the end of it, "
-       "or disable session logging for this session.";
-
-    char *text;
-    SessionWindow *win = (SessionWindow *)frontend;
-    struct appendstate *state;
-    NSAlert *alert;
-
-    text = dupprintf(msgtemplate, filename.path);
-
-    state = snew(struct appendstate);
-    state->callback = callback;
-    state->ctx = ctx;
-
-    alert = [[NSAlert alloc] init];
-    [alert setInformativeText:[NSString stringWithCString:text]];
-    [alert addButtonWithTitle:@"Overwrite"];
-    [alert addButtonWithTitle:@"Append"];
-    [alert addButtonWithTitle:@"Disable"];
-    [win startAlert:alert withCallback:askappend_callback andCtx:state];
-
-    return -1;
-}
-
-struct algstate {
-    void (*callback)(void *ctx, int result);
-    void *ctx;
-};
-
-static void askalg_callback(void *ctx, int result)
-{
-    struct algstate *state = (struct algstate *)ctx;
-
-    state->callback(state->ctx, result == NSAlertFirstButtonReturn);
-    sfree(state);
-}
-
-int askalg(void *frontend, const char *algtype, const char *algname,
-          void (*callback)(void *ctx, int result), void *ctx)
-{
-    static const char msg[] =
-       "The first %s supported by the server is "
-       "%s, which is below the configured warning threshold.\n"
-       "Continue with connection?";
-
-    char *text;
-    SessionWindow *win = (SessionWindow *)frontend;
-    struct algstate *state;
-    NSAlert *alert;
-
-    text = dupprintf(msg, algtype, algname);
-
-    state = snew(struct algstate);
-    state->callback = callback;
-    state->ctx = ctx;
-
-    alert = [[NSAlert alloc] init];
-    [alert setInformativeText:[NSString stringWithCString:text]];
-    [alert addButtonWithTitle:@"Yes"];
-    [alert addButtonWithTitle:@"No"];
-    [win startAlert:alert withCallback:askalg_callback andCtx:state];
-
-    return -1;
-}
-
-struct hostkeystate {
-    char *host, *keytype, *keystr;
-    int port;
-    void (*callback)(void *ctx, int result);
-    void *ctx;
-};
-
-static void verify_ssh_host_key_callback(void *ctx, int result)
-{
-    struct hostkeystate *state = (struct hostkeystate *)ctx;
-
-    if (result == NSAlertThirdButtonReturn)   /* `Accept' */
-       store_host_key(state->host, state->port,
-                      state->keytype, state->keystr);
-    state->callback(state->ctx, result != NSAlertFirstButtonReturn);
-    sfree(state->host);
-    sfree(state->keytype);
-    sfree(state->keystr);
-    sfree(state);
-}
-
-int verify_ssh_host_key(void *frontend, char *host, int port,
-                        const char *keytype, char *keystr, char *fingerprint,
-                        void (*callback)(void *ctx, int result), void *ctx)
-{
-    static const char absenttxt[] =
-       "The server's host key is not cached. You have no guarantee "
-       "that the server is the computer you think it is.\n"
-       "The server's %s key fingerprint is:\n"
-       "%s\n"
-       "If you trust this host, press \"Accept\" to add the key to "
-       "PuTTY's cache and carry on connecting.\n"
-       "If you want to carry on connecting just once, without "
-       "adding the key to the cache, press \"Connect Once\".\n"
-       "If you do not trust this host, press \"Cancel\" to abandon the "
-       "connection.";
-    static const char wrongtxt[] =
-       "WARNING - POTENTIAL SECURITY BREACH!\n"
-       "The server's host key does not match the one PuTTY has "
-       "cached. This means that either the server administrator "
-       "has changed the host key, or you have actually connected "
-       "to another computer pretending to be the server.\n"
-       "The new %s key fingerprint is:\n"
-       "%s\n"
-       "If you were expecting this change and trust the new key, "
-       "press \"Accept\" to update PuTTY's cache and continue connecting.\n"
-       "If you want to carry on connecting but without updating "
-       "the cache, press \"Connect Once\".\n"
-       "If you want to abandon the connection completely, press "
-       "\"Cancel\" to cancel. Pressing \"Cancel\" is the ONLY guaranteed "
-       "safe choice.";
-
-    int ret;
-    char *text;
-    SessionWindow *win = (SessionWindow *)frontend;
-    struct hostkeystate *state;
-    NSAlert *alert;
-
-    /*
-     * Verify the key.
-     */
-    ret = verify_host_key(host, port, keytype, keystr);
-
-    if (ret == 0)
-       return 1;
-
-    text = dupprintf((ret == 2 ? wrongtxt : absenttxt), keytype, fingerprint);
-
-    state = snew(struct hostkeystate);
-    state->callback = callback;
-    state->ctx = ctx;
-    state->host = dupstr(host);
-    state->port = port;
-    state->keytype = dupstr(keytype);
-    state->keystr = dupstr(keystr);
-
-    alert = [[NSAlert alloc] init];
-    [alert setInformativeText:[NSString stringWithCString:text]];
-    [alert addButtonWithTitle:@"Cancel"];
-    [alert addButtonWithTitle:@"Connect Once"];
-    [alert addButtonWithTitle:@"Accept"];
-    [win startAlert:alert withCallback:verify_ssh_host_key_callback
-     andCtx:state];
-
-    return -1;
-}
-
-void old_keyfile_warning(void)
-{
-    /*
-     * This should never happen on OS X. We hope.
-     */
-}
-
-static void connection_fatal_callback(void *ctx, int result)
-{
-    SessionWindow *win = (SessionWindow *)ctx;
-
-    [win endSession:FALSE];
-}
-
-void connection_fatal(void *frontend, const char *p, ...)
-{
-    SessionWindow *win = (SessionWindow *)frontend;
-    va_list ap;
-    char *msg;
-    NSAlert *alert;
-
-    va_start(ap, p);
-    msg = dupvprintf(p, ap);
-    va_end(ap);
-
-    alert = [[NSAlert alloc] init];
-    [alert setInformativeText:[NSString stringWithCString:msg]];
-    [alert addButtonWithTitle:@"Proceed"];
-    [win startAlert:alert withCallback:connection_fatal_callback
-     andCtx:win];
-}
diff --git a/macosx/osxmain.m b/macosx/osxmain.m
deleted file mode 100644 (file)
index 2ac772a..0000000
+++ /dev/null
@@ -1,426 +0,0 @@
-/*
- * osxmain.m: main-program file of Mac OS X PuTTY.
- */
-
-#import <Cocoa/Cocoa.h>
-
-#define PUTTY_DO_GLOBALS              /* actually _define_ globals */
-
-#include "putty.h"
-#include "osxclass.h"
-
-/* ----------------------------------------------------------------------
- * Global variables.
- */
-
-AppController *controller;
-
-/* ----------------------------------------------------------------------
- * Miscellaneous elements of the interface to the cross-platform
- * and Unix PuTTY code.
- */
-
-char *platform_get_x_display(void) {
-    return NULL;
-}
-
-FontSpec platform_default_fontspec(const char *name)
-{
-    FontSpec ret;
-    /* FIXME */
-    return ret;
-}
-
-Filename platform_default_filename(const char *name)
-{
-    Filename ret;
-    if (!strcmp(name, "LogFileName"))
-       strcpy(ret.path, "putty.log");
-    else
-       *ret.path = '\0';
-    return ret;
-}
-
-char *platform_default_s(const char *name)
-{
-    return NULL;
-}
-
-int platform_default_i(const char *name, int def)
-{
-    if (!strcmp(name, "CloseOnExit"))
-       return 2;  /* maps to FORCE_ON after painful rearrangement :-( */
-    return def;
-}
-
-char *x_get_default(const char *key)
-{
-    return NULL;                      /* this is a stub */
-}
-
-static void commonfatalbox(const char *p, va_list ap)
-{
-    char errorbuf[2048];
-    NSAlert *alert;
-
-    /*
-     * We may have come here because we ran out of memory, in which
-     * case it's entirely likely that that further memory
-     * allocations will fail. So (a) we use vsnprintf to format the
-     * error message rather than the usual dupvprintf; and (b) we
-     * have a fallback way to get the message out via stderr if
-     * even creating an NSAlert fails.
-     */
-    vsnprintf(errorbuf, lenof(errorbuf), p, ap);
-
-    alert = [NSAlert alloc];
-    if (!alert) {
-       fprintf(stderr, "fatal error (and NSAlert failed): %s\n", errorbuf);
-    } else {
-       alert = [[alert init] autorelease];
-       [alert addButtonWithTitle:@"Terminate"];
-       [alert setInformativeText:[NSString stringWithCString:errorbuf]];
-       [alert runModal];
-    }
-    exit(1);
-}
-
-void nonfatal(void *frontend, const char *p, ...)
-{
-    char *errorbuf;
-    NSAlert *alert;
-    va_list ap;
-
-    va_start(ap, p);
-    errorbuf = dupvprintf(p, ap);
-    va_end(ap);
-
-    alert = [[[NSAlert alloc] init] autorelease];
-    [alert addButtonWithTitle:@"Error"];
-    [alert setInformativeText:[NSString stringWithCString:errorbuf]];
-    [alert runModal];
-
-    sfree(errorbuf);
-}
-
-void fatalbox(const char *p, ...)
-{
-    va_list ap;
-    va_start(ap, p);
-    commonfatalbox(p, ap);
-    va_end(ap);
-}
-
-void modalfatalbox(const char *p, ...)
-{
-    va_list ap;
-    va_start(ap, p);
-    commonfatalbox(p, ap);
-    va_end(ap);
-}
-
-void cmdline_error(const char *p, ...)
-{
-    va_list ap;
-    fprintf(stderr, "%s: ", appname);
-    va_start(ap, p);
-    vfprintf(stderr, p, ap);
-    va_end(ap);
-    fputc('\n', stderr);
-    exit(1);
-}
-
-/*
- * Clean up and exit.
- */
-void cleanup_exit(int code)
-{
-    /*
-     * Clean up.
-     */
-    sk_cleanup();
-    random_save_seed();
-    exit(code);
-}
-
-/* ----------------------------------------------------------------------
- * Tiny extension to NSMenuItem which carries a payload of a `void
- * *', allowing several menu items to invoke the same message but
- * pass different data through it.
- */
-@interface DataMenuItem : NSMenuItem
-{
-    void *payload;
-}
-- (void)setPayload:(void *)d;
-- (void *)getPayload;
-@end
-@implementation DataMenuItem
-- (void)setPayload:(void *)d
-{
-    payload = d;
-}
-- (void *)getPayload
-{
-    return payload;
-}
-@end
-
-/* ----------------------------------------------------------------------
- * Utility routines for constructing OS X menus.
- */
-
-NSMenu *newmenu(const char *title)
-{
-    return [[[NSMenu allocWithZone:[NSMenu menuZone]]
-            initWithTitle:[NSString stringWithCString:title]]
-           autorelease];
-}
-
-NSMenu *newsubmenu(NSMenu *parent, const char *title)
-{
-    NSMenuItem *item;
-    NSMenu *child;
-
-    item = [[[NSMenuItem allocWithZone:[NSMenu menuZone]]
-            initWithTitle:[NSString stringWithCString:title]
-            action:NULL
-            keyEquivalent:@""]
-           autorelease];
-    child = newmenu(title);
-    [item setEnabled:YES];
-    [item setSubmenu:child];
-    [parent addItem:item];
-    return child;
-}
-
-id initnewitem(NSMenuItem *item, NSMenu *parent, const char *title,
-              const char *key, id target, SEL action)
-{
-    unsigned mask = NSCommandKeyMask;
-
-    if (key[strcspn(key, "-")]) {
-       while (*key && *key != '-') {
-           int c = tolower((unsigned char)*key);
-           if (c == 's') {
-               mask |= NSShiftKeyMask;
-           } else if (c == 'o' || c == 'a') {
-               mask |= NSAlternateKeyMask;
-           }
-           key++;
-       }
-       if (*key)
-           key++;
-    }
-
-    item = [[item initWithTitle:[NSString stringWithCString:title]
-            action:NULL
-            keyEquivalent:[NSString stringWithCString:key]]
-           autorelease];
-
-    if (*key)
-       [item setKeyEquivalentModifierMask: mask];
-
-    [item setEnabled:YES];
-    [item setTarget:target];
-    [item setAction:action];
-
-    [parent addItem:item];
-
-    return item;
-}
-
-NSMenuItem *newitem(NSMenu *parent, char *title, char *key,
-                   id target, SEL action)
-{
-    return initnewitem([NSMenuItem allocWithZone:[NSMenu menuZone]],
-                      parent, title, key, target, action);
-}
-
-/* ----------------------------------------------------------------------
- * AppController: the object which receives the messages from all
- * menu selections that aren't standard OS X functions.
- */
-@implementation AppController
-
-- (id)init
-{
-    self = [super init];
-    timer = NULL;
-    return self;
-}
-
-- (void)newTerminal:(id)sender
-{
-    id win;
-    Config cfg;
-
-    do_defaults(NULL, &cfg);
-
-    cfg.protocol = -1;                /* PROT_TERMINAL */
-
-    win = [[SessionWindow alloc] initWithConfig:cfg];
-    [win makeKeyAndOrderFront:self];
-}
-
-- (void)newSessionConfig:(id)sender
-{
-    id win;
-    Config cfg;
-
-    do_defaults(NULL, &cfg);
-
-    win = [[ConfigWindow alloc] initWithConfig:cfg];
-    [win makeKeyAndOrderFront:self];
-}
-
-- (void)newSessionWithConfig:(id)vdata
-{
-    id win;
-    Config cfg;
-    NSData *data = (NSData *)vdata;
-
-    assert([data length] == sizeof(cfg));
-    [data getBytes:&cfg];
-
-    win = [[SessionWindow alloc] initWithConfig:cfg];
-    [win makeKeyAndOrderFront:self];
-}
-
-- (NSMenu *)applicationDockMenu:(NSApplication *)sender
-{
-    NSMenu *menu = newmenu("Dock Menu");
-    /*
-     * FIXME: Add some useful things to this, probably including
-     * the saved session list.
-     */
-    return menu;
-}
-
-- (void)timerFired:(id)sender
-{
-    long now, next;
-
-    assert(sender == timer);
-
-    /* `sender' is the timer itself, so its userInfo is an NSNumber. */
-    now = [(NSNumber *)[sender userInfo] longValue];
-
-    [sender invalidate];
-
-    timer = NULL;
-
-    if (run_timers(now, &next))
-       [self setTimer:next];
-}
-
-- (void)setTimer:(long)next
-{
-    long interval = next - GETTICKCOUNT();
-    float finterval;
-
-    if (interval <= 0)
-       interval = 1;                  /* just in case */
-
-    finterval = interval / (float)TICKSPERSEC;
-
-    if (timer) {
-       [timer invalidate];
-    }
-
-    timer = [NSTimer scheduledTimerWithTimeInterval:finterval
-            target:self selector:@selector(timerFired:)
-            userInfo:[NSNumber numberWithLong:next] repeats:NO];
-}
-
-@end
-
-void timer_change_notify(long next)
-{
-    [controller setTimer:next];
-}
-
-/* ----------------------------------------------------------------------
- * Annoyingly, it looks as if I have to actually subclass
- * NSApplication if I want to catch NSApplicationDefined events. So
- * here goes.
- */
-@interface MyApplication : NSApplication
-{
-}
-@end
-@implementation MyApplication
-- (void)sendEvent:(NSEvent *)ev
-{
-    if ([ev type] == NSApplicationDefined)
-       osxsel_process_results();
-
-    [super sendEvent:ev];
-}    
-@end
-
-/* ----------------------------------------------------------------------
- * Main program. Constructs the menus and runs the application.
- */
-int main(int argc, char **argv)
-{
-    NSAutoreleasePool *pool;
-    NSMenu *menu;
-    NSMenuItem *item;
-    NSImage *icon;
-
-    pool = [[NSAutoreleasePool alloc] init];
-
-    icon = [NSImage imageNamed:@"NSApplicationIcon"];
-    [MyApplication sharedApplication];
-    [NSApp setApplicationIconImage:icon];
-
-    controller = [[[AppController alloc] init] autorelease];
-    [NSApp setDelegate:controller];
-
-    [NSApp setMainMenu: newmenu("Main Menu")];
-
-    menu = newsubmenu([NSApp mainMenu], "Apple Menu");
-    [NSApp setServicesMenu:newsubmenu(menu, "Services")];
-    [menu addItem:[NSMenuItem separatorItem]];
-    item = newitem(menu, "Hide PuTTY", "h", NSApp, @selector(hide:));
-    item = newitem(menu, "Hide Others", "o-h", NSApp, @selector(hideOtherApplications:));
-    item = newitem(menu, "Show All", "", NSApp, @selector(unhideAllApplications:));
-    [menu addItem:[NSMenuItem separatorItem]];
-    item = newitem(menu, "Quit", "q", NSApp, @selector(terminate:));
-    [NSApp setAppleMenu: menu];
-
-    menu = newsubmenu([NSApp mainMenu], "File");
-    item = newitem(menu, "New", "n", NULL, @selector(newSessionConfig:));
-    item = newitem(menu, "New Terminal", "t", NULL, @selector(newTerminal:));
-    item = newitem(menu, "Close", "w", NULL, @selector(performClose:));
-
-    menu = newsubmenu([NSApp mainMenu], "Window");
-    [NSApp setWindowsMenu: menu];
-    item = newitem(menu, "Minimise Window", "m", NULL, @selector(performMiniaturize:));
-
-//    menu = newsubmenu([NSApp mainMenu], "Help");
-//    item = newitem(menu, "PuTTY Help", "?", NSApp, @selector(showHelp:));
-
-    /*
-     * Start up the sub-thread doing select().
-     */
-    osxsel_init();
-
-    /*
-     * Start up networking.
-     */
-    sk_init();
-
-    /*
-     * FIXME: To make initial debugging more convenient I'm going
-     * to start by opening a session window unconditionally. This
-     * will probably change later on.
-     */
-    [controller newSessionConfig:nil];
-
-    [NSApp run];
-    [pool release];
-
-    return 0;
-}
diff --git a/macosx/osxsel.m b/macosx/osxsel.m
deleted file mode 100644 (file)
index eac6028..0000000
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
- * osxsel.m: OS X implementation of the front end interface to uxsel.
- */
-
-#import <Cocoa/Cocoa.h>
-#include <unistd.h>
-#include "putty.h"
-#include "osxclass.h"
-
-/*
- * The unofficial Cocoa FAQ at
- *
- *   http://www.alastairs-place.net/cocoa/faq.txt
- * 
- * says that Cocoa has the native ability to be given an fd and
- * tell you when it becomes readable, but cannot tell you when it
- * becomes _writable_. This is unacceptable to PuTTY, which depends
- * for correct functioning on being told both. Therefore, I can't
- * use the Cocoa native mechanism.
- * 
- * Instead, I'm going to resort to threads. I start a second thread
- * whose job is to do selects. At the termination of every select,
- * it posts a Cocoa event into the main thread's event queue, so
- * that the main thread gets select results interleaved with other
- * GUI operations. Communication from the main thread _to_ the
- * select thread is performed by writing to a pipe whose other end
- * is one of the file descriptors being selected on. (This is the
- * only sensible way, because we have to be able to interrupt a
- * select in order to provide a new fd list.)
- */
-
-/*
- * In more detail, the select thread must:
- * 
- *  - start off by listening to _just_ the pipe, waiting to be told
- *    to begin a select.
- * 
- *  - when it receives the `start' command, it should read the
- *    shared uxsel data (which is protected by a mutex), set up its
- *    select, and begin it.
- * 
- *  - when the select terminates, it should write the results
- *    (perhaps minus the inter-thread pipe if it's there) into
- *    shared memory and dispatch a GUI event to let the main thread
- *    know.
- * 
- *  - the main thread will then think about it, do some processing,
- *    and _then_ send a command saying `now restart select'. Before
- *    sending that command it might easily have tinkered with the
- *    uxsel structures, which is why it waited before sending it.
- * 
- *  - EOF on the inter-thread pipe, of course, means the process
- *    has finished completely, so the select thread terminates.
- * 
- *  - The main thread may wish to adjust the uxsel settings in the
- *    middle of a select. In this situation it first writes the new
- *    data to the shared memory area, then notifies the select
- *    thread by writing to the inter-thread pipe.
- * 
- * So the upshot is that the sequence of operations performed in
- * the select thread must be:
- * 
- *  - read a byte from the pipe (which may block)
- * 
- *  - read the shared uxsel data and perform a select
- * 
- *  - notify the main thread of interesting select results (if any)
- * 
- *  - loop round again from the top.
- * 
- * This is sufficient. Notifying the select thread asynchronously
- * by writing to the pipe will cause its select to terminate and
- * another to begin immediately without blocking. If the select
- * thread's select terminates due to network data, its subsequent
- * pipe read will block until the main thread is ready to let it
- * loose again.
- */
-
-static int osxsel_pipe[2];
-
-static NSLock *osxsel_inlock;
-static fd_set osxsel_rfds_in;
-static fd_set osxsel_wfds_in;
-static fd_set osxsel_xfds_in;
-static int osxsel_inmax;
-
-static NSLock *osxsel_outlock;
-static fd_set osxsel_rfds_out;
-static fd_set osxsel_wfds_out;
-static fd_set osxsel_xfds_out;
-static int osxsel_outmax;
-
-static int inhibit_start_select;
-
-/*
- * NSThread requires an object method as its thread procedure, so
- * here I define a trivial holding class.
- */
-@class OSXSel;
-@interface OSXSel : NSObject
-{
-}
-- (void)runThread:(id)arg;
-@end
-@implementation OSXSel
-- (void)runThread:(id)arg
-{
-    char c;
-    fd_set r, w, x;
-    int n, ret;
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-
-    while (1) {
-       /*
-        * Read one byte from the pipe.
-        */
-       ret = read(osxsel_pipe[0], &c, 1);
-
-       if (ret <= 0)
-           return;                    /* terminate the thread */
-
-       /*
-        * Now set up the select data.
-        */
-       [osxsel_inlock lock];
-       memcpy(&r, &osxsel_rfds_in, sizeof(fd_set));
-       memcpy(&w, &osxsel_wfds_in, sizeof(fd_set));
-       memcpy(&x, &osxsel_xfds_in, sizeof(fd_set));
-       n = osxsel_inmax;
-       [osxsel_inlock unlock];
-       FD_SET(osxsel_pipe[0], &r);
-       if (n < osxsel_pipe[0]+1)
-           n = osxsel_pipe[0]+1;
-
-       /*
-        * Perform the select.
-        */
-       ret = select(n, &r, &w, &x, NULL);
-
-       /*
-        * Detect the one special case in which the only
-        * interesting fd was the inter-thread pipe. In that
-        * situation only we are interested - the main thread will
-        * not be!
-        */
-       if (ret == 1 && FD_ISSET(osxsel_pipe[0], &r))
-           continue;                  /* just loop round again */
-
-       /*
-        * Write the select results to shared data.
-        * 
-        * I _think_ we don't need this data to be lock-protected:
-        * it won't be read by the main thread until after we send
-        * a message indicating that we've finished writing it, and
-        * we won't start another select (hence potentially writing
-        * it again) until the main thread notifies us in return.
-        * 
-        * However, I'm scared of multithreading and not totally
-        * convinced of my reasoning, so I'm going to lock it
-        * anyway.
-        */
-       [osxsel_outlock lock];
-       memcpy(&osxsel_rfds_out, &r, sizeof(fd_set));
-       memcpy(&osxsel_wfds_out, &w, sizeof(fd_set));
-       memcpy(&osxsel_xfds_out, &x, sizeof(fd_set));
-       osxsel_outmax = n;
-       [osxsel_outlock unlock];
-
-       /*
-        * Post a message to the main thread's message queue
-        * telling it that select data is available.
-        */
-       [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
-                         location:NSMakePoint(0,0)
-                         modifierFlags:0
-                         timestamp:0
-                         windowNumber:0
-                         context:nil
-                         subtype:0
-                         data1:0
-                         data2:0]
-        atStart:NO];
-    }
-
-    [pool release];
-}
-@end
-
-void osxsel_init(void)
-{
-    uxsel_init();
-
-    if (pipe(osxsel_pipe) < 0) {
-       fatalbox("Unable to set up inter-thread pipe for select");
-    }
-    [NSThread detachNewThreadSelector:@selector(runThread:)
-       toTarget:[[[OSXSel alloc] init] retain] withObject:nil];
-    /*
-     * Also initialise (i.e. clear) the input fd_sets. Need not
-     * start a select just yet - the select thread will block until
-     * we have at least one fd for it!
-     */
-    FD_ZERO(&osxsel_rfds_in);
-    FD_ZERO(&osxsel_wfds_in);
-    FD_ZERO(&osxsel_xfds_in);
-    osxsel_inmax = 0;
-    /*
-     * Initialise the mutex locks used to protect the data passed
-     * between threads.
-     */
-    osxsel_inlock = [[[NSLock alloc] init] retain];
-    osxsel_outlock = [[[NSLock alloc] init] retain];
-}
-
-static void osxsel_start_select(void)
-{
-    char c = 'g';                     /* for `Go!' :-) but it's never used */
-
-    if (!inhibit_start_select)
-       write(osxsel_pipe[1], &c, 1);
-}
-
-int uxsel_input_add(int fd, int rwx)
-{
-    /*
-     * Add the new fd to the appropriate input fd_sets, then write
-     * to the inter-thread pipe.
-     */
-    [osxsel_inlock lock];
-    if (rwx & 1)
-       FD_SET(fd, &osxsel_rfds_in);
-    else
-       FD_CLR(fd, &osxsel_rfds_in);
-    if (rwx & 2)
-       FD_SET(fd, &osxsel_wfds_in);
-    else
-       FD_CLR(fd, &osxsel_wfds_in);
-    if (rwx & 4)
-       FD_SET(fd, &osxsel_xfds_in);
-    else
-       FD_CLR(fd, &osxsel_xfds_in);
-    if (osxsel_inmax < fd+1)
-       osxsel_inmax = fd+1;
-    [osxsel_inlock unlock];
-    osxsel_start_select();
-
-    /*
-     * We must return an `id' which will be passed back to us at
-     * the time of uxsel_input_remove. Since we have no need to
-     * store ids in that sense, we might as well go with the fd
-     * itself.
-     */
-    return fd;
-}
-
-void uxsel_input_remove(int id)
-{
-    /*
-     * Remove the fd from all the input fd_sets. In this
-     * implementation, the simplest way to do that is to call
-     * uxsel_input_add with rwx==0!
-     */
-    uxsel_input_add(id, 0);
-}
-
-/*
- * Function called in the main thread to process results. It will
- * have to read the output fd_sets, go through them, call back to
- * uxsel with the results, and then write to the inter-thread pipe.
- * 
- * This function will have to be called from an event handler in
- * osxmain.m, which will therefore necessarily contain a small part
- * of this mechanism (along with calling osxsel_init).
- */
-void osxsel_process_results(void)
-{
-    int i;
-
-    /*
-     * We must write to the pipe to start a fresh select _even if_
-     * there were no changes. So for efficiency, we set a flag here
-     * which inhibits uxsel_input_{add,remove} from writing to the
-     * pipe; then once we finish processing, we clear the flag
-     * again and write a single byte ourselves. It's cleaner,
-     * because it wakes up the select thread fewer times.
-     */
-    inhibit_start_select = TRUE;
-
-    [osxsel_outlock lock];
-
-    for (i = 0; i < osxsel_outmax; i++) {
-       if (FD_ISSET(i, &osxsel_xfds_out))
-           select_result(i, 4);
-    }
-    for (i = 0; i < osxsel_outmax; i++) {
-       if (FD_ISSET(i, &osxsel_rfds_out))
-           select_result(i, 1);
-    }
-    for (i = 0; i < osxsel_outmax; i++) {
-       if (FD_ISSET(i, &osxsel_wfds_out))
-           select_result(i, 2);
-    }
-
-    [osxsel_outlock unlock];
-
-    inhibit_start_select = FALSE;
-    osxsel_start_select();
-}
diff --git a/macosx/osxwin.m b/macosx/osxwin.m
deleted file mode 100644 (file)
index 0629136..0000000
+++ /dev/null
@@ -1,1227 +0,0 @@
-/*
- * osxwin.m: code to manage a session window in Mac OS X PuTTY.
- */
-
-#import <Cocoa/Cocoa.h>
-#include "putty.h"
-#include "terminal.h"
-#include "osxclass.h"
-
-/* Colours come in two flavours: configurable, and xterm-extended. */
-#define NCFGCOLOURS (lenof(((Config *)0)->colours))
-#define NEXTCOLOURS 240 /* 216 colour-cube plus 24 shades of grey */
-#define NALLCOLOURS (NCFGCOLOURS + NEXTCOLOURS)
-
-/*
- * The key component of the per-session data is the SessionWindow
- * class. A pointer to this is used as the frontend handle, to be
- * passed to all the platform-independent subsystems that require
- * one.
- */
-
-@interface TerminalView : NSImageView
-{
-    NSFont *font;
-    NSImage *image;
-    Terminal *term;
-    Config cfg;
-    NSColor *colours[NALLCOLOURS];
-    float fw, fasc, fdesc, fh;
-}
-- (void)drawStartFinish:(BOOL)start;
-- (void)setColour:(int)n r:(float)r g:(float)g b:(float)b;
-- (void)doText:(wchar_t *)text len:(int)len x:(int)x y:(int)y
-    attr:(unsigned long)attr lattr:(int)lattr;
-@end
-
-@implementation TerminalView
-- (BOOL)isFlipped
-{
-    return YES;
-}
-- (id)initWithTerminal:(Terminal *)aTerm config:(Config)aCfg
-{
-    float w, h;
-
-    self = [self initWithFrame:NSMakeRect(0,0,100,100)];
-
-    term = aTerm;
-    cfg = aCfg;
-
-    /*
-     * Initialise the fonts we're going to use.
-     * 
-     * FIXME: for the moment I'm sticking with exactly one default font.
-     */
-    font = [NSFont userFixedPitchFontOfSize:0];
-
-    /*
-     * Now determine the size of the primary font.
-     * 
-     * FIXME: If we have multiple fonts, we may need to set fasc
-     * and fdesc to the _maximum_ asc and desc out of all the
-     * fonts, _before_ adding them together to get fh.
-     */
-    fw = [font widthOfString:@"A"];
-    fasc = [font ascender];
-    fdesc = -[font descender];
-    fh = fasc + fdesc;
-    fh = (int)fh + (fh > (int)fh);     /* round up, ickily */
-
-    /*
-     * Use this to figure out the size of the terminal view.
-     */
-    w = fw * term->cols;
-    h = fh * term->rows;
-
-    /*
-     * And set our size and subimage.
-     */
-    image = [[NSImage alloc] initWithSize:NSMakeSize(w,h)];
-    [image setFlipped:YES];
-    [self setImage:image];
-    [self setFrame:NSMakeRect(0,0,w,h)];
-
-    term_invalidate(term);
-
-    return self;
-}
-- (void)drawStartFinish:(BOOL)start
-{
-    if (start)
-       [image lockFocus];
-    else
-       [image unlockFocus];
-}
-- (void)doText:(wchar_t *)text len:(int)len x:(int)x y:(int)y
-    attr:(unsigned long)attr lattr:(int)lattr
-{
-    int nfg, nbg, rlen, widefactor;
-    float ox, oy, tw, th;
-    NSDictionary *attrdict;
-
-    /* FIXME: TATTR_COMBINING */
-
-    nfg = ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
-    nbg = ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
-    if (attr & ATTR_REVERSE) {
-       int t = nfg;
-       nfg = nbg;
-       nbg = t;
-    }
-    if ((cfg.bold_style & 2) && (attr & ATTR_BOLD)) {
-       if (nfg < 16) nfg |= 8;
-       else if (nfg >= 256) nfg |= 1;
-    }
-    if ((cfg.bold_style & 2) && (attr & ATTR_BLINK)) {
-       if (nbg < 16) nbg |= 8;
-       else if (nbg >= 256) nbg |= 1;
-    }
-    if (attr & TATTR_ACTCURS) {
-       nfg = 260;
-       nbg = 261;
-    }
-
-    if (attr & ATTR_WIDE) {
-       widefactor = 2;
-       /* FIXME: what do we actually have to do about wide characters? */
-    } else {
-       widefactor = 1;
-    }
-
-    /* FIXME: ATTR_BOLD if cfg.bold_style & 1 */
-
-    if ((lattr & LATTR_MODE) != LATTR_NORM) {
-       x *= 2;
-       if (x >= term->cols)
-           return;
-       if (x + len*2*widefactor > term->cols)
-           len = (term->cols-x)/2/widefactor;/* trim to LH half */
-       rlen = len * 2;
-    } else
-       rlen = len;
-
-    /* FIXME: how do we actually implement double-{width,height} lattrs? */
-
-    ox = x * fw;
-    oy = y * fh;
-    tw = rlen * widefactor * fw;
-    th = fh;
-
-    /*
-     * Set the clipping rectangle.
-     */
-    [[NSGraphicsContext currentContext] saveGraphicsState];
-    [NSBezierPath clipRect:NSMakeRect(ox, oy, tw, th)];
-
-    attrdict = [NSDictionary dictionaryWithObjectsAndKeys:
-               colours[nfg], NSForegroundColorAttributeName,
-               colours[nbg], NSBackgroundColorAttributeName,
-               font, NSFontAttributeName, nil];
-
-    /*
-     * Create an NSString and draw it.
-     * 
-     * Annoyingly, although our input is wchar_t which is four
-     * bytes wide on OS X and terminal.c supports 32-bit Unicode,
-     * we must convert into the two-byte type `unichar' to store in
-     * NSString, so we lose display capability for extra-BMP stuff
-     * at this point.
-     */
-    {
-       NSString *string;
-       unichar *utext;
-       int i;
-
-       utext = snewn(len, unichar);
-       for (i = 0; i < len; i++)
-           utext[i] = (text[i] >= 0x10000 ? 0xFFFD : text[i]);
-
-       string = [NSString stringWithCharacters:utext length:len];
-       [string drawAtPoint:NSMakePoint(ox, oy) withAttributes:attrdict];
-
-       sfree(utext);
-    }
-
-    /*
-     * Restore the graphics state from before the clipRect: call.
-     */
-    [[NSGraphicsContext currentContext] restoreGraphicsState];
-
-    /*
-     * And flag this area as needing display.
-     */
-    [self setNeedsDisplayInRect:NSMakeRect(ox, oy, tw, th)];
-}
-
-- (void)setColour:(int)n r:(float)r g:(float)g b:(float)b
-{
-    assert(n >= 0 && n < lenof(colours));
-    colours[n] = [[NSColor colorWithDeviceRed:r green:g blue:b alpha:1.0]
-                 retain];
-}
-@end
-
-@implementation SessionWindow
-- (id)initWithConfig:(Config)aCfg
-{
-    NSRect rect = { {0,0}, {0,0} };
-
-    alert_ctx = NULL;
-
-    cfg = aCfg;                               /* structure copy */
-
-    init_ucs(&ucsdata, cfg.line_codepage, cfg.utf8_override,
-            CS_UTF8, cfg.vtmode);
-    term = term_init(&cfg, &ucsdata, self);
-    logctx = log_init(self, &cfg);
-    term_provide_logctx(term, logctx);
-    term_size(term, cfg.height, cfg.width, cfg.savelines);
-
-    termview = [[[TerminalView alloc] initWithTerminal:term config:cfg]
-               autorelease];
-
-    /*
-     * Now work out the size of the window.
-     */
-    rect = [termview frame];
-    rect.origin = NSMakePoint(0,0);
-    rect.size.width += 2 * cfg.window_border;
-    rect.size.height += 2 * cfg.window_border;
-
-    /*
-     * Set up a backend.
-     */
-    back = backend_from_proto(cfg.protocol);
-    if (!back)
-       back = &pty_backend;
-
-    {
-       const char *error;
-       char *realhost = NULL;
-       error = back->init(self, &backhandle, &cfg, cfg.host, cfg.port,
-                          &realhost, cfg.tcp_nodelay, cfg.tcp_keepalives);
-       if (error) {
-           fatalbox("%s\n", error);   /* FIXME: connection_fatal at worst */
-       }
-
-       if (realhost)
-           sfree(realhost);           /* FIXME: do something with this */
-    }
-    back->provide_logctx(backhandle, logctx);
-
-    /*
-     * Create a line discipline. (This must be done after creating
-     * the terminal _and_ the backend, since it needs to be passed
-     * pointers to both.)
-     */
-    ldisc = ldisc_create(&cfg, term, back, backhandle, self);
-
-    /*
-     * FIXME: Set up a scrollbar.
-     */
-
-    self = [super initWithContentRect:rect
-           styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask |
-                      NSClosableWindowMask)
-           backing:NSBackingStoreBuffered
-           defer:YES];
-    [self setTitle:@"PuTTY"];
-
-    [self setIgnoresMouseEvents:NO];
-
-    /*
-     * Put the terminal view in the window.
-     */
-    rect = [termview frame];
-    rect.origin = NSMakePoint(cfg.window_border, cfg.window_border);
-    [termview setFrame:rect];
-    [[self contentView] addSubview:termview];
-
-    /*
-     * Set up the colour palette.
-     */
-    palette_reset(self);
-
-    /*
-     * FIXME: Only the _first_ document window should be centred.
-     * The subsequent ones should appear down and to the right of
-     * it, probably using the cascade function provided by Cocoa.
-     * Also we're apparently required by the HIG to remember and
-     * reuse previous positions of windows, although I'm not sure
-     * how that works if the user opens more than one of the same
-     * session type.
-     */
-    [self center];                    /* :-) */
-
-    exited = FALSE;
-
-    return self;
-}
-
-- (void)dealloc
-{
-    /*
-     * FIXME: Here we must deallocate all sorts of stuff: the
-     * terminal, the backend, the ldisc, the logctx, you name it.
-     * Do so.
-     */
-    sfree(alert_ctx);
-    if (back)
-       back->free(backhandle);
-    if (ldisc)
-       ldisc_free(ldisc);
-    /* ldisc must be freed before term, since ldisc_free expects term
-     * still to be around. */
-    if (logctx)
-       log_free(logctx);
-    if (term)
-       term_free(term);
-    [super dealloc];
-}
-
-- (void)drawStartFinish:(BOOL)start
-{
-    [termview drawStartFinish:start];
-}
-
-- (void)setColour:(int)n r:(float)r g:(float)g b:(float)b
-{
-    [termview setColour:n r:r g:g b:b];
-}
-
-- (void)doText:(wchar_t *)text len:(int)len x:(int)x y:(int)y
-    attr:(unsigned long)attr lattr:(int)lattr
-{
-    /* Pass this straight on to the TerminalView. */
-    [termview doText:text len:len x:x y:y attr:attr lattr:lattr];
-}
-
-- (Config *)cfg
-{
-    return &cfg;
-}
-
-- (void)keyDown:(NSEvent *)ev
-{
-    NSString *s = [ev characters];
-    int i;
-    int n = [s length], c = [s characterAtIndex:0], m = [ev modifierFlags];
-    int cm = [[ev charactersIgnoringModifiers] characterAtIndex:0];
-    wchar_t output[32];
-    char coutput[32];
-    int use_coutput = FALSE, special = FALSE, start, end;
-
-//printf("n=%d c=U+%04x cm=U+%04x m=%08x\n", n, c, cm, m);
-
-    /*
-     * FIXME: Alt+numberpad codes.
-     */
-
-    /*
-     * Shift and Ctrl with PageUp/PageDown for scrollback.
-     */
-    if (n == 1 && c == NSPageUpFunctionKey && (m & NSShiftKeyMask)) {
-       term_scroll(term, 0, -term->rows/2);
-       return;
-    }
-    if (n == 1 && c == NSPageUpFunctionKey && (m & NSControlKeyMask)) {
-       term_scroll(term, 0, -1);
-       return;
-    }
-    if (n == 1 && c == NSPageDownFunctionKey && (m & NSShiftKeyMask)) {
-       term_scroll(term, 0, +term->rows/2);
-       return;
-    }
-    if (n == 1 && c == NSPageDownFunctionKey && (m & NSControlKeyMask)) {
-       term_scroll(term, 0, +1);
-       return;
-    }
-
-    /*
-     * FIXME: Shift-Ins for paste? Or is that not Maccy enough?
-     */
-
-    /*
-     * FIXME: Alt (Option? Command?) prefix in general.
-     * 
-     * (Note that Alt-Shift-thing will work just by looking at
-     * charactersIgnoringModifiers; but Alt-Ctrl-thing will need
-     * processing properly, and Alt-as-in-Option won't happen at
-     * all. Hmmm.)
-     * 
-     * (Note also that we need to be able to override menu key
-     * equivalents before this is particularly useful.)
-     */
-    start = 1;
-    end = start;
-
-    /*
-     * Ctrl-` is the same as Ctrl-\, unless we already have a
-     * better idea.
-     */
-    if ((m & NSControlKeyMask) && n == 1 && cm == '`' && c == '`') {
-       output[1] = '\x1c';
-       end = 2;
-    }
-
-    /* We handle Return ourselves, because it needs to be flagged as
-     * special to ldisc. */
-    if (n == 1 && c == '\015') {
-       coutput[1] = '\015';
-       use_coutput = TRUE;
-       end = 2;
-       special = TRUE;
-    }
-
-    /* Control-Shift-Space is 160 (ISO8859 nonbreaking space) */
-    if (n == 1 && (m & NSControlKeyMask) && (m & NSShiftKeyMask) &&
-       cm == ' ') {
-       output[1] = '\240';
-       end = 2;
-    }
-
-    /* Control-2, Control-Space and Control-@ are all NUL. */
-    if ((m & NSControlKeyMask) && n == 1 &&
-       (cm == '2' || cm == '@' || cm == ' ') && c == cm) {
-       output[1] = '\0';
-       end = 2;
-    }
-
-    /* We don't let MacOS tell us what Backspace is! We know better. */
-    if (cm == 0x7F && !(m & NSShiftKeyMask)) {
-       coutput[1] = cfg.bksp_is_delete ? '\x7F' : '\x08';
-       end = 2;
-       use_coutput = special = TRUE;
-    }
-    /* For Shift Backspace, do opposite of what is configured. */
-    if (cm == 0x7F && (m & NSShiftKeyMask)) {
-       coutput[1] = cfg.bksp_is_delete ? '\x08' : '\x7F';
-       end = 2;
-       use_coutput = special = TRUE;
-    }
-
-    /* Shift-Tab is ESC [ Z. Oddly, this combination generates ^Y by
-     * default on MacOS! */
-    if (cm == 0x19 && (m & NSShiftKeyMask) && !(m & NSControlKeyMask)) {
-       end = 1;
-       output[end++] = '\033';
-       output[end++] = '[';
-       output[end++] = 'Z';
-    }
-
-    /*
-     * NetHack keypad mode.
-     */
-    if (cfg.nethack_keypad && (m & NSNumericPadKeyMask)) {
-       wchar_t *keys = NULL;
-       switch (cm) {
-         case '1': keys = L"bB"; break;
-         case '2': keys = L"jJ"; break;
-         case '3': keys = L"nN"; break;
-         case '4': keys = L"hH"; break;
-         case '5': keys = L".."; break;
-         case '6': keys = L"lL"; break;
-         case '7': keys = L"yY"; break;
-         case '8': keys = L"kK"; break;
-         case '9': keys = L"uU"; break;
-       }
-       if (keys) {
-           end = 2;
-           if (m & NSShiftKeyMask)
-               output[1] = keys[1];
-           else
-               output[1] = keys[0];
-           goto done;
-       }
-    }
-
-    /*
-     * Application keypad mode.
-     */
-    if (term->app_keypad_keys && !cfg.no_applic_k &&
-       (m & NSNumericPadKeyMask)) {
-       int xkey = 0;
-       switch (cm) {
-         case NSClearLineFunctionKey: xkey = 'P'; break;
-         case '=': xkey = 'Q'; break;
-         case '/': xkey = 'R'; break;
-         case '*': xkey = 'S'; break;
-           /*
-            * FIXME: keypad - and + need to be mapped to ESC O l
-            * and ESC O k, or ESC O l and ESC O m, depending on
-            * xterm function key mode, and I can't remember which
-            * goes where.
-            */
-         case '\003': xkey = 'M'; break;
-         case '0': xkey = 'p'; break;
-         case '1': xkey = 'q'; break;
-         case '2': xkey = 'r'; break;
-         case '3': xkey = 's'; break;
-         case '4': xkey = 't'; break;
-         case '5': xkey = 'u'; break;
-         case '6': xkey = 'v'; break;
-         case '7': xkey = 'w'; break;
-         case '8': xkey = 'x'; break;
-         case '9': xkey = 'y'; break;
-         case '.': xkey = 'n'; break;
-       }
-       if (xkey) {
-           if (term->vt52_mode) {
-               if (xkey >= 'P' && xkey <= 'S') {
-                   output[end++] = '\033';
-                   output[end++] = xkey;
-               } else {
-                   output[end++] = '\033';
-                   output[end++] = '?';
-                   output[end++] = xkey;
-               }
-           } else {
-               output[end++] = '\033';
-               output[end++] = 'O';
-               output[end++] = xkey;
-           }
-           goto done;
-       }
-    }
-
-    /*
-     * Next, all the keys that do tilde codes. (ESC '[' nn '~',
-     * for integer decimal nn.)
-     *
-     * We also deal with the weird ones here. Linux VCs replace F1
-     * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
-     * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
-     * respectively.
-     */
-    {
-       int code = 0;
-       switch (cm) {
-         case NSF1FunctionKey:
-           code = (m & NSShiftKeyMask ? 23 : 11);
-           break;
-         case NSF2FunctionKey:
-           code = (m & NSShiftKeyMask ? 24 : 12);
-           break;
-         case NSF3FunctionKey:
-           code = (m & NSShiftKeyMask ? 25 : 13);
-           break;
-         case NSF4FunctionKey:
-           code = (m & NSShiftKeyMask ? 26 : 14);
-           break;
-         case NSF5FunctionKey:
-           code = (m & NSShiftKeyMask ? 28 : 15);
-           break;
-         case NSF6FunctionKey:
-           code = (m & NSShiftKeyMask ? 29 : 17);
-           break;
-         case NSF7FunctionKey:
-           code = (m & NSShiftKeyMask ? 31 : 18);
-           break;
-         case NSF8FunctionKey:
-           code = (m & NSShiftKeyMask ? 32 : 19);
-           break;
-         case NSF9FunctionKey:
-           code = (m & NSShiftKeyMask ? 33 : 20);
-           break;
-         case NSF10FunctionKey:
-           code = (m & NSShiftKeyMask ? 34 : 21);
-           break;
-         case NSF11FunctionKey:
-           code = 23;
-           break;
-         case NSF12FunctionKey:
-           code = 24;
-           break;
-         case NSF13FunctionKey:
-           code = 25;
-           break;
-         case NSF14FunctionKey:
-           code = 26;
-           break;
-         case NSF15FunctionKey:
-           code = 28;
-           break;
-         case NSF16FunctionKey:
-           code = 29;
-           break;
-         case NSF17FunctionKey:
-           code = 31;
-           break;
-         case NSF18FunctionKey:
-           code = 32;
-           break;
-         case NSF19FunctionKey:
-           code = 33;
-           break;
-         case NSF20FunctionKey:
-           code = 34;
-           break;
-       }
-       if (!(m & NSControlKeyMask)) switch (cm) {
-         case NSHomeFunctionKey:
-           code = 1;
-           break;
-#ifdef FIXME
-         case GDK_Insert: case GDK_KP_Insert:
-           code = 2;
-           break;
-#endif
-         case NSDeleteFunctionKey:
-           code = 3;
-           break;
-         case NSEndFunctionKey:
-           code = 4;
-           break;
-         case NSPageUpFunctionKey:
-           code = 5;
-           break;
-         case NSPageDownFunctionKey:
-           code = 6;
-           break;
-       }
-       /* Reorder edit keys to physical order */
-       if (cfg.funky_type == FUNKY_VT400 && code <= 6)
-           code = "\0\2\1\4\5\3\6"[code];
-
-       if (term->vt52_mode && code > 0 && code <= 6) {
-           output[end++] = '\033';
-           output[end++] = " HLMEIG"[code];
-           goto done;
-       }
-
-       if (cfg.funky_type == FUNKY_SCO &&     /* SCO function keys */
-           code >= 11 && code <= 34) {
-           char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
-           int index = 0;
-           switch (cm) {
-             case NSF1FunctionKey: index = 0; break;
-             case NSF2FunctionKey: index = 1; break;
-             case NSF3FunctionKey: index = 2; break;
-             case NSF4FunctionKey: index = 3; break;
-             case NSF5FunctionKey: index = 4; break;
-             case NSF6FunctionKey: index = 5; break;
-             case NSF7FunctionKey: index = 6; break;
-             case NSF8FunctionKey: index = 7; break;
-             case NSF9FunctionKey: index = 8; break;
-             case NSF10FunctionKey: index = 9; break;
-             case NSF11FunctionKey: index = 10; break;
-             case NSF12FunctionKey: index = 11; break;
-           }
-           if (m & NSShiftKeyMask) index += 12;
-           if (m & NSControlKeyMask) index += 24;
-           output[end++] = '\033';
-           output[end++] = '[';
-           output[end++] = codes[index];
-           goto done;
-       }
-       if (cfg.funky_type == FUNKY_SCO &&     /* SCO small keypad */
-           code >= 1 && code <= 6) {
-           char codes[] = "HL.FIG";
-           if (code == 3) {
-               output[1] = '\x7F';
-               end = 2;
-           } else {
-               output[end++] = '\033';
-               output[end++] = '[';
-               output[end++] = codes[code-1];
-           }
-           goto done;
-       }
-       if ((term->vt52_mode || cfg.funky_type == FUNKY_VT100P) &&
-           code >= 11 && code <= 24) {
-           int offt = 0;
-           if (code > 15)
-               offt++;
-           if (code > 21)
-               offt++;
-           if (term->vt52_mode) {
-               output[end++] = '\033';
-               output[end++] = code + 'P' - 11 - offt;
-           } else {
-               output[end++] = '\033';
-               output[end++] = 'O';
-               output[end++] = code + 'P' - 11 - offt;
-           }
-           goto done;
-       }
-       if (cfg.funky_type == FUNKY_LINUX && code >= 11 && code <= 15) {
-           output[end++] = '\033';
-           output[end++] = '[';
-           output[end++] = '[';        
-           output[end++] = code + 'A' - 11;
-           goto done;
-       }
-       if (cfg.funky_type == FUNKY_XTERM && code >= 11 && code <= 14) {
-           if (term->vt52_mode) {
-               output[end++] = '\033';
-               output[end++] = code + 'P' - 11;
-           } else {
-               output[end++] = '\033';
-               output[end++] = 'O';
-               output[end++] = code + 'P' - 11;
-           }
-           goto done;
-       }
-       if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
-           if (code == 1) {
-               output[end++] = '\033';
-               output[end++] = '[';
-               output[end++] = 'H';
-           } else {
-               output[end++] = '\033';
-               output[end++] = 'O';
-               output[end++] = 'w';
-           }
-           goto done;
-       }
-       if (code) {
-           char buf[20];
-           sprintf(buf, "\x1B[%d~", code);
-           for (i = 0; buf[i]; i++)
-               output[end++] = buf[i];
-           goto done;
-       }
-    }
-
-    /*
-     * Cursor keys. (This includes the numberpad cursor keys,
-     * if we haven't already done them due to app keypad mode.)
-     */
-    {
-       int xkey = 0;
-       switch (cm) {
-         case NSUpArrowFunctionKey: xkey = 'A'; break;
-         case NSDownArrowFunctionKey: xkey = 'B'; break;
-         case NSRightArrowFunctionKey: xkey = 'C'; break;
-         case NSLeftArrowFunctionKey: xkey = 'D'; break;
-       }
-       if (xkey) {
-           end += format_arrow_key(output+end, term, xkey,
-                                   m & NSControlKeyMask);
-           goto done;
-       }
-    }
-
-    done:
-
-    /*
-     * Failing everything else, send the exact Unicode we got from
-     * OS X.
-     */
-    if (end == start) {
-       if (n > lenof(output)-start)
-           n = lenof(output)-start;   /* _shouldn't_ happen! */
-       for (i = 0; i < n; i++) {
-           output[i+start] = [s characterAtIndex:i];
-       }
-       end = n+start;
-    }
-
-    if (use_coutput) {
-       assert(special);
-       assert(end < lenof(coutput));
-       coutput[end] = '\0';
-       ldisc_send(ldisc, coutput+start, -2, TRUE);
-    } else {
-       luni_send(ldisc, output+start, end-start, TRUE);
-    }
-}
-
-- (int)fromBackend:(const char *)data len:(int)len isStderr:(int)is_stderr
-{
-    return term_data(term, is_stderr, data, len);
-}
-
-- (int)fromBackendUntrusted:(const char *)data len:(int)len
-{
-    return term_data_untrusted(term, data, len);
-}
-
-- (void)startAlert:(NSAlert *)alert
-    withCallback:(void (*)(void *, int))callback andCtx:(void *)ctx
-{
-    if (alert_ctx || alert_qhead) {
-       /*
-        * Queue this alert to be shown later.
-        */
-       struct alert_queue *qitem = snew(struct alert_queue);
-       qitem->next = NULL;
-       qitem->alert = alert;
-       qitem->callback = callback;
-       qitem->ctx = ctx;
-       if (alert_qtail)
-           alert_qtail->next = qitem;
-       else
-           alert_qhead = qitem;
-       alert_qtail = qitem;
-    } else {
-       alert_callback = callback;
-       alert_ctx = ctx;               /* NB this is assumed to need freeing! */
-       [alert beginSheetModalForWindow:self modalDelegate:self
-        didEndSelector:@selector(alertSheetDidEnd:returnCode:contextInfo:)
-        contextInfo:NULL];
-    }
-}
-
-- (void)alertSheetDidEnd:(NSAlert *)alert returnCode:(int)returnCode
-    contextInfo:(void *)contextInfo
-{
-    [self performSelectorOnMainThread:
-     @selector(alertSheetDidFinishEnding:)
-     withObject:[NSNumber numberWithInt:returnCode]
-     waitUntilDone:NO];
-}
-
-- (void)alertSheetDidFinishEnding:(id)object
-{
-    int returnCode = [object intValue];
-
-    alert_callback(alert_ctx, returnCode);   /* transfers ownership of ctx */
-
-    /*
-     * If there's an alert in our queue (either already or because
-     * the callback just queued it), start it.
-     */
-    if (alert_qhead) {
-       struct alert_queue *qnext;
-
-       alert_callback = alert_qhead->callback;
-       alert_ctx = alert_qhead->ctx;
-       [alert_qhead->alert beginSheetModalForWindow:self modalDelegate:self
-        didEndSelector:@selector(alertSheetDidEnd:returnCode:contextInfo:)
-        contextInfo:NULL];
-
-       qnext = alert_qhead->next;
-       sfree(alert_qhead);
-       alert_qhead = qnext;
-       if (!qnext)
-           alert_qtail = NULL;
-    } else {
-       alert_ctx = NULL;
-    }
-}
-
-- (void)notifyRemoteExit
-{
-    int exitcode;
-
-    if (!exited && (exitcode = back->exitcode(backhandle)) >= 0)
-       [self endSession:(exitcode == 0)];
-}
-
-- (void)endSession:(int)clean
-{
-    exited = TRUE;
-    if (ldisc) {
-       ldisc_free(ldisc);
-       ldisc = NULL;
-    }
-    if (back) {
-       back->free(backhandle);
-       backhandle = NULL;
-       back = NULL;
-       //FIXME: update specials menu;
-    }
-    if (cfg.close_on_exit == FORCE_ON ||
-       (cfg.close_on_exit == AUTO && clean))
-       [self close];
-    // FIXME: else show restart menu item
-}
-
-- (Terminal *)term
-{
-    return term;
-}
-
-@end
-
-int from_backend(void *frontend, int is_stderr, const char *data, int len)
-{
-    SessionWindow *win = (SessionWindow *)frontend;
-    return [win fromBackend:data len:len isStderr:is_stderr];
-}
-
-int from_backend_untrusted(void *frontend, const char *data, int len)
-{
-    SessionWindow *win = (SessionWindow *)frontend;
-    return [win fromBackendUntrusted:data len:len];
-}
-
-int get_userpass_input(prompts_t *p, const unsigned char *in, int inlen)
-{
-    SessionWindow *win = (SessionWindow *)p->frontend;
-    Terminal *term = [win term];
-    return term_get_userpass_input(term, p, in, inlen);
-}
-
-void frontend_keypress(void *handle)
-{
-    /* FIXME */
-}
-
-void notify_remote_exit(void *frontend)
-{
-    SessionWindow *win = (SessionWindow *)frontend;
-
-    [win notifyRemoteExit];
-}
-
-void frontend_echoedit_update(void *frontend, int echo, int edit)
-{
-    //SessionWindow *win = (SessionWindow *)frontend;
-    /*
-     * In a GUI front end, this need do nothing.
-     */
-}
-
-char *get_ttymode(void *frontend, const char *mode)
-{
-    SessionWindow *win = (SessionWindow *)frontend;
-    Terminal *term = [win term];
-    return term_get_ttymode(term, mode);
-}
-
-void update_specials_menu(void *frontend)
-{
-    //SessionWindow *win = (SessionWindow *)frontend;
-    /* FIXME */
-}
-
-/*
- * This is still called when mode==BELL_VISUAL, even though the
- * visual bell is handled entirely within terminal.c, because we
- * may want to perform additional actions on any kind of bell (for
- * example, taskbar flashing in Windows).
- */
-void do_beep(void *frontend, int mode)
-{
-    //SessionWindow *win = (SessionWindow *)frontend;
-    if (mode != BELL_VISUAL)
-       NSBeep();
-}
-
-int char_width(Context ctx, int uc)
-{
-    /*
-     * Under X, any fixed-width font really _is_ fixed-width.
-     * Double-width characters will be dealt with using a separate
-     * font. For the moment we can simply return 1.
-     */
-    return 1;
-}
-
-void palette_set(void *frontend, int n, int r, int g, int b)
-{
-    SessionWindow *win = (SessionWindow *)frontend;
-
-    if (n >= 16)
-       n += 256 - 16;
-    if (n >= NALLCOLOURS)
-       return;
-    [win setColour:n r:r/255.0 g:g/255.0 b:b/255.0];
-
-    /*
-     * FIXME: do we need an OS X equivalent of set_window_background?
-     */
-}
-
-void palette_reset(void *frontend)
-{
-    SessionWindow *win = (SessionWindow *)frontend;
-    Config *cfg = [win cfg];
-
-    /* This maps colour indices in cfg to those used in colours[]. */
-    static const int ww[] = {
-       256, 257, 258, 259, 260, 261,
-       0, 8, 1, 9, 2, 10, 3, 11,
-       4, 12, 5, 13, 6, 14, 7, 15
-    };
-
-    int i;
-
-    for (i = 0; i < NCFGCOLOURS; i++) {
-       [win setColour:ww[i] r:cfg->colours[i][0]/255.0
-        g:cfg->colours[i][1]/255.0 b:cfg->colours[i][2]/255.0];
-    }
-
-    for (i = 0; i < NEXTCOLOURS; i++) {
-       if (i < 216) {
-           int r = i / 36, g = (i / 6) % 6, b = i % 6;
-           r = r ? r*40+55 : 0; g = g ? b*40+55 : 0; b = b ? b*40+55 : 0;
-           [win setColour:i+16 r:r/255.0 g:g/255.0 b:b/255.0];
-       } else {
-           int shade = i - 216;
-           float fshade = (shade * 10 + 8) / 255.0;
-           [win setColour:i+16 r:fshade g:fshade b:fshade];
-       }
-    }
-
-    /*
-     * FIXME: do we need an OS X equivalent of set_window_background?
-     */
-}
-
-Context get_ctx(void *frontend)
-{
-    SessionWindow *win = (SessionWindow *)frontend;
-
-    /*
-     * Lock the drawing focus on the image inside the TerminalView.
-     */
-    [win drawStartFinish:YES];
-
-    [[NSGraphicsContext currentContext] setShouldAntialias:YES];
-
-    /*
-     * Cocoa drawing functions don't take a graphics context: that
-     * parameter is implicit. Therefore, we'll use the frontend
-     * handle itself as the context, on the grounds that it's as
-     * good a thing to use as any.
-     */
-    return frontend;
-}
-
-void free_ctx(Context ctx)
-{
-    SessionWindow *win = (SessionWindow *)ctx;
-
-    [win drawStartFinish:NO];
-}
-
-void do_text(Context ctx, int x, int y, wchar_t *text, int len,
-            unsigned long attr, int lattr)
-{
-    SessionWindow *win = (SessionWindow *)ctx;
-
-    [win doText:text len:len x:x y:y attr:attr lattr:lattr];
-}
-
-void do_cursor(Context ctx, int x, int y, wchar_t *text, int len,
-              unsigned long attr, int lattr)
-{
-    SessionWindow *win = (SessionWindow *)ctx;
-    Config *cfg = [win cfg];
-    int active, passive;
-
-    if (attr & TATTR_PASCURS) {
-       attr &= ~TATTR_PASCURS;
-       passive = 1;
-    } else
-       passive = 0;
-    if ((attr & TATTR_ACTCURS) && cfg->cursor_type != 0) {
-       attr &= ~TATTR_ACTCURS;
-        active = 1;
-    } else
-        active = 0;
-
-    [win doText:text len:len x:x y:y attr:attr lattr:lattr];
-
-    /*
-     * FIXME: now draw the various cursor types (both passive and
-     * active underlines and vertical lines, plus passive blocks).
-     */
-}
-
-/*
- * Minimise or restore the window in response to a server-side
- * request.
- */
-void set_iconic(void *frontend, int iconic)
-{
-    //SessionWindow *win = (SessionWindow *)frontend;
-    /* FIXME */
-}
-
-/*
- * Move the window in response to a server-side request.
- */
-void move_window(void *frontend, int x, int y)
-{
-    //SessionWindow *win = (SessionWindow *)frontend; 
-    /* FIXME */
-}
-
-/*
- * Move the window to the top or bottom of the z-order in response
- * to a server-side request.
- */
-void set_zorder(void *frontend, int top)
-{
-    //SessionWindow *win = (SessionWindow *)frontend;
-    /* FIXME */
-}
-
-/*
- * Refresh the window in response to a server-side request.
- */
-void refresh_window(void *frontend)
-{
-    //SessionWindow *win = (SessionWindow *)frontend;
-    /* FIXME */
-}
-
-/*
- * Maximise or restore the window in response to a server-side
- * request.
- */
-void set_zoomed(void *frontend, int zoomed)
-{
-    //SessionWindow *win = (SessionWindow *)frontend;
-    /* FIXME */
-}
-
-/*
- * Report whether the window is iconic, for terminal reports.
- */
-int is_iconic(void *frontend)
-{
-    //SessionWindow *win = (SessionWindow *)frontend;
-    return NO;                                /* FIXME */
-}
-
-/*
- * Report the window's position, for terminal reports.
- */
-void get_window_pos(void *frontend, int *x, int *y)
-{
-    //SessionWindow *win = (SessionWindow *)frontend;
-    /* FIXME */
-}
-
-/*
- * Report the window's pixel size, for terminal reports.
- */
-void get_window_pixels(void *frontend, int *x, int *y)
-{
-    //SessionWindow *win = (SessionWindow *)frontend;
-    /* FIXME */
-}
-
-/*
- * Return the window or icon title.
- */
-char *get_window_title(void *frontend, int icon)
-{
-    //SessionWindow *win = (SessionWindow *)frontend;
-    return NULL; /* FIXME */
-}
-
-void set_title(void *frontend, char *title)
-{
-    //SessionWindow *win = (SessionWindow *)frontend;
-    /* FIXME */
-}
-
-void set_icon(void *frontend, char *title)
-{
-    //SessionWindow *win = (SessionWindow *)frontend;
-    /* FIXME */
-}
-
-void set_sbar(void *frontend, int total, int start, int page)
-{
-    //SessionWindow *win = (SessionWindow *)frontend;
-    /* FIXME */
-}
-
-void get_clip(void *frontend, wchar_t ** p, int *len)
-{
-    //SessionWindow *win = (SessionWindow *)frontend;
-    /* FIXME */
-}
-
-void write_clip(void *frontend, wchar_t *data, int *attr, int len, int must_deselect)
-{
-    //SessionWindow *win = (SessionWindow *)frontend;
-    /* FIXME */
-}
-
-void request_paste(void *frontend)
-{
-    //SessionWindow *win = (SessionWindow *)frontend;
-    /* FIXME */
-}
-
-void set_raw_mouse_mode(void *frontend, int activate)
-{
-    //SessionWindow *win = (SessionWindow *)frontend;
-    /* FIXME */
-}
-
-void request_resize(void *frontend, int w, int h)
-{
-    //SessionWindow *win = (SessionWindow *)frontend;
-    /* FIXME */
-}
-
-void sys_cursor(void *frontend, int x, int y)
-{
-    //SessionWindow *win = (SessionWindow *)frontend;
-    /*
-     * This is probably meaningless under OS X. FIXME: find out for
-     * sure.
-     */
-}
-
-void logevent(void *frontend, const char *string)
-{
-    //SessionWindow *win = (SessionWindow *)frontend;
-    /* FIXME */
-printf("logevent: %s\n", string);
-}
-
-int font_dimension(void *frontend, int which)/* 0 for width, 1 for height */
-{
-    //SessionWindow *win = (SessionWindow *)frontend;
-    return 1; /* FIXME */
-}
-
-void set_busy_status(void *frontend, int status)
-{
-    /*
-     * We need do nothing here: the OS X `application is busy'
-     * beachball pointer appears _automatically_ when the
-     * application isn't responding to GUI messages.
-     */
-}
index 0e7484889e20244466b7d64fd1dbd497cf6f4645..a96aa9ae9b4cea88f06fe471a699b58cd6059dfb 100755 (executable)
@@ -135,7 +135,7 @@ while (<IN>) {
     } elsif (($i =~ /^\[([A-Z]*)\]$/) and defined $prog) {
       $type = substr($i,1,(length $i)-2);
       die "unrecognised program type for $prog [$type]\n"
-          if ! grep { $type eq $_ } qw(G C X U MX UT);
+          if ! grep { $type eq $_ } qw(G C X U MX XT UT);
     } else {
       push @$listref, $i;
     }
@@ -636,7 +636,7 @@ if (defined $makefiles{'vc'}) {
       "# C compilation flags\n".
       "CFLAGS = /nologo /W3 /O1 " .
       (join " ", map {"-I$dirpfx$_"} @srcdirs) .
-      " /D_WINDOWS /D_WIN32_WINDOWS=0x500 /DWINVER=0x500\n".
+      " /D_WINDOWS /D_WIN32_WINDOWS=0x500 /DWINVER=0x500 /D_CRT_SECURE_NO_WARNINGS\n".
       "LFLAGS = /incremental:no /fixed\n".
       "RCFLAGS = ".(join " ", map {"-I$dirpfx$_"} @srcdirs).
       " -DWIN32 -D_WIN32 -DWINVER=0x0400\n".
@@ -1357,7 +1357,7 @@ if (defined $makefiles{'gtk'}) {
     "# building with GTK 1.2, or you can set it to `pkg-config gtk+-2.0 x11'\n".
     "# if you want to enforce 2.0. The default is to try 2.0 and fall back\n".
     "# to 1.2 if it isn't found.\n".
-    "GTK_CONFIG = sh -c 'pkg-config gtk+-2.0 x11 \$\$0 2>/dev/null || gtk-config \$\$0'\n".
+    "GTK_CONFIG = sh -c 'pkg-config gtk+-3.0 x11 \$\$0 2>/dev/null || pkg-config gtk+-2.0 x11 \$\$0 2>/dev/null || gtk-config \$\$0'\n".
     "\n".
     "-include Makefile.local\n".
     "\n".
@@ -1393,15 +1393,17 @@ if (defined $makefiles{'gtk'}) {
     ".SUFFIXES:\n".
     "\n".
     "\n";
-    print &splitline("all:" . join "", map { " $_" } &progrealnames("X:U:UT"));
+    print &splitline("all:" . join "", map { " $_" }
+                     &progrealnames("X:XT:U:UT"));
     print "\n\n";
-    foreach $p (&prognames("X:U:UT")) {
+    foreach $p (&prognames("X:XT:U:UT")) {
       ($prog, $type) = split ",", $p;
+      ($ldflags = $type) =~ s/T$//;
       $objstr = &objects($p, "X.o", undef, undef);
       print &splitline($prog . ": " . $objstr), "\n";
       $libstr = &objects($p, undef, undef, "-lX");
       print &splitline("\t\$(CC) -o \$@ " .
-                       $objstr . " \$(${type}LDFLAGS) $libstr", 69), "\n\n";
+                       $objstr . " \$(${ldflags}LDFLAGS) $libstr", 69), "\n\n";
     }
     foreach $d (&deps("X.o", undef, $dirpfx, "/", "gtk")) {
       if ($forceobj{$d->{obj_orig}}) {
@@ -1415,7 +1417,7 @@ if (defined $makefiles{'gtk'}) {
     print "\n";
     print &def($makefile_extra{'gtk'}->{'end'});
     print "\nclean:\n".
-    "\trm -f *.o". (join "", map { " $_" } &progrealnames("X:U:UT")) . "\n";
+    "\trm -f *.o". (join "", map { " $_" } &progrealnames("X:XT:U:UT")) . "\n";
     print "\nFORCE:\n";
     select STDOUT; close OUT;
 }
@@ -1526,7 +1528,7 @@ if (defined $makefiles{'am'}) {
     print "endif\n\n";
 
     @noinstcliprogs = ("noinst_PROGRAMS", "=");
-    foreach $p (&prognames("UT")) {
+    foreach $p (&prognames("XT:UT")) {
       ($prog, $type) = split ",", $p;
       push @noinstcliprogs, $prog;
     }
@@ -1558,9 +1560,9 @@ if (defined $makefiles{'am'}) {
     print &splitline(join " ", "noinst_LIBRARIES", "=",
                      sort { $a cmp $b } values %amspeciallibs), "\n\n";
 
-    foreach $p (&prognames("X:U:UT")) {
+    foreach $p (&prognames("X:XT:U:UT")) {
       ($prog, $type) = split ",", $p;
-      print "if HAVE_GTK\n" if $type eq "X";
+      print "if HAVE_GTK\n" if $type eq "X" || $type eq "XT";
       @progsources = ("${prog}_SOURCES", "=");
       %sourcefiles = ();
       @ldadd = ();
@@ -1574,13 +1576,13 @@ if (defined $makefiles{'am'}) {
       }
       push @progsources, sort { $a cmp $b } keys %sourcefiles;
       print &splitline(join " ", @progsources), "\n";
-      if ($type eq "X") {
+      if ($type eq "X" || $type eq "XT") {
         push @ldadd, "\$(GTK_LIBS)";
       }
       if (@ldadd) {
         print &splitline(join " ", "${prog}_LDADD", "=", @ldadd), "\n";
       }
-      print "endif\n" if $type eq "X";
+      print "endif\n" if $type eq "X" || $type eq "XT";
       print "\n";
     }
     print &def($makefile_extra{'am'}->{'end'});
index 3f533ae6e0868c3721dda70ecb5b0b7cb8618e89..d8f110ac7fad6cee8a73ba7ae0c5d9cd3e1031e2 100644 (file)
--- a/pinger.c
+++ b/pinger.c
@@ -8,7 +8,7 @@
 struct pinger_tag {
     int interval;
     int pending;
-    unsigned long next;
+    unsigned long when_set, next;
     Backend *back;
     void *backhandle;
 };
@@ -28,7 +28,7 @@ static void pinger_timer(void *ctx, unsigned long now)
 
 static void pinger_schedule(Pinger pinger)
 {
-    int next;
+    unsigned long next;
 
     if (!pinger->interval) {
        pinger->pending = FALSE;       /* cancel any pending ping */
@@ -37,8 +37,10 @@ static void pinger_schedule(Pinger pinger)
 
     next = schedule_timer(pinger->interval * TICKSPERSEC,
                          pinger_timer, pinger);
-    if (!pinger->pending || next < pinger->next) {
+    if (!pinger->pending ||
+        (next - pinger->when_set) < (pinger->next - pinger->when_set)) {
        pinger->next = next;
+        pinger->when_set = timing_last_clock();
        pinger->pending = TRUE;
     }
 }
diff --git a/pscp.c b/pscp.c
index 61e6e1af0ef9a215a317d04f93a189e59ccc1965..6e1d0ff9a084576f8582d915cd63931a30f5c945 100644 (file)
--- a/pscp.c
+++ b/pscp.c
@@ -2349,6 +2349,8 @@ int psftp_main(int argc, char *argv[])
     argv += i;
     back = NULL;
 
+    platform_psftp_post_option_setup();
+
     if (list) {
        if (argc != 1)
            usage();
diff --git a/psftp.c b/psftp.c
index 92b57a2f363793b94b95aa9aa2128505dd13b6a0..784c81b1b88242871f5b76cd679f32adcbd757b1 100644 (file)
--- a/psftp.c
+++ b/psftp.c
@@ -2941,6 +2941,8 @@ int psftp_main(int argc, char *argv[])
     argv += i;
     back = NULL;
 
+    platform_psftp_post_option_setup();
+
     /*
      * If the loaded session provides a hostname, and a hostname has not
      * otherwise been specified, pop it in `userhost' so that
diff --git a/psftp.h b/psftp.h
index 57a821ab19da7522c773a19792423662a4f132b6..6f46bdcd3c9aef4fe6964ff84d237ce98ffc0587 100644 (file)
--- a/psftp.h
+++ b/psftp.h
@@ -47,6 +47,13 @@ int ssh_sftp_loop_iteration(void);
  */
 char *ssh_sftp_get_cmdline(const char *prompt, int backend_required);
 
+/*
+ * Platform-specific function called after the command line has been
+ * processed, so that any per-platform initialisation such as process
+ * ACL setup can be done.
+ */
+void platform_psftp_post_option_setup(void);
+
 /*
  * The main program in psftp.c. Called from main() in the platform-
  * specific code, after doing any platform-specific initialisation.
diff --git a/putty.h b/putty.h
index d5333942bd27dbd74eab5eb5cebeb343663d5d45..b06ff898411b2becfce93f2d0ad393baa60af989 100644 (file)
--- a/putty.h
+++ b/putty.h
@@ -182,7 +182,14 @@ typedef enum {
     /* Pseudo-specials used for constructing the specials menu. */
     TS_SEP,        /* Separator */
     TS_SUBMENU,            /* Start a new submenu with specified name */
-    TS_EXITMENU            /* Exit current submenu or end of specials */
+    TS_EXITMENU,    /* Exit current submenu or end of specials */
+    /* Starting point for protocols to invent special-action codes
+     * that can't live in this enum at all, e.g. because they change
+     * with every session.
+     *
+     * Of course, this must remain the last value in this
+     * enumeration. */
+    TS_LOCALSTART
 } Telnet_Special;
 
 struct telnet_special {
@@ -259,6 +266,18 @@ enum {
     KEX_MAX
 };
 
+enum {
+    /*
+     * SSH-2 host key algorithms
+     */
+    HK_WARN,
+    HK_RSA,
+    HK_DSA,
+    HK_ECDSA,
+    HK_ED25519,
+    HK_MAX
+};
+
 enum {
     /*
      * SSH ciphers (both SSH-1 and SSH-2)
@@ -688,6 +707,7 @@ void cleanup_exit(int);
     X(INT, NONE, nopty) \
     X(INT, NONE, compression) \
     X(INT, INT, ssh_kexlist) \
+    X(INT, INT, ssh_hklist) \
     X(INT, NONE, ssh_rekey_time) /* in minutes */ \
     X(STR, NONE, ssh_rekey_data) /* string encoding e.g. "100K", "2M", "1G" */ \
     X(INT, NONE, tryagent) \
@@ -695,7 +715,20 @@ void cleanup_exit(int);
     X(INT, NONE, change_username) /* allow username switching in SSH-2 */ \
     X(INT, INT, ssh_cipherlist) \
     X(FILENAME, NONE, keyfile) \
-    X(INT, NONE, sshprot) /* use v1 or v2 when both available */ \
+    /* \
+     * Which SSH protocol to use. \
+     * For historical reasons, the current legal values for CONF_sshprot \
+     * are: \
+     *  0 = SSH-1 only \
+     *  3 = SSH-2 only \
+     * We used to also support \
+     *  1 = SSH-1 with fallback to SSH-2 \
+     *  2 = SSH-2 with fallback to SSH-1 \
+     * and we continue to use 0/3 in storage formats rather than the more \
+     * obvious 1/2 to avoid surprises if someone saves a session and later \
+     * downgrades PuTTY. So it's easier to use these numbers internally too. \
+     */ \
+    X(INT, NONE, sshprot) \
     X(INT, NONE, ssh2_des_cbc) /* "des-cbc" unrecommended SSH-2 cipher */ \
     X(INT, NONE, ssh_no_userauth) /* bypass "ssh-userauth" (SSH-2 only) */ \
     X(INT, NONE, ssh_show_banner) /* show USERAUTH_BANNERs (SSH-2 only) */ \
@@ -1208,14 +1241,21 @@ int verify_ssh_host_key(void *frontend, char *host, int port,
                         void (*callback)(void *ctx, int result), void *ctx);
 /*
  * have_ssh_host_key() just returns true if a key of that type is
- * already chached and false otherwise.
+ * already cached and false otherwise.
  */
 int have_ssh_host_key(const char *host, int port, const char *keytype);
 /*
- * askalg has the same set of return values as verify_ssh_host_key.
+ * askalg and askhk have the same set of return values as
+ * verify_ssh_host_key.
+ *
+ * (askhk is used in the case where we're using a host key below the
+ * warning threshold because that's all we have cached, but at least
+ * one acceptable algorithm is available that we don't have cached.)
  */
 int askalg(void *frontend, const char *algtype, const char *algname,
           void (*callback)(void *ctx, int result), void *ctx);
+int askhk(void *frontend, const char *algname, const char *betteralgs,
+          void (*callback)(void *ctx, int result), void *ctx);
 /*
  * askappend can return four values:
  * 
@@ -1325,7 +1365,7 @@ void filename_free(Filename *fn);
 int filename_serialise(const Filename *f, void *data);
 Filename *filename_deserialise(void *data, int maxsize, int *used);
 char *get_username(void);             /* return value needs freeing */
-char *get_random_data(int bytes);      /* used in cmdgen.c */
+char *get_random_data(int bytes, const char *device); /* used in cmdgen.c */
 char filename_char_sanitise(char c);   /* rewrite special pathname chars */
 
 /*
@@ -1423,6 +1463,7 @@ unsigned long schedule_timer(int ticks, timer_fn_t fn, void *ctx);
 void expire_timer_context(void *ctx);
 int run_timers(unsigned long now, unsigned long *next);
 void timer_change_notify(unsigned long next);
+unsigned long timing_last_clock(void);
 
 /*
  * Exports from callback.c.
index 724bf9b8d21ae9b8a147395604484302856081ff..27916d276ae946c048328a754a580081c94bc37f 100644 (file)
--- a/puttyps.h
+++ b/puttyps.h
@@ -9,10 +9,6 @@
 
 #include "winstuff.h"
 
-#elif defined(MACOSX)
-
-#include "osx.h"
-
 #else
 
 #include "unix.h"
index 23e0ec3921384a633ea4cb10c29dfbff4f6089b5..a5d29748165b0f8619e39dd71e698d5b9ade0256 100644 (file)
@@ -10,8 +10,8 @@
 
 /* The cipher order given here is the default order. */
 static const struct keyvalwhere ciphernames[] = {
-    { "chacha20",   CIPHER_CHACHA20,        -1, -1 },
     { "aes",        CIPHER_AES,             -1, -1 },
+    { "chacha20",   CIPHER_CHACHA20,        CIPHER_AES, +1 },
     { "blowfish",   CIPHER_BLOWFISH,        -1, -1 },
     { "3des",       CIPHER_3DES,            -1, -1 },
     { "WARN",       CIPHER_WARN,            -1, -1 },
@@ -28,6 +28,14 @@ static const struct keyvalwhere kexnames[] = {
     { "WARN",               KEX_WARN,       -1, -1 }
 };
 
+static const struct keyvalwhere hknames[] = {
+    { "ed25519",    HK_ED25519,             -1, +1 },
+    { "ecdsa",      HK_ECDSA,               -1, -1 },
+    { "dsa",        HK_DSA,                 -1, -1 },
+    { "rsa",        HK_RSA,                 -1, -1 },
+    { "WARN",       HK_WARN,                -1, -1 },
+};
+
 /*
  * All the terminal modes that we know about for the "TerminalModes"
  * setting. (Also used by config.c for the drop-down list.)
@@ -378,6 +386,7 @@ static void gprefs(void *sesskey, const char *name, const char *def,
                     conf_set_int_int(conf, primary, j+1,
                                      conf_get_int_int(conf, primary, j));
                 conf_set_int_int(conf, primary, pos, mapping[i].v);
+                seen |= (1 << mapping[i].v);
                 n++;
             }
         }
@@ -493,6 +502,7 @@ void save_open_settings(void *sesskey, Conf *conf)
     write_setting_i(sesskey, "ChangeUsername", conf_get_int(conf, CONF_change_username));
     wprefs(sesskey, "Cipher", ciphernames, CIPHER_MAX, conf, CONF_ssh_cipherlist);
     wprefs(sesskey, "KEX", kexnames, KEX_MAX, conf, CONF_ssh_kexlist);
+    wprefs(sesskey, "HostKey", hknames, HK_MAX, conf, CONF_ssh_hklist);
     write_setting_i(sesskey, "RekeyTime", conf_get_int(conf, CONF_ssh_rekey_time));
     write_setting_s(sesskey, "RekeyBytes", conf_get_str(conf, CONF_ssh_rekey_data));
     write_setting_i(sesskey, "SshNoAuth", conf_get_int(conf, CONF_ssh_no_userauth));
@@ -789,10 +799,19 @@ void load_open_settings(void *sesskey, Conf *conf)
        gprefs(sesskey, "KEX", default_kexes,
               kexnames, KEX_MAX, conf, CONF_ssh_kexlist);
     }
+    gprefs(sesskey, "HostKey", "ed25519,ecdsa,rsa,dsa,WARN",
+           hknames, HK_MAX, conf, CONF_ssh_hklist);
     gppi(sesskey, "RekeyTime", 60, conf, CONF_ssh_rekey_time);
     gpps(sesskey, "RekeyBytes", "1G", conf, CONF_ssh_rekey_data);
-    /* SSH-2 only by default */
-    gppi(sesskey, "SshProt", 3, conf, CONF_sshprot);
+    {
+       /* SSH-2 only by default */
+       int sshprot = gppi_raw(sesskey, "SshProt", 3);
+       /* Old sessions may contain the values correponding to the fallbacks
+        * we used to allow; migrate them */
+       if (sshprot == 1)      sshprot = 0; /* => "SSH-1 only" */
+       else if (sshprot == 2) sshprot = 3; /* => "SSH-2 only" */
+       conf_set_int(conf, CONF_sshprot, sshprot);
+    }
     gpps(sesskey, "LogHost", "", conf, CONF_loghost);
     gppi(sesskey, "SSH2DES", 0, conf, CONF_ssh2_des_cbc);
     gppi(sesskey, "SshNoAuth", 0, conf, CONF_ssh_no_userauth);
diff --git a/ssh.c b/ssh.c
index da43bf0227d6aa3aff2f18ad8c8e2360075e9a8c..cc8832fdf8242bacd0e757349045d2d50b0591c8 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -11,6 +11,7 @@
 
 #include "putty.h"
 #include "tree234.h"
+#include "storage.h"
 #include "ssh.h"
 #ifndef NO_GSSAPI
 #include "sshgssc.h"
@@ -407,16 +408,23 @@ static void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin);
 #define OUR_V2_MAXPKT 0x4000UL
 #define OUR_V2_PACKETLIMIT 0x9000UL
 
-const static struct ssh_signkey *hostkey_algs[] = {
-    &ssh_ecdsa_ed25519,
-    &ssh_ecdsa_nistp256, &ssh_ecdsa_nistp384, &ssh_ecdsa_nistp521,
-    &ssh_rsa, &ssh_dss
+struct ssh_signkey_with_user_pref_id {
+    const struct ssh_signkey *alg;
+    int id;
+};
+const static struct ssh_signkey_with_user_pref_id hostkey_algs[] = {
+    { &ssh_ecdsa_ed25519, HK_ED25519 },
+    { &ssh_ecdsa_nistp256, HK_ECDSA },
+    { &ssh_ecdsa_nistp384, HK_ECDSA },
+    { &ssh_ecdsa_nistp521, HK_ECDSA },
+    { &ssh_dss, HK_DSA },
+    { &ssh_rsa, HK_RSA },
 };
 
-const static struct ssh_mac *macs[] = {
+const static struct ssh_mac *const macs[] = {
     &ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5
 };
-const static struct ssh_mac *buggymacs[] = {
+const static struct ssh_mac *const buggymacs[] = {
     &ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5
 };
 
@@ -443,7 +451,7 @@ const static struct ssh_compress ssh_comp_none = {
     ssh_comp_none_disable, NULL
 };
 extern const struct ssh_compress ssh_zlib;
-const static struct ssh_compress *compressions[] = {
+const static struct ssh_compress *const compressions[] = {
     &ssh_zlib, &ssh_comp_none
 };
 
@@ -954,6 +962,24 @@ struct ssh_tag {
      */
     struct ssh_gss_liblist *gsslibs;
 #endif
+
+    /*
+     * The last list returned from get_specials.
+     */
+    struct telnet_special *specials;
+
+    /*
+     * List of host key algorithms for which we _don't_ have a stored
+     * host key. These are indices into the main hostkey_algs[] array
+     */
+    int uncert_hostkeys[lenof(hostkey_algs)];
+    int n_uncert_hostkeys;
+
+    /*
+     * Flag indicating that the current rekey is intended to finish
+     * with a newly cross-certified host key.
+     */
+    int cross_certifying;
 };
 
 #define logevent(s) logevent(ssh->frontend, s)
@@ -3112,15 +3138,21 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
     /* Anything greater or equal to "1.99" means protocol 2 is supported. */
     s->proto2 = ssh_versioncmp(s->version, "1.99") >= 0;
 
-    if (conf_get_int(ssh->conf, CONF_sshprot) == 0 && !s->proto1) {
-       bombout(("SSH protocol version 1 required by configuration but "
-                "not provided by server"));
-       crStop(0);
-    }
-    if (conf_get_int(ssh->conf, CONF_sshprot) == 3 && !s->proto2) {
-       bombout(("SSH protocol version 2 required by configuration but "
-                "not provided by server"));
-       crStop(0);
+    if (conf_get_int(ssh->conf, CONF_sshprot) == 0) {
+       if (!s->proto1) {
+           bombout(("SSH protocol version 1 required by our configuration "
+                    "but not provided by server"));
+           crStop(0);
+       }
+    } else if (conf_get_int(ssh->conf, CONF_sshprot) == 3) {
+       if (!s->proto2) {
+           bombout(("SSH protocol version 2 required by our configuration "
+                    "but server only provides (old, insecure) SSH-1"));
+           crStop(0);
+       }
+    } else {
+       /* No longer support values 1 or 2 for CONF_sshprot */
+       assert(!"Unexpected value for CONF_sshprot");
     }
 
     if (s->proto2 && (conf_get_int(ssh->conf, CONF_sshprot) >= 2 || !s->proto1))
@@ -3682,13 +3714,17 @@ static const char *connect_to_host(Ssh ssh, const char *host, int port,
     }
 
     /*
-     * If the SSH version number's fixed, set it now, and if it's SSH-2,
-     * send the version string too.
+     * The SSH version number is always fixed (since we no longer support
+     * fallback between versions), so set it now, and if it's SSH-2,
+     * send the version string now too.
      */
     sshprot = conf_get_int(ssh->conf, CONF_sshprot);
+    assert(sshprot == 0 || sshprot == 3);
     if (sshprot == 0)
+       /* SSH-1 only */
        ssh->version = 1;
     if (sshprot == 3 && !ssh->bare_connection) {
+       /* SSH-2 only */
        ssh->version = 2;
        ssh_send_verstring(ssh, "SSH-", NULL);
     }
@@ -6223,7 +6259,10 @@ struct kexinit_algorithm {
            const struct ssh_kex *kex;
            int warn;
        } kex;
-       const struct ssh_signkey *hostkey;
+       struct {
+            const struct ssh_signkey *hostkey;
+            int warn;
+        } hk;
        struct {
            const struct ssh2_cipher *cipher;
            int warn;
@@ -6278,12 +6317,12 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
        "server-to-client compression method" };
     struct do_ssh2_transport_state {
        int crLine;
-       int nbits, pbits, warn_kex, warn_cscipher, warn_sccipher;
+       int nbits, pbits, warn_kex, warn_hk, warn_cscipher, warn_sccipher;
        Bignum p, g, e, f, K;
        void *our_kexinit;
        int our_kexinitlen;
        int kex_init_value, kex_reply_value;
-       const struct ssh_mac **maclist;
+       const struct ssh_mac *const *maclist;
        int nmacs;
        const struct ssh2_cipher *cscipher_tobe;
        const struct ssh2_cipher *sccipher_tobe;
@@ -6300,6 +6339,8 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
        unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN];
        int n_preferred_kex;
        const struct ssh_kexes *preferred_kex[KEX_MAX];
+       int n_preferred_hk;
+       int preferred_hk[HK_MAX];
        int n_preferred_ciphers;
        const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
        const struct ssh_compress *preferred_comp;
@@ -6376,6 +6417,20 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
            }
        }
 
+       /*
+        * Set up the preferred host key types. These are just the ids
+        * in the enum in putty.h, so 'warn below here' is indicated
+        * by HK_WARN.
+        */
+       s->n_preferred_hk = 0;
+       for (i = 0; i < HK_MAX; i++) {
+            int id = conf_get_int_int(ssh->conf, CONF_ssh_hklist, i);
+            /* As above, don't bother with HK_WARN if it's last in the
+             * list */
+           if (id != HK_WARN || i < HK_MAX - 1)
+                s->preferred_hk[s->n_preferred_hk++] = id;
+       }
+
        /*
         * Set up the preferred ciphers. (NULL => warn below here)
         */
@@ -6452,20 +6507,43 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
              * In the first key exchange, we list all the algorithms
              * we're prepared to cope with, but prefer those algorithms
             * for which we have a host key for this host.
+             *
+             * If the host key algorithm is below the warning
+             * threshold, we warn even if we did already have a key
+             * for it, on the basis that if the user has just
+             * reconfigured that host key type to be warned about,
+             * they surely _do_ want to be alerted that a server
+             * they're actually connecting to is using it.
              */
-            for (i = 0; i < lenof(hostkey_algs); i++) {
-               if (have_ssh_host_key(ssh->savedhost, ssh->savedport,
-                                     hostkey_algs[i]->keytype)) {
-                   alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
-                                             hostkey_algs[i]->name);
-                   alg->u.hostkey = hostkey_algs[i];
-               }
-           }
-            for (i = 0; i < lenof(hostkey_algs); i++) {
-               alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
-                                         hostkey_algs[i]->name);
-               alg->u.hostkey = hostkey_algs[i];
+            warn = FALSE;
+            for (i = 0; i < s->n_preferred_hk; i++) {
+                if (s->preferred_hk[i] == HK_WARN)
+                    warn = TRUE;
+                for (j = 0; j < lenof(hostkey_algs); j++) {
+                    if (hostkey_algs[j].id != s->preferred_hk[i])
+                        continue;
+                    if (have_ssh_host_key(ssh->savedhost, ssh->savedport,
+                                          hostkey_algs[j].alg->keytype)) {
+                        alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
+                                                  hostkey_algs[j].alg->name);
+                        alg->u.hk.hostkey = hostkey_algs[j].alg;
+                        alg->u.hk.warn = warn;
+                    }
+                }
            }
+            warn = FALSE;
+            for (i = 0; i < s->n_preferred_hk; i++) {
+                if (s->preferred_hk[i] == HK_WARN)
+                    warn = TRUE;
+                for (j = 0; j < lenof(hostkey_algs); j++) {
+                    if (hostkey_algs[j].id != s->preferred_hk[i])
+                        continue;
+                    alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
+                                              hostkey_algs[j].alg->name);
+                    alg->u.hk.hostkey = hostkey_algs[j].alg;
+                    alg->u.hk.warn = warn;
+                }
+            }
         } else {
             /*
              * In subsequent key exchanges, we list only the kex
@@ -6477,7 +6555,8 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
             assert(ssh->kex);
            alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
                                      ssh->hostkey->name);
-           alg->u.hostkey = ssh->hostkey;
+           alg->u.hk.hostkey = ssh->hostkey;
+            alg->u.hk.warn = FALSE;
         }
        /* List encryption algorithms (client->server then server->client). */
        for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) {
@@ -6598,7 +6677,8 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
        s->scmac_tobe = NULL;
        s->cscomp_tobe = NULL;
        s->sccomp_tobe = NULL;
-       s->warn_kex = s->warn_cscipher = s->warn_sccipher = FALSE;
+       s->warn_kex = s->warn_hk = FALSE;
+        s->warn_cscipher = s->warn_sccipher = FALSE;
 
        pktin->savedpos += 16;          /* skip garbage cookie */
 
@@ -6642,7 +6722,8 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
                        ssh->kex = alg->u.kex.kex;
                        s->warn_kex = alg->u.kex.warn;
                    } else if (i == KEXLIST_HOSTKEY) {
-                       ssh->hostkey = alg->u.hostkey;
+                       ssh->hostkey = alg->u.hk.hostkey;
+                        s->warn_hk = alg->u.hk.warn;
                    } else if (i == KEXLIST_CSCIPHER) {
                        s->cscipher_tobe = alg->u.cipher.cipher;
                        s->warn_cscipher = alg->u.cipher.warn;
@@ -6666,10 +6747,37 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
                    in_commasep_string(alg->u.comp->delayed_name, str, len))
                    s->pending_compression = TRUE;  /* try this later */
            }
-           bombout(("Couldn't agree a %s ((available: %.*s)",
+           bombout(("Couldn't agree a %s (available: %.*s)",
                     kexlist_descr[i], len, str));
            crStopV;
          matched:;
+
+            if (i == KEXLIST_HOSTKEY) {
+                int j;
+
+                /*
+                 * In addition to deciding which host key we're
+                 * actually going to use, we should make a list of the
+                 * host keys offered by the server which we _don't_
+                 * have cached. These will be offered as cross-
+                 * certification options by ssh_get_specials.
+                 *
+                 * We also count the key we're currently using for KEX
+                 * as one we've already got, because by the time this
+                 * menu becomes visible, it will be.
+                 */
+                ssh->n_uncert_hostkeys = 0;
+
+                for (j = 0; j < lenof(hostkey_algs); j++) {
+                    if (hostkey_algs[j].alg != ssh->hostkey &&
+                        in_commasep_string(hostkey_algs[j].alg->name,
+                                           str, len) &&
+                        !have_ssh_host_key(ssh->savedhost, ssh->savedport,
+                                           hostkey_algs[j].alg->keytype)) {
+                        ssh->uncert_hostkeys[ssh->n_uncert_hostkeys++] = j;
+                    }
+                }
+            }
        }
 
        if (s->pending_compression) {
@@ -6714,6 +6822,73 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
            }
        }
 
+       if (s->warn_hk) {
+            int j, k;
+            char *betteralgs;
+
+           ssh_set_frozen(ssh, 1);
+
+            /*
+             * Change warning box wording depending on why we chose a
+             * warning-level host key algorithm. If it's because
+             * that's all we have *cached*, use the askhk mechanism,
+             * and list the host keys we could usefully cross-certify.
+             * Otherwise, use askalg for the standard wording.
+             */
+            betteralgs = NULL;
+            for (j = 0; j < ssh->n_uncert_hostkeys; j++) {
+                const struct ssh_signkey_with_user_pref_id *hktype =
+                    &hostkey_algs[ssh->uncert_hostkeys[j]];
+                int better = FALSE;
+                for (k = 0; k < HK_MAX; k++) {
+                    int id = conf_get_int_int(ssh->conf, CONF_ssh_hklist, k);
+                    if (id == HK_WARN) {
+                        break;
+                    } else if (id == hktype->id) {
+                        better = TRUE;
+                        break;
+                    }
+                }
+                if (better) {
+                    if (betteralgs) {
+                        char *old_ba = betteralgs;
+                        betteralgs = dupcat(betteralgs, ",",
+                                            hktype->alg->name,
+                                            (const char *)NULL);
+                        sfree(old_ba);
+                    } else {
+                        betteralgs = dupstr(hktype->alg->name);
+                    }
+                }
+            }
+            if (betteralgs) {
+                s->dlgret = askhk(ssh->frontend, ssh->hostkey->name,
+                                  betteralgs, ssh_dialog_callback, ssh);
+                sfree(betteralgs);
+            } else {
+                s->dlgret = askalg(ssh->frontend, "host key type",
+                                   ssh->hostkey->name,
+                                   ssh_dialog_callback, ssh);
+            }
+           if (s->dlgret < 0) {
+               do {
+                   crReturnV;
+                   if (pktin) {
+                       bombout(("Unexpected data from server while"
+                                " waiting for user response"));
+                       crStopV;
+                   }
+               } while (pktin || inlen > 0);
+               s->dlgret = ssh->user_response;
+           }
+           ssh_set_frozen(ssh, 0);
+           if (s->dlgret == 0) {
+               ssh_disconnect(ssh, "User aborted at host key warning", NULL,
+                              0, TRUE);
+               crStopV;
+           }
+       }
+
        if (s->warn_cscipher) {
            ssh_set_frozen(ssh, 1);
            s->dlgret = askalg(ssh->frontend,
@@ -7130,6 +7305,40 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 
     s->keystr = ssh->hostkey->fmtkey(s->hkey);
     if (!s->got_session_id) {
+       /*
+        * Make a note of any other host key formats that are available.
+        */
+       {
+           int i, j;
+           char *list = NULL;
+           for (i = 0; i < lenof(hostkey_algs); i++) {
+               if (hostkey_algs[i].alg == ssh->hostkey)
+                   continue;
+
+                for (j = 0; j < ssh->n_uncert_hostkeys; j++)
+                    if (ssh->uncert_hostkeys[j] == i)
+                        break;
+
+                if (j < ssh->n_uncert_hostkeys) {
+                   char *newlist;
+                   if (list)
+                       newlist = dupprintf("%s/%s", list,
+                                           hostkey_algs[i].alg->name);
+                   else
+                       newlist = dupprintf("%s", hostkey_algs[i].alg->name);
+                   sfree(list);
+                   list = newlist;
+               }
+           }
+           if (list) {
+               logeventf(ssh,
+                         "Server also has %s host key%s, but we "
+                         "don't know %s", list,
+                         j > 1 ? "s" : "", j > 1 ? "any of them" : "it");
+               sfree(list);
+           }
+       }
+
         /*
          * Authenticate remote host: verify host key. (We've already
          * checked the signature of the exchange hash.)
@@ -7177,6 +7386,18 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
          * subsequent rekeys.
          */
         ssh->hostkey_str = s->keystr;
+    } else if (ssh->cross_certifying) {
+        s->fingerprint = ssh2_fingerprint(ssh->hostkey, s->hkey);
+        logevent("Storing additional host key for this host:");
+        logevent(s->fingerprint);
+        store_host_key(ssh->savedhost, ssh->savedport,
+                       ssh->hostkey->keytype, s->keystr);
+        ssh->cross_certifying = FALSE;
+        /*
+         * Don't forget to store the new key as the one we'll be
+         * re-checking in future normal rekeys.
+         */
+        ssh->hostkey_str = s->keystr;
     } else {
         /*
          * In a rekey, we never present an interactive host key
@@ -7363,6 +7584,12 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
      */
     freebn(s->K);
 
+    /*
+     * Update the specials menu to list the remaining uncertified host
+     * keys.
+     */
+    update_specials_menu(ssh->frontend);
+
     /*
      * Key exchange is over. Loop straight back round if we have a
      * deferred rekey reason.
@@ -11004,6 +11231,9 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
     ssh->connshare = NULL;
     ssh->attempting_connshare = FALSE;
     ssh->session_started = FALSE;
+    ssh->specials = NULL;
+    ssh->n_uncert_hostkeys = 0;
+    ssh->cross_certifying = FALSE;
 
     *backend_handle = ssh;
 
@@ -11151,6 +11381,7 @@ static void ssh_free(void *handle)
     sfree(ssh->v_s);
     sfree(ssh->fullhostname);
     sfree(ssh->hostkey_str);
+    sfree(ssh->specials);
     if (ssh->crcda_ctx) {
        crcda_free_context(ssh->crcda_ctx);
        ssh->crcda_ctx = NULL;
@@ -11364,19 +11595,24 @@ static const struct telnet_special *ssh_get_specials(void *handle)
     static const struct telnet_special specials_end[] = {
        {NULL, TS_EXITMENU}
     };
-    /* XXX review this length for any changes: */
-    static struct telnet_special ssh_specials[lenof(ssh2_ignore_special) +
-                                             lenof(ssh2_rekey_special) +
-                                             lenof(ssh2_session_specials) +
-                                             lenof(specials_end)];
+
+    struct telnet_special *specials = NULL;
+    int nspecials = 0, specialsize = 0;
+
     Ssh ssh = (Ssh) handle;
-    int i = 0;
-#define ADD_SPECIALS(name) \
-    do { \
-       assert((i + lenof(name)) <= lenof(ssh_specials)); \
-       memcpy(&ssh_specials[i], name, sizeof name); \
-       i += lenof(name); \
-    } while(0)
+
+    sfree(ssh->specials);
+
+#define ADD_SPECIALS(name) do                                           \
+    {                                                                   \
+        int len = lenof(name);                                          \
+        if (nspecials + len > specialsize) {                            \
+            specialsize = (nspecials + len) * 5 / 4 + 32;               \
+            specials = sresize(specials, specialsize, struct telnet_special); \
+        }                                                               \
+       memcpy(specials+nspecials, name, len*sizeof(struct telnet_special)); \
+        nspecials += len;                                               \
+    } while (0)
 
     if (ssh->version == 1) {
        /* Don't bother offering IGNORE if we've decided the remote
@@ -11391,11 +11627,37 @@ static const struct telnet_special *ssh_get_specials(void *handle)
            ADD_SPECIALS(ssh2_rekey_special);
        if (ssh->mainchan)
            ADD_SPECIALS(ssh2_session_specials);
+
+        if (ssh->n_uncert_hostkeys) {
+            static const struct telnet_special uncert_start[] = {
+                {NULL, TS_SEP},
+                {"Cache new host key type", TS_SUBMENU},
+            };
+            static const struct telnet_special uncert_end[] = {
+                {NULL, TS_EXITMENU},
+            };
+            int i;
+
+            ADD_SPECIALS(uncert_start);
+            for (i = 0; i < ssh->n_uncert_hostkeys; i++) {
+                struct telnet_special uncert[1];
+                const struct ssh_signkey *alg =
+                    hostkey_algs[ssh->uncert_hostkeys[i]].alg;
+                uncert[0].name = alg->name;
+                uncert[0].code = TS_LOCALSTART + ssh->uncert_hostkeys[i];
+                ADD_SPECIALS(uncert);
+            }
+            ADD_SPECIALS(uncert_end);
+        }
     } /* else we're not ready yet */
 
-    if (i) {
+    if (nspecials)
        ADD_SPECIALS(specials_end);
-       return ssh_specials;
+
+    ssh->specials = specials;
+
+    if (nspecials) {
+        return specials;
     } else {
        return NULL;
     }
@@ -11447,6 +11709,13 @@ static void ssh_special(void *handle, Telnet_Special code)
             ssh->version == 2) {
            do_ssh2_transport(ssh, "at user request", -1, NULL);
        }
+    } else if (code >= TS_LOCALSTART) {
+        ssh->hostkey = hostkey_algs[code - TS_LOCALSTART].alg;
+        ssh->cross_certifying = TRUE;
+       if (!ssh->kex_in_progress && !ssh->bare_connection &&
+            ssh->version == 2) {
+           do_ssh2_transport(ssh, "cross-certifying new host key", -1, NULL);
+       }
     } else if (code == TS_BRK) {
        if (ssh->state == SSH_STATE_CLOSED
            || ssh->state == SSH_STATE_PREPACKET) return;
diff --git a/ssh.h b/ssh.h
index 75aad70ba82beddc788da304ef1c4250c6d6b1de..7a7e8576e011963b88fe8c15a6c781d319bd1c62 100644 (file)
--- a/ssh.h
+++ b/ssh.h
@@ -155,6 +155,7 @@ struct ec_curve {
 const struct ssh_signkey *ec_alg_by_oid(int len, const void *oid,
                                         const struct ec_curve **curve);
 const unsigned char *ec_alg_oid(const struct ssh_signkey *alg, int *oidlen);
+extern const int ec_nist_curve_lengths[], n_ec_nist_curve_lengths;
 const int ec_nist_alg_and_curve_by_bits(int bits,
                                         const struct ec_curve **curve,
                                         const struct ssh_signkey **alg);
index d62c9b96ef6dd42ed460a56a9aac27a1d0ce1b17..46e6fadbec500c077e886ff01c64f074f8b56139 100644 (file)
--- a/sshecc.c
+++ b/sshecc.c
@@ -2937,6 +2937,9 @@ const unsigned char *ec_alg_oid(const struct ssh_signkey *alg,
     return extra->oid;
 }
 
+const int ec_nist_curve_lengths[] = { 256, 384, 521 };
+const int n_ec_nist_curve_lengths = lenof(ec_nist_curve_lengths);
+
 const int ec_nist_alg_and_curve_by_bits(int bits,
                                         const struct ec_curve **curve,
                                         const struct ssh_signkey **alg)
diff --git a/testdata/bignumtests.txt b/testdata/bignumtests.txt
new file mode 100644 (file)
index 0000000..4cb7b0d
--- /dev/null
@@ -0,0 +1,205 @@
+mul 6fcb0ed13247be24ded416f0d08612eb67d81017568e424698c442e4d7454d64315ffb51ce7af0bc6450c372d95c35967fde3adf6cec11a5c1e60847eccc8b4329b600bf917dbe4cab07ab82fbc439b5300000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000adf6cec11a5c1e60847eccc8b4329b2e4d7454d64315ffb51ce7af0bc6450c372d95c35967fde3adf6cec11a5c1e60847eccc8b4329b600bf917dbe4cab07ab82fbc439b5300000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000adf6cec11a5c1e60847eccc8b4329b600bf917dbe4cab07ab82fbc439b5300000000000000000000000000600bf917dbe4cab07ab82fbc439b5300000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000005967fde3adf6cec11a5c1e60847eccc8b4329b600bf917dbe4cab07ab82fbc439b53000000000000000000000000000000000d0000000000000000000000030000000000000000000000000000000000000000000adf6cec11a5c1e60847eccc8b4329b2e4d7454d64315ffb51ce7af0bc6450c372d95c35967fde3adf6cec11a5c1e60847eccc8b4329b600bf917dbe4cab07ab82fbc439b530d000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000adf6cec11a5c1e60847eccc8b4329b600bf917dbe4cab07ab82fbc439b5300000000000000000000000000600bf917dbe4cab07ab82fbc439b53000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000000000000000d2b00000000000000000000000000000000000000000d2b000000000000000000000000 5472abe25fd603c76d0790f25654cfcdad1c78b8d78f0043b544a82bd2f00000000000000000000000000000000000d2b00000000000000000000000000000000000000000d2b000000000000000000000000 24e0b458bbaa8f7f910bb243b2d8072f7c19f6b6b5da853b24621fe88c2833151e92cc3e22d3127aa16eeda3bf38eb59768e3b212f87e19fc0a18bf71e12baa8322778957ba93757abb8f584595e6510d943b3bb1ca9de1f034a2a0c31ab11156d1da7181ba0163c761ce7bb7def818e7900f8dd1cdd5b5943111bcc50b9b7a5845a1da04c70edf907604814320c59c0cb2ca1171de6c5c3e74e1a9628397f2de04459f13ceda25b1e3e3e102cd59a09d74f61151af91514689bb5120cba14ce64981190c6641e440e5d757f352f2814605cf8a9d0e0d710a1da7181ba0163c761ce7bb7def818e7900f8dd1cdd5b5943111bcc50b9b7a5845a1da04c70edf907604814320c59c0cb2cb17fe9b421f43acbe48d7ddceecd1d544f07fcbbed77175dd4342c31fd7b27c18fe581d6d4205dd52575894ecd965741a727d5a3c0ca1900553a89d5beacce763a00a290ebc3588f5e2accd1bb7a9b9ed93326d9e18438a9eb3a493c5e8fbb1fc57d1b057019c415a1f71e1b8e9f23e387990c1108dbf518a6218d207ed544f07fcbbed77175dd4342c322761217bc596aa22891716241df1342c9d73f75885cf0720c7f897a146394f4551aa5845a1da04c70edf907604814320c59c1765ba1171de6c5c3e74e1a9628397f2de04459f13cee4d8a1e3e3e102cd59a09d74f61151af91514689bb5120cba14ce64981190c6641e4412a74839b40d064580e65b061f43259e13a4e3a31cf3a6cad202f84218ae48e7900f8dd1cdd5b5943111bcc50b9b7a62f891da04c70edf907604814320c59c0cb2cb17fe9b4cd233acbe48d7ddceecd1d544f07fcbbed77175dd4342c31fd7b27c18fe581d6d4205dd52575894ecd965741a727d5a3c0ca1900553a89d5beacce763a00a290ebc3588f5e2accd1bb7a9b9ed93326d9e18438a9eb3a493a86ad00cb201d045e09a2c8db901dff75c9866404b4ee3f723c308da22a716cecc595685e9a9fe7c607d914cf670366158da22a716cecc595685e9a9fe78087b9ec56609129e5000000000000000000000000000000015aca7200000000000000000000000000000000000000ad6539000000000000000000000000000000000000000000000000
+pow 4240f7064c1a41a5ec4dc53f528552ea5ec963dd373c59ca03b2f2a161cdf531d1cd5c30cc48280deedb3656dcca416 393e4b8b7fdbb26aca528ce01295f4d736806e48ca53b076cb48e2039026b61dd4ae6356aa5d4be633db00df1263807 5a827999fcef32422cbec4d9baa55f4f8eb7b05d449dd426768bd642c199cc8aa57e41821d5c5161d458ff37ee41ed8 54b00f9069773b7bb477cf039383e812f645d2afa949378e86fb3a9576dbabe44e4ebc35cb7e3e3a566083dc4f258c8
+mul 169d8e69f29bd8497ec8ca970f390d39fca5aed949a931e1cf9f0a9177bd4d7156612a1841593fbc28aed186f471dc905dfe46114e392df944034cf 16a3aecd3581bb879be79d9072635f52b33ada60b6faeb0b5821e1eebd60000000000000000000000000000000000000000000000000000000000000000000000000024e5a0729c792d783819919713a5161cfaeb69da6faeb0b5821e1eebd600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007ef527ae46e531d 2000000000000000000000000000000000000000069b4e4f331f6204e5bd7808dc9b8107a41d5c358230b15a39b9e342c17f3ea02e43ca6f5293550a6d7b38497443725b83d5dbb4b20862b4375bcbb494a7891dc7248727bda5a258b93b3c95bb633a3d26d7f6074ed0bf947b23a29ca9d546ae4e3bfe71a15d583c9b4e5f82f8f022218291fb6d5aa4eb6f30aefa9398a5a75e415d150682a0a000000000000000000000000000000000000000000000000000000000000000000000000000000b373462d6da27dab3fffe3684d57a1835d8e5d3384bdde0887e1827a8a512c47c8eb28686773a45b009aaffdf5cf2d57b1fe1e16122b6ef02c6c990457ba9a9571873
+pow 8483e4834c 727c9716ffb764d5 88888888888888888888888888888888cec888888888888888888888888888888888888888883978888888d8888888888888888888888888d8888888888d8888888b504f333f9de6485 5ad35993ffd525c7f0e2fdd30c77eca0ea7a7f328a6a998bcb63271c1f52ad472e33958ef17584e1c7a2b8f9e4942ccb7594e0b38c331a69f89ae7b42fbcfba9509aa610a6b34c5e2ef
+pow 8483ee0c9834834c 727c9716ffb764d5 888888888888888888888888888888880068888888888888888888888888888888888888883978888888d8888888888888888888888888d8888888888d88888888888888889de6485 1c04e7e3a7129c5ef42180d2f8fbe0804b8f3a35b8f2d593b4ec0ce478fd78c4e22170c03d631e91c4e8d84d7606716b0576afa60dade3fa4fb77f0fd75fb4f0e2863a749ef875a7f
+pow 8483ee0c98e4834c 727c9716ffb764d5 88888888888888888888888888888888e91888888888888839778888888d88888888888888888888888888d8888888888888888888888888d8888888888d8888888b504f333f9de6485 17ff9b5d91ce08685fdd51715f0aff77216ac4911e4fb1c22bdb17609f754340d1e901c7e38f14c5569b13136b7991fcdb23d82adcb67a485b628eb01b5d1274a5eef39fd5a42dddd3d
+pow 8483ee0c9834834c 727c9716ffb764d5 88888888888888888888888888888888d86dd488888888888888888888888888888888888888888888888888d8888888d8888888b504f333f9de6485 cd6d82f283b830e0e4f42243e661fc09cf379d7ade2ee39eedd941fd93db77f9ccebec1ca7ed7b321b2378dbf1238ffa5f2be38bf9d7688eb1652f7
+pow 840000888888888888888888888888888888880000000000 727c9716ffb764d5 88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888d888888d8888888b504f333f9de6485 6ede8a488a46e535a5c25c17236cc66c4e49e5de6bd31399366b19a9346ec95b5b36126726d63241a3031e41c0c1345fa5a17a3c48f141e951f4152c9ecdc8
+pow 8483ee0c9834834c 727c9716ffb764d5 888888888888888888888888888888888888888888888888888888888888888888888888888888888888d8888888888d8888888b504f333f9d88888888888e6485 19090849092b2f94c22605d47301625de4b2a34f57c35796f6619d33b73b15ed917f776577f9f219adb6b4c7b5dce0ed6e4963d38150ad104b8bf1a03a8da74590
+pow 8483e4834c 727c9716ffb764d5 88888888888888888888888888888888888888888888888888888888888888888888888888883978888888d8888888888888888888888888d8888888888d8888888b504f333f9de6485 60ca03e099dbf17c93db528e84638037419e28fff7639cc1d9af02d62407908207a3d622c4358491b93878580d8eca7b2a1fe89bcafa1ae32cba9248162ccb65cd86c717c6ad76369ac
+pow 8483ee0c9834834c 727c9716ffb764d5 888888888888888888888888888888888888888888888888888888888888888888888888883978888888d8888888888888888888888888d8888888888d88888888888888889de6485 16e28809be035b05923c747a4ee9d75306a8529aa954028057efe5d72a09666d51afcac7ce03ddd6fbebad1e1ec67f64b7826d00123ca472abde81b56e8a4c9e532f759e60e9f1054
+pow 8483ee0c9834834c 727c9716ffb764d5 88888888888888888888888888888888888888888888886839788888888888888888888d8888888888d888888d8888888888888888888888888d8888888888d8888888b504f333f9de6485 11a1c98f64b4d3a2a926fd1237e7f39c98eb6ef7a67ed9ccfd6d6ade87e0198469ea21c3fa8fe67e87dce8e4d59da5a7f2623a074377352f7a4246612254b1672e68a8f7fb5e6574cc84ac
+pow 8483ee0c98e4834c 727c9716ffb764d5 88888888888888888888888888888888888888888888888839778888888d88888888888888888888888888d8888888888888888888888888d8888888888d8888888b504f333f9de6485 215d10c7bc86820b2c783e4b5ff4d2d6e57f3715d24cb9a7b2902b7513c261ef0c089929b26a52fcf6897c75519e83782761ef8f0ff816cf61e45de89da6a148cbab7aa93ea5d65e67b
+pow 8483ee0c9834834c 727c9716ffb764d5 888888888888888888888888888888888888888888888888888888888888888888888883978888888d8888888888888888888888888d8888888888d8888888b504f333f9de6485 2ce9a51423d4223564b726e77575aab190e26cb0d1ce2bdc29c6f0e87599808df40a2e7062d9e9e410e54b02e7ef92bed6561b72b7b0b0fc662c7d04a242aeb7229dae5f1a8af1
+pow 84 727c9716ffb764d5 8888888888888888888888888888888888888888d888888d8888888888888888888888d888888d88888888888888888888b504f333f9de6485 5c42fe284175b01a14b34e5c89b69097ec1d5dc9373285b82a946ea41899593ae43b7a482cb3cdcc5e21f97cfcc5ebdbffb5cafb60d252a2a9
+pow 8483ee0c9834834c 727c9716ffb764d5 8888888888888888888888888888888888888888888888880888888888888888888d888888d8888888b504f38888888888888888888888888888888833f9de6485 75ced9c9d94e6ffc16cfa101b9aa8dd547b25e986cc26a297ed2a4ad88e6f6e4a0e31484600194248d8e5154466191ddd67daaf3514cbfce78e0d47be37d1bf81b
+pow 8483ee0c9834834c 727c9716ffb764d5 888888888888888888888888888888888888888888888888d888888d888888888888888888888888888d888888d8888888b504f333f9de6485 537529b1dbacd7ae8e63e0356947a8ed5a7e9e770290a38e135b46183731d6276f80c68817076fae903b6c7cbba7bdcef90a70004b5a94ca1e
+pow 8483c 727c9716ffb764d5 888888888888888888888888888888888888888888d888888d8888888b504f333f9de64888d8888888b504f333f9de6485 53244b21a7a17ff24160c5b96a0a98515a52296f44e0bbfbc48980a08a1f89182ae3977ba35b0e1d7d57075dc63002e640
+pow 8483ee0c9834834c 727c9716ffb764d5 888f9de6488888888888888888888888888888888888888888888888888888888888888888d888888d8888888b504f333f9de6485 14bf546b6c806f8b9b84671e7a46096966f1fe1e8de4c450fe89b57fd5408c36c8060eb169a57e76aa7ae76f330ef137bd9566834
+pow 8483ee0c9834834c 727c9716ffb764d5 888888888888888888888888888888888888888888888888888888888888888888888888888888888888888d8888888b504f333f9de6485 511219ae4968000dae7b6d4ae111f7e8fc19dc33020ecaba3a7f45b1c1ddfcc4cbd2539307ec619481afb4d449ed8ed7a76f668558533
+pow 8483ee0c9834834c 727c9716ffb764d5 8888888888888888888888888888888888888888888888d8888888b504f333f9de888888888888888888888888888d888888d8888888b504f333f9de64de88888888888888888885 8135bbedf63d766808ea9e043323609d85eed687eebb6803271304ec68dc87de8ce362c3f16c9ac31e6dcb9542c9e7e2518e1fee06950d139aaebf9c2a7f827c26a9889491605470
+pow 8483ee0c9834834c 727c9716ffb764d5 88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888d888888d8888888b504f333f9de6485 44ed50d437512d99e38b02141a5d98fe47217f6cf5ebd237c8e79772eeac3ac2f33c13bbd80e9dbf3925147dfd135486611066110c4e1bbfe9655639b419941600032a2f9af21
+pow 34c 727c9716ffb764d5 8888888888888888888888888888888888888888888888888888888d888888d8888888b504f333f9de6485 2928a55555c4efca16738b016cb559173f877bed11969bc281440c13b7caa65ce9af4067e7f1c21d0b21f1
+pow 8483ee0c9834834c 727c9716ffb764d5 88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888d888888d8888888b504f6485 4c3ef21c414369560eab90c986b78a156045825bcdeb9d9bcea091916549f8f58ba49d0241cda890a4e2a9177f12bc71c4deec9a31b0f927582c0b5091
+pow 8483ee0c9834834c 727c9716ffb764d5 8888888888888888888888888888888888888888886888888888888888888888888d888888d8888888b504f333f9de6488b504f333f9de648585 6f03a4f2c77bec9c99564930f7972eae6f7f63c44a96e3e4747870d16aca5552300da1b7c705fa23b31e612f16275686e43b73a899a65d0e13a6
+pow 8483ec 727c97c6ffb764d5 8888888888888888888888888888888888888888888888888888888888888888888d888888d8888888b504f333f9de6485 64b634614732913628140a72e9f4e2b59f8d75427ccc5c990e989aa358d7e8ee067a0f13f2fba3f7e747b1ee3c5f105515
+pow 8483be0c9834834c 727c9716ffb764d5 88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888d888888d8888888b504f333f9de6485 2a7e622ef5d374e587a48251eac21656ad40922326ee152ff20d9c8746a879e27a4fd414938da900c0d62480ae857e17b4a8fd9b404f7d6f1f13059313b073eff
+pow 8483ee0c9834834c 727c9716ffb764a5 8888888888888888888888888888888888888888888888888888888888888888888888888888888888d888f333f9de6485 c97e716de0974491160fb8b2a2cc16ad5abbc07266e1e6cf25ca3a595981fd49cf1c526e7c04f5ed77df591ec327549e3
+pow 8483ee0c9834834c 727c9716ffb764d5 8888888888888888888888888888888888888888888888888888888888888888888d888888d8888d8888888b504f333f9de888888b504f333f9de6485 48b31ef21735201bbe4d2e6bae6cd76b1c90ddbaeb639369150b010f1b170edc966893c76d6d7edf21078529530f5232731ed399b19cd4dedcf9f8c69
+pow 8483ee0c9834834c 727c9716ffb764d5 8888888888888888888888888888888888888888888888888888888888888888888d888888d88888888888888888888888888888888888888b504f333f9de64888888888885 2ce54ff4a8a9dce961c4cfaf5a5a05b94b27cf215cd60d6b6c97da184582b90986bcfe25c0245a1a04967cc4dbebb78645fc060c58128a8252676d435c0927b987829d07e21
+pow 8483ee0c9834834c 727c9716ffb764d5 8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888d8888888d8888888b504f333f9de6485 109e5bfc2d7df8823079a2dfe19129d443268f91fecb08d0b34aaa704fce9d085656a5e90bcda51c496e0b652a8aa8d4230286f8f3e0ccf9ade06fd2
+pow 8483ee0c9834834c 727c9716ffb764d5 8888888888888888888888888888888888888888888888888888888888888888e1ed888888d8888888b504f333f9de6485 7a4973c5e0e6bb423afc9f42658e6a9325485e0a9291c3805d5a3d766795746fc6f7270c79442d8c90888427d5793e9808
+pow 8483ee0c9834834c 727c9716ffb764d5 8888888888888888888888888888888888888888888888888888888888888888d08d888888d8888888b504f333f9de6485 6a862f8f33f51553d95fe131ece8cff25b79c052de028cf66b485fd46b5f4cf913962978b56850a8e5ec9bb07fd6f5f2c
+pow 8400000000000000 727c9716ffb764d5 8888888888888888888888888888888888888888888888888888888888888888888d888888d8888888b504f333f9de6485 5851978fe5efd8a522ac7c2d0e17f7aeaebc9f8c4a77a6c7bd3d734c47f247a2b9b291961aea0b69b53bbc9906889e3912
+pow 8483ee0c9834834c 727c9716ffb764d5 8888888888888888888888888888888888888888888888888888888888888889888d888888d8888888b504f333f9de6485 1e7770c5546c601df48ccb9c929dfc9f36eaab78911c089e0b3459f0d80c80bfce2eded75c3d205a6d998aa628f3e9396b
+pow 8483ee0c9834834c 727c9716ffb764d5 8888888888888888888888888888888888888888888888880888888888888888888d888888d8888888b504f333f9de6485 2909fa1be537ec2cfd106f3f11664492283789e3f376e6179c170a4e080adc6a8a6850b77d35811c86b8dba0e6a98768c4
+modmul 13988e 194bd642cccccccccccccccccccccccccccccccccccccccccccccccccc22cbec4d97999fcef32b7b05d449dd426768bd6462cbec4d9ba58 5a827999fcef32422cbec4d97999fcef32422cbec4d9baa55baa55f4f8eb7b05d449dd426768bd642cccccccccccccccccccccccccccccccc1999fcef32422cbec4d97999fcef324229cc8aa57 1efb3f63db5da66666666666666666666666666666666666666666666596306eb2d65742a759166d49874c30845d5019afffd7a6b2db4bf19cd0
+modmul 139dddddddddddddddddddddddddddddd88e 194bd642ccccccccccccccccccccccc22cbec4d97999fcef32462cbec4d9ba58 5a8fcef32422cbec4d9baa55baa55f4f8eb7b05d449dd426768bd642cccccccccccccccccccccccccccccccc1999fcef32422cbec4d97999fcef324229cc8aa57 1f03a55c086d3a06d3a06d3a06d3a0602aba523c81830925e79c8b3b98eb9562b9d3f70790b415a26ba251983b4bbf79cd0
+pow 8483ee0c9834834c 727c9716ffb764d5 8888888888888888888888888888888888888888888888883978888888d8888888888888888888888888d8888888888d8888888b504f333f9de6485 4a0d96e0c39349b7377b15b5bf7b49f447a37349045d96c7e9f5122852162c9762d8381161f8db975e9e820b66a7042c850734679de7a7f548c1ed6
+pow 8483ee0c9834834c 727c9716ffb764d5 88888c888888888888888888888888888888888d888888d888888192ea3333f9de6485 6434b339504f053e2a5ff58f64998f96f47aa632d6343459a9d2c1f77a0f2c2e942f77
+pow 8483ee0c9834834c 727c9716ffb764d5 888888888888888888888888888888888324888d888888d8888888b504faaaaaaaaaaaaaaaaaaaaa333f9de6485 27a8e8c85285125d5617ebab1f839ecd5e20a606b4bd2e997a338399feb25fd6144045ffc50aa71167c7203b8e1
+pow 8483ee0c9834834c 727c9716ffb764d5 8888888888888888888888888888888888888888888888888888888888888888d888888d8888888b504f333f9de6485 2c6b37a2df4595500b7460400ce897817970cfadaaf1c386eeba15e08aaa6ce24019713a30e1a2c459eb2e1dacb768b
+pow 8483ee0c9834834c 727c9716ffb764d5 88888888888888888888888d88888888888888888888888888888888888888888888888d88888888888888888888d888888d8888888b504f333f9de6485 526b82e86fed0f1046b22b974a17b14d5a76f8c5c91f886e9aabf47d38c30d2dcd55f16391b4b719308169b0a6ca6e96ccf689fa6d9866dd88f36118472
+pow 8483ee0c9834834c 727c9716ffb764d5 888888888888888888888888888888888888888888888888888888888888888f88888d888888d8888888b504f333f9de6485 156e2c168bf9cdbc4d37f0863b0748cf244afc19db0ff3e8d2801efc574dc9ffe094500fc828cc6349b9f9c64b1c6ba4d7ec
+pow 8483ee0c9834834c 727c9716ffb764d5 888888888888888888888888888888888888888888888888888d888888d5 7d604b4171f1ef090338e494f47645ee6ce9b7d410cc52894bd4f25ab281
+pow c 727c9716ffb764d5 888888888888888888888888888888888888888d888888d8888888b504888d8888888b504f333f9de648f333f9de6485 74231cb11902ffe7ee56597e45e007732106d1565aee8d97cbba145ecbb034e7dfecacdf37b942b1d1c6db411b8f1ad5
+pow 8483884d9834834c 727c9716ffb764d5 888888888888888888888888888888888888888d888888d8888888b504f333f98b8d888888888888888888888888d888888d85 5adc4c88b8c112c2089468b51a545d218a07874bd9402923db6d8ba76f5f0e6e681396838574843fb46638b54e8a2f9ac71651
+pow 8483ee0a9834834c 727c9716ffb764d5 888888888888888888888888888888888888888888888888888888888888d888888d8888888b504f333f9de6485 848898833195df7201ea83c1fd5b865de18b1f7f3535407f8721c743c68c0d1e119342dc99250d516a142e8b436
+pow 8483ee0c9834834c 227c9716ffb764d5 888888888888888888888888888888888888888888888888888888b504f333f9de6485 12131509fa930f74d86f4da97b87c7c752320130b8dcc83c2bff6218a9b722def1636b
+pow 8483ee0c9834834c 727c9716ffb764d5 8888888888888888888888888888888888888888888888888888888888888d888f333f9de6485 2be812603b8a9707dadde37bb5a61dd0444eb6f30eb721057009cc7adbcc8d6667011c0504e68
+pow 8483ee0c9834834c 727c9716ffb764d5 8888888888888888888888888888888888888888888888888888888888888888888d888888d8888888b504f333f9de6485 3ad25628fe61d80cd41e987f9325b8b74725dad97c564df113d78169d6bfcf643656caa89b7156faab66c982f61b9957ad
+pow 8483ee0c9838dd4c 727c9716ffb764d5 888888888888888888888888888888888888888d888888888d888888d8888888b88d8888888b504f333f9de6485 30e87a722c8f3086881e32a662cc14a75b1a9122aebe5b4f615d4e03df5b59db82a2e1425cc2434afab8b0550b2
+pow 8483ee0c9834834c 727c9716ffb764d5 888804888888888888888888888888888888888d888888d8888888b504f333f9de6485 32c13f9ea9459a14779564d65f101dd1f8640196a875f6dbff2cd4b7b5f7542520b044
+pow 8483ee0c9834834c 727c9716ffb764d5 888888888888888888888888888888888888888d888888d8888888d504f333f9de6485 28d0c477fd109217949a66291f4e9816908ccad97823991e4179f5a503bd53ebeb364c
+pow 8483ee0c9834834c 727c9716ffb764d5 888888888888888888888888888888888888888d888888d8888888b104f333f9de6485 6985a06f4a1c1eebf0304de7f55d13a09ccb9419448e56f213f5bb4e8c369f8e9bdd4f
+modmul 13988e 194bd642ccccccccccccccccccccccc22cbec4d97999fcef32462cbec4d9ba58 5a827999fcef32422cbec4d97999fcef32422cbec4d9baa55baa55f4f8eb7b05d449dd426768bd642cccccccccccccccccccccccccccccccc1999fcef32422cbec4d97999fcef324229cc8aa57 1efb3f63db5da66666666666666666596306eb2d65742a7590dbcdfa6b2db4bf19cd0
+pow 8483ee0c9834834c 727c9716ffb764d5 8888888888888888888888888888888888d88888888888d88888888888d8888888b504f333f9de6485 5152a5e418fd58e41ca43b901cd36f7c99566568f9ff9b1e20389574908fea406f4e5ca8dd32918a07
+pow 8483ee0c9834834c 727c9716ffb764d5 888888888888888888888888888888888888888d888888d8888888b504f333f9de6485 8296d34d3b715f19a9aa3a412b6f685f476201c389f11761eab46a627b44ab5ff640b0
+pow 8483ee0c9834834c 6275 8888888888888888888888888888888888d8888888b5485 6a603ec3b6c06541519dda19c60de2767dbf3e8e68ca54d
+pow 8483ee0c9834834c 727c9716ffb764d5 88888888888888888888888888888888d8888888b50485 3e299644f713778d7d21067fea7ee62cd137f93ee3dfe
+pow 8483ee0c9834834c 727c9716ffb764d5 8888888888888888888888d7888888b504f333f9de6485 732b121547e7602f411f5b39f04de7cc39b3561431abcd
+modmul 13988e 194c22cbec4d97999fcef32422cbec4d9ba58 5a827999fcef32422cbec4d97999fcef32422cbec4d9baa55baa55f4f8eb7b05d449dd426768bd642cccccccccccccccccccccccccccccccc1999fcef32422cbec4d97999fcef324229cc8aa57 1efb9d206eb2d65742a7590d6e7d6eb2db4bf19cd0
+modmul 13988e 194c58 5a827999fcef32422cbec4d97999fcef32422cbec4d9baa55baa55f4f8eb7b05dccccccc449dd426768bd642ccccccccccccccccccccccccccccc1cc199cc8aa57 1efbde498d0
+modmul 13988e 194c58 5a827999fcef3cccccccccccccccccc199cc8aa2422cbec4d97999fcef32422cbec4d9baa55baa55f4f8eb7b05d449dd426768bd642cccccccccccccccccccccccccccccccc199cc8aa57 1efbde498d0
+pow 1ba 1c9f25c5bfedd935654670094afa6b9b4037246529d83b65a4294670094afa6b9b4037246529d83b65a47101c8135b0eea5731ab552ea5f319ed806f8931c03fe68da4b41da004c 2d413cccfe779921165f626cdd52afa7c75bd82ea24eea128b0ea2c7f9bf720f6ce43dd2a1790e71ec b816f83baec979e163d1daccc1ed4a4779ec06ae40af4dd7f3f45bc8ced54c8626ee4adef4fbdcff0
+pow 1ba f25c5bfedd93565294670094afa6b9b4037246529d83b65a47101c8135b0eea5731ab552ea5f319ed806f8931c03fe68da4b41da004c 2d413cccfe779921165f626cdd52afa7c75bd82ea24eea1338b8db2160c10eae28b0ea2c7f9bf720f6ce43cce64552bf20c10eae28b0ea2c7f9bf720f6ce43dd2a1790e71ec 23254b53a1cd644a5a998acd7e2c38f65b81909837ccc446a615a73a583ca5c92ce358370acbe6dce93031e26ce1fada16a133bf29c3143b45ecd8bb47f8383b88e8b109bdc
+pow 1ba 1c9f25c5bfedd93565294670094afa6b9b4037246529d83b65a47101c8135b0eea5731ab552ea5f319d413cccfe779921165f626cdd52afae43dd2a1724eea133b45eb2160cce64552bf20c10eae28b0ea2c7f9bf720f6ce43dd2a1790e71ec 7625b7f3f6eb39f032c9212b5b62deda285df73ec12c5f00acecd70c678d010ea35311ce8f9efbc7 b5943aa9528f0519e72ccd24686656291dab86ff5916a589b9abafc14486c820b10557eba884e79
+pow 1ba 1c9f25c5bfedd93565294670094afa6b9b4037246529d83b65a47101c8135b0eea5731ab152ea5f319ed806f8931c03fe68da4b41da004c 2d413cccfe779921165f626cdd52afa7c75bd82ea24eea133b45eb2160cce64552bf20c10eae28b0ea2c7f9bf720f6ce43dd2a1790e713fe68da4bec 10dd194113310a6cc9121c1e94db2410406ab41712c45b11ac10783e1f6ddae8ec2a2a4868db3ac8a74a13b525ccbb9469c9431e98b0fdb39e575fc
+pow 10907dc1930690697b1371 fd4a14eabe7c7b369ec448e52aa97eb1fda3d54ba97b258f74dcf167280ecbca820000000004f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e509ad87 16a09e667f3bcc908b2fb1366ea957d3e3adec17512775099da2f590b0667322 11d53e8bf0e94f38a15acad76c89426e4282a01e21a5c8cb40363b3660fb6f51
+pow 10907dc1930690697b13714fd957d3e3adec175a2f590b054ba97b258f74dcf167280ecbca858737d5c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 16a09e667f3bcc908b2fb1366ea957d3e3adec17512775099d366ea957d3e3adec175a2f590b0667323 12b697e76f7294ee42a4630ae19ebda51c4d1a40669a86ceeb78adbdf58cb9faf3320fd3cd2cfb1a886
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 16a09e667f3bcc908b2fb136e06cf924c93ae6ea957d3e3adec17512775099da2f590b0667323 518f9910b7b9847e43ba7344c4868177aed30d7b277e0c0ad64a3dc1f992e865af889a0dccbe
+pow 10907dc1930690497b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33800907dc14a57d35cda01b923294ec1db2d23880e40908b2fb1366ea957d3e3adec17512775099da2f590b0667323 1 0
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b9409ad87 16a031d9e667f3bcc908b2fb1366ea957d3e3adec17512775099da2f590b0667323 14cc00562f66093169240d19685705e073c96c7547e5797c768440c5044c0e43566
+pow 10907dc193b2d23880e409ad87 16a09e660690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ecc908b2fb1366ea957d3e3adec17512775099da2f590b0667323 a03af414acae1a25bd359e46a395895fc8f3b5c96a3c25c3c1f290062cdb4972275c3eba22a21316f3339459ae54a6b5
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 16a09e667f3bcc966e08b2fb1366ea957d3e3adec17512775099da2f590b0667323 16869520abf32acaeac583b5781802b8b5dbce8aeed2c0b3a0db0e0359f3328a278
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b97d35cda01b923294ec1db2d23880e409667f3bcc908b2fb1366ea957d3e3adec17512775099da3f590b0667323 13e06cf924c93da01b923294ec1db2d23880eae2d00206c4813c36a0aac31d2db5e2148af132b6a099da2f590b0667323 da20997b7a493f76368d529949067b3639469e0c2d681588f7f76fbd12e1aa92b1eafa89a07b7159b8d5a4b17fcb3123
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923297 16a09e657d35cda01b923294ec1db2d2388067f3bcc908b2fb1366ea957d3e3adec17512775099da2f590b0667323 115eec036127564768648670be45c3b1b7696d51d2ec76a657a5e090dd674e8a31cc0829ff63b3aa0150c5b591933
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858 37d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923267f3bcc908b2fb1366ea957d3e3adec17512775099da2f590b0667323 450ce71a8eec9fcecab453e1566cf6525c70f6a69aa16a47c7d1ed754b0967c4764f6da6cf092fd49589912bc2bd3337f4
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 16a09e667f3bcc908b2fb1366ea957d3e3adec175127750af132b6a7c9c8a399da2f590b0667323 10bea717321c68ab600971b66e7e59047ad3682b4dbc1b64258affbe7b125af26b62c994ec365a3
+pow 10907dc19306906e4f92e14fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 16a09e667f3bcc908b2fb1366ea957d3e3adec175127bbbbbbbbbbbbbbbbbbbbbbbbb537bbb75099da2f590b0667323 aafd00a40f14e94e5427612c7c8a2342576108dde4e32964fd4d100e60426e210f63ac4d884850cc8035e904d44e38
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c 4a33804a57d35cda01b923294ec1db2d23880e409ad87 16a09e667f3bcc908b2fb1366ea957d3e3adec17512775099da2f590b0667323 1462290f3612e19fc67b888f7419d278efed8ef32fd37ec435410cd4601a3e4c
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 16a09e667f3bcc94a154ba97b258f74dcf167280e08b2fb1366ea957d3e3adec17512775099da2f590b0667323 14c84cc12c5d71de79fd96dcedf077c050baca2420aa20e33f38dcd5fbc46853150257db771c31dbda3bb53905
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c 39792e2dff6ec9ab294a33804a57d35cda01b923294ed23880e409ad87 16a09e667f3bcc908b2fb1366ea957d3e3adec17512775099da2f590b0667323 12d750a696d901ffb58cc9c2004431a44d2abd08923cf585e49dd2d95c70a001
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 16a09e667f3bc366ea957d3e3adec17512775099da2f54ba97b258f74dcf167280ecbc90b0667323 145fe455eb9d42bc17ab30f1ba224fd7e58fef09ddf0f8e62baef7b8b01bdd636b5f648097c1e913
+pow 10907dc1930690697b13714fd4a154ba97b258f74d8737d4c75099da2f590b0667323 13e06cf924ccdf01b923294ec1db2d23880e409ad87 16a09e667f3bcc908b2fb1366908b2fb1366ea957d3ea957d3e3adec17512775099da2f590b0667323 b3566d563d6ba5fcc24ed18bc5f9a63e9d9b7e073d9b684156b4acc463ac9c89fe5507471d7347819
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 16a09e667ff6ec9ab294a33804a57df3bcc908b2fb1366ea957d3e3adec17512775099da2f590b0667323 9cfdd8d180de912e9490385a14cd642f05e258745625c0cd9d403ebdaa7db90109ca1d771476a5b842a5
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 16a09e667f3bcc908b923b2fb1366ea957d3e3adec17512775099da2f590b0667323 17c1f8e9e0fc930930fb5a1cbfd90f44d95eb3cf8c7918c98ec47f3ebd4cd9bd234
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f9c36a0aac31d2db5e2248af132e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 16a09e667f3bcc908b2fb1366ea957d3e3adec17512775099da2f590b0667323 2ba528582bdd37fe979ba13589832fd094dbd1ba79aa31b1a3fc15ea00d8908
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad930690697b13714fd4a154ba9787 16a09e667f3bcc366ea957d3e3adec17512775099da2f590b0667323 100b983f254cd0503a68b707952fd4e3930e57e48ef7521dc749302c
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 16a09e64d23bcc908b2fb1366ea957d3e3adec17512775099da2f590b0667323 32edca42bb85d3a7964256b95d4c12db0a3735c8501b7d24e35c17e6471aff1
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 16a092047f3bcc908b2fb1366ea957d3e3adec17512775099da2f590b0667323 100fa51ec3e14bbb7918eb18a4619b71a9c0445e1a9677387f982911df821403
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 18449e667f3bcc908b2fb1366ea957d3e3adec17512775099da2f590b0667323 16e9c0ed5254e4d5fa00a8e6630cc4174d69982c67d26303805ef489c332f713
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 16a09e6c7f3bcc908b2fb1366ea957d3e3adec17512775099da2f590b0667323 13928b672d49ab1c419847ae5c2bc6957a2792445b58bb5de8aaf35b11ec51b2
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 d6a09e667f3bcc908b2fb1366ea957d3e3adec17512775099da2f590b0667323 d3634464bd781a4f3c3de1bb2d575988d5d861944bbcfb2d5b5fb9cb5f641b62
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 16a09e667f3bcc908b2fb1366ea957d3e3adec17512775099db2f590b0667323 139a26f4ce368bcc132437fa3fdff9393e529e9ad810507065eae0fe1eae75ab
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 16a09e667f3bcc908b2fb1366ea957d3e3adeb17512775099da2f590b0667323 13cb39d2966a6cda4a61a7bcac4d54fc1e07f9a5911a6c9e418f424e1d690d88
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 16a09e667f3bcc908b2fb1366ea957d2e3adec17512775099da2f590b0667323 1045060ef67d3e02805fc6c7c73e78d9515773cebfb94a2361bb562de44f0cef
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 16a09e667f3bcc908b2fb1366ea955d3e3adec17512775099da2f590b0667323 e9f79f25a51263752bb6801ea2ea69f1bfe72eb29841a18fe897344e432778b
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 16a09e667f3bcc908b2fb1366ea857d3e3adec17512775099da2f590b0667323 1603254e45e8cea74b33e0fea929a14f8031fec82f60e443d5da2177dc5d9aa3
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 16a09e667f3bcc908b2fb1366da957d3e3adec17512775099da2f590b0667323 82799d2dc787e49afd44173d4373cc1e5e4c5f338639b0876da8b60f139032f
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 16a09e667f3bcc908b2fb1166ea957d3e3adec17512775099da2f590b0667323 550bb6f10292ca7bda8ee8ec2d849ca328123ff90f2bd85795c509a12d99e0a
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 12a09e667f3bcc908b2fb1366ea957d3e3adec17512775099da2f590b0667323 106e2d72f0365650111866243cfc5a675f4d0677452da7d86354717813c4b705
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 56a09e667f3bcc908b2fb1366ea957d3e3adec17512775099da2f590b0667323 34af9ebb1c936ee57965f89c005ea5dc91d70aefef9a3181363e39b7547856e4
+pow 10907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 96a09e667f3bcc908b2fb1366ea957d3e3adec17512775099da2f590b0667323 735eb1132cd9f59c79500163d423f3ecb1f802aa65ec6327612302f7d96ae917
+pow 11907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 16a09e667f3bcc908b2fb1366ea957d3e3adec17512775099da2f590b0667323 328fb6559104e886880c03bdc59572cc2dddb075bc405b8e37e681fdf6f14b6
+pow 12907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 16a09e667f3bcc908b2fb1366ea957d3e3adec17512775099da2f590b0667323 11d815b4b3c473a014845a6c46d4f3e0fbe1079a046d52a999a0efa4457d904e
+pow 50907dc1930690697b13714fd4a154ba97b258f74dcf167280ecbca858737d4c e4f92e2dff6ec9ab294a33804a57d35cda01b923294ec1db2d23880e409ad87 16a09e667f3bcc908b2fb1366ea957d3e3adec17512775099da2f590b0667323 12cf3bbf47b42cd6d73e7c59aa66efe37c82efd92eadb5d3166c2514fb521ec4
+pow 4241f7064c1a41a5ec4dc53f528552e9 393e4b8b7fd5b26aca528ce01295f4d6 5a827999fcef32422cbec4d9baa55f41a5ec4dc53f528552e9 337f28ff252aa48023556ab30189e1eba23c1f2d451136b029
+pow 4241f7064c1a41a552e9 393e4b3b55fa3d1aaf5b8bb26aca528ce01295f4d6 5a827999fcef32422cbec4d9baa55f4e 46aa926b3d484810eccca4e964ad5f8f
+pow 4241f7064c1a41a5ec4dc53f528552e9 393e4b8b7fdb2cbec4d9baa55f4e 3e73738d7999fcef32422cbec4d9baa55f4e 70ca5cba1b58bdc4c907d349716609a3749
+pow 4241f7064c1a41a5ec4dc53f528552e9 393e4b8b7fdbb26aca528ce01295f4d6 5a827999fcef32422cbec4d9baa55f44dc53f528552e9 3b76731b825ed64be2fdd42579bf2c10a666b49b347ed
+pow 4241f7064c1a41c51c4dc53f528552e9 393e4b8b7f73738ddbb26aca528ce01295f4d6 5a827999fcef32422cbec4d9baa55f4e 3dc6a608deca791097e423ce76dc7c3f
+pow 4241f7064c1a41a5ec4dc53f528552e9 393e4b8b7fdbb26aca528ce01295f4d6 5a827999fcef32422cbec4d9baa55179 4b5b2ee754f65abca70d2ee02783bb84
+pow 4241f7064c1a41a5ec4dc53f528552e9 393e4b8b7fdbb26aca528ce01295f4d6 5a827999fcef32422cbec4d9baa55ee5 13a5660d889b6c2b7679e4804cfb33b0
+pow 4241f7064c1a41a5ec4dc53f528552e9 393e4b8b7fdbb26aca528ce01295f4d6 5a827999fcef32422cbec4d9baa55171 b2bee6670600e5cdca742a65fd1ff8d
+pow 4241f7064c1a41a5ec4dc53f528552e9 393e4b8b7fdbb26aca528ce01295f4d6 5a827999fcef32422cbec4d9baa5c5e5 209018f064798d5de50dca68e1a67bbd
+pow 4241f7064c1a41a5ec4dc53f528552e9 393e4b8b7fdbb26aca528ce01295f4d6 5a827999fcef32422cbec4d9baa311c9 19480fc0e92332649012fe282ac6507b
+pow 4241f7064c1a41a5ec4dc53f528552e9 393e4b8b7fdbb26aca528ce01295f4d6 5a827999fcef3bb06cbec4d9baa55f4e 4b140ce53242560dbd6e997fd2c75683
+pow 2241f7064c1a41a5ec4dc53f528552e9 393e4b8b7fdbb26aca528ce01295f4d6 5a827999fcef32422cbec4d9baa55f4e 3c503d9f72a8809af1eef9883798b963
+pow 4241f7064c1a41a5ec4dc53f528552e9 393e4b8b7fdbb26aca528ce01295f4d6 5a827999fcef32422cbec4d9baa55f4d b507582651e7cd31dac1fe4f6e22f4b
+pow 4241f7064c1a41a5ec4dc53f528552e9 793e4b8b7fdbb26aca528ce01295f4d6 5a827999fcef32422cbec4d9baa55f4e 59acb6241548663232e48f1bce0780e7
+pow 40 727c9716484 870e7550437c3f9de6484 812319144f0d00b0c71c0
+pow 8483 b504f333f9de6485 3192ea3e99c8b504f333f9de6485 2b83edad87fec41bdd2a2c67757
+pow 8403ee0c9834834c 727c9716ffb764d5 b504f333f9de64504f333f9de6485 72de72a69fb7be9db98df5ff0666e
+pow 8483179c984c 727c9716ffb764d5 b504f333f333f9de6485 45f63d048e8bf05fe15
+pow 848334764d5 b504f333f9de6485 3192ea3e99c84934c 846cdce061d08961
+pow 8483ee0c98348346dd00267168dd764d5 b504f333f9de6485 31b504f333f9de6485 1d3c563ca7a8355ee2
+pow 8483ee0c9834834c 727c9716ffb764d5 8888888888888888888888d8888888b504f333f9de6485 262b90699d5729bbd17f13a5d5f72b8a37d53c5bc6b862
+pow 8483ee0c94c 72 b504f333f9de6485e6485 873c770cd74ff4fff6c16
+pow 8483e834c 727c9716ffb7644e0c9834834c 727c9716ffb7644246504f333f9de6485 98af64208d351df230c13e918ae5a012
+pow 8483ee0c9834834c 727c9716ffbf333f9de648834c 727c9716ffbf333f9de645 1d7d26536d52e6422b5b81
+pow 8483ee0c9834834c 727c9716ffb764d5 b504f333f9dde6485 ac208abc45050b286
+pow 8483ee0c9834834c 727c9716ffb764d5 b504f333f504f333f9d9de6485 5da8095154c4248389a323559c
+pow 8483ee0c9834834c 3f9de6485 82bd2a3e99c872e99c849de504f337c92ea3e99c849de504f333f9de6485 5cc8c6fc66f6da8d4fc03cf14157915bf0a46062ee2b87007a1ba5efd0ef
+pow 8483ee0c9834834c 727c9716ffb764d5 b504f33333f9de6485 9b7fbbd74f27281103
+pow 8483ee0c98345 b504f333f9 e6485 809bf
+pow 8483ee0c9834834c 727c9716ffb764d5 b5026823f9de6485 380c222e7b1c958
+pow 8483ee0c9834834c 727c9716ffb764d5 4d24f333f9de6485 2e7a29f446106c06
+pow 8483ee0c9834834c 727c9716ffb764d5 1714f333f9de6485 38687f6a43fd553
+pow 8483ee0c9834834c 727c9716ffb764d5 e914f333f9de6485 5f8173a3832a148b
+pow 8483ee0c9834834c 727c9716ffb764d5 1754f333f9de6485 eee8599db0a878e
+pow 8483ee0c9834834c 727c9716ffb764d5 2044f333f9de6485 1e559d32dc6c4d92
+pow 8604ee0c9834834c 727c9716ffb764d5 b504f333f9de6485 5341d938b4b2112e
+pow 37c3ee0c9834834c 727c9716ffb764d5 b504f333f9de6485 2783dee829d253f2
+pow 8483ee0c9834834c 727c9716ffb764d5 b504fa33f9de6485 1b81c5c38e77c229
+pow 8483ee0c9834834c 727c9716ffb764d5 b5c4f333f9de6485 6c231b78114a63d1
+pow 8483ee0c9834834c 7f7c9716ffb764d5 b504f333f9de6485 2cc9b662148976d8
+pow 8483ee0c9834834c a27c9716ffb764d5 b504f333f9de6485 9833d32827c56876
+pow 8483ee0c9834834c 827c9716ffb764d5 b504f333f9de6485 44640cc39fd4788
+pow 7483ee0c9834834c 727c9716ffb764d5 b504f333f9de6485 55086f87b6043f40
+pow 8483ee0c9834834c 727c9716ffb764d5 b504f333f9de6489 5de3cc538a4912b2
+pow 8483ee0c9834834c 727c9716ffb764d5 b504f333f9de7485 7dee09f7103356f7
+pow 8483ee0c9834834c 727c9716ffb764d5 b504f332f9de6485 31e66ba6b6f01dbc
+pow 8483ee0c9834834c 727c9716ffb764d5 b504f233f9de6485 36a16ab51897078d
+pow 8483ee0c9834834c 727c9716ffb764d5 b524f333f9de6485 50139fa7019a79b2
+pow 8483ee0c9834834c 727c9716ffb764d5 b544f333f9de6485 218964ebf2bf02bc
+pow 8483ee0c9834834c 727c9716ffb764d5 b704f333f9de6485 60f74c122162ed95
+pow 8483ee0c9834834c 727c9716ffb764d5 b104f333f9de6485 16855ca50047d45e
+pow 8483ee0c9834834c 727c9716ffb764d5 c504f333f9de6485 2d27db08d15c5e30
+pow 8483ee0c9834834c 727c9716ffb764d5 f504f333f9de6485 43c59cdd83e4cb0e
+pow 8493ee0c9834834c 727c9716ffb764d5 b504f333f9de6485 b8f2e35b869b538
+pow 8083ee0c9834834c 727c9716ffb764d5 b504f333f9de6485 709ba83061fbedec
+pow 9483ee0c9834834c 727c9716ffb764d5 b504f333f9de6485 74a637bb3a6686ab
+modmul 13988e 194c583ada5b529204a2bc83eb7b05d44d9baa55f4f890cd9bfea55a7 5a827999fcef32422cbec4d9baa55f4f8eb7b05d44d9baa55f4f89dd426768bd64 1efbde91a17127884d7d32ffd5f1f18750c651ddcb394371cfa91ff23baaa2
+modmul 13988e 194c583ada1b529204a2bc830cd9bfea55a7 5a827999fcef32422cbec4d9baa55f4f8eb7b05d449dd426768bd64 1efbde91a122c5504d7d32fec547ba91ff23baaa2
+modmul 13988e 194c583ade5b529204a2bc830cd9bfea55a7 5a827999fcef32422cbec4d9baa55f4f8eb7b05d449dd426768bd64 1efbde91a6574b084d7d32fec547ba91ff23baaa2
+modmul 13988e 194c583ada5b529204a2bc830cd9bfea55a7 3a827999fcef32422cbec4d9baa55f4 3168752910948165510fe90bd7ab5d6
+modmul 13988e 194c583ada7b529204a2b 5a827999fcef32422cb 564931ea2a366069fa0
+modmul 13988e 194c583ada1b529204a2b 5a827999fcef32422cb 5641d8b4ea366069fa0
+modmul 13988e 194c58 5a827999fcef32422cbec4d97999fcef32422cbec4d9baa55baa55f4f8eb7b05d449dd426768bd642cccccccccccccccccccccccccccccccc199cc8aa57 1efbde498d0
+modmul 13988e 194c58 5a827999fcef32422cbec4d9baa55f4f8eb7b05d449dd426768bf65f4f8eb7b05d449dd426768bd642c142c199cc8aa57 1efbde498d0
+modmul 13988e 194c58 5a827999fcef32422cbec827999fcef32422cbec4d99fcef32422cbe32422cbec8279aa55f4f8e4d9baa55f4f8eb 1efbde498d0
+modmul 13988e 194c58ee5827999fcef32422cbec4d9baa55f4f2422cbec4d9baa55f4f8ed 1efbde48eb 17247c498b
+modmul 13988e 194242b 1efbde4aa55f4f8eb 1eef84d697da
+modmul 6d28e 194c58 5a82799 4b07f1e
+modmul 13988e 194c58 3a8 90
+modmul 13988e 194c58 9a8 438
+modmul 93988e 194c58 5a8 418
+mul 16c2d9895a204eb19a1bb5b9b3c06d262c4bf90b74bfe8e1e 400000000000000000000000d294c6e5844edbe8b97f145b4ffd73ef7617d71a92e8006ee548e1e 5b0b662568813ac6686ed6e7fa92c1116b9e82723896e0c88f73a9faa96b8ac9b4c6efa28706e2696b0addabd81c3c9ff6167d1b5e537339bee8dfca0814b84
+mul 16c2d9895a204eb19a1bb5b9b3c06d262c4bf90b040fec72b389cf6fd375533 2cfd2d7d74d29145b4ffd73ef7617d71a92e8006ee548e1d 3ffffffffffffb03af375d6c1c311c6e51e081bfe99f6b41a84698ec8806beb21ddb37330f58d39c67989aa2ebdd2300fecefebf42f0c7
+mul 16c2d9895a204eb00000000000000000004bf90b74bfec72b389cf6fd375533 2cfd2d7d74d294c6e5844edbe85b4ffd73ef7617d71a92e8006ee548e1e 3ffffffffffffffb7eda681a2eca0a66bebccfbd4ca3fa67c9ba58e8735028859d32a47c8411726411fd5c7779d7ce5b04076dbea2c376bf5bc7a45fa
+mul 16c2d9895a204eb19a1bb5b9b3c06d262c4bf90b74bfec72b389cfd2d7d74d294c6e5844edbe8b97f145b4ffd73ef7617d71a92e8006ee548e1e 40000000000000 5b0b662568813ac6686ed6e6cf01b498b12fe42dd2ffb1cace273f4b5f5d34a531b96113b6fa2e5fc516d3ff5cfbdd85f5c6a4ba001bb95238780000000000000
+mul 16c2d9895a2bf90b74bfec72b389cf6fd375533 2cfd2d7d74d294c6e5844edbe8b97f145b4ffd73ef7617d71a92e8006ee548e1e 400000000020cd0df0a99c9bcec9106a845f2589fb29d491cf9909dc7c53bc3dcad9a6eca00e5b04076dbea2c376bf5bc7a45fa
+mul 16c2d9895a204eb19a1bb5b9b3c06d262c4bf90b74bfec72b389cf6fd375533 2cfd2d7d74d294c6e5844edbe8b97f145b4ffd73ef7617d71a92e8006ee548e9e 400000000000000000000000000000000000000000000000000000000000000b616cc4ad102758cd0ddadcd9e037357125cae0be6763f7fc885e7745824df7a
+mul 16c2d9895a204eb19a1bb5b9b3c06d262c4bf90b74bfec72b389cf6fd375533 cfd2d7d74d294c6e5844edbe8b97f145b4ffd73ef7617d71a92e8006ee548e1e 127a4ced4bbf629ccbc8948c987f25b3a7680de91680271a98ec6120591559a00000000000000000000000000000a25affce5b04076dbea2c376bf5bc7a45fa
+mul 16c2d9895a204eb19a1bb5b9b3c06d262c4bf90b74bfec76b389cf6fd375533 2cfd2d7d74d294c6e5844edbe8b97f145b4ffd73ef7617d71a92e8006ee548e1e 40000000000000000000000000000000000000000000000b3f4b5f5d34a531b96113b6fa2e5fc516d3ff5cfbdd869821a4885b1fc0bff71ac376bf5bc7a45fa
+mul 16c2d9895a204eb19a1bb5b9b3c06d272c4bf90b74bfec72b389cf6fd375533 2cfd2d7d74d294c6e5844edbe8b97f145b4ffd73ef7617d71a92e8006ee548e1e 40000000000000000000000000000002cfd2d7d74d294c6e5844edbe8b97f145b4ffd73ef7617d71a92e8006ee553078ffce5b04076dbea2c376bf5bc7a45fa
+mul 16c2d9895a204eb59a1bb5b9b3c06d262c4bf90b74bfec72b389cf6fd375533 2cfd2d7d74d294c6e5844edbe8b97f145b4ffd73ef7617d71a92e8006ee548e1e 400000000000000b3f4b5f5d34a531b96113b6fa2e5fc516d3ff5cfbdd85f5c6a4ba001bb9523878000000000000a25affce5b04076dbea2c376bf5bc7a45fa
+mul 12c2d9895a204eb19a1bb5b9b3c06d262c4bf90b74bfec72b389cf6fd375533 2cfd2d7d74d294c6e5844edbe8b97f145b4ffd73ef7617d71a92e8006ee548e1e 34c0b4a0a2cb5ace469eec4905d1a03ae92c00a304227a0a395b45ffe446adc78800000000000000000000000000a25affce5b04076dbea2c376bf5bc7a45fa
+mul 36c2d9895a204eb19a1bb5b9b3c06d262c4bf90b74bfec72b389cf6fd375533 2cfd2d7d74d294c6e5844edbe8b97f145b4ffd73ef7617d71a92e8006ee548e1e 99fa5afae9a5298dcb089db7d172fe28b69ffae7deec2fae3525d000ddca91c3c000000000000000000000000000a25affce5b04076dbea2c376bf5bc7a45fa
+mul 56c2d9895a204eb19a1bb5b9b3c06d262c4bf90b74bfec72b389cf6fd375533 2cfd2d7d74d294c6e5844edbe8b97f145b4ffd73ef7617d71a92e8006ee548e1e f3f4b5f5d34a531b96113b6fa2e5fc516d3ff5cfbdd85f5c6a4ba001bb9523878000000000000000000000000000a25affce5b04076dbea2c376bf5bc7a45fa
+mul 96c2d9895a204eb19a1bb5b9b3c06d262c4bf90b74bfec72b389cf6fd375533 2cfd2d7d74d294c6e5844edbe8b97f145b4ffd73ef7617d71a92e8006ee548e1e 1a7e96beba694a6372c2276df45cbf8a2da7feb9f7bb0beb8d4974003772a470f0000000000000000000000000000a25affce5b04076dbea2c376bf5bc7a45fa
+mul 1 bbbbbbbbb0 bbbbbbbbb0
+mul 1 a a
+mul 1 e e
+mul a c 78
+mul 1 1 1
+mul a 4 28
+mul 1 2 2
+mul 1 8 8
+mul 1 0 0
+mul 0 4 0
+mul 3 4 c
+mul 5 4 14
+mul 9 4 24
index b20677069b8fdd4a26a68b6b0afc01421cbe2860..ac2ce841f08fbbb54c507a4cf685aa59849db21f 100644 (file)
@@ -10,3 +10,7 @@ VT100 line drawing characters, actually using the VT100 escapes
 \ ex\ f     stuff down here         \ ex\ f       is quite inane        \ ex\ f
 \ ex\ f                             \ ex\ f                             \ ex\ f
 \ emqqqqqqqqqqpoopqrssrqqqqqqqqqqvqqqqqqqqqqpoopqrssrqqqqqqqqqqj\ f
+
+(This won't do the right thing in PuTTY's default UTF-8 translation;
+you'll just see lqqqk letters. It should work with a character set
+like ISO8859-1.)
index ccd76cd66d88f7f4916500ceac9de43c0aa5021a..696c1e1d7ce58281052299dbc9c8bb2e3e49d67c 100644 (file)
--- a/timing.c
+++ b/timing.c
@@ -148,6 +148,17 @@ unsigned long schedule_timer(int ticks, timer_fn_t fn, void *ctx)
     return when;
 }
 
+unsigned long timing_last_clock(void)
+{
+    /*
+     * Return the last value we stored in 'now'. In particular,
+     * calling this just after schedule_timer returns the value of
+     * 'now' that was used to decide when the timer you just set would
+     * go off.
+     */
+    return now;
+}
+
 /*
  * Call to run any timers whose time has reached the present.
  * Returns the time (in ticks) expected until the next timer after
diff --git a/unix/gtkapp.c b/unix/gtkapp.c
new file mode 100644 (file)
index 0000000..249be2d
--- /dev/null
@@ -0,0 +1,288 @@
+/*
+ * gtkapp.c: a top-level front end to GUI PuTTY and pterm, using
+ * GtkApplication. Suitable for OS X. Currently unfinished.
+ *
+ * (You could run it on ordinary Linux GTK too, in principle, but I
+ * don't think it would be particularly useful to do so, even once
+ * it's fully working.)
+ */
+
+/*
+
+To build on OS X, you will need a build environment with GTK 3 and
+gtk-mac-bundler, and also Halibut on the path (to build the man pages,
+without which the standard Makefile will complain). Then, from a clean
+checkout, do this:
+
+./mkfiles.pl -U --with-quartz
+make -C icons icns
+make -C doc
+make
+
+and you should get unix/PuTTY.app and unix/PTerm.app as output.
+
+*/
+
+/*
+
+TODO list for a sensible GTK3 PuTTY/pterm on OS X:
+
+Menu items' keyboard shortcuts (Command-Q for Quit, Command-V for
+Paste) do not currently work. It's intentional that if you turn on
+'Command key acts as Meta' in the configuration then those shortcuts
+should be superseded by the Meta-key functionality (e.g. Cmd-Q should
+send ESC Q to the session), for the benefit of people whose non-Mac
+keyboard reflexes expect the Meta key to be in that position; but if
+you don't turn that option on, then these shortcuts should work as an
+ordinary Mac user expects, and currently they don't.
+
+Windows don't close sensibly when their sessions terminate. This is
+because until now I've relied on calling cleanup_exit() or
+gtk_main_quit() in gtkwin.c to terminate the program, which is
+conceptually wrong in this situation (we don't want to quit the whole
+application when just one window closes) and also doesn't reliably
+work anyway (GtkApplication doesn't seem to have a gtk_main invocation
+in it at all, so those calls to gtk_main_quit produce a GTK assertion
+failure message on standard error). Need to introduce a proper 'clean
+up this struct gui_data' function (including finalising other stuff
+dangling off it like the backend), call that, and delete just that one
+window. (And then work out a replacement mechanism for having the
+ordinary Unix-style gtkmain.c based programs terminate when their
+session does.) connection_fatal() in particular should invoke this
+mechanism, and terminate just the connection that had trouble.
+
+Mouse wheel events and trackpad scrolling gestures don't work quite
+right in the terminal drawing area.
+
+There doesn't seem to be a resize handle on terminal windows. I don't
+think this is a fundamental limitation of OS X GTK (their demo app has
+one), so perhaps I need to do something to make sure it appears?
+
+A slight oddity with menus that pop up directly under the mouse
+pointer: mousing over the menu items doesn't highlight them initially,
+but if I mouse off the menu and back on (without un-popping-it-up)
+then suddenly that does work. I don't know if this is something I can
+fix, though; it might very well be a quirk of the underlying GTK.
+
+I want to arrange *some* way to paste efficiently using my Apple
+wireless keyboard and trackpad. The trackpad doesn't provide a middle
+button; I can't use the historic Shift-Ins shortcut because the
+keyboard has no Ins key; I configure the Command key to be Meta, so
+Command-V is off the table too. I can always use the menu, but I'd
+prefer there to be _some_ easily reachable mouse or keyboard gesture.
+
+Revamping the clipboard handling in general is going to be needed, as
+well. Not everybody will want the current auto-copy-on-select
+behaviour inherited from ordinary Unix PuTTY. Should arrange to have a
+mode in which you have to take an explicit Copy action, and then
+arrange that the Edit menu includes one of those.
+
+Dialog boxes shouldn't be modal. I think this is a good policy change
+in general, and the required infrastructure changes will benefit the
+Windows front end too, but for a multi-session process it's even more
+critical - you need to be able to type into one session window while
+setting up the configuration for launching another. So everywhere we
+currently run a sub-instance of gtk_main, or call any API function
+that implicitly does that (like gtk_dialog_run), we should switch to
+putting up the dialog box and going back to our ordinary main loop,
+and whatever we were going to do after the dialog closed we should
+remember to do it when that happens later on. Also then we can remove
+the post_main() horror from gtkcomm.c.
+
+The application menu bar is very minimal at the moment. Should include
+all the usual stuff from the Ctrl-right-click menu - saved sessions,
+mid-session special commands, Duplicate Session, Change Settings,
+Event Log, clear scrollback, reset terminal, about box, anything else
+I can think of.
+
+Does OS X have a standard system of online help that I could tie into?
+
+Need to work out what if anything we can do with Pageant on OS X.
+Perhaps it's too much bother and we should just talk to the
+system-provided SSH agent? Or perhaps not.
+
+Nice-to-have: a custom right-click menu from the application's dock
+tile, listing the saved sessions for quick launch. As far as I know
+there's nothing built in to GtkApplication that can produce this, but
+it's possible we might be able to drop a piece of native Cocoa code in
+under ifdef, substituting an application delegate of our own which
+forwards all methods we're not interested in to the GTK-provided one?
+
+At the point where this becomes polished enough to publish pre-built,
+I suppose I'll have to look into OS X code signing.
+https://wiki.gnome.org/Projects/GTK%2B/OSX/Bundling has some links.
+
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include <unistd.h>
+
+#include <gtk/gtk.h>
+
+#define MAY_REFER_TO_GTK_IN_HEADERS
+
+#include "putty.h"
+
+char *x_get_default(const char *key) { return NULL; }
+
+#if !GTK_CHECK_VERSION(3,0,0)
+/* This front end only works in GTK 3. If that's not what we've got,
+ * it's easier to just turn this program into a trivial stub by ifdef
+ * in the source than it is to remove it in the makefile edifice. */
+int main(int argc, char **argv)
+{
+    fprintf(stderr, "launcher does nothing on non-OSX platforms\n");
+    return 1;
+}
+GtkWidget *make_gtk_toplevel_window(void *frontend) { return NULL; }
+void launch_duplicate_session(Conf *conf) {}
+void launch_new_session(void) {}
+void launch_saved_session(const char *str) {}
+#else /* GTK_CHECK_VERSION(3,0,0) */
+
+static void startup(GApplication *app, gpointer user_data)
+{
+    GMenu *menubar, *menu, *section;
+
+    menubar = g_menu_new();
+
+    menu = g_menu_new();
+    g_menu_append_submenu(menubar, "File", G_MENU_MODEL(menu));
+
+    section = g_menu_new();
+    g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
+    g_menu_append(section, "New Window", "app.newwin");
+
+    menu = g_menu_new();
+    g_menu_append_submenu(menubar, "Edit", G_MENU_MODEL(menu));
+
+    section = g_menu_new();
+    g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
+    g_menu_append(section, "Paste", "win.paste");
+
+    gtk_application_set_menubar(GTK_APPLICATION(app),
+                                G_MENU_MODEL(menubar));
+}
+
+static void paste_cb(GSimpleAction *action,
+                     GVariant      *parameter,
+                     gpointer       user_data)
+{
+    request_paste(user_data);
+}
+
+static const GActionEntry win_actions[] = {
+    { "paste", paste_cb },
+};
+
+static GtkApplication *app;
+GtkWidget *make_gtk_toplevel_window(void *frontend)
+{
+    GtkWidget *win = gtk_application_window_new(app);
+    g_action_map_add_action_entries(G_ACTION_MAP(win),
+                                    win_actions,
+                                    G_N_ELEMENTS(win_actions),
+                                    frontend);
+    return win;
+}
+
+extern int cfgbox(Conf *conf);
+
+void launch_duplicate_session(Conf *conf)
+{
+    extern const int dup_check_launchable;
+    assert(!dup_check_launchable || conf_launchable(conf));
+    new_session_window(conf, NULL);
+}
+
+void launch_new_session(void)
+{
+    Conf *conf = conf_new();
+    do_defaults(NULL, conf);
+    if (conf_launchable(conf) || cfgbox(conf)) {
+        new_session_window(conf, NULL);
+    }
+}
+
+void launch_saved_session(const char *str)
+{
+    Conf *conf = conf_new();
+    do_defaults(str, conf);
+    if (conf_launchable(conf) || cfgbox(conf)) {
+        new_session_window(conf, NULL);
+    }
+}
+
+void new_app_win(GtkApplication *app)
+{
+    launch_new_session();
+}
+
+static void activate(GApplication *app,
+                     gpointer      user_data)
+{
+    new_app_win(GTK_APPLICATION(app));
+}
+
+static void newwin_cb(GSimpleAction *action,
+                      GVariant      *parameter,
+                      gpointer       user_data)
+{
+    new_app_win(GTK_APPLICATION(user_data));
+}
+
+static void quit_cb(GSimpleAction *action,
+                    GVariant      *parameter,
+                    gpointer       user_data)
+{
+    g_application_quit(G_APPLICATION(user_data));
+}
+
+static const GActionEntry app_actions[] = {
+    { "newwin", newwin_cb },
+    { "quit", quit_cb },
+};
+
+int main(int argc, char **argv)
+{
+    int status;
+
+    {
+        /* Call the function in ux{putty,pterm}.c to do app-type
+         * specific setup */
+        extern void setup(int);
+        setup(FALSE);     /* FALSE means we are not a one-session process */
+    }
+
+    if (argc > 1) {
+        extern char *pty_osx_envrestore_prefix;
+        pty_osx_envrestore_prefix = argv[--argc];
+    }
+
+    {
+        const char *home = getenv("HOME");
+        if (home) {
+            if (chdir(home)) {}
+        }
+    }
+
+    gtkcomm_setup();
+
+    app = gtk_application_new("org.tartarus.projects.putty.macputty",
+                              G_APPLICATION_FLAGS_NONE);
+    g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
+    g_signal_connect(app, "startup", G_CALLBACK(startup), NULL);
+    g_action_map_add_action_entries(G_ACTION_MAP(app),
+                                    app_actions,
+                                    G_N_ELEMENTS(app_actions),
+                                    app);
+
+    status = g_application_run(G_APPLICATION(app), argc, argv);
+    g_object_unref(app);
+
+    return status;
+}
+
+#endif /* GTK_CHECK_VERSION(3,0,0) */
diff --git a/unix/gtkcomm.c b/unix/gtkcomm.c
new file mode 100644 (file)
index 0000000..2888465
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+ * gtkcomm.c: machinery in the GTK front end which is common to all
+ * programs that run a session in a terminal window, and also common
+ * across all _sessions_ rather than specific to one session. (Timers,
+ * uxsel etc.)
+ */
+
+#define _GNU_SOURCE
+
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <stdio.h>
+#include <time.h>
+#include <errno.h>
+#include <locale.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <gtk/gtk.h>
+#if !GTK_CHECK_VERSION(3,0,0)
+#include <gdk/gdkkeysyms.h>
+#endif
+
+#if GTK_CHECK_VERSION(2,0,0)
+#include <gtk/gtkimmodule.h>
+#endif
+
+#define MAY_REFER_TO_GTK_IN_HEADERS
+
+#include "putty.h"
+#include "terminal.h"
+#include "gtkcompat.h"
+#include "gtkfont.h"
+#include "gtkmisc.h"
+
+#ifndef NOT_X_WINDOWS
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#endif
+
+#define CAT2(x,y) x ## y
+#define CAT(x,y) CAT2(x,y)
+#define ASSERT(x) enum {CAT(assertion_,__LINE__) = 1 / (x)}
+
+#if GTK_CHECK_VERSION(2,0,0)
+ASSERT(sizeof(long) <= sizeof(gsize));
+#define LONG_TO_GPOINTER(l) GSIZE_TO_POINTER(l)
+#define GPOINTER_TO_LONG(p) GPOINTER_TO_SIZE(p)
+#else /* Gtk 1.2 */
+ASSERT(sizeof(long) <= sizeof(gpointer));
+#define LONG_TO_GPOINTER(l) ((gpointer)(long)(l))
+#define GPOINTER_TO_LONG(p) ((long)(p))
+#endif
+
+/* ----------------------------------------------------------------------
+ * File descriptors and uxsel.
+ */
+
+struct uxsel_id {
+#if GTK_CHECK_VERSION(2,0,0)
+    GIOChannel *chan;
+    guint watch_id;
+#else
+    int id;
+#endif
+};
+
+#if GTK_CHECK_VERSION(2,0,0)
+gboolean fd_input_func(GIOChannel *source, GIOCondition condition,
+                       gpointer data)
+{
+    int sourcefd = g_io_channel_unix_get_fd(source);
+    /*
+     * We must process exceptional notifications before ordinary
+     * readability ones, or we may go straight past the urgent
+     * marker.
+     */
+    if (condition & G_IO_PRI)
+        select_result(sourcefd, 4);
+    if (condition & G_IO_IN)
+        select_result(sourcefd, 1);
+    if (condition & G_IO_OUT)
+        select_result(sourcefd, 2);
+
+    return TRUE;
+}
+#else
+void fd_input_func(gpointer data, gint sourcefd, GdkInputCondition condition)
+{
+    if (condition & GDK_INPUT_EXCEPTION)
+        select_result(sourcefd, 4);
+    if (condition & GDK_INPUT_READ)
+        select_result(sourcefd, 1);
+    if (condition & GDK_INPUT_WRITE)
+        select_result(sourcefd, 2);
+}
+#endif
+
+uxsel_id *uxsel_input_add(int fd, int rwx) {
+    uxsel_id *id = snew(uxsel_id);
+
+#if GTK_CHECK_VERSION(2,0,0)
+    int flags = 0;
+    if (rwx & 1) flags |= G_IO_IN;
+    if (rwx & 2) flags |= G_IO_OUT;
+    if (rwx & 4) flags |= G_IO_PRI;
+    id->chan = g_io_channel_unix_new(fd);
+    g_io_channel_set_encoding(id->chan, NULL, NULL);
+    id->watch_id = g_io_add_watch_full(id->chan, GDK_PRIORITY_REDRAW+1, flags,
+                                       fd_input_func, NULL, NULL);
+#else
+    int flags = 0;
+    if (rwx & 1) flags |= GDK_INPUT_READ;
+    if (rwx & 2) flags |= GDK_INPUT_WRITE;
+    if (rwx & 4) flags |= GDK_INPUT_EXCEPTION;
+    assert(flags);
+    id->id = gdk_input_add(fd, flags, fd_input_func, NULL);
+#endif
+
+    return id;
+}
+
+void uxsel_input_remove(uxsel_id *id) {
+#if GTK_CHECK_VERSION(2,0,0)
+    g_source_remove(id->watch_id);
+    g_io_channel_unref(id->chan);
+#else
+    gdk_input_remove(id->id);
+#endif
+    sfree(id);
+}
+
+/* ----------------------------------------------------------------------
+ * Timers.
+ */
+
+static guint timer_id = 0;
+
+static gint timer_trigger(gpointer data)
+{
+    unsigned long now = GPOINTER_TO_LONG(data);
+    unsigned long next, then;
+    long ticks;
+
+    /*
+     * Destroy the timer we got here on.
+     */
+    if (timer_id) {
+       g_source_remove(timer_id);
+        timer_id = 0;
+    }
+
+    /*
+     * run_timers() may cause a call to timer_change_notify, in which
+     * case a new timer will already have been set up and left in
+     * timer_id. If it hasn't, and run_timers reports that some timing
+     * still needs to be done, we do it ourselves.
+     */
+    if (run_timers(now, &next) && !timer_id) {
+       then = now;
+       now = GETTICKCOUNT();
+       if (now - then > next - then)
+           ticks = 0;
+       else
+           ticks = next - now;
+       timer_id = g_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next));
+    }
+
+    /*
+     * Returning FALSE means 'don't call this timer again', which
+     * _should_ be redundant given that we removed it above, but just
+     * in case, return FALSE anyway.
+     */
+    return FALSE;
+}
+
+void timer_change_notify(unsigned long next)
+{
+    long ticks;
+
+    if (timer_id)
+       g_source_remove(timer_id);
+
+    ticks = next - GETTICKCOUNT();
+    if (ticks <= 0)
+       ticks = 1;                     /* just in case */
+
+    timer_id = g_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next));
+}
+
+/* ----------------------------------------------------------------------
+ * Toplevel callbacks.
+ */
+
+static guint toplevel_callback_idle_id;
+static int idle_fn_scheduled;
+
+static void notify_toplevel_callback(void *);
+
+/*
+ * Replacement code for the gtk_quit_add() function, which GTK2 - in
+ * their unbounded wisdom - deprecated without providing any usable
+ * replacement, and which we were using to ensure that our idle
+ * function for toplevel callbacks was only run from the outermost
+ * gtk_main().
+ *
+ * We must make sure that all our subsidiary calls to gtk_main() are
+ * followed by a call to post_main(), so that the idle function can be
+ * re-established when we end up back at the top level.
+ */
+void post_main(void)
+{
+    if (gtk_main_level() == 1)
+        notify_toplevel_callback(NULL);
+}
+
+static gint idle_toplevel_callback_func(gpointer data)
+{
+    if (gtk_main_level() > 1) {
+        /*
+         * We don't run callbacks if we're in the middle of a
+         * subsidiary gtk_main. So unschedule this idle function; it
+         * will be rescheduled by post_main() when we come back up a
+         * level, which is the earliest we might actually do
+         * something.
+         */
+        if (idle_fn_scheduled) {      /* double-check, just in case */
+            g_source_remove(toplevel_callback_idle_id);
+            idle_fn_scheduled = FALSE;
+        }
+    } else {
+        run_toplevel_callbacks();
+    }
+
+    /*
+     * If we've emptied our toplevel callback queue, unschedule
+     * ourself. Otherwise, leave ourselves pending so we'll be called
+     * again to deal with more callbacks after another round of the
+     * event loop.
+     */
+    if (!toplevel_callback_pending() && idle_fn_scheduled) {
+        g_source_remove(toplevel_callback_idle_id);
+        idle_fn_scheduled = FALSE;
+    }
+
+    return TRUE;
+}
+
+static void notify_toplevel_callback(void *frontend)
+{
+    if (!idle_fn_scheduled) {
+        toplevel_callback_idle_id =
+            g_idle_add(idle_toplevel_callback_func, NULL);
+        idle_fn_scheduled = TRUE;
+    }
+}
+
+/* ----------------------------------------------------------------------
+ * Setup function. The real main program must call this.
+ */
+
+void gtkcomm_setup(void)
+{
+    uxsel_init();
+    request_callback_notifications(notify_toplevel_callback, NULL);
+}
index 7058c6e18e02628e37dbbe31c8606c63c5dbf774..34773137b231ef25848e9482657afc431343a19a 100644 (file)
@@ -3562,6 +3562,37 @@ int askalg(void *frontend, const char *algtype, const char *algname,
     }
 }
 
+int askhk(void *frontend, const char *algname, const char *betteralgs,
+          void (*callback)(void *ctx, int result), void *ctx)
+{
+    static const char msg[] =
+       "The first host key type we have stored for this server\n"
+       "is %s, which is below the configured warning threshold.\n"
+       "The server also provides the following types of host key\n"
+        "above the threshold, which we do not have stored:\n"
+        "%s\n"
+       "Continue with connection?";
+    char *text;
+    int ret;
+
+    text = dupprintf(msg, algname, betteralgs);
+    ret = messagebox(GTK_WIDGET(get_window(frontend)),
+                    "PuTTY Security Alert", text,
+                    string_width("is ecdsa-nistp521, which is"
+                                  " below the configured warning threshold."),
+                     FALSE,
+                    "Yes", 'y', 0, 1,
+                    "No", 'n', 0, 0,
+                    NULL);
+    sfree(text);
+
+    if (ret) {
+       return 1;
+    } else {
+       return 0;
+    }
+}
+
 void old_keyfile_warning(void)
 {
     /*
index 2f7e4a6172e96eb2b87cd6b92777befcd3732716..ed9888bceaa3b6ba4946ca1c4a9409c1e1896b19 100644 (file)
@@ -284,22 +284,12 @@ static int x11_font_width(XFontStruct *xfs, int sixteen_bit)
     }
 }
 
-static int x11_font_has_glyph(XFontStruct *xfs, int byte1, int byte2)
+static const XCharStruct *x11_char_struct(XFontStruct *xfs,
+                                          int byte1, int byte2)
 {
     int index;
 
     /*
-     * Not to be confused with x11font_has_glyph, which is a method of
-     * the x11font 'class' and hence takes a unifont as argument. This
-     * is the low-level function which grubs about in an actual
-     * XFontStruct to see if a given glyph exists.
-     *
-     * We must do this ourselves rather than letting Xlib's
-     * XTextExtents16 do the job, because XTextExtents will helpfully
-     * substitute the font's default_char for any missing glyph and
-     * not tell us it did so, which precisely won't help us find out
-     * which glyphs _are_ missing.
-     *
      * The man page for XQueryFont is rather confusing about how the
      * per_char array in the XFontStruct is laid out, because it gives
      * formulae for determining the two-byte X character code _from_
@@ -340,10 +330,27 @@ static int x11_font_has_glyph(XFontStruct *xfs, int byte1, int byte2)
     }
 
     if (!xfs->per_char)   /* per_char NULL => everything in range exists */
-        return TRUE;
+        return &xfs->max_bounds;
+
+    return &xfs->per_char[index];
+}
 
-    return (xfs->per_char[index].ascent + xfs->per_char[index].descent > 0 ||
-            xfs->per_char[index].width > 0);
+static int x11_font_has_glyph(XFontStruct *xfs, int byte1, int byte2)
+{
+    /*
+     * Not to be confused with x11font_has_glyph, which is a method of
+     * the x11font 'class' and hence takes a unifont as argument. This
+     * is the low-level function which grubs about in an actual
+     * XFontStruct to see if a given glyph exists.
+     *
+     * We must do this ourselves rather than letting Xlib's
+     * XTextExtents16 do the job, because XTextExtents will helpfully
+     * substitute the font's default_char for any missing glyph and
+     * not tell us it did so, which precisely won't help us find out
+     * which glyphs _are_ missing.
+     */
+    const XCharStruct *xcs = x11_char_struct(xfs, byte1, byte2);
+    return xcs && (xcs->ascent + xcs->descent > 0 || xcs->width > 0);
 }
 
 static unifont *x11font_create(GtkWidget *widget, const char *name,
@@ -623,14 +630,18 @@ static void x11font_cairo_cache_glyph(x11font_individual *xfi, int glyphindex)
     int x, y;
     unsigned char *bitmap;
     Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+    const XCharStruct *xcs = x11_char_struct(xfi->xfs, glyphindex >> 8,
+                                             glyphindex & 0xFF);
 
     bitmap = snewn(xfi->allsize, unsigned char);
     memset(bitmap, 0, xfi->allsize);
 
     image = XGetImage(disp, xfi->pixmap, 0, 0,
                       xfi->pixwidth, xfi->pixheight, AllPlanes, XYPixmap);
-    for (y = 0; y < xfi->pixheight; y++) {
-        for (x = 0; x < xfi->pixwidth; x++) {
+    for (y = xfi->pixoriginy - xcs->ascent;
+         y < xfi->pixoriginy + xcs->descent; y++) {
+        for (x = xfi->pixoriginx + xcs->lbearing;
+             x < xfi->pixoriginx + xcs->rbearing; x++) {
             unsigned long pixel = XGetPixel(image, x, y);
             if (pixel) {
                 int byteindex = y * xfi->rowsize + x/8;
@@ -1532,7 +1543,8 @@ static void pangofont_draw_internal(unifont_drawctx *ctx, unifont *font,
                            (unsigned char)utfptr[clen] < 0xC0)
                         clen++;
                     n++;
-                    if (pangofont_char_width(layout, pfont,
+                    if (is_rtl(string[n-1]) ||
+                        pangofont_char_width(layout, pfont,
                                              string[n-1], utfptr + oldclen,
                                              clen - oldclen) != desired) {
                         clen = oldclen;
diff --git a/unix/gtkmain.c b/unix/gtkmain.c
new file mode 100644 (file)
index 0000000..a7b4a4e
--- /dev/null
@@ -0,0 +1,631 @@
+/*
+ * gtkmain.c: the common main-program code between the straight-up
+ * Unix PuTTY and pterm, which they do not share with the
+ * multi-session gtkapp.c.
+ */
+
+#define _GNU_SOURCE
+
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <stdio.h>
+#include <time.h>
+#include <errno.h>
+#include <locale.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <gtk/gtk.h>
+#if !GTK_CHECK_VERSION(3,0,0)
+#include <gdk/gdkkeysyms.h>
+#endif
+
+#if GTK_CHECK_VERSION(2,0,0)
+#include <gtk/gtkimmodule.h>
+#endif
+
+#define MAY_REFER_TO_GTK_IN_HEADERS
+
+#include "putty.h"
+#include "terminal.h"
+#include "gtkcompat.h"
+#include "gtkfont.h"
+#include "gtkmisc.h"
+
+#ifndef NOT_X_WINDOWS
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#endif
+
+static char *progname, **gtkargvstart;
+static int ngtkargs;
+
+extern char **pty_argv;               /* declared in pty.c */
+extern int use_pty_argv;
+
+static const char *app_name = "pterm";
+
+char *x_get_default(const char *key)
+{
+#ifndef NOT_X_WINDOWS
+    return XGetDefault(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()),
+                       app_name, key);
+#else
+    return NULL;
+#endif
+}
+
+void fork_and_exec_self(int fd_to_close, ...)
+{
+    /*
+     * Re-execing ourself is not an exact science under Unix. I do
+     * the best I can by using /proc/self/exe if available and by
+     * assuming argv[0] can be found on $PATH if not.
+     * 
+     * Note that we also have to reconstruct the elements of the
+     * original argv which gtk swallowed, since the user wants the
+     * new session to appear on the same X display as the old one.
+     */
+    char **args;
+    va_list ap;
+    int i, n;
+    int pid;
+
+    /*
+     * Collect the arguments with which to re-exec ourself.
+     */
+    va_start(ap, fd_to_close);
+    n = 2;                            /* progname and terminating NULL */
+    n += ngtkargs;
+    while (va_arg(ap, char *) != NULL)
+       n++;
+    va_end(ap);
+
+    args = snewn(n, char *);
+    args[0] = progname;
+    args[n-1] = NULL;
+    for (i = 0; i < ngtkargs; i++)
+       args[i+1] = gtkargvstart[i];
+
+    i++;
+    va_start(ap, fd_to_close);
+    while ((args[i++] = va_arg(ap, char *)) != NULL);
+    va_end(ap);
+
+    assert(i == n);
+
+    /*
+     * Do the double fork.
+     */
+    pid = fork();
+    if (pid < 0) {
+       perror("fork");
+        sfree(args);
+       return;
+    }
+
+    if (pid == 0) {
+       int pid2 = fork();
+       if (pid2 < 0) {
+           perror("fork");
+           _exit(1);
+       } else if (pid2 > 0) {
+           /*
+            * First child has successfully forked second child. My
+            * Work Here Is Done. Note the use of _exit rather than
+            * exit: the latter appears to cause destroy messages
+            * to be sent to the X server. I suspect gtk uses
+            * atexit.
+            */
+           _exit(0);
+       }
+
+       /*
+        * If we reach here, we are the second child, so we now
+        * actually perform the exec.
+        */
+       if (fd_to_close >= 0)
+           close(fd_to_close);
+
+       execv("/proc/self/exe", args);
+       execvp(progname, args);
+       perror("exec");
+       _exit(127);
+
+    } else {
+       int status;
+        sfree(args);
+       waitpid(pid, &status, 0);
+    }
+
+}
+
+void launch_duplicate_session(Conf *conf)
+{
+    /*
+     * For this feature we must marshal conf and (possibly) pty_argv
+     * into a byte stream, create a pipe, and send this byte stream
+     * to the child through the pipe.
+     */
+    int i, ret, sersize, size;
+    char *data;
+    char option[80];
+    int pipefd[2];
+
+    if (pipe(pipefd) < 0) {
+       perror("pipe");
+       return;
+    }
+
+    size = sersize = conf_serialised_size(conf);
+    if (use_pty_argv && pty_argv) {
+       for (i = 0; pty_argv[i]; i++)
+           size += strlen(pty_argv[i]) + 1;
+    }
+
+    data = snewn(size, char);
+    conf_serialise(conf, data);
+    if (use_pty_argv && pty_argv) {
+       int p = sersize;
+       for (i = 0; pty_argv[i]; i++) {
+           strcpy(data + p, pty_argv[i]);
+           p += strlen(pty_argv[i]) + 1;
+       }
+       assert(p == size);
+    }
+
+    sprintf(option, "---[%d,%d]", pipefd[0], size);
+    noncloexec(pipefd[0]);
+    fork_and_exec_self(pipefd[1], option, NULL);
+    close(pipefd[0]);
+
+    i = ret = 0;
+    while (i < size && (ret = write(pipefd[1], data + i, size - i)) > 0)
+       i += ret;
+    if (ret < 0)
+       perror("write to pipe");
+    close(pipefd[1]);
+    sfree(data);
+}
+
+void launch_new_session(void)
+{
+    fork_and_exec_self(-1, NULL);
+}
+
+void launch_saved_session(const char *str)
+{
+    fork_and_exec_self(-1, "-load", str, NULL);
+}
+
+int read_dupsession_data(Conf *conf, char *arg)
+{
+    int fd, i, ret, size, size_used;
+    char *data;
+
+    if (sscanf(arg, "---[%d,%d]", &fd, &size) != 2) {
+       fprintf(stderr, "%s: malformed magic argument `%s'\n", appname, arg);
+       exit(1);
+    }
+
+    data = snewn(size, char);
+    i = ret = 0;
+    while (i < size && (ret = read(fd, data + i, size - i)) > 0)
+       i += ret;
+    if (ret < 0) {
+       perror("read from pipe");
+       exit(1);
+    } else if (i < size) {
+       fprintf(stderr, "%s: unexpected EOF in Duplicate Session data\n",
+               appname);
+       exit(1);
+    }
+
+    size_used = conf_deserialise(conf, data, size);
+    if (use_pty_argv && size > size_used) {
+       int n = 0;
+       i = size_used;
+       while (i < size) {
+           while (i < size && data[i]) i++;
+           if (i >= size) {
+               fprintf(stderr, "%s: malformed Duplicate Session data\n",
+                       appname);
+               exit(1);
+           }
+           i++;
+           n++;
+       }
+       pty_argv = snewn(n+1, char *);
+       pty_argv[n] = NULL;
+       n = 0;
+       i = size_used;
+       while (i < size) {
+           char *p = data + i;
+           while (i < size && data[i]) i++;
+           assert(i < size);
+           i++;
+           pty_argv[n++] = dupstr(p);
+       }
+    }
+
+    sfree(data);
+
+    return 0;
+}
+
+static void help(FILE *fp) {
+    if(fprintf(fp,
+"pterm option summary:\n"
+"\n"
+"  --display DISPLAY         Specify X display to use (note '--')\n"
+"  -name PREFIX              Prefix when looking up resources (default: pterm)\n"
+"  -fn FONT                  Normal text font\n"
+"  -fb FONT                  Bold text font\n"
+"  -geometry GEOMETRY        Position and size of window (size in characters)\n"
+"  -sl LINES                 Number of lines of scrollback\n"
+"  -fg COLOUR, -bg COLOUR    Foreground/background colour\n"
+"  -bfg COLOUR, -bbg COLOUR  Foreground/background bold colour\n"
+"  -cfg COLOUR, -bfg COLOUR  Foreground/background cursor colour\n"
+"  -T TITLE                  Window title\n"
+"  -ut, +ut                  Do(default) or do not update utmp\n"
+"  -ls, +ls                  Do(default) or do not make shell a login shell\n"
+"  -sb, +sb                  Do(default) or do not display a scrollbar\n"
+"  -log PATH, -sessionlog PATH  Log all output to a file\n"
+"  -nethack                  Map numeric keypad to hjklyubn direction keys\n"
+"  -xrm RESOURCE-STRING      Set an X resource\n"
+"  -e COMMAND [ARGS...]      Execute command (consumes all remaining args)\n"
+        ) < 0 || fflush(fp) < 0) {
+       perror("output error");
+       exit(1);
+    }
+}
+
+static void version(FILE *fp) {
+    if(fprintf(fp, "%s: %s\n", appname, ver) < 0 || fflush(fp) < 0) {
+       perror("output error");
+       exit(1);
+    }
+}
+
+static struct gui_data *the_inst;
+
+static const char *geometry_string;
+
+int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch,
+               Conf *conf)
+{
+    int err = 0;
+    char *val;
+
+    /*
+     * Macros to make argument handling easier. Note that because
+     * they need to call `continue', they cannot be contained in
+     * the usual do {...} while (0) wrapper to make them
+     * syntactically single statements; hence it is not legal to
+     * use one of these macros as an unbraced statement between
+     * `if' and `else'.
+     */
+#define EXPECTS_ARG { \
+    if (--argc <= 0) { \
+       err = 1; \
+       fprintf(stderr, "%s: %s expects an argument\n", appname, p); \
+        continue; \
+    } else \
+       val = *++argv; \
+}
+#define SECOND_PASS_ONLY { if (!do_everything) continue; }
+
+    while (--argc > 0) {
+       const char *p = *++argv;
+        int ret;
+
+       /*
+        * Shameless cheating. Debian requires all X terminal
+        * emulators to support `-T title'; but
+        * cmdline_process_param will eat -T (it means no-pty) and
+        * complain that pterm doesn't support it. So, in pterm
+        * only, we convert -T into -title.
+        */
+       if ((cmdline_tooltype & TOOLTYPE_NONNETWORK) &&
+           !strcmp(p, "-T"))
+           p = "-title";
+
+        ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
+                                    do_everything ? 1 : -1, conf);
+
+       if (ret == -2) {
+           cmdline_error("option \"%s\" requires an argument", p);
+       } else if (ret == 2) {
+           --argc, ++argv;            /* skip next argument */
+            continue;
+       } else if (ret == 1) {
+            continue;
+        }
+
+       if (!strcmp(p, "-fn") || !strcmp(p, "-font")) {
+           FontSpec *fs;
+           EXPECTS_ARG;
+           SECOND_PASS_ONLY;
+            fs = fontspec_new(val);
+           conf_set_fontspec(conf, CONF_font, fs);
+            fontspec_free(fs);
+
+       } else if (!strcmp(p, "-fb")) {
+           FontSpec *fs;
+           EXPECTS_ARG;
+           SECOND_PASS_ONLY;
+            fs = fontspec_new(val);
+           conf_set_fontspec(conf, CONF_boldfont, fs);
+            fontspec_free(fs);
+
+       } else if (!strcmp(p, "-fw")) {
+           FontSpec *fs;
+           EXPECTS_ARG;
+           SECOND_PASS_ONLY;
+            fs = fontspec_new(val);
+           conf_set_fontspec(conf, CONF_widefont, fs);
+            fontspec_free(fs);
+
+       } else if (!strcmp(p, "-fwb")) {
+           FontSpec *fs;
+           EXPECTS_ARG;
+           SECOND_PASS_ONLY;
+            fs = fontspec_new(val);
+           conf_set_fontspec(conf, CONF_wideboldfont, fs);
+            fontspec_free(fs);
+
+       } else if (!strcmp(p, "-cs")) {
+           EXPECTS_ARG;
+           SECOND_PASS_ONLY;
+           conf_set_str(conf, CONF_line_codepage, val);
+
+       } else if (!strcmp(p, "-geometry")) {
+           EXPECTS_ARG;
+           SECOND_PASS_ONLY;
+            geometry_string = val;
+       } else if (!strcmp(p, "-sl")) {
+           EXPECTS_ARG;
+           SECOND_PASS_ONLY;
+           conf_set_int(conf, CONF_savelines, atoi(val));
+
+       } else if (!strcmp(p, "-fg") || !strcmp(p, "-bg") ||
+                  !strcmp(p, "-bfg") || !strcmp(p, "-bbg") ||
+                  !strcmp(p, "-cfg") || !strcmp(p, "-cbg")) {
+           EXPECTS_ARG;
+           SECOND_PASS_ONLY;
+
+            {
+#if GTK_CHECK_VERSION(3,0,0)
+                GdkRGBA rgba;
+                int success = gdk_rgba_parse(&rgba, val);
+#else
+                GdkColor col;
+                int success = gdk_color_parse(val, &col);
+#endif
+
+                if (!success) {
+                    err = 1;
+                    fprintf(stderr, "%s: unable to parse colour \"%s\"\n",
+                            appname, val);
+                } else {
+#if GTK_CHECK_VERSION(3,0,0)
+                    int r = rgba.red * 255;
+                    int g = rgba.green * 255;
+                    int b = rgba.blue * 255;
+#else
+                    int r = col.red / 256;
+                    int g = col.green / 256;
+                    int b = col.blue / 256;
+#endif
+
+                    int index;
+                    index = (!strcmp(p, "-fg") ? 0 :
+                             !strcmp(p, "-bg") ? 2 :
+                             !strcmp(p, "-bfg") ? 1 :
+                             !strcmp(p, "-bbg") ? 3 :
+                             !strcmp(p, "-cfg") ? 4 :
+                             !strcmp(p, "-cbg") ? 5 : -1);
+                    assert(index != -1);
+
+                    conf_set_int_int(conf, CONF_colours, index*3+0, r);
+                    conf_set_int_int(conf, CONF_colours, index*3+1, g);
+                    conf_set_int_int(conf, CONF_colours, index*3+2, b);
+                }
+            }
+
+       } else if (use_pty_argv && !strcmp(p, "-e")) {
+           /* This option swallows all further arguments. */
+           if (!do_everything)
+               break;
+
+           if (--argc > 0) {
+               int i;
+               pty_argv = snewn(argc+1, char *);
+               ++argv;
+               for (i = 0; i < argc; i++)
+                   pty_argv[i] = argv[i];
+               pty_argv[argc] = NULL;
+               break;                 /* finished command-line processing */
+           } else
+               err = 1, fprintf(stderr, "%s: -e expects an argument\n",
+                                 appname);
+
+       } else if (!strcmp(p, "-title")) {
+           EXPECTS_ARG;
+           SECOND_PASS_ONLY;
+           conf_set_str(conf, CONF_wintitle, val);
+
+       } else if (!strcmp(p, "-log")) {
+           Filename *fn;
+           EXPECTS_ARG;
+           SECOND_PASS_ONLY;
+            fn = filename_from_str(val);
+           conf_set_filename(conf, CONF_logfilename, fn);
+           conf_set_int(conf, CONF_logtype, LGTYP_DEBUG);
+            filename_free(fn);
+
+       } else if (!strcmp(p, "-ut-") || !strcmp(p, "+ut")) {
+           SECOND_PASS_ONLY;
+           conf_set_int(conf, CONF_stamp_utmp, 0);
+
+       } else if (!strcmp(p, "-ut")) {
+           SECOND_PASS_ONLY;
+           conf_set_int(conf, CONF_stamp_utmp, 1);
+
+       } else if (!strcmp(p, "-ls-") || !strcmp(p, "+ls")) {
+           SECOND_PASS_ONLY;
+           conf_set_int(conf, CONF_login_shell, 0);
+
+       } else if (!strcmp(p, "-ls")) {
+           SECOND_PASS_ONLY;
+           conf_set_int(conf, CONF_login_shell, 1);
+
+       } else if (!strcmp(p, "-nethack")) {
+           SECOND_PASS_ONLY;
+           conf_set_int(conf, CONF_nethack_keypad, 1);
+
+       } else if (!strcmp(p, "-sb-") || !strcmp(p, "+sb")) {
+           SECOND_PASS_ONLY;
+           conf_set_int(conf, CONF_scrollbar, 0);
+
+       } else if (!strcmp(p, "-sb")) {
+           SECOND_PASS_ONLY;
+           conf_set_int(conf, CONF_scrollbar, 1);
+
+       } else if (!strcmp(p, "-name")) {
+           EXPECTS_ARG;
+           app_name = val;
+
+       } else if (!strcmp(p, "-xrm")) {
+           EXPECTS_ARG;
+           provide_xrm_string(val);
+
+       } else if(!strcmp(p, "-help") || !strcmp(p, "--help")) {
+           help(stdout);
+           exit(0);
+
+       } else if(!strcmp(p, "-version") || !strcmp(p, "--version")) {
+           version(stdout);
+           exit(0);
+
+        } else if (!strcmp(p, "-pgpfp")) {
+            pgp_fingerprints();
+            exit(1);
+
+       } else if(p[0] != '-' && (!do_everything ||
+                                  process_nonoption_arg(p, conf,
+                                                       allow_launch))) {
+            /* do nothing */
+
+       } else {
+           err = 1;
+           fprintf(stderr, "%s: unrecognized option '%s'\n", appname, p);
+       }
+    }
+
+    return err;
+}
+
+GtkWidget *make_gtk_toplevel_window(void *frontend)
+{
+    return gtk_window_new(GTK_WINDOW_TOPLEVEL);
+}
+
+extern int cfgbox(Conf *conf);
+
+int main(int argc, char **argv)
+{
+    Conf *conf;
+    int need_config_box;
+
+    setlocale(LC_CTYPE, "");
+
+    {
+        /* Call the function in ux{putty,pterm}.c to do app-type
+         * specific setup */
+        extern void setup(int);
+        setup(TRUE);     /* TRUE means we are a one-session process */
+    }
+
+    progname = argv[0];
+
+    /*
+     * Copy the original argv before letting gtk_init fiddle with
+     * it. It will be required later.
+     */
+    {
+       int i, oldargc;
+        gtkargvstart = snewn(argc-1, char *);
+       for (i = 1; i < argc; i++)
+           gtkargvstart[i-1] = dupstr(argv[i]);
+       oldargc = argc;
+       gtk_init(&argc, &argv);
+       ngtkargs = oldargc - argc;
+    }
+
+    conf = conf_new();
+
+    gtkcomm_setup();
+
+    /*
+     * Block SIGPIPE: if we attempt Duplicate Session or similar and
+     * it falls over in some way, we certainly don't want SIGPIPE
+     * terminating the main pterm/PuTTY. However, we'll have to
+     * unblock it again when pterm forks.
+     */
+    block_signal(SIGPIPE, 1);
+
+    if (argc > 1 && !strncmp(argv[1], "---", 3)) {
+        extern const int dup_check_launchable;
+
+       read_dupsession_data(conf, argv[1]);
+       /* Splatter this argument so it doesn't clutter a ps listing */
+       smemclr(argv[1], strlen(argv[1]));
+
+        assert(!dup_check_launchable || conf_launchable(conf));
+        need_config_box = FALSE;
+    } else {
+       /* By default, we bring up the config dialog, rather than launching
+        * a session. This gets set to TRUE if something happens to change
+        * that (e.g., a hostname is specified on the command-line). */
+       int allow_launch = FALSE;
+       if (do_cmdline(argc, argv, 0, &allow_launch, conf))
+           exit(1);                   /* pre-defaults pass to get -class */
+       do_defaults(NULL, conf);
+       if (do_cmdline(argc, argv, 1, &allow_launch, conf))
+           exit(1);                   /* post-defaults, do everything */
+
+       cmdline_run_saved(conf);
+
+       if (loaded_session)
+           allow_launch = TRUE;
+
+        need_config_box = (!allow_launch || !conf_launchable(conf));
+    }
+
+    /*
+     * Put up the config box.
+     */
+    if (need_config_box && !cfgbox(conf))
+        exit(0);                      /* config box hit Cancel */
+
+    /*
+     * Create the main session window. We don't really need to keep
+     * the return value - the fact that it'll be linked from a zillion
+     * GTK and glib bits and bobs known to the main loop will be
+     * sufficient to make everything actually happen - but we stash it
+     * in a global variable anyway, so that it'll be easy to find in a
+     * debugger.
+     */
+    the_inst = new_session_window(conf, geometry_string);
+
+    gtk_main();
+
+    return 0;
+}
index d3619e2d358a1493104911c9691d59d34db96617..6ee68be2e87b6d861635b4b4938636a3af29f650 100644 (file)
@@ -175,6 +175,8 @@ GtkBox *our_dialog_make_action_hbox(GtkWindow *dlg)
 #if GTK_CHECK_VERSION(3,0,0)
     GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
     our_dialog_set_action_area(dlg, hbox);
+    g_object_set(G_OBJECT(hbox), "margin", 0, (const char *)NULL);
+    g_object_set(G_OBJECT(hbox), "spacing", 8, (const char *)NULL);
     gtk_widget_show(hbox);
     return GTK_BOX(hbox);
 #else /* not GTK 3 */
index 192e20e170ac8e1667d04cd603ea8de5f51683b3..7ebccbcae79ad01285c005613b7a62e764661bf9 100644 (file)
 #include <X11/Xatom.h>
 #endif
 
-#define CAT2(x,y) x ## y
-#define CAT(x,y) CAT2(x,y)
-#define ASSERT(x) enum {CAT(assertion_,__LINE__) = 1 / (x)}
-
-#if GTK_CHECK_VERSION(2,0,0)
-ASSERT(sizeof(long) <= sizeof(gsize));
-#define LONG_TO_GPOINTER(l) GSIZE_TO_POINTER(l)
-#define GPOINTER_TO_LONG(p) GPOINTER_TO_SIZE(p)
-#else /* Gtk 1.2 */
-ASSERT(sizeof(long) <= sizeof(gpointer));
-#define LONG_TO_GPOINTER(l) ((gpointer)(long)(l))
-#define GPOINTER_TO_LONG(p) ((long)(p))
-#endif
+#include "x11misc.h"
 
 /* Colours come in two flavours: configurable, and xterm-extended. */
 #define NEXTCOLOURS 240 /* 216 colour-cube plus 24 shades of grey */
@@ -64,20 +52,11 @@ ASSERT(sizeof(long) <= sizeof(gpointer));
 
 GdkAtom compound_text_atom, utf8_string_atom;
 
-extern char **pty_argv;               /* declared in pty.c */
-extern int use_pty_argv;
-
-/*
- * Timers are global across all sessions (even if we were handling
- * multiple sessions, which we aren't), so the current timer ID is
- * a global variable.
- */
-static guint timer_id = 0;
-
 struct clipboard_data_instance;
 
 struct gui_data {
     GtkWidget *window, *area, *sbar;
+    gboolean sbar_visible;
     GtkBox *hbox;
     GtkAdjustment *sbar_adjust;
     GtkWidget *menu, *specialsmenu, *specialsitem1, *specialsitem2,
@@ -140,8 +119,6 @@ struct gui_data {
     int ignore_sbar;
     int mouseptr_visible;
     int busy_status;
-    guint toplevel_callback_idle_id;
-    int idle_fn_scheduled, quit_fn_scheduled;
     int alt_keycode;
     int alt_digits;
     char *wintitle;
@@ -156,8 +133,6 @@ struct gui_data {
     struct unicode_data ucsdata;
     Conf *conf;
     void *eventlogstuff;
-    char *progname, **gtkargvstart;
-    int ngtkargs;
     guint32 input_event_time; /* Timestamp of the most recent input event. */
     int reconfiguring;
 #if GTK_CHECK_VERSION(3,4,0)
@@ -194,21 +169,9 @@ struct draw_ctx {
 
 static int send_raw_mouse;
 
-static const char *app_name = "pterm";
-
 static void start_backend(struct gui_data *inst);
 static void exit_callback(void *vinst);
 
-char *x_get_default(const char *key)
-{
-#ifndef NOT_X_WINDOWS
-    return XGetDefault(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()),
-                       app_name, key);
-#else
-    return NULL;
-#endif
-}
-
 void connection_fatal(void *frontend, const char *p, ...)
 {
     struct gui_data *inst = (struct gui_data *)frontend;
@@ -2012,48 +1975,6 @@ static void exit_callback(void *vinst)
     }
 }
 
-/*
- * Replacement code for the gtk_quit_add() function, which GTK2 - in
- * their unbounded wisdom - deprecated without providing any usable
- * replacement, and which we were using to ensure that our idle
- * function for toplevel callbacks was only run from the outermost
- * gtk_main().
- *
- * We maintain a global variable with a list of 'struct gui_data'
- * instances on which we should call inst_post_main() when a
- * subsidiary gtk_main() terminates; then we must make sure that all
- * our subsidiary calls to gtk_main() are followed by a call to
- * post_main().
- *
- * This is kind of overkill in the sense that at the time of writing
- * we don't actually ever run more than one 'struct gui_data' instance
- * in the same process, but we're _so nearly_ prepared to do that that
- * I want to remain futureproof against the possibility of doing so in
- * future.
- */
-struct post_main_context {
-    struct post_main_context *next;
-    struct gui_data *inst;
-};
-struct post_main_context *post_main_list_head = NULL;
-static void request_post_main(struct gui_data *inst)
-{
-    struct post_main_context *node = snew(struct post_main_context);
-    node->next = post_main_list_head;
-    node->inst = inst;
-    post_main_list_head = node;
-}
-static void inst_post_main(struct gui_data *inst);
-void post_main(void)
-{
-    while (post_main_list_head) {
-        struct post_main_context *node = post_main_list_head;
-        post_main_list_head = node->next;
-        inst_post_main(node->inst);
-        sfree(node);
-    }
-}
-
 void notify_remote_exit(void *frontend)
 {
     struct gui_data *inst = (struct gui_data *)frontend;
@@ -2061,157 +1982,6 @@ void notify_remote_exit(void *frontend)
     queue_toplevel_callback(exit_callback, inst);
 }
 
-static void notify_toplevel_callback(void *frontend);
-
-static void inst_post_main(struct gui_data *inst)
-{
-    if (gtk_main_level() == 1) {
-        notify_toplevel_callback(inst);
-        inst->quit_fn_scheduled = FALSE;
-    } else {
-        /* Apparently we're _still_ more than one level deep in
-         * gtk_main() instances, so we'll need another callback for
-         * when we get out of the next one. */
-        request_post_main(inst);
-    }
-}
-
-static gint idle_toplevel_callback_func(gpointer data)
-{
-    struct gui_data *inst = (struct gui_data *)data;
-
-    if (gtk_main_level() > 1) {
-        /*
-         * We don't run the callbacks if we're in the middle of a
-         * subsidiary gtk_main. Instead, ask for a callback when we
-         * get back out of the subsidiary main loop (if we haven't
-         * already arranged one), so we can reschedule ourself then.
-         */
-        if (!inst->quit_fn_scheduled) {
-            request_post_main(inst);
-            inst->quit_fn_scheduled = TRUE;
-        }
-        /*
-         * And unschedule this idle function, since we've now done
-         * everything we can until the innermost gtk_main has quit and
-         * can reschedule us with a chance of actually taking action.
-         */
-        if (inst->idle_fn_scheduled) { /* double-check, just in case */
-            g_source_remove(inst->toplevel_callback_idle_id);
-            inst->idle_fn_scheduled = FALSE;
-        }
-    } else {
-        run_toplevel_callbacks();
-    }
-
-    /*
-     * If we've emptied our toplevel callback queue, unschedule
-     * ourself. Otherwise, leave ourselves pending so we'll be called
-     * again to deal with more callbacks after another round of the
-     * event loop.
-     */
-    if (!toplevel_callback_pending() && inst->idle_fn_scheduled) {
-        g_source_remove(inst->toplevel_callback_idle_id);
-        inst->idle_fn_scheduled = FALSE;
-    }
-
-    return TRUE;
-}
-
-static void notify_toplevel_callback(void *frontend)
-{
-    struct gui_data *inst = (struct gui_data *)frontend;
-
-    if (!inst->idle_fn_scheduled) {
-        inst->toplevel_callback_idle_id =
-            g_idle_add(idle_toplevel_callback_func, inst);
-        inst->idle_fn_scheduled = TRUE;
-    }
-}
-
-static gint timer_trigger(gpointer data)
-{
-    unsigned long now = GPOINTER_TO_LONG(data);
-    unsigned long next, then;
-    long ticks;
-
-    /*
-     * Destroy the timer we got here on.
-     */
-    if (timer_id) {
-       g_source_remove(timer_id);
-        timer_id = 0;
-    }
-
-    /*
-     * run_timers() may cause a call to timer_change_notify, in which
-     * case a new timer will already have been set up and left in
-     * timer_id. If it hasn't, and run_timers reports that some timing
-     * still needs to be done, we do it ourselves.
-     */
-    if (run_timers(now, &next) && !timer_id) {
-       then = now;
-       now = GETTICKCOUNT();
-       if (now - then > next - then)
-           ticks = 0;
-       else
-           ticks = next - now;
-       timer_id = g_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next));
-    }
-
-    /*
-     * Returning FALSE means 'don't call this timer again', which
-     * _should_ be redundant given that we removed it above, but just
-     * in case, return FALSE anyway.
-     */
-    return FALSE;
-}
-
-void timer_change_notify(unsigned long next)
-{
-    long ticks;
-
-    if (timer_id)
-       g_source_remove(timer_id);
-
-    ticks = next - GETTICKCOUNT();
-    if (ticks <= 0)
-       ticks = 1;                     /* just in case */
-
-    timer_id = g_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next));
-}
-
-#if GTK_CHECK_VERSION(2,0,0)
-gboolean fd_input_func(GIOChannel *source, GIOCondition condition,
-                       gpointer data)
-{
-    int sourcefd = g_io_channel_unix_get_fd(source);
-    /*
-     * We must process exceptional notifications before ordinary
-     * readability ones, or we may go straight past the urgent
-     * marker.
-     */
-    if (condition & G_IO_PRI)
-        select_result(sourcefd, 4);
-    if (condition & G_IO_IN)
-        select_result(sourcefd, 1);
-    if (condition & G_IO_OUT)
-        select_result(sourcefd, 2);
-
-    return TRUE;
-}
-#else
-void fd_input_func(gpointer data, gint sourcefd, GdkInputCondition condition)
-{
-    if (condition & GDK_INPUT_EXCEPTION)
-        select_result(sourcefd, 4);
-    if (condition & GDK_INPUT_READ)
-        select_result(sourcefd, 1);
-    if (condition & GDK_INPUT_WRITE)
-        select_result(sourcefd, 2);
-}
-#endif
-
 void destroy(GtkWidget *widget, gpointer data)
 {
     gtk_main_quit();
@@ -2961,20 +2731,28 @@ void init_clipboard(struct gui_data *inst)
      */
     unsigned char empty[] = "";
     Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+    x11_ignore_error(disp, BadMatch);
     XChangeProperty(disp, GDK_ROOT_WINDOW(),
                    XA_CUT_BUFFER0, XA_STRING, 8, PropModeAppend, empty, 0);
+    x11_ignore_error(disp, BadMatch);
     XChangeProperty(disp, GDK_ROOT_WINDOW(),
                    XA_CUT_BUFFER1, XA_STRING, 8, PropModeAppend, empty, 0);
+    x11_ignore_error(disp, BadMatch);
     XChangeProperty(disp, GDK_ROOT_WINDOW(),
                    XA_CUT_BUFFER2, XA_STRING, 8, PropModeAppend, empty, 0);
+    x11_ignore_error(disp, BadMatch);
     XChangeProperty(disp, GDK_ROOT_WINDOW(),
                    XA_CUT_BUFFER3, XA_STRING, 8, PropModeAppend, empty, 0);
+    x11_ignore_error(disp, BadMatch);
     XChangeProperty(disp, GDK_ROOT_WINDOW(),
                    XA_CUT_BUFFER4, XA_STRING, 8, PropModeAppend, empty, 0);
+    x11_ignore_error(disp, BadMatch);
     XChangeProperty(disp, GDK_ROOT_WINDOW(),
                    XA_CUT_BUFFER5, XA_STRING, 8, PropModeAppend, empty, 0);
+    x11_ignore_error(disp, BadMatch);
     XChangeProperty(disp, GDK_ROOT_WINDOW(),
                    XA_CUT_BUFFER6, XA_STRING, 8, PropModeAppend, empty, 0);
+    x11_ignore_error(disp, BadMatch);
     XChangeProperty(disp, GDK_ROOT_WINDOW(),
                    XA_CUT_BUFFER7, XA_STRING, 8, PropModeAppend, empty, 0);
 #endif
@@ -3055,7 +2833,9 @@ void set_sbar(void *frontend, int total, int start, int page)
     gtk_adjustment_set_step_increment(inst->sbar_adjust, 1);
     gtk_adjustment_set_page_increment(inst->sbar_adjust, page/2);
     inst->ignore_sbar = TRUE;
+#if !GTK_CHECK_VERSION(3,18,0)
     gtk_adjustment_changed(inst->sbar_adjust);
+#endif
     inst->ignore_sbar = FALSE;
 }
 
@@ -3069,6 +2849,15 @@ void scrollbar_moved(GtkAdjustment *adj, gpointer data)
        term_scroll(inst->term, 1, (int)gtk_adjustment_get_value(adj));
 }
 
+static void show_scrollbar(struct gui_data *inst, gboolean visible)
+{
+    inst->sbar_visible = visible;
+    if (visible)
+        gtk_widget_show(inst->sbar);
+    else
+        gtk_widget_hide(inst->sbar);
+}
+
 void sys_cursor(void *frontend, int x, int y)
 {
     /*
@@ -3732,340 +3521,6 @@ long get_windowid(void *frontend)
 }
 #endif
 
-static void help(FILE *fp) {
-    if(fprintf(fp,
-"pterm option summary:\n"
-"\n"
-"  --display DISPLAY         Specify X display to use (note '--')\n"
-"  -name PREFIX              Prefix when looking up resources (default: pterm)\n"
-"  -fn FONT                  Normal text font\n"
-"  -fb FONT                  Bold text font\n"
-"  -geometry GEOMETRY        Position and size of window (size in characters)\n"
-"  -sl LINES                 Number of lines of scrollback\n"
-"  -fg COLOUR, -bg COLOUR    Foreground/background colour\n"
-"  -bfg COLOUR, -bbg COLOUR  Foreground/background bold colour\n"
-"  -cfg COLOUR, -bfg COLOUR  Foreground/background cursor colour\n"
-"  -T TITLE                  Window title\n"
-"  -ut, +ut                  Do(default) or do not update utmp\n"
-"  -ls, +ls                  Do(default) or do not make shell a login shell\n"
-"  -sb, +sb                  Do(default) or do not display a scrollbar\n"
-"  -log PATH, -sessionlog PATH  Log all output to a file\n"
-"  -nethack                  Map numeric keypad to hjklyubn direction keys\n"
-"  -xrm RESOURCE-STRING      Set an X resource\n"
-"  -e COMMAND [ARGS...]      Execute command (consumes all remaining args)\n"
-        ) < 0 || fflush(fp) < 0) {
-       perror("output error");
-       exit(1);
-    }
-}
-
-static void version(FILE *fp) {
-    if(fprintf(fp, "%s: %s\n", appname, ver) < 0 || fflush(fp) < 0) {
-       perror("output error");
-       exit(1);
-    }
-}
-
-int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch,
-               struct gui_data *inst, Conf *conf)
-{
-    int err = 0;
-    char *val;
-
-    /*
-     * Macros to make argument handling easier. Note that because
-     * they need to call `continue', they cannot be contained in
-     * the usual do {...} while (0) wrapper to make them
-     * syntactically single statements; hence it is not legal to
-     * use one of these macros as an unbraced statement between
-     * `if' and `else'.
-     */
-#define EXPECTS_ARG { \
-    if (--argc <= 0) { \
-       err = 1; \
-       fprintf(stderr, "%s: %s expects an argument\n", appname, p); \
-        continue; \
-    } else \
-       val = *++argv; \
-}
-#define SECOND_PASS_ONLY { if (!do_everything) continue; }
-
-    while (--argc > 0) {
-       const char *p = *++argv;
-        int ret;
-
-       /*
-        * Shameless cheating. Debian requires all X terminal
-        * emulators to support `-T title'; but
-        * cmdline_process_param will eat -T (it means no-pty) and
-        * complain that pterm doesn't support it. So, in pterm
-        * only, we convert -T into -title.
-        */
-       if ((cmdline_tooltype & TOOLTYPE_NONNETWORK) &&
-           !strcmp(p, "-T"))
-           p = "-title";
-
-        ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
-                                    do_everything ? 1 : -1, conf);
-
-       if (ret == -2) {
-           cmdline_error("option \"%s\" requires an argument", p);
-       } else if (ret == 2) {
-           --argc, ++argv;            /* skip next argument */
-            continue;
-       } else if (ret == 1) {
-            continue;
-        }
-
-       if (!strcmp(p, "-fn") || !strcmp(p, "-font")) {
-           FontSpec *fs;
-           EXPECTS_ARG;
-           SECOND_PASS_ONLY;
-            fs = fontspec_new(val);
-           conf_set_fontspec(conf, CONF_font, fs);
-            fontspec_free(fs);
-
-       } else if (!strcmp(p, "-fb")) {
-           FontSpec *fs;
-           EXPECTS_ARG;
-           SECOND_PASS_ONLY;
-            fs = fontspec_new(val);
-           conf_set_fontspec(conf, CONF_boldfont, fs);
-            fontspec_free(fs);
-
-       } else if (!strcmp(p, "-fw")) {
-           FontSpec *fs;
-           EXPECTS_ARG;
-           SECOND_PASS_ONLY;
-            fs = fontspec_new(val);
-           conf_set_fontspec(conf, CONF_widefont, fs);
-            fontspec_free(fs);
-
-       } else if (!strcmp(p, "-fwb")) {
-           FontSpec *fs;
-           EXPECTS_ARG;
-           SECOND_PASS_ONLY;
-            fs = fontspec_new(val);
-           conf_set_fontspec(conf, CONF_wideboldfont, fs);
-            fontspec_free(fs);
-
-       } else if (!strcmp(p, "-cs")) {
-           EXPECTS_ARG;
-           SECOND_PASS_ONLY;
-           conf_set_str(conf, CONF_line_codepage, val);
-
-       } else if (!strcmp(p, "-geometry")) {
-           EXPECTS_ARG;
-           SECOND_PASS_ONLY;
-
-#if GTK_CHECK_VERSION(2,0,0)
-            inst->geometry = val;
-#else
-            /* On GTK 1, we have to do this using raw Xlib */
-            {
-                int flags, x, y;
-                unsigned int w, h;
-                flags = XParseGeometry(val, &x, &y, &w, &h);
-                if (flags & WidthValue)
-                    conf_set_int(conf, CONF_width, w);
-                if (flags & HeightValue)
-                    conf_set_int(conf, CONF_height, h);
-
-                if (flags & (XValue | YValue)) {
-                    inst->xpos = x;
-                    inst->ypos = y;
-                    inst->gotpos = TRUE;
-                    inst->gravity = ((flags & XNegative ? 1 : 0) |
-                                     (flags & YNegative ? 2 : 0));
-                }
-            }
-#endif
-
-       } else if (!strcmp(p, "-sl")) {
-           EXPECTS_ARG;
-           SECOND_PASS_ONLY;
-           conf_set_int(conf, CONF_savelines, atoi(val));
-
-       } else if (!strcmp(p, "-fg") || !strcmp(p, "-bg") ||
-                  !strcmp(p, "-bfg") || !strcmp(p, "-bbg") ||
-                  !strcmp(p, "-cfg") || !strcmp(p, "-cbg")) {
-           EXPECTS_ARG;
-           SECOND_PASS_ONLY;
-
-            {
-#if GTK_CHECK_VERSION(3,0,0)
-                GdkRGBA rgba;
-                int success = gdk_rgba_parse(&rgba, val);
-#else
-                GdkColor col;
-                int success = gdk_color_parse(val, &col);
-#endif
-
-                if (!success) {
-                    err = 1;
-                    fprintf(stderr, "%s: unable to parse colour \"%s\"\n",
-                            appname, val);
-                } else {
-#if GTK_CHECK_VERSION(3,0,0)
-                    int r = rgba.red * 255;
-                    int g = rgba.green * 255;
-                    int b = rgba.blue * 255;
-#else
-                    int r = col.red / 256;
-                    int g = col.green / 256;
-                    int b = col.blue / 256;
-#endif
-
-                    int index;
-                    index = (!strcmp(p, "-fg") ? 0 :
-                             !strcmp(p, "-bg") ? 2 :
-                             !strcmp(p, "-bfg") ? 1 :
-                             !strcmp(p, "-bbg") ? 3 :
-                             !strcmp(p, "-cfg") ? 4 :
-                             !strcmp(p, "-cbg") ? 5 : -1);
-                    assert(index != -1);
-
-                    conf_set_int_int(conf, CONF_colours, index*3+0, r);
-                    conf_set_int_int(conf, CONF_colours, index*3+1, g);
-                    conf_set_int_int(conf, CONF_colours, index*3+2, b);
-                }
-            }
-
-       } else if (use_pty_argv && !strcmp(p, "-e")) {
-           /* This option swallows all further arguments. */
-           if (!do_everything)
-               break;
-
-           if (--argc > 0) {
-               int i;
-               pty_argv = snewn(argc+1, char *);
-               ++argv;
-               for (i = 0; i < argc; i++)
-                   pty_argv[i] = argv[i];
-               pty_argv[argc] = NULL;
-               break;                 /* finished command-line processing */
-           } else
-               err = 1, fprintf(stderr, "%s: -e expects an argument\n",
-                                 appname);
-
-       } else if (!strcmp(p, "-title")) {
-           EXPECTS_ARG;
-           SECOND_PASS_ONLY;
-           conf_set_str(conf, CONF_wintitle, val);
-
-       } else if (!strcmp(p, "-log")) {
-           Filename *fn;
-           EXPECTS_ARG;
-           SECOND_PASS_ONLY;
-            fn = filename_from_str(val);
-           conf_set_filename(conf, CONF_logfilename, fn);
-           conf_set_int(conf, CONF_logtype, LGTYP_DEBUG);
-            filename_free(fn);
-
-       } else if (!strcmp(p, "-ut-") || !strcmp(p, "+ut")) {
-           SECOND_PASS_ONLY;
-           conf_set_int(conf, CONF_stamp_utmp, 0);
-
-       } else if (!strcmp(p, "-ut")) {
-           SECOND_PASS_ONLY;
-           conf_set_int(conf, CONF_stamp_utmp, 1);
-
-       } else if (!strcmp(p, "-ls-") || !strcmp(p, "+ls")) {
-           SECOND_PASS_ONLY;
-           conf_set_int(conf, CONF_login_shell, 0);
-
-       } else if (!strcmp(p, "-ls")) {
-           SECOND_PASS_ONLY;
-           conf_set_int(conf, CONF_login_shell, 1);
-
-       } else if (!strcmp(p, "-nethack")) {
-           SECOND_PASS_ONLY;
-           conf_set_int(conf, CONF_nethack_keypad, 1);
-
-       } else if (!strcmp(p, "-sb-") || !strcmp(p, "+sb")) {
-           SECOND_PASS_ONLY;
-           conf_set_int(conf, CONF_scrollbar, 0);
-
-       } else if (!strcmp(p, "-sb")) {
-           SECOND_PASS_ONLY;
-           conf_set_int(conf, CONF_scrollbar, 1);
-
-       } else if (!strcmp(p, "-name")) {
-           EXPECTS_ARG;
-           app_name = val;
-
-       } else if (!strcmp(p, "-xrm")) {
-           EXPECTS_ARG;
-           provide_xrm_string(val);
-
-       } else if(!strcmp(p, "-help") || !strcmp(p, "--help")) {
-           help(stdout);
-           exit(0);
-
-       } else if(!strcmp(p, "-version") || !strcmp(p, "--version")) {
-           version(stdout);
-           exit(0);
-
-        } else if (!strcmp(p, "-pgpfp")) {
-            pgp_fingerprints();
-            exit(1);
-
-       } else if(p[0] != '-' && (!do_everything ||
-                                  process_nonoption_arg(p, conf,
-                                                       allow_launch))) {
-            /* do nothing */
-
-       } else {
-           err = 1;
-           fprintf(stderr, "%s: unrecognized option '%s'\n", appname, p);
-       }
-    }
-
-    return err;
-}
-
-struct uxsel_id {
-#if GTK_CHECK_VERSION(2,0,0)
-    GIOChannel *chan;
-    guint watch_id;
-#else
-    int id;
-#endif
-};
-
-uxsel_id *uxsel_input_add(int fd, int rwx) {
-    uxsel_id *id = snew(uxsel_id);
-
-#if GTK_CHECK_VERSION(2,0,0)
-    int flags = 0;
-    if (rwx & 1) flags |= G_IO_IN;
-    if (rwx & 2) flags |= G_IO_OUT;
-    if (rwx & 4) flags |= G_IO_PRI;
-    id->chan = g_io_channel_unix_new(fd);
-    g_io_channel_set_encoding(id->chan, NULL, NULL);
-    id->watch_id = g_io_add_watch(id->chan, flags, fd_input_func, NULL);
-#else
-    int flags = 0;
-    if (rwx & 1) flags |= GDK_INPUT_READ;
-    if (rwx & 2) flags |= GDK_INPUT_WRITE;
-    if (rwx & 4) flags |= GDK_INPUT_EXCEPTION;
-    assert(flags);
-    id->id = gdk_input_add(fd, flags, fd_input_func, NULL);
-#endif
-
-    return id;
-}
-
-void uxsel_input_remove(uxsel_id *id) {
-#if GTK_CHECK_VERSION(2,0,0)
-    g_source_remove(id->watch_id);
-    g_io_channel_unref(id->chan);
-#else
-    gdk_input_remove(id->id);
-#endif
-    sfree(id);
-}
-
 int frontend_is_utf8(void *frontend)
 {
     struct gui_data *inst = (struct gui_data *)frontend;
@@ -4156,17 +3611,65 @@ char *setup_fonts_ucs(struct gui_data *inst)
 void set_geom_hints(struct gui_data *inst)
 {
     GdkGeometry geom;
+    gint flags;
+
+    /*
+     * Unused fields in geom.
+     */
+    geom.max_width = geom.max_height = -1;
+    geom.min_aspect = geom.max_aspect = 0;
+
+    /*
+     * Set up the geometry fields we care about, with reference to
+     * just the drawing area. We'll correct for the scrollbar in a
+     * moment.
+     */
+    flags = GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE | GDK_HINT_RESIZE_INC;
     geom.min_width = inst->font_width + 2*inst->window_border;
     geom.min_height = inst->font_height + 2*inst->window_border;
-    geom.max_width = geom.max_height = -1;
     geom.base_width = 2*inst->window_border;
     geom.base_height = 2*inst->window_border;
     geom.width_inc = inst->font_width;
     geom.height_inc = inst->font_height;
-    geom.min_aspect = geom.max_aspect = 0;
-    gtk_window_set_geometry_hints(GTK_WINDOW(inst->window), inst->area, &geom,
-                                  GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE |
-                                  GDK_HINT_RESIZE_INC);
+
+    /*
+     * If we've got a scrollbar visible, then we must include its
+     * width as part of the base and min width, and also ensure that
+     * our window's minimum height is at least the height required by
+     * the scrollbar.
+     *
+     * In the latter case, we must also take care to arrange that
+     * (geom.min_height - geom.base_height) is an integer multiple of
+     * geom.height_inc, because if it's not, then some window managers
+     * (we know of xfwm4) get confused, with the effect that they
+     * resize our window to a height based on min_height instead of
+     * base_height, which we then round down and the window ends up
+     * too short.
+     */
+    if (inst->sbar_visible) {
+        GtkRequisition req;
+        int min_sb_height;
+
+#if GTK_CHECK_VERSION(3,0,0)
+        gtk_widget_get_preferred_size(inst->sbar, &req, NULL);
+#else
+        gtk_widget_size_request(inst->sbar, &req);
+#endif
+
+        /* Compute rounded-up scrollbar height. */
+        min_sb_height = req.height;
+        min_sb_height += geom.height_inc - 1;
+        min_sb_height -= ((min_sb_height - geom.base_height % geom.height_inc)
+                          % geom.height_inc);
+
+        geom.min_width += req.width;
+        geom.base_width += req.width;
+        if (geom.min_height < min_sb_height)
+            geom.min_height = min_sb_height;
+    }
+
+    gtk_window_set_geometry_hints(GTK_WINDOW(inst->window),
+                                  NULL, &geom, flags);
 }
 
 void clear_scrollback_menuitem(GtkMenuItem *item, gpointer data)
@@ -4289,16 +3792,16 @@ void change_settings_menuitem(GtkMenuItem *item, gpointer data)
            }
         }
 
+        need_size = FALSE;
+
         /*
          * If the scrollbar needs to be shown, hidden, or moved
          * from one end to the other of the window, do so now.
          */
         if (conf_get_int(oldconf, CONF_scrollbar) !=
            conf_get_int(newconf, CONF_scrollbar)) {
-            if (conf_get_int(newconf, CONF_scrollbar))
-                gtk_widget_show(inst->sbar);
-            else
-                gtk_widget_hide(inst->sbar);
+            show_scrollbar(inst, conf_get_int(newconf, CONF_scrollbar));
+            need_size = TRUE;
         }
         if (conf_get_int(oldconf, CONF_scrollbar_on_left) !=
            conf_get_int(newconf, CONF_scrollbar_on_left)) {
@@ -4319,7 +3822,6 @@ void change_settings_menuitem(GtkMenuItem *item, gpointer data)
          * Redo the whole tangled fonts and Unicode mess if
          * necessary.
          */
-        need_size = FALSE;
         if (strcmp(conf_get_fontspec(oldconf, CONF_font)->name,
                   conf_get_fontspec(newconf, CONF_font)->name) ||
            strcmp(conf_get_fontspec(oldconf, CONF_boldfont)->name,
@@ -4397,200 +3899,16 @@ void change_settings_menuitem(GtkMenuItem *item, gpointer data)
     inst->reconfiguring = FALSE;
 }
 
-void fork_and_exec_self(struct gui_data *inst, int fd_to_close, ...)
-{
-    /*
-     * Re-execing ourself is not an exact science under Unix. I do
-     * the best I can by using /proc/self/exe if available and by
-     * assuming argv[0] can be found on $PATH if not.
-     * 
-     * Note that we also have to reconstruct the elements of the
-     * original argv which gtk swallowed, since the user wants the
-     * new session to appear on the same X display as the old one.
-     */
-    char **args;
-    va_list ap;
-    int i, n;
-    int pid;
-
-    /*
-     * Collect the arguments with which to re-exec ourself.
-     */
-    va_start(ap, fd_to_close);
-    n = 2;                            /* progname and terminating NULL */
-    n += inst->ngtkargs;
-    while (va_arg(ap, char *) != NULL)
-       n++;
-    va_end(ap);
-
-    args = snewn(n, char *);
-    args[0] = inst->progname;
-    args[n-1] = NULL;
-    for (i = 0; i < inst->ngtkargs; i++)
-       args[i+1] = inst->gtkargvstart[i];
-
-    i++;
-    va_start(ap, fd_to_close);
-    while ((args[i++] = va_arg(ap, char *)) != NULL);
-    va_end(ap);
-
-    assert(i == n);
-
-    /*
-     * Do the double fork.
-     */
-    pid = fork();
-    if (pid < 0) {
-       perror("fork");
-        sfree(args);
-       return;
-    }
-
-    if (pid == 0) {
-       int pid2 = fork();
-       if (pid2 < 0) {
-           perror("fork");
-           _exit(1);
-       } else if (pid2 > 0) {
-           /*
-            * First child has successfully forked second child. My
-            * Work Here Is Done. Note the use of _exit rather than
-            * exit: the latter appears to cause destroy messages
-            * to be sent to the X server. I suspect gtk uses
-            * atexit.
-            */
-           _exit(0);
-       }
-
-       /*
-        * If we reach here, we are the second child, so we now
-        * actually perform the exec.
-        */
-       if (fd_to_close >= 0)
-           close(fd_to_close);
-
-       execv("/proc/self/exe", args);
-       execvp(inst->progname, args);
-       perror("exec");
-       _exit(127);
-
-    } else {
-       int status;
-        sfree(args);
-       waitpid(pid, &status, 0);
-    }
-
-}
-
 void dup_session_menuitem(GtkMenuItem *item, gpointer gdata)
 {
     struct gui_data *inst = (struct gui_data *)gdata;
-    /*
-     * For this feature we must marshal conf and (possibly) pty_argv
-     * into a byte stream, create a pipe, and send this byte stream
-     * to the child through the pipe.
-     */
-    int i, ret, sersize, size;
-    char *data;
-    char option[80];
-    int pipefd[2];
-
-    if (pipe(pipefd) < 0) {
-       perror("pipe");
-       return;
-    }
-
-    size = sersize = conf_serialised_size(inst->conf);
-    if (use_pty_argv && pty_argv) {
-       for (i = 0; pty_argv[i]; i++)
-           size += strlen(pty_argv[i]) + 1;
-    }
 
-    data = snewn(size, char);
-    conf_serialise(inst->conf, data);
-    if (use_pty_argv && pty_argv) {
-       int p = sersize;
-       for (i = 0; pty_argv[i]; i++) {
-           strcpy(data + p, pty_argv[i]);
-           p += strlen(pty_argv[i]) + 1;
-       }
-       assert(p == size);
-    }
-
-    sprintf(option, "---[%d,%d]", pipefd[0], size);
-    noncloexec(pipefd[0]);
-    fork_and_exec_self(inst, pipefd[1], option, NULL);
-    close(pipefd[0]);
-
-    i = ret = 0;
-    while (i < size && (ret = write(pipefd[1], data + i, size - i)) > 0)
-       i += ret;
-    if (ret < 0)
-       perror("write to pipe");
-    close(pipefd[1]);
-    sfree(data);
-}
-
-int read_dupsession_data(struct gui_data *inst, Conf *conf, char *arg)
-{
-    int fd, i, ret, size, size_used;
-    char *data;
-
-    if (sscanf(arg, "---[%d,%d]", &fd, &size) != 2) {
-       fprintf(stderr, "%s: malformed magic argument `%s'\n", appname, arg);
-       exit(1);
-    }
-
-    data = snewn(size, char);
-    i = ret = 0;
-    while (i < size && (ret = read(fd, data + i, size - i)) > 0)
-       i += ret;
-    if (ret < 0) {
-       perror("read from pipe");
-       exit(1);
-    } else if (i < size) {
-       fprintf(stderr, "%s: unexpected EOF in Duplicate Session data\n",
-               appname);
-       exit(1);
-    }
-
-    size_used = conf_deserialise(conf, data, size);
-    if (use_pty_argv && size > size_used) {
-       int n = 0;
-       i = size_used;
-       while (i < size) {
-           while (i < size && data[i]) i++;
-           if (i >= size) {
-               fprintf(stderr, "%s: malformed Duplicate Session data\n",
-                       appname);
-               exit(1);
-           }
-           i++;
-           n++;
-       }
-       pty_argv = snewn(n+1, char *);
-       pty_argv[n] = NULL;
-       n = 0;
-       i = size_used;
-       while (i < size) {
-           char *p = data + i;
-           while (i < size && data[i]) i++;
-           assert(i < size);
-           i++;
-           pty_argv[n++] = dupstr(p);
-       }
-    }
-
-    sfree(data);
-
-    return 0;
+    launch_duplicate_session(inst->conf);
 }
 
 void new_session_menuitem(GtkMenuItem *item, gpointer data)
 {
-    struct gui_data *inst = (struct gui_data *)data;
-
-    fork_and_exec_self(inst, -1, NULL);
+    launch_new_session();
 }
 
 void restart_session_menuitem(GtkMenuItem *item, gpointer data)
@@ -4607,10 +3925,9 @@ void restart_session_menuitem(GtkMenuItem *item, gpointer data)
 
 void saved_session_menuitem(GtkMenuItem *item, gpointer data)
 {
-    struct gui_data *inst = (struct gui_data *)data;
     char *str = (char *)g_object_get_data(G_OBJECT(item), "user-data");
 
-    fork_and_exec_self(inst, -1, "-load", str, NULL);
+    launch_saved_session(str);
 }
 
 void saved_session_freedata(GtkMenuItem *item, gpointer data)
@@ -4803,13 +4120,10 @@ static void start_backend(struct gui_data *inst)
     gtk_widget_set_sensitive(inst->restartitem, FALSE);
 }
 
-int pt_main(int argc, char **argv)
+struct gui_data *new_session_window(Conf *conf, const char *geometry_string)
 {
-    extern int cfgbox(Conf *conf);
     struct gui_data *inst;
 
-    setlocale(LC_CTYPE, "");
-
     /*
      * Create an instance structure and initialise to zeroes
      */
@@ -4817,57 +4131,34 @@ int pt_main(int argc, char **argv)
     memset(inst, 0, sizeof(*inst));
     inst->alt_keycode = -1;            /* this one needs _not_ to be zero */
     inst->busy_status = BUSY_NOT;
-    inst->conf = conf_new();
+    inst->conf = conf;
     inst->wintitle = inst->icontitle = NULL;
-    inst->quit_fn_scheduled = FALSE;
-    inst->idle_fn_scheduled = FALSE;
     inst->drawtype = DRAWTYPE_DEFAULT;
 #if GTK_CHECK_VERSION(3,4,0)
     inst->cumulative_scroll = 0.0;
 #endif
 
-    /* defer any child exit handling until we're ready to deal with
-     * it */
-    block_signal(SIGCHLD, 1);
-
-    inst->progname = argv[0];
-    /*
-     * Copy the original argv before letting gtk_init fiddle with
-     * it. It will be required later.
-     */
-    {
-       int i, oldargc;
-       inst->gtkargvstart = snewn(argc-1, char *);
-       for (i = 1; i < argc; i++)
-           inst->gtkargvstart[i-1] = dupstr(argv[i]);
-       oldargc = argc;
-       gtk_init(&argc, &argv);
-       inst->ngtkargs = oldargc - argc;
-    }
-
-    if (argc > 1 && !strncmp(argv[1], "---", 3)) {
-       read_dupsession_data(inst, inst->conf, argv[1]);
-       /* Splatter this argument so it doesn't clutter a ps listing */
-       smemclr(argv[1], strlen(argv[1]));
-    } else {
-       /* By default, we bring up the config dialog, rather than launching
-        * a session. This gets set to TRUE if something happens to change
-        * that (e.g., a hostname is specified on the command-line). */
-       int allow_launch = FALSE;
-       if (do_cmdline(argc, argv, 0, &allow_launch, inst, inst->conf))
-           exit(1);                   /* pre-defaults pass to get -class */
-       do_defaults(NULL, inst->conf);
-       if (do_cmdline(argc, argv, 1, &allow_launch, inst, inst->conf))
-           exit(1);                   /* post-defaults, do everything */
-
-       cmdline_run_saved(inst->conf);
-
-       if (loaded_session)
-           allow_launch = TRUE;
-
-       if ((!allow_launch || !conf_launchable(inst->conf)) &&
-           !cfgbox(inst->conf))
-           exit(0);                   /* config box hit Cancel */
+    if (geometry_string) {
+#if GTK_CHECK_VERSION(2,0,0)
+        inst->geometry = geometry_string;
+#else
+        /* On GTK 1, we have to do this using raw Xlib */
+        int flags, x, y;
+        unsigned int w, h;
+        flags = XParseGeometry(geometry_string, &x, &y, &w, &h);
+        if (flags & WidthValue)
+            conf_set_int(conf, CONF_width, w);
+        if (flags & HeightValue)
+            conf_set_int(conf, CONF_height, h);
+
+        if (flags & (XValue | YValue)) {
+            inst->xpos = x;
+            inst->ypos = y;
+            inst->gotpos = TRUE;
+            inst->gravity = ((flags & XNegative ? 1 : 0) |
+                             (flags & YNegative ? 2 : 0));
+        }
+#endif
     }
 
     if (!compound_text_atom)
@@ -4888,7 +4179,7 @@ int pt_main(int argc, char **argv)
             exit(1);
         }
     }
-    inst->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    inst->window = make_gtk_toplevel_window(inst);
     {
         const char *winclass = conf_get_str(inst->conf, CONF_winclass);
         if (*winclass)
@@ -4907,23 +4198,6 @@ int pt_main(int argc, char **argv)
 
     init_clipboard(inst);
 
-    set_geom_hints(inst);
-
-#if GTK_CHECK_VERSION(3,0,0)
-    gtk_window_set_default_geometry(GTK_WINDOW(inst->window),
-                                    inst->width, inst->height);
-#else
-    {
-        int w = inst->font_width * inst->width + 2*inst->window_border;
-        int h = inst->font_height * inst->height + 2*inst->window_border;
-#if GTK_CHECK_VERSION(2,0,0)
-        gtk_widget_set_size_request(inst->area, w, h);
-#else
-        gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), w, h);
-#endif
-    }
-#endif
-
     inst->sbar_adjust = GTK_ADJUSTMENT(gtk_adjustment_new(0,0,0,0,0,0));
     inst->sbar = gtk_vscrollbar_new(inst->sbar_adjust);
     inst->hbox = GTK_BOX(gtk_hbox_new(FALSE, 0));
@@ -4941,12 +4215,26 @@ int pt_main(int argc, char **argv)
     gtk_container_add(GTK_CONTAINER(inst->window), GTK_WIDGET(inst->hbox));
 
     gtk_widget_show(inst->area);
-    if (conf_get_int(inst->conf, CONF_scrollbar))
-       gtk_widget_show(inst->sbar);
-    else
-       gtk_widget_hide(inst->sbar);
+    show_scrollbar(inst, conf_get_int(inst->conf, CONF_scrollbar));
     gtk_widget_show(GTK_WIDGET(inst->hbox));
 
+    set_geom_hints(inst);
+
+#if GTK_CHECK_VERSION(3,0,0)
+    gtk_window_set_default_geometry(GTK_WINDOW(inst->window),
+                                    inst->width, inst->height);
+#else
+    {
+        int w = inst->font_width * inst->width + 2*inst->window_border;
+        int h = inst->font_height * inst->height + 2*inst->window_border;
+#if GTK_CHECK_VERSION(2,0,0)
+        gtk_widget_set_size_request(inst->area, w, h);
+#else
+        gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), w, h);
+#endif
+    }
+#endif
+
 #if GTK_CHECK_VERSION(2,0,0)
     if (inst->geometry) {
         gtk_window_parse_geometry(GTK_WINDOW(inst->window), inst->geometry);
@@ -5101,14 +4389,10 @@ int pt_main(int argc, char **argv)
 
     inst->eventlogstuff = eventlogstuff_new();
 
-    request_callback_notifications(notify_toplevel_callback, inst);
-
     inst->term = term_init(inst->conf, &inst->ucsdata, inst);
     inst->logctx = log_init(inst, inst->conf);
     term_provide_logctx(inst->term, inst->logctx);
 
-    uxsel_init();
-
     term_size(inst->term, inst->height, inst->width,
              conf_get_int(inst->conf, CONF_savelines));
 
@@ -5116,22 +4400,7 @@ int pt_main(int argc, char **argv)
 
     ldisc_echoedit_update(inst->ldisc);     /* cause ldisc to notice changes */
 
-    /* now we're reday to deal with the child exit handler being
-     * called */
-    block_signal(SIGCHLD, 0);
-
-    /*
-     * Block SIGPIPE: if we attempt Duplicate Session or similar
-     * and it falls over in some way, we certainly don't want
-     * SIGPIPE terminating the main pterm/PuTTY. Note that we do
-     * this _after_ (at least pterm) forks off its child process,
-     * since the child wants SIGPIPE handled in the usual way.
-     */
-    block_signal(SIGPIPE, 1);
-
     inst->exited = FALSE;
 
-    gtk_main();
-
-    return 0;
+    return inst;
 }
diff --git a/unix/osxlaunch.c b/unix/osxlaunch.c
new file mode 100644 (file)
index 0000000..2627c64
--- /dev/null
@@ -0,0 +1,414 @@
+/*
+ * Launcher program for OS X application bundles of PuTTY.
+ */
+
+/*
+ * The 'gtk-mac-bundler' utility arranges to build an OS X application
+ * bundle containing a program compiled against the Quartz GTK
+ * backend. It does this by including all the necessary GTK shared
+ * libraries and data files inside the bundle as well as the binary.
+ *
+ * But the GTK program won't start up unless all those shared
+ * libraries etc are already pointed to by environment variables like
+ * DYLD_LIBRARY_PATH, which won't be set up when the bundle is
+ * launched.
+ *
+ * Hence, gtk-mac-bundler expects to install the program in the bundle
+ * under a name like 'Contents/MacOS/Program-bin'; and the file called
+ * 'Contents/MacOS/Program', which is the one actually executed when
+ * the bundle is launched, is a wrapper script that sets up the
+ * environment before running the actual GTK-using program.
+ *
+ * In our case, however, that's not good enough. pterm will want to
+ * launch subprocesses with general-purpose shell sessions in them,
+ * and those subprocesses _won't_ want the random stuff dumped in the
+ * environment by the gtk-mac-bundler standard wrapper script. So I
+ * have to provide my own wrapper, which has a more complicated job:
+ * not only setting up the environment for the GTK app, but also
+ * preserving all details of the _previous_ environment, so that when
+ * pterm forks off a subprocess to run in a terminal session, it can
+ * restore the environment that was in force before the wrapper
+ * started messing about. This source file implements that wrapper,
+ * and does it in C so as to make string processing more reliable and
+ * less annoying.
+ *
+ * My strategy for saving the old environment is to pick a prefix
+ * that's unused by anything currently in the environment; let's
+ * suppose it's "P" for this discussion. Any environment variable I
+ * overwrite, say "VAR", I will either set "PsVAR=old value", or
+ * "PuVAR=" ("s" and "u" for "set" and "unset"). Then I pass the
+ * prefix itself as a command-line argument to the main GTK
+ * application binary, which then knows how to restore the original
+ * environment in pterm subprocesses.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef __APPLE__
+/* When we're not compiling for OS X, it's easier to just turn this
+ * program into a trivial hello-world by ifdef in the source than it
+ * is to remove it in the makefile edifice. */
+int main(int argc, char **argv)
+{
+    fprintf(stderr, "launcher does nothing on non-OSX platforms\n");
+    return 1;
+}
+#else /* __APPLE__ */
+
+#include <unistd.h>
+#include <libgen.h>
+#include <mach-o/dyld.h>
+
+/* ----------------------------------------------------------------------
+ * Find an alphabetic prefix unused by any environment variable name.
+ */
+
+/*
+ * This linked-list based system is a bit overkill, but I enjoy an
+ * algorithmic challenge. We essentially do an incremental radix sort
+ * of all the existing environment variable names: initially divide
+ * them into 26 buckets by their first letter (discarding those that
+ * don't have a letter at that position), then subdivide each bucket
+ * in turn into 26 sub-buckets, and so on. We maintain each bucket as
+ * a linked list, and link their heads together into a secondary list
+ * that functions as a queue (meaning that we go breadth-first,
+ * processing all the buckets of a given depth before moving on to the
+ * next depth down). At any stage, if we find one of our 26
+ * sub-buckets is empty, that's our unused prefix.
+ *
+ * The running time is O(number of strings * length of output), and I
+ * doubt it's possible to do better.
+ */
+
+#define FANOUT 26
+int char_index(int ch)
+{
+    if (ch >= 'A' && ch <= 'Z')
+        return ch - 'A';
+    else if (ch >= 'a' && ch <= 'z')
+        return ch - 'a';
+    else
+        return -1;
+}
+
+struct bucket {
+    int prefixlen;
+    struct bucket *next_bucket;
+    struct node *first_node;
+};
+
+struct node {
+    const char *string;
+    int len, prefixlen;
+    struct node *next;
+};
+
+struct node *new_node(struct node *prev_head, const char *string, int len)
+{
+    struct node *ret = (struct node *)malloc(sizeof(struct node));
+
+    if (!ret) {
+        fprintf(stderr, "out of memory\n");
+        exit(1);
+    }
+
+    ret->next = prev_head;
+    ret->string = string;
+    ret->len = len;
+
+    return ret;
+}
+
+char *get_unused_env_prefix(void)
+{
+    struct bucket *qhead, *qtail;
+    extern char **environ;
+    char **e;
+
+    qhead = (struct bucket *)malloc(sizeof(struct bucket));
+    qhead->prefixlen = 0;
+    if (!qhead) {
+        fprintf(stderr, "out of memory\n");
+        exit(1);
+    }
+    for (e = environ; *e; e++)
+        qhead->first_node = new_node(qhead->first_node, *e, strcspn(*e, "="));
+
+    qtail = qhead;
+    while (1) {
+        struct bucket *buckets[FANOUT];
+        struct node *bucketnode;
+        int i, index;
+
+        for (i = 0; i < FANOUT; i++) {
+            buckets[i] = (struct bucket *)malloc(sizeof(struct bucket));
+            if (!buckets[i]) {
+                fprintf(stderr, "out of memory\n");
+                exit(1);
+            }
+            buckets[i]->prefixlen = qhead->prefixlen + 1;
+            qtail->next_bucket = buckets[i];
+            qtail = buckets[i];
+        }
+        qtail->next_bucket = NULL;
+
+        bucketnode = qhead->first_node;
+        while (bucketnode) {
+            struct node *node = bucketnode;
+            bucketnode = bucketnode->next;
+
+            if (node->len <= qhead->prefixlen)
+                continue;
+            index = char_index(node->string[qhead->prefixlen]);
+            if (!(index >= 0 && index < FANOUT))
+                continue;
+            node->prefixlen++;
+            node->next = buckets[index]->first_node;
+            buckets[index]->first_node = node;
+        }
+
+        for (i = 0; i < FANOUT; i++) {
+            if (!buckets[i]->first_node) {
+                char *ret = malloc(qhead->prefixlen + 2);
+                if (!ret) {
+                    fprintf(stderr, "out of memory\n");
+                    exit(1);
+                }
+                memcpy(ret, qhead->first_node->string, qhead->prefixlen);
+                ret[qhead->prefixlen] = i + 'A';
+                ret[qhead->prefixlen + 1] = '\0';
+
+                /* This would be where we freed everything, if we
+                 * didn't know it didn't matter because we were
+                 * imminently going to exec another program */
+                return ret;
+            }
+        }
+
+        qhead = qhead->next_bucket;
+    }
+}
+
+/* ----------------------------------------------------------------------
+ * Get the pathname of this executable, so we can locate the rest of
+ * the app bundle relative to it.
+ */
+
+/*
+ * There are several ways to try to retrieve the pathname to the
+ * running executable:
+ *
+ * (a) Declare main() as taking four arguments int main(int argc, char
+ * **argv, char **envp, char **apple); and look at apple[0].
+ *
+ * (b) Use sysctl(KERN_PROCARGS) to get the process arguments for the
+ * current pid. This involves two steps:
+ *  - sysctl(mib, 2, &argmax, &argmax_size, NULL, 0)
+ *     + mib is an array[2] of int containing
+ *       { CTL_KERN, KERN_ARGMAX }
+ *     + argmax is an int
+ *     + argmax_size is a size_t initialised to sizeof(argmax)
+ *     + returns in argmax the amount of memory you need for the next
+ *       call.
+ *  - sysctl(mib, 3, procargs, &procargs_size, NULL, 0)
+ *     + mib is an array[3] of int containing
+ *       { CTL_KERN, KERN_PROCARGS, current pid }
+ *     + procargs is a buffer of size 'argmax'
+ *     + procargs_size is a size_t initialised to argmax
+ *     + returns in the procargs buffer a collection of
+ *       zero-terminated strings of which the first is the program
+ *       name.
+ *
+ * (c) Call _NSGetExecutablePath, once to find out the needed buffer
+ * size and again to fetch the actual path.
+ *
+ * (d) Use Objective-C and Cocoa and call
+ * [[[NSProcessInfo processInfo] arguments] objectAtIndex: 0].
+ *
+ * So, how do those work in various cases? Experiments show:
+ *
+ *  - if you run the program as 'binary' (or whatever you called it)
+ *    and rely on the shell to search your PATH, all four methods
+ *    return a sensible-looking absolute pathname.
+ *
+ *  - if you run the program as './binary', (a) and (b) return just
+ *    "./binary", which has a particularly bad race condition if you
+ *    try to convert it into an absolute pathname using realpath(3).
+ *    (c) returns "/full/path/to/./binary", which still needs
+ *    realpath(3)ing to get rid of that ".", but at least it's
+ *    _trying_ to be fully qualified. (d) returns
+ *    "/full/path/to/binary" - full marks!
+ *     + Similar applies if you run it via a more interesting relative
+ *       path such as one with a ".." in: (c) gives you an absolute
+ *       path containing a ".." element, whereas (d) has sorted that
+ *       out.
+ *
+ *  - if you run the program via a path with a symlink on, _none_ of
+ *    these options successfully returns a path without the symlink.
+ *
+ * That last point suggests that even (d) is not a perfect solution on
+ * its own, and you'll have to realpath() whatever you get back from
+ * it regardless.
+ *
+ * And (d) is extra inconvenient because it returns an NSString, which
+ * is implicitly Unicode, so it's not clear how you turn that back
+ * into a char * representing a correct Unix pathname (what charset
+ * should you interpret it in?). Also because you have to bring in all
+ * of ObjC and Cocoa, which for a low-level Unix API client like this
+ * seems like overkill.
+ *
+ * So my conclusion is that (c) is most practical for these purposes.
+ */
+
+char *get_program_path(void)
+{
+    char *our_path;
+    uint32_t pathlen = 0;
+    _NSGetExecutablePath(NULL, &pathlen);
+    our_path = malloc(pathlen);
+    if (!our_path) {
+        fprintf(stderr, "out of memory\n");
+        exit(1);
+    }
+    if (_NSGetExecutablePath(our_path, &pathlen)) {
+        fprintf(stderr, "unable to get launcher executable path\n");
+        exit(1);
+    }
+
+    /* OS X guarantees to malloc the return value if we pass NULL */
+    char *our_real_path = realpath(our_path, NULL);
+    if (!our_real_path) {
+        fprintf(stderr, "realpath failed\n");
+        exit(1);
+    }
+
+    free(our_path);
+    return our_real_path;
+}
+
+/* ----------------------------------------------------------------------
+ * Wrapper on dirname(3) which mallocs its return value to whatever
+ * size is needed.
+ */
+
+char *dirname_wrapper(const char *path)
+{
+    char *path_copy = malloc(strlen(path) + 1);
+    if (!path_copy) {
+        fprintf(stderr, "out of memory\n");
+        exit(1);
+    }
+    strcpy(path_copy, path);
+    char *ret_orig = dirname(path_copy);
+    char *ret = malloc(strlen(ret_orig) + 1);
+    if (!ret) {
+        fprintf(stderr, "out of memory\n");
+        exit(1);
+    }
+    strcpy(ret, ret_orig);
+    free(path_copy);
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * mallocing string concatenation function.
+ */
+
+char *alloc_cat(const char *str1, const char *str2)
+{
+    int len1 = strlen(str1), len2 = strlen(str2);
+    char *ret = malloc(len1 + len2 + 1);
+    if (!ret) {
+        fprintf(stderr, "out of memory\n");
+        exit(1);
+    }
+    strcpy(ret, str1);
+    strcpy(ret + len1, str2);
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Overwrite an environment variable, preserving the old one for the
+ * real app to restore.
+ */
+char *prefix, *prefixset, *prefixunset;
+void overwrite_env(const char *name, const char *value)
+{
+    const char *oldvalue = getenv(name);
+    if (oldvalue) {
+        setenv(alloc_cat(prefixset, name), oldvalue, 1);
+    } else {
+        setenv(alloc_cat(prefixunset, name), "", 1);
+    }
+    if (value)
+        setenv(name, value, 1);
+    else
+        unsetenv(name);
+}
+
+/* ----------------------------------------------------------------------
+ * Main program.
+ */
+
+int main(int argc, char **argv)
+{
+    prefix = get_unused_env_prefix();
+    prefixset = alloc_cat(prefix, "s");
+    prefixunset = alloc_cat(prefix, "u");
+
+    char *prog_path = get_program_path(); // <bundle>/Contents/MacOS/<filename>
+    char *macos = dirname_wrapper(prog_path); // <bundle>/Contents/MacOS
+    char *contents = dirname_wrapper(macos);  // <bundle>/Contents
+//    char *bundle = dirname_wrapper(contents); // <bundle>
+    char *resources = alloc_cat(contents, "/Resources");
+//    char *bin = alloc_cat(resources, "/bin");
+    char *etc = alloc_cat(resources, "/etc");
+    char *lib = alloc_cat(resources, "/lib");
+    char *share = alloc_cat(resources, "/share");
+    char *xdg = alloc_cat(etc, "/xdg");
+//    char *gtkrc = alloc_cat(etc, "/gtk-2.0/gtkrc");
+    char *locale = alloc_cat(share, "/locale");
+    char *realbin = alloc_cat(prog_path, "-bin");
+
+    overwrite_env("DYLD_LIBRARY_PATH", lib);
+    overwrite_env("XDG_CONFIG_DIRS", xdg);
+    overwrite_env("XDG_DATA_DIRS", share);
+    overwrite_env("GTK_DATA_PREFIX", resources);
+    overwrite_env("GTK_EXE_PREFIX", resources);
+    overwrite_env("GTK_PATH", resources);
+    overwrite_env("PANGO_LIBDIR", lib);
+    overwrite_env("PANGO_SYSCONFDIR", etc);
+    overwrite_env("I18NDIR", locale);
+    overwrite_env("LANG", NULL);
+    overwrite_env("LC_MESSAGES", NULL);
+    overwrite_env("LC_MONETARY", NULL);
+    overwrite_env("LC_COLLATE", NULL);
+
+    char **new_argv = malloc((argc + 16) * sizeof(const char *));
+    if (!new_argv) {
+        fprintf(stderr, "out of memory\n");
+        exit(1);
+    }
+    int j = 0;
+    new_argv[j++] = realbin;
+    {
+        int i = 1;
+        if (i < argc && !strncmp(argv[i], "-psn_", 5))
+            i++;
+
+        for (; i < argc; i++)
+            new_argv[j++] = argv[i];
+    }
+    new_argv[j++] = prefix;
+    new_argv[j++] = NULL;
+
+    execv(realbin, new_argv);
+    perror("execv");
+    return 127;
+}
+
+#endif /* __APPLE__ */
diff --git a/unix/pterm.bundle b/unix/pterm.bundle
new file mode 100644 (file)
index 0000000..377fee0
--- /dev/null
@@ -0,0 +1,43 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<app-bundle>
+
+  <meta>
+    <prefix name="default">${env:JHBUILD_PREFIX}</prefix>
+    <run-install-name-tool/>
+    <gtk>gtk+-3.0</gtk>
+    <!-- Optionally specify a launcher script to use. If the
+         application sets up everything needed itself, like
+         environment variable, linker paths, etc, a launcher script is
+         not needed. If the source path is left out, the default
+         script will be used.
+    -->
+    <launcher-script>${project}/../osxlaunch</launcher-script >
+  </meta>
+
+  <plist>${project}/pterm.plist</plist>
+
+  <main-binary dest="${bundle}/Contents/MacOS">
+    ${project}/../ptermapp
+  </main-binary>
+
+  <binary>
+    ${prefix}/lib/${gtkdir}/${pkg:${gtk}:gtk_binary_version}/immodules/*.so
+  </binary>
+
+  <binary>
+    ${prefix}/lib/${gtkdir}/${pkg:${gtk}:gtk_binary_version}/printbackends/*.so
+  </binary>
+
+  <data>
+    ${prefix}/share/themes/Adwaita
+  </data>
+
+  <data dest="${bundle}/Contents/Resources">
+    ${project}/../icons/Pterm.icns
+  </data>
+
+  <icon-theme icons="auto">
+    Adwaita
+  </icon-theme>
+
+</app-bundle>
diff --git a/unix/pterm.plist b/unix/pterm.plist
new file mode 100644 (file)
index 0000000..e8bd943
--- /dev/null
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>CFBundleIconFile</key>
+       <string>Pterm.icns</string>
+       <key>CFBundleName</key>
+       <string>Pterm</string>
+       <key>CFBundleDisplayName</key>
+       <string>Pterm</string>
+       <key>CFBundleExecutable</key>
+       <string>Pterm</string>
+       <key>CFBundleVersion</key>
+       <string>Unidentified build</string>
+       <key>CFBundleShortVersionString</key>
+       <string>Unidentified build</string>
+       <key>CFBundleDevelopmentRegion</key>
+       <string>en</string>
+       <key>CFBundleIdentifier</key>
+       <string>org.tartarus.projects.putty.macpterm</string>
+       <key>CFBundleInfoDictionaryVersion</key>
+       <string>6.0</string>
+       <key>CFBundlePackageType</key>
+       <string>APPL</string>
+       <key>CFBundleSignature</key>
+       <string>????</string>
+       <key>NSHumanReadableCopyright</key>
+       <string>© 1997-2015 Simon Tatham. All rights reserved.</string>
+</dict>
+</plist>
diff --git a/unix/putty.bundle b/unix/putty.bundle
new file mode 100644 (file)
index 0000000..91b0def
--- /dev/null
@@ -0,0 +1,43 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<app-bundle>
+
+  <meta>
+    <prefix name="default">${env:JHBUILD_PREFIX}</prefix>
+    <run-install-name-tool/>
+    <gtk>gtk+-3.0</gtk>
+    <!-- Optionally specify a launcher script to use. If the
+         application sets up everything needed itself, like
+         environment variable, linker paths, etc, a launcher script is
+         not needed. If the source path is left out, the default
+         script will be used.
+    -->
+    <launcher-script>${project}/../osxlaunch</launcher-script >
+  </meta>
+
+  <plist>${project}/putty.plist</plist>
+
+  <main-binary dest="${bundle}/Contents/MacOS">
+    ${project}/../puttyapp
+  </main-binary>
+
+  <binary>
+    ${prefix}/lib/${gtkdir}/${pkg:${gtk}:gtk_binary_version}/immodules/*.so
+  </binary>
+
+  <binary>
+    ${prefix}/lib/${gtkdir}/${pkg:${gtk}:gtk_binary_version}/printbackends/*.so
+  </binary>
+
+  <data>
+    ${prefix}/share/themes/Adwaita
+  </data>
+
+  <data dest="${bundle}/Contents/Resources">
+    ${project}/../icons/PuTTY.icns
+  </data>
+
+  <icon-theme icons="auto">
+    Adwaita
+  </icon-theme>
+
+</app-bundle>
diff --git a/unix/putty.plist b/unix/putty.plist
new file mode 100644 (file)
index 0000000..cf8d53d
--- /dev/null
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>CFBundleIconFile</key>
+       <string>PuTTY.icns</string>
+       <key>CFBundleName</key>
+       <string>PuTTY</string>
+       <key>CFBundleDisplayName</key>
+       <string>PuTTY</string>
+       <key>CFBundleExecutable</key>
+       <string>PuTTY</string>
+       <key>CFBundleVersion</key>
+       <string>Unidentified build</string>
+       <key>CFBundleShortVersionString</key>
+       <string>Unidentified build</string>
+       <key>CFBundleDevelopmentRegion</key>
+       <string>en</string>
+       <key>CFBundleIdentifier</key>
+       <string>org.tartarus.projects.putty.macputty</string>
+       <key>CFBundleInfoDictionaryVersion</key>
+       <string>6.0</string>
+       <key>CFBundlePackageType</key>
+       <string>APPL</string>
+       <key>CFBundleSignature</key>
+       <string>????</string>
+       <key>NSHumanReadableCopyright</key>
+       <string>© 1997-2015 Simon Tatham. All rights reserved.</string>
+</dict>
+</plist>
index 62e20112a72cea5876bd14e88e00e40d4b992cec..12ab8b1c164efebe1c4f4291708e7771f5d33412 100644 (file)
@@ -94,6 +94,21 @@ unsigned long getticks(void);               /* based on gettimeofday(2) */
  */
 #define FLAG_STDERR_TTY 0x1000
 
+/* The per-session frontend structure managed by gtkwin.c */
+struct gui_data;
+struct gui_data *new_session_window(Conf *conf, const char *geometry_string);
+
+/* Defined in gtkmain.c */
+void launch_duplicate_session(Conf *conf);
+void launch_new_session(void);
+void launch_saved_session(const char *str);
+#ifdef MAY_REFER_TO_GTK_IN_HEADERS
+GtkWidget *make_gtk_toplevel_window(void *frontend);
+#endif
+
+/* Defined in gtkcomm.c */
+void gtkcomm_setup(void);
+
 /* Things pty.c needs from pterm.c */
 const char *get_x_display(void *frontend);
 int font_dimension(void *frontend, int which);/* 0 for width, 1 for height */
index abad00db0ef7a065460fd04a1496e9562874b422..b9fd67fe927423c7587be969ff545247218ff1fd 100644 (file)
@@ -264,6 +264,59 @@ int askalg(void *frontend, const char *algtype, const char *algname,
     }
 }
 
+int askhk(void *frontend, const char *algname, const char *betteralgs,
+          void (*callback)(void *ctx, int result), void *ctx)
+{
+    static const char msg[] =
+       "The first host key type we have stored for this server\n"
+       "is %s, which is below the configured warning threshold.\n"
+       "The server also provides the following types of host key\n"
+        "above the threshold, which we do not have stored:\n"
+        "%s\n"
+       "Continue with connection? (y/n) ";
+    static const char msg_batch[] =
+       "The first host key type we have stored for this server\n"
+       "is %s, which is below the configured warning threshold.\n"
+       "The server also provides the following types of host key\n"
+        "above the threshold, which we do not have stored:\n"
+        "%s\n"
+       "Connection abandoned.\n";
+    static const char abandoned[] = "Connection abandoned.\n";
+
+    char line[32];
+    struct termios cf;
+
+    premsg(&cf);
+    if (console_batch_mode) {
+       fprintf(stderr, msg_batch, algname, betteralgs);
+       return 0;
+    }
+
+    fprintf(stderr, msg, algname, betteralgs);
+    fflush(stderr);
+
+    {
+       struct termios oldmode, newmode;
+       tcgetattr(0, &oldmode);
+       newmode = oldmode;
+       newmode.c_lflag |= ECHO | ISIG | ICANON;
+       tcsetattr(0, TCSANOW, &newmode);
+       line[0] = '\0';
+       if (block_and_read(0, line, sizeof(line) - 1) <= 0)
+           /* handled below */;
+       tcsetattr(0, TCSANOW, &oldmode);
+    }
+
+    if (line[0] == 'y' || line[0] == 'Y') {
+       postmsg(&cf);
+       return 1;
+    } else {
+       fprintf(stderr, abandoned);
+       postmsg(&cf);
+       return 0;
+    }
+}
+
 /*
  * Ask whether to wipe a session log file before writing to it.
  * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
index 156d4efe33982e6a67b6cc15338d12ed868bfaed..f593a96064c1365fd794807b99954437bccb80a0 100644 (file)
@@ -3,21 +3,27 @@
  */
 
 #include <stdio.h>
+#include <errno.h>
+
 #include <fcntl.h>
 #include <unistd.h>
 
 #include "putty.h"
 
-char *get_random_data(int len)
+char *get_random_data(int len, const char *device)
 {
     char *buf = snewn(len, char);
     int fd;
     int ngot, ret;
 
-    fd = open("/dev/random", O_RDONLY);
+    if (!device)
+        device = "/dev/random";
+
+    fd = open(device, O_RDONLY);
     if (fd < 0) {
        sfree(buf);
-       perror("puttygen: unable to open /dev/random");
+       fprintf(stderr, "puttygen: %s: open: %s\n",
+                device, strerror(errno));
        return NULL;
     }
 
@@ -27,7 +33,8 @@ char *get_random_data(int len)
        if (ret < 0) {
            close(fd);
             sfree(buf);
-           perror("puttygen: unable to read /dev/random");
+            fprintf(stderr, "puttygen: %s: read: %s\n",
+                    device, strerror(errno));
            return NULL;
        }
        ngot += ret;
index 75f719cf38aedae18fae4d72378dad13d56f34cd..4f3dde2d1694cbd33a62636fd7d5b3267cbc3ac0 100644 (file)
@@ -193,11 +193,43 @@ struct X11Connection {
 };
 
 char *socketname;
+static enum { SHELL_AUTO, SHELL_SH, SHELL_CSH } shell_type = SHELL_AUTO;
 void pageant_print_env(int pid)
 {
-    printf("SSH_AUTH_SOCK=%s; export SSH_AUTH_SOCK;\n"
-           "SSH_AGENT_PID=%d; export SSH_AGENT_PID;\n",
-           socketname, (int)pid);
+    if (shell_type == SHELL_AUTO) {
+        /* Same policy as OpenSSH: if $SHELL ends in "csh" then assume
+         * it's csh-shaped. */
+        const char *shell = getenv("SHELL");
+        if (shell && strlen(shell) >= 3 &&
+            !strcmp(shell + strlen(shell) - 3, "csh"))
+            shell_type = SHELL_CSH;
+        else
+            shell_type = SHELL_SH;
+    }
+
+    /*
+     * These shell snippets could usefully pay some attention to
+     * escaping of interesting characters. I don't think it causes a
+     * problem at the moment, because the pathnames we use are so
+     * utterly boring, but it's a lurking bug waiting to happen once
+     * a bit more flexibility turns up.
+     */
+
+    switch (shell_type) {
+      case SHELL_SH:
+        printf("SSH_AUTH_SOCK=%s; export SSH_AUTH_SOCK;\n"
+               "SSH_AGENT_PID=%d; export SSH_AGENT_PID;\n",
+               socketname, pid);
+        break;
+      case SHELL_CSH:
+        printf("setenv SSH_AUTH_SOCK %s;\n"
+               "setenv SSH_AGENT_PID %d;\n",
+               socketname, pid);
+        break;
+      case SHELL_AUTO:
+        assert(0 && "Can't get here");
+        break;
+    }
 }
 
 void pageant_fork_and_print_env(int retain_tty)
@@ -967,6 +999,10 @@ int main(int argc, char **argv)
                 curr_keyact = KEYACT_CLIENT_ADD;
             } else if (!strcmp(p, "-d")) {
                 curr_keyact = KEYACT_CLIENT_DEL;
+            } else if (!strcmp(p, "-s")) {
+                shell_type = SHELL_SH;
+            } else if (!strcmp(p, "-c")) {
+                shell_type = SHELL_CSH;
             } else if (!strcmp(p, "-D")) {
                 add_keyact(KEYACT_CLIENT_DEL_ALL, NULL);
             } else if (!strcmp(p, "-l")) {
index 3211d45a54135ac44ef73b8587278b317dd89ff5..5d0b5f55371f3d7cb5fb15cf42683d9e65061b33 100644 (file)
@@ -10,6 +10,7 @@
 const char *const appname = "pterm";
 const int use_event_log = 0;          /* pterm doesn't need it */
 const int new_session = 0, saved_sessions = 0;   /* or these */
+const int dup_check_launchable = 0; /* no need to check host name in conf */
 const int use_pty_argv = TRUE;
 
 Backend *select_backend(Conf *conf)
@@ -43,18 +44,13 @@ char *make_default_wintitle(char *hostname)
     return dupstr("pterm");
 }
 
-int main(int argc, char **argv)
+void setup(int single)
 {
-    extern int pt_main(int argc, char **argv);
     extern void pty_pre_init(void);    /* declared in pty.c */
-    int ret;
 
     cmdline_tooltype = TOOLTYPE_NONNETWORK;
     default_protocol = -1;
 
-    pty_pre_init();
-
-    ret = pt_main(argc, argv);
-    cleanup_exit(ret);
-    return ret;             /* not reached, but placates optimisers */
+    if (single)
+        pty_pre_init();
 }
index 79a60f3c139c4fbc75517f9f9392c0d8a73db4be..39f96f126afd9a347f3c46caf2bd7c8fed914015 100644 (file)
@@ -180,6 +180,8 @@ static struct utmpx utmp_entry;
  */
 char **pty_argv;
 
+char *pty_osx_envrestore_prefix;
+
 static void pty_close(Pty pty);
 static void pty_try_write(Pty pty);
 
@@ -809,6 +811,34 @@ static const char *pty_init(void *frontend, void **backend_handle, Conf *conf,
         * We are the child.
         */
 
+        if (pty_osx_envrestore_prefix) {
+            int plen = strlen(pty_osx_envrestore_prefix);
+            extern char **environ;
+            char **ep;
+
+          restart_osx_env_restore:
+            for (ep = environ; *ep; ep++) {
+                char *e = *ep;
+
+                if (!strncmp(e, pty_osx_envrestore_prefix, plen)) {
+                    int unset = (e[plen] == 'u');
+                    char *pname = dupprintf("%.*s", (int)strcspn(e, "="), e);
+                    char *name = pname + plen + 1;
+                    char *value = e + strcspn(e, "=");
+                    if (*value) value++;
+                    value = dupstr(value);
+                    if (unset)
+                        unsetenv(name);
+                    else
+                        setenv(name, value, 1);
+                    unsetenv(pname);
+                    sfree(pname);
+                    sfree(value);
+                    goto restart_osx_env_restore;
+                }
+            }
+        }
+
        slavefd = pty_open_slave(pty);
        if (slavefd < 0) {
            perror("slave pty: open");
@@ -912,14 +942,14 @@ static const char *pty_init(void *frontend, void **backend_handle, Conf *conf,
        /*
         * SIGINT, SIGQUIT and SIGPIPE may have been set to ignored by
         * our parent, particularly by things like sh -c 'pterm &' and
-        * some window or session managers. SIGCHLD, meanwhile, was
-        * blocked during pt_main() startup. Reverse all this for our
-        * child process.
+        * some window or session managers. SIGPIPE was also
+        * (potentially) blocked by us during startup. Reverse all
+        * this for our child process.
         */
        putty_signal(SIGINT, SIG_DFL);
        putty_signal(SIGQUIT, SIG_DFL);
        putty_signal(SIGPIPE, SIG_DFL);
-       block_signal(SIGCHLD, 0);
+       block_signal(SIGPIPE, 0);
        if (pty_argv) {
             /*
              * Exec the exact argument list we were given.
index d0ba55f68177a7bc68da8084d21e96a8104f692a..6d22fceb76b2b32b8d77489d062222d455efdc7c 100644 (file)
@@ -20,6 +20,7 @@
  */
 const int use_pty_argv = FALSE;
 char **pty_argv;                      /* never used */
+char *pty_osx_envrestore_prefix;
 
 /*
  * Clean up and exit.
@@ -52,6 +53,7 @@ int cfgbox(Conf *conf)
 static int got_host = 0;
 
 const int use_event_log = 1, new_session = 1, saved_sessions = 1;
+const int dup_check_launchable = 1;
 
 int process_nonoption_arg(const char *arg, Conf *conf, int *allow_launch)
 {
@@ -132,11 +134,8 @@ char *platform_get_x_display(void) {
 const int share_can_be_downstream = TRUE;
 const int share_can_be_upstream = TRUE;
 
-int main(int argc, char **argv)
+void setup(int single)
 {
-    extern int pt_main(int argc, char **argv);
-    int ret;
-
     sk_init();
     flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
     default_protocol = be_default_protocol;
@@ -147,7 +146,4 @@ int main(int argc, char **argv)
        if (b)
            default_port = b->default_port;
     }
-    ret = pt_main(argc, argv);
-    cleanup_exit(ret);
-    return ret;             /* not reached, but placates optimisers */
 }
index 3ac1d2c346470242955b96ff61825284e78126f0..6e39491010d7a04ef4d59ebd7366ac1e374929d9 100644 (file)
@@ -618,6 +618,8 @@ char *ssh_sftp_get_cmdline(const char *prompt, int no_fds_ok)
 
 void frontend_net_error_pending(void) {}
 
+void platform_psftp_post_option_setup(void) {}
+
 /*
  * Main program: do platform-specific initialisation and then call
  * psftp_main().
diff --git a/unix/x11misc.c b/unix/x11misc.c
new file mode 100644 (file)
index 0000000..b49aa48
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * x11misc.c: miscellaneous stuff for dealing directly with X servers.
+ */
+
+#include <ctype.h>
+#include <unistd.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "putty.h"
+
+#ifndef NOT_X_WINDOWS
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+
+#include "x11misc.h"
+
+/* ----------------------------------------------------------------------
+ * Error handling mechanism which permits us to ignore specific X11
+ * errors from particular requests. We maintain a list of upcoming
+ * potential error events that we want to not treat as fatal errors.
+ */
+
+static int (*orig_x11_error_handler)(Display *thisdisp, XErrorEvent *err);
+
+struct x11_err_to_ignore {
+    Display *display;
+    unsigned char error_code;
+    unsigned long serial;
+};
+
+struct x11_err_to_ignore *errs;
+
+int nerrs, errsize;
+
+static int x11_error_handler(Display *thisdisp, XErrorEvent *err)
+{
+    int i;
+    for (i = 0; i < nerrs; i++) {
+        if (thisdisp == errs[i].display &&
+            err->serial == errs[i].serial &&
+            err->error_code == errs[i].error_code) {
+            /* Ok, this is an error we're happy to ignore */
+            return 0;
+        }
+    }
+
+    return (*orig_x11_error_handler)(thisdisp, err);
+}
+
+void x11_ignore_error(Display *disp, unsigned char errcode)
+{
+    /*
+     * Install our error handler, if we haven't already.
+     */
+    if (!orig_x11_error_handler)
+        orig_x11_error_handler = XSetErrorHandler(x11_error_handler);
+
+    /*
+     * This is as good a moment as any to winnow the ignore list based
+     * on requests we know to have been processed.
+     */
+    {
+        unsigned long last = LastKnownRequestProcessed(disp);
+        int i, j;
+        for (i = j = 0; i < nerrs; i++) {
+            if (errs[i].display == disp && errs[i].serial <= last)
+                continue;
+            errs[j++] = errs[i];
+        }
+        nerrs = j;
+    }
+
+    if (nerrs >= errsize) {
+        errsize = nerrs * 5 / 4 + 16;
+        errs = sresize(errs, errsize, struct x11_err_to_ignore);
+    }
+    errs[nerrs].display = disp;
+    errs[nerrs].error_code = errcode;
+    errs[nerrs].serial = NextRequest(disp);
+    nerrs++;
+}
+
+#endif
+
diff --git a/unix/x11misc.h b/unix/x11misc.h
new file mode 100644 (file)
index 0000000..0ad1476
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * x11misc.h: header file for functions that need to refer to Xlib
+ * data types. Has to be separate from unix.h so that we can include
+ * it only after including the X headers, which in turn has to be done
+ * after putty.h has told us whether NOT_X_WINDOWS is defined.
+ */
+
+#ifndef NOT_X_WINDOWS
+
+/*
+ * x11misc.c.
+ */
+void x11_ignore_error(Display *disp, unsigned char errcode);
+
+#endif
diff --git a/windows/README-msi.txt b/windows/README-msi.txt
new file mode 100644 (file)
index 0000000..a6c2707
--- /dev/null
@@ -0,0 +1,45 @@
+PuTTY README\r
+============\r
+\r
+This is the README file for the PuTTY MSI installer distribution. If\r
+you're reading this, you've probably just run our installer and\r
+installed PuTTY on your system.\r
+\r
+What should I do next?\r
+----------------------\r
+\r
+If you want to use PuTTY to connect to other computers, or use PSFTP\r
+to transfer files, you should just be able to run them from the\r
+Start menu.\r
+\r
+If you want to use the command-line file transfer utility PSCP, you\r
+will need to run this from a Command Prompt or equivalent, because it\r
+will not do anything useful without command-line options telling it\r
+what files to copy to and from where. You can do this by just running\r
+the command 'pscp' from a Command Prompt, if you used the installer's\r
+option to put the PuTTY installation directory on your PATH.\r
+Alternatively, you can always run pscp.exe by its full pathname, e.g.\r
+"C:\Program Files\PuTTY\pscp.exe".\r
+\r
+(Note that a Command Prompt that was already open before you ran the\r
+installer will not have inherited the update of PATH.)\r
+\r
+Some versions of Windows will refuse to run HTML Help files (.CHM)\r
+if they are installed on a network drive. If you have installed\r
+PuTTY on a network drive, you might want to check that the help file\r
+works properly. If not, see http://support.microsoft.com/kb/896054\r
+for information on how to solve this problem.\r
+\r
+What do I do if it doesn't work?\r
+--------------------------------\r
+\r
+The PuTTY home web site is\r
+\r
+    http://www.chiark.greenend.org.uk/~sgtatham/putty/\r
+\r
+Here you will find our list of known bugs and pending feature\r
+requests. If your problem is not listed in there, or in the FAQ, or\r
+in the manuals, read the Feedback page to find out how to report\r
+bugs to us. PLEASE read the Feedback page carefully: it is there to\r
+save you time as well as us. Do not send us one-line bug reports\r
+telling us `it doesn't work'.\r
index 7be97778ad69eaac925bb0697520fe320c091841..52e7cb771effeb472a5c9d3b51bc81c600ac129c 100644 (file)
           <Component Id="README_Component"
                      Guid="0AB63F2A-0FD9-4961-B8F7-AB85C22D9986">
             <File Id="README_File"
-                  Source="README.txt" KeyPath="yes" />
+                  Source="README-msi.txt" Name="README.txt" KeyPath="yes" />
           </Component>
 
           <!--
         separately disableable are the auxiliary ones for desktop
         shortcuts, .PPK file extension and modifying PATH.
 
-        (And even those are mostly historical - the first two were
-        checkbox options in Inno Setup, and the third was not done at
-        all by Inno Setup so people might be surprised by it when
-        switching to this installer. I don't actually know for sure
-        that anyone _wants_ to be without these pieces. So all of them
-        are enabled by default.)
+        DesktopFeature (the desktop icon) is disabled by default, on
+        the basis of not cluttering up desktops too much unless
+        someone actually wants it. The .PPK association and PATH are
+        behind-the-scenes sorts of thing, so they're on by default.
+
+        (The old Inno Setup installer also made it optional whether
+        PuTTY got a Start Menu subfolder. That seems to be harder in
+        WiX, because the Start Menu shortcuts are tied in to the
+        installation of the files themselves, so the Start Menu
+        subfolder is mandatory if you're using this installer at all.
+        That doesn't seem unreasonable to me - if you don't want
+        _that_, you might as well just unpack the zip file and not
+        bother with an installer at all.)
     -->
     <Feature Id="FilesFeature" Level="1" Absent="disallow" AllowAdvertise="no"
              Title="Install PuTTY files">
       <ComponentRef Id="README_Component" />
       <ComponentRef Id="ProgramMenuDir" />
     </Feature>
-    <Feature Id="DesktopFeature" Level="1" Absent="allow" AllowAdvertise="no"
+    <Feature Id="DesktopFeature" Level="2" Absent="allow" AllowAdvertise="no"
              Title="Add shortcut to PuTTY on the Desktop">
       <ComponentRef Id="Desktop_Shortcut_Component" />
     </Feature>
       <Publish Dialog="MaintenanceTypeDlg" Control="RemoveButton" Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
       <Publish Dialog="MaintenanceTypeDlg" Control="Back" Event="NewDialog" Value="MaintenanceWelcomeDlg">1</Publish>
 
+      <Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" 
+               Value="LaunchApplication">WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed</Publish>
+
       <!--
           This ARPNOMODIFY flag prohibits changing the set of
           installed features, which would otherwise be possible by
       <Property Id="ARPNOMODIFY" Value="1" />
     </UI>
 
+    <!--
+        Offer to display README after installation.
+    -->
+    <Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT"
+              Value="View README file" />
+    <Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1" />
+    <Property Id="WixShellExecTarget" Value="[#README_File]" />
+    <CustomAction Id="LaunchApplication" BinaryKey="WixCA"
+                  DllEntry="WixShellExec" Impersonate="yes" />
+
     <!-- Glue: tell the install dir part of the UI what id my actual
          install dir is known by. Otherwise the former won't know how
          to alter the setting of the latter. -->
     <WixVariable Id="WixUIDialogBmp" Value="msidialog.bmp" />
     <WixVariable Id="WixUIBannerBmp" Value="msibanner.bmp" />
 
+    <!--
+        Set the icon that will show up in Add/Remove Programs.
+
+        http://www.codeproject.com/Articles/43564/WiX-Tricks says that
+        for some weird reason the Id of this icon has to end in .exe.
+    -->
+    <Icon Id="installericon.exe" SourceFile="puttyins.ico" />
+    <Property Id="ARPPRODUCTICON" Value="installericon.exe" />
+
   </Product>
 </Wix>
index 0b60e1849712a879477490f8803350a65517408a..d7b95704174fb11a14a5e1e934d1d076bcf4e7cf 100644 (file)
@@ -197,6 +197,53 @@ int askalg(void *frontend, const char *algtype, const char *algname,
     }
 }
 
+int askhk(void *frontend, const char *algname, const char *betteralgs,
+          void (*callback)(void *ctx, int result), void *ctx)
+{
+    HANDLE hin;
+    DWORD savemode, i;
+
+    static const char msg[] =
+       "The first host key type we have stored for this server\n"
+       "is %s, which is below the configured warning threshold.\n"
+       "The server also provides the following types of host key\n"
+        "above the threshold, which we do not have stored:\n"
+        "%s\n"
+       "Continue with connection? (y/n) ";
+    static const char msg_batch[] =
+       "The first host key type we have stored for this server\n"
+       "is %s, which is below the configured warning threshold.\n"
+       "The server also provides the following types of host key\n"
+        "above the threshold, which we do not have stored:\n"
+        "%s\n"
+       "Connection abandoned.\n";
+    static const char abandoned[] = "Connection abandoned.\n";
+
+    char line[32];
+
+    if (console_batch_mode) {
+       fprintf(stderr, msg_batch, algname, betteralgs);
+       return 0;
+    }
+
+    fprintf(stderr, msg, algname, betteralgs);
+    fflush(stderr);
+
+    hin = GetStdHandle(STD_INPUT_HANDLE);
+    GetConsoleMode(hin, &savemode);
+    SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
+                        ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
+    ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
+    SetConsoleMode(hin, savemode);
+
+    if (line[0] == 'y' || line[0] == 'Y') {
+       return 1;
+    } else {
+       fprintf(stderr, abandoned);
+       return 0;
+    }
+}
+
 /*
  * Ask whether to wipe a session log file before writing to it.
  * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
index 8248ce835974fd8ffdc3142da7d118ea0f8f0913..17327866edb44d132a24f3c202044badb158aec4 100644 (file)
@@ -67,8 +67,8 @@ void force_normal(HWND hwnd)
     recurse = 0;
 }
 
-static int CALLBACK LogProc(HWND hwnd, UINT msg,
-                           WPARAM wParam, LPARAM lParam)
+static INT_PTR CALLBACK LogProc(HWND hwnd, UINT msg,
+                                WPARAM wParam, LPARAM lParam)
 {
     int i;
 
@@ -162,8 +162,8 @@ static int CALLBACK LogProc(HWND hwnd, UINT msg,
     return 0;
 }
 
-static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
-                               WPARAM wParam, LPARAM lParam)
+static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg,
+                                    WPARAM wParam, LPARAM lParam)
 {
     switch (msg) {
       case WM_INITDIALOG:
@@ -189,8 +189,8 @@ static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
     return 0;
 }
 
-static int CALLBACK AboutProc(HWND hwnd, UINT msg,
-                             WPARAM wParam, LPARAM lParam)
+static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg,
+                                  WPARAM wParam, LPARAM lParam)
 {
     char *str;
 
@@ -291,8 +291,8 @@ static void SaneEndDialog(HWND hwnd, int ret)
 /*
  * Null dialog procedure.
  */
-static int CALLBACK NullDlgProc(HWND hwnd, UINT msg,
-                               WPARAM wParam, LPARAM lParam)
+static INT_PTR CALLBACK NullDlgProc(HWND hwnd, UINT msg,
+                                    WPARAM wParam, LPARAM lParam)
 {
     return 0;
 }
@@ -375,8 +375,8 @@ static void create_controls(HWND hwnd, char *path)
  * (Being a dialog procedure, in general it returns 0 if the default
  * dialog processing should be performed, and 1 if it should not.)
  */
-static int CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg,
-                                      WPARAM wParam, LPARAM lParam)
+static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg,
+                                           WPARAM wParam, LPARAM lParam)
 {
     HWND hw, treeview;
     struct treeview_faff tvfaff;
@@ -895,6 +895,33 @@ int askalg(void *frontend, const char *algtype, const char *algname,
        return 0;
 }
 
+int askhk(void *frontend, const char *algname, const char *betteralgs,
+          void (*callback)(void *ctx, int result), void *ctx)
+{
+    static const char mbtitle[] = "%s Security Alert";
+    static const char msg[] =
+       "The first host key type we have stored for this server\n"
+       "is %s, which is below the configured warning threshold.\n"
+       "The server also provides the following types of host key\n"
+        "above the threshold, which we do not have stored:\n"
+        "%s\n"
+       "Do you want to continue with this connection?\n";
+    char *message, *title;
+    int mbret;
+
+    message = dupprintf(msg, algname, betteralgs);
+    title = dupprintf(mbtitle, appname);
+    mbret = MessageBox(NULL, message, title,
+                      MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);
+    socket_reselect_all();
+    sfree(message);
+    sfree(title);
+    if (mbret == IDYES)
+       return 1;
+    else
+       return 0;
+}
+
 /*
  * Ask whether to wipe a session log file before writing to it.
  * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
index fc51e57fb2b5fbbe95fbdc37d76831dd7cee2e82..b1530dbcb17997b78828707fad94f771eb9b4341 100644 (file)
@@ -395,7 +395,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
      * Protect our process
      */
     {
-#ifndef UNPROTECT
+#if !defined UNPROTECT && !defined NO_SECURITY
         char *error = NULL;
         if (! setprocessacl(error)) {
             char *message = dupprintf("Could not restrict process ACL: %s",
@@ -827,7 +827,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
            AppendMenu(m, MF_SEPARATOR, 0, 0);
            AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session...");
            AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
-           AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) savedsess_menu,
+           AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT_PTR) savedsess_menu,
                       "Sa&ved Sessions");
            AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings...");
            AppendMenu(m, MF_SEPARATOR, 0, 0);
@@ -1054,7 +1054,7 @@ void update_specials_menu(void *frontend)
                saved_menu = new_menu; /* XXX lame stacking */
                new_menu = CreatePopupMenu();
                AppendMenu(saved_menu, MF_POPUP | MF_ENABLED,
-                          (UINT) new_menu, specials[i].name);
+                          (UINT_PTR) new_menu, specials[i].name);
                break;
              case TS_EXITMENU:
                nesting--;
@@ -1079,13 +1079,14 @@ void update_specials_menu(void *frontend)
     for (j = 0; j < lenof(popup_menus); j++) {
        if (specials_menu) {
            /* XXX does this free up all submenus? */
-           DeleteMenu(popup_menus[j].menu, (UINT)specials_menu, MF_BYCOMMAND);
+           DeleteMenu(popup_menus[j].menu, (UINT_PTR)specials_menu,
+                       MF_BYCOMMAND);
            DeleteMenu(popup_menus[j].menu, IDM_SPECIALSEP, MF_BYCOMMAND);
        }
        if (new_menu) {
            InsertMenu(popup_menus[j].menu, IDM_SHOWLOG,
                       MF_BYCOMMAND | MF_POPUP | MF_ENABLED,
-                      (UINT) new_menu, "S&pecial Command");
+                      (UINT_PTR) new_menu, "S&pecial Command");
            InsertMenu(popup_menus[j].menu, IDM_SHOWLOG,
                       MF_BYCOMMAND | MF_SEPARATOR, IDM_SPECIALSEP, 0);
        }
index 9b21c92c17f131242bc6c95bf4fc6249bfb2a581..2e40938e5ca73d87f1bc3e72b0707806121041da 100644 (file)
 #define WINHELP_CTX_ssh_compress "ssh.compress:config-ssh-comp"
 #define WINHELP_CTX_ssh_share "ssh.sharing:config-ssh-sharing"
 #define WINHELP_CTX_ssh_kexlist "ssh.kex.order:config-ssh-kex-order"
+#define WINHELP_CTX_ssh_hklist "ssh.hostkey.order:config-ssh-hostkey-order"
 #define WINHELP_CTX_ssh_kex_repeat "ssh.kex.repeat:config-ssh-kex-rekey"
 #define WINHELP_CTX_ssh_kex_manual_hostkeys "ssh.kex.manualhostkeys:config-ssh-kex-manual-hostkeys"
 #define WINHELP_CTX_ssh_auth_bypass "ssh.auth.bypass:config-ssh-noauth"
index eca5041a8dfa949da3e91321d494ffcf354c95c6..e5bca9358ead86ddb74a8c48c7b138afe9842486 100644 (file)
@@ -445,7 +445,8 @@ static IShellLink *make_shell_link(const char *appname,
                              sessionname, "'", NULL);
     } else {
         assert(appname);
-        desc_string = dupprintf("Run %.*s", strcspn(appname, "."), appname);
+        desc_string = dupprintf("Run %.*s",
+                                (int)strcspn(appname, "."), appname);
     }
     ret->lpVtbl->SetDescription(ret, desc_string);
     sfree(desc_string);
@@ -461,7 +462,8 @@ static IShellLink *make_shell_link(const char *appname,
             pv.pszVal = dupstr(sessionname);
         } else {
             assert(appname);
-            pv.pszVal = dupprintf("Run %.*s", strcspn(appname, "."), appname);
+            pv.pszVal = dupprintf("Run %.*s",
+                                  (int)strcspn(appname, "."), appname);
         }
         pPS->lpVtbl->SetValue(pPS, &PKEY_Title, &pv);
         sfree(pv.pszVal);
index db55145c15561afe65a0252a80b8ed0228d32287..bff5ae874547220e67d0584dcc9e61b3abd0b9f9 100644 (file)
@@ -12,6 +12,7 @@
 #include "putty.h"
 #include "ssh.h"
 #include "licence.h"
+#include "winsecur.h"
 
 #include <commctrl.h>
 
@@ -21,7 +22,8 @@
 
 #define WM_DONEKEY (WM_APP + 1)
 
-#define DEFAULT_KEYSIZE 2048
+#define DEFAULT_KEY_BITS 2048
+#define DEFAULT_CURVE_INDEX 0
 
 static char *cmdline_keyfile = NULL;
 
@@ -332,7 +334,8 @@ typedef enum {RSA, DSA, ECDSA, ED25519} keytype;
 struct rsa_key_thread_params {
     HWND progressbar;                 /* notify this with progress */
     HWND dialog;                      /* notify this on completion */
-    int keysize;                      /* bits in key */
+    int key_bits;                     /* bits in key modulus (RSA, DSA) */
+    int curve_bits;                    /* bits in elliptic curve (ECDSA) */
     keytype keytype;
     union {
         struct RSAKey *key;
@@ -340,7 +343,7 @@ struct rsa_key_thread_params {
         struct ec_key *eckey;
     };
 };
-static DWORD WINAPI generate_rsa_key_thread(void *param)
+static DWORD WINAPI generate_key_thread(void *param)
 {
     struct rsa_key_thread_params *params =
        (struct rsa_key_thread_params *) param;
@@ -350,13 +353,13 @@ static DWORD WINAPI generate_rsa_key_thread(void *param)
     progress_update(&prog, PROGFN_INITIALISE, 0, 0);
 
     if (params->keytype == DSA)
-       dsa_generate(params->dsskey, params->keysize, progress_update, &prog);
+       dsa_generate(params->dsskey, params->key_bits, progress_update, &prog);
     else if (params->keytype == ECDSA)
-        ec_generate(params->eckey, params->keysize, progress_update, &prog);
+        ec_generate(params->eckey, params->curve_bits, progress_update, &prog);
     else if (params->keytype == ED25519)
-        ec_edgenerate(params->eckey, params->keysize, progress_update, &prog);
+        ec_edgenerate(params->eckey, 256, progress_update, &prog);
     else
-       rsa_generate(params->key, params->keysize, progress_update, &prog);
+       rsa_generate(params->key, params->key_bits, progress_update, &prog);
 
     PostMessage(params->dialog, WM_DONEKEY, 0, 0);
 
@@ -369,7 +372,7 @@ struct MainDlgState {
     int generation_thread_exists;
     int key_exists;
     int entropy_got, entropy_required, entropy_size;
-    int keysize;
+    int key_bits, curve_bits;
     int ssh2;
     keytype keytype;
     char **commentptr;                /* points to key.comment or ssh2key.comment */
@@ -450,6 +453,8 @@ enum {
     IDC_TYPESTATIC, IDC_KEYSSH1, IDC_KEYSSH2RSA, IDC_KEYSSH2DSA,
     IDC_KEYSSH2ECDSA, IDC_KEYSSH2ED25519,
     IDC_BITSSTATIC, IDC_BITS,
+    IDC_CURVESTATIC, IDC_CURVE,
+    IDC_NOTHINGSTATIC,
     IDC_ABOUT,
     IDC_GIVEHELP,
     IDC_IMPORT,
@@ -584,6 +589,47 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status)
     }
 }
 
+/*
+ * Helper functions to set the key type, taking care of keeping the
+ * menu and radio button selections in sync and also showing/hiding
+ * the appropriate size/curve control for the current key type.
+ */
+void ui_update_key_type_ctrls(HWND hwnd)
+{
+    enum { BITS, CURVE, NOTHING } which;
+    static const int bits_ids[] = {
+        IDC_BITSSTATIC, IDC_BITS, 0
+    };
+    static const int curve_ids[] = {
+        IDC_CURVESTATIC, IDC_CURVE, 0
+    };
+    static const int nothing_ids[] = {
+        IDC_NOTHINGSTATIC, 0
+    };
+
+    if (IsDlgButtonChecked(hwnd, IDC_KEYSSH1) ||
+        IsDlgButtonChecked(hwnd, IDC_KEYSSH2RSA) ||
+        IsDlgButtonChecked(hwnd, IDC_KEYSSH2DSA)) {
+        which = BITS;
+    } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2ECDSA)) {
+        which = CURVE;
+    } else {
+        /* ED25519 implicitly only supports one curve */
+        which = NOTHING;
+    }
+
+    hidemany(hwnd, bits_ids, which != BITS);
+    hidemany(hwnd, curve_ids, which != CURVE);
+    hidemany(hwnd, nothing_ids, which != NOTHING);
+}
+void ui_set_key_type(HWND hwnd, struct MainDlgState *state, int button)
+{
+    CheckRadioButton(hwnd, IDC_KEYSSH1, IDC_KEYSSH2ED25519, button);
+    CheckMenuRadioItem(state->keymenu, IDC_KEYSSH1, IDC_KEYSSH2ED25519,
+                       button, MF_BYCOMMAND);
+    ui_update_key_type_ctrls(hwnd);
+}
+
 void load_key_file(HWND hwnd, struct MainDlgState *state,
                   Filename *filename, int was_import_cmd)
 {
@@ -852,8 +898,9 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg,
 
        {
            struct ctlpos cp, cp2;
+            int ymax;
 
-           /* Accelerators used: acglops1rbde */
+           /* Accelerators used: acglops1rbvde */
 
            ctlposinit(&cp, hwnd, 4, 4, 4);
            beginbox(&cp, "Key", IDC_BOX_KEY);
@@ -894,14 +941,38 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg,
                       "ED&25519", IDC_KEYSSH2ED25519,
                      "SSH-&1 (RSA)", IDC_KEYSSH1,
                       NULL);
-           staticedit(&cp, "Number of &bits in a generated key:",
+            cp2 = cp;
+           staticedit(&cp2, "Number of &bits in a generated key:",
                       IDC_BITSSTATIC, IDC_BITS, 20);
+            ymax = cp2.ypos;
+            cp2 = cp;
+           staticddl(&cp2, "Cur&ve to use for generating this key:",
+                      IDC_CURVESTATIC, IDC_CURVE, 20);
+            SendDlgItemMessage(hwnd, IDC_CURVE, CB_RESETCONTENT, 0, 0);
+            {
+                int i, bits;
+                const struct ec_curve *curve;
+                const struct ssh_signkey *alg;
+
+                for (i = 0; i < n_ec_nist_curve_lengths; i++) {
+                    bits = ec_nist_curve_lengths[i];
+                    ec_nist_alg_and_curve_by_bits(bits, &curve, &alg);
+                    SendDlgItemMessage(hwnd, IDC_CURVE, CB_ADDSTRING, 0,
+                                       (LPARAM)curve->textname);
+                }
+            }
+            ymax = ymax > cp2.ypos ? ymax : cp2.ypos;
+            cp2 = cp;
+           statictext(&cp2, "(nothing to configure for this key type)",
+                      1, IDC_NOTHINGSTATIC);
+            ymax = ymax > cp2.ypos ? ymax : cp2.ypos;
+            cp.ypos = ymax;
            endbox(&cp);
        }
-        CheckRadioButton(hwnd, IDC_KEYSSH1, IDC_KEYSSH2ECDSA, IDC_KEYSSH2RSA);
-        CheckMenuRadioItem(state->keymenu, IDC_KEYSSH1, IDC_KEYSSH2ECDSA,
-                          IDC_KEYSSH2RSA, MF_BYCOMMAND);
-       SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEYSIZE, FALSE);
+        ui_set_key_type(hwnd, state, IDC_KEYSSH2RSA);
+       SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEY_BITS, FALSE);
+       SendDlgItemMessage(hwnd, IDC_CURVE, CB_SETCURSEL,
+                           DEFAULT_CURVE_INDEX, 0);
 
        /*
         * Initially, hide the progress bar and the key display,
@@ -949,12 +1020,13 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg,
                params = snew(struct rsa_key_thread_params);
                params->progressbar = GetDlgItem(hwnd, IDC_PROGRESS);
                params->dialog = hwnd;
-               params->keysize = state->keysize;
+               params->key_bits = state->key_bits;
+               params->curve_bits = state->curve_bits;
                 params->keytype = state->keytype;
                params->key = &state->key;
                params->dsskey = &state->dsskey;
 
-               if (!CreateThread(NULL, 0, generate_rsa_key_thread,
+               if (!CreateThread(NULL, 0, generate_key_thread,
                                  params, 0, &threadid)) {
                    MessageBox(hwnd, "Out of thread resources",
                               "Key generation error",
@@ -976,13 +1048,7 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg,
            {
                state = (struct MainDlgState *)
                    GetWindowLongPtr(hwnd, GWLP_USERDATA);
-               if (!IsDlgButtonChecked(hwnd, LOWORD(wParam)))
-                   CheckRadioButton(hwnd,
-                                     IDC_KEYSSH1, IDC_KEYSSH2ED25519,
-                                    LOWORD(wParam));
-               CheckMenuRadioItem(state->keymenu,
-                                   IDC_KEYSSH1, IDC_KEYSSH2ED25519,
-                                  LOWORD(wParam), MF_BYCOMMAND);
+                ui_set_key_type(hwnd, state, LOWORD(wParam));
            }
            break;
          case IDC_QUIT:
@@ -1029,9 +1095,16 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg,
                (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
            if (!state->generation_thread_exists) {
                BOOL ok;
-               state->keysize = GetDlgItemInt(hwnd, IDC_BITS, &ok, FALSE);
+               state->key_bits = GetDlgItemInt(hwnd, IDC_BITS, &ok, FALSE);
                if (!ok)
-                   state->keysize = DEFAULT_KEYSIZE;
+                   state->key_bits = DEFAULT_KEY_BITS;
+                {
+                    int curveindex = SendDlgItemMessage(hwnd, IDC_CURVE,
+                                                        CB_GETCURSEL, 0, 0);
+                    assert(curveindex >= 0);
+                    assert(curveindex < n_ec_nist_curve_lengths);
+                    state->curve_bits = ec_nist_curve_lengths[curveindex];
+                }
                /* If we ever introduce a new key type, check it here! */
                state->ssh2 = !IsDlgButtonChecked(hwnd, IDC_KEYSSH1);
                 state->keytype = RSA;
@@ -1042,44 +1115,32 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg,
                 } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2ED25519)) {
                     state->keytype = ED25519;
                 }
-               if (state->keysize < 256) {
-                   int ret = MessageBox(hwnd,
-                                        "PuTTYgen will not generate a key"
-                                        " smaller than 256 bits.\n"
-                                        "Key length reset to 256. Continue?",
-                                        "PuTTYgen Warning",
+
+               if ((state->keytype == RSA || state->keytype == DSA) &&
+                    state->key_bits < 256) {
+                    char *message = dupprintf
+                        ("PuTTYgen will not generate a key smaller than 256"
+                         " bits.\nKey length reset to default %d. Continue?",
+                         DEFAULT_KEY_BITS);
+                   int ret = MessageBox(hwnd, message, "PuTTYgen Warning",
                                         MB_ICONWARNING | MB_OKCANCEL);
+                    sfree(message);
+                   if (ret != IDOK)
+                       break;
+                   state->key_bits = DEFAULT_KEY_BITS;
+                   SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEY_BITS, FALSE);
+               } else if ((state->keytype == RSA || state->keytype == DSA) &&
+                           state->key_bits < DEFAULT_KEY_BITS) {
+                    char *message = dupprintf
+                        ("Keys shorter than %d bits are not recommended. "
+                         "Really generate this key?", DEFAULT_KEY_BITS);
+                   int ret = MessageBox(hwnd, message, "PuTTYgen Warning",
+                                        MB_ICONWARNING | MB_OKCANCEL);
+                    sfree(message);
                    if (ret != IDOK)
                        break;
-                   state->keysize = 256;
-                   SetDlgItemInt(hwnd, IDC_BITS, 256, FALSE);
-               }
-                if (state->keytype == ECDSA && !(state->keysize == 256 ||
-                                                 state->keysize == 384 ||
-                                                 state->keysize == 521)) {
-                    int ret = MessageBox(hwnd,
-                                         "Only 256, 384 and 521 bit elliptic"
-                                         " curves are supported.\n"
-                                         "Key length reset to 256. Continue?",
-                                         "PuTTYgen Warning",
-                                         MB_ICONWARNING | MB_OKCANCEL);
-                    if (ret != IDOK)
-                        break;
-                    state->keysize = 256;
-                    SetDlgItemInt(hwnd, IDC_BITS, 256, FALSE);
-                }
-                if (state->keytype == ED25519 && state->keysize != 256) {
-                    int ret = MessageBox(hwnd,
-                                         "Only 256 bit Edwards elliptic"
-                                         " curves are supported.\n"
-                                         "Key length reset to 256. Continue?",
-                                         "PuTTYgen Warning",
-                                         MB_ICONWARNING | MB_OKCANCEL);
-                    if (ret != IDOK)
-                        break;
-                    state->keysize = 256;
-                    SetDlgItemInt(hwnd, IDC_BITS, 256, FALSE);
                 }
+
                ui_set_state(hwnd, state, 1);
                SetDlgItemText(hwnd, IDC_GENERATING, entropy_msg);
                state->key_exists = FALSE;
@@ -1098,7 +1159,13 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg,
                 * so with 2 bits per mouse movement we expect 2
                 * bits every 2 words.
                 */
-               state->entropy_required = (state->keysize / 2) * 2;
+               if (state->keytype == RSA || state->keytype == DSA)
+                    state->entropy_required = (state->key_bits / 2) * 2;
+               else if (state->keytype == ECDSA)
+                    state->entropy_required = (state->curve_bits / 2) * 2;
+                else
+                    state->entropy_required = 256;
+
                state->entropy_got = 0;
                state->entropy_size = (state->entropy_required *
                                       sizeof(unsigned));
@@ -1476,6 +1543,23 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
        }
     }
 
+#if !defined UNPROTECT && !defined NO_SECURITY
+    /*
+     * Protect our process.
+     */
+    {
+        char *error = NULL;
+        if (!setprocessacl(error)) {
+            char *message = dupprintf("Could not restrict process ACL: %s",
+                                      error);
+            MessageBox(NULL, message, "PuTTYgen Warning",
+                       MB_ICONWARNING | MB_OK);
+            sfree(message);
+            sfree(error);
+        }
+    }
+#endif
+
     random_ref();
     ret = DialogBox(hinst, MAKEINTRESOURCE(201), NULL, MainDlgProc) != IDOK;
 
index 6e8016408186a9fe3a9e00672bb2aa8642749d40..bffc01f99a99f28d8e13068caf2725ee75aece02 100644 (file)
@@ -301,11 +301,32 @@ void keylist_update(void)
        for (i = 0; NULL != (skey = pageant_nth_ssh2_key(i)); i++) {
            char *listentry, *p;
            int pos;
-           /*
-            * Replace spaces with tabs in the fingerprint prefix, for
-            * nice alignment in the list box, until we encounter a :
-            * meaning we're into the fingerprint proper.
-            */
+
+            /*
+             * For nice alignment in the list box, we would ideally
+             * want every entry to align to the tab stop settings, and
+             * have a column for algorithm name, one for bit count,
+             * one for hex fingerprint, and one for key comment.
+             *
+             * Unfortunately, some of the algorithm names are so long
+             * that they overflow into the bit-count field.
+             * Fortunately, at the moment, those are _precisely_ the
+             * algorithm names that don't need a bit count displayed
+             * anyway (because for NIST-style ECDSA the bit count is
+             * mentioned in the algorithm name, and for ssh-ed25519
+             * there is only one possible value anyway). So we fudge
+             * this by simply omitting the bit count field in that
+             * situation.
+             *
+             * This is fragile not only in the face of further key
+             * types that don't follow this pattern, but also in the
+             * face of font metrics changes - the Windows semantics
+             * for list box tab stops is that \t aligns to the next
+             * one you haven't already exceeded, so I have to guess
+             * when the key type will overflow past the bit-count tab
+             * stop and leave out a tab character. Urgh.
+             */
+
            p = ssh2_fingerprint(skey->alg, skey->data);
             listentry = dupprintf("%s\t%s", p, skey->comment);
             sfree(p);
@@ -317,6 +338,26 @@ void keylist_update(void)
                     break;
                 listentry[pos++] = '\t';
             }
+            if (skey->alg != &ssh_dss && skey->alg != &ssh_rsa) {
+                /*
+                 * Remove the bit-count field, which is between the
+                 * first and second \t.
+                 */
+                int outpos;
+                pos = 0;
+                while (listentry[pos] && listentry[pos] != '\t')
+                    pos++;
+                outpos = pos;
+                pos++;
+                while (listentry[pos] && listentry[pos] != '\t')
+                    pos++;
+                while (1) {
+                    if ((listentry[outpos] = listentry[pos]) == '\0')
+                        break;
+                    outpos++;
+                    pos++;
+                }
+            }
 
            SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
                               (LPARAM) listentry);
@@ -1133,6 +1174,23 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
        }
     }
 
+#if !defined UNPROTECT && !defined NO_SECURITY
+    /*
+     * Protect our process.
+     */
+    {
+        char *error = NULL;
+        if (!setprocessacl(error)) {
+            char *message = dupprintf("Could not restrict process ACL: %s",
+                                      error);
+            MessageBox(NULL, message, "Pageant Warning",
+                       MB_ICONWARNING | MB_OK);
+            sfree(message);
+            sfree(error);
+        }
+    }
+#endif
+
     /*
      * Forget any passphrase that we retained while going over
      * command line keyfiles.
index ac4dab299fe19ffb0bb5de3f268da1b904feab7f..a0458b39a878b0ad8af99d9a7294e2129b3bf787 100644 (file)
@@ -11,6 +11,7 @@
 #include "putty.h"
 #include "storage.h"
 #include "tree234.h"
+#include "winsecur.h"
 
 #define WM_AGENT_CALLBACK (WM_APP + 4)
 
@@ -497,6 +498,22 @@ int main(int argc, char **argv)
        }
     }
 
+#if !defined UNPROTECT && !defined NO_SECURITY
+    /*
+     * Protect our process.
+     */
+    {
+        char *error = NULL;
+        if (!setprocessacl(error)) {
+            char *message = dupprintf("Could not restrict process ACL: %s",
+                                      error);
+            logevent(NULL, message);
+            sfree(message);
+            sfree(error);
+        }
+    }
+#endif
+
     if (errors)
        return 1;
 
index 0776cba94bdc19f544814d26dd7e985611fcae42..60551c99c295d4dafb70fb64e385513d2deeb369 100644 (file)
@@ -102,8 +102,12 @@ RFile *open_existing_file(const char *name, uint64 *size,
     ret = snew(RFile);
     ret->h = h;
 
-    if (size)
-        size->lo=GetFileSize(h, &(size->hi));
+    if (size) {
+        DWORD lo, hi;
+        lo = GetFileSize(h, &hi);
+        size->lo = lo;
+        size->hi = hi;
+    }
 
     if (mtime || atime) {
        FILETIME actime, wrtime;
@@ -170,8 +174,12 @@ WFile *open_existing_wfile(const char *name, uint64 *size)
     ret = snew(WFile);
     ret->h = h;
 
-    if (size)
-       size->lo=GetFileSize(h, &(size->hi));
+    if (size) {
+        DWORD lo, hi;
+        lo = GetFileSize(h, &hi);
+        size->lo = lo;
+        size->hi = hi;
+    }
 
     return ret;
 }
@@ -221,7 +229,10 @@ int seek_file(WFile *f, uint64 offset, int whence)
        return -1;
     }
 
-    SetFilePointer(f->h, offset.lo, &(offset.hi), movemethod);
+    {
+        LONG lo = offset.lo, hi = offset.hi;
+        SetFilePointer(f->h, lo, &hi, movemethod);
+    }
     
     if (GetLastError() != NO_ERROR)
        return -1;
@@ -232,9 +243,11 @@ int seek_file(WFile *f, uint64 offset, int whence)
 uint64 get_file_posn(WFile *f)
 {
     uint64 ret;
+    LONG lo, hi;
 
-    ret.hi = 0L;
-    ret.lo = SetFilePointer(f->h, 0L, &(ret.hi), FILE_CURRENT);
+    lo = SetFilePointer(f->h, 0L, &hi, FILE_CURRENT);
+    ret.lo = lo;
+    ret.hi = hi;
 
     return ret;
 }
@@ -733,6 +746,25 @@ char *ssh_sftp_get_cmdline(const char *prompt, int no_fds_ok)
     return ctx->line;
 }
 
+void platform_psftp_post_option_setup(void)
+{
+#if !defined UNPROTECT && !defined NO_SECURITY
+    /*
+     * Protect our process.
+     */
+    {
+        char *error = NULL;
+        if (!setprocessacl(error)) {
+            char *message = dupprintf("Could not restrict process ACL: %s",
+                                      error);
+            logevent(NULL, message);
+            sfree(message);
+            sfree(error);
+        }
+    }
+#endif
+}
+
 /* ----------------------------------------------------------------------
  * Main program. Parse arguments etc.
  */
index 5f1c7244010ff401a4da4889c674b349884654b0..3954045cf612500bcda284e13f8fd43a214d28ea 100644 (file)
@@ -15,6 +15,7 @@
 #include "ssh.h"
 
 #include "wincapi.h"
+#include "winsecur.h"
 
 #ifdef COVERITY
 /*