]> asedeno.scripts.mit.edu Git - PuTTY.git/commitdiff
Inaugural merge from branch 'pre-0.67'.
authorSimon Tatham <anakin@pobox.com>
Mon, 29 Feb 2016 19:59:37 +0000 (19:59 +0000)
committerSimon Tatham <anakin@pobox.com>
Mon, 29 Feb 2016 19:59:37 +0000 (19:59 +0000)
This is a 'merge -s ours', making no change to master but just
recording an ancestry relationship to make further merges from the
release branch easy.

142 files changed:
.gitignore
Buildscr
LICENCE
Recipe
be_misc.c [new file with mode: 0644]
cmdgen.c
cmdline.c
conf.c
config.c
configure.ac
contrib/logparse.pl
contrib/logrewrap.pl [new file with mode: 0755]
contrib/make1305.py [new file with mode: 0755]
cproxy.c
dialog.c
dialog.h
doc/Makefile
doc/config.but
doc/index.but
doc/man-pag.but [new file with mode: 0644]
doc/man-pg.but
doc/pageant.but
doc/plink.but
doc/pubkey.but
doc/udp.but
fuzzterm.c [new file with mode: 0644]
icons/Makefile
icons/macicon.py [new file with mode: 0755]
import.c
ldisc.c
ldiscucs.c
logging.c
macosx/osxctrls.m
macosx/osxdlg.m
macosx/osxmain.m
macosx/osxwin.m
macosx/putty.icns [deleted file]
misc.c
misc.h
mkfiles.pl
mksrcarc.sh
network.h
pageant.c [new file with mode: 0644]
pageant.h [new file with mode: 0644]
portfwd.c
pproxy.c
proxy.c
proxy.h
pscp.c
psftp.c
psftp.h
putty.h
raw.c
rlogin.c
settings.c
sftp.c
sftp.h
ssh.c
ssh.h
sshaes.c
ssharcf.c
sshbcrypt.c [new file with mode: 0644]
sshblowf.c
sshblowf.h [new file with mode: 0644]
sshbn.c
sshbn.h
sshccp.c [new file with mode: 0644]
sshdes.c
sshdh.c
sshdss.c
sshecc.c [new file with mode: 0644]
sshecdsag.c [new file with mode: 0644]
sshmd5.c
sshpubk.c
sshrand.c
sshrsa.c
sshsh256.c
sshsh512.c
sshsha.c
sshshare.c
sshzlib.c
telnet.c
terminal.c
testback.c
testbn.c [new file with mode: 0644]
testdata/bignum.py
testdata/colours.txt
testdata/display.txt [new file with mode: 0644]
unix/gtkask.c [new file with mode: 0644]
unix/gtkcfg.c
unix/gtkcols.c
unix/gtkcols.h
unix/gtkcompat.h [new file with mode: 0644]
unix/gtkdlg.c
unix/gtkfont.c
unix/gtkfont.h
unix/gtkmisc.c [new file with mode: 0644]
unix/gtkmisc.h [new file with mode: 0644]
unix/gtkwin.c
unix/unix.h
unix/uxagentc.c
unix/uxcons.c
unix/uxmisc.c
unix/uxnet.c
unix/uxpgnt.c [new file with mode: 0644]
unix/uxplink.c
unix/uxproxy.c
unix/uxpterm.c
unix/uxpty.c
unix/uxputty.c
unix/uxsel.c
unix/uxser.c
unix/uxsftp.c
unix/uxshare.c
unix/uxstore.c
unix/uxucs.c
windows/pageant.mft
windows/putty.mft
windows/puttygen.mft
windows/wincons.c
windows/winctrls.c
windows/windlg.c
windows/window.c
windows/wingss.c
windows/winhandl.c
windows/winhelp.c
windows/winhsock.c
windows/winmisc.c
windows/winnet.c
windows/winnpc.c
windows/winnps.c
windows/winpgen.c
windows/winpgnt.c
windows/winplink.c
windows/winproxy.c
windows/winser.c
windows/winsftp.c
windows/winstore.c
windows/winstuff.h
windows/winucs.c
windows/winutils.c
x11fwd.c

index 3ae8d1f33db6ed3961c703bfce92fb8637eb0986..482a02f1723ecb492426eef7b9aa53563d6aae09 100644 (file)
@@ -35,6 +35,8 @@
 /puttytel
 /puttygen
 /pterm
+/fuzzterm
+/testbn
 /*.DSA
 /*.RSA
 /*.cnt
@@ -83,6 +85,7 @@
 /doc/copy.but
 /icons/*.png
 /icons/*.ico
+/icons/*.icns
 /icons/*.xpm
 /icons/*.c
 /macosx/Makefile
index c9420658aa749040bbe614e6ffb45216dee15fb5..84dda5c77962c387d65ed81dfb6fe0698102eef1 100644 (file)
--- a/Buildscr
+++ b/Buildscr
@@ -150,7 +150,7 @@ in putty do perl -i~ -pe 'y/\015//d;s/$$/\015/' LICENCE
 
 delegate windows
   # FIXME: Cygwin alternative?
-  in putty/windows do/win vcvars32 && nmake -f Makefile.vc $(Makeargs)
+  in putty/windows do/win vcvars32 && nmake -f Makefile.vc $(Makeargs) all cleantestprogs
   # Code-sign the binaries, if the local bob config provides a script
   # to do so. We assume here that the script accepts an -i option to
   # provide a 'more info' URL, and an optional -n option to provide a
diff --git a/LICENCE b/LICENCE
index e0bf1f21bafde4948fc2b07e13d151ec9e3df559..4d917ff227d0da0fb22eff7459713650afde5123 100644 (file)
--- a/LICENCE
+++ b/LICENCE
@@ -3,7 +3,7 @@ PuTTY is copyright 1997-2016 Simon Tatham.
 Portions copyright Robert de Bath, Joris van Rantwijk, Delian
 Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry,
 Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus
-Kuhn, Colin Watson, and CORE SDI S.A.
+Kuhn, Colin Watson, Christopher Staite, and CORE SDI S.A.
 
 Permission is hereby granted, free of charge, to any person
 obtaining a copy of this software and associated documentation files
diff --git a/Recipe b/Recipe
index b3dbbcb2f8eeeb72393560243b24f729c36038a9..a480e3cdc0ddecb80a462e5628c1a0889759b2b9 100644 (file)
--- a/Recipe
+++ b/Recipe
 #      show up as GPFs at the point of failure rather than appearing
 #      later on as second-level damage.
 #
+#  - XFLAGS=/DFUZZING
+#      Builds a version of PuTTY with some tweaks to make fuzz testing
+#      easier: the SSH random number generator is replaced by one that
+#      always returns the same thing.  Note that this makes SSH
+#      completely insecure -- a FUZZING build should never be used to
+#      connect to a real server.
 !end
 
 # ------------------------------------------------------------
@@ -211,7 +217,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 xkeysym
+GTKTERM  = UXTERM gtkwin gtkcfg gtkdlg gtkfont gtkcols gtkmisc xkeysym
 OSXTERM  = UXTERM osxwin osxdlg osxctrls
 
 # Non-SSH back ends (putty, puttytel, plink).
@@ -220,8 +226,8 @@ NONSSH   = telnet raw rlogin ldisc pinger
 # SSH back end (putty, plink, pscp, psftp).
 SSH      = ssh sshcrc sshdes sshmd5 sshrsa sshrand sshsha sshblowf
          + sshdh sshcrcda sshpubk sshzlib sshdss x11fwd portfwd
-         + sshaes sshsh256 sshsh512 sshbn wildcard pinger ssharcf
-         + sshgssc pgssapi sshshare
+         + sshaes sshccp sshsh256 sshsh512 sshbn wildcard pinger ssharcf
+         + sshgssc pgssapi sshshare sshecc
 WINSSH   = SSH winnoise wincapi winpgntc wingss winshare winnps winnpc
          + winhsock errsock
 UXSSH    = SSH uxnoise uxagentc uxgss uxshare
@@ -231,12 +237,16 @@ SFTP     = sftp int64 logging
 
 # Miscellaneous objects appearing in all the network utilities (not
 # Pageant or PuTTYgen).
-MISC     = timing callback misc version settings tree234 proxy conf
+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.
+IMPORT   = import sshbcrypt sshblowf
+
 # Character set library, for use in pterm.
 CHARSET  = sbcsdat slookup sbcs utf8 toucs fromucs xenc mimeenc macenc localenc
 
@@ -272,14 +282,15 @@ pscp     : [C] pscp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC
 psftp    : [C] psftp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC
          + psftp.res winnojmp LIBS
 
-pageant  : [G] winpgnt sshrsa sshpubk sshdes sshbn sshmd5 version tree234
-         + misc sshaes sshsha winsecur winpgntc sshdss sshsh256 sshsh512
-        + winutils winmisc winhelp conf pageant.res LIBS
+pageant  : [G] winpgnt pageant sshrsa sshpubk sshdes sshbn sshmd5 version
+        + tree234 misc sshaes sshsha winsecur winpgntc sshdss sshsh256
+        + sshsh512 winutils sshecc winmisc winhelp conf pageant.res LIBS
 
 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
+         + sshpubk sshaes sshsh256 sshsh512 IMPORT winutils puttygen.res
+         + tree234 notiming winhelp winnojmp conf LIBS wintime sshecc
+         + sshecdsag
 
 pterm    : [X] GTKTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore
          + uxsignal CHARSET cmdline uxpterm version time xpmpterm xpmptcfg
@@ -296,11 +307,30 @@ plink    : [U] uxplink uxcons NONSSH UXSSH U_BE_ALL logging UXMISC uxsignal
 
 puttygen : [U] cmdgen 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
+         + sshpubk sshaes sshsh256 sshsh512 IMPORT puttygen.res time tree234
+         + uxgen notiming conf sshecc sshecdsag
 
 pscp     : [U] pscp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC
 psftp    : [U] psftp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC
 
+pageant  : [X] uxpgnt uxagentc pageant sshrsa sshpubk sshdes sshbn sshmd5
+        + version tree234 misc sshaes sshsha sshdss sshsh256 sshsh512 sshecc
+        + 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
+
+fuzzterm : [UT] UXTERM CHARSET misc uxmisc uxucs fuzzterm time settings
+        + uxstore be_none
+testbn   : [UT] testbn sshbn misc conf tree234 uxmisc
+testbn   : [C] testbn sshbn misc conf tree234 winmisc LIBS
+
+# ----------------------------------------------------------------------
+# On Windows, provide a means of removing local test binaries that we
+# aren't going to actually ship. (I prefer this to not building them
+# in the first place, so that we find out about build breakage early.)
+!begin vc
+cleantestprogs:
+       -del $(BUILDDIR)testbn.exe
+!end
diff --git a/be_misc.c b/be_misc.c
new file mode 100644 (file)
index 0000000..e479aa3
--- /dev/null
+++ b/be_misc.c
@@ -0,0 +1,111 @@
+/*
+ * be_misc.c: helper functions shared between main network backends.
+ */
+
+#include <assert.h>
+#include <string.h>
+
+#define DEFINE_PLUG_METHOD_MACROS
+#include "putty.h"
+#include "network.h"
+
+void backend_socket_log(void *frontend, int type, SockAddr addr, int port,
+                        const char *error_msg, int error_code, Conf *conf,
+                        int session_started)
+{
+    char addrbuf[256], *msg;
+
+    switch (type) {
+      case 0:
+        sk_getaddr(addr, addrbuf, lenof(addrbuf));
+        if (sk_addr_needs_port(addr)) {
+            msg = dupprintf("Connecting to %s port %d", addrbuf, port);
+        } else {
+            msg = dupprintf("Connecting to %s", addrbuf);
+        }
+        break;
+      case 1:
+        sk_getaddr(addr, addrbuf, lenof(addrbuf));
+        msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg);
+        break;
+      case 2:
+        /* Proxy-related log messages have their own identifying
+         * prefix already, put on by our caller. */
+        {
+            int len, log_to_term;
+
+            /* Suffix \r\n temporarily, so we can log to the terminal. */
+            msg = dupprintf("%s\r\n", error_msg);
+            len = strlen(msg);
+            assert(len >= 2);
+
+            log_to_term = conf_get_int(conf, CONF_proxy_log_to_term);
+            if (log_to_term == AUTO)
+                log_to_term = session_started ? FORCE_OFF : FORCE_ON;
+            if (log_to_term == FORCE_ON)
+                from_backend(frontend, TRUE, msg, len);
+
+            msg[len-2] = '\0';         /* remove the \r\n again */
+        }
+        break;
+      default:
+        msg = NULL;  /* shouldn't happen, but placate optimiser */
+        break;
+    }
+
+    if (msg) {
+        logevent(frontend, msg);
+        sfree(msg);
+    }
+}
+
+void log_proxy_stderr(Plug plug, bufchain *buf, const void *vdata, int len)
+{
+    const char *data = (const char *)vdata;
+    int pos = 0;
+    int msglen;
+    char *nlpos, *msg, *fullmsg;
+
+    /*
+     * This helper function allows us to collect the data written to a
+     * local proxy command's standard error in whatever size chunks we
+     * happen to get from its pipe, and whenever we have a complete
+     * line, we pass it to plug_log.
+     *
+     * Prerequisites: a plug to log to, and a bufchain stored
+     * somewhere to collect the data in.
+     */
+
+    while (pos < len && (nlpos = memchr(data+pos, '\n', len-pos)) != NULL) {
+        /*
+         * Found a newline in the current input buffer. Append it to
+         * the bufchain (which may contain a partial line from last
+         * time).
+         */
+        bufchain_add(buf, data + pos, nlpos - (data + pos));
+
+        /*
+         * Collect the resulting line of data and pass it to plug_log.
+         */
+        msglen = bufchain_size(buf);
+        msg = snewn(msglen+1, char);
+        bufchain_fetch(buf, msg, msglen);
+        bufchain_consume(buf, msglen);
+        msg[msglen] = '\0';
+        fullmsg = dupprintf("proxy: %s", msg);
+        plug_log(plug, 2, NULL, 0, fullmsg, 0);
+        sfree(fullmsg);
+        sfree(msg);
+
+        /*
+         * Advance past the newline.
+         */
+        pos += nlpos+1 - (data + pos);
+    }
+
+    /*
+     * Now any remaining data is a partial line, which we save for
+     * next time.
+     */
+    bufchain_add(buf, data + pos, len - pos);
+}
index c15c01dd34bab2e82bd6529b78387344bbd93980..12e1ac9e19e31822e5d16f8095e982c21f37990b 100644 (file)
--- a/cmdgen.c
+++ b/cmdgen.c
@@ -91,7 +91,7 @@ static void no_progress(void *param, int action, int phase, int iprogress)
 {
 }
 
-void modalfatalbox(char *p, ...)
+void modalfatalbox(const char *p, ...)
 {
     va_list ap;
     fprintf(stderr, "FATAL ERROR: ");
@@ -102,7 +102,7 @@ void modalfatalbox(char *p, ...)
     cleanup_exit(1);
 }
 
-void nonfatal(char *p, ...)
+void nonfatal(const char *p, ...)
 {
     va_list ap;
     fprintf(stderr, "ERROR: ");
@@ -152,7 +152,8 @@ void help(void)
     showversion();
     usage(FALSE);
     fprintf(stderr,
-           "  -t    specify key type when generating (rsa, dsa, rsa1)\n"
+           "  -t    specify key type when generating (ed25519, ecdsa, rsa, "
+                                                       "dsa, rsa1)\n"
            "  -b    specify number of bits when generating key\n"
            "  -C    change or specify key comment\n"
            "  -P    change key passphrase\n"
@@ -160,8 +161,10 @@ void help(void)
            "  -O    specify output type:\n"
            "           private             output PuTTY private key format\n"
            "           private-openssh     export OpenSSH private key\n"
+           "           private-openssh-new export OpenSSH private key "
+                                             "(force new file format)\n"
            "           private-sshcom      export ssh.com private key\n"
-           "           public              standard / ssh.com public key\n"
+           "           public              RFC 4716 / ssh.com public key\n"
            "           public-openssh      OpenSSH public key\n"
            "           fingerprint         output the key fingerprint\n"
            "  -o    specify output file\n"
@@ -171,56 +174,6 @@ void help(void)
            );
 }
 
-static int save_ssh2_pubkey(char *filename, char *comment,
-                           void *v_pub_blob, int pub_len)
-{
-    unsigned char *pub_blob = (unsigned char *)v_pub_blob;
-    char *p;
-    int i, column;
-    FILE *fp;
-
-    if (filename) {
-       fp = fopen(filename, "wb");
-       if (!fp)
-           return 0;
-    } else
-       fp = stdout;
-
-    fprintf(fp, "---- BEGIN SSH2 PUBLIC KEY ----\n");
-
-    if (comment) {
-       fprintf(fp, "Comment: \"");
-       for (p = comment; *p; p++) {
-           if (*p == '\\' || *p == '\"')
-               fputc('\\', fp);
-           fputc(*p, fp);
-       }
-       fprintf(fp, "\"\n");
-    }
-
-    i = 0;
-    column = 0;
-    while (i < pub_len) {
-       char buf[5];
-       int n = (pub_len - i < 3 ? pub_len - i : 3);
-       base64_encode_atom(pub_blob + i, n, buf);
-       i += n;
-       buf[4] = '\0';
-       fputs(buf, fp);
-       if (++column >= 16) {
-           fputc('\n', fp);
-           column = 0;
-       }
-    }
-    if (column > 0)
-       fputc('\n', fp);
-    
-    fprintf(fp, "---- END SSH2 PUBLIC KEY ----\n");
-    if (filename)
-       fclose(fp);
-    return 1;
-}
-
 static int move(char *from, char *to)
 {
     int ret;
@@ -240,35 +193,15 @@ static int move(char *from, char *to)
     return TRUE;
 }
 
-static char *blobfp(char *alg, int bits, unsigned char *blob, int bloblen)
-{
-    char buffer[128];
-    unsigned char digest[16];
-    struct MD5Context md5c;
-    int i;
-
-    MD5Init(&md5c);
-    MD5Update(&md5c, blob, bloblen);
-    MD5Final(digest, &md5c);
-
-    sprintf(buffer, "%s ", alg);
-    if (bits > 0)
-       sprintf(buffer + strlen(buffer), "%d ", bits);
-    for (i = 0; i < 16; i++)
-       sprintf(buffer + strlen(buffer), "%s%02x", i ? ":" : "",
-               digest[i]);
-
-    return dupstr(buffer);
-}
-
 int main(int argc, char **argv)
 {
     char *infile = NULL;
     Filename *infilename = NULL, *outfilename = NULL;
-    enum { NOKEYGEN, RSA1, RSA2, DSA } keytype = NOKEYGEN;    
+    enum { NOKEYGEN, RSA1, RSA2, DSA, ECDSA, ED25519 } keytype = NOKEYGEN;
     char *outfile = NULL, *outfiletmp = NULL;
-    enum { PRIVATE, PUBLIC, PUBLICO, FP, OPENSSH, SSHCOM } outtype = PRIVATE;
-    int bits = 2048;
+    enum { PRIVATE, PUBLIC, PUBLICO, FP, OPENSSH_AUTO,
+           OPENSSH_NEW, SSHCOM } outtype = PRIVATE;
+    int bits = -1;
     char *comment = NULL, *origcomment = NULL;
     int change_passphrase = FALSE;
     int errs = FALSE, nogo = FALSE;
@@ -437,6 +370,10 @@ int main(int argc, char **argv)
                            keytype = RSA1, sshver = 1;
                        else if (!strcmp(p, "dsa") || !strcmp(p, "dss"))
                            keytype = DSA, sshver = 2;
+                        else if (!strcmp(p, "ecdsa"))
+                            keytype = ECDSA, sshver = 2;
+                        else if (!strcmp(p, "ed25519"))
+                            keytype = ED25519, sshver = 2;
                        else {
                            fprintf(stderr,
                                    "puttygen: unknown key type `%s'\n", p);
@@ -459,7 +396,9 @@ int main(int argc, char **argv)
                        else if (!strcmp(p, "fingerprint"))
                            outtype = FP;
                        else if (!strcmp(p, "private-openssh"))
-                           outtype = OPENSSH, sshver = 2;
+                           outtype = OPENSSH_AUTO, sshver = 2;
+                       else if (!strcmp(p, "private-openssh-new"))
+                           outtype = OPENSSH_NEW, sshver = 2;
                        else if (!strcmp(p, "private-sshcom"))
                            outtype = SSHCOM, sshver = 2;
                        else {
@@ -497,6 +436,34 @@ int main(int argc, char **argv)
        }
     }
 
+    if (bits == -1) {
+        /*
+         * No explicit key size was specified. Default varies
+         * depending on key type.
+         */
+        switch (keytype) {
+          case ECDSA:
+            bits = 384;
+            break;
+          case ED25519:
+            bits = 256;
+            break;
+          default:
+            bits = 2048;
+            break;
+        }
+    }
+
+    if (keytype == ECDSA && (bits != 256 && bits != 384 && bits != 521)) {
+        fprintf(stderr, "puttygen: invalid bits for ECDSA, choose 256, 384 or 521\n");
+        errs = TRUE;
+    }
+
+    if (keytype == ED25519 && (bits != 256)) {
+        fprintf(stderr, "puttygen: invalid bits for ED25519, choose 256\n");
+        errs = TRUE;
+    }
+
     if (errs)
        return 1;
 
@@ -529,7 +496,8 @@ int main(int argc, char **argv)
      * We must save the private part when generating a new key.
      */
     if (keytype != NOKEYGEN &&
-       (outtype != PRIVATE && outtype != OPENSSH && outtype != SSHCOM)) {
+       (outtype != PRIVATE && outtype != OPENSSH_AUTO &&
+         outtype != OPENSSH_NEW && outtype != SSHCOM)) {
        fprintf(stderr, "puttygen: this would generate a new key but "
                "discard the private part\n");
        return 1;
@@ -545,28 +513,6 @@ int main(int argc, char **argv)
        intype = key_type(infilename);
 
        switch (intype) {
-           /*
-            * It would be nice here to be able to load _public_
-            * key files, in any of a number of forms, and (a)
-            * convert them to other public key types, (b) print
-            * out their fingerprints. Or, I suppose, for real
-            * orthogonality, (c) change their comment!
-            * 
-            * In fact this opens some interesting possibilities.
-            * Suppose ssh2_userkey_loadpub() were able to load
-            * public key files as well as extracting the public
-            * key from private ones. And suppose I did the thing
-            * I've been wanting to do, where specifying a
-            * particular private key file for authentication
-            * causes any _other_ key in the agent to be discarded.
-            * Then, if you had an agent forwarded to the machine
-            * you were running Unix PuTTY or Plink on, and you
-            * needed to specify which of the keys in the agent it
-            * should use, you could do that by supplying a
-            * _public_ key file, thus not needing to trust even
-            * your encrypted private key file to the network. Ooh!
-            */
-
          case SSH_KEYTYPE_UNOPENABLE:
          case SSH_KEYTYPE_UNKNOWN:
            fprintf(stderr, "puttygen: unable to load file `%s': %s\n",
@@ -574,6 +520,7 @@ int main(int argc, char **argv)
            return 1;
 
          case SSH_KEYTYPE_SSH1:
+          case SSH_KEYTYPE_SSH1_PUBLIC:
            if (sshver == 2) {
                fprintf(stderr, "puttygen: conversion from SSH-1 to SSH-2 keys"
                        " not supported\n");
@@ -583,7 +530,10 @@ int main(int argc, char **argv)
            break;
 
          case SSH_KEYTYPE_SSH2:
-         case SSH_KEYTYPE_OPENSSH:
+          case SSH_KEYTYPE_SSH2_PUBLIC_RFC4716:
+          case SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH:
+         case SSH_KEYTYPE_OPENSSH_PEM:
+         case SSH_KEYTYPE_OPENSSH_NEW:
          case SSH_KEYTYPE_SSHCOM:
            if (sshver == 1) {
                fprintf(stderr, "puttygen: conversion from SSH-2 to SSH-1 keys"
@@ -592,6 +542,10 @@ int main(int argc, char **argv)
            }
            sshver = 2;
            break;
+
+         case SSH_KEYTYPE_OPENSSH_AUTO:
+          default:
+            assert(0 && "Should never see these types on an input file");
        }
     }
 
@@ -607,7 +561,8 @@ int main(int argc, char **argv)
      */
     if ((intype == SSH_KEYTYPE_SSH1 && outtype == PRIVATE) ||
        (intype == SSH_KEYTYPE_SSH2 && outtype == PRIVATE) ||
-       (intype == SSH_KEYTYPE_OPENSSH && outtype == OPENSSH) ||
+       (intype == SSH_KEYTYPE_OPENSSH_PEM && outtype == OPENSSH_AUTO) ||
+       (intype == SSH_KEYTYPE_OPENSSH_NEW && outtype == OPENSSH_NEW) ||
        (intype == SSH_KEYTYPE_SSHCOM && outtype == SSHCOM)) {
        if (!outfile) {
            outfile = infile;
@@ -625,7 +580,8 @@ int main(int argc, char **argv)
             * Bomb out rather than automatically choosing to write
             * a private key file to stdout.
             */
-           if (outtype==PRIVATE || outtype==OPENSSH || outtype==SSHCOM) {
+           if (outtype == PRIVATE || outtype == OPENSSH_AUTO ||
+                outtype == OPENSSH_NEW || outtype == SSHCOM) {
                fprintf(stderr, "puttygen: need to specify an output file\n");
                return 1;
            }
@@ -638,12 +594,23 @@ int main(int argc, char **argv)
      * out a private key format, or (b) the entire input key file
      * is encrypted.
      */
-    if (outtype == PRIVATE || outtype == OPENSSH || outtype == SSHCOM ||
-       intype == SSH_KEYTYPE_OPENSSH || intype == SSH_KEYTYPE_SSHCOM)
+    if (outtype == PRIVATE || outtype == OPENSSH_AUTO ||
+        outtype == OPENSSH_NEW || outtype == SSHCOM ||
+       intype == SSH_KEYTYPE_OPENSSH_PEM ||
+       intype == SSH_KEYTYPE_OPENSSH_NEW ||
+        intype == SSH_KEYTYPE_SSHCOM)
        load_encrypted = TRUE;
     else
        load_encrypted = FALSE;
 
+    if (load_encrypted && (intype == SSH_KEYTYPE_SSH1_PUBLIC ||
+                           intype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 ||
+                           intype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH)) {
+        fprintf(stderr, "puttygen: cannot perform this action on a "
+                "public-key-only input file\n");
+        return 1;
+    }
+
     /* ------------------------------------------------------------------
      * Now we're ready to actually do some stuff.
      */
@@ -663,6 +630,10 @@ int main(int argc, char **argv)
        tm = ltime();
        if (keytype == DSA)
            strftime(default_comment, 30, "dsa-key-%Y%m%d", &tm);
+        else if (keytype == ECDSA)
+            strftime(default_comment, 30, "ecdsa-key-%Y%m%d", &tm);
+        else if (keytype == ED25519)
+            strftime(default_comment, 30, "ed25519-key-%Y%m%d", &tm);
        else
            strftime(default_comment, 30, "rsa-key-%Y%m%d", &tm);
 
@@ -684,6 +655,20 @@ int main(int argc, char **argv)
            ssh2key->data = dsskey;
            ssh2key->alg = &ssh_dss;
            ssh1key = NULL;
+        } else if (keytype == ECDSA) {
+            struct ec_key *ec = snew(struct ec_key);
+            ec_generate(ec, bits, progressfn, &prog);
+            ssh2key = snew(struct ssh2_userkey);
+            ssh2key->data = ec;
+            ssh2key->alg = ec->signalg;
+            ssh1key = NULL;
+        } else if (keytype == ED25519) {
+            struct ec_key *ec = snew(struct ec_key);
+            ec_edgenerate(ec, bits, progressfn, &prog);
+            ssh2key = snew(struct ssh2_userkey);
+            ssh2key->data = ec;
+            ssh2key->alg = &ssh_ecdsa_ed25519;
+            ssh1key = NULL;
        } else {
            struct RSAKey *rsakey = snew(struct RSAKey);
            rsa_generate(rsakey, bits, progressfn, &prog);
@@ -746,6 +731,7 @@ int main(int argc, char **argv)
            int ret;
 
          case SSH_KEYTYPE_SSH1:
+          case SSH_KEYTYPE_SSH1_PUBLIC:
            ssh1key = snew(struct RSAKey);
            if (!load_encrypted) {
                void *vblob;
@@ -786,16 +772,21 @@ int main(int argc, char **argv)
            break;
 
          case SSH_KEYTYPE_SSH2:
+          case SSH_KEYTYPE_SSH2_PUBLIC_RFC4716:
+          case SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH:
            if (!load_encrypted) {
                ssh2blob = ssh2_userkey_loadpub(infilename, &ssh2alg,
-                                               &ssh2bloblen, NULL, &error);
+                                               &ssh2bloblen, &origcomment,
+                                                &error);
                 if (ssh2blob) {
                     ssh2algf = find_pubkey_alg(ssh2alg);
                     if (ssh2algf)
-                        bits = ssh2algf->pubkey_bits(ssh2blob, ssh2bloblen);
+                        bits = ssh2algf->pubkey_bits(ssh2algf,
+                                                     ssh2blob, ssh2bloblen);
                     else
                         bits = -1;
                 }
+                sfree(ssh2alg);
            } else {
                ssh2key = ssh2_load_userkey(infilename, passphrase, &error);
            }
@@ -809,7 +800,8 @@ int main(int argc, char **argv)
            }
            break;
 
-         case SSH_KEYTYPE_OPENSSH:
+         case SSH_KEYTYPE_OPENSSH_PEM:
+         case SSH_KEYTYPE_OPENSSH_NEW:
          case SSH_KEYTYPE_SSHCOM:
            ssh2key = import_ssh2(infilename, intype, passphrase, &error);
            if (ssh2key) {
@@ -898,7 +890,7 @@ int main(int argc, char **argv)
        outfilename = filename_from_str(outfile ? outfile : "");
 
     switch (outtype) {
-       int ret;
+       int ret, real_outtype;
 
       case PRIVATE:
        if (sshver == 1) {
@@ -924,80 +916,33 @@ int main(int argc, char **argv)
 
       case PUBLIC:
       case PUBLICO:
-       if (sshver == 1) {
-           FILE *fp;
-           char *dec1, *dec2;
-
-           assert(ssh1key);
-
-           if (outfile)
-               fp = f_open(outfilename, "w", FALSE);
-           else
-               fp = stdout;
-           dec1 = bignum_decimal(ssh1key->exponent);
-           dec2 = bignum_decimal(ssh1key->modulus);
-           fprintf(fp, "%d %s %s %s\n", bignum_bitcount(ssh1key->modulus),
-                   dec1, dec2, ssh1key->comment);
-           sfree(dec1);
-           sfree(dec2);
-           if (outfile)
-               fclose(fp);
-       } else if (outtype == PUBLIC) {
-           if (!ssh2blob) {
-               assert(ssh2key);
-               ssh2blob = ssh2key->alg->public_blob(ssh2key->data,
-                                                    &ssh2bloblen);
-           }
-           save_ssh2_pubkey(outfile, ssh2key ? ssh2key->comment : origcomment,
-                            ssh2blob, ssh2bloblen);
-       } else if (outtype == PUBLICO) {
-           char *buffer, *p;
-           int i;
-           FILE *fp;
+        {
+            FILE *fp;
+
+            if (outfile)
+                fp = f_open(outfilename, "w", FALSE);
+            else
+                fp = stdout;
+
+            if (sshver == 1) {
+                ssh1_write_pubkey(fp, ssh1key);
+            } else {
+                if (!ssh2blob) {
+                    assert(ssh2key);
+                    ssh2blob = ssh2key->alg->public_blob(ssh2key->data,
+                                                         &ssh2bloblen);
+                }
 
-           if (!ssh2blob) {
-               assert(ssh2key);
-               ssh2blob = ssh2key->alg->public_blob(ssh2key->data,
-                                                    &ssh2bloblen);
-           }
-           if (!ssh2alg) {
-               assert(ssh2key);
-               ssh2alg = ssh2key->alg->name;
-           }
-           if (ssh2key)
-               comment = ssh2key->comment;
-           else
-               comment = origcomment;
-
-           buffer = snewn(strlen(ssh2alg) +
-                          4 * ((ssh2bloblen+2) / 3) +
-                          strlen(comment) + 3, char);
-           strcpy(buffer, ssh2alg);
-           p = buffer + strlen(buffer);
-           *p++ = ' ';
-           i = 0;
-           while (i < ssh2bloblen) {
-               int n = (ssh2bloblen - i < 3 ? ssh2bloblen - i : 3);
-               base64_encode_atom(ssh2blob + i, n, p);
-               i += n;
-               p += 4;
-           }
-           if (*comment) {
-               *p++ = ' ';
-               strcpy(p, comment);
-           } else
-               *p++ = '\0';
+                ssh2_write_pubkey(fp, ssh2key ? ssh2key->comment : origcomment,
+                                  ssh2blob, ssh2bloblen,
+                                  (outtype == PUBLIC ?
+                                   SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 :
+                                   SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH));
+            }
 
-           if (outfile)
-               fp = f_open(outfilename, "w", FALSE);
-           else
-               fp = stdout;
-           fprintf(fp, "%s\n", buffer);
            if (outfile)
                fclose(fp);
-
-           sfree(buffer);
-       }
+        }
        break;
 
       case FP:
@@ -1011,10 +956,11 @@ int main(int argc, char **argv)
                rsa_fingerprint(fingerprint, 128, ssh1key);
            } else {
                if (ssh2key) {
-                   fingerprint = ssh2key->alg->fingerprint(ssh2key->data);
+                   fingerprint = ssh2_fingerprint(ssh2key->alg,
+                                                   ssh2key->data);
                } else {
                    assert(ssh2blob);
-                   fingerprint = blobfp(ssh2alg, bits, ssh2blob, ssh2bloblen);
+                   fingerprint = ssh2_fingerprint_blob(ssh2blob, ssh2bloblen);
                }
            }
 
@@ -1030,13 +976,27 @@ int main(int argc, char **argv)
        }
        break;
        
-      case OPENSSH:
+      case OPENSSH_AUTO:
+      case OPENSSH_NEW:
       case SSHCOM:
        assert(sshver == 2);
        assert(ssh2key);
        random_ref(); /* both foreign key types require randomness,
                        * for IV or padding */
-       ret = export_ssh2(outfilename, outtype, ssh2key, passphrase);
+        switch (outtype) {
+          case OPENSSH_AUTO:
+            real_outtype = SSH_KEYTYPE_OPENSSH_AUTO;
+            break;
+          case OPENSSH_NEW:
+            real_outtype = SSH_KEYTYPE_OPENSSH_NEW;
+            break;
+          case SSHCOM:
+            real_outtype = SSH_KEYTYPE_SSHCOM;
+            break;
+          default:
+            assert(0 && "control flow goof");
+        }
+       ret = export_ssh2(outfilename, real_outtype, ssh2key, passphrase);
        if (!ret) {
            fprintf(stderr, "puttygen: unable to export key\n");
            return 1;
index 9f3360b2bea67e009cafbdd3f70842a1ac1b554d..92c87b3b797b61da4cd5a171699983377d10cc88 100644 (file)
--- a/cmdline.c
+++ b/cmdline.c
@@ -44,15 +44,15 @@ struct cmdline_saved_param_set {
  */
 static struct cmdline_saved_param_set saves[NPRIORITIES];
 
-static void cmdline_save_param(char *p, char *value, int pri)
+static void cmdline_save_param(const char *p, const char *value, int pri)
 {
     if (saves[pri].nsaved >= saves[pri].savesize) {
        saves[pri].savesize = saves[pri].nsaved + 32;
        saves[pri].params = sresize(saves[pri].params, saves[pri].savesize,
                                    struct cmdline_saved_param);
     }
-    saves[pri].params[saves[pri].nsaved].p = p;
-    saves[pri].params[saves[pri].nsaved].value = value;
+    saves[pri].params[saves[pri].nsaved].p = dupstr(p);
+    saves[pri].params[saves[pri].nsaved].value = dupstr(value);
     saves[pri].nsaved++;
 }
 
@@ -85,8 +85,8 @@ void cmdline_cleanup(void)
  * return means that we aren't capable of processing the prompt and
  * someone else should do it.
  */
-int cmdline_get_passwd_input(prompts_t *p, unsigned char *in, int inlen) {
-
+int cmdline_get_passwd_input(prompts_t *p, const unsigned char *in, int inlen)
+{
     static int tried_once = 0;
 
     /*
@@ -125,7 +125,7 @@ int cmdline_get_passwd_input(prompts_t *p, unsigned char *in, int inlen) {
  */
 int cmdline_tooltype = 0;
 
-static int cmdline_check_unavailable(int flag, char *p)
+static int cmdline_check_unavailable(int flag, const char *p)
 {
     if (cmdline_tooltype & flag) {
        cmdline_error("option \"%s\" not available in this tool", p);
@@ -159,7 +159,8 @@ static int cmdline_check_unavailable(int flag, char *p)
     if (need_save < 0) return x; \
 } while (0)
 
-int cmdline_process_param(char *p, char *value, int need_save, Conf *conf)
+int cmdline_process_param(const char *p, char *value,
+                          int need_save, Conf *conf)
 {
     int ret = 0;
 
@@ -328,7 +329,8 @@ int cmdline_process_param(char *p, char *value, int need_save, Conf *conf)
         sfree(host);
     }
     if (!strcmp(p, "-m")) {
-       char *filename, *command;
+       const char *filename;
+        char *command;
        int cmdlen, cmdsize;
        FILE *fp;
        int c, d;
@@ -605,8 +607,13 @@ int cmdline_process_param(char *p, char *value, int need_save, Conf *conf)
 void cmdline_run_saved(Conf *conf)
 {
     int pri, i;
-    for (pri = 0; pri < NPRIORITIES; pri++)
-       for (i = 0; i < saves[pri].nsaved; i++)
+    for (pri = 0; pri < NPRIORITIES; pri++) {
+       for (i = 0; i < saves[pri].nsaved; i++) {
            cmdline_process_param(saves[pri].params[i].p,
                                  saves[pri].params[i].value, 0, conf);
+            sfree(saves[pri].params[i].p);
+            sfree(saves[pri].params[i].value);
+        }
+        saves[pri].nsaved = 0;
+    }
 }
diff --git a/conf.c b/conf.c
index e80f5853ad9437d6069a4205eacef2cb04f510fa..92341758abf89f5de29b7794b03b0a2230c1e968 100644 (file)
--- a/conf.c
+++ b/conf.c
@@ -39,6 +39,16 @@ struct key {
     } secondary;
 };
 
+/* Variant form of struct key which doesn't contain dynamic data, used
+ * for lookups. */
+struct constkey {
+    int primary;
+    union {
+       int i;
+       const char *s;
+    } secondary;
+};
+
 struct value {
     union {
        int intval;
@@ -88,6 +98,29 @@ static int conf_cmp(void *av, void *bv)
     }
 }
 
+static int conf_cmp_constkey(void *av, void *bv)
+{
+    struct key *a = (struct key *)av;
+    struct constkey *b = (struct constkey *)bv;
+
+    if (a->primary < b->primary)
+       return -1;
+    else if (a->primary > b->primary)
+       return +1;
+    switch (subkeytypes[a->primary]) {
+      case TYPE_INT:
+       if (a->secondary.i < b->secondary.i)
+           return -1;
+       else if (a->secondary.i > b->secondary.i)
+           return +1;
+       return 0;
+      case TYPE_STR:
+       return strcmp(a->secondary.s, b->secondary.s);
+      default:
+       return 0;
+    }
+}
+
 /*
  * Free any dynamic data items pointed to by a 'struct key'. We
  * don't free the structure itself, since it's probably part of a
@@ -286,7 +319,7 @@ char *conf_get_str_str(Conf *conf, int primary, const char *secondary)
 char *conf_get_str_strs(Conf *conf, int primary,
                       char *subkeyin, char **subkeyout)
 {
-    struct key key;
+    struct constkey key;
     struct conf_entry *entry;
 
     assert(subkeytypes[primary] == TYPE_STR);
@@ -297,7 +330,7 @@ char *conf_get_str_strs(Conf *conf, int primary,
        entry = findrel234(conf->tree, &key, NULL, REL234_GT);
     } else {
        key.secondary.s = "";
-       entry = findrel234(conf->tree, &key, NULL, REL234_GE);
+       entry = findrel234(conf->tree, &key, conf_cmp_constkey, REL234_GE);
     }
     if (!entry || entry->key.primary != primary)
        return NULL;
@@ -307,7 +340,7 @@ char *conf_get_str_strs(Conf *conf, int primary,
 
 char *conf_get_str_nthstrkey(Conf *conf, int primary, int n)
 {
-    struct key key;
+    struct constkey key;
     struct conf_entry *entry;
     int index;
 
@@ -315,7 +348,8 @@ char *conf_get_str_nthstrkey(Conf *conf, int primary, int n)
     assert(valuetypes[primary] == TYPE_STR);
     key.primary = primary;
     key.secondary.s = "";
-    entry = findrelpos234(conf->tree, &key, NULL, REL234_GE, &index);
+    entry = findrelpos234(conf->tree, &key, conf_cmp_constkey,
+                          REL234_GE, &index);
     if (!entry || entry->key.primary != primary)
        return NULL;
     entry = index234(conf->tree, index + n);
index 086956fc914bc3218fe7d5c88dfd73a1291196a5..53ad2f910e24a340a2e325a814a0296938be39c7 100644 (file)
--- a/config.c
+++ b/config.c
@@ -356,7 +356,8 @@ static void cipherlist_handler(union control *ctrl, void *dlg,
     if (event == EVENT_REFRESH) {
        int i;
 
-       static const struct { char *s; int c; } ciphers[] = {
+       static const struct { const char *s; int c; } ciphers[] = {
+            { "ChaCha20 (SSH-2 only)",  CIPHER_CHACHA20 },
            { "3DES",                   CIPHER_3DES },
            { "Blowfish",               CIPHER_BLOWFISH },
            { "DES",                    CIPHER_DES },
@@ -372,7 +373,7 @@ static void cipherlist_handler(union control *ctrl, void *dlg,
        for (i = 0; i < CIPHER_MAX; i++) {
            int c = conf_get_int_int(conf, CONF_ssh_cipherlist, i);
            int j;
-           char *cstr = NULL;
+           const char *cstr = NULL;
            for (j = 0; j < (sizeof ciphers) / (sizeof ciphers[0]); j++) {
                if (ciphers[j].c == c) {
                    cstr = ciphers[j].s;
@@ -428,11 +429,12 @@ static void kexlist_handler(union control *ctrl, void *dlg,
     if (event == EVENT_REFRESH) {
        int i;
 
-       static const struct { char *s; int k; } kexes[] = {
+       static const struct { const char *s; int k; } kexes[] = {
            { "Diffie-Hellman group 1",         KEX_DHGROUP1 },
            { "Diffie-Hellman group 14",        KEX_DHGROUP14 },
            { "Diffie-Hellman group exchange",  KEX_DHGEX },
            { "RSA-based key exchange",         KEX_RSA },
+            { "ECDH key exchange",              KEX_ECDH },
            { "-- warn below here --",          KEX_WARN }
        };
 
@@ -443,7 +445,7 @@ static void kexlist_handler(union control *ctrl, void *dlg,
        for (i = 0; i < KEX_MAX; i++) {
            int k = conf_get_int_int(conf, CONF_ssh_kexlist, i);
            int j;
-           char *kstr = NULL;
+           const char *kstr = NULL;
            for (j = 0; j < (sizeof kexes) / (sizeof kexes[0]); j++) {
                if (kexes[j].k == k) {
                    kstr = kexes[j].s;
@@ -471,7 +473,7 @@ static void printerbox_handler(union control *ctrl, void *dlg,
     if (event == EVENT_REFRESH) {
        int nprinters, i;
        printer_enum *pe;
-       char *printer;
+       const char *printer;
 
        dlg_update_start(ctrl, dlg);
        /*
@@ -1118,7 +1120,8 @@ static void portfwd_handler(union control *ctrl, void *dlg,
        }
     } else if (event == EVENT_ACTION) {
        if (ctrl == pfd->addbutton) {
-           char *family, *type, *src, *key, *val;
+           const char *family, *type;
+            char *src, *key, *val;
            int whichbutton;
 
 #ifndef NO_IPV6
@@ -1177,7 +1180,8 @@ static void portfwd_handler(union control *ctrl, void *dlg,
            if (i < 0) {
                dlg_beep(dlg);
            } else {
-               char *key, *val, *p;
+               char *key, *p;
+                const char *val;
 
                key = conf_get_str_nthstrkey(conf, CONF_portfwd, i);
                if (key) {
@@ -1449,7 +1453,7 @@ void setup_config_box(struct controlbox *b, int midsession,
      * logging can sensibly be available.
      */
     {
-       char *sshlogname, *sshrawlogname;
+       const char *sshlogname, *sshrawlogname;
        if ((midsession && protocol == PROT_SSH) ||
            (!midsession && backend_from_proto(PROT_SSH))) {
            sshlogname = "SSH packets";
@@ -1926,7 +1930,7 @@ void setup_config_box(struct controlbox *b, int midsession,
 #endif
 
            {
-               char *label = backend_from_proto(PROT_SSH) ?
+               const char *label = backend_from_proto(PROT_SSH) ?
                    "Logical name of remote host (e.g. for SSH key lookup):" :
                    "Logical name of remote host:";
                s = ctrl_getset(b, "Connection", "identity",
@@ -2068,6 +2072,15 @@ void setup_config_box(struct controlbox *b, int midsession,
                     HELPCTX(proxy_command),
                     conf_editbox_handler,
                     I(CONF_proxy_telnet_command), I(1));
+
+       ctrl_radiobuttons(s, "Print proxy diagnostics "
+                          "in the terminal window", 'r', 5,
+                         HELPCTX(proxy_main),
+                         conf_radiobutton_handler,
+                         I(CONF_proxy_log_to_term),
+                         "No", I(FORCE_OFF),
+                         "Yes", I(FORCE_ON),
+                         "Only until session starts", I(AUTO), NULL);
     }
 
     /*
index 5818e032a1846fd3139d91f52e17cda0f24410d4..82fae26c40d02b6fa644d29c69bc382ac73ae690 100644 (file)
@@ -48,20 +48,26 @@ AC_ARG_WITH([gssapi],
   [],
   [with_gssapi=yes])
 
+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.])],
+  [])
+
 WITH_GSSAPI=
 AS_IF([test "x$with_gssapi" != xno],
   [AC_DEFINE([WITH_GSSAPI], [1], [Define if building with GSSAPI support.])])
 
 AC_ARG_WITH([gtk],
   [AS_HELP_STRING([--with-gtk=VER],
-                  [specify GTK version to use (`1' or `2')])
+                  [specify GTK version to use (`1', `2' or `3')])
 AS_HELP_STRING([--without-gtk],
                   [do not use GTK (build command-line tools only)])],
   [gtk_version_desired="$withval"],
   [gtk_version_desired="any"])
 
 case "$gtk_version_desired" in
-  1 | 2 | any | no) ;;
+  1 | 2 | 3 | any | no) ;;
   yes) gtk_version_desired="any" ;;
   *) AC_ERROR([Invalid GTK version specified])
 esac
@@ -70,11 +76,19 @@ AC_CHECK_HEADERS([utmpx.h sys/select.h],,,[
 #include <sys/types.h>
 #include <utmp.h>])
 
-# Look for both GTK 2 and GTK 1, in descending order of preference. If
-# we can't find either, have the makefile only build the CLI programs.
+# Look for GTK 3, GTK 2 and GTK 1, in descending order of preference.
+# If we can't find any, have the makefile only build the CLI programs.
 
 gtk=none
 
+case "$gtk_version_desired:$gtk" in
+  3:none | any:none)
+    ifdef([AM_PATH_GTK_3_0],[
+    AM_PATH_GTK_3_0([3.0.0], [gtk=3], [])
+    ],[AC_WARNING([generating configure script without GTK 3 autodetection])])
+    ;;
+esac
+
 case "$gtk_version_desired:$gtk" in
   2:none | any:none)
     ifdef([AM_PATH_GTK_2_0],[
@@ -101,7 +115,7 @@ esac
 
 AM_CONDITIONAL(HAVE_GTK, [test "$gtk" != "none"])
 
-if test "$gtk" = "2"; then
+if test "$gtk" = "2" -o "$gtk" = "3"; then
   ac_save_CFLAGS="$CFLAGS"
   ac_save_LIBS="$LIBS"
   CFLAGS="$CFLAGS $GTK_CFLAGS"
@@ -174,7 +188,7 @@ psftp will be built.
 EOF
 elif test "$gtk" = "none"; then cat <<EOF
 
-'configure' was unable to find either the GTK 1 or GTK 2 libraries on
+'configure' was unable to find any version of the GTK libraries on
 your system. Therefore, PuTTY itself and the other GUI utilities will
 not be built by the generated Makefile: only the command-line tools
 such as puttygen, plink and psftp will be built.
index 3445baa8a217b4e48706a0b8d1c04685871d8679..4805c0f4bdf994300be67053f8fe036864585204 100755 (executable)
@@ -115,6 +115,16 @@ my %packets = (
         my ($direction, $seq, $data) = @_;
         print "\n";
     },
+#define SSH2_MSG_KEX_ECDH_INIT                    30    /* 0x1e */
+    'SSH2_MSG_KEX_ECDH_INIT' => sub {
+        my ($direction, $seq, $data) = @_;
+        print "\n";
+    },
+#define SSH2_MSG_KEX_ECDH_REPLY                   31    /* 0x1f */
+    'SSH2_MSG_KEX_ECDH_REPLY' => sub {
+        my ($direction, $seq, $data) = @_;
+        print "\n";
+    },
 #define SSH2_MSG_USERAUTH_REQUEST                 50   /* 0x32 */
     'SSH2_MSG_USERAUTH_REQUEST' => sub {
         my ($direction, $seq, $data) = @_;
diff --git a/contrib/logrewrap.pl b/contrib/logrewrap.pl
new file mode 100755 (executable)
index 0000000..ca2716f
--- /dev/null
@@ -0,0 +1,44 @@
+#!/usr/bin/perl
+
+# Process a PuTTY SSH packet log that has gone through inappropriate
+# line wrapping, and try to make it legible again.
+#
+# Motivation: people often include PuTTY packet logs in email
+# messages, and if they're not careful, the sending MUA 'helpfully'
+# wraps the lines at 72 characters, corrupting all the hex dumps into
+# total unreadability.
+#
+# But as long as it's only the ASCII part of the dump at the end of
+# the line that gets wrapped, and the hex part is untouched, this is a
+# mechanically recoverable kind of corruption, because the ASCII is
+# redundant and can be reconstructed from the hex. Better still, you
+# can spot lines in which this has happened (because the ASCII at the
+# end of the line is a truncated version of what we think it should
+# say), and use that as a cue to remove the following line.
+
+use strict;
+use warnings;
+
+while (<>) {
+    if (/^  ([0-9a-f]{8})  ((?:[0-9a-f]{2} ){0,15}(?:[0-9a-f]{2}))/) {
+        my $addr = $1;
+        my $hex = $2;
+        my $ascii = "";
+        for (my $i = 0; $i < length($2); $i += 3) {
+            my $byte = hex(substr($hex, $i, 2));
+            my $char = ($byte >= 32 && $byte < 127 ? chr($byte) : ".");
+            $ascii .= $char;
+        }
+        $hex = substr($hex . (" " x 48), 0, 47);
+        my $old_line = $_;
+        chomp($old_line);
+        my $new_line = "  $addr  $hex  $ascii";
+        if ($old_line ne $new_line and
+            $old_line eq substr($new_line, 0, length($old_line))) {
+            print "$new_line\n";
+            <>; # eat the subsequent wrapped line
+            next;
+        }
+    }
+    print $_;
+}
diff --git a/contrib/make1305.py b/contrib/make1305.py
new file mode 100755 (executable)
index 0000000..c859704
--- /dev/null
@@ -0,0 +1,368 @@
+#!/usr/bin/env python
+
+import sys
+import string
+from collections import namedtuple
+
+class Multiprecision(object):
+    def __init__(self, target, minval, maxval, words):
+        self.target = target
+        self.minval = minval
+        self.maxval = maxval
+        self.words = words
+        assert 0 <= self.minval
+        assert self.minval <= self.maxval
+        assert self.target.nwords(self.maxval) == len(words)
+
+    def getword(self, n):
+        return self.words[n] if n < len(self.words) else "0"
+
+    def __add__(self, rhs):
+        newmin = self.minval + rhs.minval
+        newmax = self.maxval + rhs.maxval
+        nwords = self.target.nwords(newmax)
+        words = []
+
+        addfn = self.target.add
+        for i in range(nwords):
+            words.append(addfn(self.getword(i), rhs.getword(i)))
+            addfn = self.target.adc
+
+        return Multiprecision(self.target, newmin, newmax, words)
+
+    def __mul__(self, rhs):
+        newmin = self.minval * rhs.minval
+        newmax = self.maxval * rhs.maxval
+        nwords = self.target.nwords(newmax)
+        words = []
+
+        # There are basically two strategies we could take for
+        # multiplying two multiprecision integers. One is to enumerate
+        # the space of pairs of word indices in lexicographic order,
+        # essentially computing a*b[i] for each i and adding them
+        # together; the other is to enumerate in diagonal order,
+        # computing everything together that belongs at a particular
+        # output word index.
+        #
+        # For the moment, I've gone for the former.
+
+        sprev = []
+        for i, sword in enumerate(self.words):
+            rprev = None
+            sthis = sprev[:i]
+            for j, rword in enumerate(rhs.words):
+                prevwords = []
+                if i+j < len(sprev):
+                    prevwords.append(sprev[i+j])
+                if rprev is not None:
+                    prevwords.append(rprev)
+                vhi, vlo = self.target.muladd(sword, rword, *prevwords)
+                sthis.append(vlo)
+                rprev = vhi
+            sthis.append(rprev)
+            sprev = sthis
+
+        # Remove unneeded words from the top of the output, if we can
+        # prove by range analysis that they'll always be zero.
+        sprev = sprev[:self.target.nwords(newmax)]
+
+        return Multiprecision(self.target, newmin, newmax, sprev)
+
+    def extract_bits(self, start, bits=None):
+        if bits is None:
+            bits = (self.maxval >> start).bit_length()
+
+        # Overly thorough range analysis: if min and max have the same
+        # *quotient* by 2^bits, then the result of reducing anything
+        # in the range [min,max] mod 2^bits has to fall within the
+        # obvious range. But if they have different quotients, then
+        # you can wrap round the modulus and so any value mod 2^bits
+        # is possible.
+        newmin = self.minval >> start
+        newmax = self.maxval >> start
+        if (newmin >> bits) != (newmax >> bits):
+            newmin = 0
+            newmax = (1 << bits) - 1
+
+        nwords = self.target.nwords(newmax)
+        words = []
+        for i in range(nwords):
+            srcpos = i * self.target.bits + start
+            maxbits = min(self.target.bits, start + bits - srcpos)
+            wordindex = srcpos / self.target.bits
+            if srcpos % self.target.bits == 0:
+                word = self.getword(srcpos / self.target.bits)
+            elif (wordindex+1 >= len(self.words) or
+                  srcpos % self.target.bits + maxbits < self.target.bits):
+                word = self.target.new_value(
+                    "(%%s) >> %d" % (srcpos % self.target.bits),
+                    self.getword(srcpos / self.target.bits))
+            else:
+                word = self.target.new_value(
+                    "((%%s) >> %d) | ((%%s) << %d)" % (
+                        srcpos % self.target.bits,
+                        self.target.bits - (srcpos % self.target.bits)),
+                    self.getword(srcpos / self.target.bits),
+                    self.getword(srcpos / self.target.bits + 1))
+            if maxbits < self.target.bits and maxbits < bits:
+                word = self.target.new_value(
+                    "(%%s) & ((((BignumInt)1) << %d)-1)" % maxbits,
+                    word)
+            words.append(word)
+
+        return Multiprecision(self.target, newmin, newmax, words)
+
+# Each Statement has a list of variables it reads, and a list of ones
+# it writes. 'forms' is a list of multiple actual C statements it
+# could be generated as, depending on which of its output variables is
+# actually used (e.g. no point calling BignumADC if the generated
+# carry in a particular case is unused, or BignumMUL if nobody needs
+# the top half). It is indexed by a bitmap whose bits correspond to
+# the entries in wvars, with wvars[0] the MSB and wvars[-1] the LSB.
+Statement = namedtuple("Statement", "rvars wvars forms")
+
+class CodegenTarget(object):
+    def __init__(self, bits):
+        self.bits = bits
+        self.valindex = 0
+        self.stmts = []
+        self.generators = {}
+        self.bv_words = (130 + self.bits - 1) / self.bits
+        self.carry_index = 0
+
+    def nwords(self, maxval):
+        return (maxval.bit_length() + self.bits - 1) / self.bits
+
+    def stmt(self, stmt, needed=False):
+        index = len(self.stmts)
+        self.stmts.append([needed, stmt])
+        for val in stmt.wvars:
+            self.generators[val] = index
+
+    def new_value(self, formatstr=None, *deps):
+        name = "v%d" % self.valindex
+        self.valindex += 1
+        if formatstr is not None:
+            self.stmt(Statement(
+                    rvars=deps, wvars=[name],
+                    forms=[None, name + " = " + formatstr % deps]))
+        return name
+
+    def bigval_input(self, name, bits):
+        words = (bits + self.bits - 1) / self.bits
+        # Expect not to require an entire extra word
+        assert words == self.bv_words
+
+        return Multiprecision(self, 0, (1<<bits)-1, [
+                self.new_value("%s->w[%d]" % (name, i)) for i in range(words)])
+
+    def const(self, value):
+        # We only support constants small enough to both fit in a
+        # BignumInt (of any size supported) _and_ be expressible in C
+        # with no weird integer literal syntax like a trailing LL.
+        #
+        # Supporting larger constants would be possible - you could
+        # break 'value' up into word-sized pieces on the Python side,
+        # and generate a legal C expression for each piece by
+        # splitting it further into pieces within the
+        # standards-guaranteed 'unsigned long' limit of 32 bits and
+        # then casting those to BignumInt before combining them with
+        # shifts. But it would be a lot of effort, and since the
+        # application for this code doesn't even need it, there's no
+        # point in bothering.
+        assert value < 2**16
+        return Multiprecision(self, value, value, ["%d" % value])
+
+    def current_carry(self):
+        return "carry%d" % self.carry_index
+
+    def add(self, a1, a2):
+        ret = self.new_value()
+        adcform = "BignumADC(%s, carry, %s, %s, 0)" % (ret, a1, a2)
+        plainform = "%s = %s + %s" % (ret, a1, a2)
+        self.carry_index += 1
+        carryout = self.current_carry()
+        self.stmt(Statement(
+                rvars=[a1,a2], wvars=[ret,carryout],
+                forms=[None, adcform, plainform, adcform]))
+        return ret
+
+    def adc(self, a1, a2):
+        ret = self.new_value()
+        adcform = "BignumADC(%s, carry, %s, %s, carry)" % (ret, a1, a2)
+        plainform = "%s = %s + %s + carry" % (ret, a1, a2)
+        carryin = self.current_carry()
+        self.carry_index += 1
+        carryout = self.current_carry()
+        self.stmt(Statement(
+                rvars=[a1,a2,carryin], wvars=[ret,carryout],
+                forms=[None, adcform, plainform, adcform]))
+        return ret
+
+    def muladd(self, m1, m2, *addends):
+        rlo = self.new_value()
+        rhi = self.new_value()
+        wideform = "BignumMUL%s(%s)" % (
+            { 0:"", 1:"ADD", 2:"ADD2" }[len(addends)],
+            ", ".join([rhi, rlo, m1, m2] + list(addends)))
+        narrowform = " + ".join(["%s = %s * %s" % (rlo, m1, m2)] +
+                                list(addends))
+        self.stmt(Statement(
+                rvars=[m1,m2]+list(addends), wvars=[rhi,rlo],
+                forms=[None, narrowform, wideform, wideform]))
+        return rhi, rlo
+
+    def write_bigval(self, name, val):
+        for i in range(self.bv_words):
+            word = val.getword(i)
+            self.stmt(Statement(
+                    rvars=[word], wvars=[],
+                    forms=["%s->w[%d] = %s" % (name, i, word)]),
+                      needed=True)
+
+    def compute_needed(self):
+        used_vars = set()
+
+        self.queue = [stmt for (needed,stmt) in self.stmts if needed]
+        while len(self.queue) > 0:
+            stmt = self.queue.pop(0)
+            deps = []
+            for var in stmt.rvars:
+                if var[0] in string.digits:
+                    continue # constant
+                deps.append(self.generators[var])
+                used_vars.add(var)
+            for index in deps:
+                if not self.stmts[index][0]:
+                    self.stmts[index][0] = True
+                    self.queue.append(self.stmts[index][1])
+
+        forms = []
+        for i, (needed, stmt) in enumerate(self.stmts):
+            if needed:
+                formindex = 0
+                for (j, var) in enumerate(stmt.wvars):
+                    formindex *= 2
+                    if var in used_vars:
+                        formindex += 1
+                forms.append(stmt.forms[formindex])
+
+                # Now we must check whether this form of the statement
+                # also writes some variables we _don't_ actually need
+                # (e.g. if you only wanted the top half from a mul, or
+                # only the carry from an adc, you'd be forced to
+                # generate the other output too). Easiest way to do
+                # this is to look for an identical statement form
+                # later in the array.
+                maxindex = max(i for i in range(len(stmt.forms))
+                               if stmt.forms[i] == stmt.forms[formindex])
+                extra_vars = maxindex & ~formindex
+                bitpos = 0
+                while extra_vars != 0:
+                    if extra_vars & (1 << bitpos):
+                        extra_vars &= ~(1 << bitpos)
+                        var = stmt.wvars[-1-bitpos]
+                        used_vars.add(var)
+                        # Also, write out a cast-to-void for each
+                        # subsequently unused value, to prevent gcc
+                        # warnings when the output code is compiled.
+                        forms.append("(void)" + var)
+                    bitpos += 1
+
+        used_carry = any(v.startswith("carry") for v in used_vars)
+        used_vars = [v for v in used_vars if v.startswith("v")]
+        used_vars.sort(key=lambda v: int(v[1:]))
+
+        return used_carry, used_vars, forms
+
+    def text(self):
+        used_carry, values, forms = self.compute_needed()
+
+        ret = ""
+        while len(values) > 0:
+            prefix, sep, suffix = "    BignumInt ", ", ", ";"
+            currline = values.pop(0)
+            while (len(values) > 0 and
+                   len(prefix+currline+sep+values[0]+suffix) < 79):
+                currline += sep + values.pop(0)
+            ret += prefix + currline + suffix + "\n"
+        if used_carry:
+            ret += "    BignumCarry carry;\n"
+        if ret != "":
+            ret += "\n"
+        for stmtform in forms:
+            ret += "    %s;\n" % stmtform
+        return ret
+
+def gen_add(target):
+    # This is an addition _without_ reduction mod p, so that it can be
+    # used both during accumulation of the polynomial and for adding
+    # on the encrypted nonce at the end (which is mod 2^128, not mod
+    # p).
+    #
+    # Because one of the inputs will have come from our
+    # not-completely-reducing multiplication function, we expect up to
+    # 3 extra bits of input.
+
+    a = target.bigval_input("a", 133)
+    b = target.bigval_input("b", 133)
+    ret = a + b
+    target.write_bigval("r", ret)
+    return """\
+static void bigval_add(bigval *r, const bigval *a, const bigval *b)
+{
+%s}
+\n""" % target.text()
+
+def gen_mul(target):
+    # The inputs are not 100% reduced mod p. Specifically, we can get
+    # a full 130-bit number from the pow5==0 pass, and then a 130-bit
+    # number times 5 from the pow5==1 pass, plus a possible carry. The
+    # total of that can be easily bounded above by 2^130 * 8, so we
+    # need to assume we're multiplying two 133-bit numbers.
+
+    a = target.bigval_input("a", 133)
+    b = target.bigval_input("b", 133)
+    ab = a * b
+    ab0 = ab.extract_bits(0, 130)
+    ab1 = ab.extract_bits(130, 130)
+    ab2 = ab.extract_bits(260)
+    ab1_5 = target.const(5) * ab1
+    ab2_25 = target.const(25) * ab2
+    ret = ab0 + ab1_5 + ab2_25
+    target.write_bigval("r", ret)
+    return """\
+static void bigval_mul_mod_p(bigval *r, const bigval *a, const bigval *b)
+{
+%s}
+\n""" % target.text()
+
+def gen_final_reduce(target):
+    # We take our input number n, and compute k = n + 5*(n >> 130).
+    # Then k >> 130 is precisely the multiple of p that needs to be
+    # subtracted from n to reduce it to strictly less than p.
+
+    a = target.bigval_input("n", 133)
+    a1 = a.extract_bits(130, 130)
+    k = a + target.const(5) * a1
+    q = k.extract_bits(130)
+    adjusted = a + target.const(5) * q
+    ret = adjusted.extract_bits(0, 130)
+    target.write_bigval("n", ret)
+    return """\
+static void bigval_final_reduce(bigval *n)
+{
+%s}
+\n""" % target.text()
+
+pp_keyword = "#if"
+for bits in [16, 32, 64]:
+    sys.stdout.write("%s BIGNUM_INT_BITS == %d\n\n" % (pp_keyword, bits))
+    pp_keyword = "#elif"
+    sys.stdout.write(gen_add(CodegenTarget(bits)))
+    sys.stdout.write(gen_mul(CodegenTarget(bits)))
+    sys.stdout.write(gen_final_reduce(CodegenTarget(bits)))
+sys.stdout.write("""#else
+#error Add another bit count to contrib/make1305.py and rerun it
+#endif
+""")
index 8c8a50cb53628ba9802beb97bf504d1f2c59caae..4f9a3ffa591c8b00833d0f6b24c69f83ca64ec06 100644 (file)
--- a/cproxy.c
+++ b/cproxy.c
@@ -21,7 +21,7 @@ static void hmacmd5_chap(const unsigned char *challenge, int challen,
     void *hmacmd5_ctx;
     int pwlen;
 
-    hmacmd5_ctx = hmacmd5_make_context();
+    hmacmd5_ctx = hmacmd5_make_context(NULL);
 
     pwlen = strlen(passwd);
     if (pwlen>64) {
index cc1987751e0ef32aa507f3b4e641b13127c87d99..31a9627be1c86f1e9fbe63883c7213b105c7a14a 100644 (file)
--- a/dialog.c
+++ b/dialog.c
@@ -13,7 +13,7 @@
 #include "putty.h"
 #include "dialog.h"
 
-int ctrl_path_elements(char *path)
+int ctrl_path_elements(const char *path)
 {
     int i = 1;
     while (*path) {
@@ -25,7 +25,7 @@ int ctrl_path_elements(char *path)
 
 /* Return the number of matching path elements at the starts of p1 and p2,
  * or INT_MAX if the paths are identical. */
-int ctrl_path_compare(char *p1, char *p2)
+int ctrl_path_compare(const char *p1, const char *p2)
 {
     int i = 0;
     while (*p1 || *p2) {
@@ -86,7 +86,7 @@ void ctrl_free_set(struct controlset *s)
  * path. If that path doesn't exist, return the index where it
  * should be inserted.
  */
-static int ctrl_find_set(struct controlbox *b, char *path, int start)
+static int ctrl_find_set(struct controlbox *b, const char *path, int start)
 {
     int i, last, thisone;
 
@@ -112,7 +112,7 @@ static int ctrl_find_set(struct controlbox *b, char *path, int start)
  * path, or -1 if no such controlset exists. If -1 is passed as
  * input, finds the first.
  */
-int ctrl_find_path(struct controlbox *b, char *path, int index)
+int ctrl_find_path(struct controlbox *b, const char *path, int index)
 {
     if (index < 0)
        index = ctrl_find_set(b, path, 1);
@@ -127,7 +127,7 @@ int ctrl_find_path(struct controlbox *b, char *path, int index)
 
 /* Set up a panel title. */
 struct controlset *ctrl_settitle(struct controlbox *b,
-                                char *path, char *title)
+                                const char *path, const char *title)
 {
     
     struct controlset *s = snew(struct controlset);
@@ -151,8 +151,8 @@ struct controlset *ctrl_settitle(struct controlbox *b,
 }
 
 /* Retrieve a pointer to a controlset, creating it if absent. */
-struct controlset *ctrl_getset(struct controlbox *b,
-                              char *path, char *name, char *boxtitle)
+struct controlset *ctrl_getset(struct controlbox *b, const char *path,
+                               const char *name, const char *boxtitle)
 {
     struct controlset *s;
     int index = ctrl_find_set(b, path, 1);
@@ -257,8 +257,8 @@ union control *ctrl_columns(struct controlset *s, int ncolumns, ...)
     return c;
 }
 
-union control *ctrl_editbox(struct controlset *s, char *label, char shortcut,
-                           int percentage,
+union control *ctrl_editbox(struct controlset *s, const char *label,
+                            char shortcut, int percentage,
                            intorptr helpctx, handler_fn handler,
                            intorptr context, intorptr context2)
 {
@@ -272,8 +272,8 @@ union control *ctrl_editbox(struct controlset *s, char *label, char shortcut,
     return c;
 }
 
-union control *ctrl_combobox(struct controlset *s, char *label, char shortcut,
-                            int percentage,
+union control *ctrl_combobox(struct controlset *s, const char *label,
+                             char shortcut, int percentage,
                             intorptr helpctx, handler_fn handler,
                             intorptr context, intorptr context2)
 {
@@ -293,7 +293,7 @@ union control *ctrl_combobox(struct controlset *s, char *label, char shortcut,
  * title is expected to be followed by a shortcut _iff_ `shortcut'
  * is NO_SHORTCUT.
  */
-union control *ctrl_radiobuttons(struct controlset *s, char *label,
+union control *ctrl_radiobuttons(struct controlset *s, const char *label,
                                 char shortcut, int ncolumns, intorptr helpctx,
                                 handler_fn handler, intorptr context, ...)
 {
@@ -339,9 +339,9 @@ union control *ctrl_radiobuttons(struct controlset *s, char *label,
     return c;
 }
 
-union control *ctrl_pushbutton(struct controlset *s,char *label,char shortcut,
-                              intorptr helpctx, handler_fn handler,
-                              intorptr context)
+union control *ctrl_pushbutton(struct controlset *s, const char *label,
+                               char shortcut, intorptr helpctx,
+                               handler_fn handler, intorptr context)
 {
     union control *c = ctrl_new(s, CTRL_BUTTON, helpctx, handler, context);
     c->button.label = label ? dupstr(label) : NULL;
@@ -351,9 +351,9 @@ union control *ctrl_pushbutton(struct controlset *s,char *label,char shortcut,
     return c;
 }
 
-union control *ctrl_listbox(struct controlset *s,char *label,char shortcut,
-                           intorptr helpctx, handler_fn handler,
-                           intorptr context)
+union control *ctrl_listbox(struct controlset *s, const char *label,
+                            char shortcut, intorptr helpctx,
+                            handler_fn handler, intorptr context)
 {
     union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
     c->listbox.label = label ? dupstr(label) : NULL;
@@ -368,8 +368,8 @@ union control *ctrl_listbox(struct controlset *s,char *label,char shortcut,
     return c;
 }
 
-union control *ctrl_droplist(struct controlset *s, char *label, char shortcut,
-                            int percentage, intorptr helpctx,
+union control *ctrl_droplist(struct controlset *s, const char *label,
+                             char shortcut, int percentage, intorptr helpctx,
                             handler_fn handler, intorptr context)
 {
     union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
@@ -385,9 +385,9 @@ union control *ctrl_droplist(struct controlset *s, char *label, char shortcut,
     return c;
 }
 
-union control *ctrl_draglist(struct controlset *s,char *label,char shortcut,
-                            intorptr helpctx, handler_fn handler,
-                            intorptr context)
+union control *ctrl_draglist(struct controlset *s, const char *label,
+                             char shortcut, intorptr helpctx,
+                             handler_fn handler, intorptr context)
 {
     union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
     c->listbox.label = label ? dupstr(label) : NULL;
@@ -402,10 +402,10 @@ union control *ctrl_draglist(struct controlset *s,char *label,char shortcut,
     return c;
 }
 
-union control *ctrl_filesel(struct controlset *s,char *label,char shortcut,
-                           char const *filter, int write, char *title,
-                           intorptr helpctx, handler_fn handler,
-                           intorptr context)
+union control *ctrl_filesel(struct controlset *s, const char *label,
+                            char shortcut, const char *filter, int write,
+                            const char *title, intorptr helpctx,
+                            handler_fn handler, intorptr context)
 {
     union control *c = ctrl_new(s, CTRL_FILESELECT, helpctx, handler, context);
     c->fileselect.label = label ? dupstr(label) : NULL;
@@ -416,9 +416,9 @@ union control *ctrl_filesel(struct controlset *s,char *label,char shortcut,
     return c;
 }
 
-union control *ctrl_fontsel(struct controlset *s,char *label,char shortcut,
-                           intorptr helpctx, handler_fn handler,
-                           intorptr context)
+union control *ctrl_fontsel(struct controlset *s, const char *label,
+                            char shortcut, intorptr helpctx,
+                            handler_fn handler, intorptr context)
 {
     union control *c = ctrl_new(s, CTRL_FONTSELECT, helpctx, handler, context);
     c->fontselect.label = label ? dupstr(label) : NULL;
@@ -433,16 +433,17 @@ union control *ctrl_tabdelay(struct controlset *s, union control *ctrl)
     return c;
 }
 
-union control *ctrl_text(struct controlset *s, char *text, intorptr helpctx)
+union control *ctrl_text(struct controlset *s, const char *text,
+                         intorptr helpctx)
 {
     union control *c = ctrl_new(s, CTRL_TEXT, helpctx, NULL, P(NULL));
     c->text.label = dupstr(text);
     return c;
 }
 
-union control *ctrl_checkbox(struct controlset *s, char *label, char shortcut,
-                            intorptr helpctx, handler_fn handler,
-                            intorptr context)
+union control *ctrl_checkbox(struct controlset *s, const char *label,
+                             char shortcut, intorptr helpctx,
+                             handler_fn handler, intorptr context)
 {
     union control *c = ctrl_new(s, CTRL_CHECKBOX, helpctx, handler, context);
     c->checkbox.label = label ? dupstr(label) : NULL;
index 6775167019e886ccda93c3649386375ad9323410..9ac355c64afcab78279aa88db966d25969b15184 100644 (file)
--- a/dialog.h
+++ b/dialog.h
@@ -458,10 +458,10 @@ void ctrl_free_box(struct controlbox *);
 
 /* Set up a panel title. */
 struct controlset *ctrl_settitle(struct controlbox *,
-                                char *path, char *title);
+                                const char *path, const char *title);
 /* Retrieve a pointer to a controlset, creating it if absent. */
-struct controlset *ctrl_getset(struct controlbox *,
-                              char *path, char *name, char *boxtitle);
+struct controlset *ctrl_getset(struct controlbox *, const char *path,
+                               const char *name, const char *boxtitle);
 void ctrl_free_set(struct controlset *);
 
 void ctrl_free(union control *);
@@ -493,12 +493,12 @@ void *ctrl_alloc_with_free(struct controlbox *b, size_t size,
 
 /* `ncolumns' is followed by that many percentages, as integers. */
 union control *ctrl_columns(struct controlset *, int ncolumns, ...);
-union control *ctrl_editbox(struct controlset *, char *label, char shortcut,
-                           int percentage, intorptr helpctx,
+union control *ctrl_editbox(struct controlset *, const char *label,
+                            char shortcut, int percentage, intorptr helpctx,
                            handler_fn handler,
                            intorptr context, intorptr context2);
-union control *ctrl_combobox(struct controlset *, char *label, char shortcut,
-                            int percentage, intorptr helpctx,
+union control *ctrl_combobox(struct controlset *, const char *label,
+                             char shortcut, int percentage, intorptr helpctx,
                             handler_fn handler,
                             intorptr context, intorptr context2);
 /*
@@ -507,32 +507,32 @@ union control *ctrl_combobox(struct controlset *, char *label, char shortcut,
  * title is expected to be followed by a shortcut _iff_ `shortcut'
  * is NO_SHORTCUT.
  */
-union control *ctrl_radiobuttons(struct controlset *, char *label,
-                                char shortcut, int ncolumns,
-                                intorptr helpctx,
+union control *ctrl_radiobuttons(struct controlset *, const char *label,
+                                char shortcut, int ncolumns, intorptr helpctx,
                                 handler_fn handler, intorptr context, ...);
-union control *ctrl_pushbutton(struct controlset *,char *label,char shortcut,
-                              intorptr helpctx,
+union control *ctrl_pushbutton(struct controlset *, const char *label,
+                               char shortcut, intorptr helpctx,
                               handler_fn handler, intorptr context);
-union control *ctrl_listbox(struct controlset *,char *label,char shortcut,
-                           intorptr helpctx,
+union control *ctrl_listbox(struct controlset *, const char *label,
+                            char shortcut, intorptr helpctx,
                            handler_fn handler, intorptr context);
-union control *ctrl_droplist(struct controlset *, char *label, char shortcut,
-                            int percentage, intorptr helpctx,
+union control *ctrl_droplist(struct controlset *, const char *label,
+                             char shortcut, int percentage, intorptr helpctx,
                             handler_fn handler, intorptr context);
-union control *ctrl_draglist(struct controlset *,char *label,char shortcut,
-                            intorptr helpctx,
+union control *ctrl_draglist(struct controlset *, const char *label,
+                             char shortcut, intorptr helpctx,
                             handler_fn handler, intorptr context);
-union control *ctrl_filesel(struct controlset *,char *label,char shortcut,
-                           char const *filter, int write, char *title,
-                           intorptr helpctx,
+union control *ctrl_filesel(struct controlset *, const char *label,
+                            char shortcut, const char *filter, int write,
+                            const char *title, intorptr helpctx,
                            handler_fn handler, intorptr context);
-union control *ctrl_fontsel(struct controlset *,char *label,char shortcut,
-                           intorptr helpctx,
+union control *ctrl_fontsel(struct controlset *, const char *label,
+                            char shortcut, intorptr helpctx,
                            handler_fn handler, intorptr context);
-union control *ctrl_text(struct controlset *, char *text, intorptr helpctx);
-union control *ctrl_checkbox(struct controlset *, char *label, char shortcut,
-                            intorptr helpctx,
+union control *ctrl_text(struct controlset *, const char *text,
+                         intorptr helpctx);
+union control *ctrl_checkbox(struct controlset *, const char *label,
+                             char shortcut, intorptr helpctx,
                             handler_fn handler, intorptr context);
 union control *ctrl_tabdelay(struct controlset *, union control *);
 
@@ -597,7 +597,7 @@ union control *dlg_last_focused(union control *ctrl, void *dlg);
  * error; dlg_error() puts up a message-box or equivalent.
  */
 void dlg_beep(void *dlg);
-void dlg_error_msg(void *dlg, char *msg);
+void dlg_error_msg(void *dlg, const char *msg);
 /*
  * This function signals to the front end that the dialog's
  * processing is completed, and passes an integer value (typically
@@ -647,8 +647,8 @@ void dlg_refresh(union control *ctrl, void *dlg);
  *          ... process this controlset ...
  *      }
  */
-int ctrl_find_path(struct controlbox *b, char *path, int index);
-int ctrl_path_elements(char *path);
+int ctrl_find_path(struct controlbox *b, const char *path, int index);
+int ctrl_path_elements(const char *path);
 /* Return the number of matching path elements at the starts of p1 and p2,
  * or INT_MAX if the paths are identical. */
-int ctrl_path_compare(char *p1, char *p2);
+int ctrl_path_compare(const char *p1, const char *p2);
index 37d8b0c1aee5dcdd6ec6876b0cf28b3a6e771482..e7bf287e028ea506081c158d61e316588714bc46 100644 (file)
@@ -58,7 +58,8 @@ putty.hhp: $(INPUTS) chm.but
        $(HALIBUT) --html $(INPUTS) chm.but
 
 MKMAN = $(HALIBUT) --man=$@ mancfg.but $<
-MANPAGES = putty.1 puttygen.1 plink.1 pscp.1 psftp.1 puttytel.1 pterm.1
+MANPAGES = putty.1 puttygen.1 plink.1 pscp.1 psftp.1 puttytel.1 pterm.1 \
+           pageant.1
 man: $(MANPAGES)
 
 putty.1: man-putt.but mancfg.but; $(MKMAN)
@@ -68,6 +69,7 @@ pscp.1: man-pscp.but mancfg.but; $(MKMAN)
 psftp.1: man-psft.but mancfg.but; $(MKMAN)
 puttytel.1: man-ptel.but mancfg.but; $(MKMAN)
 pterm.1: man-pter.but mancfg.but; $(MKMAN)
+pageant.1: man-pag.but mancfg.but; $(MKMAN)
 
 mostlyclean:
        rm -f *.html *.txt *.hlp *.cnt *.1 *.info vstr.but *.hh[pck]
index 6d35f9c88c697f756c1dc029f47eefae51c9a4d5..5107858c5f54d4f52c2b06927c21466738817108 100644 (file)
@@ -2353,6 +2353,9 @@ with sharing enabled, then it can act as a downstream and use an
 existing SSH connection set up by an instance of GUI PuTTY. The one
 special case is that PSCP and PSFTP will \e{never} act as upstreams.
 
+It is possible to test programmatically for the existence of a live
+upstream using Plink. See \k{plink-option-shareexists}.
+
 \H{config-ssh-kex} The Kex panel
 
 The Kex panel (short for \q{\i{key exchange}}) allows you to configure
@@ -2385,15 +2388,17 @@ PuTTY supports a variety of SSH-2 key exchange methods, and allows you
 to choose which one you prefer to use; configuration is similar to
 cipher selection (see \k{config-ssh-encryption}).
 
-PuTTY currently supports the following varieties of \i{Diffie-Hellman key
-exchange}:
+PuTTY currently supports the following key exchange methods:
+
+\b \q{ECDH}: \i{elliptic curve} \i{Diffie-Hellman key exchange}.
 
-\b \q{Group 14}: a well-known 2048-bit group.
+\b \q{Group 14}: Diffie-Hellman key exchange with a well-known
+2048-bit group.
 
-\b \q{Group 1}: a well-known 1024-bit group. This is less secure
-\#{FIXME better words} than group 14, but may be faster with slow
-client or server machines, and may be the only method supported by
-older server software.
+\b \q{Group 1}: Diffie-Hellman key exchange with a well-known
+1024-bit group. This is less secure \#{FIXME better words} than
+group 14, but may be faster with slow client or server machines,
+and may be the only method supported by older server software.
 
 \b \q{\ii{Group exchange}}: with this method, instead of using a fixed
 group, PuTTY requests that the server suggest a group to use for key
@@ -2401,9 +2406,9 @@ exchange; the server can avoid groups known to be weak, and possibly
 invent new ones over time, without any changes required to PuTTY's
 configuration. We recommend use of this method, if possible.
 
-In addition, PuTTY supports \i{RSA key exchange}, which requires much less
-computational effort on the part of the client, and somewhat less on
-the part of the server, than Diffie-Hellman key exchange.
+\b \q{\i{RSA key exchange}}: this requires much less computational
+effort on the part of the client, and somewhat less on the part of
+the server, than Diffie-Hellman key exchange.
 
 If the first algorithm PuTTY finds is below the \q{warn below here}
 line, you will see a warning box when you make the connection, similar
@@ -2546,6 +2551,8 @@ use that.
 
 PuTTY currently supports the following algorithms:
 
+\b \i{ChaCha20-Poly1305}, a combined cipher and \i{MAC} (SSH-2 only)
+
 \b \i{AES} (Rijndael) - 256, 192, or 128-bit SDCTR or CBC (SSH-2 only)
 
 \b \i{Arcfour} (RC4) - 256 or 128-bit stream cipher (SSH-2 only)
index 77b3493a59b2f75742817527157281b36cc2dbe2..204430ce80396b6fad6855b99bb3da708b1e17ba 100644 (file)
@@ -766,6 +766,7 @@ saved sessions from
 
 \IM{-batch-plink} \c{-batch} Plink command-line option
 \IM{-s-plink} \c{-s} Plink command-line option
+\IM{-shareexists-plink} \c{-shareexists} Plink command-line option
 
 \IM{subsystem} subsystem, SSH
 \IM{subsystem} SSH subsystem
diff --git a/doc/man-pag.but b/doc/man-pag.but
new file mode 100644 (file)
index 0000000..15bc5ba
--- /dev/null
@@ -0,0 +1,270 @@
+\cfg{man-identity}{pageant}{1}{2015-05-19}{PuTTY tool suite}{PuTTY tool suite}
+
+\H{pageant-manpage} Man page for Pageant
+
+\S{pageant-manpage-name} NAME
+
+\cw{pageant} - SSH authentication agent for the PuTTY tools
+
+\S{pageant-manpage-synopsis} SYNOPSIS
+
+\c pageant ( -X | -T | --permanent | --debug ) [ key-file... ]
+\e bbbbbbb   bb   bb   bbbbbbbbbbb   bbbbbbb     iiiiiiii
+\c pageant [ key-file... ] --exec command [ args... ]
+\e bbbbbbb   iiiiiiii      bbbbbb iiiiiii   iiii
+\c pageant -a key-file...
+\e bbbbbbb bb iiiiiiii
+\c pageant ( -d | --public | --public-openssh ) key-identifier...
+\e bbbbbbb   bb   bbbbbbbb   bbbbbbbbbbbbbbbb   iiiiiiiiiiiiii
+\c pageant -D
+\e bbbbbbb bb
+\c pageant -l
+\e bbbbbbb bb
+
+\S{pageant-manpage-description} DESCRIPTION
+
+\c{pageant} is both an SSH authentication agent, and also a tool for
+communicating with an already-running agent.
+
+When running as an SSH agent, it listens on a Unix-domain socket for
+connections from client processes running under your user id. Clients
+can load SSH private keys into the agent, or request signatures on a
+given message from a key already in the agent. This permits one-touch
+authentication by SSH client programs, if Pageant is holding a key
+that the server they are connecting to will accept.
+
+\c{pageant} can also act as a client program itself, communicating
+with an already-running agent to add or remove keys, list the keys, or
+extract their public half.
+
+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
+should use either \cw{-X} or \cw{-T}, depending on whether your login
+session is GUI or purely terminal-based respectively. For example, in
+your X session startup script you might write
+
+\c eval $(pageant -X)
+\e bbbbbbbbbbbbbbbbbb
+
+which will cause Pageant to start running, monitor the X server to
+notice when your session terminates (and then it will terminate too),
+and print on standard output some shell commands to set environment
+variables that client processes will need to find the running agent.
+
+In a terminal-based login, you could do almost exactly the same thing
+but with \cw{-T}:
+
+\c eval $(pageant -T)
+\e bbbbbbbbbbbbbbbbbb
+
+This will cause Pageant to tie its lifetime to that of your
+controlling terminal: when you log out, and the terminal device ceases
+to be associated with your session, Pageant will notice that it has no
+controlling terminal any more, and will terminate automatically.
+
+In either of these modes, you can also add one or more private keys as
+extra command-line arguments, e.g.
+
+\c eval $(pageant -T ~/.ssh/key.ppk)
+\e bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+
+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.
+
+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
+with \cw{-l}, extract the full public half of any key using
+\cw{--public} or \cw{--public-openssh}, delete a key using \cw{-d}, or
+delete all keys using \cw{-D}.
+
+\S{pageant-manpage-lifetime} LIFETIME
+
+The following options are called \e{lifetime modes}. They all request
+Pageant to operate in agent mode; each one specifies a different
+method for Pageant to start up and know when to shut down.
+
+\dt \cw{-X}
+
+\dd Pageant will open a connection to your X display, and when that
+connection is lost, it will terminate. This gives it the same lifetime
+as your GUI login session, so in this mode it is suitable for running
+from a startup script such as \cw{.xsession}. The actual agent will be
+a subprocess; the main Pageant process will terminate immediately,
+after printing environment-variable setting commands on standard
+output which should be installed in any process wanting to communicate
+with the agent.
+
+\lcont{
+
+The usual approach would be to run
+
+\c eval $(pageant -X)
+\e bbbbbbbbbbbbbbbbbb
+
+in an X session startup script. However, other possibilities exist,
+such as directing the standard output of \cq{pageant -X} to a file
+which is then sourced by any new shell.
+
+}
+
+\dt \cw{-T}
+
+\dd Pageant will tie its lifetime to that of the login session running
+on its controlling terminal, by noticing when it ceases to have a
+controlling terminal (which will automatically happen as a side effect
+of the session leader process terminating). Like \cw{-X}, Pageant will
+print environment-variable commands on standard output.
+
+\dt \cw{--exec} \e{command}
+
+\dd Pageant will run the provided command as a subprocess, preloaded
+with the appropriate environment variables to access the agent it
+starts up. When the subprocess terminates, Pageant will terminate as
+well.
+
+\lcont{
+
+All arguments on Pageant's command line after \cw{--exec} will be
+treated as part of the command to run, even if they look like other
+valid Pageant options or key files.
+
+}
+
+\dt \cw{--permanent}
+
+\dd Pageant will fork off a subprocess to be the agent, and print
+environment-variable commands on standard output, like \cw{-X} and
+\cw{-T}. However, in this case, it will make no effort to limit its
+lifetime in any way; it will simply run permanently, unless manually
+killed. The environment variable \cw{SSH_AGENT_PID}, set by the
+commands printed by Pageant, permits the agent process to be found for
+this purpose.
+
+\lcont{
+
+This option is not recommended, because any method of manually killing
+the agent carries the risk of the session terminating unexpectedly
+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
+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.
+
+\S{pageant-manpage-client} CLIENT OPTIONS
+
+The following options tell Pageant to operate in client mode,
+contacting an existing agent via environment variables that it should
+already have set.
+
+\dt \cw{-a} \e{key-files}
+
+\dd Load the specified private key file(s), decrypt them if necessary
+by prompting for their passphrases, and add them to the
+already-running agent.
+
+\lcont{
+
+The private key files must be in PuTTY's \cw{.ppk} file format.
+
+}
+
+\dt \cw{-l}
+
+\dd List the keys currently in the running agent. Each key's
+fingerprint and comment string will be shown.
+
+\dt \cw{--public} \e{key-identifiers}
+
+\dd Print the public half of each specified key, in the RFC 4716
+standard format (multiple lines, starting with \cq{---- BEGIN SSH2
+PUBLIC KEY ----}).
+
+\lcont{
+
+Each \e{key-identifier} can be any of the following:
+
+\b The name of a file containing the key, either the whole key (again
+in \cw{.ppk} format) or just its public half.
+
+\b The key's comment string, as shown by \cw{pageant -l}.
+
+\b Enough hex digits of the key's fingerprint to be unique among keys
+currently loaded into the agent.
+
+If Pageant can uniquely identify one key by interpreting the
+\e{key-identifier} in any of these ways, it will assume that key was
+the one you meant. If it cannot, you will have to specify more detail.
+
+If you find that your desired \e{key-identifier} string can be validly
+interpreted as more than one of the above \e{kinds} of identification,
+you can disambiguate by prefixing it with \cq{file:}, \cq{comment:} or
+\cq{fp:} to indicate that it is a filename, comment string or
+fingerprint prefix respectively.
+
+}
+
+\dt \cw{--public-openssh} \e{key-identifiers}
+
+\dd Print the public half of each specified key, in the one-line
+format used by OpenSSH, suitable for putting in
+\cw{.ssh/authorized_keys} files.
+
+\dt \cw{-d} \e{key-identifiers}
+
+\dd Delete each specified key from the agent's memory, so that the
+agent will no longer serve it to clients unless it is loaded in again
+using \cw{pageant -a}.
+
+\dt \cw{-D}
+
+\dd Delete all keys from the agent's memory, leaving it completely
+empty.
+
+\S{pageant-manpage-options} OPTIONS
+
+\dt \cw{-v}
+
+\dd Verbose mode. When Pageant runs in agent mode, this option causes
+it to log all agent activity to its standard error. For example, you
+might run
+
+\lcont{
+
+\c eval $(pageant -X -v 2>~/.pageant.log)
+\e bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+
+and expect a list of all signatures requested by agent clients to
+build up in that log file.
+
+The log information is the same as that produced by the \cw{--debug}
+lifetime option, but \cw{--debug} sends it to standard output (since
+that is the main point of debugging mode) whereas \cw{-v} in all other
+lifetime modes sends the same log data to standard error (being a
+by-product of the program's main purpose). Using \cw{-v} in
+\cw{--debug} mode has no effect: the log still goes to standard
+output.
+
+}
+
+\dt \cw{--help}
+
+\dd Print a brief summary of command-line options and terminate.
+
+\dt \cw{--version}
+
+\dd Print the version of Pageant.
+
+\dt \cw{--}
+
+\dd Cause all subsequent arguments to be treated as key file names,
+even if they look like options.
index 51173e2230b06106099af46d53bc2ef1f0f9930a..6ee37c9932ac80b491d87296a03146344adc5774 100644 (file)
@@ -53,8 +53,8 @@ OpenSSH and ssh.com's implementation.
 \dt \cw{\-t} \e{keytype}
 
 \dd Specify a type of key to generate. The acceptable values here are
-\c{rsa} and \c{dsa} (to generate SSH-2 keys), and \c{rsa1} (to
-generate SSH-1 keys).
+\c{rsa}, \c{dsa}, \c{ecdsa}, and \c{ed25519} (to generate SSH-2 keys),
+and \c{rsa1} (to generate SSH-1 keys).
 
 \dt \cw{\-b} \e{bits}
 
@@ -117,9 +117,15 @@ algorithms are believed compatible with OpenSSH.
 
 \dt \cw{private-openssh}
 
-\dd Save an SSH-2 private key in OpenSSH's format. This option is not
+\dd Save an SSH-2 private key in OpenSSH's format, using the oldest
+format available to maximise backward compatibility. This option is not
 permitted for SSH-1 keys.
 
+\dt \cw{private-openssh-new}
+
+\dd As \c{private-openssh}, except that it forces the use of OpenSSH's
+newer format even for RSA, DSA, and ECDSA keys.
+
 \dt \cw{private-sshcom}
 
 \dd Save an SSH-2 private key in ssh.com's format. This option is not
index b99b758b7866d9cfd6c337f4d31bad532f63f9e6..8e7c5a7a0c2145170f723eeab8ca177ced17cb73 100644 (file)
@@ -71,7 +71,9 @@ For each key, the list box will tell you:
 
 \b The type of the key. Currently, this can be \c{ssh1} (an RSA key
 for use with the SSH-1 protocol), \c{ssh-rsa} (an RSA key for use
-with the SSH-2 protocol), or \c{ssh-dss} (a DSA key for use with
+with the SSH-2 protocol), \c{ssh-dss} (a DSA key for use with
+the SSH-2 protocol), \c{ecdsa-sha2-*} (an ECDSA key for use with
+the SSH-2 protocol), or \c{ssh-ed25519} (an Ed25519 key for use with
 the SSH-2 protocol).
 
 \b The size (in bits) of the key.
index 589d19ef193a7269047225b3c2e3df871fff7ad1..889d3b6acf6973c62e35f05883657fd544ebaeaf 100644 (file)
@@ -232,6 +232,27 @@ line.
 
 (This option is only meaningful with the SSH-2 protocol.)
 
+\S2{plink-option-shareexists} \I{-shareexists-plink}\c{-shareexists}:
+test for connection-sharing upstream
+
+This option does not make a new connection; instead it allows testing
+for the presence of an existing connection that can be shared.
+(See \k{config-ssh-sharing} for more information about SSH connection
+sharing.)
+
+A Plink invocation of the form:
+
+\c plink -shareexists <session>
+\e                    iiiiiiiii
+
+will test whether there is currently a viable \q{upstream} for the
+session in question, which can be specified using any syntax you'd
+normally use with Plink to make an actual connection (a host/port
+number, a bare saved session name, \c{-load}, etc). It returns a
+zero exit status if a usable \q{upstream} exists, nonzero otherwise.
+
+(This option is only meaningful with the SSH-2 protocol.)
+
 \H{plink-batch} Using Plink in \i{batch files} and \i{scripts}
 
 Once you have set up Plink to be able to log in to a remote server
index 137dba233a5b1c1c01367968d4d9f697b34ca19d..e2620bb515a6edb592ffe1b7097a38649e3b9d6c 100644 (file)
@@ -55,9 +55,9 @@ disk. Many people feel this is a good compromise between security
 and convenience. See \k{pageant} for further details.
 
 There is more than one \i{public-key algorithm} available. The most
-common is \i{RSA}, but others exist, notably \i{DSA} (otherwise known as
-DSS), the USA's federal Digital Signature Standard. The key types
-supported by PuTTY are described in \k{puttygen-keytype}.
+common are \i{RSA} and \i{ECDSA}, but others exist, notably \i{DSA}
+(otherwise known as DSS), the USA's federal Digital Signature Standard.
+The key types supported by PuTTY are described in \k{puttygen-keytype}.
 
 \H{pubkey-puttygen} Using \i{PuTTYgen}, the PuTTY key generator
 
@@ -66,7 +66,7 @@ supported by PuTTY are described in \k{puttygen-keytype}.
 PuTTYgen is a key generator. It \I{generating keys}generates pairs of
 public and private keys to be used with PuTTY, PSCP, and Plink, as well
 as the PuTTY authentication agent, Pageant (see \k{pageant}).  PuTTYgen
-generates RSA and DSA keys.
+generates RSA, DSA, ECDSA, and Ed25519 keys.
 
 When you run PuTTYgen you will see a window where you have two
 choices: \q{Generate}, to generate a new public/private key pair, or
@@ -109,7 +109,7 @@ server to accept it.
 \cfg{winhelp-topic}{puttygen.keytype}
 
 Before generating a key pair using PuTTYgen, you need to select
-which type of key you need. PuTTYgen currently supports three types
+which type of key you need. PuTTYgen currently supports these types
 of key:
 
 \b An \i{RSA} key for use with the SSH-1 protocol.
@@ -118,14 +118,21 @@ of key:
 
 \b A \i{DSA} key for use with the SSH-2 protocol.
 
+\b An \i{ECDSA} (\i{elliptic curve} DSA) key for use with the
+SSH-2 protocol.
+
+\b An \i{Ed25519} key (another elliptic curve algorithm) for use
+with the SSH-2 protocol.
+
 The SSH-1 protocol only supports RSA keys; if you will be connecting
 using the SSH-1 protocol, you must select the first key type or your
 key will be completely useless.
 
-The SSH-2 protocol supports more than one key type. The two types
-supported by PuTTY are RSA and DSA.
+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!
@@ -147,7 +154,14 @@ more than one server.
 The \q{Number of bits} input box allows you to choose the strength
 of the key PuTTYgen will generate.
 
-Currently 1024 bits should be sufficient for most purposes.
+\b For RSA, 2048 bits should currently be sufficient for most purposes.
+
+\#{FIXME: advice for DSA?}
+
+\b For ECDSA, only 256, 384, and 521 bits are supported. (ECDSA offers
+equivalent security to RSA with smaller key sizes.)
+
+\b For Ed25519, the only valid size is 256 bits.
 
 \S{puttygen-generate} The \q{Generate} button
 
@@ -183,7 +197,8 @@ appear in the window to indicate this.
 
 The \q{Key fingerprint} box shows you a fingerprint value for the
 generated key. This is derived cryptographically from the \e{public}
-key value, so it doesn't need to be kept secret.
+key value, so it doesn't need to be kept secret; it is supposed to
+be more manageable for human beings than the public key itself.
 
 The fingerprint value is intended to be cryptographically secure, in
 the sense that it is computationally infeasible for someone to
@@ -368,6 +383,16 @@ saving it (see \k{puttygen-savepriv}) - you need to have typed your
 passphrase in beforehand, and you will be warned if you are about to
 save a key without a passphrase.
 
+For OpenSSH there are two options. Modern OpenSSH actually has two
+formats it uses for storing private keys. \q{Export OpenSSH key}
+will automatically choose the oldest format supported for the key
+type, for maximum backward compatibility with older versions of
+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}.
+
 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 f7640268ee5d8a03e3c5b028d60e15c8081168b7..c50464ee14991b3720d6fbf8974f724098e00396 100644 (file)
@@ -46,7 +46,9 @@ on 32-bit architectures \e{or bigger}; so it's safe to assume that
 by ANSI C.  Similarly, we assume that the execution character
 encoding is a superset of the printable characters of ASCII, though
 we don't assume the numeric values of control characters,
-particularly \cw{'\\n'} and \cw{'\\r'}.)
+particularly \cw{'\\n'} and \cw{'\\r'}. Also, the X forwarding code
+assumes that \c{time_t} has the Unix format and semantics, i.e. an
+integer giving the number of seconds since 1970.)
 
 \H{udp-multi-backend} Multiple backends treated equally
 
diff --git a/fuzzterm.c b/fuzzterm.c
new file mode 100644 (file)
index 0000000..0da3b68
--- /dev/null
@@ -0,0 +1,185 @@
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#define PUTTY_DO_GLOBALS
+#include "putty.h"
+#include "terminal.h"
+
+int main(int argc, char **argv)
+{
+       char blk[512];
+       size_t len;
+       Terminal *term;
+       Conf *conf;
+       struct unicode_data ucsdata;
+
+       conf = conf_new();
+       do_defaults(NULL, conf);
+       init_ucs(&ucsdata, conf_get_str(conf, CONF_line_codepage),
+                conf_get_int(conf, CONF_utf8_override),
+                CS_NONE, conf_get_int(conf, CONF_vtmode));
+
+       term = term_init(conf, &ucsdata, NULL);
+       term_size(term, 24, 80, 10000);
+       term->ldisc = NULL;
+       /* Tell american fuzzy lop that this is a good place to fork. */
+#ifdef __AFL_HAVE_MANUAL_CONTROL
+       __AFL_INIT();
+#endif
+       while (!feof(stdin)) {
+               len = fread(blk, 1, sizeof(blk), stdin);
+               term_data(term, 0, blk, len);
+       }
+       term_update(term);
+       return 0;
+}
+
+int from_backend(void *frontend, int is_stderr, const char *data, int len)
+{ return 0; }
+
+/* functions required by terminal.c */
+
+void request_resize(void *frontend, int x, int y) { }
+void do_text(Context ctx, int x, int y, wchar_t * text, int len,
+            unsigned long attr, int lattr)
+{
+    int i;
+
+    printf("TEXT[attr=%08lx,lattr=%02x]@(%d,%d):", attr, lattr, x, y);
+    for (i = 0; i < len; i++) {
+       printf(" %x", (unsigned)text[i]);
+    }
+    printf("\n");
+}
+void do_cursor(Context ctx, int x, int y, wchar_t * text, int len,
+            unsigned long attr, int lattr)
+{
+    int i;
+
+    printf("CURS[attr=%08lx,lattr=%02x]@(%d,%d):", attr, lattr, x, y);
+    for (i = 0; i < len; i++) {
+       printf(" %x", (unsigned)text[i]);
+    }
+    printf("\n");
+}
+int char_width(Context ctx, int uc) { return 1; }
+void set_title(void *frontend, char *t) { }
+void set_icon(void *frontend, char *t) { }
+void set_sbar(void *frontend, int a, int b, int c) { }
+
+void ldisc_send(void *handle, const char *buf, int len, int interactive) {}
+void ldisc_echoedit_update(void *handle) {}
+Context get_ctx(void *frontend) { 
+    static char x;
+
+    return &x;
+}
+void free_ctx(Context ctx) { }
+void palette_set(void *frontend, int a, int b, int c, int d) { }
+void palette_reset(void *frontend) { }
+void write_clip(void *frontend, wchar_t *a, int *b, int c, int d) { }
+void get_clip(void *frontend, wchar_t **w, int *i) { }
+void set_raw_mouse_mode(void *frontend, int m) { }
+void request_paste(void *frontend) { }
+void do_beep(void *frontend, int a) { }
+void sys_cursor(void *frontend, int x, int y) { }
+void fatalbox(const char *fmt, ...) { exit(0); }
+void modalfatalbox(const char *fmt, ...) { exit(0); }
+void nonfatal(const char *fmt, ...) { }
+
+void set_iconic(void *frontend, int iconic) { }
+void move_window(void *frontend, int x, int y) { }
+void set_zorder(void *frontend, int top) { }
+void refresh_window(void *frontend) { }
+void set_zoomed(void *frontend, int zoomed) { }
+int is_iconic(void *frontend) { return 0; }
+void get_window_pos(void *frontend, int *x, int *y) { *x = 0; *y = 0; }
+void get_window_pixels(void *frontend, int *x, int *y) { *x = 0; *y = 0; }
+char *get_window_title(void *frontend, int icon) { return "moo"; }
+
+/* needed by timing.c */
+void timer_change_notify(unsigned long next) { }
+
+/* needed by config.c and sercfg.c */
+
+void dlg_radiobutton_set(union control *ctrl, void *dlg, int whichbutton) { }
+int dlg_radiobutton_get(union control *ctrl, void *dlg) { return 0; }
+void dlg_checkbox_set(union control *ctrl, void *dlg, int checked) { }
+int dlg_checkbox_get(union control *ctrl, void *dlg) { return 0; }
+void dlg_editbox_set(union control *ctrl, void *dlg, char const *text) { }
+char *dlg_editbox_get(union control *ctrl, void *dlg) { return dupstr("moo"); }
+void dlg_listbox_clear(union control *ctrl, void *dlg) { }
+void dlg_listbox_del(union control *ctrl, void *dlg, int index) { }
+void dlg_listbox_add(union control *ctrl, void *dlg, char const *text) { }
+void dlg_listbox_addwithid(union control *ctrl, void *dlg,
+                          char const *text, int id) { }
+int dlg_listbox_getid(union control *ctrl, void *dlg, int index) { return 0; }
+int dlg_listbox_index(union control *ctrl, void *dlg) { return -1; }
+int dlg_listbox_issel(union control *ctrl, void *dlg, int index) { return 0; }
+void dlg_listbox_select(union control *ctrl, void *dlg, int index) { }
+void dlg_text_set(union control *ctrl, void *dlg, char const *text) { }
+void dlg_filesel_set(union control *ctrl, void *dlg, Filename *fn) { }
+Filename *dlg_filesel_get(union control *ctrl, void *dlg) { return NULL; }
+void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec *fn) { }
+FontSpec *dlg_fontsel_get(union control *ctrl, void *dlg) { return NULL; }
+void dlg_update_start(union control *ctrl, void *dlg) { }
+void dlg_update_done(union control *ctrl, void *dlg) { }
+void dlg_set_focus(union control *ctrl, void *dlg) { }
+void dlg_label_change(union control *ctrl, void *dlg, char const *text) { }
+union control *dlg_last_focused(union control *ctrl, void *dlg) { return NULL; }
+void dlg_beep(void *dlg) { }
+void dlg_error_msg(void *dlg, const char *msg) { }
+void dlg_end(void *dlg, int value) { }
+void dlg_coloursel_start(union control *ctrl, void *dlg,
+                        int r, int g, int b) { }
+int dlg_coloursel_results(union control *ctrl, void *dlg,
+                         int *r, int *g, int *b) { return 0; }
+void dlg_refresh(union control *ctrl, void *dlg) { }
+
+/* miscellany */
+void logevent(void *frontend, const char *msg) { }
+int askappend(void *frontend, Filename *filename,
+             void (*callback)(void *ctx, int result), void *ctx) { return 0; }
+
+const char *const appname = "FuZZterm";
+const int ngsslibs = 0;
+const char *const gsslibnames[0] = { };
+const struct keyvalwhere gsslibkeywords[0] = { };
+
+/*
+ * Default settings that are specific to Unix plink.
+ */
+char *platform_default_s(const char *name)
+{
+    if (!strcmp(name, "TermType"))
+       return dupstr(getenv("TERM"));
+    if (!strcmp(name, "SerialLine"))
+       return dupstr("/dev/ttyS0");
+    return NULL;
+}
+
+int platform_default_i(const char *name, int def)
+{
+    return def;
+}
+
+FontSpec *platform_default_fontspec(const char *name)
+{
+    return fontspec_new("");
+}
+
+Filename *platform_default_filename(const char *name)
+{
+    if (!strcmp(name, "LogFileName"))
+       return filename_from_str("putty.log");
+    else
+       return filename_from_str("");
+}
+
+char *x_get_default(const char *key)
+{
+    return NULL;                      /* this is a stub */
+}
+
+
index 1074303eff506e313307e1190ccdb0b3563dc98f..5e845b27c88929b434ba81cb23cad7c291259e74 100644 (file)
@@ -1,7 +1,7 @@
 # Makefile for the PuTTY icon suite.
 
 ICONS = putty puttycfg puttygen pscp pageant pterm ptermcfg puttyins
-SIZES = 16 32 48
+SIZES = 16 32 48 128
 
 MODE = # override to -it on command line for opaque testing
 
@@ -11,17 +11,19 @@ TRUEPNGS = $(foreach I,$(ICONS),$(foreach S,$(SIZES),$(I)-$(S)-true.png))
 
 ICOS = putty.ico puttygen.ico pscp.ico pageant.ico pageants.ico puttycfg.ico \
        puttyins.ico
+ICNS = PuTTY.icns
 CICONS = xpmputty.c xpmpucfg.c xpmpterm.c xpmptcfg.c
 
 base: icos cicons
 
-all: pngs monopngs base # truepngs currently disabled by default
+all: pngs monopngs base icns # truepngs currently disabled by default
 
 pngs: $(PNGS)
 monopngs: $(MONOPNGS)
 truepngs: $(TRUEPNGS)
 
 icos: $(ICOS)
+icns: $(ICNS)
 cicons: $(CICONS)
 
 install: icos cicons
@@ -88,5 +90,15 @@ 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 \
+               output:$@
+
 clean:
-       rm -f *.png *.ico *.c
+       rm -f *.png *.ico *.icns *.c
diff --git a/icons/macicon.py b/icons/macicon.py
new file mode 100755 (executable)
index 0000000..9dfc87f
--- /dev/null
@@ -0,0 +1,150 @@
+#!/usr/bin/env python
+
+# Generate Mac OS X .icns files, or at least the simple subformats
+# that don't involve JPEG encoding and the like.
+#
+# Sources: https://en.wikipedia.org/wiki/Apple_Icon_Image_format and
+# some details implicitly documented by the source code of 'libicns'.
+
+import sys
+import struct
+import subprocess
+
+# The file format has a typical IFF-style (type, length, data) chunk
+# structure, with one outer chunk containing subchunks for various
+# different icon sizes and formats.
+def make_chunk(chunkid, data):
+    assert len(chunkid) == 4
+    return chunkid + struct.pack(">I", len(data) + 8) + data
+
+# Monochrome icons: a single chunk containing a 1 bpp image followed
+# by a 1 bpp transparency mask. Both uncompressed, unless you count
+# packing the bits into bytes.
+def make_mono_icon(size, rgba):
+    assert len(rgba) == size * size
+
+    # We assume our input image was monochrome, so that the R,G,B
+    # channels are all the same; we want the image and then the mask,
+    # so we take the R channel followed by the alpha channel. However,
+    # we have to flip the former, because in the output format the
+    # image has 0=white and 1=black, while the mask has 0=transparent
+    # and 1=opaque.
+    pixels = [rgba[index][chan] ^ flip for (chan, flip) in [(0,0xFF),(3,0)]
+              for index in range(len(rgba))]
+
+    # Encode in 1-bit big-endian format.
+    data = ""
+    for i in range(0, len(pixels), 8):
+        byte = 0
+        for j in range(8):
+            if pixels[i+j] >= 0x80:
+                byte |= 0x80 >> j
+        data += chr(byte)
+
+    # This size-32 chunk id is an anomaly in what would otherwise be a
+    # consistent system of using {s,l,h,t} for {16,32,48,128}-pixel
+    # icon sizes.
+    chunkid = { 16: "ics#", 32: "ICN#", 48: "ich#" }[size]
+    return make_chunk(chunkid, data)
+
+# Mask for full-colour icons: a chunk containing an 8 bpp alpha
+# bitmap, uncompressed. The RGB data appears in a separate chunk.
+def make_colour_mask(size, rgba):
+    assert len(rgba) == size * size
+
+    data = "".join(map(lambda pix: chr(pix[3]), rgba))
+
+    chunkid = { 16: "s8mk", 32: "l8mk", 48: "h8mk", 128: "t8mk" }[size]
+    return make_chunk(chunkid, data)
+
+# Helper routine for deciding when to start and stop run-length
+# encoding.
+def runof3(string, position):
+    return (position < len(string) and
+            string[position:position+3] == string[position] * 3)
+
+# RGB data for full-colour icons: a chunk containing 8 bpp red, green
+# and blue images, each run-length encoded (see comment inside the
+# function), and then concatenated.
+def make_colour_icon(size, rgba):
+    assert len(rgba) == size * size
+
+    data = ""
+
+    # Mysterious extra zero header word appearing only in the size-128
+    # icon chunk. libicns doesn't know what it's for, and neither do
+    # I.
+    if size == 128:
+        data += "\0\0\0\0"
+
+    # Handle R,G,B channels in sequence. (Ignore the alpha channel; it
+    # goes into the separate mask chunk constructed above.)
+    for chan in range(3):
+        pixels = "".join([chr(rgba[index][chan])
+                          for index in range(len(rgba))])
+
+        # Run-length encode each channel using the following format:
+        #  * byte 0x80-0xFF followed by one literal byte means repeat
+        #    that byte 3-130 times
+        #  * byte 0x00-0x7F followed by n+1 literal bytes means emit
+        #    those bytes once each.
+        pos = 0
+        while pos < len(pixels):
+            start = pos
+            if runof3(pixels, start):
+                pos += 3
+                pixval = pixels[start]
+                while (pos - start < 130 and
+                       pos < len(pixels) and
+                       pixels[pos] == pixval):
+                    pos += 1
+                data += chr(0x80 + pos-start - 3) + pixval
+            else:
+                while (pos - start < 128 and
+                       pos < len(pixels) and
+                       not runof3(pixels, pos)):
+                    pos += 1
+                data += chr(0x00 + pos-start - 1) + pixels[start:pos]
+
+    chunkid = { 16: "is32", 32: "il32", 48: "ih32", 128: "it32" }[size]
+    return make_chunk(chunkid, data)
+
+# 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.
+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
+
+data = ""
+
+# Trivial argument format: each argument is a filename prefixed with
+# "mono:", "colour:" or "output:". The first two indicate image files
+# to use as part of the icon, and the last gives the output file name.
+# Icon subformat chunks are written out in the order of the arguments.
+for arg in sys.argv[1:]:
+    kind, filename = arg.split(":", 2)
+    if kind == "output":
+        outfile = filename
+    else:
+        size, rgba = load_rgba(filename)
+        if kind == "mono":
+            data += make_mono_icon(size, rgba)
+        elif kind == "colour":
+            data += make_colour_icon(size, rgba) + make_colour_mask(size, rgba)
+        else:
+            assert False, "bad argument '%s'" % arg
+
+data = make_chunk("icns", data)
+
+with open(outfile, "w") as f:
+    f.write(data)
index 4f3ed1fab00308ece7a40c467c4a8010d05e300b..340785bdbd6000aead4857bd31dea4cad4aa5c02 100644 (file)
--- a/import.c
+++ b/import.c
 #include "ssh.h"
 #include "misc.h"
 
-int openssh_encrypted(const Filename *filename);
-struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase,
-                                 const char **errmsg_p);
-int openssh_write(const Filename *filename, struct ssh2_userkey *key,
-                 char *passphrase);
+int openssh_pem_encrypted(const Filename *filename);
+int openssh_new_encrypted(const Filename *filename);
+struct ssh2_userkey *openssh_pem_read(const Filename *filename,
+                                      char *passphrase,
+                                      const char **errmsg_p);
+struct ssh2_userkey *openssh_new_read(const Filename *filename,
+                                      char *passphrase,
+                                      const char **errmsg_p);
+int openssh_auto_write(const Filename *filename, struct ssh2_userkey *key,
+                       char *passphrase);
+int openssh_pem_write(const Filename *filename, struct ssh2_userkey *key,
+                      char *passphrase);
+int openssh_new_write(const Filename *filename, struct ssh2_userkey *key,
+                      char *passphrase);
 
 int sshcom_encrypted(const Filename *filename, char **comment);
 struct ssh2_userkey *sshcom_read(const Filename *filename, char *passphrase,
@@ -29,7 +38,9 @@ int sshcom_write(const Filename *filename, struct ssh2_userkey *key,
  */
 int import_possible(int type)
 {
-    if (type == SSH_KEYTYPE_OPENSSH)
+    if (type == SSH_KEYTYPE_OPENSSH_PEM)
+       return 1;
+    if (type == SSH_KEYTYPE_OPENSSH_NEW)
        return 1;
     if (type == SSH_KEYTYPE_SSHCOM)
        return 1;
@@ -54,12 +65,16 @@ int import_target_type(int type)
  */
 int import_encrypted(const Filename *filename, int type, char **comment)
 {
-    if (type == SSH_KEYTYPE_OPENSSH) {
-       /* OpenSSH doesn't do key comments */
+    if (type == SSH_KEYTYPE_OPENSSH_PEM) {
+       /* OpenSSH PEM format doesn't contain a key comment at all */
        *comment = dupstr(filename_to_str(filename));
-       return openssh_encrypted(filename);
-    }
-    if (type == SSH_KEYTYPE_SSHCOM) {
+       return openssh_pem_encrypted(filename);
+    } else if (type == SSH_KEYTYPE_OPENSSH_NEW) {
+       /* OpenSSH new format does, but it's inside the encrypted
+         * section for some reason */
+       *comment = dupstr(filename_to_str(filename));
+       return openssh_new_encrypted(filename);
+    } else if (type == SSH_KEYTYPE_SSHCOM) {
        return sshcom_encrypted(filename, comment);
     }
     return 0;
@@ -80,8 +95,10 @@ int import_ssh1(const Filename *filename, int type,
 struct ssh2_userkey *import_ssh2(const Filename *filename, int type,
                                 char *passphrase, const char **errmsg_p)
 {
-    if (type == SSH_KEYTYPE_OPENSSH)
-       return openssh_read(filename, passphrase, errmsg_p);
+    if (type == SSH_KEYTYPE_OPENSSH_PEM)
+       return openssh_pem_read(filename, passphrase, errmsg_p);
+    else if (type == SSH_KEYTYPE_OPENSSH_NEW)
+       return openssh_new_read(filename, passphrase, errmsg_p);
     if (type == SSH_KEYTYPE_SSHCOM)
        return sshcom_read(filename, passphrase, errmsg_p);
     return NULL;
@@ -102,8 +119,10 @@ int export_ssh1(const Filename *filename, int type, struct RSAKey *key,
 int export_ssh2(const Filename *filename, int type,
                 struct ssh2_userkey *key, char *passphrase)
 {
-    if (type == SSH_KEYTYPE_OPENSSH)
-       return openssh_write(filename, key, passphrase);
+    if (type == SSH_KEYTYPE_OPENSSH_AUTO)
+       return openssh_auto_write(filename, key, passphrase);
+    if (type == SSH_KEYTYPE_OPENSSH_NEW)
+       return openssh_new_write(filename, key, passphrase);
     if (type == SSH_KEYTYPE_SSHCOM)
        return sshcom_write(filename, key, passphrase);
     return 0;
@@ -255,7 +274,15 @@ static int ber_write_id_len(void *dest, int id, int length, int flags)
     return len;
 }
 
-static int put_string(void *target, void *data, int len)
+static int put_uint32(void *target, unsigned val)
+{
+    unsigned char *d = (unsigned char *)target;
+
+    PUT_32BIT(d, val);
+    return 4;
+}
+
+static int put_string(void *target, const void *data, int len)
 {
     unsigned char *d = (unsigned char *)target;
 
@@ -264,6 +291,11 @@ static int put_string(void *target, void *data, int len)
     return len+4;
 }
 
+static int put_string_z(void *target, const char *string)
+{
+    return put_string(target, string, strlen(string));
+}
+
 static int put_mp(void *target, void *data, int len)
 {
     unsigned char *d = (unsigned char *)target;
@@ -306,35 +338,41 @@ static int ssh2_read_mpint(void *data, int len, struct mpint_pos *ret)
 }
 
 /* ----------------------------------------------------------------------
- * Code to read and write OpenSSH private keys.
+ * Code to read and write OpenSSH private keys, in the old-style PEM
+ * format.
  */
 
-enum { OSSH_DSA, OSSH_RSA };
-enum { OSSH_ENC_3DES, OSSH_ENC_AES };
-struct openssh_key {
-    int type;
-    int encrypted, encryption;
+typedef enum {
+    OP_DSA, OP_RSA, OP_ECDSA
+} openssh_pem_keytype;
+typedef enum {
+    OP_E_3DES, OP_E_AES
+} openssh_pem_enc;
+
+struct openssh_pem_key {
+    openssh_pem_keytype keytype;
+    int encrypted;
+    openssh_pem_enc encryption;
     char iv[32];
     unsigned char *keyblob;
     int keyblob_len, keyblob_size;
 };
 
-static struct openssh_key *load_openssh_key(const Filename *filename,
-                                           const char **errmsg_p)
+static struct openssh_pem_key *load_openssh_pem_key(const Filename *filename,
+                                                    const char **errmsg_p)
 {
-    struct openssh_key *ret;
+    struct openssh_pem_key *ret;
     FILE *fp = NULL;
     char *line = NULL;
-    char *errmsg, *p;
+    const char *errmsg;
+    char *p;
     int headers_done;
     char base64_bit[4];
     int base64_chars = 0;
 
-    ret = snew(struct openssh_key);
+    ret = snew(struct openssh_pem_key);
     ret->keyblob = NULL;
     ret->keyblob_len = ret->keyblob_size = 0;
-    ret->encrypted = 0;
-    memset(ret->iv, 0, sizeof(ret->iv));
 
     fp = f_open(filename, "r", FALSE);
     if (!fp) {
@@ -352,11 +390,22 @@ static struct openssh_key *load_openssh_key(const Filename *filename,
        errmsg = "file does not begin with OpenSSH key header";
        goto error;
     }
-    if (!strcmp(line, "-----BEGIN RSA PRIVATE KEY-----"))
-       ret->type = OSSH_RSA;
-    else if (!strcmp(line, "-----BEGIN DSA PRIVATE KEY-----"))
-       ret->type = OSSH_DSA;
-    else {
+    /*
+     * Parse the BEGIN line. For old-format keys, this tells us the
+     * type of the key; for new-format keys, all it tells us is the
+     * format, and we'll find out the key type once we parse the
+     * base64.
+     */
+    if (!strcmp(line, "-----BEGIN RSA PRIVATE KEY-----")) {
+       ret->keytype = OP_RSA;
+    } else if (!strcmp(line, "-----BEGIN DSA PRIVATE KEY-----")) {
+       ret->keytype = OP_DSA;
+    } else if (!strcmp(line, "-----BEGIN EC PRIVATE KEY-----")) {
+        ret->keytype = OP_ECDSA;
+    } else if (!strcmp(line, "-----BEGIN OPENSSH PRIVATE KEY-----")) {
+       errmsg = "this is a new-style OpenSSH key";
+       goto error;
+    } else {
        errmsg = "unrecognised key type";
        goto error;
     }
@@ -364,6 +413,9 @@ static struct openssh_key *load_openssh_key(const Filename *filename,
     sfree(line);
     line = NULL;
 
+    ret->encrypted = FALSE;
+    memset(ret->iv, 0, sizeof(ret->iv));
+
     headers_done = 0;
     while (1) {
        if (!(line = fgetline(fp))) {
@@ -391,15 +443,15 @@ static struct openssh_key *load_openssh_key(const Filename *filename,
                }
                p += 2;
                if (!strcmp(p, "ENCRYPTED"))
-                   ret->encrypted = 1;
+                   ret->encrypted = TRUE;
            } else if (!strcmp(line, "DEK-Info")) {
                int i, j, ivlen;
 
                if (!strncmp(p, "DES-EDE3-CBC,", 13)) {
-                   ret->encryption = OSSH_ENC_3DES;
+                   ret->encryption = OP_E_3DES;
                    ivlen = 8;
                } else if (!strncmp(p, "AES-128-CBC,", 12)) {
-                   ret->encryption = OSSH_ENC_AES;
+                   ret->encryption = OP_E_AES;
                    ivlen = 16;
                } else {
                    errmsg = "unsupported cipher";
@@ -467,8 +519,9 @@ static struct openssh_key *load_openssh_key(const Filename *filename,
     }
 
     if (ret->encrypted && ret->keyblob_len % 8 != 0) {
-       errmsg = "encrypted key blob is not a multiple of cipher block size";
-       goto error;
+        errmsg = "encrypted key blob is not a multiple of "
+            "cipher block size";
+        goto error;
     }
 
     smemclr(base64_bit, sizeof(base64_bit));
@@ -495,9 +548,9 @@ static struct openssh_key *load_openssh_key(const Filename *filename,
     return NULL;
 }
 
-int openssh_encrypted(const Filename *filename)
+int openssh_pem_encrypted(const Filename *filename)
 {
-    struct openssh_key *key = load_openssh_key(filename, NULL);
+    struct openssh_pem_key *key = load_openssh_pem_key(filename, NULL);
     int ret;
 
     if (!key)
@@ -510,16 +563,17 @@ int openssh_encrypted(const Filename *filename)
     return ret;
 }
 
-struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase,
-                                 const char **errmsg_p)
+struct ssh2_userkey *openssh_pem_read(const Filename *filename,
+                                      char *passphrase,
+                                      const char **errmsg_p)
 {
-    struct openssh_key *key = load_openssh_key(filename, errmsg_p);
+    struct openssh_pem_key *key = load_openssh_pem_key(filename, errmsg_p);
     struct ssh2_userkey *retkey;
-    unsigned char *p;
+    unsigned char *p, *q;
     int ret, id, len, flags;
     int i, num_integers;
     struct ssh2_userkey *retval = NULL;
-    char *errmsg;
+    const char *errmsg;
     unsigned char *blob;
     int blobsize = 0, blobptr, privptr;
     char *modptr = NULL;
@@ -531,47 +585,47 @@ struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase,
        return NULL;
 
     if (key->encrypted) {
-       /*
-        * Derive encryption key from passphrase and iv/salt:
-        * 
-        *  - let block A equal MD5(passphrase || iv)
-        *  - let block B equal MD5(A || passphrase || iv)
-        *  - block C would be MD5(B || passphrase || iv) and so on
-        *  - encryption key is the first N bytes of A || B
-        *
-        * (Note that only 8 bytes of the iv are used for key
-        * derivation, even when the key is encrypted with AES and
-        * hence there are 16 bytes available.)
-        */
-       struct MD5Context md5c;
-       unsigned char keybuf[32];
+        /*
+         * Derive encryption key from passphrase and iv/salt:
+         
+         *  - let block A equal MD5(passphrase || iv)
+         *  - let block B equal MD5(A || passphrase || iv)
+         *  - block C would be MD5(B || passphrase || iv) and so on
+         *  - encryption key is the first N bytes of A || B
+         *
+         * (Note that only 8 bytes of the iv are used for key
+         * derivation, even when the key is encrypted with AES and
+         * hence there are 16 bytes available.)
+         */
+        struct MD5Context md5c;
+        unsigned char keybuf[32];
 
-       MD5Init(&md5c);
-       MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase));
-       MD5Update(&md5c, (unsigned char *)key->iv, 8);
-       MD5Final(keybuf, &md5c);
+        MD5Init(&md5c);
+        MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase));
+        MD5Update(&md5c, (unsigned char *)key->iv, 8);
+        MD5Final(keybuf, &md5c);
 
-       MD5Init(&md5c);
-       MD5Update(&md5c, keybuf, 16);
-       MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase));
-       MD5Update(&md5c, (unsigned char *)key->iv, 8);
-       MD5Final(keybuf+16, &md5c);
+        MD5Init(&md5c);
+        MD5Update(&md5c, keybuf, 16);
+        MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase));
+        MD5Update(&md5c, (unsigned char *)key->iv, 8);
+        MD5Final(keybuf+16, &md5c);
 
-       /*
-        * Now decrypt the key blob.
-        */
-       if (key->encryption == OSSH_ENC_3DES)
-           des3_decrypt_pubkey_ossh(keybuf, (unsigned char *)key->iv,
-                                    key->keyblob, key->keyblob_len);
-       else {
-           void *ctx;
-           assert(key->encryption == OSSH_ENC_AES);
-           ctx = aes_make_context();
-           aes128_key(ctx, keybuf);
-           aes_iv(ctx, (unsigned char *)key->iv);
-           aes_ssh2_decrypt_blk(ctx, key->keyblob, key->keyblob_len);
-           aes_free_context(ctx);
-       }
+        /*
+         * Now decrypt the key blob.
+         */
+        if (key->encryption == OP_E_3DES)
+            des3_decrypt_pubkey_ossh(keybuf, (unsigned char *)key->iv,
+                                     key->keyblob, key->keyblob_len);
+        else {
+            void *ctx;
+            assert(key->encryption == OP_E_AES);
+            ctx = aes_make_context();
+            aes128_key(ctx, keybuf);
+            aes_iv(ctx, (unsigned char *)key->iv);
+            aes_ssh2_decrypt_blk(ctx, key->keyblob, key->keyblob_len);
+            aes_free_context(ctx);
+        }
 
         smemclr(&md5c, sizeof(md5c));
         smemclr(keybuf, sizeof(keybuf));
@@ -593,6 +647,10 @@ struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase,
      *
      *  - For DSA, we expect them to be 0, p, q, g, y, x in that
      *    order.
+     *
+     *  - In ECDSA the format is totally different: we see the
+     *    SEQUENCE, but beneath is an INTEGER 1, OCTET STRING priv
+     *    EXPLICIT [0] OID curve, EXPLICIT [1] BIT STRING pubPoint
      */
     
     p = key->keyblob;
@@ -603,107 +661,249 @@ struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase,
     p += ret;
     if (ret < 0 || id != 16 || len < 0 ||
         key->keyblob+key->keyblob_len-p < len) {
-       errmsg = "ASN.1 decoding failure";
+        errmsg = "ASN.1 decoding failure";
         retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL;
-       goto error;
+        goto error;
     }
 
     /* Expect a load of INTEGERs. */
-    if (key->type == OSSH_RSA)
-       num_integers = 9;
-    else if (key->type == OSSH_DSA)
-       num_integers = 6;
+    if (key->keytype == OP_RSA)
+        num_integers = 9;
+    else if (key->keytype == OP_DSA)
+        num_integers = 6;
     else
-       num_integers = 0;              /* placate compiler warnings */
+        num_integers = 0;             /* placate compiler warnings */
+
+
+    if (key->keytype == OP_ECDSA) {
+        /* And now for something completely different */
+        unsigned char *priv;
+        int privlen;
+        const struct ssh_signkey *alg;
+        const struct ec_curve *curve;
+        int algnamelen, curvenamelen;
+        /* Read INTEGER 1 */
+        ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p,
+                              &id, &len, &flags);
+        p += ret;
+        if (ret < 0 || id != 2 || len != 1 ||
+            key->keyblob+key->keyblob_len-p < len || p[0] != 1) {
+            errmsg = "ASN.1 decoding failure";
+            retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL;
+            goto error;
+        }
+        p += 1;
+        /* Read private key OCTET STRING */
+        ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p,
+                              &id, &len, &flags);
+        p += ret;
+        if (ret < 0 || id != 4 || len < 0 ||
+            key->keyblob+key->keyblob_len-p < len) {
+            errmsg = "ASN.1 decoding failure";
+            retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL;
+            goto error;
+        }
+        priv = p;
+        privlen = len;
+        p += len;
+        /* Read curve OID */
+        ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p,
+                              &id, &len, &flags);
+        p += ret;
+        if (ret < 0 || id != 0 || len < 0 ||
+            key->keyblob+key->keyblob_len-p < len) {
+            errmsg = "ASN.1 decoding failure";
+            retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL;
+            goto error;
+        }
+        ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p,
+                              &id, &len, &flags);
+        p += ret;
+        if (ret < 0 || id != 6 || len < 0 ||
+            key->keyblob+key->keyblob_len-p < len) {
+            errmsg = "ASN.1 decoding failure";
+            retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL;
+            goto error;
+        }
+        alg = ec_alg_by_oid(len, p, &curve);
+        if (!alg) {
+            errmsg = "Unsupported ECDSA curve.";
+            retval = NULL;
+            goto error;
+        }
+        p += len;
+        /* Read BIT STRING point */
+        ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p,
+                              &id, &len, &flags);
+        p += ret;
+        if (ret < 0 || id != 1 || len < 0 ||
+            key->keyblob+key->keyblob_len-p < len) {
+            errmsg = "ASN.1 decoding failure";
+            retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL;
+            goto error;
+        }
+        ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p,
+                              &id, &len, &flags);
+        p += ret;
+        if (ret < 0 || id != 3 || len < 0 ||
+            key->keyblob+key->keyblob_len-p < len ||
+            len != ((((curve->fieldBits + 7) / 8) * 2) + 2)) {
+            errmsg = "ASN.1 decoding failure";
+            retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL;
+            goto error;
+        }
+        p += 1; len -= 1; /* Skip 0x00 before point */
 
-    /*
-     * Space to create key blob in.
-     */
-    blobsize = 256+key->keyblob_len;
-    blob = snewn(blobsize, unsigned char);
-    PUT_32BIT(blob, 7);
-    if (key->type == OSSH_DSA)
-       memcpy(blob+4, "ssh-dss", 7);
-    else if (key->type == OSSH_RSA)
-       memcpy(blob+4, "ssh-rsa", 7);
-    blobptr = 4+7;
-    privptr = -1;
-
-    for (i = 0; i < num_integers; i++) {
-       ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p,
-                             &id, &len, &flags);
-       p += ret;
-       if (ret < 0 || id != 2 || len < 0 ||
-           key->keyblob+key->keyblob_len-p < len) {
-           errmsg = "ASN.1 decoding failure";
-           retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL;
-           goto error;
-       }
+        /* Construct the key */
+        retkey = snew(struct ssh2_userkey);
+        if (!retkey) {
+            errmsg = "out of memory";
+            goto error;
+        }
+        retkey->alg = alg;
+        blob = snewn((4+19 + 4+8 + 4+len) + (4+1+privlen), unsigned char);
+        if (!blob) {
+            sfree(retkey);
+            errmsg = "out of memory";
+            goto error;
+        }
 
-       if (i == 0) {
-           /*
-            * The first integer should be zero always (I think
-            * this is some sort of version indication).
-            */
-           if (len != 1 || p[0] != 0) {
-               errmsg = "version number mismatch";
-               goto error;
-           }
-       } else if (key->type == OSSH_RSA) {
-           /*
-            * Integers 1 and 2 go into the public blob but in the
-            * opposite order; integers 3, 4, 5 and 8 go into the
-            * private blob. The other two (6 and 7) are ignored.
-            */
-           if (i == 1) {
-               /* Save the details for after we deal with number 2. */
-               modptr = (char *)p;
-               modlen = len;
-           } else if (i != 6 && i != 7) {
-               PUT_32BIT(blob+blobptr, len);
-               memcpy(blob+blobptr+4, p, len);
-               blobptr += 4+len;
-               if (i == 2) {
-                   PUT_32BIT(blob+blobptr, modlen);
-                   memcpy(blob+blobptr+4, modptr, modlen);
-                   blobptr += 4+modlen;
-                   privptr = blobptr;
-               }
-           }
-       } else if (key->type == OSSH_DSA) {
-           /*
-            * Integers 1-4 go into the public blob; integer 5 goes
-            * into the private blob.
-            */
-           PUT_32BIT(blob+blobptr, len);
-           memcpy(blob+blobptr+4, p, len);
-           blobptr += 4+len;
-           if (i == 4)
-               privptr = blobptr;
-       }
+        q = blob;
 
-       /* Skip past the number. */
-       p += len;
-    }
+        algnamelen = strlen(alg->name);
+        PUT_32BIT(q, algnamelen); q += 4;
+        memcpy(q, alg->name, algnamelen); q += algnamelen;
 
-    /*
-     * Now put together the actual key. Simplest way to do this is
-     * to assemble our own key blobs and feed them to the createkey
-     * functions; this is a bit faffy but it does mean we get all
-     * the sanity checks for free.
-     */
-    assert(privptr > 0);              /* should have bombed by now if not */
-    retkey = snew(struct ssh2_userkey);
-    retkey->alg = (key->type == OSSH_RSA ? &ssh_rsa : &ssh_dss);
-    retkey->data = retkey->alg->createkey(blob, privptr,
-                                         blob+privptr, blobptr-privptr);
-    if (!retkey->data) {
-       sfree(retkey);
-       errmsg = "unable to create key data structure";
+        curvenamelen = strlen(curve->name);
+        PUT_32BIT(q, curvenamelen); q += 4;
+        memcpy(q, curve->name, curvenamelen); q += curvenamelen;
+
+        PUT_32BIT(q, len); q += 4;
+        memcpy(q, p, len); q += len;
+
+        /*
+         * To be acceptable to our createkey(), the private blob must
+         * contain a valid mpint, i.e. without the top bit set. But
+         * the input private string may have the top bit set, so we
+         * prefix a zero byte to ensure createkey() doesn't fail for
+         * that reason.
+         */
+        PUT_32BIT(q, privlen+1);
+        q[4] = 0;
+        memcpy(q+5, priv, privlen);
+
+        retkey->data = retkey->alg->createkey(retkey->alg,
+                                              blob, q-blob,
+                                              q, 5+privlen);
+
+        if (!retkey->data) {
+            sfree(retkey);
+            errmsg = "unable to create key data structure";
+            goto error;
+        }
+
+    } else if (key->keytype == OP_RSA || key->keytype == OP_DSA) {
+
+        /*
+         * Space to create key blob in.
+         */
+        blobsize = 256+key->keyblob_len;
+        blob = snewn(blobsize, unsigned char);
+        PUT_32BIT(blob, 7);
+        if (key->keytype == OP_DSA)
+            memcpy(blob+4, "ssh-dss", 7);
+        else if (key->keytype == OP_RSA)
+            memcpy(blob+4, "ssh-rsa", 7);
+        blobptr = 4+7;
+        privptr = -1;
+
+        for (i = 0; i < num_integers; i++) {
+            ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p,
+                                  &id, &len, &flags);
+            p += ret;
+            if (ret < 0 || id != 2 || len < 0 ||
+                key->keyblob+key->keyblob_len-p < len) {
+                errmsg = "ASN.1 decoding failure";
+                retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL;
+                goto error;
+            }
+
+            if (i == 0) {
+                /*
+                 * The first integer should be zero always (I think
+                 * this is some sort of version indication).
+                 */
+                if (len != 1 || p[0] != 0) {
+                    errmsg = "version number mismatch";
+                    goto error;
+                }
+            } else if (key->keytype == OP_RSA) {
+                /*
+                 * Integers 1 and 2 go into the public blob but in the
+                 * opposite order; integers 3, 4, 5 and 8 go into the
+                 * private blob. The other two (6 and 7) are ignored.
+                 */
+                if (i == 1) {
+                    /* Save the details for after we deal with number 2. */
+                    modptr = (char *)p;
+                    modlen = len;
+                } else if (i != 6 && i != 7) {
+                    PUT_32BIT(blob+blobptr, len);
+                    memcpy(blob+blobptr+4, p, len);
+                    blobptr += 4+len;
+                    if (i == 2) {
+                        PUT_32BIT(blob+blobptr, modlen);
+                        memcpy(blob+blobptr+4, modptr, modlen);
+                        blobptr += 4+modlen;
+                        privptr = blobptr;
+                    }
+                }
+            } else if (key->keytype == OP_DSA) {
+                /*
+                 * Integers 1-4 go into the public blob; integer 5 goes
+                 * into the private blob.
+                 */
+                PUT_32BIT(blob+blobptr, len);
+                memcpy(blob+blobptr+4, p, len);
+                blobptr += 4+len;
+                if (i == 4)
+                    privptr = blobptr;
+            }
+
+            /* Skip past the number. */
+            p += len;
+        }
+
+        /*
+         * Now put together the actual key. Simplest way to do this is
+         * to assemble our own key blobs and feed them to the createkey
+         * functions; this is a bit faffy but it does mean we get all
+         * the sanity checks for free.
+         */
+        assert(privptr > 0);          /* should have bombed by now if not */
+        retkey = snew(struct ssh2_userkey);
+        retkey->alg = (key->keytype == OP_RSA ? &ssh_rsa : &ssh_dss);
+        retkey->data = retkey->alg->createkey(retkey->alg, blob, privptr,
+                                              blob+privptr,
+                                              blobptr-privptr);
+        if (!retkey->data) {
+            sfree(retkey);
+            errmsg = "unable to create key data structure";
+            goto error;
+        }
+
+    } else {
+        assert(0 && "Bad key type from load_openssh_pem_key");
+       errmsg = "Bad key type from load_openssh_pem_key";
        goto error;
     }
 
+    /*
+     * The old key format doesn't include a comment in the private
+     * key file.
+     */
     retkey->comment = dupstr("imported-openssh-key");
+
     errmsg = NULL;                     /* no error */
     retval = retkey;
 
@@ -720,8 +920,8 @@ struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase,
     return retval;
 }
 
-int openssh_write(const Filename *filename, struct ssh2_userkey *key,
-                 char *passphrase)
+int openssh_pem_write(const Filename *filename, struct ssh2_userkey *key,
+                      char *passphrase)
 {
     unsigned char *pubblob, *privblob, *spareblob;
     int publen, privlen, sparelen = 0;
@@ -729,7 +929,7 @@ int openssh_write(const Filename *filename, struct ssh2_userkey *key,
     int outlen;
     struct mpint_pos numbers[9];
     int nnumbers, pos, len, seqlen, i;
-    char *header, *footer;
+    const char *header, *footer;
     char zero[1];
     unsigned char iv[8];
     int ret = 0;
@@ -742,157 +942,207 @@ int openssh_write(const Filename *filename, struct ssh2_userkey *key,
     privblob = key->alg->private_blob(key->data, &privlen);
     spareblob = outblob = NULL;
 
+    outblob = NULL;
+    len = 0;
+
     /*
-     * Find the sequence of integers to be encoded into the OpenSSH
-     * key blob, and also decide on the header line.
+     * Encode the OpenSSH key blob, and also decide on the header
+     * line.
      */
-    if (key->alg == &ssh_rsa) {
-        int pos;
-        struct mpint_pos n, e, d, p, q, iqmp, dmp1, dmq1;
-        Bignum bd, bp, bq, bdmp1, bdmq1;
-
+    if (key->alg == &ssh_rsa || key->alg == &ssh_dss) {
         /*
-         * These blobs were generated from inside PuTTY, so we needn't
-         * treat them as untrusted.
+         * The RSA and DSS handlers share some code because the two
+         * key types have very similar ASN.1 representations, as a
+         * plain SEQUENCE of big integers. So we set up a list of
+         * bignums per key type and then construct the actual blob in
+         * common code after that.
          */
-        pos = 4 + GET_32BIT(pubblob);
-        pos += ssh2_read_mpint(pubblob+pos, publen-pos, &e);
-        pos += ssh2_read_mpint(pubblob+pos, publen-pos, &n);
-        pos = 0;
-        pos += ssh2_read_mpint(privblob+pos, privlen-pos, &d);
-        pos += ssh2_read_mpint(privblob+pos, privlen-pos, &p);
-        pos += ssh2_read_mpint(privblob+pos, privlen-pos, &q);
-        pos += ssh2_read_mpint(privblob+pos, privlen-pos, &iqmp);
+        if (key->alg == &ssh_rsa) {
+            int pos;
+            struct mpint_pos n, e, d, p, q, iqmp, dmp1, dmq1;
+            Bignum bd, bp, bq, bdmp1, bdmq1;
 
-        assert(e.start && iqmp.start); /* can't go wrong */
+            /*
+             * These blobs were generated from inside PuTTY, so we needn't
+             * treat them as untrusted.
+             */
+            pos = 4 + GET_32BIT(pubblob);
+            pos += ssh2_read_mpint(pubblob+pos, publen-pos, &e);
+            pos += ssh2_read_mpint(pubblob+pos, publen-pos, &n);
+            pos = 0;
+            pos += ssh2_read_mpint(privblob+pos, privlen-pos, &d);
+            pos += ssh2_read_mpint(privblob+pos, privlen-pos, &p);
+            pos += ssh2_read_mpint(privblob+pos, privlen-pos, &q);
+            pos += ssh2_read_mpint(privblob+pos, privlen-pos, &iqmp);
+
+            assert(e.start && iqmp.start); /* can't go wrong */
+
+            /* We also need d mod (p-1) and d mod (q-1). */
+            bd = bignum_from_bytes(d.start, d.bytes);
+            bp = bignum_from_bytes(p.start, p.bytes);
+            bq = bignum_from_bytes(q.start, q.bytes);
+            decbn(bp);
+            decbn(bq);
+            bdmp1 = bigmod(bd, bp);
+            bdmq1 = bigmod(bd, bq);
+            freebn(bd);
+            freebn(bp);
+            freebn(bq);
+
+            dmp1.bytes = (bignum_bitcount(bdmp1)+8)/8;
+            dmq1.bytes = (bignum_bitcount(bdmq1)+8)/8;
+            sparelen = dmp1.bytes + dmq1.bytes;
+            spareblob = snewn(sparelen, unsigned char);
+            dmp1.start = spareblob;
+            dmq1.start = spareblob + dmp1.bytes;
+            for (i = 0; i < dmp1.bytes; i++)
+                spareblob[i] = bignum_byte(bdmp1, dmp1.bytes-1 - i);
+            for (i = 0; i < dmq1.bytes; i++)
+                spareblob[i+dmp1.bytes] = bignum_byte(bdmq1, dmq1.bytes-1 - i);
+            freebn(bdmp1);
+            freebn(bdmq1);
+
+            numbers[0].start = zero; numbers[0].bytes = 1; zero[0] = '\0';
+            numbers[1] = n;
+            numbers[2] = e;
+            numbers[3] = d;
+            numbers[4] = p;
+            numbers[5] = q;
+            numbers[6] = dmp1;
+            numbers[7] = dmq1;
+            numbers[8] = iqmp;
+
+            nnumbers = 9;
+            header = "-----BEGIN RSA PRIVATE KEY-----\n";
+            footer = "-----END RSA PRIVATE KEY-----\n";
+        } else {                       /* ssh-dss */
+            int pos;
+            struct mpint_pos p, q, g, y, x;
 
-        /* We also need d mod (p-1) and d mod (q-1). */
-        bd = bignum_from_bytes(d.start, d.bytes);
-        bp = bignum_from_bytes(p.start, p.bytes);
-        bq = bignum_from_bytes(q.start, q.bytes);
-        decbn(bp);
-        decbn(bq);
-        bdmp1 = bigmod(bd, bp);
-        bdmq1 = bigmod(bd, bq);
-        freebn(bd);
-        freebn(bp);
-        freebn(bq);
-
-        dmp1.bytes = (bignum_bitcount(bdmp1)+8)/8;
-        dmq1.bytes = (bignum_bitcount(bdmq1)+8)/8;
-        sparelen = dmp1.bytes + dmq1.bytes;
-        spareblob = snewn(sparelen, unsigned char);
-        dmp1.start = spareblob;
-        dmq1.start = spareblob + dmp1.bytes;
-        for (i = 0; i < dmp1.bytes; i++)
-            spareblob[i] = bignum_byte(bdmp1, dmp1.bytes-1 - i);
-        for (i = 0; i < dmq1.bytes; i++)
-            spareblob[i+dmp1.bytes] = bignum_byte(bdmq1, dmq1.bytes-1 - i);
-        freebn(bdmp1);
-        freebn(bdmq1);
-
-        numbers[0].start = zero; numbers[0].bytes = 1; zero[0] = '\0';
-        numbers[1] = n;
-        numbers[2] = e;
-        numbers[3] = d;
-        numbers[4] = p;
-        numbers[5] = q;
-        numbers[6] = dmp1;
-        numbers[7] = dmq1;
-        numbers[8] = iqmp;
-
-        nnumbers = 9;
-        header = "-----BEGIN RSA PRIVATE KEY-----\n";
-        footer = "-----END RSA PRIVATE KEY-----\n";
-    } else if (key->alg == &ssh_dss) {
-        int pos;
-        struct mpint_pos p, q, g, y, x;
+            /*
+             * These blobs were generated from inside PuTTY, so we needn't
+             * treat them as untrusted.
+             */
+            pos = 4 + GET_32BIT(pubblob);
+            pos += ssh2_read_mpint(pubblob+pos, publen-pos, &p);
+            pos += ssh2_read_mpint(pubblob+pos, publen-pos, &q);
+            pos += ssh2_read_mpint(pubblob+pos, publen-pos, &g);
+            pos += ssh2_read_mpint(pubblob+pos, publen-pos, &y);
+            pos = 0;
+            pos += ssh2_read_mpint(privblob+pos, privlen-pos, &x);
+
+            assert(y.start && x.start); /* can't go wrong */
+
+            numbers[0].start = zero; numbers[0].bytes = 1; zero[0] = '\0';
+            numbers[1] = p;
+            numbers[2] = q;
+            numbers[3] = g;
+            numbers[4] = y;
+            numbers[5] = x;
+
+            nnumbers = 6;
+            header = "-----BEGIN DSA PRIVATE KEY-----\n";
+            footer = "-----END DSA PRIVATE KEY-----\n";
+        }
 
         /*
-         * These blobs were generated from inside PuTTY, so we needn't
-         * treat them as untrusted.
+         * Now count up the total size of the ASN.1 encoded integers,
+         * so as to determine the length of the containing SEQUENCE.
          */
-        pos = 4 + GET_32BIT(pubblob);
-        pos += ssh2_read_mpint(pubblob+pos, publen-pos, &p);
-        pos += ssh2_read_mpint(pubblob+pos, publen-pos, &q);
-        pos += ssh2_read_mpint(pubblob+pos, publen-pos, &g);
-        pos += ssh2_read_mpint(pubblob+pos, publen-pos, &y);
-        pos = 0;
-        pos += ssh2_read_mpint(privblob+pos, privlen-pos, &x);
+        len = 0;
+        for (i = 0; i < nnumbers; i++) {
+            len += ber_write_id_len(NULL, 2, numbers[i].bytes, 0);
+            len += numbers[i].bytes;
+        }
+        seqlen = len;
+        /* Now add on the SEQUENCE header. */
+        len += ber_write_id_len(NULL, 16, seqlen, ASN1_CONSTRUCTED);
 
-        assert(y.start && x.start); /* can't go wrong */
+        /*
+         * Now we know how big outblob needs to be. Allocate it.
+         */
+        outblob = snewn(len, unsigned char);
 
-        numbers[0].start = zero; numbers[0].bytes = 1; zero[0] = '\0'; 
-        numbers[1] = p;
-        numbers[2] = q;
-        numbers[3] = g;
-        numbers[4] = y;
-        numbers[5] = x;
+        /*
+         * And write the data into it.
+         */
+        pos = 0;
+        pos += ber_write_id_len(outblob+pos, 16, seqlen, ASN1_CONSTRUCTED);
+        for (i = 0; i < nnumbers; i++) {
+            pos += ber_write_id_len(outblob+pos, 2, numbers[i].bytes, 0);
+            memcpy(outblob+pos, numbers[i].start, numbers[i].bytes);
+            pos += numbers[i].bytes;
+        }
+    } else if (key->alg == &ssh_ecdsa_nistp256 ||
+               key->alg == &ssh_ecdsa_nistp384 ||
+               key->alg == &ssh_ecdsa_nistp521) {
+        const unsigned char *oid;
+        int oidlen;
+        int pointlen;
 
-        nnumbers = 6;
-        header = "-----BEGIN DSA PRIVATE KEY-----\n";
-        footer = "-----END DSA PRIVATE KEY-----\n";
+        /*
+         * Structure of asn1:
+         * SEQUENCE
+         *   INTEGER 1
+         *   OCTET STRING (private key)
+         *   [0]
+         *     OID (curve)
+         *   [1]
+         *     BIT STRING (0x00 public key point)
+         */
+        oid = ec_alg_oid(key->alg, &oidlen);
+        pointlen = (((struct ec_key *)key->data)->publicKey.curve->fieldBits
+                    + 7) / 8 * 2;
+
+        len = ber_write_id_len(NULL, 2, 1, 0);
+        len += 1;
+        len += ber_write_id_len(NULL, 4, privlen - 4, 0);
+        len+= privlen - 4;
+        len += ber_write_id_len(NULL, 0, oidlen +
+                                ber_write_id_len(NULL, 6, oidlen, 0),
+                                ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED);
+        len += ber_write_id_len(NULL, 6, oidlen, 0);
+        len += oidlen;
+        len += ber_write_id_len(NULL, 1, 2 + pointlen +
+                                ber_write_id_len(NULL, 3, 2 + pointlen, 0),
+                                ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED);
+        len += ber_write_id_len(NULL, 3, 2 + pointlen, 0);
+        len += 2 + pointlen;
+
+        seqlen = len;
+        len += ber_write_id_len(NULL, 16, seqlen, ASN1_CONSTRUCTED);
+
+        outblob = snewn(len, unsigned char);
+        assert(outblob);
+
+        pos = 0;
+        pos += ber_write_id_len(outblob+pos, 16, seqlen, ASN1_CONSTRUCTED);
+        pos += ber_write_id_len(outblob+pos, 2, 1, 0);
+        outblob[pos++] = 1;
+        pos += ber_write_id_len(outblob+pos, 4, privlen - 4, 0);
+        memcpy(outblob+pos, privblob + 4, privlen - 4);
+        pos += privlen - 4;
+        pos += ber_write_id_len(outblob+pos, 0, oidlen +
+                                ber_write_id_len(NULL, 6, oidlen, 0),
+                                ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED);
+        pos += ber_write_id_len(outblob+pos, 6, oidlen, 0);
+        memcpy(outblob+pos, oid, oidlen);
+        pos += oidlen;
+        pos += ber_write_id_len(outblob+pos, 1, 2 + pointlen +
+                                ber_write_id_len(NULL, 3, 2 + pointlen, 0),
+                                ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED);
+        pos += ber_write_id_len(outblob+pos, 3, 2 + pointlen, 0);
+        outblob[pos++] = 0;
+        memcpy(outblob+pos, pubblob+39, 1 + pointlen);
+        pos += 1 + pointlen;
+
+        header = "-----BEGIN EC PRIVATE KEY-----\n";
+        footer = "-----END EC PRIVATE KEY-----\n";
     } else {
         assert(0);                     /* zoinks! */
        exit(1); /* XXX: GCC doesn't understand assert() on some systems. */
     }
 
-    /*
-     * Now count up the total size of the ASN.1 encoded integers,
-     * so as to determine the length of the containing SEQUENCE.
-     */
-    len = 0;
-    for (i = 0; i < nnumbers; i++) {
-       len += ber_write_id_len(NULL, 2, numbers[i].bytes, 0);
-       len += numbers[i].bytes;
-    }
-    seqlen = len;
-    /* Now add on the SEQUENCE header. */
-    len += ber_write_id_len(NULL, 16, seqlen, ASN1_CONSTRUCTED);
-    /* Round up to the cipher block size, ensuring we have at least one
-     * byte of padding (see below). */
-    outlen = len;
-    if (passphrase)
-       outlen = (outlen+8) &~ 7;
-
-    /*
-     * Now we know how big outblob needs to be. Allocate it.
-     */
-    outblob = snewn(outlen, unsigned char);
-
-    /*
-     * And write the data into it.
-     */
-    pos = 0;
-    pos += ber_write_id_len(outblob+pos, 16, seqlen, ASN1_CONSTRUCTED);
-    for (i = 0; i < nnumbers; i++) {
-       pos += ber_write_id_len(outblob+pos, 2, numbers[i].bytes, 0);
-       memcpy(outblob+pos, numbers[i].start, numbers[i].bytes);
-       pos += numbers[i].bytes;
-    }
-
-    /*
-     * Padding on OpenSSH keys is deterministic. The number of
-     * padding bytes is always more than zero, and always at most
-     * the cipher block length. The value of each padding byte is
-     * equal to the number of padding bytes. So a plaintext that's
-     * an exact multiple of the block size will be padded with 08
-     * 08 08 08 08 08 08 08 (assuming a 64-bit block cipher); a
-     * plaintext one byte less than a multiple of the block size
-     * will be padded with just 01.
-     * 
-     * This enables the OpenSSL key decryption function to strip
-     * off the padding algorithmically and return the unpadded
-     * plaintext to the next layer: it looks at the final byte, and
-     * then expects to find that many bytes at the end of the data
-     * with the same value. Those are all removed and the rest is
-     * returned.
-     */
-    assert(pos == len);
-    while (pos < outlen) {
-        outblob[pos++] = outlen - len;
-    }
-
     /*
      * Encrypt the key.
      *
@@ -900,6 +1150,44 @@ int openssh_write(const Filename *filename, struct ssh2_userkey *key,
      * old-style 3DES.
      */
     if (passphrase) {
+       struct MD5Context md5c;
+       unsigned char keybuf[32];
+
+        /*
+         * Round up to the cipher block size, ensuring we have at
+         * least one byte of padding (see below).
+         */
+        outlen = (len+8) &~ 7;
+        {
+            unsigned char *tmp = snewn(outlen, unsigned char);
+            memcpy(tmp, outblob, len);
+            smemclr(outblob, len);
+            sfree(outblob);
+            outblob = tmp;
+        }
+
+        /*
+         * Padding on OpenSSH keys is deterministic. The number of
+         * padding bytes is always more than zero, and always at most
+         * the cipher block length. The value of each padding byte is
+         * equal to the number of padding bytes. So a plaintext that's
+         * an exact multiple of the block size will be padded with 08
+         * 08 08 08 08 08 08 08 (assuming a 64-bit block cipher); a
+         * plaintext one byte less than a multiple of the block size
+         * will be padded with just 01.
+         *
+         * This enables the OpenSSL key decryption function to strip
+         * off the padding algorithmically and return the unpadded
+         * plaintext to the next layer: it looks at the final byte, and
+         * then expects to find that many bytes at the end of the data
+         * with the same value. Those are all removed and the rest is
+         * returned.
+         */
+        assert(pos == len);
+        while (pos < outlen) {
+            outblob[pos++] = outlen - len;
+        }
+
        /*
         * Invent an iv. Then derive encryption key from passphrase
         * and iv/salt:
@@ -909,9 +1197,6 @@ int openssh_write(const Filename *filename, struct ssh2_userkey *key,
         *  - block C would be MD5(B || passphrase || iv) and so on
         *  - encryption key is the first N bytes of A || B
         */
-       struct MD5Context md5c;
-       unsigned char keybuf[32];
-
        for (i = 0; i < 8; i++) iv[i] = random_byte();
 
        MD5Init(&md5c);
@@ -932,6 +1217,12 @@ int openssh_write(const Filename *filename, struct ssh2_userkey *key,
 
         smemclr(&md5c, sizeof(md5c));
         smemclr(keybuf, sizeof(keybuf));
+    } else {
+        /*
+         * If no encryption, the blob has exactly its original
+         * cleartext size.
+         */
+        outlen = len;
     }
 
     /*
@@ -973,6 +1264,654 @@ int openssh_write(const Filename *filename, struct ssh2_userkey *key,
     return ret;
 }
 
+/* ----------------------------------------------------------------------
+ * Code to read and write OpenSSH private keys in the new-style format.
+ */
+
+typedef enum {
+    ON_E_NONE, ON_E_AES256CBC
+} openssh_new_cipher;
+typedef enum {
+    ON_K_NONE, ON_K_BCRYPT
+} openssh_new_kdf;
+
+struct openssh_new_key {
+    openssh_new_cipher cipher;
+    openssh_new_kdf kdf;
+    union {
+        struct {
+            int rounds;
+            /* This points to a position within keyblob, not a
+             * separately allocated thing */
+            const unsigned char *salt;
+            int saltlen;
+        } bcrypt;
+    } kdfopts;
+    int nkeys, key_wanted;
+    /* This too points to a position within keyblob */
+    unsigned char *privatestr;
+    int privatelen;
+
+    unsigned char *keyblob;
+    int keyblob_len, keyblob_size;
+};
+
+static struct openssh_new_key *load_openssh_new_key(const Filename *filename,
+                                                    const char **errmsg_p)
+{
+    struct openssh_new_key *ret;
+    FILE *fp = NULL;
+    char *line = NULL;
+    const char *errmsg;
+    char *p;
+    char base64_bit[4];
+    int base64_chars = 0;
+    const void *filedata;
+    int filelen;
+    const void *string, *kdfopts, *bcryptsalt, *pubkey;
+    int stringlen, kdfoptlen, bcryptsaltlen, pubkeylen;
+    unsigned bcryptrounds, nkeys, key_index;
+
+    ret = snew(struct openssh_new_key);
+    ret->keyblob = NULL;
+    ret->keyblob_len = ret->keyblob_size = 0;
+
+    fp = f_open(filename, "r", FALSE);
+    if (!fp) {
+       errmsg = "unable to open key file";
+       goto error;
+    }
+
+    if (!(line = fgetline(fp))) {
+       errmsg = "unexpected end of file";
+       goto error;
+    }
+    strip_crlf(line);
+    if (0 != strcmp(line, "-----BEGIN OPENSSH PRIVATE KEY-----")) {
+       errmsg = "file does not begin with OpenSSH new-style key header";
+       goto error;
+    }
+    smemclr(line, strlen(line));
+    sfree(line);
+    line = NULL;
+
+    while (1) {
+       if (!(line = fgetline(fp))) {
+           errmsg = "unexpected end of file";
+           goto error;
+       }
+       strip_crlf(line);
+       if (0 == strcmp(line, "-----END OPENSSH PRIVATE KEY-----")) {
+            sfree(line);
+            line = NULL;
+           break;                     /* done */
+        }
+
+        p = line;
+        while (isbase64(*p)) {
+            base64_bit[base64_chars++] = *p;
+            if (base64_chars == 4) {
+                unsigned char out[3];
+                int len;
+
+                base64_chars = 0;
+
+                len = base64_decode_atom(base64_bit, out);
+
+                if (len <= 0) {
+                    errmsg = "invalid base64 encoding";
+                    goto error;
+                }
+
+                if (ret->keyblob_len + len > ret->keyblob_size) {
+                    ret->keyblob_size = ret->keyblob_len + len + 256;
+                    ret->keyblob = sresize(ret->keyblob, ret->keyblob_size,
+                                           unsigned char);
+                }
+
+                memcpy(ret->keyblob + ret->keyblob_len, out, len);
+                ret->keyblob_len += len;
+
+                smemclr(out, sizeof(out));
+            }
+
+            p++;
+        }
+       smemclr(line, strlen(line));
+       sfree(line);
+       line = NULL;
+    }
+
+    fclose(fp);
+    fp = NULL;
+
+    if (ret->keyblob_len == 0 || !ret->keyblob) {
+       errmsg = "key body not present";
+       goto error;
+    }
+
+    filedata = ret->keyblob;
+    filelen = ret->keyblob_len;
+
+    if (filelen < 15 || 0 != memcmp(filedata, "openssh-key-v1\0", 15)) {
+        errmsg = "new-style OpenSSH magic number missing\n";
+        goto error;
+    }
+    filedata = (const char *)filedata + 15;
+    filelen -= 15;
+
+    if (!(string = get_ssh_string(&filelen, &filedata, &stringlen))) {
+        errmsg = "encountered EOF before cipher name\n";
+        goto error;
+    }
+    if (match_ssh_id(stringlen, string, "none")) {
+        ret->cipher = ON_E_NONE;
+    } else if (match_ssh_id(stringlen, string, "aes256-cbc")) {
+        ret->cipher = ON_E_AES256CBC;
+    } else {
+        errmsg = "unrecognised cipher name\n";
+        goto error;
+    }
+
+    if (!(string = get_ssh_string(&filelen, &filedata, &stringlen))) {
+        errmsg = "encountered EOF before kdf name\n";
+        goto error;
+    }
+    if (match_ssh_id(stringlen, string, "none")) {
+        ret->kdf = ON_K_NONE;
+    } else if (match_ssh_id(stringlen, string, "bcrypt")) {
+        ret->kdf = ON_K_BCRYPT;
+    } else {
+        errmsg = "unrecognised kdf name\n";
+        goto error;
+    }
+
+    if (!(kdfopts = get_ssh_string(&filelen, &filedata, &kdfoptlen))) {
+        errmsg = "encountered EOF before kdf options\n";
+        goto error;
+    }
+    switch (ret->kdf) {
+      case ON_K_NONE:
+        if (kdfoptlen != 0) {
+            errmsg = "expected empty options string for 'none' kdf";
+            goto error;
+        }
+        break;
+      case ON_K_BCRYPT:
+        if (!(bcryptsalt = get_ssh_string(&kdfoptlen, &kdfopts,
+                                          &bcryptsaltlen))) {
+            errmsg = "bcrypt options string did not contain salt\n";
+            goto error;
+        }
+        if (!get_ssh_uint32(&kdfoptlen, &kdfopts, &bcryptrounds)) {
+            errmsg = "bcrypt options string did not contain round count\n";
+            goto error;
+        }
+        ret->kdfopts.bcrypt.salt = bcryptsalt;
+        ret->kdfopts.bcrypt.saltlen = bcryptsaltlen;
+        ret->kdfopts.bcrypt.rounds = bcryptrounds;
+        break;
+    }
+
+    /*
+     * At this point we expect a uint32 saying how many keys are
+     * stored in this file. OpenSSH new-style key files can
+     * contain more than one. Currently we don't have any user
+     * interface to specify which one we're trying to extract, so
+     * we just bomb out with an error if more than one is found in
+     * the file. However, I've put in all the mechanism here to
+     * extract the nth one for a given n, in case we later connect
+     * up some UI to that mechanism. Just arrange that the
+     * 'key_wanted' field is set to a value in the range [0,
+     * nkeys) by some mechanism.
+     */
+    if (!get_ssh_uint32(&filelen, &filedata, &nkeys)) {
+        errmsg = "encountered EOF before key count\n";
+        goto error;
+    }
+    if (nkeys != 1) {
+        errmsg = "multiple keys in new-style OpenSSH key file "
+            "not supported\n";
+        goto error;
+    }
+    ret->nkeys = nkeys;
+    ret->key_wanted = 0;
+
+    for (key_index = 0; key_index < nkeys; key_index++) {
+        if (!(pubkey = get_ssh_string(&filelen, &filedata, &pubkeylen))) {
+            errmsg = "encountered EOF before kdf options\n";
+            goto error;
+        }
+    }
+
+    /*
+     * Now we expect a string containing the encrypted part of the
+     * key file.
+     */
+    if (!(string = get_ssh_string(&filelen, &filedata, &stringlen))) {
+        errmsg = "encountered EOF before private key container\n";
+        goto error;
+    }
+    ret->privatestr = (unsigned char *)string;
+    ret->privatelen = stringlen;
+
+    /*
+     * And now we're done, until asked to actually decrypt.
+     */
+
+    smemclr(base64_bit, sizeof(base64_bit));
+    if (errmsg_p) *errmsg_p = NULL;
+    return ret;
+
+    error:
+    if (line) {
+       smemclr(line, strlen(line));
+       sfree(line);
+       line = NULL;
+    }
+    smemclr(base64_bit, sizeof(base64_bit));
+    if (ret) {
+       if (ret->keyblob) {
+            smemclr(ret->keyblob, ret->keyblob_size);
+            sfree(ret->keyblob);
+        }
+        smemclr(ret, sizeof(*ret));
+       sfree(ret);
+    }
+    if (errmsg_p) *errmsg_p = errmsg;
+    if (fp) fclose(fp);
+    return NULL;
+}
+
+int openssh_new_encrypted(const Filename *filename)
+{
+    struct openssh_new_key *key = load_openssh_new_key(filename, NULL);
+    int ret;
+
+    if (!key)
+       return 0;
+    ret = (key->cipher != ON_E_NONE);
+    smemclr(key->keyblob, key->keyblob_size);
+    sfree(key->keyblob);
+    smemclr(key, sizeof(*key));
+    sfree(key);
+    return ret;
+}
+
+struct ssh2_userkey *openssh_new_read(const Filename *filename,
+                                      char *passphrase,
+                                      const char **errmsg_p)
+{
+    struct openssh_new_key *key = load_openssh_new_key(filename, errmsg_p);
+    struct ssh2_userkey *retkey;
+    int i;
+    struct ssh2_userkey *retval = NULL;
+    const char *errmsg;
+    unsigned char *blob;
+    int blobsize = 0;
+    unsigned checkint0, checkint1;
+    const void *priv, *string;
+    int privlen, stringlen, key_index;
+    const struct ssh_signkey *alg;
+
+    blob = NULL;
+
+    if (!key)
+       return NULL;
+
+    if (key->cipher != ON_E_NONE) {
+        unsigned char keybuf[48];
+        int keysize;
+
+        /*
+         * Construct the decryption key, and decrypt the string.
+         */
+        switch (key->cipher) {
+          case ON_E_NONE:
+            keysize = 0;
+            break;
+          case ON_E_AES256CBC:
+            keysize = 48;              /* 32 byte key + 16 byte IV */
+            break;
+          default:
+            assert(0 && "Bad cipher enumeration value");
+        }
+        assert(keysize <= sizeof(keybuf));
+        switch (key->kdf) {
+          case ON_K_NONE:
+            memset(keybuf, 0, keysize);
+            break;
+          case ON_K_BCRYPT:
+            openssh_bcrypt(passphrase,
+                           key->kdfopts.bcrypt.salt,
+                           key->kdfopts.bcrypt.saltlen,
+                           key->kdfopts.bcrypt.rounds,
+                           keybuf, keysize);
+            break;
+          default:
+            assert(0 && "Bad kdf enumeration value");
+        }
+        switch (key->cipher) {
+          case ON_E_NONE:
+            break;
+          case ON_E_AES256CBC:
+            if (key->privatelen % 16 != 0) {
+                errmsg = "private key container length is not a"
+                    " multiple of AES block size\n";
+                goto error;
+            }
+            {
+                void *ctx = aes_make_context();
+                aes256_key(ctx, keybuf);
+                aes_iv(ctx, keybuf + 32);
+                aes_ssh2_decrypt_blk(ctx, key->privatestr,
+                                     key->privatelen);
+                aes_free_context(ctx);
+            }
+            break;
+          default:
+            assert(0 && "Bad cipher enumeration value");
+        }
+    }
+
+    /*
+     * Now parse the entire encrypted section, and extract the key
+     * identified by key_wanted.
+     */
+    priv = key->privatestr;
+    privlen = key->privatelen;
+
+    if (!get_ssh_uint32(&privlen, &priv, &checkint0) ||
+        !get_ssh_uint32(&privlen, &priv, &checkint1) ||
+        checkint0 != checkint1) {
+        errmsg = "decryption check failed";
+        goto error;
+    }
+
+    retkey = NULL;
+    for (key_index = 0; key_index < key->nkeys; key_index++) {
+        const unsigned char *thiskey;
+        int thiskeylen;
+
+        /*
+         * Read the key type, which will tell us how to scan over
+         * the key to get to the next one.
+         */
+        if (!(string = get_ssh_string(&privlen, &priv, &stringlen))) {
+            errmsg = "expected key type in private string";
+            goto error;
+        }
+
+        /*
+         * Preliminary key type identification, and decide how
+         * many pieces of key we expect to see. Currently
+         * (conveniently) all key types can be seen as some number
+         * of strings, so we just need to know how many of them to
+         * skip over. (The numbers below exclude the key comment.)
+         */
+        {
+            /* find_pubkey_alg needs a zero-terminated copy of the
+             * algorithm name */
+            char *name_zt = dupprintf("%.*s", stringlen, (char *)string);
+            alg = find_pubkey_alg(name_zt);
+            sfree(name_zt);
+        }
+
+        if (!alg) {
+            errmsg = "private key type not recognised\n";
+            goto error;
+        }
+
+        thiskey = priv;
+
+        /*
+         * Skip over the pieces of key.
+         */
+        for (i = 0; i < alg->openssh_private_npieces; i++) {
+            if (!(string = get_ssh_string(&privlen, &priv, &stringlen))) {
+                errmsg = "ran out of data in mid-private-key";
+                goto error;
+            }
+        }
+
+        thiskeylen = (int)((const unsigned char *)priv -
+                           (const unsigned char *)thiskey);
+        if (key_index == key->key_wanted) {
+            retkey = snew(struct ssh2_userkey);
+            retkey->alg = alg;
+            retkey->data = alg->openssh_createkey(alg, &thiskey, &thiskeylen);
+            if (!retkey->data) {
+                sfree(retkey);
+                errmsg = "unable to create key data structure";
+                goto error;
+            }
+        }
+
+        /*
+         * Read the key comment.
+         */
+        if (!(string = get_ssh_string(&privlen, &priv, &stringlen))) {
+            errmsg = "ran out of data at key comment";
+            goto error;
+        }
+        if (key_index == key->key_wanted) {
+            assert(retkey);
+            retkey->comment = dupprintf("%.*s", stringlen,
+                                        (const char *)string);
+        }
+    }
+
+    if (!retkey) {
+        errmsg = "key index out of range";
+        goto error;
+    }
+
+    /*
+     * Now we expect nothing left but padding.
+     */
+    for (i = 0; i < privlen; i++) {
+        if (((const unsigned char *)priv)[i] != (unsigned char)(i+1)) {
+            errmsg = "padding at end of private string did not match";
+            goto error;
+        }
+    }
+
+    errmsg = NULL;                     /* no error */
+    retval = retkey;
+
+    error:
+    if (blob) {
+        smemclr(blob, blobsize);
+        sfree(blob);
+    }
+    smemclr(key->keyblob, key->keyblob_size);
+    sfree(key->keyblob);
+    smemclr(key, sizeof(*key));
+    sfree(key);
+    if (errmsg_p) *errmsg_p = errmsg;
+    return retval;
+}
+
+int openssh_new_write(const Filename *filename, struct ssh2_userkey *key,
+                      char *passphrase)
+{
+    unsigned char *pubblob, *privblob, *outblob, *p;
+    unsigned char *private_section_start, *private_section_length_field;
+    int publen, privlen, commentlen, maxsize, padvalue, i;
+    unsigned checkint;
+    int ret = 0;
+    unsigned char bcrypt_salt[16];
+    const int bcrypt_rounds = 16;
+    FILE *fp;
+
+    /*
+     * Fetch the key blobs and find out the lengths of things.
+     */
+    pubblob = key->alg->public_blob(key->data, &publen);
+    i = key->alg->openssh_fmtkey(key->data, NULL, 0);
+    privblob = snewn(i, unsigned char);
+    privlen = key->alg->openssh_fmtkey(key->data, privblob, i);
+    assert(privlen == i);
+    commentlen = strlen(key->comment);
+
+    /*
+     * Allocate enough space for the full binary key format. No need
+     * to be absolutely precise here.
+     */
+    maxsize = (16 +                    /* magic number */
+               32 +                    /* cipher name string */
+               32 +                    /* kdf name string */
+               64 +                    /* kdf options string */
+               4 +                     /* key count */
+               4+publen +              /* public key string */
+               4 +                     /* string header for private section */
+               8 +                     /* checkint x 2 */
+               4+strlen(key->alg->name) + /* key type string */
+               privlen +               /* private blob */
+               4+commentlen +          /* comment string */
+               16);                    /* padding at end of private section */
+    outblob = snewn(maxsize, unsigned char);
+
+    /*
+     * Construct the cleartext version of the blob.
+     */
+    p = outblob;
+
+    /* Magic number. */
+    memcpy(p, "openssh-key-v1\0", 15);
+    p += 15;
+
+    /* Cipher and kdf names, and kdf options. */
+    if (!passphrase) {
+        memset(bcrypt_salt, 0, sizeof(bcrypt_salt)); /* prevent warnings */
+        p += put_string_z(p, "none");
+        p += put_string_z(p, "none");
+        p += put_string_z(p, "");
+    } else {
+        unsigned char *q;
+        for (i = 0; i < (int)sizeof(bcrypt_salt); i++)
+            bcrypt_salt[i] = random_byte();
+        p += put_string_z(p, "aes256-cbc");
+        p += put_string_z(p, "bcrypt");
+        q = p;
+        p += 4;
+        p += put_string(p, bcrypt_salt, sizeof(bcrypt_salt));
+        p += put_uint32(p, bcrypt_rounds);
+        PUT_32BIT_MSB_FIRST(q, (unsigned)(p - (q+4)));
+    }
+
+    /* Number of keys. */
+    p += put_uint32(p, 1);
+
+    /* Public blob. */
+    p += put_string(p, pubblob, publen);
+
+    /* Begin private section. */
+    private_section_length_field = p;
+    p += 4;
+    private_section_start = p;
+
+    /* checkint. */
+    checkint = 0;
+    for (i = 0; i < 4; i++)
+        checkint = (checkint << 8) + random_byte();
+    p += put_uint32(p, checkint);
+    p += put_uint32(p, checkint);
+
+    /* Private key. The main private blob goes inline, with no string
+     * wrapper. */
+    p += put_string_z(p, key->alg->name);
+    memcpy(p, privblob, privlen);
+    p += privlen;
+
+    /* Comment. */
+    p += put_string_z(p, key->comment);
+
+    /* Pad out the encrypted section. */
+    padvalue = 1;
+    do {
+        *p++ = padvalue++;
+    } while ((p - private_section_start) & 15);
+
+    assert(p - outblob < maxsize);
+
+    /* Go back and fill in the length field for the private section. */
+    PUT_32BIT_MSB_FIRST(private_section_length_field,
+                        p - private_section_start);
+
+    if (passphrase) {
+        /*
+         * Encrypt the private section. We need 48 bytes of key
+         * material: 32 bytes AES key + 16 bytes iv.
+         */
+        unsigned char keybuf[48];
+        void *ctx;
+
+        openssh_bcrypt(passphrase,
+                       bcrypt_salt, sizeof(bcrypt_salt), bcrypt_rounds,
+                       keybuf, sizeof(keybuf));
+
+        ctx = aes_make_context();
+        aes256_key(ctx, keybuf);
+        aes_iv(ctx, keybuf + 32);
+        aes_ssh2_encrypt_blk(ctx, private_section_start,
+                             p - private_section_start);
+        aes_free_context(ctx);
+
+        smemclr(keybuf, sizeof(keybuf));
+    }
+
+    /*
+     * And save it. We'll use Unix line endings just in case it's
+     * subsequently transferred in binary mode.
+     */
+    fp = f_open(filename, "wb", TRUE);      /* ensure Unix line endings */
+    if (!fp)
+       goto error;
+    fputs("-----BEGIN OPENSSH PRIVATE KEY-----\n", fp);
+    base64_encode(fp, outblob, p - outblob, 64);
+    fputs("-----END OPENSSH PRIVATE KEY-----\n", fp);
+    fclose(fp);
+    ret = 1;
+
+    error:
+    if (outblob) {
+        smemclr(outblob, maxsize);
+        sfree(outblob);
+    }
+    if (privblob) {
+        smemclr(privblob, privlen);
+        sfree(privblob);
+    }
+    if (pubblob) {
+        smemclr(pubblob, publen);
+        sfree(pubblob);
+    }
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * The switch function openssh_auto_write(), which chooses one of the
+ * concrete OpenSSH output formats based on the key type.
+ */
+int openssh_auto_write(const Filename *filename, struct ssh2_userkey *key,
+                       char *passphrase)
+{
+    /*
+     * The old OpenSSH format supports a fixed list of key types. We
+     * assume that anything not in that fixed list is newer, and hence
+     * will use the new format.
+     */
+    if (key->alg == &ssh_dss ||
+        key->alg == &ssh_rsa ||
+        key->alg == &ssh_ecdsa_nistp256 ||
+        key->alg == &ssh_ecdsa_nistp384 ||
+        key->alg == &ssh_ecdsa_nistp521)
+        return openssh_pem_write(filename, key, passphrase);
+    else
+        return openssh_new_write(filename, key, passphrase);
+}
+
 /* ----------------------------------------------------------------------
  * Code to read ssh.com private keys.
  */
@@ -1062,7 +2001,8 @@ static struct sshcom_key *load_sshcom_key(const Filename *filename,
     FILE *fp;
     char *line = NULL;
     int hdrstart, len;
-    char *errmsg, *p;
+    const char *errmsg;
+    char *p;
     int headers_done;
     char base64_bit[4];
     int base64_chars = 0;
@@ -1307,7 +2247,7 @@ struct ssh2_userkey *sshcom_read(const Filename *filename, char *passphrase,
                                 const char **errmsg_p)
 {
     struct sshcom_key *key = load_sshcom_key(filename, errmsg_p);
-    char *errmsg;
+    const char *errmsg;
     int pos, len;
     const char prefix_rsa[] = "if-modn{sign{rsa";
     const char prefix_dsa[] = "dl-modp{sign{dsa";
@@ -1518,7 +2458,7 @@ struct ssh2_userkey *sshcom_read(const Filename *filename, char *passphrase,
 
     retkey = snew(struct ssh2_userkey);
     retkey->alg = alg;
-    retkey->data = alg->createkey(blob, publen, blob+publen, privlen);
+    retkey->data = alg->createkey(alg, blob, publen, blob+publen, privlen);
     if (!retkey->data) {
        sfree(retkey);
        errmsg = "unable to create key data structure";
@@ -1551,7 +2491,7 @@ int sshcom_write(const Filename *filename, struct ssh2_userkey *key,
     int outlen;
     struct mpint_pos numbers[6];
     int nnumbers, initial_zero, pos, lenpos, i;
-    char *type;
+    const char *type;
     char *ciphertext;
     int cipherlen;
     int ret = 0;
@@ -1647,7 +2587,7 @@ int sshcom_write(const Filename *filename, struct ssh2_userkey *key,
     pos += 4;                         /* length field, fill in later */
     pos += put_string(outblob+pos, type, strlen(type));
     {
-       char *ciphertype = passphrase ? "3des-cbc" : "none";
+       const char *ciphertype = passphrase ? "3des-cbc" : "none";
        pos += put_string(outblob+pos, ciphertype, strlen(ciphertype));
     }
     lenpos = pos;                     /* remember this position */
diff --git a/ldisc.c b/ldisc.c
index 311854060e03038b84f9f9e90ae4718e95a1a7b4..320a93607c3cb6204732bf0e4889e4a9db9e32ba 100644 (file)
--- a/ldisc.c
+++ b/ldisc.c
@@ -22,7 +22,7 @@
                       (ldisc->back->ldisc(ldisc->backhandle, LD_EDIT) || \
                           term_ldisc(ldisc->term, LD_EDIT))))
 
-static void c_write(Ldisc ldisc, char *buf, int len)
+static void c_write(Ldisc ldisc, const char *buf, int len)
 {
     from_backend(ldisc->frontend, 0, buf, len);
 }
@@ -128,28 +128,19 @@ void ldisc_free(void *handle)
     sfree(ldisc);
 }
 
-void ldisc_send(void *handle, char *buf, int len, int interactive)
+void ldisc_echoedit_update(void *handle)
+{
+    Ldisc ldisc = (Ldisc) handle;
+    frontend_echoedit_update(ldisc->frontend, ECHOING, EDITING);
+}
+
+void ldisc_send(void *handle, const char *buf, int len, int interactive)
 {
     Ldisc ldisc = (Ldisc) handle;
     int keyflag = 0;
-    /*
-     * Called with len=0 when the options change. We must inform
-     * the front end in case it needs to know.
-     */
-    if (len == 0) {
-       ldisc_update(ldisc->frontend, ECHOING, EDITING);
-       return;
-    }
 
-    /*
-     * If that wasn't true, then we expect ldisc->term to be non-NULL
-     * hereafter. (The only front ends which have an ldisc but no term
-     * are those which do networking but no terminal emulation, in
-     * which case they need the above if statement to handle
-     * ldisc_updates passed from the back ends, but should never send
-     * any actual input through this function.)
-     */
     assert(ldisc->term);
+    assert(len);
 
     /*
      * Notify the front end that something was pressed, in case
index fe0da8a6ae25217ca1eab4110b6ce2a584d155e0..1634bc43fd603520927dce87b558f720ef8cbf92 100644 (file)
@@ -13,7 +13,7 @@
 #include "ldisc.h"
 
 void lpage_send(void *handle,
-               int codepage, char *buf, int len, int interactive)
+               int codepage, const char *buf, int len, int interactive)
 {
     Ldisc ldisc = (Ldisc)handle;
     wchar_t *widebuffer = 0;
@@ -34,7 +34,7 @@ void lpage_send(void *handle,
     sfree(widebuffer);
 }
 
-void luni_send(void *handle, wchar_t * widebuf, int len, int interactive)
+void luni_send(void *handle, const wchar_t *widebuf, int len, int interactive)
 {
     Ldisc ldisc = (Ldisc)handle;
     int ratio = (in_utf(ldisc->term))?3:1;
index a40d32a6e28fdb82d9387e6216014840624df98c..865fe9b82ed8dc547b48ddb2d06cdc010dd8973d 100644 (file)
--- a/logging.c
+++ b/logging.c
@@ -256,7 +256,7 @@ void log_eventlog(void *handle, const char *event)
  * Set of blanking areas must be in increasing order.
  */
 void log_packet(void *handle, int direction, int type,
-               char *texttype, const void *data, int len,
+               const char *texttype, const void *data, int len,
                int n_blanks, const struct logblank_t *blanks,
                const unsigned long *seq,
                 unsigned downstream_id, const char *additional_log_text)
index b4270f9d4c38a011a7212ccfea3509e210da0728..27c2c52bac6da661b235135586185c4d0cc12aaf 100644 (file)
@@ -1736,7 +1736,7 @@ void dlg_beep(void *dv)
     NSBeep();
 }
 
-void dlg_error_msg(void *dv, char *msg)
+void dlg_error_msg(void *dv, const char *msg)
 {
     /* FIXME */
 }
index 295b675520d790c08a1b6afc81115016758b228a..84a761fe68e0ea873c4c6c48b38abedf51aa7c62 100644 (file)
@@ -410,8 +410,8 @@ static void verify_ssh_host_key_callback(void *ctx, int result)
     sfree(state);
 }
 
-int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
-                        char *keystr, char *fingerprint,
+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[] =
@@ -490,7 +490,7 @@ static void connection_fatal_callback(void *ctx, int result)
     [win endSession:FALSE];
 }
 
-void connection_fatal(void *frontend, char *p, ...)
+void connection_fatal(void *frontend, const char *p, ...)
 {
     SessionWindow *win = (SessionWindow *)frontend;
     va_list ap;
index 2eba150c39a5160f5964b4c0634359d31c7ff200..2ac772a835aa2883b984e1214fd1cfc9f9981604 100644 (file)
@@ -58,7 +58,7 @@ char *x_get_default(const char *key)
     return NULL;                      /* this is a stub */
 }
 
-static void commonfatalbox(char *p, va_list ap)
+static void commonfatalbox(const char *p, va_list ap)
 {
     char errorbuf[2048];
     NSAlert *alert;
@@ -85,7 +85,7 @@ static void commonfatalbox(char *p, va_list ap)
     exit(1);
 }
 
-void nonfatal(void *frontend, char *p, ...)
+void nonfatal(void *frontend, const char *p, ...)
 {
     char *errorbuf;
     NSAlert *alert;
@@ -103,7 +103,7 @@ void nonfatal(void *frontend, char *p, ...)
     sfree(errorbuf);
 }
 
-void fatalbox(char *p, ...)
+void fatalbox(const char *p, ...)
 {
     va_list ap;
     va_start(ap, p);
@@ -111,7 +111,7 @@ void fatalbox(char *p, ...)
     va_end(ap);
 }
 
-void modalfatalbox(char *p, ...)
+void modalfatalbox(const char *p, ...)
 {
     va_list ap;
     va_start(ap, p);
@@ -119,7 +119,7 @@ void modalfatalbox(char *p, ...)
     va_end(ap);
 }
 
-void cmdline_error(char *p, ...)
+void cmdline_error(const char *p, ...)
 {
     va_list ap;
     fprintf(stderr, "%s: ", appname);
index b58742d9f65f804cbd69172ab9f4eee19a490b1d..062913694b06cfbdf9101d0212a1dec6c08d1fa3 100644 (file)
@@ -888,7 +888,7 @@ int from_backend_untrusted(void *frontend, const char *data, int len)
     return [win fromBackendUntrusted:data len:len];
 }
 
-int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+int get_userpass_input(prompts_t *p, const unsigned char *in, int inlen)
 {
     SessionWindow *win = (SessionWindow *)p->frontend;
     Terminal *term = [win term];
@@ -907,7 +907,7 @@ void notify_remote_exit(void *frontend)
     [win notifyRemoteExit];
 }
 
-void ldisc_update(void *frontend, int echo, int edit)
+void frontend_echoedit_update(void *frontend, int echo, int edit)
 {
     //SessionWindow *win = (SessionWindow *)frontend;
     /*
diff --git a/macosx/putty.icns b/macosx/putty.icns
deleted file mode 100644 (file)
index 72eab29..0000000
Binary files a/macosx/putty.icns and /dev/null differ
diff --git a/misc.c b/misc.c
index d40f99017e74284ccacccb930ebb4dab9d750e73..5ff403d6b495f60ef9f8a7daf7a7075ef28b5693 100644 (file)
--- a/misc.c
+++ b/misc.c
@@ -9,6 +9,7 @@
 #include <ctype.h>
 #include <assert.h>
 #include "putty.h"
+#include "misc.h"
 
 /*
  * Parse a string block size specification. This is approximately a
@@ -174,7 +175,7 @@ int main(void)
     return fails != 0 ? 1 : 0;
 }
 /* Stubs to stop the rest of this module causing compile failures. */
-void modalfatalbox(char *fmt, ...) {}
+void modalfatalbox(const char *fmt, ...) {}
 int conf_get_int(Conf *conf, int primary) { return 0; }
 char *conf_get_str(Conf *conf, int primary) { return NULL; }
 #endif /* TEST_HOST_STRFOO */
@@ -411,7 +412,7 @@ char *dupvprintf(const char *fmt, va_list ap)
     size = 512;
 
     while (1) {
-#ifdef _WINDOWS
+#if defined _WINDOWS && _MSC_VER < 1900 /* 1900 == VS2015 has real snprintf */
 #define vsnprintf _vsnprintf
 #endif
 #ifdef va_copy
@@ -472,11 +473,29 @@ char *fgetline(FILE *fp)
     return ret;
 }
 
+/*
+ * Perl-style 'chomp', for a line we just read with fgetline. Unlike
+ * Perl chomp, however, we're deliberately forgiving of strange
+ * line-ending conventions. Also we forgive NULL on input, so you can
+ * just write 'line = chomp(fgetline(fp));' and not bother checking
+ * for NULL until afterwards.
+ */
+char *chomp(char *str)
+{
+    if (str) {
+        int len = strlen(str);
+        while (len > 0 && (str[len-1] == '\r' || str[len-1] == '\n'))
+            len--;
+        str[len] = '\0';
+    }
+    return str;
+}
+
 /* ----------------------------------------------------------------------
  * Core base64 encoding and decoding routines.
  */
 
-void base64_encode_atom(unsigned char *data, int n, char *out)
+void base64_encode_atom(const unsigned char *data, int n, char *out)
 {
     static const char base64_chars[] =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -500,7 +519,7 @@ void base64_encode_atom(unsigned char *data, int n, char *out)
        out[3] = '=';
 }
 
-int base64_decode_atom(char *atom, unsigned char *out)
+int base64_decode_atom(const char *atom, unsigned char *out)
 {
     int vals[4];
     int i, v, len;
@@ -811,9 +830,9 @@ void safefree(void *ptr)
  */
 
 #ifdef DEBUG
-extern void dputs(char *);             /* defined in per-platform *misc.c */
+extern void dputs(const char *); /* defined in per-platform *misc.c */
 
-void debug_printf(char *fmt, ...)
+void debug_printf(const char *fmt, ...)
 {
     char *buf;
     va_list ap;
@@ -826,15 +845,15 @@ void debug_printf(char *fmt, ...)
 }
 
 
-void debug_memdump(void *buf, int len, int L)
+void debug_memdump(const void *buf, int len, int L)
 {
     int i;
-    unsigned char *p = buf;
+    const unsigned char *p = buf;
     char foo[17];
     if (L) {
        int delta;
        debug_printf("\t%d (0x%x) bytes:\n", len, len);
-       delta = 15 & (unsigned long int) p;
+       delta = 15 & (uintptr_t)p;
        p -= delta;
        len += delta;
     }
@@ -1036,6 +1055,39 @@ int smemeq(const void *av, const void *bv, size_t len)
     return (0x100 - val) >> 8;
 }
 
+int match_ssh_id(int stringlen, const void *string, const char *id)
+{
+    int idlen = strlen(id);
+    return (idlen == stringlen && !memcmp(string, id, idlen));
+}
+
+void *get_ssh_string(int *datalen, const void **data, int *stringlen)
+{
+    void *ret;
+    unsigned int len;
+
+    if (*datalen < 4)
+        return NULL;
+    len = GET_32BIT_MSB_FIRST((const unsigned char *)*data);
+    if (*datalen < len+4)
+        return NULL;
+    ret = (void *)((const char *)*data + 4);
+    *datalen -= len + 4;
+    *data = (const char *)*data + len + 4;
+    *stringlen = len;
+    return ret;
+}
+
+int get_ssh_uint32(int *datalen, const void **data, unsigned *ret)
+{
+    if (*datalen < 4)
+        return FALSE;
+    *ret = GET_32BIT_MSB_FIRST((const unsigned char *)*data);
+    *datalen -= 4;
+    *data = (const char *)*data + 4;
+    return TRUE;
+}
+
 int strstartswith(const char *s, const char *t)
 {
     return !memcmp(s, t, strlen(t));
diff --git a/misc.h b/misc.h
index e53f89299cd70b15b4668dfcff4b29eed27496ef..ae33e96e3fff7cb8dfd8d3d6cfc26a5bd5d2370c 100644 (file)
--- a/misc.h
+++ b/misc.h
@@ -51,11 +51,12 @@ wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string);
 int toint(unsigned);
 
 char *fgetline(FILE *fp);
+char *chomp(char *str);
 int strstartswith(const char *s, const char *t);
 int strendswith(const char *s, const char *t);
 
-void base64_encode_atom(unsigned char *data, int n, char *out);
-int base64_decode_atom(char *atom, unsigned char *out);
+void base64_encode_atom(const unsigned char *data, int n, char *out);
+int base64_decode_atom(const char *atom, unsigned char *out);
 
 struct bufchain_granule;
 typedef struct bufchain_tag {
@@ -89,6 +90,23 @@ void smemclr(void *b, size_t len);
  * by the 'eq' in the name. */
 int smemeq(const void *av, const void *bv, size_t len);
 
+/* Extracts an SSH-marshalled string from the start of *data. If
+ * successful (*datalen is not too small), advances data/datalen past
+ * the string and returns a pointer to the string itself and its
+ * length in *stringlen. Otherwise does nothing and returns NULL.
+ *
+ * Like strchr, this function can discard const from its parameter.
+ * Treat it as if it was a family of two functions, one returning a
+ * non-const string given a non-const pointer, and one taking and
+ * returning const. */
+void *get_ssh_string(int *datalen, const void **data, int *stringlen);
+/* Extracts an SSH uint32, similarly. Returns TRUE on success, and
+ * leaves the extracted value in *ret. */
+int get_ssh_uint32(int *datalen, const void **data, unsigned *ret);
+/* Given a not-necessarily-zero-terminated string in (length,data)
+ * form, check if it equals an ordinary C zero-terminated string. */
+int match_ssh_id(int stringlen, const void *string, const char *id);
+
 /*
  * Debugging functions.
  *
@@ -103,8 +121,8 @@ int smemeq(const void *av, const void *bv, size_t len);
  */
 
 #ifdef DEBUG
-void debug_printf(char *fmt, ...);
-void debug_memdump(void *buf, int len, int L);
+void debug_printf(const char *fmt, ...);
+void debug_memdump(const void *buf, int len, int L);
 #define debug(x) (debug_printf x)
 #define dmemdump(buf,len) debug_memdump (buf, len, 0);
 #define dmemdumpl(buf,len) debug_memdump (buf, len, 1);
index 342db4d0a965db406c663b71cc543d94fc681601..0e7484889e20244466b7d64fd1dbd497cf6f4645 100755 (executable)
@@ -132,9 +132,10 @@ while (<IN>) {
     $i = shift @objs;
     if ($groups{$i}) {
       foreach $j (@{$groups{$i}}) { unshift @objs, $j; }
-    } elsif (($i eq "[G]" or $i eq "[C]" or $i eq "[M]" or
-              $i eq "[X]" or $i eq "[U]" or $i eq "[MX]") and defined $prog) {
+    } 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);
     } else {
       push @$listref, $i;
     }
@@ -643,63 +644,76 @@ if (defined $makefiles{'vc'}) {
       &def($makefile_extra{'vc'}->{'vars'}) .
       "\n".
       "\n";
-    print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C"));
+    print &splitline("all:" . join "", map { " \$(BUILDDIR)$_.exe" } &progrealnames("G:C"));
     print "\n\n";
     foreach $p (&prognames("G:C")) {
        ($prog, $type) = split ",", $p;
-       $objstr = &objects($p, "X.obj", "X.res", undef);
-       print &splitline("$prog.exe: " . $objstr . " $prog.rsp"), "\n";
-       print "\tlink \$(LFLAGS) \$(XLFLAGS) -out:$prog.exe -map:$prog.map \@$prog.rsp\n\n";
-    }
-    foreach $p (&prognames("G:C")) {
-       ($prog, $type) = split ",", $p;
-       print $prog, ".rsp: \$(MAKEFILE)\n";
-       $objstr = &objects($p, "X.obj", "X.res", "X.lib");
+       $objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", undef);
+       print &splitline("\$(BUILDDIR)$prog.exe: " . $objstr), "\n";
+
+       $objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", "X.lib");
+       $subsys = ($type eq "G") ? "windows" : "console";
+        $inlinefilename = "link_$prog";
+        print "\ttype <<$inlinefilename\n";
        @objlist = split " ", $objstr;
        @objlines = ("");
        foreach $i (@objlist) {
-           if (length($objlines[$#objlines] . " $i") > 50) {
+           if (length($objlines[$#objlines] . " $i") > 72) {
                push @objlines, "";
            }
            $objlines[$#objlines] .= " $i";
        }
-       $subsys = ($type eq "G") ? "windows" : "console";
-       print "\techo /nologo /subsystem:$subsys > $prog.rsp\n";
        for ($i=0; $i<=$#objlines; $i++) {
-           print "\techo$objlines[$i] >> $prog.rsp\n";
+           print "$objlines[$i]\n";
        }
-       print "\n";
+       print "<<\n";
+       print "\tlink \$(LFLAGS) \$(XLFLAGS) -out:\$(BUILDDIR)$prog.exe -map:\$(BUILDDIR)$prog.map -nologo -subsystem:$subsys \@$inlinefilename\n\n";
     }
-    foreach $d (&deps("X.obj", "X.res", $dirpfx, "\\", "vc")) {
+    foreach $d (&deps("\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", $dirpfx, "\\", "vc")) {
         $extradeps = $forceobj{$d->{obj_orig}} ? ["*.c","*.h","*.rc"] : [];
         print &splitline(sprintf("%s: %s", $d->{obj},
                                  join " ", @$extradeps, @{$d->{deps}})), "\n";
-        if ($d->{obj} =~ /.obj$/) {
-           print "\tcl \$(COMPAT) \$(CFLAGS) \$(XFLAGS) /c ".$d->{deps}->[0],"\n\n";
-       } else {
-           print "\trc \$(RCFL) -r \$(RCFLAGS) ".$d->{deps}->[0],"\n\n";
+        if ($d->{obj} =~ /.res$/) {
+           print "\trc /Fo@{[$d->{obj}]} \$(RCFL) -r \$(RCFLAGS) ".$d->{deps}->[0],"\n\n";
        }
     }
     print "\n";
+    foreach $real_srcdir ("", @srcdirs) {
+        $srcdir = $real_srcdir;
+        if ($srcdir ne "") {
+            $srcdir =~ s!/!\\!g;
+            $srcdir = $dirpfx . $srcdir;
+            $srcdir =~ s!\\\.\\!\\!;
+            $srcdir = "{$srcdir}";
+        }
+        # The double colon at the end of the line makes this a
+        # 'batch-mode inference rule', which means that nmake will
+        # aggregate multiple invocations of the rule and issue just
+        # one cl command with multiple source-file arguments. That
+        # noticeably speeds up builds, since starting up the cl
+        # process is a noticeable overhead and now has to be done far
+        # fewer times.
+        print "${srcdir}.c.obj::\n\tcl /Fo\$(BUILDDIR) \$(COMPAT) \$(CFLAGS) \$(XFLAGS) /c \$<\n\n";
+    }
     print &def($makefile_extra{'vc'}->{'end'});
     print "\nclean: tidy\n".
-      "\t-del *.exe\n\n".
+      "\t-del \$(BUILDDIR)*.exe\n\n".
       "tidy:\n".
-      "\t-del *.obj\n".
-      "\t-del *.res\n".
-      "\t-del *.pch\n".
-      "\t-del *.aps\n".
-      "\t-del *.ilk\n".
-      "\t-del *.pdb\n".
-      "\t-del *.rsp\n".
-      "\t-del *.dsp\n".
-      "\t-del *.dsw\n".
-      "\t-del *.ncb\n".
-      "\t-del *.opt\n".
-      "\t-del *.plg\n".
-      "\t-del *.map\n".
-      "\t-del *.idb\n".
-      "\t-del debug.log\n";
+      "\t-del \$(BUILDDIR)*.obj\n".
+      "\t-del \$(BUILDDIR)*.res\n".
+      "\t-del \$(BUILDDIR)*.pch\n".
+      "\t-del \$(BUILDDIR)*.aps\n".
+      "\t-del \$(BUILDDIR)*.ilk\n".
+      "\t-del \$(BUILDDIR)*.pdb\n".
+      "\t-del \$(BUILDDIR)*.rsp\n".
+      "\t-del \$(BUILDDIR)*.dsp\n".
+      "\t-del \$(BUILDDIR)*.dsw\n".
+      "\t-del \$(BUILDDIR)*.ncb\n".
+      "\t-del \$(BUILDDIR)*.opt\n".
+      "\t-del \$(BUILDDIR)*.plg\n".
+      "\t-del \$(BUILDDIR)*.map\n".
+      "\t-del \$(BUILDDIR)*.idb\n".
+      "\t-del \$(BUILDDIR)debug.log\n";
     select STDOUT; close OUT;
 }
 
@@ -1379,9 +1393,9 @@ if (defined $makefiles{'gtk'}) {
     ".SUFFIXES:\n".
     "\n".
     "\n";
-    print &splitline("all:" . join "", map { " $_" } &progrealnames("X:U"));
+    print &splitline("all:" . join "", map { " $_" } &progrealnames("X:U:UT"));
     print "\n\n";
-    foreach $p (&prognames("X:U")) {
+    foreach $p (&prognames("X:U:UT")) {
       ($prog, $type) = split ",", $p;
       $objstr = &objects($p, "X.o", undef, undef);
       print &splitline($prog . ": " . $objstr), "\n";
@@ -1401,7 +1415,7 @@ if (defined $makefiles{'gtk'}) {
     print "\n";
     print &def($makefile_extra{'gtk'}->{'end'});
     print "\nclean:\n".
-    "\trm -f *.o". (join "", map { " $_" } &progrealnames("X:U")) . "\n";
+    "\trm -f *.o". (join "", map { " $_" } &progrealnames("X:U:UT")) . "\n";
     print "\nFORCE:\n";
     select STDOUT; close OUT;
 }
@@ -1446,9 +1460,9 @@ if (defined $makefiles{'unix'}) {
     ".SUFFIXES:\n".
     "\n".
     "\n";
-    print &splitline("all:" . join "", map { " $_" } &progrealnames("U"));
+    print &splitline("all:" . join "", map { " $_" } &progrealnames("U:UT"));
     print "\n\n";
-    foreach $p (&prognames("U")) {
+    foreach $p (&prognames("U:UT")) {
       ($prog, $type) = split ",", $p;
       $objstr = &objects($p, "X.o", undef, undef);
       print &splitline($prog . ": " . $objstr), "\n";
@@ -1468,7 +1482,7 @@ if (defined $makefiles{'unix'}) {
     print "\n";
     print &def($makefile_extra{'unix'}->{'end'});
     print "\nclean:\n".
-    "\trm -f *.o". (join "", map { " $_" } &progrealnames("U")) . "\n";
+    "\trm -f *.o". (join "", map { " $_" } &progrealnames("U:UT")) . "\n";
     print "\nFORCE:\n";
     select STDOUT; close OUT;
 }
@@ -1511,6 +1525,13 @@ if (defined $makefiles{'am'}) {
     print &splitline(join " ", @cliprogs), "\n";
     print "endif\n\n";
 
+    @noinstcliprogs = ("noinst_PROGRAMS", "=");
+    foreach $p (&prognames("UT")) {
+      ($prog, $type) = split ",", $p;
+      push @noinstcliprogs, $prog;
+    }
+    print &splitline(join " ", @noinstcliprogs), "\n";
+
     %objtosrc = ();
     foreach $d (&deps("X", undef, "", "/", "am")) {
       $objtosrc{$d->{obj}} = $d->{deps}->[0];
@@ -1537,7 +1558,7 @@ if (defined $makefiles{'am'}) {
     print &splitline(join " ", "noinst_LIBRARIES", "=",
                      sort { $a cmp $b } values %amspeciallibs), "\n\n";
 
-    foreach $p (&prognames("X:U")) {
+    foreach $p (&prognames("X:U:UT")) {
       ($prog, $type) = split ",", $p;
       print "if HAVE_GTK\n" if $type eq "X";
       @progsources = ("${prog}_SOURCES", "=");
@@ -1657,7 +1678,7 @@ if (defined $makefiles{'osx'}) {
     "\n" .
     &def($makefile_extra{'osx'}->{'vars'}) .
     "\n" .
-    &splitline("all:" . join "", map { " $_" } &progrealnames("MX:U")) .
+    &splitline("all:" . join "", map { " $_" } &progrealnames("MX:U:UT")) .
     "\n";
     foreach $p (&prognames("MX")) {
       ($prog, $type) = split ",", $p;
@@ -1685,7 +1706,7 @@ if (defined $makefiles{'osx'}) {
       print &splitline("\t\$(CC) \$(MLDFLAGS) -o \$@ " .
                        $objstr . " $libstr", 69), "\n\n";
     }
-    foreach $p (&prognames("U")) {
+    foreach $p (&prognames("U:UT")) {
       ($prog, $type) = split ",", $p;
       $objstr = &objects($p, "X.o", undef, undef);
       print &splitline($prog . ": " . $objstr), "\n";
@@ -1709,7 +1730,7 @@ if (defined $makefiles{'osx'}) {
     }
     print "\n".&def($makefile_extra{'osx'}->{'end'});
     print "\nclean:\n".
-    "\trm -f *.o *.dmg". (join "", map { " $_" } &progrealnames("U")) . "\n".
+    "\trm -f *.o *.dmg". (join "", map { " $_" } &progrealnames("U:UT")) . "\n".
     "\trm -rf *.app\n".
     "\n".
     "FORCE:\n";
index 87327df7c8d97c8b6b47330126835bcf9d585816..a8b01b5bbeed7f84934d15ee7e29081fdc52a8b7 100755 (executable)
@@ -20,7 +20,7 @@ text=`{ find . -name CVS -prune -o \
 # files.
 bintext=testdata/*.txt
 # These are actual binary files which we don't want transforming.
-bin=`{ ls -1 windows/*.ico windows/putty.iss windows/website.url macosx/*.icns; \
+bin=`{ ls -1 windows/*.ico windows/putty.iss windows/website.url; \
        find . -name '*.dsp' -print -o -name '*.dsw' -print; }`
 
 verbosely() {
index 319475e7909d6510b7557b3e17bb019ea6e83126..10be046feb10a577ac57e2717f37e1df77cc95a5 100644 (file)
--- a/network.h
+++ b/network.h
@@ -58,6 +58,11 @@ struct plug_function_table {
      *           fatal error - we may well have other candidate addresses
      *           to fall back to. When it _is_ fatal, the closing()
      *           function will be called.
+     *  - type==2 means that error_msg contains a line of generic
+     *    logging information about setting up the connection. This
+     *    will typically be a wodge of standard-error output from a
+     *    proxy command, so the receiver should probably prefix it to
+     *    indicate this.
      */
     int (*closing)
      (Plug p, const char *error_msg, int error_code, int calling_back);
@@ -93,20 +98,21 @@ struct plug_function_table {
 /* proxy indirection layer */
 /* NB, control of 'addr' is passed via new_connection, which takes
  * responsibility for freeing it */
-Socket new_connection(SockAddr addr, char *hostname,
+Socket new_connection(SockAddr addr, const char *hostname,
                      int port, int privport,
                      int oobinline, int nodelay, int keepalive,
                      Plug plug, Conf *conf);
-Socket new_listener(char *srcaddr, int port, Plug plug, int local_host_only,
-                   Conf *conf, int addressfamily);
-SockAddr name_lookup(char *host, int port, char **canonicalname,
-                    Conf *conf, int addressfamily);
+Socket new_listener(const char *srcaddr, int port, Plug plug,
+                    int local_host_only, Conf *conf, int addressfamily);
+SockAddr name_lookup(const char *host, int port, char **canonicalname,
+                    Conf *conf, int addressfamily, void *frontend_for_logging,
+                     const char *lookup_reason_for_logging);
 int proxy_for_destination (SockAddr addr, const char *hostname, int port,
                            Conf *conf);
 
 /* platform-dependent callback from new_connection() */
 /* (same caveat about addr as new_connection()) */
-Socket platform_new_connection(SockAddr addr, char *hostname,
+Socket platform_new_connection(SockAddr addr, const char *hostname,
                               int port, int privport,
                               int oobinline, int nodelay, int keepalive,
                               Plug plug, Conf *conf);
@@ -138,7 +144,8 @@ SockAddr sk_addr_dup(SockAddr addr);
 Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
              int nodelay, int keepalive, Plug p);
 
-Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only, int address_family);
+Socket sk_newlistener(const char *srcaddr, int port, Plug plug,
+                      int local_host_only, int address_family);
 
 #define sk_plug(s,p) (((*s)->plug) (s, p))
 #define sk_close(s) (((*s)->close) (s))
@@ -210,40 +217,19 @@ char *get_hostname(void);
  */
 Socket new_error_socket(const char *errmsg, Plug plug);
 
-/********** SSL stuff **********/
+/* ----------------------------------------------------------------------
+ * Functions defined outside the network code, which have to be
+ * declared in this header file rather than the main putty.h because
+ * they use types defined here.
+ */
 
 /*
- * This section is subject to change, but you get the general idea
- * of what it will eventually look like.
+ * Exports from be_misc.c.
  */
-
-typedef struct certificate *Certificate;
-typedef struct our_certificate *Our_Certificate;
-    /* to be defined somewhere else, somehow */
-
-typedef struct ssl_client_socket_function_table **SSL_Client_Socket;
-typedef struct ssl_client_plug_function_table **SSL_Client_Plug;
-
-struct ssl_client_socket_function_table {
-    struct socket_function_table base;
-    void (*renegotiate) (SSL_Client_Socket s);
-    /* renegotiate the cipher spec */
-};
-
-struct ssl_client_plug_function_table {
-    struct plug_function_table base;
-    int (*refuse_cert) (SSL_Client_Plug p, Certificate cert[]);
-    /* do we accept this certificate chain?  If not, why not? */
-    /* cert[0] is the server's certificate, cert[] is NULL-terminated */
-    /* the last certificate may or may not be the root certificate */
-     Our_Certificate(*client_cert) (SSL_Client_Plug p);
-    /* the server wants us to identify ourselves */
-    /* may return NULL if we want anonymity */
-};
-
-SSL_Client_Socket sk_ssl_client_over(Socket s, /* pre-existing (tcp) connection */
-                                    SSL_Client_Plug p);
-
-#define sk_renegotiate(s) (((*s)->renegotiate) (s))
+void backend_socket_log(void *frontend, int type, SockAddr addr, int port,
+                        const char *error_msg, int error_code, Conf *conf,
+                        int session_started);
+typedef struct bufchain_tag bufchain;  /* rest of declaration in misc.c */
+void log_proxy_stderr(Plug plug, bufchain *buf, const void *vdata, int len);
 
 #endif
diff --git a/pageant.c b/pageant.c
new file mode 100644 (file)
index 0000000..c008f00
--- /dev/null
+++ b/pageant.c
@@ -0,0 +1,1811 @@
+/*
+ * pageant.c: cross-platform code to implement Pageant.
+ */
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "pageant.h"
+
+/*
+ * We need this to link with the RSA code, because rsaencrypt()
+ * pads its data with random bytes. Since we only use rsadecrypt()
+ * and the signing functions, which are deterministic, this should
+ * never be called.
+ *
+ * If it _is_ called, there is a _serious_ problem, because it
+ * won't generate true random numbers. So we must scream, panic,
+ * and exit immediately if that should happen.
+ */
+int random_byte(void)
+{
+    modalfatalbox("Internal error: attempt to use random numbers in Pageant");
+    exit(0);
+    return 0;                 /* unreachable, but placate optimiser */
+}
+
+static int pageant_local = FALSE;
+
+/*
+ * rsakeys stores SSH-1 RSA keys. ssh2keys stores all SSH-2 keys.
+ */
+static tree234 *rsakeys, *ssh2keys;
+
+/*
+ * Blob structure for passing to the asymmetric SSH-2 key compare
+ * function, prototyped here.
+ */
+struct blob {
+    const unsigned char *blob;
+    int len;
+};
+static int cmpkeys_ssh2_asymm(void *av, void *bv);
+
+/*
+ * Key comparison function for the 2-3-4 tree of RSA keys.
+ */
+static int cmpkeys_rsa(void *av, void *bv)
+{
+    struct RSAKey *a = (struct RSAKey *) av;
+    struct RSAKey *b = (struct RSAKey *) bv;
+    Bignum am, bm;
+    int alen, blen;
+
+    am = a->modulus;
+    bm = b->modulus;
+    /*
+     * Compare by length of moduli.
+     */
+    alen = bignum_bitcount(am);
+    blen = bignum_bitcount(bm);
+    if (alen > blen)
+       return +1;
+    else if (alen < blen)
+       return -1;
+    /*
+     * Now compare by moduli themselves.
+     */
+    alen = (alen + 7) / 8;            /* byte count */
+    while (alen-- > 0) {
+       int abyte, bbyte;
+       abyte = bignum_byte(am, alen);
+       bbyte = bignum_byte(bm, alen);
+       if (abyte > bbyte)
+           return +1;
+       else if (abyte < bbyte)
+           return -1;
+    }
+    /*
+     * Give up.
+     */
+    return 0;
+}
+
+/*
+ * Key comparison function for the 2-3-4 tree of SSH-2 keys.
+ */
+static int cmpkeys_ssh2(void *av, void *bv)
+{
+    struct ssh2_userkey *a = (struct ssh2_userkey *) av;
+    struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
+    int i;
+    int alen, blen;
+    unsigned char *ablob, *bblob;
+    int c;
+
+    /*
+     * Compare purely by public blob.
+     */
+    ablob = a->alg->public_blob(a->data, &alen);
+    bblob = b->alg->public_blob(b->data, &blen);
+
+    c = 0;
+    for (i = 0; i < alen && i < blen; i++) {
+       if (ablob[i] < bblob[i]) {
+           c = -1;
+           break;
+       } else if (ablob[i] > bblob[i]) {
+           c = +1;
+           break;
+       }
+    }
+    if (c == 0 && i < alen)
+       c = +1;                        /* a is longer */
+    if (c == 0 && i < blen)
+       c = -1;                        /* a is longer */
+
+    sfree(ablob);
+    sfree(bblob);
+
+    return c;
+}
+
+/*
+ * Key comparison function for looking up a blob in the 2-3-4 tree
+ * of SSH-2 keys.
+ */
+static int cmpkeys_ssh2_asymm(void *av, void *bv)
+{
+    struct blob *a = (struct blob *) av;
+    struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
+    int i;
+    int alen, blen;
+    const unsigned char *ablob;
+    unsigned char *bblob;
+    int c;
+
+    /*
+     * Compare purely by public blob.
+     */
+    ablob = a->blob;
+    alen = a->len;
+    bblob = b->alg->public_blob(b->data, &blen);
+
+    c = 0;
+    for (i = 0; i < alen && i < blen; i++) {
+       if (ablob[i] < bblob[i]) {
+           c = -1;
+           break;
+       } else if (ablob[i] > bblob[i]) {
+           c = +1;
+           break;
+       }
+    }
+    if (c == 0 && i < alen)
+       c = +1;                        /* a is longer */
+    if (c == 0 && i < blen)
+       c = -1;                        /* a is longer */
+
+    sfree(bblob);
+
+    return c;
+}
+
+/*
+ * Create an SSH-1 key list in a malloc'ed buffer; return its
+ * length.
+ */
+void *pageant_make_keylist1(int *length)
+{
+    int i, nkeys, len;
+    struct RSAKey *key;
+    unsigned char *blob, *p, *ret;
+    int bloblen;
+
+    /*
+     * Count up the number and length of keys we hold.
+     */
+    len = 4;
+    nkeys = 0;
+    for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
+       nkeys++;
+       blob = rsa_public_blob(key, &bloblen);
+       len += bloblen;
+       sfree(blob);
+       len += 4 + strlen(key->comment);
+    }
+
+    /* Allocate the buffer. */
+    p = ret = snewn(len, unsigned char);
+    if (length) *length = len;
+
+    PUT_32BIT(p, nkeys);
+    p += 4;
+    for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
+       blob = rsa_public_blob(key, &bloblen);
+       memcpy(p, blob, bloblen);
+       p += bloblen;
+       sfree(blob);
+       PUT_32BIT(p, strlen(key->comment));
+       memcpy(p + 4, key->comment, strlen(key->comment));
+       p += 4 + strlen(key->comment);
+    }
+
+    assert(p - ret == len);
+    return ret;
+}
+
+/*
+ * Create an SSH-2 key list in a malloc'ed buffer; return its
+ * length.
+ */
+void *pageant_make_keylist2(int *length)
+{
+    struct ssh2_userkey *key;
+    int i, len, nkeys;
+    unsigned char *blob, *p, *ret;
+    int bloblen;
+
+    /*
+     * Count up the number and length of keys we hold.
+     */
+    len = 4;
+    nkeys = 0;
+    for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
+       nkeys++;
+       len += 4;              /* length field */
+       blob = key->alg->public_blob(key->data, &bloblen);
+       len += bloblen;
+       sfree(blob);
+       len += 4 + strlen(key->comment);
+    }
+
+    /* Allocate the buffer. */
+    p = ret = snewn(len, unsigned char);
+    if (length) *length = len;
+
+    /*
+     * Packet header is the obvious five bytes, plus four
+     * bytes for the key count.
+     */
+    PUT_32BIT(p, nkeys);
+    p += 4;
+    for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
+       blob = key->alg->public_blob(key->data, &bloblen);
+       PUT_32BIT(p, bloblen);
+       p += 4;
+       memcpy(p, blob, bloblen);
+       p += bloblen;
+       sfree(blob);
+       PUT_32BIT(p, strlen(key->comment));
+       memcpy(p + 4, key->comment, strlen(key->comment));
+       p += 4 + strlen(key->comment);
+    }
+
+    assert(p - ret == len);
+    return ret;
+}
+
+static void plog(void *logctx, pageant_logfn_t logfn, const char *fmt, ...)
+#ifdef __GNUC__
+__attribute__ ((format (printf, 3, 4)))
+#endif
+    ;
+
+static void plog(void *logctx, pageant_logfn_t logfn, const char *fmt, ...)
+{
+    /*
+     * This is the wrapper that takes a variadic argument list and
+     * turns it into the va_list that the log function really expects.
+     * It's safe to call this with logfn==NULL, because we
+     * double-check that below; but if you're going to do lots of work
+     * before getting here (such as looping, or hashing things) then
+     * you should probably check logfn manually before doing that.
+     */
+    if (logfn) {
+        va_list ap;
+        va_start(ap, fmt);
+        logfn(logctx, fmt, ap);
+        va_end(ap);
+    }
+}
+
+void *pageant_handle_msg(const void *msg, int msglen, int *outlen,
+                         void *logctx, pageant_logfn_t logfn)
+{
+    const unsigned char *p = msg;
+    const unsigned char *msgend;
+    unsigned char *ret = snewn(AGENT_MAX_MSGLEN, unsigned char);
+    int type;
+    const char *fail_reason;
+
+    msgend = p + msglen;
+
+    /*
+     * Get the message type.
+     */
+    if (msgend < p+1) {
+        fail_reason = "message contained no type code";
+       goto failure;
+    }
+    type = *p++;
+
+    switch (type) {
+      case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
+       /*
+        * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
+        */
+       {
+           int len;
+           void *keylist;
+
+            plog(logctx, logfn, "request: SSH1_AGENTC_REQUEST_RSA_IDENTITIES");
+
+           ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
+           keylist = pageant_make_keylist1(&len);
+           if (len + 5 > AGENT_MAX_MSGLEN) {
+               sfree(keylist);
+                fail_reason = "output would exceed max msglen";
+               goto failure;
+           }
+           PUT_32BIT(ret, len + 1);
+           memcpy(ret + 5, keylist, len);
+
+            plog(logctx, logfn, "reply: SSH1_AGENT_RSA_IDENTITIES_ANSWER");
+            if (logfn) {               /* skip this loop if not logging */
+                int i;
+                struct RSAKey *rkey;
+                for (i = 0; NULL != (rkey = pageant_nth_ssh1_key(i)); i++) {
+                    char fingerprint[128];
+                    rsa_fingerprint(fingerprint, sizeof(fingerprint), rkey);
+                    plog(logctx, logfn, "returned key: %s", fingerprint);
+                }
+            }
+           sfree(keylist);
+       }
+       break;
+      case SSH2_AGENTC_REQUEST_IDENTITIES:
+       /*
+        * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
+        */
+       {
+           int len;
+           void *keylist;
+
+            plog(logctx, logfn, "request: SSH2_AGENTC_REQUEST_IDENTITIES");
+
+           ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
+           keylist = pageant_make_keylist2(&len);
+           if (len + 5 > AGENT_MAX_MSGLEN) {
+               sfree(keylist);
+                fail_reason = "output would exceed max msglen";
+               goto failure;
+           }
+           PUT_32BIT(ret, len + 1);
+           memcpy(ret + 5, keylist, len);
+
+            plog(logctx, logfn, "reply: SSH2_AGENT_IDENTITIES_ANSWER");
+            if (logfn) {               /* skip this loop if not logging */
+                int i;
+                struct ssh2_userkey *skey;
+                for (i = 0; NULL != (skey = pageant_nth_ssh2_key(i)); i++) {
+                    char *fingerprint = ssh2_fingerprint(skey->alg,
+                                                         skey->data);
+                    plog(logctx, logfn, "returned key: %s %s",
+                         fingerprint, skey->comment);
+                    sfree(fingerprint);
+                }
+            }
+
+           sfree(keylist);
+       }
+       break;
+      case SSH1_AGENTC_RSA_CHALLENGE:
+       /*
+        * Reply with either SSH1_AGENT_RSA_RESPONSE or
+        * SSH_AGENT_FAILURE, depending on whether we have that key
+        * or not.
+        */
+       {
+           struct RSAKey reqkey, *key;
+           Bignum challenge, response;
+           unsigned char response_source[48], response_md5[16];
+           struct MD5Context md5c;
+           int i, len;
+
+            plog(logctx, logfn, "request: SSH1_AGENTC_RSA_CHALLENGE");
+
+           p += 4;
+           i = ssh1_read_bignum(p, msgend - p, &reqkey.exponent);
+           if (i < 0) {
+                fail_reason = "request truncated before key exponent";
+               goto failure;
+            }
+           p += i;
+           i = ssh1_read_bignum(p, msgend - p, &reqkey.modulus);
+           if (i < 0) {
+                freebn(reqkey.exponent);
+                fail_reason = "request truncated before key modulus";
+               goto failure;
+            }
+           p += i;
+           i = ssh1_read_bignum(p, msgend - p, &challenge);
+           if (i < 0) {
+                freebn(reqkey.exponent);
+                freebn(reqkey.modulus);
+                fail_reason = "request truncated before challenge";
+               goto failure;
+            }
+           p += i;
+           if (msgend < p+16) {
+               freebn(reqkey.exponent);
+               freebn(reqkey.modulus);
+               freebn(challenge);
+                fail_reason = "request truncated before session id";
+               goto failure;
+           }
+           memcpy(response_source + 32, p, 16);
+           p += 16;
+           if (msgend < p+4) {
+               freebn(reqkey.exponent);
+               freebn(reqkey.modulus);
+               freebn(challenge);
+                fail_reason = "request truncated before response type";
+               goto failure;
+            }
+            if (GET_32BIT(p) != 1) {
+               freebn(reqkey.exponent);
+               freebn(reqkey.modulus);
+               freebn(challenge);
+                fail_reason = "response type other than 1 not supported";
+               goto failure;
+            }
+            if (logfn) {
+                char fingerprint[128];
+                reqkey.comment = NULL;
+                rsa_fingerprint(fingerprint, sizeof(fingerprint), &reqkey);
+                plog(logctx, logfn, "requested key: %s", fingerprint);
+            }
+            if ((key = find234(rsakeys, &reqkey, NULL)) == NULL) {
+               freebn(reqkey.exponent);
+               freebn(reqkey.modulus);
+               freebn(challenge);
+                fail_reason = "key not found";
+               goto failure;
+           }
+           response = rsadecrypt(challenge, key);
+           for (i = 0; i < 32; i++)
+               response_source[i] = bignum_byte(response, 31 - i);
+
+           MD5Init(&md5c);
+           MD5Update(&md5c, response_source, 48);
+           MD5Final(response_md5, &md5c);
+           smemclr(response_source, 48);       /* burn the evidence */
+           freebn(response);          /* and that evidence */
+           freebn(challenge);         /* yes, and that evidence */
+           freebn(reqkey.exponent);   /* and free some memory ... */
+           freebn(reqkey.modulus);    /* ... while we're at it. */
+
+           /*
+            * Packet is the obvious five byte header, plus sixteen
+            * bytes of MD5.
+            */
+           len = 5 + 16;
+           PUT_32BIT(ret, len - 4);
+           ret[4] = SSH1_AGENT_RSA_RESPONSE;
+           memcpy(ret + 5, response_md5, 16);
+
+            plog(logctx, logfn, "reply: SSH1_AGENT_RSA_RESPONSE");
+       }
+       break;
+      case SSH2_AGENTC_SIGN_REQUEST:
+       /*
+        * Reply with either SSH2_AGENT_SIGN_RESPONSE or
+        * SSH_AGENT_FAILURE, depending on whether we have that key
+        * or not.
+        */
+       {
+           struct ssh2_userkey *key;
+           struct blob b;
+           const unsigned char *data;
+            unsigned char *signature;
+           int datalen, siglen, len;
+
+            plog(logctx, logfn, "request: SSH2_AGENTC_SIGN_REQUEST");
+
+           if (msgend < p+4) {
+                fail_reason = "request truncated before public key";
+               goto failure;
+            }
+           b.len = toint(GET_32BIT(p));
+            if (b.len < 0 || b.len > msgend - (p+4)) {
+                fail_reason = "request truncated before public key";
+                goto failure;
+            }
+           p += 4;
+           b.blob = p;
+           p += b.len;
+           if (msgend < p+4) {
+                fail_reason = "request truncated before string to sign";
+               goto failure;
+            }
+           datalen = toint(GET_32BIT(p));
+           p += 4;
+           if (datalen < 0 || datalen > msgend - p) {
+                fail_reason = "request truncated before string to sign";
+               goto failure;
+            }
+           data = p;
+            if (logfn) {
+                char *fingerprint = ssh2_fingerprint_blob(b.blob, b.len);
+                plog(logctx, logfn, "requested key: %s", fingerprint);
+                sfree(fingerprint);
+            }
+           key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
+           if (!key) {
+                fail_reason = "key not found";
+               goto failure;
+            }
+           signature = key->alg->sign(key->data, (const char *)data,
+                                       datalen, &siglen);
+           len = 5 + 4 + siglen;
+           PUT_32BIT(ret, len - 4);
+           ret[4] = SSH2_AGENT_SIGN_RESPONSE;
+           PUT_32BIT(ret + 5, siglen);
+           memcpy(ret + 5 + 4, signature, siglen);
+           sfree(signature);
+
+            plog(logctx, logfn, "reply: SSH2_AGENT_SIGN_RESPONSE");
+       }
+       break;
+      case SSH1_AGENTC_ADD_RSA_IDENTITY:
+       /*
+        * Add to the list and return SSH_AGENT_SUCCESS, or
+        * SSH_AGENT_FAILURE if the key was malformed.
+        */
+       {
+           struct RSAKey *key;
+           char *comment;
+            int n, commentlen;
+
+            plog(logctx, logfn, "request: SSH1_AGENTC_ADD_RSA_IDENTITY");
+
+           key = snew(struct RSAKey);
+           memset(key, 0, sizeof(struct RSAKey));
+
+           n = makekey(p, msgend - p, key, NULL, 1);
+           if (n < 0) {
+               freersakey(key);
+               sfree(key);
+                fail_reason = "request truncated before public key";
+               goto failure;
+           }
+           p += n;
+
+           n = makeprivate(p, msgend - p, key);
+           if (n < 0) {
+               freersakey(key);
+               sfree(key);
+                fail_reason = "request truncated before private key";
+               goto failure;
+           }
+           p += n;
+
+            /* SSH-1 names p and q the other way round, i.e. we have
+             * the inverse of p mod q and not of q mod p. We swap the
+             * names, because our internal RSA wants iqmp. */
+
+           n = ssh1_read_bignum(p, msgend - p, &key->iqmp);  /* p^-1 mod q */
+           if (n < 0) {
+               freersakey(key);
+               sfree(key);
+                fail_reason = "request truncated before iqmp";
+               goto failure;
+           }
+           p += n;
+
+           n = ssh1_read_bignum(p, msgend - p, &key->q);  /* p */
+           if (n < 0) {
+               freersakey(key);
+               sfree(key);
+                fail_reason = "request truncated before p";
+               goto failure;
+           }
+           p += n;
+
+           n = ssh1_read_bignum(p, msgend - p, &key->p);  /* q */
+           if (n < 0) {
+               freersakey(key);
+               sfree(key);
+                fail_reason = "request truncated before q";
+               goto failure;
+           }
+           p += n;
+
+           if (msgend < p+4) {
+               freersakey(key);
+               sfree(key);
+                fail_reason = "request truncated before key comment";
+               goto failure;
+           }
+            commentlen = toint(GET_32BIT(p));
+
+           if (commentlen < 0 || commentlen > msgend - p) {
+               freersakey(key);
+               sfree(key);
+                fail_reason = "request truncated before key comment";
+               goto failure;
+           }
+
+           comment = snewn(commentlen+1, char);
+           if (comment) {
+               memcpy(comment, p + 4, commentlen);
+                comment[commentlen] = '\0';
+               key->comment = comment;
+           }
+
+            if (logfn) {
+                char fingerprint[128];
+                rsa_fingerprint(fingerprint, sizeof(fingerprint), key);
+                plog(logctx, logfn, "submitted key: %s", fingerprint);
+            }
+
+           if (add234(rsakeys, key) == key) {
+               keylist_update();
+                PUT_32BIT(ret, 1);
+               ret[4] = SSH_AGENT_SUCCESS;
+
+                plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS");
+           } else {
+               freersakey(key);
+               sfree(key);
+
+                fail_reason = "key already present";
+                goto failure;
+           }
+       }
+       break;
+      case SSH2_AGENTC_ADD_IDENTITY:
+       /*
+        * Add to the list and return SSH_AGENT_SUCCESS, or
+        * SSH_AGENT_FAILURE if the key was malformed.
+        */
+       {
+           struct ssh2_userkey *key;
+           char *comment;
+            const char *alg;
+           int alglen, commlen;
+           int bloblen;
+
+            plog(logctx, logfn, "request: SSH2_AGENTC_ADD_IDENTITY");
+
+           if (msgend < p+4) {
+                fail_reason = "request truncated before key algorithm";
+               goto failure;
+            }
+           alglen = toint(GET_32BIT(p));
+           p += 4;
+           if (alglen < 0 || alglen > msgend - p) {
+                fail_reason = "request truncated before key algorithm";
+               goto failure;
+            }
+           alg = (const char *)p;
+           p += alglen;
+
+           key = snew(struct ssh2_userkey);
+            key->alg = find_pubkey_alg_len(alglen, alg);
+           if (!key->alg) {
+               sfree(key);
+                fail_reason = "algorithm unknown";
+               goto failure;
+           }
+
+           bloblen = msgend - p;
+           key->data = key->alg->openssh_createkey(key->alg, &p, &bloblen);
+           if (!key->data) {
+               sfree(key);
+                fail_reason = "key setup failed";
+               goto failure;
+           }
+
+           /*
+            * p has been advanced by openssh_createkey, but
+            * certainly not _beyond_ the end of the buffer.
+            */
+           assert(p <= msgend);
+
+           if (msgend < p+4) {
+               key->alg->freekey(key->data);
+               sfree(key);
+                fail_reason = "request truncated before key comment";
+               goto failure;
+           }
+           commlen = toint(GET_32BIT(p));
+           p += 4;
+
+           if (commlen < 0 || commlen > msgend - p) {
+               key->alg->freekey(key->data);
+               sfree(key);
+                fail_reason = "request truncated before key comment";
+               goto failure;
+           }
+           comment = snewn(commlen + 1, char);
+           if (comment) {
+               memcpy(comment, p, commlen);
+               comment[commlen] = '\0';
+           }
+           key->comment = comment;
+
+            if (logfn) {
+                char *fingerprint = ssh2_fingerprint(key->alg, key->data);
+                plog(logctx, logfn, "submitted key: %s %s",
+                     fingerprint, key->comment);
+                sfree(fingerprint);
+            }
+
+           if (add234(ssh2keys, key) == key) {
+               keylist_update();
+                PUT_32BIT(ret, 1);
+               ret[4] = SSH_AGENT_SUCCESS;
+
+                plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS");
+           } else {
+               key->alg->freekey(key->data);
+               sfree(key->comment);
+               sfree(key);
+
+                fail_reason = "key already present";
+                goto failure;
+           }
+       }
+       break;
+      case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
+       /*
+        * Remove from the list and return SSH_AGENT_SUCCESS, or
+        * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
+        * start with.
+        */
+       {
+           struct RSAKey reqkey, *key;
+           int n;
+
+            plog(logctx, logfn, "request: SSH1_AGENTC_REMOVE_RSA_IDENTITY");
+
+           n = makekey(p, msgend - p, &reqkey, NULL, 0);
+           if (n < 0) {
+                fail_reason = "request truncated before public key";
+               goto failure;
+            }
+
+            if (logfn) {
+                char fingerprint[128];
+                reqkey.comment = NULL;
+                rsa_fingerprint(fingerprint, sizeof(fingerprint), &reqkey);
+                plog(logctx, logfn, "unwanted key: %s", fingerprint);
+            }
+
+           key = find234(rsakeys, &reqkey, NULL);
+           freebn(reqkey.exponent);
+           freebn(reqkey.modulus);
+           PUT_32BIT(ret, 1);
+           if (key) {
+                plog(logctx, logfn, "found with comment: %s", key->comment);
+
+               del234(rsakeys, key);
+               keylist_update();
+               freersakey(key);
+               sfree(key);
+               ret[4] = SSH_AGENT_SUCCESS;
+
+                plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS");
+           } else {
+                fail_reason = "key not found";
+                goto failure;
+            }
+       }
+       break;
+      case SSH2_AGENTC_REMOVE_IDENTITY:
+       /*
+        * Remove from the list and return SSH_AGENT_SUCCESS, or
+        * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
+        * start with.
+        */
+       {
+           struct ssh2_userkey *key;
+           struct blob b;
+
+            plog(logctx, logfn, "request: SSH2_AGENTC_REMOVE_IDENTITY");
+
+           if (msgend < p+4) {
+                fail_reason = "request truncated before public key";
+               goto failure;
+            }
+           b.len = toint(GET_32BIT(p));
+           p += 4;
+
+           if (b.len < 0 || b.len > msgend - p) {
+                fail_reason = "request truncated before public key";
+               goto failure;
+            }
+           b.blob = p;
+           p += b.len;
+
+            if (logfn) {
+                char *fingerprint = ssh2_fingerprint_blob(b.blob, b.len);
+                plog(logctx, logfn, "unwanted key: %s", fingerprint);
+                sfree(fingerprint);
+            }
+
+           key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
+           if (!key) {
+                fail_reason = "key not found";
+               goto failure;
+            }
+
+            plog(logctx, logfn, "found with comment: %s", key->comment);
+
+            del234(ssh2keys, key);
+            keylist_update();
+            key->alg->freekey(key->data);
+            sfree(key);
+           PUT_32BIT(ret, 1);
+            ret[4] = SSH_AGENT_SUCCESS;
+
+            plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS");
+       }
+       break;
+      case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
+       /*
+        * Remove all SSH-1 keys. Always returns success.
+        */
+       {
+           struct RSAKey *rkey;
+
+            plog(logctx, logfn, "request:"
+                " SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES");
+
+           while ((rkey = index234(rsakeys, 0)) != NULL) {
+               del234(rsakeys, rkey);
+               freersakey(rkey);
+               sfree(rkey);
+           }
+           keylist_update();
+
+           PUT_32BIT(ret, 1);
+           ret[4] = SSH_AGENT_SUCCESS;
+
+            plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS");
+       }
+       break;
+      case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
+       /*
+        * Remove all SSH-2 keys. Always returns success.
+        */
+       {
+           struct ssh2_userkey *skey;
+
+            plog(logctx, logfn, "request: SSH2_AGENTC_REMOVE_ALL_IDENTITIES");
+
+           while ((skey = index234(ssh2keys, 0)) != NULL) {
+               del234(ssh2keys, skey);
+               skey->alg->freekey(skey->data);
+               sfree(skey);
+           }
+           keylist_update();
+
+           PUT_32BIT(ret, 1);
+           ret[4] = SSH_AGENT_SUCCESS;
+
+            plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS");
+       }
+       break;
+      default:
+        plog(logctx, logfn, "request: unknown message type %d", type);
+
+        fail_reason = "unrecognised message";
+        /* fall through */
+      failure:
+       /*
+        * Unrecognised message. Return SSH_AGENT_FAILURE.
+        */
+       PUT_32BIT(ret, 1);
+       ret[4] = SSH_AGENT_FAILURE;
+        plog(logctx, logfn, "reply: SSH_AGENT_FAILURE (%s)", fail_reason);
+       break;
+    }
+
+    *outlen = 4 + GET_32BIT(ret);
+    return ret;
+}
+
+void *pageant_failure_msg(int *outlen)
+{
+    unsigned char *ret = snewn(5, unsigned char);
+    PUT_32BIT(ret, 1);
+    ret[4] = SSH_AGENT_FAILURE;
+    *outlen = 5;
+    return ret;
+}
+
+void pageant_init(void)
+{
+    pageant_local = TRUE;
+    rsakeys = newtree234(cmpkeys_rsa);
+    ssh2keys = newtree234(cmpkeys_ssh2);
+}
+
+struct RSAKey *pageant_nth_ssh1_key(int i)
+{
+    return index234(rsakeys, i);
+}
+
+struct ssh2_userkey *pageant_nth_ssh2_key(int i)
+{
+    return index234(ssh2keys, i);
+}
+
+int pageant_count_ssh1_keys(void)
+{
+    return count234(rsakeys);
+}
+
+int pageant_count_ssh2_keys(void)
+{
+    return count234(ssh2keys);
+}
+
+int pageant_add_ssh1_key(struct RSAKey *rkey)
+{
+    return add234(rsakeys, rkey) == rkey;
+}
+
+int pageant_add_ssh2_key(struct ssh2_userkey *skey)
+{
+    return add234(ssh2keys, skey) == skey;
+}
+
+int pageant_delete_ssh1_key(struct RSAKey *rkey)
+{
+    struct RSAKey *deleted = del234(rsakeys, rkey);
+    if (!deleted)
+        return FALSE;
+    assert(deleted == rkey);
+    return TRUE;
+}
+
+int pageant_delete_ssh2_key(struct ssh2_userkey *skey)
+{
+    struct ssh2_userkey *deleted = del234(ssh2keys, skey);
+    if (!deleted)
+        return FALSE;
+    assert(deleted == skey);
+    return TRUE;
+}
+
+/* ----------------------------------------------------------------------
+ * The agent plug.
+ */
+
+/*
+ * Coroutine macros similar to, but simplified from, those in ssh.c.
+ */
+#define crBegin(v)     { int *crLine = &v; switch(v) { case 0:;
+#define crFinish(z)    } *crLine = 0; return (z); }
+#define crGetChar(c) do                                         \
+    {                                                           \
+        while (len == 0) {                                      \
+            *crLine =__LINE__; return 1; case __LINE__:;        \
+        }                                                       \
+        len--;                                                  \
+        (c) = (unsigned char)*data++;                           \
+    } while (0)
+
+struct pageant_conn_state {
+    const struct plug_function_table *fn;
+    /* the above variable absolutely *must* be the first in this structure */
+
+    Socket connsock;
+    void *logctx;
+    pageant_logfn_t logfn;
+    unsigned char lenbuf[4], pktbuf[AGENT_MAX_MSGLEN];
+    unsigned len, got;
+    int real_packet;
+    int crLine;            /* for coroutine in pageant_conn_receive */
+};
+
+static int pageant_conn_closing(Plug plug, const char *error_msg,
+                                int error_code, int calling_back)
+{
+    struct pageant_conn_state *pc = (struct pageant_conn_state *)plug;
+    if (error_msg)
+        plog(pc->logctx, pc->logfn, "%p: error: %s", pc, error_msg);
+    else
+        plog(pc->logctx, pc->logfn, "%p: connection closed", pc);
+    sk_close(pc->connsock);
+    sfree(pc);
+    return 1;
+}
+
+static void pageant_conn_sent(Plug plug, int bufsize)
+{
+    /* struct pageant_conn_state *pc = (struct pageant_conn_state *)plug; */
+
+    /*
+     * We do nothing here, because we expect that there won't be a
+     * need to throttle and unthrottle the connection to an agent -
+     * clients will typically not send many requests, and will wait
+     * until they receive each reply before sending a new request.
+     */
+}
+
+static void pageant_conn_log(void *logctx, const char *fmt, va_list ap)
+{
+    /* Wrapper on pc->logfn that prefixes the connection identifier */
+    struct pageant_conn_state *pc = (struct pageant_conn_state *)logctx;
+    char *formatted = dupvprintf(fmt, ap);
+    plog(pc->logctx, pc->logfn, "%p: %s", pc, formatted);
+    sfree(formatted);
+}
+
+static int pageant_conn_receive(Plug plug, int urgent, char *data, int len)
+{
+    struct pageant_conn_state *pc = (struct pageant_conn_state *)plug;
+    char c;
+
+    crBegin(pc->crLine);
+
+    while (len > 0) {
+        pc->got = 0;
+        while (pc->got < 4) {
+            crGetChar(c);
+            pc->lenbuf[pc->got++] = c;
+        }
+
+        pc->len = GET_32BIT(pc->lenbuf);
+        pc->got = 0;
+        pc->real_packet = (pc->len < AGENT_MAX_MSGLEN-4);
+
+        while (pc->got < pc->len) {
+            crGetChar(c);
+            if (pc->real_packet)
+                pc->pktbuf[pc->got] = c;
+            pc->got++;
+        }
+
+        {
+            void *reply;
+            int replylen;
+
+            if (pc->real_packet) {
+                reply = pageant_handle_msg(pc->pktbuf, pc->len, &replylen, pc,
+                                           pc->logfn?pageant_conn_log:NULL);
+            } else {
+                plog(pc->logctx, pc->logfn, "%p: overlong message (%u)",
+                     pc, pc->len);
+                plog(pc->logctx, pc->logfn, "%p: reply: SSH_AGENT_FAILURE "
+                     "(message too long)", pc);
+                reply = pageant_failure_msg(&replylen);
+            }
+            sk_write(pc->connsock, reply, replylen);
+            smemclr(reply, replylen);
+        }
+    }
+
+    crFinish(1);
+}
+
+struct pageant_listen_state {
+    const struct plug_function_table *fn;
+    /* the above variable absolutely *must* be the first in this structure */
+
+    Socket listensock;
+    void *logctx;
+    pageant_logfn_t logfn;
+};
+
+static int pageant_listen_closing(Plug plug, const char *error_msg,
+                                  int error_code, int calling_back)
+{
+    struct pageant_listen_state *pl = (struct pageant_listen_state *)plug;
+    if (error_msg)
+        plog(pl->logctx, pl->logfn, "listening socket: error: %s", error_msg);
+    sk_close(pl->listensock);
+    pl->listensock = NULL;
+    return 1;
+}
+
+static int pageant_listen_accepting(Plug plug,
+                                    accept_fn_t constructor, accept_ctx_t ctx)
+{
+    static const struct plug_function_table connection_fn_table = {
+       NULL, /* no log function, because that's for outgoing connections */
+       pageant_conn_closing,
+        pageant_conn_receive,
+        pageant_conn_sent,
+       NULL /* no accepting function, because we've already done it */
+    };
+    struct pageant_listen_state *pl = (struct pageant_listen_state *)plug;
+    struct pageant_conn_state *pc;
+    const char *err;
+    char *peerinfo;
+
+    pc = snew(struct pageant_conn_state);
+    pc->fn = &connection_fn_table;
+    pc->logfn = pl->logfn;
+    pc->logctx = pl->logctx;
+    pc->crLine = 0;
+
+    pc->connsock = constructor(ctx, (Plug) pc);
+    if ((err = sk_socket_error(pc->connsock)) != NULL) {
+        sk_close(pc->connsock);
+        sfree(pc);
+       return TRUE;
+    }
+
+    sk_set_frozen(pc->connsock, 0);
+
+    peerinfo = sk_peer_info(pc->connsock);
+    if (peerinfo) {
+        plog(pl->logctx, pl->logfn, "%p: new connection from %s",
+             pc, peerinfo);
+    } else {
+        plog(pl->logctx, pl->logfn, "%p: new connection", pc);
+    }
+
+    return 0;
+}
+
+struct pageant_listen_state *pageant_listener_new(void)
+{
+    static const struct plug_function_table listener_fn_table = {
+        NULL, /* no log function, because that's for outgoing connections */
+        pageant_listen_closing,
+        NULL, /* no receive function on a listening socket */
+        NULL, /* no sent function on a listening socket */
+        pageant_listen_accepting
+    };
+
+    struct pageant_listen_state *pl = snew(struct pageant_listen_state);
+    pl->fn = &listener_fn_table;
+    pl->logctx = NULL;
+    pl->logfn = NULL;
+    pl->listensock = NULL;
+    return pl;
+}
+
+void pageant_listener_got_socket(struct pageant_listen_state *pl, Socket sock)
+{
+    pl->listensock = sock;
+}
+
+void pageant_listener_set_logfn(struct pageant_listen_state *pl,
+                                void *logctx, pageant_logfn_t logfn)
+{
+    pl->logctx = logctx;
+    pl->logfn = logfn;
+}
+
+void pageant_listener_free(struct pageant_listen_state *pl)
+{
+    if (pl->listensock)
+        sk_close(pl->listensock);
+    sfree(pl);
+}
+
+/* ----------------------------------------------------------------------
+ * Code to perform agent operations either as a client, or within the
+ * same process as the running agent.
+ */
+
+static tree234 *passphrases = NULL;
+
+/*
+ * After processing a list of filenames, we want to forget the
+ * passphrases.
+ */
+void pageant_forget_passphrases(void)
+{
+    if (!passphrases)                  /* in case we never set it up at all */
+        return;
+
+    while (count234(passphrases) > 0) {
+       char *pp = index234(passphrases, 0);
+       smemclr(pp, strlen(pp));
+       delpos234(passphrases, 0);
+       free(pp);
+    }
+}
+
+void *pageant_get_keylist1(int *length)
+{
+    void *ret;
+
+    if (!pageant_local) {
+       unsigned char request[5], *response;
+       void *vresponse;
+       int resplen, retval;
+       request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
+       PUT_32BIT(request, 1);
+
+       retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
+       assert(retval == 1);
+       response = vresponse;
+       if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER) {
+            sfree(response);
+           return NULL;
+        }
+
+       ret = snewn(resplen-5, unsigned char);
+       memcpy(ret, response+5, resplen-5);
+       sfree(response);
+
+       if (length)
+           *length = resplen-5;
+    } else {
+       ret = pageant_make_keylist1(length);
+    }
+    return ret;
+}
+
+void *pageant_get_keylist2(int *length)
+{
+    void *ret;
+
+    if (!pageant_local) {
+       unsigned char request[5], *response;
+       void *vresponse;
+       int resplen, retval;
+
+       request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
+       PUT_32BIT(request, 1);
+
+       retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
+       assert(retval == 1);
+       response = vresponse;
+       if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER) {
+            sfree(response);
+           return NULL;
+        }
+
+       ret = snewn(resplen-5, unsigned char);
+       memcpy(ret, response+5, resplen-5);
+       sfree(response);
+
+       if (length)
+           *length = resplen-5;
+    } else {
+       ret = pageant_make_keylist2(length);
+    }
+    return ret;
+}
+
+int pageant_add_keyfile(Filename *filename, const char *passphrase,
+                        char **retstr)
+{
+    struct RSAKey *rkey = NULL;
+    struct ssh2_userkey *skey = NULL;
+    int needs_pass;
+    int ret;
+    int attempts;
+    char *comment;
+    const char *this_passphrase;
+    const char *error = NULL;
+    int type;
+
+    if (!passphrases) {
+        passphrases = newtree234(NULL);
+    }
+
+    *retstr = NULL;
+
+    type = key_type(filename);
+    if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2) {
+       *retstr = dupprintf("Couldn't load this key (%s)",
+                            key_type_to_str(type));
+       return PAGEANT_ACTION_FAILURE;
+    }
+
+    /*
+     * See if the key is already loaded (in the primary Pageant,
+     * which may or may not be us).
+     */
+    {
+       void *blob;
+       unsigned char *keylist, *p;
+       int i, nkeys, bloblen, keylistlen;
+
+       if (type == SSH_KEYTYPE_SSH1) {
+           if (!rsakey_pubblob(filename, &blob, &bloblen, NULL, &error)) {
+                *retstr = dupprintf("Couldn't load private key (%s)", error);
+                return PAGEANT_ACTION_FAILURE;
+           }
+           keylist = pageant_get_keylist1(&keylistlen);
+       } else {
+           unsigned char *blob2;
+           blob = ssh2_userkey_loadpub(filename, NULL, &bloblen,
+                                       NULL, &error);
+           if (!blob) {
+                *retstr = dupprintf("Couldn't load private key (%s)", error);
+               return PAGEANT_ACTION_FAILURE;
+           }
+           /* For our purposes we want the blob prefixed with its length */
+           blob2 = snewn(bloblen+4, unsigned char);
+           PUT_32BIT(blob2, bloblen);
+           memcpy(blob2 + 4, blob, bloblen);
+           sfree(blob);
+           blob = blob2;
+
+           keylist = pageant_get_keylist2(&keylistlen);
+       }
+       if (keylist) {
+           if (keylistlen < 4) {
+               *retstr = dupstr("Received broken key list from agent");
+               return PAGEANT_ACTION_FAILURE;
+           }
+           nkeys = toint(GET_32BIT(keylist));
+           if (nkeys < 0) {
+               *retstr = dupstr("Received broken key list from agent");
+               return PAGEANT_ACTION_FAILURE;
+           }
+           p = keylist + 4;
+           keylistlen -= 4;
+
+           for (i = 0; i < nkeys; i++) {
+               if (!memcmp(blob, p, bloblen)) {
+                   /* Key is already present; we can now leave. */
+                   sfree(keylist);
+                   sfree(blob);
+                    return PAGEANT_ACTION_OK;
+               }
+               /* Now skip over public blob */
+               if (type == SSH_KEYTYPE_SSH1) {
+                   int n = rsa_public_blob_len(p, keylistlen);
+                   if (n < 0) {
+                        *retstr = dupstr("Received broken key list from agent");
+                        return PAGEANT_ACTION_FAILURE;
+                   }
+                   p += n;
+                   keylistlen -= n;
+               } else {
+                   int n;
+                   if (keylistlen < 4) {
+                        *retstr = dupstr("Received broken key list from agent");
+                        return PAGEANT_ACTION_FAILURE;
+                   }
+                   n = toint(4 + GET_32BIT(p));
+                   if (n < 0 || keylistlen < n) {
+                        *retstr = dupstr("Received broken key list from agent");
+                        return PAGEANT_ACTION_FAILURE;
+                   }
+                   p += n;
+                   keylistlen -= n;
+               }
+               /* Now skip over comment field */
+               {
+                   int n;
+                   if (keylistlen < 4) {
+                        *retstr = dupstr("Received broken key list from agent");
+                        return PAGEANT_ACTION_FAILURE;
+                   }
+                   n = toint(4 + GET_32BIT(p));
+                   if (n < 0 || keylistlen < n) {
+                        *retstr = dupstr("Received broken key list from agent");
+                        return PAGEANT_ACTION_FAILURE;
+                   }
+                   p += n;
+                   keylistlen -= n;
+               }
+           }
+
+           sfree(keylist);
+       }
+
+       sfree(blob);
+    }
+
+    error = NULL;
+    if (type == SSH_KEYTYPE_SSH1)
+       needs_pass = rsakey_encrypted(filename, &comment);
+    else
+       needs_pass = ssh2_userkey_encrypted(filename, &comment);
+    attempts = 0;
+    if (type == SSH_KEYTYPE_SSH1)
+       rkey = snew(struct RSAKey);
+
+    /*
+     * Loop round repeatedly trying to load the key, until we either
+     * succeed, fail for some serious reason, or run out of
+     * passphrases to try.
+     */
+    while (1) {
+       if (needs_pass) {
+
+            /*
+             * If we've been given a passphrase on input, try using
+             * it. Otherwise, try one from our tree234 of previously
+             * useful passphrases.
+             */
+            if (passphrase) {
+                this_passphrase = (attempts == 0 ? passphrase : NULL);
+            } else {
+                this_passphrase = (const char *)index234(passphrases, attempts);
+            }
+
+            if (!this_passphrase) {
+                /*
+                 * Run out of passphrases to try.
+                 */
+                *retstr = comment;
+                return PAGEANT_ACTION_NEED_PP;
+            }
+       } else
+           this_passphrase = "";
+
+       if (type == SSH_KEYTYPE_SSH1)
+           ret = loadrsakey(filename, rkey, this_passphrase, &error);
+       else {
+           skey = ssh2_load_userkey(filename, this_passphrase, &error);
+           if (skey == SSH2_WRONG_PASSPHRASE)
+               ret = -1;
+           else if (!skey)
+               ret = 0;
+           else
+               ret = 1;
+       }
+
+        if (ret == 0) {
+            /*
+             * Failed to load the key file, for some reason other than
+             * a bad passphrase.
+             */
+            *retstr = dupstr(error);
+            return PAGEANT_ACTION_FAILURE;
+        } else if (ret == 1) {
+            /*
+             * Successfully loaded the key file.
+             */
+            break;
+        } else {
+            /*
+             * Passphrase wasn't right; go round again.
+             */
+            attempts++;
+        }
+    }
+
+    /*
+     * If we get here, we've succesfully loaded the key into
+     * rkey/skey, but not yet added it to the agent.
+     */
+
+    /*
+     * If the key was successfully decrypted, save the passphrase for
+     * use with other keys we try to load.
+     */
+    {
+        char *pp_copy = dupstr(this_passphrase);
+       if (addpos234(passphrases, pp_copy, 0) != pp_copy) {
+            /* No need; it was already there. */
+            smemclr(pp_copy, strlen(pp_copy));
+            sfree(pp_copy);
+        }
+    }
+
+    if (comment)
+       sfree(comment);
+
+    if (type == SSH_KEYTYPE_SSH1) {
+       if (!pageant_local) {
+           unsigned char *request, *response;
+           void *vresponse;
+           int reqlen, clen, resplen, ret;
+
+           clen = strlen(rkey->comment);
+
+           reqlen = 4 + 1 +           /* length, message type */
+               4 +                    /* bit count */
+               ssh1_bignum_length(rkey->modulus) +
+               ssh1_bignum_length(rkey->exponent) +
+               ssh1_bignum_length(rkey->private_exponent) +
+               ssh1_bignum_length(rkey->iqmp) +
+               ssh1_bignum_length(rkey->p) +
+               ssh1_bignum_length(rkey->q) + 4 + clen  /* comment */
+               ;
+
+           request = snewn(reqlen, unsigned char);
+
+           request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
+           reqlen = 5;
+           PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
+           reqlen += 4;
+           reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
+           reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
+           reqlen +=
+               ssh1_write_bignum(request + reqlen,
+                                 rkey->private_exponent);
+           reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
+           reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
+           reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
+           PUT_32BIT(request + reqlen, clen);
+           memcpy(request + reqlen + 4, rkey->comment, clen);
+           reqlen += 4 + clen;
+           PUT_32BIT(request, reqlen - 4);
+
+           ret = agent_query(request, reqlen, &vresponse, &resplen,
+                             NULL, NULL);
+           assert(ret == 1);
+           response = vresponse;
+           if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) {
+               *retstr = dupstr("The already running Pageant "
+                                 "refused to add the key.");
+                return PAGEANT_ACTION_FAILURE;
+            }
+           sfree(request);
+           sfree(response);
+       } else {
+           if (!pageant_add_ssh1_key(rkey)) {
+               sfree(rkey);           /* already present, don't waste RAM */
+            }
+       }
+    } else {
+       if (!pageant_local) {
+           unsigned char *request, *response;
+           void *vresponse;
+           int reqlen, alglen, clen, keybloblen, resplen, ret;
+           alglen = strlen(skey->alg->name);
+           clen = strlen(skey->comment);
+
+           keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
+
+           reqlen = 4 + 1 +           /* length, message type */
+               4 + alglen +           /* algorithm name */
+               keybloblen +           /* key data */
+               4 + clen               /* comment */
+               ;
+
+           request = snewn(reqlen, unsigned char);
+
+           request[4] = SSH2_AGENTC_ADD_IDENTITY;
+           reqlen = 5;
+           PUT_32BIT(request + reqlen, alglen);
+           reqlen += 4;
+           memcpy(request + reqlen, skey->alg->name, alglen);
+           reqlen += alglen;
+           reqlen += skey->alg->openssh_fmtkey(skey->data,
+                                               request + reqlen,
+                                               keybloblen);
+           PUT_32BIT(request + reqlen, clen);
+           memcpy(request + reqlen + 4, skey->comment, clen);
+           reqlen += clen + 4;
+           PUT_32BIT(request, reqlen - 4);
+
+           ret = agent_query(request, reqlen, &vresponse, &resplen,
+                             NULL, NULL);
+           assert(ret == 1);
+           response = vresponse;
+           if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) {
+               *retstr = dupstr("The already running Pageant "
+                                 "refused to add the key.");
+                return PAGEANT_ACTION_FAILURE;
+            }
+
+           sfree(request);
+           sfree(response);
+       } else {
+           if (!pageant_add_ssh2_key(skey)) {
+               skey->alg->freekey(skey->data);
+               sfree(skey);           /* already present, don't waste RAM */
+           }
+       }
+    }
+    return PAGEANT_ACTION_OK;
+}
+
+int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx,
+                      char **retstr)
+{
+    unsigned char *keylist, *p;
+    int i, nkeys, keylistlen;
+    char *comment;
+    struct pageant_pubkey cbkey;
+
+    keylist = pageant_get_keylist1(&keylistlen);
+    if (keylistlen < 4) {
+        *retstr = dupstr("Received broken SSH-1 key list from agent");
+        sfree(keylist);
+        return PAGEANT_ACTION_FAILURE;
+    }
+    nkeys = toint(GET_32BIT(keylist));
+    if (nkeys < 0) {
+        *retstr = dupstr("Received broken SSH-1 key list from agent");
+        sfree(keylist);
+        return PAGEANT_ACTION_FAILURE;
+    }
+    p = keylist + 4;
+    keylistlen -= 4;
+
+    for (i = 0; i < nkeys; i++) {
+        struct RSAKey rkey;
+        char fingerprint[128];
+        int n;
+
+        /* public blob and fingerprint */
+        memset(&rkey, 0, sizeof(rkey));
+        n = makekey(p, keylistlen, &rkey, NULL, 0);
+        if (n < 0 || n > keylistlen) {
+            freersakey(&rkey);
+            *retstr = dupstr("Received broken SSH-1 key list from agent");
+            sfree(keylist);
+            return PAGEANT_ACTION_FAILURE;
+        }
+        p += n, keylistlen -= n;
+        rsa_fingerprint(fingerprint, sizeof(fingerprint), &rkey);
+
+        /* comment */
+        if (keylistlen < 4) {
+            *retstr = dupstr("Received broken SSH-1 key list from agent");
+            freersakey(&rkey);
+            sfree(keylist);
+            return PAGEANT_ACTION_FAILURE;
+        }
+        n = toint(GET_32BIT(p));
+        p += 4, keylistlen -= 4;
+        if (n < 0 || keylistlen < n) {
+            *retstr = dupstr("Received broken SSH-1 key list from agent");
+            freersakey(&rkey);
+            sfree(keylist);
+            return PAGEANT_ACTION_FAILURE;
+        }
+        comment = dupprintf("%.*s", (int)n, (const char *)p);
+        p += n, keylistlen -= n;
+
+        cbkey.blob = rsa_public_blob(&rkey, &cbkey.bloblen);
+        cbkey.comment = comment;
+        cbkey.ssh_version = 1;
+        callback(callback_ctx, fingerprint, comment, &cbkey);
+        sfree(cbkey.blob);
+        freersakey(&rkey);
+        sfree(comment);
+    }
+
+    sfree(keylist);
+
+    if (keylistlen != 0) {
+        *retstr = dupstr("Received broken SSH-1 key list from agent");
+        return PAGEANT_ACTION_FAILURE;
+    }
+
+    keylist = pageant_get_keylist2(&keylistlen);
+    if (keylistlen < 4) {
+        *retstr = dupstr("Received broken SSH-2 key list from agent");
+        sfree(keylist);
+        return PAGEANT_ACTION_FAILURE;
+    }
+    nkeys = toint(GET_32BIT(keylist));
+    if (nkeys < 0) {
+        *retstr = dupstr("Received broken SSH-2 key list from agent");
+        sfree(keylist);
+        return PAGEANT_ACTION_FAILURE;
+    }
+    p = keylist + 4;
+    keylistlen -= 4;
+
+    for (i = 0; i < nkeys; i++) {
+        char *fingerprint;
+        int n;
+
+        /* public blob */
+        if (keylistlen < 4) {
+            *retstr = dupstr("Received broken SSH-2 key list from agent");
+            sfree(keylist);
+            return PAGEANT_ACTION_FAILURE;
+        }
+        n = toint(GET_32BIT(p));
+        p += 4, keylistlen -= 4;
+        if (n < 0 || keylistlen < n) {
+            *retstr = dupstr("Received broken SSH-2 key list from agent");
+            sfree(keylist);
+            return PAGEANT_ACTION_FAILURE;
+        }
+        fingerprint = ssh2_fingerprint_blob(p, n);
+        cbkey.blob = p;
+        cbkey.bloblen = n;
+        p += n, keylistlen -= n;
+
+        /* comment */
+        if (keylistlen < 4) {
+            *retstr = dupstr("Received broken SSH-2 key list from agent");
+            sfree(fingerprint);
+            sfree(keylist);
+            return PAGEANT_ACTION_FAILURE;
+        }
+        n = toint(GET_32BIT(p));
+        p += 4, keylistlen -= 4;
+        if (n < 0 || keylistlen < n) {
+            *retstr = dupstr("Received broken SSH-2 key list from agent");
+            sfree(fingerprint);
+            sfree(keylist);
+            return PAGEANT_ACTION_FAILURE;
+        }
+        comment = dupprintf("%.*s", (int)n, (const char *)p);
+        p += n, keylistlen -= n;
+
+        cbkey.ssh_version = 2;
+        cbkey.comment = comment;
+        callback(callback_ctx, fingerprint, comment, &cbkey);
+        sfree(fingerprint);
+        sfree(comment);
+    }
+
+    sfree(keylist);
+
+    if (keylistlen != 0) {
+        *retstr = dupstr("Received broken SSH-1 key list from agent");
+        return PAGEANT_ACTION_FAILURE;
+    }
+
+    return PAGEANT_ACTION_OK;
+}
+
+int pageant_delete_key(struct pageant_pubkey *key, char **retstr)
+{
+    unsigned char *request, *response;
+    int reqlen, resplen, ret;
+    void *vresponse;
+
+    if (key->ssh_version == 1) {
+        reqlen = 5 + key->bloblen;
+        request = snewn(reqlen, unsigned char);
+        PUT_32BIT(request, reqlen - 4);
+        request[4] = SSH1_AGENTC_REMOVE_RSA_IDENTITY;
+        memcpy(request + 5, key->blob, key->bloblen);
+    } else {
+        reqlen = 9 + key->bloblen;
+        request = snewn(reqlen, unsigned char);
+        PUT_32BIT(request, reqlen - 4);
+        request[4] = SSH2_AGENTC_REMOVE_IDENTITY;
+        PUT_32BIT(request + 5, key->bloblen);
+        memcpy(request + 9, key->blob, key->bloblen);
+    }
+
+    ret = agent_query(request, reqlen, &vresponse, &resplen, NULL, NULL);
+    assert(ret == 1);
+    response = vresponse;
+    if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) {
+        *retstr = dupstr("Agent failed to delete key");
+        ret = PAGEANT_ACTION_FAILURE;
+    } else {
+        *retstr = NULL;
+        ret = PAGEANT_ACTION_OK;
+    }
+    sfree(request);
+    sfree(response);
+    return ret;
+}
+
+int pageant_delete_all_keys(char **retstr)
+{
+    unsigned char request[5], *response;
+    int reqlen, resplen, success, ret;
+    void *vresponse;
+
+    PUT_32BIT(request, 1);
+    request[4] = SSH2_AGENTC_REMOVE_ALL_IDENTITIES;
+    reqlen = 5;
+    ret = agent_query(request, reqlen, &vresponse, &resplen, NULL, NULL);
+    assert(ret == 1);
+    response = vresponse;
+    success = (resplen >= 4 && response[4] == SSH_AGENT_SUCCESS);
+    sfree(response);
+    if (!success) {
+        *retstr = dupstr("Agent failed to delete SSH-2 keys");
+        return PAGEANT_ACTION_FAILURE;
+    }
+
+    PUT_32BIT(request, 1);
+    request[4] = SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES;
+    reqlen = 5;
+    ret = agent_query(request, reqlen, &vresponse, &resplen, NULL, NULL);
+    assert(ret == 1);
+    response = vresponse;
+    success = (resplen >= 4 && response[4] == SSH_AGENT_SUCCESS);
+    sfree(response);
+    if (!success) {
+        *retstr = dupstr("Agent failed to delete SSH-1 keys");
+        return PAGEANT_ACTION_FAILURE;
+    }
+
+    *retstr = NULL;
+    return PAGEANT_ACTION_OK;
+}
+
+struct pageant_pubkey *pageant_pubkey_copy(struct pageant_pubkey *key)
+{
+    struct pageant_pubkey *ret = snew(struct pageant_pubkey);
+    ret->blob = snewn(key->bloblen, unsigned char);
+    memcpy(ret->blob, key->blob, key->bloblen);
+    ret->bloblen = key->bloblen;
+    ret->comment = key->comment ? dupstr(key->comment) : NULL;
+    ret->ssh_version = key->ssh_version;
+    return ret;
+}
+
+void pageant_pubkey_free(struct pageant_pubkey *key)
+{
+    sfree(key->comment);
+    sfree(key->blob);
+    sfree(key);
+}
diff --git a/pageant.h b/pageant.h
new file mode 100644 (file)
index 0000000..6e29f40
--- /dev/null
+++ b/pageant.h
@@ -0,0 +1,143 @@
+/*
+ * pageant.h: header for pageant.c.
+ */
+
+#include <stdarg.h>
+
+/*
+ * FIXME: it would be nice not to have this arbitrary limit. It's
+ * currently needed because the Windows Pageant IPC system needs an
+ * upper bound known to the client, but it's also reused as a basic
+ * sanity check on incoming messages' length fields.
+ */
+#define AGENT_MAX_MSGLEN  8192
+
+typedef void (*pageant_logfn_t)(void *logctx, const char *fmt, va_list ap);
+
+/*
+ * Initial setup.
+ */
+void pageant_init(void);
+
+/*
+ * The main agent function that answers messages.
+ *
+ * Expects a message/length pair as input, minus its initial length
+ * field but still with its type code on the front.
+ *
+ * Returns a fully formatted message as output, *with* its initial
+ * length field, and sets *outlen to the full size of that message.
+ */
+void *pageant_handle_msg(const void *msg, int msglen, int *outlen,
+                         void *logctx, pageant_logfn_t logfn);
+
+/*
+ * Construct a failure response. Useful for agent front ends which
+ * suffer a problem before they even get to pageant_handle_msg.
+ */
+void *pageant_failure_msg(int *outlen);
+
+/*
+ * Construct a list of public keys, just as the two LIST_IDENTITIES
+ * requests would have returned them.
+ */
+void *pageant_make_keylist1(int *length);
+void *pageant_make_keylist2(int *length);
+
+/*
+ * Accessor functions for Pageant's internal key lists. Fetch the nth
+ * key; count the keys; attempt to add a key (returning true on
+ * success, in which case the ownership of the key structure has been
+ * taken over by pageant.c); attempt to delete a key (returning true
+ * on success, in which case the ownership of the key structure is
+ * passed back to the client).
+ */
+struct RSAKey *pageant_nth_ssh1_key(int i);
+struct ssh2_userkey *pageant_nth_ssh2_key(int i);
+int pageant_count_ssh1_keys(void);
+int pageant_count_ssh2_keys(void);
+int pageant_add_ssh1_key(struct RSAKey *rkey);
+int pageant_add_ssh2_key(struct ssh2_userkey *skey);
+int pageant_delete_ssh1_key(struct RSAKey *rkey);
+int pageant_delete_ssh2_key(struct ssh2_userkey *skey);
+
+/*
+ * This callback must be provided by the Pageant front end code.
+ * pageant_handle_msg calls it to indicate that the message it's just
+ * handled has changed the list of keys held by the agent. Front ends
+ * which expose that key list through dedicated UI may need to refresh
+ * that UI's state in this function; other front ends can leave it
+ * empty.
+ */
+void keylist_update(void);
+
+/*
+ * Functions to establish a listening socket speaking the SSH agent
+ * protocol. Call pageant_listener_new() to set up a state; then
+ * create a socket using the returned pointer as a Plug; then call
+ * pageant_listener_got_socket() to give the listening state its own
+ * socket pointer. Also, provide a logging function later if you want
+ * to.
+ */
+struct pageant_listen_state;
+struct pageant_listen_state *pageant_listener_new(void);
+void pageant_listener_got_socket(struct pageant_listen_state *pl, Socket sock);
+void pageant_listener_set_logfn(struct pageant_listen_state *pl,
+                                void *logctx, pageant_logfn_t logfn);
+void pageant_listener_free(struct pageant_listen_state *pl);
+
+/*
+ * Functions to perform specific key actions, either as a client of an
+ * ssh-agent running elsewhere, or directly on the agent state in this
+ * process. (On at least one platform we want to do this in an
+ * agnostic way between the two situations.)
+ *
+ * pageant_get_keylist{1,2} work just like pageant_make_keylist{1,2}
+ * above, except that they can also cope if they have to contact an
+ * external agent.
+ *
+ * pageant_add_keyfile() is used to load a private key from a file and
+ * add it to the agent. Initially, you should call it with passphrase
+ * NULL, and it will check if the key is already in the agent, and
+ * whether a passphrase is required. Return values are given in the
+ * enum below. On return, *retstr will either be NULL, or a
+ * dynamically allocated string containing a key comment or an error
+ * message.
+ *
+ * pageant_add_keyfile() also remembers passphrases with which it's
+ * successfully decrypted keys (because if you try to add multiple
+ * keys in one go, you might very well have used the same passphrase
+ * for keys that have the same trust properties). Call
+ * pageant_forget_passphrases() to get rid of them all.
+ */
+void *pageant_get_keylist1(int *length);
+void *pageant_get_keylist2(int *length);
+enum {
+    PAGEANT_ACTION_OK,       /* success; no further action needed */
+    PAGEANT_ACTION_FAILURE,  /* failure; *retstr is error message */
+    PAGEANT_ACTION_NEED_PP   /* need passphrase: *retstr is key comment */
+};
+int pageant_add_keyfile(Filename *filename, const char *passphrase,
+                        char **retstr);
+void pageant_forget_passphrases(void);
+
+struct pageant_pubkey {
+    /* Everything needed to identify a public key found by
+     * pageant_enum_keys and pass it back to the agent or other code
+     * later */
+    void *blob;
+    int bloblen;
+    char *comment;
+    int ssh_version;
+};
+struct pageant_pubkey *pageant_pubkey_copy(struct pageant_pubkey *key);
+void pageant_pubkey_free(struct pageant_pubkey *key);
+
+typedef void (*pageant_key_enum_fn_t)(void *ctx,
+                                      const char *fingerprint,
+                                      const char *comment,
+                                      struct pageant_pubkey *key);
+int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx,
+                      char **retstr);
+int pageant_delete_key(struct pageant_pubkey *key, char **retstr);
+int pageant_delete_all_keys(char **retstr);
index edbd7bcc3978bc12e6fe5c9ef6306c1ca803d886..8a73a182d55329ab33a0ab763ff6d3c89d1cac39 100644 (file)
--- a/portfwd.c
+++ b/portfwd.c
@@ -157,7 +157,7 @@ static int pfl_closing(Plug plug, const char *error_msg, int error_code,
     return 1;
 }
 
-static void wrap_send_port_open(void *channel, char *hostname, int port,
+static void wrap_send_port_open(void *channel, const char *hostname, int port,
                                 Socket s)
 {
     char *peerinfo, *description;
@@ -443,7 +443,8 @@ char *pfd_connect(struct PortForwarding **pf_ret, char *hostname,int port,
     /*
      * Try to find host.
      */
-    addr = name_lookup(hostname, port, &dummy_realhost, conf, addressfamily);
+    addr = name_lookup(hostname, port, &dummy_realhost, conf, addressfamily,
+                       NULL, NULL);
     if ((err = sk_addr_error(addr)) != NULL) {
         char *err_ret = dupstr(err);
        sk_addr_free(addr);
index 1bea6fb84a06e648061ff6f48aaabcd48975d507..34e569bce6291eb36ce05bde9bda778011ef1d3e 100644 (file)
--- a/pproxy.c
+++ b/pproxy.c
@@ -8,7 +8,7 @@
 #include "network.h"
 #include "proxy.h"
 
-Socket platform_new_connection(SockAddr addr, char *hostname,
+Socket platform_new_connection(SockAddr addr, const char *hostname,
                               int port, int privport,
                               int oobinline, int nodelay, int keepalive,
                               Plug plug, Conf *conf)
diff --git a/proxy.c b/proxy.c
index cf45e1b79fe178b926eb58364f0db1b451ed9069..f9f2cd62df8de99f521dc9588c89be35b5b22f38 100644 (file)
--- a/proxy.c
+++ b/proxy.c
@@ -362,20 +362,45 @@ int proxy_for_destination (SockAddr addr, const char *hostname,
     return 1;
 }
 
-SockAddr name_lookup(char *host, int port, char **canonicalname,
-                    Conf *conf, int addressfamily)
+static char *dns_log_msg(const char *host, int addressfamily,
+                         const char *reason)
 {
+    return dupprintf("Looking up host \"%s\"%s for %s", host,
+                     (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" :
+                      addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" :
+                      ""), reason);
+}
+
+SockAddr name_lookup(const char *host, int port, char **canonicalname,
+                    Conf *conf, int addressfamily, void *frontend,
+                     const char *reason)
+{
+    char *logmsg;
     if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE &&
        do_proxy_dns(conf) &&
        proxy_for_destination(NULL, host, port, conf)) {
+
+        if (frontend) {
+            logmsg = dupprintf("Leaving host lookup to proxy of \"%s\""
+                               " (for %s)", host, reason);
+            logevent(frontend, logmsg);
+            sfree(logmsg);
+        }
+
        *canonicalname = dupstr(host);
        return sk_nonamelookup(host);
+    } else {
+        if (frontend) {
+            logmsg = dns_log_msg(host, addressfamily, reason);
+            logevent(frontend, logmsg);
+            sfree(logmsg);
+        }
+
+        return sk_namelookup(host, canonicalname, addressfamily);
     }
-
-    return sk_namelookup(host, canonicalname, addressfamily);
 }
 
-Socket new_connection(SockAddr addr, char *hostname,
+Socket new_connection(SockAddr addr, const char *hostname,
                      int port, int privport,
                      int oobinline, int nodelay, int keepalive,
                      Plug plug, Conf *conf)
@@ -407,6 +432,7 @@ Socket new_connection(SockAddr addr, char *hostname,
        Proxy_Plug pplug;
        SockAddr proxy_addr;
        char *proxy_canonical_name;
+        const char *proxy_type;
        Socket sret;
        int type;
 
@@ -439,23 +465,45 @@ Socket new_connection(SockAddr addr, char *hostname,
        type = conf_get_int(conf, CONF_proxy_type);
        if (type == PROXY_HTTP) {
            ret->negotiate = proxy_http_negotiate;
+            proxy_type = "HTTP";
        } else if (type == PROXY_SOCKS4) {
             ret->negotiate = proxy_socks4_negotiate;
+            proxy_type = "SOCKS 4";
        } else if (type == PROXY_SOCKS5) {
             ret->negotiate = proxy_socks5_negotiate;
+            proxy_type = "SOCKS 5";
        } else if (type == PROXY_TELNET) {
            ret->negotiate = proxy_telnet_negotiate;
+            proxy_type = "Telnet";
        } else {
            ret->error = "Proxy error: Unknown proxy method";
            return (Socket) ret;
        }
 
+        {
+            char *logmsg = dupprintf("Will use %s proxy at %s:%d to connect"
+                                      " to %s:%d", proxy_type,
+                                      conf_get_str(conf, CONF_proxy_host),
+                                      conf_get_int(conf, CONF_proxy_port),
+                                      hostname, port);
+            plug_log(plug, 2, NULL, 0, logmsg, 0);
+            sfree(logmsg);
+        }
+
        /* create the proxy plug to map calls from the actual
         * socket into our proxy socket layer */
        pplug = snew(struct Plug_proxy_tag);
        pplug->fn = &plug_fn_table;
        pplug->proxy_socket = ret;
 
+        {
+            char *logmsg = dns_log_msg(conf_get_str(conf, CONF_proxy_host),
+                                       conf_get_int(conf, CONF_addressfamily),
+                                       "proxy");
+            plug_log(plug, 2, NULL, 0, logmsg, 0);
+            sfree(logmsg);
+        }
+
        /* look-up proxy */
        proxy_addr = sk_namelookup(conf_get_str(conf, CONF_proxy_host),
                                   &proxy_canonical_name,
@@ -468,6 +516,16 @@ Socket new_connection(SockAddr addr, char *hostname,
        }
        sfree(proxy_canonical_name);
 
+        {
+            char addrbuf[256], *logmsg;
+            sk_getaddr(addr, addrbuf, lenof(addrbuf));
+            logmsg = dupprintf("Connecting to %s proxy at %s port %d",
+                               proxy_type, addrbuf,
+                               conf_get_int(conf, CONF_proxy_port));
+            plug_log(plug, 2, NULL, 0, logmsg, 0);
+            sfree(logmsg);
+        }
+
        /* create the actual socket we will be using,
         * connected to our proxy server and port.
         */
@@ -489,8 +547,8 @@ Socket new_connection(SockAddr addr, char *hostname,
     return sk_new(addr, port, privport, oobinline, nodelay, keepalive, plug);
 }
 
-Socket new_listener(char *srcaddr, int port, Plug plug, int local_host_only,
-                   Conf *conf, int addressfamily)
+Socket new_listener(const char *srcaddr, int port, Plug plug,
+                    int local_host_only, Conf *conf, int addressfamily)
 {
     /* TODO: SOCKS (and potentially others) support inbound
      * TODO: connections via the proxy. support them.
@@ -1456,6 +1514,39 @@ int proxy_telnet_negotiate (Proxy_Socket p, int change)
        formatted_cmd = format_telnet_command(p->remote_addr, p->remote_port,
                                              p->conf);
 
+        {
+            /*
+             * Re-escape control chars in the command, for logging.
+             */
+            char *reescaped = snewn(4*strlen(formatted_cmd) + 1, char);
+            const char *in;
+            char *out;
+            char *logmsg;
+
+            for (in = formatted_cmd, out = reescaped; *in; in++) {
+                if (*in == '\n') {
+                    *out++ = '\\'; *out++ = 'n';
+                } else if (*in == '\r') {
+                    *out++ = '\\'; *out++ = 'r';
+                } else if (*in == '\t') {
+                    *out++ = '\\'; *out++ = 't';
+                } else if (*in == '\\') {
+                    *out++ = '\\'; *out++ = '\\';
+                } else if ((unsigned)(((unsigned char)*in) - 0x20) <
+                           (0x7F-0x20)) {
+                    *out++ = *in;
+                } else {
+                    out += sprintf(out, "\\x%02X", (unsigned)*in & 0xFF);
+                }
+            }
+            *out = '\0';
+
+            logmsg = dupprintf("Sending Telnet proxy command: %s", reescaped);
+            plug_log(p->plug, 2, NULL, 0, logmsg, 0);
+            sfree(logmsg);
+            sfree(reescaped);
+        }
+
        sk_write(p->sub_socket, formatted_cmd, strlen(formatted_cmd));
        sfree(formatted_cmd);
 
diff --git a/proxy.h b/proxy.h
index 12b47e16de88d3860faee4ab6d7639a07f77e780..2e2324c078343ae5d2229a79e5b08d20659b10d0 100644 (file)
--- a/proxy.h
+++ b/proxy.h
@@ -19,7 +19,7 @@ struct Socket_proxy_tag {
     const struct socket_function_table *fn;
     /* the above variable absolutely *must* be the first in this structure */
 
-    char * error;
+    const char *error;
 
     Socket sub_socket;
     Plug plug;
diff --git a/pscp.c b/pscp.c
index 3e41454d3d4e02c5028f1cab7a83a1c951a31184..a4e55fe09b457be378152051125797071afe9b60 100644 (file)
--- a/pscp.c
+++ b/pscp.c
@@ -48,9 +48,9 @@ static void *backhandle;
 static Conf *conf;
 int sent_eof = FALSE;
 
-static void source(char *src);
-static void rsource(char *src);
-static void sink(char *targ, char *src);
+static void source(const char *src);
+static void rsource(const char *src);
+static void sink(const char *targ, const char *src);
 
 const char *const appname = "PSCP";
 
@@ -60,23 +60,14 @@ const char *const appname = "PSCP";
  */
 #define MAX_SCP_BUFSIZE 16384
 
-void ldisc_send(void *handle, char *buf, int len, int interactive)
-{
-    /*
-     * This is only here because of the calls to ldisc_send(NULL,
-     * 0) in ssh.c. Nothing in PSCP actually needs to use the ldisc
-     * as an ldisc. So if we get called with any real data, I want
-     * to know about it.
-     */
-    assert(len == 0);
-}
+void ldisc_echoedit_update(void *handle) { }
 
-static void tell_char(FILE * stream, char c)
+static void tell_char(FILE *stream, char c)
 {
     fputc(c, stream);
 }
 
-static void tell_str(FILE * stream, char *str)
+static void tell_str(FILE *stream, const char *str)
 {
     unsigned int i;
 
@@ -84,7 +75,7 @@ static void tell_str(FILE * stream, char *str)
        tell_char(stream, str[i]);
 }
 
-static void tell_user(FILE * stream, char *fmt, ...)
+static void tell_user(FILE *stream, const char *fmt, ...)
 {
     char *str, *str2;
     va_list ap;
@@ -100,7 +91,7 @@ static void tell_user(FILE * stream, char *fmt, ...)
 /*
  *  Print an error message and perform a fatal exit.
  */
-void fatalbox(char *fmt, ...)
+void fatalbox(const char *fmt, ...)
 {
     char *str, *str2;
     va_list ap;
@@ -115,7 +106,7 @@ void fatalbox(char *fmt, ...)
 
     cleanup_exit(1);
 }
-void modalfatalbox(char *fmt, ...)
+void modalfatalbox(const char *fmt, ...)
 {
     char *str, *str2;
     va_list ap;
@@ -130,7 +121,7 @@ void modalfatalbox(char *fmt, ...)
 
     cleanup_exit(1);
 }
-void nonfatal(char *fmt, ...)
+void nonfatal(const char *fmt, ...)
 {
     char *str, *str2;
     va_list ap;
@@ -143,7 +134,7 @@ void nonfatal(char *fmt, ...)
     sfree(str2);
     errs++;
 }
-void connection_fatal(void *frontend, char *fmt, ...)
+void connection_fatal(void *frontend, const char *fmt, ...)
 {
     char *str, *str2;
     va_list ap;
@@ -311,7 +302,7 @@ static void ssh_scp_init(void)
 /*
  *  Print an error message and exit after closing the SSH link.
  */
-static void bump(char *fmt, ...)
+static void bump(const char *fmt, ...)
 {
     char *str, *str2;
     va_list ap;
@@ -542,7 +533,7 @@ static void do_cmd(char *host, char *user, char *cmd)
 /*
  *  Update statistic information about current file.
  */
-static void print_stats(char *name, uint64 size, uint64 done,
+static void print_stats(const char *name, uint64 size, uint64 done,
                        time_t start, time_t now)
 {
     float ratebs;
@@ -614,30 +605,6 @@ static char *colon(char *str)
        return (NULL);
 }
 
-/*
- * Return a pointer to the portion of str that comes after the last
- * slash (or backslash or colon, if `local' is TRUE).
- */
-static char *stripslashes(char *str, int local)
-{
-    char *p;
-
-    if (local) {
-        p = strchr(str, ':');
-        if (p) str = p+1;
-    }
-
-    p = strrchr(str, '/');
-    if (p) str = p+1;
-
-    if (local) {
-       p = strrchr(str, '\\');
-       if (p) str = p+1;
-    }
-
-    return str;
-}
-
 /*
  * Determine whether a string is entirely composed of dots.
  */
@@ -701,7 +668,7 @@ static int sftp_ls_compare(const void *av, const void *bv)
     const struct fxp_name *b = (const struct fxp_name *) bv;
     return strcmp(a->filename, b->filename);
 }
-void scp_sftp_listdir(char *dirname)
+void scp_sftp_listdir(const char *dirname)
 {
     struct fxp_handle *dirh;
     struct fxp_names *names;
@@ -800,7 +767,7 @@ static struct fxp_handle *scp_sftp_filehandle;
 static struct fxp_xfer *scp_sftp_xfer;
 static uint64 scp_sftp_fileoffset;
 
-int scp_source_setup(char *target, int shouldbedir)
+int scp_source_setup(const char *target, int shouldbedir)
 {
     if (using_sftp) {
        /*
@@ -866,7 +833,7 @@ int scp_send_filetimes(unsigned long mtime, unsigned long atime)
     }
 }
 
-int scp_send_filename(char *name, uint64 size, int permissions)
+int scp_send_filename(const char *name, uint64 size, int permissions)
 {
     if (using_sftp) {
        char *fullname;
@@ -1021,7 +988,7 @@ void scp_restore_remotepath(char *data)
        scp_sftp_remotepath = data;
 }
 
-int scp_send_dirname(char *name, int modes)
+int scp_send_dirname(const char *name, int modes)
 {
     if (using_sftp) {
        char *fullname;
@@ -1096,7 +1063,7 @@ int scp_send_enddir(void)
  * right at the start, whereas scp_sink_init is called to
  * initialise every level of recursion in the protocol.
  */
-int scp_sink_setup(char *source, int preserve, int recursive)
+int scp_sink_setup(const char *source, int preserve, int recursive)
 {
     if (using_sftp) {
        char *newsource;
@@ -1666,12 +1633,12 @@ static void run_err(const char *fmt, ...)
 /*
  *  Execute the source part of the SCP protocol.
  */
-static void source(char *src)
+static void source(const char *src)
 {
     uint64 size;
     unsigned long mtime, atime;
     long permissions;
-    char *last;
+    const char *last;
     RFile *f;
     int attr;
     uint64 i;
@@ -1691,7 +1658,7 @@ static void source(char *src)
            /*
             * Avoid . and .. directories.
             */
-           char *p;
+           const char *p;
            p = strrchr(src, '/');
            if (!p)
                p = strrchr(src, '\\');
@@ -1779,9 +1746,9 @@ static void source(char *src)
 /*
  *  Recursively send the contents of a directory.
  */
-static void rsource(char *src)
+static void rsource(const char *src)
 {
-    char *last;
+    const char *last;
     char *save_target;
     DirHandle *dir;
 
@@ -1823,7 +1790,7 @@ static void rsource(char *src)
 /*
  * Execute the sink part of the SCP protocol.
  */
-static void sink(char *targ, char *src)
+static void sink(const char *targ, const char *src)
 {
     char *destfname;
     int targisdir = 0;
@@ -2029,23 +1996,26 @@ static void sink(char *targ, char *src)
  */
 static void toremote(int argc, char *argv[])
 {
-    char *src, *targ, *host, *user;
+    char *src, *wtarg, *host, *user;
+    const char *targ;
     char *cmd;
     int i, wc_type;
 
     uploading = 1;
 
-    targ = argv[argc - 1];
+    wtarg = argv[argc - 1];
 
     /* Separate host from filename */
-    host = targ;
-    targ = colon(targ);
-    if (targ == NULL)
-       bump("targ == NULL in toremote()");
-    *targ++ = '\0';
-    if (*targ == '\0')
-       targ = ".";
+    host = wtarg;
+    wtarg = colon(wtarg);
+    if (wtarg == NULL)
+       bump("wtarg == NULL in toremote()");
+    *wtarg++ = '\0';
     /* Substitute "." for empty target */
+    if (*wtarg == '\0')
+       targ = ".";
+    else
+        targ = wtarg;
 
     /* Separate host and username */
     user = host;
@@ -2121,7 +2091,8 @@ static void toremote(int argc, char *argv[])
  */
 static void tolocal(int argc, char *argv[])
 {
-    char *src, *targ, *host, *user;
+    char *wsrc, *host, *user;
+    const char *src, *targ;
     char *cmd;
 
     uploading = 0;
@@ -2129,18 +2100,20 @@ static void tolocal(int argc, char *argv[])
     if (argc != 2)
        bump("More than one remote source not supported");
 
-    src = argv[0];
+    wsrc = argv[0];
     targ = argv[1];
 
     /* Separate host from filename */
-    host = src;
-    src = colon(src);
-    if (src == NULL)
+    host = wsrc;
+    wsrc = colon(wsrc);
+    if (wsrc == NULL)
        bump("Local to local copy not supported");
-    *src++ = '\0';
-    if (*src == '\0')
-       src = ".";
+    *wsrc++ = '\0';
     /* Substitute "." for empty filename */
+    if (*wsrc == '\0')
+       src = ".";
+    else
+        src = wsrc;
 
     /* Separate username and hostname */
     user = host;
@@ -2173,21 +2146,25 @@ static void tolocal(int argc, char *argv[])
  */
 static void get_dir_list(int argc, char *argv[])
 {
-    char *src, *host, *user;
-    char *cmd, *p, *q;
+    char *wsrc, *host, *user;
+    const char *src;
+    char *cmd, *p;
+    const char *q;
     char c;
 
-    src = argv[0];
+    wsrc = argv[0];
 
     /* Separate host from filename */
-    host = src;
-    src = colon(src);
-    if (src == NULL)
+    host = wsrc;
+    wsrc = colon(wsrc);
+    if (wsrc == NULL)
        bump("Local file listing not supported");
-    *src++ = '\0';
-    if (*src == '\0')
-       src = ".";
+    *wsrc++ = '\0';
     /* Substitute "." for empty filename */
+    if (*wsrc == '\0')
+       src = ".";
+    else
+        src = wsrc;
 
     /* Separate username and hostname */
     user = host;
@@ -2285,7 +2262,7 @@ void version(void)
     cleanup_exit(1);
 }
 
-void cmdline_error(char *p, ...)
+void cmdline_error(const char *p, ...)
 {
     va_list ap;
     fprintf(stderr, "pscp: ");
diff --git a/psftp.c b/psftp.c
index 7f081ab5680384b4f6f2549412251a1ea75934f2..92b57a2f363793b94b95aa9aa2128505dd13b6a0 100644 (file)
--- a/psftp.c
+++ b/psftp.c
@@ -68,7 +68,7 @@ struct sftp_packet *sftp_wait_for_reply(struct sftp_request *req)
  * canonification fails, at least fall back to returning a _valid_
  * pathname (though it may be ugly, eg /home/simon/../foobar).
  */
-char *canonify(char *name)
+char *canonify(const char *name)
 {
     char *fullname, *canonname;
     struct sftp_packet *pktin;
@@ -77,7 +77,7 @@ char *canonify(char *name)
     if (name[0] == '/') {
        fullname = dupstr(name);
     } else {
-       char *slash;
+       const char *slash;
        if (pwd[strlen(pwd) - 1] == '/')
            slash = "";
        else
@@ -169,30 +169,6 @@ char *canonify(char *name)
     }
 }
 
-/*
- * Return a pointer to the portion of str that comes after the last
- * slash (or backslash or colon, if `local' is TRUE).
- */
-static char *stripslashes(char *str, int local)
-{
-    char *p;
-
-    if (local) {
-        p = strchr(str, ':');
-        if (p) str = p+1;
-    }
-
-    p = strrchr(str, '/');
-    if (p) str = p+1;
-
-    if (local) {
-       p = strrchr(str, '\\');
-       if (p) str = p+1;
-    }
-
-    return str;
-}
-
 /*
  * qsort comparison routine for fxp_name structures. Sorts by real
  * file name.
@@ -1011,7 +987,8 @@ int sftp_cmd_ls(struct sftp_command *cmd)
     struct fxp_names *names;
     struct fxp_name **ournames;
     int nnames, namesize;
-    char *dir, *cdir, *unwcdir, *wildcard;
+    const char *dir;
+    char *cdir, *unwcdir, *wildcard;
     struct sftp_packet *pktin;
     struct sftp_request *req;
     int i;
@@ -1901,7 +1878,7 @@ static int sftp_cmd_pling(struct sftp_command *cmd)
 static int sftp_cmd_help(struct sftp_command *cmd);
 
 static struct sftp_cmd_lookup {
-    char *name;
+    const char *name;
     /*
      * For help purposes, there are two kinds of command:
      * 
@@ -1915,8 +1892,8 @@ static struct sftp_cmd_lookup {
      *    contains the help that should double up for this command.
      */
     int listed;                               /* do we list this in primary help? */
-    char *shorthelp;
-    char *longhelp;
+    const char *shorthelp;
+    const char *longhelp;
     int (*obey) (struct sftp_command *);
 } sftp_lookup[] = {
     /*
@@ -2139,7 +2116,7 @@ static struct sftp_cmd_lookup {
     }
 };
 
-const struct sftp_cmd_lookup *lookup_command(char *name)
+const struct sftp_cmd_lookup *lookup_command(const char *name)
 {
     int i, j, k, cmp;
 
@@ -2456,7 +2433,7 @@ static int verbose = 0;
 /*
  *  Print an error message and perform a fatal exit.
  */
-void fatalbox(char *fmt, ...)
+void fatalbox(const char *fmt, ...)
 {
     char *str, *str2;
     va_list ap;
@@ -2470,7 +2447,7 @@ void fatalbox(char *fmt, ...)
 
     cleanup_exit(1);
 }
-void modalfatalbox(char *fmt, ...)
+void modalfatalbox(const char *fmt, ...)
 {
     char *str, *str2;
     va_list ap;
@@ -2484,7 +2461,7 @@ void modalfatalbox(char *fmt, ...)
 
     cleanup_exit(1);
 }
-void nonfatal(char *fmt, ...)
+void nonfatal(const char *fmt, ...)
 {
     char *str, *str2;
     va_list ap;
@@ -2496,7 +2473,7 @@ void nonfatal(char *fmt, ...)
     fputs(str2, stderr);
     sfree(str2);
 }
-void connection_fatal(void *frontend, char *fmt, ...)
+void connection_fatal(void *frontend, const char *fmt, ...)
 {
     char *str, *str2;
     va_list ap;
@@ -2511,16 +2488,7 @@ void connection_fatal(void *frontend, char *fmt, ...)
     cleanup_exit(1);
 }
 
-void ldisc_send(void *handle, char *buf, int len, int interactive)
-{
-    /*
-     * This is only here because of the calls to ldisc_send(NULL,
-     * 0) in ssh.c. Nothing in PSFTP actually needs to use the
-     * ldisc as an ldisc. So if we get called with any real data, I
-     * want to know about it.
-     */
-    assert(len == 0);
-}
+void ldisc_echoedit_update(void *handle) { }
 
 /*
  * In psftp, all agent requests should be synchronous, so this is a
@@ -2884,7 +2852,7 @@ static int psftp_connect(char *userhost, char *user, int portnumber)
     return 0;
 }
 
-void cmdline_error(char *p, ...)
+void cmdline_error(const char *p, ...)
 {
     va_list ap;
     fprintf(stderr, "psftp: ");
diff --git a/psftp.h b/psftp.h
index e6ad00f6946f7a09ee56ef6b6d00db0534ce333a..57a821ab19da7522c773a19792423662a4f132b6 100644 (file)
--- a/psftp.h
+++ b/psftp.h
@@ -45,7 +45,7 @@ int ssh_sftp_loop_iteration(void);
  * FALSE, a back end is not (intentionally) active at all (e.g.
  * psftp before an `open' command).
  */
-char *ssh_sftp_get_cmdline(char *prompt, int backend_required);
+char *ssh_sftp_get_cmdline(const char *prompt, int backend_required);
 
 /*
  * The main program in psftp.c. Called from main() in the platform-
@@ -59,13 +59,13 @@ int psftp_main(int argc, char *argv[]);
  * probably only ever be supported on Windows, so these functions
  * can safely be stubs on all other platforms.
  */
-void gui_update_stats(char *name, unsigned long size,
+void gui_update_stats(const char *name, unsigned long size,
                      int percentage, unsigned long elapsed,
                      unsigned long done, unsigned long eta,
                      unsigned long ratebs);
 void gui_send_errcount(int list, int errs);
 void gui_send_char(int is_stderr, int c);
-void gui_enable(char *arg);
+void gui_enable(const char *arg);
 
 /*
  * It's likely that a given platform's implementation of file
@@ -87,15 +87,15 @@ typedef struct RFile RFile;
 typedef struct WFile WFile;
 /* Output params size, perms, mtime and atime can all be NULL if
  * desired. perms will be -1 if the OS does not support POSIX permissions. */
-RFile *open_existing_file(char *name, uint64 *size,
+RFile *open_existing_file(const char *name, uint64 *size,
                          unsigned long *mtime, unsigned long *atime,
                           long *perms);
-WFile *open_existing_wfile(char *name, uint64 *size);
+WFile *open_existing_wfile(const char *name, uint64 *size);
 /* Returns <0 on error, 0 on eof, or number of bytes read, as usual */
 int read_from_file(RFile *f, void *buffer, int length);
 /* Closes and frees the RFile */
 void close_rfile(RFile *f);
-WFile *open_new_file(char *name, long perms);
+WFile *open_new_file(const char *name, long perms);
 /* Returns <0 on error, 0 on eof, or number of bytes written, as usual */
 int write_to_file(WFile *f, void *buffer, int length);
 void set_file_times(WFile *f, unsigned long mtime, unsigned long atime);
@@ -117,13 +117,13 @@ uint64 get_file_posn(WFile *f);
 enum {
     FILE_TYPE_NONEXISTENT, FILE_TYPE_FILE, FILE_TYPE_DIRECTORY, FILE_TYPE_WEIRD
 };
-int file_type(char *name);
+int file_type(const char *name);
 
 /*
  * Read all the file names out of a directory.
  */
 typedef struct DirHandle DirHandle;
-DirHandle *open_directory(char *name);
+DirHandle *open_directory(const char *name);
 /* The string returned from this will need freeing if not NULL */
 char *read_filename(DirHandle *dir);
 void close_directory(DirHandle *dir);
@@ -145,13 +145,13 @@ void close_directory(DirHandle *dir);
 enum {
     WCTYPE_NONEXISTENT, WCTYPE_FILENAME, WCTYPE_WILDCARD
 };
-int test_wildcard(char *name, int cmdline);
+int test_wildcard(const char *name, int cmdline);
 
 /*
  * Actually return matching file names for a local wildcard.
  */
 typedef struct WildcardMatcher WildcardMatcher;
-WildcardMatcher *begin_wildcard_matching(char *name);
+WildcardMatcher *begin_wildcard_matching(const char *name);
 /* The string returned from this will need freeing if not NULL */
 char *wildcard_get_filename(WildcardMatcher *dir);
 void finish_wildcard_matching(WildcardMatcher *dir);
@@ -164,17 +164,34 @@ void finish_wildcard_matching(WildcardMatcher *dir);
  * 
  * Returns TRUE if the filename is kosher, FALSE if dangerous.
  */
-int vet_filename(char *name);
+int vet_filename(const char *name);
 
 /*
  * Create a directory. Returns 0 on error, !=0 on success.
  */
-int create_directory(char *name);
+int create_directory(const char *name);
 
 /*
  * Concatenate a directory name and a file name. The way this is
  * done will depend on the OS.
  */
-char *dir_file_cat(char *dir, char *file);
+char *dir_file_cat(const char *dir, const char *file);
+
+/*
+ * Return a pointer to the portion of str that comes after the last
+ * path component separator.
+ *
+ * If 'local' is false, path component separators are taken to just be
+ * '/', on the assumption that we're discussing the path syntax on the
+ * server. But if 'local' is true, the separators are whatever the
+ * local OS will treat that way - so that includes '\' and ':' on
+ * Windows.
+ *
+ * This function has the annoying strstr() property of taking a const
+ * char * and returning a char *. You should treat it as if it was a
+ * pair of overloaded functions, one mapping mutable->mutable and the
+ * other const->const :-(
+ */
+char *stripslashes(const char *str, int local);
 
 #endif /* PUTTY_PSFTP_H */
diff --git a/putty.h b/putty.h
index 9580c3cde1ac8c3ddf5771606224b63befba51af..d5333942bd27dbd74eab5eb5cebeb343663d5d45 100644 (file)
--- a/putty.h
+++ b/putty.h
@@ -141,7 +141,7 @@ typedef struct terminal_tag Terminal;
 
 struct sesslist {
     int nsessions;
-    char **sessions;
+    const char **sessions;
     char *buffer;                     /* so memory can be freed later */
 };
 
@@ -255,6 +255,7 @@ enum {
     KEX_DHGROUP14,
     KEX_DHGEX,
     KEX_RSA,
+    KEX_ECDH,
     KEX_MAX
 };
 
@@ -268,6 +269,7 @@ enum {
     CIPHER_AES,                               /* (SSH-2 only) */
     CIPHER_DES,
     CIPHER_ARCFOUR,
+    CIPHER_CHACHA20,
     CIPHER_MAX                        /* no. ciphers (inc warn) */
 };
 
@@ -277,9 +279,9 @@ enum {
      * three-way settings whose values are `always yes', `always
      * no', and `decide by some more complex automated means'. This
      * is true of line discipline options (local echo and line
-     * editing), proxy DNS, Close On Exit, and SSH server bug
-     * workarounds. Accordingly I supply a single enum here to deal
-     * with them all.
+     * editing), proxy DNS, proxy terminal logging, Close On Exit, and
+     * SSH server bug workarounds. Accordingly I supply a single enum
+     * here to deal with them all.
      */
     FORCE_ON, FORCE_OFF, AUTO
 };
@@ -289,7 +291,7 @@ enum {
      * Proxy types.
      */
     PROXY_NONE, PROXY_SOCKS4, PROXY_SOCKS5,
-    PROXY_HTTP, PROXY_TELNET, PROXY_CMD
+    PROXY_HTTP, PROXY_TELNET, PROXY_CMD, PROXY_FUZZ
 };
 
 enum {
@@ -360,7 +362,7 @@ struct keyvalwhere {
      * Two fields which define a string and enum value to be
      * equivalent to each other.
      */
-    char *s;
+    const char *s;
     int v;
 
     /*
@@ -417,13 +419,13 @@ enum {
 
 struct backend_tag {
     const char *(*init) (void *frontend_handle, void **backend_handle,
-                        Conf *conf, char *host, int port, char **realhost,
-                        int nodelay, int keepalive);
+                        Conf *conf, const char *host, int port,
+                         char **realhost, int nodelay, int keepalive);
     void (*free) (void *handle);
     /* back->reconfig() passes in a replacement configuration. */
     void (*reconfig) (void *handle, Conf *conf);
     /* back->send() returns the current amount of buffered data. */
-    int (*send) (void *handle, char *buf, int len);
+    int (*send) (void *handle, const char *buf, int len);
     /* back->sendbuffer() does the same thing but without attempting a send */
     int (*sendbuffer) (void *handle);
     void (*size) (void *handle, int width, int height);
@@ -443,7 +445,10 @@ struct backend_tag {
      */
     void (*unthrottle) (void *handle, int);
     int (*cfg_info) (void *handle);
-    char *name;
+    /* Only implemented in the SSH protocol: check whether a
+     * connection-sharing upstream exists for a given configuration. */
+    int (*test_for_upstream)(const char *host, int port, Conf *conf);
+    const char *name;
     int protocol;
     int default_port;
 };
@@ -590,10 +595,10 @@ void write_clip(void *frontend, wchar_t *, int *, int, int);
 void get_clip(void *frontend, wchar_t **, int *);
 void optimised_move(void *frontend, int, int, int);
 void set_raw_mouse_mode(void *frontend, int);
-void connection_fatal(void *frontend, char *, ...);
-void nonfatal(char *, ...);
-void fatalbox(char *, ...);
-void modalfatalbox(char *, ...);
+void connection_fatal(void *frontend, const char *, ...);
+void nonfatal(const char *, ...);
+void fatalbox(const char *, ...);
+void modalfatalbox(const char *, ...);
 #ifdef macintosh
 #pragma noreturn(fatalbox)
 #pragma noreturn(modalfatalbox)
@@ -603,7 +608,7 @@ void begin_session(void *frontend);
 void sys_cursor(void *frontend, int x, int y);
 void request_paste(void *frontend);
 void frontend_keypress(void *frontend);
-void ldisc_update(void *frontend, int echo, int edit);
+void frontend_echoedit_update(void *frontend, int echo, int edit);
 /* It's the backend's responsibility to invoke this at the start of a
  * connection, if necessary; it can also invoke it later if the set of
  * special commands changes. It does not need to invoke it at session
@@ -625,7 +630,7 @@ char *get_ttymode(void *frontend, const char *mode);
  * 0  = `user cancelled' (FIXME distinguish "give up entirely" and "next auth"?)
  * <0 = `please call back later with more in/inlen'
  */
-int get_userpass_input(prompts_t *p, unsigned char *in, int inlen);
+int get_userpass_input(prompts_t *p, const unsigned char *in, int inlen);
 #define OPTIMISE_IS_SCROLL 1
 
 void set_iconic(void *frontend, int iconic);
@@ -676,6 +681,7 @@ void cleanup_exit(int);
     X(STR, NONE, proxy_username) \
     X(STR, NONE, proxy_password) \
     X(STR, NONE, proxy_telnet_command) \
+    X(INT, NONE, proxy_log_to_term) \
     /* SSH options */ \
     X(STR, NONE, remote_cmd) \
     X(STR, NONE, remote_cmd2) /* fallback if remote_cmd fails; never loaded or saved */ \
@@ -751,6 +757,8 @@ void cleanup_exit(int);
     X(INT, NONE, erase_to_scrollback) \
     X(INT, NONE, compose_key) \
     X(INT, NONE, ctrlaltkeys) \
+    X(INT, NONE, osx_option_meta) \
+    X(INT, NONE, osx_command_meta) \
     X(STR, NONE, wintitle) /* initial window title */ \
     /* Terminal options */ \
     X(INT, NONE, savelines) \
@@ -941,12 +949,12 @@ void random_destroy_seed(void);
 Backend *backend_from_name(const char *name);
 Backend *backend_from_proto(int proto);
 char *get_remote_username(Conf *conf); /* dynamically allocated */
-char *save_settings(char *section, Conf *conf);
+char *save_settings(const char *section, Conf *conf);
 void save_open_settings(void *sesskey, Conf *conf);
-void load_settings(char *section, Conf *conf);
+void load_settings(const char *section, Conf *conf);
 void load_open_settings(void *sesskey, Conf *conf);
 void get_sesslist(struct sesslist *, int allocate);
-void do_defaults(char *, Conf *);
+void do_defaults(const char *, Conf *);
 void registry_cleanup(void);
 
 /*
@@ -1004,7 +1012,7 @@ void term_provide_logctx(Terminal *term, void *logctx);
 void term_set_focus(Terminal *term, int has_focus);
 char *term_get_ttymode(Terminal *term, const char *mode);
 int term_get_userpass_input(Terminal *term, prompts_t *p,
-                           unsigned char *in, int inlen);
+                           const unsigned char *in, int inlen);
 
 int format_arrow_key(char *buf, Terminal *term, int xkey, int ctrl);
 
@@ -1027,7 +1035,7 @@ struct logblank_t {
     int type;
 };
 void log_packet(void *logctx, int direction, int type,
-               char *texttype, const void *data, int len,
+               const char *texttype, const void *data, int len,
                int n_blanks, const struct logblank_t *blanks,
                const unsigned long *sequence,
                 unsigned downstream_id, const char *additional_log_text);
@@ -1068,13 +1076,15 @@ extern Backend ssh_backend;
 void *ldisc_create(Conf *, Terminal *, Backend *, void *, void *);
 void ldisc_configure(void *, Conf *);
 void ldisc_free(void *);
-void ldisc_send(void *handle, char *buf, int len, int interactive);
+void ldisc_send(void *handle, const char *buf, int len, int interactive);
+void ldisc_echoedit_update(void *handle);
 
 /*
  * Exports from ldiscucs.c.
  */
-void lpage_send(void *, int codepage, char *buf, int len, int interactive);
-void luni_send(void *, wchar_t * widebuf, int len, int interactive);
+void lpage_send(void *, int codepage, const char *buf, int len,
+                int interactive);
+void luni_send(void *, const wchar_t * widebuf, int len, int interactive);
 
 /*
  * Exports from sshrand.c.
@@ -1128,7 +1138,7 @@ int is_dbcs_leadbyte(int codepage, char byte);
 int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen,
             wchar_t *wcstr, int wclen);
 int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen,
-            char *mbstr, int mblen, char *defchr, int *defused,
+            char *mbstr, int mblen, const char *defchr, int *defused,
             struct unicode_data *ucsdata);
 wchar_t xlat_uskbd2cyrllic(int ch);
 int check_compose(int first, int second);
@@ -1193,9 +1203,14 @@ void pgp_fingerprints(void);
  *    back via the provided function with a result that's either 0
  *    or +1'.
  */
-int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
-                        char *keystr, char *fingerprint,
+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);
+/*
+ * have_ssh_host_key() just returns true if a key of that type is
+ * already chached 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.
  */
@@ -1217,7 +1232,8 @@ int askappend(void *frontend, Filename *filename,
  * that aren't equivalents to things in windlg.c et al.
  */
 extern int console_batch_mode;
-int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen);
+int console_get_userpass_input(prompts_t *p, const unsigned char *in,
+                               int inlen);
 void console_provide_logctx(void *logctx);
 int is_interactive(void);
 
@@ -1237,16 +1253,21 @@ void printer_finish_job(printer_job *);
  * Exports from cmdline.c (and also cmdline_error(), which is
  * defined differently in various places and required _by_
  * cmdline.c).
+ *
+ * Note that cmdline_process_param takes a const option string, but a
+ * writable argument string. That's not a mistake - that's so it can
+ * zero out password arguments in the hope of not having them show up
+ * avoidably in Unix 'ps'.
  */
-int cmdline_process_param(char *, char *, int, Conf *);
+int cmdline_process_param(const char *, char *, int, Conf *);
 void cmdline_run_saved(Conf *);
 void cmdline_cleanup(void);
-int cmdline_get_passwd_input(prompts_t *p, unsigned char *in, int inlen);
+int cmdline_get_passwd_input(prompts_t *p, const unsigned char *in, int inlen);
 #define TOOLTYPE_FILETRANSFER 1
 #define TOOLTYPE_NONNETWORK 2
 extern int cmdline_tooltype;
 
-void cmdline_error(char *, ...);
+void cmdline_error(const char *, ...);
 
 /*
  * Exports from config.c.
diff --git a/raw.c b/raw.c
index 97355e8ad768bd38df89bc653736a386c9143c5b..0c5445ad432c28619033cc7cf13f7a0e1c639365 100644 (file)
--- a/raw.c
+++ b/raw.c
@@ -25,7 +25,9 @@ typedef struct raw_backend_data {
     int closed_on_socket_error;
     int bufsize;
     void *frontend;
-    int sent_console_eof, sent_socket_eof;
+    int sent_console_eof, sent_socket_eof, session_started;
+
+    Conf *conf;
 } *Raw;
 
 static void raw_size(void *handle, int width, int height);
@@ -40,17 +42,8 @@ static void raw_log(Plug plug, int type, SockAddr addr, int port,
                    const char *error_msg, int error_code)
 {
     Raw raw = (Raw) plug;
-    char addrbuf[256], *msg;
-
-    sk_getaddr(addr, addrbuf, lenof(addrbuf));
-
-    if (type == 0)
-       msg = dupprintf("Connecting to %s port %d", addrbuf, port);
-    else
-       msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg);
-
-    logevent(raw->frontend, msg);
-    sfree(msg);
+    backend_socket_log(raw->frontend, type, addr, port,
+                       error_msg, error_code, raw->conf, raw->session_started);
 }
 
 static void raw_check_close(Raw raw)
@@ -106,6 +99,9 @@ static int raw_receive(Plug plug, int urgent, char *data, int len)
 {
     Raw raw = (Raw) plug;
     c_write(raw, data, len);
+    /* We count 'session start', for proxy logging purposes, as being
+     * when data is received from the network and printed. */
+    raw->session_started = TRUE;
     return 1;
 }
 
@@ -125,8 +121,8 @@ static void raw_sent(Plug plug, int bufsize)
  */
 static const char *raw_init(void *frontend_handle, void **backend_handle,
                            Conf *conf,
-                           char *host, int port, char **realhost, int nodelay,
-                           int keepalive)
+                           const char *host, int port, char **realhost,
+                            int nodelay, int keepalive)
 {
     static const struct plug_function_table fn_table = {
        raw_log,
@@ -147,6 +143,8 @@ static const char *raw_init(void *frontend_handle, void **backend_handle,
     *backend_handle = raw;
     raw->sent_console_eof = raw->sent_socket_eof = FALSE;
     raw->bufsize = 0;
+    raw->session_started = FALSE;
+    raw->conf = conf_copy(conf);
 
     raw->frontend = frontend_handle;
 
@@ -154,16 +152,8 @@ static const char *raw_init(void *frontend_handle, void **backend_handle,
     /*
      * Try to find host.
      */
-    {
-       char *buf;
-       buf = dupprintf("Looking up host \"%s\"%s", host,
-                       (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" :
-                        (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" :
-                         "")));
-       logevent(raw->frontend, buf);
-       sfree(buf);
-    }
-    addr = name_lookup(host, port, realhost, conf, addressfamily);
+    addr = name_lookup(host, port, realhost, conf, addressfamily,
+                       raw->frontend, "main connection");
     if ((err = sk_addr_error(addr)) != NULL) {
        sk_addr_free(addr);
        return err;
@@ -201,6 +191,7 @@ static void raw_free(void *handle)
 
     if (raw->s)
        sk_close(raw->s);
+    conf_free(raw->conf);
     sfree(raw);
 }
 
@@ -214,7 +205,7 @@ static void raw_reconfig(void *handle, Conf *conf)
 /*
  * Called to send data down the raw connection.
  */
-static int raw_send(void *handle, char *buf, int len)
+static int raw_send(void *handle, const char *buf, int len)
 {
     Raw raw = (Raw) handle;
 
@@ -339,6 +330,7 @@ Backend raw_backend = {
     raw_provide_logctx,
     raw_unthrottle,
     raw_cfg_info,
+    NULL /* test_for_upstream */,
     "raw",
     PROT_RAW,
     0
index 3c86eee8575f20981ae725ed22fe71ef6ec334c7..eba468da235a8689f83753a4b1c8235edc538dab 100644 (file)
--- a/rlogin.c
+++ b/rlogin.c
@@ -48,17 +48,9 @@ static void rlogin_log(Plug plug, int type, SockAddr addr, int port,
                       const char *error_msg, int error_code)
 {
     Rlogin rlogin = (Rlogin) plug;
-    char addrbuf[256], *msg;
-
-    sk_getaddr(addr, addrbuf, lenof(addrbuf));
-
-    if (type == 0)
-       msg = dupprintf("Connecting to %s port %d", addrbuf, port);
-    else
-       msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg);
-
-    logevent(rlogin->frontend, msg);
-    sfree(msg);
+    backend_socket_log(rlogin->frontend, type, addr, port,
+                       error_msg, error_code,
+                       rlogin->conf, !rlogin->firstbyte);
 }
 
 static int rlogin_closing(Plug plug, const char *error_msg, int error_code,
@@ -161,7 +153,7 @@ static void rlogin_startup(Rlogin rlogin, const char *ruser)
  */
 static const char *rlogin_init(void *frontend_handle, void **backend_handle,
                               Conf *conf,
-                              char *host, int port, char **realhost,
+                              const char *host, int port, char **realhost,
                               int nodelay, int keepalive)
 {
     static const struct plug_function_table fn_table = {
@@ -194,16 +186,8 @@ static const char *rlogin_init(void *frontend_handle, void **backend_handle,
     /*
      * Try to find host.
      */
-    {
-       char *buf;
-       buf = dupprintf("Looking up host \"%s\"%s", host,
-                       (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" :
-                        (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" :
-                         "")));
-       logevent(rlogin->frontend, buf);
-       sfree(buf);
-    }
-    addr = name_lookup(host, port, realhost, conf, addressfamily);
+    addr = name_lookup(host, port, realhost, conf, addressfamily,
+                       rlogin->frontend, "rlogin connection");
     if ((err = sk_addr_error(addr)) != NULL) {
        sk_addr_free(addr);
        return err;
@@ -279,7 +263,7 @@ static void rlogin_reconfig(void *handle, Conf *conf)
 /*
  * Called to send data down the rlogin connection.
  */
-static int rlogin_send(void *handle, char *buf, int len)
+static int rlogin_send(void *handle, const char *buf, int len)
 {
     Rlogin rlogin = (Rlogin) handle;
 
@@ -425,6 +409,7 @@ Backend rlogin_backend = {
     rlogin_provide_logctx,
     rlogin_unthrottle,
     rlogin_cfg_info,
+    NULL /* test_for_upstream */,
     "rlogin",
     PROT_RLOGIN,
     513
index 02d7506b121aeb25d3b7b2fd8dab3581544ffbe0..23e0ec3921384a633ea4cb10c29dfbff4f6089b5 100644 (file)
@@ -10,6 +10,7 @@
 
 /* The cipher order given here is the default order. */
 static const struct keyvalwhere ciphernames[] = {
+    { "chacha20",   CIPHER_CHACHA20,        -1, -1 },
     { "aes",        CIPHER_AES,             -1, -1 },
     { "blowfish",   CIPHER_BLOWFISH,        -1, -1 },
     { "3des",       CIPHER_3DES,            -1, -1 },
@@ -19,6 +20,7 @@ static const struct keyvalwhere ciphernames[] = {
 };
 
 static const struct keyvalwhere kexnames[] = {
+    { "ecdh",               KEX_ECDH,       -1, +1 },
     { "dh-gex-sha1",        KEX_DHGEX,      -1, -1 },
     { "dh-group14-sha1",    KEX_DHGROUP14,  -1, -1 },
     { "dh-group1-sha1",     KEX_DHGROUP1,   -1, -1 },
@@ -123,13 +125,14 @@ static void gppfile(void *handle, const char *name, Conf *conf, int primary)
     filename_free(result);
 }
 
-static int gppi_raw(void *handle, char *name, int def)
+static int gppi_raw(void *handle, const char *name, int def)
 {
     def = platform_default_i(name, def);
     return read_setting_i(handle, name, def);
 }
 
-static void gppi(void *handle, char *name, int def, Conf *conf, int primary)
+static void gppi(void *handle, const char *name, int def,
+                 Conf *conf, int primary)
 {
     conf_set_int(conf, primary, gppi_raw(handle, name, def));
 }
@@ -141,7 +144,7 @@ static void gppi(void *handle, char *name, int def, Conf *conf, int primary)
  * If there's no "=VALUE" (e.g. just NAME,NAME,NAME) then those keys
  * are mapped to the empty string.
  */
-static int gppmap(void *handle, char *name, Conf *conf, int primary)
+static int gppmap(void *handle, const char *name, Conf *conf, int primary)
 {
     char *buf, *p, *q, *key, *val;
 
@@ -211,7 +214,8 @@ static int gppmap(void *handle, char *name, Conf *conf, int primary)
 static void wmap(void *handle, char const *outkey, Conf *conf, int primary,
                  int include_values)
 {
-    char *buf, *p, *q, *key, *realkey, *val;
+    char *buf, *p, *key, *realkey;
+    const char *val, *q;
     int len;
 
     len = 1;                          /* allow for NUL */
@@ -297,7 +301,7 @@ static const char *val2key(const struct keyvalwhere *mapping,
  * to the end and duplicates are weeded.
  * XXX: assumes vals in 'mapping' are small +ve integers
  */
-static void gprefs(void *sesskey, char *name, char *def,
+static void gprefs(void *sesskey, const char *name, const char *def,
                   const struct keyvalwhere *mapping, int nvals,
                   Conf *conf, int primary)
 {
@@ -383,7 +387,7 @@ static void gprefs(void *sesskey, char *name, char *def,
 /* 
  * Write out a preference list.
  */
-static void wprefs(void *sesskey, char *name,
+static void wprefs(void *sesskey, const char *name,
                   const struct keyvalwhere *mapping, int nvals,
                   Conf *conf, int primary)
 {
@@ -417,7 +421,7 @@ static void wprefs(void *sesskey, char *name,
     sfree(buf);
 }
 
-char *save_settings(char *section, Conf *conf)
+char *save_settings(const char *section, Conf *conf)
 {
     void *sesskey;
     char *errmsg;
@@ -433,7 +437,7 @@ char *save_settings(char *section, Conf *conf)
 void save_open_settings(void *sesskey, Conf *conf)
 {
     int i;
-    char *p;
+    const char *p;
 
     write_setting_i(sesskey, "Present", 1);
     write_setting_s(sesskey, "HostName", conf_get_str(conf, CONF_host));
@@ -476,6 +480,7 @@ void save_open_settings(void *sesskey, Conf *conf)
     write_setting_s(sesskey, "ProxyUsername", conf_get_str(conf, CONF_proxy_username));
     write_setting_s(sesskey, "ProxyPassword", conf_get_str(conf, CONF_proxy_password));
     write_setting_s(sesskey, "ProxyTelnetCommand", conf_get_str(conf, CONF_proxy_telnet_command));
+    write_setting_i(sesskey, "ProxyLogToTerm", conf_get_int(conf, CONF_proxy_log_to_term));
     wmap(sesskey, "Environment", conf, CONF_environmt, TRUE);
     write_setting_s(sesskey, "UserName", conf_get_str(conf, CONF_username));
     write_setting_i(sesskey, "UserNameFromEnvironment", conf_get_int(conf, CONF_username_from_env));
@@ -527,6 +532,10 @@ void save_open_settings(void *sesskey, Conf *conf)
     write_setting_i(sesskey, "AltOnly", conf_get_int(conf, CONF_alt_only));
     write_setting_i(sesskey, "ComposeKey", conf_get_int(conf, CONF_compose_key));
     write_setting_i(sesskey, "CtrlAltKeys", conf_get_int(conf, CONF_ctrlaltkeys));
+#ifdef OSX_META_KEY_CONFIG
+    write_setting_i(sesskey, "OSXOptionMeta", conf_get_int(conf, CONF_osx_option_meta));
+    write_setting_i(sesskey, "OSXCommandMeta", conf_get_int(conf, CONF_osx_command_meta));
+#endif
     write_setting_i(sesskey, "TelnetKey", conf_get_int(conf, CONF_telnet_keyboard));
     write_setting_i(sesskey, "TelnetRet", conf_get_int(conf, CONF_telnet_newline));
     write_setting_i(sesskey, "LocalEcho", conf_get_int(conf, CONF_localecho));
@@ -654,7 +663,7 @@ void save_open_settings(void *sesskey, Conf *conf)
     wmap(sesskey, "SSHManualHostKeys", conf, CONF_ssh_manual_hostkeys, FALSE);
 }
 
-void load_settings(char *section, Conf *conf)
+void load_settings(const char *section, Conf *conf)
 {
     void *sesskey;
 
@@ -751,6 +760,7 @@ void load_open_settings(void *sesskey, Conf *conf)
     gpps(sesskey, "ProxyPassword", "", conf, CONF_proxy_password);
     gpps(sesskey, "ProxyTelnetCommand", "connect %host %port\\n",
         conf, CONF_proxy_telnet_command);
+    gppi(sesskey, "ProxyLogToTerm", FORCE_OFF, conf, CONF_proxy_log_to_term);
     gppmap(sesskey, "Environment", conf, CONF_environmt);
     gpps(sesskey, "UserName", "", conf, CONF_username);
     gppi(sesskey, "UserNameFromEnvironment", 0, conf, CONF_username_from_env);
@@ -768,12 +778,14 @@ void load_open_settings(void *sesskey, Conf *conf)
         * disable gex under the "bugs" panel after one report of
         * a server which offered it then choked, but we never got
         * a server version string or any other reports. */
-       char *default_kexes;
+       const char *default_kexes;
        i = 2 - gppi_raw(sesskey, "BugDHGEx2", 0);
        if (i == FORCE_ON)
-           default_kexes = "dh-group14-sha1,dh-group1-sha1,rsa,WARN,dh-gex-sha1";
+            default_kexes = "ecdh,dh-group14-sha1,dh-group1-sha1,rsa,"
+                "WARN,dh-gex-sha1";
        else
-           default_kexes = "dh-gex-sha1,dh-group14-sha1,dh-group1-sha1,rsa,WARN";
+            default_kexes = "ecdh,dh-gex-sha1,dh-group14-sha1,"
+                "dh-group1-sha1,rsa,WARN";
        gprefs(sesskey, "KEX", default_kexes,
               kexnames, KEX_MAX, conf, CONF_ssh_kexlist);
     }
@@ -827,6 +839,10 @@ void load_open_settings(void *sesskey, Conf *conf)
     gppi(sesskey, "AltOnly", 0, conf, CONF_alt_only);
     gppi(sesskey, "ComposeKey", 0, conf, CONF_compose_key);
     gppi(sesskey, "CtrlAltKeys", 1, conf, CONF_ctrlaltkeys);
+#ifdef OSX_META_KEY_CONFIG
+    gppi(sesskey, "OSXOptionMeta", 1, conf, CONF_osx_option_meta);
+    gppi(sesskey, "OSXCommandMeta", 0, conf, CONF_osx_command_meta);
+#endif
     gppi(sesskey, "TelnetKey", 0, conf, CONF_telnet_keyboard);
     gppi(sesskey, "TelnetRet", 1, conf, CONF_telnet_newline);
     gppi(sesskey, "LocalEcho", AUTO, conf, CONF_localecho);
@@ -1003,7 +1019,7 @@ void load_open_settings(void *sesskey, Conf *conf)
     gppmap(sesskey, "SSHManualHostKeys", conf, CONF_ssh_manual_hostkeys);
 }
 
-void do_defaults(char *session, Conf *conf)
+void do_defaults(const char *session, Conf *conf)
 {
     load_settings(session, conf);
 }
@@ -1073,7 +1089,7 @@ void get_sesslist(struct sesslist *list, int allocate)
            p++;
        }
 
-       list->sessions = snewn(list->nsessions + 1, char *);
+       list->sessions = snewn(list->nsessions + 1, const char *);
        list->sessions[0] = "Default Settings";
        p = list->buffer;
        i = 1;
@@ -1085,7 +1101,7 @@ void get_sesslist(struct sesslist *list, int allocate)
            p++;
        }
 
-       qsort(list->sessions, i, sizeof(char *), sessioncmp);
+       qsort(list->sessions, i, sizeof(const char *), sessioncmp);
     } else {
        sfree(list->buffer);
        sfree(list->sessions);
diff --git a/sftp.c b/sftp.c
index bf75779df7e549660eeda4a0a866b05bb8fe5d9b..b4421f779e41dd56085bf5c840defa9594e78948 100644 (file)
--- a/sftp.c
+++ b/sftp.c
@@ -23,7 +23,7 @@ struct sftp_packet {
 static const char *fxp_error_message;
 static int fxp_errtype;
 
-static void fxp_internal_error(char *msg);
+static void fxp_internal_error(const char *msg);
 
 /* ----------------------------------------------------------------------
  * SFTP packet construction functions.
@@ -35,7 +35,8 @@ static void sftp_pkt_ensure(struct sftp_packet *pkt, int length)
        pkt->data = sresize(pkt->data, pkt->maxlen, char);
     }
 }
-static void sftp_pkt_adddata(struct sftp_packet *pkt, void *data, int len)
+static void sftp_pkt_adddata(struct sftp_packet *pkt,
+                             const void *data, int len)
 {
     pkt->length += len;
     sftp_pkt_ensure(pkt, pkt->length);
@@ -82,18 +83,18 @@ static void sftp_pkt_addstring_start(struct sftp_packet *pkt)
     sftp_pkt_adduint32(pkt, 0);
     pkt->savedpos = pkt->length;
 }
-static void sftp_pkt_addstring_str(struct sftp_packet *pkt, char *data)
+static void sftp_pkt_addstring_str(struct sftp_packet *pkt, const char *data)
 {
     sftp_pkt_adddata(pkt, data, strlen(data));
     PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos);
 }
 static void sftp_pkt_addstring_data(struct sftp_packet *pkt,
-                                   char *data, int len)
+                                   const char *data, int len)
 {
     sftp_pkt_adddata(pkt, data, len);
     PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos);
 }
-static void sftp_pkt_addstring(struct sftp_packet *pkt, char *data)
+static void sftp_pkt_addstring(struct sftp_packet *pkt, const char *data)
 {
     sftp_pkt_addstring_start(pkt);
     sftp_pkt_addstring_str(pkt, data);
@@ -438,7 +439,7 @@ static int fxp_got_status(struct sftp_packet *pktin)
        return -1;
 }
 
-static void fxp_internal_error(char *msg)
+static void fxp_internal_error(const char *msg)
 {
     fxp_error_message = msg;
     fxp_errtype = -1;
@@ -501,7 +502,7 @@ int fxp_init(void)
 /*
  * Canonify a pathname.
  */
-struct sftp_request *fxp_realpath_send(char *path)
+struct sftp_request *fxp_realpath_send(const char *path)
 {
     struct sftp_request *req = sftp_alloc_request();
     struct sftp_packet *pktout;
@@ -547,7 +548,7 @@ char *fxp_realpath_recv(struct sftp_packet *pktin, struct sftp_request *req)
 /*
  * Open a file.
  */
-struct sftp_request *fxp_open_send(char *path, int type,
+struct sftp_request *fxp_open_send(const char *path, int type,
                                    struct fxp_attrs *attrs)
 {
     struct sftp_request *req = sftp_alloc_request();
@@ -596,7 +597,7 @@ struct fxp_handle *fxp_open_recv(struct sftp_packet *pktin,
 /*
  * Open a directory.
  */
-struct sftp_request *fxp_opendir_send(char *path)
+struct sftp_request *fxp_opendir_send(const char *path)
 {
     struct sftp_request *req = sftp_alloc_request();
     struct sftp_packet *pktout;
@@ -662,7 +663,7 @@ void fxp_close_recv(struct sftp_packet *pktin, struct sftp_request *req)
     sftp_pkt_free(pktin);
 }
 
-struct sftp_request *fxp_mkdir_send(char *path)
+struct sftp_request *fxp_mkdir_send(const char *path)
 {
     struct sftp_request *req = sftp_alloc_request();
     struct sftp_packet *pktout;
@@ -688,7 +689,7 @@ int fxp_mkdir_recv(struct sftp_packet *pktin, struct sftp_request *req)
     return 1;
 }
 
-struct sftp_request *fxp_rmdir_send(char *path)
+struct sftp_request *fxp_rmdir_send(const char *path)
 {
     struct sftp_request *req = sftp_alloc_request();
     struct sftp_packet *pktout;
@@ -713,7 +714,7 @@ int fxp_rmdir_recv(struct sftp_packet *pktin, struct sftp_request *req)
     return 1;
 }
 
-struct sftp_request *fxp_remove_send(char *fname)
+struct sftp_request *fxp_remove_send(const char *fname)
 {
     struct sftp_request *req = sftp_alloc_request();
     struct sftp_packet *pktout;
@@ -738,7 +739,8 @@ int fxp_remove_recv(struct sftp_packet *pktin, struct sftp_request *req)
     return 1;
 }
 
-struct sftp_request *fxp_rename_send(char *srcfname, char *dstfname)
+struct sftp_request *fxp_rename_send(const char *srcfname,
+                                     const char *dstfname)
 {
     struct sftp_request *req = sftp_alloc_request();
     struct sftp_packet *pktout;
@@ -768,7 +770,7 @@ int fxp_rename_recv(struct sftp_packet *pktin, struct sftp_request *req)
  * Retrieve the attributes of a file. We have fxp_stat which works
  * on filenames, and fxp_fstat which works on open file handles.
  */
-struct sftp_request *fxp_stat_send(char *fname)
+struct sftp_request *fxp_stat_send(const char *fname)
 {
     struct sftp_request *req = sftp_alloc_request();
     struct sftp_packet *pktout;
@@ -836,7 +838,8 @@ int fxp_fstat_recv(struct sftp_packet *pktin, struct sftp_request *req,
 /*
  * Set the attributes of a file.
  */
-struct sftp_request *fxp_setstat_send(char *fname, struct fxp_attrs attrs)
+struct sftp_request *fxp_setstat_send(const char *fname,
+                                      struct fxp_attrs attrs)
 {
     struct sftp_request *req = sftp_alloc_request();
     struct sftp_packet *pktout;
diff --git a/sftp.h b/sftp.h
index c3167a57a0cf3d0619f9a4d7d35a365c37b56f9f..c61340c1582273b9e8e723b60f56b2eeeb548e44 100644 (file)
--- a/sftp.h
+++ b/sftp.h
@@ -125,14 +125,14 @@ int fxp_init(void);
  * Canonify a pathname. Concatenate the two given path elements
  * with a separating slash, unless the second is NULL.
  */
-struct sftp_request *fxp_realpath_send(char *path);
+struct sftp_request *fxp_realpath_send(const char *path);
 char *fxp_realpath_recv(struct sftp_packet *pktin, struct sftp_request *req);
 
 /*
  * Open a file. 'attrs' contains attributes to be applied to the file
  * if it's being created.
  */
-struct sftp_request *fxp_open_send(char *path, int type,
+struct sftp_request *fxp_open_send(const char *path, int type,
                                    struct fxp_attrs *attrs);
 struct fxp_handle *fxp_open_recv(struct sftp_packet *pktin,
                                 struct sftp_request *req);
@@ -140,7 +140,7 @@ struct fxp_handle *fxp_open_recv(struct sftp_packet *pktin,
 /*
  * Open a directory.
  */
-struct sftp_request *fxp_opendir_send(char *path);
+struct sftp_request *fxp_opendir_send(const char *path);
 struct fxp_handle *fxp_opendir_recv(struct sftp_packet *pktin,
                                    struct sftp_request *req);
 
@@ -153,31 +153,32 @@ void fxp_close_recv(struct sftp_packet *pktin, struct sftp_request *req);
 /*
  * Make a directory.
  */
-struct sftp_request *fxp_mkdir_send(char *path);
+struct sftp_request *fxp_mkdir_send(const char *path);
 int fxp_mkdir_recv(struct sftp_packet *pktin, struct sftp_request *req);
 
 /*
  * Remove a directory.
  */
-struct sftp_request *fxp_rmdir_send(char *path);
+struct sftp_request *fxp_rmdir_send(const char *path);
 int fxp_rmdir_recv(struct sftp_packet *pktin, struct sftp_request *req);
 
 /*
  * Remove a file.
  */
-struct sftp_request *fxp_remove_send(char *fname);
+struct sftp_request *fxp_remove_send(const char *fname);
 int fxp_remove_recv(struct sftp_packet *pktin, struct sftp_request *req);
 
 /*
  * Rename a file.
  */
-struct sftp_request *fxp_rename_send(char *srcfname, char *dstfname);
+struct sftp_request *fxp_rename_send(const char *srcfname,
+                                     const char *dstfname);
 int fxp_rename_recv(struct sftp_packet *pktin, struct sftp_request *req);
 
 /*
  * Return file attributes.
  */
-struct sftp_request *fxp_stat_send(char *fname);
+struct sftp_request *fxp_stat_send(const char *fname);
 int fxp_stat_recv(struct sftp_packet *pktin, struct sftp_request *req,
                  struct fxp_attrs *attrs);
 struct sftp_request *fxp_fstat_send(struct fxp_handle *handle);
@@ -187,7 +188,8 @@ int fxp_fstat_recv(struct sftp_packet *pktin, struct sftp_request *req,
 /*
  * Set file attributes.
  */
-struct sftp_request *fxp_setstat_send(char *fname, struct fxp_attrs attrs);
+struct sftp_request *fxp_setstat_send(const char *fname,
+                                      struct fxp_attrs attrs);
 int fxp_setstat_recv(struct sftp_packet *pktin, struct sftp_request *req);
 struct sftp_request *fxp_fsetstat_send(struct fxp_handle *handle,
                                       struct fxp_attrs attrs);
diff --git a/ssh.c b/ssh.c
index 1077209ae8e1fee800c0c0258db04da3b2742934..9429724804485dea33c388c0094a2996c1e50368 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -32,6 +32,7 @@ typedef enum {
     SSH2_PKTCTX_NOKEX,
     SSH2_PKTCTX_DHGROUP,
     SSH2_PKTCTX_DHGEX,
+    SSH2_PKTCTX_ECDHKEX,
     SSH2_PKTCTX_RSAKEX
 } Pkt_KCtx;
 typedef enum {
@@ -188,7 +189,7 @@ static unsigned int ssh_tty_parse_boolean(char *s)
 #define translate(x) if (type == x) return #x
 #define translatek(x,ctx) if (type == x && (pkt_kctx == ctx)) return #x
 #define translatea(x,ctx) if (type == x && (pkt_actx == ctx)) return #x
-static char *ssh1_pkt_type(int type)
+static const char *ssh1_pkt_type(int type)
 {
     translate(SSH1_MSG_DISCONNECT);
     translate(SSH1_SMSG_PUBLIC_KEY);
@@ -233,7 +234,8 @@ static char *ssh1_pkt_type(int type)
     translate(SSH1_CMSG_AUTH_CCARD_RESPONSE);
     return "unknown";
 }
-static char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type)
+static const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx,
+                                 int type)
 {
     translatea(SSH2_MSG_USERAUTH_GSSAPI_RESPONSE,SSH2_PKTCTX_GSSAPI);
     translatea(SSH2_MSG_USERAUTH_GSSAPI_TOKEN,SSH2_PKTCTX_GSSAPI);
@@ -259,6 +261,8 @@ static char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type)
     translatek(SSH2_MSG_KEXRSA_PUBKEY, SSH2_PKTCTX_RSAKEX);
     translatek(SSH2_MSG_KEXRSA_SECRET, SSH2_PKTCTX_RSAKEX);
     translatek(SSH2_MSG_KEXRSA_DONE, SSH2_PKTCTX_RSAKEX);
+    translatek(SSH2_MSG_KEX_ECDH_INIT, SSH2_PKTCTX_ECDHKEX);
+    translatek(SSH2_MSG_KEX_ECDH_REPLY, SSH2_PKTCTX_ECDHKEX);
     translate(SSH2_MSG_USERAUTH_REQUEST);
     translate(SSH2_MSG_USERAUTH_FAILURE);
     translate(SSH2_MSG_USERAUTH_SUCCESS);
@@ -354,9 +358,9 @@ static void ssh2_pkt_addmp(struct Packet *, Bignum b);
 static int ssh2_pkt_construct(Ssh, struct Packet *);
 static void ssh2_pkt_send(Ssh, struct Packet *);
 static void ssh2_pkt_send_noqueue(Ssh, struct Packet *);
-static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
+static int do_ssh1_login(Ssh ssh, const unsigned char *in, int inlen,
                         struct Packet *pktin);
-static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
+static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen,
                             struct Packet *pktin);
 static void ssh2_channel_check_close(struct ssh_channel *c);
 static void ssh_channel_destroy(struct ssh_channel *c);
@@ -403,7 +407,11 @@ 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_rsa, &ssh_dss };
+const static struct ssh_signkey *hostkey_algs[] = {
+    &ssh_ecdsa_ed25519,
+    &ssh_ecdsa_nistp256, &ssh_ecdsa_nistp384, &ssh_ecdsa_nistp521,
+    &ssh_rsa, &ssh_dss
+};
 
 const static struct ssh_mac *macs[] = {
     &ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5
@@ -680,11 +688,11 @@ struct Packet {
     const char *additional_log_text;
 };
 
-static void ssh1_protocol(Ssh ssh, void *vin, int inlen,
+static void ssh1_protocol(Ssh ssh, const void *vin, int inlen,
                          struct Packet *pktin);
-static void ssh2_protocol(Ssh ssh, void *vin, int inlen,
+static void ssh2_protocol(Ssh ssh, const void *vin, int inlen,
                          struct Packet *pktin);
-static void ssh2_bare_connection_protocol(Ssh ssh, void *vin, int inlen,
+static void ssh2_bare_connection_protocol(Ssh ssh, const void *vin, int inlen,
                                           struct Packet *pktin);
 static void ssh1_protocol_setup(Ssh ssh);
 static void ssh2_protocol_setup(Ssh ssh);
@@ -692,7 +700,8 @@ static void ssh2_bare_connection_protocol_setup(Ssh ssh);
 static void ssh_size(void *handle, int width, int height);
 static void ssh_special(void *handle, Telnet_Special);
 static int ssh2_try_send(struct ssh_channel *c);
-static void ssh2_add_channel_data(struct ssh_channel *c, char *buf, int len);
+static void ssh2_add_channel_data(struct ssh_channel *c,
+                                  const char *buf, int len);
 static void ssh_throttle_all(Ssh ssh, int enable, int bufsize);
 static void ssh2_set_window(struct ssh_channel *c, int newwin);
 static int ssh_sendbuffer(void *handle);
@@ -701,7 +710,7 @@ static unsigned long ssh_pkt_getuint32(struct Packet *pkt);
 static int ssh2_pkt_getbool(struct Packet *pkt);
 static void ssh_pkt_getstring(struct Packet *pkt, char **p, int *length);
 static void ssh2_timer(void *ctx, unsigned long now);
-static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
+static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
                              struct Packet *pktin);
 static void ssh2_msg_unexpected(Ssh ssh, struct Packet *pktin);
 
@@ -763,6 +772,7 @@ struct ssh_tag {
     const struct ssh2_cipher *cscipher, *sccipher;
     void *cs_cipher_ctx, *sc_cipher_ctx;
     const struct ssh_mac *csmac, *scmac;
+    int csmac_etm, scmac_etm;
     void *cs_mac_ctx, *sc_mac_ctx;
     const struct ssh_compress *cscomp, *sccomp;
     void *cs_comp_ctx, *sc_comp_ctx;
@@ -782,6 +792,7 @@ struct ssh_tag {
     int send_ok;
     int echoing, editing;
 
+    int session_started;
     void *frontend;
 
     int ospeed, ispeed;                       /* temporaries */
@@ -857,9 +868,10 @@ struct ssh_tag {
     /* SSH-1 and SSH-2 use this for different things, but both use it */
     int protocol_initial_phase_done;
 
-    void (*protocol) (Ssh ssh, void *vin, int inlen,
+    void (*protocol) (Ssh ssh, const void *vin, int inlen,
                      struct Packet *pkt);
-    struct Packet *(*s_rdpkt) (Ssh ssh, unsigned char **data, int *datalen);
+    struct Packet *(*s_rdpkt) (Ssh ssh, const unsigned char **data,
+                               int *datalen);
     int (*do_ssh_init)(Ssh ssh, unsigned char c);
 
     /*
@@ -929,7 +941,7 @@ struct ssh_tag {
     unsigned long max_data_size;
     int kex_in_progress;
     unsigned long next_rekey, last_rekey;
-    char *deferred_rekey_reason;    /* points to STATIC string; don't free */
+    const char *deferred_rekey_reason;
 
     /*
      * Fully qualified host name, which we need if doing GSSAPI.
@@ -1287,7 +1299,8 @@ static void ssh1_log_outgoing_packet(Ssh ssh, struct Packet *pkt)
  * Update the *data and *datalen variables.
  * Return a Packet structure when a packet is completed.
  */
-static struct Packet *ssh1_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
+static struct Packet *ssh1_rdpkt(Ssh ssh, const unsigned char **data,
+                                 int *datalen)
 {
     struct rdpkt1_state_tag *st = &ssh->rdpkt1_state;
 
@@ -1542,7 +1555,8 @@ static void ssh2_log_outgoing_packet(Ssh ssh, struct Packet *pkt)
     pkt->length += (pkt->body - pkt->data);
 }
 
-static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
+static struct Packet *ssh2_rdpkt(Ssh ssh, const unsigned char **data,
+                                 int *datalen)
 {
     struct rdpkt2_state_tag *st = &ssh->rdpkt2_state;
 
@@ -1561,7 +1575,7 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
     st->maclen = ssh->scmac ? ssh->scmac->len : 0;
 
     if (ssh->sccipher && (ssh->sccipher->flags & SSH_CIPHER_IS_CBC) &&
-       ssh->scmac) {
+       ssh->scmac && !ssh->scmac_etm) {
        /*
         * When dealing with a CBC-mode cipher, we want to avoid the
         * possibility of an attacker's tweaking the ciphertext stream
@@ -1573,6 +1587,11 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
         * length, so we just read data and check the MAC repeatedly,
         * and when the MAC passes, see if the length we've got is
         * plausible.
+         *
+         * This defence is unnecessary in OpenSSH ETM mode, because
+         * the whole point of ETM mode is that the attacker can't
+         * tweak the ciphertext stream at all without the MAC
+         * detecting it before we decrypt anything.
         */
 
        /* May as well allocate the whole lot now. */
@@ -1627,6 +1646,80 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
        st->pktin->data = sresize(st->pktin->data,
                                  st->pktin->maxlen + APIEXTRA,
                                  unsigned char);
+    } else if (ssh->scmac && ssh->scmac_etm) {
+       st->pktin->data = snewn(4 + APIEXTRA, unsigned char);
+
+        /*
+         * OpenSSH encrypt-then-MAC mode: the packet length is
+         * unencrypted, unless the cipher supports length encryption.
+         */
+       for (st->i = st->len = 0; st->i < 4; st->i++) {
+           while ((*datalen) == 0)
+               crReturn(NULL);
+           st->pktin->data[st->i] = *(*data)++;
+           (*datalen)--;
+       }
+        /* Cipher supports length decryption, so do it */
+        if (ssh->sccipher && (ssh->sccipher->flags & SSH_CIPHER_SEPARATE_LENGTH)) {
+            /* Keep the packet the same though, so the MAC passes */
+            unsigned char len[4];
+            memcpy(len, st->pktin->data, 4);
+            ssh->sccipher->decrypt_length(ssh->sc_cipher_ctx, len, 4, st->incoming_sequence);
+            st->len = toint(GET_32BIT(len));
+        } else {
+            st->len = toint(GET_32BIT(st->pktin->data));
+        }
+
+       /*
+        * _Completely_ silly lengths should be stomped on before they
+        * do us any more damage.
+        */
+       if (st->len < 0 || st->len > OUR_V2_PACKETLIMIT ||
+           st->len % st->cipherblk != 0) {
+           bombout(("Incoming packet length field was garbled"));
+           ssh_free_packet(st->pktin);
+           crStop(NULL);
+       }
+
+       /*
+        * So now we can work out the total packet length.
+        */
+       st->packetlen = st->len + 4;
+
+       /*
+        * Allocate memory for the rest of the packet.
+        */
+       st->pktin->maxlen = st->packetlen + st->maclen;
+       st->pktin->data = sresize(st->pktin->data,
+                                 st->pktin->maxlen + APIEXTRA,
+                                 unsigned char);
+
+       /*
+        * Read the remainder of the packet.
+        */
+       for (st->i = 4; st->i < st->packetlen + st->maclen; st->i++) {
+           while ((*datalen) == 0)
+               crReturn(NULL);
+           st->pktin->data[st->i] = *(*data)++;
+           (*datalen)--;
+       }
+
+       /*
+        * Check the MAC.
+        */
+       if (ssh->scmac
+           && !ssh->scmac->verify(ssh->sc_mac_ctx, st->pktin->data,
+                                  st->len + 4, st->incoming_sequence)) {
+           bombout(("Incorrect MAC received on packet"));
+           ssh_free_packet(st->pktin);
+           crStop(NULL);
+       }
+
+       /* Decrypt everything between the length field and the MAC. */
+       if (ssh->sccipher)
+           ssh->sccipher->decrypt(ssh->sc_cipher_ctx,
+                                  st->pktin->data + 4,
+                                  st->packetlen - 4);
     } else {
        st->pktin->data = snewn(st->cipherblk + APIEXTRA, unsigned char);
 
@@ -1769,7 +1862,8 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
     crFinish(st->pktin);
 }
 
-static struct Packet *ssh2_bare_connection_rdpkt(Ssh ssh, unsigned char **data,
+static struct Packet *ssh2_bare_connection_rdpkt(Ssh ssh,
+                                                 const unsigned char **data,
                                                  int *datalen)
 {
     struct rdpkt2_bare_state_tag *st = &ssh->rdpkt2_bare_state;
@@ -1982,7 +2076,7 @@ static void defer_packet(Ssh ssh, int pkttype, ...)
     s_wrpkt_defer(ssh, pkt);
 }
 
-static int ssh_versioncmp(char *a, char *b)
+static int ssh_versioncmp(const char *a, const char *b)
 {
     char *ae, *be;
     unsigned long av, bv;
@@ -2059,17 +2153,16 @@ static void ssh_pkt_addstring_start(struct Packet *pkt)
     ssh_pkt_adduint32(pkt, 0);
     pkt->savedpos = pkt->length;
 }
-static void ssh_pkt_addstring_str(struct Packet *pkt, const char *data)
-{
-    ssh_pkt_adddata(pkt, data, strlen(data));
-    PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos);
-}
 static void ssh_pkt_addstring_data(struct Packet *pkt, const char *data,
                                    int len)
 {
     ssh_pkt_adddata(pkt, data, len);
     PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos);
 }
+static void ssh_pkt_addstring_str(struct Packet *pkt, const char *data)
+{
+  ssh_pkt_addstring_data(pkt, data, strlen(data));
+}
 static void ssh_pkt_addstring(struct Packet *pkt, const char *data)
 {
     ssh_pkt_addstring_start(pkt);
@@ -2150,7 +2243,7 @@ static struct Packet *ssh2_pkt_init(int pkt_type)
  */
 static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt)
 {
-    int cipherblk, maclen, padding, i;
+    int cipherblk, maclen, padding, unencrypted_prefix, i;
 
     if (ssh->logctx)
         ssh2_log_outgoing_packet(ssh, pkt);
@@ -2191,10 +2284,12 @@ static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt)
     cipherblk = ssh->cscipher ? ssh->cscipher->blksize : 8;  /* block size */
     cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */
     padding = 4;
+    unencrypted_prefix = (ssh->csmac && ssh->csmac_etm) ? 4 : 0;
     if (pkt->length + padding < pkt->forcepad)
        padding = pkt->forcepad - pkt->length;
     padding +=
-       (cipherblk - (pkt->length + padding) % cipherblk) % cipherblk;
+       (cipherblk - (pkt->length - unencrypted_prefix + padding) % cipherblk)
+        % cipherblk;
     assert(padding <= 255);
     maclen = ssh->csmac ? ssh->csmac->len : 0;
     ssh2_pkt_ensure(pkt, pkt->length + padding + maclen);
@@ -2202,16 +2297,37 @@ static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt)
     for (i = 0; i < padding; i++)
        pkt->data[pkt->length + i] = random_byte();
     PUT_32BIT(pkt->data, pkt->length + padding - 4);
-    if (ssh->csmac)
-       ssh->csmac->generate(ssh->cs_mac_ctx, pkt->data,
-                            pkt->length + padding,
-                            ssh->v2_outgoing_sequence);
-    ssh->v2_outgoing_sequence++;       /* whether or not we MACed */
 
-    if (ssh->cscipher)
-       ssh->cscipher->encrypt(ssh->cs_cipher_ctx,
-                              pkt->data, pkt->length + padding);
+    /* Encrypt length if the scheme requires it */
+    if (ssh->cscipher && (ssh->cscipher->flags & SSH_CIPHER_SEPARATE_LENGTH)) {
+        ssh->cscipher->encrypt_length(ssh->cs_cipher_ctx, pkt->data, 4,
+                                      ssh->v2_outgoing_sequence);
+    }
 
+    if (ssh->csmac && ssh->csmac_etm) {
+        /*
+         * OpenSSH-defined encrypt-then-MAC protocol.
+         */
+        if (ssh->cscipher)
+            ssh->cscipher->encrypt(ssh->cs_cipher_ctx,
+                                   pkt->data + 4, pkt->length + padding - 4);
+        ssh->csmac->generate(ssh->cs_mac_ctx, pkt->data,
+                             pkt->length + padding,
+                             ssh->v2_outgoing_sequence);
+    } else {
+        /*
+         * SSH-2 standard protocol.
+         */
+        if (ssh->csmac)
+            ssh->csmac->generate(ssh->cs_mac_ctx, pkt->data,
+                                 pkt->length + padding,
+                                 ssh->v2_outgoing_sequence);
+        if (ssh->cscipher)
+            ssh->cscipher->encrypt(ssh->cs_cipher_ctx,
+                                   pkt->data, pkt->length + padding);
+    }
+
+    ssh->v2_outgoing_sequence++;       /* whether or not we MACed */
     pkt->encrypted_len = pkt->length + padding;
 
     /* Ready-to-send packet starts at pkt->data. We return length. */
@@ -2544,7 +2660,7 @@ static void *ssh_pkt_getdata(struct Packet *pkt, int length)
     return pkt->body + (pkt->savedpos - length);
 }
 static int ssh1_pkt_getrsakey(struct Packet *pkt, struct RSAKey *key,
-                             unsigned char **keystr)
+                             const unsigned char **keystr)
 {
     int j;
 
@@ -2825,7 +2941,8 @@ static void ssh_detect_bugs(Ssh ssh, char *vstring)
         (wc_match("OpenSSH_2.[235]*", imp)))) {
        /*
         * These versions only support the original (pre-RFC4419)
-        * SSH-2 GEX request.
+        * SSH-2 GEX request, and disconnect with a protocol error if
+        * we use the newer version.
         */
        ssh->remote_bugs |= BUG_SSH2_OLDGEX;
        logevent("We believe remote version has outdated SSH-2 GEX");
@@ -2901,6 +3018,10 @@ static void ssh_send_verstring(Ssh ssh, const char *protoname, char *svers)
     }
 
     ssh_fix_verstring(verstring + strlen(protoname));
+#ifdef FUZZING
+    /* FUZZING make PuTTY insecure, so make live use difficult. */
+    verstring[0] = 'I';
+#endif
 
     if (ssh->version == 2) {
        size_t len;
@@ -2950,6 +3071,8 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
        crReturn(1);
     }
 
+    ssh->session_started = TRUE;
+
     s->vstrsize = sizeof(protoname) + 16;
     s->vstring = snewn(s->vstrsize, char);
     strcpy(s->vstring, protoname);
@@ -3159,7 +3282,7 @@ static int do_ssh_connection_init(Ssh ssh, unsigned char c)
 }
 
 static void ssh_process_incoming_data(Ssh ssh,
-                                     unsigned char **data, int *datalen)
+                                     const unsigned char **data, int *datalen)
 {
     struct Packet *pktin;
 
@@ -3171,7 +3294,7 @@ static void ssh_process_incoming_data(Ssh ssh,
 }
 
 static void ssh_queue_incoming_data(Ssh ssh,
-                                   unsigned char **data, int *datalen)
+                                   const unsigned char **data, int *datalen)
 {
     bufchain_add(&ssh->queued_incoming_data, *data, *datalen);
     *data += *datalen;
@@ -3181,7 +3304,7 @@ static void ssh_queue_incoming_data(Ssh ssh,
 static void ssh_process_queued_incoming_data(Ssh ssh)
 {
     void *vdata;
-    unsigned char *data;
+    const unsigned char *data;
     int len, origlen;
 
     while (!ssh->frozen && bufchain_size(&ssh->queued_incoming_data)) {
@@ -3204,7 +3327,7 @@ static void ssh_set_frozen(Ssh ssh, int frozen)
     ssh->frozen = frozen;
 }
 
-static void ssh_gotdata(Ssh ssh, unsigned char *data, int datalen)
+static void ssh_gotdata(Ssh ssh, const unsigned char *data, int datalen)
 {
     /* Log raw data, if we're in that mode. */
     if (ssh->logctx)
@@ -3332,34 +3455,20 @@ static void ssh_socket_log(Plug plug, int type, SockAddr addr, int port,
                            const char *error_msg, int error_code)
 {
     Ssh ssh = (Ssh) plug;
-    char addrbuf[256], *msg;
 
-    if (ssh->attempting_connshare) {
-        /*
-         * While we're attempting connection sharing, don't loudly log
-         * everything that happens. Real TCP connections need to be
-         * logged when we _start_ trying to connect, because it might
-         * be ages before they respond if something goes wrong; but
-         * connection sharing is local and quick to respond, and it's
-         * sufficient to simply wait and see whether it worked
-         * afterwards.
-         */
-    } else {
-        sk_getaddr(addr, addrbuf, lenof(addrbuf));
-
-        if (type == 0) {
-            if (sk_addr_needs_port(addr)) {
-                msg = dupprintf("Connecting to %s port %d", addrbuf, port);
-            } else {
-                msg = dupprintf("Connecting to %s", addrbuf);
-            }
-        } else {
-            msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg);
-        }
+    /*
+     * While we're attempting connection sharing, don't loudly log
+     * everything that happens. Real TCP connections need to be logged
+     * when we _start_ trying to connect, because it might be ages
+     * before they respond if something goes wrong; but connection
+     * sharing is local and quick to respond, and it's sufficient to
+     * simply wait and see whether it worked afterwards.
+     */
 
-        logevent(msg);
-        sfree(msg);
-    }
+    if (!ssh->attempting_connshare)
+        backend_socket_log(ssh->frontend, type, addr, port,
+                           error_msg, error_code, ssh->conf,
+                           ssh->session_started);
 }
 
 void ssh_connshare_log(Ssh ssh, int event, const char *logtext,
@@ -3443,35 +3552,20 @@ static void ssh_sent(Plug plug, int bufsize)
        ssh_throttle_all(ssh, 0, bufsize);
 }
 
-/*
- * Connect to specified host and port.
- * Returns an error message, or NULL on success.
- * Also places the canonical host name into `realhost'. It must be
- * freed by the caller.
- */
-static const char *connect_to_host(Ssh ssh, char *host, int port,
-                                  char **realhost, int nodelay, int keepalive)
+static void ssh_hostport_setup(const char *host, int port, Conf *conf,
+                               char **savedhost, int *savedport,
+                               char **loghost_ret)
 {
-    static const struct plug_function_table fn_table = {
-       ssh_socket_log,
-       ssh_closing,
-       ssh_receive,
-       ssh_sent,
-       NULL
-    };
+    char *loghost = conf_get_str(conf, CONF_loghost);
+    if (loghost_ret)
+        *loghost_ret = loghost;
 
-    SockAddr addr;
-    const char *err;
-    char *loghost;
-    int addressfamily, sshprot;
-    
-    loghost = conf_get_str(ssh->conf, CONF_loghost);
     if (*loghost) {
        char *tmphost;
         char *colon;
 
         tmphost = dupstr(loghost);
-       ssh->savedport = 22;           /* default ssh port */
+       *savedport = 22;               /* default ssh port */
 
        /*
         * A colon suffix on the hostname string also lets us affect
@@ -3482,17 +3576,58 @@ static const char *connect_to_host(Ssh ssh, char *host, int port,
        if (colon && colon == host_strchr(tmphost, ':')) {
            *colon++ = '\0';
            if (*colon)
-               ssh->savedport = atoi(colon);
+               *savedport = atoi(colon);
        }
 
-        ssh->savedhost = host_strduptrim(tmphost);
+        *savedhost = host_strduptrim(tmphost);
         sfree(tmphost);
     } else {
-       ssh->savedhost = host_strduptrim(host);
+       *savedhost = host_strduptrim(host);
        if (port < 0)
            port = 22;                 /* default ssh port */
-       ssh->savedport = port;
+       *savedport = port;
     }
+}
+
+static int ssh_test_for_upstream(const char *host, int port, Conf *conf)
+{
+    char *savedhost;
+    int savedport;
+    int ret;
+
+    random_ref(); /* platform may need this to determine share socket name */
+    ssh_hostport_setup(host, port, conf, &savedhost, &savedport, NULL);
+    ret = ssh_share_test_for_upstream(savedhost, savedport, conf);
+    sfree(savedhost);
+    random_unref();
+
+    return ret;
+}
+
+/*
+ * Connect to specified host and port.
+ * Returns an error message, or NULL on success.
+ * Also places the canonical host name into `realhost'. It must be
+ * freed by the caller.
+ */
+static const char *connect_to_host(Ssh ssh, const char *host, int port,
+                                  char **realhost, int nodelay, int keepalive)
+{
+    static const struct plug_function_table fn_table = {
+       ssh_socket_log,
+       ssh_closing,
+       ssh_receive,
+       ssh_sent,
+       NULL
+    };
+
+    SockAddr addr;
+    const char *err;
+    char *loghost;
+    int addressfamily, sshprot;
+
+    ssh_hostport_setup(host, port, ssh->conf,
+                       &ssh->savedhost, &ssh->savedport, &loghost);
 
     ssh->fn = &fn_table;               /* make 'ssh' usable as a Plug */
 
@@ -3528,10 +3663,8 @@ static const char *connect_to_host(Ssh ssh, char *host, int port,
          * Try to find host.
          */
         addressfamily = conf_get_int(ssh->conf, CONF_addressfamily);
-        logeventf(ssh, "Looking up host \"%s\"%s", host,
-                  (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" :
-                   (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : "")));
-        addr = name_lookup(host, port, realhost, ssh->conf, addressfamily);
+        addr = name_lookup(host, port, realhost, ssh->conf, addressfamily,
+                           ssh->frontend, "SSH connection");
         if ((err = sk_addr_error(addr)) != NULL) {
             sk_addr_free(addr);
             return err;
@@ -3656,7 +3789,7 @@ static void ssh_agentf_callback(void *cv, void *reply, int replylen)
 {
     struct ssh_channel *c = (struct ssh_channel *)cv;
     Ssh ssh = c->ssh;
-    void *sentreply = reply;
+    const void *sentreply = reply;
 
     c->u.a.outstanding_requests--;
     if (!sentreply) {
@@ -3689,7 +3822,8 @@ static void ssh_agentf_callback(void *cv, void *reply, int replylen)
  * non-NULL, otherwise just close the connection. `client_reason' == NULL
  * => log `wire_reason'.
  */
-static void ssh_disconnect(Ssh ssh, char *client_reason, char *wire_reason,
+static void ssh_disconnect(Ssh ssh, const char *client_reason,
+                           const char *wire_reason,
                           int code, int clean_exit)
 {
     char *error;
@@ -3773,7 +3907,7 @@ int verify_ssh_manual_host_key(Ssh ssh, const char *fingerprint,
 /*
  * Handle the key exchange and user authentication phases.
  */
-static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
+static int do_ssh1_login(Ssh ssh, const unsigned char *in, int inlen,
                         struct Packet *pktin)
 {
     int i, j, ret;
@@ -3782,7 +3916,8 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
     struct do_ssh1_login_state {
        int crLine;
        int len;
-       unsigned char *rsabuf, *keystr1, *keystr2;
+       unsigned char *rsabuf;
+        const unsigned char *keystr1, *keystr2;
        unsigned long supported_ciphers_mask, supported_auths_mask;
        int tried_publickey, tried_agent;
        int tis_auth_refused, ccard_auth_refused;
@@ -3791,7 +3926,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
        void *publickey_blob;
        int publickey_bloblen;
        char *publickey_comment;
-       int publickey_encrypted;
+       int privatekey_available, privatekey_encrypted;
        prompts_t *cur_prompt;
        char c;
        int pwpkt_type;
@@ -3906,6 +4041,9 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
                                             "rsa", keystr, fingerprint,
                                             ssh_dialog_callback, ssh);
             sfree(keystr);
+#ifdef FUZZING
+           s->dlgret = 1;
+#endif
             if (s->dlgret < 0) {
                 do {
                     crReturn(0);
@@ -3953,7 +4091,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
 
     {
        int cipher_chosen = 0, warn = 0;
-       char *cipher_string = NULL;
+       const char *cipher_string = NULL;
        int i;
        for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) {
            int next_cipher = conf_get_int_int(ssh->conf,
@@ -4125,20 +4263,24 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
     s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile);
     if (!filename_is_null(s->keyfile)) {
        int keytype;
-       logeventf(ssh, "Reading private key file \"%.150s\"",
+       logeventf(ssh, "Reading key file \"%.150s\"",
                  filename_to_str(s->keyfile));
        keytype = key_type(s->keyfile);
-       if (keytype == SSH_KEYTYPE_SSH1) {
+       if (keytype == SSH_KEYTYPE_SSH1 ||
+            keytype == SSH_KEYTYPE_SSH1_PUBLIC) {
            const char *error;
            if (rsakey_pubblob(s->keyfile,
                               &s->publickey_blob, &s->publickey_bloblen,
                               &s->publickey_comment, &error)) {
-               s->publickey_encrypted = rsakey_encrypted(s->keyfile,
-                                                         NULL);
+                s->privatekey_available = (keytype == SSH_KEYTYPE_SSH1);
+                if (!s->privatekey_available)
+                    logeventf(ssh, "Key file contains public key only");
+               s->privatekey_encrypted = rsakey_encrypted(s->keyfile,
+                                                           NULL);
            } else {
                char *msgbuf;
-               logeventf(ssh, "Unable to load private key (%s)", error);
-               msgbuf = dupprintf("Unable to load private key file "
+               logeventf(ssh, "Unable to load key (%s)", error);
+               msgbuf = dupprintf("Unable to load key file "
                                   "\"%.150s\" (%s)\r\n",
                                   filename_to_str(s->keyfile),
                                   error);
@@ -4346,7 +4488,8 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
            if (s->authed)
                break;
        }
-       if (s->publickey_blob && !s->tried_publickey) {
+       if (s->publickey_blob && s->privatekey_available &&
+            !s->tried_publickey) {
            /*
             * Try public key authentication with the specified
             * key file.
@@ -4365,7 +4508,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
                 */
                char *passphrase = NULL;    /* only written after crReturn */
                const char *error;
-               if (!s->publickey_encrypted) {
+               if (!s->privatekey_encrypted) {
                    if (flags & FLAG_VERBOSE)
                        c_write_str(ssh, "No passphrase required.\r\n");
                    passphrase = NULL;
@@ -5683,7 +5826,7 @@ int ssh_agent_forwarding_permitted(Ssh ssh)
     return conf_get_int(ssh->conf, CONF_agentfwd) && agent_exists();
 }
 
-static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen,
+static void do_ssh1_connection(Ssh ssh, const unsigned char *in, int inlen,
                               struct Packet *pktin)
 {
     crBegin(ssh->do_ssh1_connection_crstate);
@@ -5856,7 +5999,7 @@ static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen,
        ssh_special(ssh, TS_EOF);
 
     if (ssh->ldisc)
-       ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */
+       ldisc_echoedit_update(ssh->ldisc);  /* cause ldisc to notice changes */
     ssh->send_ok = 1;
     ssh->channels = newtree234(ssh_channelcmp);
     while (1) {
@@ -5938,10 +6081,10 @@ static void ssh1_protocol_setup(Ssh ssh)
     ssh->packet_dispatch[SSH1_MSG_DEBUG] = ssh1_msg_debug;
 }
 
-static void ssh1_protocol(Ssh ssh, void *vin, int inlen,
+static void ssh1_protocol(Ssh ssh, const void *vin, int inlen,
                          struct Packet *pktin)
 {
-    unsigned char *in=(unsigned char*)vin;
+    const unsigned char *in = (const unsigned char *)vin;
     if (ssh->state == SSH_STATE_CLOSED)
        return;
 
@@ -5961,69 +6104,77 @@ static void ssh1_protocol(Ssh ssh, void *vin, int inlen,
 }
 
 /*
- * Utility routine for decoding comma-separated strings in KEXINIT.
+ * Utility routines for decoding comma-separated strings in KEXINIT.
  */
-static int in_commasep_string(char *needle, char *haystack, int haylen)
+static int first_in_commasep_string(char const *needle, char const *haystack,
+                                   int haylen)
 {
     int needlen;
     if (!needle || !haystack)         /* protect against null pointers */
        return 0;
     needlen = strlen(needle);
-    while (1) {
-       /*
-        * Is it at the start of the string?
-        */
-       if (haylen >= needlen &&       /* haystack is long enough */
-           !memcmp(needle, haystack, needlen) &&       /* initial match */
-           (haylen == needlen || haystack[needlen] == ',')
-           /* either , or EOS follows */
-           )
-           return 1;
-       /*
-        * If not, search for the next comma and resume after that.
-        * If no comma found, terminate.
-        */
-       while (haylen > 0 && *haystack != ',')
-           haylen--, haystack++;
-       if (haylen == 0)
-           return 0;
-       haylen--, haystack++;          /* skip over comma itself */
-    }
+
+    if (haylen >= needlen &&       /* haystack is long enough */
+       !memcmp(needle, haystack, needlen) &&   /* initial match */
+       (haylen == needlen || haystack[needlen] == ',')
+       /* either , or EOS follows */
+       )
+       return 1;
+    return 0;
 }
 
-/*
- * Similar routine for checking whether we have the first string in a list.
- */
-static int first_in_commasep_string(char *needle, char *haystack, int haylen)
+static int in_commasep_string(char const *needle, char const *haystack,
+                             int haylen)
 {
-    int needlen;
+    char *p;
+
     if (!needle || !haystack)         /* protect against null pointers */
        return 0;
-    needlen = strlen(needle);
     /*
      * Is it at the start of the string?
      */
-    if (haylen >= needlen &&       /* haystack is long enough */
-       !memcmp(needle, haystack, needlen) &&   /* initial match */
-       (haylen == needlen || haystack[needlen] == ',')
-       /* either , or EOS follows */
-       )
+    if (first_in_commasep_string(needle, haystack, haylen))
        return 1;
-    return 0;
+    /*
+     * If not, search for the next comma and resume after that.
+     * If no comma found, terminate.
+     */
+    p = memchr(haystack, ',', haylen);
+    if (!p) return 0;
+    /* + 1 to skip over comma */
+    return in_commasep_string(needle, p + 1, haylen - (p + 1 - haystack));
+}
+
+/*
+ * Add a value to the comma-separated string at the end of the packet.
+ */
+static void ssh2_pkt_addstring_commasep(struct Packet *pkt, const char *data)
+{
+    if (pkt->length - pkt->savedpos > 0)
+       ssh_pkt_addstring_str(pkt, ",");
+    ssh_pkt_addstring_str(pkt, data);
 }
 
 
 /*
- * SSH-2 key creation method.
- * (Currently assumes 2 lots of any hash are sufficient to generate
- * keys/IVs for any cipher/MAC. SSH2_MKKEY_ITERS documents this assumption.)
+ * SSH-2 key derivation (RFC 4253 section 7.2).
  */
-#define SSH2_MKKEY_ITERS (2)
-static void ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H, char chr,
-                      unsigned char *keyspace)
+static unsigned char *ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H,
+                                 char chr, int keylen)
 {
     const struct ssh_hash *h = ssh->kex->hash;
-    void *s;
+    int keylen_padded;
+    unsigned char *key;
+    void *s, *s2;
+
+    if (keylen == 0)
+        return NULL;
+
+    /* Round up to the next multiple of hash length. */
+    keylen_padded = ((keylen + h->hlen - 1) / h->hlen) * h->hlen;
+
+    key = snewn(keylen_padded, unsigned char);
+
     /* First hlen bytes. */
     s = h->init();
     if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY))
@@ -6031,23 +6182,99 @@ static void ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H, char chr,
     h->bytes(s, H, h->hlen);
     h->bytes(s, &chr, 1);
     h->bytes(s, ssh->v2_session_id, ssh->v2_session_id_len);
-    h->final(s, keyspace);
-    /* Next hlen bytes. */
-    s = h->init();
-    if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY))
-       hash_mpint(h, s, K);
-    h->bytes(s, H, h->hlen);
-    h->bytes(s, keyspace, h->hlen);
-    h->final(s, keyspace + h->hlen);
+    h->final(s, key);
+
+    /* Subsequent blocks of hlen bytes. */
+    if (keylen_padded > h->hlen) {
+        int offset;
+
+        s = h->init();
+        if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY))
+            hash_mpint(h, s, K);
+        h->bytes(s, H, h->hlen);
+
+        for (offset = h->hlen; offset < keylen_padded; offset += h->hlen) {
+            h->bytes(s, key + offset - h->hlen, h->hlen);
+            s2 = h->copy(s);
+            h->final(s2, key + offset);
+        }
+
+        h->free(s);
+    }
+
+    /* Now clear any extra bytes of key material beyond the length
+     * we're officially returning, because the caller won't know to
+     * smemclr those. */
+    if (keylen_padded > keylen)
+        smemclr(key + keylen, keylen_padded - keylen);
+
+    return key;
+}
+
+/*
+ * Structure for constructing KEXINIT algorithm lists.
+ */
+#define MAXKEXLIST 16
+struct kexinit_algorithm {
+    const char *name;
+    union {
+       struct {
+           const struct ssh_kex *kex;
+           int warn;
+       } kex;
+       const struct ssh_signkey *hostkey;
+       struct {
+           const struct ssh2_cipher *cipher;
+           int warn;
+       } cipher;
+       struct {
+           const struct ssh_mac *mac;
+           int etm;
+       } mac;
+       const struct ssh_compress *comp;
+    } u;
+};
+
+/*
+ * Find a slot in a KEXINIT algorithm list to use for a new algorithm.
+ * If the algorithm is already in the list, return a pointer to its
+ * entry, otherwise return an entry from the end of the list.
+ * This assumes that every time a particular name is passed in, it
+ * comes from the same string constant.  If this isn't true, this
+ * function may need to be rewritten to use strcmp() instead.
+ */
+static struct kexinit_algorithm *ssh2_kexinit_addalg(struct kexinit_algorithm
+                                                    *list, const char *name)
+{
+    int i;
+
+    for (i = 0; i < MAXKEXLIST; i++)
+       if (list[i].name == NULL || list[i].name == name) {
+           list[i].name = name;
+           return &list[i];
+       }
+    assert(!"No space in KEXINIT list");
+    return NULL;
 }
 
 /*
  * Handle the SSH-2 transport layer.
  */
-static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
+static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
                             struct Packet *pktin)
 {
-    unsigned char *in = (unsigned char *)vin;
+    const unsigned char *in = (const unsigned char *)vin;
+    enum kexlist {
+       KEXLIST_KEX, KEXLIST_HOSTKEY, KEXLIST_CSCIPHER, KEXLIST_SCCIPHER,
+       KEXLIST_CSMAC, KEXLIST_SCMAC, KEXLIST_CSCOMP, KEXLIST_SCCOMP,
+       NKEXLIST
+    };
+    const char * kexlist_descr[NKEXLIST] = {
+       "key exchange algorithm", "host key algorithm",
+       "client-to-server cipher", "server-to-client cipher",
+       "client-to-server MAC", "server-to-client MAC",
+       "client-to-server compression method",
+       "server-to-client compression method" };
     struct do_ssh2_transport_state {
        int crLine;
        int nbits, pbits, warn_kex, warn_cscipher, warn_sccipher;
@@ -6061,12 +6288,14 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
        const struct ssh2_cipher *sccipher_tobe;
        const struct ssh_mac *csmac_tobe;
        const struct ssh_mac *scmac_tobe;
+        int csmac_etm_tobe, scmac_etm_tobe;
        const struct ssh_compress *cscomp_tobe;
        const struct ssh_compress *sccomp_tobe;
        char *hostkeydata, *sigdata, *rsakeydata, *keystr, *fingerprint;
        int hostkeylen, siglen, rsakeylen;
        void *hkey;                    /* actual host key */
        void *rsakey;                  /* for RSA kex */
+        void *eckey;                   /* for ECDH kex */
        unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN];
        int n_preferred_kex;
        const struct ssh_kexes *preferred_kex[KEX_MAX];
@@ -6080,6 +6309,7 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
         int dlgret;
        int guessok;
        int ignorepkt;
+       struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST];
     };
     crState(do_ssh2_transport_state);
 
@@ -6106,7 +6336,8 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
   begin_key_exchange:
     ssh->pkt_kctx = SSH2_PKTCTX_NOKEX;
     {
-       int i, j, k, commalist_started;
+       int i, j, k, warn;
+       struct kexinit_algorithm *alg;
 
        /*
         * Set up the preferred key exchange. (NULL => warn below here)
@@ -6130,6 +6361,10 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                s->preferred_kex[s->n_preferred_kex++] =
                    &ssh_rsa_kex;
                break;
+              case KEX_ECDH:
+                s->preferred_kex[s->n_preferred_kex++] =
+                    &ssh_ecdh_kex;
+                break;
              case KEX_WARN:
                /* Flag for later. Don't bother if it's the last in
                 * the list. */
@@ -6163,6 +6398,9 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
              case CIPHER_ARCFOUR:
                s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_arcfour;
                break;
+              case CIPHER_CHACHA20:
+                s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_ccp;
+                break;
              case CIPHER_WARN:
                /* Flag for later. Don't bother if it's the last in
                 * the list. */
@@ -6192,37 +6430,41 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
         */
        ssh->kex_in_progress = TRUE;
 
-       /*
-        * Construct and send our key exchange packet.
-        */
-       s->pktout = ssh2_pkt_init(SSH2_MSG_KEXINIT);
-       for (i = 0; i < 16; i++)
-           ssh2_pkt_addbyte(s->pktout, (unsigned char) random_byte());
+       for (i = 0; i < NKEXLIST; i++)
+           for (j = 0; j < MAXKEXLIST; j++)
+               s->kexlists[i][j].name = NULL;
        /* List key exchange algorithms. */
-       ssh2_pkt_addstring_start(s->pktout);
-       commalist_started = 0;
+       warn = FALSE;
        for (i = 0; i < s->n_preferred_kex; i++) {
            const struct ssh_kexes *k = s->preferred_kex[i];
-           if (!k) continue;          /* warning flag */
-           for (j = 0; j < k->nkexes; j++) {
-               if (commalist_started)
-                   ssh2_pkt_addstring_str(s->pktout, ",");
-               ssh2_pkt_addstring_str(s->pktout, k->list[j]->name);
-               commalist_started = 1;
+           if (!k) warn = TRUE;
+           else for (j = 0; j < k->nkexes; j++) {
+               alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_KEX],
+                                         k->list[j]->name);
+               alg->u.kex.kex = k->list[j];
+               alg->u.kex.warn = warn;
            }
        }
        /* List server host key algorithms. */
         if (!s->got_session_id) {
             /*
              * In the first key exchange, we list all the algorithms
-             * we're prepared to cope with.
+             * we're prepared to cope with, but prefer those algorithms
+            * for which we have a host key for this host.
              */
-            ssh2_pkt_addstring_start(s->pktout);
             for (i = 0; i < lenof(hostkey_algs); i++) {
-                ssh2_pkt_addstring_str(s->pktout, hostkey_algs[i]->name);
-                if (i < lenof(hostkey_algs) - 1)
-                    ssh2_pkt_addstring_str(s->pktout, ",");
-            }
+               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];
+           }
         } else {
             /*
              * In subsequent key exchanges, we list only the kex
@@ -6232,60 +6474,90 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
              * reverification.
              */
             assert(ssh->kex);
-            ssh2_pkt_addstring(s->pktout, ssh->hostkey->name);
+           alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
+                                     ssh->hostkey->name);
+           alg->u.hostkey = ssh->hostkey;
         }
        /* List encryption algorithms (client->server then server->client). */
-       for (k = 0; k < 2; k++) {
-           ssh2_pkt_addstring_start(s->pktout);
-           commalist_started = 0;
+       for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) {
+           warn = FALSE;
+#ifdef FUZZING
+           alg = ssh2_kexinit_addalg(s->kexlists[k], "none");
+           alg->u.cipher.cipher = NULL;
+           alg->u.cipher.warn = warn;
+#endif /* FUZZING */
            for (i = 0; i < s->n_preferred_ciphers; i++) {
                const struct ssh2_ciphers *c = s->preferred_ciphers[i];
-               if (!c) continue;              /* warning flag */
-               for (j = 0; j < c->nciphers; j++) {
-                   if (commalist_started)
-                       ssh2_pkt_addstring_str(s->pktout, ",");
-                   ssh2_pkt_addstring_str(s->pktout, c->list[j]->name);
-                   commalist_started = 1;
+               if (!c) warn = TRUE;
+               else for (j = 0; j < c->nciphers; j++) {
+                   alg = ssh2_kexinit_addalg(s->kexlists[k],
+                                             c->list[j]->name);
+                   alg->u.cipher.cipher = c->list[j];
+                   alg->u.cipher.warn = warn;
                }
            }
        }
        /* List MAC algorithms (client->server then server->client). */
-       for (j = 0; j < 2; j++) {
-           ssh2_pkt_addstring_start(s->pktout);
+       for (j = KEXLIST_CSMAC; j <= KEXLIST_SCMAC; j++) {
+#ifdef FUZZING
+           alg = ssh2_kexinit_addalg(s->kexlists[j], "none");
+           alg->u.mac.mac = NULL;
+           alg->u.mac.etm = FALSE;
+#endif /* FUZZING */
            for (i = 0; i < s->nmacs; i++) {
-               ssh2_pkt_addstring_str(s->pktout, s->maclist[i]->name);
-               if (i < s->nmacs - 1)
-                   ssh2_pkt_addstring_str(s->pktout, ",");
-           }
+               alg = ssh2_kexinit_addalg(s->kexlists[j], s->maclist[i]->name);
+               alg->u.mac.mac = s->maclist[i];
+               alg->u.mac.etm = FALSE;
+            }
+           for (i = 0; i < s->nmacs; i++)
+                /* For each MAC, there may also be an ETM version,
+                 * which we list second. */
+                if (s->maclist[i]->etm_name) {
+                   alg = ssh2_kexinit_addalg(s->kexlists[j],
+                                             s->maclist[i]->etm_name);
+                   alg->u.mac.mac = s->maclist[i];
+                   alg->u.mac.etm = TRUE;
+               }
        }
        /* List client->server compression algorithms,
         * then server->client compression algorithms. (We use the
         * same set twice.) */
-       for (j = 0; j < 2; j++) {
-           ssh2_pkt_addstring_start(s->pktout);
+       for (j = KEXLIST_CSCOMP; j <= KEXLIST_SCCOMP; j++) {
            assert(lenof(compressions) > 1);
            /* Prefer non-delayed versions */
-           ssh2_pkt_addstring_str(s->pktout, s->preferred_comp->name);
+           alg = ssh2_kexinit_addalg(s->kexlists[j], s->preferred_comp->name);
+           alg->u.comp = s->preferred_comp;
            /* We don't even list delayed versions of algorithms until
             * they're allowed to be used, to avoid a race. See the end of
             * this function. */
            if (s->userauth_succeeded && s->preferred_comp->delayed_name) {
-               ssh2_pkt_addstring_str(s->pktout, ",");
-               ssh2_pkt_addstring_str(s->pktout,
-                                      s->preferred_comp->delayed_name);
+               alg = ssh2_kexinit_addalg(s->kexlists[j],
+                                         s->preferred_comp->delayed_name);
+               alg->u.comp = s->preferred_comp;
            }
            for (i = 0; i < lenof(compressions); i++) {
                const struct ssh_compress *c = compressions[i];
-               if (c != s->preferred_comp) {
-                   ssh2_pkt_addstring_str(s->pktout, ",");
-                   ssh2_pkt_addstring_str(s->pktout, c->name);
-                   if (s->userauth_succeeded && c->delayed_name) {
-                       ssh2_pkt_addstring_str(s->pktout, ",");
-                       ssh2_pkt_addstring_str(s->pktout, c->delayed_name);
-                   }
+               alg = ssh2_kexinit_addalg(s->kexlists[j], c->name);
+               alg->u.comp = c;
+               if (s->userauth_succeeded && c->delayed_name) {
+                   alg = ssh2_kexinit_addalg(s->kexlists[j], c->delayed_name);
+                   alg->u.comp = c;
                }
            }
        }
+       /*
+        * Construct and send our key exchange packet.
+        */
+       s->pktout = ssh2_pkt_init(SSH2_MSG_KEXINIT);
+       for (i = 0; i < 16; i++)
+           ssh2_pkt_addbyte(s->pktout, (unsigned char) random_byte());
+       for (i = 0; i < NKEXLIST; i++) {
+           ssh2_pkt_addstring_start(s->pktout);
+           for (j = 0; j < MAXKEXLIST; j++) {
+               if (s->kexlists[i][j].name == NULL) break;
+               ssh2_pkt_addstring_commasep(s->pktout, s->kexlists[i][j].name);
+           }
+       }
        /* List client->server languages. Empty list. */
        ssh2_pkt_addstring_start(s->pktout);
        /* List server->client languages. Empty list. */
@@ -6310,7 +6582,7 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
      * to.
      */
     {
-       char *str, *preferred;
+       char *str;
        int i, j, len;
 
        if (pktin->type != SSH2_MSG_KEXINIT) {
@@ -6328,173 +6600,77 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
        s->warn_kex = s->warn_cscipher = s->warn_sccipher = FALSE;
 
        pktin->savedpos += 16;          /* skip garbage cookie */
-       ssh_pkt_getstring(pktin, &str, &len);    /* key exchange algorithms */
-        if (!str) {
-            bombout(("KEXINIT packet was incomplete"));
-            crStopV;
-        }
 
-       preferred = NULL;
-       for (i = 0; i < s->n_preferred_kex; i++) {
-           const struct ssh_kexes *k = s->preferred_kex[i];
-           if (!k) {
-               s->warn_kex = TRUE;
-           } else {
-               for (j = 0; j < k->nkexes; j++) {
-                   if (!preferred) preferred = k->list[j]->name;
-                   if (in_commasep_string(k->list[j]->name, str, len)) {
-                       ssh->kex = k->list[j];
-                       break;
-                   }
-               }
-           }
-           if (ssh->kex)
-               break;
-       }
-       if (!ssh->kex) {
-            bombout(("Couldn't agree a key exchange algorithm"
-                     " (available: %.*s)", len, str));
-           crStopV;
-       }
-       /*
-        * Note that the server's guess is considered wrong if it doesn't match
-        * the first algorithm in our list, even if it's still the algorithm
-        * we end up using.
-        */
-       s->guessok = first_in_commasep_string(preferred, str, len);
-       ssh_pkt_getstring(pktin, &str, &len);    /* host key algorithms */
-        if (!str) {
-            bombout(("KEXINIT packet was incomplete"));
-            crStopV;
-        }
-       for (i = 0; i < lenof(hostkey_algs); i++) {
-           if (in_commasep_string(hostkey_algs[i]->name, str, len)) {
-               ssh->hostkey = hostkey_algs[i];
-               break;
+       s->guessok = FALSE;
+       for (i = 0; i < NKEXLIST; i++) {
+           ssh_pkt_getstring(pktin, &str, &len);
+           if (!str) {
+               bombout(("KEXINIT packet was incomplete"));
+               crStopV;
            }
-       }
-       if (!ssh->hostkey) {
-            bombout(("Couldn't agree a host key algorithm"
-                     " (available: %.*s)", len, str));
-           crStopV;
-       }
 
-       s->guessok = s->guessok &&
-           first_in_commasep_string(hostkey_algs[0]->name, str, len);
-       ssh_pkt_getstring(pktin, &str, &len);    /* client->server cipher */
-        if (!str) {
-            bombout(("KEXINIT packet was incomplete"));
-            crStopV;
-        }
-       for (i = 0; i < s->n_preferred_ciphers; i++) {
-           const struct ssh2_ciphers *c = s->preferred_ciphers[i];
-           if (!c) {
-               s->warn_cscipher = TRUE;
-           } else {
-               for (j = 0; j < c->nciphers; j++) {
-                   if (in_commasep_string(c->list[j]->name, str, len)) {
-                       s->cscipher_tobe = c->list[j];
-                       break;
-                   }
-               }
-           }
-           if (s->cscipher_tobe)
-               break;
-       }
-       if (!s->cscipher_tobe) {
-            bombout(("Couldn't agree a client-to-server cipher"
-                     " (available: %.*s)", len, str));
-           crStopV;
-       }
+            /* If we've already selected a cipher which requires a
+             * particular MAC, then just select that, and don't even
+             * bother looking through the server's KEXINIT string for
+             * MACs. */
+            if (i == KEXLIST_CSMAC && s->cscipher_tobe &&
+                s->cscipher_tobe->required_mac) {
+                s->csmac_tobe = s->cscipher_tobe->required_mac;
+                s->csmac_etm_tobe = !!(s->csmac_tobe->etm_name);
+                goto matched;
+            }
+            if (i == KEXLIST_SCMAC && s->sccipher_tobe &&
+                s->sccipher_tobe->required_mac) {
+                s->scmac_tobe = s->sccipher_tobe->required_mac;
+                s->scmac_etm_tobe = !!(s->scmac_tobe->etm_name);
+                goto matched;
+            }
 
-       ssh_pkt_getstring(pktin, &str, &len);    /* server->client cipher */
-        if (!str) {
-            bombout(("KEXINIT packet was incomplete"));
-            crStopV;
-        }
-       for (i = 0; i < s->n_preferred_ciphers; i++) {
-           const struct ssh2_ciphers *c = s->preferred_ciphers[i];
-           if (!c) {
-               s->warn_sccipher = TRUE;
-           } else {
-               for (j = 0; j < c->nciphers; j++) {
-                   if (in_commasep_string(c->list[j]->name, str, len)) {
-                       s->sccipher_tobe = c->list[j];
-                       break;
+           for (j = 0; j < MAXKEXLIST; j++) {
+               struct kexinit_algorithm *alg = &s->kexlists[i][j];
+               if (alg->name == NULL) break;
+               if (in_commasep_string(alg->name, str, len)) {
+                   /* We've found a matching algorithm. */
+                   if (i == KEXLIST_KEX || i == KEXLIST_HOSTKEY) {
+                       /* Check if we might need to ignore first kex pkt */
+                       if (j != 0 ||
+                           !first_in_commasep_string(alg->name, str, len))
+                           s->guessok = FALSE;
+                   }
+                   if (i == KEXLIST_KEX) {
+                       ssh->kex = alg->u.kex.kex;
+                       s->warn_kex = alg->u.kex.warn;
+                   } else if (i == KEXLIST_HOSTKEY) {
+                       ssh->hostkey = alg->u.hostkey;
+                   } else if (i == KEXLIST_CSCIPHER) {
+                       s->cscipher_tobe = alg->u.cipher.cipher;
+                       s->warn_cscipher = alg->u.cipher.warn;
+                   } else if (i == KEXLIST_SCCIPHER) {
+                       s->sccipher_tobe = alg->u.cipher.cipher;
+                       s->warn_sccipher = alg->u.cipher.warn;
+                   } else if (i == KEXLIST_CSMAC) {
+                       s->csmac_tobe = alg->u.mac.mac;
+                       s->csmac_etm_tobe = alg->u.mac.etm;
+                   } else if (i == KEXLIST_SCMAC) {
+                       s->scmac_tobe = alg->u.mac.mac;
+                       s->scmac_etm_tobe = alg->u.mac.etm;
+                   } else if (i == KEXLIST_CSCOMP) {
+                       s->cscomp_tobe = alg->u.comp;
+                   } else if (i == KEXLIST_SCCOMP) {
+                       s->sccomp_tobe = alg->u.comp;
                    }
+                   goto matched;
                }
+               if ((i == KEXLIST_CSCOMP || i == KEXLIST_SCCOMP) &&
+                   in_commasep_string(alg->u.comp->delayed_name, str, len))
+                   s->pending_compression = TRUE;  /* try this later */
            }
-           if (s->sccipher_tobe)
-               break;
-       }
-       if (!s->sccipher_tobe) {
-            bombout(("Couldn't agree a server-to-client cipher"
-                     " (available: %.*s)", len, str));
+           bombout(("Couldn't agree a %s ((available: %.*s)",
+                    kexlist_descr[i], len, str));
            crStopV;
+         matched:;
        }
 
-       ssh_pkt_getstring(pktin, &str, &len);    /* client->server mac */
-        if (!str) {
-            bombout(("KEXINIT packet was incomplete"));
-            crStopV;
-        }
-       for (i = 0; i < s->nmacs; i++) {
-           if (in_commasep_string(s->maclist[i]->name, str, len)) {
-               s->csmac_tobe = s->maclist[i];
-               break;
-           }
-       }
-       ssh_pkt_getstring(pktin, &str, &len);    /* server->client mac */
-        if (!str) {
-            bombout(("KEXINIT packet was incomplete"));
-            crStopV;
-        }
-       for (i = 0; i < s->nmacs; i++) {
-           if (in_commasep_string(s->maclist[i]->name, str, len)) {
-               s->scmac_tobe = s->maclist[i];
-               break;
-           }
-       }
-       ssh_pkt_getstring(pktin, &str, &len);  /* client->server compression */
-        if (!str) {
-            bombout(("KEXINIT packet was incomplete"));
-            crStopV;
-        }
-       for (i = 0; i < lenof(compressions) + 1; i++) {
-           const struct ssh_compress *c =
-               i == 0 ? s->preferred_comp : compressions[i - 1];
-           if (in_commasep_string(c->name, str, len)) {
-               s->cscomp_tobe = c;
-               break;
-           } else if (in_commasep_string(c->delayed_name, str, len)) {
-               if (s->userauth_succeeded) {
-                   s->cscomp_tobe = c;
-                   break;
-               } else {
-                   s->pending_compression = TRUE;  /* try this later */
-               }
-           }
-       }
-       ssh_pkt_getstring(pktin, &str, &len);  /* server->client compression */
-        if (!str) {
-            bombout(("KEXINIT packet was incomplete"));
-            crStopV;
-        }
-       for (i = 0; i < lenof(compressions) + 1; i++) {
-           const struct ssh_compress *c =
-               i == 0 ? s->preferred_comp : compressions[i - 1];
-           if (in_commasep_string(c->name, str, len)) {
-               s->sccomp_tobe = c;
-               break;
-           } else if (in_commasep_string(c->delayed_name, str, len)) {
-               if (s->userauth_succeeded) {
-                   s->sccomp_tobe = c;
-                   break;
-               } else {
-                   s->pending_compression = TRUE;  /* try this later */
-               }
-           }
-       }
        if (s->pending_compression) {
            logevent("Server supports delayed compression; "
                     "will try this later");
@@ -6600,8 +6776,8 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
         {
             int csbits, scbits;
 
-            csbits = s->cscipher_tobe->keylen;
-            scbits = s->sccipher_tobe->keylen;
+            csbits = s->cscipher_tobe ? s->cscipher_tobe->real_keybits : 0;
+            scbits = s->sccipher_tobe ? s->sccipher_tobe->real_keybits : 0;
             s->nbits = (csbits > scbits ? csbits : scbits);
         }
         /* The keys only have hlen-bit entropy, since they're based on
@@ -6613,7 +6789,7 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
          * If we're doing Diffie-Hellman group exchange, start by
          * requesting a group.
          */
-        if (!ssh->kex->pdata) {
+        if (dh_is_gex(ssh->kex)) {
             logevent("Doing Diffie-Hellman group exchange");
             ssh->pkt_kctx = SSH2_PKTCTX_DHGEX;
             /*
@@ -6682,7 +6858,8 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
             bombout(("unable to parse key exchange reply packet"));
             crStopV;
         }
-        s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen);
+        s->hkey = ssh->hostkey->newkey(ssh->hostkey,
+                                       s->hostkeydata, s->hostkeylen);
         s->f = ssh2_pkt_getmp(pktin);
         if (!s->f) {
             bombout(("unable to parse key exchange reply packet"));
@@ -6708,7 +6885,7 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
         set_busy_status(ssh->frontend, BUSY_NOT);
 
         hash_string(ssh->kex->hash, ssh->exhash, s->hostkeydata, s->hostkeylen);
-        if (!ssh->kex->pdata) {
+        if (dh_is_gex(ssh->kex)) {
             if (!(ssh->remote_bugs & BUG_SSH2_OLDGEX))
                 hash_uint32(ssh->kex->hash, ssh->exhash, DH_MIN_SIZE);
             hash_uint32(ssh->kex->hash, ssh->exhash, s->pbits);
@@ -6722,10 +6899,94 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
 
         dh_cleanup(ssh->kex_ctx);
         freebn(s->f);
-        if (!ssh->kex->pdata) {
+        if (dh_is_gex(ssh->kex)) {
             freebn(s->g);
             freebn(s->p);
         }
+    } else if (ssh->kex->main_type == KEXTYPE_ECDH) {
+
+        logeventf(ssh, "Doing ECDH key exchange with curve %s and hash %s",
+                  ssh_ecdhkex_curve_textname(ssh->kex),
+                  ssh->kex->hash->text_name);
+        ssh->pkt_kctx = SSH2_PKTCTX_ECDHKEX;
+
+        s->eckey = ssh_ecdhkex_newkey(ssh->kex);
+        if (!s->eckey) {
+            bombout(("Unable to generate key for ECDH"));
+            crStopV;
+        }
+
+        {
+            char *publicPoint;
+            int publicPointLength;
+            publicPoint = ssh_ecdhkex_getpublic(s->eckey, &publicPointLength);
+            if (!publicPoint) {
+                ssh_ecdhkex_freekey(s->eckey);
+                bombout(("Unable to encode public key for ECDH"));
+                crStopV;
+            }
+            s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_ECDH_INIT);
+            ssh2_pkt_addstring_start(s->pktout);
+            ssh2_pkt_addstring_data(s->pktout, publicPoint, publicPointLength);
+            sfree(publicPoint);
+        }
+
+        ssh2_pkt_send_noqueue(ssh, s->pktout);
+
+        crWaitUntilV(pktin);
+        if (pktin->type != SSH2_MSG_KEX_ECDH_REPLY) {
+            ssh_ecdhkex_freekey(s->eckey);
+            bombout(("expected ECDH reply packet from server"));
+            crStopV;
+        }
+
+        ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen);
+        if (!s->hostkeydata) {
+            bombout(("unable to parse ECDH reply packet"));
+            crStopV;
+        }
+        hash_string(ssh->kex->hash, ssh->exhash, s->hostkeydata, s->hostkeylen);
+        s->hkey = ssh->hostkey->newkey(ssh->hostkey,
+                                       s->hostkeydata, s->hostkeylen);
+
+        {
+            char *publicPoint;
+            int publicPointLength;
+            publicPoint = ssh_ecdhkex_getpublic(s->eckey, &publicPointLength);
+            if (!publicPoint) {
+                ssh_ecdhkex_freekey(s->eckey);
+                bombout(("Unable to encode public key for ECDH hash"));
+                crStopV;
+            }
+            hash_string(ssh->kex->hash, ssh->exhash,
+                        publicPoint, publicPointLength);
+            sfree(publicPoint);
+        }
+
+        {
+            char *keydata;
+            int keylen;
+            ssh_pkt_getstring(pktin, &keydata, &keylen);
+            if (!keydata) {
+                bombout(("unable to parse ECDH reply packet"));
+                crStopV;
+            }
+            hash_string(ssh->kex->hash, ssh->exhash, keydata, keylen);
+            s->K = ssh_ecdhkex_getkey(s->eckey, keydata, keylen);
+            if (!s->K) {
+                ssh_ecdhkex_freekey(s->eckey);
+                bombout(("point received in ECDH was not valid"));
+                crStopV;
+            }
+        }
+
+        ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen);
+        if (!s->sigdata) {
+            bombout(("unable to parse key exchange reply packet"));
+            crStopV;
+        }
+
+        ssh_ecdhkex_freekey(s->eckey);
     } else {
        logeventf(ssh, "Doing RSA key exchange with hash %s",
                  ssh->kex->hash->text_name);
@@ -6747,7 +7008,8 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
         }
         hash_string(ssh->kex->hash, ssh->exhash,
                    s->hostkeydata, s->hostkeylen);
-       s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen);
+       s->hkey = ssh->hostkey->newkey(ssh->hostkey,
+                                       s->hostkeydata, s->hostkeylen);
 
         {
             char *keydata;
@@ -6851,12 +7113,18 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
     dmemdump(s->exchange_hash, ssh->kex->hash->hlen);
 #endif
 
-    if (!s->hkey ||
-       !ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen,
+    if (!s->hkey) {
+       bombout(("Server's host key is invalid"));
+       crStopV;
+    }
+
+    if (!ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen,
                                 (char *)s->exchange_hash,
                                 ssh->kex->hash->hlen)) {
+#ifndef FUZZING
        bombout(("Server's host key did not match the signature supplied"));
        crStopV;
+#endif
     }
 
     s->keystr = ssh->hostkey->fmtkey(s->hkey);
@@ -6865,7 +7133,7 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
          * Authenticate remote host: verify host key. (We've already
          * checked the signature of the exchange hash.)
          */
-        s->fingerprint = ssh->hostkey->fingerprint(s->hkey);
+        s->fingerprint = ssh2_fingerprint(ssh->hostkey, s->hkey);
         logevent("Host key fingerprint is:");
         logevent(s->fingerprint);
         /* First check against manually configured host keys. */
@@ -6881,6 +7149,9 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
                                             ssh->hostkey->keytype, s->keystr,
                                             s->fingerprint,
                                             ssh_dialog_callback, ssh);
+#ifdef FUZZING
+           s->dlgret = 1;
+#endif
             if (s->dlgret < 0) {
                 do {
                     crReturnV;
@@ -6913,8 +7184,10 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
          * the one we saw before.
          */
         if (strcmp(ssh->hostkey_str, s->keystr)) {
+#ifndef FUZZING
             bombout(("Host key was different in repeat key exchange"));
             crStopV;
+#endif
         }
         sfree(s->keystr);
     }
@@ -6948,12 +7221,14 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
     if (ssh->cs_cipher_ctx)
        ssh->cscipher->free_context(ssh->cs_cipher_ctx);
     ssh->cscipher = s->cscipher_tobe;
-    ssh->cs_cipher_ctx = ssh->cscipher->make_context();
+    if (ssh->cscipher) ssh->cs_cipher_ctx = ssh->cscipher->make_context();
 
     if (ssh->cs_mac_ctx)
        ssh->csmac->free_context(ssh->cs_mac_ctx);
     ssh->csmac = s->csmac_tobe;
-    ssh->cs_mac_ctx = ssh->csmac->make_context();
+    ssh->csmac_etm = s->csmac_etm_tobe;
+    if (ssh->csmac)
+        ssh->cs_mac_ctx = ssh->csmac->make_context(ssh->cs_cipher_ctx);
 
     if (ssh->cs_comp_ctx)
        ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx);
@@ -6964,28 +7239,39 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
      * Set IVs on client-to-server keys. Here we use the exchange
      * hash from the _first_ key exchange.
      */
-    {
-       unsigned char keyspace[SSH2_KEX_MAX_HASH_LEN * SSH2_MKKEY_ITERS];
-       assert(sizeof(keyspace) >= ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
-       ssh2_mkkey(ssh,s->K,s->exchange_hash,'C',keyspace);
-       assert((ssh->cscipher->keylen+7) / 8 <=
-              ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
-       ssh->cscipher->setkey(ssh->cs_cipher_ctx, keyspace);
-       ssh2_mkkey(ssh,s->K,s->exchange_hash,'A',keyspace);
-       assert(ssh->cscipher->blksize <=
-              ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
-       ssh->cscipher->setiv(ssh->cs_cipher_ctx, keyspace);
-       ssh2_mkkey(ssh,s->K,s->exchange_hash,'E',keyspace);
-       assert(ssh->csmac->len <=
-              ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
-       ssh->csmac->setkey(ssh->cs_mac_ctx, keyspace);
-       smemclr(keyspace, sizeof(keyspace));
-    }
-
-    logeventf(ssh, "Initialised %.200s client->server encryption",
-             ssh->cscipher->text_name);
-    logeventf(ssh, "Initialised %.200s client->server MAC algorithm",
-             ssh->csmac->text_name);
+    if (ssh->cscipher) {
+       unsigned char *key;
+
+       key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'C',
+                         ssh->cscipher->padded_keybytes);
+       ssh->cscipher->setkey(ssh->cs_cipher_ctx, key);
+        smemclr(key, ssh->cscipher->padded_keybytes);
+        sfree(key);
+
+       key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'A',
+                         ssh->cscipher->blksize);
+       ssh->cscipher->setiv(ssh->cs_cipher_ctx, key);
+        smemclr(key, ssh->cscipher->blksize);
+        sfree(key);
+    }
+    if (ssh->csmac) {
+       unsigned char *key;
+
+       key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'E',
+                         ssh->csmac->keylen);
+       ssh->csmac->setkey(ssh->cs_mac_ctx, key);
+        smemclr(key, ssh->csmac->keylen);
+        sfree(key);
+    }
+
+    if (ssh->cscipher)
+       logeventf(ssh, "Initialised %.200s client->server encryption",
+                 ssh->cscipher->text_name);
+    if (ssh->csmac)
+       logeventf(ssh, "Initialised %.200s client->server MAC algorithm%s%s",
+                 ssh->csmac->text_name,
+                 ssh->csmac_etm ? " (in ETM mode)" : "",
+                 ssh->cscipher->required_mac ? " (required by cipher)" : "");
     if (ssh->cscomp->text_name)
        logeventf(ssh, "Initialised %s compression",
                  ssh->cscomp->text_name);
@@ -7013,13 +7299,18 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
      */
     if (ssh->sc_cipher_ctx)
        ssh->sccipher->free_context(ssh->sc_cipher_ctx);
-    ssh->sccipher = s->sccipher_tobe;
-    ssh->sc_cipher_ctx = ssh->sccipher->make_context();
+    if (s->sccipher_tobe) {
+       ssh->sccipher = s->sccipher_tobe;
+       ssh->sc_cipher_ctx = ssh->sccipher->make_context();
+    }
 
     if (ssh->sc_mac_ctx)
        ssh->scmac->free_context(ssh->sc_mac_ctx);
-    ssh->scmac = s->scmac_tobe;
-    ssh->sc_mac_ctx = ssh->scmac->make_context();
+    if (s->scmac_tobe) {
+       ssh->scmac = s->scmac_tobe;
+       ssh->scmac_etm = s->scmac_etm_tobe;
+       ssh->sc_mac_ctx = ssh->scmac->make_context(ssh->sc_cipher_ctx);
+    }
 
     if (ssh->sc_comp_ctx)
        ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx);
@@ -7030,27 +7321,38 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
      * Set IVs on server-to-client keys. Here we use the exchange
      * hash from the _first_ key exchange.
      */
-    {
-       unsigned char keyspace[SSH2_KEX_MAX_HASH_LEN * SSH2_MKKEY_ITERS];
-       assert(sizeof(keyspace) >= ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
-       ssh2_mkkey(ssh,s->K,s->exchange_hash,'D',keyspace);
-       assert((ssh->sccipher->keylen+7) / 8 <=
-              ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
-       ssh->sccipher->setkey(ssh->sc_cipher_ctx, keyspace);
-       ssh2_mkkey(ssh,s->K,s->exchange_hash,'B',keyspace);
-       assert(ssh->sccipher->blksize <=
-              ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
-       ssh->sccipher->setiv(ssh->sc_cipher_ctx, keyspace);
-       ssh2_mkkey(ssh,s->K,s->exchange_hash,'F',keyspace);
-       assert(ssh->scmac->len <=
-              ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
-       ssh->scmac->setkey(ssh->sc_mac_ctx, keyspace);
-       smemclr(keyspace, sizeof(keyspace));
-    }
-    logeventf(ssh, "Initialised %.200s server->client encryption",
-             ssh->sccipher->text_name);
-    logeventf(ssh, "Initialised %.200s server->client MAC algorithm",
-             ssh->scmac->text_name);
+    if (ssh->sccipher) {
+       unsigned char *key;
+
+       key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'D',
+                         ssh->sccipher->padded_keybytes);
+       ssh->sccipher->setkey(ssh->sc_cipher_ctx, key);
+        smemclr(key, ssh->sccipher->padded_keybytes);
+        sfree(key);
+
+       key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'B',
+                         ssh->sccipher->blksize);
+       ssh->sccipher->setiv(ssh->sc_cipher_ctx, key);
+        smemclr(key, ssh->sccipher->blksize);
+        sfree(key);
+    }
+    if (ssh->scmac) {
+       unsigned char *key;
+
+       key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'F',
+                         ssh->scmac->keylen);
+       ssh->scmac->setkey(ssh->sc_mac_ctx, key);
+        smemclr(key, ssh->scmac->keylen);
+        sfree(key);
+    }
+    if (ssh->sccipher)
+       logeventf(ssh, "Initialised %.200s server->client encryption",
+                 ssh->sccipher->text_name);
+    if (ssh->scmac)
+       logeventf(ssh, "Initialised %.200s server->client MAC algorithm%s%s",
+                 ssh->scmac->text_name,
+                 ssh->scmac_etm ? " (in ETM mode)" : "",
+                 ssh->sccipher->required_mac ? " (required by cipher)" : "");
     if (ssh->sccomp->text_name)
        logeventf(ssh, "Initialised %s decompression",
                  ssh->sccomp->text_name);
@@ -7170,7 +7472,7 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
 /*
  * Add data to an SSH-2 channel output buffer.
  */
-static void ssh2_add_channel_data(struct ssh_channel *c, char *buf,
+static void ssh2_add_channel_data(struct ssh_channel *c, const char *buf,
                                  int len)
 {
     bufchain_add(&c->v.v2.outbuffer, buf, len);
@@ -7277,7 +7579,8 @@ static void ssh2_channel_init(struct ssh_channel *c)
 /*
  * Construct the common parts of a CHANNEL_OPEN.
  */
-static struct Packet *ssh2_chanopen_init(struct ssh_channel *c, char *type)
+static struct Packet *ssh2_chanopen_init(struct ssh_channel *c,
+                                         const char *type)
 {
     struct Packet *pktout;
 
@@ -7324,7 +7627,8 @@ static void ssh2_queue_chanreq_handler(struct ssh_channel *c,
  * the server initiated channel closure before we saw the response)
  * and the handler should free any storage it's holding.
  */
-static struct Packet *ssh2_chanreq_init(struct ssh_channel *c, char *type,
+static struct Packet *ssh2_chanreq_init(struct ssh_channel *c,
+                                        const char *type,
                                        cchandler_fn_t handler, void *ctx)
 {
     struct Packet *pktout;
@@ -8039,7 +8343,7 @@ static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin)
                   !memcmp(type, "exit-signal", 11)) {
 
            int is_plausible = TRUE, is_int = FALSE;
-           char *fmt_sig = "", *fmt_msg = "";
+            char *fmt_sig = NULL, *fmt_msg = NULL;
            char *msg;
            int msglen = 0, core = FALSE;
            /* ICK: older versions of OpenSSH (e.g. 3.4p1)
@@ -8162,10 +8466,11 @@ static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin)
                /* ignore lang tag */
            } /* else don't attempt to parse */
            logeventf(ssh, "Server exited on signal%s%s%s",
-                     fmt_sig, core ? " (core dumped)" : "",
-                     fmt_msg);
-           if (*fmt_sig) sfree(fmt_sig);
-           if (*fmt_msg) sfree(fmt_msg);
+                     fmt_sig ? fmt_sig : "",
+                      core ? " (core dumped)" : "",
+                     fmt_msg ? fmt_msg : "");
+           sfree(fmt_sig);
+            sfree(fmt_msg);
            reply = SSH2_MSG_CHANNEL_SUCCESS;
 
        }
@@ -8237,7 +8542,7 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
     char *peeraddr;
     int peeraddrlen;
     int peerport;
-    char *error = NULL;
+    const char *error = NULL;
     struct ssh_channel *c;
     unsigned remid, winsize, pktsize;
     unsigned our_winsize_override = 0;
@@ -8650,7 +8955,7 @@ static void ssh2_response_authconn(struct ssh_channel *c, struct Packet *pktin,
         do_ssh2_authconn(c->ssh, NULL, 0, pktin);
 }
 
-static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
+static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen,
                             struct Packet *pktin)
 {
     struct do_ssh2_authconn_state {
@@ -8681,7 +8986,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        int got_username;
        void *publickey_blob;
        int publickey_bloblen;
-       int publickey_encrypted;
+       int privatekey_available, privatekey_encrypted;
        char *publickey_algorithm;
        char *publickey_comment;
        unsigned char agent_request[5], *agent_response, *agentp;
@@ -8787,10 +9092,12 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile);
        if (!filename_is_null(s->keyfile)) {
            int keytype;
-           logeventf(ssh, "Reading private key file \"%.150s\"",
+           logeventf(ssh, "Reading key file \"%.150s\"",
                      filename_to_str(s->keyfile));
            keytype = key_type(s->keyfile);
-           if (keytype == SSH_KEYTYPE_SSH2) {
+           if (keytype == SSH_KEYTYPE_SSH2 ||
+                keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 ||
+                keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {
                const char *error;
                s->publickey_blob =
                    ssh2_userkey_loadpub(s->keyfile,
@@ -8798,13 +9105,16 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                                         &s->publickey_bloblen, 
                                         &s->publickey_comment, &error);
                if (s->publickey_blob) {
-                   s->publickey_encrypted =
+                   s->privatekey_available = (keytype == SSH_KEYTYPE_SSH2);
+                    if (!s->privatekey_available)
+                        logeventf(ssh, "Key file contains public key only");
+                   s->privatekey_encrypted =
                        ssh2_userkey_encrypted(s->keyfile, NULL);
                } else {
                    char *msgbuf;
-                   logeventf(ssh, "Unable to load private key (%s)", 
+                   logeventf(ssh, "Unable to load key (%s)", 
                              error);
-                   msgbuf = dupprintf("Unable to load private key file "
+                   msgbuf = dupprintf("Unable to load key file "
                                       "\"%.150s\" (%s)\r\n",
                                       filename_to_str(s->keyfile),
                                       error);
@@ -9327,7 +9637,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                }
 
            } else if (s->can_pubkey && s->publickey_blob &&
-                      !s->tried_pubkey_config) {
+                      s->privatekey_available && !s->tried_pubkey_config) {
 
                struct ssh2_userkey *key;   /* not live over crReturn */
                char *passphrase;           /* not live over crReturn */
@@ -9378,7 +9688,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                key = NULL;
                while (!key) {
                    const char *error;  /* not live over crReturn */
-                   if (s->publickey_encrypted) {
+                   if (s->privatekey_encrypted) {
                        /*
                         * Get a passphrase from the user.
                         */
@@ -9941,7 +10251,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                    int prompt_len; /* not live over crReturn */
                    
                    {
-                       char *msg;
+                       const char *msg;
                        if (changereq_first_time)
                            msg = "Server requested password change";
                        else
@@ -10103,6 +10413,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
 
     /* Clear up various bits and pieces from authentication. */
     if (s->publickey_blob) {
+       sfree(s->publickey_algorithm);
        sfree(s->publickey_blob);
        sfree(s->publickey_comment);
     }
@@ -10334,7 +10645,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
      * Transfer data!
      */
     if (ssh->ldisc)
-       ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */
+       ldisc_echoedit_update(ssh->ldisc);  /* cause ldisc to notice changes */
     if (ssh->mainchan)
        ssh->send_ok = 1;
     while (1) {
@@ -10565,10 +10876,10 @@ static void ssh2_timer(void *ctx, unsigned long now)
     }
 }
 
-static void ssh2_protocol(Ssh ssh, void *vin, int inlen,
+static void ssh2_protocol(Ssh ssh, const void *vin, int inlen,
                          struct Packet *pktin)
 {
-    unsigned char *in = (unsigned char *)vin;
+    const unsigned char *in = (const unsigned char *)vin;
     if (ssh->state == SSH_STATE_CLOSED)
        return;
 
@@ -10588,10 +10899,10 @@ static void ssh2_protocol(Ssh ssh, void *vin, int inlen,
        do_ssh2_authconn(ssh, in, inlen, pktin);
 }
 
-static void ssh2_bare_connection_protocol(Ssh ssh, void *vin, int inlen,
+static void ssh2_bare_connection_protocol(Ssh ssh, const void *vin, int inlen,
                                           struct Packet *pktin)
 {
-    unsigned char *in = (unsigned char *)vin;
+    const unsigned char *in = (const unsigned char *)vin;
     if (ssh->state == SSH_STATE_CLOSED)
        return;
 
@@ -10612,7 +10923,8 @@ static void ssh_cache_conf_values(Ssh ssh)
  * Returns an error message, or NULL on success.
  */
 static const char *ssh_init(void *frontend_handle, void **backend_handle,
-                           Conf *conf, char *host, int port, char **realhost,
+                           Conf *conf,
+                            const char *host, int port, char **realhost,
                            int nodelay, int keepalive)
 {
     const char *p;
@@ -10690,6 +11002,7 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
     ssh->X11_fwd_enabled = FALSE;
     ssh->connshare = NULL;
     ssh->attempting_connshare = FALSE;
+    ssh->session_started = FALSE;
 
     *backend_handle = ssh;
 
@@ -10864,7 +11177,8 @@ static void ssh_free(void *handle)
 static void ssh_reconfig(void *handle, Conf *conf)
 {
     Ssh ssh = (Ssh) handle;
-    char *rekeying = NULL, rekey_mandatory = FALSE;
+    const char *rekeying = NULL;
+    int rekey_mandatory = FALSE;
     unsigned long old_max_data_size;
     int i, rekey_time;
 
@@ -10929,14 +11243,14 @@ static void ssh_reconfig(void *handle, Conf *conf)
 /*
  * Called to send data down the SSH connection.
  */
-static int ssh_send(void *handle, char *buf, int len)
+static int ssh_send(void *handle, const char *buf, int len)
 {
     Ssh ssh = (Ssh) handle;
 
     if (ssh == NULL || ssh->s == NULL || ssh->protocol == NULL)
        return 0;
 
-    ssh->protocol(ssh, (unsigned char *)buf, len, 0);
+    ssh->protocol(ssh, (const unsigned char *)buf, len, 0);
 
     return ssh_sendbuffer(ssh);
 }
@@ -11144,7 +11458,7 @@ static void ssh_special(void *handle, Telnet_Special code)
        }
     } else {
        /* Is is a POSIX signal? */
-       char *signame = NULL;
+       const char *signame = NULL;
        if (code == TS_SIGABRT) signame = "ABRT";
        if (code == TS_SIGALRM) signame = "ALRM";
        if (code == TS_SIGFPE)  signame = "FPE";
@@ -11261,7 +11575,8 @@ static void ssh_unthrottle(void *handle, int bufsize)
     ssh_process_queued_incoming_data(ssh);
 }
 
-void ssh_send_port_open(void *channel, char *hostname, int port, char *org)
+void ssh_send_port_open(void *channel, const char *hostname, int port,
+                        const char *org)
 {
     struct ssh_channel *c = (struct ssh_channel *)channel;
     Ssh ssh = c->ssh;
@@ -11386,6 +11701,7 @@ Backend ssh_backend = {
     ssh_provide_logctx,
     ssh_unthrottle,
     ssh_cfg_info,
+    ssh_test_for_upstream,
     "ssh",
     PROT_SSH,
     22
diff --git a/ssh.h b/ssh.h
index f55136e9aa580a46a4f1d2cf61e7b4968021eb38..75aad70ba82beddc788da304ef1c4250c6d6b1de 100644 (file)
--- a/ssh.h
+++ b/ssh.h
@@ -24,6 +24,7 @@ void sshfwd_x11_is_local(struct ssh_channel *c);
 
 extern Socket ssh_connection_sharing_init(const char *host, int port,
                                           Conf *conf, Ssh ssh, void **state);
+int ssh_share_test_for_upstream(const char *host, int port, Conf *conf);
 void share_got_pkt_from_server(void *ctx, int type,
                                unsigned char *pkt, int pktlen);
 void share_activate(void *state, const char *server_verstring);
@@ -100,9 +101,80 @@ struct dss_key {
     Bignum p, q, g, y, x;
 };
 
-int makekey(unsigned char *data, int len, struct RSAKey *result,
-           unsigned char **keystr, int order);
-int makeprivate(unsigned char *data, int len, struct RSAKey *result);
+struct ec_curve;
+
+struct ec_point {
+    const struct ec_curve *curve;
+    Bignum x, y;
+    Bignum z;  /* Jacobian denominator */
+    unsigned char infinity;
+};
+
+void ec_point_free(struct ec_point *point);
+
+/* Weierstrass form curve */
+struct ec_wcurve
+{
+    Bignum a, b, n;
+    struct ec_point G;
+};
+
+/* Montgomery form curve */
+struct ec_mcurve
+{
+    Bignum a, b;
+    struct ec_point G;
+};
+
+/* Edwards form curve */
+struct ec_ecurve
+{
+    Bignum l, d;
+    struct ec_point B;
+};
+
+struct ec_curve {
+    enum { EC_WEIERSTRASS, EC_MONTGOMERY, EC_EDWARDS } type;
+    /* 'name' is the identifier of the curve when it has to appear in
+     * wire protocol encodings, as it does in e.g. the public key and
+     * signature formats for NIST curves. Curves which do not format
+     * their keys or signatures in this way just have name==NULL.
+     *
+     * 'textname' is non-NULL for all curves, and is a human-readable
+     * identification suitable for putting in log messages. */
+    const char *name, *textname;
+    unsigned int fieldBits;
+    Bignum p;
+    union {
+        struct ec_wcurve w;
+        struct ec_mcurve m;
+        struct ec_ecurve e;
+    };
+};
+
+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);
+const int ec_nist_alg_and_curve_by_bits(int bits,
+                                        const struct ec_curve **curve,
+                                        const struct ssh_signkey **alg);
+const int ec_ed_alg_and_curve_by_bits(int bits,
+                                      const struct ec_curve **curve,
+                                      const struct ssh_signkey **alg);
+
+struct ssh_signkey;
+
+struct ec_key {
+    const struct ssh_signkey *signalg;
+    struct ec_point publicKey;
+    Bignum privateKey;
+};
+
+struct ec_point *ec_public(const Bignum privateKey, const struct ec_curve *curve);
+
+int makekey(const unsigned char *data, int len, struct RSAKey *result,
+           const unsigned char **keystr, int order);
+int makeprivate(const unsigned char *data, int len, struct RSAKey *result);
 int rsaencrypt(unsigned char *data, int length, struct RSAKey *key);
 Bignum rsadecrypt(Bignum input, struct RSAKey *key);
 void rsasign(unsigned char *data, int length, struct RSAKey *key);
@@ -142,6 +214,22 @@ void ssh_rsakex_encrypt(const struct ssh_hash *h, unsigned char *in, int inlen,
                         unsigned char *out, int outlen,
                         void *key);
 
+/*
+ * SSH2 ECDH key exchange functions
+ */
+struct ssh_kex;
+const char *ssh_ecdhkex_curve_textname(const struct ssh_kex *kex);
+void *ssh_ecdhkex_newkey(const struct ssh_kex *kex);
+void ssh_ecdhkex_freekey(void *key);
+char *ssh_ecdhkex_getpublic(void *key, int *len);
+Bignum ssh_ecdhkex_getkey(void *key, char *remoteKey, int remoteKeyLen);
+
+/*
+ * Helper function for k generation in DSA, reused in ECDSA
+ */
+Bignum *dss_gen_k(const char *id_string, Bignum modulus, Bignum private_key,
+                  unsigned char *digest, int digest_len);
+
 typedef struct {
     uint32 h[4];
 } MD5_Core_State;
@@ -163,7 +251,7 @@ void MD5Update(struct MD5Context *context, unsigned char const *buf,
 void MD5Final(unsigned char digest[16], struct MD5Context *context);
 void MD5Simple(void const *p, unsigned len, unsigned char output[16]);
 
-void *hmacmd5_make_context(void);
+void *hmacmd5_make_context(void *);
 void hmacmd5_free_context(void *handle);
 void hmacmd5_key(void *handle, void const *key, int len);
 void hmacmd5_do_hmac(void *handle, unsigned char const *blk, int len,
@@ -199,11 +287,17 @@ typedef struct {
     int blkused;
     uint32 len[4];
 } SHA512_State;
+#define SHA384_State SHA512_State
 void SHA512_Init(SHA512_State * s);
 void SHA512_Bytes(SHA512_State * s, const void *p, int len);
 void SHA512_Final(SHA512_State * s, unsigned char *output);
 void SHA512_Simple(const void *p, int len, unsigned char *output);
+void SHA384_Init(SHA384_State * s);
+#define SHA384_Bytes(s, p, len) SHA512_Bytes(s, p, len)
+void SHA384_Final(SHA384_State * s, unsigned char *output);
+void SHA384_Simple(const void *p, int len, unsigned char *output);
 
+struct ssh_mac;
 struct ssh_cipher {
     void *(*make_context)(void);
     void (*free_context)(void *);
@@ -211,7 +305,7 @@ struct ssh_cipher {
     void (*encrypt) (void *, unsigned char *blk, int len);
     void (*decrypt) (void *, unsigned char *blk, int len);
     int blksize;
-    char *text_name;
+    const char *text_name;
 };
 
 struct ssh2_cipher {
@@ -221,12 +315,30 @@ struct ssh2_cipher {
     void (*setkey) (void *, unsigned char *key);/* for SSH-2 */
     void (*encrypt) (void *, unsigned char *blk, int len);
     void (*decrypt) (void *, unsigned char *blk, int len);
-    char *name;
+    /* Ignored unless SSH_CIPHER_SEPARATE_LENGTH flag set */
+    void (*encrypt_length) (void *, unsigned char *blk, int len, unsigned long seq);
+    void (*decrypt_length) (void *, unsigned char *blk, int len, unsigned long seq);
+    const char *name;
     int blksize;
-    int keylen;
+    /* real_keybits is the number of bits of entropy genuinely used by
+     * the cipher scheme; it's used for deciding how big a
+     * Diffie-Hellman group is needed to exchange a key for the
+     * cipher. */
+    int real_keybits;
+    /* padded_keybytes is the number of bytes of key data expected as
+     * input to the setkey function; it's used for deciding how much
+     * data needs to be generated from the post-kex generation of key
+     * material. In a sensible cipher which uses all its key bytes for
+     * real work, this will just be real_keybits/8, but in DES-type
+     * ciphers which ignore one bit in each byte, it'll be slightly
+     * different. */
+    int padded_keybytes;
     unsigned int flags;
 #define SSH_CIPHER_IS_CBC      1
-    char *text_name;
+#define SSH_CIPHER_SEPARATE_LENGTH      2
+    const char *text_name;
+    /* If set, this takes priority over other MAC. */
+    const struct ssh_mac *required_mac;
 };
 
 struct ssh2_ciphers {
@@ -235,7 +347,8 @@ struct ssh2_ciphers {
 };
 
 struct ssh_mac {
-    void *(*make_context)(void);
+    /* Passes in the cipher context */
+    void *(*make_context)(void *);
     void (*free_context)(void *);
     void (*setkey) (void *, unsigned char *key);
     /* whole-packet operations */
@@ -246,26 +359,26 @@ struct ssh_mac {
     void (*bytes) (void *, unsigned char const *, int);
     void (*genresult) (void *, unsigned char *);
     int (*verresult) (void *, unsigned char const *);
-    char *name;
-    int len;
-    char *text_name;
+    const char *name, *etm_name;
+    int len, keylen;
+    const char *text_name;
 };
 
 struct ssh_hash {
     void *(*init)(void); /* also allocates context */
-    void (*bytes)(void *, void *, int);
+    void *(*copy)(const void *);
+    void (*bytes)(void *, const void *, int);
     void (*final)(void *, unsigned char *); /* also frees context */
+    void (*free)(void *);
     int hlen; /* output length in bytes */
-    char *text_name;
+    const char *text_name;
 };   
 
 struct ssh_kex {
-    char *name, *groupname;
-    enum { KEXTYPE_DH, KEXTYPE_RSA } main_type;
-    /* For DH */
-    const unsigned char *pdata, *gdata; /* NULL means group exchange */
-    int plen, glen;
+    const char *name, *groupname;
+    enum { KEXTYPE_DH, KEXTYPE_RSA, KEXTYPE_ECDH } main_type;
     const struct ssh_hash *hash;
+    const void *extra;                 /* private to the kex methods */
 };
 
 struct ssh_kexes {
@@ -274,30 +387,43 @@ struct ssh_kexes {
 };
 
 struct ssh_signkey {
-    void *(*newkey) (char *data, int len);
+    void *(*newkey) (const struct ssh_signkey *self,
+                     const char *data, int len);
     void (*freekey) (void *key);
     char *(*fmtkey) (void *key);
     unsigned char *(*public_blob) (void *key, int *len);
     unsigned char *(*private_blob) (void *key, int *len);
-    void *(*createkey) (unsigned char *pub_blob, int pub_len,
-                       unsigned char *priv_blob, int priv_len);
-    void *(*openssh_createkey) (unsigned char **blob, int *len);
+    void *(*createkey) (const struct ssh_signkey *self,
+                        const unsigned char *pub_blob, int pub_len,
+                       const unsigned char *priv_blob, int priv_len);
+    void *(*openssh_createkey) (const struct ssh_signkey *self,
+                                const unsigned char **blob, int *len);
     int (*openssh_fmtkey) (void *key, unsigned char *blob, int len);
-    int (*pubkey_bits) (void *blob, int len);
-    char *(*fingerprint) (void *key);
-    int (*verifysig) (void *key, char *sig, int siglen,
-                     char *data, int datalen);
-    unsigned char *(*sign) (void *key, char *data, int datalen,
+    /* OpenSSH private key blobs, as created by openssh_fmtkey and
+     * consumed by openssh_createkey, always (at least so far...) take
+     * the form of a number of SSH-2 strings / mpints concatenated
+     * end-to-end. Because the new-style OpenSSH private key format
+     * stores those blobs without a containing string wrapper, we need
+     * to know how many strings each one consists of, so that we can
+     * skip over the right number to find the next key in the file.
+     * openssh_private_npieces gives that information. */
+    int openssh_private_npieces;
+    int (*pubkey_bits) (const struct ssh_signkey *self,
+                        const void *blob, int len);
+    int (*verifysig) (void *key, const char *sig, int siglen,
+                     const char *data, int datalen);
+    unsigned char *(*sign) (void *key, const char *data, int datalen,
                            int *siglen);
-    char *name;
-    char *keytype;                    /* for host key cache */
+    const char *name;
+    const char *keytype;               /* for host key cache */
+    const void *extra;                 /* private to the public key methods */
 };
 
 struct ssh_compress {
-    char *name;
+    const char *name;
     /* For zlib@openssh.com: if non-NULL, this name will be considered once
      * userauth has completed successfully. */
-    char *delayed_name;
+    const char *delayed_name;
     void *(*compress_init) (void);
     void (*compress_cleanup) (void *);
     int (*compress) (void *, unsigned char *block, int len,
@@ -307,7 +433,7 @@ struct ssh_compress {
     int (*decompress) (void *, unsigned char *block, int len,
                       unsigned char **outblock, int *outlen);
     int (*disable_compression) (void *);
-    char *text_name;
+    const char *text_name;
 };
 
 struct ssh2_userkey {
@@ -317,7 +443,7 @@ struct ssh2_userkey {
 };
 
 /* The maximum length of any hash algorithm used in kex. (bytes) */
-#define SSH2_KEX_MAX_HASH_LEN (32) /* SHA-256 */
+#define SSH2_KEX_MAX_HASH_LEN (64) /* SHA-512 */
 
 extern const struct ssh_cipher ssh_3des;
 extern const struct ssh_cipher ssh_des;
@@ -327,14 +453,22 @@ extern const struct ssh2_ciphers ssh2_des;
 extern const struct ssh2_ciphers ssh2_aes;
 extern const struct ssh2_ciphers ssh2_blowfish;
 extern const struct ssh2_ciphers ssh2_arcfour;
+extern const struct ssh2_ciphers ssh2_ccp;
 extern const struct ssh_hash ssh_sha1;
 extern const struct ssh_hash ssh_sha256;
+extern const struct ssh_hash ssh_sha384;
+extern const struct ssh_hash ssh_sha512;
 extern const struct ssh_kexes ssh_diffiehellman_group1;
 extern const struct ssh_kexes ssh_diffiehellman_group14;
 extern const struct ssh_kexes ssh_diffiehellman_gex;
 extern const struct ssh_kexes ssh_rsa_kex;
+extern const struct ssh_kexes ssh_ecdh_kex;
 extern const struct ssh_signkey ssh_dss;
 extern const struct ssh_signkey ssh_rsa;
+extern const struct ssh_signkey ssh_ecdsa_ed25519;
+extern const struct ssh_signkey ssh_ecdsa_nistp256;
+extern const struct ssh_signkey ssh_ecdsa_nistp384;
+extern const struct ssh_signkey ssh_ecdsa_nistp521;
 extern const struct ssh_mac ssh_hmac_md5;
 extern const struct ssh_mac ssh_hmac_sha1;
 extern const struct ssh_mac ssh_hmac_sha1_buggy;
@@ -377,7 +511,8 @@ struct PortForwarding;
 
 /* Allocate and register a new channel for port forwarding */
 void *new_sock_channel(void *handle, struct PortForwarding *pf);
-void ssh_send_port_open(void *channel, char *hostname, int port, char *org);
+void ssh_send_port_open(void *channel, const char *hostname, int port,
+                        const char *org);
 
 /* Exports from portfwd.c */
 extern char *pfd_connect(struct PortForwarding **pf, char *hostname, int port,
@@ -456,7 +591,7 @@ int x11_authcmp(void *av, void *bv); /* for putting X11FakeAuth in a tree234 */
  * authorisation protocol to use at the remote end. The local auth
  * details are looked up by calling platform_get_x11_auth.
  */
-extern struct X11Display *x11_setup_display(char *display, Conf *);
+extern struct X11Display *x11_setup_display(const char *display, Conf *);
 void x11_free_display(struct X11Display *disp);
 struct X11FakeAuth *x11_invent_fake_auth(tree234 *t, int authtype);
 void x11_free_fake_auth(struct X11FakeAuth *auth);
@@ -503,9 +638,12 @@ Bignum bignum_from_long(unsigned long n);
 void freebn(Bignum b);
 Bignum modpow(Bignum base, Bignum exp, Bignum mod);
 Bignum modmul(Bignum a, Bignum b, Bignum mod);
+Bignum modsub(const Bignum a, const Bignum b, const Bignum n);
 void decbn(Bignum n);
 extern Bignum Zero, One;
 Bignum bignum_from_bytes(const unsigned char *data, int nbytes);
+Bignum bignum_from_bytes_le(const unsigned char *data, int nbytes);
+Bignum bignum_random_in_range(const Bignum lower, const Bignum upper);
 int ssh1_read_bignum(const unsigned char *data, int len, Bignum * result);
 int bignum_bitcount(Bignum bn);
 int ssh1_bignum_length(Bignum bn);
@@ -526,13 +664,16 @@ Bignum bigmod(Bignum a, Bignum b);
 Bignum modinv(Bignum number, Bignum modulus);
 Bignum bignum_bitmask(Bignum number);
 Bignum bignum_rshift(Bignum number, int shift);
+Bignum bignum_lshift(Bignum number, int shift);
 int bignum_cmp(Bignum a, Bignum b);
 char *bignum_decimal(Bignum x);
+Bignum bignum_from_decimal(const char *decimal);
 
 #ifdef DEBUG
 void diagbn(char *prefix, Bignum md);
 #endif
 
+int dh_is_gex(const struct ssh_kex *kex);
 void *dh_setup_group(const struct ssh_kex *kex);
 void *dh_setup_gex(Bignum pval, Bignum gval);
 void dh_cleanup(void *);
@@ -541,17 +682,18 @@ const char *dh_validate_f(void *handle, Bignum f);
 Bignum dh_find_K(void *, Bignum f);
 
 int loadrsakey(const Filename *filename, struct RSAKey *key,
-              char *passphrase, const char **errorstr);
+              const char *passphrase, const char **errorstr);
 int rsakey_encrypted(const Filename *filename, char **comment);
 int rsakey_pubblob(const Filename *filename, void **blob, int *bloblen,
                   char **commentptr, const char **errorstr);
 
 int saversakey(const Filename *filename, struct RSAKey *key, char *passphrase);
 
-extern int base64_decode_atom(char *atom, unsigned char *out);
+extern int base64_decode_atom(const char *atom, unsigned char *out);
 extern int base64_lines(int datalen);
-extern void base64_encode_atom(unsigned char *data, int n, char *out);
-extern void base64_encode(FILE *fp, unsigned char *data, int datalen, int cpl);
+extern void base64_encode_atom(const unsigned char *data, int n, char *out);
+extern void base64_encode(FILE *fp, const unsigned char *data, int datalen,
+                          int cpl);
 
 /* ssh2_load_userkey can return this as an error */
 extern struct ssh2_userkey ssh2_wrong_passphrase;
@@ -559,22 +701,69 @@ extern struct ssh2_userkey ssh2_wrong_passphrase;
 
 int ssh2_userkey_encrypted(const Filename *filename, char **comment);
 struct ssh2_userkey *ssh2_load_userkey(const Filename *filename,
-                                      char *passphrase, const char **errorstr);
+                                      const char *passphrase,
+                                       const char **errorstr);
 unsigned char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm,
                                    int *pub_blob_len, char **commentptr,
                                    const char **errorstr);
 int ssh2_save_userkey(const Filename *filename, struct ssh2_userkey *key,
                      char *passphrase);
 const struct ssh_signkey *find_pubkey_alg(const char *name);
+const struct ssh_signkey *find_pubkey_alg_len(int namelen, const char *name);
 
 enum {
     SSH_KEYTYPE_UNOPENABLE,
     SSH_KEYTYPE_UNKNOWN,
     SSH_KEYTYPE_SSH1, SSH_KEYTYPE_SSH2,
-    SSH_KEYTYPE_OPENSSH, SSH_KEYTYPE_SSHCOM
+    /*
+     * The OpenSSH key types deserve a little explanation. OpenSSH has
+     * two physical formats for private key storage: an old PEM-based
+     * one largely dictated by their use of OpenSSL and full of ASN.1,
+     * and a new one using the same private key formats used over the
+     * wire for talking to ssh-agent. The old format can only support
+     * a subset of the key types, because it needs redesign for each
+     * key type, and after a while they decided to move to the new
+     * format so as not to have to do that.
+     *
+     * On input, key files are identified as either
+     * SSH_KEYTYPE_OPENSSH_PEM or SSH_KEYTYPE_OPENSSH_NEW, describing
+     * accurately which actual format the keys are stored in.
+     *
+     * On output, however, we default to following OpenSSH's own
+     * policy of writing out PEM-style keys for maximum backwards
+     * compatibility if the key type supports it, and otherwise
+     * switching to the new format. So the formats you can select for
+     * output are SSH_KEYTYPE_OPENSSH_NEW (forcing the new format for
+     * any key type), and SSH_KEYTYPE_OPENSSH_AUTO to use the oldest
+     * format supported by whatever key type you're writing out.
+     *
+     * So we have three type codes, but only two of them usable in any
+     * given circumstance. An input key file will never be identified
+     * as AUTO, only PEM or NEW; key export UIs should not be able to
+     * select PEM, only AUTO or NEW.
+     */
+    SSH_KEYTYPE_OPENSSH_AUTO,
+    SSH_KEYTYPE_OPENSSH_PEM,
+    SSH_KEYTYPE_OPENSSH_NEW,
+    SSH_KEYTYPE_SSHCOM,
+    /*
+     * Public-key-only formats, which we still want to be able to read
+     * for various purposes.
+     */
+    SSH_KEYTYPE_SSH1_PUBLIC,
+    SSH_KEYTYPE_SSH2_PUBLIC_RFC4716,
+    SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH
 };
+char *ssh1_pubkey_str(struct RSAKey *ssh1key);
+void ssh1_write_pubkey(FILE *fp, struct RSAKey *ssh1key);
+char *ssh2_pubkey_openssh_str(struct ssh2_userkey *key);
+void ssh2_write_pubkey(FILE *fp, const char *comment,
+                       const void *v_pub_blob, int pub_len,
+                       int keytype);
+char *ssh2_fingerprint_blob(const void *blob, int bloblen);
+char *ssh2_fingerprint(const struct ssh_signkey *alg, void *data);
 int key_type(const Filename *filename);
-char *key_type_to_str(int type);
+const char *key_type_to_str(int type);
 
 int import_possible(int type);
 int import_target_type(int type);
@@ -604,6 +793,10 @@ void des_encrypt_xdmauth(const unsigned char *key,
 void des_decrypt_xdmauth(const unsigned char *key,
                          unsigned char *blk, int len);
 
+void openssh_bcrypt(const char *passphrase,
+                    const unsigned char *salt, int saltbytes,
+                    int rounds, unsigned char *out, int outbytes);
+
 /*
  * For progress updates in the key generation utility.
  */
@@ -619,6 +812,10 @@ int rsa_generate(struct RSAKey *key, int bits, progfn_t pfn,
                 void *pfnparam);
 int dsa_generate(struct dss_key *key, int bits, progfn_t pfn,
                 void *pfnparam);
+int ec_generate(struct ec_key *key, int bits, progfn_t pfn,
+                void *pfnparam);
+int ec_edgenerate(struct ec_key *key, int bits, progfn_t pfn,
+                  void *pfnparam);
 Bignum primegen(int bits, int modulus, int residue, Bignum factor,
                int phase, progfn_t pfn, void *pfnparam, unsigned firstbits);
 void invent_firstbits(unsigned *one, unsigned *two);
@@ -729,6 +926,10 @@ void platform_ssh_share_cleanup(const char *name);
 #define SSH2_MSG_KEXRSA_PUBKEY                    30    /* 0x1e */
 #define SSH2_MSG_KEXRSA_SECRET                    31    /* 0x1f */
 #define SSH2_MSG_KEXRSA_DONE                      32    /* 0x20 */
+#define SSH2_MSG_KEX_ECDH_INIT                    30    /* 0x1e */
+#define SSH2_MSG_KEX_ECDH_REPLY                   31    /* 0x1f */
+#define SSH2_MSG_KEX_ECMQV_INIT                   30    /* 0x1e */
+#define SSH2_MSG_KEX_ECMQV_REPLY                  31    /* 0x1f */
 #define SSH2_MSG_USERAUTH_REQUEST                 50   /* 0x32 */
 #define SSH2_MSG_USERAUTH_FAILURE                 51   /* 0x33 */
 #define SSH2_MSG_USERAUTH_SUCCESS                 52   /* 0x34 */
index 97935b7f19ee4c8f59f5cbd1df5e665083014973..904cbdb2b6f997a3e8dc2ca77c1dcf835d95f891 100644 (file)
--- a/sshaes.c
+++ b/sshaes.c
@@ -1171,51 +1171,58 @@ void aes256_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len)
 
 static const struct ssh2_cipher ssh_aes128_ctr = {
     aes_make_context, aes_free_context, aes_iv, aes128_key,
-    aes_ssh2_sdctr, aes_ssh2_sdctr,
+    aes_ssh2_sdctr, aes_ssh2_sdctr, NULL, NULL,
     "aes128-ctr",
-    16, 128, 0, "AES-128 SDCTR"
+    16, 128, 16, 0, "AES-128 SDCTR",
+    NULL
 };
 
 static const struct ssh2_cipher ssh_aes192_ctr = {
     aes_make_context, aes_free_context, aes_iv, aes192_key,
-    aes_ssh2_sdctr, aes_ssh2_sdctr,
+    aes_ssh2_sdctr, aes_ssh2_sdctr, NULL, NULL,
     "aes192-ctr",
-    16, 192, 0, "AES-192 SDCTR"
+    16, 192, 24, 0, "AES-192 SDCTR",
+    NULL
 };
 
 static const struct ssh2_cipher ssh_aes256_ctr = {
     aes_make_context, aes_free_context, aes_iv, aes256_key,
-    aes_ssh2_sdctr, aes_ssh2_sdctr,
+    aes_ssh2_sdctr, aes_ssh2_sdctr, NULL, NULL,
     "aes256-ctr",
-    16, 256, 0, "AES-256 SDCTR"
+    16, 256, 32, 0, "AES-256 SDCTR",
+    NULL
 };
 
 static const struct ssh2_cipher ssh_aes128 = {
     aes_make_context, aes_free_context, aes_iv, aes128_key,
-    aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk,
+    aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk, NULL, NULL,
     "aes128-cbc",
-    16, 128, SSH_CIPHER_IS_CBC, "AES-128 CBC"
+    16, 128, 16, SSH_CIPHER_IS_CBC, "AES-128 CBC",
+    NULL
 };
 
 static const struct ssh2_cipher ssh_aes192 = {
     aes_make_context, aes_free_context, aes_iv, aes192_key,
-    aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk,
+    aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk, NULL, NULL,
     "aes192-cbc",
-    16, 192, SSH_CIPHER_IS_CBC, "AES-192 CBC"
+    16, 192, 24, SSH_CIPHER_IS_CBC, "AES-192 CBC",
+    NULL
 };
 
 static const struct ssh2_cipher ssh_aes256 = {
     aes_make_context, aes_free_context, aes_iv, aes256_key,
-    aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk,
+    aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk, NULL, NULL,
     "aes256-cbc",
-    16, 256, SSH_CIPHER_IS_CBC, "AES-256 CBC"
+    16, 256, 32, SSH_CIPHER_IS_CBC, "AES-256 CBC",
+    NULL
 };
 
 static const struct ssh2_cipher ssh_rijndael_lysator = {
     aes_make_context, aes_free_context, aes_iv, aes256_key,
-    aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk,
+    aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk, NULL, NULL,
     "rijndael-cbc@lysator.liu.se",
-    16, 256, SSH_CIPHER_IS_CBC, "AES-256 CBC"
+    16, 256, 32, SSH_CIPHER_IS_CBC, "AES-256 CBC",
+    NULL
 };
 
 static const struct ssh2_cipher *const aes_list[] = {
index 06f235c5be069ab7d70aaedc1be7f4d910088ed4..d16ce22540b85a8f937c7f03cb3b06bddee8e612 100644 (file)
--- a/ssharcf.c
+++ b/ssharcf.c
@@ -100,16 +100,18 @@ static void arcfour_iv(void *handle, unsigned char *key)
 
 const struct ssh2_cipher ssh_arcfour128_ssh2 = {
     arcfour_make_context, arcfour_free_context, arcfour_iv, arcfour128_key,
-    arcfour_block, arcfour_block,
+    arcfour_block, arcfour_block, NULL, NULL,
     "arcfour128",
-    1, 128, 0, "Arcfour-128"
+    1, 128, 16, 0, "Arcfour-128",
+    NULL
 };
 
 const struct ssh2_cipher ssh_arcfour256_ssh2 = {
     arcfour_make_context, arcfour_free_context, arcfour_iv, arcfour256_key,
-    arcfour_block, arcfour_block,
+    arcfour_block, arcfour_block, NULL, NULL,
     "arcfour256",
-    1, 256, 0, "Arcfour-256"
+    1, 256, 32, 0, "Arcfour-256",
+    NULL
 };
 
 static const struct ssh2_cipher *const arcfour_list[] = {
diff --git a/sshbcrypt.c b/sshbcrypt.c
new file mode 100644 (file)
index 0000000..a6c163c
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * 'bcrypt' password hash function, for PuTTY's import/export of
+ * OpenSSH encrypted private key files.
+ *
+ * This is not really the same as the original bcrypt; OpenSSH has
+ * modified it in various ways, and of course we have to do the same.
+ */
+
+#include <stddef.h>
+#include <string.h>
+#include "ssh.h"
+#include "sshblowf.h"
+
+BlowfishContext *bcrypt_setup(const unsigned char *key, int keybytes,
+                              const unsigned char *salt, int saltbytes)
+{
+    int i;
+    BlowfishContext *ctx;
+
+    ctx = blowfish_make_context();
+    blowfish_initkey(ctx);
+    blowfish_expandkey(ctx, key, keybytes, salt, saltbytes);
+
+    /* Original bcrypt replaces this fixed loop count with the
+     * variable cost. OpenSSH instead iterates the whole thing more
+     * than once if it wants extra rounds. */
+    for (i = 0; i < 64; i++) {
+        blowfish_expandkey(ctx, salt, saltbytes, NULL, 0);
+        blowfish_expandkey(ctx, key, keybytes, NULL, 0);
+    }
+
+    return ctx;
+}
+
+void bcrypt_hash(const unsigned char *key, int keybytes,
+                 const unsigned char *salt, int saltbytes,
+                 unsigned char output[32])
+{
+    BlowfishContext *ctx;
+    int i;
+
+    ctx = bcrypt_setup(key, keybytes, salt, saltbytes);
+    /* This was quite a nice starting string until it ran into
+     * little-endian Blowfish :-/ */
+    memcpy(output, "cyxOmorhcitawolBhsiftawSanyDetim", 32);
+    for (i = 0; i < 64; i++) {
+        blowfish_lsb_encrypt_ecb(output, 32, ctx);
+    }
+    blowfish_free_context(ctx);
+}
+
+void bcrypt_genblock(int counter,
+                     const unsigned char hashed_passphrase[64],
+                     const unsigned char *salt, int saltbytes,
+                     unsigned char output[32])
+{
+    SHA512_State shastate;
+    unsigned char hashed_salt[64];
+    unsigned char countbuf[4];
+
+    /* Hash the input salt with the counter value optionally suffixed
+     * to get our real 32-byte salt */
+    SHA512_Init(&shastate);
+    SHA512_Bytes(&shastate, salt, saltbytes);
+    if (counter) {
+        PUT_32BIT_MSB_FIRST(countbuf, counter);
+        SHA512_Bytes(&shastate, countbuf, 4);
+    }
+    SHA512_Final(&shastate, hashed_salt);
+
+    bcrypt_hash(hashed_passphrase, 64, hashed_salt, 64, output);
+
+    smemclr(&shastate, sizeof(shastate));
+    smemclr(&hashed_salt, sizeof(hashed_salt));
+}
+
+void openssh_bcrypt(const char *passphrase,
+                    const unsigned char *salt, int saltbytes,
+                    int rounds, unsigned char *out, int outbytes)
+{
+    unsigned char hashed_passphrase[64];
+    unsigned char block[32], outblock[32];
+    const unsigned char *thissalt;
+    int thissaltbytes;
+    int modulus, residue, i, j, round;
+
+    /* Hash the passphrase to get the bcrypt key material */
+    SHA512_Simple(passphrase, strlen(passphrase), hashed_passphrase);
+
+    /* We output key bytes in a scattered fashion to meld all output
+     * key blocks into all parts of the output. To do this, we pick a
+     * modulus, and we output the key bytes to indices of out[] in the
+     * following order: first the indices that are multiples of the
+     * modulus, then the ones congruent to 1 mod modulus, etc. Each of
+     * those passes consumes exactly one block output from
+     * bcrypt_genblock, so we must pick a modulus large enough that at
+     * most 32 bytes are used in the pass. */
+    modulus = (outbytes + 31) / 32;
+
+    for (residue = 0; residue < modulus; residue++) {
+        /* Our output block of data is the XOR of all blocks generated
+         * by bcrypt in the following loop */
+        memset(outblock, 0, sizeof(outblock));
+
+        thissalt = salt;
+        thissaltbytes = saltbytes;
+        for (round = 0; round < rounds; round++) {
+            bcrypt_genblock(round == 0 ? residue+1 : 0,
+                            hashed_passphrase,
+                            thissalt, thissaltbytes, block);
+            /* Each subsequent bcrypt call reuses the previous one's
+             * output as its salt */
+            thissalt = block;
+            thissaltbytes = 32;
+
+            for (i = 0; i < 32; i++)
+                outblock[i] ^= block[i];
+        }
+
+        for (i = residue, j = 0; i < outbytes; i += modulus, j++)
+            out[i] = outblock[j];
+    }
+    smemclr(&hashed_passphrase, sizeof(hashed_passphrase));
+}
index 8a106cbe94a89b693104fa525aea107db80f11a1..353116a6604fdc8334d774f4004999e934f9d321 100644 (file)
@@ -7,11 +7,12 @@
 #include <assert.h>
 #include <stdio.h>
 #include "ssh.h"
+#include "sshblowf.h"
 
-typedef struct {
+struct BlowfishContext {
     word32 S0[256], S1[256], S2[256], S3[256], P[18];
     word32 iv0, iv1;                  /* for CBC mode */
-} BlowfishContext;
+};
 
 /*
  * The Blowfish init data: hex digits of the fractional part of pi.
@@ -326,6 +327,24 @@ static void blowfish_lsb_encrypt_cbc(unsigned char *blk, int len,
     ctx->iv1 = iv1;
 }
 
+void blowfish_lsb_encrypt_ecb(unsigned char *blk, int len,
+                              BlowfishContext * ctx)
+{
+    word32 xL, xR, out[2];
+
+    assert((len & 7) == 0);
+
+    while (len > 0) {
+       xL = GET_32BIT_LSB_FIRST(blk);
+       xR = GET_32BIT_LSB_FIRST(blk + 4);
+       blowfish_encrypt(xL, xR, out, ctx);
+       PUT_32BIT_LSB_FIRST(blk, out[0]);
+       PUT_32BIT_LSB_FIRST(blk + 4, out[1]);
+       blk += 8;
+       len -= 8;
+    }
+}
+
 static void blowfish_lsb_decrypt_cbc(unsigned char *blk, int len,
                                     BlowfishContext * ctx)
 {
@@ -436,8 +455,25 @@ static void blowfish_msb_sdctr(unsigned char *blk, int len,
     ctx->iv1 = iv1;
 }
 
-static void blowfish_setkey(BlowfishContext * ctx,
-                           const unsigned char *key, short keybytes)
+void blowfish_initkey(BlowfishContext *ctx)
+{
+    int i;
+
+    for (i = 0; i < 18; i++) {
+       ctx->P[i] = parray[i];
+    }
+
+    for (i = 0; i < 256; i++) {
+       ctx->S0[i] = sbox0[i];
+       ctx->S1[i] = sbox1[i];
+       ctx->S2[i] = sbox2[i];
+       ctx->S3[i] = sbox3[i];
+    }
+}
+
+void blowfish_expandkey(BlowfishContext * ctx,
+                        const unsigned char *key, short keybytes,
+                        const unsigned char *salt, short saltbytes)
 {
     word32 *S0 = ctx->S0;
     word32 *S1 = ctx->S1;
@@ -445,10 +481,18 @@ static void blowfish_setkey(BlowfishContext * ctx,
     word32 *S3 = ctx->S3;
     word32 *P = ctx->P;
     word32 str[2];
-    int i;
+    int i, j;
+    int saltpos;
+    unsigned char dummysalt[1];
+
+    saltpos = 0;
+    if (!salt) {
+        saltbytes = 1;
+        salt = dummysalt;
+        dummysalt[0] = 0;
+    }
 
     for (i = 0; i < 18; i++) {
-       P[i] = parray[i];
        P[i] ^=
            ((word32) (unsigned char) (key[(i * 4 + 0) % keybytes])) << 24;
        P[i] ^=
@@ -458,48 +502,59 @@ static void blowfish_setkey(BlowfishContext * ctx,
        P[i] ^= ((word32) (unsigned char) (key[(i * 4 + 3) % keybytes]));
     }
 
-    for (i = 0; i < 256; i++) {
-       S0[i] = sbox0[i];
-       S1[i] = sbox1[i];
-       S2[i] = sbox2[i];
-       S3[i] = sbox3[i];
-    }
-
     str[0] = str[1] = 0;
 
     for (i = 0; i < 18; i += 2) {
+        for (j = 0; j < 8; j++)
+            str[j/4] ^= ((word32)salt[saltpos++ % saltbytes]) << (24-8*(j%4));
+
        blowfish_encrypt(str[0], str[1], str, ctx);
        P[i] = str[0];
        P[i + 1] = str[1];
     }
 
     for (i = 0; i < 256; i += 2) {
+        for (j = 0; j < 8; j++)
+            str[j/4] ^= ((word32)salt[saltpos++ % saltbytes]) << (24-8*(j%4));
        blowfish_encrypt(str[0], str[1], str, ctx);
        S0[i] = str[0];
        S0[i + 1] = str[1];
     }
     for (i = 0; i < 256; i += 2) {
+        for (j = 0; j < 8; j++)
+            str[j/4] ^= ((word32)salt[saltpos++ % saltbytes]) << (24-8*(j%4));
        blowfish_encrypt(str[0], str[1], str, ctx);
        S1[i] = str[0];
        S1[i + 1] = str[1];
     }
     for (i = 0; i < 256; i += 2) {
+        for (j = 0; j < 8; j++)
+            str[j/4] ^= ((word32)salt[saltpos++ % saltbytes]) << (24-8*(j%4));
        blowfish_encrypt(str[0], str[1], str, ctx);
        S2[i] = str[0];
        S2[i + 1] = str[1];
     }
     for (i = 0; i < 256; i += 2) {
+        for (j = 0; j < 8; j++)
+            str[j/4] ^= ((word32)salt[saltpos++ % saltbytes]) << (24-8*(j%4));
        blowfish_encrypt(str[0], str[1], str, ctx);
        S3[i] = str[0];
        S3[i + 1] = str[1];
     }
 }
 
+static void blowfish_setkey(BlowfishContext *ctx,
+                            const unsigned char *key, short keybytes)
+{
+    blowfish_initkey(ctx);
+    blowfish_expandkey(ctx, key, keybytes, NULL, 0);
+}
+
 /* -- Interface with PuTTY -- */
 
 #define SSH_SESSION_KEY_LENGTH 32
 
-static void *blowfish_make_context(void)
+void *blowfish_make_context(void)
 {
     return snew(BlowfishContext);
 }
@@ -510,7 +565,7 @@ static void *blowfish_ssh1_make_context(void)
     return snewn(2, BlowfishContext);
 }
 
-static void blowfish_free_context(void *handle)
+void blowfish_free_context(void *handle)
 {
     sfree(handle);
 }
@@ -586,16 +641,18 @@ const struct ssh_cipher ssh_blowfish_ssh1 = {
 
 static const struct ssh2_cipher ssh_blowfish_ssh2 = {
     blowfish_make_context, blowfish_free_context, blowfish_iv, blowfish_key,
-    blowfish_ssh2_encrypt_blk, blowfish_ssh2_decrypt_blk,
+    blowfish_ssh2_encrypt_blk, blowfish_ssh2_decrypt_blk, NULL, NULL,
     "blowfish-cbc",
-    8, 128, SSH_CIPHER_IS_CBC, "Blowfish-128 CBC"
+    8, 128, 16, SSH_CIPHER_IS_CBC, "Blowfish-128 CBC",
+    NULL
 };
 
 static const struct ssh2_cipher ssh_blowfish_ssh2_ctr = {
     blowfish_make_context, blowfish_free_context, blowfish_iv, blowfish256_key,
-    blowfish_ssh2_sdctr, blowfish_ssh2_sdctr,
+    blowfish_ssh2_sdctr, blowfish_ssh2_sdctr, NULL, NULL,
     "blowfish-ctr",
-    8, 256, 0, "Blowfish-256 SDCTR"
+    8, 256, 32, 0, "Blowfish-256 SDCTR",
+    NULL
 };
 
 static const struct ssh2_cipher *const blowfish_list[] = {
diff --git a/sshblowf.h b/sshblowf.h
new file mode 100644 (file)
index 0000000..eb6a48c
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * Header file shared between sshblowf.c and sshbcrypt.c. Exposes the
+ * internal Blowfish routines needed by bcrypt.
+ */
+
+typedef struct BlowfishContext BlowfishContext;
+
+void *blowfish_make_context(void);
+void blowfish_free_context(void *handle);
+void blowfish_initkey(BlowfishContext *ctx);
+void blowfish_expandkey(BlowfishContext *ctx,
+                        const unsigned char *key, short keybytes,
+                        const unsigned char *salt, short saltbytes);
+void blowfish_lsb_encrypt_ecb(unsigned char *blk, int len,
+                              BlowfishContext *ctx);
diff --git a/sshbn.c b/sshbn.c
index 8393721af138eefb11b560b43a7fe8c05bb06dee..724cf3011583682adf754fe609b59d7a998e5e91 100644 (file)
--- a/sshbn.c
+++ b/sshbn.c
@@ -7,6 +7,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <limits.h>
+#include <ctype.h>
 
 #include "misc.h"
 
@@ -19,6 +20,7 @@ typedef BignumInt *Bignum;
 
 BignumInt bnZero[1] = { 0 };
 BignumInt bnOne[2] = { 1, 1 };
+BignumInt bnTen[2] = { 1, 10 };
 
 /*
  * The Bignum format is an array of `BignumInt'. The first
@@ -34,7 +36,7 @@ BignumInt bnOne[2] = { 1, 1 };
  * nonzero.
  */
 
-Bignum Zero = bnZero, One = bnOne;
+Bignum Zero = bnZero, One = bnOne, Ten = bnTen;
 
 static Bignum newbn(int length)
 {
@@ -43,8 +45,6 @@ static Bignum newbn(int length)
     assert(length >= 0 && length < INT_MAX / BIGNUM_INT_BITS);
 
     b = snewn(length + 1, BignumInt);
-    if (!b)
-       abort();                       /* FIXME */
     memset(b, 0, (length + 1) * sizeof(*b));
     b[0] = length;
     return b;
@@ -87,20 +87,17 @@ Bignum bn_power_2(int n)
 
 /*
  * Internal addition. Sets c = a - b, where 'a', 'b' and 'c' are all
- * big-endian arrays of 'len' BignumInts. Returns a BignumInt carried
- * off the top.
+ * big-endian arrays of 'len' BignumInts. Returns the carry off the
+ * top.
  */
-static BignumInt internal_add(const BignumInt *a, const BignumInt *b,
-                              BignumInt *c, int len)
+static BignumCarry internal_add(const BignumInt *a, const BignumInt *b,
+                                BignumInt *c, int len)
 {
     int i;
-    BignumDblInt carry = 0;
+    BignumCarry carry = 0;
 
-    for (i = len-1; i >= 0; i--) {
-        carry += (BignumDblInt)a[i] + b[i];
-        c[i] = (BignumInt)carry;
-        carry >>= BIGNUM_INT_BITS;
-    }
+    for (i = len-1; i >= 0; i--)
+        BignumADC(c[i], carry, a[i], b[i], carry);
 
     return (BignumInt)carry;
 }
@@ -114,13 +111,10 @@ static void internal_sub(const BignumInt *a, const BignumInt *b,
                          BignumInt *c, int len)
 {
     int i;
-    BignumDblInt carry = 1;
+    BignumCarry carry = 1;
 
-    for (i = len-1; i >= 0; i--) {
-        carry += (BignumDblInt)a[i] + (b[i] ^ BIGNUM_INT_MASK);
-        c[i] = (BignumInt)carry;
-        carry >>= BIGNUM_INT_BITS;
-    }
+    for (i = len-1; i >= 0; i--)
+        BignumADC(c[i], carry, a[i], ~b[i], carry);
 }
 
 /*
@@ -184,7 +178,7 @@ static void internal_mul(const BignumInt *a, const BignumInt *b,
 
         int toplen = len/2, botlen = len - toplen; /* botlen is the bigger */
         int midlen = botlen + 1;
-        BignumDblInt carry;
+        BignumCarry carry;
 #ifdef KARA_DEBUG
         int i;
 #endif
@@ -313,9 +307,7 @@ static void internal_mul(const BignumInt *a, const BignumInt *b,
         i = 2*len - botlen - 2*midlen - 1;
         while (carry) {
             assert(i >= 0);
-            carry += c[i];
-            c[i] = (BignumInt)carry;
-            carry >>= BIGNUM_INT_BITS;
+            BignumADC(c[i], carry, c[i], 0, carry);
             i--;
         }
 #ifdef KARA_DEBUG
@@ -329,7 +321,6 @@ static void internal_mul(const BignumInt *a, const BignumInt *b,
     } else {
         int i;
         BignumInt carry;
-        BignumDblInt t;
         const BignumInt *ap, *bp;
         BignumInt *cp, *cps;
 
@@ -342,11 +333,8 @@ static void internal_mul(const BignumInt *a, const BignumInt *b,
 
         for (cps = c + 2*len, ap = a + len; ap-- > a; cps--) {
             carry = 0;
-            for (cp = cps, bp = b + len; cp--, bp-- > b ;) {
-                t = (MUL_WORD(*ap, *bp) + carry) + *cp;
-                *cp = (BignumInt) t;
-                carry = (BignumInt)(t >> BIGNUM_INT_BITS);
-            }
+            for (cp = cps, bp = b + len; cp--, bp-- > b ;)
+                BignumMULADD2(carry, *cp, *ap, *bp, *cp, carry);
             *cp = carry;
         }
     }
@@ -431,7 +419,6 @@ static void internal_mul_low(const BignumInt *a, const BignumInt *b,
     } else {
         int i;
         BignumInt carry;
-        BignumDblInt t;
         const BignumInt *ap, *bp;
         BignumInt *cp, *cps;
 
@@ -444,11 +431,8 @@ static void internal_mul_low(const BignumInt *a, const BignumInt *b,
 
         for (cps = c + len, ap = a + len; ap-- > a; cps--) {
             carry = 0;
-            for (cp = cps, bp = b + len; bp--, cp-- > c ;) {
-                t = (MUL_WORD(*ap, *bp) + carry) + *cp;
-                *cp = (BignumInt) t;
-                carry = (BignumInt)(t >> BIGNUM_INT_BITS);
-            }
+            for (cp = cps, bp = b + len; bp--, cp-- > c ;)
+                BignumMULADD2(carry, *cp, *ap, *bp, *cp, carry);
         }
     }
 }
@@ -519,120 +503,436 @@ static void internal_add_shifted(BignumInt *number,
 {
     int word = 1 + (shift / BIGNUM_INT_BITS);
     int bshift = shift % BIGNUM_INT_BITS;
-    BignumDblInt addend;
-
-    addend = (BignumDblInt)n << bshift;
-
-    while (addend) {
+    BignumInt addendh, addendl;
+    BignumCarry carry;
+
+    addendl = n << bshift;
+    addendh = (bshift == 0 ? 0 : n >> (BIGNUM_INT_BITS - bshift));
+
+    assert(word <= number[0]);
+    BignumADC(number[word], carry, number[word], addendl, 0);
+    word++;
+    if (!addendh && !carry)
+        return;
+    assert(word <= number[0]);
+    BignumADC(number[word], carry, number[word], addendh, carry);
+    word++;
+    while (carry) {
         assert(word <= number[0]);
-       addend += number[word];
-       number[word] = (BignumInt) addend & BIGNUM_INT_MASK;
-       addend >>= BIGNUM_INT_BITS;
+        BignumADC(number[word], carry, number[word], 0, carry);
        word++;
     }
 }
 
+static int bn_clz(BignumInt x)
+{
+    /*
+     * Count the leading zero bits in x. Equivalently, how far left
+     * would we need to shift x to make its top bit set?
+     *
+     * Precondition: x != 0.
+     */
+
+    /* FIXME: would be nice to put in some compiler intrinsics under
+     * ifdef here */
+    int i, ret = 0;
+    for (i = BIGNUM_INT_BITS / 2; i != 0; i >>= 1) {
+        if ((x >> (BIGNUM_INT_BITS-i)) == 0) {
+            x <<= i;
+            ret += i;
+        }
+    }
+    return ret;
+}
+
+static BignumInt reciprocal_word(BignumInt d)
+{
+    BignumInt dshort, recip, prodh, prodl;
+    int corrections;
+
+    /*
+     * Input: a BignumInt value d, with its top bit set.
+     */
+    assert(d >> (BIGNUM_INT_BITS-1) == 1);
+
+    /*
+     * Output: a value, shifted to fill a BignumInt, which is strictly
+     * less than 1/(d+1), i.e. is an *under*-estimate (but by as
+     * little as possible within the constraints) of the reciprocal of
+     * any number whose first BIGNUM_INT_BITS bits match d.
+     *
+     * Ideally we'd like to _totally_ fill BignumInt, i.e. always
+     * return a value with the top bit set. Unfortunately we can't
+     * quite guarantee that for all inputs and also return a fixed
+     * exponent. So instead we take our reciprocal to be
+     * 2^(BIGNUM_INT_BITS*2-1) / d, so that it has the top bit clear
+     * only in the exceptional case where d takes exactly the maximum
+     * value BIGNUM_INT_MASK; in that case, the top bit is clear and
+     * the next bit down is set.
+     */
+
+    /*
+     * Start by computing a half-length version of the answer, by
+     * straightforward division within a BignumInt.
+     */
+    dshort = (d >> (BIGNUM_INT_BITS/2)) + 1;
+    recip = (BIGNUM_TOP_BIT + dshort - 1) / dshort;
+    recip <<= BIGNUM_INT_BITS - BIGNUM_INT_BITS/2;
+
+    /*
+     * Newton-Raphson iteration to improve that starting reciprocal
+     * estimate: take f(x) = d - 1/x, and then the N-R formula gives
+     * x_new = x - f(x)/f'(x) = x - (d-1/x)/(1/x^2) = x(2-d*x). Or,
+     * taking our fixed-point representation into account, take f(x)
+     * to be d - K/x (where K = 2^(BIGNUM_INT_BITS*2-1) as discussed
+     * above) and then we get (2K - d*x) * x/K.
+     *
+     * Newton-Raphson doubles the number of correct bits at every
+     * iteration, and the initial division above already gave us half
+     * the output word, so it's only worth doing one iteration.
+     */
+    BignumMULADD(prodh, prodl, recip, d, recip);
+    prodl = ~prodl;
+    prodh = ~prodh;
+    {
+        BignumCarry c;
+        BignumADC(prodl, c, prodl, 1, 0);
+        prodh += c;
+    }
+    BignumMUL(prodh, prodl, prodh, recip);
+    recip = (prodh << 1) | (prodl >> (BIGNUM_INT_BITS-1));
+
+    /*
+     * Now make sure we have the best possible reciprocal estimate,
+     * before we return it. We might have been off by a handful either
+     * way - not enough to bother with any better-thought-out kind of
+     * correction loop.
+     */
+    BignumMULADD(prodh, prodl, recip, d, recip);
+    corrections = 0;
+    if (prodh >= BIGNUM_TOP_BIT) {
+        do {
+            BignumCarry c = 1;
+            BignumADC(prodl, c, prodl, ~d, c); prodh += BIGNUM_INT_MASK + c;
+            recip--;
+            corrections++;
+        } while (prodh >= ((BignumInt)1 << (BIGNUM_INT_BITS-1)));
+    } else {
+        while (1) {
+            BignumInt newprodh, newprodl;
+            BignumCarry c = 0;
+            BignumADC(newprodl, c, prodl, d, c); newprodh = prodh + c;
+            if (newprodh >= BIGNUM_TOP_BIT)
+                break;
+            prodh = newprodh;
+            prodl = newprodl;
+            recip++;
+            corrections++;
+        }
+    }
+
+    return recip;
+}
+
 /*
  * Compute a = a % m.
  * Input in first alen words of a and first mlen words of m.
  * Output in first alen words of a
  * (of which first alen-mlen words will be zero).
- * The MSW of m MUST have its high bit set.
  * Quotient is accumulated in the `quotient' array, which is a Bignum
- * rather than the internal bigendian format. Quotient parts are shifted
- * left by `qshift' before adding into quot.
+ * rather than the internal bigendian format.
+ *
+ * 'recip' must be the result of calling reciprocal_word() on the top
+ * BIGNUM_INT_BITS of the modulus (denoted m0 in comments below), with
+ * the topmost set bit normalised to the MSB of the input to
+ * reciprocal_word. 'rshift' is how far left the top nonzero word of
+ * the modulus had to be shifted to set that top bit.
  */
 static void internal_mod(BignumInt *a, int alen,
                         BignumInt *m, int mlen,
-                        BignumInt *quot, int qshift)
+                        BignumInt *quot, BignumInt recip, int rshift)
 {
-    BignumInt m0, m1, h;
     int i, k;
 
-    m0 = m[0];
-    assert(m0 >> (BIGNUM_INT_BITS-1) == 1);
-    if (mlen > 1)
-       m1 = m[1];
-    else
-       m1 = 0;
+#ifdef DIVISION_DEBUG
+    {
+        int d;
+        printf("start division, m=0x");
+        for (d = 0; d < mlen; d++)
+            printf("%0*llx", BIGNUM_INT_BITS/4, (unsigned long long)m[d]);
+        printf(", recip=%#0*llx, rshift=%d\n",
+               BIGNUM_INT_BITS/4, (unsigned long long)recip, rshift);
+    }
+#endif
 
-    for (i = 0; i <= alen - mlen; i++) {
-       BignumDblInt t;
-        BignumInt q, r, c, ai1;
+    /*
+     * Repeatedly use that reciprocal estimate to get a decent number
+     * of quotient bits, and subtract off the resulting multiple of m.
+     *
+     * Normally we expect to terminate this loop by means of finding
+     * out q=0 part way through, but one way in which we might not get
+     * that far in the first place is if the input a is actually zero,
+     * in which case we'll discard zero words from the front of a
+     * until we reach the termination condition in the for statement
+     * here.
+     */
+    for (i = 0; i <= alen - mlen ;) {
+       BignumInt product;
+        BignumInt aword, q;
+        int shift, full_bitoffset, bitoffset, wordoffset;
+
+#ifdef DIVISION_DEBUG
+        {
+            int d;
+            printf("main loop, a=0x");
+            for (d = 0; d < alen; d++)
+                printf("%0*llx", BIGNUM_INT_BITS/4, (unsigned long long)a[d]);
+            printf("\n");
+        }
+#endif
 
-       if (i == 0) {
-           h = 0;
-       } else {
-           h = a[i - 1];
-           a[i - 1] = 0;
-       }
+        if (a[i] == 0) {
+#ifdef DIVISION_DEBUG
+            printf("zero word at i=%d\n", i);
+#endif
+            i++;
+            continue;
+        }
 
-       if (i == alen - 1)
-           ai1 = 0;
-       else
-           ai1 = a[i + 1];
-
-       /* Find q = h:a[i] / m0 */
-       if (h >= m0) {
-           /*
-            * Special case.
-            * 
-            * To illustrate it, suppose a BignumInt is 8 bits, and
-            * we are dividing (say) A1:23:45:67 by A1:B2:C3. Then
-            * our initial division will be 0xA123 / 0xA1, which
-            * will give a quotient of 0x100 and a divide overflow.
-            * However, the invariants in this division algorithm
-            * are not violated, since the full number A1:23:... is
-            * _less_ than the quotient prefix A1:B2:... and so the
-            * following correction loop would have sorted it out.
-            * 
-            * In this situation we set q to be the largest
-            * quotient we _can_ stomach (0xFF, of course).
-            */
-           q = BIGNUM_INT_MASK;
-       } else {
-           /* Macro doesn't want an array subscript expression passed
-            * into it (see definition), so use a temporary. */
-           BignumInt tmplo = a[i];
-           DIVMOD_WORD(q, r, h, tmplo, m0);
-
-           /* Refine our estimate of q by looking at
-            h:a[i]:a[i+1] / m0:m1 */
-           t = MUL_WORD(m1, q);
-           if (t > ((BignumDblInt) r << BIGNUM_INT_BITS) + ai1) {
-               q--;
-               t -= m1;
-               r = (r + m0) & BIGNUM_INT_MASK;     /* overflow? */
-               if (r >= (BignumDblInt) m0 &&
-                   t > ((BignumDblInt) r << BIGNUM_INT_BITS) + ai1) q--;
-           }
-       }
+        aword = a[i];
+        shift = bn_clz(aword);
+        aword <<= shift;
+        if (shift > 0 && i+1 < alen)
+            aword |= a[i+1] >> (BIGNUM_INT_BITS - shift);
 
-       /* Subtract q * m from a[i...] */
-       c = 0;
-       for (k = mlen - 1; k >= 0; k--) {
-           t = MUL_WORD(q, m[k]);
-           t += c;
-           c = (BignumInt)(t >> BIGNUM_INT_BITS);
-           if ((BignumInt) t > a[i + k])
-               c++;
-           a[i + k] -= (BignumInt) t;
-       }
+        {
+            BignumInt unused;
+            BignumMUL(q, unused, recip, aword);
+            (void)unused;
+        }
 
-       /* Add back m in case of borrow */
-       if (c != h) {
-           t = 0;
-           for (k = mlen - 1; k >= 0; k--) {
-               t += m[k];
-               t += a[i + k];
-               a[i + k] = (BignumInt) t;
-               t = t >> BIGNUM_INT_BITS;
-           }
-           q--;
-       }
-       if (quot)
-           internal_add_shifted(quot, q, qshift + BIGNUM_INT_BITS * (alen - mlen - i));
+#ifdef DIVISION_DEBUG
+        printf("i=%d, aword=%#0*llx, shift=%d, q=%#0*llx\n",
+               i, BIGNUM_INT_BITS/4, (unsigned long long)aword,
+               shift, BIGNUM_INT_BITS/4, (unsigned long long)q);
+#endif
+
+        /*
+         * Work out the right bit and word offsets to use when
+         * subtracting q*m from a.
+         *
+         * aword was taken from a[i], which means its LSB was at bit
+         * position (alen-1-i) * BIGNUM_INT_BITS. But then we shifted
+         * it left by 'shift', so now the low bit of aword corresponds
+         * to bit position (alen-1-i) * BIGNUM_INT_BITS - shift, i.e.
+         * aword is approximately equal to a / 2^(that).
+         *
+         * m0 comes from the top word of mod, so its LSB is at bit
+         * position (mlen-1) * BIGNUM_INT_BITS - rshift, i.e. it can
+         * be considered to be m / 2^(that power). 'recip' is the
+         * reciprocal of m0, times 2^(BIGNUM_INT_BITS*2-1), i.e. it's
+         * about 2^((mlen+1) * BIGNUM_INT_BITS - rshift - 1) / m.
+         *
+         * Hence, recip * aword is approximately equal to the product
+         * of those, which simplifies to
+         *
+         * a/m * 2^((mlen+2+i-alen)*BIGNUM_INT_BITS + shift - rshift - 1)
+         *
+         * But we've also shifted recip*aword down by BIGNUM_INT_BITS
+         * to form q, so we have
+         *
+         * q ~= a/m * 2^((mlen+1+i-alen)*BIGNUM_INT_BITS + shift - rshift - 1)
+         *
+         * and hence, when we now compute q*m, it will be about
+         * a*2^(all that lot), i.e. the negation of that expression is
+         * how far left we have to shift the product q*m to make it
+         * approximately equal to a.
+         */
+        full_bitoffset = -((mlen+1+i-alen)*BIGNUM_INT_BITS + shift-rshift-1);
+#ifdef DIVISION_DEBUG
+        printf("full_bitoffset=%d\n", full_bitoffset);
+#endif
+
+        if (full_bitoffset < 0) {
+            /*
+             * If we find ourselves needing to shift q*m _right_, that
+             * means we've reached the bottom of the quotient. Clip q
+             * so that its right shift becomes zero, and if that means
+             * q becomes _actually_ zero, this loop is done.
+             */
+            if (full_bitoffset <= -BIGNUM_INT_BITS)
+                break;
+            q >>= -full_bitoffset;
+            full_bitoffset = 0;
+            if (!q)
+                break;
+#ifdef DIVISION_DEBUG
+            printf("now full_bitoffset=%d, q=%#0*llx\n",
+                   full_bitoffset, BIGNUM_INT_BITS/4, (unsigned long long)q);
+#endif
+        }
+
+        wordoffset = full_bitoffset / BIGNUM_INT_BITS;
+        bitoffset = full_bitoffset % BIGNUM_INT_BITS;
+#ifdef DIVISION_DEBUG
+        printf("wordoffset=%d, bitoffset=%d\n", wordoffset, bitoffset);
+#endif
+
+        /* wordoffset as computed above is the offset between the LSWs
+         * of m and a. But in fact m and a are stored MSW-first, so we
+         * need to adjust it to be the offset between the actual array
+         * indices, and flip the sign too. */
+        wordoffset = alen - mlen - wordoffset;
+
+        if (bitoffset == 0) {
+            BignumCarry c = 1;
+            BignumInt prev_hi_word = 0;
+            for (k = mlen - 1; wordoffset+k >= i; k--) {
+                BignumInt mword = k<0 ? 0 : m[k];
+                BignumMULADD(prev_hi_word, product, q, mword, prev_hi_word);
+#ifdef DIVISION_DEBUG
+                printf("  aligned sub: product word for m[%d] = %#0*llx\n",
+                       k, BIGNUM_INT_BITS/4,
+                       (unsigned long long)product);
+#endif
+#ifdef DIVISION_DEBUG
+                printf("  aligned sub: subtrahend for a[%d] = %#0*llx\n",
+                       wordoffset+k, BIGNUM_INT_BITS/4,
+                       (unsigned long long)product);
+#endif
+                BignumADC(a[wordoffset+k], c, a[wordoffset+k], ~product, c);
+            }
+        } else {
+            BignumInt add_word = 0;
+            BignumInt c = 1;
+            BignumInt prev_hi_word = 0;
+            for (k = mlen - 1; wordoffset+k >= i; k--) {
+                BignumInt mword = k<0 ? 0 : m[k];
+                BignumMULADD(prev_hi_word, product, q, mword, prev_hi_word);
+#ifdef DIVISION_DEBUG
+                printf("  unaligned sub: product word for m[%d] = %#0*llx\n",
+                       k, BIGNUM_INT_BITS/4,
+                       (unsigned long long)product);
+#endif
+
+                add_word |= product << bitoffset;
+
+#ifdef DIVISION_DEBUG
+                printf("  unaligned sub: subtrahend for a[%d] = %#0*llx\n",
+                       wordoffset+k,
+                       BIGNUM_INT_BITS/4, (unsigned long long)add_word);
+#endif
+                BignumADC(a[wordoffset+k], c, a[wordoffset+k], ~add_word, c);
+
+                add_word = product >> (BIGNUM_INT_BITS - bitoffset);
+            }
+        }
+
+       if (quot) {
+#ifdef DIVISION_DEBUG
+            printf("adding quotient word %#0*llx << %d\n",
+                   BIGNUM_INT_BITS/4, (unsigned long long)q, full_bitoffset);
+#endif
+           internal_add_shifted(quot, q, full_bitoffset);
+#ifdef DIVISION_DEBUG
+            {
+                int d;
+                printf("now quot=0x");
+                for (d = quot[0]; d > 0; d--)
+                    printf("%0*llx", BIGNUM_INT_BITS/4,
+                           (unsigned long long)quot[d]);
+                printf("\n");
+            }
+#endif
+        }
+    }
+
+#ifdef DIVISION_DEBUG
+    {
+        int d;
+        printf("end main loop, a=0x");
+        for (d = 0; d < alen; d++)
+            printf("%0*llx", BIGNUM_INT_BITS/4, (unsigned long long)a[d]);
+        if (quot) {
+            printf(", quot=0x");
+            for (d = quot[0]; d > 0; d--)
+                printf("%0*llx", BIGNUM_INT_BITS/4,
+                       (unsigned long long)quot[d]);
+        }
+        printf("\n");
+    }
+#endif
+
+    /*
+     * The above loop should terminate with the remaining value in a
+     * being strictly less than 2*m (if a >= 2*m then we should always
+     * have managed to get a nonzero q word), but we can't guarantee
+     * that it will be strictly less than m: consider a case where the
+     * remainder is 1, and another where the remainder is m-1. By the
+     * time a contains a value that's _about m_, you clearly can't
+     * distinguish those cases by looking at only the top word of a -
+     * you have to go all the way down to the bottom before you find
+     * out whether it's just less or just more than m.
+     *
+     * Hence, we now do a final fixup in which we subtract one last
+     * copy of m, or don't, accordingly. We should never have to
+     * subtract more than one copy of m here.
+     */
+    for (i = 0; i < alen; i++) {
+        /* Compare a with m, word by word, from the MSW down. As soon
+         * as we encounter a difference, we know whether we need the
+         * fixup. */
+        int mindex = mlen-alen+i;
+        BignumInt mword = mindex < 0 ? 0 : m[mindex];
+        if (a[i] < mword) {
+#ifdef DIVISION_DEBUG
+            printf("final fixup not needed, a < m\n");
+#endif
+            return;
+        } else if (a[i] > mword) {
+#ifdef DIVISION_DEBUG
+            printf("final fixup is needed, a > m\n");
+#endif
+            break;
+        }
+        /* If neither of those cases happened, the words are the same,
+         * so keep going and look at the next one. */
+    }
+#ifdef DIVISION_DEBUG
+    if (i == mlen) /* if we printed neither of the above diagnostics */
+        printf("final fixup is needed, a == m\n");
+#endif
+
+    /*
+     * If we got here without returning, then a >= m, so we must
+     * subtract m, and increment the quotient.
+     */
+    {
+        BignumCarry c = 1;
+        for (i = alen - 1; i >= 0; i--) {
+            int mindex = mlen-alen+i;
+            BignumInt mword = mindex < 0 ? 0 : m[mindex];
+            BignumADC(a[i], c, a[i], ~mword, c);
+        }
     }
+    if (quot)
+        internal_add_shifted(quot, 1, 0);
+
+#ifdef DIVISION_DEBUG
+    {
+        int d;
+        printf("after final fixup, a=0x");
+        for (d = 0; d < alen; d++)
+            printf("%0*llx", BIGNUM_INT_BITS/4, (unsigned long long)a[d]);
+        if (quot) {
+            printf(", quot=0x");
+            for (d = quot[0]; d > 0; d--)
+                printf("%0*llx", BIGNUM_INT_BITS/4,
+                       (unsigned long long)quot[d]);
+        }
+        printf("\n");
+    }
+#endif
 }
 
 /*
@@ -641,7 +941,8 @@ static void internal_mod(BignumInt *a, int alen,
 Bignum modpow_simple(Bignum base_in, Bignum exp, Bignum mod)
 {
     BignumInt *a, *b, *n, *m, *scratch;
-    int mshift;
+    BignumInt recip;
+    int rshift;
     int mlen, scratchlen, i, j;
     Bignum base, result;
 
@@ -664,16 +965,6 @@ Bignum modpow_simple(Bignum base_in, Bignum exp, Bignum mod)
     for (j = 0; j < mlen; j++)
        m[j] = mod[mod[0] - j];
 
-    /* Shift m left to make msb bit set */
-    for (mshift = 0; mshift < BIGNUM_INT_BITS-1; mshift++)
-       if ((m[0] << mshift) & BIGNUM_TOP_BIT)
-           break;
-    if (mshift) {
-       for (i = 0; i < mlen - 1; i++)
-           m[i] = (m[i] << mshift) | (m[i + 1] >> (BIGNUM_INT_BITS - mshift));
-       m[mlen - 1] = m[mlen - 1] << mshift;
-    }
-
     /* Allocate n of size mlen, copy base to n */
     n = snewn(mlen, BignumInt);
     i = mlen - base[0];
@@ -704,14 +995,26 @@ Bignum modpow_simple(Bignum base_in, Bignum exp, Bignum mod)
        }
     }
 
+    /* Compute reciprocal of the top full word of the modulus */
+    {
+        BignumInt m0 = m[0];
+        rshift = bn_clz(m0);
+        if (rshift) {
+            m0 <<= rshift;
+            if (mlen > 1)
+                m0 |= m[1] >> (BIGNUM_INT_BITS - rshift);
+        }
+        recip = reciprocal_word(m0);
+    }
+
     /* Main computation */
     while (i < (int)exp[0]) {
        while (j >= 0) {
            internal_mul(a + mlen, a + mlen, b, mlen, scratch);
-           internal_mod(b, mlen * 2, m, mlen, NULL, 0);
+           internal_mod(b, mlen * 2, m, mlen, NULL, recip, rshift);
            if ((exp[exp[0] - i] & ((BignumInt)1 << j)) != 0) {
                internal_mul(b + mlen, n, a, mlen, scratch);
-               internal_mod(a, mlen * 2, m, mlen, NULL, 0);
+               internal_mod(a, mlen * 2, m, mlen, NULL, recip, rshift);
            } else {
                BignumInt *t;
                t = a;
@@ -724,16 +1027,6 @@ Bignum modpow_simple(Bignum base_in, Bignum exp, Bignum mod)
        j = BIGNUM_INT_BITS-1;
     }
 
-    /* Fixup result in case the modulus was shifted */
-    if (mshift) {
-       for (i = mlen - 1; i < 2 * mlen - 1; i++)
-           a[i] = (a[i] << mshift) | (a[i + 1] >> (BIGNUM_INT_BITS - mshift));
-       a[2 * mlen - 1] = a[2 * mlen - 1] << mshift;
-       internal_mod(a, mlen * 2, m, mlen, NULL, 0);
-       for (i = 2 * mlen - 1; i >= mlen; i--)
-           a[i] = (a[i] >> mshift) | (a[i - 1] << (BIGNUM_INT_BITS - mshift));
-    }
-
     /* Copy result to buffer */
     result = newbn(mod[0]);
     for (i = 0; i < mlen; i++)
@@ -912,7 +1205,8 @@ Bignum modpow(Bignum base_in, Bignum exp, Bignum mod)
 Bignum modmul(Bignum p, Bignum q, Bignum mod)
 {
     BignumInt *a, *n, *m, *o, *scratch;
-    int mshift, scratchlen;
+    BignumInt recip;
+    int rshift, scratchlen;
     int pqlen, mlen, rlen, i, j;
     Bignum result;
 
@@ -929,16 +1223,6 @@ Bignum modmul(Bignum p, Bignum q, Bignum mod)
     for (j = 0; j < mlen; j++)
        m[j] = mod[mod[0] - j];
 
-    /* Shift m left to make msb bit set */
-    for (mshift = 0; mshift < BIGNUM_INT_BITS-1; mshift++)
-       if ((m[0] << mshift) & BIGNUM_TOP_BIT)
-           break;
-    if (mshift) {
-       for (i = 0; i < mlen - 1; i++)
-           m[i] = (m[i] << mshift) | (m[i + 1] >> (BIGNUM_INT_BITS - mshift));
-       m[mlen - 1] = m[mlen - 1] << mshift;
-    }
-
     pqlen = (p[0] > q[0] ? p[0] : q[0]);
 
     /*
@@ -971,19 +1255,21 @@ Bignum modmul(Bignum p, Bignum q, Bignum mod)
     scratchlen = mul_compute_scratch(pqlen);
     scratch = snewn(scratchlen, BignumInt);
 
+    /* Compute reciprocal of the top full word of the modulus */
+    {
+        BignumInt m0 = m[0];
+        rshift = bn_clz(m0);
+        if (rshift) {
+            m0 <<= rshift;
+            if (mlen > 1)
+                m0 |= m[1] >> (BIGNUM_INT_BITS - rshift);
+        }
+        recip = reciprocal_word(m0);
+    }
+
     /* Main computation */
     internal_mul(n, o, a, pqlen, scratch);
-    internal_mod(a, pqlen * 2, m, mlen, NULL, 0);
-
-    /* Fixup result in case the modulus was shifted */
-    if (mshift) {
-       for (i = 2 * pqlen - mlen - 1; i < 2 * pqlen - 1; i++)
-           a[i] = (a[i] << mshift) | (a[i + 1] >> (BIGNUM_INT_BITS - mshift));
-       a[2 * pqlen - 1] = a[2 * pqlen - 1] << mshift;
-       internal_mod(a, pqlen * 2, m, mlen, NULL, 0);
-       for (i = 2 * pqlen - 1; i >= 2 * pqlen - mlen; i--)
-           a[i] = (a[i] >> mshift) | (a[i - 1] << (BIGNUM_INT_BITS - mshift));
-    }
+    internal_mod(a, pqlen * 2, m, mlen, NULL, recip, rshift);
 
     /* Copy result to buffer */
     rlen = (mlen < pqlen * 2 ? mlen : pqlen * 2);
@@ -1008,6 +1294,35 @@ Bignum modmul(Bignum p, Bignum q, Bignum mod)
     return result;
 }
 
+Bignum modsub(const Bignum a, const Bignum b, const Bignum n)
+{
+    Bignum a1, b1, ret;
+
+    if (bignum_cmp(a, n) >= 0) a1 = bigmod(a, n);
+    else a1 = a;
+    if (bignum_cmp(b, n) >= 0) b1 = bigmod(b, n);
+    else b1 = b;
+
+    if (bignum_cmp(a1, b1) >= 0) /* a >= b */
+    {
+        ret = bigsub(a1, b1);
+    }
+    else
+    {
+        /* Handle going round the corner of the modulus without having
+         * negative support in Bignum */
+        Bignum tmp = bigsub(n, b1);
+        assert(tmp);
+        ret = bigadd(tmp, a1);
+        freebn(tmp);
+    }
+
+    if (a != a1) freebn(a1);
+    if (b != b1) freebn(b1);
+
+    return ret;
+}
+
 /*
  * Compute p % mod.
  * The most significant word of mod MUST be non-zero.
@@ -1018,7 +1333,8 @@ Bignum modmul(Bignum p, Bignum q, Bignum mod)
 static void bigdivmod(Bignum p, Bignum mod, Bignum result, Bignum quotient)
 {
     BignumInt *n, *m;
-    int mshift;
+    BignumInt recip;
+    int rshift;
     int plen, mlen, i, j;
 
     /*
@@ -1034,16 +1350,6 @@ static void bigdivmod(Bignum p, Bignum mod, Bignum result, Bignum quotient)
     for (j = 0; j < mlen; j++)
        m[j] = mod[mod[0] - j];
 
-    /* Shift m left to make msb bit set */
-    for (mshift = 0; mshift < BIGNUM_INT_BITS-1; mshift++)
-       if ((m[0] << mshift) & BIGNUM_TOP_BIT)
-           break;
-    if (mshift) {
-       for (i = 0; i < mlen - 1; i++)
-           m[i] = (m[i] << mshift) | (m[i + 1] >> (BIGNUM_INT_BITS - mshift));
-       m[mlen - 1] = m[mlen - 1] << mshift;
-    }
-
     plen = p[0];
     /* Ensure plen > mlen */
     if (plen <= mlen)
@@ -1056,19 +1362,21 @@ static void bigdivmod(Bignum p, Bignum mod, Bignum result, Bignum quotient)
     for (j = 1; j <= (int)p[0]; j++)
        n[plen - j] = p[j];
 
-    /* Main computation */
-    internal_mod(n, plen, m, mlen, quotient, mshift);
-
-    /* Fixup result in case the modulus was shifted */
-    if (mshift) {
-       for (i = plen - mlen - 1; i < plen - 1; i++)
-           n[i] = (n[i] << mshift) | (n[i + 1] >> (BIGNUM_INT_BITS - mshift));
-       n[plen - 1] = n[plen - 1] << mshift;
-       internal_mod(n, plen, m, mlen, quotient, 0);
-       for (i = plen - 1; i >= plen - mlen; i--)
-           n[i] = (n[i] >> mshift) | (n[i - 1] << (BIGNUM_INT_BITS - mshift));
+    /* Compute reciprocal of the top full word of the modulus */
+    {
+        BignumInt m0 = m[0];
+        rshift = bn_clz(m0);
+        if (rshift) {
+            m0 <<= rshift;
+            if (mlen > 1)
+                m0 |= m[1] >> (BIGNUM_INT_BITS - rshift);
+        }
+        recip = reciprocal_word(m0);
     }
 
+    /* Main computation */
+    internal_mod(n, plen, m, mlen, quotient, recip, rshift);
+
     /* Copy result to buffer */
     if (result) {
        for (i = 1; i <= (int)result[0]; i++) {
@@ -1113,11 +1421,89 @@ Bignum bignum_from_bytes(const unsigned char *data, int nbytes)
             (BignumInt)byte << (8*i % BIGNUM_INT_BITS);
     }
 
-    while (result[0] > 1 && result[result[0]] == 0)
-       result[0]--;
+    bn_restore_invariant(result);
     return result;
 }
 
+Bignum bignum_from_bytes_le(const unsigned char *data, int nbytes)
+{
+    Bignum result;
+    int w, i;
+
+    assert(nbytes >= 0 && nbytes < INT_MAX/8);
+
+    w = (nbytes + BIGNUM_INT_BYTES - 1) / BIGNUM_INT_BYTES; /* bytes->words */
+
+    result = newbn(w);
+    for (i = 1; i <= w; i++)
+        result[i] = 0;
+    for (i = 0; i < nbytes; ++i) {
+        unsigned char byte = *data++;
+        result[1 + i / BIGNUM_INT_BYTES] |=
+            (BignumInt)byte << (8*i % BIGNUM_INT_BITS);
+    }
+
+    bn_restore_invariant(result);
+    return result;
+}
+
+Bignum bignum_from_decimal(const char *decimal)
+{
+    Bignum result = copybn(Zero);
+
+    while (*decimal) {
+        Bignum tmp, tmp2;
+
+        if (!isdigit((unsigned char)*decimal)) {
+            freebn(result);
+            return 0;
+        }
+
+        tmp = bigmul(result, Ten);
+        tmp2 = bignum_from_long(*decimal - '0');
+        result = bigadd(tmp, tmp2);
+        freebn(tmp);
+        freebn(tmp2);
+
+        decimal++;
+    }
+
+    return result;
+}
+
+Bignum bignum_random_in_range(const Bignum lower, const Bignum upper)
+{
+    Bignum ret = NULL;
+    unsigned char *bytes;
+    int upper_len = bignum_bitcount(upper);
+    int upper_bytes = upper_len / 8;
+    int upper_bits = upper_len % 8;
+    if (upper_bits) ++upper_bytes;
+
+    bytes = snewn(upper_bytes, unsigned char);
+    do {
+        int i;
+
+        if (ret) freebn(ret);
+
+        for (i = 0; i < upper_bytes; ++i)
+        {
+            bytes[i] = (unsigned char)random_byte();
+        }
+        /* Mask the top to reduce failure rate to 50/50 */
+        if (upper_bits)
+        {
+            bytes[i - 1] &= 0xFF >> (8 - upper_bits);
+        }
+
+        ret = bignum_from_bytes(bytes, upper_bytes);
+    } while (bignum_cmp(ret, lower) < 0 || bignum_cmp(ret, upper) > 0);
+    smemclr(bytes, upper_bytes);
+    sfree(bytes);
+
+    return ret;
+}
+
 /*
  * Read an SSH-1-format bignum from a data buffer. Return the number
  * of bytes consumed, or -1 if there wasn't enough data.
@@ -1292,6 +1678,44 @@ Bignum bignum_rshift(Bignum a, int shift)
     return ret;
 }
 
+/*
+ * Left-shift one bignum to form another.
+ */
+Bignum bignum_lshift(Bignum a, int shift)
+{
+    Bignum ret;
+    int bits, shiftWords, shiftBits;
+
+    assert(shift >= 0);
+
+    bits = bignum_bitcount(a) + shift;
+    ret = newbn((bits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS);
+
+    shiftWords = shift / BIGNUM_INT_BITS;
+    shiftBits = shift % BIGNUM_INT_BITS;
+
+    if (shiftBits == 0)
+    {
+        memcpy(&ret[1 + shiftWords], &a[1], sizeof(BignumInt) * a[0]);
+    }
+    else
+    {
+        int i;
+        BignumInt carry = 0;
+
+        /* Remember that Bignum[0] is length, so add 1 */
+        for (i = shiftWords + 1; i < ((int)a[0]) + shiftWords + 1; ++i)
+        {
+            BignumInt from = a[i - shiftWords];
+            ret[i] = (from << shiftBits) | carry;
+            carry = from >> (BIGNUM_INT_BITS - shiftBits);
+        }
+        if (carry) ret[i] = carry;
+    }
+
+    return ret;
+}
+
 /*
  * Non-modular multiplication and addition.
  */
@@ -1331,12 +1755,11 @@ Bignum bigmuladd(Bignum a, Bignum b, Bignum addend)
 
     /* now add in the addend, if any */
     if (addend) {
-       BignumDblInt carry = 0;
+       BignumCarry carry = 0;
        for (i = 1; i <= rlen; i++) {
-           carry += (i <= (int)ret[0] ? ret[i] : 0);
-           carry += (i <= (int)addend[0] ? addend[i] : 0);
-           ret[i] = (BignumInt) carry & BIGNUM_INT_MASK;
-           carry >>= BIGNUM_INT_BITS;
+            BignumInt retword = (i <= (int)ret[0] ? ret[i] : 0);
+            BignumInt addword = (i <= (int)addend[0] ? addend[i] : 0);
+            BignumADC(ret[i], carry, retword, addword, carry);
            if (ret[i] != 0 && i > maxspot)
                maxspot = i;
        }
@@ -1365,17 +1788,16 @@ Bignum bigadd(Bignum a, Bignum b)
     int rlen = (alen > blen ? alen : blen) + 1;
     int i, maxspot;
     Bignum ret;
-    BignumDblInt carry;
+    BignumCarry carry;
 
     ret = newbn(rlen);
 
     carry = 0;
     maxspot = 0;
     for (i = 1; i <= rlen; i++) {
-        carry += (i <= (int)a[0] ? a[i] : 0);
-        carry += (i <= (int)b[0] ? b[i] : 0);
-        ret[i] = (BignumInt) carry & BIGNUM_INT_MASK;
-        carry >>= BIGNUM_INT_BITS;
+        BignumInt aword = (i <= (int)a[0] ? a[i] : 0);
+        BignumInt bword = (i <= (int)b[0] ? b[i] : 0);
+        BignumADC(ret[i], carry, aword, bword, carry);
         if (ret[i] != 0 && i > maxspot)
             maxspot = i;
     }
@@ -1395,17 +1817,16 @@ Bignum bigsub(Bignum a, Bignum b)
     int rlen = (alen > blen ? alen : blen);
     int i, maxspot;
     Bignum ret;
-    BignumDblInt carry;
+    BignumCarry carry;
 
     ret = newbn(rlen);
 
     carry = 1;
     maxspot = 0;
     for (i = 1; i <= rlen; i++) {
-        carry += (i <= (int)a[0] ? a[i] : 0);
-        carry += (i <= (int)b[0] ? b[i] ^ BIGNUM_INT_MASK : BIGNUM_INT_MASK);
-        ret[i] = (BignumInt) carry & BIGNUM_INT_MASK;
-        carry >>= BIGNUM_INT_BITS;
+        BignumInt aword = (i <= (int)a[0] ? a[i] : 0);
+        BignumInt bword = (i <= (int)b[0] ? b[i] : 0);
+        BignumADC(ret[i], carry, aword, ~bword, carry);
         if (ret[i] != 0 && i > maxspot)
             maxspot = i;
     }
@@ -1445,40 +1866,52 @@ Bignum bignum_bitmask(Bignum n)
 }
 
 /*
- * Convert a (max 32-bit) long into a bignum.
+ * Convert an unsigned long into a bignum.
  */
-Bignum bignum_from_long(unsigned long nn)
+Bignum bignum_from_long(unsigned long n)
 {
+    const int maxwords =
+        (sizeof(unsigned long) + sizeof(BignumInt) - 1) / sizeof(BignumInt);
     Bignum ret;
-    BignumDblInt n = nn;
+    int i;
+
+    ret = newbn(maxwords);
+    ret[0] = 0;
+    for (i = 0; i < maxwords; i++) {
+        ret[i+1] = n >> (i * BIGNUM_INT_BITS);
+        if (ret[i+1] != 0)
+            ret[0] = i+1;
+    }
 
-    ret = newbn(3);
-    ret[1] = (BignumInt)(n & BIGNUM_INT_MASK);
-    ret[2] = (BignumInt)((n >> BIGNUM_INT_BITS) & BIGNUM_INT_MASK);
-    ret[3] = 0;
-    ret[0] = (ret[2]  ? 2 : 1);
     return ret;
 }
 
 /*
  * Add a long to a bignum.
  */
-Bignum bignum_add_long(Bignum number, unsigned long addendx)
+Bignum bignum_add_long(Bignum number, unsigned long n)
 {
-    Bignum ret = newbn(number[0] + 1);
-    int i, maxspot = 0;
-    BignumDblInt carry = 0, addend = addendx;
+    const int maxwords =
+        (sizeof(unsigned long) + sizeof(BignumInt) - 1) / sizeof(BignumInt);
+    Bignum ret;
+    int words, i;
+    BignumCarry carry;
 
-    for (i = 1; i <= (int)ret[0]; i++) {
-       carry += addend & BIGNUM_INT_MASK;
-       carry += (i <= (int)number[0] ? number[i] : 0);
-       addend >>= BIGNUM_INT_BITS;
-       ret[i] = (BignumInt) carry & BIGNUM_INT_MASK;
-       carry >>= BIGNUM_INT_BITS;
-       if (ret[i] != 0)
-           maxspot = i;
+    words = number[0];
+    if (words < maxwords)
+        words = maxwords;
+    words++;
+    ret = newbn(words);
+
+    carry = 0;
+    ret[0] = 0;
+    for (i = 0; i < words; i++) {
+        BignumInt nword = (i < maxwords ? n >> (i * BIGNUM_INT_BITS) : 0);
+        BignumInt numword = (i < number[0] ? number[i+1] : 0);
+        BignumADC(ret[i+1], carry, numword, nword, carry);
+       if (ret[i+1] != 0)
+            ret[0] = i+1;
     }
-    ret[0] = maxspot;
     return ret;
 }
 
@@ -1487,13 +1920,17 @@ Bignum bignum_add_long(Bignum number, unsigned long addendx)
  */
 unsigned short bignum_mod_short(Bignum number, unsigned short modulus)
 {
-    BignumDblInt mod, r;
+    unsigned long mod = modulus, r = 0;
+    /* Precompute (BIGNUM_INT_MASK+1) % mod */
+    unsigned long base_r = (BIGNUM_INT_MASK - modulus + 1) % mod;
     int i;
 
-    r = 0;
-    mod = modulus;
-    for (i = number[0]; i > 0; i--)
-       r = (r * (BIGNUM_TOP_BIT % mod) * 2 + number[i] % mod) % mod;
+    for (i = number[0]; i > 0; i--) {
+        /*
+         * Conceptually, ((r << BIGNUM_INT_BITS) + number[i]) % mod
+         */
+        r = ((r * base_r) + (number[i] % mod)) % mod;
+    }
     return (unsigned short) r;
 }
 
@@ -1651,7 +2088,7 @@ char *bignum_decimal(Bignum x)
 {
     int ndigits, ndigit;
     int i, iszero;
-    BignumDblInt carry;
+    BignumInt carry;
     char *ret;
     BignumInt *workspace;
 
@@ -1698,11 +2135,33 @@ char *bignum_decimal(Bignum x)
        iszero = 1;
        carry = 0;
        for (i = 0; i < (int)x[0]; i++) {
-           carry = (carry << BIGNUM_INT_BITS) + workspace[i];
-           workspace[i] = (BignumInt) (carry / 10);
+            /*
+             * Conceptually, we want to compute
+             *
+             *   (carry << BIGNUM_INT_BITS) + workspace[i]
+             *   -----------------------------------------
+             *                      10
+             *
+             * but we don't have an integer type longer than BignumInt
+             * to work with. So we have to do it in pieces.
+             */
+
+            BignumInt q, r;
+            q = workspace[i] / 10;
+            r = workspace[i] % 10;
+
+            /* I want (BIGNUM_INT_MASK+1)/10 but can't say so directly! */
+            q += carry * ((BIGNUM_INT_MASK-9) / 10 + 1);
+            r += carry * ((BIGNUM_INT_MASK-9) % 10);
+
+            q += r / 10;
+            r %= 10;
+
+           workspace[i] = q;
+           carry = r;
+
            if (workspace[i])
                iszero = 0;
-           carry %= 10;
        }
        ret[--ndigit] = (char) (carry + '0');
     } while (!iszero);
@@ -1721,210 +2180,3 @@ char *bignum_decimal(Bignum x)
     sfree(workspace);
     return ret;
 }
-
-#ifdef TESTBN
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <ctype.h>
-
-/*
- * gcc -Wall -g -O0 -DTESTBN -o testbn sshbn.c misc.c conf.c tree234.c unix/uxmisc.c -I. -I unix -I charset
- *
- * Then feed to this program's standard input the output of
- * testdata/bignum.py .
- */
-
-void modalfatalbox(char *p, ...)
-{
-    va_list ap;
-    fprintf(stderr, "FATAL ERROR: ");
-    va_start(ap, p);
-    vfprintf(stderr, p, ap);
-    va_end(ap);
-    fputc('\n', stderr);
-    exit(1);
-}
-
-int random_byte(void)
-{
-    modalfatalbox("random_byte called in testbn");
-    return 0;
-}
-
-#define fromxdigit(c) ( (c)>'9' ? ((c)&0xDF) - 'A' + 10 : (c) - '0' )
-
-int main(int argc, char **argv)
-{
-    char *buf;
-    int line = 0;
-    int passes = 0, fails = 0;
-
-    while ((buf = fgetline(stdin)) != NULL) {
-        int maxlen = strlen(buf);
-        unsigned char *data = snewn(maxlen, unsigned char);
-        unsigned char *ptrs[5], *q;
-        int ptrnum;
-        char *bufp = buf;
-
-        line++;
-
-        q = data;
-        ptrnum = 0;
-
-        while (*bufp && !isspace((unsigned char)*bufp))
-            bufp++;
-        if (bufp)
-            *bufp++ = '\0';
-
-        while (*bufp) {
-            char *start, *end;
-            int i;
-
-            while (*bufp && !isxdigit((unsigned char)*bufp))
-                bufp++;
-            start = bufp;
-
-            if (!*bufp)
-                break;
-
-            while (*bufp && isxdigit((unsigned char)*bufp))
-                bufp++;
-            end = bufp;
-
-            if (ptrnum >= lenof(ptrs))
-                break;
-            ptrs[ptrnum++] = q;
-            
-            for (i = -((end - start) & 1); i < end-start; i += 2) {
-                unsigned char val = (i < 0 ? 0 : fromxdigit(start[i]));
-                val = val * 16 + fromxdigit(start[i+1]);
-                *q++ = val;
-            }
-
-            ptrs[ptrnum] = q;
-        }
-
-        if (!strcmp(buf, "mul")) {
-            Bignum a, b, c, p;
-
-            if (ptrnum != 3) {
-                printf("%d: mul with %d parameters, expected 3\n", line, ptrnum);
-                exit(1);
-            }
-            a = bignum_from_bytes(ptrs[0], ptrs[1]-ptrs[0]);
-            b = bignum_from_bytes(ptrs[1], ptrs[2]-ptrs[1]);
-            c = bignum_from_bytes(ptrs[2], ptrs[3]-ptrs[2]);
-            p = bigmul(a, b);
-
-            if (bignum_cmp(c, p) == 0) {
-                passes++;
-            } else {
-                char *as = bignum_decimal(a);
-                char *bs = bignum_decimal(b);
-                char *cs = bignum_decimal(c);
-                char *ps = bignum_decimal(p);
-                
-                printf("%d: fail: %s * %s gave %s expected %s\n",
-                       line, as, bs, ps, cs);
-                fails++;
-
-                sfree(as);
-                sfree(bs);
-                sfree(cs);
-                sfree(ps);
-            }
-            freebn(a);
-            freebn(b);
-            freebn(c);
-            freebn(p);
-        } else if (!strcmp(buf, "modmul")) {
-            Bignum a, b, m, c, p;
-
-            if (ptrnum != 4) {
-                printf("%d: modmul with %d parameters, expected 4\n",
-                       line, ptrnum);
-                exit(1);
-            }
-            a = bignum_from_bytes(ptrs[0], ptrs[1]-ptrs[0]);
-            b = bignum_from_bytes(ptrs[1], ptrs[2]-ptrs[1]);
-            m = bignum_from_bytes(ptrs[2], ptrs[3]-ptrs[2]);
-            c = bignum_from_bytes(ptrs[3], ptrs[4]-ptrs[3]);
-            p = modmul(a, b, m);
-
-            if (bignum_cmp(c, p) == 0) {
-                passes++;
-            } else {
-                char *as = bignum_decimal(a);
-                char *bs = bignum_decimal(b);
-                char *ms = bignum_decimal(m);
-                char *cs = bignum_decimal(c);
-                char *ps = bignum_decimal(p);
-                
-                printf("%d: fail: %s * %s mod %s gave %s expected %s\n",
-                       line, as, bs, ms, ps, cs);
-                fails++;
-
-                sfree(as);
-                sfree(bs);
-                sfree(ms);
-                sfree(cs);
-                sfree(ps);
-            }
-            freebn(a);
-            freebn(b);
-            freebn(m);
-            freebn(c);
-            freebn(p);
-        } else if (!strcmp(buf, "pow")) {
-            Bignum base, expt, modulus, expected, answer;
-
-            if (ptrnum != 4) {
-                printf("%d: mul with %d parameters, expected 4\n", line, ptrnum);
-                exit(1);
-            }
-
-            base = bignum_from_bytes(ptrs[0], ptrs[1]-ptrs[0]);
-            expt = bignum_from_bytes(ptrs[1], ptrs[2]-ptrs[1]);
-            modulus = bignum_from_bytes(ptrs[2], ptrs[3]-ptrs[2]);
-            expected = bignum_from_bytes(ptrs[3], ptrs[4]-ptrs[3]);
-            answer = modpow(base, expt, modulus);
-
-            if (bignum_cmp(expected, answer) == 0) {
-                passes++;
-            } else {
-                char *as = bignum_decimal(base);
-                char *bs = bignum_decimal(expt);
-                char *cs = bignum_decimal(modulus);
-                char *ds = bignum_decimal(answer);
-                char *ps = bignum_decimal(expected);
-                
-                printf("%d: fail: %s ^ %s mod %s gave %s expected %s\n",
-                       line, as, bs, cs, ds, ps);
-                fails++;
-
-                sfree(as);
-                sfree(bs);
-                sfree(cs);
-                sfree(ds);
-                sfree(ps);
-            }
-            freebn(base);
-            freebn(expt);
-            freebn(modulus);
-            freebn(expected);
-            freebn(answer);
-        } else {
-            printf("%d: unrecognised test keyword: '%s'\n", line, buf);
-            exit(1);
-        }
-
-        sfree(buf);
-        sfree(data);
-    }
-
-    printf("passed %d failed %d total %d\n", passes, fails, passes+fails);
-    return fails != 0;
-}
-
-#endif
diff --git a/sshbn.h b/sshbn.h
index a043241eea67ac4453a6951a60c6156b6758f24e..6ee97ee65785e70360631e818c6328fdd0677bf1 100644 (file)
--- a/sshbn.h
+++ b/sshbn.h
 /*
  * sshbn.h: the assorted conditional definitions of BignumInt and
- * multiply/divide macros used throughout the bignum code to treat
- * numbers as arrays of the most conveniently sized word for the
- * target machine. Exported so that other code (e.g. poly1305) can use
- * it too.
- */
-
-/*
- * Usage notes:
- *  * Do not call the DIVMOD_WORD macro with expressions such as array
- *    subscripts, as some implementations object to this (see below).
- *  * Note that none of the division methods below will cope if the
- *    quotient won't fit into BIGNUM_INT_BITS. Callers should be careful
- *    to avoid this case.
- *    If this condition occurs, in the case of the x86 DIV instruction,
- *    an overflow exception will occur, which (according to a correspondent)
- *    will manifest on Windows as something like
- *      0xC0000095: Integer overflow
- *    The C variant won't give the right answer, either.
+ * multiply macros used throughout the bignum code to treat numbers as
+ * arrays of the most conveniently sized word for the target machine.
+ * Exported so that other code (e.g. poly1305) can use it too.
+ *
+ * This file must export, in whatever ifdef branch it ends up in:
+ *
+ *  - two types: 'BignumInt' and 'BignumCarry'. BignumInt is an
+ *    unsigned integer type which will be used as the base word size
+ *    for all bignum operations. BignumCarry is an unsigned integer
+ *    type used to hold the carry flag taken as input and output by
+ *    the BignumADC macro (see below).
+ *
+ *  - four constant macros: BIGNUM_INT_BITS, BIGNUM_INT_BYTES,
+ *    BIGNUM_TOP_BIT, BIGNUM_INT_MASK. These should be more or less
+ *    self-explanatory, but just in case, they give the number of bits
+ *    in BignumInt, the number of bytes that works out to, the
+ *    BignumInt value consisting of only the top bit, and the
+ *    BignumInt value with all bits set.
+ *
+ *  - four statement macros: BignumADC, BignumMUL, BignumMULADD,
+ *    BignumMULADD2. These do various kinds of multi-word arithmetic,
+ *    and all produce two output values.
+ *     * BignumADC(ret,retc,a,b,c) takes input BignumInt values a,b
+ *       and a BignumCarry c, and outputs a BignumInt ret = a+b+c and
+ *       a BignumCarry retc which is the carry off the top of that
+ *       addition.
+ *     * BignumMUL(rh,rl,a,b) returns the two halves of the
+ *       double-width product a*b.
+ *     * BignumMULADD(rh,rl,a,b,addend) returns the two halves of the
+ *       double-width value a*b + addend.
+ *     * BignumMULADD2(rh,rl,a,b,addend1,addend2) returns the two
+ *       halves of the double-width value a*b + addend1 + addend2.
+ *
+ * Every branch of the main ifdef below defines the type BignumInt and
+ * the value BIGNUM_INT_BITS. The other three constant macros are
+ * filled in by common code further down.
+ *
+ * Most branches also define a macro DEFINE_BIGNUMDBLINT containing a
+ * typedef statement which declares a type _twice_ the length of a
+ * BignumInt. This causes the common code further down to produce a
+ * default implementation of the four statement macros in terms of
+ * that double-width type, and also to defined BignumCarry to be
+ * BignumInt.
+ *
+ * However, if a particular compile target does not have a type twice
+ * the length of the BignumInt you want to use but it does provide
+ * some alternative means of doing add-with-carry and double-word
+ * multiply, then the ifdef branch in question can just define
+ * BignumCarry and the four statement macros itself, and that's fine
+ * too.
  */
 
 #if defined __SIZEOF_INT128__
-/* gcc and clang both provide a __uint128_t type on 64-bit targets
- * (and, when they do, indicate its presence by the above macro),
- * using the same 'two machine registers' kind of code generation that
- * 32-bit targets use for 64-bit ints. If we have one of these, we can
- * use a 64-bit BignumInt and a 128-bit BignumDblInt. */
-typedef __uint64_t BignumInt;
-typedef __uint128_t BignumDblInt;
-#define BIGNUM_INT_MASK  0xFFFFFFFFFFFFFFFFULL
-#define BIGNUM_TOP_BIT   0x8000000000000000ULL
-#define BIGNUM_INT_BITS  64
-#define MUL_WORD(w1, w2) ((BignumDblInt)w1 * w2)
-#define DIVMOD_WORD(q, r, hi, lo, w) do { \
-    BignumDblInt n = (((BignumDblInt)hi) << BIGNUM_INT_BITS) | lo; \
-    q = n / w; \
-    r = n % w; \
-} while (0)
-#elif defined __GNUC__ && defined __i386__
-typedef unsigned long BignumInt;
-typedef unsigned long long BignumDblInt;
-#define BIGNUM_INT_MASK  0xFFFFFFFFUL
-#define BIGNUM_TOP_BIT   0x80000000UL
-#define BIGNUM_INT_BITS  32
-#define MUL_WORD(w1, w2) ((BignumDblInt)w1 * w2)
-#define DIVMOD_WORD(q, r, hi, lo, w) \
-    __asm__("div %2" : \
-           "=d" (r), "=a" (q) : \
-           "r" (w), "d" (hi), "a" (lo))
+
+  /*
+   * 64-bit BignumInt using gcc/clang style 128-bit BignumDblInt.
+   *
+   * gcc and clang both provide a __uint128_t type on 64-bit targets
+   * (and, when they do, indicate its presence by the above macro),
+   * using the same 'two machine registers' kind of code generation
+   * that 32-bit targets use for 64-bit ints.
+   */
+
+  typedef unsigned long long BignumInt;
+  #define BIGNUM_INT_BITS 64
+  #define DEFINE_BIGNUMDBLINT typedef __uint128_t BignumDblInt
+
+#elif defined _MSC_VER && defined _M_AMD64
+
+  /*
+   * 64-bit BignumInt, using Visual Studio x86-64 compiler intrinsics.
+   *
+   * 64-bit Visual Studio doesn't provide very much in the way of help
+   * here: there's no int128 type, and also no inline assembler giving
+   * us direct access to the x86-64 MUL or ADC instructions. However,
+   * there are compiler intrinsics giving us that access, so we can
+   * use those - though it turns out we have to be a little careful,
+   * since they seem to generate wrong code if their pointer-typed
+   * output parameters alias their inputs. Hence all the internal temp
+   * variables inside the macros.
+   */
+
+  #include <intrin.h>
+  typedef unsigned char BignumCarry; /* the type _addcarry_u64 likes to use */
+  typedef unsigned __int64 BignumInt;
+  #define BIGNUM_INT_BITS 64
+  #define BignumADC(ret, retc, a, b, c) do                \
+      {                                                   \
+          BignumInt ADC_tmp;                              \
+          (retc) = _addcarry_u64(c, a, b, &ADC_tmp);      \
+          (ret) = ADC_tmp;                                \
+      } while (0)
+  #define BignumMUL(rh, rl, a, b) do              \
+      {                                           \
+          BignumInt MULADD_hi;                    \
+          (rl) = _umul128(a, b, &MULADD_hi);      \
+          (rh) = MULADD_hi;                       \
+      } while (0)
+  #define BignumMULADD(rh, rl, a, b, addend) do                           \
+      {                                                                   \
+          BignumInt MULADD_lo, MULADD_hi;                                 \
+          MULADD_lo = _umul128(a, b, &MULADD_hi);                         \
+          MULADD_hi += _addcarry_u64(0, MULADD_lo, (addend), &(rl));     \
+          (rh) = MULADD_hi;                                               \
+      } while (0)
+  #define BignumMULADD2(rh, rl, a, b, addend1, addend2) do                \
+      {                                                                   \
+          BignumInt MULADD_lo1, MULADD_lo2, MULADD_hi;                    \
+          MULADD_lo1 = _umul128(a, b, &MULADD_hi);                        \
+          MULADD_hi += _addcarry_u64(0, MULADD_lo1, (addend1), &MULADD_lo2); \
+          MULADD_hi += _addcarry_u64(0, MULADD_lo2, (addend2), &(rl));    \
+          (rh) = MULADD_hi;                                               \
+      } while (0)
+
+#elif defined __GNUC__ || defined _LLP64 || __STDC__ >= 199901L
+
+  /* 32-bit BignumInt, using C99 unsigned long long as BignumDblInt */
+
+  typedef unsigned int BignumInt;
+  #define BIGNUM_INT_BITS 32
+  #define DEFINE_BIGNUMDBLINT typedef unsigned long long BignumDblInt
+
 #elif defined _MSC_VER && defined _M_IX86
-typedef unsigned __int32 BignumInt;
-typedef unsigned __int64 BignumDblInt;
-#define BIGNUM_INT_MASK  0xFFFFFFFFUL
-#define BIGNUM_TOP_BIT   0x80000000UL
-#define BIGNUM_INT_BITS  32
-#define MUL_WORD(w1, w2) ((BignumDblInt)w1 * w2)
-/* Note: MASM interprets array subscripts in the macro arguments as
- * assembler syntax, which gives the wrong answer. Don't supply them.
- * <http://msdn2.microsoft.com/en-us/library/bf1dw62z.aspx> */
-#define DIVMOD_WORD(q, r, hi, lo, w) do { \
-    __asm mov edx, hi \
-    __asm mov eax, lo \
-    __asm div w \
-    __asm mov r, edx \
-    __asm mov q, eax \
-} while(0)
+
+  /* 32-bit BignumInt, using Visual Studio __int64 as BignumDblInt */
+
+  typedef unsigned int BignumInt;
+  #define BIGNUM_INT_BITS  32
+  #define DEFINE_BIGNUMDBLINT typedef unsigned __int64 BignumDblInt
+
 #elif defined _LP64
-/* 64-bit architectures can do 32x32->64 chunks at a time */
-typedef unsigned int BignumInt;
-typedef unsigned long BignumDblInt;
-#define BIGNUM_INT_MASK  0xFFFFFFFFU
-#define BIGNUM_TOP_BIT   0x80000000U
-#define BIGNUM_INT_BITS  32
-#define MUL_WORD(w1, w2) ((BignumDblInt)w1 * w2)
-#define DIVMOD_WORD(q, r, hi, lo, w) do { \
-    BignumDblInt n = (((BignumDblInt)hi) << BIGNUM_INT_BITS) | lo; \
-    q = n / w; \
-    r = n % w; \
-} while (0)
-#elif defined _LLP64
-/* 64-bit architectures in which unsigned long is 32 bits, not 64 */
-typedef unsigned long BignumInt;
-typedef unsigned long long BignumDblInt;
-#define BIGNUM_INT_MASK  0xFFFFFFFFUL
-#define BIGNUM_TOP_BIT   0x80000000UL
-#define BIGNUM_INT_BITS  32
-#define MUL_WORD(w1, w2) ((BignumDblInt)w1 * w2)
-#define DIVMOD_WORD(q, r, hi, lo, w) do { \
-    BignumDblInt n = (((BignumDblInt)hi) << BIGNUM_INT_BITS) | lo; \
-    q = n / w; \
-    r = n % w; \
-} while (0)
+
+  /*
+   * 32-bit BignumInt, using unsigned long itself as BignumDblInt.
+   *
+   * Only for platforms where long is 64 bits, of course.
+   */
+
+  typedef unsigned int BignumInt;
+  #define BIGNUM_INT_BITS  32
+  #define DEFINE_BIGNUMDBLINT typedef unsigned long BignumDblInt
+
 #else
-/* Fallback for all other cases */
-typedef unsigned short BignumInt;
-typedef unsigned long BignumDblInt;
-#define BIGNUM_INT_MASK  0xFFFFU
-#define BIGNUM_TOP_BIT   0x8000U
-#define BIGNUM_INT_BITS  16
-#define MUL_WORD(w1, w2) ((BignumDblInt)w1 * w2)
-#define DIVMOD_WORD(q, r, hi, lo, w) do { \
-    BignumDblInt n = (((BignumDblInt)hi) << BIGNUM_INT_BITS) | lo; \
-    q = n / w; \
-    r = n % w; \
-} while (0)
+
+  /*
+   * 16-bit BignumInt, using unsigned long as BignumDblInt.
+   *
+   * This is the final fallback for real emergencies: C89 guarantees
+   * unsigned short/long to be at least the required sizes, so this
+   * should work on any C implementation at all. But it'll be
+   * noticeably slow, so if you find yourself in this case you
+   * probably want to move heaven and earth to find an alternative!
+   */
+
+  typedef unsigned short BignumInt;
+  #define BIGNUM_INT_BITS  16
+  #define DEFINE_BIGNUMDBLINT typedef unsigned long BignumDblInt
+
 #endif
 
+/*
+ * Common code across all branches of that ifdef: define the three
+ * easy constant macros in terms of BIGNUM_INT_BITS.
+ */
 #define BIGNUM_INT_BYTES (BIGNUM_INT_BITS / 8)
+#define BIGNUM_TOP_BIT (((BignumInt)1) << (BIGNUM_INT_BITS-1))
+#define BIGNUM_INT_MASK (BIGNUM_TOP_BIT | (BIGNUM_TOP_BIT-1))
+
+/*
+ * Common code across _most_ branches of the ifdef: define a set of
+ * statement macros in terms of the BignumDblInt type provided. In
+ * this case, we also define BignumCarry to be the same thing as
+ * BignumInt, for simplicity.
+ */
+#ifdef DEFINE_BIGNUMDBLINT
+
+  typedef BignumInt BignumCarry;
+  #define BignumADC(ret, retc, a, b, c) do                        \
+      {                                                           \
+          DEFINE_BIGNUMDBLINT;                                    \
+          BignumDblInt ADC_temp = (BignumInt)(a);                 \
+          ADC_temp += (BignumInt)(b);                             \
+          ADC_temp += (c);                                        \
+          (ret) = (BignumInt)ADC_temp;                            \
+          (retc) = (BignumCarry)(ADC_temp >> BIGNUM_INT_BITS);    \
+      } while (0)
+  
+  #define BignumMUL(rh, rl, a, b) do                              \
+      {                                                           \
+          DEFINE_BIGNUMDBLINT;                                    \
+          BignumDblInt MUL_temp = (BignumInt)(a);                 \
+          MUL_temp *= (BignumInt)(b);                             \
+          (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS);        \
+          (rl) = (BignumInt)(MUL_temp);                           \
+      } while (0)
+  
+  #define BignumMULADD(rh, rl, a, b, addend) do                   \
+      {                                                           \
+          DEFINE_BIGNUMDBLINT;                                    \
+          BignumDblInt MUL_temp = (BignumInt)(a);                 \
+          MUL_temp *= (BignumInt)(b);                             \
+          MUL_temp += (BignumInt)(addend);                        \
+          (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS);        \
+          (rl) = (BignumInt)(MUL_temp);                           \
+      } while (0)
+  
+  #define BignumMULADD2(rh, rl, a, b, addend1, addend2) do        \
+      {                                                           \
+          DEFINE_BIGNUMDBLINT;                                    \
+          BignumDblInt MUL_temp = (BignumInt)(a);                 \
+          MUL_temp *= (BignumInt)(b);                             \
+          MUL_temp += (BignumInt)(addend1);                       \
+          MUL_temp += (BignumInt)(addend2);                       \
+          (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS);        \
+          (rl) = (BignumInt)(MUL_temp);                           \
+      } while (0)
+
+#endif /* DEFINE_BIGNUMDBLINT */
diff --git a/sshccp.c b/sshccp.c
new file mode 100644 (file)
index 0000000..d0a6177
--- /dev/null
+++ b/sshccp.c
@@ -0,0 +1,1059 @@
+/*
+ * ChaCha20-Poly1305 Implementation for SSH-2
+ *
+ * Protocol spec:
+ *  http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?rev=1.2&content-type=text/x-cvsweb-markup
+ *
+ * ChaCha20 spec:
+ *  http://cr.yp.to/chacha/chacha-20080128.pdf
+ *
+ * Salsa20 spec:
+ *  http://cr.yp.to/snuffle/spec.pdf
+ *
+ * Poly1305-AES spec:
+ *  http://cr.yp.to/mac/poly1305-20050329.pdf
+ *
+ * The nonce for the Poly1305 is the second part of the key output
+ * from the first round of ChaCha20. This removes the AES requirement.
+ * This is undocumented!
+ *
+ * This has an intricate link between the cipher and the MAC. The
+ * keying of both is done in by the cipher and setting of the IV is
+ * done by the MAC. One cannot operate without the other. The
+ * configuration of the ssh2_cipher structure ensures that the MAC is
+ * set (and others ignored) if this cipher is chosen.
+ *
+ * This cipher also encrypts the length using a different
+ * instantiation of the cipher using a different key and IV made from
+ * the sequence number which is passed in addition when calling
+ * encrypt/decrypt on it.
+ */
+
+#include "ssh.h"
+#include "sshbn.h"
+
+#ifndef INLINE
+#define INLINE
+#endif
+
+/* ChaCha20 implementation, only supporting 256-bit keys */
+
+/* State for each ChaCha20 instance */
+struct chacha20 {
+    /* Current context, usually with the count incremented
+     * 0-3 are the static constant
+     * 4-11 are the key
+     * 12-13 are the counter
+     * 14-15 are the IV */
+    uint32 state[16];
+    /* The output of the state above ready to xor */
+    unsigned char current[64];
+    /* The index of the above currently used to allow a true streaming cipher */
+    int currentIndex;
+};
+
+static INLINE void chacha20_round(struct chacha20 *ctx)
+{
+    int i;
+    uint32 copy[16];
+
+    /* Take a copy */
+    memcpy(copy, ctx->state, sizeof(copy));
+
+    /* A circular rotation for a 32bit number */
+#define rotl(x, shift) x = ((x << shift) | (x >> (32 - shift)))
+
+    /* What to do for each quarter round operation */
+#define qrop(a, b, c, d)                        \
+    copy[a] += copy[b];                         \
+    copy[c] ^= copy[a];                         \
+    rotl(copy[c], d)
+
+    /* A quarter round */
+#define quarter(a, b, c, d)                     \
+    qrop(a, b, d, 16);                          \
+    qrop(c, d, b, 12);                          \
+    qrop(a, b, d, 8);                           \
+    qrop(c, d, b, 7)
+
+    /* Do 20 rounds, in pairs because every other is different */
+    for (i = 0; i < 20; i += 2) {
+        /* A round */
+        quarter(0, 4, 8, 12);
+        quarter(1, 5, 9, 13);
+        quarter(2, 6, 10, 14);
+        quarter(3, 7, 11, 15);
+        /* Another slightly different round */
+        quarter(0, 5, 10, 15);
+        quarter(1, 6, 11, 12);
+        quarter(2, 7, 8, 13);
+        quarter(3, 4, 9, 14);
+    }
+
+    /* Dump the macros, don't need them littering */
+#undef rotl
+#undef qrop
+#undef quarter
+
+    /* Add the initial state */
+    for (i = 0; i < 16; ++i) {
+        copy[i] += ctx->state[i];
+    }
+
+    /* Update the content of the xor buffer */
+    for (i = 0; i < 16; ++i) {
+        ctx->current[i * 4 + 0] = copy[i] >> 0;
+        ctx->current[i * 4 + 1] = copy[i] >> 8;
+        ctx->current[i * 4 + 2] = copy[i] >> 16;
+        ctx->current[i * 4 + 3] = copy[i] >> 24;
+    }
+    /* State full, reset pointer to beginning */
+    ctx->currentIndex = 0;
+    smemclr(copy, sizeof(copy));
+
+    /* Increment round counter */
+    ++ctx->state[12];
+    /* Check for overflow, not done in one line so the 32 bits are chopped by the type */
+    if (!(uint32)(ctx->state[12])) {
+        ++ctx->state[13];
+    }
+}
+
+/* Initialise context with 256bit key */
+static void chacha20_key(struct chacha20 *ctx, const unsigned char *key)
+{
+    static const char constant[16] = "expand 32-byte k";
+
+    /* Add the fixed string to the start of the state */
+    ctx->state[0] = GET_32BIT_LSB_FIRST(constant + 0);
+    ctx->state[1] = GET_32BIT_LSB_FIRST(constant + 4);
+    ctx->state[2] = GET_32BIT_LSB_FIRST(constant + 8);
+    ctx->state[3] = GET_32BIT_LSB_FIRST(constant + 12);
+
+    /* Add the key */
+    ctx->state[4]  = GET_32BIT_LSB_FIRST(key + 0);
+    ctx->state[5]  = GET_32BIT_LSB_FIRST(key + 4);
+    ctx->state[6]  = GET_32BIT_LSB_FIRST(key + 8);
+    ctx->state[7]  = GET_32BIT_LSB_FIRST(key + 12);
+    ctx->state[8]  = GET_32BIT_LSB_FIRST(key + 16);
+    ctx->state[9]  = GET_32BIT_LSB_FIRST(key + 20);
+    ctx->state[10] = GET_32BIT_LSB_FIRST(key + 24);
+    ctx->state[11] = GET_32BIT_LSB_FIRST(key + 28);
+
+    /* New key, dump context */
+    ctx->currentIndex = 64;
+}
+
+static void chacha20_iv(struct chacha20 *ctx, const unsigned char *iv)
+{
+    ctx->state[12] = 0;
+    ctx->state[13] = 0;
+    ctx->state[14] = GET_32BIT_MSB_FIRST(iv);
+    ctx->state[15] = GET_32BIT_MSB_FIRST(iv + 4);
+
+    /* New IV, dump context */
+    ctx->currentIndex = 64;
+}
+
+static void chacha20_encrypt(struct chacha20 *ctx, unsigned char *blk, int len)
+{
+    while (len) {
+        /* If we don't have any state left, then cycle to the next */
+        if (ctx->currentIndex >= 64) {
+            chacha20_round(ctx);
+        }
+
+        /* Do the xor while there's some state left and some plaintext left */
+        while (ctx->currentIndex < 64 && len) {
+            *blk++ ^= ctx->current[ctx->currentIndex++];
+            --len;
+        }
+    }
+}
+
+/* Decrypt is encrypt... It's xor against a PRNG... */
+static INLINE void chacha20_decrypt(struct chacha20 *ctx,
+                                    unsigned char *blk, int len)
+{
+    chacha20_encrypt(ctx, blk, len);
+}
+
+/* Poly1305 implementation (no AES, nonce is not encrypted) */
+
+#define NWORDS ((130 + BIGNUM_INT_BITS-1) / BIGNUM_INT_BITS)
+typedef struct bigval {
+    BignumInt w[NWORDS];
+} bigval;
+
+static void bigval_clear(bigval *r)
+{
+    int i;
+    for (i = 0; i < NWORDS; i++)
+        r->w[i] = 0;
+}
+
+static void bigval_import_le(bigval *r, const void *vdata, int len)
+{
+    const unsigned char *data = (const unsigned char *)vdata;
+    int i;
+    bigval_clear(r);
+    for (i = 0; i < len; i++)
+        r->w[i / BIGNUM_INT_BYTES] |=
+            (BignumInt)data[i] << (8 * (i % BIGNUM_INT_BYTES));
+}
+
+static void bigval_export_le(const bigval *r, void *vdata, int len)
+{
+    unsigned char *data = (unsigned char *)vdata;
+    int i;
+    for (i = 0; i < len; i++)
+        data[i] = r->w[i / BIGNUM_INT_BYTES] >> (8 * (i % BIGNUM_INT_BYTES));
+}
+
+/*
+ * Core functions to do arithmetic mod p = 2^130-5. The whole
+ * collection of these, up to and including the surrounding #if, are
+ * generated automatically for various sizes of BignumInt by
+ * contrib/make1305.py.
+ */
+
+#if BIGNUM_INT_BITS == 16
+
+static void bigval_add(bigval *r, const bigval *a, const bigval *b)
+{
+    BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14;
+    BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26;
+    BignumCarry carry;
+
+    v0 = a->w[0];
+    v1 = a->w[1];
+    v2 = a->w[2];
+    v3 = a->w[3];
+    v4 = a->w[4];
+    v5 = a->w[5];
+    v6 = a->w[6];
+    v7 = a->w[7];
+    v8 = a->w[8];
+    v9 = b->w[0];
+    v10 = b->w[1];
+    v11 = b->w[2];
+    v12 = b->w[3];
+    v13 = b->w[4];
+    v14 = b->w[5];
+    v15 = b->w[6];
+    v16 = b->w[7];
+    v17 = b->w[8];
+    BignumADC(v18, carry, v0, v9, 0);
+    BignumADC(v19, carry, v1, v10, carry);
+    BignumADC(v20, carry, v2, v11, carry);
+    BignumADC(v21, carry, v3, v12, carry);
+    BignumADC(v22, carry, v4, v13, carry);
+    BignumADC(v23, carry, v5, v14, carry);
+    BignumADC(v24, carry, v6, v15, carry);
+    BignumADC(v25, carry, v7, v16, carry);
+    v26 = v8 + v17 + carry;
+    r->w[0] = v18;
+    r->w[1] = v19;
+    r->w[2] = v20;
+    r->w[3] = v21;
+    r->w[4] = v22;
+    r->w[5] = v23;
+    r->w[6] = v24;
+    r->w[7] = v25;
+    r->w[8] = v26;
+}
+
+static void bigval_mul_mod_p(bigval *r, const bigval *a, const bigval *b)
+{
+    BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14;
+    BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27;
+    BignumInt v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40;
+    BignumInt v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53;
+    BignumInt v54, v55, v56, v57, v58, v59, v60, v61, v62, v63, v64, v65, v66;
+    BignumInt v67, v68, v69, v70, v71, v72, v73, v74, v75, v76, v77, v78, v79;
+    BignumInt v80, v81, v82, v83, v84, v85, v86, v87, v88, v89, v90, v91, v92;
+    BignumInt v93, v94, v95, v96, v97, v98, v99, v100, v101, v102, v103, v104;
+    BignumInt v105, v106, v107, v108, v109, v110, v111, v112, v113, v114;
+    BignumInt v115, v116, v117, v118, v119, v120, v121, v122, v123, v124;
+    BignumInt v125, v126, v127, v128, v129, v130, v131, v132, v133, v134;
+    BignumInt v135, v136, v137, v138, v139, v140, v141, v142, v143, v144;
+    BignumInt v145, v146, v147, v148, v149, v150, v151, v152, v153, v154;
+    BignumInt v155, v156, v157, v158, v159, v160, v161, v162, v163, v164;
+    BignumInt v165, v166, v167, v168, v169, v170, v171, v172, v173, v174;
+    BignumInt v175, v176, v177, v178, v180, v181, v182, v183, v184, v185;
+    BignumInt v186, v187, v188, v189, v190, v191, v192, v193, v194, v195;
+    BignumInt v196, v197, v198, v199, v200, v201, v202, v203, v204, v205;
+    BignumInt v206, v207, v208, v210, v212, v213, v214, v215, v216, v217;
+    BignumInt v218, v219, v220, v221, v222, v223, v224, v225, v226, v227;
+    BignumInt v228, v229;
+    BignumCarry carry;
+
+    v0 = a->w[0];
+    v1 = a->w[1];
+    v2 = a->w[2];
+    v3 = a->w[3];
+    v4 = a->w[4];
+    v5 = a->w[5];
+    v6 = a->w[6];
+    v7 = a->w[7];
+    v8 = a->w[8];
+    v9 = b->w[0];
+    v10 = b->w[1];
+    v11 = b->w[2];
+    v12 = b->w[3];
+    v13 = b->w[4];
+    v14 = b->w[5];
+    v15 = b->w[6];
+    v16 = b->w[7];
+    v17 = b->w[8];
+    BignumMUL(v19, v18, v0, v9);
+    BignumMULADD(v21, v20, v0, v10, v19);
+    BignumMULADD(v23, v22, v0, v11, v21);
+    BignumMULADD(v25, v24, v0, v12, v23);
+    BignumMULADD(v27, v26, v0, v13, v25);
+    BignumMULADD(v29, v28, v0, v14, v27);
+    BignumMULADD(v31, v30, v0, v15, v29);
+    BignumMULADD(v33, v32, v0, v16, v31);
+    BignumMULADD(v35, v34, v0, v17, v33);
+    BignumMULADD(v37, v36, v1, v9, v20);
+    BignumMULADD2(v39, v38, v1, v10, v22, v37);
+    BignumMULADD2(v41, v40, v1, v11, v24, v39);
+    BignumMULADD2(v43, v42, v1, v12, v26, v41);
+    BignumMULADD2(v45, v44, v1, v13, v28, v43);
+    BignumMULADD2(v47, v46, v1, v14, v30, v45);
+    BignumMULADD2(v49, v48, v1, v15, v32, v47);
+    BignumMULADD2(v51, v50, v1, v16, v34, v49);
+    BignumMULADD2(v53, v52, v1, v17, v35, v51);
+    BignumMULADD(v55, v54, v2, v9, v38);
+    BignumMULADD2(v57, v56, v2, v10, v40, v55);
+    BignumMULADD2(v59, v58, v2, v11, v42, v57);
+    BignumMULADD2(v61, v60, v2, v12, v44, v59);
+    BignumMULADD2(v63, v62, v2, v13, v46, v61);
+    BignumMULADD2(v65, v64, v2, v14, v48, v63);
+    BignumMULADD2(v67, v66, v2, v15, v50, v65);
+    BignumMULADD2(v69, v68, v2, v16, v52, v67);
+    BignumMULADD2(v71, v70, v2, v17, v53, v69);
+    BignumMULADD(v73, v72, v3, v9, v56);
+    BignumMULADD2(v75, v74, v3, v10, v58, v73);
+    BignumMULADD2(v77, v76, v3, v11, v60, v75);
+    BignumMULADD2(v79, v78, v3, v12, v62, v77);
+    BignumMULADD2(v81, v80, v3, v13, v64, v79);
+    BignumMULADD2(v83, v82, v3, v14, v66, v81);
+    BignumMULADD2(v85, v84, v3, v15, v68, v83);
+    BignumMULADD2(v87, v86, v3, v16, v70, v85);
+    BignumMULADD2(v89, v88, v3, v17, v71, v87);
+    BignumMULADD(v91, v90, v4, v9, v74);
+    BignumMULADD2(v93, v92, v4, v10, v76, v91);
+    BignumMULADD2(v95, v94, v4, v11, v78, v93);
+    BignumMULADD2(v97, v96, v4, v12, v80, v95);
+    BignumMULADD2(v99, v98, v4, v13, v82, v97);
+    BignumMULADD2(v101, v100, v4, v14, v84, v99);
+    BignumMULADD2(v103, v102, v4, v15, v86, v101);
+    BignumMULADD2(v105, v104, v4, v16, v88, v103);
+    BignumMULADD2(v107, v106, v4, v17, v89, v105);
+    BignumMULADD(v109, v108, v5, v9, v92);
+    BignumMULADD2(v111, v110, v5, v10, v94, v109);
+    BignumMULADD2(v113, v112, v5, v11, v96, v111);
+    BignumMULADD2(v115, v114, v5, v12, v98, v113);
+    BignumMULADD2(v117, v116, v5, v13, v100, v115);
+    BignumMULADD2(v119, v118, v5, v14, v102, v117);
+    BignumMULADD2(v121, v120, v5, v15, v104, v119);
+    BignumMULADD2(v123, v122, v5, v16, v106, v121);
+    BignumMULADD2(v125, v124, v5, v17, v107, v123);
+    BignumMULADD(v127, v126, v6, v9, v110);
+    BignumMULADD2(v129, v128, v6, v10, v112, v127);
+    BignumMULADD2(v131, v130, v6, v11, v114, v129);
+    BignumMULADD2(v133, v132, v6, v12, v116, v131);
+    BignumMULADD2(v135, v134, v6, v13, v118, v133);
+    BignumMULADD2(v137, v136, v6, v14, v120, v135);
+    BignumMULADD2(v139, v138, v6, v15, v122, v137);
+    BignumMULADD2(v141, v140, v6, v16, v124, v139);
+    BignumMULADD2(v143, v142, v6, v17, v125, v141);
+    BignumMULADD(v145, v144, v7, v9, v128);
+    BignumMULADD2(v147, v146, v7, v10, v130, v145);
+    BignumMULADD2(v149, v148, v7, v11, v132, v147);
+    BignumMULADD2(v151, v150, v7, v12, v134, v149);
+    BignumMULADD2(v153, v152, v7, v13, v136, v151);
+    BignumMULADD2(v155, v154, v7, v14, v138, v153);
+    BignumMULADD2(v157, v156, v7, v15, v140, v155);
+    BignumMULADD2(v159, v158, v7, v16, v142, v157);
+    BignumMULADD2(v161, v160, v7, v17, v143, v159);
+    BignumMULADD(v163, v162, v8, v9, v146);
+    BignumMULADD2(v165, v164, v8, v10, v148, v163);
+    BignumMULADD2(v167, v166, v8, v11, v150, v165);
+    BignumMULADD2(v169, v168, v8, v12, v152, v167);
+    BignumMULADD2(v171, v170, v8, v13, v154, v169);
+    BignumMULADD2(v173, v172, v8, v14, v156, v171);
+    BignumMULADD2(v175, v174, v8, v15, v158, v173);
+    BignumMULADD2(v177, v176, v8, v16, v160, v175);
+    v178 = v8 * v17 + v161 + v177;
+    v180 = (v162) & ((((BignumInt)1) << 2)-1);
+    v181 = ((v162) >> 2) | ((v164) << 14);
+    v182 = ((v164) >> 2) | ((v166) << 14);
+    v183 = ((v166) >> 2) | ((v168) << 14);
+    v184 = ((v168) >> 2) | ((v170) << 14);
+    v185 = ((v170) >> 2) | ((v172) << 14);
+    v186 = ((v172) >> 2) | ((v174) << 14);
+    v187 = ((v174) >> 2) | ((v176) << 14);
+    v188 = ((v176) >> 2) | ((v178) << 14);
+    v189 = (v178) >> 2;
+    v190 = (v189) & ((((BignumInt)1) << 2)-1);
+    v191 = (v178) >> 4;
+    BignumMUL(v193, v192, 5, v181);
+    BignumMULADD(v195, v194, 5, v182, v193);
+    BignumMULADD(v197, v196, 5, v183, v195);
+    BignumMULADD(v199, v198, 5, v184, v197);
+    BignumMULADD(v201, v200, 5, v185, v199);
+    BignumMULADD(v203, v202, 5, v186, v201);
+    BignumMULADD(v205, v204, 5, v187, v203);
+    BignumMULADD(v207, v206, 5, v188, v205);
+    v208 = 5 * v190 + v207;
+    v210 = 25 * v191;
+    BignumADC(v212, carry, v18, v192, 0);
+    BignumADC(v213, carry, v36, v194, carry);
+    BignumADC(v214, carry, v54, v196, carry);
+    BignumADC(v215, carry, v72, v198, carry);
+    BignumADC(v216, carry, v90, v200, carry);
+    BignumADC(v217, carry, v108, v202, carry);
+    BignumADC(v218, carry, v126, v204, carry);
+    BignumADC(v219, carry, v144, v206, carry);
+    v220 = v180 + v208 + carry;
+    BignumADC(v221, carry, v212, v210, 0);
+    BignumADC(v222, carry, v213, 0, carry);
+    BignumADC(v223, carry, v214, 0, carry);
+    BignumADC(v224, carry, v215, 0, carry);
+    BignumADC(v225, carry, v216, 0, carry);
+    BignumADC(v226, carry, v217, 0, carry);
+    BignumADC(v227, carry, v218, 0, carry);
+    BignumADC(v228, carry, v219, 0, carry);
+    v229 = v220 + 0 + carry;
+    r->w[0] = v221;
+    r->w[1] = v222;
+    r->w[2] = v223;
+    r->w[3] = v224;
+    r->w[4] = v225;
+    r->w[5] = v226;
+    r->w[6] = v227;
+    r->w[7] = v228;
+    r->w[8] = v229;
+}
+
+static void bigval_final_reduce(bigval *n)
+{
+    BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v12, v13, v14, v15;
+    BignumInt v16, v17, v18, v19, v20, v21, v22, v24, v25, v26, v27, v28, v29;
+    BignumInt v30, v31, v32, v33;
+    BignumCarry carry;
+
+    v0 = n->w[0];
+    v1 = n->w[1];
+    v2 = n->w[2];
+    v3 = n->w[3];
+    v4 = n->w[4];
+    v5 = n->w[5];
+    v6 = n->w[6];
+    v7 = n->w[7];
+    v8 = n->w[8];
+    v9 = (v8) >> 2;
+    v10 = 5 * v9;
+    BignumADC(v12, carry, v0, v10, 0);
+    (void)v12;
+    BignumADC(v13, carry, v1, 0, carry);
+    (void)v13;
+    BignumADC(v14, carry, v2, 0, carry);
+    (void)v14;
+    BignumADC(v15, carry, v3, 0, carry);
+    (void)v15;
+    BignumADC(v16, carry, v4, 0, carry);
+    (void)v16;
+    BignumADC(v17, carry, v5, 0, carry);
+    (void)v17;
+    BignumADC(v18, carry, v6, 0, carry);
+    (void)v18;
+    BignumADC(v19, carry, v7, 0, carry);
+    (void)v19;
+    v20 = v8 + 0 + carry;
+    v21 = (v20) >> 2;
+    v22 = 5 * v21;
+    BignumADC(v24, carry, v0, v22, 0);
+    BignumADC(v25, carry, v1, 0, carry);
+    BignumADC(v26, carry, v2, 0, carry);
+    BignumADC(v27, carry, v3, 0, carry);
+    BignumADC(v28, carry, v4, 0, carry);
+    BignumADC(v29, carry, v5, 0, carry);
+    BignumADC(v30, carry, v6, 0, carry);
+    BignumADC(v31, carry, v7, 0, carry);
+    v32 = v8 + 0 + carry;
+    v33 = (v32) & ((((BignumInt)1) << 2)-1);
+    n->w[0] = v24;
+    n->w[1] = v25;
+    n->w[2] = v26;
+    n->w[3] = v27;
+    n->w[4] = v28;
+    n->w[5] = v29;
+    n->w[6] = v30;
+    n->w[7] = v31;
+    n->w[8] = v33;
+}
+
+#elif BIGNUM_INT_BITS == 32
+
+static void bigval_add(bigval *r, const bigval *a, const bigval *b)
+{
+    BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14;
+    BignumCarry carry;
+
+    v0 = a->w[0];
+    v1 = a->w[1];
+    v2 = a->w[2];
+    v3 = a->w[3];
+    v4 = a->w[4];
+    v5 = b->w[0];
+    v6 = b->w[1];
+    v7 = b->w[2];
+    v8 = b->w[3];
+    v9 = b->w[4];
+    BignumADC(v10, carry, v0, v5, 0);
+    BignumADC(v11, carry, v1, v6, carry);
+    BignumADC(v12, carry, v2, v7, carry);
+    BignumADC(v13, carry, v3, v8, carry);
+    v14 = v4 + v9 + carry;
+    r->w[0] = v10;
+    r->w[1] = v11;
+    r->w[2] = v12;
+    r->w[3] = v13;
+    r->w[4] = v14;
+}
+
+static void bigval_mul_mod_p(bigval *r, const bigval *a, const bigval *b)
+{
+    BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14;
+    BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27;
+    BignumInt v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40;
+    BignumInt v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53;
+    BignumInt v54, v55, v56, v57, v58, v60, v61, v62, v63, v64, v65, v66, v67;
+    BignumInt v68, v69, v70, v71, v72, v73, v74, v75, v76, v78, v80, v81, v82;
+    BignumInt v83, v84, v85, v86, v87, v88, v89;
+    BignumCarry carry;
+
+    v0 = a->w[0];
+    v1 = a->w[1];
+    v2 = a->w[2];
+    v3 = a->w[3];
+    v4 = a->w[4];
+    v5 = b->w[0];
+    v6 = b->w[1];
+    v7 = b->w[2];
+    v8 = b->w[3];
+    v9 = b->w[4];
+    BignumMUL(v11, v10, v0, v5);
+    BignumMULADD(v13, v12, v0, v6, v11);
+    BignumMULADD(v15, v14, v0, v7, v13);
+    BignumMULADD(v17, v16, v0, v8, v15);
+    BignumMULADD(v19, v18, v0, v9, v17);
+    BignumMULADD(v21, v20, v1, v5, v12);
+    BignumMULADD2(v23, v22, v1, v6, v14, v21);
+    BignumMULADD2(v25, v24, v1, v7, v16, v23);
+    BignumMULADD2(v27, v26, v1, v8, v18, v25);
+    BignumMULADD2(v29, v28, v1, v9, v19, v27);
+    BignumMULADD(v31, v30, v2, v5, v22);
+    BignumMULADD2(v33, v32, v2, v6, v24, v31);
+    BignumMULADD2(v35, v34, v2, v7, v26, v33);
+    BignumMULADD2(v37, v36, v2, v8, v28, v35);
+    BignumMULADD2(v39, v38, v2, v9, v29, v37);
+    BignumMULADD(v41, v40, v3, v5, v32);
+    BignumMULADD2(v43, v42, v3, v6, v34, v41);
+    BignumMULADD2(v45, v44, v3, v7, v36, v43);
+    BignumMULADD2(v47, v46, v3, v8, v38, v45);
+    BignumMULADD2(v49, v48, v3, v9, v39, v47);
+    BignumMULADD(v51, v50, v4, v5, v42);
+    BignumMULADD2(v53, v52, v4, v6, v44, v51);
+    BignumMULADD2(v55, v54, v4, v7, v46, v53);
+    BignumMULADD2(v57, v56, v4, v8, v48, v55);
+    v58 = v4 * v9 + v49 + v57;
+    v60 = (v50) & ((((BignumInt)1) << 2)-1);
+    v61 = ((v50) >> 2) | ((v52) << 30);
+    v62 = ((v52) >> 2) | ((v54) << 30);
+    v63 = ((v54) >> 2) | ((v56) << 30);
+    v64 = ((v56) >> 2) | ((v58) << 30);
+    v65 = (v58) >> 2;
+    v66 = (v65) & ((((BignumInt)1) << 2)-1);
+    v67 = (v58) >> 4;
+    BignumMUL(v69, v68, 5, v61);
+    BignumMULADD(v71, v70, 5, v62, v69);
+    BignumMULADD(v73, v72, 5, v63, v71);
+    BignumMULADD(v75, v74, 5, v64, v73);
+    v76 = 5 * v66 + v75;
+    v78 = 25 * v67;
+    BignumADC(v80, carry, v10, v68, 0);
+    BignumADC(v81, carry, v20, v70, carry);
+    BignumADC(v82, carry, v30, v72, carry);
+    BignumADC(v83, carry, v40, v74, carry);
+    v84 = v60 + v76 + carry;
+    BignumADC(v85, carry, v80, v78, 0);
+    BignumADC(v86, carry, v81, 0, carry);
+    BignumADC(v87, carry, v82, 0, carry);
+    BignumADC(v88, carry, v83, 0, carry);
+    v89 = v84 + 0 + carry;
+    r->w[0] = v85;
+    r->w[1] = v86;
+    r->w[2] = v87;
+    r->w[3] = v88;
+    r->w[4] = v89;
+}
+
+static void bigval_final_reduce(bigval *n)
+{
+    BignumInt v0, v1, v2, v3, v4, v5, v6, v8, v9, v10, v11, v12, v13, v14;
+    BignumInt v16, v17, v18, v19, v20, v21;
+    BignumCarry carry;
+
+    v0 = n->w[0];
+    v1 = n->w[1];
+    v2 = n->w[2];
+    v3 = n->w[3];
+    v4 = n->w[4];
+    v5 = (v4) >> 2;
+    v6 = 5 * v5;
+    BignumADC(v8, carry, v0, v6, 0);
+    (void)v8;
+    BignumADC(v9, carry, v1, 0, carry);
+    (void)v9;
+    BignumADC(v10, carry, v2, 0, carry);
+    (void)v10;
+    BignumADC(v11, carry, v3, 0, carry);
+    (void)v11;
+    v12 = v4 + 0 + carry;
+    v13 = (v12) >> 2;
+    v14 = 5 * v13;
+    BignumADC(v16, carry, v0, v14, 0);
+    BignumADC(v17, carry, v1, 0, carry);
+    BignumADC(v18, carry, v2, 0, carry);
+    BignumADC(v19, carry, v3, 0, carry);
+    v20 = v4 + 0 + carry;
+    v21 = (v20) & ((((BignumInt)1) << 2)-1);
+    n->w[0] = v16;
+    n->w[1] = v17;
+    n->w[2] = v18;
+    n->w[3] = v19;
+    n->w[4] = v21;
+}
+
+#elif BIGNUM_INT_BITS == 64
+
+static void bigval_add(bigval *r, const bigval *a, const bigval *b)
+{
+    BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8;
+    BignumCarry carry;
+
+    v0 = a->w[0];
+    v1 = a->w[1];
+    v2 = a->w[2];
+    v3 = b->w[0];
+    v4 = b->w[1];
+    v5 = b->w[2];
+    BignumADC(v6, carry, v0, v3, 0);
+    BignumADC(v7, carry, v1, v4, carry);
+    v8 = v2 + v5 + carry;
+    r->w[0] = v6;
+    r->w[1] = v7;
+    r->w[2] = v8;
+}
+
+static void bigval_mul_mod_p(bigval *r, const bigval *a, const bigval *b)
+{
+    BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14;
+    BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v24, v25, v26, v27, v28;
+    BignumInt v29, v30, v31, v32, v33, v34, v36, v38, v39, v40, v41, v42, v43;
+    BignumCarry carry;
+
+    v0 = a->w[0];
+    v1 = a->w[1];
+    v2 = a->w[2];
+    v3 = b->w[0];
+    v4 = b->w[1];
+    v5 = b->w[2];
+    BignumMUL(v7, v6, v0, v3);
+    BignumMULADD(v9, v8, v0, v4, v7);
+    BignumMULADD(v11, v10, v0, v5, v9);
+    BignumMULADD(v13, v12, v1, v3, v8);
+    BignumMULADD2(v15, v14, v1, v4, v10, v13);
+    BignumMULADD2(v17, v16, v1, v5, v11, v15);
+    BignumMULADD(v19, v18, v2, v3, v14);
+    BignumMULADD2(v21, v20, v2, v4, v16, v19);
+    v22 = v2 * v5 + v17 + v21;
+    v24 = (v18) & ((((BignumInt)1) << 2)-1);
+    v25 = ((v18) >> 2) | ((v20) << 62);
+    v26 = ((v20) >> 2) | ((v22) << 62);
+    v27 = (v22) >> 2;
+    v28 = (v27) & ((((BignumInt)1) << 2)-1);
+    v29 = (v22) >> 4;
+    BignumMUL(v31, v30, 5, v25);
+    BignumMULADD(v33, v32, 5, v26, v31);
+    v34 = 5 * v28 + v33;
+    v36 = 25 * v29;
+    BignumADC(v38, carry, v6, v30, 0);
+    BignumADC(v39, carry, v12, v32, carry);
+    v40 = v24 + v34 + carry;
+    BignumADC(v41, carry, v38, v36, 0);
+    BignumADC(v42, carry, v39, 0, carry);
+    v43 = v40 + 0 + carry;
+    r->w[0] = v41;
+    r->w[1] = v42;
+    r->w[2] = v43;
+}
+
+static void bigval_final_reduce(bigval *n)
+{
+    BignumInt v0, v1, v2, v3, v4, v6, v7, v8, v9, v10, v12, v13, v14, v15;
+    BignumCarry carry;
+
+    v0 = n->w[0];
+    v1 = n->w[1];
+    v2 = n->w[2];
+    v3 = (v2) >> 2;
+    v4 = 5 * v3;
+    BignumADC(v6, carry, v0, v4, 0);
+    (void)v6;
+    BignumADC(v7, carry, v1, 0, carry);
+    (void)v7;
+    v8 = v2 + 0 + carry;
+    v9 = (v8) >> 2;
+    v10 = 5 * v9;
+    BignumADC(v12, carry, v0, v10, 0);
+    BignumADC(v13, carry, v1, 0, carry);
+    v14 = v2 + 0 + carry;
+    v15 = (v14) & ((((BignumInt)1) << 2)-1);
+    n->w[0] = v12;
+    n->w[1] = v13;
+    n->w[2] = v15;
+}
+
+#else
+#error Add another bit count to contrib/make1305.py and rerun it
+#endif
+
+struct poly1305 {
+    unsigned char nonce[16];
+    bigval r;
+    bigval h;
+
+    /* Buffer in case we get less that a multiple of 16 bytes */
+    unsigned char buffer[16];
+    int bufferIndex;
+};
+
+static void poly1305_init(struct poly1305 *ctx)
+{
+    memset(ctx->nonce, 0, 16);
+    ctx->bufferIndex = 0;
+    bigval_clear(&ctx->h);
+}
+
+/* Takes a 256 bit key */
+static void poly1305_key(struct poly1305 *ctx, const unsigned char *key)
+{
+    unsigned char key_copy[16];
+    memcpy(key_copy, key, 16);
+
+    /* Key the MAC itself
+     * bytes 4, 8, 12 and 16 are required to have their top four bits clear */
+    key_copy[3] &= 0x0f;
+    key_copy[7] &= 0x0f;
+    key_copy[11] &= 0x0f;
+    key_copy[15] &= 0x0f;
+    /* bytes 5, 9 and 13 are required to have their bottom two bits clear */
+    key_copy[4] &= 0xfc;
+    key_copy[8] &= 0xfc;
+    key_copy[12] &= 0xfc;
+    bigval_import_le(&ctx->r, key_copy, 16);
+    smemclr(key_copy, sizeof(key_copy));
+
+    /* Use second 128 bits are the nonce */
+    memcpy(ctx->nonce, key+16, 16);
+}
+
+/* Feed up to 16 bytes (should only be less for the last chunk) */
+static void poly1305_feed_chunk(struct poly1305 *ctx,
+                                const unsigned char *chunk, int len)
+{
+    bigval c;
+    bigval_import_le(&c, chunk, len);
+    c.w[len / BIGNUM_INT_BYTES] |=
+        (BignumInt)1 << (8 * (len % BIGNUM_INT_BYTES));
+    bigval_add(&c, &c, &ctx->h);
+    bigval_mul_mod_p(&ctx->h, &c, &ctx->r);
+}
+
+static void poly1305_feed(struct poly1305 *ctx,
+                          const unsigned char *buf, int len)
+{
+    /* Check for stuff left in the buffer from last time */
+    if (ctx->bufferIndex) {
+        /* Try to fill up to 16 */
+        while (ctx->bufferIndex < 16 && len) {
+            ctx->buffer[ctx->bufferIndex++] = *buf++;
+            --len;
+        }
+        if (ctx->bufferIndex == 16) {
+            poly1305_feed_chunk(ctx, ctx->buffer, 16);
+            ctx->bufferIndex = 0;
+        }
+    }
+
+    /* Process 16 byte whole chunks */
+    while (len >= 16) {
+        poly1305_feed_chunk(ctx, buf, 16);
+        len -= 16;
+        buf += 16;
+    }
+
+    /* Cache stuff that's left over */
+    if (len) {
+        memcpy(ctx->buffer, buf, len);
+        ctx->bufferIndex = len;
+    }
+}
+
+/* Finalise and populate buffer with 16 byte with MAC */
+static void poly1305_finalise(struct poly1305 *ctx, unsigned char *mac)
+{
+    bigval tmp;
+
+    if (ctx->bufferIndex) {
+        poly1305_feed_chunk(ctx, ctx->buffer, ctx->bufferIndex);
+    }
+
+    bigval_import_le(&tmp, ctx->nonce, 16);
+    bigval_final_reduce(&ctx->h);
+    bigval_add(&tmp, &tmp, &ctx->h);
+    bigval_export_le(&tmp, mac, 16);
+}
+
+/* SSH-2 wrapper */
+
+struct ccp_context {
+    struct chacha20 a_cipher; /* Used for length */
+    struct chacha20 b_cipher; /* Used for content */
+
+    /* Cache of the first 4 bytes because they are the sequence number */
+    /* Kept in 8 bytes with the top as zero to allow easy passing to setiv */
+    int mac_initialised; /* Where we have got to in filling mac_iv */
+    unsigned char mac_iv[8];
+
+    struct poly1305 mac;
+};
+
+static void *poly_make_context(void *ctx)
+{
+    return ctx;
+}
+
+static void poly_free_context(void *ctx)
+{
+    /* Not allocated, just forwarded, no need to free */
+}
+
+static void poly_setkey(void *ctx, unsigned char *key)
+{
+    /* Uses the same context as ChaCha20, so ignore */
+}
+
+static void poly_start(void *handle)
+{
+    struct ccp_context *ctx = (struct ccp_context *)handle;
+
+    ctx->mac_initialised = 0;
+    memset(ctx->mac_iv, 0, 8);
+    poly1305_init(&ctx->mac);
+}
+
+static void poly_bytes(void *handle, unsigned char const *blk, int len)
+{
+    struct ccp_context *ctx = (struct ccp_context *)handle;
+
+    /* First 4 bytes are the IV */
+    while (ctx->mac_initialised < 4 && len) {
+        ctx->mac_iv[7 - ctx->mac_initialised] = *blk++;
+        ++ctx->mac_initialised;
+        --len;
+    }
+
+    /* Initialise the IV if needed */
+    if (ctx->mac_initialised == 4) {
+        chacha20_iv(&ctx->b_cipher, ctx->mac_iv);
+        ++ctx->mac_initialised;  /* Don't do it again */
+
+        /* Do first rotation */
+        chacha20_round(&ctx->b_cipher);
+
+        /* Set the poly key */
+        poly1305_key(&ctx->mac, ctx->b_cipher.current);
+
+        /* Set the first round as used */
+        ctx->b_cipher.currentIndex = 64;
+    }
+
+    /* Update the MAC with anything left */
+    if (len) {
+        poly1305_feed(&ctx->mac, blk, len);
+    }
+}
+
+static void poly_genresult(void *handle, unsigned char *blk)
+{
+    struct ccp_context *ctx = (struct ccp_context *)handle;
+    poly1305_finalise(&ctx->mac, blk);
+}
+
+static int poly_verresult(void *handle, unsigned char const *blk)
+{
+    struct ccp_context *ctx = (struct ccp_context *)handle;
+    int res;
+    unsigned char mac[16];
+    poly1305_finalise(&ctx->mac, mac);
+    res = smemeq(blk, mac, 16);
+    return res;
+}
+
+/* The generic poly operation used before generate and verify */
+static void poly_op(void *handle, unsigned char *blk, int len, unsigned long seq)
+{
+    unsigned char iv[4];
+    poly_start(handle);
+    PUT_32BIT_MSB_FIRST(iv, seq);
+    /* poly_bytes expects the first 4 bytes to be the IV */
+    poly_bytes(handle, iv, 4);
+    smemclr(iv, sizeof(iv));
+    poly_bytes(handle, blk, len);
+}
+
+static void poly_generate(void *handle, unsigned char *blk, int len, unsigned long seq)
+{
+    poly_op(handle, blk, len, seq);
+    poly_genresult(handle, blk+len);
+}
+
+static int poly_verify(void *handle, unsigned char *blk, int len, unsigned long seq)
+{
+    poly_op(handle, blk, len, seq);
+    return poly_verresult(handle, blk+len);
+}
+
+static const struct ssh_mac ssh2_poly1305 = {
+    poly_make_context, poly_free_context,
+    poly_setkey,
+
+    /* whole-packet operations */
+    poly_generate, poly_verify,
+
+    /* partial-packet operations */
+    poly_start, poly_bytes, poly_genresult, poly_verresult,
+
+    "", "", /* Not selectable individually, just part of ChaCha20-Poly1305 */
+    16, 0, "Poly1305"
+};
+
+static void *ccp_make_context(void)
+{
+    struct ccp_context *ctx = snew(struct ccp_context);
+    if (ctx) {
+        poly1305_init(&ctx->mac);
+    }
+    return ctx;
+}
+
+static void ccp_free_context(void *vctx)
+{
+    struct ccp_context *ctx = (struct ccp_context *)vctx;
+    smemclr(&ctx->a_cipher, sizeof(ctx->a_cipher));
+    smemclr(&ctx->b_cipher, sizeof(ctx->b_cipher));
+    smemclr(&ctx->mac, sizeof(ctx->mac));
+    sfree(ctx);
+}
+
+static void ccp_iv(void *vctx, unsigned char *iv)
+{
+    /* struct ccp_context *ctx = (struct ccp_context *)vctx; */
+    /* IV is set based on the sequence number */
+}
+
+static void ccp_key(void *vctx, unsigned char *key)
+{
+    struct ccp_context *ctx = (struct ccp_context *)vctx;
+    /* Initialise the a_cipher (for decrypting lengths) with the first 256 bits */
+    chacha20_key(&ctx->a_cipher, key + 32);
+    /* Initialise the b_cipher (for content and MAC) with the second 256 bits */
+    chacha20_key(&ctx->b_cipher, key);
+}
+
+static void ccp_encrypt(void *vctx, unsigned char *blk, int len)
+{
+    struct ccp_context *ctx = (struct ccp_context *)vctx;
+    chacha20_encrypt(&ctx->b_cipher, blk, len);
+}
+
+static void ccp_decrypt(void *vctx, unsigned char *blk, int len)
+{
+    struct ccp_context *ctx = (struct ccp_context *)vctx;
+    chacha20_decrypt(&ctx->b_cipher, blk, len);
+}
+
+static void ccp_length_op(struct ccp_context *ctx, unsigned char *blk, int len,
+                          unsigned long seq)
+{
+    unsigned char iv[8];
+    /*
+     * According to RFC 4253 (section 6.4), the packet sequence number wraps
+     * at 2^32, so its 32 high-order bits will always be zero.
+     */
+    PUT_32BIT_LSB_FIRST(iv, 0);
+    PUT_32BIT_LSB_FIRST(iv + 4, seq);
+    chacha20_iv(&ctx->a_cipher, iv);
+    chacha20_iv(&ctx->b_cipher, iv);
+    /* Reset content block count to 1, as the first is the key for Poly1305 */
+    ++ctx->b_cipher.state[12];
+    smemclr(iv, sizeof(iv));
+}
+
+static void ccp_encrypt_length(void *vctx, unsigned char *blk, int len,
+                               unsigned long seq)
+{
+    struct ccp_context *ctx = (struct ccp_context *)vctx;
+    ccp_length_op(ctx, blk, len, seq);
+    chacha20_encrypt(&ctx->a_cipher, blk, len);
+}
+
+static void ccp_decrypt_length(void *vctx, unsigned char *blk, int len,
+                               unsigned long seq)
+{
+    struct ccp_context *ctx = (struct ccp_context *)vctx;
+    ccp_length_op(ctx, blk, len, seq);
+    chacha20_decrypt(&ctx->a_cipher, blk, len);
+}
+
+static const struct ssh2_cipher ssh2_chacha20_poly1305 = {
+
+    ccp_make_context,
+    ccp_free_context,
+    ccp_iv,
+    ccp_key,
+    ccp_encrypt,
+    ccp_decrypt,
+    ccp_encrypt_length,
+    ccp_decrypt_length,
+
+    "chacha20-poly1305@openssh.com",
+    1, 512, 64, SSH_CIPHER_SEPARATE_LENGTH, "ChaCha20",
+
+    &ssh2_poly1305
+};
+
+static const struct ssh2_cipher *const ccp_list[] = {
+    &ssh2_chacha20_poly1305
+};
+
+const struct ssh2_ciphers ssh2_ccp = {
+    sizeof(ccp_list) / sizeof(*ccp_list),
+    ccp_list
+};
index f54990e7ceac2f359b1de1cfe90fd590ad397a95..13487fcd79bdb704451eb2f296af77dc6b2f126e 100644 (file)
--- a/sshdes.c
+++ b/sshdes.c
@@ -947,16 +947,18 @@ void des_decrypt_xdmauth(const unsigned char *keydata,
 
 static const struct ssh2_cipher ssh_3des_ssh2 = {
     des3_make_context, des3_free_context, des3_iv, des3_key,
-    des3_ssh2_encrypt_blk, des3_ssh2_decrypt_blk,
+    des3_ssh2_encrypt_blk, des3_ssh2_decrypt_blk, NULL, NULL,
     "3des-cbc",
-    8, 168, SSH_CIPHER_IS_CBC, "triple-DES CBC"
+    8, 168, 24, SSH_CIPHER_IS_CBC, "triple-DES CBC",
+    NULL
 };
 
 static const struct ssh2_cipher ssh_3des_ssh2_ctr = {
     des3_make_context, des3_free_context, des3_iv, des3_key,
-    des3_ssh2_sdctr, des3_ssh2_sdctr,
+    des3_ssh2_sdctr, des3_ssh2_sdctr, NULL, NULL,
     "3des-ctr",
-    8, 168, 0, "triple-DES SDCTR"
+    8, 168, 24, 0, "triple-DES SDCTR",
+    NULL
 };
 
 /*
@@ -969,16 +971,18 @@ static const struct ssh2_cipher ssh_3des_ssh2_ctr = {
  */
 static const struct ssh2_cipher ssh_des_ssh2 = {
     des_make_context, des3_free_context, des3_iv, des_key,
-    des_ssh2_encrypt_blk, des_ssh2_decrypt_blk,
+    des_ssh2_encrypt_blk, des_ssh2_decrypt_blk, NULL, NULL,
     "des-cbc",
-    8, 56, SSH_CIPHER_IS_CBC, "single-DES CBC"
+    8, 56, 8, SSH_CIPHER_IS_CBC, "single-DES CBC",
+    NULL
 };
 
 static const struct ssh2_cipher ssh_des_sshcom_ssh2 = {
     des_make_context, des3_free_context, des3_iv, des_key,
-    des_ssh2_encrypt_blk, des_ssh2_decrypt_blk,
+    des_ssh2_encrypt_blk, des_ssh2_decrypt_blk, NULL, NULL,
     "des-cbc@ssh.com",
-    8, 56, SSH_CIPHER_IS_CBC, "single-DES CBC"
+    8, 56, 8, SSH_CIPHER_IS_CBC, "single-DES CBC",
+    NULL
 };
 
 static const struct ssh2_cipher *const des3_list[] = {
diff --git a/sshdh.c b/sshdh.c
index 8f8ab2d26aa6ef9ad7e5fe74710edca473272245..f254bc1de7c3a255f03c53ced7bc9fb5acb31b0c 100644 (file)
--- a/sshdh.c
+++ b/sshdh.c
@@ -50,9 +50,18 @@ static const unsigned char P14[] = {
  */
 static const unsigned char G[] = { 2 };
 
+struct dh_extra {
+    const unsigned char *pdata, *gdata; /* NULL means group exchange */
+    int plen, glen;
+};
+
+static const struct dh_extra extra_group1 = {
+    P1, G, lenof(P1), lenof(G),
+};
+
 static const struct ssh_kex ssh_diffiehellman_group1_sha1 = {
     "diffie-hellman-group1-sha1", "group1",
-    KEXTYPE_DH, P1, G, lenof(P1), lenof(G), &ssh_sha1
+    KEXTYPE_DH, &ssh_sha1, &extra_group1,
 };
 
 static const struct ssh_kex *const group1_list[] = {
@@ -64,9 +73,13 @@ const struct ssh_kexes ssh_diffiehellman_group1 = {
     group1_list
 };
 
+static const struct dh_extra extra_group14 = {
+    P14, G, lenof(P14), lenof(G),
+};
+
 static const struct ssh_kex ssh_diffiehellman_group14_sha1 = {
     "diffie-hellman-group14-sha1", "group14",
-    KEXTYPE_DH, P14, G, lenof(P14), lenof(G), &ssh_sha1
+    KEXTYPE_DH, &ssh_sha1, &extra_group14,
 };
 
 static const struct ssh_kex *const group14_list[] = {
@@ -78,14 +91,18 @@ const struct ssh_kexes ssh_diffiehellman_group14 = {
     group14_list
 };
 
+static const struct dh_extra extra_gex = {
+    NULL, NULL, 0, 0,
+};
+
 static const struct ssh_kex ssh_diffiehellman_gex_sha256 = {
     "diffie-hellman-group-exchange-sha256", NULL,
-    KEXTYPE_DH, NULL, NULL, 0, 0, &ssh_sha256
+    KEXTYPE_DH, &ssh_sha256, &extra_gex,
 };
 
 static const struct ssh_kex ssh_diffiehellman_gex_sha1 = {
     "diffie-hellman-group-exchange-sha1", NULL,
-    KEXTYPE_DH, NULL, NULL, 0, 0, &ssh_sha1
+    KEXTYPE_DH, &ssh_sha1, &extra_gex,
 };
 
 static const struct ssh_kex *const gex_list[] = {
@@ -115,14 +132,21 @@ static void dh_init(struct dh_ctx *ctx)
     ctx->x = ctx->e = NULL;
 }
 
+int dh_is_gex(const struct ssh_kex *kex)
+{
+    const struct dh_extra *extra = (const struct dh_extra *)kex->extra;
+    return extra->pdata == NULL;
+}
+
 /*
  * Initialise DH for a standard group.
  */
 void *dh_setup_group(const struct ssh_kex *kex)
 {
+    const struct dh_extra *extra = (const struct dh_extra *)kex->extra;
     struct dh_ctx *ctx = snew(struct dh_ctx);
-    ctx->p = bignum_from_bytes(kex->pdata, kex->plen);
-    ctx->g = bignum_from_bytes(kex->gdata, kex->glen);
+    ctx->p = bignum_from_bytes(extra->pdata, extra->plen);
+    ctx->g = bignum_from_bytes(extra->gdata, extra->glen);
     dh_init(ctx);
     return ctx;
 }
index 84fcdac799d76ce8dc7acca97641a55f3de9044a..20a5e7fa61247742a8d961a2858c213a74dbbbb1 100644 (file)
--- a/sshdss.c
+++ b/sshdss.c
@@ -37,7 +37,8 @@ static void sha512_mpint(SHA512_State * s, Bignum b)
     smemclr(lenbuf, sizeof(lenbuf));
 }
 
-static void getstring(char **data, int *datalen, char **p, int *length)
+static void getstring(const char **data, int *datalen,
+                      const char **p, int *length)
 {
     *p = NULL;
     if (*datalen < 4)
@@ -53,9 +54,9 @@ static void getstring(char **data, int *datalen, char **p, int *length)
     *data += *length;
     *datalen -= *length;
 }
-static Bignum getmp(char **data, int *datalen)
+static Bignum getmp(const char **data, int *datalen)
 {
-    char *p;
+    const char *p;
     int length;
     Bignum b;
 
@@ -64,18 +65,18 @@ static Bignum getmp(char **data, int *datalen)
        return NULL;
     if (p[0] & 0x80)
        return NULL;                   /* negative mp */
-    b = bignum_from_bytes((unsigned char *)p, length);
+    b = bignum_from_bytes((const unsigned char *)p, length);
     return b;
 }
 
-static Bignum get160(char **data, int *datalen)
+static Bignum get160(const char **data, int *datalen)
 {
     Bignum b;
 
     if (*datalen < 20)
         return NULL;
 
-    b = bignum_from_bytes((unsigned char *)*data, 20);
+    b = bignum_from_bytes((const unsigned char *)*data, 20);
     *data += 20;
     *datalen -= 20;
 
@@ -84,9 +85,10 @@ static Bignum get160(char **data, int *datalen)
 
 static void dss_freekey(void *key);    /* forward reference */
 
-static void *dss_newkey(char *data, int len)
+static void *dss_newkey(const struct ssh_signkey *self,
+                        const char *data, int len)
 {
-    char *p;
+    const char *p;
     int slen;
     struct dss_key *dss;
 
@@ -189,48 +191,11 @@ static char *dss_fmtkey(void *key)
     return p;
 }
 
-static char *dss_fingerprint(void *key)
-{
-    struct dss_key *dss = (struct dss_key *) key;
-    struct MD5Context md5c;
-    unsigned char digest[16], lenbuf[4];
-    char buffer[16 * 3 + 40];
-    char *ret;
-    int numlen, i;
-
-    MD5Init(&md5c);
-    MD5Update(&md5c, (unsigned char *)"\0\0\0\7ssh-dss", 11);
-
-#define ADD_BIGNUM(bignum) \
-    numlen = (bignum_bitcount(bignum)+8)/8; \
-    PUT_32BIT(lenbuf, numlen); MD5Update(&md5c, lenbuf, 4); \
-    for (i = numlen; i-- ;) { \
-        unsigned char c = bignum_byte(bignum, i); \
-        MD5Update(&md5c, &c, 1); \
-    }
-    ADD_BIGNUM(dss->p);
-    ADD_BIGNUM(dss->q);
-    ADD_BIGNUM(dss->g);
-    ADD_BIGNUM(dss->y);
-#undef ADD_BIGNUM
-
-    MD5Final(digest, &md5c);
-
-    sprintf(buffer, "ssh-dss %d ", bignum_bitcount(dss->p));
-    for (i = 0; i < 16; i++)
-       sprintf(buffer + strlen(buffer), "%s%02x", i ? ":" : "",
-               digest[i]);
-    ret = snewn(strlen(buffer) + 1, char);
-    if (ret)
-       strcpy(ret, buffer);
-    return ret;
-}
-
-static int dss_verifysig(void *key, char *sig, int siglen,
-                        char *data, int datalen)
+static int dss_verifysig(void *key, const char *sig, int siglen,
+                        const char *data, int datalen)
 {
     struct dss_key *dss = (struct dss_key *) key;
-    char *p;
+    const char *p;
     int slen;
     char hash[20];
     Bignum r, s, w, gu1p, yu2p, gu1yu2p, u1, u2, sha, v;
@@ -402,18 +367,19 @@ static unsigned char *dss_private_blob(void *key, int *len)
     return blob;
 }
 
-static void *dss_createkey(unsigned char *pub_blob, int pub_len,
-                          unsigned char *priv_blob, int priv_len)
+static void *dss_createkey(const struct ssh_signkey *self,
+                           const unsigned char *pub_blob, int pub_len,
+                          const unsigned char *priv_blob, int priv_len)
 {
     struct dss_key *dss;
-    char *pb = (char *) priv_blob;
-    char *hash;
+    const char *pb = (const char *) priv_blob;
+    const char *hash;
     int hashlen;
     SHA_State s;
     unsigned char digest[20];
     Bignum ytest;
 
-    dss = dss_newkey((char *) pub_blob, pub_len);
+    dss = dss_newkey(self, (char *) pub_blob, pub_len);
     if (!dss)
         return NULL;
     dss->x = getmp(&pb, &priv_len);
@@ -453,9 +419,10 @@ static void *dss_createkey(unsigned char *pub_blob, int pub_len,
     return dss;
 }
 
-static void *dss_openssh_createkey(unsigned char **blob, int *len)
+static void *dss_openssh_createkey(const struct ssh_signkey *self,
+                                   const unsigned char **blob, int *len)
 {
-    char **b = (char **) blob;
+    const char **b = (const char **) blob;
     struct dss_key *dss;
 
     dss = snew(struct dss_key);
@@ -504,12 +471,13 @@ static int dss_openssh_fmtkey(void *key, unsigned char *blob, int len)
     return bloblen;
 }
 
-static int dss_pubkey_bits(void *blob, int len)
+static int dss_pubkey_bits(const struct ssh_signkey *self,
+                           const void *blob, int len)
 {
     struct dss_key *dss;
     int ret;
 
-    dss = dss_newkey((char *) blob, len);
+    dss = dss_newkey(self, (const char *) blob, len);
     if (!dss)
         return -1;
     ret = bignum_bitcount(dss->p);
@@ -518,7 +486,8 @@ static int dss_pubkey_bits(void *blob, int len)
     return ret;
 }
 
-static unsigned char *dss_sign(void *key, char *data, int datalen, int *siglen)
+Bignum *dss_gen_k(const char *id_string, Bignum modulus, Bignum private_key,
+                  unsigned char *digest, int digest_len)
 {
     /*
      * The basic DSS signing algorithm is:
@@ -591,21 +560,16 @@ static unsigned char *dss_sign(void *key, char *data, int datalen, int *siglen)
      * Computer Security Group for helping to argue out all the
      * fine details.
      */
-    struct dss_key *dss = (struct dss_key *) key;
     SHA512_State ss;
-    unsigned char digest[20], digest512[64];
-    Bignum proto_k, k, gkp, hash, kinv, hxr, r, s;
-    unsigned char *bytes;
-    int nbytes, i;
-
-    SHA_Simple(data, datalen, digest);
+    unsigned char digest512[64];
+    Bignum proto_k, k;
 
     /*
      * Hash some identifying text plus x.
      */
     SHA512_Init(&ss);
-    SHA512_Bytes(&ss, "DSA deterministic k generator", 30);
-    sha512_mpint(&ss, dss->x);
+    SHA512_Bytes(&ss, id_string, strlen(id_string) + 1);
+    sha512_mpint(&ss, private_key);
     SHA512_Final(&ss, digest512);
 
     /*
@@ -613,7 +577,7 @@ static unsigned char *dss_sign(void *key, char *data, int datalen, int *siglen)
      */
     SHA512_Init(&ss);
     SHA512_Bytes(&ss, digest512, sizeof(digest512));
-    SHA512_Bytes(&ss, digest, sizeof(digest));
+    SHA512_Bytes(&ss, digest, digest_len);
 
     while (1) {
         SHA512_State ss2 = ss;         /* structure copy */
@@ -625,23 +589,38 @@ static unsigned char *dss_sign(void *key, char *data, int datalen, int *siglen)
          * Now convert the result into a bignum, and reduce it mod q.
          */
         proto_k = bignum_from_bytes(digest512, 64);
-        k = bigmod(proto_k, dss->q);
+        k = bigmod(proto_k, modulus);
         freebn(proto_k);
-        kinv = modinv(k, dss->q);             /* k^-1 mod q */
-        if (!kinv) {                           /* very unlikely */
-            freebn(k);
-            /* Perturb the hash to think of a different k. */
-            SHA512_Bytes(&ss, "x", 1);
-            /* Go round and try again. */
-            continue;
+
+        if (bignum_cmp(k, One) != 0 && bignum_cmp(k, Zero) != 0) {
+            smemclr(&ss, sizeof(ss));
+            smemclr(digest512, sizeof(digest512));
+            return k;
         }
 
-        break;
+        /* Very unlikely we get here, but if so, k was unsuitable. */
+        freebn(k);
+        /* Perturb the hash to think of a different k. */
+        SHA512_Bytes(&ss, "x", 1);
+        /* Go round and try again. */
     }
+}
 
-    smemclr(&ss, sizeof(ss));
+static unsigned char *dss_sign(void *key, const char *data, int datalen,
+                               int *siglen)
+{
+    struct dss_key *dss = (struct dss_key *) key;
+    Bignum k, gkp, hash, kinv, hxr, r, s;
+    unsigned char digest[20];
+    unsigned char *bytes;
+    int nbytes, i;
+
+    SHA_Simple(data, datalen, digest);
 
-    smemclr(digest512, sizeof(digest512));
+    k = dss_gen_k("DSA deterministic k generator", dss->q, dss->x,
+                  digest, sizeof(digest));
+    kinv = modinv(k, dss->q);         /* k^-1 mod q */
+    assert(kinv);
 
     /*
      * Now we have k, so just go ahead and compute the signature.
@@ -691,10 +670,11 @@ const struct ssh_signkey ssh_dss = {
     dss_createkey,
     dss_openssh_createkey,
     dss_openssh_fmtkey,
+    5 /* p,q,g,y,x */,
     dss_pubkey_bits,
-    dss_fingerprint,
     dss_verifysig,
     dss_sign,
     "ssh-dss",
-    "dss"
+    "dss",
+    NULL,
 };
diff --git a/sshecc.c b/sshecc.c
new file mode 100644 (file)
index 0000000..d62c9b9
--- /dev/null
+++ b/sshecc.c
@@ -0,0 +1,2964 @@
+/*
+ * Elliptic-curve crypto module for PuTTY
+ * Implements the three required curves, no optional curves
+ *
+ * NOTE: Only curves on prime field are handled by the maths functions
+ *       in Weierstrass form using Jacobian co-ordinates.
+ *
+ *       Montgomery form curves are supported for DH. (Curve25519)
+ *
+ *       Edwards form curves are supported for DSA. (Ed25519)
+ */
+
+/*
+ * References:
+ *
+ * Elliptic curves in SSH are specified in RFC 5656:
+ *   http://tools.ietf.org/html/rfc5656
+ *
+ * That specification delegates details of public key formatting and a
+ * lot of underlying mechanism to SEC 1:
+ *   http://www.secg.org/sec1-v2.pdf
+ *
+ * Montgomery maths from:
+ * Handbook of elliptic and hyperelliptic curve cryptography, Chapter 13
+ *   http://cs.ucsb.edu/~koc/ccs130h/2013/EllipticHyperelliptic-CohenFrey.pdf
+ *
+ * Curve25519 spec from libssh (with reference to other things in the
+ * libssh code):
+ *   https://git.libssh.org/users/aris/libssh.git/tree/doc/curve25519-sha256@libssh.org.txt
+ *
+ * Edwards DSA:
+ *   http://ed25519.cr.yp.to/ed25519-20110926.pdf
+ */
+
+#include <stdlib.h>
+#include <assert.h>
+
+#include "ssh.h"
+
+/* ----------------------------------------------------------------------
+ * Elliptic curve definitions
+ */
+
+static void initialise_wcurve(struct ec_curve *curve, int bits,
+                              const unsigned char *p,
+                              const unsigned char *a, const unsigned char *b,
+                              const unsigned char *n, const unsigned char *Gx,
+                              const unsigned char *Gy)
+{
+    int length = bits / 8;
+    if (bits % 8) ++length;
+
+    curve->type = EC_WEIERSTRASS;
+
+    curve->fieldBits = bits;
+    curve->p = bignum_from_bytes(p, length);
+
+    /* Curve co-efficients */
+    curve->w.a = bignum_from_bytes(a, length);
+    curve->w.b = bignum_from_bytes(b, length);
+
+    /* Group order and generator */
+    curve->w.n = bignum_from_bytes(n, length);
+    curve->w.G.x = bignum_from_bytes(Gx, length);
+    curve->w.G.y = bignum_from_bytes(Gy, length);
+    curve->w.G.curve = curve;
+    curve->w.G.infinity = 0;
+}
+
+static void initialise_mcurve(struct ec_curve *curve, int bits,
+                              const unsigned char *p,
+                              const unsigned char *a, const unsigned char *b,
+                              const unsigned char *Gx)
+{
+    int length = bits / 8;
+    if (bits % 8) ++length;
+
+    curve->type = EC_MONTGOMERY;
+
+    curve->fieldBits = bits;
+    curve->p = bignum_from_bytes(p, length);
+
+    /* Curve co-efficients */
+    curve->m.a = bignum_from_bytes(a, length);
+    curve->m.b = bignum_from_bytes(b, length);
+
+    /* Generator */
+    curve->m.G.x = bignum_from_bytes(Gx, length);
+    curve->m.G.y = NULL;
+    curve->m.G.z = NULL;
+    curve->m.G.curve = curve;
+    curve->m.G.infinity = 0;
+}
+
+static void initialise_ecurve(struct ec_curve *curve, int bits,
+                              const unsigned char *p,
+                              const unsigned char *l, const unsigned char *d,
+                              const unsigned char *Bx, const unsigned char *By)
+{
+    int length = bits / 8;
+    if (bits % 8) ++length;
+
+    curve->type = EC_EDWARDS;
+
+    curve->fieldBits = bits;
+    curve->p = bignum_from_bytes(p, length);
+
+    /* Curve co-efficients */
+    curve->e.l = bignum_from_bytes(l, length);
+    curve->e.d = bignum_from_bytes(d, length);
+
+    /* Group order and generator */
+    curve->e.B.x = bignum_from_bytes(Bx, length);
+    curve->e.B.y = bignum_from_bytes(By, length);
+    curve->e.B.curve = curve;
+    curve->e.B.infinity = 0;
+}
+
+static struct ec_curve *ec_p256(void)
+{
+    static struct ec_curve curve = { 0 };
+    static unsigned char initialised = 0;
+
+    if (!initialised)
+    {
+        static const unsigned char p[] = {
+            0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+        };
+        static const unsigned char a[] = {
+            0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc
+        };
+        static const unsigned char b[] = {
+            0x5a, 0xc6, 0x35, 0xd8, 0xaa, 0x3a, 0x93, 0xe7,
+            0xb3, 0xeb, 0xbd, 0x55, 0x76, 0x98, 0x86, 0xbc,
+            0x65, 0x1d, 0x06, 0xb0, 0xcc, 0x53, 0xb0, 0xf6,
+            0x3b, 0xce, 0x3c, 0x3e, 0x27, 0xd2, 0x60, 0x4b
+        };
+        static const unsigned char n[] = {
+            0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xbc, 0xe6, 0xfa, 0xad, 0xa7, 0x17, 0x9e, 0x84,
+            0xf3, 0xb9, 0xca, 0xc2, 0xfc, 0x63, 0x25, 0x51
+        };
+        static const unsigned char Gx[] = {
+            0x6b, 0x17, 0xd1, 0xf2, 0xe1, 0x2c, 0x42, 0x47,
+            0xf8, 0xbc, 0xe6, 0xe5, 0x63, 0xa4, 0x40, 0xf2,
+            0x77, 0x03, 0x7d, 0x81, 0x2d, 0xeb, 0x33, 0xa0,
+            0xf4, 0xa1, 0x39, 0x45, 0xd8, 0x98, 0xc2, 0x96
+        };
+        static const unsigned char Gy[] = {
+            0x4f, 0xe3, 0x42, 0xe2, 0xfe, 0x1a, 0x7f, 0x9b,
+            0x8e, 0xe7, 0xeb, 0x4a, 0x7c, 0x0f, 0x9e, 0x16,
+            0x2b, 0xce, 0x33, 0x57, 0x6b, 0x31, 0x5e, 0xce,
+            0xcb, 0xb6, 0x40, 0x68, 0x37, 0xbf, 0x51, 0xf5
+        };
+
+        initialise_wcurve(&curve, 256, p, a, b, n, Gx, Gy);
+        curve.textname = curve.name = "nistp256";
+
+        /* Now initialised, no need to do it again */
+        initialised = 1;
+    }
+
+    return &curve;
+}
+
+static struct ec_curve *ec_p384(void)
+{
+    static struct ec_curve curve = { 0 };
+    static unsigned char initialised = 0;
+
+    if (!initialised)
+    {
+        static const unsigned char p[] = {
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+            0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff
+        };
+        static const unsigned char a[] = {
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+            0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xfc
+        };
+        static const unsigned char b[] = {
+            0xb3, 0x31, 0x2f, 0xa7, 0xe2, 0x3e, 0xe7, 0xe4,
+            0x98, 0x8e, 0x05, 0x6b, 0xe3, 0xf8, 0x2d, 0x19,
+            0x18, 0x1d, 0x9c, 0x6e, 0xfe, 0x81, 0x41, 0x12,
+            0x03, 0x14, 0x08, 0x8f, 0x50, 0x13, 0x87, 0x5a,
+            0xc6, 0x56, 0x39, 0x8d, 0x8a, 0x2e, 0xd1, 0x9d,
+            0x2a, 0x85, 0xc8, 0xed, 0xd3, 0xec, 0x2a, 0xef
+        };
+        static const unsigned char n[] = {
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xc7, 0x63, 0x4d, 0x81, 0xf4, 0x37, 0x2d, 0xdf,
+            0x58, 0x1a, 0x0d, 0xb2, 0x48, 0xb0, 0xa7, 0x7a,
+            0xec, 0xec, 0x19, 0x6a, 0xcc, 0xc5, 0x29, 0x73
+        };
+        static const unsigned char Gx[] = {
+            0xaa, 0x87, 0xca, 0x22, 0xbe, 0x8b, 0x05, 0x37,
+            0x8e, 0xb1, 0xc7, 0x1e, 0xf3, 0x20, 0xad, 0x74,
+            0x6e, 0x1d, 0x3b, 0x62, 0x8b, 0xa7, 0x9b, 0x98,
+            0x59, 0xf7, 0x41, 0xe0, 0x82, 0x54, 0x2a, 0x38,
+            0x55, 0x02, 0xf2, 0x5d, 0xbf, 0x55, 0x29, 0x6c,
+            0x3a, 0x54, 0x5e, 0x38, 0x72, 0x76, 0x0a, 0xb7
+        };
+        static const unsigned char Gy[] = {
+            0x36, 0x17, 0xde, 0x4a, 0x96, 0x26, 0x2c, 0x6f,
+            0x5d, 0x9e, 0x98, 0xbf, 0x92, 0x92, 0xdc, 0x29,
+            0xf8, 0xf4, 0x1d, 0xbd, 0x28, 0x9a, 0x14, 0x7c,
+            0xe9, 0xda, 0x31, 0x13, 0xb5, 0xf0, 0xb8, 0xc0,
+            0x0a, 0x60, 0xb1, 0xce, 0x1d, 0x7e, 0x81, 0x9d,
+            0x7a, 0x43, 0x1d, 0x7c, 0x90, 0xea, 0x0e, 0x5f
+        };
+
+        initialise_wcurve(&curve, 384, p, a, b, n, Gx, Gy);
+        curve.textname = curve.name = "nistp384";
+
+        /* Now initialised, no need to do it again */
+        initialised = 1;
+    }
+
+    return &curve;
+}
+
+static struct ec_curve *ec_p521(void)
+{
+    static struct ec_curve curve = { 0 };
+    static unsigned char initialised = 0;
+
+    if (!initialised)
+    {
+        static const unsigned char p[] = {
+            0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff
+        };
+        static const unsigned char a[] = {
+            0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xfc
+        };
+        static const unsigned char b[] = {
+            0x00, 0x51, 0x95, 0x3e, 0xb9, 0x61, 0x8e, 0x1c,
+            0x9a, 0x1f, 0x92, 0x9a, 0x21, 0xa0, 0xb6, 0x85,
+            0x40, 0xee, 0xa2, 0xda, 0x72, 0x5b, 0x99, 0xb3,
+            0x15, 0xf3, 0xb8, 0xb4, 0x89, 0x91, 0x8e, 0xf1,
+            0x09, 0xe1, 0x56, 0x19, 0x39, 0x51, 0xec, 0x7e,
+            0x93, 0x7b, 0x16, 0x52, 0xc0, 0xbd, 0x3b, 0xb1,
+            0xbf, 0x07, 0x35, 0x73, 0xdf, 0x88, 0x3d, 0x2c,
+            0x34, 0xf1, 0xef, 0x45, 0x1f, 0xd4, 0x6b, 0x50,
+            0x3f, 0x00
+        };
+        static const unsigned char n[] = {
+            0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xfa, 0x51, 0x86, 0x87, 0x83, 0xbf, 0x2f,
+            0x96, 0x6b, 0x7f, 0xcc, 0x01, 0x48, 0xf7, 0x09,
+            0xa5, 0xd0, 0x3b, 0xb5, 0xc9, 0xb8, 0x89, 0x9c,
+            0x47, 0xae, 0xbb, 0x6f, 0xb7, 0x1e, 0x91, 0x38,
+            0x64, 0x09
+        };
+        static const unsigned char Gx[] = {
+            0x00, 0xc6, 0x85, 0x8e, 0x06, 0xb7, 0x04, 0x04,
+            0xe9, 0xcd, 0x9e, 0x3e, 0xcb, 0x66, 0x23, 0x95,
+            0xb4, 0x42, 0x9c, 0x64, 0x81, 0x39, 0x05, 0x3f,
+            0xb5, 0x21, 0xf8, 0x28, 0xaf, 0x60, 0x6b, 0x4d,
+            0x3d, 0xba, 0xa1, 0x4b, 0x5e, 0x77, 0xef, 0xe7,
+            0x59, 0x28, 0xfe, 0x1d, 0xc1, 0x27, 0xa2, 0xff,
+            0xa8, 0xde, 0x33, 0x48, 0xb3, 0xc1, 0x85, 0x6a,
+            0x42, 0x9b, 0xf9, 0x7e, 0x7e, 0x31, 0xc2, 0xe5,
+            0xbd, 0x66
+        };
+        static const unsigned char Gy[] = {
+            0x01, 0x18, 0x39, 0x29, 0x6a, 0x78, 0x9a, 0x3b,
+            0xc0, 0x04, 0x5c, 0x8a, 0x5f, 0xb4, 0x2c, 0x7d,
+            0x1b, 0xd9, 0x98, 0xf5, 0x44, 0x49, 0x57, 0x9b,
+            0x44, 0x68, 0x17, 0xaf, 0xbd, 0x17, 0x27, 0x3e,
+            0x66, 0x2c, 0x97, 0xee, 0x72, 0x99, 0x5e, 0xf4,
+            0x26, 0x40, 0xc5, 0x50, 0xb9, 0x01, 0x3f, 0xad,
+            0x07, 0x61, 0x35, 0x3c, 0x70, 0x86, 0xa2, 0x72,
+            0xc2, 0x40, 0x88, 0xbe, 0x94, 0x76, 0x9f, 0xd1,
+            0x66, 0x50
+        };
+
+        initialise_wcurve(&curve, 521, p, a, b, n, Gx, Gy);
+        curve.textname = curve.name = "nistp521";
+
+        /* Now initialised, no need to do it again */
+        initialised = 1;
+    }
+
+    return &curve;
+}
+
+static struct ec_curve *ec_curve25519(void)
+{
+    static struct ec_curve curve = { 0 };
+    static unsigned char initialised = 0;
+
+    if (!initialised)
+    {
+        static const unsigned char p[] = {
+            0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xed
+        };
+        static const unsigned char a[] = {
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x6d, 0x06
+        };
+        static const unsigned char b[] = {
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
+        };
+        static const unsigned char gx[32] = {
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09
+        };
+
+        initialise_mcurve(&curve, 256, p, a, b, gx);
+        /* This curve doesn't need a name, because it's never used in
+         * any format that embeds the curve name */
+        curve.name = NULL;
+        curve.textname = "Curve25519";
+
+        /* Now initialised, no need to do it again */
+        initialised = 1;
+    }
+
+    return &curve;
+}
+
+static struct ec_curve *ec_ed25519(void)
+{
+    static struct ec_curve curve = { 0 };
+    static unsigned char initialised = 0;
+
+    if (!initialised)
+    {
+        static const unsigned char q[] = {
+            0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xed
+        };
+        static const unsigned char l[32] = {
+            0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x14, 0xde, 0xf9, 0xde, 0xa2, 0xf7, 0x9c, 0xd6,
+            0x58, 0x12, 0x63, 0x1a, 0x5c, 0xf5, 0xd3, 0xed
+        };
+        static const unsigned char d[32] = {
+            0x52, 0x03, 0x6c, 0xee, 0x2b, 0x6f, 0xfe, 0x73,
+            0x8c, 0xc7, 0x40, 0x79, 0x77, 0x79, 0xe8, 0x98,
+            0x00, 0x70, 0x0a, 0x4d, 0x41, 0x41, 0xd8, 0xab,
+            0x75, 0xeb, 0x4d, 0xca, 0x13, 0x59, 0x78, 0xa3
+        };
+        static const unsigned char Bx[32] = {
+            0x21, 0x69, 0x36, 0xd3, 0xcd, 0x6e, 0x53, 0xfe,
+            0xc0, 0xa4, 0xe2, 0x31, 0xfd, 0xd6, 0xdc, 0x5c,
+            0x69, 0x2c, 0xc7, 0x60, 0x95, 0x25, 0xa7, 0xb2,
+            0xc9, 0x56, 0x2d, 0x60, 0x8f, 0x25, 0xd5, 0x1a
+        };
+        static const unsigned char By[32] = {
+            0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
+            0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
+            0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
+            0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x58
+        };
+
+        /* This curve doesn't need a name, because it's never used in
+         * any format that embeds the curve name */
+        curve.name = NULL;
+
+        initialise_ecurve(&curve, 256, q, l, d, Bx, By);
+        curve.textname = "Ed25519";
+
+        /* Now initialised, no need to do it again */
+        initialised = 1;
+    }
+
+    return &curve;
+}
+
+/* Return 1 if a is -3 % p, otherwise return 0
+ * This is used because there are some maths optimisations */
+static int ec_aminus3(const struct ec_curve *curve)
+{
+    int ret;
+    Bignum _p;
+
+    if (curve->type != EC_WEIERSTRASS) {
+        return 0;
+    }
+
+    _p = bignum_add_long(curve->w.a, 3);
+
+    ret = !bignum_cmp(curve->p, _p);
+    freebn(_p);
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Elliptic curve field maths
+ */
+
+static Bignum ecf_add(const Bignum a, const Bignum b,
+                      const struct ec_curve *curve)
+{
+    Bignum a1, b1, ab, ret;
+
+    a1 = bigmod(a, curve->p);
+    b1 = bigmod(b, curve->p);
+
+    ab = bigadd(a1, b1);
+    freebn(a1);
+    freebn(b1);
+
+    ret = bigmod(ab, curve->p);
+    freebn(ab);
+
+    return ret;
+}
+
+static Bignum ecf_square(const Bignum a, const struct ec_curve *curve)
+{
+    return modmul(a, a, curve->p);
+}
+
+static Bignum ecf_treble(const Bignum a, const struct ec_curve *curve)
+{
+    Bignum ret, tmp;
+
+    /* Double */
+    tmp = bignum_lshift(a, 1);
+
+    /* Add itself (i.e. treble) */
+    ret = bigadd(tmp, a);
+    freebn(tmp);
+
+    /* Normalise */
+    while (bignum_cmp(ret, curve->p) >= 0)
+    {
+        tmp = bigsub(ret, curve->p);
+        assert(tmp);
+        freebn(ret);
+        ret = tmp;
+    }
+
+    return ret;
+}
+
+static Bignum ecf_double(const Bignum a, const struct ec_curve *curve)
+{
+    Bignum ret = bignum_lshift(a, 1);
+    if (bignum_cmp(ret, curve->p) >= 0)
+    {
+        Bignum tmp = bigsub(ret, curve->p);
+        assert(tmp);
+        freebn(ret);
+        return tmp;
+    }
+    else
+    {
+        return ret;
+    }
+}
+
+/* ----------------------------------------------------------------------
+ * Memory functions
+ */
+
+void ec_point_free(struct ec_point *point)
+{
+    if (point == NULL) return;
+    point->curve = 0;
+    if (point->x) freebn(point->x);
+    if (point->y) freebn(point->y);
+    if (point->z) freebn(point->z);
+    point->infinity = 0;
+    sfree(point);
+}
+
+static struct ec_point *ec_point_new(const struct ec_curve *curve,
+                                     const Bignum x, const Bignum y, const Bignum z,
+                                     unsigned char infinity)
+{
+    struct ec_point *point = snewn(1, struct ec_point);
+    point->curve = curve;
+    point->x = x;
+    point->y = y;
+    point->z = z;
+    point->infinity = infinity ? 1 : 0;
+    return point;
+}
+
+static struct ec_point *ec_point_copy(const struct ec_point *a)
+{
+    if (a == NULL) return NULL;
+    return ec_point_new(a->curve,
+                        a->x ? copybn(a->x) : NULL,
+                        a->y ? copybn(a->y) : NULL,
+                        a->z ? copybn(a->z) : NULL,
+                        a->infinity);
+}
+
+static int ec_point_verify(const struct ec_point *a)
+{
+    if (a->infinity) {
+        return 1;
+    } else if (a->curve->type == EC_EDWARDS) {
+        /* Check y^2 - x^2 - 1 - d * x^2 * y^2 == 0 */
+        Bignum y2, x2, tmp, tmp2, tmp3;
+        int ret;
+
+        y2 = ecf_square(a->y, a->curve);
+        x2 = ecf_square(a->x, a->curve);
+        tmp = modmul(a->curve->e.d, x2, a->curve->p);
+        tmp2 = modmul(tmp, y2, a->curve->p);
+        freebn(tmp);
+        tmp = modsub(y2, x2, a->curve->p);
+        freebn(y2);
+        freebn(x2);
+        tmp3 = modsub(tmp, tmp2, a->curve->p);
+        freebn(tmp);
+        freebn(tmp2);
+        ret = !bignum_cmp(tmp3, One);
+        freebn(tmp3);
+        return ret;
+    } else if (a->curve->type == EC_WEIERSTRASS) {
+        /* Verify y^2 = x^3 + ax + b */
+        int ret = 0;
+
+        Bignum lhs = NULL, x3 = NULL, ax = NULL, x3ax = NULL, x3axm = NULL, x3axb = NULL, rhs = NULL;
+
+        Bignum Three = bignum_from_long(3);
+
+        lhs = modmul(a->y, a->y, a->curve->p);
+
+        /* This uses montgomery multiplication to optimise */
+        x3 = modpow(a->x, Three, a->curve->p);
+        freebn(Three);
+        ax = modmul(a->curve->w.a, a->x, a->curve->p);
+        x3ax = bigadd(x3, ax);
+        freebn(x3); x3 = NULL;
+        freebn(ax); ax = NULL;
+        x3axm = bigmod(x3ax, a->curve->p);
+        freebn(x3ax); x3ax = NULL;
+        x3axb = bigadd(x3axm, a->curve->w.b);
+        freebn(x3axm); x3axm = NULL;
+        rhs = bigmod(x3axb, a->curve->p);
+        freebn(x3axb);
+
+        ret = bignum_cmp(lhs, rhs) ? 0 : 1;
+        freebn(lhs);
+        freebn(rhs);
+
+        return ret;
+    } else {
+        return 0;
+    }
+}
+
+/* ----------------------------------------------------------------------
+ * Elliptic curve point maths
+ */
+
+/* Returns 1 on success and 0 on memory error */
+static int ecp_normalise(struct ec_point *a)
+{
+    if (!a) {
+        /* No point */
+        return 0;
+    }
+
+    if (a->infinity) {
+        /* Point is at infinity - i.e. normalised */
+        return 1;
+    }
+
+    if (a->curve->type == EC_WEIERSTRASS) {
+        /* In Jacobian Coordinates the triple (X, Y, Z) represents
+           the affine point (X / Z^2, Y / Z^3) */
+
+        Bignum Z2, Z2inv, Z3, Z3inv, tx, ty;
+
+        if (!a->x || !a->y) {
+            /* No point defined */
+            return 0;
+        } else if (!a->z) {
+            /* Already normalised */
+            return 1;
+        }
+
+        Z2 = ecf_square(a->z, a->curve);
+        Z2inv = modinv(Z2, a->curve->p);
+        if (!Z2inv) {
+            freebn(Z2);
+            return 0;
+        }
+        tx = modmul(a->x, Z2inv, a->curve->p);
+        freebn(Z2inv);
+
+        Z3 = modmul(Z2, a->z, a->curve->p);
+        freebn(Z2);
+        Z3inv = modinv(Z3, a->curve->p);
+        freebn(Z3);
+        if (!Z3inv) {
+            freebn(tx);
+            return 0;
+        }
+        ty = modmul(a->y, Z3inv, a->curve->p);
+        freebn(Z3inv);
+
+        freebn(a->x);
+        a->x = tx;
+        freebn(a->y);
+        a->y = ty;
+        freebn(a->z);
+        a->z = NULL;
+        return 1;
+    } else if (a->curve->type == EC_MONTGOMERY) {
+        /* In Montgomery (X : Z) represents the x co-ord (X / Z, ?) */
+
+        Bignum tmp, tmp2;
+
+        if (!a->x) {
+            /* No point defined */
+            return 0;
+        } else if (!a->z) {
+            /* Already normalised */
+            return 1;
+        }
+
+        tmp = modinv(a->z, a->curve->p);
+        if (!tmp) {
+            return 0;
+        }
+        tmp2 = modmul(a->x, tmp, a->curve->p);
+        freebn(tmp);
+
+        freebn(a->z);
+        a->z = NULL;
+        freebn(a->x);
+        a->x = tmp2;
+        return 1;
+    } else if (a->curve->type == EC_EDWARDS) {
+        /* Always normalised */
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+static struct ec_point *ecp_doublew(const struct ec_point *a, const int aminus3)
+{
+    Bignum S, M, outx, outy, outz;
+
+    if (bignum_cmp(a->y, Zero) == 0)
+    {
+        /* Identity */
+        return ec_point_new(a->curve, NULL, NULL, NULL, 1);
+    }
+
+    /* S = 4*X*Y^2 */
+    {
+        Bignum Y2, XY2, _2XY2;
+
+        Y2 = ecf_square(a->y, a->curve);
+        XY2 = modmul(a->x, Y2, a->curve->p);
+        freebn(Y2);
+
+        _2XY2 = ecf_double(XY2, a->curve);
+        freebn(XY2);
+        S = ecf_double(_2XY2, a->curve);
+        freebn(_2XY2);
+    }
+
+    /* Faster calculation if a = -3 */
+    if (aminus3) {
+        /* if a = -3, then M can also be calculated as M = 3*(X + Z^2)*(X - Z^2) */
+        Bignum Z2, XpZ2, XmZ2, second;
+
+        if (a->z == NULL) {
+            Z2 = copybn(One);
+        } else {
+            Z2 = ecf_square(a->z, a->curve);
+        }
+
+        XpZ2 = ecf_add(a->x, Z2, a->curve);
+        XmZ2 = modsub(a->x, Z2, a->curve->p);
+        freebn(Z2);
+
+        second = modmul(XpZ2, XmZ2, a->curve->p);
+        freebn(XpZ2);
+        freebn(XmZ2);
+
+        M = ecf_treble(second, a->curve);
+        freebn(second);
+    } else {
+        /* M = 3*X^2 + a*Z^4 */
+        Bignum _3X2, X2, aZ4;
+
+        if (a->z == NULL) {
+            aZ4 = copybn(a->curve->w.a);
+        } else {
+            Bignum Z2, Z4;
+
+            Z2 = ecf_square(a->z, a->curve);
+            Z4 = ecf_square(Z2, a->curve);
+            freebn(Z2);
+            aZ4 = modmul(a->curve->w.a, Z4, a->curve->p);
+            freebn(Z4);
+        }
+
+        X2 = modmul(a->x, a->x, a->curve->p);
+        _3X2 = ecf_treble(X2, a->curve);
+        freebn(X2);
+        M = ecf_add(_3X2, aZ4, a->curve);
+        freebn(_3X2);
+        freebn(aZ4);
+    }
+
+    /* X' = M^2 - 2*S */
+    {
+        Bignum M2, _2S;
+
+        M2 = ecf_square(M, a->curve);
+        _2S = ecf_double(S, a->curve);
+        outx = modsub(M2, _2S, a->curve->p);
+        freebn(M2);
+        freebn(_2S);
+    }
+
+    /* Y' = M*(S - X') - 8*Y^4 */
+    {
+        Bignum SX, MSX, Eight, Y2, Y4, _8Y4;
+
+        SX = modsub(S, outx, a->curve->p);
+        freebn(S);
+        MSX = modmul(M, SX, a->curve->p);
+        freebn(SX);
+        freebn(M);
+        Y2 = ecf_square(a->y, a->curve);
+        Y4 = ecf_square(Y2, a->curve);
+        freebn(Y2);
+        Eight = bignum_from_long(8);
+        _8Y4 = modmul(Eight, Y4, a->curve->p);
+        freebn(Eight);
+        freebn(Y4);
+        outy = modsub(MSX, _8Y4, a->curve->p);
+        freebn(MSX);
+        freebn(_8Y4);
+    }
+
+    /* Z' = 2*Y*Z */
+    {
+        Bignum YZ;
+
+        if (a->z == NULL) {
+            YZ = copybn(a->y);
+        } else {
+            YZ = modmul(a->y, a->z, a->curve->p);
+        }
+
+        outz = ecf_double(YZ, a->curve);
+        freebn(YZ);
+    }
+
+    return ec_point_new(a->curve, outx, outy, outz, 0);
+}
+
+static struct ec_point *ecp_doublem(const struct ec_point *a)
+{
+    Bignum z, outx, outz, xpz, xmz;
+
+    z = a->z;
+    if (!z) {
+        z = One;
+    }
+
+    /* 4xz = (x + z)^2 - (x - z)^2 */
+    {
+        Bignum tmp;
+
+        tmp = ecf_add(a->x, z, a->curve);
+        xpz = ecf_square(tmp, a->curve);
+        freebn(tmp);
+
+        tmp = modsub(a->x, z, a->curve->p);
+        xmz = ecf_square(tmp, a->curve);
+        freebn(tmp);
+    }
+
+    /* outx = (x + z)^2 * (x - z)^2 */
+    outx = modmul(xpz, xmz, a->curve->p);
+
+    /* outz = 4xz * ((x - z)^2 + ((A + 2) / 4)*4xz) */
+    {
+        Bignum _4xz, tmp, tmp2, tmp3;
+
+        tmp = bignum_from_long(2);
+        tmp2 = ecf_add(a->curve->m.a, tmp, a->curve);
+        freebn(tmp);
+
+        _4xz = modsub(xpz, xmz, a->curve->p);
+        freebn(xpz);
+        tmp = modmul(tmp2, _4xz, a->curve->p);
+        freebn(tmp2);
+
+        tmp2 = bignum_from_long(4);
+        tmp3 = modinv(tmp2, a->curve->p);
+        freebn(tmp2);
+        if (!tmp3) {
+            freebn(tmp);
+            freebn(_4xz);
+            freebn(outx);
+            freebn(xmz);
+            return NULL;
+        }
+        tmp2 = modmul(tmp, tmp3, a->curve->p);
+        freebn(tmp);
+        freebn(tmp3);
+
+        tmp = ecf_add(xmz, tmp2, a->curve);
+        freebn(xmz);
+        freebn(tmp2);
+        outz = modmul(_4xz, tmp, a->curve->p);
+        freebn(_4xz);
+        freebn(tmp);
+    }
+
+    return ec_point_new(a->curve, outx, NULL, outz, 0);
+}
+
+/* Forward declaration for Edwards curve doubling */
+static struct ec_point *ecp_add(const struct ec_point *a,
+                                const struct ec_point *b,
+                                const int aminus3);
+
+static struct ec_point *ecp_double(const struct ec_point *a, const int aminus3)
+{
+    if (a->infinity)
+    {
+        /* Identity */
+        return ec_point_new(a->curve, NULL, NULL, NULL, 1);
+    }
+
+    if (a->curve->type == EC_EDWARDS)
+    {
+        return ecp_add(a, a, aminus3);
+    }
+    else if (a->curve->type == EC_WEIERSTRASS)
+    {
+        return ecp_doublew(a, aminus3);
+    }
+    else
+    {
+        return ecp_doublem(a);
+    }
+}
+
+static struct ec_point *ecp_addw(const struct ec_point *a,
+                                 const struct ec_point *b,
+                                 const int aminus3)
+{
+    Bignum U1, U2, S1, S2, outx, outy, outz;
+
+    /* U1 = X1*Z2^2 */
+    /* S1 = Y1*Z2^3 */
+    if (b->z) {
+        Bignum Z2, Z3;
+
+        Z2 = ecf_square(b->z, a->curve);
+        U1 = modmul(a->x, Z2, a->curve->p);
+        Z3 = modmul(Z2, b->z, a->curve->p);
+        freebn(Z2);
+        S1 = modmul(a->y, Z3, a->curve->p);
+        freebn(Z3);
+    } else {
+        U1 = copybn(a->x);
+        S1 = copybn(a->y);
+    }
+
+    /* U2 = X2*Z1^2 */
+    /* S2 = Y2*Z1^3 */
+    if (a->z) {
+        Bignum Z2, Z3;
+
+        Z2 = ecf_square(a->z, b->curve);
+        U2 = modmul(b->x, Z2, b->curve->p);
+        Z3 = modmul(Z2, a->z, b->curve->p);
+        freebn(Z2);
+        S2 = modmul(b->y, Z3, b->curve->p);
+        freebn(Z3);
+    } else {
+        U2 = copybn(b->x);
+        S2 = copybn(b->y);
+    }
+
+    /* Check if multiplying by self */
+    if (bignum_cmp(U1, U2) == 0)
+    {
+        freebn(U1);
+        freebn(U2);
+        if (bignum_cmp(S1, S2) == 0)
+        {
+            freebn(S1);
+            freebn(S2);
+            return ecp_double(a, aminus3);
+        }
+        else
+        {
+            freebn(S1);
+            freebn(S2);
+            /* Infinity */
+            return ec_point_new(a->curve, NULL, NULL, NULL, 1);
+        }
+    }
+
+    {
+        Bignum H, R, UH2, H3;
+
+        /* H = U2 - U1 */
+        H = modsub(U2, U1, a->curve->p);
+        freebn(U2);
+
+        /* R = S2 - S1 */
+        R = modsub(S2, S1, a->curve->p);
+        freebn(S2);
+
+        /* X3 = R^2 - H^3 - 2*U1*H^2 */
+        {
+            Bignum R2, H2, _2UH2, first;
+
+            H2 = ecf_square(H, a->curve);
+            UH2 = modmul(U1, H2, a->curve->p);
+            freebn(U1);
+            H3 = modmul(H2, H, a->curve->p);
+            freebn(H2);
+            R2 = ecf_square(R, a->curve);
+            _2UH2 = ecf_double(UH2, a->curve);
+            first = modsub(R2, H3, a->curve->p);
+            freebn(R2);
+            outx = modsub(first, _2UH2, a->curve->p);
+            freebn(first);
+            freebn(_2UH2);
+        }
+
+        /* Y3 = R*(U1*H^2 - X3) - S1*H^3 */
+        {
+            Bignum RUH2mX, UH2mX, SH3;
+
+            UH2mX = modsub(UH2, outx, a->curve->p);
+            freebn(UH2);
+            RUH2mX = modmul(R, UH2mX, a->curve->p);
+            freebn(UH2mX);
+            freebn(R);
+            SH3 = modmul(S1, H3, a->curve->p);
+            freebn(S1);
+            freebn(H3);
+
+            outy = modsub(RUH2mX, SH3, a->curve->p);
+            freebn(RUH2mX);
+            freebn(SH3);
+        }
+
+        /* Z3 = H*Z1*Z2 */
+        if (a->z && b->z) {
+            Bignum ZZ;
+
+            ZZ = modmul(a->z, b->z, a->curve->p);
+            outz = modmul(H, ZZ, a->curve->p);
+            freebn(H);
+            freebn(ZZ);
+        } else if (a->z) {
+            outz = modmul(H, a->z, a->curve->p);
+            freebn(H);
+        } else if (b->z) {
+            outz = modmul(H, b->z, a->curve->p);
+            freebn(H);
+        } else {
+            outz = H;
+        }
+    }
+
+    return ec_point_new(a->curve, outx, outy, outz, 0);
+}
+
+static struct ec_point *ecp_addm(const struct ec_point *a,
+                                 const struct ec_point *b,
+                                 const struct ec_point *base)
+{
+    Bignum outx, outz, az, bz;
+
+    az = a->z;
+    if (!az) {
+        az = One;
+    }
+    bz = b->z;
+    if (!bz) {
+        bz = One;
+    }
+
+    /* a-b is maintained at 1 due to Montgomery ladder implementation */
+    /* Xa+b = Za-b * ((Xa - Za)*(Xb + Zb) + (Xa + Za)*(Xb - Zb))^2 */
+    /* Za+b = Xa-b * ((Xa - Za)*(Xb + Zb) - (Xa + Za)*(Xb - Zb))^2 */
+    {
+        Bignum tmp, tmp2, tmp3, tmp4;
+
+        /* (Xa + Za) * (Xb - Zb) */
+        tmp = ecf_add(a->x, az, a->curve);
+        tmp2 = modsub(b->x, bz, a->curve->p);
+        tmp3 = modmul(tmp, tmp2, a->curve->p);
+        freebn(tmp);
+        freebn(tmp2);
+
+        /* (Xa - Za) * (Xb + Zb) */
+        tmp = modsub(a->x, az, a->curve->p);
+        tmp2 = ecf_add(b->x, bz, a->curve);
+        tmp4 = modmul(tmp, tmp2, a->curve->p);
+        freebn(tmp);
+        freebn(tmp2);
+
+        tmp = ecf_add(tmp3, tmp4, a->curve);
+        outx = ecf_square(tmp, a->curve);
+        freebn(tmp);
+
+        tmp = modsub(tmp3, tmp4, a->curve->p);
+        freebn(tmp3);
+        freebn(tmp4);
+        tmp2 = ecf_square(tmp, a->curve);
+        freebn(tmp);
+        outz = modmul(base->x, tmp2, a->curve->p);
+        freebn(tmp2);
+    }
+
+    return ec_point_new(a->curve, outx, NULL, outz, 0);
+}
+
+static struct ec_point *ecp_adde(const struct ec_point *a,
+                                 const struct ec_point *b)
+{
+    Bignum outx, outy, dmul;
+
+    /* outx = (a->x * b->y + b->x * a->y) /
+     *        (1 + a->curve->e.d * a->x * b->x * a->y * b->y) */
+    {
+        Bignum tmp, tmp2, tmp3, tmp4;
+
+        tmp = modmul(a->x, b->y, a->curve->p);
+        tmp2 = modmul(b->x, a->y, a->curve->p);
+        tmp3 = ecf_add(tmp, tmp2, a->curve);
+
+        tmp4 = modmul(tmp, tmp2, a->curve->p);
+        freebn(tmp);
+        freebn(tmp2);
+        dmul = modmul(a->curve->e.d, tmp4, a->curve->p);
+        freebn(tmp4);
+
+        tmp = ecf_add(One, dmul, a->curve);
+        tmp2 = modinv(tmp, a->curve->p);
+        freebn(tmp);
+        if (!tmp2)
+        {
+            freebn(tmp3);
+            freebn(dmul);
+            return NULL;
+        }
+
+        outx = modmul(tmp3, tmp2, a->curve->p);
+        freebn(tmp3);
+        freebn(tmp2);
+    }
+
+    /* outy = (a->y * b->y + a->x * b->x) /
+     *        (1 - a->curve->e.d * a->x * b->x * a->y * b->y) */
+    {
+        Bignum tmp, tmp2, tmp3, tmp4;
+
+        tmp = modsub(One, dmul, a->curve->p);
+        freebn(dmul);
+
+        tmp2 = modinv(tmp, a->curve->p);
+        freebn(tmp);
+        if (!tmp2)
+        {
+            freebn(outx);
+            return NULL;
+        }
+
+        tmp = modmul(a->y, b->y, a->curve->p);
+        tmp3 = modmul(a->x, b->x, a->curve->p);
+        tmp4 = ecf_add(tmp, tmp3, a->curve);
+        freebn(tmp);
+        freebn(tmp3);
+
+        outy = modmul(tmp4, tmp2, a->curve->p);
+        freebn(tmp4);
+        freebn(tmp2);
+    }
+
+    return ec_point_new(a->curve, outx, outy, NULL, 0);
+}
+
+static struct ec_point *ecp_add(const struct ec_point *a,
+                                const struct ec_point *b,
+                                const int aminus3)
+{
+    if (a->curve != b->curve) {
+        return NULL;
+    }
+
+    /* Check if multiplying by infinity */
+    if (a->infinity) return ec_point_copy(b);
+    if (b->infinity) return ec_point_copy(a);
+
+    if (a->curve->type == EC_EDWARDS)
+    {
+        return ecp_adde(a, b);
+    }
+
+    if (a->curve->type == EC_WEIERSTRASS)
+    {
+        return ecp_addw(a, b, aminus3);
+    }
+
+    return NULL;
+}
+
+static struct ec_point *ecp_mul_(const struct ec_point *a, const Bignum b, int aminus3)
+{
+    struct ec_point *A, *ret;
+    int bits, i;
+
+    A = ec_point_copy(a);
+    ret = ec_point_new(a->curve, NULL, NULL, NULL, 1);
+
+    bits = bignum_bitcount(b);
+    for (i = 0; i < bits; ++i)
+    {
+        if (bignum_bit(b, i))
+        {
+            struct ec_point *tmp = ecp_add(ret, A, aminus3);
+            ec_point_free(ret);
+            ret = tmp;
+        }
+        if (i+1 != bits)
+        {
+            struct ec_point *tmp = ecp_double(A, aminus3);
+            ec_point_free(A);
+            A = tmp;
+        }
+    }
+
+    ec_point_free(A);
+    return ret;
+}
+
+static struct ec_point *ecp_mulw(const struct ec_point *a, const Bignum b)
+{
+    struct ec_point *ret = ecp_mul_(a, b, ec_aminus3(a->curve));
+
+    if (!ecp_normalise(ret)) {
+        ec_point_free(ret);
+        return NULL;
+    }
+
+    return ret;
+}
+
+static struct ec_point *ecp_mule(const struct ec_point *a, const Bignum b)
+{
+    int i;
+    struct ec_point *ret;
+
+    ret = ec_point_new(a->curve, NULL, NULL, NULL, 1);
+
+    for (i = bignum_bitcount(b); i >= 0 && ret; --i)
+    {
+        {
+            struct ec_point *tmp = ecp_double(ret, 0);
+            ec_point_free(ret);
+            ret = tmp;
+        }
+        if (ret && bignum_bit(b, i))
+        {
+            struct ec_point *tmp = ecp_add(ret, a, 0);
+            ec_point_free(ret);
+            ret = tmp;
+        }
+    }
+
+    return ret;
+}
+
+static struct ec_point *ecp_mulm(const struct ec_point *p, const Bignum n)
+{
+    struct ec_point *P1, *P2;
+    int bits, i;
+
+    /* P1 <- P and P2 <- [2]P */
+    P2 = ecp_double(p, 0);
+    P1 = ec_point_copy(p);
+
+    /* for i = bits − 2 down to 0 */
+    bits = bignum_bitcount(n);
+    for (i = bits - 2; i >= 0; --i)
+    {
+        if (!bignum_bit(n, i))
+        {
+            /* P2 <- P1 + P2 */
+            struct ec_point *tmp = ecp_addm(P1, P2, p);
+            ec_point_free(P2);
+            P2 = tmp;
+
+            /* P1 <- [2]P1 */
+            tmp = ecp_double(P1, 0);
+            ec_point_free(P1);
+            P1 = tmp;
+        }
+        else
+        {
+            /* P1 <- P1 + P2 */
+            struct ec_point *tmp = ecp_addm(P1, P2, p);
+            ec_point_free(P1);
+            P1 = tmp;
+
+            /* P2 <- [2]P2 */
+            tmp = ecp_double(P2, 0);
+            ec_point_free(P2);
+            P2 = tmp;
+        }
+    }
+
+    ec_point_free(P2);
+
+    if (!ecp_normalise(P1)) {
+        ec_point_free(P1);
+        return NULL;
+    }
+
+    return P1;
+}
+
+/* Not static because it is used by sshecdsag.c to generate a new key */
+struct ec_point *ecp_mul(const struct ec_point *a, const Bignum b)
+{
+    if (a->curve->type == EC_WEIERSTRASS) {
+        return ecp_mulw(a, b);
+    } else if (a->curve->type == EC_EDWARDS) {
+        return ecp_mule(a, b);
+    } else {
+        return ecp_mulm(a, b);
+    }
+}
+
+static struct ec_point *ecp_summul(const Bignum a, const Bignum b,
+                                   const struct ec_point *point)
+{
+    struct ec_point *aG, *bP, *ret;
+    int aminus3;
+
+    if (point->curve->type != EC_WEIERSTRASS) {
+        return NULL;
+    }
+
+    aminus3 = ec_aminus3(point->curve);
+
+    aG = ecp_mul_(&point->curve->w.G, a, aminus3);
+    if (!aG) return NULL;
+    bP = ecp_mul_(point, b, aminus3);
+    if (!bP) {
+        ec_point_free(aG);
+        return NULL;
+    }
+
+    ret = ecp_add(aG, bP, aminus3);
+
+    ec_point_free(aG);
+    ec_point_free(bP);
+
+    if (!ecp_normalise(ret)) {
+        ec_point_free(ret);
+        return NULL;
+    }
+
+    return ret;
+}
+static Bignum *ecp_edx(const struct ec_curve *curve, const Bignum y)
+{
+    /* Get the x value on the given Edwards curve for a given y */
+    Bignum x, xx;
+
+    /* xx = (y^2 - 1) / (d * y^2 + 1) */
+    {
+        Bignum tmp, tmp2, tmp3;
+
+        tmp = ecf_square(y, curve);
+        tmp2 = modmul(curve->e.d, tmp, curve->p);
+        tmp3 = ecf_add(tmp2, One, curve);
+        freebn(tmp2);
+        tmp2 = modinv(tmp3, curve->p);
+        freebn(tmp3);
+        if (!tmp2) {
+            freebn(tmp);
+            return NULL;
+        }
+
+        tmp3 = modsub(tmp, One, curve->p);
+        freebn(tmp);
+        xx = modmul(tmp3, tmp2, curve->p);
+        freebn(tmp3);
+        freebn(tmp2);
+    }
+
+    /* x = xx^((p + 3) / 8) */
+    {
+        Bignum tmp, tmp2;
+
+        tmp = bignum_add_long(curve->p, 3);
+        tmp2 = bignum_rshift(tmp, 3);
+        freebn(tmp);
+        x = modpow(xx, tmp2, curve->p);
+        freebn(tmp2);
+    }
+
+    /* if x^2 - xx != 0 then x = x*(2^((p - 1) / 4)) */
+    {
+        Bignum tmp, tmp2;
+
+        tmp = ecf_square(x, curve);
+        tmp2 = modsub(tmp, xx, curve->p);
+        freebn(tmp);
+        freebn(xx);
+        if (bignum_cmp(tmp2, Zero)) {
+            Bignum tmp3;
+
+            freebn(tmp2);
+
+            tmp = modsub(curve->p, One, curve->p);
+            tmp2 = bignum_rshift(tmp, 2);
+            freebn(tmp);
+            tmp = bignum_from_long(2);
+            tmp3 = modpow(tmp, tmp2, curve->p);
+            freebn(tmp);
+            freebn(tmp2);
+
+            tmp = modmul(x, tmp3, curve->p);
+            freebn(x);
+            freebn(tmp3);
+            x = tmp;
+        } else {
+            freebn(tmp2);
+        }
+    }
+
+    /* if x % 2 != 0 then x = p - x */
+    if (bignum_bit(x, 0)) {
+        Bignum tmp = modsub(curve->p, x, curve->p);
+        freebn(x);
+        x = tmp;
+    }
+
+    return x;
+}
+
+/* ----------------------------------------------------------------------
+ * Public point from private
+ */
+
+struct ec_point *ec_public(const Bignum privateKey, const struct ec_curve *curve)
+{
+    if (curve->type == EC_WEIERSTRASS) {
+        return ecp_mul(&curve->w.G, privateKey);
+    } else if (curve->type == EC_EDWARDS) {
+        /* hash = H(sk) (where hash creates 2 * fieldBits)
+         * b = fieldBits
+         * a = 2^(b-2) + SUM(2^i * h_i) for i = 2 -> b-2
+         * publicKey = aB */
+        struct ec_point *ret;
+        unsigned char hash[512/8];
+        Bignum a;
+        int i, keylen;
+        SHA512_State s;
+        SHA512_Init(&s);
+
+        keylen = curve->fieldBits / 8;
+        for (i = 0; i < keylen; ++i) {
+            unsigned char b = bignum_byte(privateKey, i);
+            SHA512_Bytes(&s, &b, 1);
+        }
+        SHA512_Final(&s, hash);
+
+        /* The second part is simply turning the hash into a Bignum,
+         * however the 2^(b-2) bit *must* be set, and the bottom 3
+         * bits *must* not be */
+        hash[0] &= 0xf8; /* Unset bottom 3 bits (if set) */
+        hash[31] &= 0x7f; /* Unset above (b-2) */
+        hash[31] |= 0x40; /* Set 2^(b-2) */
+        /* Chop off the top part and convert to int */
+        a = bignum_from_bytes_le(hash, 32);
+
+        ret = ecp_mul(&curve->e.B, a);
+        freebn(a);
+        return ret;
+    } else {
+        return NULL;
+    }
+}
+
+/* ----------------------------------------------------------------------
+ * Basic sign and verify routines
+ */
+
+static int _ecdsa_verify(const struct ec_point *publicKey,
+                         const unsigned char *data, const int dataLen,
+                         const Bignum r, const Bignum s)
+{
+    int z_bits, n_bits;
+    Bignum z;
+    int valid = 0;
+
+    if (publicKey->curve->type != EC_WEIERSTRASS) {
+        return 0;
+    }
+
+    /* Sanity checks */
+    if (bignum_cmp(r, Zero) == 0 || bignum_cmp(r, publicKey->curve->w.n) >= 0
+        || bignum_cmp(s, Zero) == 0 || bignum_cmp(s, publicKey->curve->w.n) >= 0)
+    {
+        return 0;
+    }
+
+    /* z = left most bitlen(curve->n) of data */
+    z = bignum_from_bytes(data, dataLen);
+    n_bits = bignum_bitcount(publicKey->curve->w.n);
+    z_bits = bignum_bitcount(z);
+    if (z_bits > n_bits)
+    {
+        Bignum tmp = bignum_rshift(z, z_bits - n_bits);
+        freebn(z);
+        z = tmp;
+    }
+
+    /* Ensure z in range of n */
+    {
+        Bignum tmp = bigmod(z, publicKey->curve->w.n);
+        freebn(z);
+        z = tmp;
+    }
+
+    /* Calculate signature */
+    {
+        Bignum w, x, u1, u2;
+        struct ec_point *tmp;
+
+        w = modinv(s, publicKey->curve->w.n);
+        if (!w) {
+            freebn(z);
+            return 0;
+        }
+        u1 = modmul(z, w, publicKey->curve->w.n);
+        u2 = modmul(r, w, publicKey->curve->w.n);
+        freebn(w);
+
+        tmp = ecp_summul(u1, u2, publicKey);
+        freebn(u1);
+        freebn(u2);
+        if (!tmp) {
+            freebn(z);
+            return 0;
+        }
+
+        x = bigmod(tmp->x, publicKey->curve->w.n);
+        ec_point_free(tmp);
+
+        valid = (bignum_cmp(r, x) == 0) ? 1 : 0;
+        freebn(x);
+    }
+
+    freebn(z);
+
+    return valid;
+}
+
+static void _ecdsa_sign(const Bignum privateKey, const struct ec_curve *curve,
+                        const unsigned char *data, const int dataLen,
+                        Bignum *r, Bignum *s)
+{
+    unsigned char digest[20];
+    int z_bits, n_bits;
+    Bignum z, k;
+    struct ec_point *kG;
+
+    *r = NULL;
+    *s = NULL;
+
+    if (curve->type != EC_WEIERSTRASS) {
+        return;
+    }
+
+    /* z = left most bitlen(curve->n) of data */
+    z = bignum_from_bytes(data, dataLen);
+    n_bits = bignum_bitcount(curve->w.n);
+    z_bits = bignum_bitcount(z);
+    if (z_bits > n_bits)
+    {
+        Bignum tmp;
+        tmp = bignum_rshift(z, z_bits - n_bits);
+        freebn(z);
+        z = tmp;
+    }
+
+    /* Generate k between 1 and curve->n, using the same deterministic
+     * k generation system we use for conventional DSA. */
+    SHA_Simple(data, dataLen, digest);
+    k = dss_gen_k("ECDSA deterministic k generator", curve->w.n, privateKey,
+                  digest, sizeof(digest));
+
+    kG = ecp_mul(&curve->w.G, k);
+    if (!kG) {
+        freebn(z);
+        freebn(k);
+        return;
+    }
+
+    /* r = kG.x mod n */
+    *r = bigmod(kG->x, curve->w.n);
+    ec_point_free(kG);
+
+    /* s = (z + r * priv)/k mod n */
+    {
+        Bignum rPriv, zMod, first, firstMod, kInv;
+        rPriv = modmul(*r, privateKey, curve->w.n);
+        zMod = bigmod(z, curve->w.n);
+        freebn(z);
+        first = bigadd(rPriv, zMod);
+        freebn(rPriv);
+        freebn(zMod);
+        firstMod = bigmod(first, curve->w.n);
+        freebn(first);
+        kInv = modinv(k, curve->w.n);
+        freebn(k);
+        if (!kInv) {
+            freebn(firstMod);
+            freebn(*r);
+            return;
+        }
+        *s = modmul(firstMod, kInv, curve->w.n);
+        freebn(firstMod);
+        freebn(kInv);
+    }
+}
+
+/* ----------------------------------------------------------------------
+ * Misc functions
+ */
+
+static void getstring(const char **data, int *datalen,
+                      const char **p, int *length)
+{
+    *p = NULL;
+    if (*datalen < 4)
+        return;
+    *length = toint(GET_32BIT(*data));
+    if (*length < 0)
+        return;
+    *datalen -= 4;
+    *data += 4;
+    if (*datalen < *length)
+        return;
+    *p = *data;
+    *data += *length;
+    *datalen -= *length;
+}
+
+static Bignum getmp(const char **data, int *datalen)
+{
+    const char *p;
+    int length;
+
+    getstring(data, datalen, &p, &length);
+    if (!p)
+        return NULL;
+    if (p[0] & 0x80)
+        return NULL;                   /* negative mp */
+    return bignum_from_bytes((unsigned char *)p, length);
+}
+
+static Bignum getmp_le(const char **data, int *datalen)
+{
+    const char *p;
+    int length;
+
+    getstring(data, datalen, &p, &length);
+    if (!p)
+        return NULL;
+    return bignum_from_bytes_le((const unsigned char *)p, length);
+}
+
+static int decodepoint_ed(const char *p, int length, struct ec_point *point)
+{
+    /* Got some conversion to do, first read in the y co-ord */
+    int negative;
+
+    point->y = bignum_from_bytes_le((const unsigned char*)p, length);
+    if ((unsigned)bignum_bitcount(point->y) > point->curve->fieldBits) {
+        freebn(point->y);
+        point->y = NULL;
+        return 0;
+    }
+    /* Read x bit and then reset it */
+    negative = bignum_bit(point->y, point->curve->fieldBits - 1);
+    bignum_set_bit(point->y, point->curve->fieldBits - 1, 0);
+    bn_restore_invariant(point->y);
+
+    /* Get the x from the y */
+    point->x = ecp_edx(point->curve, point->y);
+    if (!point->x) {
+        freebn(point->y);
+        point->y = NULL;
+        return 0;
+    }
+    if (negative) {
+        Bignum tmp = modsub(point->curve->p, point->x, point->curve->p);
+        freebn(point->x);
+        point->x = tmp;
+    }
+
+    /* Verify the point is on the curve */
+    if (!ec_point_verify(point)) {
+        freebn(point->x);
+        point->x = NULL;
+        freebn(point->y);
+        point->y = NULL;
+        return 0;
+    }
+
+    return 1;
+}
+
+static int decodepoint(const char *p, int length, struct ec_point *point)
+{
+    if (point->curve->type == EC_EDWARDS) {
+        return decodepoint_ed(p, length, point);
+    }
+
+    if (length < 1 || p[0] != 0x04) /* Only support uncompressed point */
+        return 0;
+    /* Skip compression flag */
+    ++p;
+    --length;
+    /* The two values must be equal length */
+    if (length % 2 != 0) {
+        point->x = NULL;
+        point->y = NULL;
+        point->z = NULL;
+        return 0;
+    }
+    length = length / 2;
+    point->x = bignum_from_bytes((const unsigned char *)p, length);
+    p += length;
+    point->y = bignum_from_bytes((const unsigned char *)p, length);
+    point->z = NULL;
+
+    /* Verify the point is on the curve */
+    if (!ec_point_verify(point)) {
+        freebn(point->x);
+        point->x = NULL;
+        freebn(point->y);
+        point->y = NULL;
+        return 0;
+    }
+
+    return 1;
+}
+
+static int getmppoint(const char **data, int *datalen, struct ec_point *point)
+{
+    const char *p;
+    int length;
+
+    getstring(data, datalen, &p, &length);
+    if (!p) return 0;
+    return decodepoint(p, length, point);
+}
+
+/* ----------------------------------------------------------------------
+ * Exposed ECDSA interface
+ */
+
+struct ecsign_extra {
+    struct ec_curve *(*curve)(void);
+    const struct ssh_hash *hash;
+
+    /* These fields are used by the OpenSSH PEM format importer/exporter */
+    const unsigned char *oid;
+    int oidlen;
+};
+
+static void ecdsa_freekey(void *key)
+{
+    struct ec_key *ec = (struct ec_key *) key;
+    if (!ec) return;
+
+    if (ec->publicKey.x)
+        freebn(ec->publicKey.x);
+    if (ec->publicKey.y)
+        freebn(ec->publicKey.y);
+    if (ec->publicKey.z)
+        freebn(ec->publicKey.z);
+    if (ec->privateKey)
+        freebn(ec->privateKey);
+    sfree(ec);
+}
+
+static void *ecdsa_newkey(const struct ssh_signkey *self,
+                          const char *data, int len)
+{
+    const struct ecsign_extra *extra =
+        (const struct ecsign_extra *)self->extra;
+    const char *p;
+    int slen;
+    struct ec_key *ec;
+    struct ec_curve *curve;
+
+    getstring(&data, &len, &p, &slen);
+
+    if (!p) {
+        return NULL;
+    }
+    curve = extra->curve();
+    assert(curve->type == EC_WEIERSTRASS || curve->type == EC_EDWARDS);
+
+    /* Curve name is duplicated for Weierstrass form */
+    if (curve->type == EC_WEIERSTRASS) {
+        getstring(&data, &len, &p, &slen);
+       if (!p) return NULL;
+        if (!match_ssh_id(slen, p, curve->name)) return NULL;
+    }
+
+    ec = snew(struct ec_key);
+
+    ec->signalg = self;
+    ec->publicKey.curve = curve;
+    ec->publicKey.infinity = 0;
+    ec->publicKey.x = NULL;
+    ec->publicKey.y = NULL;
+    ec->publicKey.z = NULL;
+    ec->privateKey = NULL;
+    if (!getmppoint(&data, &len, &ec->publicKey)) {
+        ecdsa_freekey(ec);
+        return NULL;
+    }
+
+    if (!ec->publicKey.x || !ec->publicKey.y ||
+        bignum_cmp(ec->publicKey.x, curve->p) >= 0 ||
+        bignum_cmp(ec->publicKey.y, curve->p) >= 0)
+    {
+        ecdsa_freekey(ec);
+        ec = NULL;
+    }
+
+    return ec;
+}
+
+static char *ecdsa_fmtkey(void *key)
+{
+    struct ec_key *ec = (struct ec_key *) key;
+    char *p;
+    int len, i, pos, nibbles;
+    static const char hex[] = "0123456789abcdef";
+    if (!ec->publicKey.x || !ec->publicKey.y || !ec->publicKey.curve)
+        return NULL;
+
+    len = 4 + 2 + 1;                  /* 2 x "0x", punctuation, \0 */
+    if (ec->publicKey.curve->name)
+        len += strlen(ec->publicKey.curve->name); /* Curve name */
+    len += 4 * (bignum_bitcount(ec->publicKey.x) + 15) / 16;
+    len += 4 * (bignum_bitcount(ec->publicKey.y) + 15) / 16;
+    p = snewn(len, char);
+
+    pos = 0;
+    if (ec->publicKey.curve->name)
+        pos += sprintf(p + pos, "%s,", ec->publicKey.curve->name);
+    pos += sprintf(p + pos, "0x");
+    nibbles = (3 + bignum_bitcount(ec->publicKey.x)) / 4;
+    if (nibbles < 1)
+        nibbles = 1;
+    for (i = nibbles; i--;) {
+        p[pos++] =
+            hex[(bignum_byte(ec->publicKey.x, i / 2) >> (4 * (i % 2))) & 0xF];
+    }
+    pos += sprintf(p + pos, ",0x");
+    nibbles = (3 + bignum_bitcount(ec->publicKey.y)) / 4;
+    if (nibbles < 1)
+        nibbles = 1;
+    for (i = nibbles; i--;) {
+        p[pos++] =
+            hex[(bignum_byte(ec->publicKey.y, i / 2) >> (4 * (i % 2))) & 0xF];
+    }
+    p[pos] = '\0';
+    return p;
+}
+
+static unsigned char *ecdsa_public_blob(void *key, int *len)
+{
+    struct ec_key *ec = (struct ec_key *) key;
+    int pointlen, bloblen, fullnamelen, namelen;
+    int i;
+    unsigned char *blob, *p;
+
+    fullnamelen = strlen(ec->signalg->name);
+
+    if (ec->publicKey.curve->type == EC_EDWARDS) {
+        /* Edwards compressed form "ssh-ed25519" point y[:-1] + x[0:1] */
+
+        pointlen = ec->publicKey.curve->fieldBits / 8;
+
+        /* Can't handle this in our loop */
+        if (pointlen < 2) return NULL;
+
+        bloblen = 4 + fullnamelen + 4 + pointlen;
+        blob = snewn(bloblen, unsigned char);
+
+        p = blob;
+        PUT_32BIT(p, fullnamelen);
+        p += 4;
+        memcpy(p, ec->signalg->name, fullnamelen);
+        p += fullnamelen;
+        PUT_32BIT(p, pointlen);
+        p += 4;
+
+        /* Unset last bit of y and set first bit of x in its place */
+        for (i = 0; i < pointlen - 1; ++i) {
+            *p++ = bignum_byte(ec->publicKey.y, i);
+        }
+        /* Unset last bit of y and set first bit of x in its place */
+        *p = bignum_byte(ec->publicKey.y, i) & 0x7f;
+        *p++ |= bignum_bit(ec->publicKey.x, 0) << 7;
+    } else if (ec->publicKey.curve->type == EC_WEIERSTRASS) {
+        assert(ec->publicKey.curve->name);
+        namelen = strlen(ec->publicKey.curve->name);
+
+        pointlen = (bignum_bitcount(ec->publicKey.curve->p) + 7) / 8;
+
+        /*
+         * string "ecdsa-sha2-<name>", string "<name>", 0x04 point x, y.
+         */
+        bloblen = 4 + fullnamelen + 4 + namelen + 4 + 1 + (pointlen * 2);
+        blob = snewn(bloblen, unsigned char);
+
+        p = blob;
+        PUT_32BIT(p, fullnamelen);
+        p += 4;
+        memcpy(p, ec->signalg->name, fullnamelen);
+        p += fullnamelen;
+        PUT_32BIT(p, namelen);
+        p += 4;
+        memcpy(p, ec->publicKey.curve->name, namelen);
+        p += namelen;
+        PUT_32BIT(p, (2 * pointlen) + 1);
+        p += 4;
+        *p++ = 0x04;
+        for (i = pointlen; i--;) {
+            *p++ = bignum_byte(ec->publicKey.x, i);
+        }
+        for (i = pointlen; i--;) {
+            *p++ = bignum_byte(ec->publicKey.y, i);
+        }
+    } else {
+        return NULL;
+    }
+
+    assert(p == blob + bloblen);
+    *len = bloblen;
+
+    return blob;
+}
+
+static unsigned char *ecdsa_private_blob(void *key, int *len)
+{
+    struct ec_key *ec = (struct ec_key *) key;
+    int keylen, bloblen;
+    int i;
+    unsigned char *blob, *p;
+
+    if (!ec->privateKey) return NULL;
+
+    if (ec->publicKey.curve->type == EC_EDWARDS) {
+        /* Unsigned */
+        keylen = (bignum_bitcount(ec->privateKey) + 7) / 8;
+    } else {
+        /* Signed */
+        keylen = (bignum_bitcount(ec->privateKey) + 8) / 8;
+    }
+
+    /*
+     * mpint privateKey. Total 4 + keylen.
+     */
+    bloblen = 4 + keylen;
+    blob = snewn(bloblen, unsigned char);
+
+    p = blob;
+    PUT_32BIT(p, keylen);
+    p += 4;
+    if (ec->publicKey.curve->type == EC_EDWARDS) {
+        /* Little endian */
+        for (i = 0; i < keylen; ++i)
+            *p++ = bignum_byte(ec->privateKey, i);
+    } else {
+        for (i = keylen; i--;)
+            *p++ = bignum_byte(ec->privateKey, i);
+    }
+
+    assert(p == blob + bloblen);
+    *len = bloblen;
+    return blob;
+}
+
+static void *ecdsa_createkey(const struct ssh_signkey *self,
+                             const unsigned char *pub_blob, int pub_len,
+                             const unsigned char *priv_blob, int priv_len)
+{
+    struct ec_key *ec;
+    struct ec_point *publicKey;
+    const char *pb = (const char *) priv_blob;
+
+    ec = (struct ec_key*)ecdsa_newkey(self, (const char *) pub_blob, pub_len);
+    if (!ec) {
+        return NULL;
+    }
+
+    if (ec->publicKey.curve->type != EC_WEIERSTRASS
+        && ec->publicKey.curve->type != EC_EDWARDS) {
+        ecdsa_freekey(ec);
+        return NULL;
+    }
+
+    if (ec->publicKey.curve->type == EC_EDWARDS) {
+        ec->privateKey = getmp_le(&pb, &priv_len);
+    } else {
+        ec->privateKey = getmp(&pb, &priv_len);
+    }
+    if (!ec->privateKey) {
+        ecdsa_freekey(ec);
+        return NULL;
+    }
+
+    /* Check that private key generates public key */
+    publicKey = ec_public(ec->privateKey, ec->publicKey.curve);
+
+    if (!publicKey ||
+        bignum_cmp(publicKey->x, ec->publicKey.x) ||
+        bignum_cmp(publicKey->y, ec->publicKey.y))
+    {
+        ecdsa_freekey(ec);
+        ec = NULL;
+    }
+    ec_point_free(publicKey);
+
+    return ec;
+}
+
+static void *ed25519_openssh_createkey(const struct ssh_signkey *self,
+                                       const unsigned char **blob, int *len)
+{
+    struct ec_key *ec;
+    struct ec_point *publicKey;
+    const char *p, *q;
+    int plen, qlen;
+
+    getstring((const char**)blob, len, &p, &plen);
+    if (!p)
+    {
+        return NULL;
+    }
+
+    ec = snew(struct ec_key);
+
+    ec->signalg = self;
+    ec->publicKey.curve = ec_ed25519();
+    ec->publicKey.infinity = 0;
+    ec->privateKey = NULL;
+    ec->publicKey.x = NULL;
+    ec->publicKey.z = NULL;
+    ec->publicKey.y = NULL;
+
+    if (!decodepoint_ed(p, plen, &ec->publicKey))
+    {
+        ecdsa_freekey(ec);
+        return NULL;
+    }
+
+    getstring((const char**)blob, len, &q, &qlen);
+    if (!q)
+        return NULL;
+    if (qlen != 64)
+        return NULL;
+
+    ec->privateKey = bignum_from_bytes_le((const unsigned char *)q, 32);
+
+    /* Check that private key generates public key */
+    publicKey = ec_public(ec->privateKey, ec->publicKey.curve);
+
+    if (!publicKey ||
+        bignum_cmp(publicKey->x, ec->publicKey.x) ||
+        bignum_cmp(publicKey->y, ec->publicKey.y))
+    {
+        ecdsa_freekey(ec);
+        ec = NULL;
+    }
+    ec_point_free(publicKey);
+
+    /* The OpenSSH format for ed25519 private keys also for some
+     * reason encodes an extra copy of the public key in the second
+     * half of the secret-key string. Check that that's present and
+     * correct as well, otherwise the key we think we've imported
+     * won't behave identically to the way OpenSSH would have treated
+     * it. */
+    if (plen != 32 || 0 != memcmp(q + 32, p, 32)) {
+        ecdsa_freekey(ec);
+        return NULL;
+    }
+
+    return ec;
+}
+
+static int ed25519_openssh_fmtkey(void *key, unsigned char *blob, int len)
+{
+    struct ec_key *ec = (struct ec_key *) key;
+
+    int pointlen;
+    int keylen;
+    int bloblen;
+    int i;
+
+    if (ec->publicKey.curve->type != EC_EDWARDS) {
+        return 0;
+    }
+
+    pointlen = (bignum_bitcount(ec->publicKey.y) + 7) / 8;
+    keylen = (bignum_bitcount(ec->privateKey) + 7) / 8;
+    bloblen = 4 + pointlen + 4 + keylen + pointlen;
+
+    if (bloblen > len)
+        return bloblen;
+
+    /* Encode the public point */
+    PUT_32BIT(blob, pointlen);
+    blob += 4;
+
+    for (i = 0; i < pointlen - 1; ++i) {
+         *blob++ = bignum_byte(ec->publicKey.y, i);
+    }
+    /* Unset last bit of y and set first bit of x in its place */
+    *blob = bignum_byte(ec->publicKey.y, i) & 0x7f;
+    *blob++ |= bignum_bit(ec->publicKey.x, 0) << 7;
+
+    PUT_32BIT(blob, keylen + pointlen);
+    blob += 4;
+    for (i = 0; i < keylen; ++i) {
+         *blob++ = bignum_byte(ec->privateKey, i);
+    }
+    /* Now encode an extra copy of the public point as the second half
+     * of the private key string, as the OpenSSH format for some
+     * reason requires */
+    for (i = 0; i < pointlen - 1; ++i) {
+         *blob++ = bignum_byte(ec->publicKey.y, i);
+    }
+    /* Unset last bit of y and set first bit of x in its place */
+    *blob = bignum_byte(ec->publicKey.y, i) & 0x7f;
+    *blob++ |= bignum_bit(ec->publicKey.x, 0) << 7;
+
+    return bloblen;
+}
+
+static void *ecdsa_openssh_createkey(const struct ssh_signkey *self,
+                                     const unsigned char **blob, int *len)
+{
+    const struct ecsign_extra *extra =
+        (const struct ecsign_extra *)self->extra;
+    const char **b = (const char **) blob;
+    const char *p;
+    int slen;
+    struct ec_key *ec;
+    struct ec_curve *curve;
+    struct ec_point *publicKey;
+
+    getstring(b, len, &p, &slen);
+
+    if (!p) {
+        return NULL;
+    }
+    curve = extra->curve();
+    assert(curve->type == EC_WEIERSTRASS);
+
+    ec = snew(struct ec_key);
+
+    ec->signalg = self;
+    ec->publicKey.curve = curve;
+    ec->publicKey.infinity = 0;
+    ec->publicKey.x = NULL;
+    ec->publicKey.y = NULL;
+    ec->publicKey.z = NULL;
+    if (!getmppoint(b, len, &ec->publicKey)) {
+        ecdsa_freekey(ec);
+        return NULL;
+    }
+    ec->privateKey = NULL;
+
+    if (!ec->publicKey.x || !ec->publicKey.y ||
+        bignum_cmp(ec->publicKey.x, curve->p) >= 0 ||
+        bignum_cmp(ec->publicKey.y, curve->p) >= 0)
+    {
+        ecdsa_freekey(ec);
+        return NULL;
+    }
+
+    ec->privateKey = getmp(b, len);
+    if (ec->privateKey == NULL)
+    {
+        ecdsa_freekey(ec);
+        return NULL;
+    }
+
+    /* Now check that the private key makes the public key */
+    publicKey = ec_public(ec->privateKey, ec->publicKey.curve);
+    if (!publicKey)
+    {
+        ecdsa_freekey(ec);
+        return NULL;
+    }
+
+    if (bignum_cmp(ec->publicKey.x, publicKey->x) ||
+        bignum_cmp(ec->publicKey.y, publicKey->y))
+    {
+        /* Private key doesn't make the public key on the given curve */
+        ecdsa_freekey(ec);
+        ec_point_free(publicKey);
+        return NULL;
+    }
+
+    ec_point_free(publicKey);
+
+    return ec;
+}
+
+static int ecdsa_openssh_fmtkey(void *key, unsigned char *blob, int len)
+{
+    struct ec_key *ec = (struct ec_key *) key;
+
+    int pointlen;
+    int namelen;
+    int bloblen;
+    int i;
+
+    if (ec->publicKey.curve->type != EC_WEIERSTRASS) {
+        return 0;
+    }
+
+    pointlen = (bignum_bitcount(ec->publicKey.curve->p) + 7) / 8;
+    namelen = strlen(ec->publicKey.curve->name);
+    bloblen =
+        4 + namelen /* <LEN> nistpXXX */
+        + 4 + 1 + (pointlen * 2) /* <LEN> 0x04 pX pY */
+        + ssh2_bignum_length(ec->privateKey);
+
+    if (bloblen > len)
+        return bloblen;
+
+    bloblen = 0;
+
+    PUT_32BIT(blob+bloblen, namelen);
+    bloblen += 4;
+    memcpy(blob+bloblen, ec->publicKey.curve->name, namelen);
+    bloblen += namelen;
+
+    PUT_32BIT(blob+bloblen, 1 + (pointlen * 2));
+    bloblen += 4;
+    blob[bloblen++] = 0x04;
+    for (i = pointlen; i--; )
+        blob[bloblen++] = bignum_byte(ec->publicKey.x, i);
+    for (i = pointlen; i--; )
+        blob[bloblen++] = bignum_byte(ec->publicKey.y, i);
+
+    pointlen = (bignum_bitcount(ec->privateKey) + 8) / 8;
+    PUT_32BIT(blob+bloblen, pointlen);
+    bloblen += 4;
+    for (i = pointlen; i--; )
+        blob[bloblen++] = bignum_byte(ec->privateKey, i);
+
+    return bloblen;
+}
+
+static int ecdsa_pubkey_bits(const struct ssh_signkey *self,
+                             const void *blob, int len)
+{
+    struct ec_key *ec;
+    int ret;
+
+    ec = (struct ec_key*)ecdsa_newkey(self, (const char *) blob, len);
+    if (!ec)
+        return -1;
+    ret = ec->publicKey.curve->fieldBits;
+    ecdsa_freekey(ec);
+
+    return ret;
+}
+
+static int ecdsa_verifysig(void *key, const char *sig, int siglen,
+                           const char *data, int datalen)
+{
+    struct ec_key *ec = (struct ec_key *) key;
+    const struct ecsign_extra *extra =
+        (const struct ecsign_extra *)ec->signalg->extra;
+    const char *p;
+    int slen;
+    int digestLen;
+    int ret;
+
+    if (!ec->publicKey.x || !ec->publicKey.y || !ec->publicKey.curve)
+        return 0;
+
+    /* Check the signature starts with the algorithm name */
+    getstring(&sig, &siglen, &p, &slen);
+    if (!p) {
+        return 0;
+    }
+    if (!match_ssh_id(slen, p, ec->signalg->name)) {
+        return 0;
+    }
+
+    getstring(&sig, &siglen, &p, &slen);
+    if (!p) return 0;
+    if (ec->publicKey.curve->type == EC_EDWARDS) {
+        struct ec_point *r;
+        Bignum s, h;
+
+        /* Check that the signature is two times the length of a point */
+        if (slen != (ec->publicKey.curve->fieldBits / 8) * 2) {
+            return 0;
+        }
+
+        /* Check it's the 256 bit field so that SHA512 is the correct hash */
+        if (ec->publicKey.curve->fieldBits != 256) {
+            return 0;
+        }
+
+        /* Get the signature */
+        r = ec_point_new(ec->publicKey.curve, NULL, NULL, NULL, 0);
+        if (!r) {
+            return 0;
+        }
+        if (!decodepoint(p, ec->publicKey.curve->fieldBits / 8, r)) {
+            ec_point_free(r);
+            return 0;
+        }
+        s = bignum_from_bytes_le((unsigned char*)p + (ec->publicKey.curve->fieldBits / 8),
+                                 ec->publicKey.curve->fieldBits / 8);
+
+        /* Get the hash of the encoded value of R + encoded value of pk + message */
+        {
+            int i, pointlen;
+            unsigned char b;
+            unsigned char digest[512 / 8];
+            SHA512_State hs;
+            SHA512_Init(&hs);
+
+            /* Add encoded r (no need to encode it again, it was in the signature) */
+            SHA512_Bytes(&hs, p, ec->publicKey.curve->fieldBits / 8);
+
+            /* Encode pk and add it */
+            pointlen = ec->publicKey.curve->fieldBits / 8;
+            for (i = 0; i < pointlen - 1; ++i) {
+                b = bignum_byte(ec->publicKey.y, i);
+                SHA512_Bytes(&hs, &b, 1);
+            }
+            /* Unset last bit of y and set first bit of x in its place */
+            b = bignum_byte(ec->publicKey.y, i) & 0x7f;
+            b |= bignum_bit(ec->publicKey.x, 0) << 7;
+            SHA512_Bytes(&hs, &b, 1);
+
+            /* Add the message itself */
+            SHA512_Bytes(&hs, data, datalen);
+
+            /* Get the hash */
+            SHA512_Final(&hs, digest);
+
+            /* Convert to Bignum */
+            h = bignum_from_bytes_le(digest, sizeof(digest));
+        }
+
+        /* Verify sB == r + h*publicKey */
+        {
+            struct ec_point *lhs, *rhs, *tmp;
+
+            /* lhs = sB */
+            lhs = ecp_mul(&ec->publicKey.curve->e.B, s);
+            freebn(s);
+            if (!lhs) {
+                ec_point_free(r);
+                freebn(h);
+                return 0;
+            }
+
+            /* rhs = r + h*publicKey */
+            tmp = ecp_mul(&ec->publicKey, h);
+            freebn(h);
+            if (!tmp) {
+                ec_point_free(lhs);
+                ec_point_free(r);
+                return 0;
+            }
+            rhs = ecp_add(r, tmp, 0);
+            ec_point_free(r);
+            ec_point_free(tmp);
+            if (!rhs) {
+                ec_point_free(lhs);
+                return 0;
+            }
+
+            /* Check the point is the same */
+            ret = !bignum_cmp(lhs->x, rhs->x);
+            if (ret) {
+                ret = !bignum_cmp(lhs->y, rhs->y);
+                if (ret) {
+                    ret = 1;
+                }
+            }
+            ec_point_free(lhs);
+            ec_point_free(rhs);
+        }
+    } else {
+        Bignum r, s;
+        unsigned char digest[512 / 8];
+        void *hashctx;
+
+        r = getmp(&p, &slen);
+        if (!r) return 0;
+        s = getmp(&p, &slen);
+        if (!s) {
+            freebn(r);
+            return 0;
+        }
+
+        digestLen = extra->hash->hlen;
+        assert(digestLen <= sizeof(digest));
+        hashctx = extra->hash->init();
+        extra->hash->bytes(hashctx, data, datalen);
+        extra->hash->final(hashctx, digest);
+
+        /* Verify the signature */
+        ret = _ecdsa_verify(&ec->publicKey, digest, digestLen, r, s);
+
+        freebn(r);
+        freebn(s);
+    }
+
+    return ret;
+}
+
+static unsigned char *ecdsa_sign(void *key, const char *data, int datalen,
+                                 int *siglen)
+{
+    struct ec_key *ec = (struct ec_key *) key;
+    const struct ecsign_extra *extra =
+        (const struct ecsign_extra *)ec->signalg->extra;
+    unsigned char digest[512 / 8];
+    int digestLen;
+    Bignum r = NULL, s = NULL;
+    unsigned char *buf, *p;
+    int rlen, slen, namelen;
+    int i;
+
+    if (!ec->privateKey || !ec->publicKey.curve) {
+        return NULL;
+    }
+
+    if (ec->publicKey.curve->type == EC_EDWARDS) {
+        struct ec_point *rp;
+        int pointlen = ec->publicKey.curve->fieldBits / 8;
+
+        /* hash = H(sk) (where hash creates 2 * fieldBits)
+         * b = fieldBits
+         * a = 2^(b-2) + SUM(2^i * h_i) for i = 2 -> b-2
+         * r = H(h[b/8:b/4] + m)
+         * R = rB
+         * S = (r + H(encodepoint(R) + encodepoint(pk) + m) * a) % l */
+        {
+            unsigned char hash[512/8];
+            unsigned char b;
+            Bignum a;
+            SHA512_State hs;
+            SHA512_Init(&hs);
+
+            for (i = 0; i < pointlen; ++i) {
+                unsigned char b = (unsigned char)bignum_byte(ec->privateKey, i);
+                SHA512_Bytes(&hs, &b, 1);
+            }
+
+            SHA512_Final(&hs, hash);
+
+            /* The second part is simply turning the hash into a
+             * Bignum, however the 2^(b-2) bit *must* be set, and the
+             * bottom 3 bits *must* not be */
+            hash[0] &= 0xf8; /* Unset bottom 3 bits (if set) */
+            hash[31] &= 0x7f; /* Unset above (b-2) */
+            hash[31] |= 0x40; /* Set 2^(b-2) */
+            /* Chop off the top part and convert to int */
+            a = bignum_from_bytes_le(hash, 32);
+
+            SHA512_Init(&hs);
+            SHA512_Bytes(&hs,
+                         hash+(ec->publicKey.curve->fieldBits / 8),
+                         (ec->publicKey.curve->fieldBits / 4)
+                         - (ec->publicKey.curve->fieldBits / 8));
+            SHA512_Bytes(&hs, data, datalen);
+            SHA512_Final(&hs, hash);
+
+            r = bignum_from_bytes_le(hash, 512/8);
+            rp = ecp_mul(&ec->publicKey.curve->e.B, r);
+            if (!rp) {
+                freebn(r);
+                freebn(a);
+                return NULL;
+            }
+
+            /* Now calculate s */
+            SHA512_Init(&hs);
+            /* Encode the point R */
+            for (i = 0; i < pointlen - 1; ++i) {
+                b = bignum_byte(rp->y, i);
+                SHA512_Bytes(&hs, &b, 1);
+            }
+            /* Unset last bit of y and set first bit of x in its place */
+            b = bignum_byte(rp->y, i) & 0x7f;
+            b |= bignum_bit(rp->x, 0) << 7;
+            SHA512_Bytes(&hs, &b, 1);
+
+            /* Encode the point pk */
+            for (i = 0; i < pointlen - 1; ++i) {
+                b = bignum_byte(ec->publicKey.y, i);
+                SHA512_Bytes(&hs, &b, 1);
+            }
+            /* Unset last bit of y and set first bit of x in its place */
+            b = bignum_byte(ec->publicKey.y, i) & 0x7f;
+            b |= bignum_bit(ec->publicKey.x, 0) << 7;
+            SHA512_Bytes(&hs, &b, 1);
+
+            /* Add the message */
+            SHA512_Bytes(&hs, data, datalen);
+            SHA512_Final(&hs, hash);
+
+            {
+                Bignum tmp, tmp2;
+
+                tmp = bignum_from_bytes_le(hash, 512/8);
+                tmp2 = modmul(tmp, a, ec->publicKey.curve->e.l);
+                freebn(a);
+                freebn(tmp);
+                tmp = bigadd(r, tmp2);
+                freebn(r);
+                freebn(tmp2);
+                s = bigmod(tmp, ec->publicKey.curve->e.l);
+                freebn(tmp);
+            }
+        }
+
+        /* Format the output */
+        namelen = strlen(ec->signalg->name);
+        *siglen = 4+namelen+4+((ec->publicKey.curve->fieldBits / 8)*2);
+        buf = snewn(*siglen, unsigned char);
+        p = buf;
+        PUT_32BIT(p, namelen);
+        p += 4;
+        memcpy(p, ec->signalg->name, namelen);
+        p += namelen;
+        PUT_32BIT(p, ((ec->publicKey.curve->fieldBits / 8)*2));
+        p += 4;
+
+        /* Encode the point */
+        pointlen = ec->publicKey.curve->fieldBits / 8;
+        for (i = 0; i < pointlen - 1; ++i) {
+            *p++ = bignum_byte(rp->y, i);
+        }
+        /* Unset last bit of y and set first bit of x in its place */
+        *p = bignum_byte(rp->y, i) & 0x7f;
+        *p++ |= bignum_bit(rp->x, 0) << 7;
+        ec_point_free(rp);
+
+        /* Encode the int */
+        for (i = 0; i < pointlen; ++i) {
+            *p++ = bignum_byte(s, i);
+        }
+        freebn(s);
+    } else {
+        void *hashctx;
+
+        digestLen = extra->hash->hlen;
+        assert(digestLen <= sizeof(digest));
+        hashctx = extra->hash->init();
+        extra->hash->bytes(hashctx, data, datalen);
+        extra->hash->final(hashctx, digest);
+
+        /* Do the signature */
+        _ecdsa_sign(ec->privateKey, ec->publicKey.curve, digest, digestLen, &r, &s);
+        if (!r || !s) {
+            if (r) freebn(r);
+            if (s) freebn(s);
+            return NULL;
+        }
+
+        rlen = (bignum_bitcount(r) + 8) / 8;
+        slen = (bignum_bitcount(s) + 8) / 8;
+
+        namelen = strlen(ec->signalg->name);
+
+        /* Format the output */
+        *siglen = 8+namelen+rlen+slen+8;
+        buf = snewn(*siglen, unsigned char);
+        p = buf;
+        PUT_32BIT(p, namelen);
+        p += 4;
+        memcpy(p, ec->signalg->name, namelen);
+        p += namelen;
+        PUT_32BIT(p, rlen + slen + 8);
+        p += 4;
+        PUT_32BIT(p, rlen);
+        p += 4;
+        for (i = rlen; i--;)
+            *p++ = bignum_byte(r, i);
+        PUT_32BIT(p, slen);
+        p += 4;
+        for (i = slen; i--;)
+            *p++ = bignum_byte(s, i);
+
+        freebn(r);
+        freebn(s);
+    }
+
+    return buf;
+}
+
+const struct ecsign_extra sign_extra_ed25519 = {
+    ec_ed25519, NULL,
+    NULL, 0,
+};
+const struct ssh_signkey ssh_ecdsa_ed25519 = {
+    ecdsa_newkey,
+    ecdsa_freekey,
+    ecdsa_fmtkey,
+    ecdsa_public_blob,
+    ecdsa_private_blob,
+    ecdsa_createkey,
+    ed25519_openssh_createkey,
+    ed25519_openssh_fmtkey,
+    2 /* point, private exponent */,
+    ecdsa_pubkey_bits,
+    ecdsa_verifysig,
+    ecdsa_sign,
+    "ssh-ed25519",
+    "ssh-ed25519",
+    &sign_extra_ed25519,
+};
+
+/* OID: 1.2.840.10045.3.1.7 (ansiX9p256r1) */
+static const unsigned char nistp256_oid[] = {
+    0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07
+};
+const struct ecsign_extra sign_extra_nistp256 = {
+    ec_p256, &ssh_sha256,
+    nistp256_oid, lenof(nistp256_oid),
+};
+const struct ssh_signkey ssh_ecdsa_nistp256 = {
+    ecdsa_newkey,
+    ecdsa_freekey,
+    ecdsa_fmtkey,
+    ecdsa_public_blob,
+    ecdsa_private_blob,
+    ecdsa_createkey,
+    ecdsa_openssh_createkey,
+    ecdsa_openssh_fmtkey,
+    3 /* curve name, point, private exponent */,
+    ecdsa_pubkey_bits,
+    ecdsa_verifysig,
+    ecdsa_sign,
+    "ecdsa-sha2-nistp256",
+    "ecdsa-sha2-nistp256",
+    &sign_extra_nistp256,
+};
+
+/* OID: 1.3.132.0.34 (secp384r1) */
+static const unsigned char nistp384_oid[] = {
+    0x2b, 0x81, 0x04, 0x00, 0x22
+};
+const struct ecsign_extra sign_extra_nistp384 = {
+    ec_p384, &ssh_sha384,
+    nistp384_oid, lenof(nistp384_oid),
+};
+const struct ssh_signkey ssh_ecdsa_nistp384 = {
+    ecdsa_newkey,
+    ecdsa_freekey,
+    ecdsa_fmtkey,
+    ecdsa_public_blob,
+    ecdsa_private_blob,
+    ecdsa_createkey,
+    ecdsa_openssh_createkey,
+    ecdsa_openssh_fmtkey,
+    3 /* curve name, point, private exponent */,
+    ecdsa_pubkey_bits,
+    ecdsa_verifysig,
+    ecdsa_sign,
+    "ecdsa-sha2-nistp384",
+    "ecdsa-sha2-nistp384",
+    &sign_extra_nistp384,
+};
+
+/* OID: 1.3.132.0.35 (secp521r1) */
+static const unsigned char nistp521_oid[] = {
+    0x2b, 0x81, 0x04, 0x00, 0x23
+};
+const struct ecsign_extra sign_extra_nistp521 = {
+    ec_p521, &ssh_sha512,
+    nistp521_oid, lenof(nistp521_oid),
+};
+const struct ssh_signkey ssh_ecdsa_nistp521 = {
+    ecdsa_newkey,
+    ecdsa_freekey,
+    ecdsa_fmtkey,
+    ecdsa_public_blob,
+    ecdsa_private_blob,
+    ecdsa_createkey,
+    ecdsa_openssh_createkey,
+    ecdsa_openssh_fmtkey,
+    3 /* curve name, point, private exponent */,
+    ecdsa_pubkey_bits,
+    ecdsa_verifysig,
+    ecdsa_sign,
+    "ecdsa-sha2-nistp521",
+    "ecdsa-sha2-nistp521",
+    &sign_extra_nistp521,
+};
+
+/* ----------------------------------------------------------------------
+ * Exposed ECDH interface
+ */
+
+struct eckex_extra {
+    struct ec_curve *(*curve)(void);
+};
+
+static Bignum ecdh_calculate(const Bignum private,
+                             const struct ec_point *public)
+{
+    struct ec_point *p;
+    Bignum ret;
+    p = ecp_mul(public, private);
+    if (!p) return NULL;
+    ret = p->x;
+    p->x = NULL;
+
+    if (p->curve->type == EC_MONTGOMERY) {
+        /*
+         * Endianness-swap. The Curve25519 algorithm definition
+         * assumes you were doing your computation in arrays of 32
+         * little-endian bytes, and now specifies that you take your
+         * final one of those and convert it into a bignum in
+         * _network_ byte order, i.e. big-endian.
+         *
+         * In particular, the spec says, you convert the _whole_ 32
+         * bytes into a bignum. That is, on the rare occasions that
+         * p->x has come out with the most significant 8 bits zero, we
+         * have to imagine that being represented by a 32-byte string
+         * with the last byte being zero, so that has to be converted
+         * into an SSH-2 bignum with the _low_ byte zero, i.e. a
+         * multiple of 256.
+         */
+        int i;
+        int bytes = (p->curve->fieldBits+7) / 8;
+        unsigned char *byteorder = snewn(bytes, unsigned char);
+        for (i = 0; i < bytes; ++i) {
+            byteorder[i] = bignum_byte(ret, i);
+        }
+        freebn(ret);
+        ret = bignum_from_bytes(byteorder, bytes);
+        smemclr(byteorder, bytes);
+        sfree(byteorder);
+    }
+
+    ec_point_free(p);
+    return ret;
+}
+
+const char *ssh_ecdhkex_curve_textname(const struct ssh_kex *kex)
+{
+    const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra;
+    struct ec_curve *curve = extra->curve();
+    return curve->textname;
+}
+
+void *ssh_ecdhkex_newkey(const struct ssh_kex *kex)
+{
+    const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra;
+    struct ec_curve *curve;
+    struct ec_key *key;
+    struct ec_point *publicKey;
+
+    curve = extra->curve();
+
+    key = snew(struct ec_key);
+
+    key->signalg = NULL;
+    key->publicKey.curve = curve;
+
+    if (curve->type == EC_MONTGOMERY) {
+        unsigned char bytes[32] = {0};
+        int i;
+
+        for (i = 0; i < sizeof(bytes); ++i)
+        {
+            bytes[i] = (unsigned char)random_byte();
+        }
+        bytes[0] &= 248;
+        bytes[31] &= 127;
+        bytes[31] |= 64;
+        key->privateKey = bignum_from_bytes(bytes, sizeof(bytes));
+        smemclr(bytes, sizeof(bytes));
+        if (!key->privateKey) {
+            sfree(key);
+            return NULL;
+        }
+        publicKey = ecp_mul(&key->publicKey.curve->m.G, key->privateKey);
+        if (!publicKey) {
+            freebn(key->privateKey);
+            sfree(key);
+            return NULL;
+        }
+        key->publicKey.x = publicKey->x;
+        key->publicKey.y = publicKey->y;
+        key->publicKey.z = NULL;
+        sfree(publicKey);
+    } else {
+        key->privateKey = bignum_random_in_range(One, key->publicKey.curve->w.n);
+        if (!key->privateKey) {
+            sfree(key);
+            return NULL;
+        }
+        publicKey = ecp_mul(&key->publicKey.curve->w.G, key->privateKey);
+        if (!publicKey) {
+            freebn(key->privateKey);
+            sfree(key);
+            return NULL;
+        }
+        key->publicKey.x = publicKey->x;
+        key->publicKey.y = publicKey->y;
+        key->publicKey.z = NULL;
+        sfree(publicKey);
+    }
+    return key;
+}
+
+char *ssh_ecdhkex_getpublic(void *key, int *len)
+{
+    struct ec_key *ec = (struct ec_key*)key;
+    char *point, *p;
+    int i;
+    int pointlen;
+
+    pointlen = (bignum_bitcount(ec->publicKey.curve->p) + 7) / 8;
+
+    if (ec->publicKey.curve->type == EC_WEIERSTRASS) {
+        *len = 1 + pointlen * 2;
+    } else {
+        *len = pointlen;
+    }
+    point = (char*)snewn(*len, char);
+
+    p = point;
+    if (ec->publicKey.curve->type == EC_WEIERSTRASS) {
+        *p++ = 0x04;
+        for (i = pointlen; i--;) {
+            *p++ = bignum_byte(ec->publicKey.x, i);
+        }
+        for (i = pointlen; i--;) {
+            *p++ = bignum_byte(ec->publicKey.y, i);
+        }
+    } else {
+        for (i = 0; i < pointlen; ++i) {
+            *p++ = bignum_byte(ec->publicKey.x, i);
+        }
+    }
+
+    return point;
+}
+
+Bignum ssh_ecdhkex_getkey(void *key, char *remoteKey, int remoteKeyLen)
+{
+    struct ec_key *ec = (struct ec_key*) key;
+    struct ec_point remote;
+    Bignum ret;
+
+    if (ec->publicKey.curve->type == EC_WEIERSTRASS) {
+        remote.curve = ec->publicKey.curve;
+        remote.infinity = 0;
+        if (!decodepoint(remoteKey, remoteKeyLen, &remote)) {
+            return NULL;
+        }
+    } else {
+        /* Point length has to be the same length */
+        if (remoteKeyLen != (bignum_bitcount(ec->publicKey.curve->p) + 7) / 8) {
+            return NULL;
+        }
+
+        remote.curve = ec->publicKey.curve;
+        remote.infinity = 0;
+        remote.x = bignum_from_bytes_le((unsigned char*)remoteKey, remoteKeyLen);
+        remote.y = NULL;
+        remote.z = NULL;
+    }
+
+    ret = ecdh_calculate(ec->privateKey, &remote);
+    if (remote.x) freebn(remote.x);
+    if (remote.y) freebn(remote.y);
+    return ret;
+}
+
+void ssh_ecdhkex_freekey(void *key)
+{
+    ecdsa_freekey(key);
+}
+
+static const struct eckex_extra kex_extra_curve25519 = { ec_curve25519 };
+static const struct ssh_kex ssh_ec_kex_curve25519 = {
+    "curve25519-sha256@libssh.org", NULL, KEXTYPE_ECDH,
+    &ssh_sha256, &kex_extra_curve25519,
+};
+
+const struct eckex_extra kex_extra_nistp256 = { ec_p256 };
+static const struct ssh_kex ssh_ec_kex_nistp256 = {
+    "ecdh-sha2-nistp256", NULL, KEXTYPE_ECDH,
+    &ssh_sha256, &kex_extra_nistp256,
+};
+
+const struct eckex_extra kex_extra_nistp384 = { ec_p384 };
+static const struct ssh_kex ssh_ec_kex_nistp384 = {
+    "ecdh-sha2-nistp384", NULL, KEXTYPE_ECDH,
+    &ssh_sha384, &kex_extra_nistp384,
+};
+
+const struct eckex_extra kex_extra_nistp521 = { ec_p521 };
+static const struct ssh_kex ssh_ec_kex_nistp521 = {
+    "ecdh-sha2-nistp521", NULL, KEXTYPE_ECDH,
+    &ssh_sha512, &kex_extra_nistp521,
+};
+
+static const struct ssh_kex *const ec_kex_list[] = {
+    &ssh_ec_kex_curve25519,
+    &ssh_ec_kex_nistp256,
+    &ssh_ec_kex_nistp384,
+    &ssh_ec_kex_nistp521,
+};
+
+const struct ssh_kexes ssh_ecdh_kex = {
+    sizeof(ec_kex_list) / sizeof(*ec_kex_list),
+    ec_kex_list
+};
+
+/* ----------------------------------------------------------------------
+ * Helper functions for finding key algorithms and returning auxiliary
+ * data.
+ */
+
+const struct ssh_signkey *ec_alg_by_oid(int len, const void *oid,
+                                        const struct ec_curve **curve)
+{
+    static const struct ssh_signkey *algs_with_oid[] = {
+        &ssh_ecdsa_nistp256,
+        &ssh_ecdsa_nistp384,
+        &ssh_ecdsa_nistp521,
+    };
+    int i;
+
+    for (i = 0; i < lenof(algs_with_oid); i++) {
+        const struct ssh_signkey *alg = algs_with_oid[i];
+        const struct ecsign_extra *extra =
+            (const struct ecsign_extra *)alg->extra;
+        if (len == extra->oidlen && !memcmp(oid, extra->oid, len)) {
+            *curve = extra->curve();
+            return alg;
+        }
+    }
+    return NULL;
+}
+
+const unsigned char *ec_alg_oid(const struct ssh_signkey *alg,
+                                int *oidlen)
+{
+    const struct ecsign_extra *extra = (const struct ecsign_extra *)alg->extra;
+    *oidlen = extra->oidlen;
+    return extra->oid;
+}
+
+const int ec_nist_alg_and_curve_by_bits(int bits,
+                                        const struct ec_curve **curve,
+                                        const struct ssh_signkey **alg)
+{
+    switch (bits) {
+      case 256: *alg = &ssh_ecdsa_nistp256; break;
+      case 384: *alg = &ssh_ecdsa_nistp384; break;
+      case 521: *alg = &ssh_ecdsa_nistp521; break;
+      default: return FALSE;
+    }
+    *curve = ((struct ecsign_extra *)(*alg)->extra)->curve();
+    return TRUE;
+}
+
+const int ec_ed_alg_and_curve_by_bits(int bits,
+                                      const struct ec_curve **curve,
+                                      const struct ssh_signkey **alg)
+{
+    switch (bits) {
+      case 256: *alg = &ssh_ecdsa_ed25519; break;
+      default: return FALSE;
+    }
+    *curve = ((struct ecsign_extra *)(*alg)->extra)->curve();
+    return TRUE;
+}
diff --git a/sshecdsag.c b/sshecdsag.c
new file mode 100644 (file)
index 0000000..83eeeb0
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * EC key generation.
+ */
+
+#include "ssh.h"
+
+/* Forward reference from sshecc.c */
+struct ec_point *ecp_mul(const struct ec_point *a, const Bignum b);
+
+int ec_generate(struct ec_key *key, int bits, progfn_t pfn,
+                void *pfnparam)
+{
+    struct ec_point *publicKey;
+
+    if (!ec_nist_alg_and_curve_by_bits(bits, &key->publicKey.curve,
+                                       &key->signalg))
+        return 0;
+
+    key->privateKey = bignum_random_in_range(One, key->publicKey.curve->w.n);
+    if (!key->privateKey) return 0;
+
+    publicKey = ec_public(key->privateKey, key->publicKey.curve);
+    if (!publicKey) {
+        freebn(key->privateKey);
+        key->privateKey = NULL;
+        return 0;
+    }
+
+    key->publicKey.x = publicKey->x;
+    key->publicKey.y = publicKey->y;
+    key->publicKey.z = NULL;
+    sfree(publicKey);
+
+    return 1;
+}
+
+int ec_edgenerate(struct ec_key *key, int bits, progfn_t pfn,
+                  void *pfnparam)
+{
+    struct ec_point *publicKey;
+
+    if (!ec_ed_alg_and_curve_by_bits(bits, &key->publicKey.curve,
+                                     &key->signalg))
+        return 0;
+
+    {
+        /* EdDSA secret keys are just 32 bytes of hash preimage; the
+         * 64-byte SHA-512 hash of that key will be used when signing,
+         * but the form of the key stored on disk is the preimage
+         * only. */
+        Bignum privMax = bn_power_2(bits);
+        if (!privMax) return 0;
+        key->privateKey = bignum_random_in_range(Zero, privMax);
+        freebn(privMax);
+        if (!key->privateKey) return 0;
+    }
+
+    publicKey = ec_public(key->privateKey, key->publicKey.curve);
+    if (!publicKey) {
+        freebn(key->privateKey);
+        key->privateKey = NULL;
+        return 0;
+    }
+
+    key->publicKey.x = publicKey->x;
+    key->publicKey.y = publicKey->y;
+    key->publicKey.z = NULL;
+    sfree(publicKey);
+
+    return 1;
+}
index ca5c426cc4de9b4290b1106917922b42b2ae3f11..b39dfd3e52dba4a399706c3769193c05ff738168 100644 (file)
--- a/sshmd5.c
+++ b/sshmd5.c
@@ -221,7 +221,7 @@ void MD5Simple(void const *p, unsigned len, unsigned char output[16])
  * useful elsewhere (SOCKS5 CHAP authentication uses HMAC-MD5).
  */
 
-void *hmacmd5_make_context(void)
+void *hmacmd5_make_context(void *cipher_ctx)
 {
     return snewn(3, struct MD5Context);
 }
@@ -336,7 +336,7 @@ const struct ssh_mac ssh_hmac_md5 = {
     hmacmd5_make_context, hmacmd5_free_context, hmacmd5_key_16,
     hmacmd5_generate, hmacmd5_verify,
     hmacmd5_start, hmacmd5_bytes, hmacmd5_genresult, hmacmd5_verresult,
-    "hmac-md5",
-    16,
+    "hmac-md5", "hmac-md5-etm@openssh.com",
+    16, 16,
     "HMAC-MD5"
 };
index a4ecb9d5c846082a6f82c4d9d783b148e27cc36b..0323335ee0bfb0e98ae18de25538627bb8313e7b 100644 (file)
--- a/sshpubk.c
+++ b/sshpubk.c
                           (x)=='+' ? 62 : \
                           (x)=='/' ? 63 : 0 )
 
+static int key_type_fp(FILE *fp);
+
 static int loadrsakey_main(FILE * fp, struct RSAKey *key, int pub_only,
-                          char **commentptr, char *passphrase,
+                          char **commentptr, const char *passphrase,
                           const char **error)
 {
     unsigned char buf[16384];
@@ -155,8 +157,8 @@ static int loadrsakey_main(FILE * fp, struct RSAKey *key, int pub_only,
     return ret;
 }
 
-int loadrsakey(const Filename *filename, struct RSAKey *key, char *passphrase,
-              const char **errorstr)
+int loadrsakey(const Filename *filename, struct RSAKey *key,
+               const char *passphrase, const char **errorstr)
 {
     FILE *fp;
     char buf[64];
@@ -261,6 +263,56 @@ int rsakey_pubblob(const Filename *filename, void **blob, int *bloblen,
        }
        fp = NULL; /* loadrsakey_main unconditionally closes fp */
     } else {
+        /*
+         * Try interpreting the file as an SSH-1 public key.
+         */
+        char *line, *p, *bitsp, *expp, *modp, *commentp;
+
+        rewind(fp);
+        line = chomp(fgetline(fp));
+        p = line;
+
+        bitsp = p;
+        p += strspn(p, "0123456789");
+        if (*p != ' ')
+            goto not_public_either;
+        *p++ = '\0';
+
+        expp = p;
+        p += strspn(p, "0123456789");
+        if (*p != ' ')
+            goto not_public_either;
+        *p++ = '\0';
+
+        modp = p;
+        p += strspn(p, "0123456789");
+        if (*p) {
+            if (*p != ' ')
+                goto not_public_either;
+            *p++ = '\0';
+            commentp = p;
+        } else {
+            commentp = NULL;
+        }
+
+       memset(&key, 0, sizeof(key));
+        key.exponent = bignum_from_decimal(expp);
+        key.modulus = bignum_from_decimal(modp);
+        if (atoi(bitsp) != bignum_bitcount(key.modulus)) {
+            freebn(key.exponent);
+            freebn(key.modulus);
+            sfree(line);
+            error = "key bit count does not match in SSH-1 public key file";
+            goto end;
+        }
+        if (commentptr)
+            *commentptr = commentp ? dupstr(commentp) : NULL;
+        *blob = rsa_public_blob(&key, bloblen);
+        freersakey(&key);
+        return 1;
+
+      not_public_either:
+        sfree(line);
        error = "not an SSH-1 RSA file";
     }
 
@@ -557,18 +609,32 @@ struct ssh2_userkey ssh2_wrong_passphrase = {
     NULL, NULL, NULL
 };
 
-const struct ssh_signkey *find_pubkey_alg(const char *name)
+const struct ssh_signkey *find_pubkey_alg_len(int namelen, const char *name)
 {
-    if (!strcmp(name, "ssh-rsa"))
+    if (match_ssh_id(namelen, name, "ssh-rsa"))
        return &ssh_rsa;
-    else if (!strcmp(name, "ssh-dss"))
+    else if (match_ssh_id(namelen, name, "ssh-dss"))
        return &ssh_dss;
+    else if (match_ssh_id(namelen, name, "ecdsa-sha2-nistp256"))
+        return &ssh_ecdsa_nistp256;
+    else if (match_ssh_id(namelen, name, "ecdsa-sha2-nistp384"))
+        return &ssh_ecdsa_nistp384;
+    else if (match_ssh_id(namelen, name, "ecdsa-sha2-nistp521"))
+        return &ssh_ecdsa_nistp521;
+    else if (match_ssh_id(namelen, name, "ssh-ed25519"))
+        return &ssh_ecdsa_ed25519;
     else
        return NULL;
 }
 
+const struct ssh_signkey *find_pubkey_alg(const char *name)
+{
+    return find_pubkey_alg_len(strlen(name), name);
+}
+
 struct ssh2_userkey *ssh2_load_userkey(const Filename *filename,
-                                      char *passphrase, const char **errorstr)
+                                      const char *passphrase,
+                                       const char **errorstr)
 {
     FILE *fp;
     char header[40], *b, *encryption, *comment, *mac;
@@ -787,7 +853,7 @@ struct ssh2_userkey *ssh2_load_userkey(const Filename *filename,
     ret = snew(struct ssh2_userkey);
     ret->alg = alg;
     ret->comment = comment;
-    ret->data = alg->createkey(public_blob, public_blob_len,
+    ret->data = alg->createkey(alg, public_blob, public_blob_len,
                               private_blob, private_blob_len);
     if (!ret->data) {
        sfree(ret);
@@ -826,6 +892,214 @@ struct ssh2_userkey *ssh2_load_userkey(const Filename *filename,
     return ret;
 }
 
+unsigned char *rfc4716_loadpub(FILE *fp, char **algorithm,
+                               int *pub_blob_len, char **commentptr,
+                               const char **errorstr)
+{
+    const char *error;
+    char *line, *colon, *value;
+    char *comment = NULL;
+    unsigned char *pubblob = NULL;
+    int pubbloblen, pubblobsize;
+    char base64in[4];
+    unsigned char base64out[3];
+    int base64bytes;
+    int alglen;
+
+    line = chomp(fgetline(fp));
+    if (!line || 0 != strcmp(line, "---- BEGIN SSH2 PUBLIC KEY ----")) {
+        error = "invalid begin line in SSH-2 public key file";
+        goto error;
+    }
+    sfree(line); line = NULL;
+
+    while (1) {
+        line = chomp(fgetline(fp));
+        if (!line) {
+            error = "truncated SSH-2 public key file";
+            goto error;
+        }
+        colon = strstr(line, ": ");
+        if (!colon)
+            break;
+        *colon = '\0';
+        value = colon + 2;
+
+        if (!strcmp(line, "Comment")) {
+            char *p, *q;
+
+            /* Remove containing double quotes, if present */
+            p = value;
+            if (*p == '"' && p[strlen(p)-1] == '"') {
+                p[strlen(p)-1] = '\0';
+                p++;
+            }
+
+            /* Remove \-escaping, not in RFC4716 but seen in the wild
+             * in practice. */
+            for (q = line; *p; p++) {
+                if (*p == '\\' && p[1])
+                    p++;
+                *q++ = *p;
+            }
+
+            *q = '\0';
+            comment = dupstr(line);
+        } else if (!strcmp(line, "Subject") ||
+                   !strncmp(line, "x-", 2)) {
+            /* Headers we recognise and ignore. Do nothing. */
+        } else {
+            error = "unrecognised header in SSH-2 public key file";
+            goto error;
+        }
+
+        sfree(line); line = NULL;
+    }
+
+    /*
+     * Now line contains the initial line of base64 data. Loop round
+     * while it still does contain base64.
+     */
+    pubblobsize = 4096;
+    pubblob = snewn(pubblobsize, unsigned char);
+    pubbloblen = 0;
+    base64bytes = 0;
+    while (line && line[0] != '-') {
+        char *p;
+        for (p = line; *p; p++) {
+            base64in[base64bytes++] = *p;
+            if (base64bytes == 4) {
+                int n = base64_decode_atom(base64in, base64out);
+                if (pubbloblen + n > pubblobsize) {
+                    pubblobsize = (pubbloblen + n) * 5 / 4 + 1024;
+                    pubblob = sresize(pubblob, pubblobsize, unsigned char);
+                }
+                memcpy(pubblob + pubbloblen, base64out, n);
+                pubbloblen += n;
+                base64bytes = 0;
+            }
+        }
+        sfree(line); line = NULL;
+        line = chomp(fgetline(fp));
+    }
+
+    /*
+     * Finally, check the END line makes sense.
+     */
+    if (!line || 0 != strcmp(line, "---- END SSH2 PUBLIC KEY ----")) {
+        error = "invalid end line in SSH-2 public key file";
+        goto error;
+    }
+    sfree(line); line = NULL;
+
+    /*
+     * OK, we now have a public blob and optionally a comment. We must
+     * return the key algorithm string too, so look for that at the
+     * start of the public blob.
+     */
+    if (pubbloblen < 4) {
+        error = "not enough data in SSH-2 public key file";
+        goto error;
+    }
+    alglen = toint(GET_32BIT(pubblob));
+    if (alglen < 0 || alglen > pubbloblen-4) {
+        error = "invalid algorithm prefix in SSH-2 public key file";
+        goto error;
+    }
+    if (algorithm)
+        *algorithm = dupprintf("%.*s", alglen, pubblob+4);
+    if (pub_blob_len)
+        *pub_blob_len = pubbloblen;
+    if (commentptr)
+        *commentptr = comment;
+    else
+        sfree(comment);
+    return pubblob;
+
+  error:
+    sfree(line);
+    sfree(comment);
+    sfree(pubblob);
+    if (errorstr)
+        *errorstr = error;
+    return NULL;
+}
+
+unsigned char *openssh_loadpub(FILE *fp, char **algorithm,
+                               int *pub_blob_len, char **commentptr,
+                               const char **errorstr)
+{
+    const char *error;
+    char *line, *base64;
+    char *comment = NULL;
+    unsigned char *pubblob = NULL;
+    int pubbloblen, pubblobsize;
+    int alglen;
+
+    line = chomp(fgetline(fp));
+
+    base64 = strchr(line, ' ');
+    if (!base64) {
+        error = "no key blob in OpenSSH public key file";
+        goto error;
+    }
+    *base64++ = '\0';
+
+    comment = strchr(base64, ' ');
+    if (comment) {
+        *comment++ = '\0';
+        comment = dupstr(comment);
+    }
+
+    pubblobsize = strlen(base64) / 4 * 3;
+    pubblob = snewn(pubblobsize, unsigned char);
+    pubbloblen = 0;
+
+    while (!memchr(base64, '\0', 4)) {
+        assert(pubbloblen + 3 <= pubblobsize);
+        pubbloblen += base64_decode_atom(base64, pubblob + pubbloblen);
+        base64 += 4;
+    }
+    if (*base64) {
+        error = "invalid length for base64 data in OpenSSH public key file";
+        goto error;
+    }
+
+    /*
+     * Sanity check: the first word on the line should be the key
+     * algorithm, and should match the encoded string at the start of
+     * the public blob.
+     */
+    alglen = strlen(line);
+    if (pubbloblen < alglen + 4 ||
+        GET_32BIT(pubblob) != alglen ||
+        0 != memcmp(pubblob + 4, line, alglen)) {
+        error = "key algorithms do not match in OpenSSH public key file";
+        goto error;
+    }
+
+    /*
+     * Done.
+     */
+    if (algorithm)
+        *algorithm = dupstr(line);
+    if (pub_blob_len)
+        *pub_blob_len = pubbloblen;
+    if (commentptr)
+        *commentptr = comment;
+    else
+        sfree(comment);
+    return pubblob;
+
+  error:
+    sfree(line);
+    sfree(comment);
+    sfree(pubblob);
+    if (errorstr)
+        *errorstr = error;
+    return NULL;
+}
+
 unsigned char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm,
                                    int *pub_blob_len, char **commentptr,
                                    const char **errorstr)
@@ -835,7 +1109,7 @@ unsigned char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm,
     const struct ssh_signkey *alg;
     unsigned char *public_blob;
     int public_blob_len;
-    int i;
+    int type, i;
     const char *error = NULL;
     char *comment = NULL;
 
@@ -847,6 +1121,24 @@ unsigned char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm,
        goto error;
     }
 
+    /* Initially, check if this is a public-only key file. Sometimes
+     * we'll be asked to read a public blob from one of those. */
+    type = key_type_fp(fp);
+    if (type == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716) {
+        unsigned char *ret = rfc4716_loadpub(fp, algorithm, pub_blob_len,
+                                             commentptr, errorstr);
+        fclose(fp);
+        return ret;
+    } else if (type == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {
+        unsigned char *ret = openssh_loadpub(fp, algorithm, pub_blob_len,
+                                             commentptr, errorstr);
+        fclose(fp);
+        return ret;
+    } else if (type != SSH_KEYTYPE_SSH2) {
+        error = "not a PuTTY SSH-2 private key";
+        goto error;
+    }
+
     /* Read the first header line which contains the key type. */
     if (!read_header(fp, header)
        || (0 != strcmp(header, "PuTTY-User-Key-File-2") &&
@@ -899,7 +1191,7 @@ unsigned char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm,
     if (pub_blob_len)
        *pub_blob_len = public_blob_len;
     if (algorithm)
-       *algorithm = alg->name;
+       *algorithm = dupstr(alg->name);
     return public_blob;
 
     /*
@@ -984,7 +1276,7 @@ int base64_lines(int datalen)
     return (datalen + 47) / 48;
 }
 
-void base64_encode(FILE * fp, unsigned char *data, int datalen, int cpl)
+void base64_encode(FILE *fp, const unsigned char *data, int datalen, int cpl)
 {
     int linelen = 0;
     char out[4];
@@ -1016,7 +1308,7 @@ int ssh2_save_userkey(const Filename *filename, struct ssh2_userkey *key,
     int passlen;
     int cipherblk;
     int i;
-    char *cipherstr;
+    const char *cipherstr;
     unsigned char priv_mac[20];
 
     /*
@@ -1142,51 +1434,280 @@ int ssh2_save_userkey(const Filename *filename, struct ssh2_userkey *key,
 }
 
 /* ----------------------------------------------------------------------
- * A function to determine the type of a private key file. Returns
- * 0 on failure, 1 or 2 on success.
+ * Output public keys.
  */
-int key_type(const Filename *filename)
+char *ssh1_pubkey_str(struct RSAKey *key)
 {
-    FILE *fp;
-    char buf[32];
+    char *buffer;
+    char *dec1, *dec2;
+
+    dec1 = bignum_decimal(key->exponent);
+    dec2 = bignum_decimal(key->modulus);
+    buffer = dupprintf("%d %s %s%s%s", bignum_bitcount(key->modulus),
+                      dec1, dec2,
+                       key->comment ? " " : "",
+                       key->comment ? key->comment : "");
+    sfree(dec1);
+    sfree(dec2);
+    return buffer;
+}
+
+void ssh1_write_pubkey(FILE *fp, struct RSAKey *key)
+{
+    char *buffer = ssh1_pubkey_str(key);
+    fprintf(fp, "%s\n", buffer);
+    sfree(buffer);
+}
+
+static char *ssh2_pubkey_openssh_str_internal(const char *comment,
+                                              const void *v_pub_blob,
+                                              int pub_len)
+{
+    const unsigned char *ssh2blob = (const unsigned char *)v_pub_blob;
+    const char *alg;
+    int alglen;
+    char *buffer, *p;
+    int i;
+
+    if (pub_len < 4) {
+        alg = NULL;
+    } else {
+        alglen = GET_32BIT(ssh2blob);
+        if (alglen > 0 && alglen < pub_len - 4) {
+            alg = (const char *)ssh2blob + 4;
+        } else {
+            alg = NULL;
+        }
+    }
+
+    if (!alg) {
+        alg = "INVALID-ALGORITHM";
+        alglen = strlen(alg);
+    }
+
+    buffer = snewn(alglen +
+                   4 * ((pub_len+2) / 3) +
+                   (comment ? strlen(comment) : 0) + 3, char);
+    p = buffer + sprintf(buffer, "%.*s ", alglen, alg);
+    i = 0;
+    while (i < pub_len) {
+        int n = (pub_len - i < 3 ? pub_len - i : 3);
+        base64_encode_atom(ssh2blob + i, n, p);
+        i += n;
+        p += 4;
+    }
+    if (*comment) {
+        *p++ = ' ';
+        strcpy(p, comment);
+    } else
+        *p++ = '\0';
+
+    return buffer;
+}
+
+char *ssh2_pubkey_openssh_str(struct ssh2_userkey *key)
+{
+    int bloblen;
+    unsigned char *blob;
+    char *ret;
+
+    blob = key->alg->public_blob(key->data, &bloblen);
+    ret = ssh2_pubkey_openssh_str_internal(key->comment, blob, bloblen);
+    sfree(blob);
+
+    return ret;
+}
+
+void ssh2_write_pubkey(FILE *fp, const char *comment,
+                       const void *v_pub_blob, int pub_len,
+                       int keytype)
+{
+    unsigned char *pub_blob = (unsigned char *)v_pub_blob;
+
+    if (keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716) {
+        const char *p;
+        int i, column;
+
+        fprintf(fp, "---- BEGIN SSH2 PUBLIC KEY ----\n");
+
+        if (comment) {
+            fprintf(fp, "Comment: \"");
+            for (p = comment; *p; p++) {
+                if (*p == '\\' || *p == '\"')
+                    fputc('\\', fp);
+                fputc(*p, fp);
+            }
+            fprintf(fp, "\"\n");
+        }
+
+        i = 0;
+        column = 0;
+        while (i < pub_len) {
+            char buf[5];
+            int n = (pub_len - i < 3 ? pub_len - i : 3);
+            base64_encode_atom(pub_blob + i, n, buf);
+            i += n;
+            buf[4] = '\0';
+            fputs(buf, fp);
+            if (++column >= 16) {
+                fputc('\n', fp);
+                column = 0;
+            }
+        }
+        if (column > 0)
+            fputc('\n', fp);
+
+        fprintf(fp, "---- END SSH2 PUBLIC KEY ----\n");
+    } else if (keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {
+        char *buffer = ssh2_pubkey_openssh_str_internal(comment,
+                                                        v_pub_blob, pub_len);
+        fprintf(fp, "%s\n", buffer);
+        sfree(buffer);
+    } else {
+        assert(0 && "Bad key type in ssh2_write_pubkey");
+    }
+}
+
+/* ----------------------------------------------------------------------
+ * Utility functions to compute SSH-2 fingerprints in a uniform way.
+ */
+char *ssh2_fingerprint_blob(const void *blob, int bloblen)
+{
+    unsigned char digest[16];
+    char fingerprint_str[16*3];
+    const char *algstr;
+    int alglen;
+    const struct ssh_signkey *alg;
+    int i;
+
+    /*
+     * The fingerprint hash itself is always just the MD5 of the blob.
+     */
+    MD5Simple(blob, bloblen, digest);
+    for (i = 0; i < 16; i++)
+        sprintf(fingerprint_str + i*3, "%02x%s", digest[i], i==15 ? "" : ":");
+
+    /*
+     * Identify the key algorithm, if possible.
+     */
+    alglen = toint(GET_32BIT((const unsigned char *)blob));
+    if (alglen > 0 && alglen < bloblen-4) {
+        algstr = (const char *)blob + 4;
+
+        /*
+         * If we can actually identify the algorithm as one we know
+         * about, get hold of the key's bit count too.
+         */
+        alg = find_pubkey_alg_len(alglen, algstr);
+        if (alg) {
+            int bits = alg->pubkey_bits(alg, blob, bloblen);
+            return dupprintf("%.*s %d %s", alglen, algstr,
+                             bits, fingerprint_str);
+        } else {
+            return dupprintf("%.*s %s", alglen, algstr, fingerprint_str);
+        }
+    } else {
+        /*
+         * No algorithm available (which means a seriously confused
+         * key blob, but there we go). Return only the hash.
+         */
+        return dupstr(fingerprint_str);
+    }
+}
+
+char *ssh2_fingerprint(const struct ssh_signkey *alg, void *data)
+{
+    int len;
+    unsigned char *blob = alg->public_blob(data, &len);
+    char *ret = ssh2_fingerprint_blob(blob, len);
+    sfree(blob);
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Determine the type of a private key file.
+ */
+static int key_type_fp(FILE *fp)
+{
+    char buf[1024];
+    const char public_std_sig[] = "---- BEGIN SSH2 PUBLIC KEY";
     const char putty2_sig[] = "PuTTY-User-Key-File-";
     const char sshcom_sig[] = "---- BEGIN SSH2 ENCRYPTED PRIVAT";
+    const char openssh_new_sig[] = "-----BEGIN OPENSSH PRIVATE KEY";
     const char openssh_sig[] = "-----BEGIN ";
     int i;
+    char *p;
+
+    i = fread(buf, 1, sizeof(buf)-1, fp);
+    rewind(fp);
 
-    fp = f_open(filename, "r", FALSE);
-    if (!fp)
-       return SSH_KEYTYPE_UNOPENABLE;
-    i = fread(buf, 1, sizeof(buf), fp);
-    fclose(fp);
     if (i < 0)
        return SSH_KEYTYPE_UNOPENABLE;
     if (i < 32)
        return SSH_KEYTYPE_UNKNOWN;
+    assert(i > 0 && i < sizeof(buf));
+    buf[i] = '\0';
     if (!memcmp(buf, rsa_signature, sizeof(rsa_signature)-1))
        return SSH_KEYTYPE_SSH1;
+    if (!memcmp(buf, public_std_sig, sizeof(public_std_sig)-1))
+       return SSH_KEYTYPE_SSH2_PUBLIC_RFC4716;
     if (!memcmp(buf, putty2_sig, sizeof(putty2_sig)-1))
        return SSH_KEYTYPE_SSH2;
+    if (!memcmp(buf, openssh_new_sig, sizeof(openssh_new_sig)-1))
+       return SSH_KEYTYPE_OPENSSH_NEW;
     if (!memcmp(buf, openssh_sig, sizeof(openssh_sig)-1))
-       return SSH_KEYTYPE_OPENSSH;
+       return SSH_KEYTYPE_OPENSSH_PEM;
     if (!memcmp(buf, sshcom_sig, sizeof(sshcom_sig)-1))
        return SSH_KEYTYPE_SSHCOM;
+    if ((p = buf + strspn(buf, "0123456789"), *p == ' ') &&
+        (p = p+1 + strspn(p+1, "0123456789"), *p == ' ') &&
+        (p = p+1 + strspn(p+1, "0123456789"), *p == ' ' || *p == '\n' || !*p))
+       return SSH_KEYTYPE_SSH1_PUBLIC;
+    if ((p = buf + strcspn(buf, " "), find_pubkey_alg_len(p-buf, buf)) &&
+        (p = p+1 + strspn(p+1, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij"
+                          "klmnopqrstuvwxyz+/="),
+         *p == ' ' || *p == '\n' || !*p))
+       return SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH;
     return SSH_KEYTYPE_UNKNOWN;               /* unrecognised or EOF */
 }
 
+int key_type(const Filename *filename)
+{
+    FILE *fp;
+    int ret;
+
+    fp = f_open(filename, "r", FALSE);
+    if (!fp)
+       return SSH_KEYTYPE_UNOPENABLE;
+    ret = key_type_fp(fp);
+    fclose(fp);
+    return ret;
+}
+
 /*
  * Convert the type word to a string, for `wrong type' error
  * messages.
  */
-char *key_type_to_str(int type)
+const char *key_type_to_str(int type)
 {
     switch (type) {
       case SSH_KEYTYPE_UNOPENABLE: return "unable to open file"; break;
-      case SSH_KEYTYPE_UNKNOWN: return "not a private key"; break;
+      case SSH_KEYTYPE_UNKNOWN: return "not a recognised key file format"; break;
+      case SSH_KEYTYPE_SSH1_PUBLIC: return "SSH-1 public key"; break;
+      case SSH_KEYTYPE_SSH2_PUBLIC_RFC4716: return "SSH-2 public key (RFC 4716 format)"; break;
+      case SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH: return "SSH-2 public key (OpenSSH format)"; break;
       case SSH_KEYTYPE_SSH1: return "SSH-1 private key"; break;
       case SSH_KEYTYPE_SSH2: return "PuTTY SSH-2 private key"; break;
-      case SSH_KEYTYPE_OPENSSH: return "OpenSSH SSH-2 private key"; break;
+      case SSH_KEYTYPE_OPENSSH_PEM: return "OpenSSH SSH-2 private key (old PEM format)"; break;
+      case SSH_KEYTYPE_OPENSSH_NEW: return "OpenSSH SSH-2 private key (new format)"; break;
       case SSH_KEYTYPE_SSHCOM: return "ssh.com SSH-2 private key"; break;
+        /*
+         * This function is called with a key type derived from
+         * looking at an actual key file, so the output-only type
+         * OPENSSH_AUTO should never get here, and is much an INTERNAL
+         * ERROR as a code we don't even understand.
+         */
+      case SSH_KEYTYPE_OPENSSH_AUTO: return "INTERNAL ERROR (OPENSSH_AUTO)"; break;
       default: return "INTERNAL ERROR"; break;
     }
 }
index ead39a9bdad917ec144faddd1ee4df1ec2cd22fd..0fbefb48370179f7b6b3fdf3bcd24c014dc0b6c6 100644 (file)
--- a/sshrand.c
+++ b/sshrand.c
@@ -45,8 +45,23 @@ struct RandPool {
     int stir_pending;
 };
 
-static struct RandPool pool;
 int random_active = 0;
+
+#ifdef FUZZING
+/*
+ * Special dummy version of the RNG for use when fuzzing.
+ */
+void random_add_noise(void *noise, int length) { }
+void random_add_heavynoise(void *noise, int length) { }
+void random_ref(void) { }
+void random_unref(void) { }
+int random_byte(void)
+{
+    return 0x45; /* Chosen by eight fair coin tosses */
+}
+void random_get_savedata(void **data, int *len) { }
+#else /* !FUZZING */
+static struct RandPool pool;
 long next_noise_collection;
 
 #ifdef RANDOM_DIAGNOSTICS
@@ -326,3 +341,4 @@ void random_get_savedata(void **data, int *len)
     *data = buf;
     random_stir();
 }
+#endif
index 5c1991effe9d265558ae2117cfada116b0142baa..e565a64ac791ff7be104a17f27814f4962f32fc7 100644 (file)
--- a/sshrsa.c
+++ b/sshrsa.c
 #include "ssh.h"
 #include "misc.h"
 
-int makekey(unsigned char *data, int len, struct RSAKey *result,
-           unsigned char **keystr, int order)
+int makekey(const unsigned char *data, int len, struct RSAKey *result,
+           const unsigned char **keystr, int order)
 {
-    unsigned char *p = data;
+    const unsigned char *p = data;
     int i, n;
 
     if (len < 4)
@@ -59,7 +59,7 @@ int makekey(unsigned char *data, int len, struct RSAKey *result,
     return p - data;
 }
 
-int makeprivate(unsigned char *data, int len, struct RSAKey *result)
+int makeprivate(const unsigned char *data, int len, struct RSAKey *result)
 {
     return ssh1_read_bignum(data, len, &result->private_exponent);
 }
@@ -533,7 +533,8 @@ void freersakey(struct RSAKey *key)
  * Implementation of the ssh-rsa signing key type. 
  */
 
-static void getstring(char **data, int *datalen, char **p, int *length)
+static void getstring(const char **data, int *datalen,
+                      const char **p, int *length)
 {
     *p = NULL;
     if (*datalen < 4)
@@ -549,9 +550,9 @@ static void getstring(char **data, int *datalen, char **p, int *length)
     *data += *length;
     *datalen -= *length;
 }
-static Bignum getmp(char **data, int *datalen)
+static Bignum getmp(const char **data, int *datalen)
 {
-    char *p;
+    const char *p;
     int length;
     Bignum b;
 
@@ -564,9 +565,10 @@ static Bignum getmp(char **data, int *datalen)
 
 static void rsa2_freekey(void *key);   /* forward reference */
 
-static void *rsa2_newkey(char *data, int len)
+static void *rsa2_newkey(const struct ssh_signkey *self,
+                         const char *data, int len)
 {
-    char *p;
+    const char *p;
     int slen;
     struct RSAKey *rsa;
 
@@ -684,13 +686,14 @@ static unsigned char *rsa2_private_blob(void *key, int *len)
     return blob;
 }
 
-static void *rsa2_createkey(unsigned char *pub_blob, int pub_len,
-                           unsigned char *priv_blob, int priv_len)
+static void *rsa2_createkey(const struct ssh_signkey *self,
+                            const unsigned char *pub_blob, int pub_len,
+                           const unsigned char *priv_blob, int priv_len)
 {
     struct RSAKey *rsa;
-    char *pb = (char *) priv_blob;
+    const char *pb = (const char *) priv_blob;
 
-    rsa = rsa2_newkey((char *) pub_blob, pub_len);
+    rsa = rsa2_newkey(self, (char *) pub_blob, pub_len);
     rsa->private_exponent = getmp(&pb, &priv_len);
     rsa->p = getmp(&pb, &priv_len);
     rsa->q = getmp(&pb, &priv_len);
@@ -704,9 +707,10 @@ static void *rsa2_createkey(unsigned char *pub_blob, int pub_len,
     return rsa;
 }
 
-static void *rsa2_openssh_createkey(unsigned char **blob, int *len)
+static void *rsa2_openssh_createkey(const struct ssh_signkey *self,
+                                    const unsigned char **blob, int *len)
 {
-    char **b = (char **) blob;
+    const char **b = (const char **) blob;
     struct RSAKey *rsa;
 
     rsa = snew(struct RSAKey);
@@ -762,12 +766,13 @@ static int rsa2_openssh_fmtkey(void *key, unsigned char *blob, int len)
     return bloblen;
 }
 
-static int rsa2_pubkey_bits(void *blob, int len)
+static int rsa2_pubkey_bits(const struct ssh_signkey *self,
+                            const void *blob, int len)
 {
     struct RSAKey *rsa;
     int ret;
 
-    rsa = rsa2_newkey((char *) blob, len);
+    rsa = rsa2_newkey(self, (const char *) blob, len);
     if (!rsa)
        return -1;
     ret = bignum_bitcount(rsa->modulus);
@@ -776,41 +781,6 @@ static int rsa2_pubkey_bits(void *blob, int len)
     return ret;
 }
 
-static char *rsa2_fingerprint(void *key)
-{
-    struct RSAKey *rsa = (struct RSAKey *) key;
-    struct MD5Context md5c;
-    unsigned char digest[16], lenbuf[4];
-    char buffer[16 * 3 + 40];
-    char *ret;
-    int numlen, i;
-
-    MD5Init(&md5c);
-    MD5Update(&md5c, (unsigned char *)"\0\0\0\7ssh-rsa", 11);
-
-#define ADD_BIGNUM(bignum) \
-    numlen = (bignum_bitcount(bignum)+8)/8; \
-    PUT_32BIT(lenbuf, numlen); MD5Update(&md5c, lenbuf, 4); \
-    for (i = numlen; i-- ;) { \
-        unsigned char c = bignum_byte(bignum, i); \
-        MD5Update(&md5c, &c, 1); \
-    }
-    ADD_BIGNUM(rsa->exponent);
-    ADD_BIGNUM(rsa->modulus);
-#undef ADD_BIGNUM
-
-    MD5Final(digest, &md5c);
-
-    sprintf(buffer, "ssh-rsa %d ", bignum_bitcount(rsa->modulus));
-    for (i = 0; i < 16; i++)
-       sprintf(buffer + strlen(buffer), "%s%02x", i ? ":" : "",
-               digest[i]);
-    ret = snewn(strlen(buffer) + 1, char);
-    if (ret)
-       strcpy(ret, buffer);
-    return ret;
-}
-
 /*
  * This is the magic ASN.1/DER prefix that goes in the decoded
  * signature, between the string of FFs and the actual SHA hash
@@ -842,12 +812,12 @@ static const unsigned char asn1_weird_stuff[] = {
 
 #define ASN1_LEN ( (int) sizeof(asn1_weird_stuff) )
 
-static int rsa2_verifysig(void *key, char *sig, int siglen,
-                         char *data, int datalen)
+static int rsa2_verifysig(void *key, const char *sig, int siglen,
+                         const char *data, int datalen)
 {
     struct RSAKey *rsa = (struct RSAKey *) key;
     Bignum in, out;
-    char *p;
+    const char *p;
     int slen;
     int bytes, i, j, ret;
     unsigned char hash[20];
@@ -892,7 +862,7 @@ static int rsa2_verifysig(void *key, char *sig, int siglen,
     return ret;
 }
 
-static unsigned char *rsa2_sign(void *key, char *data, int datalen,
+static unsigned char *rsa2_sign(void *key, const char *data, int datalen,
                                int *siglen)
 {
     struct RSAKey *rsa = (struct RSAKey *) key;
@@ -944,17 +914,18 @@ const struct ssh_signkey ssh_rsa = {
     rsa2_createkey,
     rsa2_openssh_createkey,
     rsa2_openssh_fmtkey,
+    6 /* n,e,d,iqmp,q,p */,
     rsa2_pubkey_bits,
-    rsa2_fingerprint,
     rsa2_verifysig,
     rsa2_sign,
     "ssh-rsa",
-    "rsa2"
+    "rsa2",
+    NULL,
 };
 
 void *ssh_rsakex_newkey(char *data, int len)
 {
-    return rsa2_newkey(data, len);
+    return rsa2_newkey(&ssh_rsa, data, len);
 }
 
 void ssh_rsakex_freekey(void *key)
@@ -1089,11 +1060,11 @@ void ssh_rsakex_encrypt(const struct ssh_hash *h, unsigned char *in, int inlen,
 }
 
 static const struct ssh_kex ssh_rsa_kex_sha1 = {
-    "rsa1024-sha1", NULL, KEXTYPE_RSA, NULL, NULL, 0, 0, &ssh_sha1
+    "rsa1024-sha1", NULL, KEXTYPE_RSA, &ssh_sha1, NULL,
 };
 
 static const struct ssh_kex ssh_rsa_kex_sha256 = {
-    "rsa2048-sha256", NULL, KEXTYPE_RSA, NULL, NULL, 0, 0, &ssh_sha256
+    "rsa2048-sha256", NULL, KEXTYPE_RSA, &ssh_sha256, NULL,
 };
 
 static const struct ssh_kex *const rsa_kex_list[] = {
index 2f7b52ba422f7a074299bfed72fccd39dcf6cbc8..4186f3e8180fc54b54fdefa19851200fd17157fe 100644 (file)
@@ -200,7 +200,25 @@ static void *sha256_init(void)
     return s;
 }
 
-static void sha256_bytes(void *handle, void *p, int len)
+static void *sha256_copy(const void *vold)
+{
+    const SHA256_State *old = (const SHA256_State *)vold;
+    SHA256_State *s;
+
+    s = snew(SHA256_State);
+    *s = *old;
+    return s;
+}
+
+static void sha256_free(void *handle)
+{
+    SHA256_State *s = handle;
+
+    smemclr(s, sizeof(*s));
+    sfree(s);
+}
+
+static void sha256_bytes(void *handle, const void *p, int len)
 {
     SHA256_State *s = handle;
 
@@ -212,12 +230,12 @@ static void sha256_final(void *handle, unsigned char *output)
     SHA256_State *s = handle;
 
     SHA256_Final(s, output);
-    smemclr(s, sizeof(*s));
-    sfree(s);
+    sha256_free(s);
 }
 
 const struct ssh_hash ssh_sha256 = {
-    sha256_init, sha256_bytes, sha256_final, 32, "SHA-256"
+    sha256_init, sha256_copy, sha256_bytes, sha256_final, sha256_free,
+    32, "SHA-256"
 };
 
 /* ----------------------------------------------------------------------
@@ -225,7 +243,7 @@ const struct ssh_hash ssh_sha256 = {
  * HMAC wrapper on it.
  */
 
-static void *sha256_make_context(void)
+static void *sha256_make_context(void *cipher_ctx)
 {
     return snewn(3, SHA256_State);
 }
@@ -326,8 +344,8 @@ const struct ssh_mac ssh_hmac_sha256 = {
     sha256_generate, sha256_verify,
     hmacsha256_start, hmacsha256_bytes,
     hmacsha256_genresult, hmacsha256_verresult,
-    "hmac-sha2-256",
-    32,
+    "hmac-sha2-256", "hmac-sha2-256-etm@openssh.com",
+    32, 32,
     "HMAC-SHA-256"
 };
 
index eef733e67c8f55b986850f70ee4c0bdb71f48208..bdfc1e9dc71298630dfb444d49f83114136896c8 100644 (file)
@@ -2,6 +2,8 @@
  * SHA-512 algorithm as described at
  * 
  *   http://csrc.nist.gov/cryptval/shs.html
+ *
+ * Modifications made for SHA-384 also
  */
 
 #include "ssh.h"
@@ -61,6 +63,22 @@ static void SHA512_Core_Init(SHA512_State *s) {
        s->h[i] = iv[i];
 }
 
+static void SHA384_Core_Init(SHA512_State *s) {
+    static const uint64 iv[] = {
+        INIT(0xcbbb9d5d, 0xc1059ed8),
+        INIT(0x629a292a, 0x367cd507),
+        INIT(0x9159015a, 0x3070dd17),
+        INIT(0x152fecd8, 0xf70e5939),
+        INIT(0x67332667, 0xffc00b31),
+        INIT(0x8eb44a87, 0x68581511),
+        INIT(0xdb0c2e0d, 0x64f98fa7),
+        INIT(0x47b5481d, 0xbefa4fa4),
+    };
+    int i;
+    for (i = 0; i < 8; i++)
+        s->h[i] = iv[i];
+}
+
 static void SHA512_Block(SHA512_State *s, uint64 *block) {
     uint64 w[80];
     uint64 a,b,c,d,e,f,g,h;
@@ -175,6 +193,14 @@ void SHA512_Init(SHA512_State *s) {
        s->len[i] = 0;
 }
 
+void SHA384_Init(SHA512_State *s) {
+    int i;
+    SHA384_Core_Init(s);
+    s->blkused = 0;
+    for (i = 0; i < 4; i++)
+        s->len[i] = 0;
+}
+
 void SHA512_Bytes(SHA512_State *s, const void *p, int len) {
     unsigned char *q = (unsigned char *)p;
     uint64 wordblock[16];
@@ -268,6 +294,12 @@ void SHA512_Final(SHA512_State *s, unsigned char *digest) {
     }
 }
 
+void SHA384_Final(SHA512_State *s, unsigned char *digest) {
+    unsigned char biggerDigest[512 / 8];
+    SHA512_Final(s, biggerDigest);
+    memcpy(digest, biggerDigest, 384 / 8);
+}
+
 void SHA512_Simple(const void *p, int len, unsigned char *output) {
     SHA512_State s;
 
@@ -277,6 +309,89 @@ void SHA512_Simple(const void *p, int len, unsigned char *output) {
     smemclr(&s, sizeof(s));
 }
 
+void SHA384_Simple(const void *p, int len, unsigned char *output) {
+    SHA512_State s;
+
+    SHA384_Init(&s);
+    SHA512_Bytes(&s, p, len);
+    SHA384_Final(&s, output);
+    smemclr(&s, sizeof(s));
+}
+
+/*
+ * Thin abstraction for things where hashes are pluggable.
+ */
+
+static void *sha512_init(void)
+{
+    SHA512_State *s;
+
+    s = snew(SHA512_State);
+    SHA512_Init(s);
+    return s;
+}
+
+static void *sha512_copy(const void *vold)
+{
+    const SHA512_State *old = (const SHA512_State *)vold;
+    SHA512_State *s;
+
+    s = snew(SHA512_State);
+    *s = *old;
+    return s;
+}
+
+static void sha512_free(void *handle)
+{
+    SHA512_State *s = handle;
+
+    smemclr(s, sizeof(*s));
+    sfree(s);
+}
+
+static void sha512_bytes(void *handle, const void *p, int len)
+{
+    SHA512_State *s = handle;
+
+    SHA512_Bytes(s, p, len);
+}
+
+static void sha512_final(void *handle, unsigned char *output)
+{
+    SHA512_State *s = handle;
+
+    SHA512_Final(s, output);
+    sha512_free(s);
+}
+
+const struct ssh_hash ssh_sha512 = {
+    sha512_init, sha512_copy, sha512_bytes, sha512_final, sha512_free,
+    64, "SHA-512"
+};
+
+static void *sha384_init(void)
+{
+    SHA512_State *s;
+
+    s = snew(SHA512_State);
+    SHA384_Init(s);
+    return s;
+}
+
+static void sha384_final(void *handle, unsigned char *output)
+{
+    SHA512_State *s = handle;
+
+    SHA384_Final(s, output);
+    smemclr(s, sizeof(*s));
+    sfree(s);
+}
+
+const struct ssh_hash ssh_sha384 = {
+    sha384_init, sha512_copy, sha512_bytes, sha384_final, sha512_free,
+    48, "SHA-384"
+};
+
 #ifdef TEST
 
 #include <stdio.h>
index 747e44140ad4cb47997c36ab68bc67705fd7936e..c10a82177ced3d64294a51747ec0f3155bd72bd8 100644 (file)
--- a/sshsha.c
+++ b/sshsha.c
@@ -230,7 +230,25 @@ static void *sha1_init(void)
     return s;
 }
 
-static void sha1_bytes(void *handle, void *p, int len)
+static void *sha1_copy(const void *vold)
+{
+    const SHA_State *old = (const SHA_State *)vold;
+    SHA_State *s;
+
+    s = snew(SHA_State);
+    *s = *old;
+    return s;
+}
+
+static void sha1_free(void *handle)
+{
+    SHA_State *s = handle;
+
+    smemclr(s, sizeof(*s));
+    sfree(s);
+}
+
+static void sha1_bytes(void *handle, const void *p, int len)
 {
     SHA_State *s = handle;
 
@@ -242,12 +260,11 @@ static void sha1_final(void *handle, unsigned char *output)
     SHA_State *s = handle;
 
     SHA_Final(s, output);
-    smemclr(s, sizeof(*s));
-    sfree(s);
+    sha1_free(s);
 }
 
 const struct ssh_hash ssh_sha1 = {
-    sha1_init, sha1_bytes, sha1_final, 20, "SHA-1"
+    sha1_init, sha1_copy, sha1_bytes, sha1_final, sha1_free, 20, "SHA-1"
 };
 
 /* ----------------------------------------------------------------------
@@ -255,7 +272,7 @@ const struct ssh_hash ssh_sha1 = {
  * HMAC wrapper on it.
  */
 
-static void *sha1_make_context(void)
+static void *sha1_make_context(void *cipher_ctx)
 {
     return snewn(3, SHA_State);
 }
@@ -403,8 +420,8 @@ const struct ssh_mac ssh_hmac_sha1 = {
     sha1_make_context, sha1_free_context, sha1_key,
     sha1_generate, sha1_verify,
     hmacsha1_start, hmacsha1_bytes, hmacsha1_genresult, hmacsha1_verresult,
-    "hmac-sha1",
-    20,
+    "hmac-sha1", "hmac-sha1-etm@openssh.com",
+    20, 20,
     "HMAC-SHA1"
 };
 
@@ -413,8 +430,8 @@ const struct ssh_mac ssh_hmac_sha1_96 = {
     sha1_96_generate, sha1_96_verify,
     hmacsha1_start, hmacsha1_bytes,
     hmacsha1_96_genresult, hmacsha1_96_verresult,
-    "hmac-sha1-96",
-    12,
+    "hmac-sha1-96", "hmac-sha1-96-etm@openssh.com",
+    12, 20,
     "HMAC-SHA1-96"
 };
 
@@ -422,8 +439,8 @@ const struct ssh_mac ssh_hmac_sha1_buggy = {
     sha1_make_context, sha1_free_context, sha1_key_buggy,
     sha1_generate, sha1_verify,
     hmacsha1_start, hmacsha1_bytes, hmacsha1_genresult, hmacsha1_verresult,
-    "hmac-sha1",
-    20,
+    "hmac-sha1", NULL,
+    20, 16,
     "bug-compatible HMAC-SHA1"
 };
 
@@ -432,7 +449,7 @@ const struct ssh_mac ssh_hmac_sha1_96_buggy = {
     sha1_96_generate, sha1_96_verify,
     hmacsha1_start, hmacsha1_bytes,
     hmacsha1_96_genresult, hmacsha1_96_verresult,
-    "hmac-sha1-96",
-    12,
+    "hmac-sha1-96", NULL,
+    12, 16,
     "bug-compatible HMAC-SHA1-96"
 };
index 1c0e3cba8f64f41a6748a8e6a935739e7cd42076..82c4bd31ed8867755fcad1b467356450c3df5410 100644 (file)
 #include <stdlib.h>
 #include <assert.h>
 #include <limits.h>
+#include <errno.h>
 
 #include "putty.h"
 #include "tree234.h"
@@ -914,8 +915,25 @@ static int share_closing(Plug plug, const char *error_msg, int error_code,
                          int calling_back)
 {
     struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)plug;
-    if (error_msg)
-        ssh_sharing_logf(cs->parent->ssh, cs->id, "%s", error_msg);
+
+    if (error_msg) {
+#ifdef BROKEN_PIPE_ERROR_CODE
+        /*
+         * Most of the time, we log what went wrong when a downstream
+         * disappears with a socket error. One exception, though, is
+         * receiving EPIPE when we haven't received a protocol version
+         * string from the downstream, because that can happen as a result
+         * of plink -shareexists (opening the connection and instantly
+         * closing it again without bothering to read our version string).
+         * So that one case is not treated as a log-worthy error.
+         */
+        if (error_code == BROKEN_PIPE_ERROR_CODE && !cs->got_verstring)
+            /* do nothing */;
+        else
+#endif
+            ssh_sharing_logf(cs->parent->ssh, cs->id,
+                             "Socket error: %s", error_msg);
+    }
     share_begin_cleanup(cs);
     return 1;
 }
@@ -1810,6 +1828,7 @@ static int share_receive(Plug plug, int urgent, char *data, int len)
     ssh_sharing_logf(cs->parent->ssh, cs->id,
                      "Downstream version string: %.*s",
                      cs->recvlen, cs->recvbuf);
+    cs->got_verstring = TRUE;
 
     /*
      * Loop round reading packets.
@@ -1982,6 +2001,99 @@ static int share_listen_accepting(Plug plug,
 extern const int share_can_be_downstream;
 extern const int share_can_be_upstream;
 
+/*
+ * Decide on the string used to identify the connection point between
+ * upstream and downstream (be it a Windows named pipe or a
+ * Unix-domain socket or whatever else).
+ *
+ * I wondered about making this a SHA hash of all sorts of pieces of
+ * the PuTTY configuration - essentially everything PuTTY uses to know
+ * where and how to make a connection, including all the proxy details
+ * (or rather, all the _relevant_ ones - only including settings that
+ * other settings didn't prevent from having any effect), plus the
+ * username. However, I think it's better to keep it really simple:
+ * the connection point identifier is derived from the hostname and
+ * port used to index the host-key cache (not necessarily where we
+ * _physically_ connected to, in cases involving proxies or
+ * CONF_loghost), plus the username if one is specified.
+ *
+ * The per-platform code will quite likely hash or obfuscate this name
+ * in turn, for privacy from other users; failing that, it might
+ * transform it to avoid dangerous filename characters and so on. But
+ * that doesn't matter to us: for us, the point is that two session
+ * configurations which return the same string from this function will
+ * be treated as potentially shareable with each other.
+ */
+char *ssh_share_sockname(const char *host, int port, Conf *conf)
+{
+    char *username = get_remote_username(conf);
+    char *sockname;
+
+    if (port == 22) {
+        if (username)
+            sockname = dupprintf("%s@%s", username, host);
+        else
+            sockname = dupprintf("%s", host);
+    } else {
+        if (username)
+            sockname = dupprintf("%s@%s:%d", username, host, port);
+        else
+            sockname = dupprintf("%s:%d", host, port);
+    }
+
+    sfree(username);
+    return sockname;
+}
+
+static void nullplug_socket_log(Plug plug, int type, SockAddr addr, int port,
+                                const char *error_msg, int error_code) {}
+static int nullplug_closing(Plug plug, const char *error_msg, int error_code,
+                            int calling_back) { return 0; }
+static int nullplug_receive(Plug plug, int urgent, char *data,
+                            int len) { return 0; }
+static void nullplug_sent(Plug plug, int bufsize) {}
+
+int ssh_share_test_for_upstream(const char *host, int port, Conf *conf)
+{
+    static const struct plug_function_table fn_table = {
+       nullplug_socket_log,
+       nullplug_closing,
+       nullplug_receive,
+       nullplug_sent,
+       NULL
+    };
+    struct nullplug {
+        const struct plug_function_table *fn;
+    } np;
+
+    char *sockname, *logtext, *ds_err, *us_err;
+    int result;
+    Socket sock;
+
+    np.fn = &fn_table;
+
+    sockname = ssh_share_sockname(host, port, conf);
+
+    sock = NULL;
+    logtext = ds_err = us_err = NULL;
+    result = platform_ssh_share(sockname, conf, (Plug)&np, (Plug)NULL, &sock,
+                                &logtext, &ds_err, &us_err, FALSE, TRUE);
+
+    sfree(logtext);
+    sfree(ds_err);
+    sfree(us_err);
+    sfree(sockname);
+
+    if (result == SHARE_NONE) {
+        assert(sock == NULL);
+        return FALSE;
+    } else {
+        assert(result == SHARE_DOWNSTREAM);
+        sk_close(sock);
+        return TRUE;
+    }
+}
+
 /*
  * Init function for connection sharing. We either open a listening
  * socket and become an upstream, or connect to an existing one and
@@ -2018,47 +2130,7 @@ Socket ssh_connection_sharing_init(const char *host, int port,
     if (!can_upstream && !can_downstream)
         return NULL;
 
-    /*
-     * Decide on the string used to identify the connection point
-     * between upstream and downstream (be it a Windows named pipe or
-     * a Unix-domain socket or whatever else).
-     *
-     * I wondered about making this a SHA hash of all sorts of pieces
-     * of the PuTTY configuration - essentially everything PuTTY uses
-     * to know where and how to make a connection, including all the
-     * proxy details (or rather, all the _relevant_ ones - only
-     * including settings that other settings didn't prevent from
-     * having any effect), plus the username. However, I think it's
-     * better to keep it really simple: the connection point
-     * identifier is derived from the hostname and port used to index
-     * the host-key cache (not necessarily where we _physically_
-     * connected to, in cases involving proxies or CONF_loghost), plus
-     * the username if one is specified.
-     */
-    {
-        char *username = get_remote_username(conf);
-
-        if (port == 22) {
-            if (username)
-                sockname = dupprintf("%s@%s", username, host);
-            else
-                sockname = dupprintf("%s", host);
-        } else {
-            if (username)
-                sockname = dupprintf("%s@%s:%d", username, host, port);
-            else
-                sockname = dupprintf("%s:%d", host, port);
-        }
-
-        sfree(username);
-
-        /*
-         * The platform-specific code may transform this further in
-         * order to conform to local namespace conventions (e.g. not
-         * using slashes in filenames), but that's its job and not
-         * ours.
-         */
-    }
+    sockname = ssh_share_sockname(host, port, conf);
 
     /*
      * Create a data structure for the listening plug if we turn out
index c69edfb81f9a09648baa67c5cf909b3e83aaabcc..ee1bc42d3054b8707ea41ffa04da5719048ec205 100644 (file)
--- a/sshzlib.c
+++ b/sshzlib.c
@@ -1366,6 +1366,7 @@ int main(int argc, char **argv)
             sfree(outbuf);
         } else {
             fprintf(stderr, "decoding error\n");
+            fclose(fp);
             return 1;
         }
     }
index 098db292cc05a0c9a64e50db80ecf04954f78a71..c4b0413272be222e5e3c0806805c0f13a6a90ad6 100644 (file)
--- a/telnet.c
+++ b/telnet.c
@@ -113,7 +113,7 @@ enum { TELOPTS(telnet_enum) dummy=0 };
        ( (x) != IAC && \
              (telnet->opt_states[o_we_bin.index] == ACTIVE || (x) != CR))
 
-static char *telopt(int opt)
+static const char *telopt(int opt)
 {
 #define telnet_str(x,y) case TELOPT_##x: return #x;
     switch (opt) {
@@ -197,6 +197,7 @@ typedef struct telnet_tag {
     int sb_opt, sb_len;
     unsigned char *sb_buf;
     int sb_size;
+    int session_started;
 
     enum {
        TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT,
@@ -212,14 +213,14 @@ typedef struct telnet_tag {
 
 #define SB_DELTA 1024
 
-static void c_write(Telnet telnet, char *buf, int len)
+static void c_write(Telnet telnet, const char *buf, int len)
 {
     int backlog;
     backlog = from_backend(telnet->frontend, 0, buf, len);
     sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG);
 }
 
-static void log_option(Telnet telnet, char *sender, int cmd, int option)
+static void log_option(Telnet telnet, const char *sender, int cmd, int option)
 {
     char *buf;
     /*
@@ -264,7 +265,7 @@ static void option_side_effects(Telnet telnet, const struct Opt *o, int enabled)
     else if (o->option == TELOPT_SGA && o->send == DO)
        telnet->editing = !enabled;
     if (telnet->ldisc)                /* cause ldisc to notice the change */
-       ldisc_send(telnet->ldisc, NULL, 0, 0);
+       ldisc_echoedit_update(telnet->ldisc);
 
     /* Ensure we get the minimum options */
     if (!telnet->activated) {
@@ -652,17 +653,9 @@ static void telnet_log(Plug plug, int type, SockAddr addr, int port,
                       const char *error_msg, int error_code)
 {
     Telnet telnet = (Telnet) plug;
-    char addrbuf[256], *msg;
-
-    sk_getaddr(addr, addrbuf, lenof(addrbuf));
-
-    if (type == 0)
-       msg = dupprintf("Connecting to %s port %d", addrbuf, port);
-    else
-       msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg);
-
-    logevent(telnet->frontend, msg);
-    sfree(msg);
+    backend_socket_log(telnet->frontend, type, addr, port,
+                       error_msg, error_code, telnet->conf,
+                       telnet->session_started);
 }
 
 static int telnet_closing(Plug plug, const char *error_msg, int error_code,
@@ -696,6 +689,7 @@ static int telnet_receive(Plug plug, int urgent, char *data, int len)
     Telnet telnet = (Telnet) plug;
     if (urgent)
        telnet->in_synch = TRUE;
+    telnet->session_started = TRUE;
     do_telnet_read(telnet, data, len);
     return 1;
 }
@@ -715,7 +709,7 @@ static void telnet_sent(Plug plug, int bufsize)
  * freed by the caller.
  */
 static const char *telnet_init(void *frontend_handle, void **backend_handle,
-                              Conf *conf, char *host, int port,
+                              Conf *conf, const char *host, int port,
                               char **realhost, int nodelay, int keepalive)
 {
     static const struct plug_function_table fn_table = {
@@ -746,22 +740,15 @@ static const char *telnet_init(void *frontend_handle, void **backend_handle,
     telnet->state = TOP_LEVEL;
     telnet->ldisc = NULL;
     telnet->pinger = NULL;
+    telnet->session_started = TRUE;
     *backend_handle = telnet;
 
     /*
      * Try to find host.
      */
-    {
-       char *buf;
-       addressfamily = conf_get_int(telnet->conf, CONF_addressfamily);
-       buf = dupprintf("Looking up host \"%s\"%s", host,
-                       (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" :
-                        (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" :
-                         "")));
-       logevent(telnet->frontend, buf);
-       sfree(buf);
-    }
-    addr = name_lookup(host, port, realhost, telnet->conf, addressfamily);
+    addressfamily = conf_get_int(telnet->conf, CONF_addressfamily);
+    addr = name_lookup(host, port, realhost, telnet->conf, addressfamily,
+                       telnet->frontend, "Telnet connection");
     if ((err = sk_addr_error(addr)) != NULL) {
        sk_addr_free(addr);
        return err;
@@ -855,7 +842,7 @@ static void telnet_reconfig(void *handle, Conf *conf)
 /*
  * Called to send data down the Telnet connection.
  */
-static int telnet_send(void *handle, char *buf, int len)
+static int telnet_send(void *handle, const char *buf, int len)
 {
     Telnet telnet = (Telnet) handle;
     unsigned char *p, *end;
@@ -1129,6 +1116,7 @@ Backend telnet_backend = {
     telnet_provide_logctx,
     telnet_unthrottle,
     telnet_cfg_info,
+    NULL /* test_for_upstream */,
     "telnet",
     PROT_TELNET,
     23
index ae85eb58a6a892d2a26c16788707fd6027af70ec..26a3f0c93f64b90228802ea6a50027a6500dd21d 100644 (file)
@@ -66,7 +66,7 @@
 
 #define has_compat(x) ( ((CL_##x)&term->compatibility_level) != 0 )
 
-char *EMPTY_WINDOW_TITLE = "";
+const char *EMPTY_WINDOW_TITLE = "";
 
 const char sco2ansicolour[] = { 0, 4, 2, 6, 1, 5, 3, 7 };
 
@@ -1351,7 +1351,7 @@ void term_pwron(Terminal *term, int clear)
 {
     power_on(term, clear);
     if (term->ldisc)                  /* cause ldisc to notice changes */
-       ldisc_send(term->ldisc, NULL, 0, 0);
+       ldisc_echoedit_update(term->ldisc);
     term->disptop = 0;
     deselect(term);
     term_update(term);
@@ -2575,7 +2575,7 @@ static void toggle_mode(Terminal *term, int mode, int query, int state)
          case 10:                     /* DECEDM: set local edit mode */
            term->term_editing = state;
            if (term->ldisc)           /* cause ldisc to notice changes */
-               ldisc_send(term->ldisc, NULL, 0, 0);
+               ldisc_echoedit_update(term->ldisc);
            break;
          case 25:                     /* DECTCEM: enable/disable cursor */
            compatibility2(OTHER, VT220);
@@ -2639,7 +2639,7 @@ static void toggle_mode(Terminal *term, int mode, int query, int state)
          case 12:                     /* SRM: set echo mode */
            term->term_echoing = !state;
            if (term->ldisc)           /* cause ldisc to notice changes */
-               ldisc_send(term->ldisc, NULL, 0, 0);
+               ldisc_echoedit_update(term->ldisc);
            break;
          case 20:                     /* LNM: Return sends ... */
            term->cr_lf_return = state;
@@ -3362,7 +3362,7 @@ static void term_out(Terminal *term)
                    compatibility(VT100);
                    power_on(term, TRUE);
                    if (term->ldisc)   /* cause ldisc to notice changes */
-                       ldisc_send(term->ldisc, NULL, 0, 0);
+                       ldisc_echoedit_update(term->ldisc);
                    if (term->reset_132) {
                        if (!term->no_remote_resize)
                            request_resize(term->frontend, 80, term->rows);
@@ -3965,7 +3965,8 @@ static void term_out(Terminal *term)
 
                            switch (term->esc_args[0]) {
                                int x, y, len;
-                               char buf[80], *p;
+                               char buf[80];
+                                const char *p;
                              case 1:
                                set_iconic(term->frontend, FALSE);
                                break;
@@ -4724,7 +4725,7 @@ static void term_out(Terminal *term)
     }
 
     term_print_flush(term);
-    if (term->logflush)
+    if (term->logflush && term->logctx)
        logflush(term->logctx);
 }
 
@@ -6079,7 +6080,8 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked,
            } else if (c <= 223 && r <= 223) {
                len = sprintf(abuf, "\033[M%c%c%c", encstate + 32, c + 32, r + 32);
            }
-           ldisc_send(term->ldisc, abuf, len, 0);
+            if (len > 0)
+                ldisc_send(term->ldisc, abuf, len, 0);
        }
        return;
     }
@@ -6113,6 +6115,19 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked,
        sel_spread(term);
     } else if ((bcooked == MBT_SELECT && a == MA_DRAG) ||
               (bcooked == MBT_EXTEND && a != MA_RELEASE)) {
+        if (a == MA_DRAG &&
+            (term->selstate == NO_SELECTION || term->selstate == SELECTED)) {
+            /*
+             * This can happen if a front end has passed us a MA_DRAG
+             * without a prior MA_CLICK. OS X GTK does so, for
+             * example, if the initial button press was eaten by the
+             * WM when it activated the window in the first place. The
+             * nicest thing to do in this situation is to ignore
+             * further drags, and wait for the user to click in the
+             * window again properly if they want to select.
+             */
+            return;
+        }
        if (term->selstate == ABOUT_TO && poseq(term->selanchor, selpoint))
            return;
        if (bcooked == MBT_EXTEND && a != MA_DRAG &&
@@ -6356,7 +6371,7 @@ void term_set_focus(Terminal *term, int has_focus)
  */
 char *term_get_ttymode(Terminal *term, const char *mode)
 {
-    char *val = NULL;
+    const char *val = NULL;
     if (strcmp(mode, "ERASE") == 0) {
        val = term->bksp_is_delete ? "^?" : "^H";
     }
@@ -6376,7 +6391,7 @@ struct term_userpass_state {
  * input.
  */
 int term_get_userpass_input(Terminal *term, prompts_t *p,
-                           unsigned char *in, int inlen)
+                           const unsigned char *in, int inlen)
 {
     struct term_userpass_state *s = (struct term_userpass_state *)p->data;
     if (!s) {
index bf3047efd8bb51248e2f541d00de58994a3cba3b..752ec4ca03dc73eaa9d4a772f3b2a048837cba68 100644 (file)
@@ -58,14 +58,14 @@ Backend null_backend = {
     null_init, null_free, null_reconfig, null_send, null_sendbuffer, null_size,
     null_special, null_get_specials, null_connected, null_exitcode, null_sendok,
     null_ldisc, null_provide_ldisc, null_provide_logctx, null_unthrottle,
-    null_cfg_info, "null", -1, 0
+    null_cfg_info, NULL /* test_for_upstream */, "null", -1, 0
 };
 
 Backend loop_backend = {
     loop_init, loop_free, null_reconfig, loop_send, null_sendbuffer, null_size,
     null_special, null_get_specials, null_connected, null_exitcode, null_sendok,
     null_ldisc, null_provide_ldisc, null_provide_logctx, null_unthrottle,
-    null_cfg_info, "loop", -1, 0
+    null_cfg_info, NULL /* test_for_upstream */, "loop", -1, 0
 };
 
 struct loop_state {
diff --git a/testbn.c b/testbn.c
new file mode 100644 (file)
index 0000000..15f3b98
--- /dev/null
+++ b/testbn.c
@@ -0,0 +1,268 @@
+/*
+ * testbn.c: standalone test program for the bignum code.
+ */
+
+/*
+ * Accepts input on standard input, in the form generated by
+ * testdata/bignum.py.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "ssh.h"
+#include "sshbn.h"
+
+void modalfatalbox(const char *p, ...)
+{
+    va_list ap;
+    fprintf(stderr, "FATAL ERROR: ");
+    va_start(ap, p);
+    vfprintf(stderr, p, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+    exit(1);
+}
+
+int random_byte(void)
+{
+    modalfatalbox("random_byte called in testbn");
+    return 0;
+}
+
+#define fromxdigit(c) ( (c)>'9' ? ((c)&0xDF) - 'A' + 10 : (c) - '0' )
+
+int main(int argc, char **argv)
+{
+    char *buf;
+    int line = 0;
+    int passes = 0, fails = 0;
+
+    printf("BIGNUM_INT_BITS = %d\n", (int)BIGNUM_INT_BITS);
+
+    while ((buf = fgetline(stdin)) != NULL) {
+        int maxlen = strlen(buf);
+        unsigned char *data = snewn(maxlen, unsigned char);
+        unsigned char *ptrs[5], *q;
+        int ptrnum;
+        char *bufp = buf;
+
+        line++;
+
+        q = data;
+        ptrnum = 0;
+
+        while (*bufp && !isspace((unsigned char)*bufp))
+            bufp++;
+        if (bufp)
+            *bufp++ = '\0';
+
+        while (*bufp) {
+            char *start, *end;
+            int i;
+
+            while (*bufp && !isxdigit((unsigned char)*bufp))
+                bufp++;
+            start = bufp;
+
+            if (!*bufp)
+                break;
+
+            while (*bufp && isxdigit((unsigned char)*bufp))
+                bufp++;
+            end = bufp;
+
+            if (ptrnum >= lenof(ptrs))
+                break;
+            ptrs[ptrnum++] = q;
+            
+            for (i = -((end - start) & 1); i < end-start; i += 2) {
+                unsigned char val = (i < 0 ? 0 : fromxdigit(start[i]));
+                val = val * 16 + fromxdigit(start[i+1]);
+                *q++ = val;
+            }
+
+            ptrs[ptrnum] = q;
+        }
+
+        if (!strcmp(buf, "mul")) {
+            Bignum a, b, c, p;
+
+            if (ptrnum != 3) {
+                printf("%d: mul with %d parameters, expected 3\n", line, ptrnum);
+                exit(1);
+            }
+            a = bignum_from_bytes(ptrs[0], ptrs[1]-ptrs[0]);
+            b = bignum_from_bytes(ptrs[1], ptrs[2]-ptrs[1]);
+            c = bignum_from_bytes(ptrs[2], ptrs[3]-ptrs[2]);
+            p = bigmul(a, b);
+
+            if (bignum_cmp(c, p) == 0) {
+                passes++;
+            } else {
+                char *as = bignum_decimal(a);
+                char *bs = bignum_decimal(b);
+                char *cs = bignum_decimal(c);
+                char *ps = bignum_decimal(p);
+                
+                printf("%d: fail: %s * %s gave %s expected %s\n",
+                       line, as, bs, ps, cs);
+                fails++;
+
+                sfree(as);
+                sfree(bs);
+                sfree(cs);
+                sfree(ps);
+            }
+            freebn(a);
+            freebn(b);
+            freebn(c);
+            freebn(p);
+        } else if (!strcmp(buf, "modmul")) {
+            Bignum a, b, m, c, p;
+
+            if (ptrnum != 4) {
+                printf("%d: modmul with %d parameters, expected 4\n",
+                       line, ptrnum);
+                exit(1);
+            }
+            a = bignum_from_bytes(ptrs[0], ptrs[1]-ptrs[0]);
+            b = bignum_from_bytes(ptrs[1], ptrs[2]-ptrs[1]);
+            m = bignum_from_bytes(ptrs[2], ptrs[3]-ptrs[2]);
+            c = bignum_from_bytes(ptrs[3], ptrs[4]-ptrs[3]);
+            p = modmul(a, b, m);
+
+            if (bignum_cmp(c, p) == 0) {
+                passes++;
+            } else {
+                char *as = bignum_decimal(a);
+                char *bs = bignum_decimal(b);
+                char *ms = bignum_decimal(m);
+                char *cs = bignum_decimal(c);
+                char *ps = bignum_decimal(p);
+                
+                printf("%d: fail: %s * %s mod %s gave %s expected %s\n",
+                       line, as, bs, ms, ps, cs);
+                fails++;
+
+                sfree(as);
+                sfree(bs);
+                sfree(ms);
+                sfree(cs);
+                sfree(ps);
+            }
+            freebn(a);
+            freebn(b);
+            freebn(m);
+            freebn(c);
+            freebn(p);
+        } else if (!strcmp(buf, "pow")) {
+            Bignum base, expt, modulus, expected, answer;
+
+            if (ptrnum != 4) {
+                printf("%d: pow with %d parameters, expected 4\n", line, ptrnum);
+                exit(1);
+            }
+
+            base = bignum_from_bytes(ptrs[0], ptrs[1]-ptrs[0]);
+            expt = bignum_from_bytes(ptrs[1], ptrs[2]-ptrs[1]);
+            modulus = bignum_from_bytes(ptrs[2], ptrs[3]-ptrs[2]);
+            expected = bignum_from_bytes(ptrs[3], ptrs[4]-ptrs[3]);
+            answer = modpow(base, expt, modulus);
+
+            if (bignum_cmp(expected, answer) == 0) {
+                passes++;
+            } else {
+                char *as = bignum_decimal(base);
+                char *bs = bignum_decimal(expt);
+                char *cs = bignum_decimal(modulus);
+                char *ds = bignum_decimal(answer);
+                char *ps = bignum_decimal(expected);
+                
+                printf("%d: fail: %s ^ %s mod %s gave %s expected %s\n",
+                       line, as, bs, cs, ds, ps);
+                fails++;
+
+                sfree(as);
+                sfree(bs);
+                sfree(cs);
+                sfree(ds);
+                sfree(ps);
+            }
+            freebn(base);
+            freebn(expt);
+            freebn(modulus);
+            freebn(expected);
+            freebn(answer);
+        } else if (!strcmp(buf, "divmod")) {
+            Bignum n, d, expect_q, expect_r, answer_q, answer_r;
+            int fail;
+
+            if (ptrnum != 4) {
+                printf("%d: divmod with %d parameters, expected 4\n", line, ptrnum);
+                exit(1);
+            }
+
+            n = bignum_from_bytes(ptrs[0], ptrs[1]-ptrs[0]);
+            d = bignum_from_bytes(ptrs[1], ptrs[2]-ptrs[1]);
+            expect_q = bignum_from_bytes(ptrs[2], ptrs[3]-ptrs[2]);
+            expect_r = bignum_from_bytes(ptrs[3], ptrs[4]-ptrs[3]);
+            answer_q = bigdiv(n, d);
+            answer_r = bigmod(n, d);
+
+            fail = FALSE;
+            if (bignum_cmp(expect_q, answer_q) != 0) {
+                char *as = bignum_decimal(n);
+                char *bs = bignum_decimal(d);
+                char *cs = bignum_decimal(answer_q);
+                char *ds = bignum_decimal(expect_q);
+
+                printf("%d: fail: %s / %s gave %s expected %s\n",
+                       line, as, bs, cs, ds);
+                fail = TRUE;
+
+                sfree(as);
+                sfree(bs);
+                sfree(cs);
+                sfree(ds);
+            }
+            if (bignum_cmp(expect_r, answer_r) != 0) {
+                char *as = bignum_decimal(n);
+                char *bs = bignum_decimal(d);
+                char *cs = bignum_decimal(answer_r);
+                char *ds = bignum_decimal(expect_r);
+
+                printf("%d: fail: %s mod %s gave %s expected %s\n",
+                       line, as, bs, cs, ds);
+                fail = TRUE;
+
+                sfree(as);
+                sfree(bs);
+                sfree(cs);
+                sfree(ds);
+            }
+
+            freebn(n);
+            freebn(d);
+            freebn(expect_q);
+            freebn(expect_r);
+            freebn(answer_q);
+            freebn(answer_r);
+
+            if (fail)
+                fails++;
+            else
+                passes++;
+        } else {
+            printf("%d: unrecognised test keyword: '%s'\n", line, buf);
+            exit(1);
+        }
+
+        sfree(buf);
+        sfree(data);
+    }
+
+    printf("passed %d failed %d total %d\n", passes, fails, passes+fails);
+    return fails != 0;
+}
index b2a6614b03ad2ee6763d0081d86b336f2d523c15..15ffe319b42d1637bf140d39b5ab5bde32f5487d 100644 (file)
@@ -103,6 +103,21 @@ for i in range(1,4200):
     a, b, p = findprod((1<<i)+1, +1, (i, i+1))
     print "mul", hexstr(a), hexstr(b), hexstr(p)
 
+# Bare tests of division/modulo.
+prefixes = [2**63, int(2**63.5), 2**64-1]
+for nsize in range(20, 200):
+    for dsize in range(20, 200):
+        for dprefix in prefixes:
+            d = sqrt(3<<(2*dsize)) + (dprefix<<dsize)
+            for nprefix in prefixes:
+                nbase = sqrt(3<<(2*nsize)) + (nprefix<<nsize)
+                for modulus in sorted({-1, 0, +1, d/2, nbase % d}):
+                    n = nbase - (nbase % d) + modulus
+                    if n < 0:
+                        n += d
+                        assert n >= 0
+                    print "divmod", hexstr(n), hexstr(d), hexstr(n/d), hexstr(n%d)
+
 # Simple tests of modmul.
 for ai in range(20, 200, 60):
     a = sqrt(3<<(2*ai-1))
index 541eabb7cc9aa9df64a62bc3afe2ab8f16694e31..33709d2381d479fa3f1c84f0a718be50b9e05b79 100644 (file)
@@ -3,7 +3,7 @@ since they are destructive.
 Normal text \e[1mand bold\e[m; \e[7mreverse video \e[1mand bold\e[m
 ANSI plus bold: \e[30m0 \e[1mbold\e[m \e[31m1 \e[1mbold\e[m \e[32m2 \e[1mbold\e[m \e[33m3 \e[1mbold\e[m \e[34m4 \e[1mbold\e[m \e[35m5 \e[1mbold\e[m \e[36m6 \e[1mbold\e[m \e[37m7 \e[1mbold\e[m
 xterm bright: \e[90mfg0\e[m \e[100mbg0\e[m \e[91mfg1\e[m \e[101mbg1\e[m \e[92mfg2\e[m \e[102mbg2\e[m \e[93mfg3\e[m \e[103mbg3\e[m \e[94mfg4\e[m \e[104mbg4\e[m \e[95mfg5\e[m \e[105mbg5\e[m \e[96mfg6\e[m \e[106mbg6\e[m \e[97mfg7\e[m \e[107mbg7\e[m
-xterm 256:
+xterm 256: \e[48;5;16mg\e[48;5;232mr\e[48;5;233me\e[48;5;234my\e[48;5;235ms\e[48;5;236m \e[48;5;237m \e[48;5;238m \e[48;5;239m \e[48;5;240m \e[48;5;241m \e[48;5;242m \e[48;5;243m \e[48;5;244m \e[48;5;245m \e[48;5;246m \e[48;5;247m \e[48;5;248m \e[48;5;249m \e[48;5;250m \e[48;5;251m \e[48;5;252m \e[48;5;253m \e[48;5;254m \e[48;5;255m \e[48;5;231m \e[m \e[31;48;5;16mr\e[48;5;52me\e[48;5;88md\e[48;5;124ms\e[48;5;160m \e[48;5;196m \e[m \e[32;48;5;16mg\e[48;5;22mr\e[48;5;28me\e[48;5;34me\e[48;5;40mn\e[48;5;46ms\e[m \e[34;48;5;16mb\e[48;5;17ml\e[48;5;18mu\e[48;5;19me\e[48;5;20ms\e[48;5;21m \e[m \e[33;48;5;16my\e[48;5;58me\e[48;5;100ml\e[48;5;142ml\e[48;5;184mo\e[48;5;226mw\e[m \e[35;48;5;16mm\e[48;5;53ma\e[48;5;90mg\e[48;5;127me\e[48;5;164mn\e[48;5;201mt\e[m \e[36;48;5;16mc\e[48;5;23my\e[48;5;30ma\e[48;5;37mn\e[48;5;44ms\e[48;5;51m \e[m
 \e[38;5;0m   0\e[m\e[38;5;1m   1\e[m\e[38;5;2m   2\e[m\e[38;5;3m   3\e[m\e[38;5;4m   4\e[m\e[38;5;5m   5\e[m\e[38;5;6m   6\e[m\e[38;5;7m   7\e[m\e[38;5;8m   8\e[m\e[38;5;9m   9\e[m\e[38;5;10m  10\e[m\e[38;5;11m  11\e[m\e[38;5;12m  12\e[m\e[38;5;13m  13\e[m\e[38;5;14m  14\e[m\e[38;5;15m  15\e[m
 \e[38;5;16m  16\e[m\e[38;5;17m  17\e[m\e[38;5;18m  18\e[m\e[38;5;19m  19\e[m\e[38;5;20m  20\e[m\e[38;5;21m  21\e[m\e[38;5;22m  22\e[m\e[38;5;23m  23\e[m\e[38;5;24m  24\e[m\e[38;5;25m  25\e[m\e[38;5;26m  26\e[m\e[38;5;27m  27\e[m\e[38;5;28m  28\e[m\e[38;5;29m  29\e[m\e[38;5;30m  30\e[m\e[38;5;31m  31\e[m
 \e[38;5;32m  32\e[m\e[38;5;33m  33\e[m\e[38;5;34m  34\e[m\e[38;5;35m  35\e[m\e[38;5;36m  36\e[m\e[38;5;37m  37\e[m\e[38;5;38m  38\e[m\e[38;5;39m  39\e[m\e[38;5;40m  40\e[m\e[38;5;41m  41\e[m\e[38;5;42m  42\e[m\e[38;5;43m  43\e[m\e[38;5;44m  44\e[m\e[38;5;45m  45\e[m\e[38;5;46m  46\e[m\e[38;5;47m  47\e[m
diff --git a/testdata/display.txt b/testdata/display.txt
new file mode 100644 (file)
index 0000000..f7b5c90
--- /dev/null
@@ -0,0 +1,14 @@
+Test of all features involved in do_text()
+==========================================
+
+Reverse video + red on yellow:      \e[31;43;7m bing! \e[m
+Yellow on red should look the same: \e[33;41m bong! \e[m
+
+Basic attrs, combining chars, both widths: \e[1mBold\e[22m \e[5mblink\e[25m \e[4munderline\e[24m [Λ̊][チ][text]
+Wide char should be off by 1 narrow char: \e[1mBold\e[22m \e[5mblink\e[25m \e[4munderline\e[24m [Λ̊][チ][text]
+
+Double width, double height. Should be red top, magenta bottom, blue DW only:
+\e#3\e[41m\e[1mBold\e[22m \e[5mblink\e[25m \e[4munderline\e[24m [Λ̊][チ][text]\e[m
+\e#4\e[45m\e[1mBold\e[22m \e[5mblink\e[25m \e[4munderline\e[24m [Λ̊][チ][text]\e[m
+\e#6\e[44m\e[1mBold\e[22m \e[5mblink\e[25m \e[4munderline\e[24m [Λ̊][チ][text]\e[m
+
diff --git a/unix/gtkask.c b/unix/gtkask.c
new file mode 100644 (file)
index 0000000..e5439bb
--- /dev/null
@@ -0,0 +1,518 @@
+/*
+ * GTK implementation of a GUI password/passphrase prompt.
+ */
+
+#include <assert.h>
+#include <time.h>
+#include <stdlib.h>
+
+#include <unistd.h>
+
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#if !GTK_CHECK_VERSION(3,0,0)
+#include <gdk/gdkkeysyms.h>
+#endif
+
+#include "gtkfont.h"
+#include "gtkcompat.h"
+#include "gtkmisc.h"
+
+#include "misc.h"
+
+#define N_DRAWING_AREAS 3
+
+struct drawing_area_ctx {
+    GtkWidget *area;
+#ifndef DRAW_DEFAULT_CAIRO
+    GdkColor *cols;
+#endif
+    int width, height, current;
+};
+
+struct askpass_ctx {
+    GtkWidget *dialog, *promptlabel;
+    struct drawing_area_ctx drawingareas[N_DRAWING_AREAS];
+    int active_area;
+#if GTK_CHECK_VERSION(2,0,0)
+    GtkIMContext *imc;
+#endif
+#ifndef DRAW_DEFAULT_CAIRO
+    GdkColormap *colmap;
+    GdkColor cols[2];
+#endif
+    char *passphrase;
+    int passlen, passsize;
+#if GTK_CHECK_VERSION(3,0,0)
+    GdkDevice *keyboard;               /* for gdk_device_grab */
+#endif
+};
+
+static void visually_acknowledge_keypress(struct askpass_ctx *ctx)
+{
+    int new_active;
+    new_active = rand() % (N_DRAWING_AREAS - 1);
+    if (new_active >= ctx->active_area)
+        new_active++;
+    ctx->drawingareas[ctx->active_area].current = 0;
+    gtk_widget_queue_draw(ctx->drawingareas[ctx->active_area].area);
+    ctx->drawingareas[new_active].current = 1;
+    gtk_widget_queue_draw(ctx->drawingareas[new_active].area);
+    ctx->active_area = new_active;
+}
+
+static int last_char_len(struct askpass_ctx *ctx)
+{
+    /*
+     * GTK always encodes in UTF-8, so we can do this in a fixed way.
+     */
+    int i;
+    assert(ctx->passlen > 0);
+    i = ctx->passlen - 1;
+    while ((unsigned)((unsigned char)ctx->passphrase[i] - 0x80) < 0x40) {
+        if (i == 0)
+            break;
+        i--;
+    }
+    return ctx->passlen - i;
+}
+
+static void add_text_to_passphrase(struct askpass_ctx *ctx, gchar *str)
+{
+    int len = strlen(str);
+    if (ctx->passlen + len >= ctx->passsize) {
+        /* Take some care with buffer expansion, because there are
+         * pieces of passphrase in the old buffer so we should ensure
+         * realloc doesn't leave a copy lying around in the address
+         * space. */
+        int oldsize = ctx->passsize;
+        char *newbuf;
+
+        ctx->passsize = (ctx->passlen + len) * 5 / 4 + 1024;
+        newbuf = snewn(ctx->passsize, char);
+        memcpy(newbuf, ctx->passphrase, oldsize);
+        smemclr(ctx->passphrase, oldsize);
+        sfree(ctx->passphrase);
+        ctx->passphrase = newbuf;
+    }
+    strcpy(ctx->passphrase + ctx->passlen, str);
+    ctx->passlen += len;
+    visually_acknowledge_keypress(ctx);
+}
+
+static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
+{
+    struct askpass_ctx *ctx = (struct askpass_ctx *)data;
+
+    if (event->keyval == GDK_KEY_Return &&
+        event->type == GDK_KEY_PRESS) {
+        gtk_main_quit();
+    } else if (event->keyval == GDK_KEY_Escape &&
+               event->type == GDK_KEY_PRESS) {
+        smemclr(ctx->passphrase, ctx->passsize);
+        ctx->passphrase = NULL;
+        gtk_main_quit();
+    } else {
+#if GTK_CHECK_VERSION(2,0,0)
+        if (gtk_im_context_filter_keypress(ctx->imc, event))
+            return TRUE;
+#endif
+
+        if (event->type == GDK_KEY_PRESS) {
+            if (!strcmp(event->string, "\x15")) {
+                /* Ctrl-U. Wipe out the whole line */
+                ctx->passlen = 0;
+                visually_acknowledge_keypress(ctx);
+            } else if (!strcmp(event->string, "\x17")) {
+                /* Ctrl-W. Delete back to the last space->nonspace
+                 * boundary. We interpret 'space' in a really simple
+                 * way (mimicking terminal drivers), and don't attempt
+                 * to second-guess exciting Unicode space
+                 * characters. */
+                while (ctx->passlen > 0) {
+                    char deleted, prior;
+                    ctx->passlen -= last_char_len(ctx);
+                    deleted = ctx->passphrase[ctx->passlen];
+                    prior = (ctx->passlen == 0 ? ' ' :
+                             ctx->passphrase[ctx->passlen-1]);
+                    if (!g_ascii_isspace(deleted) && g_ascii_isspace(prior))
+                        break;
+                }
+                visually_acknowledge_keypress(ctx);
+            } else if (event->keyval == GDK_KEY_BackSpace) {
+                /* Backspace. Delete one character. */
+                if (ctx->passlen > 0)
+                    ctx->passlen -= last_char_len(ctx);
+                visually_acknowledge_keypress(ctx);
+#if !GTK_CHECK_VERSION(2,0,0)
+            } else if (event->string[0]) {
+                add_text_to_passphrase(ctx, event->string);
+#endif
+            }
+        }
+    }
+    return TRUE;
+}
+
+#if GTK_CHECK_VERSION(2,0,0)
+static void input_method_commit_event(GtkIMContext *imc, gchar *str,
+                                      gpointer data)
+{
+    struct askpass_ctx *ctx = (struct askpass_ctx *)data;
+    add_text_to_passphrase(ctx, str);
+}
+#endif
+
+static gint configure_area(GtkWidget *widget, GdkEventConfigure *event,
+                           gpointer data)
+{
+    struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data;
+    ctx->width = event->width;
+    ctx->height = event->height;
+    gtk_widget_queue_draw(widget);
+    return TRUE;
+}
+
+#ifdef DRAW_DEFAULT_CAIRO
+static void askpass_redraw_cairo(cairo_t *cr, struct drawing_area_ctx *ctx)
+{
+    cairo_set_source_rgb(cr, 1-ctx->current, 1-ctx->current, 1-ctx->current);
+    cairo_paint(cr);
+}
+#else
+static void askpass_redraw_gdk(GdkWindow *win, struct drawing_area_ctx *ctx)
+{
+    GdkGC *gc = gdk_gc_new(win);
+    gdk_gc_set_foreground(gc, &ctx->cols[ctx->current]);
+    gdk_draw_rectangle(win, gc, TRUE, 0, 0, ctx->width, ctx->height);
+    gdk_gc_unref(gc);
+}
+#endif
+
+#if GTK_CHECK_VERSION(3,0,0)
+static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data)
+{
+    struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data;
+    askpass_redraw_cairo(cr, ctx);
+    return TRUE;
+}
+#else
+static gint expose_area(GtkWidget *widget, GdkEventExpose *event,
+                        gpointer data)
+{
+    struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data;
+
+#ifdef DRAW_DEFAULT_CAIRO
+    cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(ctx->area));
+    askpass_redraw_cairo(cr, ctx);
+    cairo_destroy(cr);
+#else
+    askpass_redraw_gdk(gtk_widget_get_window(ctx->area), ctx);
+#endif
+
+    return TRUE;
+}
+#endif
+
+static int try_grab_keyboard(struct askpass_ctx *ctx)
+{
+    int ret;
+
+#if GTK_CHECK_VERSION(3,0,0)
+    /*
+     * Grabbing the keyboard is quite complicated in GTK 3.
+     */
+    GdkDeviceManager *dm;
+    GdkDevice *pointer, *keyboard;
+
+    dm = gdk_display_get_device_manager
+        (gtk_widget_get_display(ctx->dialog));
+    if (!dm)
+        return FALSE;
+
+    pointer = gdk_device_manager_get_client_pointer(dm);
+    if (!pointer)
+        return FALSE;
+    keyboard = gdk_device_get_associated_device(pointer);
+    if (!keyboard)
+        return FALSE;
+    if (gdk_device_get_source(keyboard) != GDK_SOURCE_KEYBOARD)
+        return FALSE;
+
+    ctx->keyboard = keyboard;
+    ret = gdk_device_grab(ctx->keyboard,
+                          gtk_widget_get_window(ctx->dialog),
+                          GDK_OWNERSHIP_NONE,
+                          TRUE,
+                          GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
+                          NULL,
+                          GDK_CURRENT_TIME);
+#else
+    /*
+     * It's much simpler in GTK 1 and 2!
+     */
+    ret = gdk_keyboard_grab(gtk_widget_get_window(ctx->dialog),
+                            FALSE, GDK_CURRENT_TIME);
+#endif
+
+    return ret == GDK_GRAB_SUCCESS;
+}
+
+typedef int (try_grab_fn_t)(struct askpass_ctx *ctx);
+
+static int repeatedly_try_grab(struct askpass_ctx *ctx, try_grab_fn_t fn)
+{
+    /*
+     * Repeatedly try to grab some aspect of the X server. We have to
+     * do this rather than just trying once, because there is at least
+     * one important situation in which the grab may fail the first
+     * time: any user who is launching an add-key operation off some
+     * kind of window manager hotkey will almost by definition be
+     * running this script with a keyboard grab already active, namely
+     * the one-key grab that the WM (or whatever) uses to detect
+     * presses of the hotkey. So at the very least we have to give the
+     * user time to release that key.
+     */
+    const useconds_t ms_limit = 5*1000000;  /* try for 5 seconds */
+    const useconds_t ms_step = 1000000/8;   /* at 1/8 second intervals */
+    useconds_t ms;
+
+    for (ms = 0; ms < ms_limit; ms += ms_step) {
+        if (fn(ctx))
+            return TRUE;
+        usleep(ms_step);
+    }
+    return FALSE;
+}
+
+static const char *gtk_askpass_setup(struct askpass_ctx *ctx,
+                                     const char *window_title,
+                                     const char *prompt_text)
+{
+    int i;
+    GtkBox *action_area;
+
+    ctx->passlen = 0;
+    ctx->passsize = 2048;
+    ctx->passphrase = snewn(ctx->passsize, char);
+
+    /*
+     * Create widgets.
+     */
+    ctx->dialog = our_dialog_new();
+    gtk_window_set_title(GTK_WINDOW(ctx->dialog), window_title);
+    gtk_window_set_position(GTK_WINDOW(ctx->dialog), GTK_WIN_POS_CENTER);
+    ctx->promptlabel = gtk_label_new(prompt_text);
+    align_label_left(GTK_LABEL(ctx->promptlabel));
+    gtk_widget_show(ctx->promptlabel);
+    gtk_label_set_line_wrap(GTK_LABEL(ctx->promptlabel), TRUE);
+#if GTK_CHECK_VERSION(3,0,0)
+    gtk_label_set_width_chars(GTK_LABEL(ctx->promptlabel), 48);
+#endif
+    our_dialog_add_to_content_area(GTK_WINDOW(ctx->dialog),
+                                   ctx->promptlabel, TRUE, TRUE, 0);
+#if GTK_CHECK_VERSION(2,0,0)
+    ctx->imc = gtk_im_multicontext_new();
+#endif
+#ifndef DRAW_DEFAULT_CAIRO
+    {
+        gboolean success[2];
+        ctx->colmap = gdk_colormap_get_system();
+        ctx->cols[0].red = ctx->cols[0].green = ctx->cols[0].blue = 0xFFFF;
+        ctx->cols[1].red = ctx->cols[1].green = ctx->cols[1].blue = 0;
+        gdk_colormap_alloc_colors(ctx->colmap, ctx->cols, 2,
+                                  FALSE, TRUE, success);
+        if (!success[0] | !success[1])
+            return "unable to allocate colours";
+    }
+#endif
+
+    action_area = our_dialog_make_action_hbox(GTK_WINDOW(ctx->dialog));
+
+    for (i = 0; i < N_DRAWING_AREAS; i++) {
+        ctx->drawingareas[i].area = gtk_drawing_area_new();
+#ifndef DRAW_DEFAULT_CAIRO
+        ctx->drawingareas[i].cols = ctx->cols;
+#endif
+        ctx->drawingareas[i].current = 0;
+        ctx->drawingareas[i].width = ctx->drawingareas[i].height = 0;
+        /* It would be nice to choose this size in some more
+         * context-sensitive way, like measuring the size of some
+         * piece of template text. */
+        gtk_widget_set_size_request(ctx->drawingareas[i].area, 32, 32);
+        gtk_box_pack_end(action_area, ctx->drawingareas[i].area,
+                         TRUE, TRUE, 5);
+        g_signal_connect(G_OBJECT(ctx->drawingareas[i].area),
+                         "configure_event",
+                         G_CALLBACK(configure_area),
+                         &ctx->drawingareas[i]);
+#if GTK_CHECK_VERSION(3,0,0)
+        g_signal_connect(G_OBJECT(ctx->drawingareas[i].area),
+                         "draw",
+                         G_CALLBACK(draw_area),
+                         &ctx->drawingareas[i]);
+#else
+        g_signal_connect(G_OBJECT(ctx->drawingareas[i].area),
+                         "expose_event",
+                         G_CALLBACK(expose_area),
+                         &ctx->drawingareas[i]);
+#endif
+        gtk_widget_show(ctx->drawingareas[i].area);
+    }
+    ctx->active_area = rand() % N_DRAWING_AREAS;
+    ctx->drawingareas[ctx->active_area].current = 1;
+
+    /*
+     * Arrange to receive key events. We don't really need to worry
+     * from a UI perspective about which widget gets the events, as
+     * long as we know which it is so we can catch them. So we'll pick
+     * the prompt label at random, and we'll use gtk_grab_add to
+     * ensure key events go to it.
+     */
+    gtk_widget_set_sensitive(ctx->promptlabel, TRUE);
+
+#if GTK_CHECK_VERSION(2,0,0)
+    gtk_window_set_keep_above(GTK_WINDOW(ctx->dialog), TRUE);
+#endif
+
+    /*
+     * Actually show the window, and wait for it to be shown.
+     */
+    gtk_widget_show_now(ctx->dialog);
+
+    /*
+     * Now that the window is displayed, make it grab the input focus.
+     */
+    gtk_grab_add(ctx->promptlabel);
+    if (!repeatedly_try_grab(ctx, try_grab_keyboard))
+        return "unable to grab keyboard";
+
+    /*
+     * And now that we've got the keyboard grab, connect up our
+     * keyboard handlers.
+     */
+#if GTK_CHECK_VERSION(2,0,0)
+    g_signal_connect(G_OBJECT(ctx->imc), "commit",
+                     G_CALLBACK(input_method_commit_event), ctx);
+#endif
+    g_signal_connect(G_OBJECT(ctx->promptlabel), "key_press_event",
+                     G_CALLBACK(key_event), ctx);
+    g_signal_connect(G_OBJECT(ctx->promptlabel), "key_release_event",
+                     G_CALLBACK(key_event), ctx);
+#if GTK_CHECK_VERSION(2,0,0)
+    gtk_im_context_set_client_window(ctx->imc,
+                                     gtk_widget_get_window(ctx->dialog));
+#endif
+
+    return NULL;
+}
+
+static void gtk_askpass_cleanup(struct askpass_ctx *ctx)
+{
+#if GTK_CHECK_VERSION(3,0,0)
+    gdk_device_ungrab(ctx->keyboard, GDK_CURRENT_TIME);
+#else
+    gdk_keyboard_ungrab(GDK_CURRENT_TIME);
+#endif
+    gtk_grab_remove(ctx->promptlabel);
+
+    if (ctx->passphrase) {
+        assert(ctx->passlen < ctx->passsize);
+        ctx->passphrase[ctx->passlen] = '\0';
+    }
+
+    gtk_widget_destroy(ctx->dialog);
+}
+
+static int setup_gtk(const char *display)
+{
+    static int gtk_initialised = FALSE;
+    int argc;
+    char *real_argv[3];
+    char **argv = real_argv;
+    int ret;
+
+    if (gtk_initialised)
+        return TRUE;
+
+    argc = 0;
+    argv[argc++] = dupstr("dummy");
+    argv[argc++] = dupprintf("--display=%s", display);
+    argv[argc] = NULL;
+    ret = gtk_init_check(&argc, &argv);
+    while (argc > 0)
+        sfree(argv[--argc]);
+
+    gtk_initialised = ret;
+    return ret;
+}
+
+char *gtk_askpass_main(const char *display, const char *wintitle,
+                       const char *prompt, int *success)
+{
+    struct askpass_ctx actx, *ctx = &actx;
+    const char *err;
+
+    /* In case gtk_init hasn't been called yet by the program */
+    if (!setup_gtk(display)) {
+        *success = FALSE;
+        return dupstr("unable to initialise GTK");
+    }
+
+    if ((err = gtk_askpass_setup(ctx, wintitle, prompt)) != NULL) {
+        *success = FALSE;
+        return dupprintf("%s", err);
+    }
+    gtk_main();
+    gtk_askpass_cleanup(ctx);
+
+    if (ctx->passphrase) {
+        *success = TRUE;
+        return ctx->passphrase;
+    } else {
+        *success = FALSE;
+        return dupstr("passphrase input cancelled");
+    }
+}
+
+#ifdef TEST_ASKPASS
+void modalfatalbox(const char *p, ...)
+{
+    va_list ap;
+    fprintf(stderr, "FATAL ERROR: ");
+    va_start(ap, p);
+    vfprintf(stderr, p, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+    exit(1);
+}
+
+int main(int argc, char **argv)
+{
+    int success, exitcode;
+    char *ret;
+
+    gtk_init(&argc, &argv);
+
+    if (argc != 2) {
+        success = FALSE;
+        ret = dupprintf("usage: %s <prompt text>", argv[0]);
+    } else {
+        srand(time(NULL));
+        ret = gtk_askpass_main(NULL, "Enter passphrase", argv[1], &success);
+    }
+
+    if (!success) {
+        fputs(ret, stderr);
+        fputc('\n', stderr);
+        exitcode = 1;
+    } else {
+        fputs(ret, stdout);
+        fputc('\n', stdout);
+        exitcode = 0;
+    }
+
+    smemclr(ret, strlen(ret));
+    return exitcode;
+}
+#endif
index 958a3f6653e6a4571ab03c4ced9ffabd6566b960..4307176f13698b8fea65cd9819176830004bfe3d 100644 (file)
@@ -129,6 +129,22 @@ void gtk_setup_config_box(struct controlbox *b, int midsession, void *win)
                  conf_checkbox_handler,
                  I(CONF_utf8_override));
 
+#ifdef OSX_META_KEY_CONFIG
+    /*
+     * On OS X, there are multiple reasonable opinions about whether
+     * Option or Command (or both, or neither) should act as a Meta
+     * key, or whether they should have their normal OS functions.
+     */
+    s = ctrl_getset(b, "Terminal/Keyboard", "meta",
+                   "Choose the Meta key:");
+    ctrl_checkbox(s, "Option key acts as Meta", 'p',
+                 HELPCTX(no_help),
+                 conf_checkbox_handler, I(CONF_osx_option_meta));
+    ctrl_checkbox(s, "Command key acts as Meta", 'm',
+                 HELPCTX(no_help),
+                 conf_checkbox_handler, I(CONF_osx_command_meta));
+#endif
+
     if (!midsession) {
         /*
          * Allow the user to specify the window class as part of the saved
index 8cb5d14fcc7e459f8fe4546a64210988f19d39f5..e8223a726e3bab89fb6357419ccf4807632dddb1 100644 (file)
@@ -2,8 +2,9 @@
  * gtkcols.c - implementation of the `Columns' GTK layout container.
  */
 
-#include "gtkcols.h"
 #include <gtk/gtk.h>
+#include "gtkcompat.h"
+#include "gtkcols.h"
 
 static void columns_init(Columns *cols);
 static void columns_class_init(ColumnsClass *klass);
@@ -20,16 +21,29 @@ static void columns_forall(GtkContainer *container, gboolean include_internals,
 #if !GTK_CHECK_VERSION(2,0,0)
 static gint columns_focus(GtkContainer *container, GtkDirectionType dir);
 #endif
-static GtkType columns_child_type(GtkContainer *container);
+static GType columns_child_type(GtkContainer *container);
+#if GTK_CHECK_VERSION(3,0,0)
+static void columns_get_preferred_width(GtkWidget *widget,
+                                        gint *min, gint *nat);
+static void columns_get_preferred_height(GtkWidget *widget,
+                                         gint *min, gint *nat);
+static void columns_get_preferred_width_for_height(GtkWidget *widget,
+                                                   gint height,
+                                                   gint *min, gint *nat);
+static void columns_get_preferred_height_for_width(GtkWidget *widget,
+                                                   gint width,
+                                                   gint *min, gint *nat);
+#else
 static void columns_size_request(GtkWidget *widget, GtkRequisition *req);
+#endif
 static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc);
 
 static GtkContainerClass *parent_class = NULL;
 
 #if !GTK_CHECK_VERSION(2,0,0)
-GtkType columns_get_type(void)
+GType columns_get_type(void)
 {
-    static GtkType columns_type = 0;
+    static GType columns_type = 0;
 
     if (!columns_type) {
         static const GtkTypeInfo columns_info = {
@@ -103,7 +117,16 @@ static void columns_class_init(ColumnsClass *klass)
     widget_class->draw = columns_draw;
     widget_class->expose_event = columns_expose;
 #endif
+#if GTK_CHECK_VERSION(3,0,0)
+    widget_class->get_preferred_width = columns_get_preferred_width;
+    widget_class->get_preferred_height = columns_get_preferred_height;
+    widget_class->get_preferred_width_for_height =
+        columns_get_preferred_width_for_height;
+    widget_class->get_preferred_height_for_width =
+        columns_get_preferred_height_for_width;
+#else
     widget_class->size_request = columns_size_request;
+#endif
     widget_class->size_allocate = columns_size_allocate;
 
     container_class->add = columns_base_add;
@@ -120,7 +143,7 @@ static void columns_class_init(ColumnsClass *klass)
 
 static void columns_init(Columns *cols)
 {
-    GTK_WIDGET_SET_FLAGS(cols, GTK_NO_WINDOW);
+    gtk_widget_set_has_window(GTK_WIDGET(cols), FALSE);
 
     cols->children = NULL;
     cols->spacing = 0;
@@ -141,14 +164,14 @@ static void columns_map(GtkWidget *widget)
     g_return_if_fail(IS_COLUMNS(widget));
 
     cols = COLUMNS(widget);
-    GTK_WIDGET_SET_FLAGS(cols, GTK_MAPPED);
+    gtk_widget_set_mapped(GTK_WIDGET(cols), TRUE);
 
     for (children = cols->children;
          children && (child = children->data);
          children = children->next) {
         if (child->widget &&
-           GTK_WIDGET_VISIBLE(child->widget) &&
-            !GTK_WIDGET_MAPPED(child->widget))
+           gtk_widget_get_visible(child->widget) &&
+            !gtk_widget_get_mapped(child->widget))
             gtk_widget_map(child->widget);
     }
 }
@@ -162,14 +185,14 @@ static void columns_unmap(GtkWidget *widget)
     g_return_if_fail(IS_COLUMNS(widget));
 
     cols = COLUMNS(widget);
-    GTK_WIDGET_UNSET_FLAGS(cols, GTK_MAPPED);
+    gtk_widget_set_mapped(GTK_WIDGET(cols), FALSE);
 
     for (children = cols->children;
          children && (child = children->data);
          children = children->next) {
         if (child->widget &&
-           GTK_WIDGET_VISIBLE(child->widget) &&
-            GTK_WIDGET_MAPPED(child->widget))
+           gtk_widget_get_visible(child->widget) &&
+            gtk_widget_get_mapped(child->widget))
             gtk_widget_unmap(child->widget);
     }
 }
@@ -263,10 +286,18 @@ static void columns_remove(GtkContainer *container, GtkWidget *widget)
         if (child->widget != widget)
             continue;
 
-        was_visible = GTK_WIDGET_VISIBLE(widget);
+        was_visible = gtk_widget_get_visible(widget);
         gtk_widget_unparent(widget);
         cols->children = g_list_remove_link(cols->children, children);
         g_list_free(children);
+
+        if (child->same_height_as) {
+            g_return_if_fail(child->same_height_as->same_height_as == child);
+            child->same_height_as->same_height_as = NULL;
+            if (gtk_widget_get_visible(child->same_height_as->widget))
+                gtk_widget_queue_resize(GTK_WIDGET(container));
+        }
+
         g_free(child);
         if (was_visible)
             gtk_widget_queue_resize(GTK_WIDGET(container));
@@ -318,7 +349,7 @@ static void columns_forall(GtkContainer *container, gboolean include_internals,
     }
 }
 
-static GtkType columns_child_type(GtkContainer *container)
+static GType columns_child_type(GtkContainer *container)
 {
     return GTK_TYPE_WIDGET;
 }
@@ -367,13 +398,14 @@ void columns_add(Columns *cols, GtkWidget *child,
     g_return_if_fail(cols != NULL);
     g_return_if_fail(IS_COLUMNS(cols));
     g_return_if_fail(child != NULL);
-    g_return_if_fail(child->parent == NULL);
+    g_return_if_fail(gtk_widget_get_parent(child) == NULL);
 
     childdata = g_new(ColumnsChild, 1);
     childdata->widget = child;
     childdata->colstart = colstart;
     childdata->colspan = colspan;
     childdata->force_left = FALSE;
+    childdata->same_height_as = NULL;
 
     cols->children = g_list_append(cols->children, childdata);
     cols->taborder = g_list_append(cols->taborder, child);
@@ -384,36 +416,67 @@ void columns_add(Columns *cols, GtkWidget *child,
     gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder);
 #endif
 
-    if (GTK_WIDGET_REALIZED(cols))
+    if (gtk_widget_get_realized(GTK_WIDGET(cols)))
         gtk_widget_realize(child);
 
-    if (GTK_WIDGET_VISIBLE(cols) && GTK_WIDGET_VISIBLE(child)) {
-        if (GTK_WIDGET_MAPPED(cols))
+    if (gtk_widget_get_visible(GTK_WIDGET(cols)) &&
+        gtk_widget_get_visible(child)) {
+        if (gtk_widget_get_mapped(GTK_WIDGET(cols)))
             gtk_widget_map(child);
         gtk_widget_queue_resize(child);
     }
 }
 
+static ColumnsChild *columns_find_child(Columns *cols, GtkWidget *widget)
+{
+    GList *children;
+    ColumnsChild *child;
+
+    for (children = cols->children;
+         children && (child = children->data);
+         children = children->next) {
+
+        if (child->widget == widget)
+            return child;
+    }
+
+    return NULL;
+}
+
 void columns_force_left_align(Columns *cols, GtkWidget *widget)
 {
     ColumnsChild *child;
-    GList *children;
 
     g_return_if_fail(cols != NULL);
     g_return_if_fail(IS_COLUMNS(cols));
     g_return_if_fail(widget != NULL);
 
-    for (children = cols->children;
-         children && (child = children->data);
-         children = children->next) {
-        if (child->widget != widget)
-            continue;
+    child = columns_find_child(cols, widget);
+    g_return_if_fail(child != NULL);
 
-       child->force_left = TRUE;
-        if (GTK_WIDGET_VISIBLE(widget))
-            gtk_widget_queue_resize(GTK_WIDGET(cols));
-        break;
-    }
+    child->force_left = TRUE;
+    if (gtk_widget_get_visible(widget))
+        gtk_widget_queue_resize(GTK_WIDGET(cols));
+}
+
+void columns_force_same_height(Columns *cols, GtkWidget *cw1, GtkWidget *cw2)
+{
+    ColumnsChild *child1, *child2;
+
+    g_return_if_fail(cols != NULL);
+    g_return_if_fail(IS_COLUMNS(cols));
+    g_return_if_fail(cw1 != NULL);
+    g_return_if_fail(cw2 != NULL);
+
+    child1 = columns_find_child(cols, cw1);
+    g_return_if_fail(child1 != NULL);
+    child2 = columns_find_child(cols, cw2);
+    g_return_if_fail(child2 != NULL);
+
+    child1->same_height_as = child2;
+    child2->same_height_as = child1;
+    if (gtk_widget_get_visible(cw1) || gtk_widget_get_visible(cw2))
+        gtk_widget_queue_resize(GTK_WIDGET(cols));
 }
 
 void columns_taborder_last(Columns *cols, GtkWidget *widget)
@@ -508,77 +571,74 @@ static gint columns_focus(GtkContainer *container, GtkDirectionType dir)
 #endif
 
 /*
- * Now here comes the interesting bit. The actual layout part is
- * done in the following two functions:
- * 
- * columns_size_request() examines the list of widgets held in the
- * Columns, and returns a requisition stating the absolute minimum
- * size it can bear to be.
- * 
- * columns_size_allocate() is given an allocation telling it what
- * size the whole container is going to be, and it calls
- * gtk_widget_size_allocate() on all of its (visible) children to
- * set their size and position relative to the top left of the
- * container.
+ * Underlying parts of the layout algorithm, to compute the Columns
+ * container's width or height given the widths or heights of its
+ * children. These will be called in various ways with different
+ * notions of width and height in use, so we abstract them out and
+ * pass them a 'get width' or 'get height' function pointer.
  */
 
-static void columns_size_request(GtkWidget *widget, GtkRequisition *req)
+typedef gint (*widget_dim_fn_t)(ColumnsChild *child);
+
+static gint columns_compute_width(Columns *cols, widget_dim_fn_t get_width)
 {
-    Columns *cols;
     ColumnsChild *child;
     GList *children;
-    gint i, ncols, colspan, *colypos;
+    gint i, ncols, colspan, retwidth, childwidth;
     const gint *percentages;
     static const gint onecol[] = { 100 };
 
-    g_return_if_fail(widget != NULL);
-    g_return_if_fail(IS_COLUMNS(widget));
-    g_return_if_fail(req != NULL);
-
-    cols = COLUMNS(widget);
+#ifdef COLUMNS_WIDTH_DIAGNOSTICS
+    printf("compute_width(%p): start\n", cols);
+#endif
 
-    req->width = 0;
-    req->height = cols->spacing;
+    retwidth = 0;
 
     ncols = 1;
-    colypos = g_new(gint, 1);
-    colypos[0] = 0;
     percentages = onecol;
 
     for (children = cols->children;
          children && (child = children->data);
          children = children->next) {
-        GtkRequisition creq;
 
        if (!child->widget) {
            /* Column reconfiguration. */
-           for (i = 1; i < ncols; i++) {
-               if (colypos[0] < colypos[i])
-                   colypos[0] = colypos[i];
-           }
            ncols = child->ncols;
            percentages = child->percentages;
-           colypos = g_renew(gint, colypos, ncols);
-           for (i = 1; i < ncols; i++)
-               colypos[i] = colypos[0];
            continue;
        }
 
         /* Only take visible widgets into account. */
-        if (!GTK_WIDGET_VISIBLE(child->widget))
+        if (!gtk_widget_get_visible(child->widget))
             continue;
 
-        gtk_widget_size_request(child->widget, &creq);
+        childwidth = get_width(child);
        colspan = child->colspan ? child->colspan : ncols-child->colstart;
 
+#ifdef COLUMNS_WIDTH_DIAGNOSTICS
+        printf("compute_width(%p): ", cols);
+        if (GTK_IS_LABEL(child->widget))
+            printf("label %p '%s' wrap=%s: ", child->widget,
+                   gtk_label_get_text(GTK_LABEL(child->widget)),
+                   (gtk_label_get_line_wrap(GTK_LABEL(child->widget))
+                    ? "TRUE" : "FALSE"));
+        else
+            printf("widget %p: ", child->widget);
+        {
+            gint min, nat;
+            gtk_widget_get_preferred_width(child->widget, &min, &nat);
+            printf("minwidth=%d natwidth=%d ", min, nat);
+        }
+        printf("thiswidth=%d span=%d\n", childwidth, colspan);
+#endif
+
         /*
-         * To compute width: we know that creq.width plus
-         * cols->spacing needs to equal a certain percentage of the
-         * full width of the container. So we work this value out,
-         * figure out how wide the container will need to be to
-         * make that percentage of it equal to that width, and
-         * ensure our returned width is at least that much. Very
-         * simple really.
+         * To compute width: we know that childwidth + cols->spacing
+         * needs to equal a certain percentage of the full width of
+         * the container. So we work this value out, figure out how
+         * wide the container will need to be to make that percentage
+         * of it equal to that width, and ensure our returned width is
+         * at least that much. Very simple really.
          */
         {
             int percent, thiswid, fullwid;
@@ -587,15 +647,19 @@ static void columns_size_request(GtkWidget *widget, GtkRequisition *req)
             for (i = 0; i < colspan; i++)
                 percent += percentages[child->colstart+i];
 
-            thiswid = creq.width + cols->spacing;
+            thiswid = childwidth + cols->spacing;
             /*
-             * Since creq is the _minimum_ size the child needs, we
-             * must ensure that it gets _at least_ that size.
-             * Hence, when scaling thiswid up to fullwid, we must
-             * round up, which means adding percent-1 before
-             * dividing by percent.
+             * Since childwidth is (at least sometimes) the _minimum_
+             * size the child needs, we must ensure that it gets _at
+             * least_ that size. Hence, when scaling thiswid up to
+             * fullwid, we must round up, which means adding percent-1
+             * before dividing by percent.
              */
             fullwid = (thiswid * 100 + percent - 1) / percent;
+#ifdef COLUMNS_WIDTH_DIAGNOSTICS
+            printf("compute_width(%p): after %p, thiswid=%d fullwid=%d\n",
+                   cols, child->widget, thiswid, fullwid);
+#endif
 
             /*
              * The above calculation assumes every widget gets
@@ -603,59 +667,30 @@ static void columns_size_request(GtkWidget *widget, GtkRequisition *req)
              * cols->spacing here to account for the extra load of
              * spacing on the right.
              */
-            if (req->width < fullwid - cols->spacing)
-                req->width = fullwid - cols->spacing;
-        }
-
-        /*
-         * To compute height: the widget's top will be positioned
-         * at the largest y value so far reached in any of the
-         * columns it crosses. Then it will go down by creq.height
-         * plus padding; and the point it reaches at the bottom is
-         * the new y value in all those columns, and minus the
-         * padding it is also a lower bound on our own size
-         * request.
-         */
-        {
-            int topy, boty;
-
-            topy = 0;
-            for (i = 0; i < colspan; i++) {
-                if (topy < colypos[child->colstart+i])
-                    topy = colypos[child->colstart+i];
-            }
-            boty = topy + creq.height + cols->spacing;
-            for (i = 0; i < colspan; i++) {
-                colypos[child->colstart+i] = boty;
-            }
-
-            if (req->height < boty - cols->spacing)
-                req->height = boty - cols->spacing;
+            if (retwidth < fullwid - cols->spacing)
+                retwidth = fullwid - cols->spacing;
         }
     }
 
-    req->width += 2*GTK_CONTAINER(cols)->border_width;
-    req->height += 2*GTK_CONTAINER(cols)->border_width;
+    retwidth += 2*gtk_container_get_border_width(GTK_CONTAINER(cols));
 
-    g_free(colypos);
+#ifdef COLUMNS_WIDTH_DIAGNOSTICS
+    printf("compute_width(%p): done, returning %d\n", cols, retwidth);
+#endif
+
+    return retwidth;
 }
 
-static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
+static void columns_alloc_horiz(Columns *cols, gint ourwidth,
+                                widget_dim_fn_t get_width)
 {
-    Columns *cols;
     ColumnsChild *child;
     GList *children;
-    gint i, ncols, colspan, border, *colxpos, *colypos;
+    gint i, ncols, colspan, border, *colxpos, childwidth;
     const gint *percentages;
     static const gint onecol[] = { 100 };
 
-    g_return_if_fail(widget != NULL);
-    g_return_if_fail(IS_COLUMNS(widget));
-    g_return_if_fail(alloc != NULL);
-
-    cols = COLUMNS(widget);
-    widget->allocation = *alloc;
-    border = GTK_CONTAINER(cols)->border_width;
+    border = gtk_container_get_border_width(GTK_CONTAINER(cols));
 
     ncols = 1;
     percentages = onecol;
@@ -665,46 +700,34 @@ static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
      * subtracting the spacing. */
     colxpos = g_new(gint, 2);
     colxpos[0] = 0;
-    colxpos[1] = alloc->width - 2*border + cols->spacing;
-    /* As in size_request, colypos is the lowest y reached in each column. */
-    colypos = g_new(gint, 1);
-    colypos[0] = 0;
+    colxpos[1] = ourwidth - 2*border + cols->spacing;
 
     for (children = cols->children;
          children && (child = children->data);
          children = children->next) {
-        GtkRequisition creq;
-        GtkAllocation call;
 
        if (!child->widget) {
            gint percent;
 
            /* Column reconfiguration. */
-           for (i = 1; i < ncols; i++) {
-               if (colypos[0] < colypos[i])
-                   colypos[0] = colypos[i];
-           }
            ncols = child->ncols;
            percentages = child->percentages;
-           colypos = g_renew(gint, colypos, ncols);
-           for (i = 1; i < ncols; i++)
-               colypos[i] = colypos[0];
            colxpos = g_renew(gint, colxpos, ncols + 1);
            colxpos[0] = 0;
            percent = 0;
            for (i = 0; i < ncols; i++) {
                percent += percentages[i];
-               colxpos[i+1] = (((alloc->width - 2*border) + cols->spacing)
+               colxpos[i+1] = (((ourwidth - 2*border) + cols->spacing)
                                * percent / 100);
            }
            continue;
        }
 
         /* Only take visible widgets into account. */
-        if (!GTK_WIDGET_VISIBLE(child->widget))
+        if (!gtk_widget_get_visible(child->widget))
             continue;
 
-        gtk_widget_get_child_requisition(child->widget, &creq);
+        childwidth = get_width(child);
        colspan = child->colspan ? child->colspan : ncols-child->colstart;
 
         /*
@@ -714,12 +737,130 @@ static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
         * Unless we're forcing left, in which case the width is
         * exactly the requisition width.
          */
-        call.x = alloc->x + border + colxpos[child->colstart];
+        child->x = colxpos[child->colstart];
        if (child->force_left)
-           call.width = creq.width;
+           child->w = childwidth;
        else
-           call.width = (colxpos[child->colstart+colspan] -
-                         colxpos[child->colstart] - cols->spacing);
+           child->w = (colxpos[child->colstart+colspan] -
+                        colxpos[child->colstart] - cols->spacing);
+    }
+
+    g_free(colxpos);
+}
+
+static gint columns_compute_height(Columns *cols, widget_dim_fn_t get_height)
+{
+    ColumnsChild *child;
+    GList *children;
+    gint i, ncols, colspan, *colypos, retheight, childheight;
+
+    retheight = cols->spacing;
+
+    ncols = 1;
+    colypos = g_new(gint, 1);
+    colypos[0] = 0;
+
+    for (children = cols->children;
+         children && (child = children->data);
+         children = children->next) {
+
+       if (!child->widget) {
+           /* Column reconfiguration. */
+           for (i = 1; i < ncols; i++) {
+               if (colypos[0] < colypos[i])
+                   colypos[0] = colypos[i];
+           }
+           ncols = child->ncols;
+           colypos = g_renew(gint, colypos, ncols);
+           for (i = 1; i < ncols; i++)
+               colypos[i] = colypos[0];
+           continue;
+       }
+
+        /* Only take visible widgets into account. */
+        if (!gtk_widget_get_visible(child->widget))
+            continue;
+
+        childheight = get_height(child);
+        if (child->same_height_as) {
+            gint childheight2 = get_height(child->same_height_as);
+            if (childheight < childheight2)
+                childheight = childheight2;
+        }
+       colspan = child->colspan ? child->colspan : ncols-child->colstart;
+
+        /*
+         * To compute height: the widget's top will be positioned at
+         * the largest y value so far reached in any of the columns it
+         * crosses. Then it will go down by childheight plus padding;
+         * and the point it reaches at the bottom is the new y value
+         * in all those columns, and minus the padding it is also a
+         * lower bound on our own height.
+         */
+        {
+            int topy, boty;
+
+            topy = 0;
+            for (i = 0; i < colspan; i++) {
+                if (topy < colypos[child->colstart+i])
+                    topy = colypos[child->colstart+i];
+            }
+            boty = topy + childheight + cols->spacing;
+            for (i = 0; i < colspan; i++) {
+                colypos[child->colstart+i] = boty;
+            }
+
+            if (retheight < boty - cols->spacing)
+                retheight = boty - cols->spacing;
+        }
+    }
+
+    retheight += 2*gtk_container_get_border_width(GTK_CONTAINER(cols));
+
+    g_free(colypos);
+
+    return retheight;
+}
+
+static void columns_alloc_vert(Columns *cols, gint ourheight,
+                               widget_dim_fn_t get_height)
+{
+    ColumnsChild *child;
+    GList *children;
+    gint i, ncols, colspan, *colypos, realheight, fakeheight;
+
+    ncols = 1;
+    /* As in size_request, colypos is the lowest y reached in each column. */
+    colypos = g_new(gint, 1);
+    colypos[0] = 0;
+
+    for (children = cols->children;
+         children && (child = children->data);
+         children = children->next) {
+       if (!child->widget) {
+           /* Column reconfiguration. */
+           for (i = 1; i < ncols; i++) {
+               if (colypos[0] < colypos[i])
+                   colypos[0] = colypos[i];
+           }
+           ncols = child->ncols;
+           colypos = g_renew(gint, colypos, ncols);
+           for (i = 1; i < ncols; i++)
+               colypos[i] = colypos[0];
+           continue;
+       }
+
+        /* Only take visible widgets into account. */
+        if (!gtk_widget_get_visible(child->widget))
+            continue;
+
+        realheight = fakeheight = get_height(child);
+        if (child->same_height_as) {
+            gint childheight2 = get_height(child->same_height_as);
+            if (fakeheight < childheight2)
+                fakeheight = childheight2;
+        }
+       colspan = child->colspan ? child->colspan : ncols-child->colstart;
 
         /*
          * To compute height: the widget's top will be positioned
@@ -736,17 +877,279 @@ static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
                 if (topy < colypos[child->colstart+i])
                     topy = colypos[child->colstart+i];
             }
-            call.y = alloc->y + border + topy;
-            call.height = creq.height;
-            boty = topy + creq.height + cols->spacing;
+            child->y = topy + fakeheight/2 - realheight/2;
+            child->h = realheight;
+            boty = topy + fakeheight + cols->spacing;
             for (i = 0; i < colspan; i++) {
                 colypos[child->colstart+i] = boty;
             }
         }
-
-        gtk_widget_size_allocate(child->widget, &call);
     }
 
-    g_free(colxpos);
     g_free(colypos);    
 }
+
+/*
+ * Now here comes the interesting bit. The actual layout part is
+ * done in the following two functions:
+ *
+ * columns_size_request() examines the list of widgets held in the
+ * Columns, and returns a requisition stating the absolute minimum
+ * size it can bear to be.
+ *
+ * columns_size_allocate() is given an allocation telling it what
+ * size the whole container is going to be, and it calls
+ * gtk_widget_size_allocate() on all of its (visible) children to
+ * set their size and position relative to the top left of the
+ * container.
+ */
+
+#if !GTK_CHECK_VERSION(3,0,0)
+
+static gint columns_gtk2_get_width(ColumnsChild *child)
+{
+    GtkRequisition creq;
+    gtk_widget_size_request(child->widget, &creq);
+    return creq.width;
+}
+
+static gint columns_gtk2_get_height(ColumnsChild *child)
+{
+    GtkRequisition creq;
+    gtk_widget_size_request(child->widget, &creq);
+    return creq.height;
+}
+
+static void columns_size_request(GtkWidget *widget, GtkRequisition *req)
+{
+    Columns *cols;
+
+    g_return_if_fail(widget != NULL);
+    g_return_if_fail(IS_COLUMNS(widget));
+    g_return_if_fail(req != NULL);
+
+    cols = COLUMNS(widget);
+
+    req->width = columns_compute_width(cols, columns_gtk2_get_width);
+    req->height = columns_compute_height(cols, columns_gtk2_get_height);
+}
+
+static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
+{
+    Columns *cols;
+    ColumnsChild *child;
+    GList *children;
+    gint border;
+
+    g_return_if_fail(widget != NULL);
+    g_return_if_fail(IS_COLUMNS(widget));
+    g_return_if_fail(alloc != NULL);
+
+    cols = COLUMNS(widget);
+    gtk_widget_set_allocation(widget, alloc);
+
+    border = gtk_container_get_border_width(GTK_CONTAINER(cols));
+
+    columns_alloc_horiz(cols, alloc->width, columns_gtk2_get_width);
+    columns_alloc_vert(cols, alloc->height, columns_gtk2_get_height);
+
+    for (children = cols->children;
+         children && (child = children->data);
+         children = children->next) {
+        if (child->widget && gtk_widget_get_visible(child->widget)) {
+            GtkAllocation call;
+            call.x = alloc->x + border + child->x;
+            call.y = alloc->y + border + child->y;
+            call.width = child->w;
+            call.height = child->h;
+            gtk_widget_size_allocate(child->widget, &call);
+        }
+    }
+}
+
+#else /* GTK_CHECK_VERSION(3,0,0) */
+
+static gint columns_gtk3_get_min_width(ColumnsChild *child)
+{
+    gint ret;
+    gtk_widget_get_preferred_width(child->widget, &ret, NULL);
+    return ret;
+}
+
+static gint columns_gtk3_get_nat_width(ColumnsChild *child)
+{
+    gint ret;
+
+    if ((GTK_IS_LABEL(child->widget) &&
+         gtk_label_get_line_wrap(GTK_LABEL(child->widget))) ||
+        GTK_IS_ENTRY(child->widget)) {
+        /*
+         * We treat wrapping GtkLabels as a special case in this
+         * layout class, because the whole point of those is that I
+         * _don't_ want them to take up extra horizontal space for
+         * long text, but instead to wrap it to whatever size is used
+         * by the rest of the layout.
+         *
+         * GtkEntry gets similar treatment, because in OS X GTK I've
+         * found that it requests a natural width regardless of the
+         * output of gtk_entry_set_width_chars.
+         */
+        gtk_widget_get_preferred_width(child->widget, &ret, NULL);
+    } else {
+        gtk_widget_get_preferred_width(child->widget, NULL, &ret);
+    }
+    return ret;
+}
+
+static gint columns_gtk3_get_minfh_width(ColumnsChild *child)
+{
+    gint ret;
+    gtk_widget_get_preferred_width_for_height(child->widget, child->h,
+                                              &ret, NULL);
+    return ret;
+}
+
+static gint columns_gtk3_get_natfh_width(ColumnsChild *child)
+{
+    gint ret;
+    gtk_widget_get_preferred_width_for_height(child->widget, child->h,
+                                              NULL, &ret);
+    return ret;
+}
+
+static gint columns_gtk3_get_min_height(ColumnsChild *child)
+{
+    gint ret;
+    gtk_widget_get_preferred_height(child->widget, &ret, NULL);
+    return ret;
+}
+
+static gint columns_gtk3_get_nat_height(ColumnsChild *child)
+{
+    gint ret;
+    gtk_widget_get_preferred_height(child->widget, NULL, &ret);
+    return ret;
+}
+
+static gint columns_gtk3_get_minfw_height(ColumnsChild *child)
+{
+    gint ret;
+    gtk_widget_get_preferred_height_for_width(child->widget, child->w,
+                                              &ret, NULL);
+    return ret;
+}
+
+static gint columns_gtk3_get_natfw_height(ColumnsChild *child)
+{
+    gint ret;
+    gtk_widget_get_preferred_height_for_width(child->widget, child->w,
+                                              NULL, &ret);
+    return ret;
+}
+
+static void columns_get_preferred_width(GtkWidget *widget,
+                                        gint *min, gint *nat)
+{
+    Columns *cols;
+
+    g_return_if_fail(widget != NULL);
+    g_return_if_fail(IS_COLUMNS(widget));
+
+    cols = COLUMNS(widget);
+
+    if (min)
+        *min = columns_compute_width(cols, columns_gtk3_get_min_width);
+    if (nat)
+        *nat = columns_compute_width(cols, columns_gtk3_get_nat_width);
+}
+
+static void columns_get_preferred_height(GtkWidget *widget,
+                                         gint *min, gint *nat)
+{
+    Columns *cols;
+
+    g_return_if_fail(widget != NULL);
+    g_return_if_fail(IS_COLUMNS(widget));
+
+    cols = COLUMNS(widget);
+
+    if (min)
+        *min = columns_compute_height(cols, columns_gtk3_get_min_height);
+    if (nat)
+        *nat = columns_compute_height(cols, columns_gtk3_get_nat_height);
+}
+
+static void columns_get_preferred_width_for_height(GtkWidget *widget,
+                                                   gint height,
+                                                   gint *min, gint *nat)
+{
+    Columns *cols;
+
+    g_return_if_fail(widget != NULL);
+    g_return_if_fail(IS_COLUMNS(widget));
+
+    cols = COLUMNS(widget);
+
+    /* FIXME: which one should the get-height function here be? */
+    columns_alloc_vert(cols, height, columns_gtk3_get_nat_height);
+
+    if (min)
+        *min = columns_compute_width(cols, columns_gtk3_get_minfh_width);
+    if (nat)
+        *nat = columns_compute_width(cols, columns_gtk3_get_natfh_width);
+}
+
+static void columns_get_preferred_height_for_width(GtkWidget *widget,
+                                                   gint width,
+                                                   gint *min, gint *nat)
+{
+    Columns *cols;
+
+    g_return_if_fail(widget != NULL);
+    g_return_if_fail(IS_COLUMNS(widget));
+
+    cols = COLUMNS(widget);
+
+    /* FIXME: which one should the get-height function here be? */
+    columns_alloc_horiz(cols, width, columns_gtk3_get_nat_width);
+
+    if (min)
+        *min = columns_compute_height(cols, columns_gtk3_get_minfw_height);
+    if (nat)
+        *nat = columns_compute_height(cols, columns_gtk3_get_natfw_height);
+}
+
+static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
+{
+    Columns *cols;
+    ColumnsChild *child;
+    GList *children;
+    gint border;
+
+    g_return_if_fail(widget != NULL);
+    g_return_if_fail(IS_COLUMNS(widget));
+    g_return_if_fail(alloc != NULL);
+
+    cols = COLUMNS(widget);
+    gtk_widget_set_allocation(widget, alloc);
+
+    border = gtk_container_get_border_width(GTK_CONTAINER(cols));
+
+    columns_alloc_horiz(cols, alloc->width, columns_gtk3_get_min_width);
+    columns_alloc_vert(cols, alloc->height, columns_gtk3_get_minfw_height);
+
+    for (children = cols->children;
+         children && (child = children->data);
+         children = children->next) {
+        if (child->widget && gtk_widget_get_visible(child->widget)) {
+            GtkAllocation call;
+            call.x = alloc->x + border + child->x;
+            call.y = alloc->y + border + child->y;
+            call.width = child->w;
+            call.height = child->h;
+            gtk_widget_size_allocate(child->widget, &call);
+        }
+    }
+}
+
+#endif
index cdbb15c640a771cf75e10f33ce6d8a9dedaba4ce..b7410cb41c131b85fc9f115d3c587d24d621e59f 100644 (file)
@@ -15,11 +15,11 @@ extern "C" {
 #endif /* __cplusplus */
 
 #define TYPE_COLUMNS (columns_get_type())
-#define COLUMNS(obj) (GTK_CHECK_CAST((obj), TYPE_COLUMNS, Columns))
-#define COLUMNS_CLASS(klass) \
-                (GTK_CHECK_CLASS_CAST((klass), TYPE_COLUMNS, ColumnsClass))
-#define IS_COLUMNS(obj) (GTK_CHECK_TYPE((obj), TYPE_COLUMNS))
-#define IS_COLUMNS_CLASS(klass) (GTK_CHECK_CLASS_TYPE((klass), TYPE_COLUMNS))
+#define COLUMNS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_COLUMNS, Columns))
+#define COLUMNS_CLASS(klass)                                            \
+    (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_COLUMNS, ColumnsClass))
+#define IS_COLUMNS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_COLUMNS))
+#define IS_COLUMNS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TYPE_COLUMNS))
 
 typedef struct Columns_tag Columns;
 typedef struct ColumnsClass_tag ColumnsClass;
@@ -42,18 +42,21 @@ struct ColumnsChild_tag {
     GtkWidget *widget;
     gint colstart, colspan;
     gboolean force_left;              /* for recalcitrant GtkLabels */
+    ColumnsChild *same_height_as;
     /* Otherwise, this entry represents a change in the column setup. */
     gint ncols;
     gint *percentages;
+    gint x, y, w, h;           /* used during an individual size computation */
 };
 
-GtkType columns_get_type(void);
+GType columns_get_type(void);
 GtkWidget *columns_new(gint spacing);
 void columns_set_cols(Columns *cols, gint ncols, const gint *percentages);
 void columns_add(Columns *cols, GtkWidget *child,
                  gint colstart, gint colspan);
 void columns_taborder_last(Columns *cols, GtkWidget *child);
 void columns_force_left_align(Columns *cols, GtkWidget *child);
+void columns_force_same_height(Columns *cols, GtkWidget *ch1, GtkWidget *ch2);
 
 #ifdef __cplusplus
 }
diff --git a/unix/gtkcompat.h b/unix/gtkcompat.h
new file mode 100644 (file)
index 0000000..1908615
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * Header file to make compatibility with older GTK versions less
+ * painful, by #defining various things that are pure spelling changes
+ * between GTK1 and GTK2, or between 2 and 3.
+ */
+
+#if !GTK_CHECK_VERSION(2,0,0)
+
+#include <ctype.h>
+#include <X11/X.h>
+
+/* Helper macro used in definitions below. Note that although it
+ * *expands* w and flag twice, it does not *evaluate* them twice
+ * because the evaluations are in exclusive branches of ?:. So it's
+ * a side-effect-safe macro. */
+#define gtk1_widget_set_unset_flag(w, flag, b)                    \
+    ((b) ? GTK_WIDGET_SET_FLAGS(w, flag) : GTK_WIDGET_UNSET_FLAGS(w, flag))
+
+#define GType GtkType
+#define GObject GtkObject
+#define G_CALLBACK(x) GTK_SIGNAL_FUNC(x)
+#define G_OBJECT(x) GTK_OBJECT(x)
+#define G_TYPE_CHECK_INSTANCE_TYPE GTK_CHECK_TYPE
+#define G_TYPE_CHECK_INSTANCE_CAST GTK_CHECK_CAST
+#define G_TYPE_CHECK_CLASS_TYPE GTK_CHECK_CLASS_TYPE
+#define G_TYPE_CHECK_CLASS_CAST GTK_CHECK_CLASS_CAST
+
+#define g_ascii_isspace(x) (isspace((unsigned char)(x)))
+#define g_signal_connect gtk_signal_connect
+#define g_signal_connect_swapped gtk_signal_connect_object
+#define g_signal_stop_emission_by_name gtk_signal_emit_stop_by_name
+#define g_signal_emit_by_name gtk_signal_emit_by_name
+#define g_signal_handler_disconnect gtk_signal_disconnect
+#define g_object_get_data gtk_object_get_data
+#define g_object_set_data gtk_object_set_data
+#define g_object_ref_sink gtk_object_sink
+
+#define GDK_GRAB_SUCCESS GrabSuccess
+
+#define GDK_WINDOW_XID GDK_WINDOW_XWINDOW
+
+#define gtk_widget_set_size_request gtk_widget_set_usize
+#define gtk_radio_button_get_group gtk_radio_button_group
+#define gtk_notebook_set_current_page gtk_notebook_set_page
+#define gtk_color_selection_set_has_opacity_control \
+    gtk_color_selection_set_opacity
+
+#define gtk_dialog_get_content_area(dlg) ((dlg)->vbox)
+#define gtk_dialog_get_action_area(dlg) ((dlg)->action_area)
+#define gtk_dialog_set_can_default(dlg) ((dlg)->action_area)
+#define gtk_widget_get_window(w) ((w)->window)
+#define gtk_widget_get_parent(w) ((w)->parent)
+#define gtk_widget_set_allocation(w, a) ((w)->allocation = *(a))
+#define gtk_container_get_border_width(c) ((c)->border_width)
+#define gtk_bin_get_child(b) ((b)->child)
+#define gtk_color_selection_dialog_get_color_selection(cs) ((cs)->colorsel)
+#define gtk_selection_data_get_target(sd) ((sd)->target)
+#define gtk_selection_data_get_data_type(sd) ((sd)->type)
+#define gtk_selection_data_get_data(sd) ((sd)->data)
+#define gtk_selection_data_get_length(sd) ((sd)->length)
+#define gtk_selection_data_get_format(sd) ((sd)->format)
+#define gtk_adjustment_set_lower(a, val) ((a)->lower = (val))
+#define gtk_adjustment_set_upper(a, val) ((a)->upper = (val))
+#define gtk_adjustment_set_page_size(a, val) ((a)->page_size = (val))
+#define gtk_adjustment_set_page_increment(a, val) ((a)->page_increment = (val))
+#define gtk_adjustment_set_step_increment(a, val) ((a)->step_increment = (val))
+#define gtk_adjustment_get_value(a) ((a)->value)
+#define gdk_visual_get_depth(v) ((v)->depth)
+
+#define gtk_widget_set_has_window(w, b)                 \
+    gtk1_widget_set_unset_flag(w, GTK_NO_WINDOW, !(b))
+#define gtk_widget_set_mapped(w, b)                     \
+    gtk1_widget_set_unset_flag(w, GTK_MAPPED, (b))
+#define gtk_widget_set_can_default(w, b)                \
+    gtk1_widget_set_unset_flag(w, GTK_CAN_DEFAULT, (b))
+#define gtk_widget_get_visible(w) GTK_WIDGET_VISIBLE(w)
+#define gtk_widget_get_mapped(w) GTK_WIDGET_MAPPED(w)
+#define gtk_widget_get_realized(w) GTK_WIDGET_REALIZED(w)
+#define gtk_widget_get_state(w) GTK_WIDGET_STATE(w)
+
+/* This is a bit of a bodge because it relies on us only calling this
+ * macro as GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), so under
+ * GTK1 it makes sense to omit the contained function call and just
+ * return the GDK default display. */
+#define GDK_DISPLAY_XDISPLAY(x) GDK_DISPLAY()
+
+#define GDK_KEY_Alt_L                GDK_Alt_L
+#define GDK_KEY_Alt_R                GDK_Alt_R
+#define GDK_KEY_BackSpace            GDK_BackSpace
+#define GDK_KEY_Begin                GDK_Begin
+#define GDK_KEY_Break                GDK_Break
+#define GDK_KEY_Delete               GDK_Delete
+#define GDK_KEY_Down                 GDK_Down
+#define GDK_KEY_End                  GDK_End
+#define GDK_KEY_Escape               GDK_Escape
+#define GDK_KEY_F10                  GDK_F10
+#define GDK_KEY_F11                  GDK_F11
+#define GDK_KEY_F12                  GDK_F12
+#define GDK_KEY_F13                  GDK_F13
+#define GDK_KEY_F14                  GDK_F14
+#define GDK_KEY_F15                  GDK_F15
+#define GDK_KEY_F16                  GDK_F16
+#define GDK_KEY_F17                  GDK_F17
+#define GDK_KEY_F18                  GDK_F18
+#define GDK_KEY_F19                  GDK_F19
+#define GDK_KEY_F1                   GDK_F1
+#define GDK_KEY_F20                  GDK_F20
+#define GDK_KEY_F2                   GDK_F2
+#define GDK_KEY_F3                   GDK_F3
+#define GDK_KEY_F4                   GDK_F4
+#define GDK_KEY_F5                   GDK_F5
+#define GDK_KEY_F6                   GDK_F6
+#define GDK_KEY_F7                   GDK_F7
+#define GDK_KEY_F8                   GDK_F8
+#define GDK_KEY_F9                   GDK_F9
+#define GDK_KEY_Home                 GDK_Home
+#define GDK_KEY_Insert               GDK_Insert
+#define GDK_KEY_ISO_Left_Tab         GDK_ISO_Left_Tab
+#define GDK_KEY_KP_0                 GDK_KP_0
+#define GDK_KEY_KP_1                 GDK_KP_1
+#define GDK_KEY_KP_2                 GDK_KP_2
+#define GDK_KEY_KP_3                 GDK_KP_3
+#define GDK_KEY_KP_4                 GDK_KP_4
+#define GDK_KEY_KP_5                 GDK_KP_5
+#define GDK_KEY_KP_6                 GDK_KP_6
+#define GDK_KEY_KP_7                 GDK_KP_7
+#define GDK_KEY_KP_8                 GDK_KP_8
+#define GDK_KEY_KP_9                 GDK_KP_9
+#define GDK_KEY_KP_Add               GDK_KP_Add
+#define GDK_KEY_KP_Begin             GDK_KP_Begin
+#define GDK_KEY_KP_Decimal           GDK_KP_Decimal
+#define GDK_KEY_KP_Delete            GDK_KP_Delete
+#define GDK_KEY_KP_Divide            GDK_KP_Divide
+#define GDK_KEY_KP_Down              GDK_KP_Down
+#define GDK_KEY_KP_End               GDK_KP_End
+#define GDK_KEY_KP_Enter             GDK_KP_Enter
+#define GDK_KEY_KP_Home              GDK_KP_Home
+#define GDK_KEY_KP_Insert            GDK_KP_Insert
+#define GDK_KEY_KP_Left              GDK_KP_Left
+#define GDK_KEY_KP_Multiply          GDK_KP_Multiply
+#define GDK_KEY_KP_Page_Down         GDK_KP_Page_Down
+#define GDK_KEY_KP_Page_Up           GDK_KP_Page_Up
+#define GDK_KEY_KP_Right             GDK_KP_Right
+#define GDK_KEY_KP_Subtract          GDK_KP_Subtract
+#define GDK_KEY_KP_Up                GDK_KP_Up
+#define GDK_KEY_Left                 GDK_Left
+#define GDK_KEY_Meta_L               GDK_Meta_L
+#define GDK_KEY_Meta_R               GDK_Meta_R
+#define GDK_KEY_Num_Lock             GDK_Num_Lock
+#define GDK_KEY_Page_Down            GDK_Page_Down
+#define GDK_KEY_Page_Up              GDK_Page_Up
+#define GDK_KEY_Return               GDK_Return
+#define GDK_KEY_Right                GDK_Right
+#define GDK_KEY_Tab                  GDK_Tab
+#define GDK_KEY_Up                   GDK_Up
+
+#endif
+
+#if GTK_CHECK_VERSION(3,0,0)
+#define STANDARD_OK_LABEL "_OK"
+#define STANDARD_OPEN_LABEL "_Open"
+#define STANDARD_CANCEL_LABEL "_Cancel"
+#else
+#define STANDARD_OK_LABEL GTK_STOCK_OK
+#define STANDARD_OPEN_LABEL GTK_STOCK_OPEN
+#define STANDARD_CANCEL_LABEL GTK_STOCK_CANCEL
+#endif
+
+#if GTK_CHECK_VERSION(3,0,0)
+#define gtk_hseparator_new() gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)
+/* Fortunately, my hboxes and vboxes never actually set homogeneous to
+ * TRUE, so I can just wrap these deprecated constructors with a macro
+ * without also having to arrange a call to gtk_box_set_homogeneous. */
+#define gtk_hbox_new(homogeneous, spacing) \
+    gtk_box_new(GTK_ORIENTATION_HORIZONTAL, spacing)
+#define gtk_vbox_new(homogeneous, spacing) \
+    gtk_box_new(GTK_ORIENTATION_VERTICAL, spacing)
+#define gtk_vscrollbar_new(adjust) \
+    gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, adjust)
+
+#define gdk_get_display() gdk_display_get_name(gdk_display_get_default())
+
+#define gdk_cursor_new(cur) \
+    gdk_cursor_new_for_display(gdk_display_get_default(), cur)
+
+#endif
index aa97e0029746b41fa201928a8dc063f0e73403f8..7058c6e18e02628e37dbbe31c8606c63c5dbf774 100644 (file)
@@ -6,25 +6,40 @@
 #include <stdarg.h>
 #include <ctype.h>
 #include <time.h>
+
 #include <gtk/gtk.h>
+#if !GTK_CHECK_VERSION(3,0,0)
 #include <gdk/gdkkeysyms.h>
-#include <gdk/gdkx.h>
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
+#endif
+
+#define MAY_REFER_TO_GTK_IN_HEADERS
 
+#include "putty.h"
+#include "gtkcompat.h"
 #include "gtkcols.h"
 #include "gtkfont.h"
+#include "gtkmisc.h"
+
+#ifndef NOT_X_WINDOWS
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#endif
 
 #ifdef TESTMODE
 #define PUTTY_DO_GLOBALS              /* actually _define_ globals */
 #endif
 
-#include "putty.h"
 #include "storage.h"
 #include "dialog.h"
 #include "tree234.h"
 #include "licence.h"
 
+#if GTK_CHECK_VERSION(2,0,0)
+/* Decide which of GtkFileChooserDialog and GtkFileSelection to use */
+#define USE_GTK_FILE_CHOOSER_DIALOG
+#endif
+
 struct Shortcut {
     GtkWidget *widget;
     struct uctrl *uc;
@@ -73,6 +88,9 @@ struct dlgparam {
 #if !GTK_CHECK_VERSION(2,0,0)
     GtkWidget *currtreeitem, **treeitems;
     int ntreeitems;
+#else
+    int nselparams;
+    struct selparam *selparams;
 #endif
     int retval;
 };
@@ -117,8 +135,13 @@ static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event,
 #if !GTK_CHECK_VERSION(2,4,0)
 static void menuitem_activate(GtkMenuItem *item, gpointer data);
 #endif
+#if GTK_CHECK_VERSION(3,0,0)
+static void colourchoose_response(GtkDialog *dialog,
+                                  gint response_id, gpointer data);
+#else
 static void coloursel_ok(GtkButton *button, gpointer data);
 static void coloursel_cancel(GtkButton *button, gpointer data);
+#endif
 static void window_destroy(GtkWidget *widget, gpointer data);
 int get_listitemheight(GtkWidget *widget);
 
@@ -220,7 +243,7 @@ static struct uctrl *dlg_find_bywidget(struct dlgparam *dp, GtkWidget *w)
        ret = find234(dp->bywidget, w, uctrl_cmp_bywidget_find);
        if (ret)
            return ret;
-       w = w->parent;
+       w = gtk_widget_get_parent(w);
     } while (w);
     return ret;
 }
@@ -320,12 +343,8 @@ char *dlg_editbox_get(union control *ctrl, void *dlg)
 
 #if GTK_CHECK_VERSION(2,4,0)
     if (uc->combo) {
-#if GTK_CHECK_VERSION(2,6,0)
-       return dupstr(gtk_combo_box_get_active_text(GTK_COMBO_BOX(uc->combo)));
-#else
        return dupstr(gtk_entry_get_text
                      (GTK_ENTRY(gtk_bin_get_child(GTK_BIN(uc->combo)))));
-#endif
     }
 #endif
 
@@ -449,10 +468,10 @@ void dlg_listbox_addwithid(union control *ctrl, void *dlg,
        gtk_container_add(GTK_CONTAINER(uc->menu), menuitem);
        gtk_widget_show(menuitem);
 
-       gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",
-                           GINT_TO_POINTER(id));
-       gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
-                          GTK_SIGNAL_FUNC(menuitem_activate), dp);
+        g_object_set_data(G_OBJECT(menuitem), "user-data",
+                          GINT_TO_POINTER(id));
+        g_signal_connect(G_OBJECT(menuitem), "activate",
+                         G_CALLBACK(menuitem_activate), dp);
        goto done;
     }
     if (uc->list && uc->entry) {
@@ -466,8 +485,8 @@ void dlg_listbox_addwithid(union control *ctrl, void *dlg,
        gtk_container_add(GTK_CONTAINER(uc->list), listitem);
        gtk_widget_show(listitem);
 
-       gtk_object_set_data(GTK_OBJECT(listitem), "user-data",
-                           GINT_TO_POINTER(id));
+        g_object_set_data(G_OBJECT(listitem), "user-data",
+                          GINT_TO_POINTER(id));
        goto done;
     }
 #endif
@@ -521,20 +540,20 @@ void dlg_listbox_addwithid(union control *ctrl, void *dlg,
        gtk_widget_show(listitem);
 
         if (ctrl->listbox.multisel) {
-            gtk_signal_connect(GTK_OBJECT(listitem), "key_press_event",
-                               GTK_SIGNAL_FUNC(listitem_multi_key), uc->adj);
+            g_signal_connect(G_OBJECT(listitem), "key_press_event",
+                             G_CALLBACK(listitem_multi_key), uc->adj);
         } else {
-            gtk_signal_connect(GTK_OBJECT(listitem), "key_press_event",
-                               GTK_SIGNAL_FUNC(listitem_single_key), uc->adj);
+            g_signal_connect(G_OBJECT(listitem), "key_press_event",
+                             G_CALLBACK(listitem_single_key), uc->adj);
         }
-        gtk_signal_connect(GTK_OBJECT(listitem), "focus_in_event",
-                           GTK_SIGNAL_FUNC(widget_focus), dp);
-       gtk_signal_connect(GTK_OBJECT(listitem), "button_press_event",
-                          GTK_SIGNAL_FUNC(listitem_button_press), dp);
-       gtk_signal_connect(GTK_OBJECT(listitem), "button_release_event",
-                          GTK_SIGNAL_FUNC(listitem_button_release), dp);
-       gtk_object_set_data(GTK_OBJECT(listitem), "user-data",
-                           GINT_TO_POINTER(id));
+        g_signal_connect(G_OBJECT(listitem), "focus_in_event",
+                         G_CALLBACK(widget_focus), dp);
+        g_signal_connect(G_OBJECT(listitem), "button_press_event",
+                         G_CALLBACK(listitem_button_press), dp);
+        g_signal_connect(G_OBJECT(listitem), "button_release_event",
+                         G_CALLBACK(listitem_button_release), dp);
+        g_object_set_data(G_OBJECT(listitem), "user-data",
+                          GINT_TO_POINTER(id));
        goto done;
     }
 #else
@@ -582,15 +601,14 @@ int dlg_listbox_getid(union control *ctrl, void *dlg, int index)
 #if !GTK_CHECK_VERSION(2,4,0)
     if (uc->menu || uc->list) {
        GList *children;
-       GtkObject *item;
+        GObject *item;
 
        children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu :
                                                        uc->list));
-       item = GTK_OBJECT(g_list_nth_data(children, index));
+        item = G_OBJECT(g_list_nth_data(children, index));
        g_list_free(children);
 
-       return GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(item),
-                                                  "user-data"));
+        return GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "user-data"));
     }
 #endif
 #if GTK_CHECK_VERSION(2,0,0)
@@ -1022,20 +1040,23 @@ void dlg_beep(void *dlg)
     gdk_beep();
 }
 
+#if !GTK_CHECK_VERSION(3,0,0)
 static void errmsg_button_clicked(GtkButton *button, gpointer data)
 {
     gtk_widget_destroy(GTK_WIDGET(data));
 }
+#endif
 
 static void set_transient_window_pos(GtkWidget *parent, GtkWidget *child)
 {
+#if !GTK_CHECK_VERSION(2,0,0)
     gint x, y, w, h, dx, dy;
     GtkRequisition req;
     gtk_window_set_position(GTK_WINDOW(child), GTK_WIN_POS_NONE);
     gtk_widget_size_request(GTK_WIDGET(child), &req);
 
-    gdk_window_get_origin(GTK_WIDGET(parent)->window, &x, &y);
-    gdk_window_get_size(GTK_WIDGET(parent)->window, &w, &h);
+    gdk_window_get_origin(gtk_widget_get_window(GTK_WIDGET(parent)), &x, &y);
+    gdk_window_get_size(gtk_widget_get_window(GTK_WIDGET(parent)), &w, &h);
 
     /*
      * One corner of the transient will be offset inwards, by 1/4
@@ -1054,39 +1075,55 @@ static void set_transient_window_pos(GtkWidget *parent, GtkWidget *child)
     else
         dy = y + 3*h/4 - req.height;   /* work from bottom edges */
     gtk_widget_set_uposition(GTK_WIDGET(child), dx, dy);
+#endif
 }
 
-void dlg_error_msg(void *dlg, char *msg)
+void dlg_error_msg(void *dlg, const char *msg)
 {
     struct dlgparam *dp = (struct dlgparam *)dlg;
-    GtkWidget *window, *hbox, *text, *ok;
+    GtkWidget *window;
+
+#if GTK_CHECK_VERSION(3,0,0)
+    window = gtk_message_dialog_new(GTK_WINDOW(dp->window),
+                                    (GTK_DIALOG_MODAL |
+                                     GTK_DIALOG_DESTROY_WITH_PARENT),
+                                    GTK_MESSAGE_ERROR,
+                                    GTK_BUTTONS_CLOSE,
+                                    "%s", msg);
+    gtk_dialog_run(GTK_DIALOG(window));
+    gtk_widget_destroy(window);
+#else
+    GtkWidget *hbox, *text, *ok;
 
     window = gtk_dialog_new();
     text = gtk_label_new(msg);
-    gtk_misc_set_alignment(GTK_MISC(text), 0.0, 0.0);
+    align_label_left(GTK_LABEL(text));
     hbox = gtk_hbox_new(FALSE, 0);
     gtk_box_pack_start(GTK_BOX(hbox), text, FALSE, FALSE, 20);
-    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox),
+    gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(window))),
                        hbox, FALSE, FALSE, 20);
     gtk_widget_show(text);
     gtk_widget_show(hbox);
     gtk_window_set_title(GTK_WINDOW(window), "Error");
     gtk_label_set_line_wrap(GTK_LABEL(text), TRUE);
     ok = gtk_button_new_with_label("OK");
-    gtk_box_pack_end(GTK_BOX(GTK_DIALOG(window)->action_area),
+    gtk_box_pack_end(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(window))),
                      ok, FALSE, FALSE, 0);
     gtk_widget_show(ok);
-    GTK_WIDGET_SET_FLAGS(ok, GTK_CAN_DEFAULT);
+    gtk_widget_set_can_default(ok, TRUE);
     gtk_window_set_default(GTK_WINDOW(window), ok);
-    gtk_signal_connect(GTK_OBJECT(ok), "clicked",
-                       GTK_SIGNAL_FUNC(errmsg_button_clicked), window);
-    gtk_signal_connect(GTK_OBJECT(window), "destroy",
-                       GTK_SIGNAL_FUNC(window_destroy), NULL);
+    g_signal_connect(G_OBJECT(ok), "clicked",
+                     G_CALLBACK(errmsg_button_clicked), window);
+    g_signal_connect(G_OBJECT(window), "destroy",
+                     G_CALLBACK(window_destroy), NULL);
     gtk_window_set_modal(GTK_WINDOW(window), TRUE);
     gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(dp->window));
     set_transient_window_pos(dp->window, window);
     gtk_widget_show(window);
     gtk_main();
+#endif
+
+    post_main();
 }
 
 /*
@@ -1125,41 +1162,85 @@ void dlg_coloursel_start(union control *ctrl, void *dlg, int r, int g, int b)
 {
     struct dlgparam *dp = (struct dlgparam *)dlg;
     struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-    gdouble cvals[4];
 
+#if GTK_CHECK_VERSION(3,0,0)
+    GtkWidget *coloursel =
+       gtk_color_chooser_dialog_new("Select a colour",
+                                     GTK_WINDOW(dp->window));
+    gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(coloursel), FALSE);
+#else
+    GtkWidget *okbutton, *cancelbutton;
     GtkWidget *coloursel =
        gtk_color_selection_dialog_new("Select a colour");
     GtkColorSelectionDialog *ccs = GTK_COLOR_SELECTION_DIALOG(coloursel);
+    GtkColorSelection *cs = GTK_COLOR_SELECTION
+        (gtk_color_selection_dialog_get_color_selection(ccs));
+    gtk_color_selection_set_has_opacity_control(cs, FALSE);
+#endif
 
     dp->coloursel_result.ok = FALSE;
 
     gtk_window_set_modal(GTK_WINDOW(coloursel), TRUE);
-#if GTK_CHECK_VERSION(2,0,0)
-    gtk_color_selection_set_has_opacity_control(GTK_COLOR_SELECTION(ccs->colorsel), FALSE);
+
+#if GTK_CHECK_VERSION(3,0,0)
+    {
+        GdkRGBA rgba;
+        rgba.red = r / 255.0;
+        rgba.green = g / 255.0;
+        rgba.blue = b / 255.0;
+        rgba.alpha = 1.0;              /* fully opaque! */
+        gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(coloursel), &rgba);
+    }
+#elif GTK_CHECK_VERSION(2,0,0)
+    {
+        GdkColor col;
+        col.red = r * 0x0101;
+        col.green = g * 0x0101;
+        col.blue = b * 0x0101;
+        gtk_color_selection_set_current_color(cs, &col);
+    }
 #else
-    gtk_color_selection_set_opacity(GTK_COLOR_SELECTION(ccs->colorsel), FALSE);
+    {
+        gdouble cvals[4];
+        cvals[0] = r / 255.0;
+        cvals[1] = g / 255.0;
+        cvals[2] = b / 255.0;
+        cvals[3] = 1.0;                       /* fully opaque! */
+        gtk_color_selection_set_color(cs, cvals);
+    }
 #endif
-    cvals[0] = r / 255.0;
-    cvals[1] = g / 255.0;
-    cvals[2] = b / 255.0;
-    cvals[3] = 1.0;                   /* fully opaque! */
-    gtk_color_selection_set_color(GTK_COLOR_SELECTION(ccs->colorsel), cvals);
 
-    gtk_object_set_data(GTK_OBJECT(ccs->ok_button), "user-data",
+    g_object_set_data(G_OBJECT(coloursel), "user-data", (gpointer)uc);
+
+#if GTK_CHECK_VERSION(3,0,0)
+    g_signal_connect(G_OBJECT(coloursel), "response",
+                     G_CALLBACK(colourchoose_response), (gpointer)dp);
+#else
+
+#if GTK_CHECK_VERSION(2,0,0)
+    g_object_get(G_OBJECT(ccs),
+                 "ok-button", &okbutton,
+                 "cancel-button", &cancelbutton,
+                 (const char *)NULL);
+#else
+    okbutton = ccs->ok_button;
+    cancelbutton = ccs->cancel_button;
+#endif
+    g_object_set_data(G_OBJECT(okbutton), "user-data",
                        (gpointer)coloursel);
-    gtk_object_set_data(GTK_OBJECT(ccs->cancel_button), "user-data",
+    g_object_set_data(G_OBJECT(cancelbutton), "user-data",
                        (gpointer)coloursel);
-    gtk_object_set_data(GTK_OBJECT(coloursel), "user-data", (gpointer)uc);
-    gtk_signal_connect(GTK_OBJECT(ccs->ok_button), "clicked",
-                      GTK_SIGNAL_FUNC(coloursel_ok), (gpointer)dp);
-    gtk_signal_connect(GTK_OBJECT(ccs->cancel_button), "clicked",
-                      GTK_SIGNAL_FUNC(coloursel_cancel), (gpointer)dp);
-    gtk_signal_connect_object(GTK_OBJECT(ccs->ok_button), "clicked",
-                             GTK_SIGNAL_FUNC(gtk_widget_destroy),
-                             (gpointer)coloursel);
-    gtk_signal_connect_object(GTK_OBJECT(ccs->cancel_button), "clicked",
-                             GTK_SIGNAL_FUNC(gtk_widget_destroy),
-                             (gpointer)coloursel);
+    g_signal_connect(G_OBJECT(okbutton), "clicked",
+                     G_CALLBACK(coloursel_ok), (gpointer)dp);
+    g_signal_connect(G_OBJECT(cancelbutton), "clicked",
+                     G_CALLBACK(coloursel_cancel), (gpointer)dp);
+    g_signal_connect_swapped(G_OBJECT(okbutton), "clicked",
+                             G_CALLBACK(gtk_widget_destroy),
+                             (gpointer)coloursel);
+    g_signal_connect_swapped(G_OBJECT(cancelbutton), "clicked",
+                             G_CALLBACK(gtk_widget_destroy),
+                             (gpointer)coloursel);
+#endif
     gtk_widget_show(coloursel);
 }
 
@@ -1226,11 +1307,12 @@ static gboolean editbox_key(GtkWidget *widget, GdkEventKey *event,
      * Return in an edit box will now activate the default button
      * in the dialog just like it will everywhere else.
      */
-    if (event->keyval == GDK_Return && widget->parent != NULL) {
+    GtkWidget *parent = gtk_widget_get_parent(widget);
+    if (event->keyval == GDK_KEY_Return && parent != NULL) {
        gboolean return_val;
-       gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event");
-       gtk_signal_emit_by_name(GTK_OBJECT(widget->parent), "key_press_event",
-                               event, &return_val);
+        g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
+       g_signal_emit_by_name(G_OBJECT(parent), "key_press_event",
+                              event, &return_val);
        return return_val;
     }
     return FALSE;
@@ -1288,7 +1370,7 @@ static gboolean listitem_key(GtkWidget *item, GdkEventKey *event,
          */
         GtkWidget *list = item->parent;
 
-        gtk_signal_emit_stop_by_name(GTK_OBJECT(item), "key_press_event");
+        g_signal_stop_emission_by_name(G_OBJECT(item), "key_press_event");
 
         if (!multiple &&
             GTK_WIDGET_STATE(item) != GTK_STATE_SELECTED) {
@@ -1527,7 +1609,7 @@ static void menuitem_activate(GtkMenuItem *item, gpointer data)
 {
     struct dlgparam *dp = (struct dlgparam *)data;
     GtkWidget *menushell = GTK_WIDGET(item)->parent;
-    gpointer optmenu = gtk_object_get_data(GTK_OBJECT(menushell), "user-data");
+    gpointer optmenu = g_object_get_data(G_OBJECT(menushell), "user-data");
     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(optmenu));
     uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
 }
@@ -1544,15 +1626,30 @@ static void droplist_selchange(GtkComboBox *combo, gpointer data)
 
 #endif /* !GTK_CHECK_VERSION(2,4,0) */
 
+#ifdef USE_GTK_FILE_CHOOSER_DIALOG
+static void filechoose_response(GtkDialog *dialog, gint response,
+                                gpointer data)
+{
+    /* struct dlgparam *dp = (struct dlgparam *)data; */
+    struct uctrl *uc = g_object_get_data(G_OBJECT(dialog), "user-data");
+    if (response == GTK_RESPONSE_ACCEPT) {
+        gchar *name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+    gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
+        g_free(name);
+    }
+    gtk_widget_destroy(GTK_WIDGET(dialog));
+}
+#else
 static void filesel_ok(GtkButton *button, gpointer data)
 {
     /* struct dlgparam *dp = (struct dlgparam *)data; */
-    gpointer filesel = gtk_object_get_data(GTK_OBJECT(button), "user-data");
-    struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(filesel), "user-data");
+    gpointer filesel = g_object_get_data(G_OBJECT(button), "user-data");
+    struct uctrl *uc = g_object_get_data(G_OBJECT(filesel), "user-data");
     const char *name = gtk_file_selection_get_filename
        (GTK_FILE_SELECTION(filesel));
     gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
 }
+#endif
 
 static void fontsel_ok(GtkButton *button, gpointer data)
 {
@@ -1560,16 +1657,16 @@ static void fontsel_ok(GtkButton *button, gpointer data)
 
 #if !GTK_CHECK_VERSION(2,0,0)
 
-    gpointer fontsel = gtk_object_get_data(GTK_OBJECT(button), "user-data");
-    struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(fontsel), "user-data");
+    gpointer fontsel = g_object_get_data(G_OBJECT(button), "user-data");
+    struct uctrl *uc = g_object_get_data(G_OBJECT(fontsel), "user-data");
     const char *name = gtk_font_selection_dialog_get_font_name
        (GTK_FONT_SELECTION_DIALOG(fontsel));
     gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
 
 #else
 
-    unifontsel *fontsel = (unifontsel *)gtk_object_get_data
-       (GTK_OBJECT(button), "user-data");
+    unifontsel *fontsel = (unifontsel *)g_object_get_data
+        (G_OBJECT(button), "user-data");
     struct uctrl *uc = (struct uctrl *)fontsel->user_data;
     char *name = unifontsel_get_name(fontsel);
     assert(name);              /* should always be ok after OK pressed */
@@ -1579,18 +1676,61 @@ static void fontsel_ok(GtkButton *button, gpointer data)
 #endif
 }
 
+#if GTK_CHECK_VERSION(3,0,0)
+
+static void colourchoose_response(GtkDialog *dialog,
+                                  gint response_id, gpointer data)
+{
+    struct dlgparam *dp = (struct dlgparam *)data;
+    struct uctrl *uc = g_object_get_data(G_OBJECT(dialog), "user-data");
+
+    if (response_id == GTK_RESPONSE_OK) {
+        GdkRGBA rgba;
+        gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(dialog), &rgba);
+        dp->coloursel_result.r = (int) (255 * rgba.red);
+        dp->coloursel_result.g = (int) (255 * rgba.green);
+        dp->coloursel_result.b = (int) (255 * rgba.blue);
+        dp->coloursel_result.ok = TRUE;
+    } else {
+        dp->coloursel_result.ok = FALSE;
+    }
+
+    uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK);
+
+    gtk_widget_destroy(GTK_WIDGET(dialog));
+}
+
+#else /* GTK 1/2 coloursel response handlers */
+
 static void coloursel_ok(GtkButton *button, gpointer data)
 {
     struct dlgparam *dp = (struct dlgparam *)data;
-    gpointer coloursel = gtk_object_get_data(GTK_OBJECT(button), "user-data");
-    struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(coloursel), "user-data");
-    gdouble cvals[4];
-    gtk_color_selection_get_color
-       (GTK_COLOR_SELECTION(GTK_COLOR_SELECTION_DIALOG(coloursel)->colorsel),
-        cvals);
-    dp->coloursel_result.r = (int) (255 * cvals[0]);
-    dp->coloursel_result.g = (int) (255 * cvals[1]);
-    dp->coloursel_result.b = (int) (255 * cvals[2]);
+    gpointer coloursel = g_object_get_data(G_OBJECT(button), "user-data");
+    struct uctrl *uc = g_object_get_data(G_OBJECT(coloursel), "user-data");
+
+#if GTK_CHECK_VERSION(2,0,0)
+    {
+        GtkColorSelection *cs = GTK_COLOR_SELECTION
+            (gtk_color_selection_dialog_get_color_selection
+             (GTK_COLOR_SELECTION_DIALOG(coloursel)));
+        GdkColor col;
+        gtk_color_selection_get_current_color(cs, &col);
+        dp->coloursel_result.r = col.red / 0x0100;
+        dp->coloursel_result.g = col.green / 0x0100;
+        dp->coloursel_result.b = col.blue / 0x0100;
+    }
+#else
+    {
+        GtkColorSelection *cs = GTK_COLOR_SELECTION
+            (gtk_color_selection_dialog_get_color_selection
+             (GTK_COLOR_SELECTION_DIALOG(coloursel)));
+        gdouble cvals[4];
+        gtk_color_selection_get_color(cs, cvals);
+        dp->coloursel_result.r = (int) (255 * cvals[0]);
+        dp->coloursel_result.g = (int) (255 * cvals[1]);
+        dp->coloursel_result.b = (int) (255 * cvals[2]);
+    }
+#endif
     dp->coloursel_result.ok = TRUE;
     uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK);
 }
@@ -1598,35 +1738,53 @@ static void coloursel_ok(GtkButton *button, gpointer data)
 static void coloursel_cancel(GtkButton *button, gpointer data)
 {
     struct dlgparam *dp = (struct dlgparam *)data;
-    gpointer coloursel = gtk_object_get_data(GTK_OBJECT(button), "user-data");
-    struct uctrl *uc = gtk_object_get_data(GTK_OBJECT(coloursel), "user-data");
+    gpointer coloursel = g_object_get_data(G_OBJECT(button), "user-data");
+    struct uctrl *uc = g_object_get_data(G_OBJECT(coloursel), "user-data");
     dp->coloursel_result.ok = FALSE;
     uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK);
 }
 
+#endif /* end of coloursel response handlers */
+
 static void filefont_clicked(GtkButton *button, gpointer data)
 {
     struct dlgparam *dp = (struct dlgparam *)data;
     struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
 
     if (uc->ctrl->generic.type == CTRL_FILESELECT) {
+#ifdef USE_GTK_FILE_CHOOSER_DIALOG
+        GtkWidget *filechoose = gtk_file_chooser_dialog_new
+            (uc->ctrl->fileselect.title, GTK_WINDOW(dp->window),
+             (uc->ctrl->fileselect.for_writing ?
+              GTK_FILE_CHOOSER_ACTION_SAVE :
+              GTK_FILE_CHOOSER_ACTION_OPEN),
+             STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL,
+             STANDARD_OPEN_LABEL, GTK_RESPONSE_ACCEPT,
+             (const gchar *)NULL);
+       gtk_window_set_modal(GTK_WINDOW(filechoose), TRUE);
+       g_object_set_data(G_OBJECT(filechoose), "user-data", (gpointer)uc);
+       g_signal_connect(G_OBJECT(filechoose), "response",
+                         G_CALLBACK(filechoose_response), (gpointer)dp);
+       gtk_widget_show(filechoose);
+#else
        GtkWidget *filesel =
            gtk_file_selection_new(uc->ctrl->fileselect.title);
        gtk_window_set_modal(GTK_WINDOW(filesel), TRUE);
-       gtk_object_set_data
-           (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data",
+        g_object_set_data
+            (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data",
             (gpointer)filesel);
-       gtk_object_set_data(GTK_OBJECT(filesel), "user-data", (gpointer)uc);
-       gtk_signal_connect
-           (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
-            GTK_SIGNAL_FUNC(filesel_ok), (gpointer)dp);
-       gtk_signal_connect_object
-           (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
-            GTK_SIGNAL_FUNC(gtk_widget_destroy), (gpointer)filesel);
-       gtk_signal_connect_object
-           (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked",
-            GTK_SIGNAL_FUNC(gtk_widget_destroy), (gpointer)filesel);
+        g_object_set_data(G_OBJECT(filesel), "user-data", (gpointer)uc);
+        g_signal_connect
+            (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
+             G_CALLBACK(filesel_ok), (gpointer)dp);
+        g_signal_connect_swapped
+            (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
+             G_CALLBACK(gtk_widget_destroy), (gpointer)filesel);
+        g_signal_connect_swapped
+            (G_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked",
+             G_CALLBACK(gtk_widget_destroy), (gpointer)filesel);
        gtk_widget_show(filesel);
+#endif
     }
 
     if (uc->ctrl->generic.type == CTRL_FONTSELECT) {
@@ -1670,20 +1828,20 @@ static void filefont_clicked(GtkButton *button, gpointer data)
                 gdk_font_unref(font);
             }
         }
-       gtk_object_set_data
-           (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
+        g_object_set_data
+            (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
             "user-data", (gpointer)fontsel);
-       gtk_object_set_data(GTK_OBJECT(fontsel), "user-data", (gpointer)uc);
-       gtk_signal_connect
-           (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
-            "clicked", GTK_SIGNAL_FUNC(fontsel_ok), (gpointer)dp);
-       gtk_signal_connect_object
-           (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
-            "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy),
+        g_object_set_data(G_OBJECT(fontsel), "user-data", (gpointer)uc);
+        g_signal_connect
+            (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
+             "clicked", G_CALLBACK(fontsel_ok), (gpointer)dp);
+        g_signal_connect_swapped
+            (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
+             "clicked", G_CALLBACK(gtk_widget_destroy),
             (gpointer)fontsel);
-       gtk_signal_connect_object
-           (GTK_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->cancel_button),
-            "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy),
+        g_signal_connect_swapped
+            (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->cancel_button),
+             "clicked", G_CALLBACK(gtk_widget_destroy),
             (gpointer)fontsel);
        gtk_widget_show(fontsel);
 
@@ -1698,17 +1856,17 @@ static void filefont_clicked(GtkButton *button, gpointer data)
        gtk_window_set_modal(fontsel->window, TRUE);
        unifontsel_set_name(fontsel, fontname);
        
-       gtk_object_set_data(GTK_OBJECT(fontsel->ok_button),
-                           "user-data", (gpointer)fontsel);
+        g_object_set_data(G_OBJECT(fontsel->ok_button),
+                          "user-data", (gpointer)fontsel);
        fontsel->user_data = uc;
-       gtk_signal_connect(GTK_OBJECT(fontsel->ok_button), "clicked",
-                          GTK_SIGNAL_FUNC(fontsel_ok), (gpointer)dp);
-       gtk_signal_connect_object(GTK_OBJECT(fontsel->ok_button), "clicked",
-                                 GTK_SIGNAL_FUNC(unifontsel_destroy),
-                                 (gpointer)fontsel);
-       gtk_signal_connect_object(GTK_OBJECT(fontsel->cancel_button),"clicked",
-                                 GTK_SIGNAL_FUNC(unifontsel_destroy),
-                                 (gpointer)fontsel);
+        g_signal_connect(G_OBJECT(fontsel->ok_button), "clicked",
+                         G_CALLBACK(fontsel_ok), (gpointer)dp);
+        g_signal_connect_swapped(G_OBJECT(fontsel->ok_button), "clicked",
+                                 G_CALLBACK(unifontsel_destroy),
+                                 (gpointer)fontsel);
+        g_signal_connect_swapped(G_OBJECT(fontsel->cancel_button),"clicked",
+                                 G_CALLBACK(unifontsel_destroy),
+                                 (gpointer)fontsel);
 
        gtk_widget_show(GTK_WIDGET(fontsel->window));
 
@@ -1717,16 +1875,18 @@ static void filefont_clicked(GtkButton *button, gpointer data)
     }
 }
 
+#if !GTK_CHECK_VERSION(3,0,0)
 static void label_sizealloc(GtkWidget *widget, GtkAllocation *alloc,
                            gpointer data)
 {
     struct dlgparam *dp = (struct dlgparam *)data;
     struct uctrl *uc = dlg_find_bywidget(dp, widget);
 
-    gtk_widget_set_usize(uc->text, alloc->width, -1);
+    gtk_widget_set_size_request(uc->text, alloc->width, -1);
     gtk_label_set_text(GTK_LABEL(uc->text), uc->ctrl->generic.label);
-    gtk_signal_disconnect(GTK_OBJECT(uc->text), uc->textsig);
+    g_signal_handler_disconnect(G_OBJECT(uc->text), uc->textsig);
 }
+#endif
 
 /* ----------------------------------------------------------------------
  * This function does the main layout work: it reads a controlset,
@@ -1820,27 +1980,27 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
           case CTRL_BUTTON:
             w = gtk_button_new_with_label(ctrl->generic.label);
            if (win) {
-               GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT);
+               gtk_widget_set_can_default(w, TRUE);
                if (ctrl->button.isdefault)
                    gtk_window_set_default(win, w);
                if (ctrl->button.iscancel)
                    dp->cancelbutton = w;
            }
-           gtk_signal_connect(GTK_OBJECT(w), "clicked",
-                              GTK_SIGNAL_FUNC(button_clicked), dp);
-            gtk_signal_connect(GTK_OBJECT(w), "focus_in_event",
-                               GTK_SIGNAL_FUNC(widget_focus), dp);
-           shortcut_add(scs, GTK_BIN(w)->child, ctrl->button.shortcut,
-                        SHORTCUT_UCTRL, uc);
+            g_signal_connect(G_OBJECT(w), "clicked",
+                             G_CALLBACK(button_clicked), dp);
+            g_signal_connect(G_OBJECT(w), "focus_in_event",
+                             G_CALLBACK(widget_focus), dp);
+            shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)),
+                         ctrl->button.shortcut, SHORTCUT_UCTRL, uc);
             break;
           case CTRL_CHECKBOX:
             w = gtk_check_button_new_with_label(ctrl->generic.label);
-           gtk_signal_connect(GTK_OBJECT(w), "toggled",
-                              GTK_SIGNAL_FUNC(button_toggled), dp);
-            gtk_signal_connect(GTK_OBJECT(w), "focus_in_event",
-                               GTK_SIGNAL_FUNC(widget_focus), dp);
-           shortcut_add(scs, GTK_BIN(w)->child, ctrl->checkbox.shortcut,
-                        SHORTCUT_UCTRL, uc);
+            g_signal_connect(G_OBJECT(w), "toggled",
+                             G_CALLBACK(button_toggled), dp);
+            g_signal_connect(G_OBJECT(w), "focus_in_event",
+                             G_CALLBACK(widget_focus), dp);
+            shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)),
+                         ctrl->checkbox.shortcut, SHORTCUT_UCTRL, uc);
            left = TRUE;
             break;
           case CTRL_RADIO:
@@ -1883,19 +2043,19 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                     b = (gtk_radio_button_new_with_label
                          (group, ctrl->radio.buttons[i]));
                    uc->buttons[i] = b;
-                    group = gtk_radio_button_group(GTK_RADIO_BUTTON(b));
+                    group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b));
                     colstart = i % ctrl->radio.ncolumns;
                     columns_add(COLUMNS(w), b, colstart,
                                 (i == ctrl->radio.nbuttons-1 ?
                                  ctrl->radio.ncolumns - colstart : 1));
                    columns_force_left_align(COLUMNS(w), b);
                     gtk_widget_show(b);
-                   gtk_signal_connect(GTK_OBJECT(b), "toggled",
-                                      GTK_SIGNAL_FUNC(button_toggled), dp);
-                    gtk_signal_connect(GTK_OBJECT(b), "focus_in_event",
-                                       GTK_SIGNAL_FUNC(widget_focus), dp);
+                    g_signal_connect(G_OBJECT(b), "toggled",
+                                     G_CALLBACK(button_toggled), dp);
+                    g_signal_connect(G_OBJECT(b), "focus_in_event",
+                                     G_CALLBACK(widget_focus), dp);
                    if (ctrl->radio.shortcuts) {
-                       shortcut_add(scs, GTK_BIN(b)->child,
+                       shortcut_add(scs, gtk_bin_get_child(GTK_BIN(b)),
                                     ctrl->radio.shortcuts[i],
                                     SHORTCUT_UCTRL, uc);
                    }
@@ -1904,7 +2064,6 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
             break;
           case CTRL_EDITBOX:
            {
-                GtkRequisition req;
                GtkWidget *signalobject;
 
                if (ctrl->editbox.has_list) {
@@ -1923,8 +2082,10 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                     */
                    uc->listmodel = gtk_list_store_new(2, G_TYPE_INT,
                                                       G_TYPE_STRING);
-                   w = gtk_combo_box_entry_new_with_model
-                       (GTK_TREE_MODEL(uc->listmodel), 1);
+                   w = gtk_combo_box_new_with_model_and_entry
+                       (GTK_TREE_MODEL(uc->listmodel));
+                    g_object_set(G_OBJECT(w), "entry-text-column", 1,
+                                 (const char *)NULL);
                    /* We cannot support password combo boxes. */
                    assert(!ctrl->editbox.password);
                    uc->combo = w;
@@ -1938,28 +2099,37 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                    signalobject = w;
                }
                uc->entrysig =
-                   gtk_signal_connect(GTK_OBJECT(signalobject), "changed",
-                                      GTK_SIGNAL_FUNC(editbox_changed), dp);
-               gtk_signal_connect(GTK_OBJECT(signalobject), "key_press_event",
-                                  GTK_SIGNAL_FUNC(editbox_key), dp);
-               gtk_signal_connect(GTK_OBJECT(signalobject), "focus_in_event",
-                                  GTK_SIGNAL_FUNC(widget_focus), dp);
-               gtk_signal_connect(GTK_OBJECT(signalobject), "focus_out_event",
-                                  GTK_SIGNAL_FUNC(editbox_lostfocus), dp);
-               gtk_signal_connect(GTK_OBJECT(signalobject), "focus_out_event",
-                                  GTK_SIGNAL_FUNC(editbox_lostfocus), dp);
+                    g_signal_connect(G_OBJECT(signalobject), "changed",
+                                     G_CALLBACK(editbox_changed), dp);
+                g_signal_connect(G_OBJECT(signalobject), "key_press_event",
+                                 G_CALLBACK(editbox_key), dp);
+                g_signal_connect(G_OBJECT(signalobject), "focus_in_event",
+                                 G_CALLBACK(widget_focus), dp);
+                g_signal_connect(G_OBJECT(signalobject), "focus_out_event",
+                                 G_CALLBACK(editbox_lostfocus), dp);
+                g_signal_connect(G_OBJECT(signalobject), "focus_out_event",
+                                 G_CALLBACK(editbox_lostfocus), dp);
+
+#if !GTK_CHECK_VERSION(3,0,0)
                /*
                 * Edit boxes, for some strange reason, have a minimum
                 * width of 150 in GTK 1.2. We don't want this - we'd
                 * rather the edit boxes acquired their natural width
                 * from the column layout of the rest of the box.
-                *
-                * Also, while we're here, we'll squirrel away the
-                * edit box height so we can use that to centre its
-                * label vertically beside it.
                 */
-                gtk_widget_size_request(w, &req);
-                gtk_widget_set_usize(w, 10, req.height);
+                {
+                    GtkRequisition req;
+                    gtk_widget_size_request(w, &req);
+                    gtk_widget_set_size_request(w, 10, req.height);
+                }
+#else
+                /*
+                 * In GTK 3, this is still true, but there's a special
+                 * method for GtkEntry in particular to fix it.
+                 */
+                if (GTK_IS_ENTRY(w))
+                    gtk_entry_set_width_chars(GTK_ENTRY(w), 1);
+#endif
 
                if (ctrl->generic.label) {
                    GtkWidget *label, *container;
@@ -1982,9 +2152,8 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                        columns_add(COLUMNS(container), label, 0, 1);
                        columns_force_left_align(COLUMNS(container), label);
                        columns_add(COLUMNS(container), w, 1, 1);
-                       /* Centre the label vertically. */
-                       gtk_widget_set_usize(label, -1, req.height);
-                       gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
+                        columns_force_same_height(COLUMNS(container),
+                                                  label, w);
                    }
                    gtk_widget_show(label);
                    gtk_widget_show(w);
@@ -1998,8 +2167,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
           case CTRL_FONTSELECT:
             {
                 GtkWidget *ww;
-                GtkRequisition req;
-                char *browsebtn =
+                const char *browsebtn =
                     (ctrl->generic.type == CTRL_FILESELECT ?
                      "Browse..." : "Change...");
 
@@ -2021,8 +2189,15 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                 }
 
                 uc->entry = ww = gtk_entry_new();
-                gtk_widget_size_request(ww, &req);
-                gtk_widget_set_usize(ww, 10, req.height);
+#if !GTK_CHECK_VERSION(3,0,0)
+                {
+                    GtkRequisition req;
+                    gtk_widget_size_request(ww, &req);
+                    gtk_widget_set_size_request(ww, 10, req.height);
+                }
+#else
+                gtk_entry_set_width_chars(GTK_ENTRY(ww), 1);
+#endif
                 columns_add(COLUMNS(w), ww, 0, 1);
                 gtk_widget_show(ww);
 
@@ -2030,17 +2205,19 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                 columns_add(COLUMNS(w), ww, 1, 1);
                 gtk_widget_show(ww);
 
-               gtk_signal_connect(GTK_OBJECT(uc->entry), "key_press_event",
-                                  GTK_SIGNAL_FUNC(editbox_key), dp);
+                columns_force_same_height(COLUMNS(w), uc->entry, uc->button);
+
+                g_signal_connect(G_OBJECT(uc->entry), "key_press_event",
+                                 G_CALLBACK(editbox_key), dp);
                uc->entrysig =
-                   gtk_signal_connect(GTK_OBJECT(uc->entry), "changed",
-                                      GTK_SIGNAL_FUNC(editbox_changed), dp);
-                gtk_signal_connect(GTK_OBJECT(uc->entry), "focus_in_event",
-                                   GTK_SIGNAL_FUNC(widget_focus), dp);
-                gtk_signal_connect(GTK_OBJECT(uc->button), "focus_in_event",
-                                   GTK_SIGNAL_FUNC(widget_focus), dp);
-               gtk_signal_connect(GTK_OBJECT(ww), "clicked",
-                                  GTK_SIGNAL_FUNC(filefont_clicked), dp);
+                    g_signal_connect(G_OBJECT(uc->entry), "changed",
+                                     G_CALLBACK(editbox_changed), dp);
+                g_signal_connect(G_OBJECT(uc->entry), "focus_in_event",
+                                 G_CALLBACK(widget_focus), dp);
+                g_signal_connect(G_OBJECT(uc->button), "focus_in_event",
+                                 G_CALLBACK(widget_focus), dp);
+                g_signal_connect(G_OBJECT(ww), "clicked",
+                                 G_CALLBACK(filefont_clicked), dp);
             }
             break;
           case CTRL_LISTBOX:
@@ -2087,10 +2264,10 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                 uc->optmenu = w = gtk_option_menu_new();
                uc->menu = gtk_menu_new();
                gtk_option_menu_set_menu(GTK_OPTION_MENU(w), uc->menu);
-               gtk_object_set_data(GTK_OBJECT(uc->menu), "user-data",
-                                   (gpointer)uc->optmenu);
-                gtk_signal_connect(GTK_OBJECT(uc->optmenu), "focus_in_event",
-                                   GTK_SIGNAL_FUNC(widget_focus), dp);
+                g_object_set_data(G_OBJECT(uc->menu), "user-data",
+                                  (gpointer)uc->optmenu);
+                g_signal_connect(G_OBJECT(uc->optmenu), "focus_in_event",
+                                 G_CALLBACK(widget_focus), dp);
 #else
                /*
                 * Late-GTK2 style using a GtkComboBox.
@@ -2120,6 +2297,9 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                 */
                g_signal_connect(G_OBJECT(w), "changed",
                                 G_CALLBACK(droplist_selchange), dp);
+
+                g_signal_connect(G_OBJECT(w), "focus_in_event",
+                                 G_CALLBACK(widget_focus), dp);
 #endif
             } else {
 #if !GTK_CHECK_VERSION(2,0,0)
@@ -2147,10 +2327,10 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                     (GTK_SCROLLED_WINDOW(w));
 
                 gtk_widget_show(uc->list);
-               gtk_signal_connect(GTK_OBJECT(uc->list), "selection-changed",
-                                  GTK_SIGNAL_FUNC(list_selchange), dp);
-                gtk_signal_connect(GTK_OBJECT(uc->list), "focus_in_event",
-                                   GTK_SIGNAL_FUNC(widget_focus), dp);
+                g_signal_connect(G_OBJECT(uc->list), "selection-changed",
+                                 G_CALLBACK(list_selchange), dp);
+                g_signal_connect(G_OBJECT(uc->list), "focus_in_event",
+                                 G_CALLBACK(widget_focus), dp);
 
                 /*
                  * Adjust the height of the scrolled window to the
@@ -2167,7 +2347,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                {
                    int edge;
                    edge = GTK_WIDGET(uc->list)->style->klass->ythickness;
-                    gtk_widget_set_usize(w, 10,
+                    gtk_widget_set_size_request(w, 10,
                                          2*edge + (ctrl->listbox.height *
                                                   get_listitemheight(w)));
                }
@@ -2190,17 +2370,17 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                     button = gtk_button_new_with_label("Up");
                     columns_add(COLUMNS(cols), button, 1, 1);
                     gtk_widget_show(button);
-                   gtk_signal_connect(GTK_OBJECT(button), "clicked",
-                                      GTK_SIGNAL_FUNC(draglist_up), dp);
-                    gtk_signal_connect(GTK_OBJECT(button), "focus_in_event",
-                                       GTK_SIGNAL_FUNC(widget_focus), dp);
+                    g_signal_connect(G_OBJECT(button), "clicked",
+                                     G_CALLBACK(draglist_up), dp);
+                    g_signal_connect(G_OBJECT(button), "focus_in_event",
+                                     G_CALLBACK(widget_focus), dp);
                     button = gtk_button_new_with_label("Down");
                     columns_add(COLUMNS(cols), button, 1, 1);
                     gtk_widget_show(button);
-                   gtk_signal_connect(GTK_OBJECT(button), "clicked",
-                                      GTK_SIGNAL_FUNC(draglist_down), dp);
-                    gtk_signal_connect(GTK_OBJECT(button), "focus_in_event",
-                                       GTK_SIGNAL_FUNC(widget_focus), dp);
+                    g_signal_connect(G_OBJECT(button), "clicked",
+                                     G_CALLBACK(draglist_down), dp);
+                    g_signal_connect(G_OBJECT(button), "focus_in_event",
+                                     G_CALLBACK(widget_focus), dp);
 
                     w = cols;
                 }
@@ -2224,8 +2404,10 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                    (sel, ctrl->listbox.multisel ? GTK_SELECTION_MULTIPLE :
                     GTK_SELECTION_SINGLE);
                uc->treeview = w;
-               gtk_signal_connect(GTK_OBJECT(w), "row-activated",
-                                  GTK_SIGNAL_FUNC(listbox_doubleclick), dp);
+                g_signal_connect(G_OBJECT(w), "row-activated",
+                                 G_CALLBACK(listbox_doubleclick), dp);
+                g_signal_connect(G_OBJECT(w), "focus_in_event",
+                                 G_CALLBACK(widget_focus), dp);
                g_signal_connect(G_OBJECT(sel), "changed",
                                 G_CALLBACK(listbox_selchange), dp);
 
@@ -2252,10 +2434,10 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                         */
                         cellrend = gtk_cell_renderer_text_new();
                         if (!ctrl->listbox.hscroll) {
-                            gtk_object_set(GTK_OBJECT(cellrend),
-                                           "ellipsize", PANGO_ELLIPSIZE_END,
-                                           "ellipsize-set", TRUE,
-                                           NULL);
+                            g_object_set(G_OBJECT(cellrend),
+                                         "ellipsize", PANGO_ELLIPSIZE_END,
+                                         "ellipsize-set", TRUE,
+                                         (const char *)NULL);
                         }
                        column = gtk_tree_view_column_new_with_attributes
                            ("heading", cellrend, "text", i+1, (char *)NULL);
@@ -2287,12 +2469,14 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
 
            if (ctrl->generic.label) {
                GtkWidget *label, *container;
-                GtkRequisition req;
 
                label = gtk_label_new(ctrl->generic.label);
+#if GTK_CHECK_VERSION(3,0,0)
+                gtk_label_set_width_chars(GTK_LABEL(label), 3);
+#endif
 
                shortcut_add(scs, label, ctrl->listbox.shortcut,
-                            SHORTCUT_FOCUS, w);
+                            SHORTCUT_UCTRL, uc);
 
                container = columns_new(4);
                if (ctrl->listbox.percentwidth == 100) {
@@ -2307,10 +2491,8 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
                    columns_add(COLUMNS(container), label, 0, 1);
                    columns_force_left_align(COLUMNS(container), label);
                    columns_add(COLUMNS(container), w, 1, 1);
-                   /* Centre the label vertically. */
-                   gtk_widget_size_request(w, &req);
-                   gtk_widget_set_usize(label, -1, req.height);
-                   gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
+                    columns_force_same_height(COLUMNS(container),
+                                              label, w);
                }
                gtk_widget_show(label);
                gtk_widget_show(w);
@@ -2321,8 +2503,9 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
 
            break;
           case CTRL_TEXT:
+#if !GTK_CHECK_VERSION(3,0,0)
            /*
-            * Wrapping text widgets don't sit well with the GTK
+            * Wrapping text widgets don't sit well with the GTK2
             * layout model, in which widgets state a minimum size
             * and the whole window then adjusts to the smallest
             * size it can sensibly take given its contents. A
@@ -2347,11 +2530,20 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs,
             * than one line).
             */
             uc->text = w = gtk_label_new("X");
-            gtk_misc_set_alignment(GTK_MISC(w), 0.0, 0.0);
-            gtk_label_set_line_wrap(GTK_LABEL(w), TRUE);
            uc->textsig =
-               gtk_signal_connect(GTK_OBJECT(w), "size-allocate",
-                                  GTK_SIGNAL_FUNC(label_sizealloc), dp);
+                g_signal_connect(G_OBJECT(w), "size-allocate",
+                                 G_CALLBACK(label_sizealloc), dp);
+#else
+            /*
+             * In GTK3, this is all fixed, because the main aim of the
+             * new 'height-for-width' geometry management is to make
+             * wrapping labels behave sensibly. So now we can just do
+             * the obvious thing.
+             */
+            uc->text = w = gtk_label_new(uc->ctrl->generic.label);
+#endif
+            align_label_left(GTK_LABEL(w));
+            gtk_label_set_line_wrap(GTK_LABEL(w), TRUE);
             break;
         }
 
@@ -2401,7 +2593,7 @@ static void treeselection_changed(GtkTreeSelection *treeselection,
     sp = &sps[spindex];
 
     page_num = gtk_notebook_page_num(sp->panels, sp->panel);
-    gtk_notebook_set_page(sp->panels, page_num);
+    gtk_notebook_set_current_page(sp->panels, page_num);
 
     dlg_refresh(NULL, sp->dp);
 
@@ -2456,7 +2648,7 @@ gint tree_focus(GtkContainer *container, GtkDirectionType direction,
 {
     struct dlgparam *dp = (struct dlgparam *)data;
 
-    gtk_signal_emit_stop_by_name(GTK_OBJECT(container), "focus");
+    g_signal_stop_emission_by_name(G_OBJECT(container), "focus");
     /*
      * If there's a focused treeitem, we return FALSE to cause the
      * focus to move on to some totally other control. If not, we
@@ -2470,8 +2662,8 @@ int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
 {
     struct dlgparam *dp = (struct dlgparam *)data;
 
-    if (event->keyval == GDK_Escape && dp->cancelbutton) {
-       gtk_signal_emit_by_name(GTK_OBJECT(dp->cancelbutton), "clicked");
+    if (event->keyval == GDK_KEY_Escape && dp->cancelbutton) {
+        g_signal_emit_by_name(G_OBJECT(dp->cancelbutton), "clicked");
        return TRUE;
     }
 
@@ -2503,15 +2695,13 @@ int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
              case CTRL_BUTTON:
                /* Check boxes and buttons get the focus _and_ get toggled. */
                gtk_widget_grab_focus(sc->uc->toplevel);
-               gtk_signal_emit_by_name(GTK_OBJECT(sc->uc->toplevel),
-                                       "clicked");
+               g_signal_emit_by_name(G_OBJECT(sc->uc->toplevel), "clicked");
                break;
              case CTRL_FILESELECT:
              case CTRL_FONTSELECT:
                /* File/font selectors have their buttons pressed (ooer),
                 * and focus transferred to the edit box. */
-               gtk_signal_emit_by_name(GTK_OBJECT(sc->uc->button),
-                                       "clicked");
+               g_signal_emit_by_name(G_OBJECT(sc->uc->button), "clicked");
                gtk_widget_grab_focus(sc->uc->entry);
                break;
              case CTRL_RADIO:
@@ -2536,8 +2726,8 @@ int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
                    for (i = 0; i < sc->uc->ctrl->radio.nbuttons; i++)
                        if (schr == sc->uc->ctrl->radio.shortcuts[i]) {
                            gtk_widget_grab_focus(sc->uc->buttons[i]);
-                           gtk_signal_emit_by_name
-                               (GTK_OBJECT(sc->uc->buttons[i]), "clicked");
+                            g_signal_emit_by_name
+                                (G_OBJECT(sc->uc->buttons[i]), "clicked");
                        }
                }
                break;
@@ -2553,9 +2743,9 @@ int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
                     * We need to manufacture a button press event :-/ */
                    bev.type = GDK_BUTTON_PRESS;
                    bev.button = 1;
-                   gtk_signal_emit_by_name(GTK_OBJECT(sc->uc->optmenu),
-                                           "button_press_event",
-                                           &bev, &returnval);
+                    g_signal_emit_by_name(G_OBJECT(sc->uc->optmenu),
+                                          "button_press_event",
+                                          &bev, &returnval);
                    break;
                }
 #else
@@ -2633,10 +2823,9 @@ int tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
                }
            }
        }
-        gtk_signal_emit_stop_by_name(GTK_OBJECT(widget),
-                                     "key_press_event");
+        g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
         if (j >= 0) {
-            gtk_signal_emit_by_name(GTK_OBJECT(dp->treeitems[j]), "toggle");
+            g_signal_emit_by_name(G_OBJECT(dp->treeitems[j]), "toggle");
             gtk_widget_grab_focus(dp->treeitems[j]);
         }
         return TRUE;
@@ -2647,14 +2836,12 @@ int tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
      * branches.
      */
     if (event->keyval == GDK_Left || event->keyval == GDK_KP_Left) {
-        gtk_signal_emit_stop_by_name(GTK_OBJECT(widget),
-                                     "key_press_event");
+        g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
        gtk_tree_item_collapse(GTK_TREE_ITEM(widget));
        return TRUE;
     }
     if (event->keyval == GDK_Right || event->keyval == GDK_KP_Right) {
-        gtk_signal_emit_stop_by_name(GTK_OBJECT(widget),
-                                     "key_press_event");
+        g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
        gtk_tree_item_expand(GTK_TREE_ITEM(widget));
        return TRUE;
     }
@@ -2666,20 +2853,24 @@ int tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
 static void shortcut_highlight(GtkWidget *labelw, int chr)
 {
     GtkLabel *label = GTK_LABEL(labelw);
-    gchar *currstr, *pattern;
+    const gchar *currstr;
+    gchar *pattern;
     int i;
 
-    gtk_label_get(label, &currstr);
+#if !GTK_CHECK_VERSION(2,0,0)
+    {
+        gchar *currstr_nonconst;
+        gtk_label_get(label, &currstr_nonconst);
+        currstr = currstr_nonconst;
+    }
+#else
+    currstr = gtk_label_get_text(label);
+#endif
+
     for (i = 0; currstr[i]; i++)
        if (tolower((unsigned char)currstr[i]) == chr) {
-           GtkRequisition req;
-
            pattern = dupprintf("%*s_", i, "");
-
-           gtk_widget_size_request(GTK_WIDGET(label), &req);
            gtk_label_set_pattern(label, pattern);
-           gtk_widget_set_usize(GTK_WIDGET(label), -1, req.height);
-
            sfree(pattern);
            break;
        }
@@ -2697,7 +2888,7 @@ void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
 
     scs->sc[chr].action = action;
 
-    if (action == SHORTCUT_FOCUS) {
+    if (action == SHORTCUT_FOCUS || action == SHORTCUT_TREE) {
        scs->sc[chr].uc = NULL;
        scs->sc[chr].widget = (GtkWidget *)ptr;
     } else {
@@ -2714,85 +2905,58 @@ int get_listitemheight(GtkWidget *w)
     GtkWidget *listitem = gtk_list_item_new_with_label("foo");
     GtkRequisition req;
     gtk_widget_size_request(listitem, &req);
-    gtk_object_sink(GTK_OBJECT(listitem));
+    g_object_ref_sink(G_OBJECT(listitem));
     return req.height;
 #else
     int height;
     GtkCellRenderer *cr = gtk_cell_renderer_text_new();
+#if GTK_CHECK_VERSION(3,0,0)
+    {
+        GtkRequisition req;
+        /*
+         * Since none of my list items wraps in this GUI, no
+         * interesting width-for-height behaviour should be happening,
+         * so I don't think it should matter here whether I ask for
+         * the minimum or natural height.
+         */
+        gtk_cell_renderer_get_preferred_size(cr, w, &req, NULL);
+        height = req.height;
+    }
+#else
     gtk_cell_renderer_get_size(cr, w, NULL, NULL, NULL, NULL, &height);
+#endif
     g_object_ref(G_OBJECT(cr));
-    gtk_object_sink(GTK_OBJECT(cr));
+    g_object_ref_sink(G_OBJECT(cr));
     g_object_unref(G_OBJECT(cr));
     return height;
 #endif
 }
 
-void set_dialog_action_area(GtkDialog *dlg, GtkWidget *w)
+#if GTK_CHECK_VERSION(2,0,0)
+void initial_treeview_collapse(struct dlgparam *dp, GtkWidget *tree)
 {
-#if !GTK_CHECK_VERSION(2,0,0)
-
-    /*
-     * In GTK 1, laying out the buttons at the bottom of the
-     * configuration box is nice and easy, because a GtkDialog's
-     * action_area is a GtkHBox which stretches to cover the full
-     * width of the dialog. So we just put our Columns widget
-     * straight into that hbox, and it ends up just where we want
-     * it.
-     */
-    gtk_box_pack_start(GTK_BOX(dlg->action_area), w, TRUE, TRUE, 0);
-
-#else
     /*
-     * In GTK 2, the action area is now a GtkHButtonBox and its
-     * layout behaviour seems to be different: it doesn't stretch
-     * to cover the full width of the window, but instead finds its
-     * own preferred width and right-aligns that within the window.
-     * This isn't what we want, because we have both left-aligned
-     * and right-aligned buttons coming out of the above call to
-     * layout_ctrls(), and right-aligning the whole thing will
-     * result in the former being centred and looking weird.
-     *
-     * So instead we abandon the dialog's action area completely:
-     * we gtk_widget_hide() it in the below code, and we also call
-     * gtk_dialog_set_has_separator() to remove the separator above
-     * it. We then insert our own action area into the end of the
-     * dialog's main vbox, and add our own separator above that.
-     *
-     * (Ideally, if we were a native GTK app, we would use the
-     * GtkHButtonBox's _own_ innate ability to support one set of
-     * buttons being right-aligned and one left-aligned. But to do
-     * that here, we would have to either (a) pick apart our cross-
-     * platform layout structures and treat them specially for this
-     * particular set of controls, which would be painful, or else
-     * (b) develop a special and simpler cross-platform
-     * representation for these particular controls, and introduce
-     * special-case code into all the _other_ platforms to handle
-     * it. Neither appeals. Therefore, I regretfully discard the
-     * GTKHButtonBox and go it alone.)
-     */
-
-    GtkWidget *align;
-    align = gtk_alignment_new(0, 0, 1, 1);
-    gtk_container_add(GTK_CONTAINER(align), w);
-    /*
-     * The purpose of this GtkAlignment is to provide padding
-     * around the buttons. The padding we use is twice the padding
-     * used in our GtkColumns, because we nest two GtkColumns most
-     * of the time (one separating the tree view from the main
-     * controls, and another for the main controls themselves).
+     * Collapse the deeper branches of the treeview into the state we
+     * like them to start off in. See comment below in do_config_box.
      */
-#if GTK_CHECK_VERSION(2,4,0)
-    gtk_alignment_set_padding(GTK_ALIGNMENT(align), 8, 8, 8, 8);
-#endif
-    gtk_widget_show(align);
-    gtk_box_pack_end(GTK_BOX(dlg->vbox), align, FALSE, TRUE, 0);
-    w = gtk_hseparator_new();
-    gtk_box_pack_end(GTK_BOX(dlg->vbox), w, FALSE, TRUE, 0);
-    gtk_widget_show(w);
-    gtk_widget_hide(dlg->action_area);
-    gtk_dialog_set_has_separator(dlg, FALSE);
+    int i;
+    for (i = 0; i < dp->nselparams; i++)
+        if (dp->selparams[i].depth >= 2)
+            gtk_tree_view_collapse_row(GTK_TREE_VIEW(tree),
+                                       dp->selparams[i].treepath);
+}
 #endif
+
+#if GTK_CHECK_VERSION(3,0,0)
+void treeview_map_event(GtkWidget *tree, gpointer data)
+{
+    struct dlgparam *dp = (struct dlgparam *)data;
+    GtkAllocation alloc;
+    gtk_widget_get_allocation(tree, &alloc);
+    gtk_widget_set_size_request(tree, alloc.width, -1);
+    initial_treeview_collapse(dp, tree);
 }
+#endif
 
 int do_config_box(const char *title, Conf *conf, int midsession,
                  int protcfginfo)
@@ -2824,7 +2988,7 @@ int do_config_box(const char *title, Conf *conf, int midsession,
        scs.sc[index].action = SHORTCUT_EMPTY;
     }
 
-    window = gtk_dialog_new();
+    window = our_dialog_new();
 
     ctrlbox = ctrl_new_box();
     protocol = conf_get_int(conf, CONF_protocol);
@@ -2834,7 +2998,7 @@ int do_config_box(const char *title, Conf *conf, int midsession,
 
     gtk_window_set_title(GTK_WINDOW(window), title);
     hbox = gtk_hbox_new(FALSE, 4);
-    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), hbox, TRUE, TRUE, 0);
+    our_dialog_add_to_content_area(GTK_WINDOW(window), hbox, TRUE, TRUE, 0);
     gtk_container_set_border_width(GTK_CONTAINER(hbox), 10);
     gtk_widget_show(hbox);
     vbox = gtk_vbox_new(FALSE, 4);
@@ -2864,11 +3028,11 @@ int do_config_box(const char *title, Conf *conf, int midsession,
     tree = gtk_tree_new();
     gtk_tree_set_view_mode(GTK_TREE(tree), GTK_TREE_VIEW_ITEM);
     gtk_tree_set_selection_mode(GTK_TREE(tree), GTK_SELECTION_BROWSE);
-    gtk_signal_connect(GTK_OBJECT(tree), "focus",
-                      GTK_SIGNAL_FUNC(tree_focus), &dp);
+    g_signal_connect(G_OBJECT(tree), "focus",
+                     G_CALLBACK(tree_focus), &dp);
 #endif
-    gtk_signal_connect(GTK_OBJECT(tree), "focus_in_event",
-                       GTK_SIGNAL_FUNC(widget_focus), &dp);
+    g_signal_connect(G_OBJECT(tree), "focus_in_event",
+                     G_CALLBACK(widget_focus), &dp);
     shortcut_add(&scs, label, 'g', SHORTCUT_TREE, tree);
     gtk_widget_show(treescroll);
     gtk_box_pack_start(GTK_BOX(vbox), treescroll, TRUE, TRUE, 0);
@@ -2888,7 +3052,7 @@ int do_config_box(const char *title, Conf *conf, int midsession,
        if (!*s->pathname) {
            w = layout_ctrls(&dp, &scs, s, GTK_WINDOW(window));
 
-           set_dialog_action_area(GTK_DIALOG(window), w);
+           our_dialog_set_action_area(GTK_WINDOW(window), w);
        } else {
            int j = path ? ctrl_path_compare(s->pathname, path) : 0;
            if (j != INT_MAX) {        /* add to treeview, start new panel */
@@ -2931,7 +3095,8 @@ int do_config_box(const char *title, Conf *conf, int midsession,
 
                    page_num = gtk_notebook_page_num(GTK_NOTEBOOK(panels),
                                                     panelvbox);
-                   gtk_notebook_set_page(GTK_NOTEBOOK(panels), page_num);
+                   gtk_notebook_set_current_page(GTK_NOTEBOOK(panels),
+                                                  page_num);
                }
 
                if (nselparams >= selparamsize) {
@@ -2998,10 +3163,10 @@ int do_config_box(const char *title, Conf *conf, int midsession,
                treeitemlevels[j] = GTK_TREE_ITEM(treeitem);
                treelevels[j] = NULL;
 
-                gtk_signal_connect(GTK_OBJECT(treeitem), "key_press_event",
-                                   GTK_SIGNAL_FUNC(tree_key_press), &dp);
-                gtk_signal_connect(GTK_OBJECT(treeitem), "focus_in_event",
-                                   GTK_SIGNAL_FUNC(widget_focus), &dp);
+                g_signal_connect(G_OBJECT(treeitem), "key_press_event",
+                                 G_CALLBACK(tree_key_press), &dp);
+                g_signal_connect(G_OBJECT(treeitem), "focus_in_event",
+                                 G_CALLBACK(widget_focus), &dp);
 
                gtk_widget_show(treeitem);
 
@@ -3021,33 +3186,43 @@ int do_config_box(const char *title, Conf *conf, int midsession,
     }
 
 #if GTK_CHECK_VERSION(2,0,0)
-    {
-       GtkRequisition req;
-       int i;
+    /*
+     * We want our tree view to come up with all branches at depth 2
+     * or more collapsed. However, if we start off with those branches
+     * collapsed, then the tree view's size request will be calculated
+     * based on the width of the collapsed tree, and then when the
+     * collapsed branches are expanded later, the tree view will
+     * jarringly change size.
+     *
+     * So instead we start with everything expanded; then, once the
+     * tree view has computed its resulting width requirement, we
+     * collapse the relevant rows, but force the width to be the value
+     * we just retrieved. This arranges that the tree view is wide
+     * enough to have all branches expanded without further resizing.
+     */
 
-       /*
-        * We want our tree view to come up with all branches at
-        * depth 2 or more collapsed. However, if we start off
-        * with those branches collapsed, then the tree view's
-        * size request will be calculated based on the width of
-        * the collapsed tree. So instead we start with them all
-        * expanded; then we ask for the current size request,
-        * collapse the relevant rows, and force the width to the
-        * value we just computed. This arranges that the tree
-        * view is wide enough to have all branches expanded
-        * safely.
-        */
+    dp.nselparams = nselparams;
+    dp.selparams = selparams;
 
+#if !GTK_CHECK_VERSION(3,0,0)
+    {
+        /*
+         * In GTK2, we can just do the job right now.
+         */
+       GtkRequisition req;
        gtk_widget_size_request(tree, &req);
-
-       for (i = 0; i < nselparams; i++)
-           if (selparams[i].depth >= 2)
-               gtk_tree_view_collapse_row(GTK_TREE_VIEW(tree),
-                                          selparams[i].treepath);
-
+        initial_treeview_collapse(&dp, tree);
        gtk_widget_set_size_request(tree, req.width, -1);
     }
-#endif
+#else
+    /*
+     * But in GTK3, we have to wait until the widget is about to be
+     * mapped, because the size computation won't have been done yet.
+     */
+    g_signal_connect(G_OBJECT(tree), "map",
+                     G_CALLBACK(treeview_map_event), &dp);
+#endif /* GTK 2 vs 3 */
+#endif /* GTK 2+ vs 1 */
 
 #if GTK_CHECK_VERSION(2,0,0)
     g_signal_connect(G_OBJECT(treeselection), "changed",
@@ -3055,11 +3230,10 @@ int do_config_box(const char *title, Conf *conf, int midsession,
 #else
     dp.ntreeitems = nselparams;
     dp.treeitems = snewn(dp.ntreeitems, GtkWidget *);
-
     for (index = 0; index < nselparams; index++) {
-       gtk_signal_connect(GTK_OBJECT(selparams[index].treeitem), "select",
-                          GTK_SIGNAL_FUNC(treeitem_sel),
-                          &selparams[index]);
+        g_signal_connect(G_OBJECT(selparams[index].treeitem), "select",
+                         G_CALLBACK(treeitem_sel),
+                         &selparams[index]);
         dp.treeitems[index] = selparams[index].treeitem;
     }
 #endif
@@ -3120,12 +3294,13 @@ int do_config_box(const char *title, Conf *conf, int midsession,
             break;
     }
 
-    gtk_signal_connect(GTK_OBJECT(window), "destroy",
-                      GTK_SIGNAL_FUNC(window_destroy), NULL);
-    gtk_signal_connect(GTK_OBJECT(window), "key_press_event",
-                      GTK_SIGNAL_FUNC(win_key_press), &dp);
+    g_signal_connect(G_OBJECT(window), "destroy",
+                     G_CALLBACK(window_destroy), NULL);
+    g_signal_connect(G_OBJECT(window), "key_press_event",
+                     G_CALLBACK(win_key_press), &dp);
 
     gtk_main();
+    post_main();
 
     dlg_cleanup(&dp);
     sfree(selparams);
@@ -3139,7 +3314,7 @@ static void messagebox_handler(union control *ctrl, void *dlg,
     if (event == EVENT_ACTION)
        dlg_end(dlg, ctrl->generic.context.i);
 }
-int messagebox(GtkWidget *parentwin, char *title, char *msg,
+int messagebox(GtkWidget *parentwin, const char *title, const char *msg,
                int minwid, int selectable, ...)
 {
     GtkWidget *window, *w0, *w1;
@@ -3148,7 +3323,7 @@ int messagebox(GtkWidget *parentwin, char *title, char *msg,
     union control *c, *textctrl;
     struct dlgparam dp;
     struct Shortcuts scs;
-    int index, ncols;
+    int index, ncols, min_type;
     va_list ap;
 
     dlg_init(&dp);
@@ -3159,13 +3334,23 @@ int messagebox(GtkWidget *parentwin, char *title, char *msg,
 
     ctrlbox = ctrl_new_box();
 
+    /*
+     * Preliminary pass over the va_list, to count up the number of
+     * buttons and find out what kinds there are.
+     */
     ncols = 0;
     va_start(ap, selectable);
+    min_type = +1;
     while (va_arg(ap, char *) != NULL) {
-       ncols++;
+        int type;
+
        (void) va_arg(ap, int);        /* shortcut */
-       (void) va_arg(ap, int);        /* normal/default/cancel */
+       type = va_arg(ap, int);        /* normal/default/cancel */
        (void) va_arg(ap, int);        /* end value */
+
+       ncols++;
+        if (min_type > type)
+            min_type = type;
     }
     va_end(ap);
 
@@ -3190,7 +3375,17 @@ int messagebox(GtkWidget *parentwin, char *title, char *msg,
        c->generic.column = index++;
        if (type > 0)
            c->button.isdefault = TRUE;
-       else if (type < 0)
+
+        /* We always arrange that _some_ button is labelled as
+         * 'iscancel', so that pressing Escape will always cause
+         * win_key_press to do something. The button we choose is
+         * whichever has the smallest type value: this means that real
+         * cancel buttons (labelled -1) will be picked if one is
+         * there, or in cases where the options are yes/no (1,0) then
+         * no will be picked, and if there's only one option (a box
+         * that really is just showing a _message_ and not even asking
+         * a question) then that will be picked. */
+       if (type == min_type)
            c->button.iscancel = TRUE;
     }
     va_end(ap);
@@ -3198,16 +3393,15 @@ int messagebox(GtkWidget *parentwin, char *title, char *msg,
     s1 = ctrl_getset(ctrlbox, "x", "", "");
     textctrl = ctrl_text(s1, msg, HELPCTX(no_help));
 
-    window = gtk_dialog_new();
+    window = our_dialog_new();
     gtk_window_set_title(GTK_WINDOW(window), title);
     w0 = layout_ctrls(&dp, &scs, s0, GTK_WINDOW(window));
-    set_dialog_action_area(GTK_DIALOG(window), w0);
+    our_dialog_set_action_area(GTK_WINDOW(window), w0);
     gtk_widget_show(w0);
     w1 = layout_ctrls(&dp, &scs, s1, GTK_WINDOW(window));
     gtk_container_set_border_width(GTK_CONTAINER(w1), 10);
-    gtk_widget_set_usize(w1, minwid+20, -1);
-    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox),
-                      w1, TRUE, TRUE, 0);
+    gtk_widget_set_size_request(w1, minwid+20, -1);
+    our_dialog_add_to_content_area(GTK_WINDOW(window), w1, TRUE, TRUE, 0);
     gtk_widget_show(w1);
 
     dp.shortcuts = &scs;
@@ -3246,12 +3440,13 @@ int messagebox(GtkWidget *parentwin, char *title, char *msg,
     gtk_widget_show(window);
     gtk_window_set_focus(GTK_WINDOW(window), NULL);
 
-    gtk_signal_connect(GTK_OBJECT(window), "destroy",
-                      GTK_SIGNAL_FUNC(window_destroy), NULL);
-    gtk_signal_connect(GTK_OBJECT(window), "key_press_event",
-                      GTK_SIGNAL_FUNC(win_key_press), &dp);
+    g_signal_connect(G_OBJECT(window), "destroy",
+                     G_CALLBACK(window_destroy), NULL);
+    g_signal_connect(G_OBJECT(window), "key_press_event",
+                     G_CALLBACK(win_key_press), &dp);
 
     gtk_main();
+    post_main();
 
     dlg_cleanup(&dp);
     ctrl_free_box(ctrlbox);
@@ -3259,15 +3454,6 @@ int messagebox(GtkWidget *parentwin, char *title, char *msg,
     return dp.retval;
 }
 
-int string_width(char *text)
-{
-    GtkWidget *label = gtk_label_new(text);
-    GtkRequisition req;
-    gtk_widget_size_request(label, &req);
-    gtk_object_sink(GTK_OBJECT(label));
-    return req.width;
-}
-
 int reallyclose(void *frontend)
 {
     char *title = dupcat(appname, " Exit Confirmation", NULL);
@@ -3282,8 +3468,8 @@ int reallyclose(void *frontend)
     return ret;
 }
 
-int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
-                        char *keystr, char *fingerprint,
+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[] =
@@ -3361,7 +3547,8 @@ int askalg(void *frontend, const char *algtype, const char *algname,
     text = dupprintf(msg, algtype, algname);
     ret = messagebox(GTK_WIDGET(get_window(frontend)),
                     "PuTTY Security Alert", text,
-                    string_width("Continue with connection?"),
+                    string_width("Reasonably long line of text as a width"
+                                  " template"),
                      FALSE,
                     "Yes", 'y', 0, 1,
                     "No", 'n', 0, 0,
@@ -3382,21 +3569,21 @@ void old_keyfile_warning(void)
      */
 }
 
-void fatal_message_box(void *window, char *msg)
+void fatal_message_box(void *window, const char *msg)
 {
     messagebox(window, "PuTTY Fatal Error", msg,
                string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"),
                FALSE, "OK", 'o', 1, 1, NULL);
 }
 
-void nonfatal_message_box(void *window, char *msg)
+void nonfatal_message_box(void *window, const char *msg)
 {
     messagebox(window, "PuTTY Error", msg,
                string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"),
                FALSE, "OK", 'o', 1, 1, NULL);
 }
 
-void fatalbox(char *p, ...)
+void fatalbox(const char *p, ...)
 {
     va_list ap;
     char *msg;
@@ -3408,7 +3595,7 @@ void fatalbox(char *p, ...)
     cleanup_exit(1);
 }
 
-void nonfatal(char *p, ...)
+void nonfatal(const char *p, ...)
 {
     va_list ap;
     char *msg;
@@ -3427,6 +3614,15 @@ static void about_close_clicked(GtkButton *button, gpointer data)
     aboutbox = NULL;
 }
 
+static void about_key_press(GtkWidget *widget, GdkEventKey *event,
+                            gpointer data)
+{
+    if (event->keyval == GDK_KEY_Escape && aboutbox) {
+        gtk_widget_destroy(aboutbox);
+        aboutbox = NULL;
+    }
+}
+
 static void licence_clicked(GtkButton *button, gpointer data)
 {
     char *title;
@@ -3443,6 +3639,7 @@ static void licence_clicked(GtkButton *button, gpointer data)
 void about_box(void *window)
 {
     GtkWidget *w;
+    GtkBox *action_area;
     char *title;
 
     if (aboutbox) {
@@ -3450,27 +3647,26 @@ void about_box(void *window)
        return;
     }
 
-    aboutbox = gtk_dialog_new();
+    aboutbox = our_dialog_new();
     gtk_container_set_border_width(GTK_CONTAINER(aboutbox), 10);
     title = dupcat("About ", appname, NULL);
     gtk_window_set_title(GTK_WINDOW(aboutbox), title);
     sfree(title);
 
     w = gtk_button_new_with_label("Close");
-    GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT);
+    gtk_widget_set_can_default(w, TRUE);
     gtk_window_set_default(GTK_WINDOW(aboutbox), w);
-    gtk_box_pack_end(GTK_BOX(GTK_DIALOG(aboutbox)->action_area),
-                    w, FALSE, FALSE, 0);
-    gtk_signal_connect(GTK_OBJECT(w), "clicked",
-                      GTK_SIGNAL_FUNC(about_close_clicked), NULL);
+    action_area = our_dialog_make_action_hbox(GTK_WINDOW(aboutbox));
+    gtk_box_pack_end(action_area, w, FALSE, FALSE, 0);
+    g_signal_connect(G_OBJECT(w), "clicked",
+                     G_CALLBACK(about_close_clicked), NULL);
     gtk_widget_show(w);
 
     w = gtk_button_new_with_label("View Licence");
-    GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT);
-    gtk_box_pack_end(GTK_BOX(GTK_DIALOG(aboutbox)->action_area),
-                    w, FALSE, FALSE, 0);
-    gtk_signal_connect(GTK_OBJECT(w), "clicked",
-                      GTK_SIGNAL_FUNC(licence_clicked), NULL);
+    gtk_widget_set_can_default(w, TRUE);
+    gtk_box_pack_end(action_area, w, FALSE, FALSE, 0);
+    g_signal_connect(G_OBJECT(w), "clicked",
+                     G_CALLBACK(licence_clicked), NULL);
     gtk_widget_show(w);
 
     {
@@ -3485,8 +3681,7 @@ void about_box(void *window)
 #endif
         sfree(label_text);
     }
-    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(aboutbox)->vbox),
-                      w, FALSE, FALSE, 0);
+    our_dialog_add_to_content_area(GTK_WINDOW(aboutbox), w, FALSE, FALSE, 0);
 #if GTK_CHECK_VERSION(2,0,0)
     /*
      * Same precautions against initial select-all as in messagebox().
@@ -3496,6 +3691,9 @@ void about_box(void *window)
 #endif
     gtk_widget_show(w);
 
+    g_signal_connect(G_OBJECT(aboutbox), "key_press_event",
+                     G_CALLBACK(about_key_press), NULL);
+
     set_transient_window_pos(GTK_WIDGET(window), aboutbox);
     gtk_window_set_transient_for(GTK_WINDOW(aboutbox),
                                 GTK_WINDOW(window));
@@ -3599,7 +3797,7 @@ void eventlog_selection_get(GtkWidget *widget, GtkSelectionData *seldata,
 {
     struct eventlog_stuff *es = (struct eventlog_stuff *)data;
 
-    gtk_selection_data_set(seldata, seldata->target, 8,
+    gtk_selection_data_set(seldata, gtk_selection_data_get_target(seldata), 8,
                            (unsigned char *)es->seldata, es->sellen);
 }
 
@@ -3671,21 +3869,20 @@ void showeventlog(void *estuff, void *parentwin)
     c->listbox.percentages[1] = 10;
     c->listbox.percentages[2] = 65;
 
-    es->window = window = gtk_dialog_new();
+    es->window = window = our_dialog_new();
     title = dupcat(appname, " Event Log", NULL);
     gtk_window_set_title(GTK_WINDOW(window), title);
     sfree(title);
     w0 = layout_ctrls(&es->dp, &es->scs, s0, GTK_WINDOW(window));
-    set_dialog_action_area(GTK_DIALOG(window), w0);
+    our_dialog_set_action_area(GTK_WINDOW(window), w0);
     gtk_widget_show(w0);
     w1 = layout_ctrls(&es->dp, &es->scs, s1, GTK_WINDOW(window));
     gtk_container_set_border_width(GTK_CONTAINER(w1), 10);
-    gtk_widget_set_usize(w1, 20 +
-                        string_width("LINE OF TEXT GIVING WIDTH OF EVENT LOG"
-                                     " IS QUITE LONG 'COS SSH LOG ENTRIES"
-                                     " ARE WIDE"), -1);
-    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox),
-                      w1, TRUE, TRUE, 0);
+    gtk_widget_set_size_request(w1, 20 + string_width
+                                ("LINE OF TEXT GIVING WIDTH OF EVENT LOG IS "
+                                 "QUITE LONG 'COS SSH LOG ENTRIES ARE WIDE"),
+                                -1);
+    our_dialog_add_to_content_area(GTK_WINDOW(window), w1, TRUE, TRUE, 0);
     gtk_widget_show(w1);
 
     es->dp.data = es;
@@ -3704,14 +3901,14 @@ void showeventlog(void *estuff, void *parentwin)
        gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
     gtk_widget_show(window);
 
-    gtk_signal_connect(GTK_OBJECT(window), "destroy",
-                      GTK_SIGNAL_FUNC(eventlog_destroy), es);
-    gtk_signal_connect(GTK_OBJECT(window), "key_press_event",
-                      GTK_SIGNAL_FUNC(win_key_press), &es->dp);
-    gtk_signal_connect(GTK_OBJECT(window), "selection_get",
-                      GTK_SIGNAL_FUNC(eventlog_selection_get), es);
-    gtk_signal_connect(GTK_OBJECT(window), "selection_clear_event",
-                      GTK_SIGNAL_FUNC(eventlog_selection_clear), es);
+    g_signal_connect(G_OBJECT(window), "destroy",
+                     G_CALLBACK(eventlog_destroy), es);
+    g_signal_connect(G_OBJECT(window), "key_press_event",
+                     G_CALLBACK(win_key_press), &es->dp);
+    g_signal_connect(G_OBJECT(window), "selection_get",
+                     G_CALLBACK(eventlog_selection_get), es);
+    g_signal_connect(G_OBJECT(window), "selection_clear_event",
+                     G_CALLBACK(eventlog_selection_clear), es);
 }
 
 void *eventlogstuff_new(void)
index b152f2bbf8e9436b593841d1326074221a3a67b8..2f7e4a6172e96eb2b87cd6b92777befcd3732716 100644 (file)
 #include <assert.h>
 #include <stdlib.h>
 #include <string.h>
+
 #include <gtk/gtk.h>
+#if !GTK_CHECK_VERSION(3,0,0)
 #include <gdk/gdkkeysyms.h>
-#include <gdk/gdkx.h>
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/Xatom.h>
+#endif
+
+#define MAY_REFER_TO_GTK_IN_HEADERS
 
 #include "putty.h"
 #include "gtkfont.h"
+#include "gtkcompat.h"
+#include "gtkmisc.h"
 #include "tree234.h"
 
+#ifndef NOT_X_WINDOWS
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#endif
+
 /*
  * Future work:
  * 
  *  - it would be nice to have a display of the current font name,
  *    and in particular whether it's client- or server-side,
  *    during the progress of the font selector.
- * 
- *  - it would be nice if we could move the processing of
- *    underline and VT100 double width into this module, so that
- *    instead of using the ghastly pixmap-stretching technique
- *    everywhere we could tell the Pango backend to scale its
- *    fonts to double size properly and at full resolution.
- *    However, this requires me to learn how to make Pango stretch
- *    text to an arbitrary aspect ratio (for double-width only
- *    text, which perversely is harder than DW+DH), and right now
- *    I haven't the energy.
  */
 
 #if !GLIB_CHECK_VERSION(1,3,7)
@@ -80,9 +80,12 @@ struct unifont_vtable {
                                 int bold, int shadowoffset, int shadowalways);
     void (*destroy)(unifont *font);
     int (*has_glyph)(unifont *font, wchar_t glyph);
-    void (*draw_text)(GdkDrawable *target, GdkGC *gc, unifont *font,
-                     int x, int y, const wchar_t *string, int len, int wide,
-                     int bold, int cellwidth);
+    void (*draw_text)(unifont_drawctx *ctx, unifont *font,
+                      int x, int y, const wchar_t *string, int len,
+                      int wide, int bold, int cellwidth);
+    void (*draw_combining)(unifont_drawctx *ctx, unifont *font,
+                           int x, int y, const wchar_t *string, int len,
+                           int wide, int bold, int cellwidth);
     void (*enum_fonts)(GtkWidget *widget,
                       fontsel_add_entry callback, void *callback_ctx);
     char *(*canonify_fontname)(GtkWidget *widget, const char *name, int *size,
@@ -95,14 +98,21 @@ struct unifont_vtable {
     const char *prefix;
 };
 
+#ifndef NOT_X_WINDOWS
+
 /* ----------------------------------------------------------------------
- * X11 font implementation, directly using Xlib calls.
+ * X11 font implementation, directly using Xlib calls. Conditioned out
+ * if X11 fonts aren't available at all (e.g. building with GTK3 for a
+ * back end other than X).
  */
 
 static int x11font_has_glyph(unifont *font, wchar_t glyph);
-static void x11font_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
-                             int x, int y, const wchar_t *string, int len,
-                             int wide, int bold, int cellwidth);
+static void x11font_draw_text(unifont_drawctx *ctx, unifont *font,
+                              int x, int y, const wchar_t *string, int len,
+                              int wide, int bold, int cellwidth);
+static void x11font_draw_combining(unifont_drawctx *ctx, unifont *font,
+                                   int x, int y, const wchar_t *string,
+                                   int len, int wide, int bold, int cellwidth);
 static unifont *x11font_create(GtkWidget *widget, const char *name,
                               int wide, int bold,
                               int shadowoffset, int shadowalways);
@@ -115,20 +125,61 @@ static char *x11font_canonify_fontname(GtkWidget *widget, const char *name,
 static char *x11font_scale_fontname(GtkWidget *widget, const char *name,
                                    int size);
 
+#ifdef DRAW_TEXT_CAIRO
+struct cairo_cached_glyph {
+    cairo_surface_t *surface;
+    unsigned char *bitmap;
+};
+#endif
+
+/*
+ * Structure storing a single physical XFontStruct, plus associated
+ * data.
+ */
+typedef struct x11font_individual {
+    /* The XFontStruct itself. */
+    XFontStruct *xfs;
+
+    /*
+     * The `allocated' flag indicates whether we've tried to fetch
+     * this subfont already (thus distinguishing xfs==NULL because we
+     * haven't tried yet from xfs==NULL because we tried and failed,
+     * so that we don't keep trying and failing subsequently).
+     */
+    int allocated;
+
+#ifdef DRAW_TEXT_CAIRO
+    /*
+     * A cache of glyph bitmaps downloaded from the X server when
+     * we're in Cairo rendering mode. If glyphcache itself is
+     * non-NULL, then entries in [0,nglyphs) are expected to be
+     * initialised to either NULL or a bitmap pointer.
+     */
+    struct cairo_cached_glyph *glyphcache;
+    int nglyphs;
+
+    /*
+     * X server paraphernalia for actually downloading the glyphs.
+     */
+    Pixmap pixmap;
+    GC gc;
+    int pixwidth, pixheight, pixoriginx, pixoriginy;
+
+    /*
+     * Paraphernalia for loading the resulting bitmaps into Cairo.
+     */
+    int rowsize, allsize, indexflip;
+#endif
+
+} x11font_individual;
+
 struct x11font {
     struct unifont u;
     /*
-     * Actual font objects. We store a number of these, for
+     * Individual physical X fonts. We store a number of these, for
      * automatically guessed bold and wide variants.
-     * 
-     * The parallel array `allocated' indicates whether we've
-     * tried to fetch a subfont already (thus distinguishing NULL
-     * because we haven't tried yet from NULL because we tried and
-     * failed, so that we don't keep trying and failing
-     * subsequently).
      */
-    XFontStruct *fonts[4];
-    int allocated[4];
+    x11font_individual fonts[4];
     /*
      * `sixteen_bit' is true iff the font object is indexed by
      * values larger than a byte. That is, this flag tells us
@@ -161,6 +212,7 @@ static const struct unifont_vtable x11font_vtable = {
     x11font_destroy,
     x11font_has_glyph,
     x11font_draw_text,
+    x11font_draw_combining,
     x11font_enum_fonts,
     x11font_canonify_fontname,
     x11font_scale_fontname,
@@ -169,13 +221,13 @@ static const struct unifont_vtable x11font_vtable = {
 
 static char *x11_guess_derived_font_name(XFontStruct *xfs, int bold, int wide)
 {
-    Display *disp = GDK_DISPLAY();
+    Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
     Atom fontprop = XInternAtom(disp, "FONT", False);
     unsigned long ret;
     if (XGetFontProperty(xfs, fontprop, &ret)) {
        char *name = XGetAtomName(disp, (Atom)ret);
        if (name && name[0] == '-') {
-           char *strings[13];
+           const char *strings[13];
            char *dupname, *extrafree = NULL, *ret;
            char *p, *q;
            int nstr;
@@ -300,7 +352,7 @@ static unifont *x11font_create(GtkWidget *widget, const char *name,
 {
     struct x11font *xfont;
     XFontStruct *xfs;
-    Display *disp = GDK_DISPLAY();
+    Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
     Atom charset_registry, charset_encoding, spacing;
     unsigned long registry_ret, encoding_ret, spacing_ret;
     int pubcs, realcs, sixteen_bit, variable;
@@ -373,9 +425,14 @@ static unifont *x11font_create(GtkWidget *widget, const char *name,
     xfont->u.height = xfont->u.ascent + xfont->u.descent;
     xfont->u.public_charset = pubcs;
     xfont->u.want_fallback = TRUE;
+#ifdef DRAW_TEXT_GDK
+    xfont->u.preferred_drawtype = DRAWTYPE_GDK;
+#elif defined DRAW_TEXT_CAIRO
+    xfont->u.preferred_drawtype = DRAWTYPE_CAIRO;
+#else
+#error No drawtype available at all
+#endif
     xfont->real_charset = realcs;
-    xfont->fonts[0] = xfs;
-    xfont->allocated[0] = TRUE;
     xfont->sixteen_bit = sixteen_bit;
     xfont->variable = variable;
     xfont->wide = wide;
@@ -383,35 +440,58 @@ static unifont *x11font_create(GtkWidget *widget, const char *name,
     xfont->shadowoffset = shadowoffset;
     xfont->shadowalways = shadowalways;
 
-    for (i = 1; i < lenof(xfont->fonts); i++) {
-       xfont->fonts[i] = NULL;
-       xfont->allocated[i] = FALSE;
+    for (i = 0; i < lenof(xfont->fonts); i++) {
+       xfont->fonts[i].xfs = NULL;
+       xfont->fonts[i].allocated = FALSE;
+#ifdef DRAW_TEXT_CAIRO
+       xfont->fonts[i].glyphcache = NULL;
+       xfont->fonts[i].nglyphs = 0;
+       xfont->fonts[i].pixmap = None;
+       xfont->fonts[i].gc = None;
+#endif
     }
+    xfont->fonts[0].xfs = xfs;
+    xfont->fonts[0].allocated = TRUE;
 
     return (unifont *)xfont;
 }
 
 static void x11font_destroy(unifont *font)
 {
-    Display *disp = GDK_DISPLAY();
+    Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
     struct x11font *xfont = (struct x11font *)font;
     int i;
 
-    for (i = 0; i < lenof(xfont->fonts); i++)
-       if (xfont->fonts[i])
-           XFreeFont(disp, xfont->fonts[i]);
+    for (i = 0; i < lenof(xfont->fonts); i++) {
+       if (xfont->fonts[i].xfs)
+           XFreeFont(disp, xfont->fonts[i].xfs);
+#ifdef DRAW_TEXT_CAIRO
+       if (xfont->fonts[i].gc != None)
+           XFreeGC(disp, xfont->fonts[i].gc);
+       if (xfont->fonts[i].pixmap != None)
+           XFreePixmap(disp, xfont->fonts[i].pixmap);
+       if (xfont->fonts[i].glyphcache) {
+            int j;
+            for (j = 0; j < xfont->fonts[i].nglyphs; j++) {
+                cairo_surface_destroy(xfont->fonts[i].glyphcache[j].surface);
+                sfree(xfont->fonts[i].glyphcache[j].bitmap);
+            }
+            sfree(xfont->fonts[i].glyphcache);
+        }
+#endif
+    }
     sfree(font);
 }
 
 static void x11_alloc_subfont(struct x11font *xfont, int sfid)
 {
-    Display *disp = GDK_DISPLAY();
+    Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
     char *derived_name = x11_guess_derived_font_name
-       (xfont->fonts[0], sfid & 1, !!(sfid & 2));
-    xfont->fonts[sfid] = XLoadQueryFont(disp, derived_name);
-    xfont->allocated[sfid] = TRUE;
+       (xfont->fonts[0].xfs, sfid & 1, !!(sfid & 2));
+    xfont->fonts[sfid].xfs = XLoadQueryFont(disp, derived_name);
+    xfont->fonts[sfid].allocated = TRUE;
     sfree(derived_name);
-    /* Note that xfont->fonts[sfid] may still be NULL, if XLQF failed. */
+    /* Note that xfont->fonts[sfid].xfs may still be NULL, if XLQF failed. */
 }
 
 static int x11font_has_glyph(unifont *font, wchar_t glyph)
@@ -423,7 +503,8 @@ static int x11font_has_glyph(unifont *font, wchar_t glyph)
         * This X font has 16-bit character indices, which means
         * we can directly use our Unicode input value.
         */
-        return x11_font_has_glyph(xfont->fonts[0], glyph >> 8, glyph & 0xFF);
+        return x11_font_has_glyph(xfont->fonts[0].xfs,
+                                  glyph >> 8, glyph & 0xFF);
     } else {
         /*
          * This X font has 8-bit indices, so we must convert to the
@@ -435,65 +516,261 @@ static int x11font_has_glyph(unifont *font, wchar_t glyph)
         if (sblen == 0 || !sbstring[0])
             return FALSE;              /* not even in the charset */
 
-        return x11_font_has_glyph(xfont->fonts[0], 0,
+        return x11_font_has_glyph(xfont->fonts[0].xfs, 0,
                                   (unsigned char)sbstring[0]);
     }
 }
 
 #if !GTK_CHECK_VERSION(2,0,0)
 #define GDK_DRAWABLE_XID(d) GDK_WINDOW_XWINDOW(d) /* GTK1's name for this */
+#elif GTK_CHECK_VERSION(3,0,0)
+#define GDK_DRAWABLE_XID(d) GDK_WINDOW_XID(d) /* GTK3's name for this */
 #endif
 
-static void x11font_really_draw_text_16(GdkDrawable *target, XFontStruct *xfs,
-                                        GC gc, int x, int y,
-                                        const XChar2b *string, int nchars,
-                                        int shadowoffset,
-                                        int fontvariable, int cellwidth)
+static int x11font_width_16(unifont_drawctx *ctx, x11font_individual *xfi,
+                            const void *vstring, int start, int length)
 {
-    Display *disp = GDK_DISPLAY();
-    int step, nsteps, centre;
+    const XChar2b *string = (const XChar2b *)vstring;
+    return XTextWidth16(xfi->xfs, string+start, length);
+}
 
-    if (fontvariable) {
-       /*
-        * In a variable-pitch font, we draw one character at a
-        * time, and centre it in the character cell.
-        */
-       step = 1;
-       nsteps = nchars;
-       centre = TRUE;
-    } else {
-        /*
-         * In a fixed-pitch font, we can draw the whole lot in one go.
-         */
-        step = nchars;
-        nsteps = 1;
-        centre = FALSE;
+static int x11font_width_8(unifont_drawctx *ctx, x11font_individual *xfi,
+                           const void *vstring, int start, int length)
+{
+    const char *string = (const char *)vstring;
+    return XTextWidth(xfi->xfs, string+start, length);
+}
+
+#ifdef DRAW_TEXT_GDK
+static void x11font_gdk_setup(unifont_drawctx *ctx, x11font_individual *xfi)
+{
+    Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+    XSetFont(disp, GDK_GC_XGC(ctx->u.gdk.gc), xfi->xfs->fid);
+}
+
+static void x11font_gdk_draw_16(unifont_drawctx *ctx,
+                                x11font_individual *xfi, int x, int y,
+                                const void *vstring, int start, int length)
+{
+    Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+    const XChar2b *string = (const XChar2b *)vstring;
+    XDrawString16(disp, GDK_DRAWABLE_XID(ctx->u.gdk.target),
+                  GDK_GC_XGC(ctx->u.gdk.gc), x, y, string+start, length);
+}
+
+static void x11font_gdk_draw_8(unifont_drawctx *ctx,
+                               x11font_individual *xfi, int x, int y,
+                               const void *vstring, int start, int length)
+{
+    Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+    const char *string = (const char *)vstring;
+    XDrawString(disp, GDK_DRAWABLE_XID(ctx->u.gdk.target),
+                GDK_GC_XGC(ctx->u.gdk.gc), x, y, string+start, length);
+}
+#endif
+
+#ifdef DRAW_TEXT_CAIRO
+static void x11font_cairo_setup(unifont_drawctx *ctx, x11font_individual *xfi)
+{
+    if (xfi->pixmap == None) {
+        Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+        XGCValues gcvals;
+        GdkWindow *widgetwin = gtk_widget_get_window(ctx->u.cairo.widget);
+        int widgetscr = GDK_SCREEN_XNUMBER(gdk_window_get_screen(widgetwin));
+
+        xfi->pixwidth =
+            xfi->xfs->max_bounds.rbearing - xfi->xfs->min_bounds.lbearing;
+        xfi->pixheight =
+            xfi->xfs->max_bounds.ascent + xfi->xfs->max_bounds.descent;
+        xfi->pixoriginx = -xfi->xfs->min_bounds.lbearing;
+        xfi->pixoriginy = xfi->xfs->max_bounds.ascent;
+
+        xfi->rowsize = cairo_format_stride_for_width(CAIRO_FORMAT_A1,
+                                                     xfi->pixwidth);
+        xfi->allsize = xfi->rowsize * xfi->pixheight;
+
+        {
+            /*
+             * Test host endianness and use it to set xfi->indexflip,
+             * which is XORed into our left-shift counts in order to
+             * implement the CAIRO_FORMAT_A1 specification, in which
+             * each bitmap byte is oriented LSB-first on little-endian
+             * platforms and MSB-first on big-endian ones.
+             *
+             * This is the same technique Cairo itself uses to test
+             * endianness, so hopefully it'll work in any situation
+             * where Cairo is usable at all.
+             */
+            static const int endianness_test = 1;
+            xfi->indexflip = (*((char *) &endianness_test) == 1) ? 0 : 7;
+        }
+
+        xfi->pixmap = XCreatePixmap
+            (disp,
+             GDK_DRAWABLE_XID(gtk_widget_get_window(ctx->u.cairo.widget)),
+             xfi->pixwidth, xfi->pixheight, 1);
+        gcvals.foreground = WhitePixel(disp, widgetscr);
+        gcvals.background = BlackPixel(disp, widgetscr);
+        gcvals.font = xfi->xfs->fid;
+        xfi->gc = XCreateGC(disp, xfi->pixmap,
+                            GCForeground | GCBackground | GCFont, &gcvals);
     }
+}
 
-    while (nsteps-- > 0) {
-       int X = x;
-       if (centre)
-           X += (cellwidth - XTextWidth16(xfs, string, step)) / 2;
+static void x11font_cairo_cache_glyph(x11font_individual *xfi, int glyphindex)
+{
+    XImage *image;
+    int x, y;
+    unsigned char *bitmap;
+    Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+
+    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++) {
+            unsigned long pixel = XGetPixel(image, x, y);
+            if (pixel) {
+                int byteindex = y * xfi->rowsize + x/8;
+                int bitindex = (x & 7) ^ xfi->indexflip;
+                bitmap[byteindex] |= 1U << bitindex;
+            }
+        }
+    }
+    XDestroyImage(image);
+
+    if (xfi->nglyphs <= glyphindex) {
+        /* Round up to the next multiple of 256 on the general
+         * principle that Unicode characters come in contiguous blocks
+         * often used together */
+        int old_nglyphs = xfi->nglyphs;
+        xfi->nglyphs = (glyphindex + 0x100) & ~0xFF;
+        xfi->glyphcache = sresize(xfi->glyphcache, xfi->nglyphs,
+                                  struct cairo_cached_glyph);
+
+        while (old_nglyphs < xfi->nglyphs) {
+            xfi->glyphcache[old_nglyphs].surface = NULL;
+            xfi->glyphcache[old_nglyphs].bitmap = NULL;
+            old_nglyphs++;
+        }
+    }
+    xfi->glyphcache[glyphindex].bitmap = bitmap;
+    xfi->glyphcache[glyphindex].surface = cairo_image_surface_create_for_data
+        (bitmap, CAIRO_FORMAT_A1, xfi->pixwidth, xfi->pixheight, xfi->rowsize);
+}
 
-        XDrawString16(disp, GDK_DRAWABLE_XID(target), gc,
-                      X, y, string, step);
-       if (shadowoffset)
-            XDrawString16(disp, GDK_DRAWABLE_XID(target), gc,
-                          X + shadowoffset, y, string, step);
+static void x11font_cairo_draw_glyph(unifont_drawctx *ctx,
+                                     x11font_individual *xfi, int x, int y,
+                                     int glyphindex)
+{
+    if (xfi->glyphcache[glyphindex].surface) {
+        cairo_mask_surface(ctx->u.cairo.cr,
+                           xfi->glyphcache[glyphindex].surface,
+                           x - xfi->pixoriginx, y - xfi->pixoriginy);
+    }
+}
 
-       x += cellwidth;
-       string += step;
+static void x11font_cairo_draw_16(unifont_drawctx *ctx,
+                                  x11font_individual *xfi, int x, int y,
+                                  const void *vstring, int start, int length)
+{
+    Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+    const XChar2b *string = (const XChar2b *)vstring + start;
+    int i;
+    for (i = 0; i < length; i++) {
+        if (x11_font_has_glyph(xfi->xfs, string[i].byte1, string[i].byte2)) {
+            int glyphindex = (256 * (unsigned char)string[i].byte1 +
+                              (unsigned char)string[i].byte2);
+            if (glyphindex >= xfi->nglyphs ||
+                !xfi->glyphcache[glyphindex].surface) {
+                XDrawImageString16(disp, xfi->pixmap, xfi->gc,
+                                   xfi->pixoriginx, xfi->pixoriginy,
+                                   string+i, 1);
+                x11font_cairo_cache_glyph(xfi, glyphindex);
+            }
+            x11font_cairo_draw_glyph(ctx, xfi, x, y, glyphindex);
+            x += XTextWidth16(xfi->xfs, string+i, 1);
+        }
+    }
+}
+
+static void x11font_cairo_draw_8(unifont_drawctx *ctx,
+                                 x11font_individual *xfi, int x, int y,
+                                 const void *vstring, int start, int length)
+{
+    Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+    const char *string = (const char *)vstring + start;
+    int i;
+    for (i = 0; i < length; i++) {
+        if (x11_font_has_glyph(xfi->xfs, 0, string[i])) {
+            int glyphindex = (unsigned char)string[i];
+            if (glyphindex >= xfi->nglyphs ||
+                !xfi->glyphcache[glyphindex].surface) {
+                XDrawImageString(disp, xfi->pixmap, xfi->gc,
+                                 xfi->pixoriginx, xfi->pixoriginy,
+                                 string+i, 1);
+                x11font_cairo_cache_glyph(xfi, glyphindex);
+            }
+            x11font_cairo_draw_glyph(ctx, xfi, x, y, glyphindex);
+            x += XTextWidth(xfi->xfs, string+i, 1);
+        }
     }
 }
+#endif /* DRAW_TEXT_CAIRO */
+
+struct x11font_drawfuncs {
+    int (*width)(unifont_drawctx *ctx, x11font_individual *xfi,
+                 const void *vstring, int start, int length);
+    void (*setup)(unifont_drawctx *ctx, x11font_individual *xfi);
+    void (*draw)(unifont_drawctx *ctx, x11font_individual *xfi, int x, int y,
+                 const void *vstring, int start, int length);
+};
+
+/*
+ * This array has two entries per compiled-in drawtype; of each pair,
+ * the first is for an 8-bit font and the second for 16-bit.
+ */
+static const struct x11font_drawfuncs x11font_drawfuncs[2*DRAWTYPE_NTYPES] = {
+#ifdef DRAW_TEXT_GDK
+    /* gdk, 8-bit */
+    {
+        x11font_width_8,
+        x11font_gdk_setup,
+        x11font_gdk_draw_8,
+    },
+    /* gdk, 16-bit */
+    {
+        x11font_width_16,
+        x11font_gdk_setup,
+        x11font_gdk_draw_16,
+    },
+#endif
+#ifdef DRAW_TEXT_CAIRO
+    /* cairo, 8-bit */
+    {
+        x11font_width_8,
+        x11font_cairo_setup,
+        x11font_cairo_draw_8,
+    },
+    /* [3] cairo, 16-bit */
+    {
+        x11font_width_16,
+        x11font_cairo_setup,
+        x11font_cairo_draw_16,
+    },
+#endif
+};
 
-static void x11font_really_draw_text(GdkDrawable *target, XFontStruct *xfs,
-                                     GC gc, int x, int y,
-                                     const char *string, int nchars,
+static void x11font_really_draw_text(const struct x11font_drawfuncs *dfns,
+                                     unifont_drawctx *ctx,
+                                     x11font_individual *xfi, int x, int y,
+                                     const void *string, int nchars,
                                      int shadowoffset,
                                      int fontvariable, int cellwidth)
 {
-    Display *disp = GDK_DISPLAY();
-    int step, nsteps, centre;
+    int start = 0, step, nsteps, centre;
 
     if (fontvariable) {
        /*
@@ -512,32 +789,31 @@ static void x11font_really_draw_text(GdkDrawable *target, XFontStruct *xfs,
         centre = FALSE;
     }
 
+    dfns->setup(ctx, xfi);
+
     while (nsteps-- > 0) {
        int X = x;
        if (centre)
-           X += (cellwidth - XTextWidth(xfs, string, step)) / 2;
+           X += (cellwidth - dfns->width(ctx, xfi, string, start, step)) / 2;
 
-        XDrawString(disp, GDK_DRAWABLE_XID(target), gc,
-                    X, y, string, step);
+        dfns->draw(ctx, xfi, X, y, string, start, step);
        if (shadowoffset)
-            XDrawString(disp, GDK_DRAWABLE_XID(target), gc,
-                        X + shadowoffset, y, string, step);
+            dfns->draw(ctx, xfi, X + shadowoffset, y, string, start, step);
 
        x += cellwidth;
-       string += step;
+        start += step;
     }
 }
 
-static void x11font_draw_text(GdkDrawable *target, GdkGC *gdkgc, unifont *font,
+static void x11font_draw_text(unifont_drawctx *ctx, unifont *font,
                              int x, int y, const wchar_t *string, int len,
                              int wide, int bold, int cellwidth)
 {
-    Display *disp = GDK_DISPLAY();
     struct x11font *xfont = (struct x11font *)font;
-    GC gc = GDK_GC_XGC(gdkgc);
     int sfid;
     int shadowoffset = 0;
     int mult = (wide ? 2 : 1);
+    int index = 2 * (int)ctx->type;
 
     wide -= xfont->wide;
     bold -= xfont->bold;
@@ -551,21 +827,19 @@ static void x11font_draw_text(GdkDrawable *target, GdkGC *gdkgc, unifont *font,
        bold = 0;
     }
     sfid = 2 * wide + bold;
-    if (!xfont->allocated[sfid])
+    if (!xfont->fonts[sfid].allocated)
        x11_alloc_subfont(xfont, sfid);
-    if (bold && !xfont->fonts[sfid]) {
+    if (bold && !xfont->fonts[sfid].xfs) {
        bold = 0;
        shadowoffset = xfont->shadowoffset;
        sfid = 2 * wide + bold;
-       if (!xfont->allocated[sfid])
+       if (!xfont->fonts[sfid].allocated)
            x11_alloc_subfont(xfont, sfid);
     }
 
-    if (!xfont->fonts[sfid])
+    if (!xfont->fonts[sfid].xfs)
        return;                        /* we've tried our best, but no luck */
 
-    XSetFont(disp, gc, xfont->fonts[sfid]->fid);
-
     if (xfont->sixteen_bit) {
        /*
         * This X font has 16-bit character indices, which means
@@ -580,9 +854,10 @@ static void x11font_draw_text(GdkDrawable *target, GdkGC *gdkgc, unifont *font,
            xcs[i].byte2 = string[i];
        }
 
-       x11font_really_draw_text_16(target, xfont->fonts[sfid], gc, x, y,
-                                    xcs, len, shadowoffset,
-                                    xfont->variable, cellwidth * mult);
+       x11font_really_draw_text(x11font_drawfuncs + index + 1, ctx,
+                                 &xfont->fonts[sfid], x, y,
+                                 xcs, len, shadowoffset,
+                                 xfont->variable, cellwidth * mult);
        sfree(xcs);
     } else {
         /*
@@ -592,23 +867,39 @@ static void x11font_draw_text(GdkDrawable *target, GdkGC *gdkgc, unifont *font,
         char *sbstring = snewn(len+1, char);
         int sblen = wc_to_mb(xfont->real_charset, 0, string, len,
                              sbstring, len+1, ".", NULL, NULL);
-       x11font_really_draw_text(target, xfont->fonts[sfid], gc, x, y,
+       x11font_really_draw_text(x11font_drawfuncs + index + 0, ctx,
+                                 &xfont->fonts[sfid], x, y,
                                 sbstring, sblen, shadowoffset,
                                 xfont->variable, cellwidth * mult);
         sfree(sbstring);
     }
 }
 
+static void x11font_draw_combining(unifont_drawctx *ctx, unifont *font,
+                                   int x, int y, const wchar_t *string,
+                                   int len, int wide, int bold, int cellwidth)
+{
+    /*
+     * For server-side fonts, there's no sophisticated system for
+     * combining characters intelligently, so the best we can do is to
+     * overprint them on each other in the obvious way.
+     */
+    int i;
+    for (i = 0; i < len; i++)
+        x11font_draw_text(ctx, font, x, y, string+i, 1, wide, bold, cellwidth);
+}
+
 static void x11font_enum_fonts(GtkWidget *widget,
                               fontsel_add_entry callback, void *callback_ctx)
 {
+    Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
     char **fontnames;
     char *tmp = NULL;
     int nnames, i, max, tmpsize;
 
     max = 32768;
     while (1) {
-       fontnames = XListFonts(GDK_DISPLAY(), "*", max, &nnames);
+       fontnames = XListFonts(disp, "*", max, &nnames);
        if (nnames >= max) {
            XFreeFontNames(fontnames);
            max *= 2;
@@ -783,7 +1074,7 @@ static char *x11font_canonify_fontname(GtkWidget *widget, const char *name,
      * selector treats them as worthwhile in their own right.
      */
     XFontStruct *xfs;
-    Display *disp = GDK_DISPLAY();
+    Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
     Atom fontprop, fontprop2;
     unsigned long ret;
 
@@ -826,6 +1117,8 @@ static char *x11font_scale_fontname(GtkWidget *widget, const char *name,
     return NULL;                      /* shan't */
 }
 
+#endif /* NOT_X_WINDOWS */
+
 #if GTK_CHECK_VERSION(2,0,0)
 
 /* ----------------------------------------------------------------------
@@ -837,9 +1130,13 @@ static char *x11font_scale_fontname(GtkWidget *widget, const char *name,
 #endif
 
 static int pangofont_has_glyph(unifont *font, wchar_t glyph);
-static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
-                               int x, int y, const wchar_t *string, int len,
-                               int wide, int bold, int cellwidth);
+static void pangofont_draw_text(unifont_drawctx *ctx, unifont *font,
+                                int x, int y, const wchar_t *string, int len,
+                                int wide, int bold, int cellwidth);
+static void pangofont_draw_combining(unifont_drawctx *ctx, unifont *font,
+                                     int x, int y, const wchar_t *string,
+                                     int len, int wide, int bold,
+                                     int cellwidth);
 static unifont *pangofont_create(GtkWidget *widget, const char *name,
                                 int wide, int bold,
                                 int shadowoffset, int shadowalways);
@@ -885,6 +1182,7 @@ static const struct unifont_vtable pangofont_vtable = {
     pangofont_destroy,
     pangofont_has_glyph,
     pangofont_draw_text,
+    pangofont_draw_combining,
     pangofont_enum_fonts,
     pangofont_canonify_fontname,
     pangofont_scale_fontname,
@@ -985,6 +1283,13 @@ static unifont *pangofont_create_internal(GtkWidget *widget,
     pfont->u.descent = PANGO_PIXELS(pango_font_metrics_get_descent(metrics));
     pfont->u.height = pfont->u.ascent + pfont->u.descent;
     pfont->u.want_fallback = FALSE;
+#ifdef DRAW_TEXT_CAIRO
+    pfont->u.preferred_drawtype = DRAWTYPE_CAIRO;
+#elif defined DRAW_TEXT_GDK
+    pfont->u.preferred_drawtype = DRAWTYPE_GDK;
+#else
+#error No drawtype available at all
+#endif
     /* The Pango API is hardwired to UTF-8 */
     pfont->u.public_charset = CS_UTF8;
     pfont->desc = desc;
@@ -1059,10 +1364,10 @@ static int pangofont_char_width(PangoLayout *layout, struct pangofont *pfont,
     /*
      * Here we check whether a character has the same width as the
      * character cell it'll be drawn in. Because profiling showed that
-     * pango_layout_get_pixel_extents() was a huge bottleneck when we
-     * were calling it every time we needed to know this, we instead
-     * call it only on characters we don't already know about, and
-     * cache the results.
+     * asking Pango for text sizes was a huge bottleneck when we were
+     * calling it every time we needed to know this, we instead call
+     * it only on characters we don't already know about, and cache
+     * the results.
      */
 
     if ((unsigned)uchr >= pfont->nwidthcache) {
@@ -1075,7 +1380,7 @@ static int pangofont_char_width(PangoLayout *layout, struct pangofont *pfont,
     if (pfont->widthcache[uchr] < 0) {
         PangoRectangle rect;
         pango_layout_set_text(layout, utfchr, utflen);
-        pango_layout_get_pixel_extents(layout, NULL, &rect);
+        pango_layout_get_extents(layout, NULL, &rect);
         pfont->widthcache[uchr] = rect.width;
     }
 
@@ -1088,9 +1393,27 @@ static int pangofont_has_glyph(unifont *font, wchar_t glyph)
     return TRUE;
 }
 
-static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
-                               int x, int y, const wchar_t *string, int len,
-                               int wide, int bold, int cellwidth)
+#ifdef DRAW_TEXT_GDK
+static void pango_gdk_draw_layout(unifont_drawctx *ctx,
+                                  gint x, gint y, PangoLayout *layout)
+{
+    gdk_draw_layout(ctx->u.gdk.target, ctx->u.gdk.gc, x, y, layout);
+}
+#endif
+
+#ifdef DRAW_TEXT_CAIRO
+static void pango_cairo_draw_layout(unifont_drawctx *ctx,
+                                    gint x, gint y, PangoLayout *layout)
+{
+    cairo_move_to(ctx->u.cairo.cr, x, y);
+    pango_cairo_show_layout(ctx->u.cairo.cr, layout);
+}
+#endif
+
+static void pangofont_draw_internal(unifont_drawctx *ctx, unifont *font,
+                                    int x, int y, const wchar_t *string,
+                                    int len, int wide, int bold, int cellwidth,
+                                    int combining)
 {
     struct pangofont *pfont = (struct pangofont *)font;
     PangoLayout *layout;
@@ -1098,6 +1421,19 @@ static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
     char *utfstring, *utfptr;
     int utflen;
     int shadowbold = FALSE;
+    void (*draw_layout)(unifont_drawctx *ctx,
+                        gint x, gint y, PangoLayout *layout) = NULL;
+
+#ifdef DRAW_TEXT_GDK
+    if (ctx->type == DRAWTYPE_GDK) {
+        draw_layout = pango_gdk_draw_layout;
+    }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+    if (ctx->type == DRAWTYPE_CAIRO) {
+        draw_layout = pango_cairo_draw_layout;
+    }
+#endif
 
     if (wide)
        cellwidth *= 2;
@@ -1128,6 +1464,7 @@ static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
     utfptr = utfstring;
     while (utflen > 0) {
        int clen, n;
+        int desired = cellwidth * PANGO_SCALE;
 
        /*
         * We want to display every character from this string in
@@ -1153,56 +1490,69 @@ static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
         * them to do that.
         */
 
-       /*
-        * Start by extracting a single UTF-8 character from the
-        * string.
-        */
-       clen = 1;
-       while (clen < utflen &&
-              (unsigned char)utfptr[clen] >= 0x80 &&
-              (unsigned char)utfptr[clen] < 0xC0)
-           clen++;
-       n = 1;
-
-        if (is_rtl(string[0]) ||
-            pangofont_char_width(layout, pfont, string[n-1],
-                                 utfptr, clen) != cellwidth) {
+        if (combining) {
             /*
-             * If this character is a right-to-left one, or has an
-             * unusual width, then we must display it on its own.
+             * For a character with combining stuff, we just dump the
+             * whole lot in one go, and expect it to take up just one
+             * character cell.
              */
+            clen = utflen;
+            n = 1;
         } else {
             /*
-             * Try to amalgamate a contiguous string of characters
-             * with the expected sensible width, for the common case
-             * in which we're using a monospaced font and everything
-             * works as expected.
+             * Start by extracting a single UTF-8 character from the
+             * string.
              */
-            while (clen < utflen) {
-                int oldclen = clen;
-                clen++;                       /* skip UTF-8 introducer byte */
-                while (clen < utflen &&
-                       (unsigned char)utfptr[clen] >= 0x80 &&
-                       (unsigned char)utfptr[clen] < 0xC0)
-                    clen++;
-                n++;
-                if (pangofont_char_width(layout, pfont,
-                                         string[n-1], utfptr + oldclen,
-                                         clen - oldclen) != cellwidth) {
-                    clen = oldclen;
-                    n--;
-                    break;
+            clen = 1;
+            while (clen < utflen &&
+                   (unsigned char)utfptr[clen] >= 0x80 &&
+                   (unsigned char)utfptr[clen] < 0xC0)
+                clen++;
+            n = 1;
+
+            if (is_rtl(string[0]) ||
+                pangofont_char_width(layout, pfont, string[n-1],
+                                     utfptr, clen) != desired) {
+                /*
+                 * If this character is a right-to-left one, or has an
+                 * unusual width, then we must display it on its own.
+                 */
+            } else {
+                /*
+                 * Try to amalgamate a contiguous string of characters
+                 * with the expected sensible width, for the common case
+                 * in which we're using a monospaced font and everything
+                 * works as expected.
+                 */
+                while (clen < utflen) {
+                    int oldclen = clen;
+                    clen++;                   /* skip UTF-8 introducer byte */
+                    while (clen < utflen &&
+                           (unsigned char)utfptr[clen] >= 0x80 &&
+                           (unsigned char)utfptr[clen] < 0xC0)
+                        clen++;
+                    n++;
+                    if (pangofont_char_width(layout, pfont,
+                                             string[n-1], utfptr + oldclen,
+                                             clen - oldclen) != desired) {
+                        clen = oldclen;
+                        n--;
+                        break;
+                    }
                 }
             }
         }
 
        pango_layout_set_text(layout, utfptr, clen);
        pango_layout_get_pixel_extents(layout, NULL, &rect);
-       gdk_draw_layout(target, gc, x + (n*cellwidth - rect.width)/2,
-                       y + (pfont->u.height - rect.height)/2, layout);
+        
+       draw_layout(ctx,
+                    x + (n*cellwidth - rect.width)/2,
+                    y + (pfont->u.height - rect.height)/2, layout);
        if (shadowbold)
-           gdk_draw_layout(target, gc, x + (n*cellwidth - rect.width)/2 + pfont->shadowoffset,
-                           y + (pfont->u.height - rect.height)/2, layout);
+           draw_layout(ctx,
+                        x + (n*cellwidth - rect.width)/2 + pfont->shadowoffset,
+                        y + (pfont->u.height - rect.height)/2, layout);
 
        utflen -= clen;
        utfptr += clen;
@@ -1215,6 +1565,37 @@ static void pangofont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
     g_object_unref(layout);
 }
 
+static void pangofont_draw_text(unifont_drawctx *ctx, unifont *font,
+                                int x, int y, const wchar_t *string, int len,
+                                int wide, int bold, int cellwidth)
+{
+    pangofont_draw_internal(ctx, font, x, y, string, len, wide, bold,
+                            cellwidth, FALSE);
+}
+
+static void pangofont_draw_combining(unifont_drawctx *ctx, unifont *font,
+                                     int x, int y, const wchar_t *string,
+                                     int len, int wide, int bold,
+                                     int cellwidth)
+{
+    wchar_t *tmpstring = NULL;
+    if (mk_wcwidth(string[0]) == 0) {
+        /*
+         * If we've been told to draw a sequence of _only_ combining
+         * characters, prefix a space so that they have something to
+         * combine with.
+         */
+        tmpstring = snewn(len+1, wchar_t);
+        memcpy(tmpstring+1, string, len * sizeof(wchar_t));
+        tmpstring[0] = L' ';
+        string = tmpstring;
+        len++;
+    }
+    pangofont_draw_internal(ctx, font, x, y, string, len, wide, bold,
+                            cellwidth, TRUE);
+    sfree(tmpstring);
+}
+
 /*
  * Dummy size value to be used when converting a
  * PangoFontDescription of a scalable font to a string for
@@ -1481,7 +1862,9 @@ static const struct unifont_vtable *unifont_types[] = {
 #if GTK_CHECK_VERSION(2,0,0)
     &pangofont_vtable,
 #endif
+#ifndef NOT_X_WINDOWS
     &x11font_vtable,
+#endif
 };
 
 /*
@@ -1551,12 +1934,19 @@ void unifont_destroy(unifont *font)
     font->vt->destroy(font);
 }
 
-void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
+void unifont_draw_text(unifont_drawctx *ctx, unifont *font,
                       int x, int y, const wchar_t *string, int len,
                       int wide, int bold, int cellwidth)
 {
-    font->vt->draw_text(target, gc, font, x, y, string, len,
-                       wide, bold, cellwidth);
+    font->vt->draw_text(ctx, font, x, y, string, len, wide, bold, cellwidth);
+}
+
+void unifont_draw_combining(unifont_drawctx *ctx, unifont *font,
+                            int x, int y, const wchar_t *string, int len,
+                            int wide, int bold, int cellwidth)
+{
+    font->vt->draw_combining(ctx, font, x, y, string, len, wide, bold,
+                             cellwidth);
 }
 
 /* ----------------------------------------------------------------------
@@ -1572,9 +1962,13 @@ void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
  * destroy.
  */
 
-static void multifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
-                               int x, int y, const wchar_t *string, int len,
-                               int wide, int bold, int cellwidth);
+static void multifont_draw_text(unifont_drawctx *ctx, unifont *font,
+                                int x, int y, const wchar_t *string, int len,
+                                int wide, int bold, int cellwidth);
+static void multifont_draw_combining(unifont_drawctx *ctx, unifont *font,
+                                     int x, int y, const wchar_t *string,
+                                     int len, int wide, int bold,
+                                     int cellwidth);
 static void multifont_destroy(unifont *font);
 
 struct multifont {
@@ -1589,6 +1983,7 @@ static const struct unifont_vtable multifont_vtable = {
     multifont_destroy,
     NULL,
     multifont_draw_text,
+    multifont_draw_combining,
     NULL,
     NULL,
     NULL,
@@ -1633,6 +2028,7 @@ unifont *multifont_create(GtkWidget *widget, const char *name,
     mfont->u.height = font->height;
     mfont->u.public_charset = font->public_charset;
     mfont->u.want_fallback = FALSE; /* shouldn't be needed, but just in case */
+    mfont->u.preferred_drawtype = font->preferred_drawtype;
     mfont->main = font;
     mfont->fallback = fallback;
 
@@ -1648,11 +2044,18 @@ static void multifont_destroy(unifont *font)
     sfree(font);
 }
 
-static void multifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
-                               int x, int y, const wchar_t *string, int len,
-                               int wide, int bold, int cellwidth)
+typedef void (*unifont_draw_func_t)(unifont_drawctx *ctx, unifont *font,
+                                    int x, int y, const wchar_t *string,
+                                    int len, int wide, int bold,
+                                    int cellwidth);
+
+static void multifont_draw_main(unifont_drawctx *ctx, unifont *font, int x,
+                                int y, const wchar_t *string, int len,
+                                int wide, int bold, int cellwidth,
+                                int cellinc, unifont_draw_func_t draw)
 {
     struct multifont *mfont = (struct multifont *)font;
+    unifont *f;
     int ok, i;
 
     while (len > 0) {
@@ -1669,14 +2072,32 @@ static void multifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
         /*
          * Now display it.
          */
-        unifont_draw_text(target, gc, ok ? mfont->main : mfont->fallback,
-                          x, y, string, i, wide, bold, cellwidth);
+        f = ok ? mfont->main : mfont->fallback;
+        if (f)
+            draw(ctx, f, x, y, string, i, wide, bold, cellwidth);
         string += i;
         len -= i;
-        x += i * cellwidth;
+        x += i * cellinc;
     }
 }
 
+static void multifont_draw_text(unifont_drawctx *ctx, unifont *font, int x,
+                                int y, const wchar_t *string, int len,
+                                int wide, int bold, int cellwidth)
+{
+    multifont_draw_main(ctx, font, x, y, string, len, wide, bold,
+                        cellwidth, cellwidth, unifont_draw_text);
+}
+
+static void multifont_draw_combining(unifont_drawctx *ctx, unifont *font,
+                                     int x, int y, const wchar_t *string,
+                                     int len, int wide, int bold,
+                                     int cellwidth)
+{
+    multifont_draw_main(ctx, font, x, y, string, len, wide, bold,
+                        cellwidth, 0, unifont_draw_combining);
+}
+
 #if GTK_CHECK_VERSION(2,0,0)
 
 /* ----------------------------------------------------------------------
@@ -1692,8 +2113,11 @@ typedef struct unifontsel_internal {
     GtkListStore *family_model, *style_model, *size_model;
     GtkWidget *family_list, *style_list, *size_entry, *size_list;
     GtkWidget *filter_buttons[4];
+    int n_filter_buttons;
     GtkWidget *preview_area;
+#ifndef NO_BACKING_PIXMAPS
     GdkPixmap *preview_pixmap;
+#endif
     int preview_width, preview_height;
     GdkColor preview_fg, preview_bg;
     int filter_flags;
@@ -1812,6 +2236,8 @@ static int fontinfo_selorder_compare(void *av, void *bv)
     return 0;
 }
 
+static void unifontsel_draw_preview_text(unifontsel_internal *fs);
+
 static void unifontsel_deselect(unifontsel_internal *fs)
 {
     fs->selected = NULL;
@@ -1819,6 +2245,7 @@ static void unifontsel_deselect(unifontsel_internal *fs)
     gtk_list_store_clear(fs->size_model);
     gtk_widget_set_sensitive(fs->u.ok_button, FALSE);
     gtk_widget_set_sensitive(fs->size_entry, FALSE);
+    unifontsel_draw_preview_text(fs);
 }
 
 static void unifontsel_setup_familylist(unifontsel_internal *fs)
@@ -1829,6 +2256,8 @@ static void unifontsel_setup_familylist(unifontsel_internal *fs)
     int currflags = -1;
     fontinfo *info;
 
+    fs->inhibit_response = TRUE;
+
     gtk_list_store_clear(fs->family_model);
     listindex = 0;
 
@@ -1879,6 +2308,8 @@ static void unifontsel_setup_familylist(unifontsel_internal *fs)
      */
     if (fs->selected && fs->selected->familyindex < 0)
        unifontsel_deselect(fs);
+
+    fs->inhibit_response = FALSE;
 }
 
 static void unifontsel_setup_stylelist(unifontsel_internal *fs,
@@ -1924,7 +2355,7 @@ static void unifontsel_setup_stylelist(unifontsel_internal *fs,
                gtk_list_store_append(fs->style_model, &iter);
                gtk_list_store_set(fs->style_model, &iter,
                                   0, currstyle, 1, minpos, 2, maxpos+1,
-                                  3, TRUE, -1);
+                                  3, TRUE, 4, PANGO_WEIGHT_NORMAL, -1);
                listindex++;
            }
            if (info) {
@@ -1933,7 +2364,7 @@ static void unifontsel_setup_stylelist(unifontsel_internal *fs,
                    gtk_list_store_append(fs->style_model, &iter);
                    gtk_list_store_set(fs->style_model, &iter,
                                       0, info->charset, 1, -1, 2, -1,
-                                      3, FALSE, -1);
+                                      3, FALSE, 4, PANGO_WEIGHT_BOLD, -1);
                    listindex++;
                }
                currcs = info->charset;
@@ -1999,16 +2430,17 @@ static void unifontsel_set_filter_buttons(unifontsel_internal *fs)
 {
     int i;
 
-    for (i = 0; i < lenof(fs->filter_buttons); i++) {
-       int flagbit = GPOINTER_TO_INT(gtk_object_get_data
-                                     (GTK_OBJECT(fs->filter_buttons[i]),
+    for (i = 0; i < fs->n_filter_buttons; i++) {
+        int flagbit = GPOINTER_TO_INT(g_object_get_data
+                                      (G_OBJECT(fs->filter_buttons[i]),
                                       "user-data"));
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fs->filter_buttons[i]),
                                     !!(fs->filter_flags & flagbit));
     }
 }
 
-static void unifontsel_draw_preview_text(unifontsel_internal *fs)
+static void unifontsel_draw_preview_text_inner(unifont_drawctx *dctx,
+                                               unifontsel_internal *fs)
 {
     unifont *font;
     char *sizename = NULL;
@@ -2023,74 +2455,132 @@ static void unifontsel_draw_preview_text(unifontsel_internal *fs)
     } else
        font = NULL;
 
-    if (fs->preview_pixmap) {
-       GdkGC *gc = gdk_gc_new(fs->preview_pixmap);
-       gdk_gc_set_foreground(gc, &fs->preview_bg);
-       gdk_draw_rectangle(fs->preview_pixmap, gc, 1, 0, 0,
-                          fs->preview_width, fs->preview_height);
-       gdk_gc_set_foreground(gc, &fs->preview_fg);
-       if (font) {
-           /*
-            * The pangram used here is rather carefully
-            * constructed: it contains a sequence of very narrow
-            * letters (`jil') and a pair of adjacent very wide
-            * letters (`wm').
-            *
-            * If the user selects a proportional font, it will be
-            * coerced into fixed-width character cells when used
-            * in the actual terminal window. We therefore display
-            * it the same way in the preview pane, so as to show
-            * it the way it will actually be displayed - and we
-            * deliberately pick a pangram which will show the
-            * resulting miskerning at its worst.
-            *
-            * We aren't trying to sell people these fonts; we're
-            * trying to let them make an informed choice. Better
-            * that they find out the problems with using
-            * proportional fonts in terminal windows here than
-            * that they go to the effort of selecting their font
-            * and _then_ realise it was a mistake.
-            */
-           info->fontclass->draw_text(fs->preview_pixmap, gc, font,
-                                      0, font->ascent,
-                                      L"bankrupt jilted showmen quiz convex fogey",
-                                      41, FALSE, FALSE, font->width);
-           info->fontclass->draw_text(fs->preview_pixmap, gc, font,
-                                      0, font->ascent + font->height,
-                                      L"BANKRUPT JILTED SHOWMEN QUIZ CONVEX FOGEY",
-                                      41, FALSE, FALSE, font->width);
-           /*
-            * The ordering of punctuation here is also selected
-            * with some specific aims in mind. I put ` and '
-            * together because some software (and people) still
-            * use them as matched quotes no matter what Unicode
-            * might say on the matter, so people can quickly
-            * check whether they look silly in a candidate font.
-            * The sequence #_@ is there to let people judge the
-            * suitability of the underscore as an effectively
-            * alphabetic character (since that's how it's often
-            * used in practice, at least by programmers).
-            */
-           info->fontclass->draw_text(fs->preview_pixmap, gc, font,
-                                      0, font->ascent + font->height * 2,
-                                      L"0123456789!?,.:;<>()[]{}\\/`'\"+*-=~#_@|%&^$",
-                                      42, FALSE, FALSE, font->width);
-       }
-       gdk_gc_unref(gc);
-       gdk_window_invalidate_rect(fs->preview_area->window, NULL, FALSE);
+#ifdef DRAW_TEXT_GDK
+    if (dctx->type == DRAWTYPE_GDK) {
+        gdk_gc_set_foreground(dctx->u.gdk.gc, &fs->preview_bg);
+        gdk_draw_rectangle(dctx->u.gdk.target, dctx->u.gdk.gc, 1, 0, 0,
+                           fs->preview_width, fs->preview_height);
+        gdk_gc_set_foreground(dctx->u.gdk.gc, &fs->preview_fg);
     }
-    if (font)
+#endif
+#ifdef DRAW_TEXT_CAIRO
+    if (dctx->type == DRAWTYPE_CAIRO) {
+        cairo_set_source_rgb(dctx->u.cairo.cr,
+                             fs->preview_bg.red / 65535.0,
+                             fs->preview_bg.green / 65535.0,
+                             fs->preview_bg.blue / 65535.0);
+        cairo_paint(dctx->u.cairo.cr);
+        cairo_set_source_rgb(dctx->u.cairo.cr,
+                             fs->preview_fg.red / 65535.0,
+                             fs->preview_fg.green / 65535.0,
+                             fs->preview_fg.blue / 65535.0);
+    }
+#endif
+
+    if (font) {
+        /*
+         * The pangram used here is rather carefully
+         * constructed: it contains a sequence of very narrow
+         * letters (`jil') and a pair of adjacent very wide
+         * letters (`wm').
+         *
+         * If the user selects a proportional font, it will be
+         * coerced into fixed-width character cells when used
+         * in the actual terminal window. We therefore display
+         * it the same way in the preview pane, so as to show
+         * it the way it will actually be displayed - and we
+         * deliberately pick a pangram which will show the
+         * resulting miskerning at its worst.
+         *
+         * We aren't trying to sell people these fonts; we're
+         * trying to let them make an informed choice. Better
+         * that they find out the problems with using
+         * proportional fonts in terminal windows here than
+         * that they go to the effort of selecting their font
+         * and _then_ realise it was a mistake.
+         */
+        info->fontclass->draw_text(dctx, font,
+                                   0, font->ascent,
+                                   L"bankrupt jilted showmen quiz convex fogey",
+                                   41, FALSE, FALSE, font->width);
+        info->fontclass->draw_text(dctx, font,
+                                   0, font->ascent + font->height,
+                                   L"BANKRUPT JILTED SHOWMEN QUIZ CONVEX FOGEY",
+                                   41, FALSE, FALSE, font->width);
+        /*
+         * The ordering of punctuation here is also selected
+         * with some specific aims in mind. I put ` and '
+         * together because some software (and people) still
+         * use them as matched quotes no matter what Unicode
+         * might say on the matter, so people can quickly
+         * check whether they look silly in a candidate font.
+         * The sequence #_@ is there to let people judge the
+         * suitability of the underscore as an effectively
+         * alphabetic character (since that's how it's often
+         * used in practice, at least by programmers).
+         */
+        info->fontclass->draw_text(dctx, font,
+                                   0, font->ascent + font->height * 2,
+                                   L"0123456789!?,.:;<>()[]{}\\/`'\"+*-=~#_@|%&^$",
+                                   42, FALSE, FALSE, font->width);
+
        info->fontclass->destroy(font);
+    }
 
     sfree(sizename);
 }
 
+static void unifontsel_draw_preview_text(unifontsel_internal *fs)
+{
+    unifont_drawctx dctx;
+    GdkWindow *target;
+
+#ifndef NO_BACKING_PIXMAPS
+    target = fs->preview_pixmap;
+#else
+    target = gtk_widget_get_window(fs->preview_area);
+#endif
+    if (!target) /* we may be called when we haven't created everything yet */
+        return;
+
+    dctx.type = DRAWTYPE_DEFAULT;
+#ifdef DRAW_TEXT_GDK
+    if (dctx.type == DRAWTYPE_GDK) {
+        dctx.u.gdk.target = target;
+        dctx.u.gdk.gc = gdk_gc_new(target);
+    }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+    if (dctx.type == DRAWTYPE_CAIRO) {
+        dctx.u.cairo.widget = GTK_WIDGET(fs->preview_area);
+        dctx.u.cairo.cr = gdk_cairo_create(target);
+    }
+#endif
+
+    unifontsel_draw_preview_text_inner(&dctx, fs);
+
+#ifdef DRAW_TEXT_GDK
+    if (dctx.type == DRAWTYPE_GDK) {
+        gdk_gc_unref(dctx.u.gdk.gc);
+    }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+    if (dctx.type == DRAWTYPE_CAIRO) {
+        cairo_destroy(dctx.u.cairo.cr);
+    }
+#endif
+
+    gdk_window_invalidate_rect(gtk_widget_get_window(fs->preview_area),
+                               NULL, FALSE);
+}
+
 static void unifontsel_select_font(unifontsel_internal *fs,
                                   fontinfo *info, int size, int leftlist,
                                   int size_is_explicit)
 {
     int index;
     int minval, maxval;
+    gboolean success;
     GtkTreePath *treepath;
     GtkTreeIter iter;
 
@@ -2131,7 +2621,9 @@ static void unifontsel_select_font(unifontsel_internal *fs,
         treepath);
     gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->family_list),
                                 treepath, NULL, FALSE, 0.0, 0.0);
-    gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), &iter, treepath);
+    success = gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model),
+                                      &iter, treepath);
+    assert(success);
     gtk_tree_path_free(treepath);
 
     /*
@@ -2210,7 +2702,8 @@ static void unifontsel_select_font(unifontsel_internal *fs,
      * Grey out the font size edit box if we're not using a
      * scalable font.
      */
-    gtk_entry_set_editable(GTK_ENTRY(fs->size_entry), fs->selected->size == 0);
+    gtk_editable_set_editable(GTK_EDITABLE(fs->size_entry),
+                              fs->selected->size == 0);
     gtk_widget_set_sensitive(fs->size_entry, fs->selected->size == 0);
 
     unifontsel_draw_preview_text(fs);
@@ -2223,8 +2716,8 @@ static void unifontsel_button_toggled(GtkToggleButton *tb, gpointer data)
     unifontsel_internal *fs = (unifontsel_internal *)data;
     int newstate = gtk_toggle_button_get_active(tb);
     int newflags;
-    int flagbit = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(tb),
-                                                     "user-data"));
+    int flagbit = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(tb),
+                                                    "user-data"));
 
     if (newstate)
        newflags = fs->filter_flags | flagbit;
@@ -2504,25 +2997,47 @@ static void alias_resolve(GtkTreeView *treeview, GtkTreePath *path,
     }
 }
 
+#if GTK_CHECK_VERSION(3,0,0)
+static gint unifontsel_draw_area(GtkWidget *widget, cairo_t *cr, gpointer data)
+{
+    unifontsel_internal *fs = (unifontsel_internal *)data;
+    unifont_drawctx dctx;
+
+    dctx.type = DRAWTYPE_CAIRO;
+    dctx.u.cairo.widget = widget;
+    dctx.u.cairo.cr = cr;
+    unifontsel_draw_preview_text_inner(&dctx, fs);
+
+    return TRUE;
+}
+#else
 static gint unifontsel_expose_area(GtkWidget *widget, GdkEventExpose *event,
                                   gpointer data)
 {
     unifontsel_internal *fs = (unifontsel_internal *)data;
 
+#ifndef NO_BACKING_PIXMAPS
     if (fs->preview_pixmap) {
-       gdk_draw_pixmap(widget->window,
-                       widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
+        gdk_draw_pixmap(gtk_widget_get_window(widget),
+                       (gtk_widget_get_style(widget)->fg_gc
+                         [gtk_widget_get_state(widget)]),
                        fs->preview_pixmap,
                        event->area.x, event->area.y,
                        event->area.x, event->area.y,
                        event->area.width, event->area.height);
     }
+#else
+    unifontsel_draw_preview_text(fs);
+#endif
+
     return TRUE;
 }
+#endif
 
 static gint unifontsel_configure_area(GtkWidget *widget,
                                      GdkEventConfigure *event, gpointer data)
 {
+#ifndef NO_BACKING_PIXMAPS
     unifontsel_internal *fs = (unifontsel_internal *)data;
     int ox, oy, nx, ny, x, y;
 
@@ -2539,14 +3054,16 @@ static gint unifontsel_configure_area(GtkWidget *widget,
        
        nx = (x > ox ? x : ox);
        ny = (y > oy ? y : oy);
-       fs->preview_pixmap = gdk_pixmap_new(widget->window, nx, ny, -1);
+       fs->preview_pixmap = gdk_pixmap_new(gtk_widget_get_window(widget),
+                                            nx, ny, -1);
        fs->preview_width = nx;
        fs->preview_height = ny;
 
        unifontsel_draw_preview_text(fs);
     }
+#endif
 
-    gdk_window_invalidate_rect(widget->window, NULL, FALSE);
+    gdk_window_invalidate_rect(gtk_widget_get_window(widget), NULL, FALSE);
 
     return TRUE;
 }
@@ -2564,27 +3081,22 @@ unifontsel *unifontsel_new(const char *wintitle)
     fs->selected = NULL;
 
     {
+        int width, height;
+
        /*
         * Invent some magic size constants.
         */
-       GtkRequisition req;
-       label = gtk_label_new("Quite Long Font Name (Foundry)");
-       gtk_widget_size_request(label, &req);
-       font_width = req.width;
-       lists_height = 14 * req.height;
-       preview_height = 5 * req.height;
-       gtk_label_set_text(GTK_LABEL(label), "Italic Extra Condensed");
-       gtk_widget_size_request(label, &req);
-       style_width = req.width;
-       gtk_label_set_text(GTK_LABEL(label), "48000");
-       gtk_widget_size_request(label, &req);
-       size_width = req.width;
-#if GTK_CHECK_VERSION(2,10,0)
-       g_object_ref_sink(label);
-       g_object_unref(label);
-#else
-        gtk_object_sink(GTK_OBJECT(label));
-#endif
+       get_label_text_dimensions("Quite Long Font Name (Foundry)",
+                                  &width, &height);
+       font_width = width;
+       lists_height = 14 * height;
+       preview_height = 5 * height;
+
+       get_label_text_dimensions("Italic Extra Condensed", &width, &height);
+       style_width = width;
+
+       get_label_text_dimensions("48000", &width, &height);
+       size_width = width;
     }
 
     /*
@@ -2595,34 +3107,53 @@ unifontsel *unifontsel_new(const char *wintitle)
     fs->u.window = GTK_WINDOW(gtk_dialog_new());
     gtk_window_set_title(fs->u.window, wintitle);
     fs->u.cancel_button = gtk_dialog_add_button
-       (GTK_DIALOG(fs->u.window), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+       (GTK_DIALOG(fs->u.window), STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL);
     fs->u.ok_button = gtk_dialog_add_button
-       (GTK_DIALOG(fs->u.window), GTK_STOCK_OK, GTK_RESPONSE_OK);
+       (GTK_DIALOG(fs->u.window), STANDARD_OK_LABEL, GTK_RESPONSE_OK);
     gtk_widget_grab_default(fs->u.ok_button);
 
     /*
      * Now set up the internal fields, including in particular all
      * the controls that actually allow the user to select fonts.
      */
+#if GTK_CHECK_VERSION(3,0,0)
+    table = gtk_grid_new();
+    gtk_grid_set_column_spacing(GTK_GRID(table), 8);
+#else
     table = gtk_table_new(8, 3, FALSE);
-    gtk_widget_show(table);
     gtk_table_set_col_spacings(GTK_TABLE(table), 8);
-#if GTK_CHECK_VERSION(2,4,0)
+#endif
+    gtk_widget_show(table);
+
+#if GTK_CHECK_VERSION(3,0,0)
+    /* GtkAlignment has become deprecated and we use the "margin"
+     * property */
+    w = table;
+    g_object_set(G_OBJECT(w), "margin", 8, (const char *)NULL);
+#elif GTK_CHECK_VERSION(2,4,0)
     /* GtkAlignment seems to be the simplest way to put padding round things */
     w = gtk_alignment_new(0, 0, 1, 1);
     gtk_alignment_set_padding(GTK_ALIGNMENT(w), 8, 8, 8, 8);
     gtk_container_add(GTK_CONTAINER(w), table);
     gtk_widget_show(w);
 #else
+    /* In GTK < 2.4, even that isn't available */
     w = table;
 #endif
-    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fs->u.window)->vbox),
+
+    gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area
+                               (GTK_DIALOG(fs->u.window))),
                       w, TRUE, TRUE, 0);
 
     label = gtk_label_new_with_mnemonic("_Font:");
     gtk_widget_show(label);
-    gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
+    align_label_left(GTK_LABEL(label));
+#if GTK_CHECK_VERSION(3,0,0)
+    gtk_grid_attach(GTK_GRID(table), label, 0, 0, 1, 1);
+    g_object_set(G_OBJECT(label), "hexpand", TRUE, (const char *)NULL);
+#else
     gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+#endif
 
     /*
      * The Font list box displays only a string, but additionally
@@ -2652,30 +3183,42 @@ unifontsel *unifontsel_new(const char *wintitle)
     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
     gtk_widget_set_size_request(scroll, font_width, lists_height);
+#if GTK_CHECK_VERSION(3,0,0)
+    gtk_grid_attach(GTK_GRID(table), scroll, 0, 1, 1, 2);
+    g_object_set(G_OBJECT(scroll), "expand", TRUE, (const char *)NULL);
+#else
     gtk_table_attach(GTK_TABLE(table), scroll, 0, 1, 1, 3, GTK_FILL,
                     GTK_EXPAND | GTK_FILL, 0, 0);
+#endif
     fs->family_model = model;
     fs->family_list = w;
 
     label = gtk_label_new_with_mnemonic("_Style:");
     gtk_widget_show(label);
-    gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
+    align_label_left(GTK_LABEL(label));
+#if GTK_CHECK_VERSION(3,0,0)
+    gtk_grid_attach(GTK_GRID(table), label, 1, 0, 1, 1);
+    g_object_set(G_OBJECT(label), "hexpand", TRUE, (const char *)NULL);
+#else
     gtk_table_attach(GTK_TABLE(table), label, 1, 2, 0, 1, GTK_FILL, 0, 0, 0);
+#endif
 
     /*
-     * The Style list box can contain insensitive elements
-     * (character set headings for server-side fonts), so we add
-     * an extra column to the list store to hold that information.
+     * The Style list box can contain insensitive elements (character
+     * set headings for server-side fonts), so we add an extra column
+     * to the list store to hold that information. Also, since GTK3 at
+     * least doesn't seem to display insensitive elements differently
+     * by default, we add a further column to change their style.
      */
-    model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT,
-                              G_TYPE_BOOLEAN);
+    model = gtk_list_store_new(5, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT,
+                              G_TYPE_BOOLEAN, G_TYPE_INT);
     w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), FALSE);
     gtk_label_set_mnemonic_widget(GTK_LABEL(label), w);
     gtk_widget_show(w);
     column = gtk_tree_view_column_new_with_attributes
        ("Style", gtk_cell_renderer_text_new(),
-        "text", 0, "sensitive", 3, (char *)NULL);
+        "text", 0, "sensitive", 3, "weight", 4, (char *)NULL);
     gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
     gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
     g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))),
@@ -2689,15 +3232,25 @@ unifontsel *unifontsel_new(const char *wintitle)
     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
     gtk_widget_set_size_request(scroll, style_width, lists_height);
+#if GTK_CHECK_VERSION(3,0,0)
+    gtk_grid_attach(GTK_GRID(table), scroll, 1, 1, 1, 2);
+    g_object_set(G_OBJECT(scroll), "expand", TRUE, (const char *)NULL);
+#else
     gtk_table_attach(GTK_TABLE(table), scroll, 1, 2, 1, 3, GTK_FILL,
                     GTK_EXPAND | GTK_FILL, 0, 0);
+#endif
     fs->style_model = model;
     fs->style_list = w;
 
     label = gtk_label_new_with_mnemonic("Si_ze:");
     gtk_widget_show(label);
-    gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
+    align_label_left(GTK_LABEL(label));
+#if GTK_CHECK_VERSION(3,0,0)
+    gtk_grid_attach(GTK_GRID(table), label, 2, 0, 1, 1);
+    g_object_set(G_OBJECT(label), "hexpand", TRUE, (const char *)NULL);
+#else
     gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0);
+#endif
 
     /*
      * The Size label attaches primarily to a text input box so
@@ -2708,7 +3261,12 @@ unifontsel *unifontsel_new(const char *wintitle)
     gtk_label_set_mnemonic_widget(GTK_LABEL(label), w);
     gtk_widget_set_size_request(w, size_width, -1);
     gtk_widget_show(w);
+#if GTK_CHECK_VERSION(3,0,0)
+    gtk_grid_attach(GTK_GRID(table), w, 2, 1, 1, 1);
+    g_object_set(G_OBJECT(w), "hexpand", TRUE, (const char *)NULL);
+#else
     gtk_table_attach(GTK_TABLE(table), w, 2, 3, 1, 2, GTK_FILL, 0, 0, 0);
+#endif
     g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(size_entry_changed),
                     fs);
 
@@ -2731,8 +3289,13 @@ unifontsel *unifontsel_new(const char *wintitle)
     gtk_widget_show(scroll);
     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
+#if GTK_CHECK_VERSION(3,0,0)
+    gtk_grid_attach(GTK_GRID(table), scroll, 2, 2, 1, 1);
+    g_object_set(G_OBJECT(scroll), "expand", TRUE, (const char *)NULL);
+#else
     gtk_table_attach(GTK_TABLE(table), scroll, 2, 3, 2, 3, GTK_FILL,
                     GTK_EXPAND | GTK_FILL, 0, 0);
+#endif
     fs->size_model = model;
     fs->size_list = w;
 
@@ -2740,27 +3303,41 @@ unifontsel *unifontsel_new(const char *wintitle)
      * Preview widget.
      */
     fs->preview_area = gtk_drawing_area_new();
+#ifndef NO_BACKING_PIXMAPS
     fs->preview_pixmap = NULL;
+#endif
     fs->preview_width = 0;
     fs->preview_height = 0;
     fs->preview_fg.pixel = fs->preview_bg.pixel = 0;
     fs->preview_fg.red = fs->preview_fg.green = fs->preview_fg.blue = 0x0000;
     fs->preview_bg.red = fs->preview_bg.green = fs->preview_bg.blue = 0xFFFF;
+#if !GTK_CHECK_VERSION(3,0,0)
     gdk_colormap_alloc_color(gdk_colormap_get_system(), &fs->preview_fg,
                             FALSE, FALSE);
     gdk_colormap_alloc_color(gdk_colormap_get_system(), &fs->preview_bg,
                             FALSE, FALSE);
-    gtk_signal_connect(GTK_OBJECT(fs->preview_area), "expose_event",
-                      GTK_SIGNAL_FUNC(unifontsel_expose_area), fs);
-    gtk_signal_connect(GTK_OBJECT(fs->preview_area), "configure_event",
-                      GTK_SIGNAL_FUNC(unifontsel_configure_area), fs);
+#endif
+#if GTK_CHECK_VERSION(3,0,0)
+    g_signal_connect(G_OBJECT(fs->preview_area), "draw",
+                     G_CALLBACK(unifontsel_draw_area), fs);
+#else
+    g_signal_connect(G_OBJECT(fs->preview_area), "expose_event",
+                     G_CALLBACK(unifontsel_expose_area), fs);
+#endif
+    g_signal_connect(G_OBJECT(fs->preview_area), "configure_event",
+                     G_CALLBACK(unifontsel_configure_area), fs);
     gtk_widget_set_size_request(fs->preview_area, 1, preview_height);
     gtk_widget_show(fs->preview_area);
     ww = fs->preview_area;
     w = gtk_frame_new(NULL);
     gtk_container_add(GTK_CONTAINER(w), ww);
     gtk_widget_show(w);
-#if GTK_CHECK_VERSION(2,4,0)
+
+#if GTK_CHECK_VERSION(3,0,0)
+    /* GtkAlignment has become deprecated and we use the "margin"
+     * property */
+    g_object_set(G_OBJECT(w), "margin", 8, (const char *)NULL);
+#elif GTK_CHECK_VERSION(2,4,0)
     ww = w;
     /* GtkAlignment seems to be the simplest way to put padding round things */
     w = gtk_alignment_new(0, 0, 1, 1);
@@ -2768,48 +3345,82 @@ unifontsel *unifontsel_new(const char *wintitle)
     gtk_container_add(GTK_CONTAINER(w), ww);
     gtk_widget_show(w);
 #endif
+
     ww = w;
     w = gtk_frame_new("Preview of font");
     gtk_container_add(GTK_CONTAINER(w), ww);
     gtk_widget_show(w);
+#if GTK_CHECK_VERSION(3,0,0)
+    gtk_grid_attach(GTK_GRID(table), w, 0, 3, 3, 1);
+    g_object_set(G_OBJECT(w), "expand", TRUE, (const char *)NULL);
+#else
     gtk_table_attach(GTK_TABLE(table), w, 0, 3, 3, 4,
                     GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 8);
+#endif
 
-    i = 0;
+    /*
+     * We only provide the checkboxes for client- and server-side
+     * fonts if we have the X11 back end available, because that's the
+     * only situation in which more than one class of font is
+     * available anyway.
+     */
+    fs->n_filter_buttons = 0;
+#ifndef NOT_X_WINDOWS
     w = gtk_check_button_new_with_label("Show client-side fonts");
-    gtk_object_set_data(GTK_OBJECT(w), "user-data",
-                       GINT_TO_POINTER(FONTFLAG_CLIENTSIDE));
-    gtk_signal_connect(GTK_OBJECT(w), "toggled",
-                      GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs);
+    g_object_set_data(G_OBJECT(w), "user-data",
+                      GINT_TO_POINTER(FONTFLAG_CLIENTSIDE));
+    g_signal_connect(G_OBJECT(w), "toggled",
+                     G_CALLBACK(unifontsel_button_toggled), fs);
     gtk_widget_show(w);
-    fs->filter_buttons[i++] = w;
+    fs->filter_buttons[fs->n_filter_buttons++] = w;
+#if GTK_CHECK_VERSION(3,0,0)
+    gtk_grid_attach(GTK_GRID(table), w, 0, 4, 3, 1);
+    g_object_set(G_OBJECT(w), "hexpand", TRUE, (const char *)NULL);
+#else
     gtk_table_attach(GTK_TABLE(table), w, 0, 3, 4, 5, GTK_FILL, 0, 0, 0);
+#endif
     w = gtk_check_button_new_with_label("Show server-side fonts");
-    gtk_object_set_data(GTK_OBJECT(w), "user-data",
-                       GINT_TO_POINTER(FONTFLAG_SERVERSIDE));
-    gtk_signal_connect(GTK_OBJECT(w), "toggled",
-                      GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs);
+    g_object_set_data(G_OBJECT(w), "user-data",
+                      GINT_TO_POINTER(FONTFLAG_SERVERSIDE));
+    g_signal_connect(G_OBJECT(w), "toggled",
+                     G_CALLBACK(unifontsel_button_toggled), fs);
     gtk_widget_show(w);
-    fs->filter_buttons[i++] = w;
+    fs->filter_buttons[fs->n_filter_buttons++] = w;
+#if GTK_CHECK_VERSION(3,0,0)
+    gtk_grid_attach(GTK_GRID(table), w, 0, 5, 3, 1);
+    g_object_set(G_OBJECT(w), "hexpand", TRUE, (const char *)NULL);
+#else
     gtk_table_attach(GTK_TABLE(table), w, 0, 3, 5, 6, GTK_FILL, 0, 0, 0);
+#endif
     w = gtk_check_button_new_with_label("Show server-side font aliases");
-    gtk_object_set_data(GTK_OBJECT(w), "user-data",
-                       GINT_TO_POINTER(FONTFLAG_SERVERALIAS));
-    gtk_signal_connect(GTK_OBJECT(w), "toggled",
-                      GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs);
+    g_object_set_data(G_OBJECT(w), "user-data",
+                      GINT_TO_POINTER(FONTFLAG_SERVERALIAS));
+    g_signal_connect(G_OBJECT(w), "toggled",
+                     G_CALLBACK(unifontsel_button_toggled), fs);
     gtk_widget_show(w);
-    fs->filter_buttons[i++] = w;
+    fs->filter_buttons[fs->n_filter_buttons++] = w;
+#if GTK_CHECK_VERSION(3,0,0)
+    gtk_grid_attach(GTK_GRID(table), w, 0, 6, 3, 1);
+    g_object_set(G_OBJECT(w), "hexpand", TRUE, (const char *)NULL);
+#else
     gtk_table_attach(GTK_TABLE(table), w, 0, 3, 6, 7, GTK_FILL, 0, 0, 0);
+#endif
+#endif /* NOT_X_WINDOWS */
     w = gtk_check_button_new_with_label("Show non-monospaced fonts");
-    gtk_object_set_data(GTK_OBJECT(w), "user-data",
-                       GINT_TO_POINTER(FONTFLAG_NONMONOSPACED));
-    gtk_signal_connect(GTK_OBJECT(w), "toggled",
-                      GTK_SIGNAL_FUNC(unifontsel_button_toggled), fs);
+    g_object_set_data(G_OBJECT(w), "user-data",
+                      GINT_TO_POINTER(FONTFLAG_NONMONOSPACED));
+    g_signal_connect(G_OBJECT(w), "toggled",
+                     G_CALLBACK(unifontsel_button_toggled), fs);
     gtk_widget_show(w);
-    fs->filter_buttons[i++] = w;
+    fs->filter_buttons[fs->n_filter_buttons++] = w;
+#if GTK_CHECK_VERSION(3,0,0)
+    gtk_grid_attach(GTK_GRID(table), w, 0, 7, 3, 1);
+    g_object_set(G_OBJECT(w), "hexpand", TRUE, (const char *)NULL);
+#else
     gtk_table_attach(GTK_TABLE(table), w, 0, 3, 7, 8, GTK_FILL, 0, 0, 0);
+#endif
 
-    assert(i == lenof(fs->filter_buttons));
+    assert(fs->n_filter_buttons <= lenof(fs->filter_buttons));
     fs->filter_flags = FONTFLAG_CLIENTSIDE | FONTFLAG_SERVERSIDE |
        FONTFLAG_SERVERALIAS;
     unifontsel_set_filter_buttons(fs);
@@ -2840,8 +3451,10 @@ void unifontsel_destroy(unifontsel *fontsel)
     unifontsel_internal *fs = (unifontsel_internal *)fontsel;
     fontinfo *info;
 
+#ifndef NO_BACKING_PIXMAPS
     if (fs->preview_pixmap)
        gdk_pixmap_unref(fs->preview_pixmap);
+#endif
 
     freetree234(fs->fonts_by_selorder);
     while ((info = delpos234(fs->fonts_by_realname, 0)) != NULL)
@@ -2863,7 +3476,7 @@ void unifontsel_set_name(unifontsel *fontsel, const char *fontname)
      * Provide a default if given an empty or null font name.
      */
     if (!fontname || !*fontname)
-       fontname = "server:fixed";
+       fontname = DEFAULT_GTK_FONT;
 
     /*
      * Call the canonify_fontname function.
index 1ed202bfc134c91d1af829f88ddefff704f31829..6aa8ce2eb302d662fe4936aafbd77694dd401f82 100644 (file)
@@ -7,6 +7,45 @@
 #ifndef PUTTY_GTKFONT_H
 #define PUTTY_GTKFONT_H
 
+/*
+ * We support two entirely different drawing systems: the old
+ * GDK1/GDK2 one which works on server-side X drawables, and the
+ * new-style Cairo one. GTK1 only supports GDK drawing; GTK3 only
+ * supports Cairo; GTK2 supports both, but deprecates GTK, so we only
+ * enable it if we aren't trying on purpose to compile without the
+ * deprecated functions.
+ *
+ * Our different font classes may prefer different drawing systems: X
+ * server-side fonts are a lot faster to draw with GDK, but for
+ * everything else we prefer Cairo, on general grounds of modernness
+ * and also in particular because its matrix-based scaling system
+ * gives much nicer results for double-width and double-height text
+ * when a scalable font is in use.
+ */
+#if !GTK_CHECK_VERSION(3,0,0) && !defined GDK_DISABLE_DEPRECATED
+#define DRAW_TEXT_GDK
+#endif
+#if GTK_CHECK_VERSION(2,8,0)
+#define DRAW_TEXT_CAIRO
+#endif
+
+#if GTK_CHECK_VERSION(3,0,0) || defined GDK_DISABLE_DEPRECATED
+/*
+ * Where the facility is available, we prefer to render text on to a
+ * persistent server-side pixmap, and redraw windows by simply
+ * blitting rectangles of that pixmap into them as needed. This is
+ * better for performance since we avoid expensive font rendering
+ * calls where possible, and it's particularly good over a non-local X
+ * connection because the response to an expose event can now be a
+ * very simple rectangle-copy operation rather than a lot of fiddly
+ * drawing or bitmap transfer.
+ *
+ * However, GTK is deprecating the use of server-side pixmaps, so we
+ * have to disable this mode under some circumstances.
+ */
+#define NO_BACKING_PIXMAPS
+#endif
+
 /*
  * Exports from gtkfont.c.
  */
@@ -36,15 +75,73 @@ typedef struct unifont {
      * fallback font to cope with missing glyphs.
      */
     int want_fallback;
+
+    /*
+     * Preferred drawing API to use when this class of font is active.
+     * (See the enum below, in unifont_drawctx.)
+     */
+    int preferred_drawtype;
 } unifont;
 
+/* A default drawtype, for the case where no font exists to make the
+ * decision with. */
+#ifdef DRAW_TEXT_CAIRO
+#define DRAW_DEFAULT_CAIRO
+#define DRAWTYPE_DEFAULT DRAWTYPE_CAIRO
+#elif defined DRAW_TEXT_GDK
+#define DRAW_DEFAULT_GDK
+#define DRAWTYPE_DEFAULT DRAWTYPE_GDK
+#else
+#error No drawtype available at all
+#endif
+
+/*
+ * Drawing context passed in to unifont_draw_text, which contains
+ * everything required to know where and how to draw the requested
+ * text.
+ */
+typedef struct unifont_drawctx {
+    enum {
+#ifdef DRAW_TEXT_GDK
+        DRAWTYPE_GDK,
+#endif
+#ifdef DRAW_TEXT_CAIRO
+        DRAWTYPE_CAIRO,
+#endif
+        DRAWTYPE_NTYPES
+    } type;
+    union {
+#ifdef DRAW_TEXT_GDK
+        struct {
+            GdkDrawable *target;
+            GdkGC *gc;
+        } gdk;
+#endif
+#ifdef DRAW_TEXT_CAIRO
+        struct {
+            /* Need an actual widget, in order to backtrack to its X
+             * screen number when creating server-side pixmaps */
+            GtkWidget *widget;
+            cairo_t *cr;
+            cairo_matrix_t origmatrix;
+        } cairo;
+#endif
+    } u;
+} unifont_drawctx;
+
 unifont *unifont_create(GtkWidget *widget, const char *name,
                        int wide, int bold,
                        int shadowoffset, int shadowalways);
 void unifont_destroy(unifont *font);
-void unifont_draw_text(GdkDrawable *target, GdkGC *gc, unifont *font,
-                      int x, int y, const wchar_t *string, int len,
-                      int wide, int bold, int cellwidth);
+void unifont_draw_text(unifont_drawctx *ctx, unifont *font,
+                       int x, int y, const wchar_t *string, int len,
+                       int wide, int bold, int cellwidth);
+/* Same as unifont_draw_text, but expects 'string' to contain one
+ * normal char plus combining chars, and overdraws them all in the
+ * same character cell. */
+void unifont_draw_combining(unifont_drawctx *ctx, unifont *font,
+                            int x, int y, const wchar_t *string, int len,
+                            int wide, int bold, int cellwidth);
 
 /*
  * This function behaves exactly like the low-level unifont_create,
diff --git a/unix/gtkmisc.c b/unix/gtkmisc.c
new file mode 100644 (file)
index 0000000..d3619e2
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ * Miscellaneous GTK helper functions.
+ */
+
+#include <assert.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <time.h>
+
+#include <gtk/gtk.h>
+#if !GTK_CHECK_VERSION(3,0,0)
+#include <gdk/gdkkeysyms.h>
+#endif
+
+#include "putty.h"
+#include "gtkcompat.h"
+
+void get_label_text_dimensions(const char *text, int *width, int *height)
+{
+    /*
+     * Determine the dimensions of a piece of text in the standard
+     * font used in GTK interface elements like labels. We do this by
+     * instantiating an actual GtkLabel, and then querying its size.
+     *
+     * But GTK2 and GTK3 require us to query the size completely
+     * differently. I'm sure there ought to be an easier approach than
+     * the way I'm doing this in GTK3, too!
+     */
+    GtkWidget *label = gtk_label_new(text);
+
+#if GTK_CHECK_VERSION(3,0,0)
+    PangoLayout *layout = gtk_label_get_layout(GTK_LABEL(label));
+    PangoRectangle logrect;
+    pango_layout_get_extents(layout, NULL, &logrect);
+    if (width)
+        *width = logrect.width / PANGO_SCALE;
+    if (height)
+        *height = logrect.height / PANGO_SCALE;
+#else
+    GtkRequisition req;
+    gtk_widget_size_request(label, &req);
+    if (width)
+        *width = req.width;
+    if (height)
+        *height = req.height;
+#endif
+
+    g_object_ref_sink(G_OBJECT(label));
+#if GTK_CHECK_VERSION(2,10,0)
+    g_object_unref(label);
+#endif
+}
+
+int string_width(const char *text)
+{
+    int ret;
+    get_label_text_dimensions(text, &ret, NULL);
+    return ret;
+}
+
+void align_label_left(GtkLabel *label)
+{
+#if GTK_CHECK_VERSION(3,16,0)
+    gtk_label_set_xalign(label, 0.0);
+#elif GTK_CHECK_VERSION(3,14,0)
+    gtk_widget_set_halign(GTK_WIDGET(label), GTK_ALIGN_START);
+#else
+    gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
+#endif
+}
+
+/* ----------------------------------------------------------------------
+ * Functions to arrange controls in a basically dialog-like window.
+ *
+ * The best method for doing this has varied wildly with versions of
+ * GTK, hence the set of wrapper functions here.
+ *
+ * In GTK 1, a GtkDialog has an 'action_area' at the bottom, which is
+ * a GtkHBox which stretches to cover the full width of the dialog. So
+ * we can either add buttons or other widgets to that box directly, or
+ * alternatively we can fill the hbox with some layout class of our
+ * own such as a Columns widget.
+ *
+ * In GTK 2, the action area has become a GtkHButtonBox, and its
+ * layout behaviour seems to be different and not what we want. So
+ * instead we abandon the dialog's action area completely: we
+ * gtk_widget_hide() it in the below code, and we also call
+ * gtk_dialog_set_has_separator() to remove the separator above it. We
+ * then insert our own action area into the end of the dialog's main
+ * vbox, and add our own separator above that.
+ *
+ * In GTK 3, we typically don't even want to use GtkDialog at all,
+ * because GTK 3 has become a lot more restrictive about what you can
+ * sensibly use GtkDialog for - it deprecates direct access to the
+ * action area in favour of making you provide nothing but
+ * dialog-ending buttons in the form of (text, response code) pairs,
+ * so you can't put any other kind of control in there, or fiddle with
+ * alignment and positioning, or even have a button that _doesn't_ end
+ * the dialog (e.g. 'View Licence' in our About box). So instead of
+ * GtkDialog, we use a straight-up GtkWindow and have it contain a
+ * vbox as its (unique) child widget; and we implement the action area
+ * by adding a separator and another widget at the bottom of that
+ * vbox.
+ */
+
+GtkWidget *our_dialog_new(void)
+{
+#if GTK_CHECK_VERSION(3,0,0)
+    /*
+     * See comment in our_dialog_set_action_area(): in GTK 3, we use
+     * GtkWindow in place of GtkDialog for most purposes.
+     */
+    GtkWidget *w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
+    gtk_container_add(GTK_CONTAINER(w), vbox);
+    gtk_widget_show(vbox);
+    return w;
+#else
+    return gtk_dialog_new();
+#endif
+}
+
+void our_dialog_set_action_area(GtkWindow *dlg, GtkWidget *w)
+{
+#if !GTK_CHECK_VERSION(2,0,0)
+
+    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area),
+                       w, TRUE, TRUE, 0);
+
+#elif !GTK_CHECK_VERSION(3,0,0)
+
+    GtkWidget *align;
+    align = gtk_alignment_new(0, 0, 1, 1);
+    gtk_container_add(GTK_CONTAINER(align), w);
+    /*
+     * The purpose of this GtkAlignment is to provide padding
+     * around the buttons. The padding we use is twice the padding
+     * used in our GtkColumns, because we nest two GtkColumns most
+     * of the time (one separating the tree view from the main
+     * controls, and another for the main controls themselves).
+     */
+#if GTK_CHECK_VERSION(2,4,0)
+    gtk_alignment_set_padding(GTK_ALIGNMENT(align), 8, 8, 8, 8);
+#endif
+    gtk_widget_show(align);
+    gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))),
+                     align, FALSE, TRUE, 0);
+
+    w = gtk_hseparator_new();
+    gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))),
+                     w, FALSE, TRUE, 0);
+    gtk_widget_show(w);
+    gtk_widget_hide(gtk_dialog_get_action_area(GTK_DIALOG(dlg)));
+    g_object_set(G_OBJECT(dlg), "has-separator", TRUE, (const char *)NULL);
+
+#else /* GTK 3 */
+
+    /* GtkWindow is a GtkBin, hence contains exactly one child, which
+     * here we always expect to be a vbox */
+    GtkBox *vbox = GTK_BOX(gtk_bin_get_child(GTK_BIN(dlg)));
+    GtkWidget *sep;
+
+    g_object_set(G_OBJECT(w), "margin", 8, (const char *)NULL);
+    gtk_box_pack_end(vbox, w, FALSE, TRUE, 0);
+
+    sep = gtk_hseparator_new();
+    gtk_box_pack_end(vbox, sep, FALSE, TRUE, 0);
+    gtk_widget_show(sep);
+
+#endif
+}
+
+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);
+    gtk_widget_show(hbox);
+    return GTK_BOX(hbox);
+#else /* not GTK 3 */
+    return GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(dlg)));
+#endif
+}
+
+void our_dialog_add_to_content_area(GtkWindow *dlg, GtkWidget *w,
+                                    gboolean expand, gboolean fill,
+                                    guint padding)
+{
+#if GTK_CHECK_VERSION(3,0,0)
+    /* GtkWindow is a GtkBin, hence contains exactly one child, which
+     * here we always expect to be a vbox */
+    GtkBox *vbox = GTK_BOX(gtk_bin_get_child(GTK_BIN(dlg)));
+
+    gtk_box_pack_start(vbox, w, expand, fill, padding);
+#else
+    gtk_box_pack_start
+        (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))),
+         w, expand, fill, padding);
+#endif
+}
diff --git a/unix/gtkmisc.h b/unix/gtkmisc.h
new file mode 100644 (file)
index 0000000..e670d9d
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Miscellaneous helper functions for GTK.
+ */
+
+#ifndef PUTTY_GTKMISC_H
+#define PUTTY_GTKMISC_H
+
+int string_width(const char *text);
+void get_label_text_dimensions(const char *text, int *width, int *height);
+
+void align_label_left(GtkLabel *label);
+
+GtkWidget *our_dialog_new(void);
+void our_dialog_add_to_content_area(GtkWindow *dlg, GtkWidget *w,
+                                    gboolean expand, gboolean fill,
+                                    guint padding);
+void our_dialog_set_action_area(GtkWindow *dlg, GtkWidget *w);
+GtkBox *our_dialog_make_action_hbox(GtkWindow *dlg);
+
+#endif /* PUTTY_GTKMISC_H */
index 3d3789f8ea49c2fa189781ba026faa061b7962e3..192e20e170ac8e1667d04cd603ea8de5f51683b3 100644 (file)
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <gtk/gtk.h>
+#if !GTK_CHECK_VERSION(3,0,0)
 #include <gdk/gdkkeysyms.h>
-#include <gdk/gdkx.h>
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/Xatom.h>
+#endif
 
 #if GTK_CHECK_VERSION(2,0,0)
 #include <gtk/gtkimmodule.h>
 
 #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)
@@ -67,6 +74,8 @@ extern int use_pty_argv;
  */
 static guint timer_id = 0;
 
+struct clipboard_data_instance;
+
 struct gui_data {
     GtkWidget *window, *area, *sbar;
     GtkBox *hbox;
@@ -74,20 +83,58 @@ struct gui_data {
     GtkWidget *menu, *specialsmenu, *specialsitem1, *specialsitem2,
        *restartitem;
     GtkWidget *sessionsmenu;
+#ifndef NO_BACKING_PIXMAPS
+    /*
+     * Server-side pixmap which we use to cache the terminal window's
+     * contents. When we draw text in the terminal, we draw it to this
+     * pixmap first, and then blit from there to the actual window;
+     * this way, X expose events can be handled with an absolute
+     * minimum of network traffic, by just sending a command to
+     * re-blit an appropriate rectangle from this pixmap.
+     */
     GdkPixmap *pixmap;
+#endif
+#ifdef DRAW_TEXT_CAIRO
+    /*
+     * If we're drawing using Cairo, we cache the same image on the
+     * client side in a Cairo surface.
+     *
+     * In GTK2+Cairo, this happens _as well_ as having the server-side
+     * pixmap cache above; in GTK3+Cairo, server-side pixmaps are
+     * deprecated, so we _just_ have this client-side cache. In the
+     * latter case that means we have to transmit a big wodge of
+     * bitmap data over the X connection on every expose event; but
+     * GTK3 apparently deliberately provides no way to avoid that
+     * inefficiency, and at least this way we don't _also_ have to
+     * redo any font rendering just because the window was temporarily
+     * covered.
+     */
+    cairo_surface_t *surface;
+#endif
 #if GTK_CHECK_VERSION(2,0,0)
     GtkIMContext *imc;
 #endif
     unifont *fonts[4];                 /* normal, bold, wide, widebold */
+#if GTK_CHECK_VERSION(2,0,0)
+    const char *geometry;
+#else
     int xpos, ypos, gotpos, gravity;
+#endif
     GdkCursor *rawcursor, *textcursor, *blankcursor, *waitcursor, *currcursor;
     GdkColor cols[NALLCOLOURS];
+#if !GTK_CHECK_VERSION(3,0,0)
     GdkColormap *colmap;
-    wchar_t *pastein_data;
+#endif
     int direct_to_font;
+    wchar_t *pastein_data;
     int pastein_data_len;
+#ifdef JUST_USE_GTK_CLIPBOARD_UTF8
+    GtkClipboard *clipboard;
+    struct clipboard_data_instance *current_cdi;
+#else
     char *pasteout_data, *pasteout_data_ctext, *pasteout_data_utf8;
     int pasteout_data_len, pasteout_data_ctext_len, pasteout_data_utf8_len;
+#endif
     int font_width, font_height;
     int width, height;
     int ignore_sbar;
@@ -113,10 +160,15 @@ struct gui_data {
     int ngtkargs;
     guint32 input_event_time; /* Timestamp of the most recent input event. */
     int reconfiguring;
+#if GTK_CHECK_VERSION(3,4,0)
+    gdouble cumulative_scroll;
+#endif
     /* Cached things out of conf that we refer to a lot */
     int bold_style;
     int window_border;
     int cursor_type;
+    int drawtype;
+    int meta_mod_mask;
 };
 
 static void cache_conf_values(struct gui_data *inst)
@@ -124,26 +176,40 @@ static void cache_conf_values(struct gui_data *inst)
     inst->bold_style = conf_get_int(inst->conf, CONF_bold_style);
     inst->window_border = conf_get_int(inst->conf, CONF_window_border);
     inst->cursor_type = conf_get_int(inst->conf, CONF_cursor_type);
+#ifdef OSX_META_KEY_CONFIG
+    inst->meta_mod_mask = 0;
+    if (conf_get_int(inst->conf, CONF_osx_option_meta))
+        inst->meta_mod_mask |= GDK_MOD1_MASK;
+    if (conf_get_int(inst->conf, CONF_osx_command_meta))
+        inst->meta_mod_mask |= GDK_MOD2_MASK;
+#else
+    inst->meta_mod_mask = GDK_MOD1_MASK;
+#endif
 }
 
 struct draw_ctx {
-    GdkGC *gc;
     struct gui_data *inst;
+    unifont_drawctx uctx;
 };
 
 static int send_raw_mouse;
 
-static char *app_name = "pterm";
+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)
 {
-    return XGetDefault(GDK_DISPLAY(), app_name, 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, char *p, ...)
+void connection_fatal(void *frontend, const char *p, ...)
 {
     struct gui_data *inst = (struct gui_data *)frontend;
 
@@ -164,7 +230,7 @@ void connection_fatal(void *frontend, char *p, ...)
 FontSpec *platform_default_fontspec(const char *name)
 {
     if (!strcmp(name, "Font"))
-       return fontspec_new("server:fixed");
+       return fontspec_new(DEFAULT_GTK_FONT);
     else
         return fontspec_new("");
 }
@@ -194,7 +260,7 @@ int platform_default_i(const char *name, int def)
 }
 
 /* Dummy routine, only required in plink. */
-void ldisc_update(void *frontend, int echo, int edit)
+void frontend_echoedit_update(void *frontend, int echo, int edit)
 {
 }
 
@@ -221,7 +287,7 @@ int from_backend_eof(void *frontend)
     return TRUE;   /* do respond to incoming EOF with outgoing */
 }
 
-int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+int get_userpass_input(prompts_t *p, const unsigned char *in, int inlen)
 {
     struct gui_data *inst = (struct gui_data *)p->frontend;
     int ret;
@@ -314,7 +380,7 @@ void move_window(void *frontend, int x, int y)
 #if GTK_CHECK_VERSION(2,0,0)
     gtk_window_move(GTK_WINDOW(inst->window), x, y);
 #else
-    gdk_window_move(inst->window->window, x, y);
+    gdk_window_move(gtk_widget_get_window(inst->window), x, y);
 #endif
 }
 
@@ -326,9 +392,9 @@ void set_zorder(void *frontend, int top)
 {
     struct gui_data *inst = (struct gui_data *)frontend;
     if (top)
-       gdk_window_raise(inst->window->window);
+       gdk_window_raise(gtk_widget_get_window(inst->window));
     else
-       gdk_window_lower(inst->window->window);
+       gdk_window_lower(gtk_widget_get_window(inst->window));
 }
 
 /*
@@ -364,7 +430,7 @@ void set_zoomed(void *frontend, int zoomed)
 int is_iconic(void *frontend)
 {
     struct gui_data *inst = (struct gui_data *)frontend;
-    return !gdk_window_is_viewable(inst->window->window);
+    return !gdk_window_is_viewable(gtk_widget_get_window(inst->window));
 }
 
 /*
@@ -381,7 +447,7 @@ void get_window_pos(void *frontend, int *x, int *y)
 #if GTK_CHECK_VERSION(2,0,0)
     gtk_window_get_position(GTK_WINDOW(inst->window), x, y);
 #else
-    gdk_window_get_position(inst->window->window, x, y);
+    gdk_window_get_position(gtk_widget_get_window(inst->window), x, y);
 #endif
 }
 
@@ -399,7 +465,7 @@ void get_window_pixels(void *frontend, int *x, int *y)
 #if GTK_CHECK_VERSION(2,0,0)
     gtk_window_get_size(GTK_WINDOW(inst->window), x, y);
 #else
-    gdk_window_get_size(inst->window->window, x, y);
+    gdk_window_get_size(gtk_widget_get_window(inst->window), x, y);
 #endif
 }
 
@@ -427,17 +493,21 @@ static void update_mouseptr(struct gui_data *inst)
     switch (inst->busy_status) {
       case BUSY_NOT:
        if (!inst->mouseptr_visible) {
-           gdk_window_set_cursor(inst->area->window, inst->blankcursor);
+           gdk_window_set_cursor(gtk_widget_get_window(inst->area),
+                                  inst->blankcursor);
        } else if (send_raw_mouse) {
-           gdk_window_set_cursor(inst->area->window, inst->rawcursor);
+           gdk_window_set_cursor(gtk_widget_get_window(inst->area),
+                                  inst->rawcursor);
        } else {
-           gdk_window_set_cursor(inst->area->window, inst->textcursor);
+           gdk_window_set_cursor(gtk_widget_get_window(inst->area),
+                                  inst->textcursor);
        }
        break;
       case BUSY_WAITING:    /* XXX can we do better? */
       case BUSY_CPU:
        /* We always display these cursors. */
-       gdk_window_set_cursor(inst->area->window, inst->waitcursor);
+       gdk_window_set_cursor(gtk_widget_get_window(inst->area),
+                              inst->waitcursor);
        break;
       default:
        assert(0);
@@ -452,15 +522,7 @@ static void show_mouseptr(struct gui_data *inst, int show)
     update_mouseptr(inst);
 }
 
-void draw_backing_rect(struct gui_data *inst)
-{
-    GdkGC *gc = gdk_gc_new(inst->area->window);
-    gdk_gc_set_foreground(gc, &inst->cols[258]);    /* default background */
-    gdk_draw_rectangle(inst->pixmap, gc, 1, 0, 0,
-                      inst->width * inst->font_width + 2*inst->window_border,
-                      inst->height * inst->font_height + 2*inst->window_border);
-    gdk_gc_unref(gc);
-}
+static void draw_backing_rect(struct gui_data *inst);
 
 gint configure_area(GtkWidget *widget, GdkEventConfigure *event, gpointer data)
 {
@@ -481,14 +543,30 @@ gint configure_area(GtkWidget *widget, GdkEventConfigure *event, gpointer data)
        need_size = 1;
     }
 
-    if (inst->pixmap) {
-       gdk_pixmap_unref(inst->pixmap);
-       inst->pixmap = NULL;
-    }
+    {
+        int backing_w = w * inst->font_width + 2*inst->window_border;
+        int backing_h = h * inst->font_height + 2*inst->window_border;
 
-    inst->pixmap = gdk_pixmap_new(widget->window,
-                                 (w * inst->font_width + 2*inst->window_border),
-                                 (h * inst->font_height + 2*inst->window_border), -1);
+#ifndef NO_BACKING_PIXMAPS
+        if (inst->pixmap) {
+            gdk_pixmap_unref(inst->pixmap);
+            inst->pixmap = NULL;
+        }
+
+        inst->pixmap = gdk_pixmap_new(gtk_widget_get_window(widget),
+                                      backing_w, backing_h, -1);
+#endif
+
+#ifdef DRAW_TEXT_CAIRO
+        if (inst->surface) {
+            cairo_surface_destroy(inst->surface);
+            inst->surface = NULL;
+        }
+
+        inst->surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
+                                                   backing_w, backing_h);
+#endif
+    }
 
     draw_backing_rect(inst);
 
@@ -500,34 +578,105 @@ gint configure_area(GtkWidget *widget, GdkEventConfigure *event, gpointer data)
        term_invalidate(inst->term);
 
 #if GTK_CHECK_VERSION(2,0,0)
-    gtk_im_context_set_client_window(inst->imc, widget->window);
+    gtk_im_context_set_client_window(inst->imc, gtk_widget_get_window(widget));
 #endif
 
     return TRUE;
 }
 
+#ifdef DRAW_TEXT_CAIRO
+static void cairo_setup_dctx(struct draw_ctx *dctx)
+{
+    cairo_get_matrix(dctx->uctx.u.cairo.cr,
+                     &dctx->uctx.u.cairo.origmatrix);
+    cairo_set_line_width(dctx->uctx.u.cairo.cr, 1.0);
+    cairo_set_line_cap(dctx->uctx.u.cairo.cr, CAIRO_LINE_CAP_SQUARE);
+    cairo_set_line_join(dctx->uctx.u.cairo.cr, CAIRO_LINE_JOIN_MITER);
+    /* This antialiasing setting appears to be ignored for Pango
+     * font rendering but honoured for stroking and filling paths;
+     * I don't quite understand the logic of that, but I won't
+     * complain since it's exactly what I happen to want */
+    cairo_set_antialias(dctx->uctx.u.cairo.cr, CAIRO_ANTIALIAS_NONE);
+}
+#endif
+
+#if GTK_CHECK_VERSION(3,0,0)
+static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data)
+{
+    struct gui_data *inst = (struct gui_data *)data;
+
+    /*
+     * GTK3 window redraw: we always expect Cairo to be enabled, so
+     * that inst->surface exists, and pixmaps to be disabled, so that
+     * inst->pixmap does not exist. Hence, we just blit from
+     * inst->surface to the window.
+     */
+    if (inst->surface) {
+        GdkRectangle dirtyrect;
+
+        gdk_cairo_get_clip_rectangle(cr, &dirtyrect);
+
+        cairo_set_source_surface(cr, inst->surface, 0, 0);
+        cairo_rectangle(cr, dirtyrect.x, dirtyrect.y,
+                        dirtyrect.width, dirtyrect.height);
+        cairo_fill(cr);
+    }
+
+    return TRUE;
+}
+#else
 gint expose_area(GtkWidget *widget, GdkEventExpose *event, gpointer data)
 {
     struct gui_data *inst = (struct gui_data *)data;
 
+#ifndef NO_BACKING_PIXMAPS
     /*
-     * Pass the exposed rectangle to terminal.c, which will call us
-     * back to do the actual painting.
+     * Draw to the exposed part of the window from the server-side
+     * backing pixmap.
      */
     if (inst->pixmap) {
-       gdk_draw_pixmap(widget->window,
-                       widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
+       gdk_draw_pixmap(gtk_widget_get_window(widget),
+                       (gtk_widget_get_style(widget)->fg_gc
+                         [gtk_widget_get_state(widget)]),
                        inst->pixmap,
                        event->area.x, event->area.y,
                        event->area.x, event->area.y,
                        event->area.width, event->area.height);
     }
+#else
+    /*
+     * Failing that, draw from the client-side Cairo surface. (We
+     * should never be compiled in a context where we have _neither_
+     * inst->surface nor inst->pixmap.)
+     */
+    if (inst->surface) {
+        cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(widget));
+        cairo_set_source_surface(cr, inst->surface, 0, 0);
+        cairo_rectangle(cr, event->area.x, event->area.y,
+                       event->area.width, event->area.height);
+        cairo_fill(cr);
+        cairo_destroy(cr);
+    }
+#endif
+
     return TRUE;
 }
+#endif
 
 #define KEY_PRESSED(k) \
     (inst->keystate[(k) / 32] & (1 << ((k) % 32)))
 
+#ifdef KEY_EVENT_DIAGNOSTICS
+char *dup_keyval_name(guint keyval)
+{
+    const char *name = gdk_keyval_name(keyval);
+    if (name)
+        return dupstr(name);
+    else
+        return dupprintf("UNKNOWN[%u]", (unsigned)keyval);
+}
+#endif
+
 gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
 {
     struct gui_data *inst = (struct gui_data *)data;
@@ -544,6 +693,85 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
     special = use_ucsoutput = FALSE;
     output_charset = CS_ISO8859_1;
 
+#ifdef KEY_EVENT_DIAGNOSTICS
+    {
+        char *type_string, *state_string, *keyval_string, *string_string;
+
+        type_string = (event->type == GDK_KEY_PRESS ? dupstr("PRESS") :
+                       event->type == GDK_KEY_RELEASE ? dupstr("RELEASE") :
+                       dupprintf("UNKNOWN[%d]", (int)event->type));
+
+        {
+            static const struct {
+                int mod_bit;
+                const char *name;
+            } mod_bits[] = {
+                {GDK_SHIFT_MASK, "SHIFT"},
+                {GDK_LOCK_MASK, "LOCK"},
+                {GDK_CONTROL_MASK, "CONTROL"},
+                {GDK_MOD1_MASK, "MOD1"},
+                {GDK_MOD2_MASK, "MOD2"},
+                {GDK_MOD3_MASK, "MOD3"},
+                {GDK_MOD4_MASK, "MOD4"},
+                {GDK_MOD5_MASK, "MOD5"},
+                {GDK_SUPER_MASK, "SUPER"},
+                {GDK_HYPER_MASK, "HYPER"},
+                {GDK_META_MASK, "META"},
+            };
+            int i;
+            int val = event->state;
+
+            state_string = dupstr("");
+
+            for (i = 0; i < lenof(mod_bits); i++) {
+                if (val & mod_bits[i].mod_bit) {
+                    char *old = state_string;
+                    state_string = dupcat(state_string,
+                                          state_string[0] ? "|" : "",
+                                          mod_bits[i].name,
+                                          (char *)NULL);
+                    sfree(old);
+
+                    val &= ~mod_bits[i].mod_bit;
+                }
+            }
+
+            if (val || !state_string[0]) {
+                char *old = state_string;
+                state_string = dupprintf("%s%s%d", state_string,
+                                         state_string[0] ? "|" : "", val);
+                sfree(old);
+            }
+        }
+
+        keyval_string = dup_keyval_name(event->keyval);
+
+        string_string = dupstr("");
+        {
+            int i;
+            for (i = 0; event->string[i]; i++) {
+                char *old = string_string;
+                string_string = dupprintf("%s%s%02x", string_string,
+                                          string_string[0] ? " " : "",
+                                          (unsigned)event->string[i] & 0xFF);
+                sfree(old);
+            }
+        }
+
+        debug(("key_event: type=%s keyval=%s state=%s "
+               "hardware_keycode=%d is_modifier=%s string=[%s]\n",
+               type_string, keyval_string, state_string,
+               (int)event->hardware_keycode,
+               event->is_modifier ? "TRUE" : "FALSE",
+               string_string));
+
+        sfree(type_string);
+        sfree(state_string);
+        sfree(keyval_string);
+        sfree(string_string);
+    }
+#endif /* KEY_EVENT_DIAGNOSTICS */
+
     /*
      * If Alt is being released after typing an Alt+numberpad
      * sequence, we should generate the code that was typed.
@@ -555,11 +783,14 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
      * character code.
      */
     if (event->type == GDK_KEY_RELEASE) {
-        if ((event->keyval == GDK_Meta_L || event->keyval == GDK_Alt_L ||
-             event->keyval == GDK_Meta_R || event->keyval == GDK_Alt_R) &&
+        if ((event->keyval == GDK_KEY_Meta_L ||
+             event->keyval == GDK_KEY_Meta_R ||
+             event->keyval == GDK_KEY_Alt_L ||
+             event->keyval == GDK_KEY_Alt_R) &&
             inst->alt_keycode >= 0 && inst->alt_digits > 1) {
-#ifdef KEY_DEBUGGING
-            printf("Alt key up, keycode = %d\n", inst->alt_keycode);
+#ifdef KEY_EVENT_DIAGNOSTICS
+            debug((" - modifier release terminates Alt+numberpad input, "
+                   "keycode = %d\n", inst->alt_keycode));
 #endif
             /*
              * FIXME: we might usefully try to do something clever here
@@ -571,73 +802,75 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
             goto done;
         }
 #if GTK_CHECK_VERSION(2,0,0)
-        if (gtk_im_context_filter_keypress(inst->imc, event))
+#ifdef KEY_EVENT_DIAGNOSTICS
+        debug((" - key release, passing to IM\n"));
+#endif
+        if (gtk_im_context_filter_keypress(inst->imc, event)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+            debug((" - key release accepted by IM\n"));
+#endif
             return TRUE;
+        } else {
+#ifdef KEY_EVENT_DIAGNOSTICS
+            debug((" - key release not accepted by IM\n"));
+#endif
+        }
 #endif
     }
 
     if (event->type == GDK_KEY_PRESS) {
-#ifdef KEY_DEBUGGING
-       {
-           int i;
-           printf("keypress: keyval = %04x, state = %08x; string =",
-                  event->keyval, event->state);
-           for (i = 0; event->string[i]; i++)
-               printf(" %02x", (unsigned char) event->string[i]);
-           printf("\n");
-       }
-#endif
-
-       /*
-        * NYI: Compose key (!!! requires Unicode faff before even trying)
-        */
-
        /*
         * If Alt has just been pressed, we start potentially
         * accumulating an Alt+numberpad code. We do this by
         * setting alt_keycode to -1 (nothing yet but plausible).
         */
-       if ((event->keyval == GDK_Meta_L || event->keyval == GDK_Alt_L ||
-            event->keyval == GDK_Meta_R || event->keyval == GDK_Alt_R)) {
+       if ((event->keyval == GDK_KEY_Meta_L ||
+            event->keyval == GDK_KEY_Meta_R ||
+             event->keyval == GDK_KEY_Alt_L ||
+             event->keyval == GDK_KEY_Alt_R)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+            debug((" - modifier press potentially begins Alt+numberpad "
+                   "input\n"));
+#endif
            inst->alt_keycode = -1;
             inst->alt_digits = 0;
            goto done;                 /* this generates nothing else */
        }
 
        /*
-        * If we're seeing a numberpad key press with Mod1 down,
+        * If we're seeing a numberpad key press with Meta down,
         * consider adding it to alt_keycode if that's sensible.
-        * Anything _else_ with Mod1 down cancels any possibility
+        * Anything _else_ with Meta down cancels any possibility
         * of an ALT keycode: we set alt_keycode to -2.
         */
-       if ((event->state & GDK_MOD1_MASK) && inst->alt_keycode != -2) {
+       if ((event->state & inst->meta_mod_mask) && inst->alt_keycode != -2) {
            int digit = -1;
            switch (event->keyval) {
-             case GDK_KP_0: case GDK_KP_Insert: digit = 0; break;
-             case GDK_KP_1: case GDK_KP_End: digit = 1; break;
-             case GDK_KP_2: case GDK_KP_Down: digit = 2; break;
-             case GDK_KP_3: case GDK_KP_Page_Down: digit = 3; break;
-             case GDK_KP_4: case GDK_KP_Left: digit = 4; break;
-             case GDK_KP_5: case GDK_KP_Begin: digit = 5; break;
-             case GDK_KP_6: case GDK_KP_Right: digit = 6; break;
-             case GDK_KP_7: case GDK_KP_Home: digit = 7; break;
-             case GDK_KP_8: case GDK_KP_Up: digit = 8; break;
-             case GDK_KP_9: case GDK_KP_Page_Up: digit = 9; break;
+             case GDK_KEY_KP_0: case GDK_KEY_KP_Insert: digit = 0; break;
+             case GDK_KEY_KP_1: case GDK_KEY_KP_End: digit = 1; break;
+             case GDK_KEY_KP_2: case GDK_KEY_KP_Down: digit = 2; break;
+             case GDK_KEY_KP_3: case GDK_KEY_KP_Page_Down: digit = 3; break;
+             case GDK_KEY_KP_4: case GDK_KEY_KP_Left: digit = 4; break;
+             case GDK_KEY_KP_5: case GDK_KEY_KP_Begin: digit = 5; break;
+             case GDK_KEY_KP_6: case GDK_KEY_KP_Right: digit = 6; break;
+             case GDK_KEY_KP_7: case GDK_KEY_KP_Home: digit = 7; break;
+             case GDK_KEY_KP_8: case GDK_KEY_KP_Up: digit = 8; break;
+             case GDK_KEY_KP_9: case GDK_KEY_KP_Page_Up: digit = 9; break;
            }
            if (digit < 0)
                inst->alt_keycode = -2;   /* it's invalid */
            else {
-#ifdef KEY_DEBUGGING
-               printf("Adding digit %d to keycode %d", digit,
-                      inst->alt_keycode);
+#ifdef KEY_EVENT_DIAGNOSTICS
+                int old_keycode = inst->alt_keycode;
 #endif
                if (inst->alt_keycode == -1)
                    inst->alt_keycode = digit;   /* one-digit code */
                else
                    inst->alt_keycode = inst->alt_keycode * 10 + digit;
                 inst->alt_digits++;
-#ifdef KEY_DEBUGGING
-               printf(" gives new code %d\n", inst->alt_keycode);
+#ifdef KEY_EVENT_DIAGNOSTICS
+                debug((" - Alt+numberpad digit %d added to keycode %d"
+                       " gives %d\n", digit, old_keycode, inst->alt_keycode));
 #endif
                /* Having used this digit, we now do nothing more with it. */
                goto done;
@@ -648,19 +881,35 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
         * Shift-PgUp and Shift-PgDn don't even generate keystrokes
         * at all.
         */
-       if (event->keyval == GDK_Page_Up && (event->state & GDK_SHIFT_MASK)) {
+       if (event->keyval == GDK_KEY_Page_Up &&
+            (event->state & GDK_SHIFT_MASK)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+            debug((" - Shift-PgUp scroll\n"));
+#endif
            term_scroll(inst->term, 0, -inst->height/2);
            return TRUE;
        }
-       if (event->keyval == GDK_Page_Up && (event->state & GDK_CONTROL_MASK)) {
+       if (event->keyval == GDK_KEY_Page_Up &&
+            (event->state & GDK_CONTROL_MASK)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+            debug((" - Ctrl-PgUp scroll\n"));
+#endif
            term_scroll(inst->term, 0, -1);
            return TRUE;
        }
-       if (event->keyval == GDK_Page_Down && (event->state & GDK_SHIFT_MASK)) {
+       if (event->keyval == GDK_KEY_Page_Down &&
+            (event->state & GDK_SHIFT_MASK)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+            debug((" - Shift-PgDn scroll\n"));
+#endif
            term_scroll(inst->term, 0, +inst->height/2);
            return TRUE;
        }
-       if (event->keyval == GDK_Page_Down && (event->state & GDK_CONTROL_MASK)) {
+       if (event->keyval == GDK_KEY_Page_Down &&
+            (event->state & GDK_CONTROL_MASK)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+            debug((" - Ctrl-PgDn scroll\n"));
+#endif
            term_scroll(inst->term, 0, +1);
            return TRUE;
        }
@@ -668,7 +917,11 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
        /*
         * Neither does Shift-Ins.
         */
-       if (event->keyval == GDK_Insert && (event->state & GDK_SHIFT_MASK)) {
+       if (event->keyval == GDK_KEY_Insert &&
+            (event->state & GDK_SHIFT_MASK)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+            debug((" - Shift-Insert paste\n"));
+#endif
            request_paste(inst);
            return TRUE;
        }
@@ -692,7 +945,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
         */
        output_charset = CS_ISO8859_1;
        strncpy(output+1, event->string, lenof(output)-1);
-#else
+#else /* !GTK_CHECK_VERSION(2,0,0) */
         /*
          * Most things can now be passed to
          * gtk_im_context_filter_keypress without breaking anything
@@ -702,58 +955,90 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
          * it to.
          */
        if (app_keypad_mode &&
-            (event->keyval == GDK_Num_Lock ||
-             event->keyval == GDK_KP_Divide ||
-             event->keyval == GDK_KP_Multiply ||
-             event->keyval == GDK_KP_Subtract ||
-             event->keyval == GDK_KP_Add ||
-             event->keyval == GDK_KP_Enter ||
-             event->keyval == GDK_KP_0 ||
-             event->keyval == GDK_KP_Insert ||
-             event->keyval == GDK_KP_1 ||
-             event->keyval == GDK_KP_End ||
-             event->keyval == GDK_KP_2 ||
-             event->keyval == GDK_KP_Down ||
-             event->keyval == GDK_KP_3 ||
-             event->keyval == GDK_KP_Page_Down ||
-             event->keyval == GDK_KP_4 ||
-             event->keyval == GDK_KP_Left ||
-             event->keyval == GDK_KP_5 ||
-             event->keyval == GDK_KP_Begin ||
-             event->keyval == GDK_KP_6 ||
-             event->keyval == GDK_KP_Right ||
-             event->keyval == GDK_KP_7 ||
-             event->keyval == GDK_KP_Home ||
-             event->keyval == GDK_KP_8 ||
-             event->keyval == GDK_KP_Up ||
-             event->keyval == GDK_KP_9 ||
-             event->keyval == GDK_KP_Page_Up ||
-             event->keyval == GDK_KP_Decimal ||
-             event->keyval == GDK_KP_Delete)) {
+            (event->keyval == GDK_KEY_Num_Lock ||
+             event->keyval == GDK_KEY_KP_Divide ||
+             event->keyval == GDK_KEY_KP_Multiply ||
+             event->keyval == GDK_KEY_KP_Subtract ||
+             event->keyval == GDK_KEY_KP_Add ||
+             event->keyval == GDK_KEY_KP_Enter ||
+             event->keyval == GDK_KEY_KP_0 ||
+             event->keyval == GDK_KEY_KP_Insert ||
+             event->keyval == GDK_KEY_KP_1 ||
+             event->keyval == GDK_KEY_KP_End ||
+             event->keyval == GDK_KEY_KP_2 ||
+             event->keyval == GDK_KEY_KP_Down ||
+             event->keyval == GDK_KEY_KP_3 ||
+             event->keyval == GDK_KEY_KP_Page_Down ||
+             event->keyval == GDK_KEY_KP_4 ||
+             event->keyval == GDK_KEY_KP_Left ||
+             event->keyval == GDK_KEY_KP_5 ||
+             event->keyval == GDK_KEY_KP_Begin ||
+             event->keyval == GDK_KEY_KP_6 ||
+             event->keyval == GDK_KEY_KP_Right ||
+             event->keyval == GDK_KEY_KP_7 ||
+             event->keyval == GDK_KEY_KP_Home ||
+             event->keyval == GDK_KEY_KP_8 ||
+             event->keyval == GDK_KEY_KP_Up ||
+             event->keyval == GDK_KEY_KP_9 ||
+             event->keyval == GDK_KEY_KP_Page_Up ||
+             event->keyval == GDK_KEY_KP_Decimal ||
+             event->keyval == GDK_KEY_KP_Delete)) {
             /* app keypad; do nothing */
         } else if (nethack_mode &&
-                   (event->keyval == GDK_KP_1 ||
-                    event->keyval == GDK_KP_End ||
-                    event->keyval == GDK_KP_2 ||
-                    event->keyval == GDK_KP_Down ||
-                    event->keyval == GDK_KP_3 ||
-                    event->keyval == GDK_KP_Page_Down ||
-                    event->keyval == GDK_KP_4 ||
-                    event->keyval == GDK_KP_Left ||
-                    event->keyval == GDK_KP_5 ||
-                    event->keyval == GDK_KP_Begin ||
-                    event->keyval == GDK_KP_6 ||
-                    event->keyval == GDK_KP_Right ||
-                    event->keyval == GDK_KP_7 ||
-                    event->keyval == GDK_KP_Home ||
-                    event->keyval == GDK_KP_8 ||
-                    event->keyval == GDK_KP_Up ||
-                    event->keyval == GDK_KP_9 ||
-                    event->keyval == GDK_KP_Page_Up)) {
+                   (event->keyval == GDK_KEY_KP_1 ||
+                    event->keyval == GDK_KEY_KP_End ||
+                    event->keyval == GDK_KEY_KP_2 ||
+                    event->keyval == GDK_KEY_KP_Down ||
+                    event->keyval == GDK_KEY_KP_3 ||
+                    event->keyval == GDK_KEY_KP_Page_Down ||
+                    event->keyval == GDK_KEY_KP_4 ||
+                    event->keyval == GDK_KEY_KP_Left ||
+                    event->keyval == GDK_KEY_KP_5 ||
+                    event->keyval == GDK_KEY_KP_Begin ||
+                    event->keyval == GDK_KEY_KP_6 ||
+                    event->keyval == GDK_KEY_KP_Right ||
+                    event->keyval == GDK_KEY_KP_7 ||
+                    event->keyval == GDK_KEY_KP_Home ||
+                    event->keyval == GDK_KEY_KP_8 ||
+                    event->keyval == GDK_KEY_KP_Up ||
+                    event->keyval == GDK_KEY_KP_9 ||
+                    event->keyval == GDK_KEY_KP_Page_Up)) {
             /* nethack mode; do nothing */
         } else {
-            if (gtk_im_context_filter_keypress(inst->imc, event))
-                return TRUE;
+            int try_filter = TRUE;
+
+#ifdef META_MANUAL_MASK
+            if (event->state & META_MANUAL_MASK & inst->meta_mod_mask) {
+                /*
+                 * If this key event had a Meta modifier bit set which
+                 * is also in META_MANUAL_MASK, that means passing
+                 * such an event to the GtkIMContext will be unhelpful
+                 * (it will eat the keystroke and turn it into
+                 * something not what we wanted).
+                 */
+#ifdef KEY_EVENT_DIAGNOSTICS
+                debug((" - Meta modifier requiring manual intervention, "
+                       "suppressing IM filtering\n"));
+#endif
+                try_filter = FALSE;
+            }
+#endif
+
+            if (try_filter) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+                debug((" - general key press, passing to IM\n"));
+#endif
+                if (gtk_im_context_filter_keypress(inst->imc, event)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+                    debug((" - key press accepted by IM\n"));
+#endif
+                    return TRUE;
+                } else {
+#ifdef KEY_EVENT_DIAGNOSTICS
+                    debug((" - key press not accepted by IM\n"));
+#endif
+                }
+            }
         }
 
        /*
@@ -779,40 +1064,162 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
                            event->string, strlen(event->string),
                            widedata, lenof(widedata)-1);
 
+#ifdef KEY_EVENT_DIAGNOSTICS
+            {
+                char *string_string = dupstr("");
+                int i;
+
+                for (i = 0; i < wlen; i++) {
+                    char *old = string_string;
+                    string_string = dupprintf("%s%s%04x", string_string,
+                                              string_string[0] ? " " : "",
+                                              (unsigned)widedata[i]);
+                    sfree(old);
+                }
+                debug((" - string translated into Unicode = [%s]\n",
+                       string_string));
+                sfree(string_string);
+            }
+#endif
+
            wp = widedata;
            ulen = charset_from_unicode(&wp, &wlen, output+1, lenof(output)-2,
                                        CS_UTF8, NULL, NULL, 0);
+
+#ifdef KEY_EVENT_DIAGNOSTICS
+            {
+                char *string_string = dupstr("");
+                int i;
+
+                for (i = 0; i < ulen; i++) {
+                    char *old = string_string;
+                    string_string = dupprintf("%s%s%02x", string_string,
+                                              string_string[0] ? " " : "",
+                                              (unsigned)output[i+1] & 0xFF);
+                    sfree(old);
+                }
+                debug((" - string translated into UTF-8 = [%s]\n",
+                       string_string));
+                sfree(string_string);
+            }
+#endif
+
            output[1+ulen] = '\0';
        }
-#endif
+#endif /* !GTK_CHECK_VERSION(2,0,0) */
 
        if (!output[1] &&
            (ucsval = keysym_to_unicode(event->keyval)) >= 0) {
            ucsoutput[0] = '\033';
            ucsoutput[1] = ucsval;
+#ifdef KEY_EVENT_DIAGNOSTICS
+            debug((" - keysym_to_unicode gave %04x\n",
+                   (unsigned)ucsoutput[1]));
+#endif
            use_ucsoutput = TRUE;
            end = 2;
        } else {
            output[lenof(output)-1] = '\0';
            end = strlen(output);
        }
-       if (event->state & GDK_MOD1_MASK) {
+       if (event->state & inst->meta_mod_mask) {
            start = 0;
            if (end == 1) end = 0;
+
+#ifdef META_MANUAL_MASK
+            if (event->state & META_MANUAL_MASK) {
+                /*
+                 * Key events which have a META_MANUAL_MASK meta bit
+                 * set may have a keyval reflecting that, e.g. on OS X
+                 * the Option key acts as an AltGr-like modifier and
+                 * causes different Unicode characters to be output.
+                 *
+                 * To work around this, we clear the dangerous
+                 * modifier bit and retranslate from the hardware
+                 * keycode as if the key had been pressed without that
+                 * modifier. Then we prefix Esc to *that*.
+                 */
+                guint new_keyval;
+                GdkModifierType consumed;
+                if (gdk_keymap_translate_keyboard_state
+                    (gdk_keymap_get_for_display(gdk_display_get_default()),
+                     event->hardware_keycode, event->state & ~META_MANUAL_MASK,
+                     0, &new_keyval, NULL, NULL, &consumed)) {
+                    ucsoutput[0] = '\033';
+                    ucsoutput[1] = gdk_keyval_to_unicode(new_keyval);
+#ifdef KEY_EVENT_DIAGNOSTICS
+                    {
+                        char *keyval_name = dup_keyval_name(new_keyval);
+                        debug((" - retranslation for manual Meta: "
+                               "new keyval = %s, Unicode = %04x\n",
+                               keyval_name, (unsigned)ucsoutput[1]));
+                        sfree(keyval_name);
+                    }
+#endif
+                    use_ucsoutput = TRUE;
+                    end = 2;
+                }
+            }
+#endif
        } else
            start = 1;
 
        /* Control-` is the same as Control-\ (unless gtk has a better idea) */
        if (!output[1] && event->keyval == '`' &&
            (event->state & GDK_CONTROL_MASK)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+            debug((" - Ctrl-` special case, translating as 1c\n"));
+#endif
            output[1] = '\x1C';
            use_ucsoutput = FALSE;
            end = 2;
        }
 
+        /* Some GTK backends (e.g. Quartz) do not change event->string
+         * in response to the Control modifier. So we do it ourselves
+         * here, if it's not already happened.
+         *
+         * The translations below are in line with X11 policy as far
+         * as I know. */
+        if ((event->state & GDK_CONTROL_MASK) && end == 2) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+            int orig = output[1];
+#endif
+
+            if (output[1] >= '3' && output[1] <= '7') {
+                /* ^3,...,^7 map to 0x1B,...,0x1F */
+                output[1] += '\x1B' - '3';
+            } else if (output[1] == '2' || output[1] == ' ') {
+                /* ^2 and ^Space are both ^@, i.e. \0 */
+                output[1] = '\0';
+            } else if (output[1] == '8') {
+                /* ^8 is DEL */
+                output[1] = '\x7F';
+            } else if (output[1] == '/') {
+                /* ^/ is the same as ^_ */
+                output[1] = '\x1F';
+            } else if (output[1] >= 0x40 && output[1] < 0x7F) {
+                /* Everything anywhere near the alphabetics just gets
+                 * masked. */
+                output[1] &= 0x1F;
+            }
+            /* Anything else, e.g. '0', is unchanged. */
+
+#ifdef KEY_EVENT_DIAGNOSTICS
+            if (orig == output[1])
+                debug((" - manual Ctrl key handling did nothing\n"));
+            else
+                debug((" - manual Ctrl key handling: %02x -> %02x\n",
+                       (unsigned)orig, (unsigned)output[1]));
+#endif
+        }
+
        /* Control-Break sends a Break special to the backend */
-       if (event->keyval == GDK_Break &&
+       if (event->keyval == GDK_KEY_Break &&
            (event->state & GDK_CONTROL_MASK)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+            debug((" - Ctrl-Break special case, sending TS_BRK\n"));
+#endif
            if (inst->back)
                inst->back->special(inst->backhandle, TS_BRK);
            return TRUE;
@@ -820,7 +1227,10 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
 
        /* We handle Return ourselves, because it needs to be flagged as
         * special to ldisc. */
-       if (event->keyval == GDK_Return) {
+       if (event->keyval == GDK_KEY_Return) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+            debug((" - Return special case, translating as 0d + special\n"));
+#endif
            output[1] = '\015';
            use_ucsoutput = FALSE;
            end = 2;
@@ -833,6 +1243,9 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
             event->keyval == '@') &&
            (event->state & (GDK_SHIFT_MASK |
                             GDK_CONTROL_MASK)) == GDK_CONTROL_MASK) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+            debug((" - Ctrl-{space,2,@} special case, translating as 00\n"));
+#endif
            output[1] = '\0';
            use_ucsoutput = FALSE;
            end = 2;
@@ -842,6 +1255,9 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
        if (!output[1] && event->keyval == ' ' &&
            (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) ==
            (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+            debug((" - Ctrl-Shift-space special case, translating as 00a0\n"));
+#endif
            output[1] = '\240';
            output_charset = CS_ISO8859_1;
            use_ucsoutput = FALSE;
@@ -849,34 +1265,49 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
        }
 
        /* We don't let GTK tell us what Backspace is! We know better. */
-       if (event->keyval == GDK_BackSpace &&
+       if (event->keyval == GDK_KEY_BackSpace &&
            !(event->state & GDK_SHIFT_MASK)) {
            output[1] = conf_get_int(inst->conf, CONF_bksp_is_delete) ?
                '\x7F' : '\x08';
+#ifdef KEY_EVENT_DIAGNOSTICS
+            debug((" - Backspace, translating as %02x\n",
+                   (unsigned)output[1]));
+#endif
            use_ucsoutput = FALSE;
            end = 2;
            special = TRUE;
        }
        /* For Shift Backspace, do opposite of what is configured. */
-       if (event->keyval == GDK_BackSpace &&
+       if (event->keyval == GDK_KEY_BackSpace &&
            (event->state & GDK_SHIFT_MASK)) {
            output[1] = conf_get_int(inst->conf, CONF_bksp_is_delete) ?
                '\x08' : '\x7F';
+#ifdef KEY_EVENT_DIAGNOSTICS
+            debug((" - Shift-Backspace, translating as %02x\n",
+                   (unsigned)output[1]));
+#endif
            use_ucsoutput = FALSE;
            end = 2;
            special = TRUE;
        }
 
        /* Shift-Tab is ESC [ Z */
-       if (event->keyval == GDK_ISO_Left_Tab ||
-           (event->keyval == GDK_Tab && (event->state & GDK_SHIFT_MASK))) {
+       if (event->keyval == GDK_KEY_ISO_Left_Tab ||
+           (event->keyval == GDK_KEY_Tab &&
+             (event->state & GDK_SHIFT_MASK))) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+            debug((" - Shift-Tab, translating as ESC [ Z\n"));
+#endif
            end = 1 + sprintf(output+1, "\033[Z");
            use_ucsoutput = FALSE;
        }
        /* And normal Tab is Tab, if the keymap hasn't already told us.
         * (Curiously, at least one version of the MacOS 10.5 X server
         * doesn't translate Tab for us. */
-       if (event->keyval == GDK_Tab && end <= 1) {
+       if (event->keyval == GDK_KEY_Tab && end <= 1) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+            debug((" - Tab, translating as 09\n"));
+#endif
            output[1] = '\t';
            end = 2;
        }
@@ -885,17 +1316,26 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
         * NetHack keypad mode.
         */
        if (nethack_mode) {
-           char *keys = NULL;
+           const char *keys = NULL;
            switch (event->keyval) {
-             case GDK_KP_1: case GDK_KP_End: keys = "bB\002"; break;
-             case GDK_KP_2: case GDK_KP_Down: keys = "jJ\012"; break;
-             case GDK_KP_3: case GDK_KP_Page_Down: keys = "nN\016"; break;
-             case GDK_KP_4: case GDK_KP_Left: keys = "hH\010"; break;
-             case GDK_KP_5: case GDK_KP_Begin: keys = "..."; break;
-             case GDK_KP_6: case GDK_KP_Right: keys = "lL\014"; break;
-             case GDK_KP_7: case GDK_KP_Home: keys = "yY\031"; break;
-             case GDK_KP_8: case GDK_KP_Up: keys = "kK\013"; break;
-             case GDK_KP_9: case GDK_KP_Page_Up: keys = "uU\025"; break;
+             case GDK_KEY_KP_1: case GDK_KEY_KP_End:
+                keys = "bB\002"; break;
+             case GDK_KEY_KP_2: case GDK_KEY_KP_Down:
+                keys = "jJ\012"; break;
+             case GDK_KEY_KP_3: case GDK_KEY_KP_Page_Down:
+                keys = "nN\016"; break;
+             case GDK_KEY_KP_4: case GDK_KEY_KP_Left:
+                keys = "hH\010"; break;
+             case GDK_KEY_KP_5: case GDK_KEY_KP_Begin:
+                keys = "..."; break;
+             case GDK_KEY_KP_6: case GDK_KEY_KP_Right:
+                keys = "lL\014"; break;
+             case GDK_KEY_KP_7: case GDK_KEY_KP_Home:
+                keys = "yY\031"; break;
+             case GDK_KEY_KP_8: case GDK_KEY_KP_Up:
+                keys = "kK\013"; break;
+             case GDK_KEY_KP_9: case GDK_KEY_KP_Page_Up:
+                keys = "uU\025"; break;
            }
            if (keys) {
                end = 2;
@@ -905,6 +1345,9 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
                    output[1] = keys[1];
                else
                    output[1] = keys[0];
+#ifdef KEY_EVENT_DIAGNOSTICS
+                debug((" - Nethack-mode key"));
+#endif
                use_ucsoutput = FALSE;
                goto done;
            }
@@ -916,17 +1359,17 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
        if (app_keypad_mode) {
            int xkey = 0;
            switch (event->keyval) {
-             case GDK_Num_Lock: xkey = 'P'; break;
-             case GDK_KP_Divide: xkey = 'Q'; break;
-             case GDK_KP_Multiply: xkey = 'R'; break;
-             case GDK_KP_Subtract: xkey = 'S'; break;
+             case GDK_KEY_Num_Lock: xkey = 'P'; break;
+             case GDK_KEY_KP_Divide: xkey = 'Q'; break;
+             case GDK_KEY_KP_Multiply: xkey = 'R'; break;
+             case GDK_KEY_KP_Subtract: xkey = 'S'; break;
                /*
                 * Keypad + is tricky. It covers a space that would
                 * be taken up on the VT100 by _two_ keys; so we
                 * let Shift select between the two. Worse still,
                 * in xterm function key mode we change which two...
                 */
-             case GDK_KP_Add:
+             case GDK_KEY_KP_Add:
                if (conf_get_int(inst->conf, CONF_funky_type) == FUNKY_XTERM) {
                    if (event->state & GDK_SHIFT_MASK)
                        xkey = 'l';
@@ -937,18 +1380,19 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
                else
                    xkey = 'l';
                break;
-             case GDK_KP_Enter: xkey = 'M'; break;
-             case GDK_KP_0: case GDK_KP_Insert: xkey = 'p'; break;
-             case GDK_KP_1: case GDK_KP_End: xkey = 'q'; break;
-             case GDK_KP_2: case GDK_KP_Down: xkey = 'r'; break;
-             case GDK_KP_3: case GDK_KP_Page_Down: xkey = 's'; break;
-             case GDK_KP_4: case GDK_KP_Left: xkey = 't'; break;
-             case GDK_KP_5: case GDK_KP_Begin: xkey = 'u'; break;
-             case GDK_KP_6: case GDK_KP_Right: xkey = 'v'; break;
-             case GDK_KP_7: case GDK_KP_Home: xkey = 'w'; break;
-             case GDK_KP_8: case GDK_KP_Up: xkey = 'x'; break;
-             case GDK_KP_9: case GDK_KP_Page_Up: xkey = 'y'; break;
-             case GDK_KP_Decimal: case GDK_KP_Delete: xkey = 'n'; break;
+             case GDK_KEY_KP_Enter: xkey = 'M'; break;
+             case GDK_KEY_KP_0: case GDK_KEY_KP_Insert: xkey = 'p'; break;
+             case GDK_KEY_KP_1: case GDK_KEY_KP_End: xkey = 'q'; break;
+             case GDK_KEY_KP_2: case GDK_KEY_KP_Down: xkey = 'r'; break;
+             case GDK_KEY_KP_3: case GDK_KEY_KP_Page_Down: xkey = 's'; break;
+             case GDK_KEY_KP_4: case GDK_KEY_KP_Left: xkey = 't'; break;
+             case GDK_KEY_KP_5: case GDK_KEY_KP_Begin: xkey = 'u'; break;
+             case GDK_KEY_KP_6: case GDK_KEY_KP_Right: xkey = 'v'; break;
+             case GDK_KEY_KP_7: case GDK_KEY_KP_Home: xkey = 'w'; break;
+             case GDK_KEY_KP_8: case GDK_KEY_KP_Up: xkey = 'x'; break;
+             case GDK_KEY_KP_9: case GDK_KEY_KP_Page_Up: xkey = 'y'; break;
+             case GDK_KEY_KP_Decimal: case GDK_KEY_KP_Delete:
+                xkey = 'n'; break;
            }
            if (xkey) {
                if (inst->term->vt52_mode) {
@@ -959,6 +1403,9 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
                } else
                    end = 1 + sprintf(output+1, "\033O%c", xkey);
                use_ucsoutput = FALSE;
+#ifdef KEY_EVENT_DIAGNOSTICS
+                debug((" - Application keypad mode key"));
+#endif
                goto done;
            }
        }
@@ -976,84 +1423,84 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
            int code = 0;
            int funky_type = conf_get_int(inst->conf, CONF_funky_type);
            switch (event->keyval) {
-             case GDK_F1:
+             case GDK_KEY_F1:
                code = (event->state & GDK_SHIFT_MASK ? 23 : 11);
                break;
-             case GDK_F2:
+             case GDK_KEY_F2:
                code = (event->state & GDK_SHIFT_MASK ? 24 : 12);
                break;
-             case GDK_F3:
+             case GDK_KEY_F3:
                code = (event->state & GDK_SHIFT_MASK ? 25 : 13);
                break;
-             case GDK_F4:
+             case GDK_KEY_F4:
                code = (event->state & GDK_SHIFT_MASK ? 26 : 14);
                break;
-             case GDK_F5:
+             case GDK_KEY_F5:
                code = (event->state & GDK_SHIFT_MASK ? 28 : 15);
                break;
-             case GDK_F6:
+             case GDK_KEY_F6:
                code = (event->state & GDK_SHIFT_MASK ? 29 : 17);
                break;
-             case GDK_F7:
+             case GDK_KEY_F7:
                code = (event->state & GDK_SHIFT_MASK ? 31 : 18);
                break;
-             case GDK_F8:
+             case GDK_KEY_F8:
                code = (event->state & GDK_SHIFT_MASK ? 32 : 19);
                break;
-             case GDK_F9:
+             case GDK_KEY_F9:
                code = (event->state & GDK_SHIFT_MASK ? 33 : 20);
                break;
-             case GDK_F10:
+             case GDK_KEY_F10:
                code = (event->state & GDK_SHIFT_MASK ? 34 : 21);
                break;
-             case GDK_F11:
+             case GDK_KEY_F11:
                code = 23;
                break;
-             case GDK_F12:
+             case GDK_KEY_F12:
                code = 24;
                break;
-             case GDK_F13:
+             case GDK_KEY_F13:
                code = 25;
                break;
-             case GDK_F14:
+             case GDK_KEY_F14:
                code = 26;
                break;
-             case GDK_F15:
+             case GDK_KEY_F15:
                code = 28;
                break;
-             case GDK_F16:
+             case GDK_KEY_F16:
                code = 29;
                break;
-             case GDK_F17:
+             case GDK_KEY_F17:
                code = 31;
                break;
-             case GDK_F18:
+             case GDK_KEY_F18:
                code = 32;
                break;
-             case GDK_F19:
+             case GDK_KEY_F19:
                code = 33;
                break;
-             case GDK_F20:
+             case GDK_KEY_F20:
                code = 34;
                break;
            }
            if (!(event->state & GDK_CONTROL_MASK)) switch (event->keyval) {
-             case GDK_Home: case GDK_KP_Home:
+             case GDK_KEY_Home: case GDK_KEY_KP_Home:
                code = 1;
                break;
-             case GDK_Insert: case GDK_KP_Insert:
+             case GDK_KEY_Insert: case GDK_KEY_KP_Insert:
                code = 2;
                break;
-             case GDK_Delete: case GDK_KP_Delete:
+             case GDK_KEY_Delete: case GDK_KEY_KP_Delete:
                code = 3;
                break;
-             case GDK_End: case GDK_KP_End:
+             case GDK_KEY_End: case GDK_KEY_KP_End:
                code = 4;
                break;
-             case GDK_Page_Up: case GDK_KP_Page_Up:
+             case GDK_KEY_Page_Up: case GDK_KEY_KP_Page_Up:
                code = 5;
                break;
-             case GDK_Page_Down: case GDK_KP_Page_Down:
+             case GDK_KEY_Page_Down: case GDK_KEY_KP_Page_Down:
                code = 6;
                break;
            }
@@ -1063,6 +1510,9 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
 
            if (inst->term->vt52_mode && code > 0 && code <= 6) {
                end = 1 + sprintf(output+1, "\x1B%c", " HLMEIG"[code]);
+#ifdef KEY_EVENT_DIAGNOSTICS
+                debug((" - VT52 mode small keypad key"));
+#endif
                use_ucsoutput = FALSE;
                goto done;
            }
@@ -1072,22 +1522,25 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
                char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
                int index = 0;
                switch (event->keyval) {
-                 case GDK_F1: index = 0; break;
-                 case GDK_F2: index = 1; break;
-                 case GDK_F3: index = 2; break;
-                 case GDK_F4: index = 3; break;
-                 case GDK_F5: index = 4; break;
-                 case GDK_F6: index = 5; break;
-                 case GDK_F7: index = 6; break;
-                 case GDK_F8: index = 7; break;
-                 case GDK_F9: index = 8; break;
-                 case GDK_F10: index = 9; break;
-                 case GDK_F11: index = 10; break;
-                 case GDK_F12: index = 11; break;
+                 case GDK_KEY_F1: index = 0; break;
+                 case GDK_KEY_F2: index = 1; break;
+                 case GDK_KEY_F3: index = 2; break;
+                 case GDK_KEY_F4: index = 3; break;
+                 case GDK_KEY_F5: index = 4; break;
+                 case GDK_KEY_F6: index = 5; break;
+                 case GDK_KEY_F7: index = 6; break;
+                 case GDK_KEY_F8: index = 7; break;
+                 case GDK_KEY_F9: index = 8; break;
+                 case GDK_KEY_F10: index = 9; break;
+                 case GDK_KEY_F11: index = 10; break;
+                 case GDK_KEY_F12: index = 11; break;
                }
                if (event->state & GDK_SHIFT_MASK) index += 12;
                if (event->state & GDK_CONTROL_MASK) index += 24;
                end = 1 + sprintf(output+1, "\x1B[%c", codes[index]);
+#ifdef KEY_EVENT_DIAGNOSTICS
+                debug((" - SCO mode function key"));
+#endif
                use_ucsoutput = FALSE;
                goto done;
            }
@@ -1100,6 +1553,9 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
                } else {
                    end = 1 + sprintf(output+1, "\x1B[%c", codes[code-1]);
                }
+#ifdef KEY_EVENT_DIAGNOSTICS
+                debug((" - SCO mode small keypad key"));
+#endif
                use_ucsoutput = FALSE;
                goto done;
            }
@@ -1110,35 +1566,59 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
                    offt++;
                if (code > 21)
                    offt++;
-               if (inst->term->vt52_mode)
+               if (inst->term->vt52_mode) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+                    debug((" - VT52 mode function key"));
+#endif
                    end = 1 + sprintf(output+1,
                                      "\x1B%c", code + 'P' - 11 - offt);
-               else
+                } else {
+#ifdef KEY_EVENT_DIAGNOSTICS
+                    debug((" - VT100+ mode function key"));
+#endif
                    end = 1 + sprintf(output+1,
                                      "\x1BO%c", code + 'P' - 11 - offt);
+                }
                use_ucsoutput = FALSE;
                goto done;
            }
            if (funky_type == FUNKY_LINUX && code >= 11 && code <= 15) {
                end = 1 + sprintf(output+1, "\x1B[[%c", code + 'A' - 11);
+#ifdef KEY_EVENT_DIAGNOSTICS
+                debug((" - Linux mode F1-F5 function key"));
+#endif
                use_ucsoutput = FALSE;
                goto done;
            }
            if (funky_type == FUNKY_XTERM && code >= 11 && code <= 14) {
-               if (inst->term->vt52_mode)
+               if (inst->term->vt52_mode) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+                    debug((" - VT52 mode (overriding xterm) F1-F4 function"
+                           " key"));
+#endif
                    end = 1 + sprintf(output+1, "\x1B%c", code + 'P' - 11);
-               else
+                } else {
+#ifdef KEY_EVENT_DIAGNOSTICS
+                    debug((" - xterm mode F1-F4 function key"));
+#endif
                    end = 1 + sprintf(output+1, "\x1BO%c", code + 'P' - 11);
+                }
                use_ucsoutput = FALSE;
                goto done;
            }
            if ((code == 1 || code == 4) &&
                conf_get_int(inst->conf, CONF_rxvt_homeend)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+                debug((" - rxvt style Home/End"));
+#endif
                end = 1 + sprintf(output+1, code == 1 ? "\x1B[H" : "\x1BOw");
                use_ucsoutput = FALSE;
                goto done;
            }
            if (code) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+                debug((" - ordinary function key encoding"));
+#endif
                end = 1 + sprintf(output+1, "\x1B[%d~", code);
                use_ucsoutput = FALSE;
                goto done;
@@ -1155,15 +1635,18 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
        {
            int xkey = 0;
            switch (event->keyval) {
-             case GDK_Up: case GDK_KP_Up: xkey = 'A'; break;
-             case GDK_Down: case GDK_KP_Down: xkey = 'B'; break;
-             case GDK_Right: case GDK_KP_Right: xkey = 'C'; break;
-             case GDK_Left: case GDK_KP_Left: xkey = 'D'; break;
-             case GDK_Begin: case GDK_KP_Begin: xkey = 'G'; break;
+             case GDK_KEY_Up: case GDK_KEY_KP_Up: xkey = 'A'; break;
+             case GDK_KEY_Down: case GDK_KEY_KP_Down: xkey = 'B'; break;
+             case GDK_KEY_Right: case GDK_KEY_KP_Right: xkey = 'C'; break;
+             case GDK_KEY_Left: case GDK_KEY_KP_Left: xkey = 'D'; break;
+             case GDK_KEY_Begin: case GDK_KEY_KP_Begin: xkey = 'G'; break;
            }
            if (xkey) {
                end = 1 + format_arrow_key(output+1, inst->term, xkey,
                                           event->state & GDK_CONTROL_MASK);
+#ifdef KEY_EVENT_DIAGNOSTICS
+                debug((" - arrow key"));
+#endif
                use_ucsoutput = FALSE;
                goto done;
            }
@@ -1174,15 +1657,22 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
     done:
 
     if (end-start > 0) {
-#ifdef KEY_DEBUGGING
-       int i;
-       printf("generating sequence:");
-       for (i = start; i < end; i++)
-           printf(" %02x", (unsigned char) output[i]);
-       printf("\n");
-#endif
-
        if (special) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+            char *string_string = dupstr("");
+            int i;
+
+            for (i = start; i < end; i++) {
+                char *old = string_string;
+                string_string = dupprintf("%s%s%02x", string_string,
+                                          string_string[0] ? " " : "",
+                                          (unsigned)output[i] & 0xFF);
+                sfree(old);
+            }
+            debug((" - final output, special, generic encoding = [%s]\n",
+                   charset_to_localenc(output_charset), string_string));
+            sfree(string_string);
+#endif
            /*
             * For special control characters, the character set
             * should never matter.
@@ -1192,10 +1682,41 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
                ldisc_send(inst->ldisc, output+start, -2, 1);
        } else if (!inst->direct_to_font) {
            if (!use_ucsoutput) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+                char *string_string = dupstr("");
+                int i;
+
+                for (i = start; i < end; i++) {
+                    char *old = string_string;
+                    string_string = dupprintf("%s%s%02x", string_string,
+                                              string_string[0] ? " " : "",
+                                              (unsigned)output[i] & 0xFF);
+                    sfree(old);
+                }
+                debug((" - final output in %s = [%s]\n",
+                       charset_to_localenc(output_charset), string_string));
+                sfree(string_string);
+#endif
                if (inst->ldisc)
                    lpage_send(inst->ldisc, output_charset, output+start,
                               end-start, 1);
            } else {
+#ifdef KEY_EVENT_DIAGNOSTICS
+                char *string_string = dupstr("");
+                int i;
+
+                for (i = start; i < end; i++) {
+                    char *old = string_string;
+                    string_string = dupprintf("%s%s%04x", string_string,
+                                              string_string[0] ? " " : "",
+                                              (unsigned)ucsoutput[i]);
+                    sfree(old);
+                }
+                debug((" - final output in Unicode = [%s]\n",
+                       string_string));
+                sfree(string_string);
+#endif
+
                /*
                 * We generated our own Unicode key data from the
                 * keysym, so use that instead.
@@ -1208,6 +1729,21 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
             * In direct-to-font mode, we just send the string
             * exactly as we received it.
             */
+#ifdef KEY_EVENT_DIAGNOSTICS
+            char *string_string = dupstr("");
+            int i;
+
+            for (i = start; i < end; i++) {
+                char *old = string_string;
+                string_string = dupprintf("%s%s%02x", string_string,
+                                          string_string[0] ? " " : "",
+                                          (unsigned)output[i] & 0xFF);
+                sfree(old);
+            }
+            debug((" - final output in direct-to-font encoding = [%s]\n",
+                   string_string));
+            sfree(string_string);
+#endif
            if (inst->ldisc)
                ldisc_send(inst->ldisc, output+start, end-start, 1);
        }
@@ -1223,6 +1759,22 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
 void input_method_commit_event(GtkIMContext *imc, gchar *str, gpointer data)
 {
     struct gui_data *inst = (struct gui_data *)data;
+
+#ifdef KEY_EVENT_DIAGNOSTICS
+    char *string_string = dupstr("");
+    int i;
+
+    for (i = 0; str[i]; i++) {
+        char *old = string_string;
+        string_string = dupprintf("%s%s%02x", string_string,
+                                  string_string[0] ? " " : "",
+                                  (unsigned)str[i] & 0xFF);
+        sfree(old);
+    }
+    debug((" - IM commit event in UTF-8 = [%s]\n", string_string));
+    sfree(string_string);
+#endif
+
     if (inst->ldisc)
         lpage_send(inst->ldisc, CS_UTF8, str, strlen(str), 1);
     show_mouseptr(inst, 0);
@@ -1230,6 +1782,61 @@ void input_method_commit_event(GtkIMContext *imc, gchar *str, gpointer data)
 }
 #endif
 
+#define SCROLL_INCREMENT_LINES 5
+
+#if GTK_CHECK_VERSION(3,4,0)
+gboolean scroll_internal(struct gui_data *inst, gdouble delta, guint state,
+                        gdouble ex, gdouble ey)
+{
+    int shift, ctrl, alt, x, y, raw_mouse_mode;
+
+    show_mouseptr(inst, 1);
+
+    shift = state & GDK_SHIFT_MASK;
+    ctrl = state & GDK_CONTROL_MASK;
+    alt = state & inst->meta_mod_mask;
+
+    x = (ex - inst->window_border) / inst->font_width;
+    y = (ey - inst->window_border) / inst->font_height;
+
+    raw_mouse_mode =
+        send_raw_mouse && !(shift && conf_get_int(inst->conf,
+                                                  CONF_mouse_override));
+
+    inst->cumulative_scroll += delta * SCROLL_INCREMENT_LINES;
+
+    if (!raw_mouse_mode) {
+        int scroll_lines = (int)inst->cumulative_scroll; /* rounds toward 0 */
+        if (scroll_lines) {
+            term_scroll(inst->term, 0, scroll_lines);
+            inst->cumulative_scroll -= scroll_lines;
+        }
+        return TRUE;
+    } else {
+        int scroll_events = (int)(inst->cumulative_scroll /
+                                  SCROLL_INCREMENT_LINES);
+        if (scroll_events) {
+            int button;
+
+            inst->cumulative_scroll -= scroll_events * SCROLL_INCREMENT_LINES;
+
+            if (scroll_events > 0) {
+                button = MBT_WHEEL_DOWN;
+            } else {
+                button = MBT_WHEEL_UP;
+                scroll_events = -scroll_events;
+            }
+
+            while (scroll_events-- > 0) {
+                term_mouse(inst->term, button, translate_button(button),
+                           MA_CLICK, x, y, shift, ctrl, alt);
+            }
+        }
+        return TRUE;
+    }
+}
+#endif
+
 gboolean button_internal(struct gui_data *inst, guint32 timestamp,
                         GdkEventType type, guint ebutton, guint state,
                         gdouble ex, gdouble ey)
@@ -1243,7 +1850,7 @@ gboolean button_internal(struct gui_data *inst, guint32 timestamp,
 
     shift = state & GDK_SHIFT_MASK;
     ctrl = state & GDK_CONTROL_MASK;
-    alt = state & GDK_MOD1_MASK;
+    alt = state & inst->meta_mod_mask;
 
     raw_mouse_mode =
         send_raw_mouse && !(shift && conf_get_int(inst->conf,
@@ -1251,11 +1858,11 @@ gboolean button_internal(struct gui_data *inst, guint32 timestamp,
 
     if (!raw_mouse_mode) {
         if (ebutton == 4 && type == GDK_BUTTON_PRESS) {
-            term_scroll(inst->term, 0, -5);
+            term_scroll(inst->term, 0, -SCROLL_INCREMENT_LINES);
             return TRUE;
         }
         if (ebutton == 5 && type == GDK_BUTTON_PRESS) {
-            term_scroll(inst->term, 0, +5);
+            term_scroll(inst->term, 0, +SCROLL_INCREMENT_LINES);
             return TRUE;
         }
     }
@@ -1315,8 +1922,15 @@ gboolean button_event(GtkWidget *widget, GdkEventButton *event, gpointer data)
 gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data)
 {
     struct gui_data *inst = (struct gui_data *)data;
-    guint button;
 
+#if GTK_CHECK_VERSION(3,4,0)
+    gdouble dx, dy;
+    if (gdk_event_get_scroll_deltas((GdkEvent *)event, &dx, &dy)) {
+        return scroll_internal(inst, dy, event->state, event->x, event->y);
+    } else
+        return FALSE;
+#else
+    guint button;
     if (event->direction == GDK_SCROLL_UP)
        button = 4;
     else if (event->direction == GDK_SCROLL_DOWN)
@@ -1326,6 +1940,7 @@ gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data)
 
     return button_internal(inst, event->time, GDK_BUTTON_PRESS,
                           button, event->state, event->x, event->y);
+#endif
 }
 #endif
 
@@ -1341,7 +1956,7 @@ gint motion_event(GtkWidget *widget, GdkEventMotion *event, gpointer data)
 
     shift = event->state & GDK_SHIFT_MASK;
     ctrl = event->state & GDK_CONTROL_MASK;
-    alt = event->state & GDK_MOD1_MASK;
+    alt = event->state & inst->meta_mod_mask;
     if (event->state & GDK_BUTTON1_MASK)
        button = MBT_LEFT;
     else if (event->state & GDK_BUTTON2_MASK)
@@ -1397,6 +2012,48 @@ 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;
@@ -1406,15 +2063,17 @@ void notify_remote_exit(void *frontend)
 
 static void notify_toplevel_callback(void *frontend);
 
-static gint quit_toplevel_callback_func(gpointer data)
+static void inst_post_main(struct gui_data *inst)
 {
-    struct gui_data *inst = (struct gui_data *)data;
-
-    notify_toplevel_callback(inst);
-
-    inst->quit_fn_scheduled = FALSE;
-
-    return 0;
+    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)
@@ -1429,7 +2088,7 @@ static gint idle_toplevel_callback_func(gpointer data)
          * already arranged one), so we can reschedule ourself then.
          */
         if (!inst->quit_fn_scheduled) {
-            gtk_quit_add(2, quit_toplevel_callback_func, inst);
+            request_post_main(inst);
             inst->quit_fn_scheduled = TRUE;
         }
         /*
@@ -1438,7 +2097,7 @@ static gint idle_toplevel_callback_func(gpointer data)
          * can reschedule us with a chance of actually taking action.
          */
         if (inst->idle_fn_scheduled) { /* double-check, just in case */
-            gtk_idle_remove(inst->toplevel_callback_idle_id);
+            g_source_remove(inst->toplevel_callback_idle_id);
             inst->idle_fn_scheduled = FALSE;
         }
     } else {
@@ -1452,7 +2111,7 @@ static gint idle_toplevel_callback_func(gpointer data)
      * event loop.
      */
     if (!toplevel_callback_pending() && inst->idle_fn_scheduled) {
-        gtk_idle_remove(inst->toplevel_callback_idle_id);
+        g_source_remove(inst->toplevel_callback_idle_id);
         inst->idle_fn_scheduled = FALSE;
     }
 
@@ -1465,7 +2124,7 @@ static void notify_toplevel_callback(void *frontend)
 
     if (!inst->idle_fn_scheduled) {
         inst->toplevel_callback_idle_id =
-            gtk_idle_add(idle_toplevel_callback_func, inst);
+            g_idle_add(idle_toplevel_callback_func, inst);
         inst->idle_fn_scheduled = TRUE;
     }
 }
@@ -1480,7 +2139,7 @@ static gint timer_trigger(gpointer data)
      * Destroy the timer we got here on.
      */
     if (timer_id) {
-       gtk_timeout_remove(timer_id);
+       g_source_remove(timer_id);
         timer_id = 0;
     }
 
@@ -1497,8 +2156,7 @@ static gint timer_trigger(gpointer data)
            ticks = 0;
        else
            ticks = next - now;
-       timer_id = gtk_timeout_add(ticks, timer_trigger,
-                                  LONG_TO_GPOINTER(next));
+       timer_id = g_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next));
     }
 
     /*
@@ -1514,23 +2172,37 @@ void timer_change_notify(unsigned long next)
     long ticks;
 
     if (timer_id)
-       gtk_timeout_remove(timer_id);
+       g_source_remove(timer_id);
 
     ticks = next - GETTICKCOUNT();
     if (ticks <= 0)
        ticks = 1;                     /* just in case */
 
-    timer_id = gtk_timeout_add(ticks, timer_trigger,
-                              LONG_TO_GPOINTER(next));
+    timer_id = g_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next));
 }
 
-void fd_input_func(gpointer data, gint sourcefd, GdkInputCondition condition)
+#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)
@@ -1538,6 +2210,7 @@ void fd_input_func(gpointer data, gint sourcefd, GdkInputCondition condition)
     if (condition & GDK_INPUT_WRITE)
         select_result(sourcefd, 2);
 }
+#endif
 
 void destroy(GtkWidget *widget, gpointer data)
 {
@@ -1574,6 +2247,9 @@ void set_raw_mouse_mode(void *frontend, int activate)
 void request_resize(void *frontend, int w, int h)
 {
     struct gui_data *inst = (struct gui_data *)frontend;
+
+#if !GTK_CHECK_VERSION(3,0,0)
+
     int large_x, large_y;
     int offset_x, offset_y;
     int area_x, area_y;
@@ -1603,11 +2279,7 @@ void request_resize(void *frontend, int w, int h)
     large_x += 32;
     large_y += 32;
 
-#if GTK_CHECK_VERSION(2,0,0)
     gtk_widget_set_size_request(inst->area, large_x, large_y);
-#else
-    gtk_widget_set_usize(inst->area, large_x, large_y);
-#endif
     gtk_widget_size_request(inst->area, &inner);
     gtk_widget_size_request(inst->window, &outer);
 
@@ -1623,12 +2295,11 @@ void request_resize(void *frontend, int w, int h)
      * way to do this, I think, is to set it to what the size is
      * really going to end up being.
      */
-#if GTK_CHECK_VERSION(2,0,0)
     gtk_widget_set_size_request(inst->area, area_x, area_y);
+#if GTK_CHECK_VERSION(2,0,0)
     gtk_window_resize(GTK_WINDOW(inst->window),
                      area_x + offset_x, area_y + offset_y);
 #else
-    gtk_widget_set_usize(inst->area, area_x, area_y);
     gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), area_x, area_y);
     /*
      * I can no longer remember what this call to
@@ -1648,33 +2319,73 @@ void request_resize(void *frontend, int w, int h)
      * above.
      */
     gtk_container_dequeue_resize_handler(GTK_CONTAINER(inst->window));
-    gdk_window_resize(inst->window->window,
+    gdk_window_resize(gtk_widget_get_window(inst->window),
                      area_x + offset_x, area_y + offset_y);
 #endif
+
+#else /* GTK_CHECK_VERSION(3,0,0) */
+
+    /*
+     * In GTK3, we can do this by using gtk_window_resize_to_geometry,
+     * which uses the fact that we've already set up the main window's
+     * WM hints to reflect the terminal drawing area's resize
+     * increment (i.e. character cell) and the fixed amount of stuff
+     * round the edges.
+     */
+    gtk_window_resize_to_geometry(GTK_WINDOW(inst->window), w, h);
+
+#endif
+
 }
 
 static void real_palette_set(struct gui_data *inst, int n, int r, int g, int b)
 {
-    gboolean success[1];
-
     inst->cols[n].red = r * 0x0101;
     inst->cols[n].green = g * 0x0101;
     inst->cols[n].blue = b * 0x0101;
 
-    gdk_colormap_free_colors(inst->colmap, inst->cols + n, 1);
-    gdk_colormap_alloc_colors(inst->colmap, inst->cols + n, 1,
-                             FALSE, TRUE, success);
-    if (!success[0])
-       g_error("%s: couldn't allocate colour %d (#%02x%02x%02x)\n", appname,
-               n, r, g, b);
+#if !GTK_CHECK_VERSION(3,0,0)
+    {
+        gboolean success[1];
+        gdk_colormap_free_colors(inst->colmap, inst->cols + n, 1);
+        gdk_colormap_alloc_colors(inst->colmap, inst->cols + n, 1,
+                                  FALSE, TRUE, success);
+        if (!success[0])
+            g_error("%s: couldn't allocate colour %d (#%02x%02x%02x)\n",
+                    appname, n, r, g, b);
+    }
+#endif
+}
+
+void set_gdk_window_background(GdkWindow *win, const GdkColor *col)
+{
+#if GTK_CHECK_VERSION(3,0,0)
+    /* gdk_window_set_background is deprecated; work around its
+     * absence. */
+    GdkRGBA rgba;
+    rgba.red = col->red / 65535.0;
+    rgba.green = col->green / 65535.0;
+    rgba.blue = col->blue / 65535.0;
+    rgba.alpha = 1.0;
+    gdk_window_set_background_rgba(win, &rgba);
+#else
+    {
+        /* For GTK1, which doesn't have a 'const' on
+         * gdk_window_set_background's second parameter type. */
+        GdkColor col_mutable = *col;
+        gdk_window_set_background(win, &col_mutable);
+    }
+#endif
 }
 
 void set_window_background(struct gui_data *inst)
 {
-    if (inst->area && inst->area->window)
-       gdk_window_set_background(inst->area->window, &inst->cols[258]);
-    if (inst->window && inst->window->window)
-       gdk_window_set_background(inst->window->window, &inst->cols[258]);
+    if (inst->area && gtk_widget_get_window(inst->area))
+       set_gdk_window_background(gtk_widget_get_window(inst->area),
+                                  &inst->cols[258]);
+    if (inst->window && gtk_widget_get_window(inst->window))
+       set_gdk_window_background(gtk_widget_get_window(inst->window),
+                                  &inst->cols[258]);
 }
 
 void palette_set(void *frontend, int n, int r, int g, int b)
@@ -1703,16 +2414,17 @@ void palette_reset(void *frontend)
        0, 8, 1, 9, 2, 10, 3, 11,
        4, 12, 5, 13, 6, 14, 7, 15
     };
-    gboolean success[NALLCOLOURS];
     int i;
 
     assert(lenof(ww) == NCFGCOLOURS);
 
+#if !GTK_CHECK_VERSION(3,0,0)
     if (!inst->colmap) {
        inst->colmap = gdk_colormap_get_system();
     } else {
        gdk_colormap_free_colors(inst->colmap, inst->cols, NALLCOLOURS);
     }
+#endif
 
     for (i = 0; i < NCFGCOLOURS; i++) {
        inst->cols[ww[i]].red =
@@ -1737,73 +2449,219 @@ void palette_reset(void *frontend)
        }
     }
 
-    gdk_colormap_alloc_colors(inst->colmap, inst->cols, NALLCOLOURS,
-                             FALSE, TRUE, success);
-    for (i = 0; i < NALLCOLOURS; i++) {
-       if (!success[i])
-           g_error("%s: couldn't allocate colour %d (#%02x%02x%02x)\n",
-                    appname, i,
-                   conf_get_int_int(inst->conf, CONF_colours, i*3+0),
-                   conf_get_int_int(inst->conf, CONF_colours, i*3+1),
-                   conf_get_int_int(inst->conf, CONF_colours, i*3+2));
+#if !GTK_CHECK_VERSION(3,0,0)
+    {
+        gboolean success[NALLCOLOURS];
+        gdk_colormap_alloc_colors(inst->colmap, inst->cols, NALLCOLOURS,
+                                  FALSE, TRUE, success);
+        for (i = 0; i < NALLCOLOURS; i++) {
+            if (!success[i])
+                g_error("%s: couldn't allocate colour %d (#%02x%02x%02x)\n",
+                        appname, i,
+                        conf_get_int_int(inst->conf, CONF_colours, i*3+0),
+                        conf_get_int_int(inst->conf, CONF_colours, i*3+1),
+                        conf_get_int_int(inst->conf, CONF_colours, i*3+2));
+        }
     }
+#endif
 
     /* Since Default Background may have changed, ensure that space
      * between text area and window border is refreshed. */
     set_window_background(inst);
-    if (inst->area && inst->area->window) {
+    if (inst->area && gtk_widget_get_window(inst->area)) {
        draw_backing_rect(inst);
        gtk_widget_queue_draw(inst->area);
     }
 }
 
-/* Ensure that all the cut buffers exist - according to the ICCCM, we must
- * do this before we start using cut buffers.
+#ifdef JUST_USE_GTK_CLIPBOARD_UTF8
+
+/* ----------------------------------------------------------------------
+ * Clipboard handling, using the high-level GtkClipboard interface in
+ * as hands-off a way as possible. We write and read the clipboard as
+ * UTF-8 text, and let GTK deal with converting to any other text
+ * formats it feels like.
+ */
+
+void init_clipboard(struct gui_data *inst)
+{
+    inst->clipboard = gtk_clipboard_get_for_display(gdk_display_get_default(),
+                                                    DEFAULT_CLIPBOARD);
+}
+
+/*
+ * Because calling gtk_clipboard_set_with_data triggers a call to the
+ * clipboard_clear function from the last time, we need to arrange a
+ * way to distinguish a real call to clipboard_clear for the _new_
+ * instance of the clipboard data from the leftover call for the
+ * outgoing one. We do this by setting the user data field in our
+ * gtk_clipboard_set_with_data() call, instead of the obvious pointer
+ * to 'inst', to one of these.
+ */
+struct clipboard_data_instance {
+    struct gui_data *inst;
+    char *pasteout_data_utf8;
+    int pasteout_data_utf8_len;
+};
+
+static void clipboard_provide_data(GtkClipboard *clipboard,
+                                   GtkSelectionData *selection_data,
+                                   guint info, gpointer data)
+{
+    struct clipboard_data_instance *cdi =
+        (struct clipboard_data_instance *)data;
+    struct gui_data *inst = cdi->inst;
+
+    if (inst->current_cdi == cdi) {
+        gtk_selection_data_set_text(selection_data, cdi->pasteout_data_utf8,
+                                    cdi->pasteout_data_utf8_len);
+    }
+}
+
+static void clipboard_clear(GtkClipboard *clipboard, gpointer data)
+{
+    struct clipboard_data_instance *cdi =
+        (struct clipboard_data_instance *)data;
+    struct gui_data *inst = cdi->inst;
+
+    if (inst->current_cdi == cdi) {
+        term_deselect(inst->term);
+        inst->current_cdi = NULL;
+    }
+    sfree(cdi->pasteout_data_utf8);
+    sfree(cdi);
+}
+
+void write_clip(void *frontend, wchar_t *data, int *attr, int len,
+                int must_deselect)
+{
+    struct gui_data *inst = (struct gui_data *)frontend;
+    struct clipboard_data_instance *cdi;
+
+    if (inst->direct_to_font) {
+        /* In this clipboard mode, we just can't paste if we're in
+         * direct-to-font mode. Fortunately, that shouldn't be
+         * important, because we'll only use this clipboard handling
+         * code on systems where that kind of font doesn't exist
+         * anyway. */
+        return;
+    }
+
+    cdi = snew(struct clipboard_data_instance);
+    cdi->inst = inst;
+    inst->current_cdi = cdi;
+    cdi->pasteout_data_utf8 = snewn(len*6, char);
+    {
+        const wchar_t *tmp = data;
+        int tmplen = len;
+        cdi->pasteout_data_utf8_len =
+            charset_from_unicode(&tmp, &tmplen, cdi->pasteout_data_utf8,
+                                 len*6, CS_UTF8, NULL, NULL, 0);
+    }
+
+    /*
+     * It would be nice to just call gtk_clipboard_set_text() in place
+     * of all of the faffing below. Unfortunately, that won't give me
+     * access to the clipboard-clear event, which we use to visually
+     * deselect text in the terminal.
+     */
+    {
+        GtkTargetList *targetlist;
+        GtkTargetEntry *targettable;
+        gint n_targets;
+
+        targetlist = gtk_target_list_new(NULL, 0);
+        gtk_target_list_add_text_targets(targetlist, 0);
+        targettable = gtk_target_table_new_from_list(targetlist, &n_targets);
+        gtk_clipboard_set_with_data(inst->clipboard, targettable, n_targets,
+                                    clipboard_provide_data, clipboard_clear,
+                                    cdi);
+        gtk_target_table_free(targettable, n_targets);
+        gtk_target_list_unref(targetlist);
+    }
+}
+
+static void clipboard_text_received(GtkClipboard *clipboard,
+                                    const gchar *text, gpointer data)
+{
+    struct gui_data *inst = (struct gui_data *)data;
+    int length;
+
+    if (!text)
+        return;
+
+    length = strlen(text);
+
+    if (inst->pastein_data)
+       sfree(inst->pastein_data);
+
+    inst->pastein_data = snewn(length, wchar_t);
+    inst->pastein_data_len = mb_to_wc(CS_UTF8, 0, text, length,
+                                      inst->pastein_data, length);
+
+    term_do_paste(inst->term);
+}
+
+void request_paste(void *frontend)
+{
+    struct gui_data *inst = (struct gui_data *)frontend;
+    gtk_clipboard_request_text(inst->clipboard, clipboard_text_received, inst);
+}
+
+#else /* JUST_USE_GTK_CLIPBOARD_UTF8 */
+
+/* ----------------------------------------------------------------------
+ * Clipboard handling for X, using the low-level gtk_selection_*
+ * interface, handling conversions to fiddly things like compound text
+ * ourselves, and storing in X cut buffers too.
+ *
+ * This version of the clipboard code has to be kept around for GTK1,
+ * which doesn't have the higher-level GtkClipboard interface at all.
+ * And since it works on GTK2 and GTK3 too and has had a good few
+ * years of shakedown and bug fixing, we might as well keep using it
+ * where it's applicable.
+ *
+ * It's _possible_ that we might be able to replicate all the
+ * important wrinkles of this code in GtkClipboard. (In particular,
+ * cut buffers or local analogue look as if they might be accessible
+ * via gtk_clipboard_set_can_store(), and delivering text in
+ * non-Unicode formats only in the direct-to-font case ought to be
+ * possible if we can figure out the right set of things to put in the
+ * GtkTargetList.) But that work can wait until there's a need for it!
  */
-void init_cutbuffers()
-{
-    unsigned char empty[] = "";
-    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
-                   XA_CUT_BUFFER0, XA_STRING, 8, PropModeAppend, empty, 0);
-    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
-                   XA_CUT_BUFFER1, XA_STRING, 8, PropModeAppend, empty, 0);
-    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
-                   XA_CUT_BUFFER2, XA_STRING, 8, PropModeAppend, empty, 0);
-    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
-                   XA_CUT_BUFFER3, XA_STRING, 8, PropModeAppend, empty, 0);
-    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
-                   XA_CUT_BUFFER4, XA_STRING, 8, PropModeAppend, empty, 0);
-    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
-                   XA_CUT_BUFFER5, XA_STRING, 8, PropModeAppend, empty, 0);
-    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
-                   XA_CUT_BUFFER6, XA_STRING, 8, PropModeAppend, empty, 0);
-    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
-                   XA_CUT_BUFFER7, XA_STRING, 8, PropModeAppend, empty, 0);
-}
 
 /* Store the data in a cut-buffer. */
-void store_cutbuffer(char * ptr, int len)
+static void store_cutbuffer(char * ptr, int len)
 {
+#ifndef NOT_X_WINDOWS
+    Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
     /* ICCCM says we must rotate the buffers before storing to buffer 0. */
-    XRotateBuffers(GDK_DISPLAY(), 1);
-    XStoreBytes(GDK_DISPLAY(), ptr, len);
+    XRotateBuffers(disp, 1);
+    XStoreBytes(disp, ptr, len);
+#endif
 }
 
 /* Retrieve data from a cut-buffer.
  * Returned data needs to be freed with XFree().
  */
-char * retrieve_cutbuffer(int * nbytes)
+static char *retrieve_cutbuffer(int *nbytes)
 {
+#ifndef NOT_X_WINDOWS
+    Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
     char * ptr;
-    ptr = XFetchBytes(GDK_DISPLAY(), nbytes);
+    ptr = XFetchBytes(disp, nbytes);
     if (*nbytes <= 0 && ptr != 0) {
        XFree(ptr);
        ptr = 0;
     }
     return ptr;
+#else
+    return NULL;
+#endif
 }
 
-void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_deselect)
+void write_clip(void *frontend, wchar_t *data, int *attr, int len,
+                int must_deselect)
 {
     struct gui_data *inst = (struct gui_data *)frontend;
     if (inst->pasteout_data)
@@ -1820,8 +2678,11 @@ void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_des
     if (!inst->direct_to_font) {
        const wchar_t *tmp = data;
        int tmplen = len;
+#ifndef NOT_X_WINDOWS
        XTextProperty tp;
        char *list[1];
+        Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+#endif
 
        inst->pasteout_data_utf8 = snewn(len*6, char);
        inst->pasteout_data_utf8_len = len*6;
@@ -1842,14 +2703,17 @@ void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_des
        /*
         * Now let Xlib convert our UTF-8 data into compound text.
         */
+#ifndef NOT_X_WINDOWS
        list[0] = inst->pasteout_data_utf8;
-       if (Xutf8TextListToTextProperty(GDK_DISPLAY(), list, 1,
+       if (Xutf8TextListToTextProperty(disp, list, 1,
                                        XCompoundTextStyle, &tp) == 0) {
            inst->pasteout_data_ctext = snewn(tp.nitems+1, char);
            memcpy(inst->pasteout_data_ctext, tp.value, tp.nitems);
            inst->pasteout_data_ctext_len = tp.nitems;
            XFree(tp.value);
-       } else {
+       } else
+#endif
+        {
             inst->pasteout_data_ctext = NULL;
             inst->pasteout_data_ctext_len = 0;
         }
@@ -1895,26 +2759,27 @@ void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_des
        term_deselect(inst->term);
 }
 
-void selection_get(GtkWidget *widget, GtkSelectionData *seldata,
-                  guint info, guint time_stamp, gpointer data)
+static void selection_get(GtkWidget *widget, GtkSelectionData *seldata,
+                          guint info, guint time_stamp, gpointer data)
 {
     struct gui_data *inst = (struct gui_data *)data;
-    if (seldata->target == utf8_string_atom)
-       gtk_selection_data_set(seldata, seldata->target, 8,
-                              (unsigned char *)inst->pasteout_data_utf8,
+    GdkAtom target = gtk_selection_data_get_target(seldata);
+    if (target == utf8_string_atom)
+       gtk_selection_data_set(seldata, target, 8,
+                               (unsigned char *)inst->pasteout_data_utf8,
                               inst->pasteout_data_utf8_len);
-    else if (seldata->target == compound_text_atom)
-       gtk_selection_data_set(seldata, seldata->target, 8,
-                              (unsigned char *)inst->pasteout_data_ctext,
+    else if (target == compound_text_atom)
+       gtk_selection_data_set(seldata, target, 8,
+                               (unsigned char *)inst->pasteout_data_ctext,
                               inst->pasteout_data_ctext_len);
     else
-       gtk_selection_data_set(seldata, seldata->target, 8,
-                              (unsigned char *)inst->pasteout_data,
+       gtk_selection_data_set(seldata, target, 8,
+                               (unsigned char *)inst->pasteout_data,
                               inst->pasteout_data_len);
 }
 
-gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata,
-                    gpointer data)
+static gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata,
+                            gpointer data)
 {
     struct gui_data *inst = (struct gui_data *)data;
 
@@ -1965,21 +2830,24 @@ void request_paste(void *frontend)
     }
 }
 
-gint idle_paste_func(gpointer data);   /* forward ref */
-
-void selection_received(GtkWidget *widget, GtkSelectionData *seldata,
-                       guint time, gpointer data)
+static void selection_received(GtkWidget *widget, GtkSelectionData *seldata,
+                               guint time, gpointer data)
 {
     struct gui_data *inst = (struct gui_data *)data;
-    XTextProperty tp;
-    char **list;
     char *text;
-    int length, count, ret;
+    int length;
+#ifndef NOT_X_WINDOWS
+    char **list;
     int free_list_required = 0;
     int free_required = 0;
+#endif
     int charset;
+    GdkAtom seldata_target = gtk_selection_data_get_target(seldata);
+    GdkAtom seldata_type = gtk_selection_data_get_data_type(seldata);
+    const guchar *seldata_data = gtk_selection_data_get_data(seldata);
+    gint seldata_length = gtk_selection_data_get_length(seldata);
 
-    if (seldata->target == utf8_string_atom && seldata->length <= 0) {
+    if (seldata_target == utf8_string_atom && seldata_length <= 0) {
        /*
         * Failed to get a UTF-8 selection string. Try compound
         * text next.
@@ -1990,7 +2858,7 @@ void selection_received(GtkWidget *widget, GtkSelectionData *seldata,
        return;
     }
 
-    if (seldata->target == compound_text_atom && seldata->length <= 0) {
+    if (seldata_target == compound_text_atom && seldata_length <= 0) {
        /*
         * Failed to get UTF-8 or compound text. Try an ordinary
         * string.
@@ -2005,16 +2873,17 @@ void selection_received(GtkWidget *widget, GtkSelectionData *seldata,
      * If we have data, but it's not of a type we can deal with,
      * we have to ignore the data.
      */
-    if (seldata->length > 0 &&
-       seldata->type != GDK_SELECTION_TYPE_STRING &&
-       seldata->type != compound_text_atom &&
-       seldata->type != utf8_string_atom)
+    if (seldata_length > 0 &&
+       seldata_type != GDK_SELECTION_TYPE_STRING &&
+       seldata_type != compound_text_atom &&
+       seldata_type != utf8_string_atom)
        return;
 
     /*
      * If we have no data, try looking in a cut buffer.
      */
-    if (seldata->length <= 0) {
+    if (seldata_length <= 0) {
+#ifndef NOT_X_WINDOWS
        text = retrieve_cutbuffer(&length);
        if (length == 0)
            return;
@@ -2022,18 +2891,32 @@ void selection_received(GtkWidget *widget, GtkSelectionData *seldata,
         * source, so use that as a de-facto standard. */
        charset = CS_ISO8859_1;
        free_required = 1;
+#else
+        return;
+#endif
     } else {
        /*
         * Convert COMPOUND_TEXT into UTF-8.
         */
-       if (seldata->type == compound_text_atom) {
-           tp.value = seldata->data;
-           tp.encoding = (Atom) seldata->type;
-           tp.format = seldata->format;
-           tp.nitems = seldata->length;
-           ret = Xutf8TextPropertyToTextList(GDK_DISPLAY(), &tp,
-                                             &list, &count);
-           if (ret != 0 || count != 1) {
+       if (seldata_type == compound_text_atom) {
+#ifndef NOT_X_WINDOWS
+            XTextProperty tp;
+            int ret, count;
+            Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+
+           tp.value = (unsigned char *)seldata_data;
+           tp.encoding = (Atom) seldata_type;
+           tp.format = gtk_selection_data_get_format(seldata);
+           tp.nitems = seldata_length;
+           ret = Xutf8TextPropertyToTextList(disp, &tp, &list, &count);
+           if (ret == 0 && count == 1) {
+                text = list[0];
+                length = strlen(list[0]);
+                charset = CS_UTF8;
+                free_list_required = 1;
+            } else
+#endif
+            {
                /*
                 * Compound text failed; fall back to STRING.
                 */
@@ -2042,14 +2925,10 @@ void selection_received(GtkWidget *widget, GtkSelectionData *seldata,
                                      inst->input_event_time);
                return;
            }
-           text = list[0];
-           length = strlen(list[0]);
-           charset = CS_UTF8;
-           free_list_required = 1;
        } else {
-           text = (char *)seldata->data;
-           length = seldata->length;
-           charset = (seldata->type == utf8_string_atom ?
+           text = (char *)seldata_data;
+           length = seldata_length;
+           charset = (seldata_type == utf8_string_atom ?
                       CS_UTF8 : inst->ucsdata.line_codepage);
        }
     }
@@ -2065,12 +2944,56 @@ void selection_received(GtkWidget *widget, GtkSelectionData *seldata,
 
     term_do_paste(inst->term);
 
+#ifndef NOT_X_WINDOWS
     if (free_list_required)
        XFreeStringList(list);
     if (free_required)
        XFree(text);
+#endif
+}
+
+void init_clipboard(struct gui_data *inst)
+{
+#ifndef NOT_X_WINDOWS
+    /*
+     * Ensure that all the cut buffers exist - according to the ICCCM,
+     * we must do this before we start using cut buffers.
+     */
+    unsigned char empty[] = "";
+    Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+    XChangeProperty(disp, GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER0, XA_STRING, 8, PropModeAppend, empty, 0);
+    XChangeProperty(disp, GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER1, XA_STRING, 8, PropModeAppend, empty, 0);
+    XChangeProperty(disp, GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER2, XA_STRING, 8, PropModeAppend, empty, 0);
+    XChangeProperty(disp, GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER3, XA_STRING, 8, PropModeAppend, empty, 0);
+    XChangeProperty(disp, GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER4, XA_STRING, 8, PropModeAppend, empty, 0);
+    XChangeProperty(disp, GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER5, XA_STRING, 8, PropModeAppend, empty, 0);
+    XChangeProperty(disp, GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER6, XA_STRING, 8, PropModeAppend, empty, 0);
+    XChangeProperty(disp, GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER7, XA_STRING, 8, PropModeAppend, empty, 0);
+#endif
+
+    g_signal_connect(G_OBJECT(inst->area), "selection_received",
+                     G_CALLBACK(selection_received), inst);
+    g_signal_connect(G_OBJECT(inst->area), "selection_get",
+                     G_CALLBACK(selection_get), inst);
+    g_signal_connect(G_OBJECT(inst->area), "selection_clear_event",
+                     G_CALLBACK(selection_clear), inst);
 }
 
+/*
+ * End of selection/clipboard handling.
+ * ----------------------------------------------------------------------
+ */
+
+#endif /* JUST_USE_GTK_CLIPBOARD_UTF8 */
+
 void get_clip(void *frontend, wchar_t ** p, int *len)
 {
     struct gui_data *inst = (struct gui_data *)frontend;
@@ -2090,7 +3013,8 @@ static void set_window_titles(struct gui_data *inst)
      */
     gtk_window_set_title(GTK_WINDOW(inst->window), inst->wintitle);
     if (!conf_get_int(inst->conf, CONF_win_name_always))
-       gdk_window_set_icon_name(inst->window->window, inst->icontitle);
+       gdk_window_set_icon_name(gtk_widget_get_window(inst->window),
+                                 inst->icontitle);
 }
 
 void set_title(void *frontend, char *title)
@@ -2124,12 +3048,12 @@ void set_sbar(void *frontend, int total, int start, int page)
     struct gui_data *inst = (struct gui_data *)frontend;
     if (!conf_get_int(inst->conf, CONF_scrollbar))
        return;
-    inst->sbar_adjust->lower = 0;
-    inst->sbar_adjust->upper = total;
-    inst->sbar_adjust->value = start;
-    inst->sbar_adjust->page_size = page;
-    inst->sbar_adjust->step_increment = 1;
-    inst->sbar_adjust->page_increment = page/2;
+    gtk_adjustment_set_lower(inst->sbar_adjust, 0);
+    gtk_adjustment_set_upper(inst->sbar_adjust, total);
+    gtk_adjustment_set_value(inst->sbar_adjust, start);
+    gtk_adjustment_set_page_size(inst->sbar_adjust, page);
+    gtk_adjustment_set_step_increment(inst->sbar_adjust, 1);
+    gtk_adjustment_set_page_increment(inst->sbar_adjust, page/2);
     inst->ignore_sbar = TRUE;
     gtk_adjustment_changed(inst->sbar_adjust);
     inst->ignore_sbar = FALSE;
@@ -2142,7 +3066,7 @@ void scrollbar_moved(GtkAdjustment *adj, gpointer data)
     if (!conf_get_int(inst->conf, CONF_scrollbar))
        return;
     if (!inst->ignore_sbar)
-       term_scroll(inst->term, 1, (int)adj->value);
+       term_scroll(inst->term, 1, (int)gtk_adjustment_get_value(adj));
 }
 
 void sys_cursor(void *frontend, int x, int y)
@@ -2167,11 +3091,8 @@ void do_beep(void *frontend, int mode)
 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.
-     * 
-     * FIXME: but is that also true of Pango?
+     * In this front end, double-width characters are handled using a
+     * separate font, so this can safely just return 1 always.
      */
     return 1;
 }
@@ -2181,12 +3102,30 @@ Context get_ctx(void *frontend)
     struct gui_data *inst = (struct gui_data *)frontend;
     struct draw_ctx *dctx;
 
-    if (!inst->area->window)
+    if (!gtk_widget_get_window(inst->area))
        return NULL;
 
     dctx = snew(struct draw_ctx);
     dctx->inst = inst;
-    dctx->gc = gdk_gc_new(inst->area->window);
+    dctx->uctx.type = inst->drawtype;
+#ifdef DRAW_TEXT_GDK
+    if (dctx->uctx.type == DRAWTYPE_GDK) {
+        /* If we're doing GDK-based drawing, then we also expect
+         * inst->pixmap to exist. */
+        dctx->uctx.u.gdk.target = inst->pixmap;
+        dctx->uctx.u.gdk.gc = gdk_gc_new(gtk_widget_get_window(inst->area));
+    }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+    if (dctx->uctx.type == DRAWTYPE_CAIRO) {
+        dctx->uctx.u.cairo.widget = GTK_WIDGET(inst->area);
+        /* If we're doing Cairo drawing, we expect inst->surface to
+         * exist, and we draw to that first, regardless of whether we
+         * subsequently copy the results to inst->pixmap. */
+        dctx->uctx.u.cairo.cr = cairo_create(inst->surface);
+        cairo_setup_dctx(dctx);
+    }
+#endif
     return dctx;
 }
 
@@ -2194,11 +3133,252 @@ void free_ctx(Context ctx)
 {
     struct draw_ctx *dctx = (struct draw_ctx *)ctx;
     /* struct gui_data *inst = dctx->inst; */
-    GdkGC *gc = dctx->gc;
-    gdk_gc_unref(gc);
+#ifdef DRAW_TEXT_GDK
+    if (dctx->uctx.type == DRAWTYPE_GDK) {
+        gdk_gc_unref(dctx->uctx.u.gdk.gc);
+    }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+    if (dctx->uctx.type == DRAWTYPE_CAIRO) {
+        cairo_destroy(dctx->uctx.u.cairo.cr);
+    }
+#endif
     sfree(dctx);
 }
 
+
+static void draw_update(struct draw_ctx *dctx, int x, int y, int w, int h)
+{
+#if defined DRAW_TEXT_CAIRO && !defined NO_BACKING_PIXMAPS
+    if (dctx->uctx.type == DRAWTYPE_CAIRO) {
+        /*
+         * If inst->surface and inst->pixmap both exist, then we've
+         * just drawn new content to the former which we must copy to
+         * the latter.
+         */
+        cairo_t *cr = gdk_cairo_create(dctx->inst->pixmap);
+        cairo_set_source_surface(cr, dctx->inst->surface, 0, 0);
+        cairo_rectangle(cr, x, y, w, h);
+        cairo_fill(cr);
+        cairo_destroy(cr);
+    }
+#endif
+
+    /*
+     * Now we just queue a window redraw, which will cause
+     * inst->surface or inst->pixmap (whichever is appropriate for our
+     * compile mode) to be copied to the real window when we receive
+     * the resulting "expose" or "draw" event.
+     *
+     * Amazingly, this one API call is actually valid in all versions
+     * of GTK :-)
+     */
+    gtk_widget_queue_draw_area(dctx->inst->area, x, y, w, h);
+}
+
+static void draw_set_colour(struct draw_ctx *dctx, int col)
+{
+#ifdef DRAW_TEXT_GDK
+    if (dctx->uctx.type == DRAWTYPE_GDK) {
+        gdk_gc_set_foreground(dctx->uctx.u.gdk.gc, &dctx->inst->cols[col]);
+    }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+    if (dctx->uctx.type == DRAWTYPE_CAIRO) {
+        cairo_set_source_rgb(dctx->uctx.u.cairo.cr,
+                             dctx->inst->cols[col].red / 65535.0,
+                             dctx->inst->cols[col].green / 65535.0,
+                             dctx->inst->cols[col].blue / 65535.0);
+    }
+#endif
+}
+
+static void draw_rectangle(struct draw_ctx *dctx, int filled,
+                           int x, int y, int w, int h)
+{
+#ifdef DRAW_TEXT_GDK
+    if (dctx->uctx.type == DRAWTYPE_GDK) {
+        gdk_draw_rectangle(dctx->uctx.u.gdk.target, dctx->uctx.u.gdk.gc,
+                           filled, x, y, w, h);
+    }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+    if (dctx->uctx.type == DRAWTYPE_CAIRO) {
+        cairo_new_path(dctx->uctx.u.cairo.cr);
+        if (filled) {
+            cairo_rectangle(dctx->uctx.u.cairo.cr, x, y, w, h);
+            cairo_fill(dctx->uctx.u.cairo.cr);
+        } else {
+            cairo_rectangle(dctx->uctx.u.cairo.cr,
+                            x + 0.5, y + 0.5, w, h);
+            cairo_close_path(dctx->uctx.u.cairo.cr);
+            cairo_stroke(dctx->uctx.u.cairo.cr);
+        }
+    }
+#endif
+}
+
+static void draw_clip(struct draw_ctx *dctx, int x, int y, int w, int h)
+{
+#ifdef DRAW_TEXT_GDK
+    if (dctx->uctx.type == DRAWTYPE_GDK) {
+       GdkRectangle r;
+
+       r.x = x;
+       r.y = y;
+       r.width = w;
+       r.height = h;
+
+        gdk_gc_set_clip_rectangle(dctx->uctx.u.gdk.gc, &r);
+    }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+    if (dctx->uctx.type == DRAWTYPE_CAIRO) {
+        cairo_reset_clip(dctx->uctx.u.cairo.cr);
+        cairo_new_path(dctx->uctx.u.cairo.cr);
+        cairo_rectangle(dctx->uctx.u.cairo.cr, x, y, w, h);
+        cairo_clip(dctx->uctx.u.cairo.cr);
+    }
+#endif
+}
+
+static void draw_point(struct draw_ctx *dctx, int x, int y)
+{
+#ifdef DRAW_TEXT_GDK
+    if (dctx->uctx.type == DRAWTYPE_GDK) {
+        gdk_draw_point(dctx->uctx.u.gdk.target, dctx->uctx.u.gdk.gc, x, y);
+    }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+    if (dctx->uctx.type == DRAWTYPE_CAIRO) {
+        cairo_new_path(dctx->uctx.u.cairo.cr);
+        cairo_rectangle(dctx->uctx.u.cairo.cr, x, y, 1, 1);
+        cairo_fill(dctx->uctx.u.cairo.cr);
+    }
+#endif
+}
+
+static void draw_line(struct draw_ctx *dctx, int x0, int y0, int x1, int y1)
+{
+#ifdef DRAW_TEXT_GDK
+    if (dctx->uctx.type == DRAWTYPE_GDK) {
+        gdk_draw_line(dctx->uctx.u.gdk.target, dctx->uctx.u.gdk.gc,
+                      x0, y0, x1, y1);
+    }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+    if (dctx->uctx.type == DRAWTYPE_CAIRO) {
+        cairo_new_path(dctx->uctx.u.cairo.cr);
+        cairo_move_to(dctx->uctx.u.cairo.cr, x0 + 0.5, y0 + 0.5);
+        cairo_line_to(dctx->uctx.u.cairo.cr, x1 + 0.5, y1 + 0.5);
+        cairo_stroke(dctx->uctx.u.cairo.cr);
+    }
+#endif
+}
+
+static void draw_stretch_before(struct draw_ctx *dctx, int x, int y,
+                                int w, int wdouble,
+                                int h, int hdouble, int hbothalf)
+{
+#ifdef DRAW_TEXT_CAIRO
+    if (dctx->uctx.type == DRAWTYPE_CAIRO) {
+        cairo_matrix_t matrix;
+
+        matrix.xy = 0;
+        matrix.yx = 0;
+
+        if (wdouble) {
+            matrix.xx = 2;
+            matrix.x0 = -x;
+        } else {
+            matrix.xx = 1;
+            matrix.x0 = 0;
+        }
+
+        if (hdouble) {
+            matrix.yy = 2;
+            if (hbothalf) {
+                matrix.y0 = -(y+h);
+            } else {
+                matrix.y0 = -y;
+            }
+        } else {
+            matrix.yy = 1;
+            matrix.y0 = 0;
+        }
+        cairo_transform(dctx->uctx.u.cairo.cr, &matrix);
+    }
+#endif
+}
+
+static void draw_stretch_after(struct draw_ctx *dctx, int x, int y,
+                               int w, int wdouble,
+                               int h, int hdouble, int hbothalf)
+{
+#ifdef DRAW_TEXT_GDK
+#ifndef NO_BACKING_PIXMAPS
+    if (dctx->uctx.type == DRAWTYPE_GDK) {
+       /*
+        * I can't find any plausible StretchBlt equivalent in the X
+        * server, so I'm going to do this the slow and painful way.
+        * This will involve repeated calls to gdk_draw_pixmap() to
+        * stretch the text horizontally. It's O(N^2) in time and O(N)
+        * in network bandwidth, but you try thinking of a better way.
+        * :-(
+        */
+       int i;
+        if (wdouble) {
+            for (i = 0; i < w; i++) {
+                gdk_draw_pixmap(dctx->uctx.u.gdk.target,
+                                dctx->uctx.u.gdk.gc,
+                                dctx->uctx.u.gdk.target,
+                                x + 2*i, y,
+                                x + 2*i+1, y,
+                                w - i, h);
+            }
+            w *= 2;
+        }
+
+       if (hdouble) {
+           int dt, db;
+           /* Now stretch vertically, in the same way. */
+           if (hbothalf)
+               dt = 0, db = 1;
+           else
+               dt = 1, db = 0;
+           for (i = 0; i < h; i += 2) {
+               gdk_draw_pixmap(dctx->uctx.u.gdk.target,
+                                dctx->uctx.u.gdk.gc,
+                                dctx->uctx.u.gdk.target,
+                                x, y + dt*i + db,
+                               x, y + dt*(i+1),
+                               w, h-i-1);
+           }
+       }
+    }
+#else
+#error No way to implement stretching in GDK without a reliable backing pixmap
+#endif
+#endif /* DRAW_TEXT_GDK */
+#ifdef DRAW_TEXT_CAIRO
+    if (dctx->uctx.type == DRAWTYPE_CAIRO) {
+        cairo_set_matrix(dctx->uctx.u.cairo.cr,
+                         &dctx->uctx.u.cairo.origmatrix);
+    }
+#endif
+}
+
+static void draw_backing_rect(struct gui_data *inst)
+{
+    struct draw_ctx *dctx = get_ctx(inst);
+    int w = inst->width * inst->font_width + 2*inst->window_border;
+    int h = inst->height * inst->font_height + 2*inst->window_border;
+    draw_set_colour(dctx, 258);
+    draw_rectangle(dctx, 1, 0, 0, w, h);
+    draw_update(dctx, 0, 0, w, h);
+    free_ctx(dctx);
+}
+
 /*
  * Draw a line of text in the window, at given character
  * coordinates, in given attributes.
@@ -2210,10 +3390,10 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len,
 {
     struct draw_ctx *dctx = (struct draw_ctx *)ctx;
     struct gui_data *inst = dctx->inst;
-    GdkGC *gc = dctx->gc;
-    int ncombining, combining;
+    int ncombining;
     int nfg, nbg, t, fontid, shadow, rlen, widefactor, bold;
-    int monochrome = gtk_widget_get_visual(inst->area)->depth == 1;
+    int monochrome =
+        gdk_visual_get_depth(gtk_widget_get_visual(inst->area)) == 1;
 
     if (attr & TATTR_COMBINING) {
        ncombining = len;
@@ -2284,28 +3464,43 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len,
     } else
        rlen = len;
 
-    {
-       GdkRectangle r;
+    draw_clip(dctx,
+              x*inst->font_width+inst->window_border,
+              y*inst->font_height+inst->window_border,
+              rlen*widefactor*inst->font_width,
+              inst->font_height);
 
-       r.x = x*inst->font_width+inst->window_border;
-       r.y = y*inst->font_height+inst->window_border;
-       r.width = rlen*widefactor*inst->font_width;
-       r.height = inst->font_height;
-       gdk_gc_set_clip_rectangle(gc, &r);
+    if ((lattr & LATTR_MODE) != LATTR_NORM) {
+        draw_stretch_before(dctx,
+                            x*inst->font_width+inst->window_border,
+                            y*inst->font_height+inst->window_border,
+                            rlen*widefactor*inst->font_width, TRUE,
+                            inst->font_height,
+                            ((lattr & LATTR_MODE) != LATTR_WIDE),
+                            ((lattr & LATTR_MODE) == LATTR_BOT));
     }
 
-    gdk_gc_set_foreground(gc, &inst->cols[nbg]);
-    gdk_draw_rectangle(inst->pixmap, gc, 1,
-                      x*inst->font_width+inst->window_border,
-                      y*inst->font_height+inst->window_border,
-                      rlen*widefactor*inst->font_width, inst->font_height);
-
-    gdk_gc_set_foreground(gc, &inst->cols[nfg]);
-    for (combining = 0; combining < ncombining; combining++) {
-        unifont_draw_text(inst->pixmap, gc, inst->fonts[fontid],
+    draw_set_colour(dctx, nbg);
+    draw_rectangle(dctx, TRUE,
+                   x*inst->font_width+inst->window_border,
+                   y*inst->font_height+inst->window_border,
+                   rlen*widefactor*inst->font_width, inst->font_height);
+
+    draw_set_colour(dctx, nfg);
+    if (ncombining > 1) {
+        assert(len == 1);
+        unifont_draw_combining(&dctx->uctx, inst->fonts[fontid],
+                               x*inst->font_width+inst->window_border,
+                               (y*inst->font_height+inst->window_border+
+                                inst->fonts[0]->ascent),
+                               text, ncombining, widefactor > 1,
+                               bold, inst->font_width);
+    } else {
+        unifont_draw_text(&dctx->uctx, inst->fonts[fontid],
                           x*inst->font_width+inst->window_border,
-                          y*inst->font_height+inst->window_border+inst->fonts[0]->ascent,
-                          text + combining, len, widefactor > 1,
+                          (y*inst->font_height+inst->window_border+
+                           inst->fonts[0]->ascent),
+                          text, len, widefactor > 1,
                           bold, inst->font_width);
     }
 
@@ -2313,47 +3508,20 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len,
        int uheight = inst->fonts[0]->ascent + 1;
        if (uheight >= inst->font_height)
            uheight = inst->font_height - 1;
-       gdk_draw_line(inst->pixmap, gc, x*inst->font_width+inst->window_border,
-                     y*inst->font_height + uheight + inst->window_border,
-                     (x+len)*widefactor*inst->font_width-1+inst->window_border,
-                     y*inst->font_height + uheight + inst->window_border);
+        draw_line(dctx, x*inst->font_width+inst->window_border,
+                  y*inst->font_height + uheight + inst->window_border,
+                  (x+len)*widefactor*inst->font_width-1+inst->window_border,
+                  y*inst->font_height + uheight + inst->window_border);
     }
 
     if ((lattr & LATTR_MODE) != LATTR_NORM) {
-       /*
-        * I can't find any plausible StretchBlt equivalent in the
-        * X server, so I'm going to do this the slow and painful
-        * way. This will involve repeated calls to
-        * gdk_draw_pixmap() to stretch the text horizontally. It's
-        * O(N^2) in time and O(N) in network bandwidth, but you
-        * try thinking of a better way. :-(
-        */
-       int i;
-       for (i = 0; i < len * widefactor * inst->font_width; i++) {
-           gdk_draw_pixmap(inst->pixmap, gc, inst->pixmap,
-                           x*inst->font_width+inst->window_border + 2*i,
-                           y*inst->font_height+inst->window_border,
-                           x*inst->font_width+inst->window_border + 2*i+1,
-                           y*inst->font_height+inst->window_border,
-                           len * widefactor * inst->font_width - i, inst->font_height);
-       }
-       len *= 2;
-       if ((lattr & LATTR_MODE) != LATTR_WIDE) {
-           int dt, db;
-           /* Now stretch vertically, in the same way. */
-           if ((lattr & LATTR_MODE) == LATTR_BOT)
-               dt = 0, db = 1;
-           else
-               dt = 1, db = 0;
-           for (i = 0; i < inst->font_height; i+=2) {
-               gdk_draw_pixmap(inst->pixmap, gc, inst->pixmap,
-                               x*inst->font_width+inst->window_border,
-                               y*inst->font_height+inst->window_border+dt*i+db,
-                               x*inst->font_width+inst->window_border,
-                               y*inst->font_height+inst->window_border+dt*(i+1),
-                               len * widefactor * inst->font_width, inst->font_height-i-1);
-           }
-       }
+        draw_stretch_after(dctx,
+                           x*inst->font_width+inst->window_border,
+                           y*inst->font_height+inst->window_border,
+                           rlen*widefactor*inst->font_width, TRUE,
+                           inst->font_height,
+                           ((lattr & LATTR_MODE) != LATTR_WIDE),
+                           ((lattr & LATTR_MODE) == LATTR_BOT));
     }
 }
 
@@ -2362,7 +3530,6 @@ void do_text(Context ctx, int x, int y, wchar_t *text, int len,
 {
     struct draw_ctx *dctx = (struct draw_ctx *)ctx;
     struct gui_data *inst = dctx->inst;
-    GdkGC *gc = dctx->gc;
     int widefactor;
 
     do_text_internal(ctx, x, y, text, len, attr, lattr);
@@ -2382,12 +3549,10 @@ void do_text(Context ctx, int x, int y, wchar_t *text, int len,
        len *= 2;
     }
 
-    gdk_draw_pixmap(inst->area->window, gc, inst->pixmap,
-                   x*inst->font_width+inst->window_border,
-                   y*inst->font_height+inst->window_border,
-                   x*inst->font_width+inst->window_border,
-                   y*inst->font_height+inst->window_border,
-                   len*widefactor*inst->font_width, inst->font_height);
+    draw_update(dctx,
+                x*inst->font_width+inst->window_border,
+                y*inst->font_height+inst->window_border,
+                len*widefactor*inst->font_width, inst->font_height);
 }
 
 void do_cursor(Context ctx, int x, int y, wchar_t *text, int len,
@@ -2395,7 +3560,6 @@ void do_cursor(Context ctx, int x, int y, wchar_t *text, int len,
 {
     struct draw_ctx *dctx = (struct draw_ctx *)ctx;
     struct gui_data *inst = dctx->inst;
-    GdkGC *gc = dctx->gc;
 
     int active, passive, widefactor;
 
@@ -2436,11 +3600,12 @@ void do_cursor(Context ctx, int x, int y, wchar_t *text, int len,
         * if it's passive.
         */
        if (passive) {
-           gdk_gc_set_foreground(gc, &inst->cols[261]);
-           gdk_draw_rectangle(inst->pixmap, gc, 0,
-                              x*inst->font_width+inst->window_border,
-                              y*inst->font_height+inst->window_border,
-                              len*widefactor*inst->font_width-1, inst->font_height-1);
+            draw_set_colour(dctx, 261);
+            draw_rectangle(dctx, FALSE,
+                           x*inst->font_width+inst->window_border,
+                           y*inst->font_height+inst->window_border,
+                           len*widefactor*inst->font_width-1,
+                           inst->font_height-1);
        }
     } else {
        int uheight;
@@ -2474,27 +3639,25 @@ void do_cursor(Context ctx, int x, int y, wchar_t *text, int len,
            length = inst->font_height;
        }
 
-       gdk_gc_set_foreground(gc, &inst->cols[261]);
+        draw_set_colour(dctx, 261);
        if (passive) {
            for (i = 0; i < length; i++) {
                if (i % 2 == 0) {
-                   gdk_draw_point(inst->pixmap, gc, startx, starty);
+                   draw_point(dctx, startx, starty);
                }
                startx += dx;
                starty += dy;
            }
        } else if (active) {
-           gdk_draw_line(inst->pixmap, gc, startx, starty,
-                         startx + (length-1) * dx, starty + (length-1) * dy);
+           draw_line(dctx, startx, starty,
+                      startx + (length-1) * dx, starty + (length-1) * dy);
        } /* else no cursor (e.g., blinked off) */
     }
 
-    gdk_draw_pixmap(inst->area->window, gc, inst->pixmap,
-                   x*inst->font_width+inst->window_border,
-                   y*inst->font_height+inst->window_border,
-                   x*inst->font_width+inst->window_border,
-                   y*inst->font_height+inst->window_border,
-                   len*widefactor*inst->font_width, inst->font_height);
+    draw_update(dctx,
+                x*inst->font_width+inst->window_border,
+                y*inst->font_height+inst->window_border,
+                len*widefactor*inst->font_width, inst->font_height);
 
 #if GTK_CHECK_VERSION(2,0,0)
     {
@@ -2510,100 +3673,31 @@ void do_cursor(Context ctx, int x, int y, wchar_t *text, int len,
 
 GdkCursor *make_mouse_ptr(struct gui_data *inst, int cursor_val)
 {
-    /*
-     * Truly hideous hack: GTK doesn't allow us to set the mouse
-     * cursor foreground and background colours unless we've _also_
-     * created our own cursor from bitmaps. Therefore, I need to
-     * load the `cursor' font and draw glyphs from it on to
-     * pixmaps, in order to construct my cursors with the fg and bg
-     * I want. This is a gross hack, but it's more self-contained
-     * than linking in Xlib to find the X window handle to
-     * inst->area and calling XRecolorCursor, and it's more
-     * futureproof than hard-coding the shapes as bitmap arrays.
-     */
-    static GdkFont *cursor_font = NULL;
-    GdkPixmap *source, *mask;
-    GdkGC *gc;
-    GdkColor cfg = { 0, 65535, 65535, 65535 };
-    GdkColor cbg = { 0, 0, 0, 0 };
-    GdkColor dfg = { 1, 65535, 65535, 65535 };
-    GdkColor dbg = { 0, 0, 0, 0 };
-    GdkCursor *ret;
-    gchar text[2];
-    gint lb, rb, wid, asc, desc, w, h, x, y;
-
-    if (cursor_val == -2) {
-       gdk_font_unref(cursor_font);
-       return NULL;
-    }
-
-    if (cursor_val >= 0 && !cursor_font) {
-       cursor_font = gdk_font_load("cursor");
-       if (cursor_font)
-           gdk_font_ref(cursor_font);
-    }
-
-    /*
-     * Get the text extent of the cursor in question. We use the
-     * mask character for this, because it's typically slightly
-     * bigger than the main character.
-     */
-    if (cursor_val >= 0) {
-       text[1] = '\0';
-       text[0] = (char)cursor_val + 1;
-       gdk_string_extents(cursor_font, text, &lb, &rb, &wid, &asc, &desc);
-       w = rb-lb; h = asc+desc; x = -lb; y = asc;
-    } else {
-       w = h = 1;
-       x = y = 0;
-    }
-
-    source = gdk_pixmap_new(NULL, w, h, 1);
-    mask = gdk_pixmap_new(NULL, w, h, 1);
-
-    /*
-     * Draw the mask character on the mask pixmap.
-     */
-    gc = gdk_gc_new(mask);
-    gdk_gc_set_foreground(gc, &dbg);
-    gdk_draw_rectangle(mask, gc, 1, 0, 0, w, h);
-    if (cursor_val >= 0) {
-       text[1] = '\0';
-       text[0] = (char)cursor_val + 1;
-       gdk_gc_set_foreground(gc, &dfg);
-       gdk_draw_text(mask, cursor_font, gc, x, y, text, 1);
-    }
-    gdk_gc_unref(gc);
-
-    /*
-     * Draw the main character on the source pixmap.
-     */
-    gc = gdk_gc_new(source);
-    gdk_gc_set_foreground(gc, &dbg);
-    gdk_draw_rectangle(source, gc, 1, 0, 0, w, h);
-    if (cursor_val >= 0) {
-       text[1] = '\0';
-       text[0] = (char)cursor_val;
-       gdk_gc_set_foreground(gc, &dfg);
-       gdk_draw_text(source, cursor_font, gc, x, y, text, 1);
+    if (cursor_val == -1) {
+#if GTK_CHECK_VERSION(2,16,0)
+        cursor_val = GDK_BLANK_CURSOR;
+#else
+        /*
+         * Work around absence of GDK_BLANK_CURSOR by inventing a
+         * blank pixmap.
+         */
+        GdkCursor *ret;
+        GdkColor bg = { 0, 0, 0, 0 };
+        GdkPixmap *pm = gdk_pixmap_new(NULL, 1, 1, 1);
+        GdkGC *gc = gdk_gc_new(pm);
+        gdk_gc_set_foreground(gc, &bg);
+        gdk_draw_rectangle(pm, gc, 1, 0, 0, 1, 1);
+        gdk_gc_unref(gc);
+        ret = gdk_cursor_new_from_pixmap(pm, pm, &bg, &bg, 1, 1);
+        gdk_pixmap_unref(pm);
+        return ret;
+#endif
     }
-    gdk_gc_unref(gc);
-
-    /*
-     * Create the cursor.
-     */
-    ret = gdk_cursor_new_from_pixmap(source, mask, &cfg, &cbg, x, y);
-
-    /*
-     * Clean up.
-     */
-    gdk_pixmap_unref(source);
-    gdk_pixmap_unref(mask);
 
-    return ret;
+    return gdk_cursor_new(cursor_val);
 }
 
-void modalfatalbox(char *p, ...)
+void modalfatalbox(const char *p, ...)
 {
     va_list ap;
     fprintf(stderr, "FATAL ERROR: ");
@@ -2614,7 +3708,7 @@ void modalfatalbox(char *p, ...)
     exit(1);
 }
 
-void cmdline_error(char *p, ...)
+void cmdline_error(const char *p, ...)
 {
     va_list ap;
     fprintf(stderr, "%s: ", appname);
@@ -2625,16 +3719,18 @@ void cmdline_error(char *p, ...)
     exit(1);
 }
 
-char *get_x_display(void *frontend)
+const char *get_x_display(void *frontend)
 {
     return gdk_get_display();
 }
 
+#ifndef NOT_X_WINDOWS
 long get_windowid(void *frontend)
 {
     struct gui_data *inst = (struct gui_data *)frontend;
-    return (long)GDK_WINDOW_XWINDOW(inst->area->window);
+    return (long)GDK_WINDOW_XID(gtk_widget_get_window(inst->area));
 }
+#endif
 
 static void help(FILE *fp) {
     if(fprintf(fp,
@@ -2695,7 +3791,7 @@ int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch,
 #define SECOND_PASS_ONLY { if (!do_everything) continue; }
 
     while (--argc > 0) {
-       char *p = *++argv;
+       const char *p = *++argv;
         int ret;
 
        /*
@@ -2759,24 +3855,31 @@ int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch,
            conf_set_str(conf, CONF_line_codepage, val);
 
        } else if (!strcmp(p, "-geometry")) {
-           int flags, x, y;
-           unsigned int w, h;
            EXPECTS_ARG;
            SECOND_PASS_ONLY;
 
-           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));
+#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;
@@ -2786,27 +3889,47 @@ int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch,
        } else if (!strcmp(p, "-fg") || !strcmp(p, "-bg") ||
                   !strcmp(p, "-bfg") || !strcmp(p, "-bbg") ||
                   !strcmp(p, "-cfg") || !strcmp(p, "-cbg")) {
-           GdkColor col;
-
            EXPECTS_ARG;
            SECOND_PASS_ONLY;
-           if (!gdk_color_parse(val, &col)) {
-               err = 1;
-               fprintf(stderr, "%s: unable to parse colour \"%s\"\n",
-                        appname, val);
-           } else {
-               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, col.red / 256);
-               conf_set_int_int(conf, CONF_colours, index*3+1,col.green/ 256);
-               conf_set_int_int(conf, CONF_colours, index*3+2, col.blue/ 256);
-           }
+
+            {
+#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. */
@@ -2901,17 +4024,46 @@ int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch,
     return err;
 }
 
-int uxsel_input_add(int fd, int rwx) {
+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);
-    return gdk_input_add(fd, flags, fd_input_func, NULL);
+    id->id = gdk_input_add(fd, flags, fd_input_func, NULL);
+#endif
+
+    return id;
 }
 
-void uxsel_input_remove(int id) {
-    gdk_input_remove(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)
@@ -2996,6 +4148,8 @@ char *setup_fonts_ucs(struct gui_data *inst)
                                    inst->fonts[0]->public_charset,
                                    conf_get_int(inst->conf, CONF_vtmode));
 
+    inst->drawtype = inst->fonts[0]->preferred_drawtype;
+
     return NULL;
 }
 
@@ -3026,7 +4180,7 @@ void reset_terminal_menuitem(GtkMenuItem *item, gpointer data)
     struct gui_data *inst = (struct gui_data *)data;
     term_pwron(inst->term, TRUE);
     if (inst->ldisc)
-       ldisc_send(inst->ldisc, NULL, 0, 0);
+       ldisc_echoedit_update(inst->ldisc);
 }
 
 void copy_all_menuitem(GtkMenuItem *item, gpointer data)
@@ -3038,8 +4192,8 @@ void copy_all_menuitem(GtkMenuItem *item, gpointer data)
 void special_menuitem(GtkMenuItem *item, gpointer data)
 {
     struct gui_data *inst = (struct gui_data *)data;
-    int code = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(item),
-                                                  "user-data"));
+    int code = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item),
+                                                 "user-data"));
 
     if (inst->back)
        inst->back->special(inst->backhandle, code);
@@ -3094,7 +4248,7 @@ void change_settings_menuitem(GtkMenuItem *item, gpointer data)
          */
         if (inst->ldisc) {
             ldisc_configure(inst->ldisc, inst->conf);
-           ldisc_send(inst->ldisc, NULL, 0, 0);
+            ldisc_echoedit_update(inst->ldisc);
         }
         /* Pass new config data to the terminal */
         term_reconfig(inst->term, inst->conf);
@@ -3454,14 +4608,14 @@ 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 *)gtk_object_get_data(GTK_OBJECT(item), "user-data");
+    char *str = (char *)g_object_get_data(G_OBJECT(item), "user-data");
 
     fork_and_exec_self(inst, -1, "-load", str, NULL);
 }
 
 void saved_session_freedata(GtkMenuItem *item, gpointer data)
 {
-    char *str = (char *)gtk_object_get_data(GTK_OBJECT(item), "user-data");
+    char *str = (char *)g_object_get_data(G_OBJECT(item), "user-data");
 
     sfree(str);
 }
@@ -3482,14 +4636,14 @@ static void update_savedsess_menu(GtkMenuItem *menuitem, gpointer data)
            gtk_menu_item_new_with_label(sesslist.sessions[i]);
        gtk_container_add(GTK_CONTAINER(inst->sessionsmenu), menuitem);
        gtk_widget_show(menuitem);
-       gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",
-                           dupstr(sesslist.sessions[i]));
-       gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
-                          GTK_SIGNAL_FUNC(saved_session_menuitem),
-                          inst);
-       gtk_signal_connect(GTK_OBJECT(menuitem), "destroy",
-                          GTK_SIGNAL_FUNC(saved_session_freedata),
-                          inst);
+        g_object_set_data(G_OBJECT(menuitem), "user-data",
+                          dupstr(sesslist.sessions[i]));
+        g_signal_connect(G_OBJECT(menuitem), "activate",
+                         G_CALLBACK(saved_session_menuitem),
+                         inst);
+        g_signal_connect(G_OBJECT(menuitem), "destroy",
+                         G_CALLBACK(saved_session_freedata),
+                         inst);
     }
     if (sesslist.nsessions <= 1) {
        GtkWidget *menuitem =
@@ -3504,20 +4658,26 @@ static void update_savedsess_menu(GtkMenuItem *menuitem, gpointer data)
 void set_window_icon(GtkWidget *window, const char *const *const *icon,
                     int n_icon)
 {
-    GdkPixmap *iconpm;
-    GdkBitmap *iconmask;
 #if GTK_CHECK_VERSION(2,0,0)
     GList *iconlist;
     int n;
+#else
+    GdkPixmap *iconpm;
+    GdkBitmap *iconmask;
 #endif
 
     if (!n_icon)
        return;
 
     gtk_widget_realize(window);
-    iconpm = gdk_pixmap_create_from_xpm_d(window->window, &iconmask,
-                                         NULL, (gchar **)icon[0]);
-    gdk_window_set_icon(window->window, NULL, iconpm, iconmask);
+#if GTK_CHECK_VERSION(2,0,0)
+    gtk_window_set_icon(GTK_WINDOW(window),
+                        gdk_pixbuf_new_from_xpm_data((const gchar **)icon[0]));
+#else
+    iconpm = gdk_pixmap_create_from_xpm_d(gtk_widget_get_window(window),
+                                          &iconmask, NULL, (gchar **)icon[0]);
+    gdk_window_set_icon(gtk_widget_get_window(window), NULL, iconpm, iconmask);
+#endif
 
 #if GTK_CHECK_VERSION(2,0,0)
     iconlist = NULL;
@@ -3527,7 +4687,7 @@ void set_window_icon(GtkWidget *window, const char *const *const *icon,
                          gdk_pixbuf_new_from_xpm_data((const gchar **)
                                                       icon[n]));
     }
-    gdk_window_set_icon_list(window->window, iconlist);
+    gtk_window_set_icon_list(GTK_WINDOW(window), iconlist);
 #endif
 }
 
@@ -3577,10 +4737,10 @@ void update_specials_menu(void *frontend)
                break;
              default:
                menuitem = gtk_menu_item_new_with_label(specials[i].name);
-               gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",
-                                   GINT_TO_POINTER(specials[i].code));
-               gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
-                                  GTK_SIGNAL_FUNC(special_menuitem), inst);
+                g_object_set_data(G_OBJECT(menuitem), "user-data",
+                                  GINT_TO_POINTER(specials[i].code));
+                g_signal_connect(G_OBJECT(menuitem), "activate",
+                                 G_CALLBACK(special_menuitem), inst);
                break;
            }
            if (menuitem) {
@@ -3661,6 +4821,10 @@ int pt_main(int argc, char **argv)
     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 */
@@ -3724,8 +4888,6 @@ int pt_main(int argc, char **argv)
             exit(1);
         }
     }
-    init_cutbuffers();
-
     inst->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
     {
         const char *winclass = conf_get_str(inst->conf, CONF_winclass);
@@ -3743,9 +4905,25 @@ int pt_main(int argc, char **argv)
     inst->height = conf_get_int(inst->conf, CONF_height);
     cache_conf_values(inst);
 
-    gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area),
-                         inst->font_width * inst->width + 2*inst->window_border,
-                         inst->font_height * inst->height + 2*inst->window_border);
+    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));
@@ -3762,8 +4940,6 @@ int pt_main(int argc, char **argv)
 
     gtk_container_add(GTK_CONTAINER(inst->window), GTK_WIDGET(inst->hbox));
 
-    set_geom_hints(inst);
-
     gtk_widget_show(inst->area);
     if (conf_get_int(inst->conf, CONF_scrollbar))
        gtk_widget_show(inst->sbar);
@@ -3771,6 +4947,11 @@ int pt_main(int argc, char **argv)
        gtk_widget_hide(inst->sbar);
     gtk_widget_show(GTK_WIDGET(inst->hbox));
 
+#if GTK_CHECK_VERSION(2,0,0)
+    if (inst->geometry) {
+        gtk_window_parse_geometry(GTK_WINDOW(inst->window), inst->geometry);
+    }
+#else
     if (inst->gotpos) {
         int x = inst->xpos, y = inst->ypos;
         GtkRequisition req;
@@ -3780,50 +4961,54 @@ int pt_main(int argc, char **argv)
        gtk_window_set_position(GTK_WINDOW(inst->window), GTK_WIN_POS_NONE);
        gtk_widget_set_uposition(GTK_WIDGET(inst->window), x, y);
     }
+#endif
 
-    gtk_signal_connect(GTK_OBJECT(inst->window), "destroy",
-                      GTK_SIGNAL_FUNC(destroy), inst);
-    gtk_signal_connect(GTK_OBJECT(inst->window), "delete_event",
-                      GTK_SIGNAL_FUNC(delete_window), inst);
-    gtk_signal_connect(GTK_OBJECT(inst->window), "key_press_event",
-                      GTK_SIGNAL_FUNC(key_event), inst);
-    gtk_signal_connect(GTK_OBJECT(inst->window), "key_release_event",
-                      GTK_SIGNAL_FUNC(key_event), inst);
-    gtk_signal_connect(GTK_OBJECT(inst->window), "focus_in_event",
-                      GTK_SIGNAL_FUNC(focus_event), inst);
-    gtk_signal_connect(GTK_OBJECT(inst->window), "focus_out_event",
-                      GTK_SIGNAL_FUNC(focus_event), inst);
-    gtk_signal_connect(GTK_OBJECT(inst->area), "configure_event",
-                      GTK_SIGNAL_FUNC(configure_area), inst);
-    gtk_signal_connect(GTK_OBJECT(inst->area), "expose_event",
-                      GTK_SIGNAL_FUNC(expose_area), inst);
-    gtk_signal_connect(GTK_OBJECT(inst->area), "button_press_event",
-                      GTK_SIGNAL_FUNC(button_event), inst);
-    gtk_signal_connect(GTK_OBJECT(inst->area), "button_release_event",
-                      GTK_SIGNAL_FUNC(button_event), inst);
+    g_signal_connect(G_OBJECT(inst->window), "destroy",
+                     G_CALLBACK(destroy), inst);
+    g_signal_connect(G_OBJECT(inst->window), "delete_event",
+                     G_CALLBACK(delete_window), inst);
+    g_signal_connect(G_OBJECT(inst->window), "key_press_event",
+                     G_CALLBACK(key_event), inst);
+    g_signal_connect(G_OBJECT(inst->window), "key_release_event",
+                     G_CALLBACK(key_event), inst);
+    g_signal_connect(G_OBJECT(inst->window), "focus_in_event",
+                     G_CALLBACK(focus_event), inst);
+    g_signal_connect(G_OBJECT(inst->window), "focus_out_event",
+                     G_CALLBACK(focus_event), inst);
+    g_signal_connect(G_OBJECT(inst->area), "configure_event",
+                     G_CALLBACK(configure_area), inst);
+#if GTK_CHECK_VERSION(3,0,0)
+    g_signal_connect(G_OBJECT(inst->area), "draw",
+                     G_CALLBACK(draw_area), inst);
+#else
+    g_signal_connect(G_OBJECT(inst->area), "expose_event",
+                     G_CALLBACK(expose_area), inst);
+#endif
+    g_signal_connect(G_OBJECT(inst->area), "button_press_event",
+                     G_CALLBACK(button_event), inst);
+    g_signal_connect(G_OBJECT(inst->area), "button_release_event",
+                     G_CALLBACK(button_event), inst);
 #if GTK_CHECK_VERSION(2,0,0)
-    gtk_signal_connect(GTK_OBJECT(inst->area), "scroll_event",
-                      GTK_SIGNAL_FUNC(scroll_event), inst);
-#endif
-    gtk_signal_connect(GTK_OBJECT(inst->area), "motion_notify_event",
-                      GTK_SIGNAL_FUNC(motion_event), inst);
-    gtk_signal_connect(GTK_OBJECT(inst->area), "selection_received",
-                      GTK_SIGNAL_FUNC(selection_received), inst);
-    gtk_signal_connect(GTK_OBJECT(inst->area), "selection_get",
-                      GTK_SIGNAL_FUNC(selection_get), inst);
-    gtk_signal_connect(GTK_OBJECT(inst->area), "selection_clear_event",
-                      GTK_SIGNAL_FUNC(selection_clear), inst);
+    g_signal_connect(G_OBJECT(inst->area), "scroll_event",
+                     G_CALLBACK(scroll_event), inst);
+#endif
+    g_signal_connect(G_OBJECT(inst->area), "motion_notify_event",
+                     G_CALLBACK(motion_event), inst);
 #if GTK_CHECK_VERSION(2,0,0)
     g_signal_connect(G_OBJECT(inst->imc), "commit",
                      G_CALLBACK(input_method_commit_event), inst);
 #endif
     if (conf_get_int(inst->conf, CONF_scrollbar))
-       gtk_signal_connect(GTK_OBJECT(inst->sbar_adjust), "value_changed",
-                          GTK_SIGNAL_FUNC(scrollbar_moved), inst);
+        g_signal_connect(G_OBJECT(inst->sbar_adjust), "value_changed",
+                         G_CALLBACK(scrollbar_moved), inst);
     gtk_widget_add_events(GTK_WIDGET(inst->area),
                          GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
                          GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
-                         GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK);
+                         GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK
+#if GTK_CHECK_VERSION(3,4,0)
+                          | GDK_SMOOTH_SCROLL_MASK
+#endif
+        );
 
     {
        extern const char *const *const main_icon[];
@@ -3850,8 +5035,8 @@ int pt_main(int argc, char **argv)
             menuitem = gtk_menu_item_new_with_label(title);             \
             gtk_container_add(GTK_CONTAINER(inst->menu), menuitem);     \
             gtk_widget_show(menuitem);                                  \
-            gtk_signal_connect(GTK_OBJECT(menuitem), "activate",        \
-                               GTK_SIGNAL_FUNC(func), inst);            \
+            g_signal_connect(G_OBJECT(menuitem), "activate",            \
+                             G_CALLBACK(func), inst);                   \
         } while (0)
 
 #define MKSUBMENU(title) do                                             \
@@ -3911,7 +5096,6 @@ int pt_main(int argc, char **argv)
     inst->rawcursor = make_mouse_ptr(inst, GDK_LEFT_PTR);
     inst->waitcursor = make_mouse_ptr(inst, GDK_WATCH);
     inst->blankcursor = make_mouse_ptr(inst, -1);
-    make_mouse_ptr(inst, -2);         /* clean up cursor font */
     inst->currcursor = inst->textcursor;
     show_mouseptr(inst, 1);
 
@@ -3930,7 +5114,7 @@ int pt_main(int argc, char **argv)
 
     start_backend(inst);
 
-    ldisc_send(inst->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */
+    ldisc_echoedit_update(inst->ldisc);     /* cause ldisc to notice changes */
 
     /* now we're reday to deal with the child exit handler being
      * called */
index 1968a423fd23442c2d474d873afd9c96254472e9..62e20112a72cea5876bd14e88e00e40d4b992cec 100644 (file)
 #endif /*  NO_LIBDL */
 #include "charset.h"
 
+#ifdef OSX_GTK
+/*
+ * Assorted tweaks to various parts of the GTK front end which all
+ * need to be enabled when compiling on OS X. Because I might need the
+ * same tweaks on other systems in future, I don't want to
+ * conditionalise all of them on OSX_GTK directly, so instead, each
+ * one has its own name and we enable them all centrally here if
+ * OSX_GTK is defined at configure time.
+ */
+#define NOT_X_WINDOWS /* of course, all the X11 stuff should be disabled */
+#define NO_PTY_PRE_INIT /* OS X gets very huffy if we try to set[ug]id */
+#define SET_NONBLOCK_VIA_OPENPT /* work around missing fcntl functionality */
+#define OSX_META_KEY_CONFIG /* two possible Meta keys to choose from */
+/* this potential one of the Meta keys needs manual handling */
+#define META_MANUAL_MASK (GDK_MOD1_MASK)
+#define JUST_USE_GTK_CLIPBOARD_UTF8 /* low-level gdk_selection_* fails */
+#define DEFAULT_CLIPBOARD GDK_SELECTION_CLIPBOARD /* OS X has no PRIMARY */
+#endif
+
 struct Filename {
     char *path;
 };
@@ -26,6 +45,8 @@ typedef void *Context;                 /* FIXME: probably needs changing */
 
 extern Backend pty_backend;
 
+#define BROKEN_PIPE_ERROR_CODE EPIPE   /* used in sshshare.c */
+
 typedef uint32_t uint32; /* C99: uint32_t defined in stdint.h */
 #define PUTTY_UINT32_DEFINED
 
@@ -74,33 +95,33 @@ unsigned long getticks(void);              /* based on gettimeofday(2) */
 #define FLAG_STDERR_TTY 0x1000
 
 /* Things pty.c needs from pterm.c */
-char *get_x_display(void *frontend);
+const char *get_x_display(void *frontend);
 int font_dimension(void *frontend, int which);/* 0 for width, 1 for height */
 long get_windowid(void *frontend);
 int frontend_is_utf8(void *frontend);
 
 /* Things gtkdlg.c needs from pterm.c */
 void *get_window(void *frontend);      /* void * to avoid depending on gtk.h */
+void post_main(void);     /* called after any subsidiary gtk_main() */
 
 /* Things pterm.c needs from gtkdlg.c */
 int do_config_box(const char *title, Conf *conf,
                  int midsession, int protcfginfo);
-void fatal_message_box(void *window, char *msg);
-void nonfatal_message_box(void *window, char *msg);
+void fatal_message_box(void *window, const char *msg);
+void nonfatal_message_box(void *window, const char *msg);
 void about_box(void *window);
 void *eventlogstuff_new(void);
 void showeventlog(void *estuff, void *parentwin);
 void logevent_dlg(void *estuff, const char *string);
 int reallyclose(void *frontend);
 #ifdef MAY_REFER_TO_GTK_IN_HEADERS
-int messagebox(GtkWidget *parentwin, char *title,
-               char *msg, int minwid, int selectable, ...);
-int string_width(char *text);
+int messagebox(GtkWidget *parentwin, const char *title,
+               const char *msg, int minwid, int selectable, ...);
 #endif
 
 /* Things pterm.c needs from {ptermm,uxputty}.c */
 char *make_default_wintitle(char *hostname);
-int process_nonoption_arg(char *arg, Conf *conf, int *allow_launch);
+int process_nonoption_arg(const char *arg, Conf *conf, int *allow_launch);
 
 /* pterm.c needs this special function in xkeysym.c */
 int keysym_to_unicode(int keysym);
@@ -118,6 +139,7 @@ void premsg(struct termios *);
 void postmsg(struct termios *);
 
 /* The interface used by uxsel.c */
+typedef struct uxsel_id uxsel_id;
 void uxsel_init(void);
 typedef int (*uxsel_callback_fn)(int fd, int event);
 void uxsel_set(int fd, int rwx, uxsel_callback_fn callback);
@@ -126,8 +148,8 @@ int select_result(int fd, int event);
 int first_fd(int *state, int *rwx);
 int next_fd(int *state, int *rwx);
 /* The following are expected to be provided _to_ uxsel.c by the frontend */
-int uxsel_input_add(int fd, int rwx);  /* returns an id */
-void uxsel_input_remove(int id);
+uxsel_id *uxsel_input_add(int fd, int rwx);  /* returns an id */
+void uxsel_input_remove(uxsel_id *id);
 
 /* uxcfg.c */
 struct controlbox;
@@ -159,6 +181,7 @@ void cloexec(int);
 void noncloexec(int);
 int nonblock(int);
 int no_nonblock(int);
+char *make_dir_and_check_ours(const char *dirname);
 
 /*
  * Exports from unicode.c.
@@ -191,4 +214,13 @@ extern Backend serial_backend;
  */
 int so_peercred(int fd, int *pid, int *uid, int *gid);
 
+/*
+ * Default font setting, which can vary depending on NOT_X_WINDOWS.
+ */
+#ifdef NOT_X_WINDOWS
+#define DEFAULT_GTK_FONT "client:Monospace 12"
+#else
+#define DEFAULT_GTK_FONT "server:fixed"
+#endif
+
 #endif
index 5734a7b0ea62c9f545b3cdb70b357d2f4682d871..326ee66b832439e35cb40a471339bc989074669c 100644 (file)
@@ -53,25 +53,23 @@ static int agent_connfind(void *av, void *bv)
     return 0;
 }
 
-static int agent_select_result(int fd, int event)
+/*
+ * Attempt to read from an agent socket fd. Returns 0 if the expected
+ * response is as yet incomplete; returns 1 if it's either complete
+ * (conn->retbuf non-NULL and filled with something useful) or has
+ * failed totally (conn->retbuf is NULL).
+ */
+static int agent_try_read(struct agent_connection *conn)
 {
     int ret;
-    struct agent_connection *conn;
-
-    assert(event == 1);                       /* not selecting for anything but R */
-
-    conn = find234(agent_connections, &fd, agent_connfind);
-    if (!conn) {
-       uxsel_del(fd);
-       return 1;
-    }
 
-    ret = read(fd, conn->retbuf+conn->retlen, conn->retsize-conn->retlen);
+    ret = read(conn->fd, conn->retbuf+conn->retlen,
+               conn->retsize-conn->retlen);
     if (ret <= 0) {
        if (conn->retbuf != conn->sizebuf) sfree(conn->retbuf);
        conn->retbuf = NULL;
        conn->retlen = 0;
-       goto done;
+        return 1;
     }
     conn->retlen += ret;
     if (conn->retsize == 4 && conn->retlen == 4) {
@@ -79,7 +77,7 @@ static int agent_select_result(int fd, int event)
        if (conn->retsize <= 0) {
            conn->retbuf = NULL;
            conn->retlen = 0;
-           goto done;
+            return -1;                 /* way too large */
        }
        assert(conn->retbuf == conn->sizebuf);
        conn->retbuf = snewn(conn->retsize, char);
@@ -89,7 +87,24 @@ static int agent_select_result(int fd, int event)
     if (conn->retlen < conn->retsize)
        return 0;                      /* more data to come */
 
-    done:
+    return 1;
+}
+
+static int agent_select_result(int fd, int event)
+{
+    struct agent_connection *conn;
+
+    assert(event == 1);                       /* not selecting for anything but R */
+
+    conn = find234(agent_connections, &fd, agent_connfind);
+    if (!conn) {
+       uxsel_del(fd);
+       return 1;
+    }
+
+    if (!agent_try_read(conn))
+       return 0;                      /* more data to come */
+
     /*
      * We have now completed the agent query. Do the callback, and
      * clean up. (Of course we don't free retbuf, since ownership
@@ -140,9 +155,6 @@ int agent_query(void *in, int inlen, void **out, int *outlen,
        done += ret;
     }
 
-    if (!agent_connections)
-       agent_connections = newtree234(agent_conncmp);
-
     conn = snew(struct agent_connection);
     conn->fd = sock;
     conn->retbuf = conn->sizebuf;
@@ -150,6 +162,34 @@ int agent_query(void *in, int inlen, void **out, int *outlen,
     conn->retlen = 0;
     conn->callback = callback;
     conn->callback_ctx = callback_ctx;
+
+    if (!callback) {
+        /*
+         * Bodge to permit making deliberately synchronous agent
+         * requests. Used by Unix Pageant in command-line client mode,
+         * which is legit because it really is true that no other part
+         * of the program is trying to get anything useful done
+         * simultaneously. But this special case shouldn't be used in
+         * any more general program.
+         */
+        no_nonblock(conn->fd);
+        while (!agent_try_read(conn))
+            /* empty loop body */;
+
+        *out = conn->retbuf;
+        *outlen = conn->retlen;
+        sfree(conn);
+        return 1;
+    }
+
+    /*
+     * Otherwise do it properly: add conn to the tree of agent
+     * connections currently in flight, return 0 to indicate that the
+     * response hasn't been received yet, and call the callback when
+     * select_result comes back to us.
+     */
+    if (!agent_connections)
+       agent_connections = newtree234(agent_conncmp);
     add234(agent_connections, conn);
 
     uxsel_set(sock, 1, agent_select_result);
index fa1c43f2ece0d45de5a1f8cd7d29ff24fc90e03b..abad00db0ef7a065460fd04a1496e9562874b422 100644 (file)
@@ -107,8 +107,8 @@ static int block_and_read(int fd, void *buf, size_t len)
     return ret;
 }
 
-int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
-                        char *keystr, char *fingerprint,
+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)
 {
     int ret;
@@ -397,7 +397,8 @@ static void console_prompt_text(FILE *outfp, const char *data, int len)
     fflush(outfp);
 }
 
-int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+int console_get_userpass_input(prompts_t *p, const unsigned char *in,
+                               int inlen)
 {
     size_t curr_prompt;
     FILE *outfp = NULL;
index b7727cb21cad41e74d98b9d45f7094b3cfa4dea4..a7a2fcb93576bf7248a409757458a41fdbe0b827 100644 (file)
@@ -11,6 +11,7 @@
 #include <time.h>
 #include <sys/time.h>
 #include <sys/types.h>
+#include <sys/stat.h>
 #include <pwd.h>
 
 #include "putty.h"
@@ -104,7 +105,7 @@ char filename_char_sanitise(char c)
 #ifdef DEBUG
 static FILE *debug_fp = NULL;
 
-void dputs(char *buf)
+void dputs(const char *buf)
 {
     if (!debug_fp) {
        debug_fp = fopen("debug.log", "w");
@@ -290,3 +291,34 @@ FontSpec *fontspec_deserialise(void *vdata, int maxsize, int *used)
     *used = end - data + 1;
     return fontspec_new(data);
 }
+
+char *make_dir_and_check_ours(const char *dirname)
+{
+    struct stat st;
+
+    /*
+     * Create the directory. We might have created it before, so
+     * EEXIST is an OK error; but anything else is doom.
+     */
+    if (mkdir(dirname, 0700) < 0 && errno != EEXIST)
+        return dupprintf("%s: mkdir: %s", dirname, strerror(errno));
+
+    /*
+     * Now check that that directory is _owned by us_ and not writable
+     * by anybody else. This protects us against somebody else
+     * previously having created the directory in a way that's
+     * writable to us, and thus manipulating us into creating the
+     * actual socket in a directory they can see so that they can
+     * connect to it and use our authenticated SSH sessions.
+     */
+    if (stat(dirname, &st) < 0)
+        return dupprintf("%s: stat: %s", dirname, strerror(errno));
+    if (st.st_uid != getuid())
+        return dupprintf("%s: directory owned by uid %d, not by us",
+                         dirname, st.st_uid);
+    if ((st.st_mode & 077) != 0)
+        return dupprintf("%s: directory has overgenerous permissions %03o"
+                         " (expected 700)", dirname, st.st_mode & 0777);
+
+    return NULL;
+}
index b15031f465865eb6a7edd2a009d7474e63957193..730f1fa7d5024d3e0dd6d0e28a064005cd045ac4 100644 (file)
@@ -778,7 +778,8 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
     return (Socket) ret;
 }
 
-Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only, int orig_address_family)
+Socket sk_newlistener(const char *srcaddr, int port, Plug plug,
+                      int local_host_only, int orig_address_family)
 {
     int s;
 #ifndef NO_IPV6
diff --git a/unix/uxpgnt.c b/unix/uxpgnt.c
new file mode 100644 (file)
index 0000000..75f719c
--- /dev/null
@@ -0,0 +1,1078 @@
+/*
+ * Unix Pageant, more or less similar to ssh-agent.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <assert.h>
+#include <signal.h>
+#include <ctype.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#define PUTTY_DO_GLOBALS              /* actually _define_ globals */
+#include "putty.h"
+#include "ssh.h"
+#include "misc.h"
+#include "pageant.h"
+
+SockAddr unix_sock_addr(const char *path);
+Socket new_unix_listener(SockAddr listenaddr, Plug plug);
+
+void fatalbox(const char *p, ...)
+{
+    va_list ap;
+    fprintf(stderr, "FATAL ERROR: ");
+    va_start(ap, p);
+    vfprintf(stderr, p, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+    exit(1);
+}
+void modalfatalbox(const char *p, ...)
+{
+    va_list ap;
+    fprintf(stderr, "FATAL ERROR: ");
+    va_start(ap, p);
+    vfprintf(stderr, p, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+    exit(1);
+}
+void nonfatal(const char *p, ...)
+{
+    va_list ap;
+    fprintf(stderr, "ERROR: ");
+    va_start(ap, p);
+    vfprintf(stderr, p, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+}
+void connection_fatal(void *frontend, const char *p, ...)
+{
+    va_list ap;
+    fprintf(stderr, "FATAL ERROR: ");
+    va_start(ap, p);
+    vfprintf(stderr, p, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+    exit(1);
+}
+void cmdline_error(const char *p, ...)
+{
+    va_list ap;
+    fprintf(stderr, "pageant: ");
+    va_start(ap, p);
+    vfprintf(stderr, p, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+    exit(1);
+}
+
+FILE *pageant_logfp = NULL;
+void pageant_log(void *ctx, const char *fmt, va_list ap)
+{
+    if (!pageant_logfp)
+        return;
+
+    fprintf(pageant_logfp, "pageant: ");
+    vfprintf(pageant_logfp, fmt, ap);
+    fprintf(pageant_logfp, "\n");
+}
+
+/*
+ * In Pageant our selects are synchronous, so these functions are
+ * empty stubs.
+ */
+uxsel_id *uxsel_input_add(int fd, int rwx) { return NULL; }
+void uxsel_input_remove(uxsel_id *id) { }
+
+/*
+ * More stubs.
+ */
+void random_save_seed(void) {}
+void random_destroy_seed(void) {}
+void noise_ultralight(unsigned long data) {}
+char *platform_default_s(const char *name) { return NULL; }
+int platform_default_i(const char *name, int def) { return def; }
+FontSpec *platform_default_fontspec(const char *name) { return fontspec_new(""); }
+Filename *platform_default_filename(const char *name) { return filename_from_str(""); }
+char *x_get_default(const char *key) { return NULL; }
+void log_eventlog(void *handle, const char *event) {}
+int from_backend(void *frontend, int is_stderr, const char *data, int datalen)
+{ assert(!"only here to satisfy notional call from backend_socket_log"); }
+
+/*
+ * Short description of parameters.
+ */
+static void usage(void)
+{
+    printf("Pageant: SSH agent\n");
+    printf("%s\n", ver);
+    printf("Usage: pageant <lifetime> [key files]\n");
+    printf("       pageant [key files] --exec <command> [args]\n");
+    printf("       pageant -a [key files]\n");
+    printf("       pageant -d [key identifiers]\n");
+    printf("       pageant --public [key identifiers]\n");
+    printf("       pageant --public-openssh [key identifiers]\n");
+    printf("       pageant -l\n");
+    printf("       pageant -D\n");
+    printf("Lifetime options, for running Pageant as an agent:\n");
+    printf("  -X           run with the lifetime of the X server\n");
+    printf("  -T           run with the lifetime of the controlling tty\n");
+    printf("  --permanent  run permanently\n");
+    printf("  --debug      run in debugging mode, without forking\n");
+    printf("  --exec <command>   run with the lifetime of that command\n");
+    printf("Client options, for talking to an existing agent:\n");
+    printf("  -a           add key(s) to the existing agent\n");
+    printf("  -l           list currently loaded key fingerprints and comments\n");
+    printf("  --public     print public keys in RFC 4716 format\n");
+    printf("  --public-openssh   print public keys in OpenSSH format\n");
+    printf("  -d           delete key(s) from the agent\n");
+    printf("  -D           delete all keys from the agent\n");
+    printf("Other options:\n");
+    printf("  -v           verbose mode (in agent mode)\n");
+    exit(1);
+}
+
+static void version(void)
+{
+    printf("pageant: %s\n", ver);
+    exit(1);
+}
+
+void keylist_update(void)
+{
+    /* Nothing needs doing in Unix Pageant */
+}
+
+#define PAGEANT_DIR_PREFIX "/tmp/pageant"
+
+const char *const appname = "Pageant";
+
+static int time_to_die = FALSE;
+
+/* Stub functions to permit linking against x11fwd.c. These never get
+ * used, because in LIFE_X11 mode we connect to the X server using a
+ * straightforward Socket and don't try to create an ersatz SSH
+ * forwarding too. */
+int sshfwd_write(struct ssh_channel *c, char *data, int len) { return 0; }
+void sshfwd_write_eof(struct ssh_channel *c) { }
+void sshfwd_unclean_close(struct ssh_channel *c, const char *err) { }
+void sshfwd_unthrottle(struct ssh_channel *c, int bufsize) {}
+Conf *sshfwd_get_conf(struct ssh_channel *c) { return NULL; }
+void sshfwd_x11_sharing_handover(struct ssh_channel *c,
+                                 void *share_cs, void *share_chan,
+                                 const char *peer_addr, int peer_port,
+                                 int endian, int protomajor, int protominor,
+                                 const void *initial_data, int initial_len) {}
+void sshfwd_x11_is_local(struct ssh_channel *c) {}
+
+/*
+ * These functions are part of the plug for our connection to the X
+ * display, so they do get called. They needn't actually do anything,
+ * except that x11_closing has to signal back to the main loop that
+ * it's time to terminate.
+ */
+static void x11_log(Plug p, int type, SockAddr addr, int port,
+                   const char *error_msg, int error_code) {}
+static int x11_receive(Plug plug, int urgent, char *data, int len) {return 0;}
+static void x11_sent(Plug plug, int bufsize) {}
+static int x11_closing(Plug plug, const char *error_msg, int error_code,
+                      int calling_back)
+{
+    time_to_die = TRUE;
+    return 1;
+}
+struct X11Connection {
+    const struct plug_function_table *fn;
+};
+
+char *socketname;
+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);
+}
+
+void pageant_fork_and_print_env(int retain_tty)
+{
+    pid_t pid = fork();
+    if (pid == -1) {
+        perror("fork");
+        exit(1);
+    } else if (pid != 0) {
+        pageant_print_env(pid);
+        exit(0);
+    }
+
+    /*
+     * Having forked off, we now daemonise ourselves as best we can.
+     * It's good practice in general to setsid() ourself out of any
+     * process group we didn't want to be part of, and to chdir("/")
+     * to avoid holding any directories open that we don't need in
+     * case someone wants to umount them; also, we should definitely
+     * close standard output (because it will very likely be pointing
+     * at a pipe from which some parent process is trying to read our
+     * environment variable dump, so if we hold open another copy of
+     * it then that process will never finish reading). We close
+     * standard input too on general principles, but not standard
+     * error, since we might need to shout a panicky error message
+     * down that one.
+     */
+    if (chdir("/") < 0) {
+        /* should there be an error condition, nothing we can do about
+         * it anyway */
+    }
+    close(0);
+    close(1);
+    if (retain_tty) {
+        /* Get out of our previous process group, to avoid being
+         * blasted by passing signals. But keep our controlling tty,
+         * so we can keep checking to see if we still have one. */
+        setpgrp();
+    } else {
+        /* Do that, but also leave our entire session and detach from
+         * the controlling tty (if any). */
+        setsid();
+    }
+}
+
+int signalpipe[2];
+
+void sigchld(int signum)
+{
+    if (write(signalpipe[1], "x", 1) <= 0)
+        /* not much we can do about it */;
+}
+
+#define TTY_LIFE_POLL_INTERVAL (TICKSPERSEC * 30)
+void *dummy_timer_ctx;
+static void tty_life_timer(void *ctx, unsigned long now)
+{
+    schedule_timer(TTY_LIFE_POLL_INTERVAL, tty_life_timer, &dummy_timer_ctx);
+}
+
+typedef enum {
+    KEYACT_AGENT_LOAD,
+    KEYACT_CLIENT_ADD,
+    KEYACT_CLIENT_DEL,
+    KEYACT_CLIENT_DEL_ALL,
+    KEYACT_CLIENT_LIST,
+    KEYACT_CLIENT_PUBLIC_OPENSSH,
+    KEYACT_CLIENT_PUBLIC
+} keyact;
+struct cmdline_key_action {
+    struct cmdline_key_action *next;
+    keyact action;
+    const char *filename;
+};
+
+int is_agent_action(keyact action)
+{
+    return action == KEYACT_AGENT_LOAD;
+}
+
+struct cmdline_key_action *keyact_head = NULL, *keyact_tail = NULL;
+
+void add_keyact(keyact action, const char *filename)
+{
+    struct cmdline_key_action *a = snew(struct cmdline_key_action);
+    a->action = action;
+    a->filename = filename;
+    a->next = NULL;
+    if (keyact_tail)
+        keyact_tail->next = a;
+    else
+        keyact_head = a;
+    keyact_tail = a;
+}
+
+int have_controlling_tty(void)
+{
+    int fd = open("/dev/tty", O_RDONLY);
+    if (fd < 0) {
+        if (errno != ENXIO) {
+            perror("/dev/tty: open");
+            exit(1);
+        }
+        return FALSE;
+    } else {
+        close(fd);
+        return TRUE;
+    }
+}
+
+char **exec_args = NULL;
+enum {
+    LIFE_UNSPEC, LIFE_X11, LIFE_TTY, LIFE_DEBUG, LIFE_PERM, LIFE_EXEC
+} life = LIFE_UNSPEC;
+const char *display = NULL;
+
+static char *askpass(const char *comment)
+{
+    if (have_controlling_tty()) {
+        int ret;
+        prompts_t *p = new_prompts(NULL);
+        p->to_server = FALSE;
+        p->name = dupstr("Pageant passphrase prompt");
+        add_prompt(p,
+                   dupprintf("Enter passphrase to load key '%s': ", comment),
+                   FALSE);
+        ret = console_get_userpass_input(p, NULL, 0);
+        assert(ret >= 0);
+
+        if (!ret) {
+            perror("pageant: unable to read passphrase");
+            free_prompts(p);
+            return NULL;
+        } else {
+            char *passphrase = dupstr(p->prompts[0]->result);
+            free_prompts(p);
+            return passphrase;
+        }
+    } else if (display) {
+        char *prompt, *passphrase;
+        int success;
+
+        /* in gtkask.c */
+        char *gtk_askpass_main(const char *display, const char *wintitle,
+                               const char *prompt, int *success);
+
+        prompt = dupprintf("Enter passphrase to load key '%s': ", comment);
+        passphrase = gtk_askpass_main(display,
+                                      "Pageant passphrase prompt",
+                                      prompt, &success);
+        sfree(prompt);
+        if (!success) {
+            /* return value is error message */
+            fprintf(stderr, "%s\n", passphrase);
+            sfree(passphrase);
+            passphrase = NULL;
+        }
+        return passphrase;
+    } else {
+        fprintf(stderr, "no way to read a passphrase without tty or "
+                "X display\n");
+        return NULL;
+    }
+}
+
+static int unix_add_keyfile(const char *filename_str)
+{
+    Filename *filename = filename_from_str(filename_str);
+    int status, ret;
+    char *err;
+
+    ret = TRUE;
+
+    /*
+     * Try without a passphrase.
+     */
+    status = pageant_add_keyfile(filename, NULL, &err);
+    if (status == PAGEANT_ACTION_OK) {
+        goto cleanup;
+    } else if (status == PAGEANT_ACTION_FAILURE) {
+        fprintf(stderr, "pageant: %s: %s\n", filename_str, err);
+        ret = FALSE;
+        goto cleanup;
+    }
+
+    /*
+     * And now try prompting for a passphrase.
+     */
+    while (1) {
+        char *passphrase = askpass(err);
+        sfree(err);
+        err = NULL;
+        if (!passphrase)
+            break;
+
+        status = pageant_add_keyfile(filename, passphrase, &err);
+
+        smemclr(passphrase, strlen(passphrase));
+        sfree(passphrase);
+        passphrase = NULL;
+
+        if (status == PAGEANT_ACTION_OK) {
+            goto cleanup;
+        } else if (status == PAGEANT_ACTION_FAILURE) {
+            fprintf(stderr, "pageant: %s: %s\n", filename_str, err);
+            ret = FALSE;
+            goto cleanup;
+        }
+    }
+
+  cleanup:
+    sfree(err);
+    filename_free(filename);
+    return ret;
+}
+
+void key_list_callback(void *ctx, const char *fingerprint,
+                       const char *comment, struct pageant_pubkey *key)
+{
+    printf("%s %s\n", fingerprint, comment);
+}
+
+struct key_find_ctx {
+    const char *string;
+    int match_fp, match_comment;
+    struct pageant_pubkey *found;
+    int nfound;
+};
+
+int match_fingerprint_string(const char *string, const char *fingerprint)
+{
+    const char *hash;
+
+    /* Find the hash in the fingerprint string. It'll be the word at the end. */
+    hash = strrchr(fingerprint, ' ');
+    assert(hash);
+    hash++;
+
+    /* Now see if the search string is a prefix of the full hash,
+     * neglecting colons and case differences. */
+    while (1) {
+        while (*string == ':') string++;
+        while (*hash == ':') hash++;
+        if (!*string)
+            return TRUE;
+        if (tolower((unsigned char)*string) != tolower((unsigned char)*hash))
+            return FALSE;
+        string++;
+        hash++;
+    }
+}
+
+void key_find_callback(void *vctx, const char *fingerprint,
+                       const char *comment, struct pageant_pubkey *key)
+{
+    struct key_find_ctx *ctx = (struct key_find_ctx *)vctx;
+
+    if ((ctx->match_comment && !strcmp(ctx->string, comment)) ||
+        (ctx->match_fp && match_fingerprint_string(ctx->string, fingerprint)))
+    {
+        if (!ctx->found)
+            ctx->found = pageant_pubkey_copy(key);
+        ctx->nfound++;
+    }
+}
+
+struct pageant_pubkey *find_key(const char *string, char **retstr)
+{
+    struct key_find_ctx actx, *ctx = &actx;
+    struct pageant_pubkey key_in, *key_ret;
+    int try_file = TRUE, try_fp = TRUE, try_comment = TRUE;
+    int file_errors = FALSE;
+
+    /*
+     * Trim off disambiguating prefixes telling us how to interpret
+     * the provided string.
+     */
+    if (!strncmp(string, "file:", 5)) {
+        string += 5;
+        try_fp = try_comment = FALSE;
+        file_errors = TRUE; /* also report failure to load the file */
+    } else if (!strncmp(string, "comment:", 8)) {
+        string += 8;
+        try_file = try_fp = FALSE;
+    } else if (!strncmp(string, "fp:", 3)) {
+        string += 3;
+        try_file = try_comment = FALSE;
+    } else if (!strncmp(string, "fingerprint:", 12)) {
+        string += 12;
+        try_file = try_comment = FALSE;
+    }
+
+    /*
+     * Try interpreting the string as a key file name.
+     */
+    if (try_file) {
+        Filename *fn = filename_from_str(string);
+        int keytype = key_type(fn);
+        if (keytype == SSH_KEYTYPE_SSH1 ||
+            keytype == SSH_KEYTYPE_SSH1_PUBLIC) {
+            const char *error;
+
+            if (!rsakey_pubblob(fn, &key_in.blob, &key_in.bloblen,
+                                NULL, &error)) {
+                if (file_errors) {
+                    *retstr = dupprintf("unable to load file '%s': %s",
+                                        string, error);
+                    filename_free(fn);
+                    return NULL;
+                }
+            }
+
+            /*
+             * If we've successfully loaded the file, stop here - we
+             * already have a key blob and need not go to the agent to
+             * list things.
+             */
+            key_in.ssh_version = 1;
+            key_ret = pageant_pubkey_copy(&key_in);
+            sfree(key_in.blob);
+            filename_free(fn);
+            return key_ret;
+        } else if (keytype == SSH_KEYTYPE_SSH2 ||
+                   keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 ||
+                   keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {
+            const char *error;
+
+            if ((key_in.blob = ssh2_userkey_loadpub(fn, NULL,
+                                                    &key_in.bloblen,
+                                                    NULL, &error)) == NULL) {
+                if (file_errors) {
+                    *retstr = dupprintf("unable to load file '%s': %s",
+                                        string, error);
+                    filename_free(fn);
+                    return NULL;
+                }
+            }
+
+            /*
+             * If we've successfully loaded the file, stop here - we
+             * already have a key blob and need not go to the agent to
+             * list things.
+             */
+            key_in.ssh_version = 2;
+            key_ret = pageant_pubkey_copy(&key_in);
+            sfree(key_in.blob);
+            filename_free(fn);
+            return key_ret;
+        } else {
+            if (file_errors) {
+                *retstr = dupprintf("unable to load key file '%s': %s",
+                                    string, key_type_to_str(keytype));
+                filename_free(fn);
+                return NULL;
+            }
+        }
+        filename_free(fn);
+    }
+
+    /*
+     * Failing that, go through the keys in the agent, and match
+     * against fingerprints and comments as appropriate.
+     */
+    ctx->string = string;
+    ctx->match_fp = try_fp;
+    ctx->match_comment = try_comment;
+    ctx->found = NULL;
+    ctx->nfound = 0;
+    if (pageant_enum_keys(key_find_callback, ctx, retstr) ==
+        PAGEANT_ACTION_FAILURE)
+        return NULL;
+
+    if (ctx->nfound == 0) {
+        *retstr = dupstr("no key matched");
+        assert(!ctx->found);
+        return NULL;
+    } else if (ctx->nfound > 1) {
+        *retstr = dupstr("multiple keys matched");
+        assert(ctx->found);
+        pageant_pubkey_free(ctx->found);
+        return NULL;
+    }
+
+    assert(ctx->found);
+    return ctx->found;
+}
+
+void run_client(void)
+{
+    const struct cmdline_key_action *act;
+    struct pageant_pubkey *key;
+    int errors = FALSE;
+    char *retstr;
+
+    if (!agent_exists()) {
+        fprintf(stderr, "pageant: no agent running to talk to\n");
+        exit(1);
+    }
+
+    for (act = keyact_head; act; act = act->next) {
+        switch (act->action) {
+          case KEYACT_CLIENT_ADD:
+            if (!unix_add_keyfile(act->filename))
+                errors = TRUE;
+            break;
+          case KEYACT_CLIENT_LIST:
+            if (pageant_enum_keys(key_list_callback, NULL, &retstr) ==
+                PAGEANT_ACTION_FAILURE) {
+                fprintf(stderr, "pageant: listing keys: %s\n", retstr);
+                sfree(retstr);
+                errors = TRUE;
+            }
+            break;
+          case KEYACT_CLIENT_DEL:
+            key = NULL;
+            if (!(key = find_key(act->filename, &retstr)) ||
+                pageant_delete_key(key, &retstr) == PAGEANT_ACTION_FAILURE) {
+                fprintf(stderr, "pageant: deleting key '%s': %s\n",
+                        act->filename, retstr);
+                sfree(retstr);
+                errors = TRUE;
+            }
+            if (key)
+                pageant_pubkey_free(key);
+            break;
+          case KEYACT_CLIENT_PUBLIC_OPENSSH:
+          case KEYACT_CLIENT_PUBLIC:
+            key = NULL;
+            if (!(key = find_key(act->filename, &retstr))) {
+                fprintf(stderr, "pageant: finding key '%s': %s\n",
+                        act->filename, retstr);
+                sfree(retstr);
+                errors = TRUE;
+            } else {
+                FILE *fp = stdout;     /* FIXME: add a -o option? */
+
+                if (key->ssh_version == 1) {
+                    struct RSAKey rkey;
+                    memset(&rkey, 0, sizeof(rkey));
+                    rkey.comment = dupstr(key->comment);
+                    makekey(key->blob, key->bloblen, &rkey, NULL, 0);
+                    ssh1_write_pubkey(fp, &rkey);
+                    freersakey(&rkey);
+                } else {
+                    ssh2_write_pubkey(fp, key->comment, key->blob,key->bloblen,
+                                      (act->action == KEYACT_CLIENT_PUBLIC ?
+                                       SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 :
+                                       SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH));
+                }
+                pageant_pubkey_free(key);
+            }
+            break;
+          case KEYACT_CLIENT_DEL_ALL:
+            if (pageant_delete_all_keys(&retstr) == PAGEANT_ACTION_FAILURE) {
+                fprintf(stderr, "pageant: deleting all keys: %s\n", retstr);
+                sfree(retstr);
+                errors = TRUE;
+            }
+            break;
+          default:
+            assert(0 && "Invalid client action found");
+        }
+    }
+
+    if (errors)
+        exit(1);
+}
+
+void run_agent(void)
+{
+    const char *err;
+    char *username, *socketdir;
+    struct pageant_listen_state *pl;
+    Socket sock;
+    unsigned long now;
+    int *fdlist;
+    int fd;
+    int i, fdcount, fdsize, fdstate;
+    int termination_pid = -1;
+    int errors = FALSE;
+    Conf *conf;
+    const struct cmdline_key_action *act;
+
+    fdlist = NULL;
+    fdcount = fdsize = 0;
+
+    pageant_init();
+
+    /*
+     * Start by loading any keys provided on the command line.
+     */
+    for (act = keyact_head; act; act = act->next) {
+        assert(act->action == KEYACT_AGENT_LOAD);
+        if (!unix_add_keyfile(act->filename))
+            errors = TRUE;
+    }
+    if (errors)
+        exit(1);
+
+    /*
+     * Set up a listening socket and run Pageant on it.
+     */
+    username = get_username();
+    socketdir = dupprintf("%s.%s", PAGEANT_DIR_PREFIX, username);
+    sfree(username);
+    assert(*socketdir == '/');
+    if ((err = make_dir_and_check_ours(socketdir)) != NULL) {
+        fprintf(stderr, "pageant: %s: %s\n", socketdir, err);
+        exit(1);
+    }
+    socketname = dupprintf("%s/pageant.%d", socketdir, (int)getpid());
+    pl = pageant_listener_new();
+    sock = new_unix_listener(unix_sock_addr(socketname), (Plug)pl);
+    if ((err = sk_socket_error(sock)) != NULL) {
+        fprintf(stderr, "pageant: %s: %s\n", socketname, err);
+        exit(1);
+    }
+    pageant_listener_got_socket(pl, sock);
+
+    conf = conf_new();
+    conf_set_int(conf, CONF_proxy_type, PROXY_NONE);
+
+    /*
+     * Lifetime preparations.
+     */
+    signalpipe[0] = signalpipe[1] = -1;
+    if (life == LIFE_X11) {
+        struct X11Display *disp;
+        void *greeting;
+        int greetinglen;
+        Socket s;
+        struct X11Connection *conn;
+
+        static const struct plug_function_table fn_table = {
+            x11_log,
+            x11_closing,
+            x11_receive,
+            x11_sent,
+            NULL
+        };
+
+        if (!display) {
+            fprintf(stderr, "pageant: no DISPLAY for -X mode\n");
+            exit(1);
+        }
+        disp = x11_setup_display(display, conf);
+
+        conn = snew(struct X11Connection);
+        conn->fn = &fn_table;
+        s = new_connection(sk_addr_dup(disp->addr),
+                           disp->realhost, disp->port,
+                           0, 1, 0, 0, (Plug)conn, conf);
+        if ((err = sk_socket_error(s)) != NULL) {
+            fprintf(stderr, "pageant: unable to connect to X server: %s", err);
+            exit(1);
+        }
+        greeting = x11_make_greeting('B', 11, 0, disp->localauthproto,
+                                     disp->localauthdata,
+                                     disp->localauthdatalen,
+                                     NULL, 0, &greetinglen);
+        sk_write(s, greeting, greetinglen);
+        smemclr(greeting, greetinglen);
+        sfree(greeting);
+
+        pageant_fork_and_print_env(FALSE);
+    } else if (life == LIFE_TTY) {
+        schedule_timer(TTY_LIFE_POLL_INTERVAL,
+                       tty_life_timer, &dummy_timer_ctx);
+        pageant_fork_and_print_env(TRUE);
+    } else if (life == LIFE_PERM) {
+        pageant_fork_and_print_env(FALSE);
+    } else if (life == LIFE_DEBUG) {
+        pageant_print_env(getpid());
+        pageant_logfp = stdout;
+    } else if (life == LIFE_EXEC) {
+        pid_t agentpid, pid;
+
+        agentpid = getpid();
+
+        /*
+         * Set up the pipe we'll use to tell us about SIGCHLD.
+         */
+        if (pipe(signalpipe) < 0) {
+            perror("pipe");
+            exit(1);
+        }
+        putty_signal(SIGCHLD, sigchld);
+
+        pid = fork();
+        if (pid < 0) {
+            perror("fork");
+            exit(1);
+        } else if (pid == 0) {
+            setenv("SSH_AUTH_SOCK", socketname, TRUE);
+            setenv("SSH_AGENT_PID", dupprintf("%d", (int)agentpid), TRUE);
+            execvp(exec_args[0], exec_args);
+            perror("exec");
+            _exit(127);
+        } else {
+            termination_pid = pid;
+        }
+    }
+
+    /*
+     * Now we've decided on our logging arrangements, pass them on to
+     * pageant.c.
+     */
+    pageant_listener_set_logfn(pl, NULL, pageant_logfp ? pageant_log : NULL);
+
+    now = GETTICKCOUNT();
+
+    while (!time_to_die) {
+       fd_set rset, wset, xset;
+       int maxfd;
+       int rwx;
+       int ret;
+        unsigned long next;
+
+       FD_ZERO(&rset);
+       FD_ZERO(&wset);
+       FD_ZERO(&xset);
+       maxfd = 0;
+
+        if (signalpipe[0] >= 0) {
+            FD_SET_MAX(signalpipe[0], maxfd, rset);
+        }
+
+       /* Count the currently active fds. */
+       i = 0;
+       for (fd = first_fd(&fdstate, &rwx); fd >= 0;
+            fd = next_fd(&fdstate, &rwx)) i++;
+
+       /* Expand the fdlist buffer if necessary. */
+       if (i > fdsize) {
+           fdsize = i + 16;
+           fdlist = sresize(fdlist, fdsize, int);
+       }
+
+       /*
+        * Add all currently open fds to the select sets, and store
+        * them in fdlist as well.
+        */
+       fdcount = 0;
+       for (fd = first_fd(&fdstate, &rwx); fd >= 0;
+            fd = next_fd(&fdstate, &rwx)) {
+           fdlist[fdcount++] = fd;
+           if (rwx & 1)
+               FD_SET_MAX(fd, maxfd, rset);
+           if (rwx & 2)
+               FD_SET_MAX(fd, maxfd, wset);
+           if (rwx & 4)
+               FD_SET_MAX(fd, maxfd, xset);
+       }
+
+        if (toplevel_callback_pending()) {
+            struct timeval tv;
+            tv.tv_sec = 0;
+            tv.tv_usec = 0;
+            ret = select(maxfd, &rset, &wset, &xset, &tv);
+        } else if (run_timers(now, &next)) {
+            unsigned long then;
+            long ticks;
+            struct timeval tv;
+
+            then = now;
+            now = GETTICKCOUNT();
+            if (now - then > next - then)
+                ticks = 0;
+            else
+                ticks = next - now;
+            tv.tv_sec = ticks / 1000;
+            tv.tv_usec = ticks % 1000 * 1000;
+            ret = select(maxfd, &rset, &wset, &xset, &tv);
+            if (ret == 0)
+                now = next;
+            else
+                now = GETTICKCOUNT();
+        } else {
+            ret = select(maxfd, &rset, &wset, &xset, NULL);
+        }
+
+        if (ret < 0 && errno == EINTR)
+            continue;
+
+       if (ret < 0) {
+           perror("select");
+           exit(1);
+       }
+
+        if (life == LIFE_TTY) {
+            /*
+             * Every time we wake up (whether it was due to tty_timer
+             * elapsing or for any other reason), poll to see if we
+             * still have a controlling terminal. If we don't, then
+             * our containing tty session has ended, so it's time to
+             * clean up and leave.
+             */
+            if (!have_controlling_tty()) {
+                time_to_die = TRUE;
+                break;
+            }
+        }
+
+       for (i = 0; i < fdcount; i++) {
+           fd = fdlist[i];
+            /*
+             * We must process exceptional notifications before
+             * ordinary readability ones, or we may go straight
+             * past the urgent marker.
+             */
+           if (FD_ISSET(fd, &xset))
+               select_result(fd, 4);
+           if (FD_ISSET(fd, &rset))
+               select_result(fd, 1);
+           if (FD_ISSET(fd, &wset))
+               select_result(fd, 2);
+       }
+
+        if (signalpipe[0] >= 0 && FD_ISSET(signalpipe[0], &rset)) {
+            char c[1];
+            if (read(signalpipe[0], c, 1) <= 0)
+                /* ignore error */;
+            /* ignore its value; it'll be `x' */
+            while (1) {
+                int status;
+                pid_t pid;
+                pid = waitpid(-1, &status, WNOHANG);
+                if (pid <= 0)
+                    break;
+                if (pid == termination_pid)
+                    time_to_die = TRUE;
+            }
+        }
+
+        run_toplevel_callbacks();
+    }
+
+    /*
+     * When we come here, we're terminating, and should clean up our
+     * Unix socket file if possible.
+     */
+    if (unlink(socketname) < 0) {
+        fprintf(stderr, "pageant: %s: %s\n", socketname, strerror(errno));
+        exit(1);
+    }
+}
+
+int main(int argc, char **argv)
+{
+    int doing_opts = TRUE;
+    keyact curr_keyact = KEYACT_AGENT_LOAD;
+
+    /*
+     * Process the command line.
+     */
+    while (--argc > 0) {
+       char *p = *++argv;
+       if (*p == '-' && doing_opts) {
+            if (!strcmp(p, "-V") || !strcmp(p, "--version")) {
+                version();
+           } else if (!strcmp(p, "--help")) {
+                usage();
+                exit(0);
+            } else if (!strcmp(p, "-v")) {
+                pageant_logfp = stderr;
+            } else if (!strcmp(p, "-a")) {
+                curr_keyact = KEYACT_CLIENT_ADD;
+            } else if (!strcmp(p, "-d")) {
+                curr_keyact = KEYACT_CLIENT_DEL;
+            } else if (!strcmp(p, "-D")) {
+                add_keyact(KEYACT_CLIENT_DEL_ALL, NULL);
+            } else if (!strcmp(p, "-l")) {
+                add_keyact(KEYACT_CLIENT_LIST, NULL);
+            } else if (!strcmp(p, "--public")) {
+                curr_keyact = KEYACT_CLIENT_PUBLIC;
+            } else if (!strcmp(p, "--public-openssh")) {
+                curr_keyact = KEYACT_CLIENT_PUBLIC_OPENSSH;
+            } else if (!strcmp(p, "-X")) {
+                life = LIFE_X11;
+            } else if (!strcmp(p, "-T")) {
+                life = LIFE_TTY;
+            } else if (!strcmp(p, "--debug")) {
+                life = LIFE_DEBUG;
+            } else if (!strcmp(p, "--permanent")) {
+                life = LIFE_PERM;
+            } else if (!strcmp(p, "--exec")) {
+                life = LIFE_EXEC;
+                /* Now all subsequent arguments go to the exec command. */
+                if (--argc > 0) {
+                    exec_args = ++argv;
+                    argc = 0;          /* force end of option processing */
+                } else {
+                    fprintf(stderr, "pageant: expected a command "
+                            "after --exec\n");
+                    exit(1);
+                }
+            } else if (!strcmp(p, "--")) {
+                doing_opts = FALSE;
+            }
+        } else {
+            /*
+             * Non-option arguments (apart from those after --exec,
+             * which are treated specially above) are interpreted as
+             * the names of private key files to either add or delete
+             * from an agent.
+             */
+            add_keyact(curr_keyact, p);
+        }
+    }
+
+    if (life == LIFE_EXEC && !exec_args) {
+        fprintf(stderr, "pageant: expected a command with --exec\n");
+        exit(1);
+    }
+
+    /*
+     * Block SIGPIPE, so that we'll get EPIPE individually on
+     * particular network connections that go wrong.
+     */
+    putty_signal(SIGPIPE, SIG_IGN);
+
+    sk_init();
+    uxsel_init();
+
+    if (!display) {
+        display = getenv("DISPLAY");
+        if (display && !*display)
+            display = NULL;
+    }
+
+    /*
+     * Now distinguish our two main running modes. Either we're
+     * actually starting up an agent, in which case we should have a
+     * lifetime mode, and no key actions of KEYACT_CLIENT_* type; or
+     * else we're contacting an existing agent to add or remove keys,
+     * in which case we should have no lifetime mode, and no key
+     * actions of KEYACT_AGENT_* type.
+     */
+    {
+        int has_agent_actions = FALSE;
+        int has_client_actions = FALSE;
+        int has_lifetime = FALSE;
+        const struct cmdline_key_action *act;
+
+        for (act = keyact_head; act; act = act->next) {
+            if (is_agent_action(act->action))
+                has_agent_actions = TRUE;
+            else
+                has_client_actions = TRUE;
+        }
+        if (life != LIFE_UNSPEC)
+            has_lifetime = TRUE;
+
+        if (has_lifetime && has_client_actions) {
+            fprintf(stderr, "pageant: client key actions (-a, -d, -D, -l, -L)"
+                    " do not go with an agent lifetime option\n");
+            exit(1);
+        }
+        if (!has_lifetime && has_agent_actions) {
+            fprintf(stderr, "pageant: expected an agent lifetime option with"
+                    " bare key file arguments\n");
+            exit(1);
+        }
+        if (!has_lifetime && !has_client_actions) {
+            fprintf(stderr, "pageant: expected an agent lifetime option"
+                    " or a client key action\n");
+            exit(1);
+        }
+
+        if (has_lifetime) {
+            run_agent();
+        } else if (has_client_actions) {
+            run_client();
+        }
+    }
+
+    return 0;
+}
index 90ad8d527699f47b5414ddd5e87b70d83dc35939..3d3a645fbbebae48dfdb339362f185a1314a7f84 100644 (file)
 
 #define MAX_STDIN_BACKLOG 4096
 
-void *logctx;
+static void *logctx;
 
 static struct termios orig_termios;
 
-void fatalbox(char *p, ...)
+void fatalbox(const char *p, ...)
 {
     struct termios cf;
     va_list ap;
@@ -46,7 +46,7 @@ void fatalbox(char *p, ...)
     }
     cleanup_exit(1);
 }
-void modalfatalbox(char *p, ...)
+void modalfatalbox(const char *p, ...)
 {
     struct termios cf;
     va_list ap;
@@ -63,7 +63,7 @@ void modalfatalbox(char *p, ...)
     }
     cleanup_exit(1);
 }
-void nonfatal(char *p, ...)
+void nonfatal(const char *p, ...)
 {
     struct termios cf;
     va_list ap;
@@ -75,7 +75,7 @@ void nonfatal(char *p, ...)
     fputc('\n', stderr);
     postmsg(&cf);
 }
-void connection_fatal(void *frontend, char *p, ...)
+void connection_fatal(void *frontend, const char *p, ...)
 {
     struct termios cf;
     va_list ap;
@@ -92,7 +92,7 @@ void connection_fatal(void *frontend, char *p, ...)
     }
     cleanup_exit(1);
 }
-void cmdline_error(char *p, ...)
+void cmdline_error(const char *p, ...)
 {
     struct termios cf;
     va_list ap;
@@ -150,7 +150,7 @@ int term_ldisc(Terminal *term, int mode)
 {
     return FALSE;
 }
-void ldisc_update(void *frontend, int echo, int edit)
+void frontend_echoedit_update(void *frontend, int echo, int edit)
 {
     /* Update stdin read mode to reflect changes in line discipline. */
     struct termios mode;
@@ -176,8 +176,9 @@ void ldisc_update(void *frontend, int echo, int edit)
        mode.c_cc[VMIN] = 1;
        mode.c_cc[VTIME] = 0;
        /* FIXME: perhaps what we do with IXON/IXOFF should be an
-        * argument to ldisc_update(), to allow implementation of SSH-2
-        * "xon-xoff" and Rlogin's equivalent? */
+        * argument to frontend_echoedit_update(), to allow
+        * implementation of SSH-2 "xon-xoff" and Rlogin's
+        * equivalent? */
        mode.c_iflag &= ~IXON;
        mode.c_iflag &= ~IXOFF;
     }
@@ -445,7 +446,7 @@ int from_backend_eof(void *frontend_handle)
     return FALSE;   /* do not respond to incoming EOF with outgoing */
 }
 
-int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+int get_userpass_input(prompts_t *p, const unsigned char *in, int inlen)
 {
     int ret;
     ret = cmdline_get_passwd_input(p, in, inlen);
@@ -532,8 +533,8 @@ void sigwinch(int signum)
  * In Plink our selects are synchronous, so these functions are
  * empty stubs.
  */
-int uxsel_input_add(int fd, int rwx) { return 0; }
-void uxsel_input_remove(int id) { }
+uxsel_id *uxsel_input_add(int fd, int rwx) { return NULL; }
+void uxsel_input_remove(uxsel_id *id) { }
 
 /*
  * Short description of parameters.
@@ -583,6 +584,8 @@ static void usage(void)
     printf("  -sshlog file\n");
     printf("  -sshrawlog file\n");
     printf("            log protocol details to a file\n");
+    printf("  -shareexists\n");
+    printf("            test whether a connection-sharing upstream exists\n");
     exit(1);
 }
 
@@ -609,6 +612,7 @@ int main(int argc, char **argv)
     int errors;
     int use_subsystem = 0;
     int got_host = FALSE;
+    int just_test_share_exists = FALSE;
     unsigned long now;
     struct winsize size;
 
@@ -687,6 +691,12 @@ int main(int argc, char **argv)
                     --argc;
                    provide_xrm_string(*++argv);
                }
+           } else if (!strcmp(p, "-shareexists")) {
+                just_test_share_exists = TRUE;
+           } else if (!strcmp(p, "-fuzznet")) {
+               conf_set_int(conf, CONF_proxy_type, PROXY_FUZZ);
+               conf_set_str(conf, CONF_proxy_telnet_command,
+                            "%host");
            } else {
                fprintf(stderr, "plink: unknown option \"%s\"\n", p);
                errors = 1;
@@ -961,6 +971,19 @@ int main(int argc, char **argv)
        !conf_get_str_nthstrkey(conf, CONF_portfwd, 0))
        conf_set_int(conf, CONF_ssh_simple, TRUE);
 
+    if (just_test_share_exists) {
+        if (!back->test_for_upstream) {
+            fprintf(stderr, "Connection sharing not supported for connection "
+                    "type '%s'\n", back->name);
+            return 1;
+        }
+        if (back->test_for_upstream(conf_get_str(conf, CONF_host),
+                                    conf_get_int(conf, CONF_port), conf))
+            return 0;
+        else
+            return 1;
+    }
+
     /*
      * Start up the connection.
      */
@@ -972,6 +995,11 @@ int main(int argc, char **argv)
        /* nodelay is only useful if stdin is a terminal device */
        int nodelay = conf_get_int(conf, CONF_tcp_nodelay) && isatty(0);
 
+       /* This is a good place for a fuzzer to fork us. */
+#ifdef __AFL_HAVE_MANUAL_CONTROL
+       __AFL_INIT();
+#endif
+
        error = back->init(NULL, &backhandle, conf,
                           conf_get_str(conf, CONF_host),
                           conf_get_int(conf, CONF_port),
@@ -994,7 +1022,7 @@ int main(int argc, char **argv)
      */
     local_tty = (tcgetattr(STDIN_FILENO, &orig_termios) == 0);
     atexit(cleanup_termios);
-    ldisc_update(NULL, 1, 1);
+    frontend_echoedit_update(NULL, 1, 1);
     sending = FALSE;
     now = GETTICKCOUNT();
 
index 6f1f793f68958026681c36e5fbd223e60b145012..f92c459be284ee341a35fda4c3e61b14690b75ec 100644 (file)
@@ -21,7 +21,7 @@ struct Socket_localproxy_tag {
     const struct socket_function_table *fn;
     /* the above variable absolutely *must* be the first in this structure */
 
-    int to_cmd, from_cmd;             /* fds */
+    int to_cmd, from_cmd, cmd_err;     /* fds */
 
     char *error;
 
@@ -29,6 +29,7 @@ struct Socket_localproxy_tag {
 
     bufchain pending_output_data;
     bufchain pending_input_data;
+    bufchain pending_error_data;
     enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
 };
 
@@ -37,7 +38,9 @@ static int localproxy_select_result(int fd, int event);
 /*
  * Trees to look up the pipe fds in.
  */
-static tree234 *localproxy_by_fromfd, *localproxy_by_tofd;
+static tree234 *localproxy_by_fromfd;
+static tree234 *localproxy_by_tofd;
+static tree234 *localproxy_by_errfd;
 static int localproxy_fromfd_cmp(void *av, void *bv)
 {
     Local_Proxy_Socket a = (Local_Proxy_Socket)av;
@@ -78,6 +81,26 @@ static int localproxy_tofd_find(void *av, void *bv)
        return +1;
     return 0;
 }
+static int localproxy_errfd_cmp(void *av, void *bv)
+{
+    Local_Proxy_Socket a = (Local_Proxy_Socket)av;
+    Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
+    if (a->cmd_err < b->cmd_err)
+       return -1;
+    if (a->cmd_err > b->cmd_err)
+       return +1;
+    return 0;
+}
+static int localproxy_errfd_find(void *av, void *bv)
+{
+    int a = *(int *)av;
+    Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
+    if (a < b->cmd_err)
+       return -1;
+    if (a > b->cmd_err)
+       return +1;
+    return 0;
+}
 
 /* basic proxy socket functions */
 
@@ -104,8 +127,14 @@ static void sk_localproxy_close (Socket s)
     uxsel_del(ps->from_cmd);
     close(ps->from_cmd);
 
+    del234(localproxy_by_errfd, ps);
+    uxsel_del(ps->cmd_err);
+    close(ps->cmd_err);
+
     bufchain_clear(&ps->pending_input_data);
     bufchain_clear(&ps->pending_output_data);
+    bufchain_clear(&ps->pending_error_data);
+
     sfree(ps);
 }
 
@@ -209,19 +238,26 @@ static int localproxy_select_result(int fd, int event)
     int ret;
 
     if (!(s = find234(localproxy_by_fromfd, &fd, localproxy_fromfd_find)) &&
+       !(s = find234(localproxy_by_fromfd, &fd, localproxy_errfd_find)) &&
        !(s = find234(localproxy_by_tofd, &fd, localproxy_tofd_find)) )
        return 1;                      /* boggle */
 
     if (event == 1) {
-       assert(fd == s->from_cmd);
-       ret = read(fd, buf, sizeof(buf));
-       if (ret < 0) {
-           return plug_closing(s->plug, strerror(errno), errno, 0);
-       } else if (ret == 0) {
-           return plug_closing(s->plug, NULL, 0, 0);
-       } else {
-           return plug_receive(s->plug, 0, buf, ret);
-       }
+        if (fd == s->cmd_err) {
+            ret = read(fd, buf, sizeof(buf));
+            if (ret > 0)
+                log_proxy_stderr(s->plug, &s->pending_error_data, buf, ret);
+        } else {
+            assert(fd == s->from_cmd);
+            ret = read(fd, buf, sizeof(buf));
+            if (ret < 0) {
+                return plug_closing(s->plug, strerror(errno), errno, 0);
+            } else if (ret == 0) {
+                return plug_closing(s->plug, NULL, 0, 0);
+            } else {
+                return plug_receive(s->plug, 0, buf, ret);
+            }
+        }
     } else if (event == 2) {
        assert(fd == s->to_cmd);
        if (localproxy_try_send(s))
@@ -232,7 +268,7 @@ static int localproxy_select_result(int fd, int event)
     return 1;
 }
 
-Socket platform_new_connection(SockAddr addr, char *hostname,
+Socket platform_new_connection(SockAddr addr, const char *hostname,
                               int port, int privport,
                               int oobinline, int nodelay, int keepalive,
                               Plug plug, Conf *conf)
@@ -252,13 +288,12 @@ Socket platform_new_connection(SockAddr addr, char *hostname,
     };
 
     Local_Proxy_Socket ret;
-    int to_cmd_pipe[2], from_cmd_pipe[2], pid;
+    int to_cmd_pipe[2], from_cmd_pipe[2], cmd_err_pipe[2], pid, proxytype;
 
-    if (conf_get_int(conf, CONF_proxy_type) != PROXY_CMD)
+    proxytype = conf_get_int(conf, CONF_proxy_type);
+    if (proxytype != PROXY_CMD && proxytype != PROXY_FUZZ)
        return NULL;
 
-    cmd = format_telnet_command(addr, port, conf);
-
     ret = snew(struct Socket_localproxy_tag);
     ret->fn = &socket_fn_table;
     ret->plug = plug;
@@ -267,56 +302,111 @@ Socket platform_new_connection(SockAddr addr, char *hostname,
 
     bufchain_init(&ret->pending_input_data);
     bufchain_init(&ret->pending_output_data);
+    bufchain_init(&ret->pending_error_data);
+
+    if (proxytype == PROXY_CMD) {
+       cmd = format_telnet_command(addr, port, conf);
+
+        if (flags & FLAG_STDERR) {
+            /* If we have a sensible stderr, the proxy command can
+             * send its own standard error there, so we won't
+             * interfere. */
+            cmd_err_pipe[0] = cmd_err_pipe[1] = -1;
+        } else {
+            /* If we don't have a sensible stderr, we should catch the
+             * proxy command's standard error to put in our event
+             * log. */
+            cmd_err_pipe[0] = cmd_err_pipe[1] = 0;
+        }
+
+        {
+            char *logmsg = dupprintf("Starting local proxy command: %s", cmd);
+            plug_log(plug, 2, NULL, 0, logmsg, 0);
+            sfree(logmsg);
+        }
+
+       /*
+        * Create the pipes to the proxy command, and spawn the proxy
+        * command process.
+        */
+       if (pipe(to_cmd_pipe) < 0 ||
+           pipe(from_cmd_pipe) < 0 ||
+            (cmd_err_pipe[0] == 0 && pipe(cmd_err_pipe) < 0)) {
+           ret->error = dupprintf("pipe: %s", strerror(errno));
+           sfree(cmd);
+           return (Socket)ret;
+       }
+       cloexec(to_cmd_pipe[1]);
+       cloexec(from_cmd_pipe[0]);
+       if (cmd_err_pipe[0] >= 0)
+            cloexec(cmd_err_pipe[0]);
+
+       pid = fork();
+
+       if (pid < 0) {
+           ret->error = dupprintf("fork: %s", strerror(errno));
+           sfree(cmd);
+           return (Socket)ret;
+       } else if (pid == 0) {
+           close(0);
+           close(1);
+           dup2(to_cmd_pipe[0], 0);
+           dup2(from_cmd_pipe[1], 1);
+           close(to_cmd_pipe[0]);
+           close(from_cmd_pipe[1]);
+           if (cmd_err_pipe[0] >= 0) {
+                dup2(cmd_err_pipe[1], 2);
+                close(cmd_err_pipe[1]);
+            }
+           noncloexec(0);
+           noncloexec(1);
+           execl("/bin/sh", "sh", "-c", cmd, (void *)NULL);
+           _exit(255);
+       }
+
+       sfree(cmd);
 
-    /*
-     * Create the pipes to the proxy command, and spawn the proxy
-     * command process.
-     */
-    if (pipe(to_cmd_pipe) < 0 ||
-       pipe(from_cmd_pipe) < 0) {
-       ret->error = dupprintf("pipe: %s", strerror(errno));
-        sfree(cmd);
-       return (Socket)ret;
-    }
-    cloexec(to_cmd_pipe[1]);
-    cloexec(from_cmd_pipe[0]);
-
-    pid = fork();
-
-    if (pid < 0) {
-       ret->error = dupprintf("fork: %s", strerror(errno));
-        sfree(cmd);
-       return (Socket)ret;
-    } else if (pid == 0) {
-       close(0);
-       close(1);
-       dup2(to_cmd_pipe[0], 0);
-       dup2(from_cmd_pipe[1], 1);
        close(to_cmd_pipe[0]);
        close(from_cmd_pipe[1]);
-       noncloexec(0);
-       noncloexec(1);
-       execl("/bin/sh", "sh", "-c", cmd, (void *)NULL);
-       _exit(255);
+        if (cmd_err_pipe[0] >= 0)
+            close(cmd_err_pipe[1]);
+
+       ret->to_cmd = to_cmd_pipe[1];
+       ret->from_cmd = from_cmd_pipe[0];
+       ret->cmd_err = cmd_err_pipe[0];
+    } else {
+       cmd = format_telnet_command(addr, port, conf);
+       ret->to_cmd = open("/dev/null", O_WRONLY);
+       if (ret->to_cmd == -1) {
+           ret->error = dupprintf("/dev/null: %s", strerror(errno));
+           sfree(cmd);
+           return (Socket)ret;
+       }
+       ret->from_cmd = open(cmd, O_RDONLY);
+       if (ret->from_cmd == -1) {
+           ret->error = dupprintf("%s: %s", cmd, strerror(errno));
+           sfree(cmd);
+           return (Socket)ret;
+       }
+       sfree(cmd);
+       ret->cmd_err = -1;
     }
 
-    sfree(cmd);
-
-    close(to_cmd_pipe[0]);
-    close(from_cmd_pipe[1]);
-
-    ret->to_cmd = to_cmd_pipe[1];
-    ret->from_cmd = from_cmd_pipe[0];
-
     if (!localproxy_by_fromfd)
        localproxy_by_fromfd = newtree234(localproxy_fromfd_cmp);
     if (!localproxy_by_tofd)
        localproxy_by_tofd = newtree234(localproxy_tofd_cmp);
+    if (!localproxy_by_errfd)
+       localproxy_by_errfd = newtree234(localproxy_errfd_cmp);
 
     add234(localproxy_by_fromfd, ret);
     add234(localproxy_by_tofd, ret);
+    if (ret->cmd_err >= 0)
+        add234(localproxy_by_errfd, ret);
 
     uxsel_set(ret->from_cmd, 1, localproxy_select_result);
+    if (ret->cmd_err >= 0)
+        uxsel_set(ret->cmd_err, 1, localproxy_select_result);
 
     /* We are responsible for this and don't need it any more */
     sk_addr_free(addr);
index 1f4f20c2aaf363c32a1e2ec4e24d6649ccf1518c..3211d45a54135ac44ef73b8587278b317dd89ff5 100644 (file)
@@ -33,7 +33,7 @@ void cleanup_exit(int code)
     exit(code);
 }
 
-int process_nonoption_arg(char *arg, Conf *conf, int *allow_launch)
+int process_nonoption_arg(const char *arg, Conf *conf, int *allow_launch)
 {
     return 0;                          /* pterm doesn't have any. */
 }
index e504b7050a8d28f9adc1aedba0421c18b62fb05a..79a60f3c139c4fbc75517f9f9392c0d8a73db4be 100644 (file)
@@ -259,11 +259,13 @@ static void cleanup_utmp(void)
 }
 #endif
 
+#ifndef NO_PTY_PRE_INIT
 static void sigchld_handler(int signum)
 {
     if (write(pty_signal_pipe[1], "x", 1) <= 0)
        /* not much we can do about it */;
 }
+#endif
 
 #ifndef OMIT_UTMP
 static void fatal_sig_handler(int signum)
@@ -342,7 +344,18 @@ static void pty_open_master(Pty pty)
         ;
 
 #ifdef HAVE_POSIX_OPENPT
+#ifdef SET_NONBLOCK_VIA_OPENPT
+    /*
+     * OS X, as of 10.10 at least, doesn't permit me to set O_NONBLOCK
+     * on pty master fds via the usual fcntl mechanism. Fortunately,
+     * it does let me work around this by adding O_NONBLOCK to the
+     * posix_openpt flags parameter, which isn't a documented use of
+     * the API but seems to work. So we'll do that for now.
+     */
+    pty->master_fd = posix_openpt(flags | O_NONBLOCK);
+#else
     pty->master_fd = posix_openpt(flags);
+#endif
 
     if (pty->master_fd < 0) {
        perror("posix_openpt");
@@ -373,13 +386,23 @@ static void pty_open_master(Pty pty)
     strncpy(pty->name, ptsname(pty->master_fd), FILENAME_MAX-1);
 #endif
 
+#ifndef SET_NONBLOCK_VIA_OPENPT
     nonblock(pty->master_fd);
+#endif
 
     if (!ptys_by_fd)
        ptys_by_fd = newtree234(pty_compare_by_fd);
     add234(ptys_by_fd, pty);
 }
 
+static Pty new_pty_struct(void)
+{
+    Pty pty = snew(struct pty_tag);
+    pty->conf = NULL;
+    bufchain_init(&pty->output_data);
+    return pty;
+}
+
 /*
  * Pre-initialisation. This is here to get around the fact that GTK
  * doesn't like being run in setuid/setgid programs (probably
@@ -395,6 +418,8 @@ static void pty_open_master(Pty pty)
  */
 void pty_pre_init(void)
 {
+#ifndef NO_PTY_PRE_INIT
+
     Pty pty;
 
 #ifndef OMIT_UTMP
@@ -402,9 +427,7 @@ void pty_pre_init(void)
     int pipefd[2];
 #endif
 
-    pty = single_pty = snew(struct pty_tag);
-    pty->conf = NULL;
-    bufchain_init(&pty->output_data);
+    pty = single_pty = new_pty_struct();
 
     /* set the child signal handler straight away; it needs to be set
      * before we ever fork. */
@@ -542,6 +565,9 @@ void pty_pre_init(void)
         }
 #endif
     }
+
+#endif /* NO_PTY_PRE_INIT */
+
 }
 
 int pty_real_select_result(Pty pty, int event, int status)
@@ -706,8 +732,8 @@ static void pty_uxsel_setup(Pty pty)
  * freed by the caller.
  */
 static const char *pty_init(void *frontend, void **backend_handle, Conf *conf,
-                           char *host, int port, char **realhost, int nodelay,
-                           int keepalive)
+                           const char *host, int port, char **realhost,
+                            int nodelay, int keepalive)
 {
     int slavefd;
     pid_t pid, pgrp;
@@ -720,7 +746,7 @@ static const char *pty_init(void *frontend, void **backend_handle, Conf *conf,
        pty = single_pty;
         assert(pty->conf == NULL);
     } else {
-       pty = snew(struct pty_tag);
+       pty = new_pty_struct();
        pty->master_fd = pty->slave_fd = -1;
 #ifndef OMIT_UTMP
        pty_stamped_utmp = FALSE;
@@ -737,33 +763,6 @@ static const char *pty_init(void *frontend, void **backend_handle, Conf *conf,
     if (pty->master_fd < 0)
        pty_open_master(pty);
 
-    /*
-     * Set up configuration-dependent termios settings on the new pty.
-     */
-    {
-       struct termios attrs;
-       tcgetattr(pty->master_fd, &attrs);
-
-        /*
-         * Set the backspace character to be whichever of ^H and ^? is
-         * specified by bksp_is_delete.
-         */
-       attrs.c_cc[VERASE] = conf_get_int(conf, CONF_bksp_is_delete)
-           ? '\177' : '\010';
-
-        /*
-         * Set the IUTF8 bit iff the character set is UTF-8.
-         */
-#ifdef IUTF8
-        if (frontend_is_utf8(frontend))
-            attrs.c_iflag |= IUTF8;
-        else
-            attrs.c_iflag &= ~IUTF8;
-#endif
-
-       tcsetattr(pty->master_fd, TCSANOW, &attrs);
-    }
-
 #ifndef OMIT_UTMP
     /*
      * Stamp utmp (that is, tell the utmp helper process to do so),
@@ -774,7 +773,7 @@ static const char *pty_init(void *frontend, void **backend_handle, Conf *conf,
             close(pty_utmp_helper_pipe);   /* just let the child process die */
             pty_utmp_helper_pipe = -1;
         } else {
-            char *location = get_x_display(pty->frontend);
+            const char *location = get_x_display(pty->frontend);
             int len = strlen(location)+1, pos = 0;   /* +1 to include NUL */
             while (pos < len) {
                 int ret = write(pty_utmp_helper_pipe, location+pos, len - pos);
@@ -804,6 +803,8 @@ static const char *pty_init(void *frontend, void **backend_handle, Conf *conf,
     }
 
     if (pid == 0) {
+        struct termios attrs;
+
        /*
         * We are the child.
         */
@@ -826,6 +827,34 @@ static const char *pty_init(void *frontend, void **backend_handle, Conf *conf,
 #endif
        pgrp = getpid();
        tcsetpgrp(0, pgrp);
+
+        /*
+         * Set up configuration-dependent termios settings on the new
+         * pty. Linux would have let us do this on the pty master
+         * before we forked, but that fails on OS X, so we do it here
+         * instead.
+         */
+       if (tcgetattr(0, &attrs) == 0) {
+            /*
+             * Set the backspace character to be whichever of ^H and
+             * ^? is specified by bksp_is_delete.
+             */
+            attrs.c_cc[VERASE] = conf_get_int(conf, CONF_bksp_is_delete)
+                ? '\177' : '\010';
+
+            /*
+             * Set the IUTF8 bit iff the character set is UTF-8.
+             */
+#ifdef IUTF8
+            if (frontend_is_utf8(frontend))
+                attrs.c_iflag |= IUTF8;
+            else
+                attrs.c_iflag &= ~IUTF8;
+#endif
+
+            tcsetattr(0, TCSANOW, &attrs);
+        }
+
        setpgid(pgrp, pgrp);
         {
             int ptyfd = open(pty->name, O_WRONLY, 0);
@@ -993,6 +1022,8 @@ static void pty_free(void *handle)
     del234(ptys_by_pid, pty);
     del234(ptys_by_fd, pty);
 
+    bufchain_clear(&pty->output_data);
+
     conf_free(pty->conf);
     pty->conf = NULL;
 
@@ -1036,7 +1067,7 @@ static void pty_try_write(Pty pty)
 /*
  * Called to send data down the pty.
  */
-static int pty_send(void *handle, char *buf, int len)
+static int pty_send(void *handle, const char *buf, int len)
 {
     Pty pty = (Pty)handle;
 
@@ -1187,6 +1218,7 @@ Backend pty_backend = {
     pty_provide_logctx,
     pty_unthrottle,
     pty_cfg_info,
+    NULL /* test_for_upstream */,
     "pty",
     -1,
     0
index c7b0fcb2209c05a97db9be7caf7d6d71ec9543ad..d0ba55f68177a7bc68da8084d21e96a8104f692a 100644 (file)
@@ -7,11 +7,14 @@
 #include <stdlib.h>
 #include <assert.h>
 #include <unistd.h>
+#include <gtk/gtk.h>
 #include <gdk/gdk.h>
 
 #include "putty.h"
 #include "storage.h"
 
+#include "gtkcompat.h"
+
 /*
  * Stubs to avoid uxpty.c needing to be linked in.
  */
@@ -50,9 +53,11 @@ static int got_host = 0;
 
 const int use_event_log = 1, new_session = 1, saved_sessions = 1;
 
-int process_nonoption_arg(char *arg, Conf *conf, int *allow_launch)
+int process_nonoption_arg(const char *arg, Conf *conf, int *allow_launch)
 {
-    char *p, *q = arg;
+    char *argdup, *p, *q;
+    argdup = dupstr(arg);
+    q = argdup;
 
     if (got_host) {
         /*
@@ -61,7 +66,7 @@ int process_nonoption_arg(char *arg, Conf *conf, int *allow_launch)
          * argument, so that it will be deferred until it's a good
          * moment to run it.
          */
-        int ret = cmdline_process_param("-P", arg, 1, conf);
+        int ret = cmdline_process_param("-P", argdup, 1, conf);
         assert(ret == 2);
     } else if (!strncmp(q, "telnet:", 7)) {
         /*
@@ -90,7 +95,7 @@ int process_nonoption_arg(char *arg, Conf *conf, int *allow_launch)
         /*
          * Otherwise, treat this argument as a host name.
          */
-        p = arg;
+        p = argdup;
         while (*p && !isspace((unsigned char)*p))
             p++;
         if (*p)
@@ -100,6 +105,9 @@ int process_nonoption_arg(char *arg, Conf *conf, int *allow_launch)
     }
     if (got_host)
        *allow_launch = TRUE;
+
+    sfree(argdup);
+
     return 1;
 }
 
index e2979c9a01e6aed34f27c5f2bf440bb5dd083717..ef25cdb574e2a6cf7fe4bb95112be1e05e52b947 100644 (file)
@@ -19,7 +19,7 @@ struct fd {
     int fd;
     int rwx;                          /* 4=except 2=write 1=read */
     uxsel_callback_fn callback;
-    int id;                           /* for uxsel_input_remove */
+    uxsel_id *id;                      /* for uxsel_input_remove */
 };
 
 static tree234 *fds;
@@ -64,6 +64,8 @@ void uxsel_set(int fd, int rwx, uxsel_callback_fn callback)
 {
     struct fd *newfd;
 
+    assert(fd >= 0);
+
     uxsel_del(fd);
 
     if (rwx) {
@@ -80,7 +82,8 @@ void uxsel_del(int fd)
 {
     struct fd *oldfd = find234(fds, &fd, uxsel_fd_findcmp);
     if (oldfd) {
-       uxsel_input_remove(oldfd->id);
+       if (oldfd->id)
+            uxsel_input_remove(oldfd->id);
        del234(fds, oldfd);
        sfree(oldfd);
     }
index e45f3ae1232a2d21139d9d2f0407acf02289187f..41beaf0e75699866efe5f9080e572ad5f791df9d 100644 (file)
@@ -289,8 +289,8 @@ static const char *serial_configure(Serial serial, Conf *conf)
  */
 static const char *serial_init(void *frontend_handle, void **backend_handle,
                               Conf *conf,
-                              char *host, int port, char **realhost, int nodelay,
-                              int keepalive)
+                              const char *host, int port, char **realhost,
+                               int nodelay, int keepalive)
 {
     Serial serial;
     const char *err;
@@ -462,7 +462,7 @@ static void serial_try_write(Serial serial)
 /*
  * Called to send data down the serial connection.
  */
-static int serial_send(void *handle, char *buf, int len)
+static int serial_send(void *handle, const char *buf, int len)
 {
     Serial serial = (Serial) handle;
 
@@ -591,6 +591,7 @@ Backend serial_backend = {
     serial_provide_logctx,
     serial_unthrottle,
     serial_cfg_info,
+    NULL /* test_for_upstream */,
     "serial",
     PROT_SERIAL,
     0
index 391da02126233abb52e59276a86d3721a9aef92f..3ac1d2c346470242955b96ff61825284e78126f0 100644 (file)
@@ -26,8 +26,8 @@
  * In PSFTP our selects are synchronous, so these functions are
  * empty stubs.
  */
-int uxsel_input_add(int fd, int rwx) { return 0; }
-void uxsel_input_remove(int id) { }
+uxsel_id *uxsel_input_add(int fd, int rwx) { return NULL; }
+void uxsel_input_remove(uxsel_id *id) { }
 
 char *x_get_default(const char *key)
 {
@@ -68,7 +68,7 @@ Filename *platform_default_filename(const char *name)
 
 char *get_ttymode(void *frontend, const char *mode) { return NULL; }
 
-int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+int get_userpass_input(prompts_t *p, const unsigned char *in, int inlen)
 {
     int ret;
     ret = cmdline_get_passwd_input(p, in, inlen);
@@ -120,7 +120,7 @@ struct RFile {
     int fd;
 };
 
-RFile *open_existing_file(char *name, uint64 *size,
+RFile *open_existing_file(const char *name, uint64 *size,
                          unsigned long *mtime, unsigned long *atime,
                           long *perms)
 {
@@ -174,7 +174,7 @@ struct WFile {
     char *name;
 };
 
-WFile *open_new_file(char *name, long perms)
+WFile *open_new_file(const char *name, long perms)
 {
     int fd;
     WFile *ret;
@@ -192,7 +192,7 @@ WFile *open_new_file(char *name, long perms)
 }
 
 
-WFile *open_existing_wfile(char *name, uint64 *size)
+WFile *open_existing_wfile(const char *name, uint64 *size)
 {
     int fd;
     WFile *ret;
@@ -298,7 +298,7 @@ uint64 get_file_posn(WFile *f)
     return ret;
 }
 
-int file_type(char *name)
+int file_type(const char *name)
 {
     struct stat statbuf;
 
@@ -321,7 +321,7 @@ struct DirHandle {
     DIR *dir;
 };
 
-DirHandle *open_directory(char *name)
+DirHandle *open_directory(const char *name)
 {
     DIR *dir;
     DirHandle *ret;
@@ -356,7 +356,7 @@ void close_directory(DirHandle *dir)
     sfree(dir);
 }
 
-int test_wildcard(char *name, int cmdline)
+int test_wildcard(const char *name, int cmdline)
 {
     struct stat statbuf;
 
@@ -390,7 +390,7 @@ struct WildcardMatcher {
     glob_t globbed;
     int i;
 };
-WildcardMatcher *begin_wildcard_matching(char *name) {
+WildcardMatcher *begin_wildcard_matching(const char *name) {
     WildcardMatcher *ret = snew(WildcardMatcher);
 
     if (glob(name, 0, NULL, &ret->globbed) < 0) {
@@ -413,7 +413,21 @@ void finish_wildcard_matching(WildcardMatcher *dir) {
     sfree(dir);
 }
 
-int vet_filename(char *name)
+char *stripslashes(const char *str, int local)
+{
+    char *p;
+
+    /*
+     * On Unix, we do the same thing regardless of the 'local'
+     * parameter.
+     */
+    p = strrchr(str, '/');
+    if (p) str = p+1;
+
+    return (char *)str;
+}
+
+int vet_filename(const char *name)
 {
     if (strchr(name, '/'))
        return FALSE;
@@ -424,12 +438,12 @@ int vet_filename(char *name)
     return TRUE;
 }
 
-int create_directory(char *name)
+int create_directory(const char *name)
 {
     return mkdir(name, 0777) == 0;
 }
 
-char *dir_file_cat(char *dir, char *file)
+char *dir_file_cat(const char *dir, const char *file)
 {
     return dupcat(dir, "/", file, NULL);
 }
@@ -559,7 +573,7 @@ int ssh_sftp_loop_iteration(void)
 /*
  * Read a PSFTP command line from stdin.
  */
-char *ssh_sftp_get_cmdline(char *prompt, int no_fds_ok)
+char *ssh_sftp_get_cmdline(const char *prompt, int no_fds_ok)
 {
     char *buf;
     int buflen, bufsize, ret;
index 3da52defbea7e9db1d21f3580f56086cdc54a02a..9279a04983984db408f0a6a8ecdd10150a44af4f 100644 (file)
@@ -42,37 +42,6 @@ static char *make_parentdir_name(void)
     return parent;
 }
 
-static char *make_dir_and_check_ours(const char *dirname)
-{
-    struct stat st;
-
-    /*
-     * Create the directory. We might have created it before, so
-     * EEXIST is an OK error; but anything else is doom.
-     */
-    if (mkdir(dirname, 0700) < 0 && errno != EEXIST)
-        return dupprintf("%s: mkdir: %s", dirname, strerror(errno));
-
-    /*
-     * Now check that that directory is _owned by us_ and not writable
-     * by anybody else. This protects us against somebody else
-     * previously having created the directory in a way that's
-     * writable to us, and thus manipulating us into creating the
-     * actual socket in a directory they can see so that they can
-     * connect to it and use our authenticated SSH sessions.
-     */
-    if (stat(dirname, &st) < 0)
-        return dupprintf("%s: stat: %s", dirname, strerror(errno));
-    if (st.st_uid != getuid())
-        return dupprintf("%s: directory owned by uid %d, not by us",
-                         dirname, st.st_uid);
-    if ((st.st_mode & 077) != 0)
-        return dupprintf("%s: directory has overgenerous permissions %03o"
-                         " (expected 700)", dirname, st.st_mode & 0777);
-
-    return NULL;
-}
-
 static char *make_dirname(const char *pi_name, char **logtext)
 {
     char *name, *parentdirname, *dirname, *err;
index 567b08d6ee51e52e5259a6af6c2979bc2d32b2cb..51242eab1ad018fb6f344442ab3cbde2d82289ed 100644 (file)
@@ -589,6 +589,16 @@ int verify_host_key(const char *hostname, int port,
     return ret;
 }
 
+int have_ssh_host_key(const char *hostname, int port,
+                     const char *keytype)
+{
+    /*
+     * If we have a host key, verify_host_key will return 0 or 2.
+     * If we don't have one, it'll return 1.
+     */
+    return verify_host_key(hostname, port, keytype, "") != 1;
+}
+
 void store_host_key(const char *hostname, int port,
                    const char *keytype, const char *key)
 {
index 5d3d5af0924a7caeea823fcf27f058ca73fc3161..86cc33d4f5883573e671ccba74d21706230eab5c 100644 (file)
@@ -57,7 +57,7 @@ int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen,
 }
 
 int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen,
-            char *mbstr, int mblen, char *defchr, int *defused,
+            char *mbstr, int mblen, const char *defchr, int *defused,
             struct unicode_data *ucsdata)
 {
     /* FIXME: we should remove the defused param completely... */
index 5a1b49ce62bd9c1dc952860793d0188cb5ef5778..78f7b8ffd278bdf7f06aedd6dc36582d06ec3c77 100644 (file)
@@ -5,7 +5,7 @@
 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
 <assemblyIdentity
    version="0.0.0.0"
-   processorArchitecture="x86"
+   processorArchitecture="*"
    name="Pageant"
    type="win32" />
    <description>PuTTY SSH authentication agent</description>
@@ -18,7 +18,7 @@
              version="6.0.0.0" 
              publicKeyToken="6595b64144ccf1df"
              language="*" 
-             processorArchitecture="x86"/> 
+             processorArchitecture="*"/>
    </dependentAssembly>
    </dependency>
    <!-- Declare us to be "DPI-aware". -->
index 9fa7792e3f7c8d51bbac86f0c2d0a48fe7a2fc1a..53c06aad4119679a7073429efd31b026b39f7379 100644 (file)
@@ -5,7 +5,7 @@
 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
 <assemblyIdentity
    version="0.0.0.0"
-   processorArchitecture="x86"
+   processorArchitecture="*"
    name="PuTTY"
    type="win32" />
    <description>A network client and terminal emulator</description>
@@ -18,7 +18,7 @@
              version="6.0.0.0" 
              publicKeyToken="6595b64144ccf1df"
              language="*" 
-             processorArchitecture="x86"/> 
+             processorArchitecture="*"/>
    </dependentAssembly>
    </dependency>
    <!-- Declare us to be "DPI-aware". -->
index f425980f26e4be33f50e9decfd6f9756ab342c05..2c59ba7c5e95785cc758fc07a342c5035775f4d4 100644 (file)
@@ -5,7 +5,7 @@
 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
 <assemblyIdentity
    version="0.0.0.0"
-   processorArchitecture="x86"
+   processorArchitecture="*"
    name="PuTTYgen"
    type="win32" />
    <description>SSH key generator for PuTTY</description>
@@ -18,7 +18,7 @@
              version="6.0.0.0" 
              publicKeyToken="6595b64144ccf1df"
              language="*" 
-             processorArchitecture="x86"/> 
+             processorArchitecture="*"/>
    </dependentAssembly>
    </dependency>
    <!-- Declare us to be "DPI-aware". -->
index 198ff9a768fb6a455d7d3c31ef2e03dda17c5454..0b60e1849712a879477490f8803350a65517408a 100644 (file)
@@ -45,8 +45,8 @@ void timer_change_notify(unsigned long next)
 {
 }
 
-int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
-                        char *keystr, char *fingerprint,
+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)
 {
     int ret;
@@ -306,7 +306,8 @@ static void console_data_untrusted(HANDLE hout, const char *data, int len)
     WriteFile(hout, data, len, &dummy, NULL);
 }
 
-int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+int console_get_userpass_input(prompts_t *p,
+                               const unsigned char *in, int inlen)
 {
     HANDLE hin, hout;
     size_t curr_prompt;
index 9bee927472d5fb36bd7e1a653682bd6cbe99e6ba..428b6e5565a0e449dadf98c6403dd3f73b8436ad 100644 (file)
@@ -372,7 +372,6 @@ void checkbox(struct ctlpos *cp, char *text, int id)
 char *staticwrap(struct ctlpos *cp, HWND hwnd, char *text, int *lines)
 {
     HDC hdc = GetDC(hwnd);
-    int lpx = GetDeviceCaps(hdc, LOGPIXELSX);
     int width, nlines, j;
     INT *pwidths, nfit;
     SIZE size;
@@ -2406,7 +2405,7 @@ void dlg_beep(void *dlg)
     MessageBeep(0);
 }
 
-void dlg_error_msg(void *dlg, char *msg)
+void dlg_error_msg(void *dlg, const char *msg)
 {
     struct dlgparam *dp = (struct dlgparam *)dlg;
     MessageBox(dp->hwnd, msg,
index 61ba9ad1a0e2abfa7551643172bddd53a15f4bd9..8248ce835974fd8ffdc3142da7d118ea0f8f0913 100644 (file)
@@ -513,6 +513,7 @@ static int CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg,
              * And create the actual control set for that panel, to
              * match the initial treeview selection.
              */
+            assert(firstpath);   /* config.c must have given us _something_ */
             create_controls(hwnd, firstpath);
            dlg_refresh(NULL, &dp);    /* and set up control values */
        }
@@ -785,8 +786,8 @@ void showabout(HWND hwnd)
     DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
 }
 
-int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
-                        char *keystr, char *fingerprint,
+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)
 {
     int ret;
index 03325054bf79983bd3bcc463bb5d41b2b334679d..fc51e57fb2b5fbbe95fbdc37d76831dd7cee2e82 100644 (file)
@@ -225,7 +225,7 @@ const int share_can_be_downstream = TRUE;
 const int share_can_be_upstream = TRUE;
 
 /* Dummy routine, only required in plink. */
-void ldisc_update(void *frontend, int echo, int edit)
+void frontend_echoedit_update(void *frontend, int echo, int edit)
 {
 }
 
@@ -1150,7 +1150,7 @@ void set_raw_mouse_mode(void *frontend, int activate)
 /*
  * Print a message box and close the connection.
  */
-void connection_fatal(void *frontend, char *fmt, ...)
+void connection_fatal(void *frontend, const char *fmt, ...)
 {
     va_list ap;
     char *stuff, morestuff[100];
@@ -1172,7 +1172,7 @@ void connection_fatal(void *frontend, char *fmt, ...)
 /*
  * Report an error at the command-line parsing stage.
  */
-void cmdline_error(char *fmt, ...)
+void cmdline_error(const char *fmt, ...)
 {
     va_list ap;
     char *stuff, morestuff[100];
@@ -2189,7 +2189,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
                    unsigned int sessno = ((lParam - IDM_SAVED_MIN)
                                           / MENU_SAVED_STEP) + 1;
                    if (sessno < (unsigned)sesslist.nsessions) {
-                       char *session = sesslist.sessions[sessno];
+                       const char *session = sesslist.sessions[sessno];
                        cl = dupprintf("putty @%s", session);
                        inherit_handles = FALSE;
                        freecl = TRUE;
@@ -2282,7 +2282,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
                 */
                if (ldisc) {
                     ldisc_configure(ldisc, conf);
-                   ldisc_send(ldisc, NULL, 0, 0);
+                   ldisc_echoedit_update(ldisc);
                 }
                if (pal)
                    DeleteObject(pal);
@@ -2425,7 +2425,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
          case IDM_RESET:
            term_pwron(term, TRUE);
            if (ldisc)
-               ldisc_send(ldisc, NULL, 0, 0);
+               ldisc_echoedit_update(ldisc);
            break;
          case IDM_ABOUT:
            showabout(hwnd);
@@ -5381,7 +5381,7 @@ void optimised_move(void *frontend, int to, int from, int lines)
 /*
  * Print a message box and perform a fatal exit.
  */
-void fatalbox(char *fmt, ...)
+void fatalbox(const char *fmt, ...)
 {
     va_list ap;
     char *stuff, morestuff[100];
@@ -5398,7 +5398,7 @@ void fatalbox(char *fmt, ...)
 /*
  * Print a modal (Really Bad) message box and perform a fatal exit.
  */
-void modalfatalbox(char *fmt, ...)
+void modalfatalbox(const char *fmt, ...)
 {
     va_list ap;
     char *stuff, morestuff[100];
@@ -5416,7 +5416,7 @@ void modalfatalbox(char *fmt, ...)
 /*
  * Print a message box and don't close the connection.
  */
-void nonfatal(char *fmt, ...)
+void nonfatal(const char *fmt, ...)
 {
     va_list ap;
     char *stuff, morestuff[100];
@@ -5823,7 +5823,7 @@ int from_backend_eof(void *frontend)
     return TRUE;   /* do respond to incoming EOF with outgoing */
 }
 
-int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+int get_userpass_input(prompts_t *p, const unsigned char *in, int inlen)
 {
     int ret;
     ret = cmdline_get_passwd_input(p, in, inlen);
index d08064d4bd537534d8b5662c2aaeeb8a87af43e3..0187a5a7c19bde2359cf3ea622550bc8aa1ae276 100644 (file)
@@ -355,7 +355,7 @@ static Ssh_gss_stat ssh_sspi_display_status(struct ssh_gss_library *lib,
                                            Ssh_gss_ctx ctx, Ssh_gss_buf *buf)
 {
     winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) ctx;
-    char *msg;
+    const char *msg;
 
     if (winctx == NULL) return SSH_GSS_FAILURE;
 
index b4d508b0ef30f02501223e8e3373b332631853fb..e5a5e2d541aea5c00848bd8d314fd33a65f50636 100644 (file)
@@ -411,9 +411,9 @@ static int handle_cmp_evtomain(void *av, void *bv)
     struct handle *a = (struct handle *)av;
     struct handle *b = (struct handle *)bv;
 
-    if ((unsigned)a->u.g.ev_to_main < (unsigned)b->u.g.ev_to_main)
+    if ((uintptr_t)a->u.g.ev_to_main < (uintptr_t)b->u.g.ev_to_main)
        return -1;
-    else if ((unsigned)a->u.g.ev_to_main > (unsigned)b->u.g.ev_to_main)
+    else if ((uintptr_t)a->u.g.ev_to_main > (uintptr_t)b->u.g.ev_to_main)
        return +1;
     else
        return 0;
@@ -424,9 +424,9 @@ static int handle_find_evtomain(void *av, void *bv)
     HANDLE *a = (HANDLE *)av;
     struct handle *b = (struct handle *)bv;
 
-    if ((unsigned)*a < (unsigned)b->u.g.ev_to_main)
+    if ((uintptr_t)*a < (uintptr_t)b->u.g.ev_to_main)
        return -1;
-    else if ((unsigned)*a > (unsigned)b->u.g.ev_to_main)
+    else if ((uintptr_t)*a > (uintptr_t)b->u.g.ev_to_main)
        return +1;
     else
        return 0;
index 8b652c7b15eb49c9f80fd20f34857fe2b7b9310d..64f6ad4597ac1dad4609e57e878a3ff5684bb27d 100644 (file)
@@ -105,7 +105,7 @@ void launch_help(HWND hwnd, const char *topic)
 #endif /* NO_HTMLHELP */
        if (help_path) {
            char *cmd = dupprintf("JI(`',`%.*s')", colonpos, topic);
-           WinHelp(hwnd, help_path, HELP_COMMAND, (DWORD)cmd);
+           WinHelp(hwnd, help_path, HELP_COMMAND, (ULONG_PTR)cmd);
            sfree(cmd);
        }
     } else {
index f52d5211572f3ad1e98d6194dd6793e777f9b11e..6c00181589b6f5693facb622b583ab2ab61f1162 100644 (file)
@@ -18,8 +18,8 @@ struct Socket_handle_tag {
     const struct socket_function_table *fn;
     /* the above variable absolutely *must* be the first in this structure */
 
-    HANDLE send_H, recv_H;
-    struct handle *send_h, *recv_h;
+    HANDLE send_H, recv_H, stderr_H;
+    struct handle *send_h, *recv_h, *stderr_h;
 
     /*
      * Freezing one of these sockets is a slightly fiddly business,
@@ -39,6 +39,9 @@ struct Socket_handle_tag {
     /* We buffer data here if we receive it from winhandl while frozen. */
     bufchain inputdata;
 
+    /* Data received from stderr_H, if we have one. */
+    bufchain stderrdata;
+
     char *error;
 
     Plug plug;
@@ -75,6 +78,16 @@ static int handle_gotdata(struct handle *h, void *data, int len)
     }
 }
 
+static int handle_stderr(struct handle *h, void *data, int len)
+{
+    Handle_Socket ps = (Handle_Socket) handle_get_privdata(h);
+
+    if (len > 0)
+        log_proxy_stderr(ps->plug, &ps->stderrdata, data, len);
+
+    return 0;
+}
+
 static void handle_sentdata(struct handle *h, int new_backlog)
 {
     Handle_Socket ps = (Handle_Socket) handle_get_privdata(h);
@@ -101,6 +114,7 @@ static void sk_handle_close(Socket s)
     if (ps->recv_H != ps->send_H)
         CloseHandle(ps->recv_H);
     bufchain_clear(&ps->inputdata);
+    bufchain_clear(&ps->stderrdata);
 
     sfree(ps);
 }
@@ -259,8 +273,8 @@ static char *sk_handle_peer_info(Socket s)
     return NULL;
 }
 
-Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, Plug plug,
-                          int overlapped)
+Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H,
+                          Plug plug, int overlapped)
 {
     static const struct socket_function_table socket_fn_table = {
        sk_handle_plug,
@@ -283,11 +297,16 @@ Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, Plug plug,
     ret->error = NULL;
     ret->frozen = UNFROZEN;
     bufchain_init(&ret->inputdata);
+    bufchain_init(&ret->stderrdata);
 
     ret->recv_H = recv_H;
     ret->recv_h = handle_input_new(ret->recv_H, handle_gotdata, ret, flags);
     ret->send_H = send_H;
     ret->send_h = handle_output_new(ret->send_H, handle_sentdata, ret, flags);
+    ret->stderr_H = stderr_H;
+    if (ret->stderr_H)
+        ret->stderr_h = handle_input_new(ret->stderr_H, handle_stderr,
+                                         ret, flags);
 
     return (Socket) ret;
 }
index ba15bad6f4d63264c2a6ffe49654c65aa03701c3..f2e4f223595b4b73d642df1648223f282583d85a 100644 (file)
@@ -5,7 +5,9 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include "putty.h"
+#ifndef SECURITY_WIN32
 #define SECURITY_WIN32
+#endif
 #include <security.h>
 
 OSVERSIONINFO osVersion;
@@ -227,8 +229,8 @@ const char *win_strerror(int error)
                            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                            msgtext, lenof(msgtext)-1, NULL)) {
             sprintf(msgtext,
-                    "(unable to format: FormatMessage returned %d)",
-                    GetLastError());
+                    "(unable to format: FormatMessage returned %u)",
+                    (unsigned int)GetLastError());
         } else {
             int len = strlen(msgtext);
             if (len > 0 && msgtext[len-1] == '\n')
@@ -246,7 +248,7 @@ static FILE *debug_fp = NULL;
 static HANDLE debug_hdl = INVALID_HANDLE_VALUE;
 static int debug_got_console = 0;
 
-void dputs(char *buf)
+void dputs(const char *buf)
 {
     DWORD dw;
 
index fdb83c806ca8f34df85388a8e5a45b0ec73ba204..fa2de80967565e91cd7161512fd2646f7d563bfc 100644 (file)
@@ -50,7 +50,7 @@ struct SockAddrStep_tag {
 struct Socket_tag {
     const struct socket_function_table *fn;
     /* the above variable absolutely *must* be the first in this structure */
-    char *error;
+    const char *error;
     SOCKET s;
     Plug plug;
     bufchain output_data;
@@ -138,7 +138,7 @@ static int cmpfortree(void *av, void *bv)
 static int cmpforsearch(void *av, void *bv)
 {
     Actual_Socket b = (Actual_Socket) bv;
-    unsigned long as = (unsigned long) av, bs = (unsigned long) b->s;
+    uintptr_t as = (uintptr_t) av, bs = (uintptr_t) b->s;
     if (as < bs)
        return -1;
     if (as > bs)
@@ -362,7 +362,7 @@ static int errstring_compare(void *av, void *bv)
 
 static tree234 *errstrings = NULL;
 
-char *winsock_error_string(int error)
+const char *winsock_error_string(int error)
 {
     const char prefix[] = "Network error: ";
     struct errstring *es;
@@ -478,8 +478,8 @@ char *winsock_error_string(int error)
                            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                            es->text + bufused, bufsize - bufused, NULL)) {
             sprintf(es->text + bufused,
-                    "Windows error code %d (and FormatMessage returned %d)", 
-                    error, GetLastError());
+                    "Windows error code %d (and FormatMessage returned %u)",
+                    error, (unsigned int)GetLastError());
         } else {
             int len = strlen(es->text);
             if (len > 0 && es->text[len-1] == '\n')
@@ -1171,8 +1171,8 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
     return (Socket) ret;
 }
 
-Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only,
-                     int orig_address_family)
+Socket sk_newlistener(const char *srcaddr, int port, Plug plug,
+                      int local_host_only, int orig_address_family)
 {
     static const struct socket_function_table fn_table = {
        sk_tcp_plug,
@@ -1658,7 +1658,7 @@ int select_result(WPARAM wParam, LPARAM lParam)
        ret = p_recv(s->s, buf, sizeof(buf), MSG_OOB);
        noise_ultralight(ret);
        if (ret <= 0) {
-           char *str = (ret == 0 ? "Internal networking trouble" :
+           const char *str = (ret == 0 ? "Internal networking trouble" :
                         winsock_error_string(p_WSAGetLastError()));
            /* We're inside the Windows frontend here, so we know
             * that the frontend handle is unnecessary. */
index 0e8ac6994dc5baa45e21fff76d84451a480f1a32..85a3c3ffc3b6e37b252ba0c7f1d92cd41d33fa7d 100644 (file)
@@ -16,8 +16,8 @@
 
 #include "winsecur.h"
 
-Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, Plug plug,
-                          int overlapped);
+Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H,
+                          Plug plug, int overlapped);
 
 Socket new_named_pipe_client(const char *pipename, Plug plug)
 {
@@ -96,7 +96,7 @@ Socket new_named_pipe_client(const char *pipename, Plug plug)
     LocalFree(psd);
     sfree(usersid);
 
-    return make_handle_socket(pipehandle, pipehandle, plug, TRUE);
+    return make_handle_socket(pipehandle, pipehandle, NULL, plug, TRUE);
 }
 
 #endif /* !defined NO_SECURITY */
index 2547fd71c5a3863b8114ab8ca4777a0a86e33d5e..f992a4f0cbfd98f46a7dd95f760f6da07c3c1b27 100644 (file)
@@ -16,8 +16,8 @@
 
 #include "winsecur.h"
 
-Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, Plug plug,
-                          int overlapped);
+Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H,
+                          Plug plug, int overlapped);
 
 typedef struct Socket_named_pipe_server_tag *Named_Pipe_Server_Socket;
 struct Socket_named_pipe_server_tag {
@@ -120,7 +120,7 @@ static Socket named_pipe_accept(accept_ctx_t ctx, Plug plug)
 {
     HANDLE conn = (HANDLE)ctx.p;
 
-    return make_handle_socket(conn, conn, plug, TRUE);
+    return make_handle_socket(conn, conn, NULL, plug, TRUE);
 }
 
 /*
index 9497ed6012a10444aaf6caac8561028eac5db27f..db55145c15561afe65a0252a80b8ed0228d32287 100644 (file)
@@ -28,7 +28,7 @@ static char *cmdline_keyfile = NULL;
 /*
  * Print a modal (Really Bad) message box and perform a fatal exit.
  */
-void modalfatalbox(char *fmt, ...)
+void modalfatalbox(const char *fmt, ...)
 {
     va_list ap;
     char *stuff;
@@ -45,7 +45,7 @@ void modalfatalbox(char *fmt, ...)
 /*
  * Print a non-fatal message box and do not exit.
  */
-void nonfatal(char *fmt, ...)
+void nonfatal(const char *fmt, ...)
 {
     va_list ap;
     char *stuff;
@@ -142,7 +142,7 @@ struct PassphraseProcStruct {
 /*
  * Dialog-box function for the passphrase box.
  */
-static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
+static INT_PTR CALLBACK PassphraseProc(HWND hwnd, UINT msg,
                                   WPARAM wParam, LPARAM lParam)
 {
     static char **passphrase = NULL;
@@ -234,7 +234,7 @@ static int prompt_keyfile(HWND hwnd, char *dlgtitle,
 /*
  * Dialog-box function for the Licence box.
  */
-static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
+static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg,
                                WPARAM wParam, LPARAM lParam)
 {
     switch (msg) {
@@ -274,7 +274,7 @@ static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
 /*
  * Dialog-box function for the About box.
  */
-static int CALLBACK AboutProc(HWND hwnd, UINT msg,
+static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg,
                              WPARAM wParam, LPARAM lParam)
 {
     switch (msg) {
@@ -324,6 +324,8 @@ static int CALLBACK AboutProc(HWND hwnd, UINT msg,
     return 0;
 }
 
+typedef enum {RSA, DSA, ECDSA, ED25519} keytype;
+
 /*
  * Thread to generate a key.
  */
@@ -331,9 +333,12 @@ struct rsa_key_thread_params {
     HWND progressbar;                 /* notify this with progress */
     HWND dialog;                      /* notify this on completion */
     int keysize;                      /* bits in key */
-    int is_dsa;
-    struct RSAKey *key;
-    struct dss_key *dsskey;
+    keytype keytype;
+    union {
+        struct RSAKey *key;
+        struct dss_key *dsskey;
+        struct ec_key *eckey;
+    };
 };
 static DWORD WINAPI generate_rsa_key_thread(void *param)
 {
@@ -344,8 +349,12 @@ static DWORD WINAPI generate_rsa_key_thread(void *param)
 
     progress_update(&prog, PROGFN_INITIALISE, 0, 0);
 
-    if (params->is_dsa)
+    if (params->keytype == DSA)
        dsa_generate(params->dsskey, params->keysize, progress_update, &prog);
+    else if (params->keytype == ECDSA)
+        ec_generate(params->eckey, params->keysize, progress_update, &prog);
+    else if (params->keytype == ED25519)
+        ec_edgenerate(params->eckey, params->keysize, progress_update, &prog);
     else
        rsa_generate(params->key, params->keysize, progress_update, &prog);
 
@@ -361,12 +370,16 @@ struct MainDlgState {
     int key_exists;
     int entropy_got, entropy_required, entropy_size;
     int keysize;
-    int ssh2, is_dsa;
+    int ssh2;
+    keytype keytype;
     char **commentptr;                /* points to key.comment or ssh2key.comment */
     struct ssh2_userkey ssh2key;
     unsigned *entropy;
-    struct RSAKey key;
-    struct dss_key dsskey;
+    union {
+        struct RSAKey key;
+        struct dss_key dsskey;
+        struct ec_key eckey;
+    };
     HMENU filemenu, keymenu, cvtmenu;
 };
 
@@ -379,69 +392,23 @@ static void hidemany(HWND hwnd, const int *ids, int hideit)
 
 static void setupbigedit1(HWND hwnd, int id, int idstatic, struct RSAKey *key)
 {
-    char *buffer;
-    char *dec1, *dec2;
-
-    dec1 = bignum_decimal(key->exponent);
-    dec2 = bignum_decimal(key->modulus);
-    buffer = dupprintf("%d %s %s %s", bignum_bitcount(key->modulus),
-                      dec1, dec2, key->comment);
+    char *buffer = ssh1_pubkey_str(key);
     SetDlgItemText(hwnd, id, buffer);
     SetDlgItemText(hwnd, idstatic,
                   "&Public key for pasting into authorized_keys file:");
-    sfree(dec1);
-    sfree(dec2);
     sfree(buffer);
 }
 
 static void setupbigedit2(HWND hwnd, int id, int idstatic,
                          struct ssh2_userkey *key)
 {
-    unsigned char *pub_blob;
-    char *buffer, *p;
-    int pub_len;
-    int i;
-
-    pub_blob = key->alg->public_blob(key->data, &pub_len);
-    buffer = snewn(strlen(key->alg->name) + 4 * ((pub_len + 2) / 3) +
-                  strlen(key->comment) + 3, char);
-    strcpy(buffer, key->alg->name);
-    p = buffer + strlen(buffer);
-    *p++ = ' ';
-    i = 0;
-    while (i < pub_len) {
-       int n = (pub_len - i < 3 ? pub_len - i : 3);
-       base64_encode_atom(pub_blob + i, n, p);
-       i += n;
-       p += 4;
-    }
-    *p++ = ' ';
-    strcpy(p, key->comment);
+    char *buffer = ssh2_pubkey_openssh_str(key);
     SetDlgItemText(hwnd, id, buffer);
     SetDlgItemText(hwnd, idstatic, "&Public key for pasting into "
                   "OpenSSH authorized_keys file:");
-    sfree(pub_blob);
     sfree(buffer);
 }
 
-static int save_ssh1_pubkey(char *filename, struct RSAKey *key)
-{
-    char *dec1, *dec2;
-    FILE *fp;
-
-    fp = fopen(filename, "wb");
-    if (!fp)
-       return 0;
-    dec1 = bignum_decimal(key->exponent);
-    dec2 = bignum_decimal(key->modulus);
-    fprintf(fp, "%d %s %s %s\n",
-           bignum_bitcount(key->modulus), dec1, dec2, key->comment);
-    fclose(fp);
-    sfree(dec1);
-    sfree(dec2);
-    return 1;
-}
-
 /*
  * Warn about the obsolescent key file format.
  */
@@ -462,53 +429,6 @@ void old_keyfile_warning(void)
     MessageBox(NULL, message, mbtitle, MB_OK);
 }
 
-static int save_ssh2_pubkey(char *filename, struct ssh2_userkey *key)
-{
-    unsigned char *pub_blob;
-    char *p;
-    int pub_len;
-    int i, column;
-    FILE *fp;
-
-    pub_blob = key->alg->public_blob(key->data, &pub_len);
-
-    fp = fopen(filename, "wb");
-    if (!fp)
-       return 0;
-
-    fprintf(fp, "---- BEGIN SSH2 PUBLIC KEY ----\n");
-
-    fprintf(fp, "Comment: \"");
-    for (p = key->comment; *p; p++) {
-       if (*p == '\\' || *p == '\"')
-           fputc('\\', fp);
-       fputc(*p, fp);
-    }
-    fprintf(fp, "\"\n");
-
-    i = 0;
-    column = 0;
-    while (i < pub_len) {
-       char buf[5];
-       int n = (pub_len - i < 3 ? pub_len - i : 3);
-       base64_encode_atom(pub_blob + i, n, buf);
-       i += n;
-       buf[4] = '\0';
-       fputs(buf, fp);
-       if (++column >= 16) {
-           fputc('\n', fp);
-           column = 0;
-       }
-    }
-    if (column > 0)
-       fputc('\n', fp);
-    
-    fprintf(fp, "---- END SSH2 PUBLIC KEY ----\n");
-    fclose(fp);
-    sfree(pub_blob);
-    return 1;
-}
-
 enum {
     controlidstart = 100,
     IDC_QUIT,
@@ -528,10 +448,13 @@ enum {
     IDC_SAVESTATIC, IDC_SAVE, IDC_SAVEPUB,
     IDC_BOX_PARAMS,
     IDC_TYPESTATIC, IDC_KEYSSH1, IDC_KEYSSH2RSA, IDC_KEYSSH2DSA,
+    IDC_KEYSSH2ECDSA, IDC_KEYSSH2ED25519,
     IDC_BITSSTATIC, IDC_BITS,
     IDC_ABOUT,
     IDC_GIVEHELP,
-    IDC_IMPORT, IDC_EXPORT_OPENSSH, IDC_EXPORT_SSHCOM
+    IDC_IMPORT,
+    IDC_EXPORT_OPENSSH_AUTO, IDC_EXPORT_OPENSSH_NEW,
+    IDC_EXPORT_SSHCOM
 };
 
 static const int nokey_ids[] = { IDC_NOKEY, 0 };
@@ -564,6 +487,8 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status)
        EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 1);
        EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 1);
        EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2DSA), 1);
+        EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2ECDSA), 1);
+        EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2ED25519), 1);
        EnableWindow(GetDlgItem(hwnd, IDC_BITS), 1);
        EnableMenuItem(state->filemenu, IDC_LOAD, MF_ENABLED|MF_BYCOMMAND);
        EnableMenuItem(state->filemenu, IDC_SAVE, MF_GRAYED|MF_BYCOMMAND);
@@ -572,8 +497,14 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status)
        EnableMenuItem(state->keymenu, IDC_KEYSSH1, MF_ENABLED|MF_BYCOMMAND);
        EnableMenuItem(state->keymenu, IDC_KEYSSH2RSA, MF_ENABLED|MF_BYCOMMAND);
        EnableMenuItem(state->keymenu, IDC_KEYSSH2DSA, MF_ENABLED|MF_BYCOMMAND);
+        EnableMenuItem(state->keymenu, IDC_KEYSSH2ECDSA,
+                       MF_ENABLED|MF_BYCOMMAND);
+        EnableMenuItem(state->keymenu, IDC_KEYSSH2ED25519,
+                       MF_ENABLED|MF_BYCOMMAND);
        EnableMenuItem(state->cvtmenu, IDC_IMPORT, MF_ENABLED|MF_BYCOMMAND);
-       EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH,
+       EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH_AUTO,
+                      MF_GRAYED|MF_BYCOMMAND);
+       EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH_NEW,
                       MF_GRAYED|MF_BYCOMMAND);
        EnableMenuItem(state->cvtmenu, IDC_EXPORT_SSHCOM,
                       MF_GRAYED|MF_BYCOMMAND);
@@ -589,6 +520,8 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status)
        EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 0);
        EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 0);
        EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2DSA), 0);
+        EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2ECDSA), 0);
+        EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2ED25519), 0);
        EnableWindow(GetDlgItem(hwnd, IDC_BITS), 0);
        EnableMenuItem(state->filemenu, IDC_LOAD, MF_GRAYED|MF_BYCOMMAND);
        EnableMenuItem(state->filemenu, IDC_SAVE, MF_GRAYED|MF_BYCOMMAND);
@@ -597,8 +530,14 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status)
        EnableMenuItem(state->keymenu, IDC_KEYSSH1, MF_GRAYED|MF_BYCOMMAND);
        EnableMenuItem(state->keymenu, IDC_KEYSSH2RSA, MF_GRAYED|MF_BYCOMMAND);
        EnableMenuItem(state->keymenu, IDC_KEYSSH2DSA, MF_GRAYED|MF_BYCOMMAND);
+        EnableMenuItem(state->keymenu, IDC_KEYSSH2ECDSA,
+                       MF_GRAYED|MF_BYCOMMAND);
+        EnableMenuItem(state->keymenu, IDC_KEYSSH2ED25519,
+                       MF_GRAYED|MF_BYCOMMAND);
        EnableMenuItem(state->cvtmenu, IDC_IMPORT, MF_GRAYED|MF_BYCOMMAND);
-       EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH,
+       EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH_AUTO,
+                      MF_GRAYED|MF_BYCOMMAND);
+       EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH_NEW,
                       MF_GRAYED|MF_BYCOMMAND);
        EnableMenuItem(state->cvtmenu, IDC_EXPORT_SSHCOM,
                       MF_GRAYED|MF_BYCOMMAND);
@@ -614,6 +553,8 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status)
        EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 1);
        EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 1);
        EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2DSA), 1);
+        EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2ECDSA), 1);
+        EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2ED25519), 1);
        EnableWindow(GetDlgItem(hwnd, IDC_BITS), 1);
        EnableMenuItem(state->filemenu, IDC_LOAD, MF_ENABLED|MF_BYCOMMAND);
        EnableMenuItem(state->filemenu, IDC_SAVE, MF_ENABLED|MF_BYCOMMAND);
@@ -622,6 +563,10 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status)
        EnableMenuItem(state->keymenu, IDC_KEYSSH1, MF_ENABLED|MF_BYCOMMAND);
        EnableMenuItem(state->keymenu, IDC_KEYSSH2RSA,MF_ENABLED|MF_BYCOMMAND);
        EnableMenuItem(state->keymenu, IDC_KEYSSH2DSA,MF_ENABLED|MF_BYCOMMAND);
+        EnableMenuItem(state->keymenu, IDC_KEYSSH2ECDSA,
+                       MF_ENABLED|MF_BYCOMMAND);
+        EnableMenuItem(state->keymenu, IDC_KEYSSH2ED25519,
+                       MF_ENABLED|MF_BYCOMMAND);
        EnableMenuItem(state->cvtmenu, IDC_IMPORT, MF_ENABLED|MF_BYCOMMAND);
        /*
         * Enable export menu items if and only if the key type
@@ -631,7 +576,8 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status)
 #define do_export_menuitem(x,y) \
     EnableMenuItem(state->cvtmenu, x, MF_BYCOMMAND | \
                       (import_target_type(y)==type?MF_ENABLED:MF_GRAYED))
-       do_export_menuitem(IDC_EXPORT_OPENSSH, SSH_KEYTYPE_OPENSSH);
+       do_export_menuitem(IDC_EXPORT_OPENSSH_AUTO, SSH_KEYTYPE_OPENSSH_AUTO);
+       do_export_menuitem(IDC_EXPORT_OPENSSH_NEW, SSH_KEYTYPE_OPENSSH_NEW);
        do_export_menuitem(IDC_EXPORT_SSHCOM, SSH_KEYTYPE_SSHCOM);
 #undef do_export_menuitem
        break;
@@ -769,9 +715,7 @@ void load_key_file(HWND hwnd, struct MainDlgState *state,
 
                savecomment = state->ssh2key.comment;
                state->ssh2key.comment = NULL;
-               fp =
-                   state->ssh2key.alg->
-                   fingerprint(state->ssh2key.data);
+               fp = ssh2_fingerprint(state->ssh2key.alg, state->ssh2key.data);
                state->ssh2key.comment = savecomment;
 
                SetDlgItemText(hwnd, IDC_FINGERPRINT, fp);
@@ -814,7 +758,7 @@ void load_key_file(HWND hwnd, struct MainDlgState *state,
 /*
  * Dialog-box function for the main PuTTYgen dialog box.
  */
-static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
+static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg,
                                WPARAM wParam, LPARAM lParam)
 {
     static const char generating_msg[] =
@@ -855,7 +799,7 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
            AppendMenu(menu1, MF_ENABLED, IDC_SAVE, "&Save private key");
            AppendMenu(menu1, MF_SEPARATOR, 0, 0);
            AppendMenu(menu1, MF_ENABLED, IDC_QUIT, "E&xit");
-           AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT) menu1, "&File");
+           AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT_PTR) menu1, "&File");
            state->filemenu = menu1;
 
            menu1 = CreateMenu();
@@ -864,17 +808,21 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
            AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH1, "SSH-&1 key (RSA)");
            AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH2RSA, "SSH-2 &RSA key");
            AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH2DSA, "SSH-2 &DSA key");
-           AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT) menu1, "&Key");
+            AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH2ECDSA, "SSH-2 &ECDSA key");
+            AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH2ED25519, "SSH-2 ED&25519 key");
+           AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT_PTR) menu1, "&Key");
            state->keymenu = menu1;
 
            menu1 = CreateMenu();
            AppendMenu(menu1, MF_ENABLED, IDC_IMPORT, "&Import key");
            AppendMenu(menu1, MF_SEPARATOR, 0, 0);
-           AppendMenu(menu1, MF_ENABLED, IDC_EXPORT_OPENSSH,
+           AppendMenu(menu1, MF_ENABLED, IDC_EXPORT_OPENSSH_AUTO,
                       "Export &OpenSSH key");
+           AppendMenu(menu1, MF_ENABLED, IDC_EXPORT_OPENSSH_NEW,
+                      "Export &OpenSSH key (force new file format)");
            AppendMenu(menu1, MF_ENABLED, IDC_EXPORT_SSHCOM,
                       "Export &ssh.com key");
-           AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT) menu1,
+           AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT_PTR) menu1,
                       "Con&versions");
            state->cvtmenu = menu1;
 
@@ -882,7 +830,7 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
            AppendMenu(menu1, MF_ENABLED, IDC_ABOUT, "&About");
            if (has_help())
                AppendMenu(menu1, MF_ENABLED, IDC_GIVEHELP, "&Help");
-           AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT) menu1, "&Help");
+           AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT_PTR) menu1, "&Help");
 
            SetMenu(hwnd, menu);
        }
@@ -905,7 +853,7 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
        {
            struct ctlpos cp, cp2;
 
-           /* Accelerators used: acglops1rbd */
+           /* Accelerators used: acglops1rbde */
 
            ctlposinit(&cp, hwnd, 4, 4, 4);
            beginbox(&cp, "Key", IDC_BOX_KEY);
@@ -939,16 +887,19 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
                       "&Save private key", IDC_SAVE);
            endbox(&cp);
            beginbox(&cp, "Parameters", IDC_BOX_PARAMS);
-           radioline(&cp, "Type of key to generate:", IDC_TYPESTATIC, 3,
+           radioline(&cp, "Type of key to generate:", IDC_TYPESTATIC, 5,
+                     "&RSA", IDC_KEYSSH2RSA,
+                      "&DSA", IDC_KEYSSH2DSA,
+                      "&ECDSA", IDC_KEYSSH2ECDSA,
+                      "ED&25519", IDC_KEYSSH2ED25519,
                      "SSH-&1 (RSA)", IDC_KEYSSH1,
-                     "SSH-2 &RSA", IDC_KEYSSH2RSA,
-                     "SSH-2 &DSA", IDC_KEYSSH2DSA, NULL);
+                      NULL);
            staticedit(&cp, "Number of &bits in a generated key:",
                       IDC_BITSSTATIC, IDC_BITS, 20);
            endbox(&cp);
        }
-       CheckRadioButton(hwnd, IDC_KEYSSH1, IDC_KEYSSH2DSA, IDC_KEYSSH2RSA);
-       CheckMenuRadioItem(state->keymenu, IDC_KEYSSH1, IDC_KEYSSH2DSA,
+        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);
 
@@ -999,7 +950,7 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
                params->progressbar = GetDlgItem(hwnd, IDC_PROGRESS);
                params->dialog = hwnd;
                params->keysize = state->keysize;
-               params->is_dsa = state->is_dsa;
+                params->keytype = state->keytype;
                params->key = &state->key;
                params->dsskey = &state->dsskey;
 
@@ -1020,13 +971,17 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
          case IDC_KEYSSH1:
          case IDC_KEYSSH2RSA:
          case IDC_KEYSSH2DSA:
+          case IDC_KEYSSH2ECDSA:
+          case IDC_KEYSSH2ED25519:
            {
                state = (struct MainDlgState *)
                    GetWindowLongPtr(hwnd, GWLP_USERDATA);
                if (!IsDlgButtonChecked(hwnd, LOWORD(wParam)))
-                   CheckRadioButton(hwnd, IDC_KEYSSH1, IDC_KEYSSH2DSA,
+                   CheckRadioButton(hwnd,
+                                     IDC_KEYSSH1, IDC_KEYSSH2ED25519,
                                     LOWORD(wParam));
-               CheckMenuRadioItem(state->keymenu, IDC_KEYSSH1, IDC_KEYSSH2DSA,
+               CheckMenuRadioItem(state->keymenu,
+                                   IDC_KEYSSH1, IDC_KEYSSH2ED25519,
                                   LOWORD(wParam), MF_BYCOMMAND);
            }
            break;
@@ -1079,7 +1034,14 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
                    state->keysize = DEFAULT_KEYSIZE;
                /* If we ever introduce a new key type, check it here! */
                state->ssh2 = !IsDlgButtonChecked(hwnd, IDC_KEYSSH1);
-               state->is_dsa = IsDlgButtonChecked(hwnd, IDC_KEYSSH2DSA);
+                state->keytype = RSA;
+                if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2DSA)) {
+                    state->keytype = DSA;
+                } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2ECDSA)) {
+                    state->keytype = ECDSA;
+                } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2ED25519)) {
+                    state->keytype = ED25519;
+                }
                if (state->keysize < 256) {
                    int ret = MessageBox(hwnd,
                                         "PuTTYgen will not generate a key"
@@ -1092,6 +1054,32 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
                    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;
@@ -1122,7 +1110,8 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
            }
            break;
          case IDC_SAVE:
-          case IDC_EXPORT_OPENSSH:
+          case IDC_EXPORT_OPENSSH_AUTO:
+          case IDC_EXPORT_OPENSSH_NEW:
           case IDC_EXPORT_SSHCOM:
            if (HIWORD(wParam) != BN_CLICKED)
                break;
@@ -1138,8 +1127,10 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
                 else
                     realtype = SSH_KEYTYPE_SSH1;
 
-                if (LOWORD(wParam) == IDC_EXPORT_OPENSSH)
-                    type = SSH_KEYTYPE_OPENSSH;
+                if (LOWORD(wParam) == IDC_EXPORT_OPENSSH_AUTO)
+                    type = SSH_KEYTYPE_OPENSSH_AUTO;
+                else if (LOWORD(wParam) == IDC_EXPORT_OPENSSH_NEW)
+                    type = SSH_KEYTYPE_OPENSSH_NEW;
                 else if (LOWORD(wParam) == IDC_EXPORT_SSHCOM)
                     type = SSH_KEYTYPE_SSHCOM;
                 else
@@ -1247,15 +1238,27 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
                        if (ret != IDYES)
                            break;
                    }
-                   if (state->ssh2) {
-                       ret = save_ssh2_pubkey(filename, &state->ssh2key);
-                   } else {
-                       ret = save_ssh1_pubkey(filename, &state->key);
-                   }
-                   if (ret <= 0) {
-                       MessageBox(hwnd, "Unable to save key file",
-                                  "PuTTYgen Error", MB_OK | MB_ICONERROR);
-                   }
+                    fp = fopen(filename, "w");
+                    if (!fp) {
+                        MessageBox(hwnd, "Unable to open key file",
+                                   "PuTTYgen Error", MB_OK | MB_ICONERROR);
+                    } else {
+                        if (state->ssh2) {
+                            int bloblen;
+                            unsigned char *blob;
+                            blob = state->ssh2key.alg->public_blob
+                                (state->ssh2key.data, &bloblen);
+                            ssh2_write_pubkey(fp, state->ssh2key.comment,
+                                              blob, bloblen,
+                                              SSH_KEYTYPE_SSH2_PUBLIC_RFC4716);
+                        } else {
+                            ssh1_write_pubkey(fp, &state->key);
+                        }
+                        if (fclose(fp) < 0) {
+                            MessageBox(hwnd, "Unable to save key file",
+                                       "PuTTYgen Error", MB_OK | MB_ICONERROR);
+                        }
+                    }
                }
            }
            break;
@@ -1285,9 +1288,15 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
                           MAKELPARAM(0, PROGRESSRANGE));
        SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, PROGRESSRANGE, 0);
        if (state->ssh2) {
-           if (state->is_dsa) {
+            if (state->keytype == DSA) {
                state->ssh2key.data = &state->dsskey;
                state->ssh2key.alg = &ssh_dss;
+            } else if (state->keytype == ECDSA) {
+                state->ssh2key.data = &state->eckey;
+                state->ssh2key.alg = state->eckey.signalg;
+            } else if (state->keytype == ED25519) {
+                state->ssh2key.data = &state->eckey;
+                state->ssh2key.alg = &ssh_ecdsa_ed25519;
            } else {
                state->ssh2key.data = &state->key;
                state->ssh2key.alg = &ssh_rsa;
@@ -1306,8 +1315,12 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
        {
            struct tm tm;
            tm = ltime();
-           if (state->is_dsa)
+            if (state->keytype == DSA)
                strftime(*state->commentptr, 30, "dsa-key-%Y%m%d", &tm);
+            else if (state->keytype == ECDSA)
+                strftime(*state->commentptr, 30, "ecdsa-key-%Y%m%d", &tm);
+            else if (state->keytype == ED25519)
+                strftime(*state->commentptr, 30, "ed25519-key-%Y%m%d", &tm);
            else
                strftime(*state->commentptr, 30, "rsa-key-%Y%m%d", &tm);
        }
@@ -1335,7 +1348,7 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
            *state->commentptr = NULL;
            if (state->ssh2) {
                char *fp;
-               fp = state->ssh2key.alg->fingerprint(state->ssh2key.data);
+               fp = ssh2_fingerprint(state->ssh2key.alg, state->ssh2key.data);
                SetDlgItemText(hwnd, IDC_FINGERPRINT, fp);
                sfree(fp);
            } else {
@@ -1365,7 +1378,7 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
       case WM_HELP:
         {
             int id = ((LPHELPINFO)lParam)->iCtrlId;
-            char *topic = NULL;
+            const char *topic = NULL;
             switch (id) {
               case IDC_GENERATING:
               case IDC_PROGRESS:
@@ -1398,12 +1411,15 @@ static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
               case IDC_KEYSSH1:
               case IDC_KEYSSH2RSA:
               case IDC_KEYSSH2DSA:
+              case IDC_KEYSSH2ECDSA:
+              case IDC_KEYSSH2ED25519:
                 topic = WINHELP_CTX_puttygen_keytype; break;
               case IDC_BITSSTATIC:
               case IDC_BITS:
                 topic = WINHELP_CTX_puttygen_bits; break;
               case IDC_IMPORT:
-              case IDC_EXPORT_OPENSSH:
+              case IDC_EXPORT_OPENSSH_AUTO:
+              case IDC_EXPORT_OPENSSH_NEW:
               case IDC_EXPORT_SSHCOM:
                 topic = WINHELP_CTX_puttygen_conversions; break;
             }
index f4194a68d8789b682a293f766b601acd815e617c..2109d1c6feb94c56f243da497aa8a0512f1468da 100644 (file)
@@ -15,6 +15,7 @@
 #include "misc.h"
 #include "tree234.h"
 #include "winsecur.h"
+#include "pageant.h"
 #include "licence.h"
 
 #include <shellapi.h>
 
 #define AGENT_COPYDATA_ID 0x804e50ba   /* random goop */
 
-/*
- * FIXME: maybe some day we can sort this out ...
- */
-#define AGENT_MAX_MSGLEN  8192
-
 /* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of
  * wParam are used by Windows, and should be masked off, so we shouldn't
  * attempt to store information in them. Hence all these identifiers have
@@ -75,7 +71,7 @@ static int initial_menuitems_count;
 /*
  * Print a modal (Really Bad) message box and perform a fatal exit.
  */
-void modalfatalbox(char *fmt, ...)
+void modalfatalbox(const char *fmt, ...)
 {
     va_list ap;
     char *buf;
@@ -115,71 +111,17 @@ static void unmungestr(char *in, char *out, int outlen)
     return;
 }
 
-static tree234 *rsakeys, *ssh2keys;
-
 static int has_security;
 
-/*
- * Forward references
- */
-static void *make_keylist1(int *length);
-static void *make_keylist2(int *length);
-static void *get_keylist1(int *length);
-static void *get_keylist2(int *length);
-
-/*
- * We need this to link with the RSA code, because rsaencrypt()
- * pads its data with random bytes. Since we only use rsadecrypt()
- * and the signing functions, which are deterministic, this should
- * never be called.
- *
- * If it _is_ called, there is a _serious_ problem, because it
- * won't generate true random numbers. So we must scream, panic,
- * and exit immediately if that should happen.
- */
-int random_byte(void)
-{
-    MessageBox(hwnd, "Internal Error", APPNAME, MB_OK | MB_ICONERROR);
-    exit(0);
-    /* this line can't be reached but it placates MSVC's warnings :-) */
-    return 0;
-}
-
-/*
- * Blob structure for passing to the asymmetric SSH-2 key compare
- * function, prototyped here.
- */
-struct blob {
-    unsigned char *blob;
-    int len;
-};
-static int cmpkeys_ssh2_asymm(void *av, void *bv);
-
 struct PassphraseProcStruct {
     char **passphrase;
     char *comment;
 };
 
-static tree234 *passphrases = NULL;
-
-/* 
- * After processing a list of filenames, we want to forget the
- * passphrases.
- */
-static void forget_passphrases(void)
-{
-    while (count234(passphrases) > 0) {
-       char *pp = index234(passphrases, 0);
-       smemclr(pp, strlen(pp));
-       delpos234(passphrases, 0);
-       free(pp);
-    }
-}
-
 /*
  * Dialog-box function for the Licence box.
  */
-static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
+static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg,
                                WPARAM wParam, LPARAM lParam)
 {
     switch (msg) {
@@ -204,7 +146,7 @@ static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
 /*
  * Dialog-box function for the About box.
  */
-static int CALLBACK AboutProc(HWND hwnd, UINT msg,
+static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg,
                              WPARAM wParam, LPARAM lParam)
 {
     switch (msg) {
@@ -246,7 +188,7 @@ static HWND passphrase_box;
 /*
  * Dialog-box function for the passphrase box.
  */
-static int CALLBACK PassphraseProc(HWND hwnd, UINT msg,
+static INT_PTR CALLBACK PassphraseProc(HWND hwnd, UINT msg,
                                   WPARAM wParam, LPARAM lParam)
 {
     static char **passphrase = NULL;
@@ -330,7 +272,7 @@ void old_keyfile_warning(void)
 /*
  * Update the visible key list.
  */
-static void keylist_update(void)
+void keylist_update(void)
 {
     struct RSAKey *rkey;
     struct ssh2_userkey *skey;
@@ -338,7 +280,7 @@ static void keylist_update(void)
 
     if (keylist) {
        SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0);
-       for (i = 0; NULL != (rkey = index234(rsakeys, i)); i++) {
+       for (i = 0; NULL != (rkey = pageant_nth_ssh1_key(i)); i++) {
            char listentry[512], *p;
            /*
             * Replace two spaces in the fingerprint with tabs, for
@@ -356,24 +298,25 @@ static void keylist_update(void)
            SendDlgItemMessage(keylist, 100, LB_ADDSTRING,
                               0, (LPARAM) listentry);
        }
-       for (i = 0; NULL != (skey = index234(ssh2keys, i)); i++) {
+       for (i = 0; NULL != (skey = pageant_nth_ssh2_key(i)); i++) {
            char *listentry, *p;
-           int fp_len;
+           int pos;
            /*
-            * Replace two spaces in the fingerprint with tabs, for
-            * nice alignment in the box.
+            * 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.
             */
-           p = skey->alg->fingerprint(skey->data);
+           p = ssh2_fingerprint(skey->alg, skey->data);
             listentry = dupprintf("%s\t%s", p, skey->comment);
-            fp_len = strlen(listentry);
             sfree(p);
 
-           p = strchr(listentry, ' ');
-           if (p && p < listentry + fp_len)
-               *p = '\t';
-           p = strchr(listentry, ' ');
-           if (p && p < listentry + fp_len)
-               *p = '\t';
+            pos = 0;
+            while (1) {
+                pos += strcspn(listentry + pos, " :");
+                if (listentry[pos] == ':' || !listentry[pos])
+                    break;
+                listentry[pos++] = '\t';
+            }
 
            SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0,
                               (LPARAM) listentry);
@@ -383,1050 +326,95 @@ static void keylist_update(void)
     }
 }
 
-/*
- * This function loads a key from a file and adds it.
- */
-static void add_keyfile(Filename *filename)
+static void answer_msg(void *msgv)
 {
-    char *passphrase;
-    struct RSAKey *rkey = NULL;
-    struct ssh2_userkey *skey = NULL;
-    int needs_pass;
-    int ret;
-    int attempts;
-    char *comment;
-    const char *error = NULL;
-    int type;
-    int original_pass;
-       
-    type = key_type(filename);
-    if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2) {
-       char *msg = dupprintf("Couldn't load this key (%s)",
-                             key_type_to_str(type));
-       message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
-                   HELPCTXID(errors_cantloadkey));
-       sfree(msg);
-       return;
-    }
-
-    /*
-     * See if the key is already loaded (in the primary Pageant,
-     * which may or may not be us).
-     */
-    {
-       void *blob;
-       unsigned char *keylist, *p;
-       int i, nkeys, bloblen, keylistlen;
-
-       if (type == SSH_KEYTYPE_SSH1) {
-           if (!rsakey_pubblob(filename, &blob, &bloblen, NULL, &error)) {
-               char *msg = dupprintf("Couldn't load private key (%s)", error);
-               message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
-                           HELPCTXID(errors_cantloadkey));
-               sfree(msg);
-               return;
-           }
-           keylist = get_keylist1(&keylistlen);
-       } else {
-           unsigned char *blob2;
-           blob = ssh2_userkey_loadpub(filename, NULL, &bloblen,
-                                       NULL, &error);
-           if (!blob) {
-               char *msg = dupprintf("Couldn't load private key (%s)", error);
-               message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
-                           HELPCTXID(errors_cantloadkey));
-               sfree(msg);
-               return;
-           }
-           /* For our purposes we want the blob prefixed with its length */
-           blob2 = snewn(bloblen+4, unsigned char);
-           PUT_32BIT(blob2, bloblen);
-           memcpy(blob2 + 4, blob, bloblen);
-           sfree(blob);
-           blob = blob2;
-
-           keylist = get_keylist2(&keylistlen);
-       }
-       if (keylist) {
-           if (keylistlen < 4) {
-               MessageBox(NULL, "Received broken key list?!", APPNAME,
-                          MB_OK | MB_ICONERROR);
-               return;
-           }
-           nkeys = toint(GET_32BIT(keylist));
-           if (nkeys < 0) {
-               MessageBox(NULL, "Received broken key list?!", APPNAME,
-                          MB_OK | MB_ICONERROR);
-               return;
-           }
-           p = keylist + 4;
-           keylistlen -= 4;
-
-           for (i = 0; i < nkeys; i++) {
-               if (!memcmp(blob, p, bloblen)) {
-                   /* Key is already present; we can now leave. */
-                   sfree(keylist);
-                   sfree(blob);
-                   return;
-               }
-               /* Now skip over public blob */
-               if (type == SSH_KEYTYPE_SSH1) {
-                   int n = rsa_public_blob_len(p, keylistlen);
-                   if (n < 0) {
-                       MessageBox(NULL, "Received broken key list?!", APPNAME,
-                                  MB_OK | MB_ICONERROR);
-                       return;
-                   }
-                   p += n;
-                   keylistlen -= n;
-               } else {
-                   int n;
-                   if (keylistlen < 4) {
-                       MessageBox(NULL, "Received broken key list?!", APPNAME,
-                                  MB_OK | MB_ICONERROR);
-                       return;
-                   }
-                   n = toint(4 + GET_32BIT(p));
-                   if (n < 0 || keylistlen < n) {
-                       MessageBox(NULL, "Received broken key list?!", APPNAME,
-                                  MB_OK | MB_ICONERROR);
-                       return;
-                   }
-                   p += n;
-                   keylistlen -= n;
-               }
-               /* Now skip over comment field */
-               {
-                   int n;
-                   if (keylistlen < 4) {
-                       MessageBox(NULL, "Received broken key list?!", APPNAME,
-                                  MB_OK | MB_ICONERROR);
-                       return;
-                   }
-                   n = toint(4 + GET_32BIT(p));
-                   if (n < 0 || keylistlen < n) {
-                       MessageBox(NULL, "Received broken key list?!", APPNAME,
-                                  MB_OK | MB_ICONERROR);
-                       return;
-                   }
-                   p += n;
-                   keylistlen -= n;
-               }
-           }
-
-           sfree(keylist);
-       }
-
-       sfree(blob);
-    }
-
-    error = NULL;
-    if (type == SSH_KEYTYPE_SSH1)
-       needs_pass = rsakey_encrypted(filename, &comment);
-    else
-       needs_pass = ssh2_userkey_encrypted(filename, &comment);
-    attempts = 0;
-    if (type == SSH_KEYTYPE_SSH1)
-       rkey = snew(struct RSAKey);
-    passphrase = NULL;
-    original_pass = 0;
-    do {
-        burnstr(passphrase);
-        passphrase = NULL;
-
-       if (needs_pass) {
-           /* try all the remembered passphrases first */
-           char *pp = index234(passphrases, attempts);
-           if(pp) {
-               passphrase = dupstr(pp);
-           } else {
-               int dlgret;
-                struct PassphraseProcStruct pps;
-
-                pps.passphrase = &passphrase;
-                pps.comment = comment;
-
-               original_pass = 1;
-               dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(210),
-                                       NULL, PassphraseProc, (LPARAM) &pps);
-               passphrase_box = NULL;
-               if (!dlgret) {
-                   if (comment)
-                       sfree(comment);
-                   if (type == SSH_KEYTYPE_SSH1)
-                       sfree(rkey);
-                   return;                    /* operation cancelled */
-               }
-
-                assert(passphrase != NULL);
-           }
-       } else
-           passphrase = dupstr("");
-
-       if (type == SSH_KEYTYPE_SSH1)
-           ret = loadrsakey(filename, rkey, passphrase, &error);
-       else {
-           skey = ssh2_load_userkey(filename, passphrase, &error);
-           if (skey == SSH2_WRONG_PASSPHRASE)
-               ret = -1;
-           else if (!skey)
-               ret = 0;
-           else
-               ret = 1;
-       }
-       attempts++;
-    } while (ret == -1);
-
-    if(original_pass && ret) {
-        /* If they typed in an ok passphrase, remember it */
-       addpos234(passphrases, passphrase, 0);
+    unsigned char *msg = (unsigned char *)msgv;
+    unsigned msglen;
+    void *reply;
+    int replylen;
+
+    msglen = GET_32BIT(msg);
+    if (msglen > AGENT_MAX_MSGLEN) {
+        reply = pageant_failure_msg(&replylen);
     } else {
-        /* Otherwise, destroy it */
-        burnstr(passphrase);
-    }
-    passphrase = NULL;
-
-    if (comment)
-       sfree(comment);
-    if (ret == 0) {
-       char *msg = dupprintf("Couldn't load private key (%s)", error);
-       message_box(msg, APPNAME, MB_OK | MB_ICONERROR,
-                   HELPCTXID(errors_cantloadkey));
-       sfree(msg);
-       if (type == SSH_KEYTYPE_SSH1)
-           sfree(rkey);
-       return;
-    }
-    if (type == SSH_KEYTYPE_SSH1) {
-       if (already_running) {
-           unsigned char *request, *response;
-           void *vresponse;
-           int reqlen, clen, resplen, ret;
-
-           clen = strlen(rkey->comment);
-
-           reqlen = 4 + 1 +           /* length, message type */
-               4 +                    /* bit count */
-               ssh1_bignum_length(rkey->modulus) +
-               ssh1_bignum_length(rkey->exponent) +
-               ssh1_bignum_length(rkey->private_exponent) +
-               ssh1_bignum_length(rkey->iqmp) +
-               ssh1_bignum_length(rkey->p) +
-               ssh1_bignum_length(rkey->q) + 4 + clen  /* comment */
-               ;
-
-           request = snewn(reqlen, unsigned char);
-
-           request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY;
-           reqlen = 5;
-           PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus));
-           reqlen += 4;
-           reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus);
-           reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent);
-           reqlen +=
-               ssh1_write_bignum(request + reqlen,
-                                 rkey->private_exponent);
-           reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp);
-           reqlen += ssh1_write_bignum(request + reqlen, rkey->p);
-           reqlen += ssh1_write_bignum(request + reqlen, rkey->q);
-           PUT_32BIT(request + reqlen, clen);
-           memcpy(request + reqlen + 4, rkey->comment, clen);
-           reqlen += 4 + clen;
-           PUT_32BIT(request, reqlen - 4);
-
-           ret = agent_query(request, reqlen, &vresponse, &resplen,
-                             NULL, NULL);
-           assert(ret == 1);
-           response = vresponse;
-           if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
-               MessageBox(NULL, "The already running Pageant "
-                          "refused to add the key.", APPNAME,
-                          MB_OK | MB_ICONERROR);
-
-           sfree(request);
-           sfree(response);
-       } else {
-           if (add234(rsakeys, rkey) != rkey)
-               sfree(rkey);           /* already present, don't waste RAM */
-       }
-    } else {
-       if (already_running) {
-           unsigned char *request, *response;
-           void *vresponse;
-           int reqlen, alglen, clen, keybloblen, resplen, ret;
-           alglen = strlen(skey->alg->name);
-           clen = strlen(skey->comment);
-
-           keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0);
-
-           reqlen = 4 + 1 +           /* length, message type */
-               4 + alglen +           /* algorithm name */
-               keybloblen +           /* key data */
-               4 + clen               /* comment */
-               ;
-
-           request = snewn(reqlen, unsigned char);
-
-           request[4] = SSH2_AGENTC_ADD_IDENTITY;
-           reqlen = 5;
-           PUT_32BIT(request + reqlen, alglen);
-           reqlen += 4;
-           memcpy(request + reqlen, skey->alg->name, alglen);
-           reqlen += alglen;
-           reqlen += skey->alg->openssh_fmtkey(skey->data,
-                                               request + reqlen,
-                                               keybloblen);
-           PUT_32BIT(request + reqlen, clen);
-           memcpy(request + reqlen + 4, skey->comment, clen);
-           reqlen += clen + 4;
-           PUT_32BIT(request, reqlen - 4);
-
-           ret = agent_query(request, reqlen, &vresponse, &resplen,
-                             NULL, NULL);
-           assert(ret == 1);
-           response = vresponse;
-           if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS)
-               MessageBox(NULL, "The already running Pageant "
-                          "refused to add the key.", APPNAME,
-                          MB_OK | MB_ICONERROR);
-
-           sfree(request);
-           sfree(response);
-       } else {
-           if (add234(ssh2keys, skey) != skey) {
-               skey->alg->freekey(skey->data);
-               sfree(skey);           /* already present, don't waste RAM */
-           }
-       }
+        reply = pageant_handle_msg(msg + 4, msglen, &replylen, NULL, NULL);
+        if (replylen > AGENT_MAX_MSGLEN) {
+            smemclr(reply, replylen);
+            sfree(reply);
+            reply = pageant_failure_msg(&replylen);
+        }
     }
-}
-
-/*
- * Create an SSH-1 key list in a malloc'ed buffer; return its
- * length.
- */
-static void *make_keylist1(int *length)
-{
-    int i, nkeys, len;
-    struct RSAKey *key;
-    unsigned char *blob, *p, *ret;
-    int bloblen;
 
     /*
-     * Count up the number and length of keys we hold.
+     * Windows Pageant answers messages in place, by overwriting the
+     * input message buffer.
      */
-    len = 4;
-    nkeys = 0;
-    for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
-       nkeys++;
-       blob = rsa_public_blob(key, &bloblen);
-       len += bloblen;
-       sfree(blob);
-       len += 4 + strlen(key->comment);
-    }
-
-    /* Allocate the buffer. */
-    p = ret = snewn(len, unsigned char);
-    if (length) *length = len;
-
-    PUT_32BIT(p, nkeys);
-    p += 4;
-    for (i = 0; NULL != (key = index234(rsakeys, i)); i++) {
-       blob = rsa_public_blob(key, &bloblen);
-       memcpy(p, blob, bloblen);
-       p += bloblen;
-       sfree(blob);
-       PUT_32BIT(p, strlen(key->comment));
-       memcpy(p + 4, key->comment, strlen(key->comment));
-       p += 4 + strlen(key->comment);
-    }
-
-    assert(p - ret == len);
-    return ret;
+    memcpy(msg, reply, replylen);
+    smemclr(reply, replylen);
+    sfree(reply);
 }
 
-/*
- * Create an SSH-2 key list in a malloc'ed buffer; return its
- * length.
- */
-static void *make_keylist2(int *length)
+static void win_add_keyfile(Filename *filename)
 {
-    struct ssh2_userkey *key;
-    int i, len, nkeys;
-    unsigned char *blob, *p, *ret;
-    int bloblen;
+    char *err;
+    int ret;
+    char *passphrase = NULL;
 
     /*
-     * Count up the number and length of keys we hold.
+     * Try loading the key without a passphrase. (Or rather, without a
+     * _new_ passphrase; pageant_add_keyfile will take care of trying
+     * all the passphrases we've already stored.)
      */
-    len = 4;
-    nkeys = 0;
-    for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
-       nkeys++;
-       len += 4;              /* length field */
-       blob = key->alg->public_blob(key->data, &bloblen);
-       len += bloblen;
-       sfree(blob);
-       len += 4 + strlen(key->comment);
+    ret = pageant_add_keyfile(filename, NULL, &err);
+    if (ret == PAGEANT_ACTION_OK) {
+        goto done;
+    } else if (ret == PAGEANT_ACTION_FAILURE) {
+        goto error;
     }
 
-    /* Allocate the buffer. */
-    p = ret = snewn(len, unsigned char);
-    if (length) *length = len;
-
     /*
-     * Packet header is the obvious five bytes, plus four
-     * bytes for the key count.
+     * OK, a passphrase is needed, and we've been given the key
+     * comment to use in the passphrase prompt.
      */
-    PUT_32BIT(p, nkeys);
-    p += 4;
-    for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) {
-       blob = key->alg->public_blob(key->data, &bloblen);
-       PUT_32BIT(p, bloblen);
-       p += 4;
-       memcpy(p, blob, bloblen);
-       p += bloblen;
-       sfree(blob);
-       PUT_32BIT(p, strlen(key->comment));
-       memcpy(p + 4, key->comment, strlen(key->comment));
-       p += 4 + strlen(key->comment);
-    }
-
-    assert(p - ret == len);
-    return ret;
-}
-
-/*
- * Acquire a keylist1 from the primary Pageant; this means either
- * calling make_keylist1 (if that's us) or sending a message to the
- * primary Pageant (if it's not).
- */
-static void *get_keylist1(int *length)
-{
-    void *ret;
+    while (1) {
+        INT_PTR dlgret;
+        struct PassphraseProcStruct pps;
 
-    if (already_running) {
-       unsigned char request[5], *response;
-       void *vresponse;
-       int resplen, retval;
-       request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
-       PUT_32BIT(request, 4);
-
-       retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
-       assert(retval == 1);
-       response = vresponse;
-       if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER) {
-            sfree(response);
-           return NULL;
-        }
+        pps.passphrase = &passphrase;
+        pps.comment = err;
+        dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(210),
+                                NULL, PassphraseProc, (LPARAM) &pps);
+        passphrase_box = NULL;
 
-       ret = snewn(resplen-5, unsigned char);
-       memcpy(ret, response+5, resplen-5);
-       sfree(response);
+        if (!dlgret)
+            goto done;                /* operation cancelled */
 
-       if (length)
-           *length = resplen-5;
-    } else {
-       ret = make_keylist1(length);
-    }
-    return ret;
-}
+        sfree(err);
 
-/*
- * Acquire a keylist2 from the primary Pageant; this means either
- * calling make_keylist2 (if that's us) or sending a message to the
- * primary Pageant (if it's not).
- */
-static void *get_keylist2(int *length)
-{
-    void *ret;
+        assert(passphrase != NULL);
 
-    if (already_running) {
-       unsigned char request[5], *response;
-       void *vresponse;
-       int resplen, retval;
-
-       request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
-       PUT_32BIT(request, 4);
-
-       retval = agent_query(request, 5, &vresponse, &resplen, NULL, NULL);
-       assert(retval == 1);
-       response = vresponse;
-       if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER) {
-            sfree(response);
-           return NULL;
+        ret = pageant_add_keyfile(filename, passphrase, &err);
+        if (ret == PAGEANT_ACTION_OK) {
+            goto done;
+        } else if (ret == PAGEANT_ACTION_FAILURE) {
+            goto error;
         }
 
-       ret = snewn(resplen-5, unsigned char);
-       memcpy(ret, response+5, resplen-5);
-       sfree(response);
-
-       if (length)
-           *length = resplen-5;
-    } else {
-       ret = make_keylist2(length);
-    }
-    return ret;
-}
-
-/*
- * This is the main agent function that answers messages.
- */
-static void answer_msg(void *msg)
-{
-    unsigned char *p = msg;
-    unsigned char *ret = msg;
-    unsigned char *msgend;
-    int type;
-
-    /*
-     * Get the message length.
-     */
-    msgend = p + 4 + GET_32BIT(p);
-
-    /*
-     * Get the message type.
-     */
-    if (msgend < p+5)
-       goto failure;
-    type = p[4];
-
-    p += 5;
-    switch (type) {
-      case SSH1_AGENTC_REQUEST_RSA_IDENTITIES:
-       /*
-        * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER.
-        */
-       {
-           int len;
-           void *keylist;
-
-           ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER;
-           keylist = make_keylist1(&len);
-           if (len + 5 > AGENT_MAX_MSGLEN) {
-               sfree(keylist);
-               goto failure;
-           }
-           PUT_32BIT(ret, len + 1);
-           memcpy(ret + 5, keylist, len);
-           sfree(keylist);
-       }
-       break;
-      case SSH2_AGENTC_REQUEST_IDENTITIES:
-       /*
-        * Reply with SSH2_AGENT_IDENTITIES_ANSWER.
-        */
-       {
-           int len;
-           void *keylist;
-
-           ret[4] = SSH2_AGENT_IDENTITIES_ANSWER;
-           keylist = make_keylist2(&len);
-           if (len + 5 > AGENT_MAX_MSGLEN) {
-               sfree(keylist);
-               goto failure;
-           }
-           PUT_32BIT(ret, len + 1);
-           memcpy(ret + 5, keylist, len);
-           sfree(keylist);
-       }
-       break;
-      case SSH1_AGENTC_RSA_CHALLENGE:
-       /*
-        * Reply with either SSH1_AGENT_RSA_RESPONSE or
-        * SSH_AGENT_FAILURE, depending on whether we have that key
-        * or not.
-        */
-       {
-           struct RSAKey reqkey, *key;
-           Bignum challenge, response;
-           unsigned char response_source[48], response_md5[16];
-           struct MD5Context md5c;
-           int i, len;
-
-           p += 4;
-           i = ssh1_read_bignum(p, msgend - p, &reqkey.exponent);
-           if (i < 0)
-               goto failure;
-           p += i;
-           i = ssh1_read_bignum(p, msgend - p, &reqkey.modulus);
-           if (i < 0) {
-                freebn(reqkey.exponent);
-               goto failure;
-            }
-           p += i;
-           i = ssh1_read_bignum(p, msgend - p, &challenge);
-           if (i < 0) {
-                freebn(reqkey.exponent);
-                freebn(reqkey.modulus);
-               goto failure;
-            }
-           p += i;
-           if (msgend < p+16) {
-               freebn(reqkey.exponent);
-               freebn(reqkey.modulus);
-               freebn(challenge);
-               goto failure;
-           }
-           memcpy(response_source + 32, p, 16);
-           p += 16;
-           if (msgend < p+4 ||
-               GET_32BIT(p) != 1 ||
-               (key = find234(rsakeys, &reqkey, NULL)) == NULL) {
-               freebn(reqkey.exponent);
-               freebn(reqkey.modulus);
-               freebn(challenge);
-               goto failure;
-           }
-           response = rsadecrypt(challenge, key);
-           for (i = 0; i < 32; i++)
-               response_source[i] = bignum_byte(response, 31 - i);
-
-           MD5Init(&md5c);
-           MD5Update(&md5c, response_source, 48);
-           MD5Final(response_md5, &md5c);
-           smemclr(response_source, 48);       /* burn the evidence */
-           freebn(response);          /* and that evidence */
-           freebn(challenge);         /* yes, and that evidence */
-           freebn(reqkey.exponent);   /* and free some memory ... */
-           freebn(reqkey.modulus);    /* ... while we're at it. */
-
-           /*
-            * Packet is the obvious five byte header, plus sixteen
-            * bytes of MD5.
-            */
-           len = 5 + 16;
-           PUT_32BIT(ret, len - 4);
-           ret[4] = SSH1_AGENT_RSA_RESPONSE;
-           memcpy(ret + 5, response_md5, 16);
-       }
-       break;
-      case SSH2_AGENTC_SIGN_REQUEST:
-       /*
-        * Reply with either SSH2_AGENT_SIGN_RESPONSE or
-        * SSH_AGENT_FAILURE, depending on whether we have that key
-        * or not.
-        */
-       {
-           struct ssh2_userkey *key;
-           struct blob b;
-           unsigned char *data, *signature;
-           int datalen, siglen, len;
-
-           if (msgend < p+4)
-               goto failure;
-           b.len = toint(GET_32BIT(p));
-            if (b.len < 0 || b.len > msgend - (p+4))
-                goto failure;
-           p += 4;
-           b.blob = p;
-           p += b.len;
-           if (msgend < p+4)
-               goto failure;
-           datalen = toint(GET_32BIT(p));
-           p += 4;
-           if (datalen < 0 || datalen > msgend - p)
-               goto failure;
-           data = p;
-           key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
-           if (!key)
-               goto failure;
-           signature = key->alg->sign(key->data, data, datalen, &siglen);
-           len = 5 + 4 + siglen;
-           PUT_32BIT(ret, len - 4);
-           ret[4] = SSH2_AGENT_SIGN_RESPONSE;
-           PUT_32BIT(ret + 5, siglen);
-           memcpy(ret + 5 + 4, signature, siglen);
-           sfree(signature);
-       }
-       break;
-      case SSH1_AGENTC_ADD_RSA_IDENTITY:
-       /*
-        * Add to the list and return SSH_AGENT_SUCCESS, or
-        * SSH_AGENT_FAILURE if the key was malformed.
-        */
-       {
-           struct RSAKey *key;
-           char *comment;
-            int n, commentlen;
-
-           key = snew(struct RSAKey);
-           memset(key, 0, sizeof(struct RSAKey));
-
-           n = makekey(p, msgend - p, key, NULL, 1);
-           if (n < 0) {
-               freersakey(key);
-               sfree(key);
-               goto failure;
-           }
-           p += n;
-
-           n = makeprivate(p, msgend - p, key);
-           if (n < 0) {
-               freersakey(key);
-               sfree(key);
-               goto failure;
-           }
-           p += n;
-
-           n = ssh1_read_bignum(p, msgend - p, &key->iqmp);  /* p^-1 mod q */
-           if (n < 0) {
-               freersakey(key);
-               sfree(key);
-               goto failure;
-           }
-           p += n;
-
-           n = ssh1_read_bignum(p, msgend - p, &key->p);  /* p */
-           if (n < 0) {
-               freersakey(key);
-               sfree(key);
-               goto failure;
-           }
-           p += n;
-
-           n = ssh1_read_bignum(p, msgend - p, &key->q);  /* q */
-           if (n < 0) {
-               freersakey(key);
-               sfree(key);
-               goto failure;
-           }
-           p += n;
-
-           if (msgend < p+4) {
-               freersakey(key);
-               sfree(key);
-               goto failure;
-           }
-            commentlen = toint(GET_32BIT(p));
-
-           if (commentlen < 0 || commentlen > msgend - p) {
-               freersakey(key);
-               sfree(key);
-               goto failure;
-           }
-
-           comment = snewn(commentlen+1, char);
-           if (comment) {
-               memcpy(comment, p + 4, commentlen);
-                comment[commentlen] = '\0';
-               key->comment = comment;
-           }
-           PUT_32BIT(ret, 1);
-           ret[4] = SSH_AGENT_FAILURE;
-           if (add234(rsakeys, key) == key) {
-               keylist_update();
-               ret[4] = SSH_AGENT_SUCCESS;
-           } else {
-               freersakey(key);
-               sfree(key);
-           }
-       }
-       break;
-      case SSH2_AGENTC_ADD_IDENTITY:
-       /*
-        * Add to the list and return SSH_AGENT_SUCCESS, or
-        * SSH_AGENT_FAILURE if the key was malformed.
-        */
-       {
-           struct ssh2_userkey *key;
-           char *comment, *alg;
-           int alglen, commlen;
-           int bloblen;
-
-
-           if (msgend < p+4)
-               goto failure;
-           alglen = toint(GET_32BIT(p));
-           p += 4;
-           if (alglen < 0 || alglen > msgend - p)
-               goto failure;
-           alg = p;
-           p += alglen;
-
-           key = snew(struct ssh2_userkey);
-           /* Add further algorithm names here. */
-           if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7))
-               key->alg = &ssh_rsa;
-           else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7))
-               key->alg = &ssh_dss;
-           else {
-               sfree(key);
-               goto failure;
-           }
-
-           bloblen = msgend - p;
-           key->data = key->alg->openssh_createkey(&p, &bloblen);
-           if (!key->data) {
-               sfree(key);
-               goto failure;
-           }
-
-           /*
-            * p has been advanced by openssh_createkey, but
-            * certainly not _beyond_ the end of the buffer.
-            */
-           assert(p <= msgend);
-
-           if (msgend < p+4) {
-               key->alg->freekey(key->data);
-               sfree(key);
-               goto failure;
-           }
-           commlen = toint(GET_32BIT(p));
-           p += 4;
-
-           if (commlen < 0 || commlen > msgend - p) {
-               key->alg->freekey(key->data);
-               sfree(key);
-               goto failure;
-           }
-           comment = snewn(commlen + 1, char);
-           if (comment) {
-               memcpy(comment, p, commlen);
-               comment[commlen] = '\0';
-           }
-           key->comment = comment;
-
-           PUT_32BIT(ret, 1);
-           ret[4] = SSH_AGENT_FAILURE;
-           if (add234(ssh2keys, key) == key) {
-               keylist_update();
-               ret[4] = SSH_AGENT_SUCCESS;
-           } else {
-               key->alg->freekey(key->data);
-               sfree(key->comment);
-               sfree(key);
-           }
-       }
-       break;
-      case SSH1_AGENTC_REMOVE_RSA_IDENTITY:
-       /*
-        * Remove from the list and return SSH_AGENT_SUCCESS, or
-        * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
-        * start with.
-        */
-       {
-           struct RSAKey reqkey, *key;
-           int n;
-
-           n = makekey(p, msgend - p, &reqkey, NULL, 0);
-           if (n < 0)
-               goto failure;
-
-           key = find234(rsakeys, &reqkey, NULL);
-           freebn(reqkey.exponent);
-           freebn(reqkey.modulus);
-           PUT_32BIT(ret, 1);
-           ret[4] = SSH_AGENT_FAILURE;
-           if (key) {
-               del234(rsakeys, key);
-               keylist_update();
-               freersakey(key);
-               sfree(key);
-               ret[4] = SSH_AGENT_SUCCESS;
-           }
-       }
-       break;
-      case SSH2_AGENTC_REMOVE_IDENTITY:
-       /*
-        * Remove from the list and return SSH_AGENT_SUCCESS, or
-        * perhaps SSH_AGENT_FAILURE if it wasn't in the list to
-        * start with.
-        */
-       {
-           struct ssh2_userkey *key;
-           struct blob b;
-
-           if (msgend < p+4)
-               goto failure;
-           b.len = toint(GET_32BIT(p));
-           p += 4;
-
-           if (b.len < 0 || b.len > msgend - p)
-               goto failure;
-           b.blob = p;
-           p += b.len;
-
-           key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm);
-           if (!key)
-               goto failure;
-
-           PUT_32BIT(ret, 1);
-           ret[4] = SSH_AGENT_FAILURE;
-           if (key) {
-               del234(ssh2keys, key);
-               keylist_update();
-               key->alg->freekey(key->data);
-               sfree(key);
-               ret[4] = SSH_AGENT_SUCCESS;
-           }
-       }
-       break;
-      case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
-       /*
-        * Remove all SSH-1 keys. Always returns success.
-        */
-       {
-           struct RSAKey *rkey;
-
-           while ((rkey = index234(rsakeys, 0)) != NULL) {
-               del234(rsakeys, rkey);
-               freersakey(rkey);
-               sfree(rkey);
-           }
-           keylist_update();
-
-           PUT_32BIT(ret, 1);
-           ret[4] = SSH_AGENT_SUCCESS;
-       }
-       break;
-      case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
-       /*
-        * Remove all SSH-2 keys. Always returns success.
-        */
-       {
-           struct ssh2_userkey *skey;
-
-           while ((skey = index234(ssh2keys, 0)) != NULL) {
-               del234(ssh2keys, skey);
-               skey->alg->freekey(skey->data);
-               sfree(skey);
-           }
-           keylist_update();
-
-           PUT_32BIT(ret, 1);
-           ret[4] = SSH_AGENT_SUCCESS;
-       }
-       break;
-      default:
-      failure:
-       /*
-        * Unrecognised message. Return SSH_AGENT_FAILURE.
-        */
-       PUT_32BIT(ret, 1);
-       ret[4] = SSH_AGENT_FAILURE;
-       break;
-    }
-}
-
-/*
- * Key comparison function for the 2-3-4 tree of RSA keys.
- */
-static int cmpkeys_rsa(void *av, void *bv)
-{
-    struct RSAKey *a = (struct RSAKey *) av;
-    struct RSAKey *b = (struct RSAKey *) bv;
-    Bignum am, bm;
-    int alen, blen;
-
-    am = a->modulus;
-    bm = b->modulus;
-    /*
-     * Compare by length of moduli.
-     */
-    alen = bignum_bitcount(am);
-    blen = bignum_bitcount(bm);
-    if (alen > blen)
-       return +1;
-    else if (alen < blen)
-       return -1;
-    /*
-     * Now compare by moduli themselves.
-     */
-    alen = (alen + 7) / 8;            /* byte count */
-    while (alen-- > 0) {
-       int abyte, bbyte;
-       abyte = bignum_byte(am, alen);
-       bbyte = bignum_byte(bm, alen);
-       if (abyte > bbyte)
-           return +1;
-       else if (abyte < bbyte)
-           return -1;
-    }
-    /*
-     * Give up.
-     */
-    return 0;
-}
-
-/*
- * Key comparison function for the 2-3-4 tree of SSH-2 keys.
- */
-static int cmpkeys_ssh2(void *av, void *bv)
-{
-    struct ssh2_userkey *a = (struct ssh2_userkey *) av;
-    struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
-    int i;
-    int alen, blen;
-    unsigned char *ablob, *bblob;
-    int c;
-
-    /*
-     * Compare purely by public blob.
-     */
-    ablob = a->alg->public_blob(a->data, &alen);
-    bblob = b->alg->public_blob(b->data, &blen);
-
-    c = 0;
-    for (i = 0; i < alen && i < blen; i++) {
-       if (ablob[i] < bblob[i]) {
-           c = -1;
-           break;
-       } else if (ablob[i] > bblob[i]) {
-           c = +1;
-           break;
-       }
+        smemclr(passphrase, strlen(passphrase));
+        sfree(passphrase);
+        passphrase = NULL;
     }
-    if (c == 0 && i < alen)
-       c = +1;                        /* a is longer */
-    if (c == 0 && i < blen)
-       c = -1;                        /* a is longer */
-
-    sfree(ablob);
-    sfree(bblob);
 
-    return c;
-}
-
-/*
- * Key comparison function for looking up a blob in the 2-3-4 tree
- * of SSH-2 keys.
- */
-static int cmpkeys_ssh2_asymm(void *av, void *bv)
-{
-    struct blob *a = (struct blob *) av;
-    struct ssh2_userkey *b = (struct ssh2_userkey *) bv;
-    int i;
-    int alen, blen;
-    unsigned char *ablob, *bblob;
-    int c;
-
-    /*
-     * Compare purely by public blob.
-     */
-    ablob = a->blob;
-    alen = a->len;
-    bblob = b->alg->public_blob(b->data, &blen);
-
-    c = 0;
-    for (i = 0; i < alen && i < blen; i++) {
-       if (ablob[i] < bblob[i]) {
-           c = -1;
-           break;
-       } else if (ablob[i] > bblob[i]) {
-           c = +1;
-           break;
-       }
+  error:
+    message_box(err, APPNAME, MB_OK | MB_ICONERROR,
+                HELPCTXID(errors_cantloadkey));
+  done:
+    if (passphrase) {
+        smemclr(passphrase, strlen(passphrase));
+        sfree(passphrase);
     }
-    if (c == 0 && i < alen)
-       c = +1;                        /* a is longer */
-    if (c == 0 && i < blen)
-       c = -1;                        /* a is longer */
-
-    sfree(bblob);
-
-    return c;
+    sfree(err);
+    return;
 }
 
 /*
@@ -1453,7 +441,7 @@ static void prompt_add_keyfile(void)
        if(strlen(filelist) > of.nFileOffset) {
            /* Only one filename returned? */
             Filename *fn = filename_from_str(filelist);
-           add_keyfile(fn);
+           win_add_keyfile(fn);
             filename_free(fn);
         } else {
            /* we are returned a bunch of strings, end to
@@ -1466,7 +454,7 @@ static void prompt_add_keyfile(void)
            while (*filewalker != '\0') {
                char *filename = dupcat(dir, "\\", filewalker, NULL);
                 Filename *fn = filename_from_str(filename);
-               add_keyfile(fn);
+               win_add_keyfile(fn);
                 filename_free(fn);
                sfree(filename);
                filewalker += strlen(filewalker) + 1;
@@ -1474,7 +462,7 @@ static void prompt_add_keyfile(void)
        }
 
        keylist_update();
-       forget_passphrases();
+       pageant_forget_passphrases();
     }
     sfree(filelist);
 }
@@ -1482,7 +470,7 @@ static void prompt_add_keyfile(void)
 /*
  * Dialog-box function for the key list box.
  */
-static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
+static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg,
                                WPARAM wParam, LPARAM lParam)
 {
     struct RSAKey *rkey;
@@ -1517,7 +505,7 @@ static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
 
        keylist = hwnd;
        {
-           static int tabs[] = { 35, 60, 210 };
+           static int tabs[] = { 35, 75, 250 };
            SendDlgItemMessage(hwnd, 100, LB_SETTABSTOPS,
                               sizeof(tabs) / sizeof(*tabs),
                               (LPARAM) tabs);
@@ -1568,35 +556,35 @@ static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
                                numSelected, (WPARAM)selectedArray);
                
                itemNum = numSelected - 1;
-               rCount = count234(rsakeys);
-               sCount = count234(ssh2keys);
+               rCount = pageant_count_ssh1_keys();
+               sCount = pageant_count_ssh2_keys();
                
                /* go through the non-rsakeys until we've covered them all, 
                 * and/or we're out of selected items to check. note that
                 * we go *backwards*, to avoid complications from deleting
                 * things hence altering the offset of subsequent items
                 */
-           for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
-                       skey = index234(ssh2keys, i);
+                for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
+                    skey = pageant_nth_ssh2_key(i);
                        
-                       if (selectedArray[itemNum] == rCount + i) {
-                               del234(ssh2keys, skey);
-                               skey->alg->freekey(skey->data);
-                               sfree(skey);
-                               itemNum--; 
-                       }
+                    if (selectedArray[itemNum] == rCount + i) {
+                        pageant_delete_ssh2_key(skey);
+                        skey->alg->freekey(skey->data);
+                        sfree(skey);
+                        itemNum--;
+                    }
                }
                
                /* do the same for the rsa keys */
                for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
-                       rkey = index234(rsakeys, i);
-
-                       if(selectedArray[itemNum] == i) {
-                               del234(rsakeys, rkey);
-                               freersakey(rkey);
-                               sfree(rkey);
-                               itemNum--;
-                       }
+                    rkey = pageant_nth_ssh1_key(i);
+
+                    if(selectedArray[itemNum] == i) {
+                        pageant_delete_ssh1_key(rkey);
+                        freersakey(rkey);
+                        sfree(rkey);
+                        itemNum--;
+                    }
                }
 
                sfree(selectedArray); 
@@ -1614,7 +602,7 @@ static int CALLBACK KeyListProc(HWND hwnd, UINT msg,
       case WM_HELP:
         {
             int id = ((LPHELPINFO)lParam)->iCtrlId;
-            char *topic = NULL;
+            const char *topic = NULL;
             switch (id) {
               case 100: topic = WINHELP_CTX_pageant_keylist; break;
               case 101: topic = WINHELP_CTX_pageant_addkey; break;
@@ -1765,7 +753,6 @@ PSID get_default_sid(void)
 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
                                WPARAM wParam, LPARAM lParam)
 {
-    int ret;
     static int menuinprogress;
     static UINT msgTaskbarCreated = 0;
 
@@ -1800,10 +787,10 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
            menuinprogress = 1;
            update_sessions();
            SetForegroundWindow(hwnd);
-           ret = TrackPopupMenu(systray_menu,
-                                TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
-                                TPM_RIGHTBUTTON,
-                                wParam, lParam, 0, hwnd, NULL);
+           TrackPopupMenu(systray_menu,
+                          TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
+                          TPM_RIGHTBUTTON,
+                          wParam, lParam, 0, hwnd, NULL);
            menuinprogress = 0;
        }
        break;
@@ -1811,7 +798,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
       case WM_SYSCOMMAND:
        switch (wParam & ~0xF) {       /* low 4 bits reserved to Windows */
          case IDM_PUTTY:
-           if((int)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""),
+           if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""),
                                 SW_SHOW) <= 32) {
                MessageBox(NULL, "Unable to execute PuTTY!",
                           "Error", MB_OK | MB_ICONERROR);
@@ -1878,7 +865,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
                    GetMenuItemInfo(session_menu, wParam, FALSE, &mii);
                    strcpy(param, "@");
                    strcat(param, mii.dwTypeData);
-                   if((int)ShellExecute(hwnd, NULL, putty_path, param,
+                   if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, param,
                                         _T(""), SW_SHOW) <= 32) {
                        MessageBox(NULL, "Unable to execute PuTTY!", "Error",
                                   MB_OK | MB_ICONERROR);
@@ -2009,7 +996,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
 /*
  * Fork and Exec the command in cmdline. [DBW]
  */
-void spawn_cmd(char *cmdline, char * args, int show)
+void spawn_cmd(const char *cmdline, const char *args, int show)
 {
     if (ShellExecute(NULL, _T("open"), cmdline,
                     args, NULL, show) <= (HINSTANCE) 32) {
@@ -2043,7 +1030,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
 {
     WNDCLASS wndclass;
     MSG msg;
-    char *command = NULL;
+    const char *command = NULL;
     int added_keys = 0;
     int argc, i;
     char **argv, **argstart;
@@ -2117,18 +1104,12 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
     already_running = agent_exists();
 
     /*
-     * Initialise storage for RSA keys.
+     * Initialise the cross-platform Pageant code.
      */
     if (!already_running) {
-       rsakeys = newtree234(cmpkeys_rsa);
-       ssh2keys = newtree234(cmpkeys_ssh2);
+        pageant_init();
     }
 
-    /*
-     * Initialise storage for short-term passphrase cache.
-     */
-    passphrases = newtree234(NULL);
-
     /*
      * Process the command line and add keys as listed on it.
      */
@@ -2150,7 +1131,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
            break;
        } else {
             Filename *fn = filename_from_str(argv[i]);
-           add_keyfile(fn);
+           win_add_keyfile(fn);
             filename_free(fn);
            added_keys = TRUE;
        }
@@ -2160,7 +1141,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
      * Forget any passphrase that we retained while going over
      * command line keyfiles.
      */
-    forget_passphrases();
+    pageant_forget_passphrases();
 
     if (command) {
        char *args;
@@ -2219,7 +1200,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
        session_menu = CreateMenu();
        AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session");
        AppendMenu(systray_menu, MF_POPUP | MF_ENABLED,
-                  (UINT) session_menu, "&Saved Sessions");
+                  (UINT_PTR) session_menu, "&Saved Sessions");
        AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
     }
     AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
index 77a2dc542539676f4f2d14e5c0d42229b52df00d..ac4dab299fe19ffb0bb5de3f268da1b904feab7f 100644 (file)
@@ -21,7 +21,7 @@ struct agent_callback {
     int len;
 };
 
-void fatalbox(char *p, ...)
+void fatalbox(const char *p, ...)
 {
     va_list ap;
     fprintf(stderr, "FATAL ERROR: ");
@@ -35,7 +35,7 @@ void fatalbox(char *p, ...)
     }
     cleanup_exit(1);
 }
-void modalfatalbox(char *p, ...)
+void modalfatalbox(const char *p, ...)
 {
     va_list ap;
     fprintf(stderr, "FATAL ERROR: ");
@@ -49,7 +49,7 @@ void modalfatalbox(char *p, ...)
     }
     cleanup_exit(1);
 }
-void nonfatal(char *p, ...)
+void nonfatal(const char *p, ...)
 {
     va_list ap;
     fprintf(stderr, "ERROR: ");
@@ -58,7 +58,7 @@ void nonfatal(char *p, ...)
     va_end(ap);
     fputc('\n', stderr);
 }
-void connection_fatal(void *frontend, char *p, ...)
+void connection_fatal(void *frontend, const char *p, ...)
 {
     va_list ap;
     fprintf(stderr, "FATAL ERROR: ");
@@ -72,7 +72,7 @@ void connection_fatal(void *frontend, char *p, ...)
     }
     cleanup_exit(1);
 }
-void cmdline_error(char *p, ...)
+void cmdline_error(const char *p, ...)
 {
     va_list ap;
     fprintf(stderr, "plink: ");
@@ -98,7 +98,7 @@ int term_ldisc(Terminal *term, int mode)
 {
     return FALSE;
 }
-void ldisc_update(void *frontend, int echo, int edit)
+void frontend_echoedit_update(void *frontend, int echo, int edit)
 {
     /* Update stdin read mode to reflect changes in line discipline. */
     DWORD mode;
@@ -145,7 +145,7 @@ int from_backend_eof(void *frontend_handle)
     return FALSE;   /* do not respond to incoming EOF with outgoing */
 }
 
-int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+int get_userpass_input(prompts_t *p, const unsigned char *in, int inlen)
 {
     int ret;
     ret = cmdline_get_passwd_input(p, in, inlen);
@@ -215,6 +215,8 @@ static void usage(void)
     printf("  -sshlog file\n");
     printf("  -sshrawlog file\n");
     printf("            log protocol details to a file\n");
+    printf("  -shareexists\n");
+    printf("            test whether a connection-sharing upstream exists\n");
     exit(1);
 }
 
@@ -306,6 +308,7 @@ int main(int argc, char **argv)
     int errors;
     int got_host = FALSE;
     int use_subsystem = 0;
+    int just_test_share_exists = FALSE;
     unsigned long now, next, then;
 
     sklist = NULL;
@@ -367,6 +370,8 @@ int main(int argc, char **argv)
             } else if (!strcmp(p, "-pgpfp")) {
                 pgp_fingerprints();
                 exit(1);
+           } else if (!strcmp(p, "-shareexists")) {
+                just_test_share_exists = TRUE;
            } else {
                fprintf(stderr, "plink: unknown option \"%s\"\n", p);
                errors = 1;
@@ -599,6 +604,19 @@ int main(int argc, char **argv)
     logctx = log_init(NULL, conf);
     console_provide_logctx(logctx);
 
+    if (just_test_share_exists) {
+        if (!back->test_for_upstream) {
+            fprintf(stderr, "Connection sharing not supported for connection "
+                    "type '%s'\n", back->name);
+            return 1;
+        }
+        if (back->test_for_upstream(conf_get_str(conf, CONF_host),
+                                    conf_get_int(conf, CONF_port), conf))
+            return 0;
+        else
+            return 1;
+    }
+
     /*
      * Start up the connection.
      */
index aa14b91ae9898e8ffc2a81c789b49144ab5e6920..813c4fe9a76bc21ac0a1d0667a4bd47faf5f2e80 100644 (file)
 #include "network.h"
 #include "proxy.h"
 
-Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, Plug plug,
-                          int overlapped);
+Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H,
+                          Plug plug, int overlapped);
 
-Socket platform_new_connection(SockAddr addr, char *hostname,
+Socket platform_new_connection(SockAddr addr, const char *hostname,
                               int port, int privport,
                               int oobinline, int nodelay, int keepalive,
                               Plug plug, Conf *conf)
 {
     char *cmd;
-    HANDLE us_to_cmd, us_from_cmd, cmd_to_us, cmd_from_us;
+    HANDLE us_to_cmd, cmd_from_us;
+    HANDLE us_from_cmd, cmd_to_us;
+    HANDLE us_from_cmd_err, cmd_err_to_us;
     SECURITY_ATTRIBUTES sa;
     STARTUPINFO si;
     PROCESS_INFORMATION pi;
@@ -37,9 +39,7 @@ Socket platform_new_connection(SockAddr addr, char *hostname,
 
     {
        char *msg = dupprintf("Starting local proxy command: %s", cmd);
-       /* We're allowed to pass NULL here, because we're part of the Windows
-        * front end so we know logevent doesn't expect any data. */
-       logevent(NULL, msg);
+       plug_log(plug, 2, NULL, 0, msg, 0);
        sfree(msg);
     }
 
@@ -66,8 +66,25 @@ Socket platform_new_connection(SockAddr addr, char *hostname,
        return ret;
     }
 
+    if (flags & FLAG_STDERR) {
+        /* If we have a sensible stderr, the proxy command can send
+         * its own standard error there, so we won't interfere. */
+        us_from_cmd_err = cmd_err_to_us = NULL;
+    } else {
+        /* If we don't have a sensible stderr, we should catch the
+         * proxy command's standard error to put in our event log. */
+        if (!CreatePipe(&us_from_cmd_err, &cmd_err_to_us, &sa, 0)) {
+            Socket ret = new_error_socket
+                ("Unable to create pipes for proxy command", plug);
+            sfree(cmd);
+            return ret;
+        }
+    }
+
     SetHandleInformation(us_to_cmd, HANDLE_FLAG_INHERIT, 0);
     SetHandleInformation(us_from_cmd, HANDLE_FLAG_INHERIT, 0);
+    if (us_from_cmd_err != NULL)
+        SetHandleInformation(us_from_cmd_err, HANDLE_FLAG_INHERIT, 0);
 
     si.cb = sizeof(si);
     si.lpReserved = NULL;
@@ -78,7 +95,7 @@ Socket platform_new_connection(SockAddr addr, char *hostname,
     si.lpReserved2 = NULL;
     si.hStdInput = cmd_from_us;
     si.hStdOutput = cmd_to_us;
-    si.hStdError = NULL;
+    si.hStdError = cmd_err_to_us;
     CreateProcess(NULL, cmd, NULL, NULL, TRUE,
                  CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS,
                  NULL, NULL, &si, &pi);
@@ -90,5 +107,9 @@ Socket platform_new_connection(SockAddr addr, char *hostname,
     CloseHandle(cmd_from_us);
     CloseHandle(cmd_to_us);
 
-    return make_handle_socket(us_to_cmd, us_from_cmd, plug, FALSE);
+    if (cmd_err_to_us != NULL)
+        CloseHandle(cmd_err_to_us);
+
+    return make_handle_socket(us_to_cmd, us_from_cmd, us_from_cmd_err,
+                              plug, FALSE);
 }
index 086d3e5cd4fcb2b3e7fbe8b767ac36748b0afd38..de9e61b480f91b6a9a5f6ccab81bf5142956f2bf 100644 (file)
@@ -199,7 +199,7 @@ static const char *serial_configure(Serial serial, HANDLE serport, Conf *conf)
  * freed by the caller.
  */
 static const char *serial_init(void *frontend_handle, void **backend_handle,
-                              Conf *conf, char *host, int port,
+                              Conf *conf, const char *host, int port,
                               char **realhost, int nodelay, int keepalive)
 {
     Serial serial;
@@ -302,7 +302,7 @@ static void serial_reconfig(void *handle, Conf *conf)
 /*
  * Called to send data down the serial connection.
  */
-static int serial_send(void *handle, char *buf, int len)
+static int serial_send(void *handle, const char *buf, int len)
 {
     Serial serial = (Serial) handle;
 
@@ -453,6 +453,7 @@ Backend serial_backend = {
     serial_provide_logctx,
     serial_unthrottle,
     serial_cfg_info,
+    NULL /* test_for_upstream */,
     "serial",
     PROT_SERIAL,
     0
index d061b514843a56d66bbd8d6f4ed91e945b7aa89a..0776cba94bdc19f544814d26dd7e985611fcae42 100644 (file)
@@ -11,7 +11,7 @@
 
 char *get_ttymode(void *frontend, const char *mode) { return NULL; }
 
-int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+int get_userpass_input(prompts_t *p, const unsigned char *in, int inlen)
 {
     int ret;
     ret = cmdline_get_passwd_input(p, in, inlen);
@@ -87,7 +87,7 @@ struct RFile {
     HANDLE h;
 };
 
-RFile *open_existing_file(char *name, uint64 *size,
+RFile *open_existing_file(const char *name, uint64 *size,
                          unsigned long *mtime, unsigned long *atime,
                           long *perms)
 {
@@ -141,7 +141,7 @@ struct WFile {
     HANDLE h;
 };
 
-WFile *open_new_file(char *name, long perms)
+WFile *open_new_file(const char *name, long perms)
 {
     HANDLE h;
     WFile *ret;
@@ -157,7 +157,7 @@ WFile *open_new_file(char *name, long perms)
     return ret;
 }
 
-WFile *open_existing_wfile(char *name, uint64 *size)
+WFile *open_existing_wfile(const char *name, uint64 *size)
 {
     HANDLE h;
     WFile *ret;
@@ -239,7 +239,7 @@ uint64 get_file_posn(WFile *f)
     return ret;
 }
 
-int file_type(char *name)
+int file_type(const char *name)
 {
     DWORD attr;
     attr = GetFileAttributes(name);
@@ -257,7 +257,7 @@ struct DirHandle {
     char *name;
 };
 
-DirHandle *open_directory(char *name)
+DirHandle *open_directory(const char *name)
 {
     HANDLE h;
     WIN32_FIND_DATA fdat;
@@ -316,7 +316,7 @@ void close_directory(DirHandle *dir)
     sfree(dir);
 }
 
-int test_wildcard(char *name, int cmdline)
+int test_wildcard(const char *name, int cmdline)
 {
     HANDLE fh;
     WIN32_FIND_DATA fdat;
@@ -340,14 +340,14 @@ struct WildcardMatcher {
     char *srcpath;
 };
 
-/*
- * Return a pointer to the portion of str that comes after the last
- * slash (or backslash or colon, if `local' is TRUE).
- */
-static char *stripslashes(char *str, int local)
+char *stripslashes(const char *str, int local)
 {
     char *p;
 
+    /*
+     * On Windows, \ / : are all path component separators.
+     */
+
     if (local) {
         p = strchr(str, ':');
         if (p) str = p+1;
@@ -361,10 +361,10 @@ static char *stripslashes(char *str, int local)
        if (p) str = p+1;
     }
 
-    return str;
+    return (char *)str;
 }
 
-WildcardMatcher *begin_wildcard_matching(char *name)
+WildcardMatcher *begin_wildcard_matching(const char *name)
 {
     HANDLE h;
     WIN32_FIND_DATA fdat;
@@ -424,7 +424,7 @@ void finish_wildcard_matching(WildcardMatcher *dir)
     sfree(dir);
 }
 
-int vet_filename(char *name)
+int vet_filename(const char *name)
 {
     if (strchr(name, '/') || strchr(name, '\\') || strchr(name, ':'))
        return FALSE;
@@ -435,12 +435,12 @@ int vet_filename(char *name)
     return TRUE;
 }
 
-int create_directory(char *name)
+int create_directory(const char *name)
 {
     return CreateDirectory(name, NULL) != 0;
 }
 
-char *dir_file_cat(char *dir, char *file)
+char *dir_file_cat(const char *dir, const char *file)
 {
     return dupcat(dir, "\\", file, NULL);
 }
@@ -691,7 +691,7 @@ static DWORD WINAPI command_read_thread(void *param)
     return 0;
 }
 
-char *ssh_sftp_get_cmdline(char *prompt, int no_fds_ok)
+char *ssh_sftp_get_cmdline(const char *prompt, int no_fds_ok)
 {
     int ret;
     struct command_read_ctx actx, *ctx = &actx;
index b1058832e99a11da3e48d44216a555da809aca07..6944849658f5ff185df52bfee39e239a6dce31e0 100644 (file)
@@ -454,6 +454,16 @@ int verify_host_key(const char *hostname, int port,
        return 0;                      /* key matched OK in registry */
 }
 
+int have_ssh_host_key(const char *hostname, int port,
+                     const char *keytype)
+{
+    /*
+     * If we have a host key, verify_host_key will return 0 or 2.
+     * If we don't have one, it'll return 1.
+     */
+    return verify_host_key(hostname, port, keytype, "") != 1;
+}
+
 void store_host_key(const char *hostname, int port,
                    const char *keytype, const char *key)
 {
@@ -647,7 +657,7 @@ static int transform_jumplist_registry
     int ret;
     HKEY pjumplist_key, psettings_tmp;
     DWORD type;
-    int value_length;
+    DWORD value_length;
     char *old_value, *new_value;
     char *piterator_old, *piterator_new, *piterator_tmp;
 
index 8f5e0dff0625cb485088c13fd65060e381e57652..4f2b4e887184b50dd766ca55178662c57a50aa51 100644 (file)
 #include <windows.h>
 #include <stdio.h>                    /* for FILENAME_MAX */
 
+/* We use uintptr_t for Win32/Win64 portability, so we should in
+ * principle include stdint.h, which defines it according to the C
+ * standard. But older versions of Visual Studio - including the one
+ * used for official PuTTY builds as of 2015-09-28 - don't provide
+ * stdint.h at all, but do (non-standardly) define uintptr_t in
+ * stddef.h. So here we try to make sure _some_ standard header is
+ * included which defines uintptr_t. */
+#include <stddef.h>
+#if !defined _MSC_VER || _MSC_VER >= 1600
+#include <stdint.h>
+#endif
+
 #include "tree234.h"
 
 #include "winhelp.h"
@@ -79,6 +91,8 @@ struct FontSpec *fontspec_new(const char *name,
 #define PLATFORM_HAS_SMEMCLR /* inhibit cross-platform one in misc.c */
 #endif
 
+#define BROKEN_PIPE_ERROR_CODE ERROR_BROKEN_PIPE   /* used in sshshare.c */
+
 /*
  * Dynamically linked functions. These come in two flavours:
  *
index 14728a815a86c46d9f1881dfc6c76d4381d46248..c1888f9eda93ba6663b6368ef9a36d348f8f62e7 100644 (file)
@@ -1161,7 +1161,7 @@ void get_unitab(int codepage, wchar_t * unitab, int ftype)
 }
 
 int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen,
-            char *mbstr, int mblen, char *defchr, int *defused,
+            char *mbstr, int mblen, const char *defchr, int *defused,
             struct unicode_data *ucsdata)
 {
     char *p;
index ef0db9211bf946dcf329b8cf8c27e9147d725ffe..c49612e535795a066bcaee0f960bdb59c5d818f2 100644 (file)
@@ -94,7 +94,7 @@ void filereq_free(filereq *state)
 /* Callback function to launch context help. */
 static VOID CALLBACK message_box_help_callback(LPHELPINFO lpHelpInfo)
 {
-    char *context = NULL;
+    const char *context = NULL;
 #define CHECK_CTX(name) \
     do { \
        if (lpHelpInfo->dwContextId == WINHELP_CTXID_ ## name) \
index 7ecb4cc27b42cc5c74ef928df3dee64fe69387fd..bdfc4e743570f9173cf86d82040b593d41fc6e7e 100644 (file)
--- a/x11fwd.c
+++ b/x11fwd.c
@@ -190,7 +190,7 @@ int x11_authcmp(void *av, void *bv)
     }
 }
 
-struct X11Display *x11_setup_display(char *display, Conf *conf)
+struct X11Display *x11_setup_display(const char *display, Conf *conf)
 {
     struct X11Display *disp = snew(struct X11Display);
     char *localcopy;
@@ -286,7 +286,8 @@ struct X11Display *x11_setup_display(char *display, Conf *conf)
 
        disp->port = 6000 + disp->displaynum;
        disp->addr = name_lookup(disp->hostname, disp->port,
-                                &disp->realhost, conf, ADDRTYPE_UNSPEC);
+                                &disp->realhost, conf, ADDRTYPE_UNSPEC,
+                                 NULL, NULL);
     
        if ((err = sk_addr_error(disp->addr)) != NULL) {
            sk_addr_free(disp->addr);
@@ -357,10 +358,10 @@ void x11_free_display(struct X11Display *disp)
 
 #define XDM_MAXSKEW 20*60      /* 20 minute clock skew should be OK */
 
-static char *x11_verify(unsigned long peer_ip, int peer_port,
-                       tree234 *authtree, char *proto,
-                       unsigned char *data, int dlen,
-                        struct X11FakeAuth **auth_ret)
+static const char *x11_verify(unsigned long peer_ip, int peer_port,
+                              tree234 *authtree, char *proto,
+                              unsigned char *data, int dlen,
+                              struct X11FakeAuth **auth_ret)
 {
     struct X11FakeAuth match_dummy;    /* for passing to find234 */
     struct X11FakeAuth *auth;
@@ -420,7 +421,8 @@ static char *x11_verify(unsigned long peer_ip, int peer_port,
            if (data[i] != 0)          /* zero padding wrong */
                return "XDM-AUTHORIZATION-1 data failed check";
        tim = time(NULL);
-       if (abs(t - tim) > XDM_MAXSKEW)
+       if (((unsigned long)t - (unsigned long)tim
+             + XDM_MAXSKEW) > 2*XDM_MAXSKEW)
            return "XDM-AUTHORIZATION-1 time stamp was too far out";
        seen = snew(struct XDMSeen);
        seen->time = t;