aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authormarha <marha@users.sourceforge.net>2014-04-14 23:49:11 +0200
committermarha <marha@users.sourceforge.net>2014-04-14 23:49:11 +0200
commit3053928d3e9e12e59c46917113bad496487f1d28 (patch)
tree591c17cafc365399cc88774bba985af7276d957e /tools
parent7c21629fbeb51b65fd0625bb36d888587d62fd89 (diff)
parenta3fe3e22d85e8aa795df85c21814fc84cac42e99 (diff)
downloadvcxsrv-3053928d3e9e12e59c46917113bad496487f1d28.tar.gz
vcxsrv-3053928d3e9e12e59c46917113bad496487f1d28.tar.bz2
vcxsrv-3053928d3e9e12e59c46917113bad496487f1d28.zip
Merge remote-tracking branch 'origin/released'
Conflicts: tools/plink/misc.h tools/plink/ssh.c tools/plink/sshbn.c tools/plink/winplink.c
Diffstat (limited to 'tools')
-rw-r--r--tools/plink/cmdline.c1140
-rw-r--r--tools/plink/conf.c9
-rw-r--r--tools/plink/ldisc.c708
-rw-r--r--tools/plink/logging.c920
-rw-r--r--tools/plink/misc.c1564
-rwxr-xr-x[-rw-r--r--]tools/plink/misc.h279
-rw-r--r--tools/plink/network.h490
-rw-r--r--tools/plink/pinger.c144
-rw-r--r--tools/plink/portfwd.c1203
-rw-r--r--tools/plink/proxy.c3016
-rw-r--r--tools/plink/proxy.h249
-rw-r--r--tools/plink/putty.h2863
-rw-r--r--tools/plink/puttymem.h94
-rw-r--r--tools/plink/raw.c686
-rw-r--r--tools/plink/rlogin.c859
-rw-r--r--tools/plink/settings.c2153
-rwxr-xr-x[-rw-r--r--]tools/plink/ssh.c21304
-rw-r--r--tools/plink/ssh.h1416
-rw-r--r--tools/plink/sshaes.c2468
-rw-r--r--tools/plink/ssharcf.c246
-rwxr-xr-x[-rw-r--r--]tools/plink/sshbn.c3919
-rw-r--r--tools/plink/sshdes.c2119
-rw-r--r--tools/plink/sshdss.c1343
-rw-r--r--tools/plink/sshmd5.c684
-rw-r--r--tools/plink/sshpubk.c2444
-rw-r--r--tools/plink/sshrand.c579
-rw-r--r--tools/plink/sshrsa.c2191
-rw-r--r--tools/plink/sshsh256.c648
-rw-r--r--tools/plink/sshsha.c846
-rw-r--r--tools/plink/sshzlib.c2779
-rw-r--r--tools/plink/telnet.c2267
-rw-r--r--tools/plink/terminal.h655
-rw-r--r--tools/plink/timing.c454
-rw-r--r--tools/plink/tree234.c2965
-rw-r--r--tools/plink/wildcard.c945
-rw-r--r--tools/plink/wincons.c856
-rw-r--r--tools/plink/winhandl.c1303
-rw-r--r--tools/plink/winhelp.h376
-rw-r--r--tools/plink/winmisc.c990
-rw-r--r--tools/plink/winnet.c3582
-rw-r--r--tools/plink/winnoise.c283
-rw-r--r--tools/plink/winpgntc.c452
-rwxr-xr-x[-rw-r--r--]tools/plink/winplink.c1628
-rw-r--r--tools/plink/winproxy.c321
-rw-r--r--tools/plink/winstore.c1714
-rw-r--r--tools/plink/winstuff.h1114
-rw-r--r--tools/plink/x11fwd.c1884
47 files changed, 41768 insertions, 39384 deletions
diff --git a/tools/plink/cmdline.c b/tools/plink/cmdline.c
index 1a5e2cbb6..0c7ef4c91 100644
--- a/tools/plink/cmdline.c
+++ b/tools/plink/cmdline.c
@@ -1,570 +1,570 @@
-/*
- * cmdline.c - command-line parsing shared between many of the
- * PuTTY applications
- */
-
-#include <stdio.h>
-#include <assert.h>
-#include <stdlib.h>
-#include "putty.h"
-
-/*
- * Some command-line parameters need to be saved up until after
- * we've loaded the saved session which will form the basis of our
- * eventual running configuration. For this we use the macro
- * SAVEABLE, which notices if the `need_save' parameter is set and
- * saves the parameter and value on a list.
- *
- * We also assign priorities to saved parameters, just to slightly
- * ameliorate silly ordering problems. For example, if you specify
- * a saved session to load, it will be loaded _before_ all your
- * local modifications such as -L are evaluated; and if you specify
- * a protocol and a port, the protocol is set up first so that the
- * port can override its choice of port number.
- *
- * (In fact -load is not saved at all, since in at least Plink the
- * processing of further command-line options depends on whether or
- * not the loaded session contained a hostname. So it must be
- * executed immediately.)
- */
-
-#define NPRIORITIES 2
-
-struct cmdline_saved_param {
- char *p, *value;
-};
-struct cmdline_saved_param_set {
- struct cmdline_saved_param *params;
- int nsaved, savesize;
-};
-
-/*
- * C guarantees this structure will be initialised to all zero at
- * program start, which is exactly what we want.
- */
-static struct cmdline_saved_param_set saves[NPRIORITIES];
-
-static void cmdline_save_param(char *p, 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].nsaved++;
-}
-
-static char *cmdline_password = NULL;
-
-void cmdline_cleanup(void)
-{
- int pri;
-
- if (cmdline_password) {
- memset(cmdline_password, 0, strlen(cmdline_password));
- sfree(cmdline_password);
- cmdline_password = NULL;
- }
-
- for (pri = 0; pri < NPRIORITIES; pri++) {
- sfree(saves[pri].params);
- saves[pri].params = NULL;
- saves[pri].savesize = 0;
- saves[pri].nsaved = 0;
- }
-}
-
-#define SAVEABLE(pri) do { \
- if (need_save) { cmdline_save_param(p, value, pri); return ret; } \
-} while (0)
-
-/*
- * Similar interface to get_userpass_input(), except that here a -1
- * 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) {
-
- static int tried_once = 0;
-
- /*
- * We only handle prompts which don't echo (which we assume to be
- * passwords), and (currently) we only cope with a password prompt
- * that comes in a prompt-set on its own.
- */
- if (!cmdline_password || in || p->n_prompts != 1 || p->prompts[0]->echo) {
- return -1;
- }
-
- /*
- * If we've tried once, return utter failure (no more passwords left
- * to try).
- */
- if (tried_once)
- return 0;
-
- prompt_set_result(p->prompts[0], cmdline_password);
- memset(cmdline_password, 0, strlen(cmdline_password));
- sfree(cmdline_password);
- cmdline_password = NULL;
- tried_once = 1;
- return 1;
-}
-
-/*
- * Here we have a flags word which describes the capabilities of
- * the particular tool on whose behalf we're running. We will
- * refuse certain command-line options if a particular tool
- * inherently can't do anything sensible. For example, the file
- * transfer tools (psftp, pscp) can't do a great deal with protocol
- * selections (ever tried running scp over telnet?) or with port
- * forwarding (even if it wasn't a hideously bad idea, they don't
- * have the select() infrastructure to make them work).
- */
-int cmdline_tooltype = 0;
-
-static int cmdline_check_unavailable(int flag, char *p)
-{
- if (cmdline_tooltype & flag) {
- cmdline_error("option \"%s\" not available in this tool", p);
- return 1;
- }
- return 0;
-}
-
-#define UNAVAILABLE_IN(flag) do { \
- if (cmdline_check_unavailable(flag, p)) return ret; \
-} while (0)
-
-/*
- * Process a standard command-line parameter. `p' is the parameter
- * in question; `value' is the subsequent element of argv, which
- * may or may not be required as an operand to the parameter.
- * If `need_save' is 1, arguments which need to be saved as
- * described at this top of this file are, for later execution;
- * if 0, they are processed normally. (-1 is a special value used
- * by pterm to count arguments for a preliminary pass through the
- * argument list; it causes immediate return with an appropriate
- * value with no action taken.)
- * Return value is 2 if both arguments were used; 1 if only p was
- * used; 0 if the parameter wasn't one we recognised; -2 if it
- * should have been 2 but value was NULL.
- */
-
-#define RETURN(x) do { \
- if ((x) == 2 && !value) return -2; \
- ret = x; \
- if (need_save < 0) return x; \
-} while (0)
-
-int cmdline_process_param(char *p, char *value, int need_save, Conf *conf)
-{
- int ret = 0;
-
- if (!strcmp(p, "-load")) {
- RETURN(2);
- /* This parameter must be processed immediately rather than being
- * saved. */
- do_defaults(value, conf);
- loaded_session = TRUE;
- cmdline_session_name = dupstr(value);
- return 2;
- }
- if (!strcmp(p, "-ssh")) {
- RETURN(1);
- UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
- SAVEABLE(0);
- default_protocol = PROT_SSH;
- default_port = 22;
- conf_set_int(conf, CONF_protocol, default_protocol);
- conf_set_int(conf, CONF_port, default_port);
- return 1;
- }
- if (!strcmp(p, "-telnet")) {
- RETURN(1);
- UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
- SAVEABLE(0);
- default_protocol = PROT_TELNET;
- default_port = 23;
- conf_set_int(conf, CONF_protocol, default_protocol);
- conf_set_int(conf, CONF_port, default_port);
- return 1;
- }
- if (!strcmp(p, "-rlogin")) {
- RETURN(1);
- UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
- SAVEABLE(0);
- default_protocol = PROT_RLOGIN;
- default_port = 513;
- conf_set_int(conf, CONF_protocol, default_protocol);
- conf_set_int(conf, CONF_port, default_port);
- return 1;
- }
- if (!strcmp(p, "-raw")) {
- RETURN(1);
- UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
- SAVEABLE(0);
- default_protocol = PROT_RAW;
- conf_set_int(conf, CONF_protocol, default_protocol);
- }
- if (!strcmp(p, "-serial")) {
- RETURN(1);
- /* Serial is not NONNETWORK in an odd sense of the word */
- UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
- SAVEABLE(0);
- default_protocol = PROT_SERIAL;
- conf_set_int(conf, CONF_protocol, default_protocol);
- /* The host parameter will already be loaded into CONF_host,
- * so copy it across */
- conf_set_str(conf, CONF_serline, conf_get_str(conf, CONF_host));
- }
- if (!strcmp(p, "-v")) {
- RETURN(1);
- flags |= FLAG_VERBOSE;
- }
- if (!strcmp(p, "-l")) {
- RETURN(2);
- UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
- SAVEABLE(0);
- conf_set_str(conf, CONF_username, value);
- }
- if (!strcmp(p, "-loghost")) {
- RETURN(2);
- UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
- SAVEABLE(0);
- conf_set_str(conf, CONF_loghost, value);
- }
- if ((!strcmp(p, "-L") || !strcmp(p, "-R") || !strcmp(p, "-D"))) {
- char type, *q, *qq, *key, *val;
- RETURN(2);
- UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
- SAVEABLE(0);
- if (strcmp(p, "-D")) {
- /*
- * For -L or -R forwarding types:
- *
- * We expect _at least_ two colons in this string. The
- * possible formats are `sourceport:desthost:destport',
- * or `sourceip:sourceport:desthost:destport' if you're
- * specifying a particular loopback address. We need to
- * replace the one between source and dest with a \t;
- * this means we must find the second-to-last colon in
- * the string.
- *
- * (This looks like a foolish way of doing it given the
- * existence of strrchr, but it's more efficient than
- * two strrchrs - not to mention that the second strrchr
- * would require us to modify the input string!)
- */
-
- type = p[1]; /* 'L' or 'R' */
-
- q = qq = strchr(value, ':');
- while (qq) {
- char *qqq = strchr(qq+1, ':');
- if (qqq)
- q = qq;
- qq = qqq;
- }
-
- if (!q) {
- cmdline_error("-%c expects at least two colons in its"
- " argument", type);
- return ret;
- }
-
- key = dupprintf("%c%.*s", type, q - value, value);
- val = dupstr(q+1);
- } else {
- /*
- * Dynamic port forwardings are entered under the same key
- * as if they were local (because they occupy the same
- * port space - a local and a dynamic forwarding on the
- * same local port are mutually exclusive), with the
- * special value "D" (which can be distinguished from
- * anything in the ordinary -L case by containing no
- * colon).
- */
- key = dupprintf("L%s", value);
- val = dupstr("D");
- }
- conf_set_str_str(conf, CONF_portfwd, key, val);
- sfree(key);
- sfree(val);
- }
- if ((!strcmp(p, "-nc"))) {
- char *host, *portp;
-
- RETURN(2);
- UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
- SAVEABLE(0);
-
- portp = strchr(value, ':');
- if (!portp) {
- cmdline_error("-nc expects argument of form 'host:port'");
- return ret;
- }
-
- host = dupprintf("%.*s", portp - value, value);
- conf_set_str(conf, CONF_ssh_nc_host, host);
- conf_set_int(conf, CONF_ssh_nc_port, atoi(portp + 1));
- sfree(host);
- }
- if (!strcmp(p, "-m")) {
- char *filename, *command;
- int cmdlen, cmdsize;
- FILE *fp;
- int c, d;
-
- RETURN(2);
- UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
- SAVEABLE(0);
-
- filename = value;
-
- cmdlen = cmdsize = 0;
- command = NULL;
- fp = fopen(filename, "r");
- if (!fp) {
- cmdline_error("unable to open command file \"%s\"", filename);
- return ret;
- }
- do {
- c = fgetc(fp);
- d = c;
- if (c == EOF)
- d = 0;
- if (cmdlen >= cmdsize) {
- cmdsize = cmdlen + 512;
- command = sresize(command, cmdsize, char);
- }
- command[cmdlen++] = d;
- } while (c != EOF);
- fclose(fp);
- conf_set_str(conf, CONF_remote_cmd, command);
- conf_set_str(conf, CONF_remote_cmd2, "");
- conf_set_int(conf, CONF_nopty, TRUE); /* command => no terminal */
- sfree(command);
- }
- if (!strcmp(p, "-P")) {
- RETURN(2);
- UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
- SAVEABLE(1); /* lower priority than -ssh,-telnet */
- conf_set_int(conf, CONF_port, atoi(value));
- }
- if (!strcmp(p, "-pw")) {
- RETURN(2);
- UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
- SAVEABLE(1);
- /* We delay evaluating this until after the protocol is decided,
- * so that we can warn if it's of no use with the selected protocol */
- if (conf_get_int(conf, CONF_protocol) != PROT_SSH)
- cmdline_error("the -pw option can only be used with the "
- "SSH protocol");
- else {
- cmdline_password = dupstr(value);
- /* Assuming that `value' is directly from argv, make a good faith
- * attempt to trample it, to stop it showing up in `ps' output
- * on Unix-like systems. Not guaranteed, of course. */
- memset(value, 0, strlen(value));
- }
- }
-
- if (!strcmp(p, "-agent") || !strcmp(p, "-pagent") ||
- !strcmp(p, "-pageant")) {
- RETURN(1);
- UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
- SAVEABLE(0);
- conf_set_int(conf, CONF_tryagent, TRUE);
- }
- if (!strcmp(p, "-noagent") || !strcmp(p, "-nopagent") ||
- !strcmp(p, "-nopageant")) {
- RETURN(1);
- UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
- SAVEABLE(0);
- conf_set_int(conf, CONF_tryagent, FALSE);
- }
-
- if (!strcmp(p, "-A")) {
- RETURN(1);
- UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
- SAVEABLE(0);
- conf_set_int(conf, CONF_agentfwd, 1);
- }
- if (!strcmp(p, "-a")) {
- RETURN(1);
- UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
- SAVEABLE(0);
- conf_set_int(conf, CONF_agentfwd, 0);
- }
-
- if (!strcmp(p, "-X")) {
- RETURN(1);
- UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
- SAVEABLE(0);
- conf_set_int(conf, CONF_x11_forward, 1);
- }
- if (!strcmp(p, "-x")) {
- RETURN(1);
- UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
- SAVEABLE(0);
- conf_set_int(conf, CONF_x11_forward, 0);
- }
-
- if (!strcmp(p, "-t")) {
- RETURN(1);
- UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
- SAVEABLE(1); /* lower priority than -m */
- conf_set_int(conf, CONF_nopty, 0);
- }
- if (!strcmp(p, "-T")) {
- RETURN(1);
- UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
- SAVEABLE(1);
- conf_set_int(conf, CONF_nopty, 1);
- }
-
- if (!strcmp(p, "-N")) {
- RETURN(1);
- UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
- SAVEABLE(0);
- conf_set_int(conf, CONF_ssh_no_shell, 1);
- }
-
- if (!strcmp(p, "-C")) {
- RETURN(1);
- UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
- SAVEABLE(0);
- conf_set_int(conf, CONF_compression, 1);
- }
-
- if (!strcmp(p, "-1")) {
- RETURN(1);
- UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
- SAVEABLE(0);
- conf_set_int(conf, CONF_sshprot, 0); /* ssh protocol 1 only */
- }
- if (!strcmp(p, "-2")) {
- RETURN(1);
- UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
- SAVEABLE(0);
- conf_set_int(conf, CONF_sshprot, 3); /* ssh protocol 2 only */
- }
-
- if (!strcmp(p, "-i")) {
- Filename *fn;
- RETURN(2);
- UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
- SAVEABLE(0);
- fn = filename_from_str(value);
- conf_set_filename(conf, CONF_keyfile, fn);
- filename_free(fn);
- }
-
- if (!strcmp(p, "-4") || !strcmp(p, "-ipv4")) {
- RETURN(1);
- SAVEABLE(1);
- conf_set_int(conf, CONF_addressfamily, ADDRTYPE_IPV4);
- }
- if (!strcmp(p, "-6") || !strcmp(p, "-ipv6")) {
- RETURN(1);
- SAVEABLE(1);
- conf_set_int(conf, CONF_addressfamily, ADDRTYPE_IPV6);
- }
- if (!strcmp(p, "-sercfg")) {
- char* nextitem;
- RETURN(2);
- UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
- SAVEABLE(1);
- if (conf_get_int(conf, CONF_protocol) != PROT_SERIAL)
- cmdline_error("the -sercfg option can only be used with the "
- "serial protocol");
- /* Value[0] contains one or more , separated values, like 19200,8,n,1,X */
- nextitem = value;
- while (nextitem[0] != '\0') {
- int length, skip;
- char *end = strchr(nextitem, ',');
- if (!end) {
- length = strlen(nextitem);
- skip = 0;
- } else {
- length = end - nextitem;
- nextitem[length] = '\0';
- skip = 1;
- }
- if (length == 1) {
- switch (*nextitem) {
- case '1':
- case '2':
- conf_set_int(conf, CONF_serstopbits, 2 * (*nextitem-'0'));
- break;
-
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- conf_set_int(conf, CONF_serdatabits, *nextitem-'0');
- break;
-
- case 'n':
- conf_set_int(conf, CONF_serparity, SER_PAR_NONE);
- break;
- case 'o':
- conf_set_int(conf, CONF_serparity, SER_PAR_ODD);
- break;
- case 'e':
- conf_set_int(conf, CONF_serparity, SER_PAR_EVEN);
- break;
- case 'm':
- conf_set_int(conf, CONF_serparity, SER_PAR_MARK);
- break;
- case 's':
- conf_set_int(conf, CONF_serparity, SER_PAR_SPACE);
- break;
-
- case 'N':
- conf_set_int(conf, CONF_serflow, SER_FLOW_NONE);
- break;
- case 'X':
- conf_set_int(conf, CONF_serflow, SER_FLOW_XONXOFF);
- break;
- case 'R':
- conf_set_int(conf, CONF_serflow, SER_FLOW_RTSCTS);
- break;
- case 'D':
- conf_set_int(conf, CONF_serflow, SER_FLOW_DSRDTR);
- break;
-
- default:
- cmdline_error("Unrecognised suboption \"-sercfg %c\"",
- *nextitem);
- }
- } else if (length == 3 && !strncmp(nextitem,"1.5",3)) {
- /* Messy special case */
- conf_set_int(conf, CONF_serstopbits, 3);
- } else {
- int serspeed = atoi(nextitem);
- if (serspeed != 0) {
- conf_set_int(conf, CONF_serspeed, serspeed);
- } else {
- cmdline_error("Unrecognised suboption \"-sercfg %s\"",
- nextitem);
- }
- }
- nextitem += length + skip;
- }
- }
- return ret; /* unrecognised */
-}
-
-void cmdline_run_saved(Conf *conf)
-{
- int pri, 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);
-}
+/*
+ * cmdline.c - command-line parsing shared between many of the
+ * PuTTY applications
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <stdlib.h>
+#include "putty.h"
+
+/*
+ * Some command-line parameters need to be saved up until after
+ * we've loaded the saved session which will form the basis of our
+ * eventual running configuration. For this we use the macro
+ * SAVEABLE, which notices if the `need_save' parameter is set and
+ * saves the parameter and value on a list.
+ *
+ * We also assign priorities to saved parameters, just to slightly
+ * ameliorate silly ordering problems. For example, if you specify
+ * a saved session to load, it will be loaded _before_ all your
+ * local modifications such as -L are evaluated; and if you specify
+ * a protocol and a port, the protocol is set up first so that the
+ * port can override its choice of port number.
+ *
+ * (In fact -load is not saved at all, since in at least Plink the
+ * processing of further command-line options depends on whether or
+ * not the loaded session contained a hostname. So it must be
+ * executed immediately.)
+ */
+
+#define NPRIORITIES 2
+
+struct cmdline_saved_param {
+ char *p, *value;
+};
+struct cmdline_saved_param_set {
+ struct cmdline_saved_param *params;
+ int nsaved, savesize;
+};
+
+/*
+ * C guarantees this structure will be initialised to all zero at
+ * program start, which is exactly what we want.
+ */
+static struct cmdline_saved_param_set saves[NPRIORITIES];
+
+static void cmdline_save_param(char *p, 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].nsaved++;
+}
+
+static char *cmdline_password = NULL;
+
+void cmdline_cleanup(void)
+{
+ int pri;
+
+ if (cmdline_password) {
+ smemclr(cmdline_password, strlen(cmdline_password));
+ sfree(cmdline_password);
+ cmdline_password = NULL;
+ }
+
+ for (pri = 0; pri < NPRIORITIES; pri++) {
+ sfree(saves[pri].params);
+ saves[pri].params = NULL;
+ saves[pri].savesize = 0;
+ saves[pri].nsaved = 0;
+ }
+}
+
+#define SAVEABLE(pri) do { \
+ if (need_save) { cmdline_save_param(p, value, pri); return ret; } \
+} while (0)
+
+/*
+ * Similar interface to get_userpass_input(), except that here a -1
+ * 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) {
+
+ static int tried_once = 0;
+
+ /*
+ * We only handle prompts which don't echo (which we assume to be
+ * passwords), and (currently) we only cope with a password prompt
+ * that comes in a prompt-set on its own.
+ */
+ if (!cmdline_password || in || p->n_prompts != 1 || p->prompts[0]->echo) {
+ return -1;
+ }
+
+ /*
+ * If we've tried once, return utter failure (no more passwords left
+ * to try).
+ */
+ if (tried_once)
+ return 0;
+
+ prompt_set_result(p->prompts[0], cmdline_password);
+ smemclr(cmdline_password, strlen(cmdline_password));
+ sfree(cmdline_password);
+ cmdline_password = NULL;
+ tried_once = 1;
+ return 1;
+}
+
+/*
+ * Here we have a flags word which describes the capabilities of
+ * the particular tool on whose behalf we're running. We will
+ * refuse certain command-line options if a particular tool
+ * inherently can't do anything sensible. For example, the file
+ * transfer tools (psftp, pscp) can't do a great deal with protocol
+ * selections (ever tried running scp over telnet?) or with port
+ * forwarding (even if it wasn't a hideously bad idea, they don't
+ * have the select() infrastructure to make them work).
+ */
+int cmdline_tooltype = 0;
+
+static int cmdline_check_unavailable(int flag, char *p)
+{
+ if (cmdline_tooltype & flag) {
+ cmdline_error("option \"%s\" not available in this tool", p);
+ return 1;
+ }
+ return 0;
+}
+
+#define UNAVAILABLE_IN(flag) do { \
+ if (cmdline_check_unavailable(flag, p)) return ret; \
+} while (0)
+
+/*
+ * Process a standard command-line parameter. `p' is the parameter
+ * in question; `value' is the subsequent element of argv, which
+ * may or may not be required as an operand to the parameter.
+ * If `need_save' is 1, arguments which need to be saved as
+ * described at this top of this file are, for later execution;
+ * if 0, they are processed normally. (-1 is a special value used
+ * by pterm to count arguments for a preliminary pass through the
+ * argument list; it causes immediate return with an appropriate
+ * value with no action taken.)
+ * Return value is 2 if both arguments were used; 1 if only p was
+ * used; 0 if the parameter wasn't one we recognised; -2 if it
+ * should have been 2 but value was NULL.
+ */
+
+#define RETURN(x) do { \
+ if ((x) == 2 && !value) return -2; \
+ ret = x; \
+ if (need_save < 0) return x; \
+} while (0)
+
+int cmdline_process_param(char *p, char *value, int need_save, Conf *conf)
+{
+ int ret = 0;
+
+ if (!strcmp(p, "-load")) {
+ RETURN(2);
+ /* This parameter must be processed immediately rather than being
+ * saved. */
+ do_defaults(value, conf);
+ loaded_session = TRUE;
+ cmdline_session_name = dupstr(value);
+ return 2;
+ }
+ if (!strcmp(p, "-ssh")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ default_protocol = PROT_SSH;
+ default_port = 22;
+ conf_set_int(conf, CONF_protocol, default_protocol);
+ conf_set_int(conf, CONF_port, default_port);
+ return 1;
+ }
+ if (!strcmp(p, "-telnet")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ default_protocol = PROT_TELNET;
+ default_port = 23;
+ conf_set_int(conf, CONF_protocol, default_protocol);
+ conf_set_int(conf, CONF_port, default_port);
+ return 1;
+ }
+ if (!strcmp(p, "-rlogin")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ default_protocol = PROT_RLOGIN;
+ default_port = 513;
+ conf_set_int(conf, CONF_protocol, default_protocol);
+ conf_set_int(conf, CONF_port, default_port);
+ return 1;
+ }
+ if (!strcmp(p, "-raw")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ default_protocol = PROT_RAW;
+ conf_set_int(conf, CONF_protocol, default_protocol);
+ }
+ if (!strcmp(p, "-serial")) {
+ RETURN(1);
+ /* Serial is not NONNETWORK in an odd sense of the word */
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ default_protocol = PROT_SERIAL;
+ conf_set_int(conf, CONF_protocol, default_protocol);
+ /* The host parameter will already be loaded into CONF_host,
+ * so copy it across */
+ conf_set_str(conf, CONF_serline, conf_get_str(conf, CONF_host));
+ }
+ if (!strcmp(p, "-v")) {
+ RETURN(1);
+ flags |= FLAG_VERBOSE;
+ }
+ if (!strcmp(p, "-l")) {
+ RETURN(2);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ conf_set_str(conf, CONF_username, value);
+ }
+ if (!strcmp(p, "-loghost")) {
+ RETURN(2);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ conf_set_str(conf, CONF_loghost, value);
+ }
+ if ((!strcmp(p, "-L") || !strcmp(p, "-R") || !strcmp(p, "-D"))) {
+ char type, *q, *qq, *key, *val;
+ RETURN(2);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ if (strcmp(p, "-D")) {
+ /*
+ * For -L or -R forwarding types:
+ *
+ * We expect _at least_ two colons in this string. The
+ * possible formats are `sourceport:desthost:destport',
+ * or `sourceip:sourceport:desthost:destport' if you're
+ * specifying a particular loopback address. We need to
+ * replace the one between source and dest with a \t;
+ * this means we must find the second-to-last colon in
+ * the string.
+ *
+ * (This looks like a foolish way of doing it given the
+ * existence of strrchr, but it's more efficient than
+ * two strrchrs - not to mention that the second strrchr
+ * would require us to modify the input string!)
+ */
+
+ type = p[1]; /* 'L' or 'R' */
+
+ q = qq = host_strchr(value, ':');
+ while (qq) {
+ char *qqq = host_strchr(qq+1, ':');
+ if (qqq)
+ q = qq;
+ qq = qqq;
+ }
+
+ if (!q) {
+ cmdline_error("-%c expects at least two colons in its"
+ " argument", type);
+ return ret;
+ }
+
+ key = dupprintf("%c%.*s", type, (int)(q - value), value);
+ val = dupstr(q+1);
+ } else {
+ /*
+ * Dynamic port forwardings are entered under the same key
+ * as if they were local (because they occupy the same
+ * port space - a local and a dynamic forwarding on the
+ * same local port are mutually exclusive), with the
+ * special value "D" (which can be distinguished from
+ * anything in the ordinary -L case by containing no
+ * colon).
+ */
+ key = dupprintf("L%s", value);
+ val = dupstr("D");
+ }
+ conf_set_str_str(conf, CONF_portfwd, key, val);
+ sfree(key);
+ sfree(val);
+ }
+ if ((!strcmp(p, "-nc"))) {
+ char *host, *portp;
+
+ RETURN(2);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+
+ portp = host_strchr(value, ':');
+ if (!portp) {
+ cmdline_error("-nc expects argument of form 'host:port'");
+ return ret;
+ }
+
+ host = dupprintf("%.*s", (int)(portp - value), value);
+ conf_set_str(conf, CONF_ssh_nc_host, host);
+ conf_set_int(conf, CONF_ssh_nc_port, atoi(portp + 1));
+ sfree(host);
+ }
+ if (!strcmp(p, "-m")) {
+ char *filename, *command;
+ int cmdlen, cmdsize;
+ FILE *fp;
+ int c, d;
+
+ RETURN(2);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+
+ filename = value;
+
+ cmdlen = cmdsize = 0;
+ command = NULL;
+ fp = fopen(filename, "r");
+ if (!fp) {
+ cmdline_error("unable to open command file \"%s\"", filename);
+ return ret;
+ }
+ do {
+ c = fgetc(fp);
+ d = c;
+ if (c == EOF)
+ d = 0;
+ if (cmdlen >= cmdsize) {
+ cmdsize = cmdlen + 512;
+ command = sresize(command, cmdsize, char);
+ }
+ command[cmdlen++] = d;
+ } while (c != EOF);
+ fclose(fp);
+ conf_set_str(conf, CONF_remote_cmd, command);
+ conf_set_str(conf, CONF_remote_cmd2, "");
+ conf_set_int(conf, CONF_nopty, TRUE); /* command => no terminal */
+ sfree(command);
+ }
+ if (!strcmp(p, "-P")) {
+ RETURN(2);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
+ SAVEABLE(1); /* lower priority than -ssh,-telnet */
+ conf_set_int(conf, CONF_port, atoi(value));
+ }
+ if (!strcmp(p, "-pw")) {
+ RETURN(2);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
+ SAVEABLE(1);
+ /* We delay evaluating this until after the protocol is decided,
+ * so that we can warn if it's of no use with the selected protocol */
+ if (conf_get_int(conf, CONF_protocol) != PROT_SSH)
+ cmdline_error("the -pw option can only be used with the "
+ "SSH protocol");
+ else {
+ cmdline_password = dupstr(value);
+ /* Assuming that `value' is directly from argv, make a good faith
+ * attempt to trample it, to stop it showing up in `ps' output
+ * on Unix-like systems. Not guaranteed, of course. */
+ smemclr(value, strlen(value));
+ }
+ }
+
+ if (!strcmp(p, "-agent") || !strcmp(p, "-pagent") ||
+ !strcmp(p, "-pageant")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ conf_set_int(conf, CONF_tryagent, TRUE);
+ }
+ if (!strcmp(p, "-noagent") || !strcmp(p, "-nopagent") ||
+ !strcmp(p, "-nopageant")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ conf_set_int(conf, CONF_tryagent, FALSE);
+ }
+
+ if (!strcmp(p, "-A")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ conf_set_int(conf, CONF_agentfwd, 1);
+ }
+ if (!strcmp(p, "-a")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ conf_set_int(conf, CONF_agentfwd, 0);
+ }
+
+ if (!strcmp(p, "-X")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ conf_set_int(conf, CONF_x11_forward, 1);
+ }
+ if (!strcmp(p, "-x")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ conf_set_int(conf, CONF_x11_forward, 0);
+ }
+
+ if (!strcmp(p, "-t")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(1); /* lower priority than -m */
+ conf_set_int(conf, CONF_nopty, 0);
+ }
+ if (!strcmp(p, "-T")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(1);
+ conf_set_int(conf, CONF_nopty, 1);
+ }
+
+ if (!strcmp(p, "-N")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ conf_set_int(conf, CONF_ssh_no_shell, 1);
+ }
+
+ if (!strcmp(p, "-C")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ conf_set_int(conf, CONF_compression, 1);
+ }
+
+ if (!strcmp(p, "-1")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ conf_set_int(conf, CONF_sshprot, 0); /* ssh protocol 1 only */
+ }
+ if (!strcmp(p, "-2")) {
+ RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ conf_set_int(conf, CONF_sshprot, 3); /* ssh protocol 2 only */
+ }
+
+ if (!strcmp(p, "-i")) {
+ Filename *fn;
+ RETURN(2);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ fn = filename_from_str(value);
+ conf_set_filename(conf, CONF_keyfile, fn);
+ filename_free(fn);
+ }
+
+ if (!strcmp(p, "-4") || !strcmp(p, "-ipv4")) {
+ RETURN(1);
+ SAVEABLE(1);
+ conf_set_int(conf, CONF_addressfamily, ADDRTYPE_IPV4);
+ }
+ if (!strcmp(p, "-6") || !strcmp(p, "-ipv6")) {
+ RETURN(1);
+ SAVEABLE(1);
+ conf_set_int(conf, CONF_addressfamily, ADDRTYPE_IPV6);
+ }
+ if (!strcmp(p, "-sercfg")) {
+ char* nextitem;
+ RETURN(2);
+ UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);
+ SAVEABLE(1);
+ if (conf_get_int(conf, CONF_protocol) != PROT_SERIAL)
+ cmdline_error("the -sercfg option can only be used with the "
+ "serial protocol");
+ /* Value[0] contains one or more , separated values, like 19200,8,n,1,X */
+ nextitem = value;
+ while (nextitem[0] != '\0') {
+ int length, skip;
+ char *end = strchr(nextitem, ',');
+ if (!end) {
+ length = strlen(nextitem);
+ skip = 0;
+ } else {
+ length = end - nextitem;
+ nextitem[length] = '\0';
+ skip = 1;
+ }
+ if (length == 1) {
+ switch (*nextitem) {
+ case '1':
+ case '2':
+ conf_set_int(conf, CONF_serstopbits, 2 * (*nextitem-'0'));
+ break;
+
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ conf_set_int(conf, CONF_serdatabits, *nextitem-'0');
+ break;
+
+ case 'n':
+ conf_set_int(conf, CONF_serparity, SER_PAR_NONE);
+ break;
+ case 'o':
+ conf_set_int(conf, CONF_serparity, SER_PAR_ODD);
+ break;
+ case 'e':
+ conf_set_int(conf, CONF_serparity, SER_PAR_EVEN);
+ break;
+ case 'm':
+ conf_set_int(conf, CONF_serparity, SER_PAR_MARK);
+ break;
+ case 's':
+ conf_set_int(conf, CONF_serparity, SER_PAR_SPACE);
+ break;
+
+ case 'N':
+ conf_set_int(conf, CONF_serflow, SER_FLOW_NONE);
+ break;
+ case 'X':
+ conf_set_int(conf, CONF_serflow, SER_FLOW_XONXOFF);
+ break;
+ case 'R':
+ conf_set_int(conf, CONF_serflow, SER_FLOW_RTSCTS);
+ break;
+ case 'D':
+ conf_set_int(conf, CONF_serflow, SER_FLOW_DSRDTR);
+ break;
+
+ default:
+ cmdline_error("Unrecognised suboption \"-sercfg %c\"",
+ *nextitem);
+ }
+ } else if (length == 3 && !strncmp(nextitem,"1.5",3)) {
+ /* Messy special case */
+ conf_set_int(conf, CONF_serstopbits, 3);
+ } else {
+ int serspeed = atoi(nextitem);
+ if (serspeed != 0) {
+ conf_set_int(conf, CONF_serspeed, serspeed);
+ } else {
+ cmdline_error("Unrecognised suboption \"-sercfg %s\"",
+ nextitem);
+ }
+ }
+ nextitem += length + skip;
+ }
+ }
+ return ret; /* unrecognised */
+}
+
+void cmdline_run_saved(Conf *conf)
+{
+ int pri, 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);
+}
diff --git a/tools/plink/conf.c b/tools/plink/conf.c
index 7b6a0137a..e80f5853a 100644
--- a/tools/plink/conf.c
+++ b/tools/plink/conf.c
@@ -522,14 +522,15 @@ int conf_deserialise(Conf *conf, void *vdata, int maxsize)
unsigned char *data = (unsigned char *)vdata;
unsigned char *start = data;
struct conf_entry *entry;
- int primary, used;
+ unsigned primary;
+ int used;
unsigned char *zero;
while (maxsize >= 4) {
primary = GET_32BIT_MSB_FIRST(data);
data += 4, maxsize -= 4;
- if ((unsigned)primary >= N_CONFIG_OPTIONS)
+ if (primary >= N_CONFIG_OPTIONS)
break;
entry = snew(struct conf_entry);
@@ -541,7 +542,7 @@ int conf_deserialise(Conf *conf, void *vdata, int maxsize)
sfree(entry);
goto done;
}
- entry->key.secondary.i = GET_32BIT_MSB_FIRST(data);
+ entry->key.secondary.i = toint(GET_32BIT_MSB_FIRST(data));
data += 4, maxsize -= 4;
break;
case TYPE_STR:
@@ -564,7 +565,7 @@ int conf_deserialise(Conf *conf, void *vdata, int maxsize)
sfree(entry);
goto done;
}
- entry->value.u.intval = GET_32BIT_MSB_FIRST(data);
+ entry->value.u.intval = toint(GET_32BIT_MSB_FIRST(data));
data += 4, maxsize -= 4;
break;
case TYPE_STR:
diff --git a/tools/plink/ldisc.c b/tools/plink/ldisc.c
index 7ed42a37c..8f653a18f 100644
--- a/tools/plink/ldisc.c
+++ b/tools/plink/ldisc.c
@@ -1,348 +1,360 @@
-/*
- * ldisc.c: PuTTY line discipline. Sits between the input coming
- * from keypresses in the window, and the output channel leading to
- * the back end. Implements echo and/or local line editing,
- * depending on what's currently configured.
- */
-
-#include <stdio.h>
-#include <ctype.h>
-
-#include "putty.h"
-#include "terminal.h"
-#include "ldisc.h"
-
-#define ECHOING (ldisc->localecho == FORCE_ON || \
- (ldisc->localecho == AUTO && \
- (ldisc->back->ldisc(ldisc->backhandle, LD_ECHO) || \
- term_ldisc(ldisc->term, LD_ECHO))))
-#define EDITING (ldisc->localedit == FORCE_ON || \
- (ldisc->localedit == AUTO && \
- (ldisc->back->ldisc(ldisc->backhandle, LD_EDIT) || \
- term_ldisc(ldisc->term, LD_EDIT))))
-
-static void c_write(Ldisc ldisc, char *buf, int len)
-{
- from_backend(ldisc->frontend, 0, buf, len);
-}
-
-static int plen(Ldisc ldisc, unsigned char c)
-{
- if ((c >= 32 && c <= 126) || (c >= 160 && !in_utf(ldisc->term)))
- return 1;
- else if (c < 128)
- return 2; /* ^x for some x */
- else if (in_utf(ldisc->term) && c >= 0xC0)
- return 1; /* UTF-8 introducer character
- * (FIXME: combining / wide chars) */
- else if (in_utf(ldisc->term) && c >= 0x80 && c < 0xC0)
- return 0; /* UTF-8 followup character */
- else
- return 4; /* <XY> hex representation */
-}
-
-static void pwrite(Ldisc ldisc, unsigned char c)
-{
- if ((c >= 32 && c <= 126) ||
- (!in_utf(ldisc->term) && c >= 0xA0) ||
- (in_utf(ldisc->term) && c >= 0x80)) {
- c_write(ldisc, (char *)&c, 1);
- } else if (c < 128) {
- char cc[2];
- cc[1] = (c == 127 ? '?' : c + 0x40);
- cc[0] = '^';
- c_write(ldisc, cc, 2);
- } else {
- char cc[5];
- sprintf(cc, "<%02X>", c);
- c_write(ldisc, cc, 4);
- }
-}
-
-static int char_start(Ldisc ldisc, unsigned char c)
-{
- if (in_utf(ldisc->term))
- return (c < 0x80 || c >= 0xC0);
- else
- return 1;
-}
-
-static void bsb(Ldisc ldisc, int n)
-{
- while (n--)
- c_write(ldisc, "\010 \010", 3);
-}
-
-#define CTRL(x) (x^'@')
-#define KCTRL(x) ((x^'@') | 0x100)
-
-void *ldisc_create(Conf *conf, Terminal *term,
- Backend *back, void *backhandle,
- void *frontend)
-{
- Ldisc ldisc = snew(struct ldisc_tag);
-
- ldisc->buf = NULL;
- ldisc->buflen = 0;
- ldisc->bufsiz = 0;
- ldisc->quotenext = 0;
-
- ldisc->back = back;
- ldisc->backhandle = backhandle;
- ldisc->term = term;
- ldisc->frontend = frontend;
-
- ldisc_configure(ldisc, conf);
-
- /* Link ourselves into the backend and the terminal */
- if (term)
- term->ldisc = ldisc;
- if (back)
- back->provide_ldisc(backhandle, ldisc);
-
- return ldisc;
-}
-
-void ldisc_configure(void *handle, Conf *conf)
-{
- Ldisc ldisc = (Ldisc) handle;
-
- ldisc->telnet_keyboard = conf_get_int(conf, CONF_telnet_keyboard);
- ldisc->telnet_newline = conf_get_int(conf, CONF_telnet_newline);
- ldisc->protocol = conf_get_int(conf, CONF_protocol);
- ldisc->localecho = conf_get_int(conf, CONF_localecho);
- ldisc->localedit = conf_get_int(conf, CONF_localedit);
-}
-
-void ldisc_free(void *handle)
-{
- Ldisc ldisc = (Ldisc) handle;
-
- if (ldisc->term)
- ldisc->term->ldisc = NULL;
- if (ldisc->back)
- ldisc->back->provide_ldisc(ldisc->backhandle, NULL);
- if (ldisc->buf)
- sfree(ldisc->buf);
- sfree(ldisc);
-}
-
-void ldisc_send(void *handle, 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;
- }
- /*
- * Notify the front end that something was pressed, in case
- * it's depending on finding out (e.g. keypress termination for
- * Close On Exit).
- */
- frontend_keypress(ldisc->frontend);
-
- /*
- * Less than zero means null terminated special string.
- */
- if (len < 0) {
- len = strlen(buf);
- keyflag = KCTRL('@');
- }
- /*
- * Either perform local editing, or just send characters.
- */
- if (EDITING) {
- while (len--) {
- int c;
- c = (unsigned char)(*buf++) + keyflag;
- if (!interactive && c == '\r')
- c += KCTRL('@');
- switch (ldisc->quotenext ? ' ' : c) {
- /*
- * ^h/^?: delete, and output BSBs, to return to
- * last character boundary (in UTF-8 mode this may
- * be more than one byte)
- * ^w: delete, and output BSBs, to return to last
- * space/nonspace boundary
- * ^u: delete, and output BSBs, to return to BOL
- * ^c: Do a ^u then send a telnet IP
- * ^z: Do a ^u then send a telnet SUSP
- * ^\: Do a ^u then send a telnet ABORT
- * ^r: echo "^R\n" and redraw line
- * ^v: quote next char
- * ^d: if at BOL, end of file and close connection,
- * else send line and reset to BOL
- * ^m: send line-plus-\r\n and reset to BOL
- */
- case KCTRL('H'):
- case KCTRL('?'): /* backspace/delete */
- if (ldisc->buflen > 0) {
- do {
- if (ECHOING)
- bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
- ldisc->buflen--;
- } while (!char_start(ldisc, ldisc->buf[ldisc->buflen]));
- }
- break;
- case CTRL('W'): /* delete word */
- while (ldisc->buflen > 0) {
- if (ECHOING)
- bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
- ldisc->buflen--;
- if (ldisc->buflen > 0 &&
- isspace((unsigned char)ldisc->buf[ldisc->buflen-1]) &&
- !isspace((unsigned char)ldisc->buf[ldisc->buflen]))
- break;
- }
- break;
- case CTRL('U'): /* delete line */
- case CTRL('C'): /* Send IP */
- case CTRL('\\'): /* Quit */
- case CTRL('Z'): /* Suspend */
- while (ldisc->buflen > 0) {
- if (ECHOING)
- bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
- ldisc->buflen--;
- }
- ldisc->back->special(ldisc->backhandle, TS_EL);
- /*
- * We don't send IP, SUSP or ABORT if the user has
- * configured telnet specials off! This breaks
- * talkers otherwise.
- */
- if (!ldisc->telnet_keyboard)
- goto default_case;
- if (c == CTRL('C'))
- ldisc->back->special(ldisc->backhandle, TS_IP);
- if (c == CTRL('Z'))
- ldisc->back->special(ldisc->backhandle, TS_SUSP);
- if (c == CTRL('\\'))
- ldisc->back->special(ldisc->backhandle, TS_ABORT);
- break;
- case CTRL('R'): /* redraw line */
- if (ECHOING) {
- int i;
- c_write(ldisc, "^R\r\n", 4);
- for (i = 0; i < ldisc->buflen; i++)
- pwrite(ldisc, ldisc->buf[i]);
- }
- break;
- case CTRL('V'): /* quote next char */
- ldisc->quotenext = TRUE;
- break;
- case CTRL('D'): /* logout or send */
- if (ldisc->buflen == 0) {
- ldisc->back->special(ldisc->backhandle, TS_EOF);
- } else {
- ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen);
- ldisc->buflen = 0;
- }
- break;
- /*
- * This particularly hideous bit of code from RDB
- * allows ordinary ^M^J to do the same thing as
- * magic-^M when in Raw protocol. The line `case
- * KCTRL('M'):' is _inside_ the if block. Thus:
- *
- * - receiving regular ^M goes straight to the
- * default clause and inserts as a literal ^M.
- * - receiving regular ^J _not_ directly after a
- * literal ^M (or not in Raw protocol) fails the
- * if condition, leaps to the bottom of the if,
- * and falls through into the default clause
- * again.
- * - receiving regular ^J just after a literal ^M
- * in Raw protocol passes the if condition,
- * deletes the literal ^M, and falls through
- * into the magic-^M code
- * - receiving a magic-^M empties the line buffer,
- * signals end-of-line in one of the various
- * entertaining ways, and _doesn't_ fall out of
- * the bottom of the if and through to the
- * default clause because of the break.
- */
- case CTRL('J'):
- if (ldisc->protocol == PROT_RAW &&
- ldisc->buflen > 0 && ldisc->buf[ldisc->buflen - 1] == '\r') {
- if (ECHOING)
- bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
- ldisc->buflen--;
- /* FALLTHROUGH */
- case KCTRL('M'): /* send with newline */
- if (ldisc->buflen > 0)
- ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen);
- if (ldisc->protocol == PROT_RAW)
- ldisc->back->send(ldisc->backhandle, "\r\n", 2);
- else if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline)
- ldisc->back->special(ldisc->backhandle, TS_EOL);
- else
- ldisc->back->send(ldisc->backhandle, "\r", 1);
- if (ECHOING)
- c_write(ldisc, "\r\n", 2);
- ldisc->buflen = 0;
- break;
- }
- /* FALLTHROUGH */
- default: /* get to this label from ^V handler */
- default_case:
- if (ldisc->buflen >= ldisc->bufsiz) {
- ldisc->bufsiz = ldisc->buflen + 256;
- ldisc->buf = sresize(ldisc->buf, ldisc->bufsiz, char);
- }
- ldisc->buf[ldisc->buflen++] = c;
- if (ECHOING)
- pwrite(ldisc, (unsigned char) c);
- ldisc->quotenext = FALSE;
- break;
- }
- }
- } else {
- if (ldisc->buflen != 0) {
- ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen);
- while (ldisc->buflen > 0) {
- bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
- ldisc->buflen--;
- }
- }
- if (len > 0) {
- if (ECHOING)
- c_write(ldisc, buf, len);
- if (keyflag && ldisc->protocol == PROT_TELNET && len == 1) {
- switch (buf[0]) {
- case CTRL('M'):
- if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline)
- ldisc->back->special(ldisc->backhandle, TS_EOL);
- else
- ldisc->back->send(ldisc->backhandle, "\r", 1);
- break;
- case CTRL('?'):
- case CTRL('H'):
- if (ldisc->telnet_keyboard) {
- ldisc->back->special(ldisc->backhandle, TS_EC);
- break;
- }
- case CTRL('C'):
- if (ldisc->telnet_keyboard) {
- ldisc->back->special(ldisc->backhandle, TS_IP);
- break;
- }
- case CTRL('Z'):
- if (ldisc->telnet_keyboard) {
- ldisc->back->special(ldisc->backhandle, TS_SUSP);
- break;
- }
-
- default:
- ldisc->back->send(ldisc->backhandle, buf, len);
- break;
- }
- } else
- ldisc->back->send(ldisc->backhandle, buf, len);
- }
- }
-}
+/*
+ * ldisc.c: PuTTY line discipline. Sits between the input coming
+ * from keypresses in the window, and the output channel leading to
+ * the back end. Implements echo and/or local line editing,
+ * depending on what's currently configured.
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+
+#include "putty.h"
+#include "terminal.h"
+#include "ldisc.h"
+
+#define ECHOING (ldisc->localecho == FORCE_ON || \
+ (ldisc->localecho == AUTO && \
+ (ldisc->back->ldisc(ldisc->backhandle, LD_ECHO) || \
+ term_ldisc(ldisc->term, LD_ECHO))))
+#define EDITING (ldisc->localedit == FORCE_ON || \
+ (ldisc->localedit == AUTO && \
+ (ldisc->back->ldisc(ldisc->backhandle, LD_EDIT) || \
+ term_ldisc(ldisc->term, LD_EDIT))))
+
+static void c_write(Ldisc ldisc, char *buf, int len)
+{
+ from_backend(ldisc->frontend, 0, buf, len);
+}
+
+static int plen(Ldisc ldisc, unsigned char c)
+{
+ if ((c >= 32 && c <= 126) || (c >= 160 && !in_utf(ldisc->term)))
+ return 1;
+ else if (c < 128)
+ return 2; /* ^x for some x */
+ else if (in_utf(ldisc->term) && c >= 0xC0)
+ return 1; /* UTF-8 introducer character
+ * (FIXME: combining / wide chars) */
+ else if (in_utf(ldisc->term) && c >= 0x80 && c < 0xC0)
+ return 0; /* UTF-8 followup character */
+ else
+ return 4; /* <XY> hex representation */
+}
+
+static void pwrite(Ldisc ldisc, unsigned char c)
+{
+ if ((c >= 32 && c <= 126) ||
+ (!in_utf(ldisc->term) && c >= 0xA0) ||
+ (in_utf(ldisc->term) && c >= 0x80)) {
+ c_write(ldisc, (char *)&c, 1);
+ } else if (c < 128) {
+ char cc[2];
+ cc[1] = (c == 127 ? '?' : c + 0x40);
+ cc[0] = '^';
+ c_write(ldisc, cc, 2);
+ } else {
+ char cc[5];
+ sprintf(cc, "<%02X>", c);
+ c_write(ldisc, cc, 4);
+ }
+}
+
+static int char_start(Ldisc ldisc, unsigned char c)
+{
+ if (in_utf(ldisc->term))
+ return (c < 0x80 || c >= 0xC0);
+ else
+ return 1;
+}
+
+static void bsb(Ldisc ldisc, int n)
+{
+ while (n--)
+ c_write(ldisc, "\010 \010", 3);
+}
+
+#define CTRL(x) (x^'@')
+#define KCTRL(x) ((x^'@') | 0x100)
+
+void *ldisc_create(Conf *conf, Terminal *term,
+ Backend *back, void *backhandle,
+ void *frontend)
+{
+ Ldisc ldisc = snew(struct ldisc_tag);
+
+ ldisc->buf = NULL;
+ ldisc->buflen = 0;
+ ldisc->bufsiz = 0;
+ ldisc->quotenext = 0;
+
+ ldisc->back = back;
+ ldisc->backhandle = backhandle;
+ ldisc->term = term;
+ ldisc->frontend = frontend;
+
+ ldisc_configure(ldisc, conf);
+
+ /* Link ourselves into the backend and the terminal */
+ if (term)
+ term->ldisc = ldisc;
+ if (back)
+ back->provide_ldisc(backhandle, ldisc);
+
+ return ldisc;
+}
+
+void ldisc_configure(void *handle, Conf *conf)
+{
+ Ldisc ldisc = (Ldisc) handle;
+
+ ldisc->telnet_keyboard = conf_get_int(conf, CONF_telnet_keyboard);
+ ldisc->telnet_newline = conf_get_int(conf, CONF_telnet_newline);
+ ldisc->protocol = conf_get_int(conf, CONF_protocol);
+ ldisc->localecho = conf_get_int(conf, CONF_localecho);
+ ldisc->localedit = conf_get_int(conf, CONF_localedit);
+}
+
+void ldisc_free(void *handle)
+{
+ Ldisc ldisc = (Ldisc) handle;
+
+ if (ldisc->term)
+ ldisc->term->ldisc = NULL;
+ if (ldisc->back)
+ ldisc->back->provide_ldisc(ldisc->backhandle, NULL);
+ if (ldisc->buf)
+ sfree(ldisc->buf);
+ sfree(ldisc);
+}
+
+void ldisc_send(void *handle, 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;
+ }
+ /*
+ * Notify the front end that something was pressed, in case
+ * it's depending on finding out (e.g. keypress termination for
+ * Close On Exit).
+ */
+ frontend_keypress(ldisc->frontend);
+
+ if (interactive && ldisc->term) {
+ /*
+ * Interrupt a paste from the clipboard, if one was in
+ * progress when the user pressed a key. This is easier than
+ * buffering the current piece of data and saving it until the
+ * terminal has finished pasting, and has the potential side
+ * benefit of permitting a user to cancel an accidental huge
+ * paste.
+ */
+ term_nopaste(ldisc->term);
+ }
+
+ /*
+ * Less than zero means null terminated special string.
+ */
+ if (len < 0) {
+ len = strlen(buf);
+ keyflag = KCTRL('@');
+ }
+ /*
+ * Either perform local editing, or just send characters.
+ */
+ if (EDITING) {
+ while (len--) {
+ int c;
+ c = (unsigned char)(*buf++) + keyflag;
+ if (!interactive && c == '\r')
+ c += KCTRL('@');
+ switch (ldisc->quotenext ? ' ' : c) {
+ /*
+ * ^h/^?: delete, and output BSBs, to return to
+ * last character boundary (in UTF-8 mode this may
+ * be more than one byte)
+ * ^w: delete, and output BSBs, to return to last
+ * space/nonspace boundary
+ * ^u: delete, and output BSBs, to return to BOL
+ * ^c: Do a ^u then send a telnet IP
+ * ^z: Do a ^u then send a telnet SUSP
+ * ^\: Do a ^u then send a telnet ABORT
+ * ^r: echo "^R\n" and redraw line
+ * ^v: quote next char
+ * ^d: if at BOL, end of file and close connection,
+ * else send line and reset to BOL
+ * ^m: send line-plus-\r\n and reset to BOL
+ */
+ case KCTRL('H'):
+ case KCTRL('?'): /* backspace/delete */
+ if (ldisc->buflen > 0) {
+ do {
+ if (ECHOING)
+ bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
+ ldisc->buflen--;
+ } while (!char_start(ldisc, ldisc->buf[ldisc->buflen]));
+ }
+ break;
+ case CTRL('W'): /* delete word */
+ while (ldisc->buflen > 0) {
+ if (ECHOING)
+ bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
+ ldisc->buflen--;
+ if (ldisc->buflen > 0 &&
+ isspace((unsigned char)ldisc->buf[ldisc->buflen-1]) &&
+ !isspace((unsigned char)ldisc->buf[ldisc->buflen]))
+ break;
+ }
+ break;
+ case CTRL('U'): /* delete line */
+ case CTRL('C'): /* Send IP */
+ case CTRL('\\'): /* Quit */
+ case CTRL('Z'): /* Suspend */
+ while (ldisc->buflen > 0) {
+ if (ECHOING)
+ bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
+ ldisc->buflen--;
+ }
+ ldisc->back->special(ldisc->backhandle, TS_EL);
+ /*
+ * We don't send IP, SUSP or ABORT if the user has
+ * configured telnet specials off! This breaks
+ * talkers otherwise.
+ */
+ if (!ldisc->telnet_keyboard)
+ goto default_case;
+ if (c == CTRL('C'))
+ ldisc->back->special(ldisc->backhandle, TS_IP);
+ if (c == CTRL('Z'))
+ ldisc->back->special(ldisc->backhandle, TS_SUSP);
+ if (c == CTRL('\\'))
+ ldisc->back->special(ldisc->backhandle, TS_ABORT);
+ break;
+ case CTRL('R'): /* redraw line */
+ if (ECHOING) {
+ int i;
+ c_write(ldisc, "^R\r\n", 4);
+ for (i = 0; i < ldisc->buflen; i++)
+ pwrite(ldisc, ldisc->buf[i]);
+ }
+ break;
+ case CTRL('V'): /* quote next char */
+ ldisc->quotenext = TRUE;
+ break;
+ case CTRL('D'): /* logout or send */
+ if (ldisc->buflen == 0) {
+ ldisc->back->special(ldisc->backhandle, TS_EOF);
+ } else {
+ ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen);
+ ldisc->buflen = 0;
+ }
+ break;
+ /*
+ * This particularly hideous bit of code from RDB
+ * allows ordinary ^M^J to do the same thing as
+ * magic-^M when in Raw protocol. The line `case
+ * KCTRL('M'):' is _inside_ the if block. Thus:
+ *
+ * - receiving regular ^M goes straight to the
+ * default clause and inserts as a literal ^M.
+ * - receiving regular ^J _not_ directly after a
+ * literal ^M (or not in Raw protocol) fails the
+ * if condition, leaps to the bottom of the if,
+ * and falls through into the default clause
+ * again.
+ * - receiving regular ^J just after a literal ^M
+ * in Raw protocol passes the if condition,
+ * deletes the literal ^M, and falls through
+ * into the magic-^M code
+ * - receiving a magic-^M empties the line buffer,
+ * signals end-of-line in one of the various
+ * entertaining ways, and _doesn't_ fall out of
+ * the bottom of the if and through to the
+ * default clause because of the break.
+ */
+ case CTRL('J'):
+ if (ldisc->protocol == PROT_RAW &&
+ ldisc->buflen > 0 && ldisc->buf[ldisc->buflen - 1] == '\r') {
+ if (ECHOING)
+ bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
+ ldisc->buflen--;
+ /* FALLTHROUGH */
+ case KCTRL('M'): /* send with newline */
+ if (ldisc->buflen > 0)
+ ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen);
+ if (ldisc->protocol == PROT_RAW)
+ ldisc->back->send(ldisc->backhandle, "\r\n", 2);
+ else if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline)
+ ldisc->back->special(ldisc->backhandle, TS_EOL);
+ else
+ ldisc->back->send(ldisc->backhandle, "\r", 1);
+ if (ECHOING)
+ c_write(ldisc, "\r\n", 2);
+ ldisc->buflen = 0;
+ break;
+ }
+ /* FALLTHROUGH */
+ default: /* get to this label from ^V handler */
+ default_case:
+ if (ldisc->buflen >= ldisc->bufsiz) {
+ ldisc->bufsiz = ldisc->buflen + 256;
+ ldisc->buf = sresize(ldisc->buf, ldisc->bufsiz, char);
+ }
+ ldisc->buf[ldisc->buflen++] = c;
+ if (ECHOING)
+ pwrite(ldisc, (unsigned char) c);
+ ldisc->quotenext = FALSE;
+ break;
+ }
+ }
+ } else {
+ if (ldisc->buflen != 0) {
+ ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen);
+ while (ldisc->buflen > 0) {
+ bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
+ ldisc->buflen--;
+ }
+ }
+ if (len > 0) {
+ if (ECHOING)
+ c_write(ldisc, buf, len);
+ if (keyflag && ldisc->protocol == PROT_TELNET && len == 1) {
+ switch (buf[0]) {
+ case CTRL('M'):
+ if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline)
+ ldisc->back->special(ldisc->backhandle, TS_EOL);
+ else
+ ldisc->back->send(ldisc->backhandle, "\r", 1);
+ break;
+ case CTRL('?'):
+ case CTRL('H'):
+ if (ldisc->telnet_keyboard) {
+ ldisc->back->special(ldisc->backhandle, TS_EC);
+ break;
+ }
+ case CTRL('C'):
+ if (ldisc->telnet_keyboard) {
+ ldisc->back->special(ldisc->backhandle, TS_IP);
+ break;
+ }
+ case CTRL('Z'):
+ if (ldisc->telnet_keyboard) {
+ ldisc->back->special(ldisc->backhandle, TS_SUSP);
+ break;
+ }
+
+ default:
+ ldisc->back->send(ldisc->backhandle, buf, len);
+ break;
+ }
+ } else
+ ldisc->back->send(ldisc->backhandle, buf, len);
+ }
+ }
+}
diff --git a/tools/plink/logging.c b/tools/plink/logging.c
index a7f76c35a..868ff9874 100644
--- a/tools/plink/logging.c
+++ b/tools/plink/logging.c
@@ -1,450 +1,470 @@
-/*
- * Session logging.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <ctype.h>
-
-#include <time.h>
-#include <assert.h>
-
-#include "putty.h"
-
-/* log session to file stuff ... */
-struct LogContext {
- FILE *lgfp;
- enum { L_CLOSED, L_OPENING, L_OPEN, L_ERROR } state;
- bufchain queue;
- Filename *currlogfilename;
- void *frontend;
- Conf *conf;
- int logtype; /* cached out of conf */
-};
-
-static Filename *xlatlognam(Filename *s, char *hostname, struct tm *tm);
-
-/*
- * Internal wrapper function which must be called for _all_ output
- * to the log file. It takes care of opening the log file if it
- * isn't open, buffering data if it's in the process of being
- * opened asynchronously, etc.
- */
-static void logwrite(struct LogContext *ctx, void *data, int len)
-{
- /*
- * In state L_CLOSED, we call logfopen, which will set the state
- * to one of L_OPENING, L_OPEN or L_ERROR. Hence we process all of
- * those three _after_ processing L_CLOSED.
- */
- if (ctx->state == L_CLOSED)
- logfopen(ctx);
-
- if (ctx->state == L_OPENING) {
- bufchain_add(&ctx->queue, data, len);
- } else if (ctx->state == L_OPEN) {
- assert(ctx->lgfp);
- if (fwrite(data, 1, len, ctx->lgfp) < (size_t)len) {
- logfclose(ctx);
- ctx->state = L_ERROR;
- /* Log state is L_ERROR so this won't cause a loop */
- logevent(ctx->frontend,
- "Disabled writing session log due to error while writing");
- }
- } /* else L_ERROR, so ignore the write */
-}
-
-/*
- * Convenience wrapper on logwrite() which printf-formats the
- * string.
- */
-static void logprintf(struct LogContext *ctx, const char *fmt, ...)
-{
- va_list ap;
- char *data;
-
- va_start(ap, fmt);
- data = dupvprintf(fmt, ap);
- va_end(ap);
-
- logwrite(ctx, data, strlen(data));
- sfree(data);
-}
-
-/*
- * Flush any open log file.
- */
-void logflush(void *handle) {
- struct LogContext *ctx = (struct LogContext *)handle;
- if (ctx->logtype > 0)
- if (ctx->state == L_OPEN)
- fflush(ctx->lgfp);
-}
-
-static void logfopen_callback(void *handle, int mode)
-{
- struct LogContext *ctx = (struct LogContext *)handle;
- char buf[256], *event;
- struct tm tm;
- const char *fmode;
-
- if (mode == 0) {
- ctx->state = L_ERROR; /* disable logging */
- } else {
- fmode = (mode == 1 ? "ab" : "wb");
- ctx->lgfp = f_open(ctx->currlogfilename, fmode, FALSE);
- if (ctx->lgfp)
- ctx->state = L_OPEN;
- else
- ctx->state = L_ERROR;
- }
-
- if (ctx->state == L_OPEN) {
- /* Write header line into log file. */
- tm = ltime();
- strftime(buf, 24, "%Y.%m.%d %H:%M:%S", &tm);
- logprintf(ctx, "=~=~=~=~=~=~=~=~=~=~=~= PuTTY log %s"
- " =~=~=~=~=~=~=~=~=~=~=~=\r\n", buf);
- }
-
- event = dupprintf("%s session log (%s mode) to file: %s",
- ctx->state == L_ERROR ?
- (mode == 0 ? "Disabled writing" : "Error writing") :
- (mode == 1 ? "Appending" : "Writing new"),
- (ctx->logtype == LGTYP_ASCII ? "ASCII" :
- ctx->logtype == LGTYP_DEBUG ? "raw" :
- ctx->logtype == LGTYP_PACKETS ? "SSH packets" :
- ctx->logtype == LGTYP_SSHRAW ? "SSH raw data" :
- "unknown"),
- filename_to_str(ctx->currlogfilename));
- logevent(ctx->frontend, event);
- sfree(event);
-
- /*
- * Having either succeeded or failed in opening the log file,
- * we should write any queued data out.
- */
- assert(ctx->state != L_OPENING); /* make _sure_ it won't be requeued */
- while (bufchain_size(&ctx->queue)) {
- void *data;
- int len;
- bufchain_prefix(&ctx->queue, &data, &len);
- logwrite(ctx, data, len);
- bufchain_consume(&ctx->queue, len);
- }
-}
-
-/*
- * Open the log file. Takes care of detecting an already-existing
- * file and asking the user whether they want to append, overwrite
- * or cancel logging.
- */
-void logfopen(void *handle)
-{
- struct LogContext *ctx = (struct LogContext *)handle;
- struct tm tm;
- int mode;
-
- /* Prevent repeat calls */
- if (ctx->state != L_CLOSED)
- return;
-
- if (!ctx->logtype)
- return;
-
- tm = ltime();
-
- /* substitute special codes in file name */
- if (ctx->currlogfilename)
- filename_free(ctx->currlogfilename);
- ctx->currlogfilename =
- xlatlognam(conf_get_filename(ctx->conf, CONF_logfilename),
- conf_get_str(ctx->conf, CONF_host), &tm);
-
- ctx->lgfp = f_open(ctx->currlogfilename, "r", FALSE); /* file already present? */
- if (ctx->lgfp) {
- int logxfovr = conf_get_int(ctx->conf, CONF_logxfovr);
- fclose(ctx->lgfp);
- if (logxfovr != LGXF_ASK) {
- mode = ((logxfovr == LGXF_OVR) ? 2 : 1);
- } else
- mode = askappend(ctx->frontend, ctx->currlogfilename,
- logfopen_callback, ctx);
- } else
- mode = 2; /* create == overwrite */
-
- if (mode < 0)
- ctx->state = L_OPENING;
- else
- logfopen_callback(ctx, mode); /* open the file */
-}
-
-void logfclose(void *handle)
-{
- struct LogContext *ctx = (struct LogContext *)handle;
- if (ctx->lgfp) {
- fclose(ctx->lgfp);
- ctx->lgfp = NULL;
- }
- ctx->state = L_CLOSED;
-}
-
-/*
- * Log session traffic.
- */
-void logtraffic(void *handle, unsigned char c, int logmode)
-{
- struct LogContext *ctx = (struct LogContext *)handle;
- if (ctx->logtype > 0) {
- if (ctx->logtype == logmode)
- logwrite(ctx, &c, 1);
- }
-}
-
-/*
- * Log an Event Log entry. Used in SSH packet logging mode; this is
- * also as convenient a place as any to put the output of Event Log
- * entries to stderr when a command-line tool is in verbose mode.
- * (In particular, this is a better place to put it than in the
- * front ends, because it only has to be done once for all
- * platforms. Platforms which don't have a meaningful stderr can
- * just avoid defining FLAG_STDERR.
- */
-void log_eventlog(void *handle, const char *event)
-{
- struct LogContext *ctx = (struct LogContext *)handle;
- if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)) {
- fprintf(stderr, "%s\n", event);
- fflush(stderr);
- }
- /* If we don't have a context yet (eg winnet.c init) then skip entirely */
- if (!ctx)
- return;
- if (ctx->logtype != LGTYP_PACKETS &&
- ctx->logtype != LGTYP_SSHRAW)
- return;
- logprintf(ctx, "Event Log: %s\r\n", event);
- logflush(ctx);
-}
-
-/*
- * Log an SSH packet.
- * If n_blanks != 0, blank or omit some parts.
- * 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,
- int n_blanks, const struct logblank_t *blanks,
- const unsigned long *seq)
-{
- struct LogContext *ctx = (struct LogContext *)handle;
- char dumpdata[80], smalldata[5];
- int p = 0, b = 0, omitted = 0;
- int output_pos = 0; /* NZ if pending output in dumpdata */
-
- if (!(ctx->logtype == LGTYP_SSHRAW ||
- (ctx->logtype == LGTYP_PACKETS && texttype)))
- return;
-
- /* Packet header. */
- if (texttype) {
- if (seq) {
- logprintf(ctx, "%s packet #0x%lx, type %d / 0x%02x (%s)\r\n",
- direction == PKT_INCOMING ? "Incoming" : "Outgoing",
- *seq, type, type, texttype);
- } else {
- logprintf(ctx, "%s packet type %d / 0x%02x (%s)\r\n",
- direction == PKT_INCOMING ? "Incoming" : "Outgoing",
- type, type, texttype);
- }
- } else {
- logprintf(ctx, "%s raw data\r\n",
- direction == PKT_INCOMING ? "Incoming" : "Outgoing");
- }
-
- /*
- * Output a hex/ASCII dump of the packet body, blanking/omitting
- * parts as specified.
- */
- while (p < len) {
- int blktype;
-
- /* Move to a current entry in the blanking array. */
- while ((b < n_blanks) &&
- (p >= blanks[b].offset + blanks[b].len))
- b++;
- /* Work out what type of blanking to apply to
- * this byte. */
- blktype = PKTLOG_EMIT; /* default */
- if ((b < n_blanks) &&
- (p >= blanks[b].offset) &&
- (p < blanks[b].offset + blanks[b].len))
- blktype = blanks[b].type;
-
- /* If we're about to stop omitting, it's time to say how
- * much we omitted. */
- if ((blktype != PKTLOG_OMIT) && omitted) {
- logprintf(ctx, " (%d byte%s omitted)\r\n",
- omitted, (omitted==1?"":"s"));
- omitted = 0;
- }
-
- /* (Re-)initialise dumpdata as necessary
- * (start of row, or if we've just stopped omitting) */
- if (!output_pos && !omitted)
- sprintf(dumpdata, " %08x%*s\r\n", p-(p%16), 1+3*16+2+16, "");
-
- /* Deal with the current byte. */
- if (blktype == PKTLOG_OMIT) {
- omitted++;
- } else {
- int c;
- if (blktype == PKTLOG_BLANK) {
- c = 'X';
- sprintf(smalldata, "XX");
- } else { /* PKTLOG_EMIT */
- c = ((unsigned char *)data)[p];
- sprintf(smalldata, "%02x", c);
- }
- dumpdata[10+2+3*(p%16)] = smalldata[0];
- dumpdata[10+2+3*(p%16)+1] = smalldata[1];
- dumpdata[10+1+3*16+2+(p%16)] = (isprint(c) ? c : '.');
- output_pos = (p%16) + 1;
- }
-
- p++;
-
- /* Flush row if necessary */
- if (((p % 16) == 0) || (p == len) || omitted) {
- if (output_pos) {
- strcpy(dumpdata + 10+1+3*16+2+output_pos, "\r\n");
- logwrite(ctx, dumpdata, strlen(dumpdata));
- output_pos = 0;
- }
- }
-
- }
-
- /* Tidy up */
- if (omitted)
- logprintf(ctx, " (%d byte%s omitted)\r\n",
- omitted, (omitted==1?"":"s"));
- logflush(ctx);
-}
-
-void *log_init(void *frontend, Conf *conf)
-{
- struct LogContext *ctx = snew(struct LogContext);
- ctx->lgfp = NULL;
- ctx->state = L_CLOSED;
- ctx->frontend = frontend;
- ctx->conf = conf_copy(conf);
- ctx->logtype = conf_get_int(ctx->conf, CONF_logtype);
- ctx->currlogfilename = NULL;
- bufchain_init(&ctx->queue);
- return ctx;
-}
-
-void log_free(void *handle)
-{
- struct LogContext *ctx = (struct LogContext *)handle;
-
- logfclose(ctx);
- bufchain_clear(&ctx->queue);
- if (ctx->currlogfilename)
- filename_free(ctx->currlogfilename);
- sfree(ctx);
-}
-
-void log_reconfig(void *handle, Conf *conf)
-{
- struct LogContext *ctx = (struct LogContext *)handle;
- int reset_logging;
-
- if (!filename_equal(conf_get_filename(ctx->conf, CONF_logfilename),
- conf_get_filename(conf, CONF_logfilename)) ||
- conf_get_int(ctx->conf, CONF_logtype) !=
- conf_get_int(conf, CONF_logtype))
- reset_logging = TRUE;
- else
- reset_logging = FALSE;
-
- if (reset_logging)
- logfclose(ctx);
-
- conf_free(ctx->conf);
- ctx->conf = conf_copy(conf);
-
- ctx->logtype = conf_get_int(ctx->conf, CONF_logtype);
-
- if (reset_logging)
- logfopen(ctx);
-}
-
-/*
- * translate format codes into time/date strings
- * and insert them into log file name
- *
- * "&Y":YYYY "&m":MM "&d":DD "&T":hhmmss "&h":<hostname> "&&":&
- */
-static Filename *xlatlognam(Filename *src, char *hostname, struct tm *tm)
-{
- char buf[10], *bufp;
- int size;
- char *buffer;
- int buflen, bufsize;
- const char *s;
- Filename *ret;
-
- bufsize = FILENAME_MAX;
- buffer = snewn(bufsize, char);
- buflen = 0;
- s = filename_to_str(src);
-
- while (*s) {
- /* Let (bufp, len) be the string to append. */
- bufp = buf; /* don't usually override this */
- if (*s == '&') {
- char c;
- s++;
- size = 0;
- if (*s) switch (c = *s++, tolower((unsigned char)c)) {
- case 'y':
- size = strftime(buf, sizeof(buf), "%Y", tm);
- break;
- case 'm':
- size = strftime(buf, sizeof(buf), "%m", tm);
- break;
- case 'd':
- size = strftime(buf, sizeof(buf), "%d", tm);
- break;
- case 't':
- size = strftime(buf, sizeof(buf), "%H%M%S", tm);
- break;
- case 'h':
- bufp = hostname;
- size = strlen(bufp);
- break;
- default:
- buf[0] = '&';
- size = 1;
- if (c != '&')
- buf[size++] = c;
- }
- } else {
- buf[0] = *s++;
- size = 1;
- }
- if (bufsize <= buflen + size) {
- bufsize = (buflen + size) * 5 / 4 + 512;
- buffer = sresize(buffer, bufsize, char);
- }
- memcpy(buffer + buflen, bufp, size);
- buflen += size;
- }
- buffer[buflen] = '\0';
-
- ret = filename_from_str(buffer);
- sfree(buffer);
- return ret;
-}
+/*
+ * Session logging.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include <time.h>
+#include <assert.h>
+
+#include "putty.h"
+
+/* log session to file stuff ... */
+struct LogContext {
+ FILE *lgfp;
+ enum { L_CLOSED, L_OPENING, L_OPEN, L_ERROR } state;
+ bufchain queue;
+ Filename *currlogfilename;
+ void *frontend;
+ Conf *conf;
+ int logtype; /* cached out of conf */
+};
+
+static Filename *xlatlognam(Filename *s, char *hostname, struct tm *tm);
+
+/*
+ * Internal wrapper function which must be called for _all_ output
+ * to the log file. It takes care of opening the log file if it
+ * isn't open, buffering data if it's in the process of being
+ * opened asynchronously, etc.
+ */
+static void logwrite(struct LogContext *ctx, void *data, int len)
+{
+ /*
+ * In state L_CLOSED, we call logfopen, which will set the state
+ * to one of L_OPENING, L_OPEN or L_ERROR. Hence we process all of
+ * those three _after_ processing L_CLOSED.
+ */
+ if (ctx->state == L_CLOSED)
+ logfopen(ctx);
+
+ if (ctx->state == L_OPENING) {
+ bufchain_add(&ctx->queue, data, len);
+ } else if (ctx->state == L_OPEN) {
+ assert(ctx->lgfp);
+ if (fwrite(data, 1, len, ctx->lgfp) < (size_t)len) {
+ logfclose(ctx);
+ ctx->state = L_ERROR;
+ /* Log state is L_ERROR so this won't cause a loop */
+ logevent(ctx->frontend,
+ "Disabled writing session log due to error while writing");
+ }
+ } /* else L_ERROR, so ignore the write */
+}
+
+/*
+ * Convenience wrapper on logwrite() which printf-formats the
+ * string.
+ */
+static void logprintf(struct LogContext *ctx, const char *fmt, ...)
+{
+ va_list ap;
+ char *data;
+
+ va_start(ap, fmt);
+ data = dupvprintf(fmt, ap);
+ va_end(ap);
+
+ logwrite(ctx, data, strlen(data));
+ sfree(data);
+}
+
+/*
+ * Flush any open log file.
+ */
+void logflush(void *handle) {
+ struct LogContext *ctx = (struct LogContext *)handle;
+ if (ctx->logtype > 0)
+ if (ctx->state == L_OPEN)
+ fflush(ctx->lgfp);
+}
+
+static void logfopen_callback(void *handle, int mode)
+{
+ struct LogContext *ctx = (struct LogContext *)handle;
+ char buf[256], *event;
+ struct tm tm;
+ const char *fmode;
+
+ if (mode == 0) {
+ ctx->state = L_ERROR; /* disable logging */
+ } else {
+ fmode = (mode == 1 ? "ab" : "wb");
+ ctx->lgfp = f_open(ctx->currlogfilename, fmode, FALSE);
+ if (ctx->lgfp)
+ ctx->state = L_OPEN;
+ else
+ ctx->state = L_ERROR;
+ }
+
+ if (ctx->state == L_OPEN) {
+ /* Write header line into log file. */
+ tm = ltime();
+ strftime(buf, 24, "%Y.%m.%d %H:%M:%S", &tm);
+ logprintf(ctx, "=~=~=~=~=~=~=~=~=~=~=~= PuTTY log %s"
+ " =~=~=~=~=~=~=~=~=~=~=~=\r\n", buf);
+ }
+
+ event = dupprintf("%s session log (%s mode) to file: %s",
+ ctx->state == L_ERROR ?
+ (mode == 0 ? "Disabled writing" : "Error writing") :
+ (mode == 1 ? "Appending" : "Writing new"),
+ (ctx->logtype == LGTYP_ASCII ? "ASCII" :
+ ctx->logtype == LGTYP_DEBUG ? "raw" :
+ ctx->logtype == LGTYP_PACKETS ? "SSH packets" :
+ ctx->logtype == LGTYP_SSHRAW ? "SSH raw data" :
+ "unknown"),
+ filename_to_str(ctx->currlogfilename));
+ logevent(ctx->frontend, event);
+ sfree(event);
+
+ /*
+ * Having either succeeded or failed in opening the log file,
+ * we should write any queued data out.
+ */
+ assert(ctx->state != L_OPENING); /* make _sure_ it won't be requeued */
+ while (bufchain_size(&ctx->queue)) {
+ void *data;
+ int len;
+ bufchain_prefix(&ctx->queue, &data, &len);
+ logwrite(ctx, data, len);
+ bufchain_consume(&ctx->queue, len);
+ }
+}
+
+/*
+ * Open the log file. Takes care of detecting an already-existing
+ * file and asking the user whether they want to append, overwrite
+ * or cancel logging.
+ */
+void logfopen(void *handle)
+{
+ struct LogContext *ctx = (struct LogContext *)handle;
+ struct tm tm;
+ int mode;
+
+ /* Prevent repeat calls */
+ if (ctx->state != L_CLOSED)
+ return;
+
+ if (!ctx->logtype)
+ return;
+
+ tm = ltime();
+
+ /* substitute special codes in file name */
+ if (ctx->currlogfilename)
+ filename_free(ctx->currlogfilename);
+ ctx->currlogfilename =
+ xlatlognam(conf_get_filename(ctx->conf, CONF_logfilename),
+ conf_get_str(ctx->conf, CONF_host), &tm);
+
+ ctx->lgfp = f_open(ctx->currlogfilename, "r", FALSE); /* file already present? */
+ if (ctx->lgfp) {
+ int logxfovr = conf_get_int(ctx->conf, CONF_logxfovr);
+ fclose(ctx->lgfp);
+ if (logxfovr != LGXF_ASK) {
+ mode = ((logxfovr == LGXF_OVR) ? 2 : 1);
+ } else
+ mode = askappend(ctx->frontend, ctx->currlogfilename,
+ logfopen_callback, ctx);
+ } else
+ mode = 2; /* create == overwrite */
+
+ if (mode < 0)
+ ctx->state = L_OPENING;
+ else
+ logfopen_callback(ctx, mode); /* open the file */
+}
+
+void logfclose(void *handle)
+{
+ struct LogContext *ctx = (struct LogContext *)handle;
+ if (ctx->lgfp) {
+ fclose(ctx->lgfp);
+ ctx->lgfp = NULL;
+ }
+ ctx->state = L_CLOSED;
+}
+
+/*
+ * Log session traffic.
+ */
+void logtraffic(void *handle, unsigned char c, int logmode)
+{
+ struct LogContext *ctx = (struct LogContext *)handle;
+ if (ctx->logtype > 0) {
+ if (ctx->logtype == logmode)
+ logwrite(ctx, &c, 1);
+ }
+}
+
+/*
+ * Log an Event Log entry. Used in SSH packet logging mode; this is
+ * also as convenient a place as any to put the output of Event Log
+ * entries to stderr when a command-line tool is in verbose mode.
+ * (In particular, this is a better place to put it than in the
+ * front ends, because it only has to be done once for all
+ * platforms. Platforms which don't have a meaningful stderr can
+ * just avoid defining FLAG_STDERR.
+ */
+void log_eventlog(void *handle, const char *event)
+{
+ struct LogContext *ctx = (struct LogContext *)handle;
+ if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)) {
+ fprintf(stderr, "%s\n", event);
+ fflush(stderr);
+ }
+ /* If we don't have a context yet (eg winnet.c init) then skip entirely */
+ if (!ctx)
+ return;
+ if (ctx->logtype != LGTYP_PACKETS &&
+ ctx->logtype != LGTYP_SSHRAW)
+ return;
+ logprintf(ctx, "Event Log: %s\r\n", event);
+ logflush(ctx);
+}
+
+/*
+ * Log an SSH packet.
+ * If n_blanks != 0, blank or omit some parts.
+ * 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,
+ int n_blanks, const struct logblank_t *blanks,
+ const unsigned long *seq,
+ unsigned downstream_id, const char *additional_log_text)
+{
+ struct LogContext *ctx = (struct LogContext *)handle;
+ char dumpdata[80], smalldata[5];
+ int p = 0, b = 0, omitted = 0;
+ int output_pos = 0; /* NZ if pending output in dumpdata */
+
+ if (!(ctx->logtype == LGTYP_SSHRAW ||
+ (ctx->logtype == LGTYP_PACKETS && texttype)))
+ return;
+
+ /* Packet header. */
+ if (texttype) {
+ logprintf(ctx, "%s packet ",
+ direction == PKT_INCOMING ? "Incoming" : "Outgoing");
+
+ if (seq)
+ logprintf(ctx, "#0x%lx, ", *seq);
+
+ logprintf(ctx, "type %d / 0x%02x (%s)", type, type, texttype);
+
+ if (downstream_id) {
+ logprintf(ctx, " on behalf of downstream #%u", downstream_id);
+ if (additional_log_text)
+ logprintf(ctx, " (%s)", additional_log_text);
+ }
+
+ logprintf(ctx, "\r\n");
+ } else {
+ /*
+ * Raw data is logged with a timestamp, so that it's possible
+ * to determine whether a mysterious delay occurred at the
+ * client or server end. (Timestamping the raw data avoids
+ * cluttering the normal case of only logging decrypted SSH
+ * messages, and also adds conceptual rigour in the case where
+ * an SSH message arrives in several pieces.)
+ */
+ char buf[256];
+ struct tm tm;
+ tm = ltime();
+ strftime(buf, 24, "%Y-%m-%d %H:%M:%S", &tm);
+ logprintf(ctx, "%s raw data at %s\r\n",
+ direction == PKT_INCOMING ? "Incoming" : "Outgoing",
+ buf);
+ }
+
+ /*
+ * Output a hex/ASCII dump of the packet body, blanking/omitting
+ * parts as specified.
+ */
+ while (p < len) {
+ int blktype;
+
+ /* Move to a current entry in the blanking array. */
+ while ((b < n_blanks) &&
+ (p >= blanks[b].offset + blanks[b].len))
+ b++;
+ /* Work out what type of blanking to apply to
+ * this byte. */
+ blktype = PKTLOG_EMIT; /* default */
+ if ((b < n_blanks) &&
+ (p >= blanks[b].offset) &&
+ (p < blanks[b].offset + blanks[b].len))
+ blktype = blanks[b].type;
+
+ /* If we're about to stop omitting, it's time to say how
+ * much we omitted. */
+ if ((blktype != PKTLOG_OMIT) && omitted) {
+ logprintf(ctx, " (%d byte%s omitted)\r\n",
+ omitted, (omitted==1?"":"s"));
+ omitted = 0;
+ }
+
+ /* (Re-)initialise dumpdata as necessary
+ * (start of row, or if we've just stopped omitting) */
+ if (!output_pos && !omitted)
+ sprintf(dumpdata, " %08x%*s\r\n", p-(p%16), 1+3*16+2+16, "");
+
+ /* Deal with the current byte. */
+ if (blktype == PKTLOG_OMIT) {
+ omitted++;
+ } else {
+ int c;
+ if (blktype == PKTLOG_BLANK) {
+ c = 'X';
+ sprintf(smalldata, "XX");
+ } else { /* PKTLOG_EMIT */
+ c = ((unsigned char *)data)[p];
+ sprintf(smalldata, "%02x", c);
+ }
+ dumpdata[10+2+3*(p%16)] = smalldata[0];
+ dumpdata[10+2+3*(p%16)+1] = smalldata[1];
+ dumpdata[10+1+3*16+2+(p%16)] = (isprint(c) ? c : '.');
+ output_pos = (p%16) + 1;
+ }
+
+ p++;
+
+ /* Flush row if necessary */
+ if (((p % 16) == 0) || (p == len) || omitted) {
+ if (output_pos) {
+ strcpy(dumpdata + 10+1+3*16+2+output_pos, "\r\n");
+ logwrite(ctx, dumpdata, strlen(dumpdata));
+ output_pos = 0;
+ }
+ }
+
+ }
+
+ /* Tidy up */
+ if (omitted)
+ logprintf(ctx, " (%d byte%s omitted)\r\n",
+ omitted, (omitted==1?"":"s"));
+ logflush(ctx);
+}
+
+void *log_init(void *frontend, Conf *conf)
+{
+ struct LogContext *ctx = snew(struct LogContext);
+ ctx->lgfp = NULL;
+ ctx->state = L_CLOSED;
+ ctx->frontend = frontend;
+ ctx->conf = conf_copy(conf);
+ ctx->logtype = conf_get_int(ctx->conf, CONF_logtype);
+ ctx->currlogfilename = NULL;
+ bufchain_init(&ctx->queue);
+ return ctx;
+}
+
+void log_free(void *handle)
+{
+ struct LogContext *ctx = (struct LogContext *)handle;
+
+ logfclose(ctx);
+ bufchain_clear(&ctx->queue);
+ if (ctx->currlogfilename)
+ filename_free(ctx->currlogfilename);
+ sfree(ctx);
+}
+
+void log_reconfig(void *handle, Conf *conf)
+{
+ struct LogContext *ctx = (struct LogContext *)handle;
+ int reset_logging;
+
+ if (!filename_equal(conf_get_filename(ctx->conf, CONF_logfilename),
+ conf_get_filename(conf, CONF_logfilename)) ||
+ conf_get_int(ctx->conf, CONF_logtype) !=
+ conf_get_int(conf, CONF_logtype))
+ reset_logging = TRUE;
+ else
+ reset_logging = FALSE;
+
+ if (reset_logging)
+ logfclose(ctx);
+
+ conf_free(ctx->conf);
+ ctx->conf = conf_copy(conf);
+
+ ctx->logtype = conf_get_int(ctx->conf, CONF_logtype);
+
+ if (reset_logging)
+ logfopen(ctx);
+}
+
+/*
+ * translate format codes into time/date strings
+ * and insert them into log file name
+ *
+ * "&Y":YYYY "&m":MM "&d":DD "&T":hhmmss "&h":<hostname> "&&":&
+ */
+static Filename *xlatlognam(Filename *src, char *hostname, struct tm *tm)
+{
+ char buf[10], *bufp;
+ int size;
+ char *buffer;
+ int buflen, bufsize;
+ const char *s;
+ Filename *ret;
+
+ bufsize = FILENAME_MAX;
+ buffer = snewn(bufsize, char);
+ buflen = 0;
+ s = filename_to_str(src);
+
+ while (*s) {
+ /* Let (bufp, len) be the string to append. */
+ bufp = buf; /* don't usually override this */
+ if (*s == '&') {
+ char c;
+ s++;
+ size = 0;
+ if (*s) switch (c = *s++, tolower((unsigned char)c)) {
+ case 'y':
+ size = strftime(buf, sizeof(buf), "%Y", tm);
+ break;
+ case 'm':
+ size = strftime(buf, sizeof(buf), "%m", tm);
+ break;
+ case 'd':
+ size = strftime(buf, sizeof(buf), "%d", tm);
+ break;
+ case 't':
+ size = strftime(buf, sizeof(buf), "%H%M%S", tm);
+ break;
+ case 'h':
+ bufp = hostname;
+ size = strlen(bufp);
+ break;
+ default:
+ buf[0] = '&';
+ size = 1;
+ if (c != '&')
+ buf[size++] = c;
+ }
+ } else {
+ buf[0] = *s++;
+ size = 1;
+ }
+ if (bufsize <= buflen + size) {
+ bufsize = (buflen + size) * 5 / 4 + 512;
+ buffer = sresize(buffer, bufsize, char);
+ }
+ memcpy(buffer + buflen, bufp, size);
+ buflen += size;
+ }
+ buffer[buflen] = '\0';
+
+ ret = filename_from_str(buffer);
+ sfree(buffer);
+ return ret;
+}
diff --git a/tools/plink/misc.c b/tools/plink/misc.c
index 89a21f74e..d7c32c49d 100644
--- a/tools/plink/misc.c
+++ b/tools/plink/misc.c
@@ -1,687 +1,877 @@
-/*
- * Platform-independent routines shared between all PuTTY programs.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <limits.h>
-#include <ctype.h>
-#include <assert.h>
-#include "putty.h"
-
-/*
- * Parse a string block size specification. This is approximately a
- * subset of the block size specs supported by GNU fileutils:
- * "nk" = n kilobytes
- * "nM" = n megabytes
- * "nG" = n gigabytes
- * All numbers are decimal, and suffixes refer to powers of two.
- * Case-insensitive.
- */
-unsigned long parse_blocksize(const char *bs)
-{
- char *suf;
- unsigned long r = strtoul(bs, &suf, 10);
- if (*suf != '\0') {
- while (*suf && isspace((unsigned char)*suf)) suf++;
- switch (*suf) {
- case 'k': case 'K':
- r *= 1024ul;
- break;
- case 'm': case 'M':
- r *= 1024ul * 1024ul;
- break;
- case 'g': case 'G':
- r *= 1024ul * 1024ul * 1024ul;
- break;
- case '\0':
- default:
- break;
- }
- }
- return r;
-}
-
-/*
- * Parse a ^C style character specification.
- * Returns NULL in `next' if we didn't recognise it as a control character,
- * in which case `c' should be ignored.
- * The precise current parsing is an oddity inherited from the terminal
- * answerback-string parsing code. All sequences start with ^; all except
- * ^<123> are two characters. The ones that are worth keeping are probably:
- * ^? 127
- * ^@A-Z[\]^_ 0-31
- * a-z 1-26
- * <num> specified by number (decimal, 0octal, 0xHEX)
- * ~ ^ escape
- */
-char ctrlparse(char *s, char **next)
-{
- char c = 0;
- if (*s != '^') {
- *next = NULL;
- } else {
- s++;
- if (*s == '\0') {
- *next = NULL;
- } else if (*s == '<') {
- s++;
- c = (char)strtol(s, next, 0);
- if ((*next == s) || (**next != '>')) {
- c = 0;
- *next = NULL;
- } else
- (*next)++;
- } else if (*s >= 'a' && *s <= 'z') {
- c = (*s - ('a' - 1));
- *next = s+1;
- } else if ((*s >= '@' && *s <= '_') || *s == '?' || (*s & 0x80)) {
- c = ('@' ^ *s);
- *next = s+1;
- } else if (*s == '~') {
- c = '^';
- *next = s+1;
- }
- }
- return c;
-}
-
-prompts_t *new_prompts(void *frontend)
-{
- prompts_t *p = snew(prompts_t);
- p->prompts = NULL;
- p->n_prompts = 0;
- p->frontend = frontend;
- p->data = NULL;
- p->to_server = TRUE; /* to be on the safe side */
- p->name = p->instruction = NULL;
- p->name_reqd = p->instr_reqd = FALSE;
- return p;
-}
-void add_prompt(prompts_t *p, char *promptstr, int echo)
-{
- prompt_t *pr = snew(prompt_t);
- pr->prompt = promptstr;
- pr->echo = echo;
- pr->result = NULL;
- pr->resultsize = 0;
- p->n_prompts++;
- p->prompts = sresize(p->prompts, p->n_prompts, prompt_t *);
- p->prompts[p->n_prompts-1] = pr;
-}
-void prompt_ensure_result_size(prompt_t *pr, int newlen)
-{
- if ((int)pr->resultsize < newlen) {
- char *newbuf;
- newlen = newlen * 5 / 4 + 512; /* avoid too many small allocs */
-
- /*
- * We don't use sresize / realloc here, because we will be
- * storing sensitive stuff like passwords in here, and we want
- * to make sure that the data doesn't get copied around in
- * memory without the old copy being destroyed.
- */
- newbuf = snewn(newlen, char);
- memcpy(newbuf, pr->result, pr->resultsize);
- memset(pr->result, '\0', pr->resultsize);
- sfree(pr->result);
- pr->result = newbuf;
- pr->resultsize = newlen;
- }
-}
-void prompt_set_result(prompt_t *pr, const char *newstr)
-{
- prompt_ensure_result_size(pr, strlen(newstr) + 1);
- strcpy(pr->result, newstr);
-}
-void free_prompts(prompts_t *p)
-{
- size_t i;
- for (i=0; i < p->n_prompts; i++) {
- prompt_t *pr = p->prompts[i];
- memset(pr->result, 0, pr->resultsize); /* burn the evidence */
- sfree(pr->result);
- sfree(pr->prompt);
- sfree(pr);
- }
- sfree(p->prompts);
- sfree(p->name);
- sfree(p->instruction);
- sfree(p);
-}
-
-/* ----------------------------------------------------------------------
- * String handling routines.
- */
-
-char *dupstr(const char *s)
-{
- char *p = NULL;
- if (s) {
- int len = strlen(s);
- p = snewn(len + 1, char);
- strcpy(p, s);
- }
- return p;
-}
-
-/* Allocate the concatenation of N strings. Terminate arg list with NULL. */
-char *dupcat(const char *s1, ...)
-{
- int len;
- char *p, *q, *sn;
- va_list ap;
-
- len = strlen(s1);
- va_start(ap, s1);
- while (1) {
- sn = va_arg(ap, char *);
- if (!sn)
- break;
- len += strlen(sn);
- }
- va_end(ap);
-
- p = snewn(len + 1, char);
- strcpy(p, s1);
- q = p + strlen(p);
-
- va_start(ap, s1);
- while (1) {
- sn = va_arg(ap, char *);
- if (!sn)
- break;
- strcpy(q, sn);
- q += strlen(q);
- }
- va_end(ap);
-
- return p;
-}
-
-void burnstr(char *string) /* sfree(str), only clear it first */
-{
- if (string) {
- memset(string, 0, strlen(string));
- sfree(string);
- }
-}
-
-/*
- * Do an sprintf(), but into a custom-allocated buffer.
- *
- * Currently I'm doing this via vsnprintf. This has worked so far,
- * but it's not good, because vsnprintf is not available on all
- * platforms. There's an ifdef to use `_vsnprintf', which seems
- * to be the local name for it on Windows. Other platforms may
- * lack it completely, in which case it'll be time to rewrite
- * this function in a totally different way.
- *
- * The only `properly' portable solution I can think of is to
- * implement my own format string scanner, which figures out an
- * upper bound for the length of each formatting directive,
- * allocates the buffer as it goes along, and calls sprintf() to
- * actually process each directive. If I ever need to actually do
- * this, some caveats:
- *
- * - It's very hard to find a reliable upper bound for
- * floating-point values. %f, in particular, when supplied with
- * a number near to the upper or lower limit of representable
- * numbers, could easily take several hundred characters. It's
- * probably feasible to predict this statically using the
- * constants in <float.h>, or even to predict it dynamically by
- * looking at the exponent of the specific float provided, but
- * it won't be fun.
- *
- * - Don't forget to _check_, after calling sprintf, that it's
- * used at most the amount of space we had available.
- *
- * - Fault any formatting directive we don't fully understand. The
- * aim here is to _guarantee_ that we never overflow the buffer,
- * because this is a security-critical function. If we see a
- * directive we don't know about, we should panic and die rather
- * than run any risk.
- */
-char *dupprintf(const char *fmt, ...)
-{
- char *ret;
- va_list ap;
- va_start(ap, fmt);
- ret = dupvprintf(fmt, ap);
- va_end(ap);
- return ret;
-}
-char *dupvprintf(const char *fmt, va_list ap)
-{
- char *buf;
- int len, size;
-
- buf = snewn(512, char);
- size = 512;
-
- while (1) {
-#ifdef _WINDOWS
-#define vsnprintf _vsnprintf
-#endif
-#ifdef va_copy
- /* Use the `va_copy' macro mandated by C99, if present.
- * XXX some environments may have this as __va_copy() */
- va_list aq;
- va_copy(aq, ap);
- len = vsnprintf(buf, size, fmt, aq);
- va_end(aq);
-#else
- /* Ugh. No va_copy macro, so do something nasty.
- * Technically, you can't reuse a va_list like this: it is left
- * unspecified whether advancing a va_list pointer modifies its
- * value or something it points to, so on some platforms calling
- * vsnprintf twice on the same va_list might fail hideously
- * (indeed, it has been observed to).
- * XXX the autoconf manual suggests that using memcpy() will give
- * "maximum portability". */
- len = vsnprintf(buf, size, fmt, ap);
-#endif
- if (len >= 0 && len < size) {
- /* This is the C99-specified criterion for snprintf to have
- * been completely successful. */
- return buf;
- } else if (len > 0) {
- /* This is the C99 error condition: the returned length is
- * the required buffer size not counting the NUL. */
- size = len + 1;
- } else {
- /* This is the pre-C99 glibc error condition: <0 means the
- * buffer wasn't big enough, so we enlarge it a bit and hope. */
- size += 512;
- }
- buf = sresize(buf, size, char);
- }
-}
-
-/*
- * Read an entire line of text from a file. Return a buffer
- * malloced to be as big as necessary (caller must free).
- */
-char *fgetline(FILE *fp)
-{
- char *ret = snewn(512, char);
- int size = 512, len = 0;
- while (fgets(ret + len, size - len, fp)) {
- len += strlen(ret + len);
- if (ret[len-1] == '\n')
- break; /* got a newline, we're done */
- size = len + 512;
- ret = sresize(ret, size, char);
- }
- if (len == 0) { /* first fgets returned NULL */
- sfree(ret);
- return NULL;
- }
- ret[len] = '\0';
- return ret;
-}
-
-/* ----------------------------------------------------------------------
- * Base64 encoding routine. This is required in public-key writing
- * but also in HTTP proxy handling, so it's centralised here.
- */
-
-void base64_encode_atom(unsigned char *data, int n, char *out)
-{
- static const char base64_chars[] =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-
- unsigned word;
-
- word = data[0] << 16;
- if (n > 1)
- word |= data[1] << 8;
- if (n > 2)
- word |= data[2];
- out[0] = base64_chars[(word >> 18) & 0x3F];
- out[1] = base64_chars[(word >> 12) & 0x3F];
- if (n > 1)
- out[2] = base64_chars[(word >> 6) & 0x3F];
- else
- out[2] = '=';
- if (n > 2)
- out[3] = base64_chars[word & 0x3F];
- else
- out[3] = '=';
-}
-
-/* ----------------------------------------------------------------------
- * Generic routines to deal with send buffers: a linked list of
- * smallish blocks, with the operations
- *
- * - add an arbitrary amount of data to the end of the list
- * - remove the first N bytes from the list
- * - return a (pointer,length) pair giving some initial data in
- * the list, suitable for passing to a send or write system
- * call
- * - retrieve a larger amount of initial data from the list
- * - return the current size of the buffer chain in bytes
- */
-
-#define BUFFER_GRANULE 512
-
-struct bufchain_granule {
- struct bufchain_granule *next;
- int buflen, bufpos;
- char buf[BUFFER_GRANULE];
-};
-
-void bufchain_init(bufchain *ch)
-{
- ch->head = ch->tail = NULL;
- ch->buffersize = 0;
-}
-
-void bufchain_clear(bufchain *ch)
-{
- struct bufchain_granule *b;
- while (ch->head) {
- b = ch->head;
- ch->head = ch->head->next;
- sfree(b);
- }
- ch->tail = NULL;
- ch->buffersize = 0;
-}
-
-int bufchain_size(bufchain *ch)
-{
- return ch->buffersize;
-}
-
-void bufchain_add(bufchain *ch, const void *data, int len)
-{
- const char *buf = (const char *)data;
-
- if (len == 0) return;
-
- ch->buffersize += len;
-
- if (ch->tail && ch->tail->buflen < BUFFER_GRANULE) {
- int copylen = min(len, BUFFER_GRANULE - ch->tail->buflen);
- memcpy(ch->tail->buf + ch->tail->buflen, buf, copylen);
- buf += copylen;
- len -= copylen;
- ch->tail->buflen += copylen;
- }
- while (len > 0) {
- int grainlen = min(len, BUFFER_GRANULE);
- struct bufchain_granule *newbuf;
- newbuf = snew(struct bufchain_granule);
- newbuf->bufpos = 0;
- newbuf->buflen = grainlen;
- memcpy(newbuf->buf, buf, grainlen);
- buf += grainlen;
- len -= grainlen;
- if (ch->tail)
- ch->tail->next = newbuf;
- else
- ch->head = ch->tail = newbuf;
- newbuf->next = NULL;
- ch->tail = newbuf;
- }
-}
-
-void bufchain_consume(bufchain *ch, int len)
-{
- struct bufchain_granule *tmp;
-
- assert(ch->buffersize >= len);
- while (len > 0) {
- int remlen = len;
- assert(ch->head != NULL);
- if (remlen >= ch->head->buflen - ch->head->bufpos) {
- remlen = ch->head->buflen - ch->head->bufpos;
- tmp = ch->head;
- ch->head = tmp->next;
- sfree(tmp);
- if (!ch->head)
- ch->tail = NULL;
- } else
- ch->head->bufpos += remlen;
- ch->buffersize -= remlen;
- len -= remlen;
- }
-}
-
-void bufchain_prefix(bufchain *ch, void **data, int *len)
-{
- *len = ch->head->buflen - ch->head->bufpos;
- *data = ch->head->buf + ch->head->bufpos;
-}
-
-void bufchain_fetch(bufchain *ch, void *data, int len)
-{
- struct bufchain_granule *tmp;
- char *data_c = (char *)data;
-
- tmp = ch->head;
-
- assert(ch->buffersize >= len);
- while (len > 0) {
- int remlen = len;
-
- assert(tmp != NULL);
- if (remlen >= tmp->buflen - tmp->bufpos)
- remlen = tmp->buflen - tmp->bufpos;
- memcpy(data_c, tmp->buf + tmp->bufpos, remlen);
-
- tmp = tmp->next;
- len -= remlen;
- data_c += remlen;
- }
-}
-
-/* ----------------------------------------------------------------------
- * My own versions of malloc, realloc and free. Because I want
- * malloc and realloc to bomb out and exit the program if they run
- * out of memory, realloc to reliably call malloc if passed a NULL
- * pointer, and free to reliably do nothing if passed a NULL
- * pointer. We can also put trace printouts in, if we need to; and
- * we can also replace the allocator with an ElectricFence-like
- * one.
- */
-
-#ifdef MINEFIELD
-void *minefield_c_malloc(size_t size);
-void minefield_c_free(void *p);
-void *minefield_c_realloc(void *p, size_t size);
-#endif
-
-#ifdef MALLOC_LOG
-static FILE *fp = NULL;
-
-static char *mlog_file = NULL;
-static int mlog_line = 0;
-
-void mlog(char *file, int line)
-{
- mlog_file = file;
- mlog_line = line;
- if (!fp) {
- fp = fopen("putty_mem.log", "w");
- setvbuf(fp, NULL, _IONBF, BUFSIZ);
- }
- if (fp)
- fprintf(fp, "%s:%d: ", file, line);
-}
-#endif
-
-void *safemalloc(size_t n, size_t size)
-{
- void *p;
-
- if (n > INT_MAX / size) {
- p = NULL;
- } else {
- size *= n;
- if (size == 0) size = 1;
-#ifdef MINEFIELD
- p = minefield_c_malloc(size);
-#else
- p = malloc(size);
-#endif
- }
-
- if (!p) {
- char str[200];
-#ifdef MALLOC_LOG
- sprintf(str, "Out of memory! (%s:%d, size=%d)",
- mlog_file, mlog_line, size);
- fprintf(fp, "*** %s\n", str);
- fclose(fp);
-#else
- strcpy(str, "Out of memory!");
-#endif
- modalfatalbox(str);
- }
-#ifdef MALLOC_LOG
- if (fp)
- fprintf(fp, "malloc(%d) returns %p\n", size, p);
-#endif
- return p;
-}
-
-void *saferealloc(void *ptr, size_t n, size_t size)
-{
- void *p;
-
- if (n > INT_MAX / size) {
- p = NULL;
- } else {
- size *= n;
- if (!ptr) {
-#ifdef MINEFIELD
- p = minefield_c_malloc(size);
-#else
- p = malloc(size);
-#endif
- } else {
-#ifdef MINEFIELD
- p = minefield_c_realloc(ptr, size);
-#else
- p = realloc(ptr, size);
-#endif
- }
- }
-
- if (!p) {
- char str[200];
-#ifdef MALLOC_LOG
- sprintf(str, "Out of memory! (%s:%d, size=%d)",
- mlog_file, mlog_line, size);
- fprintf(fp, "*** %s\n", str);
- fclose(fp);
-#else
- strcpy(str, "Out of memory!");
-#endif
- modalfatalbox(str);
- }
-#ifdef MALLOC_LOG
- if (fp)
- fprintf(fp, "realloc(%p,%d) returns %p\n", ptr, size, p);
-#endif
- return p;
-}
-
-void safefree(void *ptr)
-{
- if (ptr) {
-#ifdef MALLOC_LOG
- if (fp)
- fprintf(fp, "free(%p)\n", ptr);
-#endif
-#ifdef MINEFIELD
- minefield_c_free(ptr);
-#else
- free(ptr);
-#endif
- }
-#ifdef MALLOC_LOG
- else if (fp)
- fprintf(fp, "freeing null pointer - no action taken\n");
-#endif
-}
-
-/* ----------------------------------------------------------------------
- * Debugging routines.
- */
-
-#ifdef DEBUG
-extern void dputs(char *); /* defined in per-platform *misc.c */
-
-void debug_printf(char *fmt, ...)
-{
- char *buf;
- va_list ap;
-
- va_start(ap, fmt);
- buf = dupvprintf(fmt, ap);
- dputs(buf);
- sfree(buf);
- va_end(ap);
-}
-
-
-void debug_memdump(void *buf, int len, int L)
-{
- int i;
- 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;
- p -= delta;
- len += delta;
- }
- for (; 0 < len; p += 16, len -= 16) {
- dputs(" ");
- if (L)
- debug_printf("%p: ", p);
- strcpy(foo, "................"); /* sixteen dots */
- for (i = 0; i < 16 && i < len; ++i) {
- if (&p[i] < (unsigned char *) buf) {
- dputs(" "); /* 3 spaces */
- foo[i] = ' ';
- } else {
- debug_printf("%c%02.2x",
- &p[i] != (unsigned char *) buf
- && i % 4 ? '.' : ' ', p[i]
- );
- if (p[i] >= ' ' && p[i] <= '~')
- foo[i] = (char) p[i];
- }
- }
- foo[i] = '\0';
- debug_printf("%*s%s\n", (16 - i) * 3 + 2, "", foo);
- }
-}
-
-#endif /* def DEBUG */
-
-/*
- * Determine whether or not a Conf represents a session which can
- * sensibly be launched right now.
- */
-int conf_launchable(Conf *conf)
-{
- if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL)
- return conf_get_str(conf, CONF_serline)[0] != 0;
- else
- return conf_get_str(conf, CONF_host)[0] != 0;
-}
-
-char const *conf_dest(Conf *conf)
-{
- if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL)
- return conf_get_str(conf, CONF_serline);
- else
- return conf_get_str(conf, CONF_host);
-}
+/*
+ * Platform-independent routines shared between all PuTTY programs.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <ctype.h>
+#include <assert.h>
+#include "putty.h"
+
+/*
+ * Parse a string block size specification. This is approximately a
+ * subset of the block size specs supported by GNU fileutils:
+ * "nk" = n kilobytes
+ * "nM" = n megabytes
+ * "nG" = n gigabytes
+ * All numbers are decimal, and suffixes refer to powers of two.
+ * Case-insensitive.
+ */
+unsigned long parse_blocksize(const char *bs)
+{
+ char *suf;
+ unsigned long r = strtoul(bs, &suf, 10);
+ if (*suf != '\0') {
+ while (*suf && isspace((unsigned char)*suf)) suf++;
+ switch (*suf) {
+ case 'k': case 'K':
+ r *= 1024ul;
+ break;
+ case 'm': case 'M':
+ r *= 1024ul * 1024ul;
+ break;
+ case 'g': case 'G':
+ r *= 1024ul * 1024ul * 1024ul;
+ break;
+ case '\0':
+ default:
+ break;
+ }
+ }
+ return r;
+}
+
+/*
+ * Parse a ^C style character specification.
+ * Returns NULL in `next' if we didn't recognise it as a control character,
+ * in which case `c' should be ignored.
+ * The precise current parsing is an oddity inherited from the terminal
+ * answerback-string parsing code. All sequences start with ^; all except
+ * ^<123> are two characters. The ones that are worth keeping are probably:
+ * ^? 127
+ * ^@A-Z[\]^_ 0-31
+ * a-z 1-26
+ * <num> specified by number (decimal, 0octal, 0xHEX)
+ * ~ ^ escape
+ */
+char ctrlparse(char *s, char **next)
+{
+ char c = 0;
+ if (*s != '^') {
+ *next = NULL;
+ } else {
+ s++;
+ if (*s == '\0') {
+ *next = NULL;
+ } else if (*s == '<') {
+ s++;
+ c = (char)strtol(s, next, 0);
+ if ((*next == s) || (**next != '>')) {
+ c = 0;
+ *next = NULL;
+ } else
+ (*next)++;
+ } else if (*s >= 'a' && *s <= 'z') {
+ c = (*s - ('a' - 1));
+ *next = s+1;
+ } else if ((*s >= '@' && *s <= '_') || *s == '?' || (*s & 0x80)) {
+ c = ('@' ^ *s);
+ *next = s+1;
+ } else if (*s == '~') {
+ c = '^';
+ *next = s+1;
+ }
+ }
+ return c;
+}
+
+/*
+ * Find a character in a string, unless it's a colon contained within
+ * square brackets. Used for untangling strings of the form
+ * 'host:port', where host can be an IPv6 literal.
+ *
+ * We provide several variants of this function, with semantics like
+ * various standard string.h functions.
+ */
+static const char *host_strchr_internal(const char *s, const char *set,
+ int first)
+{
+ int brackets = 0;
+ const char *ret = NULL;
+
+ while (1) {
+ if (!*s)
+ return ret;
+
+ if (*s == '[')
+ brackets++;
+ else if (*s == ']' && brackets > 0)
+ brackets--;
+ else if (brackets && *s == ':')
+ /* never match */ ;
+ else if (strchr(set, *s)) {
+ ret = s;
+ if (first)
+ return ret;
+ }
+
+ s++;
+ }
+}
+size_t host_strcspn(const char *s, const char *set)
+{
+ const char *answer = host_strchr_internal(s, set, TRUE);
+ if (answer)
+ return answer - s;
+ else
+ return strlen(s);
+}
+char *host_strchr(const char *s, int c)
+{
+ char set[2];
+ set[0] = c;
+ set[1] = '\0';
+ return (char *) host_strchr_internal(s, set, TRUE);
+}
+char *host_strrchr(const char *s, int c)
+{
+ char set[2];
+ set[0] = c;
+ set[1] = '\0';
+ return (char *) host_strchr_internal(s, set, FALSE);
+}
+
+#ifdef TEST_HOST_STRFOO
+int main(void)
+{
+ int passes = 0, fails = 0;
+
+#define TEST1(func, string, arg2, suffix, result) do \
+ { \
+ const char *str = string; \
+ unsigned ret = func(string, arg2) suffix; \
+ if (ret == result) { \
+ passes++; \
+ } else { \
+ printf("fail: %s(%s,%s)%s = %u, expected %u\n", \
+ #func, #string, #arg2, #suffix, ret, result); \
+ fails++; \
+ } \
+} while (0)
+
+ TEST1(host_strchr, "[1:2:3]:4:5", ':', -str, 7);
+ TEST1(host_strrchr, "[1:2:3]:4:5", ':', -str, 9);
+ TEST1(host_strcspn, "[1:2:3]:4:5", "/:",, 7);
+ TEST1(host_strchr, "[1:2:3]", ':', == NULL, 1);
+ TEST1(host_strrchr, "[1:2:3]", ':', == NULL, 1);
+ TEST1(host_strcspn, "[1:2:3]", "/:",, 7);
+ TEST1(host_strcspn, "[1:2/3]", "/:",, 4);
+ TEST1(host_strcspn, "[1:2:3]/", "/:",, 7);
+
+ printf("passed %d failed %d total %d\n", passes, fails, passes+fails);
+ return fails != 0 ? 1 : 0;
+}
+/* Stubs to stop the rest of this module causing compile failures. */
+void modalfatalbox(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 */
+
+/*
+ * Trim square brackets off the outside of an IPv6 address literal.
+ * Leave all other strings unchanged. Returns a fresh dynamically
+ * allocated string.
+ */
+char *host_strduptrim(const char *s)
+{
+ if (s[0] == '[') {
+ const char *p = s+1;
+ int colons = 0;
+ while (*p && *p != ']') {
+ if (isxdigit((unsigned char)*p))
+ /* OK */;
+ else if (*p == ':')
+ colons++;
+ else
+ break;
+ p++;
+ }
+ if (*p == ']' && !p[1] && colons > 1) {
+ /*
+ * This looks like an IPv6 address literal (hex digits and
+ * at least two colons, contained in square brackets).
+ * Trim off the brackets.
+ */
+ return dupprintf("%.*s", (int)(p - (s+1)), s+1);
+ }
+ }
+
+ /*
+ * Any other shape of string is simply duplicated.
+ */
+ return dupstr(s);
+}
+
+prompts_t *new_prompts(void *frontend)
+{
+ prompts_t *p = snew(prompts_t);
+ p->prompts = NULL;
+ p->n_prompts = 0;
+ p->frontend = frontend;
+ p->data = NULL;
+ p->to_server = TRUE; /* to be on the safe side */
+ p->name = p->instruction = NULL;
+ p->name_reqd = p->instr_reqd = FALSE;
+ return p;
+}
+void add_prompt(prompts_t *p, char *promptstr, int echo)
+{
+ prompt_t *pr = snew(prompt_t);
+ pr->prompt = promptstr;
+ pr->echo = echo;
+ pr->result = NULL;
+ pr->resultsize = 0;
+ p->n_prompts++;
+ p->prompts = sresize(p->prompts, p->n_prompts, prompt_t *);
+ p->prompts[p->n_prompts-1] = pr;
+}
+void prompt_ensure_result_size(prompt_t *pr, int newlen)
+{
+ if ((int)pr->resultsize < newlen) {
+ char *newbuf;
+ newlen = newlen * 5 / 4 + 512; /* avoid too many small allocs */
+
+ /*
+ * We don't use sresize / realloc here, because we will be
+ * storing sensitive stuff like passwords in here, and we want
+ * to make sure that the data doesn't get copied around in
+ * memory without the old copy being destroyed.
+ */
+ newbuf = snewn(newlen, char);
+ memcpy(newbuf, pr->result, pr->resultsize);
+ smemclr(pr->result, pr->resultsize);
+ sfree(pr->result);
+ pr->result = newbuf;
+ pr->resultsize = newlen;
+ }
+}
+void prompt_set_result(prompt_t *pr, const char *newstr)
+{
+ prompt_ensure_result_size(pr, strlen(newstr) + 1);
+ strcpy(pr->result, newstr);
+}
+void free_prompts(prompts_t *p)
+{
+ size_t i;
+ for (i=0; i < p->n_prompts; i++) {
+ prompt_t *pr = p->prompts[i];
+ smemclr(pr->result, pr->resultsize); /* burn the evidence */
+ sfree(pr->result);
+ sfree(pr->prompt);
+ sfree(pr);
+ }
+ sfree(p->prompts);
+ sfree(p->name);
+ sfree(p->instruction);
+ sfree(p);
+}
+
+/* ----------------------------------------------------------------------
+ * String handling routines.
+ */
+
+char *dupstr(const char *s)
+{
+ char *p = NULL;
+ if (s) {
+ int len = strlen(s);
+ p = snewn(len + 1, char);
+ strcpy(p, s);
+ }
+ return p;
+}
+
+/* Allocate the concatenation of N strings. Terminate arg list with NULL. */
+char *dupcat(const char *s1, ...)
+{
+ int len;
+ char *p, *q, *sn;
+ va_list ap;
+
+ len = strlen(s1);
+ va_start(ap, s1);
+ while (1) {
+ sn = va_arg(ap, char *);
+ if (!sn)
+ break;
+ len += strlen(sn);
+ }
+ va_end(ap);
+
+ p = snewn(len + 1, char);
+ strcpy(p, s1);
+ q = p + strlen(p);
+
+ va_start(ap, s1);
+ while (1) {
+ sn = va_arg(ap, char *);
+ if (!sn)
+ break;
+ strcpy(q, sn);
+ q += strlen(q);
+ }
+ va_end(ap);
+
+ return p;
+}
+
+void burnstr(char *string) /* sfree(str), only clear it first */
+{
+ if (string) {
+ smemclr(string, strlen(string));
+ sfree(string);
+ }
+}
+
+int toint(unsigned u)
+{
+ /*
+ * Convert an unsigned to an int, without running into the
+ * undefined behaviour which happens by the strict C standard if
+ * the value overflows. You'd hope that sensible compilers would
+ * do the sensible thing in response to a cast, but actually I
+ * don't trust modern compilers not to do silly things like
+ * assuming that _obviously_ you wouldn't have caused an overflow
+ * and so they can elide an 'if (i < 0)' test immediately after
+ * the cast.
+ *
+ * Sensible compilers ought of course to optimise this entire
+ * function into 'just return the input value'!
+ */
+ if (u <= (unsigned)INT_MAX)
+ return (int)u;
+ else if (u >= (unsigned)INT_MIN) /* wrap in cast _to_ unsigned is OK */
+ return INT_MIN + (int)(u - (unsigned)INT_MIN);
+ else
+ return INT_MIN; /* fallback; should never occur on binary machines */
+}
+
+/*
+ * Do an sprintf(), but into a custom-allocated buffer.
+ *
+ * Currently I'm doing this via vsnprintf. This has worked so far,
+ * but it's not good, because vsnprintf is not available on all
+ * platforms. There's an ifdef to use `_vsnprintf', which seems
+ * to be the local name for it on Windows. Other platforms may
+ * lack it completely, in which case it'll be time to rewrite
+ * this function in a totally different way.
+ *
+ * The only `properly' portable solution I can think of is to
+ * implement my own format string scanner, which figures out an
+ * upper bound for the length of each formatting directive,
+ * allocates the buffer as it goes along, and calls sprintf() to
+ * actually process each directive. If I ever need to actually do
+ * this, some caveats:
+ *
+ * - It's very hard to find a reliable upper bound for
+ * floating-point values. %f, in particular, when supplied with
+ * a number near to the upper or lower limit of representable
+ * numbers, could easily take several hundred characters. It's
+ * probably feasible to predict this statically using the
+ * constants in <float.h>, or even to predict it dynamically by
+ * looking at the exponent of the specific float provided, but
+ * it won't be fun.
+ *
+ * - Don't forget to _check_, after calling sprintf, that it's
+ * used at most the amount of space we had available.
+ *
+ * - Fault any formatting directive we don't fully understand. The
+ * aim here is to _guarantee_ that we never overflow the buffer,
+ * because this is a security-critical function. If we see a
+ * directive we don't know about, we should panic and die rather
+ * than run any risk.
+ */
+char *dupprintf(const char *fmt, ...)
+{
+ char *ret;
+ va_list ap;
+ va_start(ap, fmt);
+ ret = dupvprintf(fmt, ap);
+ va_end(ap);
+ return ret;
+}
+char *dupvprintf(const char *fmt, va_list ap)
+{
+ char *buf;
+ int len, size;
+
+ buf = snewn(512, char);
+ size = 512;
+
+ while (1) {
+#ifdef _WINDOWS
+#define vsnprintf _vsnprintf
+#endif
+#ifdef va_copy
+ /* Use the `va_copy' macro mandated by C99, if present.
+ * XXX some environments may have this as __va_copy() */
+ va_list aq;
+ va_copy(aq, ap);
+ len = vsnprintf(buf, size, fmt, aq);
+ va_end(aq);
+#else
+ /* Ugh. No va_copy macro, so do something nasty.
+ * Technically, you can't reuse a va_list like this: it is left
+ * unspecified whether advancing a va_list pointer modifies its
+ * value or something it points to, so on some platforms calling
+ * vsnprintf twice on the same va_list might fail hideously
+ * (indeed, it has been observed to).
+ * XXX the autoconf manual suggests that using memcpy() will give
+ * "maximum portability". */
+ len = vsnprintf(buf, size, fmt, ap);
+#endif
+ if (len >= 0 && len < size) {
+ /* This is the C99-specified criterion for snprintf to have
+ * been completely successful. */
+ return buf;
+ } else if (len > 0) {
+ /* This is the C99 error condition: the returned length is
+ * the required buffer size not counting the NUL. */
+ size = len + 1;
+ } else {
+ /* This is the pre-C99 glibc error condition: <0 means the
+ * buffer wasn't big enough, so we enlarge it a bit and hope. */
+ size += 512;
+ }
+ buf = sresize(buf, size, char);
+ }
+}
+
+/*
+ * Read an entire line of text from a file. Return a buffer
+ * malloced to be as big as necessary (caller must free).
+ */
+char *fgetline(FILE *fp)
+{
+ char *ret = snewn(512, char);
+ int size = 512, len = 0;
+ while (fgets(ret + len, size - len, fp)) {
+ len += strlen(ret + len);
+ if (ret[len-1] == '\n')
+ break; /* got a newline, we're done */
+ size = len + 512;
+ ret = sresize(ret, size, char);
+ }
+ if (len == 0) { /* first fgets returned NULL */
+ sfree(ret);
+ return NULL;
+ }
+ ret[len] = '\0';
+ return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Base64 encoding routine. This is required in public-key writing
+ * but also in HTTP proxy handling, so it's centralised here.
+ */
+
+void base64_encode_atom(unsigned char *data, int n, char *out)
+{
+ static const char base64_chars[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+ unsigned word;
+
+ word = data[0] << 16;
+ if (n > 1)
+ word |= data[1] << 8;
+ if (n > 2)
+ word |= data[2];
+ out[0] = base64_chars[(word >> 18) & 0x3F];
+ out[1] = base64_chars[(word >> 12) & 0x3F];
+ if (n > 1)
+ out[2] = base64_chars[(word >> 6) & 0x3F];
+ else
+ out[2] = '=';
+ if (n > 2)
+ out[3] = base64_chars[word & 0x3F];
+ else
+ out[3] = '=';
+}
+
+/* ----------------------------------------------------------------------
+ * Generic routines to deal with send buffers: a linked list of
+ * smallish blocks, with the operations
+ *
+ * - add an arbitrary amount of data to the end of the list
+ * - remove the first N bytes from the list
+ * - return a (pointer,length) pair giving some initial data in
+ * the list, suitable for passing to a send or write system
+ * call
+ * - retrieve a larger amount of initial data from the list
+ * - return the current size of the buffer chain in bytes
+ */
+
+#define BUFFER_MIN_GRANULE 512
+
+struct bufchain_granule {
+ struct bufchain_granule *next;
+ char *bufpos, *bufend, *bufmax;
+};
+
+void bufchain_init(bufchain *ch)
+{
+ ch->head = ch->tail = NULL;
+ ch->buffersize = 0;
+}
+
+void bufchain_clear(bufchain *ch)
+{
+ struct bufchain_granule *b;
+ while (ch->head) {
+ b = ch->head;
+ ch->head = ch->head->next;
+ sfree(b);
+ }
+ ch->tail = NULL;
+ ch->buffersize = 0;
+}
+
+int bufchain_size(bufchain *ch)
+{
+ return ch->buffersize;
+}
+
+void bufchain_add(bufchain *ch, const void *data, int len)
+{
+ const char *buf = (const char *)data;
+
+ if (len == 0) return;
+
+ ch->buffersize += len;
+
+ while (len > 0) {
+ if (ch->tail && ch->tail->bufend < ch->tail->bufmax) {
+ int copylen = min(len, ch->tail->bufmax - ch->tail->bufend);
+ memcpy(ch->tail->bufend, buf, copylen);
+ buf += copylen;
+ len -= copylen;
+ ch->tail->bufend += copylen;
+ }
+ if (len > 0) {
+ int grainlen =
+ max(sizeof(struct bufchain_granule) + len, BUFFER_MIN_GRANULE);
+ struct bufchain_granule *newbuf;
+ newbuf = smalloc(grainlen);
+ newbuf->bufpos = newbuf->bufend =
+ (char *)newbuf + sizeof(struct bufchain_granule);
+ newbuf->bufmax = (char *)newbuf + grainlen;
+ newbuf->next = NULL;
+ if (ch->tail)
+ ch->tail->next = newbuf;
+ else
+ ch->head = newbuf;
+ ch->tail = newbuf;
+ }
+ }
+}
+
+void bufchain_consume(bufchain *ch, int len)
+{
+ struct bufchain_granule *tmp;
+
+ assert(ch->buffersize >= len);
+ while (len > 0) {
+ int remlen = len;
+ assert(ch->head != NULL);
+ if (remlen >= ch->head->bufend - ch->head->bufpos) {
+ remlen = ch->head->bufend - ch->head->bufpos;
+ tmp = ch->head;
+ ch->head = tmp->next;
+ if (!ch->head)
+ ch->tail = NULL;
+ sfree(tmp);
+ } else
+ ch->head->bufpos += remlen;
+ ch->buffersize -= remlen;
+ len -= remlen;
+ }
+}
+
+void bufchain_prefix(bufchain *ch, void **data, int *len)
+{
+ *len = ch->head->bufend - ch->head->bufpos;
+ *data = ch->head->bufpos;
+}
+
+void bufchain_fetch(bufchain *ch, void *data, int len)
+{
+ struct bufchain_granule *tmp;
+ char *data_c = (char *)data;
+
+ tmp = ch->head;
+
+ assert(ch->buffersize >= len);
+ while (len > 0) {
+ int remlen = len;
+
+ assert(tmp != NULL);
+ if (remlen >= tmp->bufend - tmp->bufpos)
+ remlen = tmp->bufend - tmp->bufpos;
+ memcpy(data_c, tmp->bufpos, remlen);
+
+ tmp = tmp->next;
+ len -= remlen;
+ data_c += remlen;
+ }
+}
+
+/* ----------------------------------------------------------------------
+ * My own versions of malloc, realloc and free. Because I want
+ * malloc and realloc to bomb out and exit the program if they run
+ * out of memory, realloc to reliably call malloc if passed a NULL
+ * pointer, and free to reliably do nothing if passed a NULL
+ * pointer. We can also put trace printouts in, if we need to; and
+ * we can also replace the allocator with an ElectricFence-like
+ * one.
+ */
+
+#ifdef MINEFIELD
+void *minefield_c_malloc(size_t size);
+void minefield_c_free(void *p);
+void *minefield_c_realloc(void *p, size_t size);
+#endif
+
+#ifdef MALLOC_LOG
+static FILE *fp = NULL;
+
+static char *mlog_file = NULL;
+static int mlog_line = 0;
+
+void mlog(char *file, int line)
+{
+ mlog_file = file;
+ mlog_line = line;
+ if (!fp) {
+ fp = fopen("putty_mem.log", "w");
+ setvbuf(fp, NULL, _IONBF, BUFSIZ);
+ }
+ if (fp)
+ fprintf(fp, "%s:%d: ", file, line);
+}
+#endif
+
+void *safemalloc(size_t n, size_t size)
+{
+ void *p;
+
+ if (n > INT_MAX / size) {
+ p = NULL;
+ } else {
+ size *= n;
+ if (size == 0) size = 1;
+#ifdef MINEFIELD
+ p = minefield_c_malloc(size);
+#else
+ p = malloc(size);
+#endif
+ }
+
+ if (!p) {
+ char str[200];
+#ifdef MALLOC_LOG
+ sprintf(str, "Out of memory! (%s:%d, size=%d)",
+ mlog_file, mlog_line, size);
+ fprintf(fp, "*** %s\n", str);
+ fclose(fp);
+#else
+ strcpy(str, "Out of memory!");
+#endif
+ modalfatalbox(str);
+ }
+#ifdef MALLOC_LOG
+ if (fp)
+ fprintf(fp, "malloc(%d) returns %p\n", size, p);
+#endif
+ return p;
+}
+
+void *saferealloc(void *ptr, size_t n, size_t size)
+{
+ void *p;
+
+ if (n > INT_MAX / size) {
+ p = NULL;
+ } else {
+ size *= n;
+ if (!ptr) {
+#ifdef MINEFIELD
+ p = minefield_c_malloc(size);
+#else
+ p = malloc(size);
+#endif
+ } else {
+#ifdef MINEFIELD
+ p = minefield_c_realloc(ptr, size);
+#else
+ p = realloc(ptr, size);
+#endif
+ }
+ }
+
+ if (!p) {
+ char str[200];
+#ifdef MALLOC_LOG
+ sprintf(str, "Out of memory! (%s:%d, size=%d)",
+ mlog_file, mlog_line, size);
+ fprintf(fp, "*** %s\n", str);
+ fclose(fp);
+#else
+ strcpy(str, "Out of memory!");
+#endif
+ modalfatalbox(str);
+ }
+#ifdef MALLOC_LOG
+ if (fp)
+ fprintf(fp, "realloc(%p,%d) returns %p\n", ptr, size, p);
+#endif
+ return p;
+}
+
+void safefree(void *ptr)
+{
+ if (ptr) {
+#ifdef MALLOC_LOG
+ if (fp)
+ fprintf(fp, "free(%p)\n", ptr);
+#endif
+#ifdef MINEFIELD
+ minefield_c_free(ptr);
+#else
+ free(ptr);
+#endif
+ }
+#ifdef MALLOC_LOG
+ else if (fp)
+ fprintf(fp, "freeing null pointer - no action taken\n");
+#endif
+}
+
+/* ----------------------------------------------------------------------
+ * Debugging routines.
+ */
+
+#ifdef DEBUG
+extern void dputs(char *); /* defined in per-platform *misc.c */
+
+void debug_printf(char *fmt, ...)
+{
+ char *buf;
+ va_list ap;
+
+ va_start(ap, fmt);
+ buf = dupvprintf(fmt, ap);
+ dputs(buf);
+ sfree(buf);
+ va_end(ap);
+}
+
+
+void debug_memdump(void *buf, int len, int L)
+{
+ int i;
+ 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;
+ p -= delta;
+ len += delta;
+ }
+ for (; 0 < len; p += 16, len -= 16) {
+ dputs(" ");
+ if (L)
+ debug_printf("%p: ", p);
+ strcpy(foo, "................"); /* sixteen dots */
+ for (i = 0; i < 16 && i < len; ++i) {
+ if (&p[i] < (unsigned char *) buf) {
+ dputs(" "); /* 3 spaces */
+ foo[i] = ' ';
+ } else {
+ debug_printf("%c%02.2x",
+ &p[i] != (unsigned char *) buf
+ && i % 4 ? '.' : ' ', p[i]
+ );
+ if (p[i] >= ' ' && p[i] <= '~')
+ foo[i] = (char) p[i];
+ }
+ }
+ foo[i] = '\0';
+ debug_printf("%*s%s\n", (16 - i) * 3 + 2, "", foo);
+ }
+}
+
+#endif /* def DEBUG */
+
+/*
+ * Determine whether or not a Conf represents a session which can
+ * sensibly be launched right now.
+ */
+int conf_launchable(Conf *conf)
+{
+ if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL)
+ return conf_get_str(conf, CONF_serline)[0] != 0;
+ else
+ return conf_get_str(conf, CONF_host)[0] != 0;
+}
+
+char const *conf_dest(Conf *conf)
+{
+ if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL)
+ return conf_get_str(conf, CONF_serline);
+ else
+ return conf_get_str(conf, CONF_host);
+}
+
+#ifndef PLATFORM_HAS_SMEMCLR
+/*
+ * Securely wipe memory.
+ *
+ * The actual wiping is no different from what memset would do: the
+ * point of 'securely' is to try to be sure over-clever compilers
+ * won't optimise away memsets on variables that are about to be freed
+ * or go out of scope. See
+ * https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/771-BSI.html
+ *
+ * Some platforms (e.g. Windows) may provide their own version of this
+ * function.
+ */
+void smemclr(void *b, size_t n) {
+ volatile char *vp;
+
+ if (b && n > 0) {
+ /*
+ * Zero out the memory.
+ */
+ memset(b, 0, n);
+
+ /*
+ * Perform a volatile access to the object, forcing the
+ * compiler to admit that the previous memset was important.
+ *
+ * This while loop should in practice run for zero iterations
+ * (since we know we just zeroed the object out), but in
+ * theory (as far as the compiler knows) it might range over
+ * the whole object. (If we had just written, say, '*vp =
+ * *vp;', a compiler could in principle have 'helpfully'
+ * optimised the memset into only zeroing out the first byte.
+ * This should be robust.)
+ */
+ vp = b;
+ while (*vp) vp++;
+ }
+}
+#endif
diff --git a/tools/plink/misc.h b/tools/plink/misc.h
index d6b9dd2e4..d6fea5948 100644..100755
--- a/tools/plink/misc.h
+++ b/tools/plink/misc.h
@@ -1,133 +1,146 @@
-/*
- * Header for misc.c.
- */
-
-#ifndef PUTTY_MISC_H
-#define PUTTY_MISC_H
-
-#include "puttymem.h"
-
-#include <stdio.h> /* for FILE * */
-#include <stdarg.h> /* for va_list */
-#include <time.h> /* for struct tm */
-
-#ifndef FALSE
-#define FALSE 0
-#endif
-#ifndef TRUE
-#define TRUE 1
-#endif
-
-typedef struct Filename Filename;
-typedef struct FontSpec FontSpec;
-
-unsigned long parse_blocksize(const char *bs);
-char ctrlparse(char *s, char **next);
-
-char *dupstr(const char *s);
-char *dupcat(const char *s1, ...);
-char *dupprintf(const char *fmt, ...);
-char *dupvprintf(const char *fmt, va_list ap);
-void burnstr(char *string);
-
-char *fgetline(FILE *fp);
-
-void base64_encode_atom(unsigned char *data, int n, char *out);
-
-struct bufchain_granule;
-typedef struct bufchain_tag {
- struct bufchain_granule *head, *tail;
- int buffersize; /* current amount of buffered data */
-} bufchain;
-
-void bufchain_init(bufchain *ch);
-void bufchain_clear(bufchain *ch);
-int bufchain_size(bufchain *ch);
-void bufchain_add(bufchain *ch, const void *data, int len);
-void bufchain_prefix(bufchain *ch, void **data, int *len);
-void bufchain_consume(bufchain *ch, int len);
-void bufchain_fetch(bufchain *ch, void *data, int len);
-
-struct tm ltime(void);
-
-/*
- * Debugging functions.
- *
- * Output goes to debug.log
- *
- * debug(()) (note the double brackets) is like printf().
- *
- * dmemdump() and dmemdumpl() both do memory dumps. The difference
- * is that dmemdumpl() is more suited for when the memory address is
- * important (say because you'll be recording pointer values later
- * on). dmemdump() is more concise.
- */
-
-#ifdef DEBUG
-void debug_printf(char *fmt, ...);
-void debug_memdump(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);
-#else
-#define debug(x)
-#define dmemdump(buf,len)
-#define dmemdumpl(buf,len)
-#endif
-
-#ifndef lenof
-#define lenof(x) ( (sizeof((x))) / (sizeof(*(x))))
-#endif
-
-#ifndef min
-#define min(x,y) ( (x) < (y) ? (x) : (y) )
-#endif
-#ifndef max
-#define max(x,y) ( (x) > (y) ? (x) : (y) )
-#endif
-
-#define GET_32BIT_LSB_FIRST(cp) \
- (((unsigned long)(unsigned char)(cp)[0]) | \
- ((unsigned long)(unsigned char)(cp)[1] << 8) | \
- ((unsigned long)(unsigned char)(cp)[2] << 16) | \
- ((unsigned long)(unsigned char)(cp)[3] << 24))
-
-#define PUT_32BIT_LSB_FIRST(cp, value) ( \
- (cp)[0] = (unsigned char)((value)&0xff), \
- (cp)[1] = (unsigned char)(((value) >> 8)&0xff), \
- (cp)[2] = (unsigned char)(((value) >> 16)&0xff), \
- (cp)[3] = (unsigned char)(((value) >> 24)&0xff) )
-
-#define GET_16BIT_LSB_FIRST(cp) \
- (((unsigned long)(unsigned char)(cp)[0]) | \
- ((unsigned long)(unsigned char)(cp)[1] << 8))
-
-#define PUT_16BIT_LSB_FIRST(cp, value) ( \
- (cp)[0] = (unsigned char)((value)&0xff), \
- (cp)[1] = (unsigned char)(((value) >> 8)&0xff) )
-
-#define GET_32BIT_MSB_FIRST(cp) \
- (((unsigned long)(unsigned char)(cp)[0] << 24) | \
- ((unsigned long)(unsigned char)(cp)[1] << 16) | \
- ((unsigned long)(unsigned char)(cp)[2] << 8) | \
- ((unsigned long)(unsigned char)(cp)[3]))
-
-#define GET_32BIT(cp) GET_32BIT_MSB_FIRST(cp)
-
-#define PUT_32BIT_MSB_FIRST(cp, value) ( \
- (cp)[0] = (unsigned char)(((value) >> 24)&0xff), \
- (cp)[1] = (unsigned char)(((value) >> 16)&0xff), \
- (cp)[2] = (unsigned char)(((value) >> 8)&0xff), \
- (cp)[3] = (unsigned char)((value)&0xff) )
-
-#define PUT_32BIT(cp, value) PUT_32BIT_MSB_FIRST(cp, value)
-
-#define GET_16BIT_MSB_FIRST(cp) \
- (((unsigned long)(unsigned char)((cp)[0] << 8)&0xff) | \
- ((unsigned long)(unsigned char)((cp)[1])&0xff))
-
-#define PUT_16BIT_MSB_FIRST(cp, value) ( \
- (cp)[0] = (unsigned char)((value) >> 8), \
- (cp)[1] = (unsigned char)(value) )
-
-#endif
+/*
+ * Header for misc.c.
+ */
+
+#ifndef PUTTY_MISC_H
+#define PUTTY_MISC_H
+
+#include "puttymem.h"
+
+#include <stdio.h> /* for FILE * */
+#include <stdarg.h> /* for va_list */
+#include <time.h> /* for struct tm */
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+typedef struct Filename Filename;
+typedef struct FontSpec FontSpec;
+
+unsigned long parse_blocksize(const char *bs);
+char ctrlparse(char *s, char **next);
+
+size_t host_strcspn(const char *s, const char *set);
+char *host_strchr(const char *s, int c);
+char *host_strrchr(const char *s, int c);
+char *host_strduptrim(const char *s);
+
+char *dupstr(const char *s);
+char *dupcat(const char *s1, ...);
+char *dupprintf(const char *fmt, ...)
+#ifdef __GNUC__
+ __attribute__ ((format (printf, 1, 2)))
+#endif
+ ;
+char *dupvprintf(const char *fmt, va_list ap);
+void burnstr(char *string);
+
+int toint(unsigned);
+
+char *fgetline(FILE *fp);
+
+void base64_encode_atom(unsigned char *data, int n, char *out);
+
+struct bufchain_granule;
+typedef struct bufchain_tag {
+ struct bufchain_granule *head, *tail;
+ int buffersize; /* current amount of buffered data */
+} bufchain;
+
+void bufchain_init(bufchain *ch);
+void bufchain_clear(bufchain *ch);
+int bufchain_size(bufchain *ch);
+void bufchain_add(bufchain *ch, const void *data, int len);
+void bufchain_prefix(bufchain *ch, void **data, int *len);
+void bufchain_consume(bufchain *ch, int len);
+void bufchain_fetch(bufchain *ch, void *data, int len);
+
+struct tm ltime(void);
+
+void smemclr(void *b, size_t len);
+
+/*
+ * Debugging functions.
+ *
+ * Output goes to debug.log
+ *
+ * debug(()) (note the double brackets) is like printf().
+ *
+ * dmemdump() and dmemdumpl() both do memory dumps. The difference
+ * is that dmemdumpl() is more suited for when the memory address is
+ * important (say because you'll be recording pointer values later
+ * on). dmemdump() is more concise.
+ */
+
+#ifdef DEBUG
+void debug_printf(char *fmt, ...);
+void debug_memdump(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);
+#else
+#define debug(x)
+#define dmemdump(buf,len)
+#define dmemdumpl(buf,len)
+#endif
+
+#ifndef lenof
+#define lenof(x) ( (sizeof((x))) / (sizeof(*(x))))
+#endif
+
+#ifndef min
+#define min(x,y) ( (x) < (y) ? (x) : (y) )
+#endif
+#ifndef max
+#define max(x,y) ( (x) > (y) ? (x) : (y) )
+#endif
+
+#define GET_32BIT_LSB_FIRST(cp) \
+ (((unsigned long)(unsigned char)(cp)[0]) | \
+ ((unsigned long)(unsigned char)(cp)[1] << 8) | \
+ ((unsigned long)(unsigned char)(cp)[2] << 16) | \
+ ((unsigned long)(unsigned char)(cp)[3] << 24))
+
+#define PUT_32BIT_LSB_FIRST(cp, value) ( \
+ (cp)[0] = (unsigned char)((value)&0xff), \
+ (cp)[1] = (unsigned char)(((value) >> 8)&0xff), \
+ (cp)[2] = (unsigned char)(((value) >> 16)&0xff), \
+ (cp)[3] = (unsigned char)(((value) >> 24)&0xff) )
+
+#define GET_16BIT_LSB_FIRST(cp) \
+ (((unsigned long)(unsigned char)(cp)[0]) | \
+ ((unsigned long)(unsigned char)(cp)[1] << 8))
+
+#define PUT_16BIT_LSB_FIRST(cp, value) ( \
+ (cp)[0] = (unsigned char)((value)&0xff), \
+ (cp)[1] = (unsigned char)(((value) >> 8)&0xff) )
+
+#define GET_32BIT_MSB_FIRST(cp) \
+ (((unsigned long)(unsigned char)(cp)[0] << 24) | \
+ ((unsigned long)(unsigned char)(cp)[1] << 16) | \
+ ((unsigned long)(unsigned char)(cp)[2] << 8) | \
+ ((unsigned long)(unsigned char)(cp)[3]))
+
+#define GET_32BIT(cp) GET_32BIT_MSB_FIRST(cp)
+
+#define PUT_32BIT_MSB_FIRST(cp, value) ( \
+ (cp)[0] = (unsigned char)(((value) >> 24)&0xff), \
+ (cp)[1] = (unsigned char)(((value) >> 16)&0xff), \
+ (cp)[2] = (unsigned char)(((value) >> 8)&0xff), \
+ (cp)[3] = (unsigned char)((value)&0xff) )
+
+#define PUT_32BIT(cp, value) PUT_32BIT_MSB_FIRST(cp, value)
+
+#define GET_16BIT_MSB_FIRST(cp) \
+ (((unsigned long)(unsigned char)((cp)[0] << 8)&0xff) | \
+ ((unsigned long)(unsigned char)((cp)[1])&0xff))
+
+#define PUT_16BIT_MSB_FIRST(cp, value) ( \
+ (cp)[0] = (unsigned char)((value) >> 8), \
+ (cp)[1] = (unsigned char)(value) )
+
+#endif
diff --git a/tools/plink/network.h b/tools/plink/network.h
index eee5452ed..675d24c7c 100644
--- a/tools/plink/network.h
+++ b/tools/plink/network.h
@@ -1,249 +1,241 @@
-/*
- * Networking abstraction in PuTTY.
- *
- * The way this works is: a back end can choose to open any number
- * of sockets - including zero, which might be necessary in some.
- * It can register a bunch of callbacks (most notably for when
- * data is received) for each socket, and it can call the networking
- * abstraction to send data without having to worry about blocking.
- * The stuff behind the abstraction takes care of selects and
- * nonblocking writes and all that sort of painful gubbins.
- */
-
-#ifndef PUTTY_NETWORK_H
-#define PUTTY_NETWORK_H
-
-#ifndef DONE_TYPEDEFS
-#define DONE_TYPEDEFS
-typedef struct conf_tag Conf;
-typedef struct backend_tag Backend;
-typedef struct terminal_tag Terminal;
-#endif
-
-typedef struct SockAddr_tag *SockAddr;
-/* pay attention to levels of indirection */
-typedef struct socket_function_table **Socket;
-typedef struct plug_function_table **Plug;
-
-#ifndef OSSOCKET_DEFINED
-typedef void *OSSocket;
-#endif
-
-struct socket_function_table {
- Plug(*plug) (Socket s, Plug p);
- /* use a different plug (return the old one) */
- /* if p is NULL, it doesn't change the plug */
- /* but it does return the one it's using */
- void (*close) (Socket s);
- int (*write) (Socket s, const char *data, int len);
- int (*write_oob) (Socket s, const char *data, int len);
- void (*write_eof) (Socket s);
- void (*flush) (Socket s);
- void (*set_private_ptr) (Socket s, void *ptr);
- void *(*get_private_ptr) (Socket s);
- void (*set_frozen) (Socket s, int is_frozen);
- /* ignored by tcp, but vital for ssl */
- const char *(*socket_error) (Socket s);
-};
-
-struct plug_function_table {
- void (*log)(Plug p, int type, SockAddr addr, int port,
- const char *error_msg, int error_code);
- /*
- * Passes the client progress reports on the process of setting
- * up the connection.
- *
- * - type==0 means we are about to try to connect to address
- * `addr' (error_msg and error_code are ignored)
- * - type==1 means we have failed to connect to address `addr'
- * (error_msg and error_code are supplied). This is not a
- * fatal error - we may well have other candidate addresses
- * to fall back to. When it _is_ fatal, the closing()
- * function will be called.
- */
- int (*closing)
- (Plug p, const char *error_msg, int error_code, int calling_back);
- /* error_msg is NULL iff it is not an error (ie it closed normally) */
- /* calling_back != 0 iff there is a Plug function */
- /* currently running (would cure the fixme in try_send()) */
- int (*receive) (Plug p, int urgent, char *data, int len);
- /*
- * - urgent==0. `data' points to `len' bytes of perfectly
- * ordinary data.
- *
- * - urgent==1. `data' points to `len' bytes of data,
- * which were read from before an Urgent pointer.
- *
- * - urgent==2. `data' points to `len' bytes of data,
- * the first of which was the one at the Urgent mark.
- */
- void (*sent) (Plug p, int bufsize);
- /*
- * The `sent' function is called when the pending send backlog
- * on a socket is cleared or partially cleared. The new backlog
- * size is passed in the `bufsize' parameter.
- */
- int (*accepting)(Plug p, OSSocket sock);
- /*
- * returns 0 if the host at address addr is a valid host for connecting or error
- */
-};
-
-/* 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,
- 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);
-
-/* platform-dependent callback from new_connection() */
-/* (same caveat about addr as new_connection()) */
-Socket platform_new_connection(SockAddr addr, char *hostname,
- int port, int privport,
- int oobinline, int nodelay, int keepalive,
- Plug plug, Conf *conf);
-
-/* socket functions */
-
-void sk_init(void); /* called once at program startup */
-void sk_cleanup(void); /* called just before program exit */
-
-SockAddr sk_namelookup(const char *host, char **canonicalname, int address_family);
-SockAddr sk_nonamelookup(const char *host);
-void sk_getaddr(SockAddr addr, char *buf, int buflen);
-int sk_hostname_is_local(char *name);
-int sk_address_is_local(SockAddr addr);
-int sk_addrtype(SockAddr addr);
-void sk_addrcopy(SockAddr addr, char *buf);
-void sk_addr_free(SockAddr addr);
-/* sk_addr_dup generates another SockAddr which contains the same data
- * as the original one and can be freed independently. May not actually
- * physically _duplicate_ it: incrementing a reference count so that
- * one more free is required before it disappears is an acceptable
- * implementation. */
-SockAddr sk_addr_dup(SockAddr addr);
-
-/* NB, control of 'addr' is passed via sk_new, which takes responsibility
- * for freeing it, as for new_connection() */
-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_register(OSSocket sock, Plug plug);
-
-#define sk_plug(s,p) (((*s)->plug) (s, p))
-#define sk_close(s) (((*s)->close) (s))
-#define sk_write(s,buf,len) (((*s)->write) (s, buf, len))
-#define sk_write_oob(s,buf,len) (((*s)->write_oob) (s, buf, len))
-#define sk_write_eof(s) (((*s)->write_eof) (s))
-#define sk_flush(s) (((*s)->flush) (s))
-
-#ifdef DEFINE_PLUG_METHOD_MACROS
-#define plug_log(p,type,addr,port,msg,code) (((*p)->log) (p, type, addr, port, msg, code))
-#define plug_closing(p,msg,code,callback) (((*p)->closing) (p, msg, code, callback))
-#define plug_receive(p,urgent,buf,len) (((*p)->receive) (p, urgent, buf, len))
-#define plug_sent(p,bufsize) (((*p)->sent) (p, bufsize))
-#define plug_accepting(p, sock) (((*p)->accepting)(p, sock))
-#endif
-
-/*
- * Each socket abstraction contains a `void *' private field in
- * which the client can keep state.
- *
- * This is perhaps unnecessary now that we have the notion of a plug,
- * but there is some existing code that uses it, so it stays.
- */
-#define sk_set_private_ptr(s, ptr) (((*s)->set_private_ptr) (s, ptr))
-#define sk_get_private_ptr(s) (((*s)->get_private_ptr) (s))
-
-/*
- * Special error values are returned from sk_namelookup and sk_new
- * if there's a problem. These functions extract an error message,
- * or return NULL if there's no problem.
- */
-const char *sk_addr_error(SockAddr addr);
-#define sk_socket_error(s) (((*s)->socket_error) (s))
-
-/*
- * Set the `frozen' flag on a socket. A frozen socket is one in
- * which all READABLE notifications are ignored, so that data is
- * not accepted from the peer until the socket is unfrozen. This
- * exists for two purposes:
- *
- * - Port forwarding: when a local listening port receives a
- * connection, we do not want to receive data from the new
- * socket until we have somewhere to send it. Hence, we freeze
- * the socket until its associated SSH channel is ready; then we
- * unfreeze it and pending data is delivered.
- *
- * - Socket buffering: if an SSH channel (or the whole connection)
- * backs up or presents a zero window, we must freeze the
- * associated local socket in order to avoid unbounded buffer
- * growth.
- */
-#define sk_set_frozen(s, is_frozen) (((*s)->set_frozen) (s, is_frozen))
-
-/*
- * Call this after an operation that might have tried to send on a
- * socket, to clean up any pending network errors.
- */
-void net_pending_errors(void);
-
-/*
- * Simple wrapper on getservbyname(), needed by ssh.c. Returns the
- * port number, in host byte order (suitable for printf and so on).
- * Returns 0 on failure. Any platform not supporting getservbyname
- * can just return 0 - this function is not required to handle
- * numeric port specifications.
- */
-int net_service_lookup(char *service);
-
-/*
- * Look up the local hostname; return value needs freeing.
- * May return NULL.
- */
-char *get_hostname(void);
-
-/********** SSL stuff **********/
-
-/*
- * This section is subject to change, but you get the general idea
- * of what it will eventually look like.
- */
-
-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))
-
-#endif
+/*
+ * Networking abstraction in PuTTY.
+ *
+ * The way this works is: a back end can choose to open any number
+ * of sockets - including zero, which might be necessary in some.
+ * It can register a bunch of callbacks (most notably for when
+ * data is received) for each socket, and it can call the networking
+ * abstraction to send data without having to worry about blocking.
+ * The stuff behind the abstraction takes care of selects and
+ * nonblocking writes and all that sort of painful gubbins.
+ */
+
+#ifndef PUTTY_NETWORK_H
+#define PUTTY_NETWORK_H
+
+#ifndef DONE_TYPEDEFS
+#define DONE_TYPEDEFS
+typedef struct conf_tag Conf;
+typedef struct backend_tag Backend;
+typedef struct terminal_tag Terminal;
+#endif
+
+typedef struct SockAddr_tag *SockAddr;
+/* pay attention to levels of indirection */
+typedef struct socket_function_table **Socket;
+typedef struct plug_function_table **Plug;
+
+struct socket_function_table {
+ Plug(*plug) (Socket s, Plug p);
+ /* use a different plug (return the old one) */
+ /* if p is NULL, it doesn't change the plug */
+ /* but it does return the one it's using */
+ void (*close) (Socket s);
+ int (*write) (Socket s, const char *data, int len);
+ int (*write_oob) (Socket s, const char *data, int len);
+ void (*write_eof) (Socket s);
+ void (*flush) (Socket s);
+ void (*set_frozen) (Socket s, int is_frozen);
+ /* ignored by tcp, but vital for ssl */
+ const char *(*socket_error) (Socket s);
+};
+
+typedef union { void *p; int i; } accept_ctx_t;
+typedef Socket (*accept_fn_t)(accept_ctx_t ctx, Plug plug);
+
+struct plug_function_table {
+ void (*log)(Plug p, int type, SockAddr addr, int port,
+ const char *error_msg, int error_code);
+ /*
+ * Passes the client progress reports on the process of setting
+ * up the connection.
+ *
+ * - type==0 means we are about to try to connect to address
+ * `addr' (error_msg and error_code are ignored)
+ * - type==1 means we have failed to connect to address `addr'
+ * (error_msg and error_code are supplied). This is not a
+ * fatal error - we may well have other candidate addresses
+ * to fall back to. When it _is_ fatal, the closing()
+ * function will be called.
+ */
+ int (*closing)
+ (Plug p, const char *error_msg, int error_code, int calling_back);
+ /* error_msg is NULL iff it is not an error (ie it closed normally) */
+ /* calling_back != 0 iff there is a Plug function */
+ /* currently running (would cure the fixme in try_send()) */
+ int (*receive) (Plug p, int urgent, char *data, int len);
+ /*
+ * - urgent==0. `data' points to `len' bytes of perfectly
+ * ordinary data.
+ *
+ * - urgent==1. `data' points to `len' bytes of data,
+ * which were read from before an Urgent pointer.
+ *
+ * - urgent==2. `data' points to `len' bytes of data,
+ * the first of which was the one at the Urgent mark.
+ */
+ void (*sent) (Plug p, int bufsize);
+ /*
+ * The `sent' function is called when the pending send backlog
+ * on a socket is cleared or partially cleared. The new backlog
+ * size is passed in the `bufsize' parameter.
+ */
+ int (*accepting)(Plug p, accept_fn_t constructor, accept_ctx_t ctx);
+ /*
+ * `accepting' is called only on listener-type sockets, and is
+ * passed a constructor function+context that will create a fresh
+ * Socket describing the connection. It returns nonzero if it
+ * doesn't want the connection for some reason, or 0 on success.
+ */
+};
+
+/* 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,
+ 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);
+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,
+ int port, int privport,
+ int oobinline, int nodelay, int keepalive,
+ Plug plug, Conf *conf);
+
+/* socket functions */
+
+void sk_init(void); /* called once at program startup */
+void sk_cleanup(void); /* called just before program exit */
+
+SockAddr sk_namelookup(const char *host, char **canonicalname, int address_family);
+SockAddr sk_nonamelookup(const char *host);
+void sk_getaddr(SockAddr addr, char *buf, int buflen);
+int sk_addr_needs_port(SockAddr addr);
+int sk_hostname_is_local(const char *name);
+int sk_address_is_local(SockAddr addr);
+int sk_address_is_special_local(SockAddr addr);
+int sk_addrtype(SockAddr addr);
+void sk_addrcopy(SockAddr addr, char *buf);
+void sk_addr_free(SockAddr addr);
+/* sk_addr_dup generates another SockAddr which contains the same data
+ * as the original one and can be freed independently. May not actually
+ * physically _duplicate_ it: incrementing a reference count so that
+ * one more free is required before it disappears is an acceptable
+ * implementation. */
+SockAddr sk_addr_dup(SockAddr addr);
+
+/* NB, control of 'addr' is passed via sk_new, which takes responsibility
+ * for freeing it, as for new_connection() */
+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);
+
+#define sk_plug(s,p) (((*s)->plug) (s, p))
+#define sk_close(s) (((*s)->close) (s))
+#define sk_write(s,buf,len) (((*s)->write) (s, buf, len))
+#define sk_write_oob(s,buf,len) (((*s)->write_oob) (s, buf, len))
+#define sk_write_eof(s) (((*s)->write_eof) (s))
+#define sk_flush(s) (((*s)->flush) (s))
+
+#ifdef DEFINE_PLUG_METHOD_MACROS
+#define plug_log(p,type,addr,port,msg,code) (((*p)->log) (p, type, addr, port, msg, code))
+#define plug_closing(p,msg,code,callback) (((*p)->closing) (p, msg, code, callback))
+#define plug_receive(p,urgent,buf,len) (((*p)->receive) (p, urgent, buf, len))
+#define plug_sent(p,bufsize) (((*p)->sent) (p, bufsize))
+#define plug_accepting(p, constructor, ctx) (((*p)->accepting)(p, constructor, ctx))
+#endif
+
+/*
+ * Special error values are returned from sk_namelookup and sk_new
+ * if there's a problem. These functions extract an error message,
+ * or return NULL if there's no problem.
+ */
+const char *sk_addr_error(SockAddr addr);
+#define sk_socket_error(s) (((*s)->socket_error) (s))
+
+/*
+ * Set the `frozen' flag on a socket. A frozen socket is one in
+ * which all READABLE notifications are ignored, so that data is
+ * not accepted from the peer until the socket is unfrozen. This
+ * exists for two purposes:
+ *
+ * - Port forwarding: when a local listening port receives a
+ * connection, we do not want to receive data from the new
+ * socket until we have somewhere to send it. Hence, we freeze
+ * the socket until its associated SSH channel is ready; then we
+ * unfreeze it and pending data is delivered.
+ *
+ * - Socket buffering: if an SSH channel (or the whole connection)
+ * backs up or presents a zero window, we must freeze the
+ * associated local socket in order to avoid unbounded buffer
+ * growth.
+ */
+#define sk_set_frozen(s, is_frozen) (((*s)->set_frozen) (s, is_frozen))
+
+/*
+ * Simple wrapper on getservbyname(), needed by ssh.c. Returns the
+ * port number, in host byte order (suitable for printf and so on).
+ * Returns 0 on failure. Any platform not supporting getservbyname
+ * can just return 0 - this function is not required to handle
+ * numeric port specifications.
+ */
+int net_service_lookup(char *service);
+
+/*
+ * Look up the local hostname; return value needs freeing.
+ * May return NULL.
+ */
+char *get_hostname(void);
+
+/*
+ * Trivial socket implementation which just stores an error. Found in
+ * errsock.c.
+ */
+Socket new_error_socket(const char *errmsg, Plug plug);
+
+/********** SSL stuff **********/
+
+/*
+ * This section is subject to change, but you get the general idea
+ * of what it will eventually look like.
+ */
+
+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))
+
+#endif
diff --git a/tools/plink/pinger.c b/tools/plink/pinger.c
index 00cd6675e..3f533ae6e 100644
--- a/tools/plink/pinger.c
+++ b/tools/plink/pinger.c
@@ -1,72 +1,72 @@
-/*
- * pinger.c: centralised module that deals with sending TS_PING
- * keepalives, to avoid replicating this code in multiple backends.
- */
-
-#include "putty.h"
-
-struct pinger_tag {
- int interval;
- int pending;
- long next;
- Backend *back;
- void *backhandle;
-};
-
-static void pinger_schedule(Pinger pinger);
-
-static void pinger_timer(void *ctx, long now)
-{
- Pinger pinger = (Pinger)ctx;
-
- if (pinger->pending && now - pinger->next >= 0) {
- pinger->back->special(pinger->backhandle, TS_PING);
- pinger->pending = FALSE;
- pinger_schedule(pinger);
- }
-}
-
-static void pinger_schedule(Pinger pinger)
-{
- int next;
-
- if (!pinger->interval) {
- pinger->pending = FALSE; /* cancel any pending ping */
- return;
- }
-
- next = schedule_timer(pinger->interval * TICKSPERSEC,
- pinger_timer, pinger);
- if (!pinger->pending || next < pinger->next) {
- pinger->next = next;
- pinger->pending = TRUE;
- }
-}
-
-Pinger pinger_new(Conf *conf, Backend *back, void *backhandle)
-{
- Pinger pinger = snew(struct pinger_tag);
-
- pinger->interval = conf_get_int(conf, CONF_ping_interval);
- pinger->pending = FALSE;
- pinger->back = back;
- pinger->backhandle = backhandle;
- pinger_schedule(pinger);
-
- return pinger;
-}
-
-void pinger_reconfig(Pinger pinger, Conf *oldconf, Conf *newconf)
-{
- int newinterval = conf_get_int(newconf, CONF_ping_interval);
- if (conf_get_int(oldconf, CONF_ping_interval) != newinterval) {
- pinger->interval = newinterval;
- pinger_schedule(pinger);
- }
-}
-
-void pinger_free(Pinger pinger)
-{
- expire_timer_context(pinger);
- sfree(pinger);
-}
+/*
+ * pinger.c: centralised module that deals with sending TS_PING
+ * keepalives, to avoid replicating this code in multiple backends.
+ */
+
+#include "putty.h"
+
+struct pinger_tag {
+ int interval;
+ int pending;
+ unsigned long next;
+ Backend *back;
+ void *backhandle;
+};
+
+static void pinger_schedule(Pinger pinger);
+
+static void pinger_timer(void *ctx, unsigned long now)
+{
+ Pinger pinger = (Pinger)ctx;
+
+ if (pinger->pending && now == pinger->next) {
+ pinger->back->special(pinger->backhandle, TS_PING);
+ pinger->pending = FALSE;
+ pinger_schedule(pinger);
+ }
+}
+
+static void pinger_schedule(Pinger pinger)
+{
+ int next;
+
+ if (!pinger->interval) {
+ pinger->pending = FALSE; /* cancel any pending ping */
+ return;
+ }
+
+ next = schedule_timer(pinger->interval * TICKSPERSEC,
+ pinger_timer, pinger);
+ if (!pinger->pending || next < pinger->next) {
+ pinger->next = next;
+ pinger->pending = TRUE;
+ }
+}
+
+Pinger pinger_new(Conf *conf, Backend *back, void *backhandle)
+{
+ Pinger pinger = snew(struct pinger_tag);
+
+ pinger->interval = conf_get_int(conf, CONF_ping_interval);
+ pinger->pending = FALSE;
+ pinger->back = back;
+ pinger->backhandle = backhandle;
+ pinger_schedule(pinger);
+
+ return pinger;
+}
+
+void pinger_reconfig(Pinger pinger, Conf *oldconf, Conf *newconf)
+{
+ int newinterval = conf_get_int(newconf, CONF_ping_interval);
+ if (conf_get_int(oldconf, CONF_ping_interval) != newinterval) {
+ pinger->interval = newinterval;
+ pinger_schedule(pinger);
+ }
+}
+
+void pinger_free(Pinger pinger)
+{
+ expire_timer_context(pinger);
+ sfree(pinger);
+}
diff --git a/tools/plink/portfwd.c b/tools/plink/portfwd.c
index 545dfecd4..e3e63e39a 100644
--- a/tools/plink/portfwd.c
+++ b/tools/plink/portfwd.c
@@ -1,567 +1,636 @@
-/*
- * SSH port forwarding.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#include "putty.h"
-#include "ssh.h"
-
-#ifndef FALSE
-#define FALSE 0
-#endif
-#ifndef TRUE
-#define TRUE 1
-#endif
-
-struct PFwdPrivate {
- const struct plug_function_table *fn;
- /* the above variable absolutely *must* be the first in this structure */
- void *c; /* (channel) data used by ssh.c */
- void *backhandle; /* instance of SSH backend itself */
- /* Note that backhandle need not be filled in if c is non-NULL */
- Socket s;
- int throttled, throttle_override;
- int ready;
- /*
- * `dynamic' does double duty. It's set to 0 for an ordinary
- * forwarded port, and nonzero for SOCKS-style dynamic port
- * forwarding; but it also represents the state of the SOCKS
- * exchange.
- */
- int dynamic;
- /*
- * `hostname' and `port' are the real hostname and port, once
- * we know what we're connecting to; they're unused for this
- * purpose while conducting a local SOCKS exchange, which means
- * we can also use them as a buffer and pointer for reading
- * data from the SOCKS client.
- */
- char hostname[256+8];
- int port;
- /*
- * When doing dynamic port forwarding, we can receive
- * connection data before we are actually able to send it; so
- * we may have to temporarily hold some in a dynamically
- * allocated buffer here.
- */
- void *buffer;
- int buflen;
-};
-
-static void pfd_log(Plug plug, int type, SockAddr addr, int port,
- const char *error_msg, int error_code)
-{
- /* we have to dump these since we have no interface to logging.c */
-}
-
-static int pfd_closing(Plug plug, const char *error_msg, int error_code,
- int calling_back)
-{
- struct PFwdPrivate *pr = (struct PFwdPrivate *) plug;
-
- if (error_msg) {
- /*
- * Socket error. Slam the connection instantly shut.
- */
- sshfwd_unclean_close(pr->c);
- } else {
- /*
- * Ordinary EOF received on socket. Send an EOF on the SSH
- * channel.
- */
- if (pr->c)
- sshfwd_write_eof(pr->c);
- }
-
- return 1;
-}
-
-static int pfd_receive(Plug plug, int urgent, char *data, int len)
-{
- struct PFwdPrivate *pr = (struct PFwdPrivate *) plug;
- if (pr->dynamic) {
- while (len--) {
- /*
- * Throughout SOCKS negotiation, "hostname" is re-used as a
- * random protocol buffer with "port" storing the length.
- */
- if (pr->port >= lenof(pr->hostname)) {
- /* Request too long. */
- if ((pr->dynamic >> 12) == 4) {
- /* Send back a SOCKS 4 error before closing. */
- char data[8];
- memset(data, 0, sizeof(data));
- data[1] = 91; /* generic `request rejected' */
- sk_write(pr->s, data, 8);
- }
- pfd_close(pr->s);
- return 1;
- }
- pr->hostname[pr->port++] = *data++;
-
- /*
- * Now check what's in the buffer to see if it's a
- * valid and complete message in the SOCKS exchange.
- */
- if ((pr->dynamic == 1 || (pr->dynamic >> 12) == 4) &&
- pr->hostname[0] == 4) {
- /*
- * SOCKS 4.
- */
- if (pr->dynamic == 1)
- pr->dynamic = 0x4000;
- if (pr->port < 2) continue;/* don't have command code yet */
- if (pr->hostname[1] != 1) {
- /* Not CONNECT. */
- /* Send back a SOCKS 4 error before closing. */
- char data[8];
- memset(data, 0, sizeof(data));
- data[1] = 91; /* generic `request rejected' */
- sk_write(pr->s, data, 8);
- pfd_close(pr->s);
- return 1;
- }
- if (pr->port <= 8) continue; /* haven't started user/hostname */
- if (pr->hostname[pr->port-1] != 0)
- continue; /* haven't _finished_ user/hostname */
- /*
- * Now we have a full SOCKS 4 request. Check it to
- * see if it's a SOCKS 4A request.
- */
- if (pr->hostname[4] == 0 && pr->hostname[5] == 0 &&
- pr->hostname[6] == 0 && pr->hostname[7] != 0) {
- /*
- * It's SOCKS 4A. So if we haven't yet
- * collected the host name, we should continue
- * waiting for data in order to do so; if we
- * have, we can go ahead.
- */
- int len;
- if (pr->dynamic == 0x4000) {
- pr->dynamic = 0x4001;
- pr->port = 8; /* reset buffer to overwrite name */
- continue;
- }
- pr->hostname[0] = 0; /* reply version code */
- pr->hostname[1] = 90; /* request granted */
- sk_write(pr->s, pr->hostname, 8);
- len= pr->port - 8;
- pr->port = GET_16BIT_MSB_FIRST(pr->hostname+2);
- memmove(pr->hostname, pr->hostname + 8, len);
- goto connect;
- } else {
- /*
- * It's SOCKS 4, which means we should format
- * the IP address into the hostname string and
- * then just go.
- */
- pr->hostname[0] = 0; /* reply version code */
- pr->hostname[1] = 90; /* request granted */
- sk_write(pr->s, pr->hostname, 8);
- pr->port = GET_16BIT_MSB_FIRST(pr->hostname+2);
- sprintf(pr->hostname, "%d.%d.%d.%d",
- (unsigned char)pr->hostname[4],
- (unsigned char)pr->hostname[5],
- (unsigned char)pr->hostname[6],
- (unsigned char)pr->hostname[7]);
- goto connect;
- }
- }
-
- if ((pr->dynamic == 1 || (pr->dynamic >> 12) == 5) &&
- pr->hostname[0] == 5) {
- /*
- * SOCKS 5.
- */
- if (pr->dynamic == 1)
- pr->dynamic = 0x5000;
-
- if (pr->dynamic == 0x5000) {
- int i, method;
- char data[2];
- /*
- * We're receiving a set of method identifiers.
- */
- if (pr->port < 2) continue;/* no method count yet */
- if (pr->port < 2 + (unsigned char)pr->hostname[1])
- continue; /* no methods yet */
- method = 0xFF; /* invalid */
- for (i = 0; i < (unsigned char)pr->hostname[1]; i++)
- if (pr->hostname[2+i] == 0) {
- method = 0;/* no auth */
- break;
- }
- data[0] = 5;
- data[1] = method;
- sk_write(pr->s, data, 2);
- pr->dynamic = 0x5001;
- pr->port = 0; /* re-empty the buffer */
- continue;
- }
-
- if (pr->dynamic == 0x5001) {
- /*
- * We're receiving a SOCKS request.
- */
- unsigned char reply[10]; /* SOCKS5 atyp=1 reply */
- int atype, alen = 0;
-
- /*
- * Pre-fill reply packet.
- * In all cases, we set BND.{HOST,ADDR} to 0.0.0.0:0
- * (atyp=1) in the reply; if we succeed, we don't know
- * the right answers, and if we fail, they should be
- * ignored.
- */
- memset(reply, 0, lenof(reply));
- reply[0] = 5; /* VER */
- reply[3] = 1; /* ATYP = 1 (IPv4, 0.0.0.0:0) */
-
- if (pr->port < 6) continue;
- atype = (unsigned char)pr->hostname[3];
- if (atype == 1) /* IPv4 address */
- alen = 4;
- if (atype == 4) /* IPv6 address */
- alen = 16;
- if (atype == 3) /* domain name has leading length */
- alen = 1 + (unsigned char)pr->hostname[4];
- if (pr->port < 6 + alen) continue;
- if (pr->hostname[1] != 1 || pr->hostname[2] != 0) {
- /* Not CONNECT or reserved field nonzero - error */
- reply[1] = 1; /* generic failure */
- sk_write(pr->s, (char *) reply, lenof(reply));
- pfd_close(pr->s);
- return 1;
- }
- /*
- * Now we have a viable connect request. Switch
- * on atype.
- */
- pr->port = GET_16BIT_MSB_FIRST(pr->hostname+4+alen);
- if (atype == 1) {
- /* REP=0 (success) already */
- sk_write(pr->s, (char *) reply, lenof(reply));
- sprintf(pr->hostname, "%d.%d.%d.%d",
- (unsigned char)pr->hostname[4],
- (unsigned char)pr->hostname[5],
- (unsigned char)pr->hostname[6],
- (unsigned char)pr->hostname[7]);
- goto connect;
- } else if (atype == 3) {
- /* REP=0 (success) already */
- sk_write(pr->s, (char *) reply, lenof(reply));
- memmove(pr->hostname, pr->hostname + 5, alen-1);
- pr->hostname[alen-1] = '\0';
- goto connect;
- } else {
- /*
- * Unknown address type. (FIXME: support IPv6!)
- */
- reply[1] = 8; /* atype not supported */
- sk_write(pr->s, (char *) reply, lenof(reply));
- pfd_close(pr->s);
- return 1;
- }
- }
- }
-
- /*
- * If we get here without either having done `continue'
- * or `goto connect', it must be because there is no
- * sensible interpretation of what's in our buffer. So
- * close the connection rudely.
- */
- pfd_close(pr->s);
- return 1;
- }
- return 1;
-
- /*
- * We come here when we're ready to make an actual
- * connection.
- */
- connect:
-
- /*
- * Freeze the socket until the SSH server confirms the
- * connection.
- */
- sk_set_frozen(pr->s, 1);
-
- pr->c = new_sock_channel(pr->backhandle, pr->s);
- if (pr->c == NULL) {
- pfd_close(pr->s);
- return 1;
- } else {
- /* asks to forward to the specified host/port for this */
- ssh_send_port_open(pr->c, pr->hostname, pr->port, "forwarding");
- }
- pr->dynamic = 0;
-
- /*
- * If there's any data remaining in our current buffer,
- * save it to be sent on pfd_confirm().
- */
- if (len > 0) {
- pr->buffer = snewn(len, char);
- memcpy(pr->buffer, data, len);
- pr->buflen = len;
- }
- }
- if (pr->ready) {
- if (sshfwd_write(pr->c, data, len) > 0) {
- pr->throttled = 1;
- sk_set_frozen(pr->s, 1);
- }
- }
- return 1;
-}
-
-static void pfd_sent(Plug plug, int bufsize)
-{
- struct PFwdPrivate *pr = (struct PFwdPrivate *) plug;
-
- if (pr->c)
- sshfwd_unthrottle(pr->c, bufsize);
-}
-
-/*
- * Called when receiving a PORT OPEN from the server
- */
-const char *pfd_newconnect(Socket *s, char *hostname, int port,
- void *c, Conf *conf, int addressfamily)
-{
- static const struct plug_function_table fn_table = {
- pfd_log,
- pfd_closing,
- pfd_receive,
- pfd_sent,
- NULL
- };
-
- SockAddr addr;
- const char *err;
- char *dummy_realhost;
- struct PFwdPrivate *pr;
-
- /*
- * Try to find host.
- */
- addr = name_lookup(hostname, port, &dummy_realhost, conf, addressfamily);
- if ((err = sk_addr_error(addr)) != NULL) {
- sk_addr_free(addr);
- return err;
- }
-
- /*
- * Open socket.
- */
- pr = snew(struct PFwdPrivate);
- pr->buffer = NULL;
- pr->fn = &fn_table;
- pr->throttled = pr->throttle_override = 0;
- pr->ready = 1;
- pr->c = c;
- pr->backhandle = NULL; /* we shouldn't need this */
- pr->dynamic = 0;
-
- pr->s = *s = new_connection(addr, dummy_realhost, port,
- 0, 1, 0, 0, (Plug) pr, conf);
- if ((err = sk_socket_error(*s)) != NULL) {
- sfree(pr);
- return err;
- }
-
- sk_set_private_ptr(*s, pr);
- return NULL;
-}
-
-/*
- called when someone connects to the local port
- */
-
-static int pfd_accepting(Plug p, OSSocket sock)
-{
- static const struct plug_function_table fn_table = {
- pfd_log,
- pfd_closing,
- pfd_receive,
- pfd_sent,
- NULL
- };
- struct PFwdPrivate *pr, *org;
- Socket s;
- const char *err;
-
- org = (struct PFwdPrivate *)p;
- pr = snew(struct PFwdPrivate);
- pr->buffer = NULL;
- pr->fn = &fn_table;
-
- pr->c = NULL;
- pr->backhandle = org->backhandle;
-
- pr->s = s = sk_register(sock, (Plug) pr);
- if ((err = sk_socket_error(s)) != NULL) {
- sfree(pr);
- return err != NULL;
- }
-
- sk_set_private_ptr(s, pr);
-
- pr->throttled = pr->throttle_override = 0;
- pr->ready = 0;
-
- if (org->dynamic) {
- pr->dynamic = 1;
- pr->port = 0; /* "hostname" buffer is so far empty */
- sk_set_frozen(s, 0); /* we want to receive SOCKS _now_! */
- } else {
- pr->dynamic = 0;
- strcpy(pr->hostname, org->hostname);
- pr->port = org->port;
- pr->c = new_sock_channel(org->backhandle, s);
-
- if (pr->c == NULL) {
- sfree(pr);
- return 1;
- } else {
- /* asks to forward to the specified host/port for this */
- ssh_send_port_open(pr->c, pr->hostname, pr->port, "forwarding");
- }
- }
-
- return 0;
-}
-
-
-/* Add a new forwarding from port -> desthost:destport
- sets up a listener on the local machine on (srcaddr:)port
- */
-const char *pfd_addforward(char *desthost, int destport, char *srcaddr,
- int port, void *backhandle, Conf *conf,
- void **sockdata, int address_family)
-{
- static const struct plug_function_table fn_table = {
- pfd_log,
- pfd_closing,
- pfd_receive, /* should not happen... */
- pfd_sent, /* also should not happen */
- pfd_accepting
- };
-
- const char *err;
- struct PFwdPrivate *pr;
- Socket s;
-
- /*
- * Open socket.
- */
- pr = snew(struct PFwdPrivate);
- pr->buffer = NULL;
- pr->fn = &fn_table;
- pr->c = NULL;
- if (desthost) {
- strcpy(pr->hostname, desthost);
- pr->port = destport;
- pr->dynamic = 0;
- } else
- pr->dynamic = 1;
- pr->throttled = pr->throttle_override = 0;
- pr->ready = 0;
- pr->backhandle = backhandle;
-
- pr->s = s = new_listener(srcaddr, port, (Plug) pr,
- !conf_get_int(conf, CONF_lport_acceptall),
- conf, address_family);
- if ((err = sk_socket_error(s)) != NULL) {
- sfree(pr);
- return err;
- }
-
- sk_set_private_ptr(s, pr);
-
- *sockdata = (void *)s;
-
- return NULL;
-}
-
-void pfd_close(Socket s)
-{
- struct PFwdPrivate *pr;
-
- if (!s)
- return;
-
- pr = (struct PFwdPrivate *) sk_get_private_ptr(s);
-
- sfree(pr->buffer);
- sfree(pr);
-
- sk_close(s);
-}
-
-/*
- * Terminate a listener.
- */
-void pfd_terminate(void *sv)
-{
- pfd_close((Socket)sv);
-}
-
-void pfd_unthrottle(Socket s)
-{
- struct PFwdPrivate *pr;
- if (!s)
- return;
- pr = (struct PFwdPrivate *) sk_get_private_ptr(s);
-
- pr->throttled = 0;
- sk_set_frozen(s, pr->throttled || pr->throttle_override);
-}
-
-void pfd_override_throttle(Socket s, int enable)
-{
- struct PFwdPrivate *pr;
- if (!s)
- return;
- pr = (struct PFwdPrivate *) sk_get_private_ptr(s);
-
- pr->throttle_override = enable;
- sk_set_frozen(s, pr->throttled || pr->throttle_override);
-}
-
-/*
- * Called to send data down the raw connection.
- */
-int pfd_send(Socket s, char *data, int len)
-{
- if (s == NULL)
- return 0;
- return sk_write(s, data, len);
-}
-
-void pfd_send_eof(Socket s)
-{
- sk_write_eof(s);
-}
-
-void pfd_confirm(Socket s)
-{
- struct PFwdPrivate *pr;
-
- if (s == NULL)
- return;
-
- pr = (struct PFwdPrivate *) sk_get_private_ptr(s);
- pr->ready = 1;
- sk_set_frozen(s, 0);
- sk_write(s, NULL, 0);
- if (pr->buffer) {
- sshfwd_write(pr->c, pr->buffer, pr->buflen);
- sfree(pr->buffer);
- pr->buffer = NULL;
- }
-}
+/*
+ * SSH port forwarding.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "ssh.h"
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+struct PortForwarding {
+ const struct plug_function_table *fn;
+ /* the above variable absolutely *must* be the first in this structure */
+ struct ssh_channel *c; /* channel structure held by ssh.c */
+ void *backhandle; /* instance of SSH backend itself */
+ /* Note that backhandle need not be filled in if c is non-NULL */
+ Socket s;
+ int throttled, throttle_override;
+ int ready;
+ /*
+ * `dynamic' does double duty. It's set to 0 for an ordinary
+ * forwarded port, and nonzero for SOCKS-style dynamic port
+ * forwarding; but the nonzero values are also a state machine
+ * tracking where the SOCKS exchange has got to.
+ */
+ int dynamic;
+ /*
+ * `hostname' and `port' are the real hostname and port, once
+ * we know what we're connecting to.
+ */
+ char *hostname;
+ int port;
+ /*
+ * `socksbuf' is the buffer we use to accumulate a SOCKS request.
+ */
+ char *socksbuf;
+ int sockslen, sockssize;
+ /*
+ * When doing dynamic port forwarding, we can receive
+ * connection data before we are actually able to send it; so
+ * we may have to temporarily hold some in a dynamically
+ * allocated buffer here.
+ */
+ void *buffer;
+ int buflen;
+};
+
+struct PortListener {
+ const struct plug_function_table *fn;
+ /* the above variable absolutely *must* be the first in this structure */
+ void *backhandle; /* instance of SSH backend itself */
+ Socket s;
+ /*
+ * `dynamic' is set to 0 for an ordinary forwarded port, and
+ * nonzero for SOCKS-style dynamic port forwarding.
+ */
+ int dynamic;
+ /*
+ * `hostname' and `port' are the real hostname and port, for
+ * ordinary forwardings.
+ */
+ char *hostname;
+ int port;
+};
+
+static struct PortForwarding *new_portfwd_state(void)
+{
+ struct PortForwarding *pf = snew(struct PortForwarding);
+ pf->hostname = NULL;
+ pf->socksbuf = NULL;
+ pf->sockslen = pf->sockssize = 0;
+ pf->buffer = NULL;
+ return pf;
+}
+
+static void free_portfwd_state(struct PortForwarding *pf)
+{
+ if (!pf)
+ return;
+ sfree(pf->hostname);
+ sfree(pf->socksbuf);
+ sfree(pf->buffer);
+ sfree(pf);
+}
+
+static struct PortListener *new_portlistener_state(void)
+{
+ struct PortListener *pl = snew(struct PortListener);
+ pl->hostname = NULL;
+ return pl;
+}
+
+static void free_portlistener_state(struct PortListener *pl)
+{
+ if (!pl)
+ return;
+ sfree(pl->hostname);
+ sfree(pl);
+}
+
+static void pfd_log(Plug plug, int type, SockAddr addr, int port,
+ const char *error_msg, int error_code)
+{
+ /* we have to dump these since we have no interface to logging.c */
+}
+
+static void pfl_log(Plug plug, int type, SockAddr addr, int port,
+ const char *error_msg, int error_code)
+{
+ /* we have to dump these since we have no interface to logging.c */
+}
+
+static int pfd_closing(Plug plug, const char *error_msg, int error_code,
+ int calling_back)
+{
+ struct PortForwarding *pf = (struct PortForwarding *) plug;
+
+ if (error_msg) {
+ /*
+ * Socket error. Slam the connection instantly shut.
+ */
+ if (pf->c) {
+ sshfwd_unclean_close(pf->c, error_msg);
+ } else {
+ /*
+ * We might not have an SSH channel, if a socket error
+ * occurred during SOCKS negotiation. If not, we must
+ * clean ourself up without sshfwd_unclean_close's call
+ * back to pfd_close.
+ */
+ pfd_close(pf);
+ }
+ } else {
+ /*
+ * Ordinary EOF received on socket. Send an EOF on the SSH
+ * channel.
+ */
+ if (pf->c)
+ sshfwd_write_eof(pf->c);
+ }
+
+ return 1;
+}
+
+static int pfl_closing(Plug plug, const char *error_msg, int error_code,
+ int calling_back)
+{
+ struct PortListener *pl = (struct PortListener *) plug;
+ pfl_terminate(pl);
+ return 1;
+}
+
+static int pfd_receive(Plug plug, int urgent, char *data, int len)
+{
+ struct PortForwarding *pf = (struct PortForwarding *) plug;
+ if (pf->dynamic) {
+ while (len--) {
+ if (pf->sockslen >= pf->sockssize) {
+ pf->sockssize = pf->sockslen * 5 / 4 + 256;
+ pf->socksbuf = sresize(pf->socksbuf, pf->sockssize, char);
+ }
+ pf->socksbuf[pf->sockslen++] = *data++;
+
+ /*
+ * Now check what's in the buffer to see if it's a
+ * valid and complete message in the SOCKS exchange.
+ */
+ if ((pf->dynamic == 1 || (pf->dynamic >> 12) == 4) &&
+ pf->socksbuf[0] == 4) {
+ /*
+ * SOCKS 4.
+ */
+ if (pf->dynamic == 1)
+ pf->dynamic = 0x4000;
+ if (pf->sockslen < 2)
+ continue; /* don't have command code yet */
+ if (pf->socksbuf[1] != 1) {
+ /* Not CONNECT. */
+ /* Send back a SOCKS 4 error before closing. */
+ char data[8];
+ memset(data, 0, sizeof(data));
+ data[1] = 91; /* generic `request rejected' */
+ sk_write(pf->s, data, 8);
+ pfd_close(pf);
+ return 1;
+ }
+ if (pf->sockslen <= 8)
+ continue; /* haven't started user/hostname */
+ if (pf->socksbuf[pf->sockslen-1] != 0)
+ continue; /* haven't _finished_ user/hostname */
+ /*
+ * Now we have a full SOCKS 4 request. Check it to
+ * see if it's a SOCKS 4A request.
+ */
+ if (pf->socksbuf[4] == 0 && pf->socksbuf[5] == 0 &&
+ pf->socksbuf[6] == 0 && pf->socksbuf[7] != 0) {
+ /*
+ * It's SOCKS 4A. So if we haven't yet
+ * collected the host name, we should continue
+ * waiting for data in order to do so; if we
+ * have, we can go ahead.
+ */
+ int len;
+ if (pf->dynamic == 0x4000) {
+ pf->dynamic = 0x4001;
+ pf->sockslen = 8; /* reset buffer to overwrite name */
+ continue;
+ }
+ pf->socksbuf[0] = 0; /* reply version code */
+ pf->socksbuf[1] = 90; /* request granted */
+ sk_write(pf->s, pf->socksbuf, 8);
+ len = pf->sockslen - 8;
+ pf->port = GET_16BIT_MSB_FIRST(pf->socksbuf+2);
+ pf->hostname = snewn(len+1, char);
+ pf->hostname[len] = '\0';
+ memcpy(pf->hostname, pf->socksbuf + 8, len);
+ goto connect;
+ } else {
+ /*
+ * It's SOCKS 4, which means we should format
+ * the IP address into the hostname string and
+ * then just go.
+ */
+ pf->socksbuf[0] = 0; /* reply version code */
+ pf->socksbuf[1] = 90; /* request granted */
+ sk_write(pf->s, pf->socksbuf, 8);
+ pf->port = GET_16BIT_MSB_FIRST(pf->socksbuf+2);
+ pf->hostname = dupprintf("%d.%d.%d.%d",
+ (unsigned char)pf->socksbuf[4],
+ (unsigned char)pf->socksbuf[5],
+ (unsigned char)pf->socksbuf[6],
+ (unsigned char)pf->socksbuf[7]);
+ goto connect;
+ }
+ }
+
+ if ((pf->dynamic == 1 || (pf->dynamic >> 12) == 5) &&
+ pf->socksbuf[0] == 5) {
+ /*
+ * SOCKS 5.
+ */
+ if (pf->dynamic == 1)
+ pf->dynamic = 0x5000;
+
+ if (pf->dynamic == 0x5000) {
+ int i, method;
+ char data[2];
+ /*
+ * We're receiving a set of method identifiers.
+ */
+ if (pf->sockslen < 2)
+ continue; /* no method count yet */
+ if (pf->sockslen < 2 + (unsigned char)pf->socksbuf[1])
+ continue; /* no methods yet */
+ method = 0xFF; /* invalid */
+ for (i = 0; i < (unsigned char)pf->socksbuf[1]; i++)
+ if (pf->socksbuf[2+i] == 0) {
+ method = 0;/* no auth */
+ break;
+ }
+ data[0] = 5;
+ data[1] = method;
+ sk_write(pf->s, data, 2);
+ pf->dynamic = 0x5001;
+ pf->sockslen = 0; /* re-empty the buffer */
+ continue;
+ }
+
+ if (pf->dynamic == 0x5001) {
+ /*
+ * We're receiving a SOCKS request.
+ */
+ unsigned char reply[10]; /* SOCKS5 atyp=1 reply */
+ int atype, alen = 0;
+
+ /*
+ * Pre-fill reply packet.
+ * In all cases, we set BND.{HOST,ADDR} to 0.0.0.0:0
+ * (atyp=1) in the reply; if we succeed, we don't know
+ * the right answers, and if we fail, they should be
+ * ignored.
+ */
+ memset(reply, 0, lenof(reply));
+ reply[0] = 5; /* VER */
+ reply[3] = 1; /* ATYP = 1 (IPv4, 0.0.0.0:0) */
+
+ if (pf->sockslen < 6) continue;
+ atype = (unsigned char)pf->socksbuf[3];
+ if (atype == 1) /* IPv4 address */
+ alen = 4;
+ if (atype == 4) /* IPv6 address */
+ alen = 16;
+ if (atype == 3) /* domain name has leading length */
+ alen = 1 + (unsigned char)pf->socksbuf[4];
+ if (pf->sockslen < 6 + alen) continue;
+ if (pf->socksbuf[1] != 1 || pf->socksbuf[2] != 0) {
+ /* Not CONNECT or reserved field nonzero - error */
+ reply[1] = 1; /* generic failure */
+ sk_write(pf->s, (char *) reply, lenof(reply));
+ pfd_close(pf);
+ return 1;
+ }
+ /*
+ * Now we have a viable connect request. Switch
+ * on atype.
+ */
+ pf->port = GET_16BIT_MSB_FIRST(pf->socksbuf+4+alen);
+ if (atype == 1) {
+ /* REP=0 (success) already */
+ sk_write(pf->s, (char *) reply, lenof(reply));
+ pf->hostname = dupprintf("%d.%d.%d.%d",
+ (unsigned char)pf->socksbuf[4],
+ (unsigned char)pf->socksbuf[5],
+ (unsigned char)pf->socksbuf[6],
+ (unsigned char)pf->socksbuf[7]);
+ goto connect;
+ } else if (atype == 3) {
+ /* REP=0 (success) already */
+ sk_write(pf->s, (char *) reply, lenof(reply));
+ pf->hostname = snewn(alen, char);
+ pf->hostname[alen-1] = '\0';
+ memcpy(pf->hostname, pf->socksbuf + 5, alen-1);
+ goto connect;
+ } else {
+ /*
+ * Unknown address type. (FIXME: support IPv6!)
+ */
+ reply[1] = 8; /* atype not supported */
+ sk_write(pf->s, (char *) reply, lenof(reply));
+ pfd_close(pf);
+ return 1;
+ }
+ }
+ }
+
+ /*
+ * If we get here without either having done `continue'
+ * or `goto connect', it must be because there is no
+ * sensible interpretation of what's in our buffer. So
+ * close the connection rudely.
+ */
+ pfd_close(pf);
+ return 1;
+ }
+ return 1;
+
+ /*
+ * We come here when we're ready to make an actual
+ * connection.
+ */
+ connect:
+ sfree(pf->socksbuf);
+ pf->socksbuf = NULL;
+
+ /*
+ * Freeze the socket until the SSH server confirms the
+ * connection.
+ */
+ sk_set_frozen(pf->s, 1);
+
+ pf->c = new_sock_channel(pf->backhandle, pf);
+ if (pf->c == NULL) {
+ pfd_close(pf);
+ return 1;
+ } else {
+ /* asks to forward to the specified host/port for this */
+ ssh_send_port_open(pf->c, pf->hostname, pf->port, "forwarding");
+ }
+ pf->dynamic = 0;
+
+ /*
+ * If there's any data remaining in our current buffer,
+ * save it to be sent on pfd_confirm().
+ */
+ if (len > 0) {
+ pf->buffer = snewn(len, char);
+ memcpy(pf->buffer, data, len);
+ pf->buflen = len;
+ }
+ }
+ if (pf->ready) {
+ if (sshfwd_write(pf->c, data, len) > 0) {
+ pf->throttled = 1;
+ sk_set_frozen(pf->s, 1);
+ }
+ }
+ return 1;
+}
+
+static void pfd_sent(Plug plug, int bufsize)
+{
+ struct PortForwarding *pf = (struct PortForwarding *) plug;
+
+ if (pf->c)
+ sshfwd_unthrottle(pf->c, bufsize);
+}
+
+/*
+ * Called when receiving a PORT OPEN from the server to make a
+ * connection to a destination host.
+ *
+ * On success, returns NULL and fills in *pf_ret. On error, returns a
+ * dynamically allocated error message string.
+ */
+char *pfd_connect(struct PortForwarding **pf_ret, char *hostname,int port,
+ void *c, Conf *conf, int addressfamily)
+{
+ static const struct plug_function_table fn_table = {
+ pfd_log,
+ pfd_closing,
+ pfd_receive,
+ pfd_sent,
+ NULL
+ };
+
+ SockAddr addr;
+ const char *err;
+ char *dummy_realhost;
+ struct PortForwarding *pf;
+
+ /*
+ * Try to find host.
+ */
+ addr = name_lookup(hostname, port, &dummy_realhost, conf, addressfamily);
+ if ((err = sk_addr_error(addr)) != NULL) {
+ char *err_ret = dupstr(err);
+ sk_addr_free(addr);
+ sfree(dummy_realhost);
+ return err_ret;
+ }
+
+ /*
+ * Open socket.
+ */
+ pf = *pf_ret = new_portfwd_state();
+ pf->fn = &fn_table;
+ pf->throttled = pf->throttle_override = 0;
+ pf->ready = 1;
+ pf->c = c;
+ pf->backhandle = NULL; /* we shouldn't need this */
+ pf->dynamic = 0;
+
+ pf->s = new_connection(addr, dummy_realhost, port,
+ 0, 1, 0, 0, (Plug) pf, conf);
+ sfree(dummy_realhost);
+ if ((err = sk_socket_error(pf->s)) != NULL) {
+ char *err_ret = dupstr(err);
+ sk_close(pf->s);
+ free_portfwd_state(pf);
+ *pf_ret = NULL;
+ return err_ret;
+ }
+
+ return NULL;
+}
+
+/*
+ called when someone connects to the local port
+ */
+
+static int pfl_accepting(Plug p, accept_fn_t constructor, accept_ctx_t ctx)
+{
+ static const struct plug_function_table fn_table = {
+ pfd_log,
+ pfd_closing,
+ pfd_receive,
+ pfd_sent,
+ NULL
+ };
+ struct PortForwarding *pf;
+ struct PortListener *pl;
+ Socket s;
+ const char *err;
+
+ pl = (struct PortListener *)p;
+ pf = new_portfwd_state();
+ pf->fn = &fn_table;
+
+ pf->c = NULL;
+ pf->backhandle = pl->backhandle;
+
+ pf->s = s = constructor(ctx, (Plug) pf);
+ if ((err = sk_socket_error(s)) != NULL) {
+ free_portfwd_state(pf);
+ return err != NULL;
+ }
+
+ pf->throttled = pf->throttle_override = 0;
+ pf->ready = 0;
+
+ if (pl->dynamic) {
+ pf->dynamic = 1;
+ pf->port = 0; /* "hostname" buffer is so far empty */
+ sk_set_frozen(s, 0); /* we want to receive SOCKS _now_! */
+ } else {
+ pf->dynamic = 0;
+ pf->hostname = dupstr(pl->hostname);
+ pf->port = pl->port;
+ pf->c = new_sock_channel(pl->backhandle, pf);
+
+ if (pf->c == NULL) {
+ free_portfwd_state(pf);
+ return 1;
+ } else {
+ /* asks to forward to the specified host/port for this */
+ ssh_send_port_open(pf->c, pf->hostname, pf->port, "forwarding");
+ }
+ }
+
+ return 0;
+}
+
+
+/*
+ * Add a new port-forwarding listener from srcaddr:port -> desthost:destport.
+ *
+ * On success, returns NULL and fills in *pl_ret. On error, returns a
+ * dynamically allocated error message string.
+ */
+char *pfl_listen(char *desthost, int destport, char *srcaddr,
+ int port, void *backhandle, Conf *conf,
+ struct PortListener **pl_ret, int address_family)
+{
+ static const struct plug_function_table fn_table = {
+ pfl_log,
+ pfl_closing,
+ NULL, /* recv */
+ NULL, /* send */
+ pfl_accepting
+ };
+
+ const char *err;
+ struct PortListener *pl;
+
+ /*
+ * Open socket.
+ */
+ pl = *pl_ret = new_portlistener_state();
+ pl->fn = &fn_table;
+ if (desthost) {
+ pl->hostname = dupstr(desthost);
+ pl->port = destport;
+ pl->dynamic = 0;
+ } else
+ pl->dynamic = 1;
+ pl->backhandle = backhandle;
+
+ pl->s = new_listener(srcaddr, port, (Plug) pl,
+ !conf_get_int(conf, CONF_lport_acceptall),
+ conf, address_family);
+ if ((err = sk_socket_error(pl->s)) != NULL) {
+ char *err_ret = dupstr(err);
+ sk_close(pl->s);
+ free_portlistener_state(pl);
+ *pl_ret = NULL;
+ return err_ret;
+ }
+
+ return NULL;
+}
+
+void pfd_close(struct PortForwarding *pf)
+{
+ if (!pf)
+ return;
+
+ sk_close(pf->s);
+ free_portfwd_state(pf);
+}
+
+/*
+ * Terminate a listener.
+ */
+void pfl_terminate(struct PortListener *pl)
+{
+ if (!pl)
+ return;
+
+ sk_close(pl->s);
+ free_portlistener_state(pl);
+}
+
+void pfd_unthrottle(struct PortForwarding *pf)
+{
+ if (!pf)
+ return;
+
+ pf->throttled = 0;
+ sk_set_frozen(pf->s, pf->throttled || pf->throttle_override);
+}
+
+void pfd_override_throttle(struct PortForwarding *pf, int enable)
+{
+ if (!pf)
+ return;
+
+ pf->throttle_override = enable;
+ sk_set_frozen(pf->s, pf->throttled || pf->throttle_override);
+}
+
+/*
+ * Called to send data down the raw connection.
+ */
+int pfd_send(struct PortForwarding *pf, char *data, int len)
+{
+ if (pf == NULL)
+ return 0;
+ return sk_write(pf->s, data, len);
+}
+
+void pfd_send_eof(struct PortForwarding *pf)
+{
+ sk_write_eof(pf->s);
+}
+
+void pfd_confirm(struct PortForwarding *pf)
+{
+ if (pf == NULL)
+ return;
+
+ pf->ready = 1;
+ sk_set_frozen(pf->s, 0);
+ sk_write(pf->s, NULL, 0);
+ if (pf->buffer) {
+ sshfwd_write(pf->c, pf->buffer, pf->buflen);
+ sfree(pf->buffer);
+ pf->buffer = NULL;
+ }
+}
diff --git a/tools/plink/proxy.c b/tools/plink/proxy.c
index fcc81475b..e2201c643 100644
--- a/tools/plink/proxy.c
+++ b/tools/plink/proxy.c
@@ -1,1507 +1,1509 @@
-/*
- * Network proxy abstraction in PuTTY
- *
- * A proxy layer, if necessary, wedges itself between the network
- * code and the higher level backend.
- */
-
-#include <assert.h>
-#include <ctype.h>
-#include <string.h>
-
-#define DEFINE_PLUG_METHOD_MACROS
-#include "putty.h"
-#include "network.h"
-#include "proxy.h"
-
-#define do_proxy_dns(conf) \
- (conf_get_int(conf, CONF_proxy_dns) == FORCE_ON || \
- (conf_get_int(conf, CONF_proxy_dns) == AUTO && \
- conf_get_int(conf, CONF_proxy_type) != PROXY_SOCKS4))
-
-/*
- * Call this when proxy negotiation is complete, so that this
- * socket can begin working normally.
- */
-void proxy_activate (Proxy_Socket p)
-{
- void *data;
- int len;
- long output_before, output_after;
-
- p->state = PROXY_STATE_ACTIVE;
-
- /* we want to ignore new receive events until we have sent
- * all of our buffered receive data.
- */
- sk_set_frozen(p->sub_socket, 1);
-
- /* how many bytes of output have we buffered? */
- output_before = bufchain_size(&p->pending_oob_output_data) +
- bufchain_size(&p->pending_output_data);
- /* and keep track of how many bytes do not get sent. */
- output_after = 0;
-
- /* send buffered OOB writes */
- while (bufchain_size(&p->pending_oob_output_data) > 0) {
- bufchain_prefix(&p->pending_oob_output_data, &data, &len);
- output_after += sk_write_oob(p->sub_socket, data, len);
- bufchain_consume(&p->pending_oob_output_data, len);
- }
-
- /* send buffered normal writes */
- while (bufchain_size(&p->pending_output_data) > 0) {
- bufchain_prefix(&p->pending_output_data, &data, &len);
- output_after += sk_write(p->sub_socket, data, len);
- bufchain_consume(&p->pending_output_data, len);
- }
-
- /* if we managed to send any data, let the higher levels know. */
- if (output_after < output_before)
- plug_sent(p->plug, output_after);
-
- /* if we were asked to flush the output during
- * the proxy negotiation process, do so now.
- */
- if (p->pending_flush) sk_flush(p->sub_socket);
-
- /* if we have a pending EOF to send, send it */
- if (p->pending_eof) sk_write_eof(p->sub_socket);
-
- /* if the backend wanted the socket unfrozen, try to unfreeze.
- * our set_frozen handler will flush buffered receive data before
- * unfreezing the actual underlying socket.
- */
- if (!p->freeze)
- sk_set_frozen((Socket)p, 0);
-}
-
-/* basic proxy socket functions */
-
-static Plug sk_proxy_plug (Socket s, Plug p)
-{
- Proxy_Socket ps = (Proxy_Socket) s;
- Plug ret = ps->plug;
- if (p)
- ps->plug = p;
- return ret;
-}
-
-static void sk_proxy_close (Socket s)
-{
- Proxy_Socket ps = (Proxy_Socket) s;
-
- sk_close(ps->sub_socket);
- sk_addr_free(ps->remote_addr);
- sfree(ps);
-}
-
-static int sk_proxy_write (Socket s, const char *data, int len)
-{
- Proxy_Socket ps = (Proxy_Socket) s;
-
- if (ps->state != PROXY_STATE_ACTIVE) {
- bufchain_add(&ps->pending_output_data, data, len);
- return bufchain_size(&ps->pending_output_data);
- }
- return sk_write(ps->sub_socket, data, len);
-}
-
-static int sk_proxy_write_oob (Socket s, const char *data, int len)
-{
- Proxy_Socket ps = (Proxy_Socket) s;
-
- if (ps->state != PROXY_STATE_ACTIVE) {
- bufchain_clear(&ps->pending_output_data);
- bufchain_clear(&ps->pending_oob_output_data);
- bufchain_add(&ps->pending_oob_output_data, data, len);
- return len;
- }
- return sk_write_oob(ps->sub_socket, data, len);
-}
-
-static void sk_proxy_write_eof (Socket s)
-{
- Proxy_Socket ps = (Proxy_Socket) s;
-
- if (ps->state != PROXY_STATE_ACTIVE) {
- ps->pending_eof = 1;
- return;
- }
- sk_write_eof(ps->sub_socket);
-}
-
-static void sk_proxy_flush (Socket s)
-{
- Proxy_Socket ps = (Proxy_Socket) s;
-
- if (ps->state != PROXY_STATE_ACTIVE) {
- ps->pending_flush = 1;
- return;
- }
- sk_flush(ps->sub_socket);
-}
-
-static void sk_proxy_set_private_ptr (Socket s, void *ptr)
-{
- Proxy_Socket ps = (Proxy_Socket) s;
- sk_set_private_ptr(ps->sub_socket, ptr);
-}
-
-static void * sk_proxy_get_private_ptr (Socket s)
-{
- Proxy_Socket ps = (Proxy_Socket) s;
- return sk_get_private_ptr(ps->sub_socket);
-}
-
-static void sk_proxy_set_frozen (Socket s, int is_frozen)
-{
- Proxy_Socket ps = (Proxy_Socket) s;
-
- if (ps->state != PROXY_STATE_ACTIVE) {
- ps->freeze = is_frozen;
- return;
- }
-
- /* handle any remaining buffered recv data first */
- if (bufchain_size(&ps->pending_input_data) > 0) {
- ps->freeze = is_frozen;
-
- /* loop while we still have buffered data, and while we are
- * unfrozen. the plug_receive call in the loop could result
- * in a call back into this function refreezing the socket,
- * so we have to check each time.
- */
- while (!ps->freeze && bufchain_size(&ps->pending_input_data) > 0) {
- void *data;
- char databuf[512];
- int len;
- bufchain_prefix(&ps->pending_input_data, &data, &len);
- if (len > lenof(databuf))
- len = lenof(databuf);
- memcpy(databuf, data, len);
- bufchain_consume(&ps->pending_input_data, len);
- plug_receive(ps->plug, 0, databuf, len);
- }
-
- /* if we're still frozen, we'll have to wait for another
- * call from the backend to finish unbuffering the data.
- */
- if (ps->freeze) return;
- }
-
- sk_set_frozen(ps->sub_socket, is_frozen);
-}
-
-static const char * sk_proxy_socket_error (Socket s)
-{
- Proxy_Socket ps = (Proxy_Socket) s;
- if (ps->error != NULL || ps->sub_socket == NULL) {
- return ps->error;
- }
- return sk_socket_error(ps->sub_socket);
-}
-
-/* basic proxy plug functions */
-
-static void plug_proxy_log(Plug plug, int type, SockAddr addr, int port,
- const char *error_msg, int error_code)
-{
- Proxy_Plug pp = (Proxy_Plug) plug;
- Proxy_Socket ps = pp->proxy_socket;
-
- plug_log(ps->plug, type, addr, port, error_msg, error_code);
-}
-
-static int plug_proxy_closing (Plug p, const char *error_msg,
- int error_code, int calling_back)
-{
- Proxy_Plug pp = (Proxy_Plug) p;
- Proxy_Socket ps = pp->proxy_socket;
-
- if (ps->state != PROXY_STATE_ACTIVE) {
- ps->closing_error_msg = error_msg;
- ps->closing_error_code = error_code;
- ps->closing_calling_back = calling_back;
- return ps->negotiate(ps, PROXY_CHANGE_CLOSING);
- }
- return plug_closing(ps->plug, error_msg,
- error_code, calling_back);
-}
-
-static int plug_proxy_receive (Plug p, int urgent, char *data, int len)
-{
- Proxy_Plug pp = (Proxy_Plug) p;
- Proxy_Socket ps = pp->proxy_socket;
-
- if (ps->state != PROXY_STATE_ACTIVE) {
- /* we will lose the urgentness of this data, but since most,
- * if not all, of this data will be consumed by the negotiation
- * process, hopefully it won't affect the protocol above us
- */
- bufchain_add(&ps->pending_input_data, data, len);
- ps->receive_urgent = urgent;
- ps->receive_data = data;
- ps->receive_len = len;
- return ps->negotiate(ps, PROXY_CHANGE_RECEIVE);
- }
- return plug_receive(ps->plug, urgent, data, len);
-}
-
-static void plug_proxy_sent (Plug p, int bufsize)
-{
- Proxy_Plug pp = (Proxy_Plug) p;
- Proxy_Socket ps = pp->proxy_socket;
-
- if (ps->state != PROXY_STATE_ACTIVE) {
- ps->sent_bufsize = bufsize;
- ps->negotiate(ps, PROXY_CHANGE_SENT);
- return;
- }
- plug_sent(ps->plug, bufsize);
-}
-
-static int plug_proxy_accepting (Plug p, OSSocket sock)
-{
- Proxy_Plug pp = (Proxy_Plug) p;
- Proxy_Socket ps = pp->proxy_socket;
-
- if (ps->state != PROXY_STATE_ACTIVE) {
- ps->accepting_sock = sock;
- return ps->negotiate(ps, PROXY_CHANGE_ACCEPTING);
- }
- return plug_accepting(ps->plug, sock);
-}
-
-/*
- * This function can accept a NULL pointer as `addr', in which case
- * it will only check the host name.
- */
-static int proxy_for_destination (SockAddr addr, char *hostname, int port,
- Conf *conf)
-{
- int s = 0, e = 0;
- char hostip[64];
- int hostip_len, hostname_len;
- const char *exclude_list;
-
- /*
- * Check the host name and IP against the hard-coded
- * representations of `localhost'.
- */
- if (!conf_get_int(conf, CONF_even_proxy_localhost) &&
- (sk_hostname_is_local(hostname) ||
- (addr && sk_address_is_local(addr))))
- return 0; /* do not proxy */
-
- /* we want a string representation of the IP address for comparisons */
- if (addr) {
- sk_getaddr(addr, hostip, 64);
- hostip_len = strlen(hostip);
- } else
- hostip_len = 0; /* placate gcc; shouldn't be required */
-
- hostname_len = strlen(hostname);
-
- exclude_list = conf_get_str(conf, CONF_proxy_exclude_list);
-
- /* now parse the exclude list, and see if either our IP
- * or hostname matches anything in it.
- */
-
- while (exclude_list[s]) {
- while (exclude_list[s] &&
- (isspace((unsigned char)exclude_list[s]) ||
- exclude_list[s] == ',')) s++;
-
- if (!exclude_list[s]) break;
-
- e = s;
-
- while (exclude_list[e] &&
- (isalnum((unsigned char)exclude_list[e]) ||
- exclude_list[e] == '-' ||
- exclude_list[e] == '.' ||
- exclude_list[e] == '*')) e++;
-
- if (exclude_list[s] == '*') {
- /* wildcard at beginning of entry */
-
- if ((addr && strnicmp(hostip + hostip_len - (e - s - 1),
- exclude_list + s + 1, e - s - 1) == 0) ||
- strnicmp(hostname + hostname_len - (e - s - 1),
- exclude_list + s + 1, e - s - 1) == 0)
- return 0; /* IP/hostname range excluded. do not use proxy. */
-
- } else if (exclude_list[e-1] == '*') {
- /* wildcard at end of entry */
-
- if ((addr && strnicmp(hostip, exclude_list + s, e - s - 1) == 0) ||
- strnicmp(hostname, exclude_list + s, e - s - 1) == 0)
- return 0; /* IP/hostname range excluded. do not use proxy. */
-
- } else {
- /* no wildcard at either end, so let's try an absolute
- * match (ie. a specific IP)
- */
-
- if (addr && strnicmp(hostip, exclude_list + s, e - s) == 0)
- return 0; /* IP/hostname excluded. do not use proxy. */
- if (strnicmp(hostname, exclude_list + s, e - s) == 0)
- return 0; /* IP/hostname excluded. do not use proxy. */
- }
-
- s = e;
-
- /* Make sure we really have reached the next comma or end-of-string */
- while (exclude_list[s] &&
- !isspace((unsigned char)exclude_list[s]) &&
- exclude_list[s] != ',') s++;
- }
-
- /* no matches in the exclude list, so use the proxy */
- return 1;
-}
-
-SockAddr name_lookup(char *host, int port, char **canonicalname,
- Conf *conf, int addressfamily)
-{
- if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE &&
- do_proxy_dns(conf) &&
- proxy_for_destination(NULL, host, port, conf)) {
- *canonicalname = dupstr(host);
- return sk_nonamelookup(host);
- }
-
- return sk_namelookup(host, canonicalname, addressfamily);
-}
-
-Socket new_connection(SockAddr addr, char *hostname,
- int port, int privport,
- int oobinline, int nodelay, int keepalive,
- Plug plug, Conf *conf)
-{
- static const struct socket_function_table socket_fn_table = {
- sk_proxy_plug,
- sk_proxy_close,
- sk_proxy_write,
- sk_proxy_write_oob,
- sk_proxy_write_eof,
- sk_proxy_flush,
- sk_proxy_set_private_ptr,
- sk_proxy_get_private_ptr,
- sk_proxy_set_frozen,
- sk_proxy_socket_error
- };
-
- static const struct plug_function_table plug_fn_table = {
- plug_proxy_log,
- plug_proxy_closing,
- plug_proxy_receive,
- plug_proxy_sent,
- plug_proxy_accepting
- };
-
- if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE &&
- proxy_for_destination(addr, hostname, port, conf))
- {
- Proxy_Socket ret;
- Proxy_Plug pplug;
- SockAddr proxy_addr;
- char *proxy_canonical_name;
- Socket sret;
- int type;
-
- if ((sret = platform_new_connection(addr, hostname, port, privport,
- oobinline, nodelay, keepalive,
- plug, conf)) !=
- NULL)
- return sret;
-
- ret = snew(struct Socket_proxy_tag);
- ret->fn = &socket_fn_table;
- ret->conf = conf_copy(conf);
- ret->plug = plug;
- ret->remote_addr = addr; /* will need to be freed on close */
- ret->remote_port = port;
-
- ret->error = NULL;
- ret->pending_flush = 0;
- ret->pending_eof = 0;
- ret->freeze = 0;
-
- bufchain_init(&ret->pending_input_data);
- bufchain_init(&ret->pending_output_data);
- bufchain_init(&ret->pending_oob_output_data);
-
- ret->sub_socket = NULL;
- ret->state = PROXY_STATE_NEW;
- ret->negotiate = NULL;
-
- type = conf_get_int(conf, CONF_proxy_type);
- if (type == PROXY_HTTP) {
- ret->negotiate = proxy_http_negotiate;
- } else if (type == PROXY_SOCKS4) {
- ret->negotiate = proxy_socks4_negotiate;
- } else if (type == PROXY_SOCKS5) {
- ret->negotiate = proxy_socks5_negotiate;
- } else if (type == PROXY_TELNET) {
- ret->negotiate = proxy_telnet_negotiate;
- } else {
- ret->error = "Proxy error: Unknown proxy method";
- return (Socket) ret;
- }
-
- /* 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;
-
- /* look-up proxy */
- proxy_addr = sk_namelookup(conf_get_str(conf, CONF_proxy_host),
- &proxy_canonical_name,
- conf_get_int(conf, CONF_addressfamily));
- if (sk_addr_error(proxy_addr) != NULL) {
- ret->error = "Proxy error: Unable to resolve proxy host name";
- return (Socket)ret;
- }
- sfree(proxy_canonical_name);
-
- /* create the actual socket we will be using,
- * connected to our proxy server and port.
- */
- ret->sub_socket = sk_new(proxy_addr,
- conf_get_int(conf, CONF_proxy_port),
- privport, oobinline,
- nodelay, keepalive, (Plug) pplug);
- if (sk_socket_error(ret->sub_socket) != NULL)
- return (Socket) ret;
-
- /* start the proxy negotiation process... */
- sk_set_frozen(ret->sub_socket, 0);
- ret->negotiate(ret, PROXY_CHANGE_NEW);
-
- return (Socket) ret;
- }
-
- /* no proxy, so just return the direct socket */
- 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)
-{
- /* TODO: SOCKS (and potentially others) support inbound
- * TODO: connections via the proxy. support them.
- */
-
- return sk_newlistener(srcaddr, port, plug, local_host_only, addressfamily);
-}
-
-/* ----------------------------------------------------------------------
- * HTTP CONNECT proxy type.
- */
-
-static int get_line_end (char * data, int len)
-{
- int off = 0;
-
- while (off < len)
- {
- if (data[off] == '\n') {
- /* we have a newline */
- off++;
-
- /* is that the only thing on this line? */
- if (off <= 2) return off;
-
- /* if not, then there is the possibility that this header
- * continues onto the next line, if it starts with a space
- * or a tab.
- */
-
- if (off + 1 < len &&
- data[off+1] != ' ' &&
- data[off+1] != '\t') return off;
-
- /* the line does continue, so we have to keep going
- * until we see an the header's "real" end of line.
- */
- off++;
- }
-
- off++;
- }
-
- return -1;
-}
-
-int proxy_http_negotiate (Proxy_Socket p, int change)
-{
- if (p->state == PROXY_STATE_NEW) {
- /* we are just beginning the proxy negotiate process,
- * so we'll send off the initial bits of the request.
- * for this proxy method, it's just a simple HTTP
- * request
- */
- char *buf, dest[512];
- char *username, *password;
-
- sk_getaddr(p->remote_addr, dest, lenof(dest));
-
- buf = dupprintf("CONNECT %s:%i HTTP/1.1\r\nHost: %s:%i\r\n",
- dest, p->remote_port, dest, p->remote_port);
- sk_write(p->sub_socket, buf, strlen(buf));
- sfree(buf);
-
- username = conf_get_str(p->conf, CONF_proxy_username);
- password = conf_get_str(p->conf, CONF_proxy_password);
- if (username[0] || password[0]) {
- char *buf, *buf2;
- int i, j, len;
- buf = dupprintf("%s:%s", username, password);
- len = strlen(buf);
- buf2 = snewn(len * 4 / 3 + 100, char);
- sprintf(buf2, "Proxy-Authorization: Basic ");
- for (i = 0, j = strlen(buf2); i < len; i += 3, j += 4)
- base64_encode_atom((unsigned char *)(buf+i),
- (len-i > 3 ? 3 : len-i), buf2+j);
- strcpy(buf2+j, "\r\n");
- sk_write(p->sub_socket, buf2, strlen(buf2));
- sfree(buf);
- sfree(buf2);
- }
-
- sk_write(p->sub_socket, "\r\n", 2);
-
- p->state = 1;
- return 0;
- }
-
- if (change == PROXY_CHANGE_CLOSING) {
- /* if our proxy negotiation process involves closing and opening
- * new sockets, then we would want to intercept this closing
- * callback when we were expecting it. if we aren't anticipating
- * a socket close, then some error must have occurred. we'll
- * just pass those errors up to the backend.
- */
- return plug_closing(p->plug, p->closing_error_msg,
- p->closing_error_code,
- p->closing_calling_back);
- }
-
- if (change == PROXY_CHANGE_SENT) {
- /* some (or all) of what we wrote to the proxy was sent.
- * we don't do anything new, however, until we receive the
- * proxy's response. we might want to set a timer so we can
- * timeout the proxy negotiation after a while...
- */
- return 0;
- }
-
- if (change == PROXY_CHANGE_ACCEPTING) {
- /* we should _never_ see this, as we are using our socket to
- * connect to a proxy, not accepting inbound connections.
- * what should we do? close the socket with an appropriate
- * error message?
- */
- return plug_accepting(p->plug, p->accepting_sock);
- }
-
- if (change == PROXY_CHANGE_RECEIVE) {
- /* we have received data from the underlying socket, which
- * we'll need to parse, process, and respond to appropriately.
- */
-
- char *data, *datap;
- int len;
- int eol;
-
- if (p->state == 1) {
-
- int min_ver, maj_ver, status;
-
- /* get the status line */
- len = bufchain_size(&p->pending_input_data);
- assert(len > 0); /* or we wouldn't be here */
- data = snewn(len+1, char);
- bufchain_fetch(&p->pending_input_data, data, len);
- /*
- * We must NUL-terminate this data, because Windows
- * sscanf appears to require a NUL at the end of the
- * string because it strlens it _first_. Sigh.
- */
- data[len] = '\0';
-
- eol = get_line_end(data, len);
- if (eol < 0) {
- sfree(data);
- return 1;
- }
-
- status = -1;
- /* We can't rely on whether the %n incremented the sscanf return */
- if (sscanf((char *)data, "HTTP/%i.%i %n",
- &maj_ver, &min_ver, &status) < 2 || status == -1) {
- plug_closing(p->plug, "Proxy error: HTTP response was absent",
- PROXY_ERROR_GENERAL, 0);
- sfree(data);
- return 1;
- }
-
- /* remove the status line from the input buffer. */
- bufchain_consume(&p->pending_input_data, eol);
- if (data[status] != '2') {
- /* error */
- char *buf;
- data[eol] = '\0';
- while (eol > status &&
- (data[eol-1] == '\r' || data[eol-1] == '\n'))
- data[--eol] = '\0';
- buf = dupprintf("Proxy error: %s", data+status);
- plug_closing(p->plug, buf, PROXY_ERROR_GENERAL, 0);
- sfree(buf);
- sfree(data);
- return 1;
- }
-
- sfree(data);
-
- p->state = 2;
- }
-
- if (p->state == 2) {
-
- /* get headers. we're done when we get a
- * header of length 2, (ie. just "\r\n")
- */
-
- len = bufchain_size(&p->pending_input_data);
- assert(len > 0); /* or we wouldn't be here */
- data = snewn(len, char);
- datap = data;
- bufchain_fetch(&p->pending_input_data, data, len);
-
- eol = get_line_end(datap, len);
- if (eol < 0) {
- sfree(data);
- return 1;
- }
- while (eol > 2)
- {
- bufchain_consume(&p->pending_input_data, eol);
- datap += eol;
- len -= eol;
- eol = get_line_end(datap, len);
- }
-
- if (eol == 2) {
- /* we're done */
- bufchain_consume(&p->pending_input_data, 2);
- proxy_activate(p);
- /* proxy activate will have dealt with
- * whatever is left of the buffer */
- sfree(data);
- return 1;
- }
-
- sfree(data);
- return 1;
- }
- }
-
- plug_closing(p->plug, "Proxy error: unexpected proxy error",
- PROXY_ERROR_UNEXPECTED, 0);
- return 1;
-}
-
-/* ----------------------------------------------------------------------
- * SOCKS proxy type.
- */
-
-/* SOCKS version 4 */
-int proxy_socks4_negotiate (Proxy_Socket p, int change)
-{
- if (p->state == PROXY_CHANGE_NEW) {
-
- /* request format:
- * version number (1 byte) = 4
- * command code (1 byte)
- * 1 = CONNECT
- * 2 = BIND
- * dest. port (2 bytes) [network order]
- * dest. address (4 bytes)
- * user ID (variable length, null terminated string)
- */
-
- int length, type, namelen;
- char *command, addr[4], hostname[512];
- char *username;
-
- type = sk_addrtype(p->remote_addr);
- if (type == ADDRTYPE_IPV6) {
- plug_closing(p->plug, "Proxy error: SOCKS version 4 does"
- " not support IPv6", PROXY_ERROR_GENERAL, 0);
- return 1;
- } else if (type == ADDRTYPE_IPV4) {
- namelen = 0;
- sk_addrcopy(p->remote_addr, addr);
- } else { /* type == ADDRTYPE_NAME */
- assert(type == ADDRTYPE_NAME);
- sk_getaddr(p->remote_addr, hostname, lenof(hostname));
- namelen = strlen(hostname) + 1; /* include the NUL */
- addr[0] = addr[1] = addr[2] = 0;
- addr[3] = 1;
- }
-
- username = conf_get_str(p->conf, CONF_proxy_username);
- length = strlen(username) + namelen + 9;
- command = snewn(length, char);
- strcpy(command + 8, username);
-
- command[0] = 4; /* version 4 */
- command[1] = 1; /* CONNECT command */
-
- /* port */
- command[2] = (char) (p->remote_port >> 8) & 0xff;
- command[3] = (char) p->remote_port & 0xff;
-
- /* address */
- memcpy(command + 4, addr, 4);
-
- /* hostname */
- memcpy(command + 8 + strlen(username) + 1,
- hostname, namelen);
-
- sk_write(p->sub_socket, command, length);
- sfree(username);
- sfree(command);
-
- p->state = 1;
- return 0;
- }
-
- if (change == PROXY_CHANGE_CLOSING) {
- /* if our proxy negotiation process involves closing and opening
- * new sockets, then we would want to intercept this closing
- * callback when we were expecting it. if we aren't anticipating
- * a socket close, then some error must have occurred. we'll
- * just pass those errors up to the backend.
- */
- return plug_closing(p->plug, p->closing_error_msg,
- p->closing_error_code,
- p->closing_calling_back);
- }
-
- if (change == PROXY_CHANGE_SENT) {
- /* some (or all) of what we wrote to the proxy was sent.
- * we don't do anything new, however, until we receive the
- * proxy's response. we might want to set a timer so we can
- * timeout the proxy negotiation after a while...
- */
- return 0;
- }
-
- if (change == PROXY_CHANGE_ACCEPTING) {
- /* we should _never_ see this, as we are using our socket to
- * connect to a proxy, not accepting inbound connections.
- * what should we do? close the socket with an appropriate
- * error message?
- */
- return plug_accepting(p->plug, p->accepting_sock);
- }
-
- if (change == PROXY_CHANGE_RECEIVE) {
- /* we have received data from the underlying socket, which
- * we'll need to parse, process, and respond to appropriately.
- */
-
- if (p->state == 1) {
- /* response format:
- * version number (1 byte) = 4
- * reply code (1 byte)
- * 90 = request granted
- * 91 = request rejected or failed
- * 92 = request rejected due to lack of IDENTD on client
- * 93 = request rejected due to difference in user ID
- * (what we sent vs. what IDENTD said)
- * dest. port (2 bytes)
- * dest. address (4 bytes)
- */
-
- char data[8];
-
- if (bufchain_size(&p->pending_input_data) < 8)
- return 1; /* not got anything yet */
-
- /* get the response */
- bufchain_fetch(&p->pending_input_data, data, 8);
-
- if (data[0] != 0) {
- plug_closing(p->plug, "Proxy error: SOCKS proxy responded with "
- "unexpected reply code version",
- PROXY_ERROR_GENERAL, 0);
- return 1;
- }
-
- if (data[1] != 90) {
-
- switch (data[1]) {
- case 92:
- plug_closing(p->plug, "Proxy error: SOCKS server wanted IDENTD on client",
- PROXY_ERROR_GENERAL, 0);
- break;
- case 93:
- plug_closing(p->plug, "Proxy error: Username and IDENTD on client don't agree",
- PROXY_ERROR_GENERAL, 0);
- break;
- case 91:
- default:
- plug_closing(p->plug, "Proxy error: Error while communicating with proxy",
- PROXY_ERROR_GENERAL, 0);
- break;
- }
-
- return 1;
- }
- bufchain_consume(&p->pending_input_data, 8);
-
- /* we're done */
- proxy_activate(p);
- /* proxy activate will have dealt with
- * whatever is left of the buffer */
- return 1;
- }
- }
-
- plug_closing(p->plug, "Proxy error: unexpected proxy error",
- PROXY_ERROR_UNEXPECTED, 0);
- return 1;
-}
-
-/* SOCKS version 5 */
-int proxy_socks5_negotiate (Proxy_Socket p, int change)
-{
- if (p->state == PROXY_CHANGE_NEW) {
-
- /* initial command:
- * version number (1 byte) = 5
- * number of available authentication methods (1 byte)
- * available authentication methods (1 byte * previous value)
- * authentication methods:
- * 0x00 = no authentication
- * 0x01 = GSSAPI
- * 0x02 = username/password
- * 0x03 = CHAP
- */
-
- char command[5];
- char *username, *password;
- int len;
-
- command[0] = 5; /* version 5 */
- username = conf_get_str(p->conf, CONF_proxy_username);
- password = conf_get_str(p->conf, CONF_proxy_password);
- if (username[0] || password[0]) {
- command[2] = 0x00; /* no authentication */
- len = 3;
- proxy_socks5_offerencryptedauth (command, &len);
- command[len++] = 0x02; /* username/password */
- command[1] = len - 2; /* Number of methods supported */
- } else {
- command[1] = 1; /* one methods supported: */
- command[2] = 0x00; /* no authentication */
- len = 3;
- }
-
- sk_write(p->sub_socket, command, len);
-
- p->state = 1;
- return 0;
- }
-
- if (change == PROXY_CHANGE_CLOSING) {
- /* if our proxy negotiation process involves closing and opening
- * new sockets, then we would want to intercept this closing
- * callback when we were expecting it. if we aren't anticipating
- * a socket close, then some error must have occurred. we'll
- * just pass those errors up to the backend.
- */
- return plug_closing(p->plug, p->closing_error_msg,
- p->closing_error_code,
- p->closing_calling_back);
- }
-
- if (change == PROXY_CHANGE_SENT) {
- /* some (or all) of what we wrote to the proxy was sent.
- * we don't do anything new, however, until we receive the
- * proxy's response. we might want to set a timer so we can
- * timeout the proxy negotiation after a while...
- */
- return 0;
- }
-
- if (change == PROXY_CHANGE_ACCEPTING) {
- /* we should _never_ see this, as we are using our socket to
- * connect to a proxy, not accepting inbound connections.
- * what should we do? close the socket with an appropriate
- * error message?
- */
- return plug_accepting(p->plug, p->accepting_sock);
- }
-
- if (change == PROXY_CHANGE_RECEIVE) {
- /* we have received data from the underlying socket, which
- * we'll need to parse, process, and respond to appropriately.
- */
-
- if (p->state == 1) {
-
- /* initial response:
- * version number (1 byte) = 5
- * authentication method (1 byte)
- * authentication methods:
- * 0x00 = no authentication
- * 0x01 = GSSAPI
- * 0x02 = username/password
- * 0x03 = CHAP
- * 0xff = no acceptable methods
- */
- char data[2];
-
- if (bufchain_size(&p->pending_input_data) < 2)
- return 1; /* not got anything yet */
-
- /* get the response */
- bufchain_fetch(&p->pending_input_data, data, 2);
-
- if (data[0] != 5) {
- plug_closing(p->plug, "Proxy error: SOCKS proxy returned unexpected version",
- PROXY_ERROR_GENERAL, 0);
- return 1;
- }
-
- if (data[1] == 0x00) p->state = 2; /* no authentication needed */
- else if (data[1] == 0x01) p->state = 4; /* GSSAPI authentication */
- else if (data[1] == 0x02) p->state = 5; /* username/password authentication */
- else if (data[1] == 0x03) p->state = 6; /* CHAP authentication */
- else {
- plug_closing(p->plug, "Proxy error: SOCKS proxy did not accept our authentication",
- PROXY_ERROR_GENERAL, 0);
- return 1;
- }
- bufchain_consume(&p->pending_input_data, 2);
- }
-
- if (p->state == 7) {
-
- /* password authentication reply format:
- * version number (1 bytes) = 1
- * reply code (1 byte)
- * 0 = succeeded
- * >0 = failed
- */
- char data[2];
-
- if (bufchain_size(&p->pending_input_data) < 2)
- return 1; /* not got anything yet */
-
- /* get the response */
- bufchain_fetch(&p->pending_input_data, data, 2);
-
- if (data[0] != 1) {
- plug_closing(p->plug, "Proxy error: SOCKS password "
- "subnegotiation contained wrong version number",
- PROXY_ERROR_GENERAL, 0);
- return 1;
- }
-
- if (data[1] != 0) {
-
- plug_closing(p->plug, "Proxy error: SOCKS proxy refused"
- " password authentication",
- PROXY_ERROR_GENERAL, 0);
- return 1;
- }
-
- bufchain_consume(&p->pending_input_data, 2);
- p->state = 2; /* now proceed as authenticated */
- }
-
- if (p->state == 8) {
- int ret;
- ret = proxy_socks5_handlechap(p);
- if (ret) return ret;
- }
-
- if (p->state == 2) {
-
- /* request format:
- * version number (1 byte) = 5
- * command code (1 byte)
- * 1 = CONNECT
- * 2 = BIND
- * 3 = UDP ASSOCIATE
- * reserved (1 byte) = 0x00
- * address type (1 byte)
- * 1 = IPv4
- * 3 = domainname (first byte has length, no terminating null)
- * 4 = IPv6
- * dest. address (variable)
- * dest. port (2 bytes) [network order]
- */
-
- char command[512];
- int len;
- int type;
-
- type = sk_addrtype(p->remote_addr);
- if (type == ADDRTYPE_IPV4) {
- len = 10; /* 4 hdr + 4 addr + 2 trailer */
- command[3] = 1; /* IPv4 */
- sk_addrcopy(p->remote_addr, command+4);
- } else if (type == ADDRTYPE_IPV6) {
- len = 22; /* 4 hdr + 16 addr + 2 trailer */
- command[3] = 4; /* IPv6 */
- sk_addrcopy(p->remote_addr, command+4);
- } else {
- assert(type == ADDRTYPE_NAME);
- command[3] = 3;
- sk_getaddr(p->remote_addr, command+5, 256);
- command[4] = strlen(command+5);
- len = 7 + command[4]; /* 4 hdr, 1 len, N addr, 2 trailer */
- }
-
- command[0] = 5; /* version 5 */
- command[1] = 1; /* CONNECT command */
- command[2] = 0x00;
-
- /* port */
- command[len-2] = (char) (p->remote_port >> 8) & 0xff;
- command[len-1] = (char) p->remote_port & 0xff;
-
- sk_write(p->sub_socket, command, len);
-
- p->state = 3;
- return 1;
- }
-
- if (p->state == 3) {
-
- /* reply format:
- * version number (1 bytes) = 5
- * reply code (1 byte)
- * 0 = succeeded
- * 1 = general SOCKS server failure
- * 2 = connection not allowed by ruleset
- * 3 = network unreachable
- * 4 = host unreachable
- * 5 = connection refused
- * 6 = TTL expired
- * 7 = command not supported
- * 8 = address type not supported
- * reserved (1 byte) = x00
- * address type (1 byte)
- * 1 = IPv4
- * 3 = domainname (first byte has length, no terminating null)
- * 4 = IPv6
- * server bound address (variable)
- * server bound port (2 bytes) [network order]
- */
- char data[5];
- int len;
-
- /* First 5 bytes of packet are enough to tell its length. */
- if (bufchain_size(&p->pending_input_data) < 5)
- return 1; /* not got anything yet */
-
- /* get the response */
- bufchain_fetch(&p->pending_input_data, data, 5);
-
- if (data[0] != 5) {
- plug_closing(p->plug, "Proxy error: SOCKS proxy returned wrong version number",
- PROXY_ERROR_GENERAL, 0);
- return 1;
- }
-
- if (data[1] != 0) {
- char buf[256];
-
- strcpy(buf, "Proxy error: ");
-
- switch (data[1]) {
- case 1: strcat(buf, "General SOCKS server failure"); break;
- case 2: strcat(buf, "Connection not allowed by ruleset"); break;
- case 3: strcat(buf, "Network unreachable"); break;
- case 4: strcat(buf, "Host unreachable"); break;
- case 5: strcat(buf, "Connection refused"); break;
- case 6: strcat(buf, "TTL expired"); break;
- case 7: strcat(buf, "Command not supported"); break;
- case 8: strcat(buf, "Address type not supported"); break;
- default: sprintf(buf+strlen(buf),
- "Unrecognised SOCKS error code %d",
- data[1]);
- break;
- }
- plug_closing(p->plug, buf, PROXY_ERROR_GENERAL, 0);
-
- return 1;
- }
-
- /*
- * Eat the rest of the reply packet.
- */
- len = 6; /* first 4 bytes, last 2 */
- switch (data[3]) {
- case 1: len += 4; break; /* IPv4 address */
- case 4: len += 16; break;/* IPv6 address */
- case 3: len += (unsigned char)data[4]; break; /* domain name */
- default:
- plug_closing(p->plug, "Proxy error: SOCKS proxy returned "
- "unrecognised address format",
- PROXY_ERROR_GENERAL, 0);
- return 1;
- }
- if (bufchain_size(&p->pending_input_data) < len)
- return 1; /* not got whole reply yet */
- bufchain_consume(&p->pending_input_data, len);
-
- /* we're done */
- proxy_activate(p);
- return 1;
- }
-
- if (p->state == 4) {
- /* TODO: Handle GSSAPI authentication */
- plug_closing(p->plug, "Proxy error: We don't support GSSAPI authentication",
- PROXY_ERROR_GENERAL, 0);
- return 1;
- }
-
- if (p->state == 5) {
- char *username = conf_get_str(p->conf, CONF_proxy_username);
- char *password = conf_get_str(p->conf, CONF_proxy_password);
- if (username[0] || password[0]) {
- char userpwbuf[255 + 255 + 3];
- int ulen, plen;
- ulen = strlen(username);
- if (ulen > 255) ulen = 255; if (ulen < 1) ulen = 1;
- plen = strlen(password);
- if (plen > 255) plen = 255; if (plen < 1) plen = 1;
- userpwbuf[0] = 1; /* version number of subnegotiation */
- userpwbuf[1] = ulen;
- memcpy(userpwbuf+2, username, ulen);
- userpwbuf[ulen+2] = plen;
- memcpy(userpwbuf+ulen+3, password, plen);
- sk_write(p->sub_socket, userpwbuf, ulen + plen + 3);
- p->state = 7;
- } else
- plug_closing(p->plug, "Proxy error: Server chose "
- "username/password authentication but we "
- "didn't offer it!",
- PROXY_ERROR_GENERAL, 0);
- return 1;
- }
-
- if (p->state == 6) {
- int ret;
- ret = proxy_socks5_selectchap(p);
- if (ret) return ret;
- }
-
- }
-
- plug_closing(p->plug, "Proxy error: Unexpected proxy error",
- PROXY_ERROR_UNEXPECTED, 0);
- return 1;
-}
-
-/* ----------------------------------------------------------------------
- * `Telnet' proxy type.
- *
- * (This is for ad-hoc proxies where you connect to the proxy's
- * telnet port and send a command such as `connect host port'. The
- * command is configurable, since this proxy type is typically not
- * standardised or at all well-defined.)
- */
-
-char *format_telnet_command(SockAddr addr, int port, Conf *conf)
-{
- char *fmt = conf_get_str(conf, CONF_proxy_telnet_command);
- char *ret = NULL;
- int retlen = 0, retsize = 0;
- int so = 0, eo = 0;
-#define ENSURE(n) do { \
- if (retsize < retlen + n) { \
- retsize = retlen + n + 512; \
- ret = sresize(ret, retsize, char); \
- } \
-} while (0)
-
- /* we need to escape \\, \%, \r, \n, \t, \x??, \0???,
- * %%, %host, %port, %user, and %pass
- */
-
- while (fmt[eo] != 0) {
-
- /* scan forward until we hit end-of-line,
- * or an escape character (\ or %) */
- while (fmt[eo] != 0 && fmt[eo] != '%' && fmt[eo] != '\\')
- eo++;
-
- /* if we hit eol, break out of our escaping loop */
- if (fmt[eo] == 0) break;
-
- /* if there was any unescaped text before the escape
- * character, send that now */
- if (eo != so) {
- ENSURE(eo - so);
- memcpy(ret + retlen, fmt + so, eo - so);
- retlen += eo - so;
- }
-
- so = eo++;
-
- /* if the escape character was the last character of
- * the line, we'll just stop and send it. */
- if (fmt[eo] == 0) break;
-
- if (fmt[so] == '\\') {
-
- /* we recognize \\, \%, \r, \n, \t, \x??.
- * anything else, we just send unescaped (including the \).
- */
-
- switch (fmt[eo]) {
-
- case '\\':
- ENSURE(1);
- ret[retlen++] = '\\';
- eo++;
- break;
-
- case '%':
- ENSURE(1);
- ret[retlen++] = '%';
- eo++;
- break;
-
- case 'r':
- ENSURE(1);
- ret[retlen++] = '\r';
- eo++;
- break;
-
- case 'n':
- ENSURE(1);
- ret[retlen++] = '\n';
- eo++;
- break;
-
- case 't':
- ENSURE(1);
- ret[retlen++] = '\t';
- eo++;
- break;
-
- case 'x':
- case 'X':
- {
- /* escaped hexadecimal value (ie. \xff) */
- unsigned char v = 0;
- int i = 0;
-
- for (;;) {
- eo++;
- if (fmt[eo] >= '0' && fmt[eo] <= '9')
- v += fmt[eo] - '0';
- else if (fmt[eo] >= 'a' && fmt[eo] <= 'f')
- v += fmt[eo] - 'a' + 10;
- else if (fmt[eo] >= 'A' && fmt[eo] <= 'F')
- v += fmt[eo] - 'A' + 10;
- else {
- /* non hex character, so we abort and just
- * send the whole thing unescaped (including \x)
- */
- ENSURE(1);
- ret[retlen++] = '\\';
- eo = so + 1;
- break;
- }
-
- /* we only extract two hex characters */
- if (i == 1) {
- ENSURE(1);
- ret[retlen++] = v;
- eo++;
- break;
- }
-
- i++;
- v <<= 4;
- }
- }
- break;
-
- default:
- ENSURE(2);
- memcpy(ret+retlen, fmt + so, 2);
- retlen += 2;
- eo++;
- break;
- }
- } else {
-
- /* % escape. we recognize %%, %host, %port, %user, %pass.
- * %proxyhost, %proxyport. Anything else we just send
- * unescaped (including the %).
- */
-
- if (fmt[eo] == '%') {
- ENSURE(1);
- ret[retlen++] = '%';
- eo++;
- }
- else if (strnicmp(fmt + eo, "host", 4) == 0) {
- char dest[512];
- int destlen;
- sk_getaddr(addr, dest, lenof(dest));
- destlen = strlen(dest);
- ENSURE(destlen);
- memcpy(ret+retlen, dest, destlen);
- retlen += destlen;
- eo += 4;
- }
- else if (strnicmp(fmt + eo, "port", 4) == 0) {
- char portstr[8], portlen;
- portlen = sprintf(portstr, "%i", port);
- ENSURE(portlen);
- memcpy(ret + retlen, portstr, portlen);
- retlen += portlen;
- eo += 4;
- }
- else if (strnicmp(fmt + eo, "user", 4) == 0) {
- char *username = conf_get_str(conf, CONF_proxy_username);
- int userlen = strlen(username);
- ENSURE(userlen);
- memcpy(ret+retlen, username, userlen);
- retlen += userlen;
- eo += 4;
- }
- else if (strnicmp(fmt + eo, "pass", 4) == 0) {
- char *password = conf_get_str(conf, CONF_proxy_password);
- int passlen = strlen(password);
- ENSURE(passlen);
- memcpy(ret+retlen, password, passlen);
- retlen += passlen;
- eo += 4;
- }
- else if (strnicmp(fmt + eo, "proxyhost", 9) == 0) {
- char *host = conf_get_str(conf, CONF_proxy_host);
- int phlen = strlen(host);
- ENSURE(phlen);
- memcpy(ret+retlen, host, phlen);
- retlen += phlen;
- eo += 9;
- }
- else if (strnicmp(fmt + eo, "proxyport", 9) == 0) {
- int port = conf_get_int(conf, CONF_proxy_port);
- char pport[50];
- int pplen;
- sprintf(pport, "%d", port);
- pplen = strlen(pport);
- ENSURE(pplen);
- memcpy(ret+retlen, pport, pplen);
- retlen += pplen;
- eo += 9;
- }
- else {
- /* we don't escape this, so send the % now, and
- * don't advance eo, so that we'll consider the
- * text immediately following the % as unescaped.
- */
- ENSURE(1);
- ret[retlen++] = '%';
- }
- }
-
- /* resume scanning for additional escapes after this one. */
- so = eo;
- }
-
- /* if there is any unescaped text at the end of the line, send it */
- if (eo != so) {
- ENSURE(eo - so);
- memcpy(ret + retlen, fmt + so, eo - so);
- retlen += eo - so;
- }
-
- ENSURE(1);
- ret[retlen] = '\0';
- return ret;
-
-#undef ENSURE
-}
-
-int proxy_telnet_negotiate (Proxy_Socket p, int change)
-{
- if (p->state == PROXY_CHANGE_NEW) {
- char *formatted_cmd;
-
- formatted_cmd = format_telnet_command(p->remote_addr, p->remote_port,
- p->conf);
-
- sk_write(p->sub_socket, formatted_cmd, strlen(formatted_cmd));
- sfree(formatted_cmd);
-
- p->state = 1;
- return 0;
- }
-
- if (change == PROXY_CHANGE_CLOSING) {
- /* if our proxy negotiation process involves closing and opening
- * new sockets, then we would want to intercept this closing
- * callback when we were expecting it. if we aren't anticipating
- * a socket close, then some error must have occurred. we'll
- * just pass those errors up to the backend.
- */
- return plug_closing(p->plug, p->closing_error_msg,
- p->closing_error_code,
- p->closing_calling_back);
- }
-
- if (change == PROXY_CHANGE_SENT) {
- /* some (or all) of what we wrote to the proxy was sent.
- * we don't do anything new, however, until we receive the
- * proxy's response. we might want to set a timer so we can
- * timeout the proxy negotiation after a while...
- */
- return 0;
- }
-
- if (change == PROXY_CHANGE_ACCEPTING) {
- /* we should _never_ see this, as we are using our socket to
- * connect to a proxy, not accepting inbound connections.
- * what should we do? close the socket with an appropriate
- * error message?
- */
- return plug_accepting(p->plug, p->accepting_sock);
- }
-
- if (change == PROXY_CHANGE_RECEIVE) {
- /* we have received data from the underlying socket, which
- * we'll need to parse, process, and respond to appropriately.
- */
-
- /* we're done */
- proxy_activate(p);
- /* proxy activate will have dealt with
- * whatever is left of the buffer */
- return 1;
- }
-
- plug_closing(p->plug, "Proxy error: Unexpected proxy error",
- PROXY_ERROR_UNEXPECTED, 0);
- return 1;
-}
+/*
+ * Network proxy abstraction in PuTTY
+ *
+ * A proxy layer, if necessary, wedges itself between the network
+ * code and the higher level backend.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <string.h>
+
+#define DEFINE_PLUG_METHOD_MACROS
+#include "putty.h"
+#include "network.h"
+#include "proxy.h"
+
+#define do_proxy_dns(conf) \
+ (conf_get_int(conf, CONF_proxy_dns) == FORCE_ON || \
+ (conf_get_int(conf, CONF_proxy_dns) == AUTO && \
+ conf_get_int(conf, CONF_proxy_type) != PROXY_SOCKS4))
+
+/*
+ * Call this when proxy negotiation is complete, so that this
+ * socket can begin working normally.
+ */
+void proxy_activate (Proxy_Socket p)
+{
+ void *data;
+ int len;
+ long output_before, output_after;
+
+ p->state = PROXY_STATE_ACTIVE;
+
+ /* we want to ignore new receive events until we have sent
+ * all of our buffered receive data.
+ */
+ sk_set_frozen(p->sub_socket, 1);
+
+ /* how many bytes of output have we buffered? */
+ output_before = bufchain_size(&p->pending_oob_output_data) +
+ bufchain_size(&p->pending_output_data);
+ /* and keep track of how many bytes do not get sent. */
+ output_after = 0;
+
+ /* send buffered OOB writes */
+ while (bufchain_size(&p->pending_oob_output_data) > 0) {
+ bufchain_prefix(&p->pending_oob_output_data, &data, &len);
+ output_after += sk_write_oob(p->sub_socket, data, len);
+ bufchain_consume(&p->pending_oob_output_data, len);
+ }
+
+ /* send buffered normal writes */
+ while (bufchain_size(&p->pending_output_data) > 0) {
+ bufchain_prefix(&p->pending_output_data, &data, &len);
+ output_after += sk_write(p->sub_socket, data, len);
+ bufchain_consume(&p->pending_output_data, len);
+ }
+
+ /* if we managed to send any data, let the higher levels know. */
+ if (output_after < output_before)
+ plug_sent(p->plug, output_after);
+
+ /* if we were asked to flush the output during
+ * the proxy negotiation process, do so now.
+ */
+ if (p->pending_flush) sk_flush(p->sub_socket);
+
+ /* if we have a pending EOF to send, send it */
+ if (p->pending_eof) sk_write_eof(p->sub_socket);
+
+ /* if the backend wanted the socket unfrozen, try to unfreeze.
+ * our set_frozen handler will flush buffered receive data before
+ * unfreezing the actual underlying socket.
+ */
+ if (!p->freeze)
+ sk_set_frozen((Socket)p, 0);
+}
+
+/* basic proxy socket functions */
+
+static Plug sk_proxy_plug (Socket s, Plug p)
+{
+ Proxy_Socket ps = (Proxy_Socket) s;
+ Plug ret = ps->plug;
+ if (p)
+ ps->plug = p;
+ return ret;
+}
+
+static void sk_proxy_close (Socket s)
+{
+ Proxy_Socket ps = (Proxy_Socket) s;
+
+ sk_close(ps->sub_socket);
+ sk_addr_free(ps->remote_addr);
+ sfree(ps);
+}
+
+static int sk_proxy_write (Socket s, const char *data, int len)
+{
+ Proxy_Socket ps = (Proxy_Socket) s;
+
+ if (ps->state != PROXY_STATE_ACTIVE) {
+ bufchain_add(&ps->pending_output_data, data, len);
+ return bufchain_size(&ps->pending_output_data);
+ }
+ return sk_write(ps->sub_socket, data, len);
+}
+
+static int sk_proxy_write_oob (Socket s, const char *data, int len)
+{
+ Proxy_Socket ps = (Proxy_Socket) s;
+
+ if (ps->state != PROXY_STATE_ACTIVE) {
+ bufchain_clear(&ps->pending_output_data);
+ bufchain_clear(&ps->pending_oob_output_data);
+ bufchain_add(&ps->pending_oob_output_data, data, len);
+ return len;
+ }
+ return sk_write_oob(ps->sub_socket, data, len);
+}
+
+static void sk_proxy_write_eof (Socket s)
+{
+ Proxy_Socket ps = (Proxy_Socket) s;
+
+ if (ps->state != PROXY_STATE_ACTIVE) {
+ ps->pending_eof = 1;
+ return;
+ }
+ sk_write_eof(ps->sub_socket);
+}
+
+static void sk_proxy_flush (Socket s)
+{
+ Proxy_Socket ps = (Proxy_Socket) s;
+
+ if (ps->state != PROXY_STATE_ACTIVE) {
+ ps->pending_flush = 1;
+ return;
+ }
+ sk_flush(ps->sub_socket);
+}
+
+static void sk_proxy_set_frozen (Socket s, int is_frozen)
+{
+ Proxy_Socket ps = (Proxy_Socket) s;
+
+ if (ps->state != PROXY_STATE_ACTIVE) {
+ ps->freeze = is_frozen;
+ return;
+ }
+
+ /* handle any remaining buffered recv data first */
+ if (bufchain_size(&ps->pending_input_data) > 0) {
+ ps->freeze = is_frozen;
+
+ /* loop while we still have buffered data, and while we are
+ * unfrozen. the plug_receive call in the loop could result
+ * in a call back into this function refreezing the socket,
+ * so we have to check each time.
+ */
+ while (!ps->freeze && bufchain_size(&ps->pending_input_data) > 0) {
+ void *data;
+ char databuf[512];
+ int len;
+ bufchain_prefix(&ps->pending_input_data, &data, &len);
+ if (len > lenof(databuf))
+ len = lenof(databuf);
+ memcpy(databuf, data, len);
+ bufchain_consume(&ps->pending_input_data, len);
+ plug_receive(ps->plug, 0, databuf, len);
+ }
+
+ /* if we're still frozen, we'll have to wait for another
+ * call from the backend to finish unbuffering the data.
+ */
+ if (ps->freeze) return;
+ }
+
+ sk_set_frozen(ps->sub_socket, is_frozen);
+}
+
+static const char * sk_proxy_socket_error (Socket s)
+{
+ Proxy_Socket ps = (Proxy_Socket) s;
+ if (ps->error != NULL || ps->sub_socket == NULL) {
+ return ps->error;
+ }
+ return sk_socket_error(ps->sub_socket);
+}
+
+/* basic proxy plug functions */
+
+static void plug_proxy_log(Plug plug, int type, SockAddr addr, int port,
+ const char *error_msg, int error_code)
+{
+ Proxy_Plug pp = (Proxy_Plug) plug;
+ Proxy_Socket ps = pp->proxy_socket;
+
+ plug_log(ps->plug, type, addr, port, error_msg, error_code);
+}
+
+static int plug_proxy_closing (Plug p, const char *error_msg,
+ int error_code, int calling_back)
+{
+ Proxy_Plug pp = (Proxy_Plug) p;
+ Proxy_Socket ps = pp->proxy_socket;
+
+ if (ps->state != PROXY_STATE_ACTIVE) {
+ ps->closing_error_msg = error_msg;
+ ps->closing_error_code = error_code;
+ ps->closing_calling_back = calling_back;
+ return ps->negotiate(ps, PROXY_CHANGE_CLOSING);
+ }
+ return plug_closing(ps->plug, error_msg,
+ error_code, calling_back);
+}
+
+static int plug_proxy_receive (Plug p, int urgent, char *data, int len)
+{
+ Proxy_Plug pp = (Proxy_Plug) p;
+ Proxy_Socket ps = pp->proxy_socket;
+
+ if (ps->state != PROXY_STATE_ACTIVE) {
+ /* we will lose the urgentness of this data, but since most,
+ * if not all, of this data will be consumed by the negotiation
+ * process, hopefully it won't affect the protocol above us
+ */
+ bufchain_add(&ps->pending_input_data, data, len);
+ ps->receive_urgent = urgent;
+ ps->receive_data = data;
+ ps->receive_len = len;
+ return ps->negotiate(ps, PROXY_CHANGE_RECEIVE);
+ }
+ return plug_receive(ps->plug, urgent, data, len);
+}
+
+static void plug_proxy_sent (Plug p, int bufsize)
+{
+ Proxy_Plug pp = (Proxy_Plug) p;
+ Proxy_Socket ps = pp->proxy_socket;
+
+ if (ps->state != PROXY_STATE_ACTIVE) {
+ ps->sent_bufsize = bufsize;
+ ps->negotiate(ps, PROXY_CHANGE_SENT);
+ return;
+ }
+ plug_sent(ps->plug, bufsize);
+}
+
+static int plug_proxy_accepting(Plug p,
+ accept_fn_t constructor, accept_ctx_t ctx)
+{
+ Proxy_Plug pp = (Proxy_Plug) p;
+ Proxy_Socket ps = pp->proxy_socket;
+
+ if (ps->state != PROXY_STATE_ACTIVE) {
+ ps->accepting_constructor = constructor;
+ ps->accepting_ctx = ctx;
+ return ps->negotiate(ps, PROXY_CHANGE_ACCEPTING);
+ }
+ return plug_accepting(ps->plug, constructor, ctx);
+}
+
+/*
+ * This function can accept a NULL pointer as `addr', in which case
+ * it will only check the host name.
+ */
+int proxy_for_destination (SockAddr addr, const char *hostname,
+ int port, Conf *conf)
+{
+ int s = 0, e = 0;
+ char hostip[64];
+ int hostip_len, hostname_len;
+ const char *exclude_list;
+
+ /*
+ * Special local connections such as Unix-domain sockets
+ * unconditionally cannot be proxied, even in proxy-localhost
+ * mode. There just isn't any way to ask any known proxy type for
+ * them.
+ */
+ if (addr && sk_address_is_special_local(addr))
+ return 0; /* do not proxy */
+
+ /*
+ * Check the host name and IP against the hard-coded
+ * representations of `localhost'.
+ */
+ if (!conf_get_int(conf, CONF_even_proxy_localhost) &&
+ (sk_hostname_is_local(hostname) ||
+ (addr && sk_address_is_local(addr))))
+ return 0; /* do not proxy */
+
+ /* we want a string representation of the IP address for comparisons */
+ if (addr) {
+ sk_getaddr(addr, hostip, 64);
+ hostip_len = strlen(hostip);
+ } else
+ hostip_len = 0; /* placate gcc; shouldn't be required */
+
+ hostname_len = strlen(hostname);
+
+ exclude_list = conf_get_str(conf, CONF_proxy_exclude_list);
+
+ /* now parse the exclude list, and see if either our IP
+ * or hostname matches anything in it.
+ */
+
+ while (exclude_list[s]) {
+ while (exclude_list[s] &&
+ (isspace((unsigned char)exclude_list[s]) ||
+ exclude_list[s] == ',')) s++;
+
+ if (!exclude_list[s]) break;
+
+ e = s;
+
+ while (exclude_list[e] &&
+ (isalnum((unsigned char)exclude_list[e]) ||
+ exclude_list[e] == '-' ||
+ exclude_list[e] == '.' ||
+ exclude_list[e] == '*')) e++;
+
+ if (exclude_list[s] == '*') {
+ /* wildcard at beginning of entry */
+
+ if ((addr && strnicmp(hostip + hostip_len - (e - s - 1),
+ exclude_list + s + 1, e - s - 1) == 0) ||
+ strnicmp(hostname + hostname_len - (e - s - 1),
+ exclude_list + s + 1, e - s - 1) == 0)
+ return 0; /* IP/hostname range excluded. do not use proxy. */
+
+ } else if (exclude_list[e-1] == '*') {
+ /* wildcard at end of entry */
+
+ if ((addr && strnicmp(hostip, exclude_list + s, e - s - 1) == 0) ||
+ strnicmp(hostname, exclude_list + s, e - s - 1) == 0)
+ return 0; /* IP/hostname range excluded. do not use proxy. */
+
+ } else {
+ /* no wildcard at either end, so let's try an absolute
+ * match (ie. a specific IP)
+ */
+
+ if (addr && strnicmp(hostip, exclude_list + s, e - s) == 0)
+ return 0; /* IP/hostname excluded. do not use proxy. */
+ if (strnicmp(hostname, exclude_list + s, e - s) == 0)
+ return 0; /* IP/hostname excluded. do not use proxy. */
+ }
+
+ s = e;
+
+ /* Make sure we really have reached the next comma or end-of-string */
+ while (exclude_list[s] &&
+ !isspace((unsigned char)exclude_list[s]) &&
+ exclude_list[s] != ',') s++;
+ }
+
+ /* no matches in the exclude list, so use the proxy */
+ return 1;
+}
+
+SockAddr name_lookup(char *host, int port, char **canonicalname,
+ Conf *conf, int addressfamily)
+{
+ if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE &&
+ do_proxy_dns(conf) &&
+ proxy_for_destination(NULL, host, port, conf)) {
+ *canonicalname = dupstr(host);
+ return sk_nonamelookup(host);
+ }
+
+ return sk_namelookup(host, canonicalname, addressfamily);
+}
+
+Socket new_connection(SockAddr addr, char *hostname,
+ int port, int privport,
+ int oobinline, int nodelay, int keepalive,
+ Plug plug, Conf *conf)
+{
+ static const struct socket_function_table socket_fn_table = {
+ sk_proxy_plug,
+ sk_proxy_close,
+ sk_proxy_write,
+ sk_proxy_write_oob,
+ sk_proxy_write_eof,
+ sk_proxy_flush,
+ sk_proxy_set_frozen,
+ sk_proxy_socket_error
+ };
+
+ static const struct plug_function_table plug_fn_table = {
+ plug_proxy_log,
+ plug_proxy_closing,
+ plug_proxy_receive,
+ plug_proxy_sent,
+ plug_proxy_accepting
+ };
+
+ if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE &&
+ proxy_for_destination(addr, hostname, port, conf))
+ {
+ Proxy_Socket ret;
+ Proxy_Plug pplug;
+ SockAddr proxy_addr;
+ char *proxy_canonical_name;
+ Socket sret;
+ int type;
+
+ if ((sret = platform_new_connection(addr, hostname, port, privport,
+ oobinline, nodelay, keepalive,
+ plug, conf)) !=
+ NULL)
+ return sret;
+
+ ret = snew(struct Socket_proxy_tag);
+ ret->fn = &socket_fn_table;
+ ret->conf = conf_copy(conf);
+ ret->plug = plug;
+ ret->remote_addr = addr; /* will need to be freed on close */
+ ret->remote_port = port;
+
+ ret->error = NULL;
+ ret->pending_flush = 0;
+ ret->pending_eof = 0;
+ ret->freeze = 0;
+
+ bufchain_init(&ret->pending_input_data);
+ bufchain_init(&ret->pending_output_data);
+ bufchain_init(&ret->pending_oob_output_data);
+
+ ret->sub_socket = NULL;
+ ret->state = PROXY_STATE_NEW;
+ ret->negotiate = NULL;
+
+ type = conf_get_int(conf, CONF_proxy_type);
+ if (type == PROXY_HTTP) {
+ ret->negotiate = proxy_http_negotiate;
+ } else if (type == PROXY_SOCKS4) {
+ ret->negotiate = proxy_socks4_negotiate;
+ } else if (type == PROXY_SOCKS5) {
+ ret->negotiate = proxy_socks5_negotiate;
+ } else if (type == PROXY_TELNET) {
+ ret->negotiate = proxy_telnet_negotiate;
+ } else {
+ ret->error = "Proxy error: Unknown proxy method";
+ return (Socket) ret;
+ }
+
+ /* 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;
+
+ /* look-up proxy */
+ proxy_addr = sk_namelookup(conf_get_str(conf, CONF_proxy_host),
+ &proxy_canonical_name,
+ conf_get_int(conf, CONF_addressfamily));
+ if (sk_addr_error(proxy_addr) != NULL) {
+ ret->error = "Proxy error: Unable to resolve proxy host name";
+ sfree(pplug);
+ sk_addr_free(proxy_addr);
+ return (Socket)ret;
+ }
+ sfree(proxy_canonical_name);
+
+ /* create the actual socket we will be using,
+ * connected to our proxy server and port.
+ */
+ ret->sub_socket = sk_new(proxy_addr,
+ conf_get_int(conf, CONF_proxy_port),
+ privport, oobinline,
+ nodelay, keepalive, (Plug) pplug);
+ if (sk_socket_error(ret->sub_socket) != NULL)
+ return (Socket) ret;
+
+ /* start the proxy negotiation process... */
+ sk_set_frozen(ret->sub_socket, 0);
+ ret->negotiate(ret, PROXY_CHANGE_NEW);
+
+ return (Socket) ret;
+ }
+
+ /* no proxy, so just return the direct socket */
+ 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)
+{
+ /* TODO: SOCKS (and potentially others) support inbound
+ * TODO: connections via the proxy. support them.
+ */
+
+ return sk_newlistener(srcaddr, port, plug, local_host_only, addressfamily);
+}
+
+/* ----------------------------------------------------------------------
+ * HTTP CONNECT proxy type.
+ */
+
+static int get_line_end (char * data, int len)
+{
+ int off = 0;
+
+ while (off < len)
+ {
+ if (data[off] == '\n') {
+ /* we have a newline */
+ off++;
+
+ /* is that the only thing on this line? */
+ if (off <= 2) return off;
+
+ /* if not, then there is the possibility that this header
+ * continues onto the next line, if it starts with a space
+ * or a tab.
+ */
+
+ if (off + 1 < len &&
+ data[off+1] != ' ' &&
+ data[off+1] != '\t') return off;
+
+ /* the line does continue, so we have to keep going
+ * until we see an the header's "real" end of line.
+ */
+ off++;
+ }
+
+ off++;
+ }
+
+ return -1;
+}
+
+int proxy_http_negotiate (Proxy_Socket p, int change)
+{
+ if (p->state == PROXY_STATE_NEW) {
+ /* we are just beginning the proxy negotiate process,
+ * so we'll send off the initial bits of the request.
+ * for this proxy method, it's just a simple HTTP
+ * request
+ */
+ char *buf, dest[512];
+ char *username, *password;
+
+ sk_getaddr(p->remote_addr, dest, lenof(dest));
+
+ buf = dupprintf("CONNECT %s:%i HTTP/1.1\r\nHost: %s:%i\r\n",
+ dest, p->remote_port, dest, p->remote_port);
+ sk_write(p->sub_socket, buf, strlen(buf));
+ sfree(buf);
+
+ username = conf_get_str(p->conf, CONF_proxy_username);
+ password = conf_get_str(p->conf, CONF_proxy_password);
+ if (username[0] || password[0]) {
+ char *buf, *buf2;
+ int i, j, len;
+ buf = dupprintf("%s:%s", username, password);
+ len = strlen(buf);
+ buf2 = snewn(len * 4 / 3 + 100, char);
+ sprintf(buf2, "Proxy-Authorization: Basic ");
+ for (i = 0, j = strlen(buf2); i < len; i += 3, j += 4)
+ base64_encode_atom((unsigned char *)(buf+i),
+ (len-i > 3 ? 3 : len-i), buf2+j);
+ strcpy(buf2+j, "\r\n");
+ sk_write(p->sub_socket, buf2, strlen(buf2));
+ sfree(buf);
+ sfree(buf2);
+ }
+
+ sk_write(p->sub_socket, "\r\n", 2);
+
+ p->state = 1;
+ return 0;
+ }
+
+ if (change == PROXY_CHANGE_CLOSING) {
+ /* if our proxy negotiation process involves closing and opening
+ * new sockets, then we would want to intercept this closing
+ * callback when we were expecting it. if we aren't anticipating
+ * a socket close, then some error must have occurred. we'll
+ * just pass those errors up to the backend.
+ */
+ return plug_closing(p->plug, p->closing_error_msg,
+ p->closing_error_code,
+ p->closing_calling_back);
+ }
+
+ if (change == PROXY_CHANGE_SENT) {
+ /* some (or all) of what we wrote to the proxy was sent.
+ * we don't do anything new, however, until we receive the
+ * proxy's response. we might want to set a timer so we can
+ * timeout the proxy negotiation after a while...
+ */
+ return 0;
+ }
+
+ if (change == PROXY_CHANGE_ACCEPTING) {
+ /* we should _never_ see this, as we are using our socket to
+ * connect to a proxy, not accepting inbound connections.
+ * what should we do? close the socket with an appropriate
+ * error message?
+ */
+ return plug_accepting(p->plug,
+ p->accepting_constructor, p->accepting_ctx);
+ }
+
+ if (change == PROXY_CHANGE_RECEIVE) {
+ /* we have received data from the underlying socket, which
+ * we'll need to parse, process, and respond to appropriately.
+ */
+
+ char *data, *datap;
+ int len;
+ int eol;
+
+ if (p->state == 1) {
+
+ int min_ver, maj_ver, status;
+
+ /* get the status line */
+ len = bufchain_size(&p->pending_input_data);
+ assert(len > 0); /* or we wouldn't be here */
+ data = snewn(len+1, char);
+ bufchain_fetch(&p->pending_input_data, data, len);
+ /*
+ * We must NUL-terminate this data, because Windows
+ * sscanf appears to require a NUL at the end of the
+ * string because it strlens it _first_. Sigh.
+ */
+ data[len] = '\0';
+
+ eol = get_line_end(data, len);
+ if (eol < 0) {
+ sfree(data);
+ return 1;
+ }
+
+ status = -1;
+ /* We can't rely on whether the %n incremented the sscanf return */
+ if (sscanf((char *)data, "HTTP/%i.%i %n",
+ &maj_ver, &min_ver, &status) < 2 || status == -1) {
+ plug_closing(p->plug, "Proxy error: HTTP response was absent",
+ PROXY_ERROR_GENERAL, 0);
+ sfree(data);
+ return 1;
+ }
+
+ /* remove the status line from the input buffer. */
+ bufchain_consume(&p->pending_input_data, eol);
+ if (data[status] != '2') {
+ /* error */
+ char *buf;
+ data[eol] = '\0';
+ while (eol > status &&
+ (data[eol-1] == '\r' || data[eol-1] == '\n'))
+ data[--eol] = '\0';
+ buf = dupprintf("Proxy error: %s", data+status);
+ plug_closing(p->plug, buf, PROXY_ERROR_GENERAL, 0);
+ sfree(buf);
+ sfree(data);
+ return 1;
+ }
+
+ sfree(data);
+
+ p->state = 2;
+ }
+
+ if (p->state == 2) {
+
+ /* get headers. we're done when we get a
+ * header of length 2, (ie. just "\r\n")
+ */
+
+ len = bufchain_size(&p->pending_input_data);
+ assert(len > 0); /* or we wouldn't be here */
+ data = snewn(len, char);
+ datap = data;
+ bufchain_fetch(&p->pending_input_data, data, len);
+
+ eol = get_line_end(datap, len);
+ if (eol < 0) {
+ sfree(data);
+ return 1;
+ }
+ while (eol > 2)
+ {
+ bufchain_consume(&p->pending_input_data, eol);
+ datap += eol;
+ len -= eol;
+ eol = get_line_end(datap, len);
+ }
+
+ if (eol == 2) {
+ /* we're done */
+ bufchain_consume(&p->pending_input_data, 2);
+ proxy_activate(p);
+ /* proxy activate will have dealt with
+ * whatever is left of the buffer */
+ sfree(data);
+ return 1;
+ }
+
+ sfree(data);
+ return 1;
+ }
+ }
+
+ plug_closing(p->plug, "Proxy error: unexpected proxy error",
+ PROXY_ERROR_UNEXPECTED, 0);
+ return 1;
+}
+
+/* ----------------------------------------------------------------------
+ * SOCKS proxy type.
+ */
+
+/* SOCKS version 4 */
+int proxy_socks4_negotiate (Proxy_Socket p, int change)
+{
+ if (p->state == PROXY_CHANGE_NEW) {
+
+ /* request format:
+ * version number (1 byte) = 4
+ * command code (1 byte)
+ * 1 = CONNECT
+ * 2 = BIND
+ * dest. port (2 bytes) [network order]
+ * dest. address (4 bytes)
+ * user ID (variable length, null terminated string)
+ */
+
+ int length, type, namelen;
+ char *command, addr[4], hostname[512];
+ char *username;
+
+ type = sk_addrtype(p->remote_addr);
+ if (type == ADDRTYPE_IPV6) {
+ p->error = "Proxy error: SOCKS version 4 does not support IPv6";
+ return 1;
+ } else if (type == ADDRTYPE_IPV4) {
+ namelen = 0;
+ sk_addrcopy(p->remote_addr, addr);
+ } else { /* type == ADDRTYPE_NAME */
+ assert(type == ADDRTYPE_NAME);
+ sk_getaddr(p->remote_addr, hostname, lenof(hostname));
+ namelen = strlen(hostname) + 1; /* include the NUL */
+ addr[0] = addr[1] = addr[2] = 0;
+ addr[3] = 1;
+ }
+
+ username = conf_get_str(p->conf, CONF_proxy_username);
+ length = strlen(username) + namelen + 9;
+ command = snewn(length, char);
+ strcpy(command + 8, username);
+
+ command[0] = 4; /* version 4 */
+ command[1] = 1; /* CONNECT command */
+
+ /* port */
+ command[2] = (char) (p->remote_port >> 8) & 0xff;
+ command[3] = (char) p->remote_port & 0xff;
+
+ /* address */
+ memcpy(command + 4, addr, 4);
+
+ /* hostname */
+ memcpy(command + 8 + strlen(username) + 1,
+ hostname, namelen);
+
+ sk_write(p->sub_socket, command, length);
+ sfree(username);
+ sfree(command);
+
+ p->state = 1;
+ return 0;
+ }
+
+ if (change == PROXY_CHANGE_CLOSING) {
+ /* if our proxy negotiation process involves closing and opening
+ * new sockets, then we would want to intercept this closing
+ * callback when we were expecting it. if we aren't anticipating
+ * a socket close, then some error must have occurred. we'll
+ * just pass those errors up to the backend.
+ */
+ return plug_closing(p->plug, p->closing_error_msg,
+ p->closing_error_code,
+ p->closing_calling_back);
+ }
+
+ if (change == PROXY_CHANGE_SENT) {
+ /* some (or all) of what we wrote to the proxy was sent.
+ * we don't do anything new, however, until we receive the
+ * proxy's response. we might want to set a timer so we can
+ * timeout the proxy negotiation after a while...
+ */
+ return 0;
+ }
+
+ if (change == PROXY_CHANGE_ACCEPTING) {
+ /* we should _never_ see this, as we are using our socket to
+ * connect to a proxy, not accepting inbound connections.
+ * what should we do? close the socket with an appropriate
+ * error message?
+ */
+ return plug_accepting(p->plug,
+ p->accepting_constructor, p->accepting_ctx);
+ }
+
+ if (change == PROXY_CHANGE_RECEIVE) {
+ /* we have received data from the underlying socket, which
+ * we'll need to parse, process, and respond to appropriately.
+ */
+
+ if (p->state == 1) {
+ /* response format:
+ * version number (1 byte) = 4
+ * reply code (1 byte)
+ * 90 = request granted
+ * 91 = request rejected or failed
+ * 92 = request rejected due to lack of IDENTD on client
+ * 93 = request rejected due to difference in user ID
+ * (what we sent vs. what IDENTD said)
+ * dest. port (2 bytes)
+ * dest. address (4 bytes)
+ */
+
+ char data[8];
+
+ if (bufchain_size(&p->pending_input_data) < 8)
+ return 1; /* not got anything yet */
+
+ /* get the response */
+ bufchain_fetch(&p->pending_input_data, data, 8);
+
+ if (data[0] != 0) {
+ plug_closing(p->plug, "Proxy error: SOCKS proxy responded with "
+ "unexpected reply code version",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+ }
+
+ if (data[1] != 90) {
+
+ switch (data[1]) {
+ case 92:
+ plug_closing(p->plug, "Proxy error: SOCKS server wanted IDENTD on client",
+ PROXY_ERROR_GENERAL, 0);
+ break;
+ case 93:
+ plug_closing(p->plug, "Proxy error: Username and IDENTD on client don't agree",
+ PROXY_ERROR_GENERAL, 0);
+ break;
+ case 91:
+ default:
+ plug_closing(p->plug, "Proxy error: Error while communicating with proxy",
+ PROXY_ERROR_GENERAL, 0);
+ break;
+ }
+
+ return 1;
+ }
+ bufchain_consume(&p->pending_input_data, 8);
+
+ /* we're done */
+ proxy_activate(p);
+ /* proxy activate will have dealt with
+ * whatever is left of the buffer */
+ return 1;
+ }
+ }
+
+ plug_closing(p->plug, "Proxy error: unexpected proxy error",
+ PROXY_ERROR_UNEXPECTED, 0);
+ return 1;
+}
+
+/* SOCKS version 5 */
+int proxy_socks5_negotiate (Proxy_Socket p, int change)
+{
+ if (p->state == PROXY_CHANGE_NEW) {
+
+ /* initial command:
+ * version number (1 byte) = 5
+ * number of available authentication methods (1 byte)
+ * available authentication methods (1 byte * previous value)
+ * authentication methods:
+ * 0x00 = no authentication
+ * 0x01 = GSSAPI
+ * 0x02 = username/password
+ * 0x03 = CHAP
+ */
+
+ char command[5];
+ char *username, *password;
+ int len;
+
+ command[0] = 5; /* version 5 */
+ username = conf_get_str(p->conf, CONF_proxy_username);
+ password = conf_get_str(p->conf, CONF_proxy_password);
+ if (username[0] || password[0]) {
+ command[2] = 0x00; /* no authentication */
+ len = 3;
+ proxy_socks5_offerencryptedauth (command, &len);
+ command[len++] = 0x02; /* username/password */
+ command[1] = len - 2; /* Number of methods supported */
+ } else {
+ command[1] = 1; /* one methods supported: */
+ command[2] = 0x00; /* no authentication */
+ len = 3;
+ }
+
+ sk_write(p->sub_socket, command, len);
+
+ p->state = 1;
+ return 0;
+ }
+
+ if (change == PROXY_CHANGE_CLOSING) {
+ /* if our proxy negotiation process involves closing and opening
+ * new sockets, then we would want to intercept this closing
+ * callback when we were expecting it. if we aren't anticipating
+ * a socket close, then some error must have occurred. we'll
+ * just pass those errors up to the backend.
+ */
+ return plug_closing(p->plug, p->closing_error_msg,
+ p->closing_error_code,
+ p->closing_calling_back);
+ }
+
+ if (change == PROXY_CHANGE_SENT) {
+ /* some (or all) of what we wrote to the proxy was sent.
+ * we don't do anything new, however, until we receive the
+ * proxy's response. we might want to set a timer so we can
+ * timeout the proxy negotiation after a while...
+ */
+ return 0;
+ }
+
+ if (change == PROXY_CHANGE_ACCEPTING) {
+ /* we should _never_ see this, as we are using our socket to
+ * connect to a proxy, not accepting inbound connections.
+ * what should we do? close the socket with an appropriate
+ * error message?
+ */
+ return plug_accepting(p->plug,
+ p->accepting_constructor, p->accepting_ctx);
+ }
+
+ if (change == PROXY_CHANGE_RECEIVE) {
+ /* we have received data from the underlying socket, which
+ * we'll need to parse, process, and respond to appropriately.
+ */
+
+ if (p->state == 1) {
+
+ /* initial response:
+ * version number (1 byte) = 5
+ * authentication method (1 byte)
+ * authentication methods:
+ * 0x00 = no authentication
+ * 0x01 = GSSAPI
+ * 0x02 = username/password
+ * 0x03 = CHAP
+ * 0xff = no acceptable methods
+ */
+ char data[2];
+
+ if (bufchain_size(&p->pending_input_data) < 2)
+ return 1; /* not got anything yet */
+
+ /* get the response */
+ bufchain_fetch(&p->pending_input_data, data, 2);
+
+ if (data[0] != 5) {
+ plug_closing(p->plug, "Proxy error: SOCKS proxy returned unexpected version",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+ }
+
+ if (data[1] == 0x00) p->state = 2; /* no authentication needed */
+ else if (data[1] == 0x01) p->state = 4; /* GSSAPI authentication */
+ else if (data[1] == 0x02) p->state = 5; /* username/password authentication */
+ else if (data[1] == 0x03) p->state = 6; /* CHAP authentication */
+ else {
+ plug_closing(p->plug, "Proxy error: SOCKS proxy did not accept our authentication",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+ }
+ bufchain_consume(&p->pending_input_data, 2);
+ }
+
+ if (p->state == 7) {
+
+ /* password authentication reply format:
+ * version number (1 bytes) = 1
+ * reply code (1 byte)
+ * 0 = succeeded
+ * >0 = failed
+ */
+ char data[2];
+
+ if (bufchain_size(&p->pending_input_data) < 2)
+ return 1; /* not got anything yet */
+
+ /* get the response */
+ bufchain_fetch(&p->pending_input_data, data, 2);
+
+ if (data[0] != 1) {
+ plug_closing(p->plug, "Proxy error: SOCKS password "
+ "subnegotiation contained wrong version number",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+ }
+
+ if (data[1] != 0) {
+
+ plug_closing(p->plug, "Proxy error: SOCKS proxy refused"
+ " password authentication",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+ }
+
+ bufchain_consume(&p->pending_input_data, 2);
+ p->state = 2; /* now proceed as authenticated */
+ }
+
+ if (p->state == 8) {
+ int ret;
+ ret = proxy_socks5_handlechap(p);
+ if (ret) return ret;
+ }
+
+ if (p->state == 2) {
+
+ /* request format:
+ * version number (1 byte) = 5
+ * command code (1 byte)
+ * 1 = CONNECT
+ * 2 = BIND
+ * 3 = UDP ASSOCIATE
+ * reserved (1 byte) = 0x00
+ * address type (1 byte)
+ * 1 = IPv4
+ * 3 = domainname (first byte has length, no terminating null)
+ * 4 = IPv6
+ * dest. address (variable)
+ * dest. port (2 bytes) [network order]
+ */
+
+ char command[512];
+ int len;
+ int type;
+
+ type = sk_addrtype(p->remote_addr);
+ if (type == ADDRTYPE_IPV4) {
+ len = 10; /* 4 hdr + 4 addr + 2 trailer */
+ command[3] = 1; /* IPv4 */
+ sk_addrcopy(p->remote_addr, command+4);
+ } else if (type == ADDRTYPE_IPV6) {
+ len = 22; /* 4 hdr + 16 addr + 2 trailer */
+ command[3] = 4; /* IPv6 */
+ sk_addrcopy(p->remote_addr, command+4);
+ } else {
+ assert(type == ADDRTYPE_NAME);
+ command[3] = 3;
+ sk_getaddr(p->remote_addr, command+5, 256);
+ command[4] = strlen(command+5);
+ len = 7 + command[4]; /* 4 hdr, 1 len, N addr, 2 trailer */
+ }
+
+ command[0] = 5; /* version 5 */
+ command[1] = 1; /* CONNECT command */
+ command[2] = 0x00;
+
+ /* port */
+ command[len-2] = (char) (p->remote_port >> 8) & 0xff;
+ command[len-1] = (char) p->remote_port & 0xff;
+
+ sk_write(p->sub_socket, command, len);
+
+ p->state = 3;
+ return 1;
+ }
+
+ if (p->state == 3) {
+
+ /* reply format:
+ * version number (1 bytes) = 5
+ * reply code (1 byte)
+ * 0 = succeeded
+ * 1 = general SOCKS server failure
+ * 2 = connection not allowed by ruleset
+ * 3 = network unreachable
+ * 4 = host unreachable
+ * 5 = connection refused
+ * 6 = TTL expired
+ * 7 = command not supported
+ * 8 = address type not supported
+ * reserved (1 byte) = x00
+ * address type (1 byte)
+ * 1 = IPv4
+ * 3 = domainname (first byte has length, no terminating null)
+ * 4 = IPv6
+ * server bound address (variable)
+ * server bound port (2 bytes) [network order]
+ */
+ char data[5];
+ int len;
+
+ /* First 5 bytes of packet are enough to tell its length. */
+ if (bufchain_size(&p->pending_input_data) < 5)
+ return 1; /* not got anything yet */
+
+ /* get the response */
+ bufchain_fetch(&p->pending_input_data, data, 5);
+
+ if (data[0] != 5) {
+ plug_closing(p->plug, "Proxy error: SOCKS proxy returned wrong version number",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+ }
+
+ if (data[1] != 0) {
+ char buf[256];
+
+ strcpy(buf, "Proxy error: ");
+
+ switch (data[1]) {
+ case 1: strcat(buf, "General SOCKS server failure"); break;
+ case 2: strcat(buf, "Connection not allowed by ruleset"); break;
+ case 3: strcat(buf, "Network unreachable"); break;
+ case 4: strcat(buf, "Host unreachable"); break;
+ case 5: strcat(buf, "Connection refused"); break;
+ case 6: strcat(buf, "TTL expired"); break;
+ case 7: strcat(buf, "Command not supported"); break;
+ case 8: strcat(buf, "Address type not supported"); break;
+ default: sprintf(buf+strlen(buf),
+ "Unrecognised SOCKS error code %d",
+ data[1]);
+ break;
+ }
+ plug_closing(p->plug, buf, PROXY_ERROR_GENERAL, 0);
+
+ return 1;
+ }
+
+ /*
+ * Eat the rest of the reply packet.
+ */
+ len = 6; /* first 4 bytes, last 2 */
+ switch (data[3]) {
+ case 1: len += 4; break; /* IPv4 address */
+ case 4: len += 16; break;/* IPv6 address */
+ case 3: len += (unsigned char)data[4]; break; /* domain name */
+ default:
+ plug_closing(p->plug, "Proxy error: SOCKS proxy returned "
+ "unrecognised address format",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+ }
+ if (bufchain_size(&p->pending_input_data) < len)
+ return 1; /* not got whole reply yet */
+ bufchain_consume(&p->pending_input_data, len);
+
+ /* we're done */
+ proxy_activate(p);
+ return 1;
+ }
+
+ if (p->state == 4) {
+ /* TODO: Handle GSSAPI authentication */
+ plug_closing(p->plug, "Proxy error: We don't support GSSAPI authentication",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+ }
+
+ if (p->state == 5) {
+ char *username = conf_get_str(p->conf, CONF_proxy_username);
+ char *password = conf_get_str(p->conf, CONF_proxy_password);
+ if (username[0] || password[0]) {
+ char userpwbuf[255 + 255 + 3];
+ int ulen, plen;
+ ulen = strlen(username);
+ if (ulen > 255) ulen = 255; if (ulen < 1) ulen = 1;
+ plen = strlen(password);
+ if (plen > 255) plen = 255; if (plen < 1) plen = 1;
+ userpwbuf[0] = 1; /* version number of subnegotiation */
+ userpwbuf[1] = ulen;
+ memcpy(userpwbuf+2, username, ulen);
+ userpwbuf[ulen+2] = plen;
+ memcpy(userpwbuf+ulen+3, password, plen);
+ sk_write(p->sub_socket, userpwbuf, ulen + plen + 3);
+ p->state = 7;
+ } else
+ plug_closing(p->plug, "Proxy error: Server chose "
+ "username/password authentication but we "
+ "didn't offer it!",
+ PROXY_ERROR_GENERAL, 0);
+ return 1;
+ }
+
+ if (p->state == 6) {
+ int ret;
+ ret = proxy_socks5_selectchap(p);
+ if (ret) return ret;
+ }
+
+ }
+
+ plug_closing(p->plug, "Proxy error: Unexpected proxy error",
+ PROXY_ERROR_UNEXPECTED, 0);
+ return 1;
+}
+
+/* ----------------------------------------------------------------------
+ * `Telnet' proxy type.
+ *
+ * (This is for ad-hoc proxies where you connect to the proxy's
+ * telnet port and send a command such as `connect host port'. The
+ * command is configurable, since this proxy type is typically not
+ * standardised or at all well-defined.)
+ */
+
+char *format_telnet_command(SockAddr addr, int port, Conf *conf)
+{
+ char *fmt = conf_get_str(conf, CONF_proxy_telnet_command);
+ char *ret = NULL;
+ int retlen = 0, retsize = 0;
+ int so = 0, eo = 0;
+#define ENSURE(n) do { \
+ if (retsize < retlen + n) { \
+ retsize = retlen + n + 512; \
+ ret = sresize(ret, retsize, char); \
+ } \
+} while (0)
+
+ /* we need to escape \\, \%, \r, \n, \t, \x??, \0???,
+ * %%, %host, %port, %user, and %pass
+ */
+
+ while (fmt[eo] != 0) {
+
+ /* scan forward until we hit end-of-line,
+ * or an escape character (\ or %) */
+ while (fmt[eo] != 0 && fmt[eo] != '%' && fmt[eo] != '\\')
+ eo++;
+
+ /* if we hit eol, break out of our escaping loop */
+ if (fmt[eo] == 0) break;
+
+ /* if there was any unescaped text before the escape
+ * character, send that now */
+ if (eo != so) {
+ ENSURE(eo - so);
+ memcpy(ret + retlen, fmt + so, eo - so);
+ retlen += eo - so;
+ }
+
+ so = eo++;
+
+ /* if the escape character was the last character of
+ * the line, we'll just stop and send it. */
+ if (fmt[eo] == 0) break;
+
+ if (fmt[so] == '\\') {
+
+ /* we recognize \\, \%, \r, \n, \t, \x??.
+ * anything else, we just send unescaped (including the \).
+ */
+
+ switch (fmt[eo]) {
+
+ case '\\':
+ ENSURE(1);
+ ret[retlen++] = '\\';
+ eo++;
+ break;
+
+ case '%':
+ ENSURE(1);
+ ret[retlen++] = '%';
+ eo++;
+ break;
+
+ case 'r':
+ ENSURE(1);
+ ret[retlen++] = '\r';
+ eo++;
+ break;
+
+ case 'n':
+ ENSURE(1);
+ ret[retlen++] = '\n';
+ eo++;
+ break;
+
+ case 't':
+ ENSURE(1);
+ ret[retlen++] = '\t';
+ eo++;
+ break;
+
+ case 'x':
+ case 'X':
+ {
+ /* escaped hexadecimal value (ie. \xff) */
+ unsigned char v = 0;
+ int i = 0;
+
+ for (;;) {
+ eo++;
+ if (fmt[eo] >= '0' && fmt[eo] <= '9')
+ v += fmt[eo] - '0';
+ else if (fmt[eo] >= 'a' && fmt[eo] <= 'f')
+ v += fmt[eo] - 'a' + 10;
+ else if (fmt[eo] >= 'A' && fmt[eo] <= 'F')
+ v += fmt[eo] - 'A' + 10;
+ else {
+ /* non hex character, so we abort and just
+ * send the whole thing unescaped (including \x)
+ */
+ ENSURE(1);
+ ret[retlen++] = '\\';
+ eo = so + 1;
+ break;
+ }
+
+ /* we only extract two hex characters */
+ if (i == 1) {
+ ENSURE(1);
+ ret[retlen++] = v;
+ eo++;
+ break;
+ }
+
+ i++;
+ v <<= 4;
+ }
+ }
+ break;
+
+ default:
+ ENSURE(2);
+ memcpy(ret+retlen, fmt + so, 2);
+ retlen += 2;
+ eo++;
+ break;
+ }
+ } else {
+
+ /* % escape. we recognize %%, %host, %port, %user, %pass.
+ * %proxyhost, %proxyport. Anything else we just send
+ * unescaped (including the %).
+ */
+
+ if (fmt[eo] == '%') {
+ ENSURE(1);
+ ret[retlen++] = '%';
+ eo++;
+ }
+ else if (strnicmp(fmt + eo, "host", 4) == 0) {
+ char dest[512];
+ int destlen;
+ sk_getaddr(addr, dest, lenof(dest));
+ destlen = strlen(dest);
+ ENSURE(destlen);
+ memcpy(ret+retlen, dest, destlen);
+ retlen += destlen;
+ eo += 4;
+ }
+ else if (strnicmp(fmt + eo, "port", 4) == 0) {
+ char portstr[8], portlen;
+ portlen = sprintf(portstr, "%i", port);
+ ENSURE(portlen);
+ memcpy(ret + retlen, portstr, portlen);
+ retlen += portlen;
+ eo += 4;
+ }
+ else if (strnicmp(fmt + eo, "user", 4) == 0) {
+ char *username = conf_get_str(conf, CONF_proxy_username);
+ int userlen = strlen(username);
+ ENSURE(userlen);
+ memcpy(ret+retlen, username, userlen);
+ retlen += userlen;
+ eo += 4;
+ }
+ else if (strnicmp(fmt + eo, "pass", 4) == 0) {
+ char *password = conf_get_str(conf, CONF_proxy_password);
+ int passlen = strlen(password);
+ ENSURE(passlen);
+ memcpy(ret+retlen, password, passlen);
+ retlen += passlen;
+ eo += 4;
+ }
+ else if (strnicmp(fmt + eo, "proxyhost", 9) == 0) {
+ char *host = conf_get_str(conf, CONF_proxy_host);
+ int phlen = strlen(host);
+ ENSURE(phlen);
+ memcpy(ret+retlen, host, phlen);
+ retlen += phlen;
+ eo += 9;
+ }
+ else if (strnicmp(fmt + eo, "proxyport", 9) == 0) {
+ int port = conf_get_int(conf, CONF_proxy_port);
+ char pport[50];
+ int pplen;
+ sprintf(pport, "%d", port);
+ pplen = strlen(pport);
+ ENSURE(pplen);
+ memcpy(ret+retlen, pport, pplen);
+ retlen += pplen;
+ eo += 9;
+ }
+ else {
+ /* we don't escape this, so send the % now, and
+ * don't advance eo, so that we'll consider the
+ * text immediately following the % as unescaped.
+ */
+ ENSURE(1);
+ ret[retlen++] = '%';
+ }
+ }
+
+ /* resume scanning for additional escapes after this one. */
+ so = eo;
+ }
+
+ /* if there is any unescaped text at the end of the line, send it */
+ if (eo != so) {
+ ENSURE(eo - so);
+ memcpy(ret + retlen, fmt + so, eo - so);
+ retlen += eo - so;
+ }
+
+ ENSURE(1);
+ ret[retlen] = '\0';
+ return ret;
+
+#undef ENSURE
+}
+
+int proxy_telnet_negotiate (Proxy_Socket p, int change)
+{
+ if (p->state == PROXY_CHANGE_NEW) {
+ char *formatted_cmd;
+
+ formatted_cmd = format_telnet_command(p->remote_addr, p->remote_port,
+ p->conf);
+
+ sk_write(p->sub_socket, formatted_cmd, strlen(formatted_cmd));
+ sfree(formatted_cmd);
+
+ p->state = 1;
+ return 0;
+ }
+
+ if (change == PROXY_CHANGE_CLOSING) {
+ /* if our proxy negotiation process involves closing and opening
+ * new sockets, then we would want to intercept this closing
+ * callback when we were expecting it. if we aren't anticipating
+ * a socket close, then some error must have occurred. we'll
+ * just pass those errors up to the backend.
+ */
+ return plug_closing(p->plug, p->closing_error_msg,
+ p->closing_error_code,
+ p->closing_calling_back);
+ }
+
+ if (change == PROXY_CHANGE_SENT) {
+ /* some (or all) of what we wrote to the proxy was sent.
+ * we don't do anything new, however, until we receive the
+ * proxy's response. we might want to set a timer so we can
+ * timeout the proxy negotiation after a while...
+ */
+ return 0;
+ }
+
+ if (change == PROXY_CHANGE_ACCEPTING) {
+ /* we should _never_ see this, as we are using our socket to
+ * connect to a proxy, not accepting inbound connections.
+ * what should we do? close the socket with an appropriate
+ * error message?
+ */
+ return plug_accepting(p->plug,
+ p->accepting_constructor, p->accepting_ctx);
+ }
+
+ if (change == PROXY_CHANGE_RECEIVE) {
+ /* we have received data from the underlying socket, which
+ * we'll need to parse, process, and respond to appropriately.
+ */
+
+ /* we're done */
+ proxy_activate(p);
+ /* proxy activate will have dealt with
+ * whatever is left of the buffer */
+ return 1;
+ }
+
+ plug_closing(p->plug, "Proxy error: Unexpected proxy error",
+ PROXY_ERROR_UNEXPECTED, 0);
+ return 1;
+}
diff --git a/tools/plink/proxy.h b/tools/plink/proxy.h
index 9e64aadd0..12b47e16d 100644
--- a/tools/plink/proxy.h
+++ b/tools/plink/proxy.h
@@ -1,124 +1,125 @@
-/*
- * Network proxy abstraction in PuTTY
- *
- * A proxy layer, if necessary, wedges itself between the
- * network code and the higher level backend.
- *
- * Supported proxies: HTTP CONNECT, generic telnet, SOCKS 4 & 5
- */
-
-#ifndef PUTTY_PROXY_H
-#define PUTTY_PROXY_H
-
-#define PROXY_ERROR_GENERAL 8000
-#define PROXY_ERROR_UNEXPECTED 8001
-
-typedef struct Socket_proxy_tag * Proxy_Socket;
-
-struct Socket_proxy_tag {
- const struct socket_function_table *fn;
- /* the above variable absolutely *must* be the first in this structure */
-
- char * error;
-
- Socket sub_socket;
- Plug plug;
- SockAddr remote_addr;
- int remote_port;
-
- bufchain pending_output_data;
- bufchain pending_oob_output_data;
- int pending_flush;
- bufchain pending_input_data;
- int pending_eof;
-
-#define PROXY_STATE_NEW -1
-#define PROXY_STATE_ACTIVE 0
-
- int state; /* proxy states greater than 0 are implementation
- * dependent, but represent various stages/states
- * of the initialization/setup/negotiation with the
- * proxy server.
- */
- int freeze; /* should we freeze the underlying socket when
- * we are done with the proxy negotiation? this
- * simply caches the value of sk_set_frozen calls.
- */
-
-#define PROXY_CHANGE_NEW -1
-#define PROXY_CHANGE_CLOSING 0
-#define PROXY_CHANGE_SENT 1
-#define PROXY_CHANGE_RECEIVE 2
-#define PROXY_CHANGE_ACCEPTING 3
-
- /* something has changed (a call from the sub socket
- * layer into our Proxy Plug layer, or we were just
- * created, etc), so the proxy layer needs to handle
- * this change (the type of which is the second argument)
- * and further the proxy negotiation process.
- */
-
- int (*negotiate) (Proxy_Socket /* this */, int /* change type */);
-
- /* current arguments of plug handlers
- * (for use by proxy's negotiate function)
- */
-
- /* closing */
- const char *closing_error_msg;
- int closing_error_code;
- int closing_calling_back;
-
- /* receive */
- int receive_urgent;
- char *receive_data;
- int receive_len;
-
- /* sent */
- int sent_bufsize;
-
- /* accepting */
- OSSocket accepting_sock;
-
- /* configuration, used to look up proxy settings */
- Conf *conf;
-
- /* CHAP transient data */
- int chap_num_attributes;
- int chap_num_attributes_processed;
- int chap_current_attribute;
- int chap_current_datalen;
-};
-
-typedef struct Plug_proxy_tag * Proxy_Plug;
-
-struct Plug_proxy_tag {
- const struct plug_function_table *fn;
- /* the above variable absolutely *must* be the first in this structure */
-
- Proxy_Socket proxy_socket;
-
-};
-
-extern void proxy_activate (Proxy_Socket);
-
-extern int proxy_http_negotiate (Proxy_Socket, int);
-extern int proxy_telnet_negotiate (Proxy_Socket, int);
-extern int proxy_socks4_negotiate (Proxy_Socket, int);
-extern int proxy_socks5_negotiate (Proxy_Socket, int);
-
-/*
- * This may be reused by local-command proxies on individual
- * platforms.
- */
-char *format_telnet_command(SockAddr addr, int port, Conf *conf);
-
-/*
- * These are implemented in cproxy.c or nocproxy.c, depending on
- * whether encrypted proxy authentication is available.
- */
-extern void proxy_socks5_offerencryptedauth(char *command, int *len);
-extern int proxy_socks5_handlechap (Proxy_Socket p);
-extern int proxy_socks5_selectchap(Proxy_Socket p);
-
-#endif
+/*
+ * Network proxy abstraction in PuTTY
+ *
+ * A proxy layer, if necessary, wedges itself between the
+ * network code and the higher level backend.
+ *
+ * Supported proxies: HTTP CONNECT, generic telnet, SOCKS 4 & 5
+ */
+
+#ifndef PUTTY_PROXY_H
+#define PUTTY_PROXY_H
+
+#define PROXY_ERROR_GENERAL 8000
+#define PROXY_ERROR_UNEXPECTED 8001
+
+typedef struct Socket_proxy_tag * Proxy_Socket;
+
+struct Socket_proxy_tag {
+ const struct socket_function_table *fn;
+ /* the above variable absolutely *must* be the first in this structure */
+
+ char * error;
+
+ Socket sub_socket;
+ Plug plug;
+ SockAddr remote_addr;
+ int remote_port;
+
+ bufchain pending_output_data;
+ bufchain pending_oob_output_data;
+ int pending_flush;
+ bufchain pending_input_data;
+ int pending_eof;
+
+#define PROXY_STATE_NEW -1
+#define PROXY_STATE_ACTIVE 0
+
+ int state; /* proxy states greater than 0 are implementation
+ * dependent, but represent various stages/states
+ * of the initialization/setup/negotiation with the
+ * proxy server.
+ */
+ int freeze; /* should we freeze the underlying socket when
+ * we are done with the proxy negotiation? this
+ * simply caches the value of sk_set_frozen calls.
+ */
+
+#define PROXY_CHANGE_NEW -1
+#define PROXY_CHANGE_CLOSING 0
+#define PROXY_CHANGE_SENT 1
+#define PROXY_CHANGE_RECEIVE 2
+#define PROXY_CHANGE_ACCEPTING 3
+
+ /* something has changed (a call from the sub socket
+ * layer into our Proxy Plug layer, or we were just
+ * created, etc), so the proxy layer needs to handle
+ * this change (the type of which is the second argument)
+ * and further the proxy negotiation process.
+ */
+
+ int (*negotiate) (Proxy_Socket /* this */, int /* change type */);
+
+ /* current arguments of plug handlers
+ * (for use by proxy's negotiate function)
+ */
+
+ /* closing */
+ const char *closing_error_msg;
+ int closing_error_code;
+ int closing_calling_back;
+
+ /* receive */
+ int receive_urgent;
+ char *receive_data;
+ int receive_len;
+
+ /* sent */
+ int sent_bufsize;
+
+ /* accepting */
+ accept_fn_t accepting_constructor;
+ accept_ctx_t accepting_ctx;
+
+ /* configuration, used to look up proxy settings */
+ Conf *conf;
+
+ /* CHAP transient data */
+ int chap_num_attributes;
+ int chap_num_attributes_processed;
+ int chap_current_attribute;
+ int chap_current_datalen;
+};
+
+typedef struct Plug_proxy_tag * Proxy_Plug;
+
+struct Plug_proxy_tag {
+ const struct plug_function_table *fn;
+ /* the above variable absolutely *must* be the first in this structure */
+
+ Proxy_Socket proxy_socket;
+
+};
+
+extern void proxy_activate (Proxy_Socket);
+
+extern int proxy_http_negotiate (Proxy_Socket, int);
+extern int proxy_telnet_negotiate (Proxy_Socket, int);
+extern int proxy_socks4_negotiate (Proxy_Socket, int);
+extern int proxy_socks5_negotiate (Proxy_Socket, int);
+
+/*
+ * This may be reused by local-command proxies on individual
+ * platforms.
+ */
+char *format_telnet_command(SockAddr addr, int port, Conf *conf);
+
+/*
+ * These are implemented in cproxy.c or nocproxy.c, depending on
+ * whether encrypted proxy authentication is available.
+ */
+extern void proxy_socks5_offerencryptedauth(char *command, int *len);
+extern int proxy_socks5_handlechap (Proxy_Socket p);
+extern int proxy_socks5_selectchap(Proxy_Socket p);
+
+#endif
diff --git a/tools/plink/putty.h b/tools/plink/putty.h
index 33a9b16d1..f97c723b0 100644
--- a/tools/plink/putty.h
+++ b/tools/plink/putty.h
@@ -1,1402 +1,1461 @@
-#ifndef PUTTY_PUTTY_H
-#define PUTTY_PUTTY_H
-
-#include <stddef.h> /* for wchar_t */
-
-/*
- * Global variables. Most modules declare these `extern', but
- * window.c will do `#define PUTTY_DO_GLOBALS' before including this
- * module, and so will get them properly defined.
- */
-#ifndef GLOBAL
-#ifdef PUTTY_DO_GLOBALS
-#define GLOBAL
-#else
-#define GLOBAL extern
-#endif
-#endif
-
-#ifndef DONE_TYPEDEFS
-#define DONE_TYPEDEFS
-typedef struct conf_tag Conf;
-typedef struct backend_tag Backend;
-typedef struct terminal_tag Terminal;
-#endif
-
-#include "puttyps.h"
-#include "network.h"
-#include "misc.h"
-
-/*
- * Fingerprints of the PGP master keys that can be used to establish a trust
- * path between an executable and other files.
- */
-#define PGP_RSA_MASTER_KEY_FP \
- "8F 15 97 DA 25 30 AB 0D 88 D1 92 54 11 CF 0C 4C"
-#define PGP_DSA_MASTER_KEY_FP \
- "313C 3E76 4B74 C2C5 F2AE 83A8 4F5E 6DF5 6A93 B34E"
-
-/* Three attribute types:
- * The ATTRs (normal attributes) are stored with the characters in
- * the main display arrays
- *
- * The TATTRs (temporary attributes) are generated on the fly, they
- * can overlap with characters but not with normal attributes.
- *
- * The LATTRs (line attributes) are an entirely disjoint space of
- * flags.
- *
- * The DATTRs (display attributes) are internal to terminal.c (but
- * defined here because their values have to match the others
- * here); they reuse the TATTR_* space but are always masked off
- * before sending to the front end.
- *
- * ATTR_INVALID is an illegal colour combination.
- */
-
-#define TATTR_ACTCURS 0x40000000UL /* active cursor (block) */
-#define TATTR_PASCURS 0x20000000UL /* passive cursor (box) */
-#define TATTR_RIGHTCURS 0x10000000UL /* cursor-on-RHS */
-#define TATTR_COMBINING 0x80000000UL /* combining characters */
-
-#define DATTR_STARTRUN 0x80000000UL /* start of redraw run */
-
-#define TDATTR_MASK 0xF0000000UL
-#define TATTR_MASK (TDATTR_MASK)
-#define DATTR_MASK (TDATTR_MASK)
-
-#define LATTR_NORM 0x00000000UL
-#define LATTR_WIDE 0x00000001UL
-#define LATTR_TOP 0x00000002UL
-#define LATTR_BOT 0x00000003UL
-#define LATTR_MODE 0x00000003UL
-#define LATTR_WRAPPED 0x00000010UL /* this line wraps to next */
-#define LATTR_WRAPPED2 0x00000020UL /* with WRAPPED: CJK wide character
- wrapped to next line, so last
- single-width cell is empty */
-
-#define ATTR_INVALID 0x03FFFFU
-
-/* Like Linux use the F000 page for direct to font. */
-#define CSET_OEMCP 0x0000F000UL /* OEM Codepage DTF */
-#define CSET_ACP 0x0000F100UL /* Ansi Codepage DTF */
-
-/* These are internal use overlapping with the UTF-16 surrogates */
-#define CSET_ASCII 0x0000D800UL /* normal ASCII charset ESC ( B */
-#define CSET_LINEDRW 0x0000D900UL /* line drawing charset ESC ( 0 */
-#define CSET_SCOACS 0x0000DA00UL /* SCO Alternate charset */
-#define CSET_GBCHR 0x0000DB00UL /* UK variant charset ESC ( A */
-#define CSET_MASK 0xFFFFFF00UL /* Character set mask */
-
-#define DIRECT_CHAR(c) ((c&0xFFFFFC00)==0xD800)
-#define DIRECT_FONT(c) ((c&0xFFFFFE00)==0xF000)
-
-#define UCSERR (CSET_LINEDRW|'a') /* UCS Format error character. */
-/*
- * UCSWIDE is a special value used in the terminal data to signify
- * the character cell containing the right-hand half of a CJK wide
- * character. We use 0xDFFF because it's part of the surrogate
- * range and hence won't be used for anything else (it's impossible
- * to input it via UTF-8 because our UTF-8 decoder correctly
- * rejects surrogates).
- */
-#define UCSWIDE 0xDFFF
-
-#define ATTR_NARROW 0x800000U
-#define ATTR_WIDE 0x400000U
-#define ATTR_BOLD 0x040000U
-#define ATTR_UNDER 0x080000U
-#define ATTR_REVERSE 0x100000U
-#define ATTR_BLINK 0x200000U
-#define ATTR_FGMASK 0x0001FFU
-#define ATTR_BGMASK 0x03FE00U
-#define ATTR_COLOURS 0x03FFFFU
-#define ATTR_FGSHIFT 0
-#define ATTR_BGSHIFT 9
-
-/*
- * The definitive list of colour numbers stored in terminal
- * attribute words is kept here. It is:
- *
- * - 0-7 are ANSI colours (KRGYBMCW).
- * - 8-15 are the bold versions of those colours.
- * - 16-255 are the remains of the xterm 256-colour mode (a
- * 216-colour cube with R at most significant and B at least,
- * followed by a uniform series of grey shades running between
- * black and white but not including either on grounds of
- * redundancy).
- * - 256 is default foreground
- * - 257 is default bold foreground
- * - 258 is default background
- * - 259 is default bold background
- * - 260 is cursor foreground
- * - 261 is cursor background
- */
-
-#define ATTR_DEFFG (256 << ATTR_FGSHIFT)
-#define ATTR_DEFBG (258 << ATTR_BGSHIFT)
-#define ATTR_DEFAULT (ATTR_DEFFG | ATTR_DEFBG)
-
-struct sesslist {
- int nsessions;
- char **sessions;
- char *buffer; /* so memory can be freed later */
-};
-
-struct unicode_data {
- char **uni_tbl;
- int dbcs_screenfont;
- int font_codepage;
- int line_codepage;
- wchar_t unitab_scoacs[256];
- wchar_t unitab_line[256];
- wchar_t unitab_font[256];
- wchar_t unitab_xterm[256];
- wchar_t unitab_oemcp[256];
- unsigned char unitab_ctrl[256];
-};
-
-#define LGXF_OVR 1 /* existing logfile overwrite */
-#define LGXF_APN 0 /* existing logfile append */
-#define LGXF_ASK -1 /* existing logfile ask */
-#define LGTYP_NONE 0 /* logmode: no logging */
-#define LGTYP_ASCII 1 /* logmode: pure ascii */
-#define LGTYP_DEBUG 2 /* logmode: all chars of traffic */
-#define LGTYP_PACKETS 3 /* logmode: SSH data packets */
-#define LGTYP_SSHRAW 4 /* logmode: SSH raw data */
-
-typedef enum {
- /* Actual special commands. Originally Telnet, but some codes have
- * been re-used for similar specials in other protocols. */
- TS_AYT, TS_BRK, TS_SYNCH, TS_EC, TS_EL, TS_GA, TS_NOP, TS_ABORT,
- TS_AO, TS_IP, TS_SUSP, TS_EOR, TS_EOF, TS_LECHO, TS_RECHO, TS_PING,
- TS_EOL,
- /* Special command for SSH. */
- TS_REKEY,
- /* POSIX-style signals. (not Telnet) */
- TS_SIGABRT, TS_SIGALRM, TS_SIGFPE, TS_SIGHUP, TS_SIGILL,
- TS_SIGINT, TS_SIGKILL, TS_SIGPIPE, TS_SIGQUIT, TS_SIGSEGV,
- TS_SIGTERM, TS_SIGUSR1, TS_SIGUSR2,
- /* Pseudo-specials used for constructing the specials menu. */
- TS_SEP, /* Separator */
- TS_SUBMENU, /* Start a new submenu with specified name */
- TS_EXITMENU /* Exit current submenu or end of specials */
-} Telnet_Special;
-
-struct telnet_special {
- const char *name;
- int code;
-};
-
-typedef enum {
- MBT_NOTHING,
- MBT_LEFT, MBT_MIDDLE, MBT_RIGHT, /* `raw' button designations */
- MBT_SELECT, MBT_EXTEND, MBT_PASTE, /* `cooked' button designations */
- MBT_WHEEL_UP, MBT_WHEEL_DOWN /* mouse wheel */
-} Mouse_Button;
-
-typedef enum {
- MA_NOTHING, MA_CLICK, MA_2CLK, MA_3CLK, MA_DRAG, MA_RELEASE
-} Mouse_Action;
-
-/* Keyboard modifiers -- keys the user is actually holding down */
-
-#define PKM_SHIFT 0x01
-#define PKM_CONTROL 0x02
-#define PKM_META 0x04
-#define PKM_ALT 0x08
-
-/* Keyboard flags that aren't really modifiers */
-#define PKF_CAPSLOCK 0x10
-#define PKF_NUMLOCK 0x20
-#define PKF_REPEAT 0x40
-
-/* Stand-alone keysyms for function keys */
-
-typedef enum {
- PK_NULL, /* No symbol for this key */
- /* Main keypad keys */
- PK_ESCAPE, PK_TAB, PK_BACKSPACE, PK_RETURN, PK_COMPOSE,
- /* Editing keys */
- PK_HOME, PK_INSERT, PK_DELETE, PK_END, PK_PAGEUP, PK_PAGEDOWN,
- /* Cursor keys */
- PK_UP, PK_DOWN, PK_RIGHT, PK_LEFT, PK_REST,
- /* Numeric keypad */ /* Real one looks like: */
- PK_PF1, PK_PF2, PK_PF3, PK_PF4, /* PF1 PF2 PF3 PF4 */
- PK_KPCOMMA, PK_KPMINUS, PK_KPDECIMAL, /* 7 8 9 - */
- PK_KP0, PK_KP1, PK_KP2, PK_KP3, PK_KP4, /* 4 5 6 , */
- PK_KP5, PK_KP6, PK_KP7, PK_KP8, PK_KP9, /* 1 2 3 en- */
- PK_KPBIGPLUS, PK_KPENTER, /* 0 . ter */
- /* Top row */
- PK_F1, PK_F2, PK_F3, PK_F4, PK_F5,
- PK_F6, PK_F7, PK_F8, PK_F9, PK_F10,
- PK_F11, PK_F12, PK_F13, PK_F14, PK_F15,
- PK_F16, PK_F17, PK_F18, PK_F19, PK_F20,
- PK_PAUSE
-} Key_Sym;
-
-#define PK_ISEDITING(k) ((k) >= PK_HOME && (k) <= PK_PAGEDOWN)
-#define PK_ISCURSOR(k) ((k) >= PK_UP && (k) <= PK_REST)
-#define PK_ISKEYPAD(k) ((k) >= PK_PF1 && (k) <= PK_KPENTER)
-#define PK_ISFKEY(k) ((k) >= PK_F1 && (k) <= PK_F20)
-
-enum {
- VT_XWINDOWS, VT_OEMANSI, VT_OEMONLY, VT_POORMAN, VT_UNICODE
-};
-
-enum {
- /*
- * SSH-2 key exchange algorithms
- */
- KEX_WARN,
- KEX_DHGROUP1,
- KEX_DHGROUP14,
- KEX_DHGEX,
- KEX_RSA,
- KEX_MAX
-};
-
-enum {
- /*
- * SSH ciphers (both SSH-1 and SSH-2)
- */
- CIPHER_WARN, /* pseudo 'cipher' */
- CIPHER_3DES,
- CIPHER_BLOWFISH,
- CIPHER_AES, /* (SSH-2 only) */
- CIPHER_DES,
- CIPHER_ARCFOUR,
- CIPHER_MAX /* no. ciphers (inc warn) */
-};
-
-enum {
- /*
- * Several different bits of the PuTTY configuration seem to be
- * 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.
- */
- FORCE_ON, FORCE_OFF, AUTO
-};
-
-enum {
- /*
- * Proxy types.
- */
- PROXY_NONE, PROXY_SOCKS4, PROXY_SOCKS5,
- PROXY_HTTP, PROXY_TELNET, PROXY_CMD
-};
-
-enum {
- /*
- * Line discipline options which the backend might try to control.
- */
- LD_EDIT, /* local line editing */
- LD_ECHO /* local echo */
-};
-
-enum {
- /* Actions on remote window title query */
- TITLE_NONE, TITLE_EMPTY, TITLE_REAL
-};
-
-enum {
- /* Protocol back ends. (CONF_protocol) */
- PROT_RAW, PROT_TELNET, PROT_RLOGIN, PROT_SSH,
- /* PROT_SERIAL is supported on a subset of platforms, but it doesn't
- * hurt to define it globally. */
- PROT_SERIAL
-};
-
-enum {
- /* Bell settings (CONF_beep) */
- BELL_DISABLED, BELL_DEFAULT, BELL_VISUAL, BELL_WAVEFILE, BELL_PCSPEAKER
-};
-
-enum {
- /* Taskbar flashing indication on bell (CONF_beep_ind) */
- B_IND_DISABLED, B_IND_FLASH, B_IND_STEADY
-};
-
-enum {
- /* Resize actions (CONF_resize_action) */
- RESIZE_TERM, RESIZE_DISABLED, RESIZE_FONT, RESIZE_EITHER
-};
-
-enum {
- /* Function key types (CONF_funky_type) */
- FUNKY_TILDE,
- FUNKY_LINUX,
- FUNKY_XTERM,
- FUNKY_VT400,
- FUNKY_VT100P,
- FUNKY_SCO
-};
-
-enum {
- FQ_DEFAULT, FQ_ANTIALIASED, FQ_NONANTIALIASED, FQ_CLEARTYPE
-};
-
-enum {
- SER_PAR_NONE, SER_PAR_ODD, SER_PAR_EVEN, SER_PAR_MARK, SER_PAR_SPACE
-};
-
-enum {
- SER_FLOW_NONE, SER_FLOW_XONXOFF, SER_FLOW_RTSCTS, SER_FLOW_DSRDTR
-};
-
-/*
- * Tables of string <-> enum value mappings used in settings.c.
- * Defined here so that backends can export their GSS library tables
- * to the cross-platform settings code.
- */
-struct keyvalwhere {
- /*
- * Two fields which define a string and enum value to be
- * equivalent to each other.
- */
- char *s;
- int v;
-
- /*
- * The next pair of fields are used by gprefs() in settings.c to
- * arrange that when it reads a list of strings representing a
- * preference list and translates it into the corresponding list
- * of integers, strings not appearing in the list are entered in a
- * configurable position rather than uniformly at the end.
- */
-
- /*
- * 'vrel' indicates which other value in the list to place this
- * element relative to. It should be a value that has occurred in
- * a 'v' field of some other element of the array, or -1 to
- * indicate that we simply place relative to one or other end of
- * the list.
- *
- * gprefs will try to process the elements in an order which makes
- * this field work (i.e. so that the element referenced has been
- * added before processing this one).
- */
- int vrel;
-
- /*
- * 'where' indicates whether to place the new value before or
- * after the one referred to by vrel. -1 means before; +1 means
- * after.
- *
- * When vrel is -1, this also implicitly indicates which end of
- * the array to use. So vrel=-1, where=-1 means to place _before_
- * some end of the list (hence, at the last element); vrel=-1,
- * where=+1 means to place _after_ an end (hence, at the first).
- */
- int where;
-};
-
-#ifndef NO_GSSAPI
-extern const int ngsslibs;
-extern const char *const gsslibnames[]; /* for displaying in configuration */
-extern const struct keyvalwhere gsslibkeywords[]; /* for settings.c */
-#endif
-
-extern const char *const ttymodes[];
-
-enum {
- /*
- * Network address types. Used for specifying choice of IPv4/v6
- * in config; also used in proxy.c to indicate whether a given
- * host name has already been resolved or will be resolved at
- * the proxy end.
- */
- ADDRTYPE_UNSPEC, ADDRTYPE_IPV4, ADDRTYPE_IPV6, ADDRTYPE_NAME
-};
-
-struct backend_tag {
- const char *(*init) (void *frontend_handle, void **backend_handle,
- Conf *conf, 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);
- /* back->sendbuffer() does the same thing but without attempting a send */
- int (*sendbuffer) (void *handle);
- void (*size) (void *handle, int width, int height);
- void (*special) (void *handle, Telnet_Special code);
- const struct telnet_special *(*get_specials) (void *handle);
- int (*connected) (void *handle);
- int (*exitcode) (void *handle);
- /* If back->sendok() returns FALSE, data sent to it from the frontend
- * may be lost. */
- int (*sendok) (void *handle);
- int (*ldisc) (void *handle, int);
- void (*provide_ldisc) (void *handle, void *ldisc);
- void (*provide_logctx) (void *handle, void *logctx);
- /*
- * back->unthrottle() tells the back end that the front end
- * buffer is clearing.
- */
- void (*unthrottle) (void *handle, int);
- int (*cfg_info) (void *handle);
- char *name;
- int protocol;
- int default_port;
-};
-
-extern Backend *backends[];
-
-/*
- * Suggested default protocol provided by the backend link module.
- * The application is free to ignore this.
- */
-extern const int be_default_protocol;
-
-/*
- * Name of this particular application, for use in the config box
- * and other pieces of text.
- */
-extern const char *const appname;
-
-/*
- * Some global flags denoting the type of application.
- *
- * FLAG_VERBOSE is set when the user requests verbose details.
- *
- * FLAG_STDERR is set in command-line applications (which have a
- * functioning stderr that it makes sense to write to) and not in
- * GUI applications (which don't).
- *
- * FLAG_INTERACTIVE is set when a full interactive shell session is
- * being run, _either_ because no remote command has been provided
- * _or_ because the application is GUI and can't run non-
- * interactively.
- *
- * These flags describe the type of _application_ - they wouldn't
- * vary between individual sessions - and so it's OK to have this
- * variable be GLOBAL.
- *
- * Note that additional flags may be defined in platform-specific
- * headers. It's probably best if those ones start from 0x1000, to
- * avoid collision.
- */
-#define FLAG_VERBOSE 0x0001
-#define FLAG_STDERR 0x0002
-#define FLAG_INTERACTIVE 0x0004
-GLOBAL int flags;
-
-/*
- * Likewise, these two variables are set up when the application
- * initialises, and inform all default-settings accesses after
- * that.
- */
-GLOBAL int default_protocol;
-GLOBAL int default_port;
-
-/*
- * This is set TRUE by cmdline.c iff a session is loaded with "-load".
- */
-GLOBAL int loaded_session;
-/*
- * This is set to the name of the loaded session.
- */
-GLOBAL char *cmdline_session_name;
-
-struct RSAKey; /* be a little careful of scope */
-
-/*
- * Mechanism for getting text strings such as usernames and passwords
- * from the front-end.
- * The fields are mostly modelled after SSH's keyboard-interactive auth.
- * FIXME We should probably mandate a character set/encoding (probably UTF-8).
- *
- * Since many of the pieces of text involved may be chosen by the server,
- * the caller must take care to ensure that the server can't spoof locally-
- * generated prompts such as key passphrase prompts. Some ground rules:
- * - If the front-end needs to truncate a string, it should lop off the
- * end.
- * - The front-end should filter out any dangerous characters and
- * generally not trust the strings. (But \n is required to behave
- * vaguely sensibly, at least in `instruction', and ideally in
- * `prompt[]' too.)
- */
-typedef struct {
- char *prompt;
- int echo;
- /*
- * 'result' must be a dynamically allocated array of exactly
- * 'resultsize' chars. The code for actually reading input may
- * realloc it bigger (and adjust resultsize accordingly) if it has
- * to. The caller should free it again when finished with it.
- *
- * If resultsize==0, then result may be NULL. When setting up a
- * prompt_t, it's therefore easiest to initialise them this way,
- * which means all actual allocation is done by the callee. This
- * is what add_prompt does.
- */
- char *result;
- size_t resultsize;
-} prompt_t;
-typedef struct {
- /*
- * Indicates whether the information entered is to be used locally
- * (for instance a key passphrase prompt), or is destined for the wire.
- * This is a hint only; the front-end is at liberty not to use this
- * information (so the caller should ensure that the supplied text is
- * sufficient).
- */
- int to_server;
- char *name; /* Short description, perhaps for dialog box title */
- int name_reqd; /* Display of `name' required or optional? */
- char *instruction; /* Long description, maybe with embedded newlines */
- int instr_reqd; /* Display of `instruction' required or optional? */
- size_t n_prompts; /* May be zero (in which case display the foregoing,
- * if any, and return success) */
- prompt_t **prompts;
- void *frontend;
- void *data; /* slot for housekeeping data, managed by
- * get_userpass_input(); initially NULL */
-} prompts_t;
-prompts_t *new_prompts(void *frontend);
-void add_prompt(prompts_t *p, char *promptstr, int echo);
-void prompt_set_result(prompt_t *pr, const char *newstr);
-void prompt_ensure_result_size(prompt_t *pr, int len);
-/* Burn the evidence. (Assumes _all_ strings want free()ing.) */
-void free_prompts(prompts_t *p);
-
-/*
- * Exports from the front end.
- */
-void request_resize(void *frontend, int, int);
-void do_text(Context, int, int, wchar_t *, int, unsigned long, int);
-void do_cursor(Context, int, int, wchar_t *, int, unsigned long, int);
-int char_width(Context ctx, int uc);
-#ifdef OPTIMISE_SCROLL
-void do_scroll(Context, int, int, int);
-#endif
-void set_title(void *frontend, char *);
-void set_icon(void *frontend, char *);
-void set_sbar(void *frontend, int, int, int);
-Context get_ctx(void *frontend);
-void free_ctx(Context);
-void palette_set(void *frontend, int, int, int, int);
-void palette_reset(void *frontend);
-void write_aclip(void *frontend, char *, int, int);
-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 fatalbox(char *, ...);
-void modalfatalbox(char *, ...);
-#ifdef macintosh
-#pragma noreturn(fatalbox)
-#pragma noreturn(modalfatalbox)
-#endif
-void do_beep(void *frontend, int);
-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);
-/* 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
- * shutdown. */
-void update_specials_menu(void *frontend);
-int from_backend(void *frontend, int is_stderr, const char *data, int len);
-int from_backend_untrusted(void *frontend, const char *data, int len);
-/* Called when the back end wants to indicate that EOF has arrived on
- * the server-to-client stream. Returns FALSE to indicate that we
- * intend to keep the session open in the other direction, or TRUE to
- * indicate that if they're closing so are we. */
-int from_backend_eof(void *frontend);
-void notify_remote_exit(void *frontend);
-/* Get a sensible value for a tty mode. NULL return = don't set.
- * Otherwise, returned value should be freed by caller. */
-char *get_ttymode(void *frontend, const char *mode);
-/*
- * >0 = `got all results, carry on'
- * 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);
-#define OPTIMISE_IS_SCROLL 1
-
-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);
-void get_window_pos(void *frontend, int *x, int *y);
-void get_window_pixels(void *frontend, int *x, int *y);
-char *get_window_title(void *frontend, int icon);
-/* Hint from backend to frontend about time-consuming operations.
- * Initial state is assumed to be BUSY_NOT. */
-enum {
- BUSY_NOT, /* Not busy, all user interaction OK */
- BUSY_WAITING, /* Waiting for something; local event loops still running
- so some local interaction (e.g. menus) OK, but network
- stuff is suspended */
- BUSY_CPU /* Locally busy (e.g. crypto); user interaction suspended */
-};
-void set_busy_status(void *frontend, int status);
-
-void cleanup_exit(int);
-
-/*
- * Exports from conf.c, and a big enum (via parametric macro) of
- * configuration option keys.
- */
-#define CONFIG_OPTIONS(X) \
- /* X(value-type, subkey-type, keyword) */ \
- X(STR, NONE, host) \
- X(INT, NONE, port) \
- X(INT, NONE, protocol) \
- X(INT, NONE, addressfamily) \
- X(INT, NONE, close_on_exit) \
- X(INT, NONE, warn_on_close) \
- X(INT, NONE, ping_interval) /* in seconds */ \
- X(INT, NONE, tcp_nodelay) \
- X(INT, NONE, tcp_keepalives) \
- X(STR, NONE, loghost) /* logical host being contacted, for host key check */ \
- /* Proxy options */ \
- X(STR, NONE, proxy_exclude_list) \
- X(INT, NONE, proxy_dns) \
- X(INT, NONE, even_proxy_localhost) \
- X(INT, NONE, proxy_type) \
- X(STR, NONE, proxy_host) \
- X(INT, NONE, proxy_port) \
- X(STR, NONE, proxy_username) \
- X(STR, NONE, proxy_password) \
- X(STR, NONE, proxy_telnet_command) \
- /* SSH options */ \
- X(STR, NONE, remote_cmd) \
- X(STR, NONE, remote_cmd2) /* fallback if remote_cmd fails; never loaded or saved */ \
- X(INT, NONE, nopty) \
- X(INT, NONE, compression) \
- X(INT, INT, ssh_kexlist) \
- X(INT, NONE, ssh_rekey_time) /* in minutes */ \
- X(STR, NONE, ssh_rekey_data) /* string encoding e.g. "100K", "2M", "1G" */ \
- X(INT, NONE, tryagent) \
- X(INT, NONE, agentfwd) \
- X(INT, NONE, change_username) /* allow username switching in SSH-2 */ \
- X(INT, INT, ssh_cipherlist) \
- X(FILENAME, NONE, keyfile) \
- X(INT, NONE, sshprot) /* use v1 or v2 when both available */ \
- X(INT, NONE, ssh2_des_cbc) /* "des-cbc" unrecommended SSH-2 cipher */ \
- X(INT, NONE, ssh_no_userauth) /* bypass "ssh-userauth" (SSH-2 only) */ \
- X(INT, NONE, ssh_show_banner) /* show USERAUTH_BANNERs (SSH-2 only) */ \
- X(INT, NONE, try_tis_auth) \
- X(INT, NONE, try_ki_auth) \
- X(INT, NONE, try_gssapi_auth) /* attempt gssapi auth */ \
- X(INT, NONE, gssapifwd) /* forward tgt via gss */ \
- X(INT, INT, ssh_gsslist) /* preference order for local GSS libs */ \
- X(FILENAME, NONE, ssh_gss_custom) \
- X(INT, NONE, ssh_subsys) /* run a subsystem rather than a command */ \
- X(INT, NONE, ssh_subsys2) /* fallback to go with remote_cmd_ptr2 */ \
- X(INT, NONE, ssh_no_shell) /* avoid running a shell */ \
- X(STR, NONE, ssh_nc_host) /* host to connect to in `nc' mode */ \
- X(INT, NONE, ssh_nc_port) /* port to connect to in `nc' mode */ \
- /* Telnet options */ \
- X(STR, NONE, termtype) \
- X(STR, NONE, termspeed) \
- X(STR, STR, ttymodes) /* values are "Vvalue" or "A" */ \
- X(STR, STR, environmt) \
- X(STR, NONE, username) \
- X(INT, NONE, username_from_env) \
- X(STR, NONE, localusername) \
- X(INT, NONE, rfc_environ) \
- X(INT, NONE, passive_telnet) \
- /* Serial port options */ \
- X(STR, NONE, serline) \
- X(INT, NONE, serspeed) \
- X(INT, NONE, serdatabits) \
- X(INT, NONE, serstopbits) \
- X(INT, NONE, serparity) \
- X(INT, NONE, serflow) \
- /* Keyboard options */ \
- X(INT, NONE, bksp_is_delete) \
- X(INT, NONE, rxvt_homeend) \
- X(INT, NONE, funky_type) \
- X(INT, NONE, no_applic_c) /* totally disable app cursor keys */ \
- X(INT, NONE, no_applic_k) /* totally disable app keypad */ \
- X(INT, NONE, no_mouse_rep) /* totally disable mouse reporting */ \
- X(INT, NONE, no_remote_resize) /* disable remote resizing */ \
- X(INT, NONE, no_alt_screen) /* disable alternate screen */ \
- X(INT, NONE, no_remote_wintitle) /* disable remote retitling */ \
- X(INT, NONE, no_dbackspace) /* disable destructive backspace */ \
- X(INT, NONE, no_remote_charset) /* disable remote charset config */ \
- X(INT, NONE, remote_qtitle_action) /* remote win title query action */ \
- X(INT, NONE, app_cursor) \
- X(INT, NONE, app_keypad) \
- X(INT, NONE, nethack_keypad) \
- X(INT, NONE, telnet_keyboard) \
- X(INT, NONE, telnet_newline) \
- X(INT, NONE, alt_f4) /* is it special? */ \
- X(INT, NONE, alt_space) /* is it special? */ \
- X(INT, NONE, alt_only) /* is it special? */ \
- X(INT, NONE, localecho) \
- X(INT, NONE, localedit) \
- X(INT, NONE, alwaysontop) \
- X(INT, NONE, fullscreenonaltenter) \
- X(INT, NONE, scroll_on_key) \
- X(INT, NONE, scroll_on_disp) \
- X(INT, NONE, erase_to_scrollback) \
- X(INT, NONE, compose_key) \
- X(INT, NONE, ctrlaltkeys) \
- X(STR, NONE, wintitle) /* initial window title */ \
- /* Terminal options */ \
- X(INT, NONE, savelines) \
- X(INT, NONE, dec_om) \
- X(INT, NONE, wrap_mode) \
- X(INT, NONE, lfhascr) \
- X(INT, NONE, cursor_type) /* 0=block 1=underline 2=vertical */ \
- X(INT, NONE, blink_cur) \
- X(INT, NONE, beep) \
- X(INT, NONE, beep_ind) \
- X(INT, NONE, bellovl) /* bell overload protection active? */ \
- X(INT, NONE, bellovl_n) /* number of bells to cause overload */ \
- X(INT, NONE, bellovl_t) /* time interval for overload (seconds) */ \
- X(INT, NONE, bellovl_s) /* period of silence to re-enable bell (s) */ \
- X(FILENAME, NONE, bell_wavefile) \
- X(INT, NONE, scrollbar) \
- X(INT, NONE, scrollbar_in_fullscreen) \
- X(INT, NONE, resize_action) \
- X(INT, NONE, bce) \
- X(INT, NONE, blinktext) \
- X(INT, NONE, win_name_always) \
- X(INT, NONE, width) \
- X(INT, NONE, height) \
- X(FONT, NONE, font) \
- X(INT, NONE, font_quality) \
- X(FILENAME, NONE, logfilename) \
- X(INT, NONE, logtype) \
- X(INT, NONE, logxfovr) \
- X(INT, NONE, logflush) \
- X(INT, NONE, logomitpass) \
- X(INT, NONE, logomitdata) \
- X(INT, NONE, hide_mouseptr) \
- X(INT, NONE, sunken_edge) \
- X(INT, NONE, window_border) \
- X(STR, NONE, answerback) \
- X(STR, NONE, printer) \
- X(INT, NONE, arabicshaping) \
- X(INT, NONE, bidi) \
- /* Colour options */ \
- X(INT, NONE, ansi_colour) \
- X(INT, NONE, xterm_256_colour) \
- X(INT, NONE, system_colour) \
- X(INT, NONE, try_palette) \
- X(INT, NONE, bold_colour) \
- X(INT, INT, colours) \
- /* Selection options */ \
- X(INT, NONE, mouse_is_xterm) \
- X(INT, NONE, rect_select) \
- X(INT, NONE, rawcnp) \
- X(INT, NONE, rtf_paste) \
- X(INT, NONE, mouse_override) \
- X(INT, INT, wordness) \
- /* translations */ \
- X(INT, NONE, vtmode) \
- X(STR, NONE, line_codepage) \
- X(INT, NONE, cjk_ambig_wide) \
- X(INT, NONE, utf8_override) \
- X(INT, NONE, xlat_capslockcyr) \
- /* X11 forwarding */ \
- X(INT, NONE, x11_forward) \
- X(STR, NONE, x11_display) \
- X(INT, NONE, x11_auth) \
- X(FILENAME, NONE, xauthfile) \
- /* port forwarding */ \
- X(INT, NONE, lport_acceptall) /* accept conns from hosts other than localhost */ \
- X(INT, NONE, rport_acceptall) /* same for remote forwarded ports (SSH-2 only) */ \
- /* \
- * Subkeys for 'portfwd' can have the following forms: \
- * \
- * [LR]localport \
- * [LR]localaddr:localport \
- * \
- * Dynamic forwardings are indicated by an 'L' key, and the \
- * special value "D". For all other forwardings, the value \
- * should be of the form 'host:port'. \
- */ \
- X(STR, STR, portfwd) \
- /* SSH bug compatibility modes */ \
- X(INT, NONE, sshbug_ignore1) \
- X(INT, NONE, sshbug_plainpw1) \
- X(INT, NONE, sshbug_rsa1) \
- X(INT, NONE, sshbug_hmac2) \
- X(INT, NONE, sshbug_derivekey2) \
- X(INT, NONE, sshbug_rsapad2) \
- X(INT, NONE, sshbug_pksessid2) \
- X(INT, NONE, sshbug_rekey2) \
- X(INT, NONE, sshbug_maxpkt2) \
- X(INT, NONE, sshbug_ignore2) \
- /* \
- * ssh_simple means that we promise never to open any channel \
- * other than the main one, which means it can safely use a very \
- * large window in SSH-2. \
- */ \
- X(INT, NONE, ssh_simple) \
- /* Options for pterm. Should split out into platform-dependent part. */ \
- X(INT, NONE, stamp_utmp) \
- X(INT, NONE, login_shell) \
- X(INT, NONE, scrollbar_on_left) \
- X(INT, NONE, shadowbold) \
- X(FONT, NONE, boldfont) \
- X(FONT, NONE, widefont) \
- X(FONT, NONE, wideboldfont) \
- X(INT, NONE, shadowboldoffset) \
- X(INT, NONE, crhaslf) \
- X(STR, NONE, winclass) \
-
-/* Now define the actual enum of option keywords using that macro. */
-#define CONF_ENUM_DEF(valtype, keytype, keyword) CONF_ ## keyword,
-enum config_primary_key { CONFIG_OPTIONS(CONF_ENUM_DEF) N_CONFIG_OPTIONS };
-#undef CONF_ENUM_DEF
-
-#define NCFGCOLOURS 22 /* number of colours in CONF_colours above */
-
-/* Functions handling configuration structures. */
-Conf *conf_new(void); /* create an empty configuration */
-void conf_free(Conf *conf);
-Conf *conf_copy(Conf *oldconf);
-void conf_copy_into(Conf *dest, Conf *src);
-/* Mandatory accessor functions: enforce by assertion that keys exist. */
-int conf_get_int(Conf *conf, int key);
-int conf_get_int_int(Conf *conf, int key, int subkey);
-char *conf_get_str(Conf *conf, int key); /* result still owned by conf */
-char *conf_get_str_str(Conf *conf, int key, const char *subkey);
-Filename *conf_get_filename(Conf *conf, int key);
-FontSpec *conf_get_fontspec(Conf *conf, int key); /* still owned by conf */
-/* Optional accessor function: return NULL if key does not exist. */
-char *conf_get_str_str_opt(Conf *conf, int key, const char *subkey);
-/* Accessor function to step through a string-subkeyed list.
- * Returns the next subkey after the provided one, or the first if NULL.
- * Returns NULL if there are none left.
- * Both the return value and *subkeyout are still owned by conf. */
-char *conf_get_str_strs(Conf *conf, int key, char *subkeyin, char **subkeyout);
-/* Return the nth string subkey in a list. Owned by conf. NULL if beyond end */
-char *conf_get_str_nthstrkey(Conf *conf, int key, int n);
-/* Functions to set entries in configuration. Always copy their inputs. */
-void conf_set_int(Conf *conf, int key, int value);
-void conf_set_int_int(Conf *conf, int key, int subkey, int value);
-void conf_set_str(Conf *conf, int key, const char *value);
-void conf_set_str_str(Conf *conf, int key,
- const char *subkey, const char *val);
-void conf_del_str_str(Conf *conf, int key, const char *subkey);
-void conf_set_filename(Conf *conf, int key, const Filename *val);
-void conf_set_fontspec(Conf *conf, int key, const FontSpec *val);
-/* Serialisation functions for Duplicate Session */
-int conf_serialised_size(Conf *conf);
-void conf_serialise(Conf *conf, void *data);
-int conf_deserialise(Conf *conf, void *data, int maxsize);/*returns size used*/
-
-/*
- * Functions to copy, free, serialise and deserialise FontSpecs.
- * Provided per-platform, to go with the platform's idea of a
- * FontSpec's contents.
- *
- * fontspec_serialise returns the number of bytes written, and can
- * handle data==NULL without crashing. So you can call it once to find
- * out a size, then again once you've allocated a buffer.
- */
-FontSpec *fontspec_copy(const FontSpec *f);
-void fontspec_free(FontSpec *f);
-int fontspec_serialise(FontSpec *f, void *data);
-FontSpec *fontspec_deserialise(void *data, int maxsize, int *used);
-
-/*
- * Exports from noise.c.
- */
-void noise_get_heavy(void (*func) (void *, int));
-void noise_get_light(void (*func) (void *, int));
-void noise_regular(void);
-void noise_ultralight(unsigned long data);
-void random_save_seed(void);
-void random_destroy_seed(void);
-
-/*
- * Exports from settings.c.
- */
-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);
-void save_open_settings(void *sesskey, Conf *conf);
-void load_settings(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 registry_cleanup(void);
-
-/*
- * Functions used by settings.c to provide platform-specific
- * default settings.
- *
- * (The integer one is expected to return `def' if it has no clear
- * opinion of its own. This is because there's no integer value
- * which I can reliably set aside to indicate `nil'. The string
- * function is perfectly all right returning NULL, of course. The
- * Filename and FontSpec functions are _not allowed_ to fail to
- * return, since these defaults _must_ be per-platform.)
- *
- * The 'Filename *' returned by platform_default_filename, and the
- * 'FontSpec *' returned by platform_default_fontspec, have ownership
- * transferred to the caller, and must be freed.
- */
-char *platform_default_s(const char *name);
-int platform_default_i(const char *name, int def);
-Filename *platform_default_filename(const char *name);
-FontSpec *platform_default_fontspec(const char *name);
-
-/*
- * Exports from terminal.c.
- */
-
-Terminal *term_init(Conf *, struct unicode_data *, void *);
-void term_free(Terminal *);
-void term_size(Terminal *, int, int, int);
-void term_paint(Terminal *, Context, int, int, int, int, int);
-void term_scroll(Terminal *, int, int);
-void term_scroll_to_selection(Terminal *, int);
-void term_pwron(Terminal *, int);
-void term_clrsb(Terminal *);
-void term_mouse(Terminal *, Mouse_Button, Mouse_Button, Mouse_Action,
- int,int,int,int,int);
-void term_key(Terminal *, Key_Sym, wchar_t *, size_t, unsigned int,
- unsigned int);
-void term_deselect(Terminal *);
-void term_update(Terminal *);
-void term_invalidate(Terminal *);
-void term_blink(Terminal *, int set_cursor);
-void term_do_paste(Terminal *);
-int term_paste_pending(Terminal *);
-void term_paste(Terminal *);
-void term_nopaste(Terminal *);
-int term_ldisc(Terminal *, int option);
-void term_copyall(Terminal *);
-void term_reconfig(Terminal *, Conf *);
-void term_seen_key_event(Terminal *);
-int term_data(Terminal *, int is_stderr, const char *data, int len);
-int term_data_untrusted(Terminal *, const char *data, int len);
-void term_provide_resize_fn(Terminal *term,
- void (*resize_fn)(void *, int, int),
- void *resize_ctx);
-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);
-
-int format_arrow_key(char *buf, Terminal *term, int xkey, int ctrl);
-
-/*
- * Exports from logging.c.
- */
-void *log_init(void *frontend, Conf *conf);
-void log_free(void *logctx);
-void log_reconfig(void *logctx, Conf *conf);
-void logfopen(void *logctx);
-void logfclose(void *logctx);
-void logtraffic(void *logctx, unsigned char c, int logmode);
-void logflush(void *logctx);
-void log_eventlog(void *logctx, const char *string);
-enum { PKT_INCOMING, PKT_OUTGOING };
-enum { PKTLOG_EMIT, PKTLOG_BLANK, PKTLOG_OMIT };
-struct logblank_t {
- int offset;
- int len;
- int type;
-};
-void log_packet(void *logctx, int direction, int type,
- char *texttype, const void *data, int len,
- int n_blanks, const struct logblank_t *blanks,
- const unsigned long *sequence);
-
-/*
- * Exports from testback.c
- */
-
-extern Backend null_backend;
-extern Backend loop_backend;
-
-/*
- * Exports from raw.c.
- */
-
-extern Backend raw_backend;
-
-/*
- * Exports from rlogin.c.
- */
-
-extern Backend rlogin_backend;
-
-/*
- * Exports from telnet.c.
- */
-
-extern Backend telnet_backend;
-
-/*
- * Exports from ssh.c.
- */
-extern Backend ssh_backend;
-
-/*
- * Exports from ldisc.c.
- */
-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);
-
-/*
- * 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);
-
-/*
- * Exports from sshrand.c.
- */
-
-void random_add_noise(void *noise, int length);
-int random_byte(void);
-void random_get_savedata(void **data, int *len);
-extern int random_active;
-/* The random number subsystem is activated if at least one other entity
- * within the program expresses an interest in it. So each SSH session
- * calls random_ref on startup and random_unref on shutdown. */
-void random_ref(void);
-void random_unref(void);
-
-/*
- * Exports from pinger.c.
- */
-typedef struct pinger_tag *Pinger;
-Pinger pinger_new(Conf *conf, Backend *back, void *backhandle);
-void pinger_reconfig(Pinger, Conf *oldconf, Conf *newconf);
-void pinger_free(Pinger);
-
-/*
- * Exports from misc.c.
- */
-
-#include "misc.h"
-int conf_launchable(Conf *conf);
-char const *conf_dest(Conf *conf);
-
-/*
- * Exports from sercfg.c.
- */
-void ser_setup_config_box(struct controlbox *b, int midsession,
- int parity_mask, int flow_mask);
-
-/*
- * Exports from version.c.
- */
-extern char ver[];
-
-/*
- * Exports from unicode.c.
- */
-#ifndef CP_UTF8
-#define CP_UTF8 65001
-#endif
-/* void init_ucs(void); -- this is now in platform-specific headers */
-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,
- struct unicode_data *ucsdata);
-wchar_t xlat_uskbd2cyrllic(int ch);
-int check_compose(int first, int second);
-int decode_codepage(char *cp_name);
-const char *cp_enumerate (int index);
-const char *cp_name(int codepage);
-void get_unitab(int codepage, wchar_t * unitab, int ftype);
-
-/*
- * Exports from wcwidth.c
- */
-int mk_wcwidth(wchar_t ucs);
-int mk_wcswidth(const wchar_t *pwcs, size_t n);
-int mk_wcwidth_cjk(wchar_t ucs);
-int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n);
-
-/*
- * Exports from mscrypto.c
- */
-#ifdef MSCRYPTOAPI
-int crypto_startup();
-void crypto_wrapup();
-#endif
-
-/*
- * Exports from pageantc.c.
- *
- * agent_query returns 1 for here's-a-response, and 0 for query-in-
- * progress. In the latter case there will be a call to `callback'
- * at some future point, passing callback_ctx as the first
- * parameter and the actual reply data as the second and third.
- *
- * The response may be a NULL pointer (in either of the synchronous
- * or asynchronous cases), which indicates failure to receive a
- * response.
- */
-int agent_query(void *in, int inlen, void **out, int *outlen,
- void (*callback)(void *, void *, int), void *callback_ctx);
-int agent_exists(void);
-
-/*
- * Exports from wildcard.c
- */
-const char *wc_error(int value);
-int wc_match(const char *wildcard, const char *target);
-int wc_unescape(char *output, const char *wildcard);
-
-/*
- * Exports from frontend (windlg.c etc)
- */
-void logevent(void *frontend, const char *);
-void pgp_fingerprints(void);
-/*
- * verify_ssh_host_key() can return one of three values:
- *
- * - +1 means `key was OK' (either already known or the user just
- * approved it) `so continue with the connection'
- *
- * - 0 means `key was not OK, abandon the connection'
- *
- * - -1 means `I've initiated enquiries, please wait to be called
- * 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,
- void (*callback)(void *ctx, int result), void *ctx);
-/*
- * askalg has the same set of return values as verify_ssh_host_key.
- */
-int askalg(void *frontend, const char *algtype, const char *algname,
- void (*callback)(void *ctx, int result), void *ctx);
-/*
- * askappend can return four values:
- *
- * - 2 means overwrite the log file
- * - 1 means append to the log file
- * - 0 means cancel logging for this session
- * - -1 means please wait.
- */
-int askappend(void *frontend, Filename *filename,
- void (*callback)(void *ctx, int result), void *ctx);
-
-/*
- * Exports from console frontends (wincons.c, uxcons.c)
- * 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);
-void console_provide_logctx(void *logctx);
-int is_interactive(void);
-
-/*
- * Exports from printing.c.
- */
-typedef struct printer_enum_tag printer_enum;
-typedef struct printer_job_tag printer_job;
-printer_enum *printer_start_enum(int *nprinters);
-char *printer_get_name(printer_enum *, int);
-void printer_finish_enum(printer_enum *);
-printer_job *printer_start_job(char *printer);
-void printer_job_data(printer_job *, void *, int);
-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).
- */
-int cmdline_process_param(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);
-#define TOOLTYPE_FILETRANSFER 1
-#define TOOLTYPE_NONNETWORK 2
-extern int cmdline_tooltype;
-
-void cmdline_error(char *, ...);
-
-/*
- * Exports from config.c.
- */
-struct controlbox;
-union control;
-void conf_radiobutton_handler(union control *ctrl, void *dlg,
- void *data, int event);
-#define CHECKBOX_INVERT (1<<30)
-void conf_checkbox_handler(union control *ctrl, void *dlg,
- void *data, int event);
-void conf_editbox_handler(union control *ctrl, void *dlg,
- void *data, int event);
-void conf_filesel_handler(union control *ctrl, void *dlg,
- void *data, int event);
-void conf_fontsel_handler(union control *ctrl, void *dlg,
- void *data, int event);
-void setup_config_box(struct controlbox *b, int midsession,
- int protocol, int protcfginfo);
-
-/*
- * Exports from minibidi.c.
- */
-typedef struct bidi_char {
- wchar_t origwc, wc;
- unsigned short index;
-} bidi_char;
-int do_bidi(bidi_char *line, int count);
-int do_shape(bidi_char *line, bidi_char *to, int count);
-int is_rtl(int c);
-
-/*
- * X11 auth mechanisms we know about.
- */
-enum {
- X11_NO_AUTH,
- X11_MIT, /* MIT-MAGIC-COOKIE-1 */
- X11_XDM, /* XDM-AUTHORIZATION-1 */
- X11_NAUTHS
-};
-extern const char *const x11_authnames[]; /* declared in x11fwd.c */
-
-/*
- * Miscellaneous exports from the platform-specific code.
- *
- * filename_serialise and filename_deserialise have the same semantics
- * as fontspec_serialise and fontspec_deserialise above.
- */
-Filename *filename_from_str(const char *string);
-const char *filename_to_str(const Filename *fn);
-int filename_equal(const Filename *f1, const Filename *f2);
-int filename_is_null(const Filename *fn);
-Filename *filename_copy(const Filename *fn);
-void filename_free(Filename *fn);
-int filename_serialise(const Filename *f, void *data);
-Filename *filename_deserialise(void *data, int maxsize, int *used);
-char *get_username(void); /* return value needs freeing */
-char *get_random_data(int bytes); /* used in cmdgen.c */
-
-/*
- * Exports and imports from timing.c.
- *
- * schedule_timer() asks the front end to schedule a callback to a
- * timer function in a given number of ticks. The returned value is
- * the time (in ticks since an arbitrary offset) at which the
- * callback can be expected. This value will also be passed as the
- * `now' parameter to the callback function. Hence, you can (for
- * example) schedule an event at a particular time by calling
- * schedule_timer() and storing the return value in your context
- * structure as the time when that event is due. The first time a
- * callback function gives you that value or more as `now', you do
- * the thing.
- *
- * expire_timer_context() drops all current timers associated with
- * a given value of ctx (for when you're about to free ctx).
- *
- * run_timers() is called from the front end when it has reason to
- * think some timers have reached their moment, or when it simply
- * needs to know how long to wait next. We pass it the time we
- * think it is. It returns TRUE and places the time when the next
- * timer needs to go off in `next', or alternatively it returns
- * FALSE if there are no timers at all pending.
- *
- * timer_change_notify() must be supplied by the front end; it
- * notifies the front end that a new timer has been added to the
- * list which is sooner than any existing ones. It provides the
- * time when that timer needs to go off.
- *
- * *** FRONT END IMPLEMENTORS NOTE:
- *
- * There's an important subtlety in the front-end implementation of
- * the timer interface. When a front end is given a `next' value,
- * either returned from run_timers() or via timer_change_notify(),
- * it should ensure that it really passes _that value_ as the `now'
- * parameter to its next run_timers call. It should _not_ simply
- * call GETTICKCOUNT() to get the `now' parameter when invoking
- * run_timers().
- *
- * The reason for this is that an OS's system clock might not agree
- * exactly with the timing mechanisms it supplies to wait for a
- * given interval. I'll illustrate this by the simple example of
- * Unix Plink, which uses timeouts to select() in a way which for
- * these purposes can simply be considered to be a wait() function.
- * Suppose, for the sake of argument, that this wait() function
- * tends to return early by 1%. Then a possible sequence of actions
- * is:
- *
- * - run_timers() tells the front end that the next timer firing
- * is 10000ms from now.
- * - Front end calls wait(10000ms), but according to
- * GETTICKCOUNT() it has only waited for 9900ms.
- * - Front end calls run_timers() again, passing time T-100ms as
- * `now'.
- * - run_timers() does nothing, and says the next timer firing is
- * still 100ms from now.
- * - Front end calls wait(100ms), which only waits for 99ms.
- * - Front end calls run_timers() yet again, passing time T-1ms.
- * - run_timers() says there's still 1ms to wait.
- * - Front end calls wait(1ms).
- *
- * If you're _lucky_ at this point, wait(1ms) will actually wait
- * for 1ms and you'll only have woken the program up three times.
- * If you're unlucky, wait(1ms) might do nothing at all due to
- * being below some minimum threshold, and you might find your
- * program spends the whole of the last millisecond tight-looping
- * between wait() and run_timers().
- *
- * Instead, what you should do is to _save_ the precise `next'
- * value provided by run_timers() or via timer_change_notify(), and
- * use that precise value as the input to the next run_timers()
- * call. So:
- *
- * - run_timers() tells the front end that the next timer firing
- * is at time T, 10000ms from now.
- * - Front end calls wait(10000ms).
- * - Front end then immediately calls run_timers() and passes it
- * time T, without stopping to check GETTICKCOUNT() at all.
- *
- * This guarantees that the program wakes up only as many times as
- * there are actual timer actions to be taken, and that the timing
- * mechanism will never send it into a tight loop.
- *
- * (It does also mean that the timer action in the above example
- * will occur 100ms early, but this is not generally critical. And
- * the hypothetical 1% error in wait() will be partially corrected
- * for anyway when, _after_ run_timers() returns, you call
- * GETTICKCOUNT() and compare the result with the returned `next'
- * value to find out how long you have to make your next wait().)
- */
-typedef void (*timer_fn_t)(void *ctx, long now);
-long schedule_timer(int ticks, timer_fn_t fn, void *ctx);
-void expire_timer_context(void *ctx);
-int run_timers(long now, long *next);
-void timer_change_notify(long next);
-
-/*
- * Define no-op macros for the jump list functions, on platforms that
- * don't support them. (This is a bit of a hack, and it'd be nicer to
- * localise even the calls to those functions into the Windows front
- * end, but it'll do for the moment.)
- */
-#ifndef JUMPLIST_SUPPORTED
-#define add_session_to_jumplist(x) ((void)0)
-#define remove_session_from_jumplist(x) ((void)0)
-#endif
-
-#endif
+#ifndef PUTTY_PUTTY_H
+#define PUTTY_PUTTY_H
+
+#include <stddef.h> /* for wchar_t */
+
+/*
+ * Global variables. Most modules declare these `extern', but
+ * window.c will do `#define PUTTY_DO_GLOBALS' before including this
+ * module, and so will get them properly defined.
+ */
+#ifndef GLOBAL
+#ifdef PUTTY_DO_GLOBALS
+#define GLOBAL
+#else
+#define GLOBAL extern
+#endif
+#endif
+
+#ifndef DONE_TYPEDEFS
+#define DONE_TYPEDEFS
+typedef struct conf_tag Conf;
+typedef struct backend_tag Backend;
+typedef struct terminal_tag Terminal;
+#endif
+
+#include "puttyps.h"
+#include "network.h"
+#include "misc.h"
+
+/*
+ * Fingerprints of the PGP master keys that can be used to establish a trust
+ * path between an executable and other files.
+ */
+#define PGP_RSA_MASTER_KEY_FP \
+ "8F 15 97 DA 25 30 AB 0D 88 D1 92 54 11 CF 0C 4C"
+#define PGP_DSA_MASTER_KEY_FP \
+ "313C 3E76 4B74 C2C5 F2AE 83A8 4F5E 6DF5 6A93 B34E"
+
+/* Three attribute types:
+ * The ATTRs (normal attributes) are stored with the characters in
+ * the main display arrays
+ *
+ * The TATTRs (temporary attributes) are generated on the fly, they
+ * can overlap with characters but not with normal attributes.
+ *
+ * The LATTRs (line attributes) are an entirely disjoint space of
+ * flags.
+ *
+ * The DATTRs (display attributes) are internal to terminal.c (but
+ * defined here because their values have to match the others
+ * here); they reuse the TATTR_* space but are always masked off
+ * before sending to the front end.
+ *
+ * ATTR_INVALID is an illegal colour combination.
+ */
+
+#define TATTR_ACTCURS 0x40000000UL /* active cursor (block) */
+#define TATTR_PASCURS 0x20000000UL /* passive cursor (box) */
+#define TATTR_RIGHTCURS 0x10000000UL /* cursor-on-RHS */
+#define TATTR_COMBINING 0x80000000UL /* combining characters */
+
+#define DATTR_STARTRUN 0x80000000UL /* start of redraw run */
+
+#define TDATTR_MASK 0xF0000000UL
+#define TATTR_MASK (TDATTR_MASK)
+#define DATTR_MASK (TDATTR_MASK)
+
+#define LATTR_NORM 0x00000000UL
+#define LATTR_WIDE 0x00000001UL
+#define LATTR_TOP 0x00000002UL
+#define LATTR_BOT 0x00000003UL
+#define LATTR_MODE 0x00000003UL
+#define LATTR_WRAPPED 0x00000010UL /* this line wraps to next */
+#define LATTR_WRAPPED2 0x00000020UL /* with WRAPPED: CJK wide character
+ wrapped to next line, so last
+ single-width cell is empty */
+
+#define ATTR_INVALID 0x03FFFFU
+
+/* Like Linux use the F000 page for direct to font. */
+#define CSET_OEMCP 0x0000F000UL /* OEM Codepage DTF */
+#define CSET_ACP 0x0000F100UL /* Ansi Codepage DTF */
+
+/* These are internal use overlapping with the UTF-16 surrogates */
+#define CSET_ASCII 0x0000D800UL /* normal ASCII charset ESC ( B */
+#define CSET_LINEDRW 0x0000D900UL /* line drawing charset ESC ( 0 */
+#define CSET_SCOACS 0x0000DA00UL /* SCO Alternate charset */
+#define CSET_GBCHR 0x0000DB00UL /* UK variant charset ESC ( A */
+#define CSET_MASK 0xFFFFFF00UL /* Character set mask */
+
+#define DIRECT_CHAR(c) ((c&0xFFFFFC00)==0xD800)
+#define DIRECT_FONT(c) ((c&0xFFFFFE00)==0xF000)
+
+#define UCSERR (CSET_LINEDRW|'a') /* UCS Format error character. */
+/*
+ * UCSWIDE is a special value used in the terminal data to signify
+ * the character cell containing the right-hand half of a CJK wide
+ * character. We use 0xDFFF because it's part of the surrogate
+ * range and hence won't be used for anything else (it's impossible
+ * to input it via UTF-8 because our UTF-8 decoder correctly
+ * rejects surrogates).
+ */
+#define UCSWIDE 0xDFFF
+
+#define ATTR_NARROW 0x800000U
+#define ATTR_WIDE 0x400000U
+#define ATTR_BOLD 0x040000U
+#define ATTR_UNDER 0x080000U
+#define ATTR_REVERSE 0x100000U
+#define ATTR_BLINK 0x200000U
+#define ATTR_FGMASK 0x0001FFU
+#define ATTR_BGMASK 0x03FE00U
+#define ATTR_COLOURS 0x03FFFFU
+#define ATTR_FGSHIFT 0
+#define ATTR_BGSHIFT 9
+
+/*
+ * The definitive list of colour numbers stored in terminal
+ * attribute words is kept here. It is:
+ *
+ * - 0-7 are ANSI colours (KRGYBMCW).
+ * - 8-15 are the bold versions of those colours.
+ * - 16-255 are the remains of the xterm 256-colour mode (a
+ * 216-colour cube with R at most significant and B at least,
+ * followed by a uniform series of grey shades running between
+ * black and white but not including either on grounds of
+ * redundancy).
+ * - 256 is default foreground
+ * - 257 is default bold foreground
+ * - 258 is default background
+ * - 259 is default bold background
+ * - 260 is cursor foreground
+ * - 261 is cursor background
+ */
+
+#define ATTR_DEFFG (256 << ATTR_FGSHIFT)
+#define ATTR_DEFBG (258 << ATTR_BGSHIFT)
+#define ATTR_DEFAULT (ATTR_DEFFG | ATTR_DEFBG)
+
+struct sesslist {
+ int nsessions;
+ char **sessions;
+ char *buffer; /* so memory can be freed later */
+};
+
+struct unicode_data {
+ char **uni_tbl;
+ int dbcs_screenfont;
+ int font_codepage;
+ int line_codepage;
+ wchar_t unitab_scoacs[256];
+ wchar_t unitab_line[256];
+ wchar_t unitab_font[256];
+ wchar_t unitab_xterm[256];
+ wchar_t unitab_oemcp[256];
+ unsigned char unitab_ctrl[256];
+};
+
+#define LGXF_OVR 1 /* existing logfile overwrite */
+#define LGXF_APN 0 /* existing logfile append */
+#define LGXF_ASK -1 /* existing logfile ask */
+#define LGTYP_NONE 0 /* logmode: no logging */
+#define LGTYP_ASCII 1 /* logmode: pure ascii */
+#define LGTYP_DEBUG 2 /* logmode: all chars of traffic */
+#define LGTYP_PACKETS 3 /* logmode: SSH data packets */
+#define LGTYP_SSHRAW 4 /* logmode: SSH raw data */
+
+typedef enum {
+ /* Actual special commands. Originally Telnet, but some codes have
+ * been re-used for similar specials in other protocols. */
+ TS_AYT, TS_BRK, TS_SYNCH, TS_EC, TS_EL, TS_GA, TS_NOP, TS_ABORT,
+ TS_AO, TS_IP, TS_SUSP, TS_EOR, TS_EOF, TS_LECHO, TS_RECHO, TS_PING,
+ TS_EOL,
+ /* Special command for SSH. */
+ TS_REKEY,
+ /* POSIX-style signals. (not Telnet) */
+ TS_SIGABRT, TS_SIGALRM, TS_SIGFPE, TS_SIGHUP, TS_SIGILL,
+ TS_SIGINT, TS_SIGKILL, TS_SIGPIPE, TS_SIGQUIT, TS_SIGSEGV,
+ TS_SIGTERM, TS_SIGUSR1, TS_SIGUSR2,
+ /* Pseudo-specials used for constructing the specials menu. */
+ TS_SEP, /* Separator */
+ TS_SUBMENU, /* Start a new submenu with specified name */
+ TS_EXITMENU /* Exit current submenu or end of specials */
+} Telnet_Special;
+
+struct telnet_special {
+ const char *name;
+ int code;
+};
+
+typedef enum {
+ MBT_NOTHING,
+ MBT_LEFT, MBT_MIDDLE, MBT_RIGHT, /* `raw' button designations */
+ MBT_SELECT, MBT_EXTEND, MBT_PASTE, /* `cooked' button designations */
+ MBT_WHEEL_UP, MBT_WHEEL_DOWN /* mouse wheel */
+} Mouse_Button;
+
+typedef enum {
+ MA_NOTHING, MA_CLICK, MA_2CLK, MA_3CLK, MA_DRAG, MA_RELEASE
+} Mouse_Action;
+
+/* Keyboard modifiers -- keys the user is actually holding down */
+
+#define PKM_SHIFT 0x01
+#define PKM_CONTROL 0x02
+#define PKM_META 0x04
+#define PKM_ALT 0x08
+
+/* Keyboard flags that aren't really modifiers */
+#define PKF_CAPSLOCK 0x10
+#define PKF_NUMLOCK 0x20
+#define PKF_REPEAT 0x40
+
+/* Stand-alone keysyms for function keys */
+
+typedef enum {
+ PK_NULL, /* No symbol for this key */
+ /* Main keypad keys */
+ PK_ESCAPE, PK_TAB, PK_BACKSPACE, PK_RETURN, PK_COMPOSE,
+ /* Editing keys */
+ PK_HOME, PK_INSERT, PK_DELETE, PK_END, PK_PAGEUP, PK_PAGEDOWN,
+ /* Cursor keys */
+ PK_UP, PK_DOWN, PK_RIGHT, PK_LEFT, PK_REST,
+ /* Numeric keypad */ /* Real one looks like: */
+ PK_PF1, PK_PF2, PK_PF3, PK_PF4, /* PF1 PF2 PF3 PF4 */
+ PK_KPCOMMA, PK_KPMINUS, PK_KPDECIMAL, /* 7 8 9 - */
+ PK_KP0, PK_KP1, PK_KP2, PK_KP3, PK_KP4, /* 4 5 6 , */
+ PK_KP5, PK_KP6, PK_KP7, PK_KP8, PK_KP9, /* 1 2 3 en- */
+ PK_KPBIGPLUS, PK_KPENTER, /* 0 . ter */
+ /* Top row */
+ PK_F1, PK_F2, PK_F3, PK_F4, PK_F5,
+ PK_F6, PK_F7, PK_F8, PK_F9, PK_F10,
+ PK_F11, PK_F12, PK_F13, PK_F14, PK_F15,
+ PK_F16, PK_F17, PK_F18, PK_F19, PK_F20,
+ PK_PAUSE
+} Key_Sym;
+
+#define PK_ISEDITING(k) ((k) >= PK_HOME && (k) <= PK_PAGEDOWN)
+#define PK_ISCURSOR(k) ((k) >= PK_UP && (k) <= PK_REST)
+#define PK_ISKEYPAD(k) ((k) >= PK_PF1 && (k) <= PK_KPENTER)
+#define PK_ISFKEY(k) ((k) >= PK_F1 && (k) <= PK_F20)
+
+enum {
+ VT_XWINDOWS, VT_OEMANSI, VT_OEMONLY, VT_POORMAN, VT_UNICODE
+};
+
+enum {
+ /*
+ * SSH-2 key exchange algorithms
+ */
+ KEX_WARN,
+ KEX_DHGROUP1,
+ KEX_DHGROUP14,
+ KEX_DHGEX,
+ KEX_RSA,
+ KEX_MAX
+};
+
+enum {
+ /*
+ * SSH ciphers (both SSH-1 and SSH-2)
+ */
+ CIPHER_WARN, /* pseudo 'cipher' */
+ CIPHER_3DES,
+ CIPHER_BLOWFISH,
+ CIPHER_AES, /* (SSH-2 only) */
+ CIPHER_DES,
+ CIPHER_ARCFOUR,
+ CIPHER_MAX /* no. ciphers (inc warn) */
+};
+
+enum {
+ /*
+ * Several different bits of the PuTTY configuration seem to be
+ * 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.
+ */
+ FORCE_ON, FORCE_OFF, AUTO
+};
+
+enum {
+ /*
+ * Proxy types.
+ */
+ PROXY_NONE, PROXY_SOCKS4, PROXY_SOCKS5,
+ PROXY_HTTP, PROXY_TELNET, PROXY_CMD
+};
+
+enum {
+ /*
+ * Line discipline options which the backend might try to control.
+ */
+ LD_EDIT, /* local line editing */
+ LD_ECHO /* local echo */
+};
+
+enum {
+ /* Actions on remote window title query */
+ TITLE_NONE, TITLE_EMPTY, TITLE_REAL
+};
+
+enum {
+ /* Protocol back ends. (CONF_protocol) */
+ PROT_RAW, PROT_TELNET, PROT_RLOGIN, PROT_SSH,
+ /* PROT_SERIAL is supported on a subset of platforms, but it doesn't
+ * hurt to define it globally. */
+ PROT_SERIAL
+};
+
+enum {
+ /* Bell settings (CONF_beep) */
+ BELL_DISABLED, BELL_DEFAULT, BELL_VISUAL, BELL_WAVEFILE, BELL_PCSPEAKER
+};
+
+enum {
+ /* Taskbar flashing indication on bell (CONF_beep_ind) */
+ B_IND_DISABLED, B_IND_FLASH, B_IND_STEADY
+};
+
+enum {
+ /* Resize actions (CONF_resize_action) */
+ RESIZE_TERM, RESIZE_DISABLED, RESIZE_FONT, RESIZE_EITHER
+};
+
+enum {
+ /* Function key types (CONF_funky_type) */
+ FUNKY_TILDE,
+ FUNKY_LINUX,
+ FUNKY_XTERM,
+ FUNKY_VT400,
+ FUNKY_VT100P,
+ FUNKY_SCO
+};
+
+enum {
+ FQ_DEFAULT, FQ_ANTIALIASED, FQ_NONANTIALIASED, FQ_CLEARTYPE
+};
+
+enum {
+ SER_PAR_NONE, SER_PAR_ODD, SER_PAR_EVEN, SER_PAR_MARK, SER_PAR_SPACE
+};
+
+enum {
+ SER_FLOW_NONE, SER_FLOW_XONXOFF, SER_FLOW_RTSCTS, SER_FLOW_DSRDTR
+};
+
+/*
+ * Tables of string <-> enum value mappings used in settings.c.
+ * Defined here so that backends can export their GSS library tables
+ * to the cross-platform settings code.
+ */
+struct keyvalwhere {
+ /*
+ * Two fields which define a string and enum value to be
+ * equivalent to each other.
+ */
+ char *s;
+ int v;
+
+ /*
+ * The next pair of fields are used by gprefs() in settings.c to
+ * arrange that when it reads a list of strings representing a
+ * preference list and translates it into the corresponding list
+ * of integers, strings not appearing in the list are entered in a
+ * configurable position rather than uniformly at the end.
+ */
+
+ /*
+ * 'vrel' indicates which other value in the list to place this
+ * element relative to. It should be a value that has occurred in
+ * a 'v' field of some other element of the array, or -1 to
+ * indicate that we simply place relative to one or other end of
+ * the list.
+ *
+ * gprefs will try to process the elements in an order which makes
+ * this field work (i.e. so that the element referenced has been
+ * added before processing this one).
+ */
+ int vrel;
+
+ /*
+ * 'where' indicates whether to place the new value before or
+ * after the one referred to by vrel. -1 means before; +1 means
+ * after.
+ *
+ * When vrel is -1, this also implicitly indicates which end of
+ * the array to use. So vrel=-1, where=-1 means to place _before_
+ * some end of the list (hence, at the last element); vrel=-1,
+ * where=+1 means to place _after_ an end (hence, at the first).
+ */
+ int where;
+};
+
+#ifndef NO_GSSAPI
+extern const int ngsslibs;
+extern const char *const gsslibnames[]; /* for displaying in configuration */
+extern const struct keyvalwhere gsslibkeywords[]; /* for settings.c */
+#endif
+
+extern const char *const ttymodes[];
+
+enum {
+ /*
+ * Network address types. Used for specifying choice of IPv4/v6
+ * in config; also used in proxy.c to indicate whether a given
+ * host name has already been resolved or will be resolved at
+ * the proxy end.
+ */
+ ADDRTYPE_UNSPEC, ADDRTYPE_IPV4, ADDRTYPE_IPV6, ADDRTYPE_NAME
+};
+
+struct backend_tag {
+ const char *(*init) (void *frontend_handle, void **backend_handle,
+ Conf *conf, 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);
+ /* back->sendbuffer() does the same thing but without attempting a send */
+ int (*sendbuffer) (void *handle);
+ void (*size) (void *handle, int width, int height);
+ void (*special) (void *handle, Telnet_Special code);
+ const struct telnet_special *(*get_specials) (void *handle);
+ int (*connected) (void *handle);
+ int (*exitcode) (void *handle);
+ /* If back->sendok() returns FALSE, data sent to it from the frontend
+ * may be lost. */
+ int (*sendok) (void *handle);
+ int (*ldisc) (void *handle, int);
+ void (*provide_ldisc) (void *handle, void *ldisc);
+ void (*provide_logctx) (void *handle, void *logctx);
+ /*
+ * back->unthrottle() tells the back end that the front end
+ * buffer is clearing.
+ */
+ void (*unthrottle) (void *handle, int);
+ int (*cfg_info) (void *handle);
+ char *name;
+ int protocol;
+ int default_port;
+};
+
+extern Backend *backends[];
+
+/*
+ * Suggested default protocol provided by the backend link module.
+ * The application is free to ignore this.
+ */
+extern const int be_default_protocol;
+
+/*
+ * Name of this particular application, for use in the config box
+ * and other pieces of text.
+ */
+extern const char *const appname;
+
+/*
+ * Some global flags denoting the type of application.
+ *
+ * FLAG_VERBOSE is set when the user requests verbose details.
+ *
+ * FLAG_STDERR is set in command-line applications (which have a
+ * functioning stderr that it makes sense to write to) and not in
+ * GUI applications (which don't).
+ *
+ * FLAG_INTERACTIVE is set when a full interactive shell session is
+ * being run, _either_ because no remote command has been provided
+ * _or_ because the application is GUI and can't run non-
+ * interactively.
+ *
+ * These flags describe the type of _application_ - they wouldn't
+ * vary between individual sessions - and so it's OK to have this
+ * variable be GLOBAL.
+ *
+ * Note that additional flags may be defined in platform-specific
+ * headers. It's probably best if those ones start from 0x1000, to
+ * avoid collision.
+ */
+#define FLAG_VERBOSE 0x0001
+#define FLAG_STDERR 0x0002
+#define FLAG_INTERACTIVE 0x0004
+GLOBAL int flags;
+
+/*
+ * Likewise, these two variables are set up when the application
+ * initialises, and inform all default-settings accesses after
+ * that.
+ */
+GLOBAL int default_protocol;
+GLOBAL int default_port;
+
+/*
+ * This is set TRUE by cmdline.c iff a session is loaded with "-load".
+ */
+GLOBAL int loaded_session;
+/*
+ * This is set to the name of the loaded session.
+ */
+GLOBAL char *cmdline_session_name;
+
+struct RSAKey; /* be a little careful of scope */
+
+/*
+ * Mechanism for getting text strings such as usernames and passwords
+ * from the front-end.
+ * The fields are mostly modelled after SSH's keyboard-interactive auth.
+ * FIXME We should probably mandate a character set/encoding (probably UTF-8).
+ *
+ * Since many of the pieces of text involved may be chosen by the server,
+ * the caller must take care to ensure that the server can't spoof locally-
+ * generated prompts such as key passphrase prompts. Some ground rules:
+ * - If the front-end needs to truncate a string, it should lop off the
+ * end.
+ * - The front-end should filter out any dangerous characters and
+ * generally not trust the strings. (But \n is required to behave
+ * vaguely sensibly, at least in `instruction', and ideally in
+ * `prompt[]' too.)
+ */
+typedef struct {
+ char *prompt;
+ int echo;
+ /*
+ * 'result' must be a dynamically allocated array of exactly
+ * 'resultsize' chars. The code for actually reading input may
+ * realloc it bigger (and adjust resultsize accordingly) if it has
+ * to. The caller should free it again when finished with it.
+ *
+ * If resultsize==0, then result may be NULL. When setting up a
+ * prompt_t, it's therefore easiest to initialise them this way,
+ * which means all actual allocation is done by the callee. This
+ * is what add_prompt does.
+ */
+ char *result;
+ size_t resultsize;
+} prompt_t;
+typedef struct {
+ /*
+ * Indicates whether the information entered is to be used locally
+ * (for instance a key passphrase prompt), or is destined for the wire.
+ * This is a hint only; the front-end is at liberty not to use this
+ * information (so the caller should ensure that the supplied text is
+ * sufficient).
+ */
+ int to_server;
+ char *name; /* Short description, perhaps for dialog box title */
+ int name_reqd; /* Display of `name' required or optional? */
+ char *instruction; /* Long description, maybe with embedded newlines */
+ int instr_reqd; /* Display of `instruction' required or optional? */
+ size_t n_prompts; /* May be zero (in which case display the foregoing,
+ * if any, and return success) */
+ prompt_t **prompts;
+ void *frontend;
+ void *data; /* slot for housekeeping data, managed by
+ * get_userpass_input(); initially NULL */
+} prompts_t;
+prompts_t *new_prompts(void *frontend);
+void add_prompt(prompts_t *p, char *promptstr, int echo);
+void prompt_set_result(prompt_t *pr, const char *newstr);
+void prompt_ensure_result_size(prompt_t *pr, int len);
+/* Burn the evidence. (Assumes _all_ strings want free()ing.) */
+void free_prompts(prompts_t *p);
+
+/*
+ * Exports from the front end.
+ */
+void request_resize(void *frontend, int, int);
+void do_text(Context, int, int, wchar_t *, int, unsigned long, int);
+void do_cursor(Context, int, int, wchar_t *, int, unsigned long, int);
+int char_width(Context ctx, int uc);
+#ifdef OPTIMISE_SCROLL
+void do_scroll(Context, int, int, int);
+#endif
+void set_title(void *frontend, char *);
+void set_icon(void *frontend, char *);
+void set_sbar(void *frontend, int, int, int);
+Context get_ctx(void *frontend);
+void free_ctx(Context);
+void palette_set(void *frontend, int, int, int, int);
+void palette_reset(void *frontend);
+void write_aclip(void *frontend, char *, int, int);
+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 *, ...);
+#ifdef macintosh
+#pragma noreturn(fatalbox)
+#pragma noreturn(modalfatalbox)
+#endif
+void do_beep(void *frontend, int);
+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);
+/* 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
+ * shutdown. */
+void update_specials_menu(void *frontend);
+int from_backend(void *frontend, int is_stderr, const char *data, int len);
+int from_backend_untrusted(void *frontend, const char *data, int len);
+/* Called when the back end wants to indicate that EOF has arrived on
+ * the server-to-client stream. Returns FALSE to indicate that we
+ * intend to keep the session open in the other direction, or TRUE to
+ * indicate that if they're closing so are we. */
+int from_backend_eof(void *frontend);
+void notify_remote_exit(void *frontend);
+/* Get a sensible value for a tty mode. NULL return = don't set.
+ * Otherwise, returned value should be freed by caller. */
+char *get_ttymode(void *frontend, const char *mode);
+/*
+ * >0 = `got all results, carry on'
+ * 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);
+#define OPTIMISE_IS_SCROLL 1
+
+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);
+void get_window_pos(void *frontend, int *x, int *y);
+void get_window_pixels(void *frontend, int *x, int *y);
+char *get_window_title(void *frontend, int icon);
+/* Hint from backend to frontend about time-consuming operations.
+ * Initial state is assumed to be BUSY_NOT. */
+enum {
+ BUSY_NOT, /* Not busy, all user interaction OK */
+ BUSY_WAITING, /* Waiting for something; local event loops still running
+ so some local interaction (e.g. menus) OK, but network
+ stuff is suspended */
+ BUSY_CPU /* Locally busy (e.g. crypto); user interaction suspended */
+};
+void set_busy_status(void *frontend, int status);
+
+void cleanup_exit(int);
+
+/*
+ * Exports from conf.c, and a big enum (via parametric macro) of
+ * configuration option keys.
+ */
+#define CONFIG_OPTIONS(X) \
+ /* X(value-type, subkey-type, keyword) */ \
+ X(STR, NONE, host) \
+ X(INT, NONE, port) \
+ X(INT, NONE, protocol) \
+ X(INT, NONE, addressfamily) \
+ X(INT, NONE, close_on_exit) \
+ X(INT, NONE, warn_on_close) \
+ X(INT, NONE, ping_interval) /* in seconds */ \
+ X(INT, NONE, tcp_nodelay) \
+ X(INT, NONE, tcp_keepalives) \
+ X(STR, NONE, loghost) /* logical host being contacted, for host key check */ \
+ /* Proxy options */ \
+ X(STR, NONE, proxy_exclude_list) \
+ X(INT, NONE, proxy_dns) \
+ X(INT, NONE, even_proxy_localhost) \
+ X(INT, NONE, proxy_type) \
+ X(STR, NONE, proxy_host) \
+ X(INT, NONE, proxy_port) \
+ X(STR, NONE, proxy_username) \
+ X(STR, NONE, proxy_password) \
+ X(STR, NONE, proxy_telnet_command) \
+ /* SSH options */ \
+ X(STR, NONE, remote_cmd) \
+ X(STR, NONE, remote_cmd2) /* fallback if remote_cmd fails; never loaded or saved */ \
+ X(INT, NONE, nopty) \
+ X(INT, NONE, compression) \
+ X(INT, INT, ssh_kexlist) \
+ X(INT, NONE, ssh_rekey_time) /* in minutes */ \
+ X(STR, NONE, ssh_rekey_data) /* string encoding e.g. "100K", "2M", "1G" */ \
+ X(INT, NONE, tryagent) \
+ X(INT, NONE, agentfwd) \
+ X(INT, NONE, change_username) /* allow username switching in SSH-2 */ \
+ X(INT, INT, ssh_cipherlist) \
+ X(FILENAME, NONE, keyfile) \
+ X(INT, NONE, sshprot) /* use v1 or v2 when both available */ \
+ X(INT, NONE, ssh2_des_cbc) /* "des-cbc" unrecommended SSH-2 cipher */ \
+ X(INT, NONE, ssh_no_userauth) /* bypass "ssh-userauth" (SSH-2 only) */ \
+ X(INT, NONE, ssh_show_banner) /* show USERAUTH_BANNERs (SSH-2 only) */ \
+ X(INT, NONE, try_tis_auth) \
+ X(INT, NONE, try_ki_auth) \
+ X(INT, NONE, try_gssapi_auth) /* attempt gssapi auth */ \
+ X(INT, NONE, gssapifwd) /* forward tgt via gss */ \
+ X(INT, INT, ssh_gsslist) /* preference order for local GSS libs */ \
+ X(FILENAME, NONE, ssh_gss_custom) \
+ X(INT, NONE, ssh_subsys) /* run a subsystem rather than a command */ \
+ X(INT, NONE, ssh_subsys2) /* fallback to go with remote_cmd_ptr2 */ \
+ X(INT, NONE, ssh_no_shell) /* avoid running a shell */ \
+ X(STR, NONE, ssh_nc_host) /* host to connect to in `nc' mode */ \
+ X(INT, NONE, ssh_nc_port) /* port to connect to in `nc' mode */ \
+ /* Telnet options */ \
+ X(STR, NONE, termtype) \
+ X(STR, NONE, termspeed) \
+ X(STR, STR, ttymodes) /* values are "Vvalue" or "A" */ \
+ X(STR, STR, environmt) \
+ X(STR, NONE, username) \
+ X(INT, NONE, username_from_env) \
+ X(STR, NONE, localusername) \
+ X(INT, NONE, rfc_environ) \
+ X(INT, NONE, passive_telnet) \
+ /* Serial port options */ \
+ X(STR, NONE, serline) \
+ X(INT, NONE, serspeed) \
+ X(INT, NONE, serdatabits) \
+ X(INT, NONE, serstopbits) \
+ X(INT, NONE, serparity) \
+ X(INT, NONE, serflow) \
+ /* Keyboard options */ \
+ X(INT, NONE, bksp_is_delete) \
+ X(INT, NONE, rxvt_homeend) \
+ X(INT, NONE, funky_type) \
+ X(INT, NONE, no_applic_c) /* totally disable app cursor keys */ \
+ X(INT, NONE, no_applic_k) /* totally disable app keypad */ \
+ X(INT, NONE, no_mouse_rep) /* totally disable mouse reporting */ \
+ X(INT, NONE, no_remote_resize) /* disable remote resizing */ \
+ X(INT, NONE, no_alt_screen) /* disable alternate screen */ \
+ X(INT, NONE, no_remote_wintitle) /* disable remote retitling */ \
+ X(INT, NONE, no_dbackspace) /* disable destructive backspace */ \
+ X(INT, NONE, no_remote_charset) /* disable remote charset config */ \
+ X(INT, NONE, remote_qtitle_action) /* remote win title query action */ \
+ X(INT, NONE, app_cursor) \
+ X(INT, NONE, app_keypad) \
+ X(INT, NONE, nethack_keypad) \
+ X(INT, NONE, telnet_keyboard) \
+ X(INT, NONE, telnet_newline) \
+ X(INT, NONE, alt_f4) /* is it special? */ \
+ X(INT, NONE, alt_space) /* is it special? */ \
+ X(INT, NONE, alt_only) /* is it special? */ \
+ X(INT, NONE, localecho) \
+ X(INT, NONE, localedit) \
+ X(INT, NONE, alwaysontop) \
+ X(INT, NONE, fullscreenonaltenter) \
+ X(INT, NONE, scroll_on_key) \
+ X(INT, NONE, scroll_on_disp) \
+ X(INT, NONE, erase_to_scrollback) \
+ X(INT, NONE, compose_key) \
+ X(INT, NONE, ctrlaltkeys) \
+ X(STR, NONE, wintitle) /* initial window title */ \
+ /* Terminal options */ \
+ X(INT, NONE, savelines) \
+ X(INT, NONE, dec_om) \
+ X(INT, NONE, wrap_mode) \
+ X(INT, NONE, lfhascr) \
+ X(INT, NONE, cursor_type) /* 0=block 1=underline 2=vertical */ \
+ X(INT, NONE, blink_cur) \
+ X(INT, NONE, beep) \
+ X(INT, NONE, beep_ind) \
+ X(INT, NONE, bellovl) /* bell overload protection active? */ \
+ X(INT, NONE, bellovl_n) /* number of bells to cause overload */ \
+ X(INT, NONE, bellovl_t) /* time interval for overload (seconds) */ \
+ X(INT, NONE, bellovl_s) /* period of silence to re-enable bell (s) */ \
+ X(FILENAME, NONE, bell_wavefile) \
+ X(INT, NONE, scrollbar) \
+ X(INT, NONE, scrollbar_in_fullscreen) \
+ X(INT, NONE, resize_action) \
+ X(INT, NONE, bce) \
+ X(INT, NONE, blinktext) \
+ X(INT, NONE, win_name_always) \
+ X(INT, NONE, width) \
+ X(INT, NONE, height) \
+ X(FONT, NONE, font) \
+ X(INT, NONE, font_quality) \
+ X(FILENAME, NONE, logfilename) \
+ X(INT, NONE, logtype) \
+ X(INT, NONE, logxfovr) \
+ X(INT, NONE, logflush) \
+ X(INT, NONE, logomitpass) \
+ X(INT, NONE, logomitdata) \
+ X(INT, NONE, hide_mouseptr) \
+ X(INT, NONE, sunken_edge) \
+ X(INT, NONE, window_border) \
+ X(STR, NONE, answerback) \
+ X(STR, NONE, printer) \
+ X(INT, NONE, arabicshaping) \
+ X(INT, NONE, bidi) \
+ /* Colour options */ \
+ X(INT, NONE, ansi_colour) \
+ X(INT, NONE, xterm_256_colour) \
+ X(INT, NONE, system_colour) \
+ X(INT, NONE, try_palette) \
+ X(INT, NONE, bold_style) \
+ X(INT, INT, colours) \
+ /* Selection options */ \
+ X(INT, NONE, mouse_is_xterm) \
+ X(INT, NONE, rect_select) \
+ X(INT, NONE, rawcnp) \
+ X(INT, NONE, rtf_paste) \
+ X(INT, NONE, mouse_override) \
+ X(INT, INT, wordness) \
+ /* translations */ \
+ X(INT, NONE, vtmode) \
+ X(STR, NONE, line_codepage) \
+ X(INT, NONE, cjk_ambig_wide) \
+ X(INT, NONE, utf8_override) \
+ X(INT, NONE, xlat_capslockcyr) \
+ /* X11 forwarding */ \
+ X(INT, NONE, x11_forward) \
+ X(STR, NONE, x11_display) \
+ X(INT, NONE, x11_auth) \
+ X(FILENAME, NONE, xauthfile) \
+ /* port forwarding */ \
+ X(INT, NONE, lport_acceptall) /* accept conns from hosts other than localhost */ \
+ X(INT, NONE, rport_acceptall) /* same for remote forwarded ports (SSH-2 only) */ \
+ /* \
+ * Subkeys for 'portfwd' can have the following forms: \
+ * \
+ * [LR]localport \
+ * [LR]localaddr:localport \
+ * \
+ * Dynamic forwardings are indicated by an 'L' key, and the \
+ * special value "D". For all other forwardings, the value \
+ * should be of the form 'host:port'. \
+ */ \
+ X(STR, STR, portfwd) \
+ /* SSH bug compatibility modes */ \
+ X(INT, NONE, sshbug_ignore1) \
+ X(INT, NONE, sshbug_plainpw1) \
+ X(INT, NONE, sshbug_rsa1) \
+ X(INT, NONE, sshbug_hmac2) \
+ X(INT, NONE, sshbug_derivekey2) \
+ X(INT, NONE, sshbug_rsapad2) \
+ X(INT, NONE, sshbug_pksessid2) \
+ X(INT, NONE, sshbug_rekey2) \
+ X(INT, NONE, sshbug_maxpkt2) \
+ X(INT, NONE, sshbug_ignore2) \
+ X(INT, NONE, sshbug_winadj) \
+ /* \
+ * ssh_simple means that we promise never to open any channel \
+ * other than the main one, which means it can safely use a very \
+ * large window in SSH-2. \
+ */ \
+ X(INT, NONE, ssh_simple) \
+ X(INT, NONE, ssh_connection_sharing) \
+ X(INT, NONE, ssh_connection_sharing_upstream) \
+ X(INT, NONE, ssh_connection_sharing_downstream) \
+ /* Options for pterm. Should split out into platform-dependent part. */ \
+ X(INT, NONE, stamp_utmp) \
+ X(INT, NONE, login_shell) \
+ X(INT, NONE, scrollbar_on_left) \
+ X(INT, NONE, shadowbold) \
+ X(FONT, NONE, boldfont) \
+ X(FONT, NONE, widefont) \
+ X(FONT, NONE, wideboldfont) \
+ X(INT, NONE, shadowboldoffset) \
+ X(INT, NONE, crhaslf) \
+ X(STR, NONE, winclass) \
+
+/* Now define the actual enum of option keywords using that macro. */
+#define CONF_ENUM_DEF(valtype, keytype, keyword) CONF_ ## keyword,
+enum config_primary_key { CONFIG_OPTIONS(CONF_ENUM_DEF) N_CONFIG_OPTIONS };
+#undef CONF_ENUM_DEF
+
+#define NCFGCOLOURS 22 /* number of colours in CONF_colours above */
+
+/* Functions handling configuration structures. */
+Conf *conf_new(void); /* create an empty configuration */
+void conf_free(Conf *conf);
+Conf *conf_copy(Conf *oldconf);
+void conf_copy_into(Conf *dest, Conf *src);
+/* Mandatory accessor functions: enforce by assertion that keys exist. */
+int conf_get_int(Conf *conf, int key);
+int conf_get_int_int(Conf *conf, int key, int subkey);
+char *conf_get_str(Conf *conf, int key); /* result still owned by conf */
+char *conf_get_str_str(Conf *conf, int key, const char *subkey);
+Filename *conf_get_filename(Conf *conf, int key);
+FontSpec *conf_get_fontspec(Conf *conf, int key); /* still owned by conf */
+/* Optional accessor function: return NULL if key does not exist. */
+char *conf_get_str_str_opt(Conf *conf, int key, const char *subkey);
+/* Accessor function to step through a string-subkeyed list.
+ * Returns the next subkey after the provided one, or the first if NULL.
+ * Returns NULL if there are none left.
+ * Both the return value and *subkeyout are still owned by conf. */
+char *conf_get_str_strs(Conf *conf, int key, char *subkeyin, char **subkeyout);
+/* Return the nth string subkey in a list. Owned by conf. NULL if beyond end */
+char *conf_get_str_nthstrkey(Conf *conf, int key, int n);
+/* Functions to set entries in configuration. Always copy their inputs. */
+void conf_set_int(Conf *conf, int key, int value);
+void conf_set_int_int(Conf *conf, int key, int subkey, int value);
+void conf_set_str(Conf *conf, int key, const char *value);
+void conf_set_str_str(Conf *conf, int key,
+ const char *subkey, const char *val);
+void conf_del_str_str(Conf *conf, int key, const char *subkey);
+void conf_set_filename(Conf *conf, int key, const Filename *val);
+void conf_set_fontspec(Conf *conf, int key, const FontSpec *val);
+/* Serialisation functions for Duplicate Session */
+int conf_serialised_size(Conf *conf);
+void conf_serialise(Conf *conf, void *data);
+int conf_deserialise(Conf *conf, void *data, int maxsize);/*returns size used*/
+
+/*
+ * Functions to copy, free, serialise and deserialise FontSpecs.
+ * Provided per-platform, to go with the platform's idea of a
+ * FontSpec's contents.
+ *
+ * fontspec_serialise returns the number of bytes written, and can
+ * handle data==NULL without crashing. So you can call it once to find
+ * out a size, then again once you've allocated a buffer.
+ */
+FontSpec *fontspec_copy(const FontSpec *f);
+void fontspec_free(FontSpec *f);
+int fontspec_serialise(FontSpec *f, void *data);
+FontSpec *fontspec_deserialise(void *data, int maxsize, int *used);
+
+/*
+ * Exports from noise.c.
+ */
+void noise_get_heavy(void (*func) (void *, int));
+void noise_get_light(void (*func) (void *, int));
+void noise_regular(void);
+void noise_ultralight(unsigned long data);
+void random_save_seed(void);
+void random_destroy_seed(void);
+
+/*
+ * Exports from settings.c.
+ */
+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);
+void save_open_settings(void *sesskey, Conf *conf);
+void load_settings(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 registry_cleanup(void);
+
+/*
+ * Functions used by settings.c to provide platform-specific
+ * default settings.
+ *
+ * (The integer one is expected to return `def' if it has no clear
+ * opinion of its own. This is because there's no integer value
+ * which I can reliably set aside to indicate `nil'. The string
+ * function is perfectly all right returning NULL, of course. The
+ * Filename and FontSpec functions are _not allowed_ to fail to
+ * return, since these defaults _must_ be per-platform.)
+ *
+ * The 'Filename *' returned by platform_default_filename, and the
+ * 'FontSpec *' returned by platform_default_fontspec, have ownership
+ * transferred to the caller, and must be freed.
+ */
+char *platform_default_s(const char *name);
+int platform_default_i(const char *name, int def);
+Filename *platform_default_filename(const char *name);
+FontSpec *platform_default_fontspec(const char *name);
+
+/*
+ * Exports from terminal.c.
+ */
+
+Terminal *term_init(Conf *, struct unicode_data *, void *);
+void term_free(Terminal *);
+void term_size(Terminal *, int, int, int);
+void term_paint(Terminal *, Context, int, int, int, int, int);
+void term_scroll(Terminal *, int, int);
+void term_scroll_to_selection(Terminal *, int);
+void term_pwron(Terminal *, int);
+void term_clrsb(Terminal *);
+void term_mouse(Terminal *, Mouse_Button, Mouse_Button, Mouse_Action,
+ int,int,int,int,int);
+void term_key(Terminal *, Key_Sym, wchar_t *, size_t, unsigned int,
+ unsigned int);
+void term_deselect(Terminal *);
+void term_update(Terminal *);
+void term_invalidate(Terminal *);
+void term_blink(Terminal *, int set_cursor);
+void term_do_paste(Terminal *);
+void term_nopaste(Terminal *);
+int term_ldisc(Terminal *, int option);
+void term_copyall(Terminal *);
+void term_reconfig(Terminal *, Conf *);
+void term_seen_key_event(Terminal *);
+int term_data(Terminal *, int is_stderr, const char *data, int len);
+int term_data_untrusted(Terminal *, const char *data, int len);
+void term_provide_resize_fn(Terminal *term,
+ void (*resize_fn)(void *, int, int),
+ void *resize_ctx);
+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);
+
+int format_arrow_key(char *buf, Terminal *term, int xkey, int ctrl);
+
+/*
+ * Exports from logging.c.
+ */
+void *log_init(void *frontend, Conf *conf);
+void log_free(void *logctx);
+void log_reconfig(void *logctx, Conf *conf);
+void logfopen(void *logctx);
+void logfclose(void *logctx);
+void logtraffic(void *logctx, unsigned char c, int logmode);
+void logflush(void *logctx);
+void log_eventlog(void *logctx, const char *string);
+enum { PKT_INCOMING, PKT_OUTGOING };
+enum { PKTLOG_EMIT, PKTLOG_BLANK, PKTLOG_OMIT };
+struct logblank_t {
+ int offset;
+ int len;
+ int type;
+};
+void log_packet(void *logctx, int direction, int type,
+ 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);
+
+/*
+ * Exports from testback.c
+ */
+
+extern Backend null_backend;
+extern Backend loop_backend;
+
+/*
+ * Exports from raw.c.
+ */
+
+extern Backend raw_backend;
+
+/*
+ * Exports from rlogin.c.
+ */
+
+extern Backend rlogin_backend;
+
+/*
+ * Exports from telnet.c.
+ */
+
+extern Backend telnet_backend;
+
+/*
+ * Exports from ssh.c.
+ */
+extern Backend ssh_backend;
+
+/*
+ * Exports from ldisc.c.
+ */
+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);
+
+/*
+ * 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);
+
+/*
+ * Exports from sshrand.c.
+ */
+
+void random_add_noise(void *noise, int length);
+int random_byte(void);
+void random_get_savedata(void **data, int *len);
+extern int random_active;
+/* The random number subsystem is activated if at least one other entity
+ * within the program expresses an interest in it. So each SSH session
+ * calls random_ref on startup and random_unref on shutdown. */
+void random_ref(void);
+void random_unref(void);
+
+/*
+ * Exports from pinger.c.
+ */
+typedef struct pinger_tag *Pinger;
+Pinger pinger_new(Conf *conf, Backend *back, void *backhandle);
+void pinger_reconfig(Pinger, Conf *oldconf, Conf *newconf);
+void pinger_free(Pinger);
+
+/*
+ * Exports from misc.c.
+ */
+
+#include "misc.h"
+int conf_launchable(Conf *conf);
+char const *conf_dest(Conf *conf);
+
+/*
+ * Exports from sercfg.c.
+ */
+void ser_setup_config_box(struct controlbox *b, int midsession,
+ int parity_mask, int flow_mask);
+
+/*
+ * Exports from version.c.
+ */
+extern char ver[];
+
+/*
+ * Exports from unicode.c.
+ */
+#ifndef CP_UTF8
+#define CP_UTF8 65001
+#endif
+/* void init_ucs(void); -- this is now in platform-specific headers */
+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,
+ struct unicode_data *ucsdata);
+wchar_t xlat_uskbd2cyrllic(int ch);
+int check_compose(int first, int second);
+int decode_codepage(char *cp_name);
+const char *cp_enumerate (int index);
+const char *cp_name(int codepage);
+void get_unitab(int codepage, wchar_t * unitab, int ftype);
+
+/*
+ * Exports from wcwidth.c
+ */
+int mk_wcwidth(unsigned int ucs);
+int mk_wcswidth(const unsigned int *pwcs, size_t n);
+int mk_wcwidth_cjk(unsigned int ucs);
+int mk_wcswidth_cjk(const unsigned int *pwcs, size_t n);
+
+/*
+ * Exports from mscrypto.c
+ */
+#ifdef MSCRYPTOAPI
+int crypto_startup();
+void crypto_wrapup();
+#endif
+
+/*
+ * Exports from pageantc.c.
+ *
+ * agent_query returns 1 for here's-a-response, and 0 for query-in-
+ * progress. In the latter case there will be a call to `callback'
+ * at some future point, passing callback_ctx as the first
+ * parameter and the actual reply data as the second and third.
+ *
+ * The response may be a NULL pointer (in either of the synchronous
+ * or asynchronous cases), which indicates failure to receive a
+ * response.
+ */
+int agent_query(void *in, int inlen, void **out, int *outlen,
+ void (*callback)(void *, void *, int), void *callback_ctx);
+int agent_exists(void);
+
+/*
+ * Exports from wildcard.c
+ */
+const char *wc_error(int value);
+int wc_match(const char *wildcard, const char *target);
+int wc_unescape(char *output, const char *wildcard);
+
+/*
+ * Exports from frontend (windlg.c etc)
+ */
+void logevent(void *frontend, const char *);
+void pgp_fingerprints(void);
+/*
+ * verify_ssh_host_key() can return one of three values:
+ *
+ * - +1 means `key was OK' (either already known or the user just
+ * approved it) `so continue with the connection'
+ *
+ * - 0 means `key was not OK, abandon the connection'
+ *
+ * - -1 means `I've initiated enquiries, please wait to be called
+ * 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,
+ void (*callback)(void *ctx, int result), void *ctx);
+/*
+ * askalg has the same set of return values as verify_ssh_host_key.
+ */
+int askalg(void *frontend, const char *algtype, const char *algname,
+ void (*callback)(void *ctx, int result), void *ctx);
+/*
+ * askappend can return four values:
+ *
+ * - 2 means overwrite the log file
+ * - 1 means append to the log file
+ * - 0 means cancel logging for this session
+ * - -1 means please wait.
+ */
+int askappend(void *frontend, Filename *filename,
+ void (*callback)(void *ctx, int result), void *ctx);
+
+/*
+ * Exports from console frontends (wincons.c, uxcons.c)
+ * 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);
+void console_provide_logctx(void *logctx);
+int is_interactive(void);
+
+/*
+ * Exports from printing.c.
+ */
+typedef struct printer_enum_tag printer_enum;
+typedef struct printer_job_tag printer_job;
+printer_enum *printer_start_enum(int *nprinters);
+char *printer_get_name(printer_enum *, int);
+void printer_finish_enum(printer_enum *);
+printer_job *printer_start_job(char *printer);
+void printer_job_data(printer_job *, void *, int);
+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).
+ */
+int cmdline_process_param(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);
+#define TOOLTYPE_FILETRANSFER 1
+#define TOOLTYPE_NONNETWORK 2
+extern int cmdline_tooltype;
+
+void cmdline_error(char *, ...);
+
+/*
+ * Exports from config.c.
+ */
+struct controlbox;
+union control;
+void conf_radiobutton_handler(union control *ctrl, void *dlg,
+ void *data, int event);
+#define CHECKBOX_INVERT (1<<30)
+void conf_checkbox_handler(union control *ctrl, void *dlg,
+ void *data, int event);
+void conf_editbox_handler(union control *ctrl, void *dlg,
+ void *data, int event);
+void conf_filesel_handler(union control *ctrl, void *dlg,
+ void *data, int event);
+void conf_fontsel_handler(union control *ctrl, void *dlg,
+ void *data, int event);
+void setup_config_box(struct controlbox *b, int midsession,
+ int protocol, int protcfginfo);
+
+/*
+ * Exports from minibidi.c.
+ */
+typedef struct bidi_char {
+ unsigned int origwc, wc;
+ unsigned short index;
+} bidi_char;
+int do_bidi(bidi_char *line, int count);
+int do_shape(bidi_char *line, bidi_char *to, int count);
+int is_rtl(int c);
+
+/*
+ * X11 auth mechanisms we know about.
+ */
+enum {
+ X11_NO_AUTH,
+ X11_MIT, /* MIT-MAGIC-COOKIE-1 */
+ X11_XDM, /* XDM-AUTHORIZATION-1 */
+ X11_NAUTHS
+};
+extern const char *const x11_authnames[]; /* declared in x11fwd.c */
+
+/*
+ * Miscellaneous exports from the platform-specific code.
+ *
+ * filename_serialise and filename_deserialise have the same semantics
+ * as fontspec_serialise and fontspec_deserialise above.
+ */
+Filename *filename_from_str(const char *string);
+const char *filename_to_str(const Filename *fn);
+int filename_equal(const Filename *f1, const Filename *f2);
+int filename_is_null(const Filename *fn);
+Filename *filename_copy(const Filename *fn);
+void filename_free(Filename *fn);
+int filename_serialise(const Filename *f, void *data);
+Filename *filename_deserialise(void *data, int maxsize, int *used);
+char *get_username(void); /* return value needs freeing */
+char *get_random_data(int bytes); /* used in cmdgen.c */
+
+/*
+ * Exports and imports from timing.c.
+ *
+ * schedule_timer() asks the front end to schedule a callback to a
+ * timer function in a given number of ticks. The returned value is
+ * the time (in ticks since an arbitrary offset) at which the
+ * callback can be expected. This value will also be passed as the
+ * `now' parameter to the callback function. Hence, you can (for
+ * example) schedule an event at a particular time by calling
+ * schedule_timer() and storing the return value in your context
+ * structure as the time when that event is due. The first time a
+ * callback function gives you that value or more as `now', you do
+ * the thing.
+ *
+ * expire_timer_context() drops all current timers associated with
+ * a given value of ctx (for when you're about to free ctx).
+ *
+ * run_timers() is called from the front end when it has reason to
+ * think some timers have reached their moment, or when it simply
+ * needs to know how long to wait next. We pass it the time we
+ * think it is. It returns TRUE and places the time when the next
+ * timer needs to go off in `next', or alternatively it returns
+ * FALSE if there are no timers at all pending.
+ *
+ * timer_change_notify() must be supplied by the front end; it
+ * notifies the front end that a new timer has been added to the
+ * list which is sooner than any existing ones. It provides the
+ * time when that timer needs to go off.
+ *
+ * *** FRONT END IMPLEMENTORS NOTE:
+ *
+ * There's an important subtlety in the front-end implementation of
+ * the timer interface. When a front end is given a `next' value,
+ * either returned from run_timers() or via timer_change_notify(),
+ * it should ensure that it really passes _that value_ as the `now'
+ * parameter to its next run_timers call. It should _not_ simply
+ * call GETTICKCOUNT() to get the `now' parameter when invoking
+ * run_timers().
+ *
+ * The reason for this is that an OS's system clock might not agree
+ * exactly with the timing mechanisms it supplies to wait for a
+ * given interval. I'll illustrate this by the simple example of
+ * Unix Plink, which uses timeouts to select() in a way which for
+ * these purposes can simply be considered to be a wait() function.
+ * Suppose, for the sake of argument, that this wait() function
+ * tends to return early by 1%. Then a possible sequence of actions
+ * is:
+ *
+ * - run_timers() tells the front end that the next timer firing
+ * is 10000ms from now.
+ * - Front end calls wait(10000ms), but according to
+ * GETTICKCOUNT() it has only waited for 9900ms.
+ * - Front end calls run_timers() again, passing time T-100ms as
+ * `now'.
+ * - run_timers() does nothing, and says the next timer firing is
+ * still 100ms from now.
+ * - Front end calls wait(100ms), which only waits for 99ms.
+ * - Front end calls run_timers() yet again, passing time T-1ms.
+ * - run_timers() says there's still 1ms to wait.
+ * - Front end calls wait(1ms).
+ *
+ * If you're _lucky_ at this point, wait(1ms) will actually wait
+ * for 1ms and you'll only have woken the program up three times.
+ * If you're unlucky, wait(1ms) might do nothing at all due to
+ * being below some minimum threshold, and you might find your
+ * program spends the whole of the last millisecond tight-looping
+ * between wait() and run_timers().
+ *
+ * Instead, what you should do is to _save_ the precise `next'
+ * value provided by run_timers() or via timer_change_notify(), and
+ * use that precise value as the input to the next run_timers()
+ * call. So:
+ *
+ * - run_timers() tells the front end that the next timer firing
+ * is at time T, 10000ms from now.
+ * - Front end calls wait(10000ms).
+ * - Front end then immediately calls run_timers() and passes it
+ * time T, without stopping to check GETTICKCOUNT() at all.
+ *
+ * This guarantees that the program wakes up only as many times as
+ * there are actual timer actions to be taken, and that the timing
+ * mechanism will never send it into a tight loop.
+ *
+ * (It does also mean that the timer action in the above example
+ * will occur 100ms early, but this is not generally critical. And
+ * the hypothetical 1% error in wait() will be partially corrected
+ * for anyway when, _after_ run_timers() returns, you call
+ * GETTICKCOUNT() and compare the result with the returned `next'
+ * value to find out how long you have to make your next wait().)
+ */
+typedef void (*timer_fn_t)(void *ctx, unsigned long now);
+unsigned long schedule_timer(int ticks, timer_fn_t fn, void *ctx);
+void expire_timer_context(void *ctx);
+int run_timers(unsigned long now, unsigned long *next);
+void timer_change_notify(unsigned long next);
+
+/*
+ * Exports from callback.c.
+ *
+ * This provides a method of queuing function calls to be run at the
+ * earliest convenience from the top-level event loop. Use it if
+ * you're deep in a nested chain of calls and want to trigger an
+ * action which will probably lead to your function being re-entered
+ * recursively if you just call the initiating function the normal
+ * way.
+ *
+ * Most front ends run the queued callbacks by simply calling
+ * run_toplevel_callbacks() after handling each event in their
+ * top-level event loop. However, if a front end doesn't have control
+ * over its own event loop (e.g. because it's using GTK) then it can
+ * instead request notifications when a callback is available, so that
+ * it knows to ask its delegate event loop to do the same thing. Also,
+ * if a front end needs to know whether a callback is pending without
+ * actually running it (e.g. so as to put a zero timeout on a select()
+ * call) then it can call toplevel_callback_pending(), which will
+ * return true if at least one callback is in the queue.
+ */
+typedef void (*toplevel_callback_fn_t)(void *ctx);
+void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx);
+void run_toplevel_callbacks(void);
+int toplevel_callback_pending(void);
+
+typedef void (*toplevel_callback_notify_fn_t)(void *frontend);
+void request_callback_notifications(toplevel_callback_notify_fn_t notify,
+ void *frontend);
+
+/*
+ * Define no-op macros for the jump list functions, on platforms that
+ * don't support them. (This is a bit of a hack, and it'd be nicer to
+ * localise even the calls to those functions into the Windows front
+ * end, but it'll do for the moment.)
+ */
+#ifndef JUMPLIST_SUPPORTED
+#define add_session_to_jumplist(x) ((void)0)
+#define remove_session_from_jumplist(x) ((void)0)
+#endif
+
+/* SURROGATE PAIR */
+#ifndef IS_HIGH_SURROGATE
+#define HIGH_SURROGATE_START 0xd800
+#define HIGH_SURROGATE_END 0xdbff
+#define LOW_SURROGATE_START 0xdc00
+#define LOW_SURROGATE_END 0xdfff
+
+#define IS_HIGH_SURROGATE(wch) (((wch) >= HIGH_SURROGATE_START) && \
+ ((wch) <= HIGH_SURROGATE_END))
+#define IS_LOW_SURROGATE(wch) (((wch) >= LOW_SURROGATE_START) && \
+ ((wch) <= LOW_SURROGATE_END))
+#define IS_SURROGATE_PAIR(hs, ls) (IS_HIGH_SURROGATE(hs) && \
+ IS_LOW_SURROGATE(ls))
+#endif
+
+
+#define IS_SURROGATE(wch) (((wch) >= HIGH_SURROGATE_START) && \
+ ((wch) <= LOW_SURROGATE_END))
+#define HIGH_SURROGATE_OF(codept) \
+ (HIGH_SURROGATE_START + (((codept) - 0x10000) >> 10))
+#define LOW_SURROGATE_OF(codept) \
+ (LOW_SURROGATE_START + (((codept) - 0x10000) & 0x3FF))
+#define FROM_SURROGATES(wch1, wch2) \
+ (0x10000 + (((wch1) & 0x3FF) << 10) + ((wch2) & 0x3FF))
+
+#endif
diff --git a/tools/plink/puttymem.h b/tools/plink/puttymem.h
index d478f7949..941aded3f 100644
--- a/tools/plink/puttymem.h
+++ b/tools/plink/puttymem.h
@@ -1,42 +1,52 @@
-/*
- * PuTTY memory-handling header.
- */
-
-#ifndef PUTTY_PUTTYMEM_H
-#define PUTTY_PUTTYMEM_H
-
-#include <stddef.h> /* for size_t */
-#include <string.h> /* for memcpy() */
-
-
-/* #define MALLOC_LOG do this if you suspect putty of leaking memory */
-#ifdef MALLOC_LOG
-#define smalloc(z) (mlog(__FILE__,__LINE__), safemalloc(z,1))
-#define snmalloc(z,s) (mlog(__FILE__,__LINE__), safemalloc(z,s))
-#define srealloc(y,z) (mlog(__FILE__,__LINE__), saferealloc(y,z,1))
-#define snrealloc(y,z,s) (mlog(__FILE__,__LINE__), saferealloc(y,z,s))
-#define sfree(z) (mlog(__FILE__,__LINE__), safefree(z))
-void mlog(char *, int);
-#else
-#define smalloc(z) safemalloc(z,1)
-#define snmalloc safemalloc
-#define srealloc(y,z) saferealloc(y,z,1)
-#define snrealloc saferealloc
-#define sfree safefree
-#endif
-
-void *safemalloc(size_t, size_t);
-void *saferealloc(void *, size_t, size_t);
-void safefree(void *);
-
-/*
- * Direct use of smalloc within the code should be avoided where
- * possible, in favour of these type-casting macros which ensure
- * you don't mistakenly allocate enough space for one sort of
- * structure and assign it to a different sort of pointer.
- */
-#define snew(type) ((type *)snmalloc(1, sizeof(type)))
-#define snewn(n, type) ((type *)snmalloc((n), sizeof(type)))
-#define sresize(ptr, n, type) ((type *)snrealloc((ptr), (n), sizeof(type)))
-
-#endif
+/*
+ * PuTTY memory-handling header.
+ */
+
+#ifndef PUTTY_PUTTYMEM_H
+#define PUTTY_PUTTYMEM_H
+
+#include <stddef.h> /* for size_t */
+#include <string.h> /* for memcpy() */
+
+
+/* #define MALLOC_LOG do this if you suspect putty of leaking memory */
+#ifdef MALLOC_LOG
+#define smalloc(z) (mlog(__FILE__,__LINE__), safemalloc(z,1))
+#define snmalloc(z,s) (mlog(__FILE__,__LINE__), safemalloc(z,s))
+#define srealloc(y,z) (mlog(__FILE__,__LINE__), saferealloc(y,z,1))
+#define snrealloc(y,z,s) (mlog(__FILE__,__LINE__), saferealloc(y,z,s))
+#define sfree(z) (mlog(__FILE__,__LINE__), safefree(z))
+void mlog(char *, int);
+#else
+#define smalloc(z) safemalloc(z,1)
+#define snmalloc safemalloc
+#define srealloc(y,z) saferealloc(y,z,1)
+#define snrealloc saferealloc
+#define sfree safefree
+#endif
+
+void *safemalloc(size_t, size_t);
+void *saferealloc(void *, size_t, size_t);
+void safefree(void *);
+
+/*
+ * Direct use of smalloc within the code should be avoided where
+ * possible, in favour of these type-casting macros which ensure
+ * you don't mistakenly allocate enough space for one sort of
+ * structure and assign it to a different sort of pointer.
+ *
+ * The nasty trick in sresize with sizeof arranges for the compiler,
+ * in passing, to type-check the expression ((type *)0 == (ptr)), i.e.
+ * to type-check that the input pointer is a pointer to the correct
+ * type. The construction sizeof(stuff) ? (b) : (b) looks like a
+ * violation of the first principle of safe macros, but in fact it's
+ * OK - although it _expands_ the macro parameter more than once, it
+ * only _evaluates_ it once, so it's still side-effect safe.
+ */
+#define snew(type) ((type *)snmalloc(1, sizeof(type)))
+#define snewn(n, type) ((type *)snmalloc((n), sizeof(type)))
+#define sresize(ptr, n, type) \
+ ((type *)snrealloc(sizeof((type *)0 == (ptr)) ? (ptr) : (ptr), \
+ (n), sizeof(type)))
+
+#endif
diff --git a/tools/plink/raw.c b/tools/plink/raw.c
index ee06a2d73..a7fbbc5cc 100644
--- a/tools/plink/raw.c
+++ b/tools/plink/raw.c
@@ -1,342 +1,344 @@
-/*
- * "Raw" backend.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#include "putty.h"
-
-#ifndef FALSE
-#define FALSE 0
-#endif
-#ifndef TRUE
-#define TRUE 1
-#endif
-
-#define RAW_MAX_BACKLOG 4096
-
-typedef struct raw_backend_data {
- const struct plug_function_table *fn;
- /* the above field _must_ be first in the structure */
-
- Socket s;
- int bufsize;
- void *frontend;
- int sent_console_eof, sent_socket_eof;
-} *Raw;
-
-static void raw_size(void *handle, int width, int height);
-
-static void c_write(Raw raw, char *buf, int len)
-{
- int backlog = from_backend(raw->frontend, 0, buf, len);
- sk_set_frozen(raw->s, backlog > RAW_MAX_BACKLOG);
-}
-
-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);
-}
-
-static void raw_check_close(Raw raw)
-{
- /*
- * Called after we send EOF on either the socket or the console.
- * Its job is to wind up the session once we have sent EOF on both.
- */
- if (raw->sent_console_eof && raw->sent_socket_eof) {
- if (raw->s) {
- sk_close(raw->s);
- raw->s = NULL;
- notify_remote_exit(raw->frontend);
- }
- }
-}
-
-static int raw_closing(Plug plug, const char *error_msg, int error_code,
- int calling_back)
-{
- Raw raw = (Raw) plug;
-
- if (error_msg) {
- /* A socket error has occurred. */
- if (raw->s) {
- sk_close(raw->s);
- raw->s = NULL;
- notify_remote_exit(raw->frontend);
- }
- logevent(raw->frontend, error_msg);
- connection_fatal(raw->frontend, "%s", error_msg);
- } else {
- /* Otherwise, the remote side closed the connection normally. */
- if (!raw->sent_console_eof && from_backend_eof(raw->frontend)) {
- /*
- * The front end wants us to close the outgoing side of the
- * connection as soon as we see EOF from the far end.
- */
- if (!raw->sent_socket_eof) {
- if (raw->s)
- sk_write_eof(raw->s);
- raw->sent_socket_eof= TRUE;
- }
- }
- raw->sent_console_eof = TRUE;
- raw_check_close(raw);
- }
- return 0;
-}
-
-static int raw_receive(Plug plug, int urgent, char *data, int len)
-{
- Raw raw = (Raw) plug;
- c_write(raw, data, len);
- return 1;
-}
-
-static void raw_sent(Plug plug, int bufsize)
-{
- Raw raw = (Raw) plug;
- raw->bufsize = bufsize;
-}
-
-/*
- * Called to set up the raw connection.
- *
- * 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 *raw_init(void *frontend_handle, void **backend_handle,
- Conf *conf,
- char *host, int port, char **realhost, int nodelay,
- int keepalive)
-{
- static const struct plug_function_table fn_table = {
- raw_log,
- raw_closing,
- raw_receive,
- raw_sent
- };
- SockAddr addr;
- const char *err;
- Raw raw;
- int addressfamily;
- char *loghost;
-
- raw = snew(struct raw_backend_data);
- raw->fn = &fn_table;
- raw->s = NULL;
- *backend_handle = raw;
- raw->sent_console_eof = raw->sent_socket_eof = FALSE;
-
- raw->frontend = frontend_handle;
-
- addressfamily = conf_get_int(conf, CONF_addressfamily);
- /*
- * 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);
- if ((err = sk_addr_error(addr)) != NULL) {
- sk_addr_free(addr);
- return err;
- }
-
- if (port < 0)
- port = 23; /* default telnet port */
-
- /*
- * Open socket.
- */
- raw->s = new_connection(addr, *realhost, port, 0, 1, nodelay, keepalive,
- (Plug) raw, conf);
- if ((err = sk_socket_error(raw->s)) != NULL)
- return err;
-
- loghost = conf_get_str(conf, CONF_loghost);
- if (*loghost) {
- char *colon;
-
- sfree(*realhost);
- *realhost = dupstr(loghost);
- colon = strrchr(*realhost, ':');
- if (colon) {
- /*
- * FIXME: if we ever update this aspect of ssh.c for
- * IPv6 literal management, this should change in line
- * with it.
- */
- *colon++ = '\0';
- }
- }
-
- return NULL;
-}
-
-static void raw_free(void *handle)
-{
- Raw raw = (Raw) handle;
-
- if (raw->s)
- sk_close(raw->s);
- sfree(raw);
-}
-
-/*
- * Stub routine (we don't have any need to reconfigure this backend).
- */
-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)
-{
- Raw raw = (Raw) handle;
-
- if (raw->s == NULL)
- return 0;
-
- raw->bufsize = sk_write(raw->s, buf, len);
-
- return raw->bufsize;
-}
-
-/*
- * Called to query the current socket sendability status.
- */
-static int raw_sendbuffer(void *handle)
-{
- Raw raw = (Raw) handle;
- return raw->bufsize;
-}
-
-/*
- * Called to set the size of the window
- */
-static void raw_size(void *handle, int width, int height)
-{
- /* Do nothing! */
- return;
-}
-
-/*
- * Send raw special codes. We only handle outgoing EOF here.
- */
-static void raw_special(void *handle, Telnet_Special code)
-{
- Raw raw = (Raw) handle;
- if (code == TS_EOF && raw->s) {
- sk_write_eof(raw->s);
- raw->sent_socket_eof= TRUE;
- raw_check_close(raw);
- }
-
- return;
-}
-
-/*
- * Return a list of the special codes that make sense in this
- * protocol.
- */
-static const struct telnet_special *raw_get_specials(void *handle)
-{
- return NULL;
-}
-
-static int raw_connected(void *handle)
-{
- Raw raw = (Raw) handle;
- return raw->s != NULL;
-}
-
-static int raw_sendok(void *handle)
-{
- return 1;
-}
-
-static void raw_unthrottle(void *handle, int backlog)
-{
- Raw raw = (Raw) handle;
- sk_set_frozen(raw->s, backlog > RAW_MAX_BACKLOG);
-}
-
-static int raw_ldisc(void *handle, int option)
-{
- if (option == LD_EDIT || option == LD_ECHO)
- return 1;
- return 0;
-}
-
-static void raw_provide_ldisc(void *handle, void *ldisc)
-{
- /* This is a stub. */
-}
-
-static void raw_provide_logctx(void *handle, void *logctx)
-{
- /* This is a stub. */
-}
-
-static int raw_exitcode(void *handle)
-{
- Raw raw = (Raw) handle;
- if (raw->s != NULL)
- return -1; /* still connected */
- else
- /* Exit codes are a meaningless concept in the Raw protocol */
- return 0;
-}
-
-/*
- * cfg_info for Raw does nothing at all.
- */
-static int raw_cfg_info(void *handle)
-{
- return 0;
-}
-
-Backend raw_backend = {
- raw_init,
- raw_free,
- raw_reconfig,
- raw_send,
- raw_sendbuffer,
- raw_size,
- raw_special,
- raw_get_specials,
- raw_connected,
- raw_exitcode,
- raw_sendok,
- raw_ldisc,
- raw_provide_ldisc,
- raw_provide_logctx,
- raw_unthrottle,
- raw_cfg_info,
- "raw",
- PROT_RAW,
- 0
-};
+/*
+ * "Raw" backend.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include "putty.h"
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#define RAW_MAX_BACKLOG 4096
+
+typedef struct raw_backend_data {
+ const struct plug_function_table *fn;
+ /* the above field _must_ be first in the structure */
+
+ Socket s;
+ int closed_on_socket_error;
+ int bufsize;
+ void *frontend;
+ int sent_console_eof, sent_socket_eof;
+} *Raw;
+
+static void raw_size(void *handle, int width, int height);
+
+static void c_write(Raw raw, char *buf, int len)
+{
+ int backlog = from_backend(raw->frontend, 0, buf, len);
+ sk_set_frozen(raw->s, backlog > RAW_MAX_BACKLOG);
+}
+
+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);
+}
+
+static void raw_check_close(Raw raw)
+{
+ /*
+ * Called after we send EOF on either the socket or the console.
+ * Its job is to wind up the session once we have sent EOF on both.
+ */
+ if (raw->sent_console_eof && raw->sent_socket_eof) {
+ if (raw->s) {
+ sk_close(raw->s);
+ raw->s = NULL;
+ notify_remote_exit(raw->frontend);
+ }
+ }
+}
+
+static int raw_closing(Plug plug, const char *error_msg, int error_code,
+ int calling_back)
+{
+ Raw raw = (Raw) plug;
+
+ if (error_msg) {
+ /* A socket error has occurred. */
+ if (raw->s) {
+ sk_close(raw->s);
+ raw->s = NULL;
+ raw->closed_on_socket_error = TRUE;
+ notify_remote_exit(raw->frontend);
+ }
+ logevent(raw->frontend, error_msg);
+ connection_fatal(raw->frontend, "%s", error_msg);
+ } else {
+ /* Otherwise, the remote side closed the connection normally. */
+ if (!raw->sent_console_eof && from_backend_eof(raw->frontend)) {
+ /*
+ * The front end wants us to close the outgoing side of the
+ * connection as soon as we see EOF from the far end.
+ */
+ if (!raw->sent_socket_eof) {
+ if (raw->s)
+ sk_write_eof(raw->s);
+ raw->sent_socket_eof= TRUE;
+ }
+ }
+ raw->sent_console_eof = TRUE;
+ raw_check_close(raw);
+ }
+ return 0;
+}
+
+static int raw_receive(Plug plug, int urgent, char *data, int len)
+{
+ Raw raw = (Raw) plug;
+ c_write(raw, data, len);
+ return 1;
+}
+
+static void raw_sent(Plug plug, int bufsize)
+{
+ Raw raw = (Raw) plug;
+ raw->bufsize = bufsize;
+}
+
+/*
+ * Called to set up the raw connection.
+ *
+ * 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 *raw_init(void *frontend_handle, void **backend_handle,
+ Conf *conf,
+ char *host, int port, char **realhost, int nodelay,
+ int keepalive)
+{
+ static const struct plug_function_table fn_table = {
+ raw_log,
+ raw_closing,
+ raw_receive,
+ raw_sent
+ };
+ SockAddr addr;
+ const char *err;
+ Raw raw;
+ int addressfamily;
+ char *loghost;
+
+ raw = snew(struct raw_backend_data);
+ raw->fn = &fn_table;
+ raw->s = NULL;
+ raw->closed_on_socket_error = FALSE;
+ *backend_handle = raw;
+ raw->sent_console_eof = raw->sent_socket_eof = FALSE;
+
+ raw->frontend = frontend_handle;
+
+ addressfamily = conf_get_int(conf, CONF_addressfamily);
+ /*
+ * 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);
+ if ((err = sk_addr_error(addr)) != NULL) {
+ sk_addr_free(addr);
+ return err;
+ }
+
+ if (port < 0)
+ port = 23; /* default telnet port */
+
+ /*
+ * Open socket.
+ */
+ raw->s = new_connection(addr, *realhost, port, 0, 1, nodelay, keepalive,
+ (Plug) raw, conf);
+ if ((err = sk_socket_error(raw->s)) != NULL)
+ return err;
+
+ loghost = conf_get_str(conf, CONF_loghost);
+ if (*loghost) {
+ char *colon;
+
+ sfree(*realhost);
+ *realhost = dupstr(loghost);
+
+ colon = host_strrchr(*realhost, ':');
+ if (colon)
+ *colon++ = '\0';
+ }
+
+ return NULL;
+}
+
+static void raw_free(void *handle)
+{
+ Raw raw = (Raw) handle;
+
+ if (raw->s)
+ sk_close(raw->s);
+ sfree(raw);
+}
+
+/*
+ * Stub routine (we don't have any need to reconfigure this backend).
+ */
+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)
+{
+ Raw raw = (Raw) handle;
+
+ if (raw->s == NULL)
+ return 0;
+
+ raw->bufsize = sk_write(raw->s, buf, len);
+
+ return raw->bufsize;
+}
+
+/*
+ * Called to query the current socket sendability status.
+ */
+static int raw_sendbuffer(void *handle)
+{
+ Raw raw = (Raw) handle;
+ return raw->bufsize;
+}
+
+/*
+ * Called to set the size of the window
+ */
+static void raw_size(void *handle, int width, int height)
+{
+ /* Do nothing! */
+ return;
+}
+
+/*
+ * Send raw special codes. We only handle outgoing EOF here.
+ */
+static void raw_special(void *handle, Telnet_Special code)
+{
+ Raw raw = (Raw) handle;
+ if (code == TS_EOF && raw->s) {
+ sk_write_eof(raw->s);
+ raw->sent_socket_eof= TRUE;
+ raw_check_close(raw);
+ }
+
+ return;
+}
+
+/*
+ * Return a list of the special codes that make sense in this
+ * protocol.
+ */
+static const struct telnet_special *raw_get_specials(void *handle)
+{
+ return NULL;
+}
+
+static int raw_connected(void *handle)
+{
+ Raw raw = (Raw) handle;
+ return raw->s != NULL;
+}
+
+static int raw_sendok(void *handle)
+{
+ return 1;
+}
+
+static void raw_unthrottle(void *handle, int backlog)
+{
+ Raw raw = (Raw) handle;
+ sk_set_frozen(raw->s, backlog > RAW_MAX_BACKLOG);
+}
+
+static int raw_ldisc(void *handle, int option)
+{
+ if (option == LD_EDIT || option == LD_ECHO)
+ return 1;
+ return 0;
+}
+
+static void raw_provide_ldisc(void *handle, void *ldisc)
+{
+ /* This is a stub. */
+}
+
+static void raw_provide_logctx(void *handle, void *logctx)
+{
+ /* This is a stub. */
+}
+
+static int raw_exitcode(void *handle)
+{
+ Raw raw = (Raw) handle;
+ if (raw->s != NULL)
+ return -1; /* still connected */
+ else if (raw->closed_on_socket_error)
+ return INT_MAX; /* a socket error counts as an unclean exit */
+ else
+ /* Exit codes are a meaningless concept in the Raw protocol */
+ return 0;
+}
+
+/*
+ * cfg_info for Raw does nothing at all.
+ */
+static int raw_cfg_info(void *handle)
+{
+ return 0;
+}
+
+Backend raw_backend = {
+ raw_init,
+ raw_free,
+ raw_reconfig,
+ raw_send,
+ raw_sendbuffer,
+ raw_size,
+ raw_special,
+ raw_get_specials,
+ raw_connected,
+ raw_exitcode,
+ raw_sendok,
+ raw_ldisc,
+ raw_provide_ldisc,
+ raw_provide_logctx,
+ raw_unthrottle,
+ raw_cfg_info,
+ "raw",
+ PROT_RAW,
+ 0
+};
diff --git a/tools/plink/rlogin.c b/tools/plink/rlogin.c
index 29ae5fd14..3c86eee85 100644
--- a/tools/plink/rlogin.c
+++ b/tools/plink/rlogin.c
@@ -1,428 +1,431 @@
-/*
- * Rlogin backend.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <ctype.h>
-
-#include "putty.h"
-
-#ifndef FALSE
-#define FALSE 0
-#endif
-#ifndef TRUE
-#define TRUE 1
-#endif
-
-#define RLOGIN_MAX_BACKLOG 4096
-
-typedef struct rlogin_tag {
- const struct plug_function_table *fn;
- /* the above field _must_ be first in the structure */
-
- Socket s;
- int bufsize;
- int firstbyte;
- int cansize;
- int term_width, term_height;
- void *frontend;
-
- Conf *conf;
-
- /* In case we need to read a username from the terminal before starting */
- prompts_t *prompt;
-} *Rlogin;
-
-static void rlogin_size(void *handle, int width, int height);
-
-static void c_write(Rlogin rlogin, char *buf, int len)
-{
- int backlog = from_backend(rlogin->frontend, 0, buf, len);
- sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG);
-}
-
-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);
-}
-
-static int rlogin_closing(Plug plug, const char *error_msg, int error_code,
- int calling_back)
-{
- Rlogin rlogin = (Rlogin) plug;
-
- /*
- * We don't implement independent EOF in each direction for Telnet
- * connections; as soon as we get word that the remote side has
- * sent us EOF, we wind up the whole connection.
- */
-
- if (rlogin->s) {
- sk_close(rlogin->s);
- rlogin->s = NULL;
- notify_remote_exit(rlogin->frontend);
- }
- if (error_msg) {
- /* A socket error has occurred. */
- logevent(rlogin->frontend, error_msg);
- connection_fatal(rlogin->frontend, "%s", error_msg);
- } /* Otherwise, the remote side closed the connection normally. */
- return 0;
-}
-
-static int rlogin_receive(Plug plug, int urgent, char *data, int len)
-{
- Rlogin rlogin = (Rlogin) plug;
- if (urgent == 2) {
- char c;
-
- c = *data++;
- len--;
- if (c == '\x80') {
- rlogin->cansize = 1;
- rlogin_size(rlogin, rlogin->term_width, rlogin->term_height);
- }
- /*
- * We should flush everything (aka Telnet SYNCH) if we see
- * 0x02, and we should turn off and on _local_ flow control
- * on 0x10 and 0x20 respectively. I'm not convinced it's
- * worth it...
- */
- } else {
- /*
- * Main rlogin protocol. This is really simple: the first
- * byte is expected to be NULL and is ignored, and the rest
- * is printed.
- */
- if (rlogin->firstbyte) {
- if (data[0] == '\0') {
- data++;
- len--;
- }
- rlogin->firstbyte = 0;
- }
- if (len > 0)
- c_write(rlogin, data, len);
- }
- return 1;
-}
-
-static void rlogin_sent(Plug plug, int bufsize)
-{
- Rlogin rlogin = (Rlogin) plug;
- rlogin->bufsize = bufsize;
-}
-
-static void rlogin_startup(Rlogin rlogin, const char *ruser)
-{
- char z = 0;
- char *p;
-
- sk_write(rlogin->s, &z, 1);
- p = conf_get_str(rlogin->conf, CONF_localusername);
- sk_write(rlogin->s, p, strlen(p));
- sk_write(rlogin->s, &z, 1);
- sk_write(rlogin->s, ruser, strlen(ruser));
- sk_write(rlogin->s, &z, 1);
- p = conf_get_str(rlogin->conf, CONF_termtype);
- sk_write(rlogin->s, p, strlen(p));
- sk_write(rlogin->s, "/", 1);
- p = conf_get_str(rlogin->conf, CONF_termspeed);
- sk_write(rlogin->s, p, strspn(p, "0123456789"));
- rlogin->bufsize = sk_write(rlogin->s, &z, 1);
-
- rlogin->prompt = NULL;
-}
-
-/*
- * Called to set up the rlogin connection.
- *
- * 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 *rlogin_init(void *frontend_handle, void **backend_handle,
- Conf *conf,
- char *host, int port, char **realhost,
- int nodelay, int keepalive)
-{
- static const struct plug_function_table fn_table = {
- rlogin_log,
- rlogin_closing,
- rlogin_receive,
- rlogin_sent
- };
- SockAddr addr;
- const char *err;
- Rlogin rlogin;
- char *ruser;
- int addressfamily;
- char *loghost;
-
- rlogin = snew(struct rlogin_tag);
- rlogin->fn = &fn_table;
- rlogin->s = NULL;
- rlogin->frontend = frontend_handle;
- rlogin->term_width = conf_get_int(conf, CONF_width);
- rlogin->term_height = conf_get_int(conf, CONF_height);
- rlogin->firstbyte = 1;
- rlogin->cansize = 0;
- rlogin->prompt = NULL;
- rlogin->conf = conf_copy(conf);
- *backend_handle = rlogin;
-
- addressfamily = conf_get_int(conf, CONF_addressfamily);
- /*
- * 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);
- if ((err = sk_addr_error(addr)) != NULL) {
- sk_addr_free(addr);
- return err;
- }
-
- if (port < 0)
- port = 513; /* default rlogin port */
-
- /*
- * Open socket.
- */
- rlogin->s = new_connection(addr, *realhost, port, 1, 0,
- nodelay, keepalive, (Plug) rlogin, conf);
- if ((err = sk_socket_error(rlogin->s)) != NULL)
- return err;
-
- loghost = conf_get_str(conf, CONF_loghost);
- if (*loghost) {
- char *colon;
-
- sfree(*realhost);
- *realhost = dupstr(loghost);
- colon = strrchr(*realhost, ':');
- if (colon) {
- /*
- * FIXME: if we ever update this aspect of ssh.c for
- * IPv6 literal management, this should change in line
- * with it.
- */
- *colon++ = '\0';
- }
- }
-
- /*
- * Send local username, remote username, terminal type and
- * terminal speed - unless we don't have the remote username yet,
- * in which case we prompt for it and may end up deferring doing
- * anything else until the local prompt mechanism returns.
- */
- if ((ruser = get_remote_username(conf)) == NULL) {
- rlogin_startup(rlogin, ruser);
- sfree(ruser);
- } else {
- int ret;
-
- rlogin->prompt = new_prompts(rlogin->frontend);
- rlogin->prompt->to_server = TRUE;
- rlogin->prompt->name = dupstr("Rlogin login name");
- add_prompt(rlogin->prompt, dupstr("rlogin username: "), TRUE);
- ret = get_userpass_input(rlogin->prompt, NULL, 0);
- if (ret >= 0) {
- rlogin_startup(rlogin, rlogin->prompt->prompts[0]->result);
- }
- }
-
- return NULL;
-}
-
-static void rlogin_free(void *handle)
-{
- Rlogin rlogin = (Rlogin) handle;
-
- if (rlogin->prompt)
- free_prompts(rlogin->prompt);
- if (rlogin->s)
- sk_close(rlogin->s);
- conf_free(rlogin->conf);
- sfree(rlogin);
-}
-
-/*
- * Stub routine (we don't have any need to reconfigure this backend).
- */
-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)
-{
- Rlogin rlogin = (Rlogin) handle;
-
- if (rlogin->s == NULL)
- return 0;
-
- if (rlogin->prompt) {
- /*
- * We're still prompting for a username, and aren't talking
- * directly to the network connection yet.
- */
- int ret = get_userpass_input(rlogin->prompt,
- (unsigned char *)buf, len);
- if (ret >= 0) {
- rlogin_startup(rlogin, rlogin->prompt->prompts[0]->result);
- /* that nulls out rlogin->prompt, so then we'll start sending
- * data down the wire in the obvious way */
- }
- } else {
- rlogin->bufsize = sk_write(rlogin->s, buf, len);
- }
-
- return rlogin->bufsize;
-}
-
-/*
- * Called to query the current socket sendability status.
- */
-static int rlogin_sendbuffer(void *handle)
-{
- Rlogin rlogin = (Rlogin) handle;
- return rlogin->bufsize;
-}
-
-/*
- * Called to set the size of the window
- */
-static void rlogin_size(void *handle, int width, int height)
-{
- Rlogin rlogin = (Rlogin) handle;
- char b[12] = { '\xFF', '\xFF', 0x73, 0x73, 0, 0, 0, 0, 0, 0, 0, 0 };
-
- rlogin->term_width = width;
- rlogin->term_height = height;
-
- if (rlogin->s == NULL || !rlogin->cansize)
- return;
-
- b[6] = rlogin->term_width >> 8;
- b[7] = rlogin->term_width & 0xFF;
- b[4] = rlogin->term_height >> 8;
- b[5] = rlogin->term_height & 0xFF;
- rlogin->bufsize = sk_write(rlogin->s, b, 12);
- return;
-}
-
-/*
- * Send rlogin special codes.
- */
-static void rlogin_special(void *handle, Telnet_Special code)
-{
- /* Do nothing! */
- return;
-}
-
-/*
- * Return a list of the special codes that make sense in this
- * protocol.
- */
-static const struct telnet_special *rlogin_get_specials(void *handle)
-{
- return NULL;
-}
-
-static int rlogin_connected(void *handle)
-{
- Rlogin rlogin = (Rlogin) handle;
- return rlogin->s != NULL;
-}
-
-static int rlogin_sendok(void *handle)
-{
- /* Rlogin rlogin = (Rlogin) handle; */
- return 1;
-}
-
-static void rlogin_unthrottle(void *handle, int backlog)
-{
- Rlogin rlogin = (Rlogin) handle;
- sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG);
-}
-
-static int rlogin_ldisc(void *handle, int option)
-{
- /* Rlogin rlogin = (Rlogin) handle; */
- return 0;
-}
-
-static void rlogin_provide_ldisc(void *handle, void *ldisc)
-{
- /* This is a stub. */
-}
-
-static void rlogin_provide_logctx(void *handle, void *logctx)
-{
- /* This is a stub. */
-}
-
-static int rlogin_exitcode(void *handle)
-{
- Rlogin rlogin = (Rlogin) handle;
- if (rlogin->s != NULL)
- return -1; /* still connected */
- else
- /* If we ever implement RSH, we'll probably need to do this properly */
- return 0;
-}
-
-/*
- * cfg_info for rlogin does nothing at all.
- */
-static int rlogin_cfg_info(void *handle)
-{
- return 0;
-}
-
-Backend rlogin_backend = {
- rlogin_init,
- rlogin_free,
- rlogin_reconfig,
- rlogin_send,
- rlogin_sendbuffer,
- rlogin_size,
- rlogin_special,
- rlogin_get_specials,
- rlogin_connected,
- rlogin_exitcode,
- rlogin_sendok,
- rlogin_ldisc,
- rlogin_provide_ldisc,
- rlogin_provide_logctx,
- rlogin_unthrottle,
- rlogin_cfg_info,
- "rlogin",
- PROT_RLOGIN,
- 513
-};
+/*
+ * Rlogin backend.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <ctype.h>
+
+#include "putty.h"
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#define RLOGIN_MAX_BACKLOG 4096
+
+typedef struct rlogin_tag {
+ const struct plug_function_table *fn;
+ /* the above field _must_ be first in the structure */
+
+ Socket s;
+ int closed_on_socket_error;
+ int bufsize;
+ int firstbyte;
+ int cansize;
+ int term_width, term_height;
+ void *frontend;
+
+ Conf *conf;
+
+ /* In case we need to read a username from the terminal before starting */
+ prompts_t *prompt;
+} *Rlogin;
+
+static void rlogin_size(void *handle, int width, int height);
+
+static void c_write(Rlogin rlogin, char *buf, int len)
+{
+ int backlog = from_backend(rlogin->frontend, 0, buf, len);
+ sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG);
+}
+
+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);
+}
+
+static int rlogin_closing(Plug plug, const char *error_msg, int error_code,
+ int calling_back)
+{
+ Rlogin rlogin = (Rlogin) plug;
+
+ /*
+ * We don't implement independent EOF in each direction for Telnet
+ * connections; as soon as we get word that the remote side has
+ * sent us EOF, we wind up the whole connection.
+ */
+
+ if (rlogin->s) {
+ sk_close(rlogin->s);
+ rlogin->s = NULL;
+ if (error_msg)
+ rlogin->closed_on_socket_error = TRUE;
+ notify_remote_exit(rlogin->frontend);
+ }
+ if (error_msg) {
+ /* A socket error has occurred. */
+ logevent(rlogin->frontend, error_msg);
+ connection_fatal(rlogin->frontend, "%s", error_msg);
+ } /* Otherwise, the remote side closed the connection normally. */
+ return 0;
+}
+
+static int rlogin_receive(Plug plug, int urgent, char *data, int len)
+{
+ Rlogin rlogin = (Rlogin) plug;
+ if (urgent == 2) {
+ char c;
+
+ c = *data++;
+ len--;
+ if (c == '\x80') {
+ rlogin->cansize = 1;
+ rlogin_size(rlogin, rlogin->term_width, rlogin->term_height);
+ }
+ /*
+ * We should flush everything (aka Telnet SYNCH) if we see
+ * 0x02, and we should turn off and on _local_ flow control
+ * on 0x10 and 0x20 respectively. I'm not convinced it's
+ * worth it...
+ */
+ } else {
+ /*
+ * Main rlogin protocol. This is really simple: the first
+ * byte is expected to be NULL and is ignored, and the rest
+ * is printed.
+ */
+ if (rlogin->firstbyte) {
+ if (data[0] == '\0') {
+ data++;
+ len--;
+ }
+ rlogin->firstbyte = 0;
+ }
+ if (len > 0)
+ c_write(rlogin, data, len);
+ }
+ return 1;
+}
+
+static void rlogin_sent(Plug plug, int bufsize)
+{
+ Rlogin rlogin = (Rlogin) plug;
+ rlogin->bufsize = bufsize;
+}
+
+static void rlogin_startup(Rlogin rlogin, const char *ruser)
+{
+ char z = 0;
+ char *p;
+
+ sk_write(rlogin->s, &z, 1);
+ p = conf_get_str(rlogin->conf, CONF_localusername);
+ sk_write(rlogin->s, p, strlen(p));
+ sk_write(rlogin->s, &z, 1);
+ sk_write(rlogin->s, ruser, strlen(ruser));
+ sk_write(rlogin->s, &z, 1);
+ p = conf_get_str(rlogin->conf, CONF_termtype);
+ sk_write(rlogin->s, p, strlen(p));
+ sk_write(rlogin->s, "/", 1);
+ p = conf_get_str(rlogin->conf, CONF_termspeed);
+ sk_write(rlogin->s, p, strspn(p, "0123456789"));
+ rlogin->bufsize = sk_write(rlogin->s, &z, 1);
+
+ rlogin->prompt = NULL;
+}
+
+/*
+ * Called to set up the rlogin connection.
+ *
+ * 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 *rlogin_init(void *frontend_handle, void **backend_handle,
+ Conf *conf,
+ char *host, int port, char **realhost,
+ int nodelay, int keepalive)
+{
+ static const struct plug_function_table fn_table = {
+ rlogin_log,
+ rlogin_closing,
+ rlogin_receive,
+ rlogin_sent
+ };
+ SockAddr addr;
+ const char *err;
+ Rlogin rlogin;
+ char *ruser;
+ int addressfamily;
+ char *loghost;
+
+ rlogin = snew(struct rlogin_tag);
+ rlogin->fn = &fn_table;
+ rlogin->s = NULL;
+ rlogin->closed_on_socket_error = FALSE;
+ rlogin->frontend = frontend_handle;
+ rlogin->term_width = conf_get_int(conf, CONF_width);
+ rlogin->term_height = conf_get_int(conf, CONF_height);
+ rlogin->firstbyte = 1;
+ rlogin->cansize = 0;
+ rlogin->prompt = NULL;
+ rlogin->conf = conf_copy(conf);
+ *backend_handle = rlogin;
+
+ addressfamily = conf_get_int(conf, CONF_addressfamily);
+ /*
+ * 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);
+ if ((err = sk_addr_error(addr)) != NULL) {
+ sk_addr_free(addr);
+ return err;
+ }
+
+ if (port < 0)
+ port = 513; /* default rlogin port */
+
+ /*
+ * Open socket.
+ */
+ rlogin->s = new_connection(addr, *realhost, port, 1, 0,
+ nodelay, keepalive, (Plug) rlogin, conf);
+ if ((err = sk_socket_error(rlogin->s)) != NULL)
+ return err;
+
+ loghost = conf_get_str(conf, CONF_loghost);
+ if (*loghost) {
+ char *colon;
+
+ sfree(*realhost);
+ *realhost = dupstr(loghost);
+
+ colon = host_strrchr(*realhost, ':');
+ if (colon)
+ *colon++ = '\0';
+ }
+
+ /*
+ * Send local username, remote username, terminal type and
+ * terminal speed - unless we don't have the remote username yet,
+ * in which case we prompt for it and may end up deferring doing
+ * anything else until the local prompt mechanism returns.
+ */
+ if ((ruser = get_remote_username(conf)) != NULL) {
+ rlogin_startup(rlogin, ruser);
+ sfree(ruser);
+ } else {
+ int ret;
+
+ rlogin->prompt = new_prompts(rlogin->frontend);
+ rlogin->prompt->to_server = TRUE;
+ rlogin->prompt->name = dupstr("Rlogin login name");
+ add_prompt(rlogin->prompt, dupstr("rlogin username: "), TRUE);
+ ret = get_userpass_input(rlogin->prompt, NULL, 0);
+ if (ret >= 0) {
+ rlogin_startup(rlogin, rlogin->prompt->prompts[0]->result);
+ }
+ }
+
+ return NULL;
+}
+
+static void rlogin_free(void *handle)
+{
+ Rlogin rlogin = (Rlogin) handle;
+
+ if (rlogin->prompt)
+ free_prompts(rlogin->prompt);
+ if (rlogin->s)
+ sk_close(rlogin->s);
+ conf_free(rlogin->conf);
+ sfree(rlogin);
+}
+
+/*
+ * Stub routine (we don't have any need to reconfigure this backend).
+ */
+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)
+{
+ Rlogin rlogin = (Rlogin) handle;
+
+ if (rlogin->s == NULL)
+ return 0;
+
+ if (rlogin->prompt) {
+ /*
+ * We're still prompting for a username, and aren't talking
+ * directly to the network connection yet.
+ */
+ int ret = get_userpass_input(rlogin->prompt,
+ (unsigned char *)buf, len);
+ if (ret >= 0) {
+ rlogin_startup(rlogin, rlogin->prompt->prompts[0]->result);
+ /* that nulls out rlogin->prompt, so then we'll start sending
+ * data down the wire in the obvious way */
+ }
+ } else {
+ rlogin->bufsize = sk_write(rlogin->s, buf, len);
+ }
+
+ return rlogin->bufsize;
+}
+
+/*
+ * Called to query the current socket sendability status.
+ */
+static int rlogin_sendbuffer(void *handle)
+{
+ Rlogin rlogin = (Rlogin) handle;
+ return rlogin->bufsize;
+}
+
+/*
+ * Called to set the size of the window
+ */
+static void rlogin_size(void *handle, int width, int height)
+{
+ Rlogin rlogin = (Rlogin) handle;
+ char b[12] = { '\xFF', '\xFF', 0x73, 0x73, 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ rlogin->term_width = width;
+ rlogin->term_height = height;
+
+ if (rlogin->s == NULL || !rlogin->cansize)
+ return;
+
+ b[6] = rlogin->term_width >> 8;
+ b[7] = rlogin->term_width & 0xFF;
+ b[4] = rlogin->term_height >> 8;
+ b[5] = rlogin->term_height & 0xFF;
+ rlogin->bufsize = sk_write(rlogin->s, b, 12);
+ return;
+}
+
+/*
+ * Send rlogin special codes.
+ */
+static void rlogin_special(void *handle, Telnet_Special code)
+{
+ /* Do nothing! */
+ return;
+}
+
+/*
+ * Return a list of the special codes that make sense in this
+ * protocol.
+ */
+static const struct telnet_special *rlogin_get_specials(void *handle)
+{
+ return NULL;
+}
+
+static int rlogin_connected(void *handle)
+{
+ Rlogin rlogin = (Rlogin) handle;
+ return rlogin->s != NULL;
+}
+
+static int rlogin_sendok(void *handle)
+{
+ /* Rlogin rlogin = (Rlogin) handle; */
+ return 1;
+}
+
+static void rlogin_unthrottle(void *handle, int backlog)
+{
+ Rlogin rlogin = (Rlogin) handle;
+ sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG);
+}
+
+static int rlogin_ldisc(void *handle, int option)
+{
+ /* Rlogin rlogin = (Rlogin) handle; */
+ return 0;
+}
+
+static void rlogin_provide_ldisc(void *handle, void *ldisc)
+{
+ /* This is a stub. */
+}
+
+static void rlogin_provide_logctx(void *handle, void *logctx)
+{
+ /* This is a stub. */
+}
+
+static int rlogin_exitcode(void *handle)
+{
+ Rlogin rlogin = (Rlogin) handle;
+ if (rlogin->s != NULL)
+ return -1; /* still connected */
+ else if (rlogin->closed_on_socket_error)
+ return INT_MAX; /* a socket error counts as an unclean exit */
+ else
+ /* If we ever implement RSH, we'll probably need to do this properly */
+ return 0;
+}
+
+/*
+ * cfg_info for rlogin does nothing at all.
+ */
+static int rlogin_cfg_info(void *handle)
+{
+ return 0;
+}
+
+Backend rlogin_backend = {
+ rlogin_init,
+ rlogin_free,
+ rlogin_reconfig,
+ rlogin_send,
+ rlogin_sendbuffer,
+ rlogin_size,
+ rlogin_special,
+ rlogin_get_specials,
+ rlogin_connected,
+ rlogin_exitcode,
+ rlogin_sendok,
+ rlogin_ldisc,
+ rlogin_provide_ldisc,
+ rlogin_provide_logctx,
+ rlogin_unthrottle,
+ rlogin_cfg_info,
+ "rlogin",
+ PROT_RLOGIN,
+ 513
+};
diff --git a/tools/plink/settings.c b/tools/plink/settings.c
index 725e468f1..a82a388bf 100644
--- a/tools/plink/settings.c
+++ b/tools/plink/settings.c
@@ -1,1070 +1,1083 @@
-/*
- * settings.c: read and write saved sessions. (platform-independent)
- */
-
-#include <assert.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include "putty.h"
-#include "storage.h"
-
-/* The cipher order given here is the default order. */
-static const struct keyvalwhere ciphernames[] = {
- { "aes", CIPHER_AES, -1, -1 },
- { "blowfish", CIPHER_BLOWFISH, -1, -1 },
- { "3des", CIPHER_3DES, -1, -1 },
- { "WARN", CIPHER_WARN, -1, -1 },
- { "arcfour", CIPHER_ARCFOUR, -1, -1 },
- { "des", CIPHER_DES, -1, -1 }
-};
-
-static const struct keyvalwhere kexnames[] = {
- { "dh-gex-sha1", KEX_DHGEX, -1, -1 },
- { "dh-group14-sha1", KEX_DHGROUP14, -1, -1 },
- { "dh-group1-sha1", KEX_DHGROUP1, -1, -1 },
- { "rsa", KEX_RSA, KEX_WARN, -1 },
- { "WARN", KEX_WARN, -1, -1 }
-};
-
-/*
- * All the terminal modes that we know about for the "TerminalModes"
- * setting. (Also used by config.c for the drop-down list.)
- * This is currently precisely the same as the set in ssh.c, but could
- * in principle differ if other backends started to support tty modes
- * (e.g., the pty backend).
- */
-const char *const ttymodes[] = {
- "INTR", "QUIT", "ERASE", "KILL", "EOF",
- "EOL", "EOL2", "START", "STOP", "SUSP",
- "DSUSP", "REPRINT", "WERASE", "LNEXT", "FLUSH",
- "SWTCH", "STATUS", "DISCARD", "IGNPAR", "PARMRK",
- "INPCK", "ISTRIP", "INLCR", "IGNCR", "ICRNL",
- "IUCLC", "IXON", "IXANY", "IXOFF", "IMAXBEL",
- "ISIG", "ICANON", "XCASE", "ECHO", "ECHOE",
- "ECHOK", "ECHONL", "NOFLSH", "TOSTOP", "IEXTEN",
- "ECHOCTL", "ECHOKE", "PENDIN", "OPOST", "OLCUC",
- "ONLCR", "OCRNL", "ONOCR", "ONLRET", "CS7",
- "CS8", "PARENB", "PARODD", NULL
-};
-
-/*
- * Convenience functions to access the backends[] array
- * (which is only present in tools that manage settings).
- */
-
-Backend *backend_from_name(const char *name)
-{
- Backend **p;
- for (p = backends; *p != NULL; p++)
- if (!strcmp((*p)->name, name))
- return *p;
- return NULL;
-}
-
-Backend *backend_from_proto(int proto)
-{
- Backend **p;
- for (p = backends; *p != NULL; p++)
- if ((*p)->protocol == proto)
- return *p;
- return NULL;
-}
-
-char *get_remote_username(Conf *conf)
-{
- char *username = conf_get_str(conf, CONF_username);
- if (*username) {
- return dupstr(username);
- } else if (conf_get_int(conf, CONF_username_from_env)) {
- /* Use local username. */
- return get_username(); /* might still be NULL */
- } else {
- return NULL;
- }
-}
-
-static char *gpps_raw(void *handle, const char *name, const char *def)
-{
- char *ret = read_setting_s(handle, name);
- if (!ret)
- ret = platform_default_s(name);
- if (!ret)
- ret = def ? dupstr(def) : NULL; /* permit NULL as final fallback */
- return ret;
-}
-
-static void gpps(void *handle, const char *name, const char *def,
- Conf *conf, int primary)
-{
- char *val = gpps_raw(handle, name, def);
- conf_set_str(conf, primary, val);
- sfree(val);
-}
-
-/*
- * gppfont and gppfile cannot have local defaults, since the very
- * format of a Filename or FontSpec is platform-dependent. So the
- * platform-dependent functions MUST return some sort of value.
- */
-static void gppfont(void *handle, const char *name, Conf *conf, int primary)
-{
- FontSpec *result = read_setting_fontspec(handle, name);
- if (!result)
- result = platform_default_fontspec(name);
- conf_set_fontspec(conf, primary, result);
- fontspec_free(result);
-}
-static void gppfile(void *handle, const char *name, Conf *conf, int primary)
-{
- Filename *result = read_setting_filename(handle, name);
- if (!result)
- result = platform_default_filename(name);
- conf_set_filename(conf, primary, result);
- filename_free(result);
-}
-
-static int gppi_raw(void *handle, 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)
-{
- conf_set_int(conf, primary, gppi_raw(handle, name, def));
-}
-
-/*
- * Read a set of name-value pairs in the format we occasionally use:
- * NAME\tVALUE\0NAME\tVALUE\0\0 in memory
- * NAME=VALUE,NAME=VALUE, in storage
- * `def' is in the storage format.
- */
-static int gppmap(void *handle, char *name, Conf *conf, int primary)
-{
- char *buf, *p, *q, *key, *val;
-
- /*
- * Start by clearing any existing subkeys of this key from conf.
- */
- while ((key = conf_get_str_nthstrkey(conf, primary, 0)) != NULL)
- conf_del_str_str(conf, primary, key);
-
- /*
- * Now read a serialised list from the settings and unmarshal it
- * into its components.
- */
- buf = gpps_raw(handle, name, NULL);
- if (!buf)
- return FALSE;
-
- p = buf;
- while (*p) {
- q = buf;
- val = NULL;
- while (*p && *p != ',') {
- int c = *p++;
- if (c == '=')
- c = '\0';
- if (c == '\\')
- c = *p++;
- *q++ = c;
- if (!c)
- val = q;
- }
- if (*p == ',')
- p++;
- if (!val)
- val = q;
- *q = '\0';
-
- if (primary == CONF_portfwd && buf[0] == 'D') {
- /*
- * Backwards-compatibility hack: dynamic forwardings are
- * indexed in the data store as a third type letter in the
- * key, 'D' alongside 'L' and 'R' - but really, they
- * should be filed under 'L' with a special _value_,
- * because local and dynamic forwardings both involve
- * _listening_ on a local port, and are hence mutually
- * exclusive on the same port number. So here we translate
- * the legacy storage format into the sensible internal
- * form.
- */
- char *newkey = dupcat("L", buf+1, NULL);
- conf_set_str_str(conf, primary, newkey, "D");
- sfree(newkey);
- } else {
- conf_set_str_str(conf, primary, buf, val);
- }
- }
- sfree(buf);
-
- return TRUE;
-}
-
-/*
- * Write a set of name/value pairs in the above format.
- */
-static void wmap(void *handle, char const *outkey, Conf *conf, int primary)
-{
- char *buf, *p, *q, *key, *realkey, *val;
- int len;
-
- len = 1; /* allow for NUL */
-
- for (val = conf_get_str_strs(conf, primary, NULL, &key);
- val != NULL;
- val = conf_get_str_strs(conf, primary, key, &key))
- len += 2 + 2 * (strlen(key) + strlen(val)); /* allow for escaping */
-
- buf = snewn(len, char);
- p = buf;
-
- for (val = conf_get_str_strs(conf, primary, NULL, &key);
- val != NULL;
- val = conf_get_str_strs(conf, primary, key, &key)) {
-
- if (primary == CONF_portfwd && !strcmp(val, "D")) {
- /*
- * Backwards-compatibility hack, as above: translate from
- * the sensible internal representation of dynamic
- * forwardings (key "L<port>", value "D") to the
- * conceptually incoherent legacy storage format (key
- * "D<port>", value empty).
- */
- realkey = key; /* restore it at end of loop */
- val = "";
- key = dupcat("D", key+1, NULL);
- } else {
- realkey = NULL;
- }
-
- if (p != buf)
- *p++ = ',';
- for (q = key; *q; q++) {
- if (*q == '=' || *q == ',' || *q == '\\')
- *p++ = '\\';
- *p++ = *q;
- }
- *p++ = '=';
- for (q = val; *q; q++) {
- if (*q == '=' || *q == ',' || *q == '\\')
- *p++ = '\\';
- *p++ = *q;
- }
-
- if (realkey) {
- free(key);
- key = realkey;
- }
- }
- *p = '\0';
- write_setting_s(handle, outkey, buf);
- sfree(buf);
-}
-
-static int key2val(const struct keyvalwhere *mapping,
- int nmaps, char *key)
-{
- int i;
- for (i = 0; i < nmaps; i++)
- if (!strcmp(mapping[i].s, key)) return mapping[i].v;
- return -1;
-}
-
-static const char *val2key(const struct keyvalwhere *mapping,
- int nmaps, int val)
-{
- int i;
- for (i = 0; i < nmaps; i++)
- if (mapping[i].v == val) return mapping[i].s;
- return NULL;
-}
-
-/*
- * Helper function to parse a comma-separated list of strings into
- * a preference list array of values. Any missing values are added
- * 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,
- const struct keyvalwhere *mapping, int nvals,
- Conf *conf, int primary)
-{
- char *commalist;
- char *p, *q;
- int i, j, n, v, pos;
- unsigned long seen = 0; /* bitmap for weeding dups etc */
-
- /*
- * Fetch the string which we'll parse as a comma-separated list.
- */
- commalist = gpps_raw(sesskey, name, def);
-
- /*
- * Go through that list and convert it into values.
- */
- n = 0;
- p = commalist;
- while (1) {
- while (*p && *p == ',') p++;
- if (!*p)
- break; /* no more words */
-
- q = p;
- while (*p && *p != ',') p++;
- if (*p) *p++ = '\0';
-
- v = key2val(mapping, nvals, q);
- if (v != -1 && !(seen & (1 << v))) {
- seen |= (1 << v);
- conf_set_int_int(conf, primary, n, v);
- n++;
- }
- }
-
- sfree(commalist);
-
- /*
- * Now go through 'mapping' and add values that weren't mentioned
- * in the list we fetched. We may have to loop over it multiple
- * times so that we add values before other values whose default
- * positions depend on them.
- */
- while (n < nvals) {
- for (i = 0; i < nvals; i++) {
- assert(mapping[i].v < 32);
-
- if (!(seen & (1 << mapping[i].v))) {
- /*
- * This element needs adding. But can we add it yet?
- */
- if (mapping[i].vrel != -1 && !(seen & (1 << mapping[i].vrel)))
- continue; /* nope */
-
- /*
- * OK, we can work out where to add this element, so
- * do so.
- */
- if (mapping[i].vrel == -1) {
- pos = (mapping[i].where < 0 ? n : 0);
- } else {
- for (j = 0; j < n; j++)
- if (conf_get_int_int(conf, primary, j) ==
- mapping[i].vrel)
- break;
- assert(j < n); /* implied by (seen & (1<<vrel)) */
- pos = (mapping[i].where < 0 ? j : j+1);
- }
-
- /*
- * And add it.
- */
- for (j = n-1; j >= pos; j--)
- conf_set_int_int(conf, primary, j+1,
- conf_get_int_int(conf, primary, j));
- conf_set_int_int(conf, primary, pos, mapping[i].v);
- n++;
- }
- }
- }
-}
-
-/*
- * Write out a preference list.
- */
-static void wprefs(void *sesskey, char *name,
- const struct keyvalwhere *mapping, int nvals,
- Conf *conf, int primary)
-{
- char *buf, *p;
- int i, maxlen;
-
- for (maxlen = i = 0; i < nvals; i++) {
- const char *s = val2key(mapping, nvals,
- conf_get_int_int(conf, primary, i));
- if (s) {
- maxlen += (maxlen > 0 ? 1 : 0) + strlen(s);
- }
- }
-
- buf = snewn(maxlen + 1, char);
- p = buf;
-
- for (i = 0; i < nvals; i++) {
- const char *s = val2key(mapping, nvals,
- conf_get_int_int(conf, primary, i));
- if (s) {
- p += sprintf(p, "%s%s", (p > buf ? "," : ""), s);
- }
- }
-
- assert(p - buf == maxlen);
- *p = '\0';
-
- write_setting_s(sesskey, name, buf);
-
- sfree(buf);
-}
-
-char *save_settings(char *section, Conf *conf)
-{
- void *sesskey;
- char *errmsg;
-
- sesskey = open_settings_w(section, &errmsg);
- if (!sesskey)
- return errmsg;
- save_open_settings(sesskey, conf);
- close_settings_w(sesskey);
- return NULL;
-}
-
-void save_open_settings(void *sesskey, Conf *conf)
-{
- int i;
- char *p;
-
- write_setting_i(sesskey, "Present", 1);
- write_setting_s(sesskey, "HostName", conf_get_str(conf, CONF_host));
- write_setting_filename(sesskey, "LogFileName", conf_get_filename(conf, CONF_logfilename));
- write_setting_i(sesskey, "LogType", conf_get_int(conf, CONF_logtype));
- write_setting_i(sesskey, "LogFileClash", conf_get_int(conf, CONF_logxfovr));
- write_setting_i(sesskey, "LogFlush", conf_get_int(conf, CONF_logflush));
- write_setting_i(sesskey, "SSHLogOmitPasswords", conf_get_int(conf, CONF_logomitpass));
- write_setting_i(sesskey, "SSHLogOmitData", conf_get_int(conf, CONF_logomitdata));
- p = "raw";
- {
- const Backend *b = backend_from_proto(conf_get_int(conf, CONF_protocol));
- if (b)
- p = b->name;
- }
- write_setting_s(sesskey, "Protocol", p);
- write_setting_i(sesskey, "PortNumber", conf_get_int(conf, CONF_port));
- /* The CloseOnExit numbers are arranged in a different order from
- * the standard FORCE_ON / FORCE_OFF / AUTO. */
- write_setting_i(sesskey, "CloseOnExit", (conf_get_int(conf, CONF_close_on_exit)+2)%3);
- write_setting_i(sesskey, "WarnOnClose", !!conf_get_int(conf, CONF_warn_on_close));
- write_setting_i(sesskey, "PingInterval", conf_get_int(conf, CONF_ping_interval) / 60); /* minutes */
- write_setting_i(sesskey, "PingIntervalSecs", conf_get_int(conf, CONF_ping_interval) % 60); /* seconds */
- write_setting_i(sesskey, "TCPNoDelay", conf_get_int(conf, CONF_tcp_nodelay));
- write_setting_i(sesskey, "TCPKeepalives", conf_get_int(conf, CONF_tcp_keepalives));
- write_setting_s(sesskey, "TerminalType", conf_get_str(conf, CONF_termtype));
- write_setting_s(sesskey, "TerminalSpeed", conf_get_str(conf, CONF_termspeed));
- wmap(sesskey, "TerminalModes", conf, CONF_ttymodes);
-
- /* Address family selection */
- write_setting_i(sesskey, "AddressFamily", conf_get_int(conf, CONF_addressfamily));
-
- /* proxy settings */
- write_setting_s(sesskey, "ProxyExcludeList", conf_get_str(conf, CONF_proxy_exclude_list));
- write_setting_i(sesskey, "ProxyDNS", (conf_get_int(conf, CONF_proxy_dns)+2)%3);
- write_setting_i(sesskey, "ProxyLocalhost", conf_get_int(conf, CONF_even_proxy_localhost));
- write_setting_i(sesskey, "ProxyMethod", conf_get_int(conf, CONF_proxy_type));
- write_setting_s(sesskey, "ProxyHost", conf_get_str(conf, CONF_proxy_host));
- write_setting_i(sesskey, "ProxyPort", conf_get_int(conf, CONF_proxy_port));
- 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));
- wmap(sesskey, "Environment", conf, CONF_environmt);
- write_setting_s(sesskey, "UserName", conf_get_str(conf, CONF_username));
- write_setting_i(sesskey, "UserNameFromEnvironment", conf_get_int(conf, CONF_username_from_env));
- write_setting_s(sesskey, "LocalUserName", conf_get_str(conf, CONF_localusername));
- write_setting_i(sesskey, "NoPTY", conf_get_int(conf, CONF_nopty));
- write_setting_i(sesskey, "Compression", conf_get_int(conf, CONF_compression));
- write_setting_i(sesskey, "TryAgent", conf_get_int(conf, CONF_tryagent));
- write_setting_i(sesskey, "AgentFwd", conf_get_int(conf, CONF_agentfwd));
- write_setting_i(sesskey, "GssapiFwd", conf_get_int(conf, CONF_gssapifwd));
- write_setting_i(sesskey, "ChangeUsername", conf_get_int(conf, CONF_change_username));
- wprefs(sesskey, "Cipher", ciphernames, CIPHER_MAX, conf, CONF_ssh_cipherlist);
- wprefs(sesskey, "KEX", kexnames, KEX_MAX, conf, CONF_ssh_kexlist);
- write_setting_i(sesskey, "RekeyTime", conf_get_int(conf, CONF_ssh_rekey_time));
- write_setting_s(sesskey, "RekeyBytes", conf_get_str(conf, CONF_ssh_rekey_data));
- write_setting_i(sesskey, "SshNoAuth", conf_get_int(conf, CONF_ssh_no_userauth));
- write_setting_i(sesskey, "SshBanner", conf_get_int(conf, CONF_ssh_show_banner));
- write_setting_i(sesskey, "AuthTIS", conf_get_int(conf, CONF_try_tis_auth));
- write_setting_i(sesskey, "AuthKI", conf_get_int(conf, CONF_try_ki_auth));
- write_setting_i(sesskey, "AuthGSSAPI", conf_get_int(conf, CONF_try_gssapi_auth));
-#ifndef NO_GSSAPI
- wprefs(sesskey, "GSSLibs", gsslibkeywords, ngsslibs, conf, CONF_ssh_gsslist);
- write_setting_filename(sesskey, "GSSCustom", conf_get_filename(conf, CONF_ssh_gss_custom));
-#endif
- write_setting_i(sesskey, "SshNoShell", conf_get_int(conf, CONF_ssh_no_shell));
- write_setting_i(sesskey, "SshProt", conf_get_int(conf, CONF_sshprot));
- write_setting_s(sesskey, "LogHost", conf_get_str(conf, CONF_loghost));
- write_setting_i(sesskey, "SSH2DES", conf_get_int(conf, CONF_ssh2_des_cbc));
- write_setting_filename(sesskey, "PublicKeyFile", conf_get_filename(conf, CONF_keyfile));
- write_setting_s(sesskey, "RemoteCommand", conf_get_str(conf, CONF_remote_cmd));
- write_setting_i(sesskey, "RFCEnviron", conf_get_int(conf, CONF_rfc_environ));
- write_setting_i(sesskey, "PassiveTelnet", conf_get_int(conf, CONF_passive_telnet));
- write_setting_i(sesskey, "BackspaceIsDelete", conf_get_int(conf, CONF_bksp_is_delete));
- write_setting_i(sesskey, "RXVTHomeEnd", conf_get_int(conf, CONF_rxvt_homeend));
- write_setting_i(sesskey, "LinuxFunctionKeys", conf_get_int(conf, CONF_funky_type));
- write_setting_i(sesskey, "NoApplicationKeys", conf_get_int(conf, CONF_no_applic_k));
- write_setting_i(sesskey, "NoApplicationCursors", conf_get_int(conf, CONF_no_applic_c));
- write_setting_i(sesskey, "NoMouseReporting", conf_get_int(conf, CONF_no_mouse_rep));
- write_setting_i(sesskey, "NoRemoteResize", conf_get_int(conf, CONF_no_remote_resize));
- write_setting_i(sesskey, "NoAltScreen", conf_get_int(conf, CONF_no_alt_screen));
- write_setting_i(sesskey, "NoRemoteWinTitle", conf_get_int(conf, CONF_no_remote_wintitle));
- write_setting_i(sesskey, "RemoteQTitleAction", conf_get_int(conf, CONF_remote_qtitle_action));
- write_setting_i(sesskey, "NoDBackspace", conf_get_int(conf, CONF_no_dbackspace));
- write_setting_i(sesskey, "NoRemoteCharset", conf_get_int(conf, CONF_no_remote_charset));
- write_setting_i(sesskey, "ApplicationCursorKeys", conf_get_int(conf, CONF_app_cursor));
- write_setting_i(sesskey, "ApplicationKeypad", conf_get_int(conf, CONF_app_keypad));
- write_setting_i(sesskey, "NetHackKeypad", conf_get_int(conf, CONF_nethack_keypad));
- write_setting_i(sesskey, "AltF4", conf_get_int(conf, CONF_alt_f4));
- write_setting_i(sesskey, "AltSpace", conf_get_int(conf, CONF_alt_space));
- 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));
- 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));
- write_setting_i(sesskey, "LocalEdit", conf_get_int(conf, CONF_localedit));
- write_setting_s(sesskey, "Answerback", conf_get_str(conf, CONF_answerback));
- write_setting_i(sesskey, "AlwaysOnTop", conf_get_int(conf, CONF_alwaysontop));
- write_setting_i(sesskey, "FullScreenOnAltEnter", conf_get_int(conf, CONF_fullscreenonaltenter));
- write_setting_i(sesskey, "HideMousePtr", conf_get_int(conf, CONF_hide_mouseptr));
- write_setting_i(sesskey, "SunkenEdge", conf_get_int(conf, CONF_sunken_edge));
- write_setting_i(sesskey, "WindowBorder", conf_get_int(conf, CONF_window_border));
- write_setting_i(sesskey, "CurType", conf_get_int(conf, CONF_cursor_type));
- write_setting_i(sesskey, "BlinkCur", conf_get_int(conf, CONF_blink_cur));
- write_setting_i(sesskey, "Beep", conf_get_int(conf, CONF_beep));
- write_setting_i(sesskey, "BeepInd", conf_get_int(conf, CONF_beep_ind));
- write_setting_filename(sesskey, "BellWaveFile", conf_get_filename(conf, CONF_bell_wavefile));
- write_setting_i(sesskey, "BellOverload", conf_get_int(conf, CONF_bellovl));
- write_setting_i(sesskey, "BellOverloadN", conf_get_int(conf, CONF_bellovl_n));
- write_setting_i(sesskey, "BellOverloadT", conf_get_int(conf, CONF_bellovl_t)
-#ifdef PUTTY_UNIX_H
- * 1000
-#endif
- );
- write_setting_i(sesskey, "BellOverloadS", conf_get_int(conf, CONF_bellovl_s)
-#ifdef PUTTY_UNIX_H
- * 1000
-#endif
- );
- write_setting_i(sesskey, "ScrollbackLines", conf_get_int(conf, CONF_savelines));
- write_setting_i(sesskey, "DECOriginMode", conf_get_int(conf, CONF_dec_om));
- write_setting_i(sesskey, "AutoWrapMode", conf_get_int(conf, CONF_wrap_mode));
- write_setting_i(sesskey, "LFImpliesCR", conf_get_int(conf, CONF_lfhascr));
- write_setting_i(sesskey, "CRImpliesLF", conf_get_int(conf, CONF_crhaslf));
- write_setting_i(sesskey, "DisableArabicShaping", conf_get_int(conf, CONF_arabicshaping));
- write_setting_i(sesskey, "DisableBidi", conf_get_int(conf, CONF_bidi));
- write_setting_i(sesskey, "WinNameAlways", conf_get_int(conf, CONF_win_name_always));
- write_setting_s(sesskey, "WinTitle", conf_get_str(conf, CONF_wintitle));
- write_setting_i(sesskey, "TermWidth", conf_get_int(conf, CONF_width));
- write_setting_i(sesskey, "TermHeight", conf_get_int(conf, CONF_height));
- write_setting_fontspec(sesskey, "Font", conf_get_fontspec(conf, CONF_font));
- write_setting_i(sesskey, "FontQuality", conf_get_int(conf, CONF_font_quality));
- write_setting_i(sesskey, "FontVTMode", conf_get_int(conf, CONF_vtmode));
- write_setting_i(sesskey, "UseSystemColours", conf_get_int(conf, CONF_system_colour));
- write_setting_i(sesskey, "TryPalette", conf_get_int(conf, CONF_try_palette));
- write_setting_i(sesskey, "ANSIColour", conf_get_int(conf, CONF_ansi_colour));
- write_setting_i(sesskey, "Xterm256Colour", conf_get_int(conf, CONF_xterm_256_colour));
- write_setting_i(sesskey, "BoldAsColour", conf_get_int(conf, CONF_bold_colour));
-
- for (i = 0; i < 22; i++) {
- char buf[20], buf2[30];
- sprintf(buf, "Colour%d", i);
- sprintf(buf2, "%d,%d,%d",
- conf_get_int_int(conf, CONF_colours, i*3+0),
- conf_get_int_int(conf, CONF_colours, i*3+1),
- conf_get_int_int(conf, CONF_colours, i*3+2));
- write_setting_s(sesskey, buf, buf2);
- }
- write_setting_i(sesskey, "RawCNP", conf_get_int(conf, CONF_rawcnp));
- write_setting_i(sesskey, "PasteRTF", conf_get_int(conf, CONF_rtf_paste));
- write_setting_i(sesskey, "MouseIsXterm", conf_get_int(conf, CONF_mouse_is_xterm));
- write_setting_i(sesskey, "RectSelect", conf_get_int(conf, CONF_rect_select));
- write_setting_i(sesskey, "MouseOverride", conf_get_int(conf, CONF_mouse_override));
- for (i = 0; i < 256; i += 32) {
- char buf[20], buf2[256];
- int j;
- sprintf(buf, "Wordness%d", i);
- *buf2 = '\0';
- for (j = i; j < i + 32; j++) {
- sprintf(buf2 + strlen(buf2), "%s%d",
- (*buf2 ? "," : ""),
- conf_get_int_int(conf, CONF_wordness, j));
- }
- write_setting_s(sesskey, buf, buf2);
- }
- write_setting_s(sesskey, "LineCodePage", conf_get_str(conf, CONF_line_codepage));
- write_setting_i(sesskey, "CJKAmbigWide", conf_get_int(conf, CONF_cjk_ambig_wide));
- write_setting_i(sesskey, "UTF8Override", conf_get_int(conf, CONF_utf8_override));
- write_setting_s(sesskey, "Printer", conf_get_str(conf, CONF_printer));
- write_setting_i(sesskey, "CapsLockCyr", conf_get_int(conf, CONF_xlat_capslockcyr));
- write_setting_i(sesskey, "ScrollBar", conf_get_int(conf, CONF_scrollbar));
- write_setting_i(sesskey, "ScrollBarFullScreen", conf_get_int(conf, CONF_scrollbar_in_fullscreen));
- write_setting_i(sesskey, "ScrollOnKey", conf_get_int(conf, CONF_scroll_on_key));
- write_setting_i(sesskey, "ScrollOnDisp", conf_get_int(conf, CONF_scroll_on_disp));
- write_setting_i(sesskey, "EraseToScrollback", conf_get_int(conf, CONF_erase_to_scrollback));
- write_setting_i(sesskey, "LockSize", conf_get_int(conf, CONF_resize_action));
- write_setting_i(sesskey, "BCE", conf_get_int(conf, CONF_bce));
- write_setting_i(sesskey, "BlinkText", conf_get_int(conf, CONF_blinktext));
- write_setting_i(sesskey, "X11Forward", conf_get_int(conf, CONF_x11_forward));
- write_setting_s(sesskey, "X11Display", conf_get_str(conf, CONF_x11_display));
- write_setting_i(sesskey, "X11AuthType", conf_get_int(conf, CONF_x11_auth));
- write_setting_filename(sesskey, "X11AuthFile", conf_get_filename(conf, CONF_xauthfile));
- write_setting_i(sesskey, "LocalPortAcceptAll", conf_get_int(conf, CONF_lport_acceptall));
- write_setting_i(sesskey, "RemotePortAcceptAll", conf_get_int(conf, CONF_rport_acceptall));
- wmap(sesskey, "PortForwardings", conf, CONF_portfwd);
- write_setting_i(sesskey, "BugIgnore1", 2-conf_get_int(conf, CONF_sshbug_ignore1));
- write_setting_i(sesskey, "BugPlainPW1", 2-conf_get_int(conf, CONF_sshbug_plainpw1));
- write_setting_i(sesskey, "BugRSA1", 2-conf_get_int(conf, CONF_sshbug_rsa1));
- write_setting_i(sesskey, "BugIgnore2", 2-conf_get_int(conf, CONF_sshbug_ignore2));
- write_setting_i(sesskey, "BugHMAC2", 2-conf_get_int(conf, CONF_sshbug_hmac2));
- write_setting_i(sesskey, "BugDeriveKey2", 2-conf_get_int(conf, CONF_sshbug_derivekey2));
- write_setting_i(sesskey, "BugRSAPad2", 2-conf_get_int(conf, CONF_sshbug_rsapad2));
- write_setting_i(sesskey, "BugPKSessID2", 2-conf_get_int(conf, CONF_sshbug_pksessid2));
- write_setting_i(sesskey, "BugRekey2", 2-conf_get_int(conf, CONF_sshbug_rekey2));
- write_setting_i(sesskey, "BugMaxPkt2", 2-conf_get_int(conf, CONF_sshbug_maxpkt2));
- write_setting_i(sesskey, "StampUtmp", conf_get_int(conf, CONF_stamp_utmp));
- write_setting_i(sesskey, "LoginShell", conf_get_int(conf, CONF_login_shell));
- write_setting_i(sesskey, "ScrollbarOnLeft", conf_get_int(conf, CONF_scrollbar_on_left));
- write_setting_fontspec(sesskey, "BoldFont", conf_get_fontspec(conf, CONF_boldfont));
- write_setting_fontspec(sesskey, "WideFont", conf_get_fontspec(conf, CONF_widefont));
- write_setting_fontspec(sesskey, "WideBoldFont", conf_get_fontspec(conf, CONF_wideboldfont));
- write_setting_i(sesskey, "ShadowBold", conf_get_int(conf, CONF_shadowbold));
- write_setting_i(sesskey, "ShadowBoldOffset", conf_get_int(conf, CONF_shadowboldoffset));
- write_setting_s(sesskey, "SerialLine", conf_get_str(conf, CONF_serline));
- write_setting_i(sesskey, "SerialSpeed", conf_get_int(conf, CONF_serspeed));
- write_setting_i(sesskey, "SerialDataBits", conf_get_int(conf, CONF_serdatabits));
- write_setting_i(sesskey, "SerialStopHalfbits", conf_get_int(conf, CONF_serstopbits));
- write_setting_i(sesskey, "SerialParity", conf_get_int(conf, CONF_serparity));
- write_setting_i(sesskey, "SerialFlowControl", conf_get_int(conf, CONF_serflow));
- write_setting_s(sesskey, "WindowClass", conf_get_str(conf, CONF_winclass));
-}
-
-void load_settings(char *section, Conf *conf)
-{
- void *sesskey;
-
- sesskey = open_settings_r(section);
- load_open_settings(sesskey, conf);
- close_settings_r(sesskey);
-
- if (conf_launchable(conf))
- add_session_to_jumplist(section);
-}
-
-void load_open_settings(void *sesskey, Conf *conf)
-{
- int i;
- char *prot;
-
- conf_set_int(conf, CONF_ssh_subsys, 0); /* FIXME: load this properly */
- conf_set_str(conf, CONF_remote_cmd, "");
- conf_set_str(conf, CONF_remote_cmd2, "");
- conf_set_str(conf, CONF_ssh_nc_host, "");
-
- gpps(sesskey, "HostName", "", conf, CONF_host);
- gppfile(sesskey, "LogFileName", conf, CONF_logfilename);
- gppi(sesskey, "LogType", 0, conf, CONF_logtype);
- gppi(sesskey, "LogFileClash", LGXF_ASK, conf, CONF_logxfovr);
- gppi(sesskey, "LogFlush", 1, conf, CONF_logflush);
- gppi(sesskey, "SSHLogOmitPasswords", 1, conf, CONF_logomitpass);
- gppi(sesskey, "SSHLogOmitData", 0, conf, CONF_logomitdata);
-
- prot = gpps_raw(sesskey, "Protocol", "default");
- conf_set_int(conf, CONF_protocol, default_protocol);
- conf_set_int(conf, CONF_port, default_port);
- {
- const Backend *b = backend_from_name(prot);
- if (b) {
- conf_set_int(conf, CONF_protocol, b->protocol);
- gppi(sesskey, "PortNumber", default_port, conf, CONF_port);
- }
- }
- sfree(prot);
-
- /* Address family selection */
- gppi(sesskey, "AddressFamily", ADDRTYPE_UNSPEC, conf, CONF_addressfamily);
-
- /* The CloseOnExit numbers are arranged in a different order from
- * the standard FORCE_ON / FORCE_OFF / AUTO. */
- i = gppi_raw(sesskey, "CloseOnExit", 1); conf_set_int(conf, CONF_close_on_exit, (i+1)%3);
- gppi(sesskey, "WarnOnClose", 1, conf, CONF_warn_on_close);
- {
- /* This is two values for backward compatibility with 0.50/0.51 */
- int pingmin, pingsec;
- pingmin = gppi_raw(sesskey, "PingInterval", 0);
- pingsec = gppi_raw(sesskey, "PingIntervalSecs", 0);
- conf_set_int(conf, CONF_ping_interval, pingmin * 60 + pingsec);
- }
- gppi(sesskey, "TCPNoDelay", 1, conf, CONF_tcp_nodelay);
- gppi(sesskey, "TCPKeepalives", 0, conf, CONF_tcp_keepalives);
- gpps(sesskey, "TerminalType", "xterm", conf, CONF_termtype);
- gpps(sesskey, "TerminalSpeed", "38400,38400", conf, CONF_termspeed);
- if (!gppmap(sesskey, "TerminalModes", conf, CONF_ttymodes)) {
- /* This hardcodes a big set of defaults in any new saved
- * sessions. Let's hope we don't change our mind. */
- for (i = 0; ttymodes[i]; i++)
- conf_set_str_str(conf, CONF_ttymodes, ttymodes[i], "A");
- }
-
- /* proxy settings */
- gpps(sesskey, "ProxyExcludeList", "", conf, CONF_proxy_exclude_list);
- i = gppi_raw(sesskey, "ProxyDNS", 1); conf_set_int(conf, CONF_proxy_dns, (i+1)%3);
- gppi(sesskey, "ProxyLocalhost", 0, conf, CONF_even_proxy_localhost);
- gppi(sesskey, "ProxyMethod", -1, conf, CONF_proxy_type);
- if (conf_get_int(conf, CONF_proxy_type) == -1) {
- int i;
- i = gppi_raw(sesskey, "ProxyType", 0);
- if (i == 0)
- conf_set_int(conf, CONF_proxy_type, PROXY_NONE);
- else if (i == 1)
- conf_set_int(conf, CONF_proxy_type, PROXY_HTTP);
- else if (i == 3)
- conf_set_int(conf, CONF_proxy_type, PROXY_TELNET);
- else if (i == 4)
- conf_set_int(conf, CONF_proxy_type, PROXY_CMD);
- else {
- i = gppi_raw(sesskey, "ProxySOCKSVersion", 5);
- if (i == 5)
- conf_set_int(conf, CONF_proxy_type, PROXY_SOCKS5);
- else
- conf_set_int(conf, CONF_proxy_type, PROXY_SOCKS4);
- }
- }
- gpps(sesskey, "ProxyHost", "proxy", conf, CONF_proxy_host);
- gppi(sesskey, "ProxyPort", 80, conf, CONF_proxy_port);
- gpps(sesskey, "ProxyUsername", "", conf, CONF_proxy_username);
- gpps(sesskey, "ProxyPassword", "", conf, CONF_proxy_password);
- gpps(sesskey, "ProxyTelnetCommand", "connect %host %port\\n",
- conf, CONF_proxy_telnet_command);
- gppmap(sesskey, "Environment", conf, CONF_environmt);
- gpps(sesskey, "UserName", "", conf, CONF_username);
- gppi(sesskey, "UserNameFromEnvironment", 0, conf, CONF_username_from_env);
- gpps(sesskey, "LocalUserName", "", conf, CONF_localusername);
- gppi(sesskey, "NoPTY", 0, conf, CONF_nopty);
- gppi(sesskey, "Compression", 0, conf, CONF_compression);
- gppi(sesskey, "TryAgent", 1, conf, CONF_tryagent);
- gppi(sesskey, "AgentFwd", 0, conf, CONF_agentfwd);
- gppi(sesskey, "ChangeUsername", 0, conf, CONF_change_username);
- gppi(sesskey, "GssapiFwd", 0, conf, CONF_gssapifwd);
- gprefs(sesskey, "Cipher", "\0",
- ciphernames, CIPHER_MAX, conf, CONF_ssh_cipherlist);
- {
- /* Backward-compatibility: we used to have an option to
- * 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;
- i = 2 - gppi_raw(sesskey, "BugDHGEx2", 0);
- if (i == FORCE_ON)
- default_kexes = "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";
- gprefs(sesskey, "KEX", default_kexes,
- kexnames, KEX_MAX, conf, CONF_ssh_kexlist);
- }
- gppi(sesskey, "RekeyTime", 60, conf, CONF_ssh_rekey_time);
- gpps(sesskey, "RekeyBytes", "1G", conf, CONF_ssh_rekey_data);
- gppi(sesskey, "SshProt", 2, conf, CONF_sshprot);
- gpps(sesskey, "LogHost", "", conf, CONF_loghost);
- gppi(sesskey, "SSH2DES", 0, conf, CONF_ssh2_des_cbc);
- gppi(sesskey, "SshNoAuth", 0, conf, CONF_ssh_no_userauth);
- gppi(sesskey, "SshBanner", 1, conf, CONF_ssh_show_banner);
- gppi(sesskey, "AuthTIS", 0, conf, CONF_try_tis_auth);
- gppi(sesskey, "AuthKI", 1, conf, CONF_try_ki_auth);
- gppi(sesskey, "AuthGSSAPI", 1, conf, CONF_try_gssapi_auth);
-#ifndef NO_GSSAPI
- gprefs(sesskey, "GSSLibs", "\0",
- gsslibkeywords, ngsslibs, conf, CONF_ssh_gsslist);
- gppfile(sesskey, "GSSCustom", conf, CONF_ssh_gss_custom);
-#endif
- gppi(sesskey, "SshNoShell", 0, conf, CONF_ssh_no_shell);
- gppfile(sesskey, "PublicKeyFile", conf, CONF_keyfile);
- gpps(sesskey, "RemoteCommand", "", conf, CONF_remote_cmd);
- gppi(sesskey, "RFCEnviron", 0, conf, CONF_rfc_environ);
- gppi(sesskey, "PassiveTelnet", 0, conf, CONF_passive_telnet);
- gppi(sesskey, "BackspaceIsDelete", 1, conf, CONF_bksp_is_delete);
- gppi(sesskey, "RXVTHomeEnd", 0, conf, CONF_rxvt_homeend);
- gppi(sesskey, "LinuxFunctionKeys", 0, conf, CONF_funky_type);
- gppi(sesskey, "NoApplicationKeys", 0, conf, CONF_no_applic_k);
- gppi(sesskey, "NoApplicationCursors", 0, conf, CONF_no_applic_c);
- gppi(sesskey, "NoMouseReporting", 0, conf, CONF_no_mouse_rep);
- gppi(sesskey, "NoRemoteResize", 0, conf, CONF_no_remote_resize);
- gppi(sesskey, "NoAltScreen", 0, conf, CONF_no_alt_screen);
- gppi(sesskey, "NoRemoteWinTitle", 0, conf, CONF_no_remote_wintitle);
- {
- /* Backward compatibility */
- int no_remote_qtitle = gppi_raw(sesskey, "NoRemoteQTitle", 1);
- /* We deliberately interpret the old setting of "no response" as
- * "empty string". This changes the behaviour, but hopefully for
- * the better; the user can always recover the old behaviour. */
- gppi(sesskey, "RemoteQTitleAction",
- no_remote_qtitle ? TITLE_EMPTY : TITLE_REAL,
- conf, CONF_remote_qtitle_action);
- }
- gppi(sesskey, "NoDBackspace", 0, conf, CONF_no_dbackspace);
- gppi(sesskey, "NoRemoteCharset", 0, conf, CONF_no_remote_charset);
- gppi(sesskey, "ApplicationCursorKeys", 0, conf, CONF_app_cursor);
- gppi(sesskey, "ApplicationKeypad", 0, conf, CONF_app_keypad);
- gppi(sesskey, "NetHackKeypad", 0, conf, CONF_nethack_keypad);
- gppi(sesskey, "AltF4", 1, conf, CONF_alt_f4);
- gppi(sesskey, "AltSpace", 0, conf, CONF_alt_space);
- gppi(sesskey, "AltOnly", 0, conf, CONF_alt_only);
- gppi(sesskey, "ComposeKey", 0, conf, CONF_compose_key);
- gppi(sesskey, "CtrlAltKeys", 1, conf, CONF_ctrlaltkeys);
- gppi(sesskey, "TelnetKey", 0, conf, CONF_telnet_keyboard);
- gppi(sesskey, "TelnetRet", 1, conf, CONF_telnet_newline);
- gppi(sesskey, "LocalEcho", AUTO, conf, CONF_localecho);
- gppi(sesskey, "LocalEdit", AUTO, conf, CONF_localedit);
- gpps(sesskey, "Answerback", "PuTTY", conf, CONF_answerback);
- gppi(sesskey, "AlwaysOnTop", 0, conf, CONF_alwaysontop);
- gppi(sesskey, "FullScreenOnAltEnter", 0, conf, CONF_fullscreenonaltenter);
- gppi(sesskey, "HideMousePtr", 0, conf, CONF_hide_mouseptr);
- gppi(sesskey, "SunkenEdge", 0, conf, CONF_sunken_edge);
- gppi(sesskey, "WindowBorder", 1, conf, CONF_window_border);
- gppi(sesskey, "CurType", 0, conf, CONF_cursor_type);
- gppi(sesskey, "BlinkCur", 0, conf, CONF_blink_cur);
- /* pedantic compiler tells me I can't use conf, CONF_beep as an int * :-) */
- gppi(sesskey, "Beep", 1, conf, CONF_beep);
- gppi(sesskey, "BeepInd", 0, conf, CONF_beep_ind);
- gppfile(sesskey, "BellWaveFile", conf, CONF_bell_wavefile);
- gppi(sesskey, "BellOverload", 1, conf, CONF_bellovl);
- gppi(sesskey, "BellOverloadN", 5, conf, CONF_bellovl_n);
- i = gppi_raw(sesskey, "BellOverloadT", 2*TICKSPERSEC
-#ifdef PUTTY_UNIX_H
- *1000
-#endif
- );
- conf_set_int(conf, CONF_bellovl_t, i
-#ifdef PUTTY_UNIX_H
- / 1000
-#endif
- );
- i = gppi_raw(sesskey, "BellOverloadS", 5*TICKSPERSEC
-#ifdef PUTTY_UNIX_H
- *1000
-#endif
- );
- conf_set_int(conf, CONF_bellovl_s, i
-#ifdef PUTTY_UNIX_H
- / 1000
-#endif
- );
- gppi(sesskey, "ScrollbackLines", 200, conf, CONF_savelines);
- gppi(sesskey, "DECOriginMode", 0, conf, CONF_dec_om);
- gppi(sesskey, "AutoWrapMode", 1, conf, CONF_wrap_mode);
- gppi(sesskey, "LFImpliesCR", 0, conf, CONF_lfhascr);
- gppi(sesskey, "CRImpliesLF", 0, conf, CONF_crhaslf);
- gppi(sesskey, "DisableArabicShaping", 0, conf, CONF_arabicshaping);
- gppi(sesskey, "DisableBidi", 0, conf, CONF_bidi);
- gppi(sesskey, "WinNameAlways", 1, conf, CONF_win_name_always);
- gpps(sesskey, "WinTitle", "", conf, CONF_wintitle);
- gppi(sesskey, "TermWidth", 80, conf, CONF_width);
- gppi(sesskey, "TermHeight", 24, conf, CONF_height);
- gppfont(sesskey, "Font", conf, CONF_font);
- gppi(sesskey, "FontQuality", FQ_DEFAULT, conf, CONF_font_quality);
- gppi(sesskey, "FontVTMode", VT_UNICODE, conf, CONF_vtmode);
- gppi(sesskey, "UseSystemColours", 0, conf, CONF_system_colour);
- gppi(sesskey, "TryPalette", 0, conf, CONF_try_palette);
- gppi(sesskey, "ANSIColour", 1, conf, CONF_ansi_colour);
- gppi(sesskey, "Xterm256Colour", 1, conf, CONF_xterm_256_colour);
- gppi(sesskey, "BoldAsColour", 1, conf, CONF_bold_colour);
-
- for (i = 0; i < 22; i++) {
- static const char *const defaults[] = {
- "187,187,187", "255,255,255", "0,0,0", "85,85,85", "0,0,0",
- "0,255,0", "0,0,0", "85,85,85", "187,0,0", "255,85,85",
- "0,187,0", "85,255,85", "187,187,0", "255,255,85", "0,0,187",
- "85,85,255", "187,0,187", "255,85,255", "0,187,187",
- "85,255,255", "187,187,187", "255,255,255"
- };
- char buf[20], *buf2;
- int c0, c1, c2;
- sprintf(buf, "Colour%d", i);
- buf2 = gpps_raw(sesskey, buf, defaults[i]);
- if (sscanf(buf2, "%d,%d,%d", &c0, &c1, &c2) == 3) {
- conf_set_int_int(conf, CONF_colours, i*3+0, c0);
- conf_set_int_int(conf, CONF_colours, i*3+1, c1);
- conf_set_int_int(conf, CONF_colours, i*3+2, c2);
- }
- sfree(buf2);
- }
- gppi(sesskey, "RawCNP", 0, conf, CONF_rawcnp);
- gppi(sesskey, "PasteRTF", 0, conf, CONF_rtf_paste);
- gppi(sesskey, "MouseIsXterm", 0, conf, CONF_mouse_is_xterm);
- gppi(sesskey, "RectSelect", 0, conf, CONF_rect_select);
- gppi(sesskey, "MouseOverride", 1, conf, CONF_mouse_override);
- for (i = 0; i < 256; i += 32) {
- static const char *const defaults[] = {
- "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
- "0,1,2,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1",
- "1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,2",
- "1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1",
- "1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1",
- "1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1",
- "2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2",
- "2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2"
- };
- char buf[20], *buf2, *p;
- int j;
- sprintf(buf, "Wordness%d", i);
- buf2 = gpps_raw(sesskey, buf, defaults[i / 32]);
- p = buf2;
- for (j = i; j < i + 32; j++) {
- char *q = p;
- while (*p && *p != ',')
- p++;
- if (*p == ',')
- *p++ = '\0';
- conf_set_int_int(conf, CONF_wordness, j, atoi(q));
- }
- sfree(buf2);
- }
- /*
- * The empty default for LineCodePage will be converted later
- * into a plausible default for the locale.
- */
- gpps(sesskey, "LineCodePage", "", conf, CONF_line_codepage);
- gppi(sesskey, "CJKAmbigWide", 0, conf, CONF_cjk_ambig_wide);
- gppi(sesskey, "UTF8Override", 1, conf, CONF_utf8_override);
- gpps(sesskey, "Printer", "", conf, CONF_printer);
- gppi(sesskey, "CapsLockCyr", 0, conf, CONF_xlat_capslockcyr);
- gppi(sesskey, "ScrollBar", 1, conf, CONF_scrollbar);
- gppi(sesskey, "ScrollBarFullScreen", 0, conf, CONF_scrollbar_in_fullscreen);
- gppi(sesskey, "ScrollOnKey", 0, conf, CONF_scroll_on_key);
- gppi(sesskey, "ScrollOnDisp", 1, conf, CONF_scroll_on_disp);
- gppi(sesskey, "EraseToScrollback", 1, conf, CONF_erase_to_scrollback);
- gppi(sesskey, "LockSize", 0, conf, CONF_resize_action);
- gppi(sesskey, "BCE", 1, conf, CONF_bce);
- gppi(sesskey, "BlinkText", 0, conf, CONF_blinktext);
- gppi(sesskey, "X11Forward", 0, conf, CONF_x11_forward);
- gpps(sesskey, "X11Display", "", conf, CONF_x11_display);
- gppi(sesskey, "X11AuthType", X11_MIT, conf, CONF_x11_auth);
- gppfile(sesskey, "X11AuthFile", conf, CONF_xauthfile);
-
- gppi(sesskey, "LocalPortAcceptAll", 0, conf, CONF_lport_acceptall);
- gppi(sesskey, "RemotePortAcceptAll", 0, conf, CONF_rport_acceptall);
- gppmap(sesskey, "PortForwardings", conf, CONF_portfwd);
- i = gppi_raw(sesskey, "BugIgnore1", 0); conf_set_int(conf, CONF_sshbug_ignore1, 2-i);
- i = gppi_raw(sesskey, "BugPlainPW1", 0); conf_set_int(conf, CONF_sshbug_plainpw1, 2-i);
- i = gppi_raw(sesskey, "BugRSA1", 0); conf_set_int(conf, CONF_sshbug_rsa1, 2-i);
- i = gppi_raw(sesskey, "BugIgnore2", 0); conf_set_int(conf, CONF_sshbug_ignore2, 2-i);
- {
- int i;
- i = gppi_raw(sesskey, "BugHMAC2", 0); conf_set_int(conf, CONF_sshbug_hmac2, 2-i);
- if (2-i == AUTO) {
- i = gppi_raw(sesskey, "BuggyMAC", 0);
- if (i == 1)
- conf_set_int(conf, CONF_sshbug_hmac2, FORCE_ON);
- }
- }
- i = gppi_raw(sesskey, "BugDeriveKey2", 0); conf_set_int(conf, CONF_sshbug_derivekey2, 2-i);
- i = gppi_raw(sesskey, "BugRSAPad2", 0); conf_set_int(conf, CONF_sshbug_rsapad2, 2-i);
- i = gppi_raw(sesskey, "BugPKSessID2", 0); conf_set_int(conf, CONF_sshbug_pksessid2, 2-i);
- i = gppi_raw(sesskey, "BugRekey2", 0); conf_set_int(conf, CONF_sshbug_rekey2, 2-i);
- i = gppi_raw(sesskey, "BugMaxPkt2", 0); conf_set_int(conf, CONF_sshbug_maxpkt2, 2-i);
- conf_set_int(conf, CONF_ssh_simple, FALSE);
- gppi(sesskey, "StampUtmp", 1, conf, CONF_stamp_utmp);
- gppi(sesskey, "LoginShell", 1, conf, CONF_login_shell);
- gppi(sesskey, "ScrollbarOnLeft", 0, conf, CONF_scrollbar_on_left);
- gppi(sesskey, "ShadowBold", 0, conf, CONF_shadowbold);
- gppfont(sesskey, "BoldFont", conf, CONF_boldfont);
- gppfont(sesskey, "WideFont", conf, CONF_widefont);
- gppfont(sesskey, "WideBoldFont", conf, CONF_wideboldfont);
- gppi(sesskey, "ShadowBoldOffset", 1, conf, CONF_shadowboldoffset);
- gpps(sesskey, "SerialLine", "", conf, CONF_serline);
- gppi(sesskey, "SerialSpeed", 9600, conf, CONF_serspeed);
- gppi(sesskey, "SerialDataBits", 8, conf, CONF_serdatabits);
- gppi(sesskey, "SerialStopHalfbits", 2, conf, CONF_serstopbits);
- gppi(sesskey, "SerialParity", SER_PAR_NONE, conf, CONF_serparity);
- gppi(sesskey, "SerialFlowControl", SER_FLOW_XONXOFF, conf, CONF_serflow);
- gpps(sesskey, "WindowClass", "", conf, CONF_winclass);
-}
-
-void do_defaults(char *session, Conf *conf)
-{
- load_settings(session, conf);
-}
-
-static int sessioncmp(const void *av, const void *bv)
-{
- const char *a = *(const char *const *) av;
- const char *b = *(const char *const *) bv;
-
- /*
- * Alphabetical order, except that "Default Settings" is a
- * special case and comes first.
- */
- if (!strcmp(a, "Default Settings"))
- return -1; /* a comes first */
- if (!strcmp(b, "Default Settings"))
- return +1; /* b comes first */
- /*
- * FIXME: perhaps we should ignore the first & in determining
- * sort order.
- */
- return strcmp(a, b); /* otherwise, compare normally */
-}
-
-void get_sesslist(struct sesslist *list, int allocate)
-{
- char otherbuf[2048];
- int buflen, bufsize, i;
- char *p, *ret;
- void *handle;
-
- if (allocate) {
-
- buflen = bufsize = 0;
- list->buffer = NULL;
- if ((handle = enum_settings_start()) != NULL) {
- do {
- ret = enum_settings_next(handle, otherbuf, sizeof(otherbuf));
- if (ret) {
- int len = strlen(otherbuf) + 1;
- if (bufsize < buflen + len) {
- bufsize = buflen + len + 2048;
- list->buffer = sresize(list->buffer, bufsize, char);
- }
- strcpy(list->buffer + buflen, otherbuf);
- buflen += strlen(list->buffer + buflen) + 1;
- }
- } while (ret);
- enum_settings_finish(handle);
- }
- list->buffer = sresize(list->buffer, buflen + 1, char);
- list->buffer[buflen] = '\0';
-
- /*
- * Now set up the list of sessions. Note that "Default
- * Settings" must always be claimed to exist, even if it
- * doesn't really.
- */
-
- p = list->buffer;
- list->nsessions = 1; /* "Default Settings" counts as one */
- while (*p) {
- if (strcmp(p, "Default Settings"))
- list->nsessions++;
- while (*p)
- p++;
- p++;
- }
-
- list->sessions = snewn(list->nsessions + 1, char *);
- list->sessions[0] = "Default Settings";
- p = list->buffer;
- i = 1;
- while (*p) {
- if (strcmp(p, "Default Settings"))
- list->sessions[i++] = p;
- while (*p)
- p++;
- p++;
- }
-
- qsort(list->sessions, i, sizeof(char *), sessioncmp);
- } else {
- sfree(list->buffer);
- sfree(list->sessions);
- list->buffer = NULL;
- list->sessions = NULL;
- }
-}
+/*
+ * settings.c: read and write saved sessions. (platform-independent)
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "putty.h"
+#include "storage.h"
+
+/* The cipher order given here is the default order. */
+static const struct keyvalwhere ciphernames[] = {
+ { "aes", CIPHER_AES, -1, -1 },
+ { "blowfish", CIPHER_BLOWFISH, -1, -1 },
+ { "3des", CIPHER_3DES, -1, -1 },
+ { "WARN", CIPHER_WARN, -1, -1 },
+ { "arcfour", CIPHER_ARCFOUR, -1, -1 },
+ { "des", CIPHER_DES, -1, -1 }
+};
+
+static const struct keyvalwhere kexnames[] = {
+ { "dh-gex-sha1", KEX_DHGEX, -1, -1 },
+ { "dh-group14-sha1", KEX_DHGROUP14, -1, -1 },
+ { "dh-group1-sha1", KEX_DHGROUP1, -1, -1 },
+ { "rsa", KEX_RSA, KEX_WARN, -1 },
+ { "WARN", KEX_WARN, -1, -1 }
+};
+
+/*
+ * All the terminal modes that we know about for the "TerminalModes"
+ * setting. (Also used by config.c for the drop-down list.)
+ * This is currently precisely the same as the set in ssh.c, but could
+ * in principle differ if other backends started to support tty modes
+ * (e.g., the pty backend).
+ */
+const char *const ttymodes[] = {
+ "INTR", "QUIT", "ERASE", "KILL", "EOF",
+ "EOL", "EOL2", "START", "STOP", "SUSP",
+ "DSUSP", "REPRINT", "WERASE", "LNEXT", "FLUSH",
+ "SWTCH", "STATUS", "DISCARD", "IGNPAR", "PARMRK",
+ "INPCK", "ISTRIP", "INLCR", "IGNCR", "ICRNL",
+ "IUCLC", "IXON", "IXANY", "IXOFF", "IMAXBEL",
+ "ISIG", "ICANON", "XCASE", "ECHO", "ECHOE",
+ "ECHOK", "ECHONL", "NOFLSH", "TOSTOP", "IEXTEN",
+ "ECHOCTL", "ECHOKE", "PENDIN", "OPOST", "OLCUC",
+ "ONLCR", "OCRNL", "ONOCR", "ONLRET", "CS7",
+ "CS8", "PARENB", "PARODD", NULL
+};
+
+/*
+ * Convenience functions to access the backends[] array
+ * (which is only present in tools that manage settings).
+ */
+
+Backend *backend_from_name(const char *name)
+{
+ Backend **p;
+ for (p = backends; *p != NULL; p++)
+ if (!strcmp((*p)->name, name))
+ return *p;
+ return NULL;
+}
+
+Backend *backend_from_proto(int proto)
+{
+ Backend **p;
+ for (p = backends; *p != NULL; p++)
+ if ((*p)->protocol == proto)
+ return *p;
+ return NULL;
+}
+
+char *get_remote_username(Conf *conf)
+{
+ char *username = conf_get_str(conf, CONF_username);
+ if (*username) {
+ return dupstr(username);
+ } else if (conf_get_int(conf, CONF_username_from_env)) {
+ /* Use local username. */
+ return get_username(); /* might still be NULL */
+ } else {
+ return NULL;
+ }
+}
+
+static char *gpps_raw(void *handle, const char *name, const char *def)
+{
+ char *ret = read_setting_s(handle, name);
+ if (!ret)
+ ret = platform_default_s(name);
+ if (!ret)
+ ret = def ? dupstr(def) : NULL; /* permit NULL as final fallback */
+ return ret;
+}
+
+static void gpps(void *handle, const char *name, const char *def,
+ Conf *conf, int primary)
+{
+ char *val = gpps_raw(handle, name, def);
+ conf_set_str(conf, primary, val);
+ sfree(val);
+}
+
+/*
+ * gppfont and gppfile cannot have local defaults, since the very
+ * format of a Filename or FontSpec is platform-dependent. So the
+ * platform-dependent functions MUST return some sort of value.
+ */
+static void gppfont(void *handle, const char *name, Conf *conf, int primary)
+{
+ FontSpec *result = read_setting_fontspec(handle, name);
+ if (!result)
+ result = platform_default_fontspec(name);
+ conf_set_fontspec(conf, primary, result);
+ fontspec_free(result);
+}
+static void gppfile(void *handle, const char *name, Conf *conf, int primary)
+{
+ Filename *result = read_setting_filename(handle, name);
+ if (!result)
+ result = platform_default_filename(name);
+ conf_set_filename(conf, primary, result);
+ filename_free(result);
+}
+
+static int gppi_raw(void *handle, 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)
+{
+ conf_set_int(conf, primary, gppi_raw(handle, name, def));
+}
+
+/*
+ * Read a set of name-value pairs in the format we occasionally use:
+ * NAME\tVALUE\0NAME\tVALUE\0\0 in memory
+ * NAME=VALUE,NAME=VALUE, in storage
+ * `def' is in the storage format.
+ */
+static int gppmap(void *handle, char *name, Conf *conf, int primary)
+{
+ char *buf, *p, *q, *key, *val;
+
+ /*
+ * Start by clearing any existing subkeys of this key from conf.
+ */
+ while ((key = conf_get_str_nthstrkey(conf, primary, 0)) != NULL)
+ conf_del_str_str(conf, primary, key);
+
+ /*
+ * Now read a serialised list from the settings and unmarshal it
+ * into its components.
+ */
+ buf = gpps_raw(handle, name, NULL);
+ if (!buf)
+ return FALSE;
+
+ p = buf;
+ while (*p) {
+ q = buf;
+ val = NULL;
+ while (*p && *p != ',') {
+ int c = *p++;
+ if (c == '=')
+ c = '\0';
+ if (c == '\\')
+ c = *p++;
+ *q++ = c;
+ if (!c)
+ val = q;
+ }
+ if (*p == ',')
+ p++;
+ if (!val)
+ val = q;
+ *q = '\0';
+
+ if (primary == CONF_portfwd && strchr(buf, 'D') != NULL) {
+ /*
+ * Backwards-compatibility hack: dynamic forwardings are
+ * indexed in the data store as a third type letter in the
+ * key, 'D' alongside 'L' and 'R' - but really, they
+ * should be filed under 'L' with a special _value_,
+ * because local and dynamic forwardings both involve
+ * _listening_ on a local port, and are hence mutually
+ * exclusive on the same port number. So here we translate
+ * the legacy storage format into the sensible internal
+ * form, by finding the D and turning it into a L.
+ */
+ char *newkey = dupstr(buf);
+ *strchr(newkey, 'D') = 'L';
+ conf_set_str_str(conf, primary, newkey, "D");
+ sfree(newkey);
+ } else {
+ conf_set_str_str(conf, primary, buf, val);
+ }
+ }
+ sfree(buf);
+
+ return TRUE;
+}
+
+/*
+ * Write a set of name/value pairs in the above format.
+ */
+static void wmap(void *handle, char const *outkey, Conf *conf, int primary)
+{
+ char *buf, *p, *q, *key, *realkey, *val;
+ int len;
+
+ len = 1; /* allow for NUL */
+
+ for (val = conf_get_str_strs(conf, primary, NULL, &key);
+ val != NULL;
+ val = conf_get_str_strs(conf, primary, key, &key))
+ len += 2 + 2 * (strlen(key) + strlen(val)); /* allow for escaping */
+
+ buf = snewn(len, char);
+ p = buf;
+
+ for (val = conf_get_str_strs(conf, primary, NULL, &key);
+ val != NULL;
+ val = conf_get_str_strs(conf, primary, key, &key)) {
+
+ if (primary == CONF_portfwd && !strcmp(val, "D")) {
+ /*
+ * Backwards-compatibility hack, as above: translate from
+ * the sensible internal representation of dynamic
+ * forwardings (key "L<port>", value "D") to the
+ * conceptually incoherent legacy storage format (key
+ * "D<port>", value empty).
+ */
+ char *L;
+
+ realkey = key; /* restore it at end of loop */
+ val = "";
+ key = dupstr(key);
+ L = strchr(key, 'L');
+ if (L) *L = 'D';
+ } else {
+ realkey = NULL;
+ }
+
+ if (p != buf)
+ *p++ = ',';
+ for (q = key; *q; q++) {
+ if (*q == '=' || *q == ',' || *q == '\\')
+ *p++ = '\\';
+ *p++ = *q;
+ }
+ *p++ = '=';
+ for (q = val; *q; q++) {
+ if (*q == '=' || *q == ',' || *q == '\\')
+ *p++ = '\\';
+ *p++ = *q;
+ }
+
+ if (realkey) {
+ free(key);
+ key = realkey;
+ }
+ }
+ *p = '\0';
+ write_setting_s(handle, outkey, buf);
+ sfree(buf);
+}
+
+static int key2val(const struct keyvalwhere *mapping,
+ int nmaps, char *key)
+{
+ int i;
+ for (i = 0; i < nmaps; i++)
+ if (!strcmp(mapping[i].s, key)) return mapping[i].v;
+ return -1;
+}
+
+static const char *val2key(const struct keyvalwhere *mapping,
+ int nmaps, int val)
+{
+ int i;
+ for (i = 0; i < nmaps; i++)
+ if (mapping[i].v == val) return mapping[i].s;
+ return NULL;
+}
+
+/*
+ * Helper function to parse a comma-separated list of strings into
+ * a preference list array of values. Any missing values are added
+ * 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,
+ const struct keyvalwhere *mapping, int nvals,
+ Conf *conf, int primary)
+{
+ char *commalist;
+ char *p, *q;
+ int i, j, n, v, pos;
+ unsigned long seen = 0; /* bitmap for weeding dups etc */
+
+ /*
+ * Fetch the string which we'll parse as a comma-separated list.
+ */
+ commalist = gpps_raw(sesskey, name, def);
+
+ /*
+ * Go through that list and convert it into values.
+ */
+ n = 0;
+ p = commalist;
+ while (1) {
+ while (*p && *p == ',') p++;
+ if (!*p)
+ break; /* no more words */
+
+ q = p;
+ while (*p && *p != ',') p++;
+ if (*p) *p++ = '\0';
+
+ v = key2val(mapping, nvals, q);
+ if (v != -1 && !(seen & (1 << v))) {
+ seen |= (1 << v);
+ conf_set_int_int(conf, primary, n, v);
+ n++;
+ }
+ }
+
+ sfree(commalist);
+
+ /*
+ * Now go through 'mapping' and add values that weren't mentioned
+ * in the list we fetched. We may have to loop over it multiple
+ * times so that we add values before other values whose default
+ * positions depend on them.
+ */
+ while (n < nvals) {
+ for (i = 0; i < nvals; i++) {
+ assert(mapping[i].v < 32);
+
+ if (!(seen & (1 << mapping[i].v))) {
+ /*
+ * This element needs adding. But can we add it yet?
+ */
+ if (mapping[i].vrel != -1 && !(seen & (1 << mapping[i].vrel)))
+ continue; /* nope */
+
+ /*
+ * OK, we can work out where to add this element, so
+ * do so.
+ */
+ if (mapping[i].vrel == -1) {
+ pos = (mapping[i].where < 0 ? n : 0);
+ } else {
+ for (j = 0; j < n; j++)
+ if (conf_get_int_int(conf, primary, j) ==
+ mapping[i].vrel)
+ break;
+ assert(j < n); /* implied by (seen & (1<<vrel)) */
+ pos = (mapping[i].where < 0 ? j : j+1);
+ }
+
+ /*
+ * And add it.
+ */
+ for (j = n-1; j >= pos; j--)
+ conf_set_int_int(conf, primary, j+1,
+ conf_get_int_int(conf, primary, j));
+ conf_set_int_int(conf, primary, pos, mapping[i].v);
+ n++;
+ }
+ }
+ }
+}
+
+/*
+ * Write out a preference list.
+ */
+static void wprefs(void *sesskey, char *name,
+ const struct keyvalwhere *mapping, int nvals,
+ Conf *conf, int primary)
+{
+ char *buf, *p;
+ int i, maxlen;
+
+ for (maxlen = i = 0; i < nvals; i++) {
+ const char *s = val2key(mapping, nvals,
+ conf_get_int_int(conf, primary, i));
+ if (s) {
+ maxlen += (maxlen > 0 ? 1 : 0) + strlen(s);
+ }
+ }
+
+ buf = snewn(maxlen + 1, char);
+ p = buf;
+
+ for (i = 0; i < nvals; i++) {
+ const char *s = val2key(mapping, nvals,
+ conf_get_int_int(conf, primary, i));
+ if (s) {
+ p += sprintf(p, "%s%s", (p > buf ? "," : ""), s);
+ }
+ }
+
+ assert(p - buf == maxlen);
+ *p = '\0';
+
+ write_setting_s(sesskey, name, buf);
+
+ sfree(buf);
+}
+
+char *save_settings(char *section, Conf *conf)
+{
+ void *sesskey;
+ char *errmsg;
+
+ sesskey = open_settings_w(section, &errmsg);
+ if (!sesskey)
+ return errmsg;
+ save_open_settings(sesskey, conf);
+ close_settings_w(sesskey);
+ return NULL;
+}
+
+void save_open_settings(void *sesskey, Conf *conf)
+{
+ int i;
+ char *p;
+
+ write_setting_i(sesskey, "Present", 1);
+ write_setting_s(sesskey, "HostName", conf_get_str(conf, CONF_host));
+ write_setting_filename(sesskey, "LogFileName", conf_get_filename(conf, CONF_logfilename));
+ write_setting_i(sesskey, "LogType", conf_get_int(conf, CONF_logtype));
+ write_setting_i(sesskey, "LogFileClash", conf_get_int(conf, CONF_logxfovr));
+ write_setting_i(sesskey, "LogFlush", conf_get_int(conf, CONF_logflush));
+ write_setting_i(sesskey, "SSHLogOmitPasswords", conf_get_int(conf, CONF_logomitpass));
+ write_setting_i(sesskey, "SSHLogOmitData", conf_get_int(conf, CONF_logomitdata));
+ p = "raw";
+ {
+ const Backend *b = backend_from_proto(conf_get_int(conf, CONF_protocol));
+ if (b)
+ p = b->name;
+ }
+ write_setting_s(sesskey, "Protocol", p);
+ write_setting_i(sesskey, "PortNumber", conf_get_int(conf, CONF_port));
+ /* The CloseOnExit numbers are arranged in a different order from
+ * the standard FORCE_ON / FORCE_OFF / AUTO. */
+ write_setting_i(sesskey, "CloseOnExit", (conf_get_int(conf, CONF_close_on_exit)+2)%3);
+ write_setting_i(sesskey, "WarnOnClose", !!conf_get_int(conf, CONF_warn_on_close));
+ write_setting_i(sesskey, "PingInterval", conf_get_int(conf, CONF_ping_interval) / 60); /* minutes */
+ write_setting_i(sesskey, "PingIntervalSecs", conf_get_int(conf, CONF_ping_interval) % 60); /* seconds */
+ write_setting_i(sesskey, "TCPNoDelay", conf_get_int(conf, CONF_tcp_nodelay));
+ write_setting_i(sesskey, "TCPKeepalives", conf_get_int(conf, CONF_tcp_keepalives));
+ write_setting_s(sesskey, "TerminalType", conf_get_str(conf, CONF_termtype));
+ write_setting_s(sesskey, "TerminalSpeed", conf_get_str(conf, CONF_termspeed));
+ wmap(sesskey, "TerminalModes", conf, CONF_ttymodes);
+
+ /* Address family selection */
+ write_setting_i(sesskey, "AddressFamily", conf_get_int(conf, CONF_addressfamily));
+
+ /* proxy settings */
+ write_setting_s(sesskey, "ProxyExcludeList", conf_get_str(conf, CONF_proxy_exclude_list));
+ write_setting_i(sesskey, "ProxyDNS", (conf_get_int(conf, CONF_proxy_dns)+2)%3);
+ write_setting_i(sesskey, "ProxyLocalhost", conf_get_int(conf, CONF_even_proxy_localhost));
+ write_setting_i(sesskey, "ProxyMethod", conf_get_int(conf, CONF_proxy_type));
+ write_setting_s(sesskey, "ProxyHost", conf_get_str(conf, CONF_proxy_host));
+ write_setting_i(sesskey, "ProxyPort", conf_get_int(conf, CONF_proxy_port));
+ 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));
+ wmap(sesskey, "Environment", conf, CONF_environmt);
+ write_setting_s(sesskey, "UserName", conf_get_str(conf, CONF_username));
+ write_setting_i(sesskey, "UserNameFromEnvironment", conf_get_int(conf, CONF_username_from_env));
+ write_setting_s(sesskey, "LocalUserName", conf_get_str(conf, CONF_localusername));
+ write_setting_i(sesskey, "NoPTY", conf_get_int(conf, CONF_nopty));
+ write_setting_i(sesskey, "Compression", conf_get_int(conf, CONF_compression));
+ write_setting_i(sesskey, "TryAgent", conf_get_int(conf, CONF_tryagent));
+ write_setting_i(sesskey, "AgentFwd", conf_get_int(conf, CONF_agentfwd));
+ write_setting_i(sesskey, "GssapiFwd", conf_get_int(conf, CONF_gssapifwd));
+ write_setting_i(sesskey, "ChangeUsername", conf_get_int(conf, CONF_change_username));
+ wprefs(sesskey, "Cipher", ciphernames, CIPHER_MAX, conf, CONF_ssh_cipherlist);
+ wprefs(sesskey, "KEX", kexnames, KEX_MAX, conf, CONF_ssh_kexlist);
+ write_setting_i(sesskey, "RekeyTime", conf_get_int(conf, CONF_ssh_rekey_time));
+ write_setting_s(sesskey, "RekeyBytes", conf_get_str(conf, CONF_ssh_rekey_data));
+ write_setting_i(sesskey, "SshNoAuth", conf_get_int(conf, CONF_ssh_no_userauth));
+ write_setting_i(sesskey, "SshBanner", conf_get_int(conf, CONF_ssh_show_banner));
+ write_setting_i(sesskey, "AuthTIS", conf_get_int(conf, CONF_try_tis_auth));
+ write_setting_i(sesskey, "AuthKI", conf_get_int(conf, CONF_try_ki_auth));
+ write_setting_i(sesskey, "AuthGSSAPI", conf_get_int(conf, CONF_try_gssapi_auth));
+#ifndef NO_GSSAPI
+ wprefs(sesskey, "GSSLibs", gsslibkeywords, ngsslibs, conf, CONF_ssh_gsslist);
+ write_setting_filename(sesskey, "GSSCustom", conf_get_filename(conf, CONF_ssh_gss_custom));
+#endif
+ write_setting_i(sesskey, "SshNoShell", conf_get_int(conf, CONF_ssh_no_shell));
+ write_setting_i(sesskey, "SshProt", conf_get_int(conf, CONF_sshprot));
+ write_setting_s(sesskey, "LogHost", conf_get_str(conf, CONF_loghost));
+ write_setting_i(sesskey, "SSH2DES", conf_get_int(conf, CONF_ssh2_des_cbc));
+ write_setting_filename(sesskey, "PublicKeyFile", conf_get_filename(conf, CONF_keyfile));
+ write_setting_s(sesskey, "RemoteCommand", conf_get_str(conf, CONF_remote_cmd));
+ write_setting_i(sesskey, "RFCEnviron", conf_get_int(conf, CONF_rfc_environ));
+ write_setting_i(sesskey, "PassiveTelnet", conf_get_int(conf, CONF_passive_telnet));
+ write_setting_i(sesskey, "BackspaceIsDelete", conf_get_int(conf, CONF_bksp_is_delete));
+ write_setting_i(sesskey, "RXVTHomeEnd", conf_get_int(conf, CONF_rxvt_homeend));
+ write_setting_i(sesskey, "LinuxFunctionKeys", conf_get_int(conf, CONF_funky_type));
+ write_setting_i(sesskey, "NoApplicationKeys", conf_get_int(conf, CONF_no_applic_k));
+ write_setting_i(sesskey, "NoApplicationCursors", conf_get_int(conf, CONF_no_applic_c));
+ write_setting_i(sesskey, "NoMouseReporting", conf_get_int(conf, CONF_no_mouse_rep));
+ write_setting_i(sesskey, "NoRemoteResize", conf_get_int(conf, CONF_no_remote_resize));
+ write_setting_i(sesskey, "NoAltScreen", conf_get_int(conf, CONF_no_alt_screen));
+ write_setting_i(sesskey, "NoRemoteWinTitle", conf_get_int(conf, CONF_no_remote_wintitle));
+ write_setting_i(sesskey, "RemoteQTitleAction", conf_get_int(conf, CONF_remote_qtitle_action));
+ write_setting_i(sesskey, "NoDBackspace", conf_get_int(conf, CONF_no_dbackspace));
+ write_setting_i(sesskey, "NoRemoteCharset", conf_get_int(conf, CONF_no_remote_charset));
+ write_setting_i(sesskey, "ApplicationCursorKeys", conf_get_int(conf, CONF_app_cursor));
+ write_setting_i(sesskey, "ApplicationKeypad", conf_get_int(conf, CONF_app_keypad));
+ write_setting_i(sesskey, "NetHackKeypad", conf_get_int(conf, CONF_nethack_keypad));
+ write_setting_i(sesskey, "AltF4", conf_get_int(conf, CONF_alt_f4));
+ write_setting_i(sesskey, "AltSpace", conf_get_int(conf, CONF_alt_space));
+ 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));
+ 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));
+ write_setting_i(sesskey, "LocalEdit", conf_get_int(conf, CONF_localedit));
+ write_setting_s(sesskey, "Answerback", conf_get_str(conf, CONF_answerback));
+ write_setting_i(sesskey, "AlwaysOnTop", conf_get_int(conf, CONF_alwaysontop));
+ write_setting_i(sesskey, "FullScreenOnAltEnter", conf_get_int(conf, CONF_fullscreenonaltenter));
+ write_setting_i(sesskey, "HideMousePtr", conf_get_int(conf, CONF_hide_mouseptr));
+ write_setting_i(sesskey, "SunkenEdge", conf_get_int(conf, CONF_sunken_edge));
+ write_setting_i(sesskey, "WindowBorder", conf_get_int(conf, CONF_window_border));
+ write_setting_i(sesskey, "CurType", conf_get_int(conf, CONF_cursor_type));
+ write_setting_i(sesskey, "BlinkCur", conf_get_int(conf, CONF_blink_cur));
+ write_setting_i(sesskey, "Beep", conf_get_int(conf, CONF_beep));
+ write_setting_i(sesskey, "BeepInd", conf_get_int(conf, CONF_beep_ind));
+ write_setting_filename(sesskey, "BellWaveFile", conf_get_filename(conf, CONF_bell_wavefile));
+ write_setting_i(sesskey, "BellOverload", conf_get_int(conf, CONF_bellovl));
+ write_setting_i(sesskey, "BellOverloadN", conf_get_int(conf, CONF_bellovl_n));
+ write_setting_i(sesskey, "BellOverloadT", conf_get_int(conf, CONF_bellovl_t)
+#ifdef PUTTY_UNIX_H
+ * 1000
+#endif
+ );
+ write_setting_i(sesskey, "BellOverloadS", conf_get_int(conf, CONF_bellovl_s)
+#ifdef PUTTY_UNIX_H
+ * 1000
+#endif
+ );
+ write_setting_i(sesskey, "ScrollbackLines", conf_get_int(conf, CONF_savelines));
+ write_setting_i(sesskey, "DECOriginMode", conf_get_int(conf, CONF_dec_om));
+ write_setting_i(sesskey, "AutoWrapMode", conf_get_int(conf, CONF_wrap_mode));
+ write_setting_i(sesskey, "LFImpliesCR", conf_get_int(conf, CONF_lfhascr));
+ write_setting_i(sesskey, "CRImpliesLF", conf_get_int(conf, CONF_crhaslf));
+ write_setting_i(sesskey, "DisableArabicShaping", conf_get_int(conf, CONF_arabicshaping));
+ write_setting_i(sesskey, "DisableBidi", conf_get_int(conf, CONF_bidi));
+ write_setting_i(sesskey, "WinNameAlways", conf_get_int(conf, CONF_win_name_always));
+ write_setting_s(sesskey, "WinTitle", conf_get_str(conf, CONF_wintitle));
+ write_setting_i(sesskey, "TermWidth", conf_get_int(conf, CONF_width));
+ write_setting_i(sesskey, "TermHeight", conf_get_int(conf, CONF_height));
+ write_setting_fontspec(sesskey, "Font", conf_get_fontspec(conf, CONF_font));
+ write_setting_i(sesskey, "FontQuality", conf_get_int(conf, CONF_font_quality));
+ write_setting_i(sesskey, "FontVTMode", conf_get_int(conf, CONF_vtmode));
+ write_setting_i(sesskey, "UseSystemColours", conf_get_int(conf, CONF_system_colour));
+ write_setting_i(sesskey, "TryPalette", conf_get_int(conf, CONF_try_palette));
+ write_setting_i(sesskey, "ANSIColour", conf_get_int(conf, CONF_ansi_colour));
+ write_setting_i(sesskey, "Xterm256Colour", conf_get_int(conf, CONF_xterm_256_colour));
+ write_setting_i(sesskey, "BoldAsColour", conf_get_int(conf, CONF_bold_style)-1);
+
+ for (i = 0; i < 22; i++) {
+ char buf[20], buf2[30];
+ sprintf(buf, "Colour%d", i);
+ sprintf(buf2, "%d,%d,%d",
+ conf_get_int_int(conf, CONF_colours, i*3+0),
+ conf_get_int_int(conf, CONF_colours, i*3+1),
+ conf_get_int_int(conf, CONF_colours, i*3+2));
+ write_setting_s(sesskey, buf, buf2);
+ }
+ write_setting_i(sesskey, "RawCNP", conf_get_int(conf, CONF_rawcnp));
+ write_setting_i(sesskey, "PasteRTF", conf_get_int(conf, CONF_rtf_paste));
+ write_setting_i(sesskey, "MouseIsXterm", conf_get_int(conf, CONF_mouse_is_xterm));
+ write_setting_i(sesskey, "RectSelect", conf_get_int(conf, CONF_rect_select));
+ write_setting_i(sesskey, "MouseOverride", conf_get_int(conf, CONF_mouse_override));
+ for (i = 0; i < 256; i += 32) {
+ char buf[20], buf2[256];
+ int j;
+ sprintf(buf, "Wordness%d", i);
+ *buf2 = '\0';
+ for (j = i; j < i + 32; j++) {
+ sprintf(buf2 + strlen(buf2), "%s%d",
+ (*buf2 ? "," : ""),
+ conf_get_int_int(conf, CONF_wordness, j));
+ }
+ write_setting_s(sesskey, buf, buf2);
+ }
+ write_setting_s(sesskey, "LineCodePage", conf_get_str(conf, CONF_line_codepage));
+ write_setting_i(sesskey, "CJKAmbigWide", conf_get_int(conf, CONF_cjk_ambig_wide));
+ write_setting_i(sesskey, "UTF8Override", conf_get_int(conf, CONF_utf8_override));
+ write_setting_s(sesskey, "Printer", conf_get_str(conf, CONF_printer));
+ write_setting_i(sesskey, "CapsLockCyr", conf_get_int(conf, CONF_xlat_capslockcyr));
+ write_setting_i(sesskey, "ScrollBar", conf_get_int(conf, CONF_scrollbar));
+ write_setting_i(sesskey, "ScrollBarFullScreen", conf_get_int(conf, CONF_scrollbar_in_fullscreen));
+ write_setting_i(sesskey, "ScrollOnKey", conf_get_int(conf, CONF_scroll_on_key));
+ write_setting_i(sesskey, "ScrollOnDisp", conf_get_int(conf, CONF_scroll_on_disp));
+ write_setting_i(sesskey, "EraseToScrollback", conf_get_int(conf, CONF_erase_to_scrollback));
+ write_setting_i(sesskey, "LockSize", conf_get_int(conf, CONF_resize_action));
+ write_setting_i(sesskey, "BCE", conf_get_int(conf, CONF_bce));
+ write_setting_i(sesskey, "BlinkText", conf_get_int(conf, CONF_blinktext));
+ write_setting_i(sesskey, "X11Forward", conf_get_int(conf, CONF_x11_forward));
+ write_setting_s(sesskey, "X11Display", conf_get_str(conf, CONF_x11_display));
+ write_setting_i(sesskey, "X11AuthType", conf_get_int(conf, CONF_x11_auth));
+ write_setting_filename(sesskey, "X11AuthFile", conf_get_filename(conf, CONF_xauthfile));
+ write_setting_i(sesskey, "LocalPortAcceptAll", conf_get_int(conf, CONF_lport_acceptall));
+ write_setting_i(sesskey, "RemotePortAcceptAll", conf_get_int(conf, CONF_rport_acceptall));
+ wmap(sesskey, "PortForwardings", conf, CONF_portfwd);
+ write_setting_i(sesskey, "BugIgnore1", 2-conf_get_int(conf, CONF_sshbug_ignore1));
+ write_setting_i(sesskey, "BugPlainPW1", 2-conf_get_int(conf, CONF_sshbug_plainpw1));
+ write_setting_i(sesskey, "BugRSA1", 2-conf_get_int(conf, CONF_sshbug_rsa1));
+ write_setting_i(sesskey, "BugIgnore2", 2-conf_get_int(conf, CONF_sshbug_ignore2));
+ write_setting_i(sesskey, "BugHMAC2", 2-conf_get_int(conf, CONF_sshbug_hmac2));
+ write_setting_i(sesskey, "BugDeriveKey2", 2-conf_get_int(conf, CONF_sshbug_derivekey2));
+ write_setting_i(sesskey, "BugRSAPad2", 2-conf_get_int(conf, CONF_sshbug_rsapad2));
+ write_setting_i(sesskey, "BugPKSessID2", 2-conf_get_int(conf, CONF_sshbug_pksessid2));
+ write_setting_i(sesskey, "BugRekey2", 2-conf_get_int(conf, CONF_sshbug_rekey2));
+ write_setting_i(sesskey, "BugMaxPkt2", 2-conf_get_int(conf, CONF_sshbug_maxpkt2));
+ write_setting_i(sesskey, "BugWinadj", 2-conf_get_int(conf, CONF_sshbug_winadj));
+ write_setting_i(sesskey, "StampUtmp", conf_get_int(conf, CONF_stamp_utmp));
+ write_setting_i(sesskey, "LoginShell", conf_get_int(conf, CONF_login_shell));
+ write_setting_i(sesskey, "ScrollbarOnLeft", conf_get_int(conf, CONF_scrollbar_on_left));
+ write_setting_fontspec(sesskey, "BoldFont", conf_get_fontspec(conf, CONF_boldfont));
+ write_setting_fontspec(sesskey, "WideFont", conf_get_fontspec(conf, CONF_widefont));
+ write_setting_fontspec(sesskey, "WideBoldFont", conf_get_fontspec(conf, CONF_wideboldfont));
+ write_setting_i(sesskey, "ShadowBold", conf_get_int(conf, CONF_shadowbold));
+ write_setting_i(sesskey, "ShadowBoldOffset", conf_get_int(conf, CONF_shadowboldoffset));
+ write_setting_s(sesskey, "SerialLine", conf_get_str(conf, CONF_serline));
+ write_setting_i(sesskey, "SerialSpeed", conf_get_int(conf, CONF_serspeed));
+ write_setting_i(sesskey, "SerialDataBits", conf_get_int(conf, CONF_serdatabits));
+ write_setting_i(sesskey, "SerialStopHalfbits", conf_get_int(conf, CONF_serstopbits));
+ write_setting_i(sesskey, "SerialParity", conf_get_int(conf, CONF_serparity));
+ write_setting_i(sesskey, "SerialFlowControl", conf_get_int(conf, CONF_serflow));
+ write_setting_s(sesskey, "WindowClass", conf_get_str(conf, CONF_winclass));
+ write_setting_i(sesskey, "ConnectionSharing", conf_get_int(conf, CONF_ssh_connection_sharing));
+ write_setting_i(sesskey, "ConnectionSharingUpstream", conf_get_int(conf, CONF_ssh_connection_sharing_upstream));
+ write_setting_i(sesskey, "ConnectionSharingDownstream", conf_get_int(conf, CONF_ssh_connection_sharing_downstream));
+}
+
+void load_settings(char *section, Conf *conf)
+{
+ void *sesskey;
+
+ sesskey = open_settings_r(section);
+ load_open_settings(sesskey, conf);
+ close_settings_r(sesskey);
+
+ if (conf_launchable(conf))
+ add_session_to_jumplist(section);
+}
+
+void load_open_settings(void *sesskey, Conf *conf)
+{
+ int i;
+ char *prot;
+
+ conf_set_int(conf, CONF_ssh_subsys, 0); /* FIXME: load this properly */
+ conf_set_str(conf, CONF_remote_cmd, "");
+ conf_set_str(conf, CONF_remote_cmd2, "");
+ conf_set_str(conf, CONF_ssh_nc_host, "");
+
+ gpps(sesskey, "HostName", "", conf, CONF_host);
+ gppfile(sesskey, "LogFileName", conf, CONF_logfilename);
+ gppi(sesskey, "LogType", 0, conf, CONF_logtype);
+ gppi(sesskey, "LogFileClash", LGXF_ASK, conf, CONF_logxfovr);
+ gppi(sesskey, "LogFlush", 1, conf, CONF_logflush);
+ gppi(sesskey, "SSHLogOmitPasswords", 1, conf, CONF_logomitpass);
+ gppi(sesskey, "SSHLogOmitData", 0, conf, CONF_logomitdata);
+
+ prot = gpps_raw(sesskey, "Protocol", "default");
+ conf_set_int(conf, CONF_protocol, default_protocol);
+ conf_set_int(conf, CONF_port, default_port);
+ {
+ const Backend *b = backend_from_name(prot);
+ if (b) {
+ conf_set_int(conf, CONF_protocol, b->protocol);
+ gppi(sesskey, "PortNumber", default_port, conf, CONF_port);
+ }
+ }
+ sfree(prot);
+
+ /* Address family selection */
+ gppi(sesskey, "AddressFamily", ADDRTYPE_UNSPEC, conf, CONF_addressfamily);
+
+ /* The CloseOnExit numbers are arranged in a different order from
+ * the standard FORCE_ON / FORCE_OFF / AUTO. */
+ i = gppi_raw(sesskey, "CloseOnExit", 1); conf_set_int(conf, CONF_close_on_exit, (i+1)%3);
+ gppi(sesskey, "WarnOnClose", 1, conf, CONF_warn_on_close);
+ {
+ /* This is two values for backward compatibility with 0.50/0.51 */
+ int pingmin, pingsec;
+ pingmin = gppi_raw(sesskey, "PingInterval", 0);
+ pingsec = gppi_raw(sesskey, "PingIntervalSecs", 0);
+ conf_set_int(conf, CONF_ping_interval, pingmin * 60 + pingsec);
+ }
+ gppi(sesskey, "TCPNoDelay", 1, conf, CONF_tcp_nodelay);
+ gppi(sesskey, "TCPKeepalives", 0, conf, CONF_tcp_keepalives);
+ gpps(sesskey, "TerminalType", "xterm", conf, CONF_termtype);
+ gpps(sesskey, "TerminalSpeed", "38400,38400", conf, CONF_termspeed);
+ if (!gppmap(sesskey, "TerminalModes", conf, CONF_ttymodes)) {
+ /* This hardcodes a big set of defaults in any new saved
+ * sessions. Let's hope we don't change our mind. */
+ for (i = 0; ttymodes[i]; i++)
+ conf_set_str_str(conf, CONF_ttymodes, ttymodes[i], "A");
+ }
+
+ /* proxy settings */
+ gpps(sesskey, "ProxyExcludeList", "", conf, CONF_proxy_exclude_list);
+ i = gppi_raw(sesskey, "ProxyDNS", 1); conf_set_int(conf, CONF_proxy_dns, (i+1)%3);
+ gppi(sesskey, "ProxyLocalhost", 0, conf, CONF_even_proxy_localhost);
+ gppi(sesskey, "ProxyMethod", -1, conf, CONF_proxy_type);
+ if (conf_get_int(conf, CONF_proxy_type) == -1) {
+ int i;
+ i = gppi_raw(sesskey, "ProxyType", 0);
+ if (i == 0)
+ conf_set_int(conf, CONF_proxy_type, PROXY_NONE);
+ else if (i == 1)
+ conf_set_int(conf, CONF_proxy_type, PROXY_HTTP);
+ else if (i == 3)
+ conf_set_int(conf, CONF_proxy_type, PROXY_TELNET);
+ else if (i == 4)
+ conf_set_int(conf, CONF_proxy_type, PROXY_CMD);
+ else {
+ i = gppi_raw(sesskey, "ProxySOCKSVersion", 5);
+ if (i == 5)
+ conf_set_int(conf, CONF_proxy_type, PROXY_SOCKS5);
+ else
+ conf_set_int(conf, CONF_proxy_type, PROXY_SOCKS4);
+ }
+ }
+ gpps(sesskey, "ProxyHost", "proxy", conf, CONF_proxy_host);
+ gppi(sesskey, "ProxyPort", 80, conf, CONF_proxy_port);
+ gpps(sesskey, "ProxyUsername", "", conf, CONF_proxy_username);
+ gpps(sesskey, "ProxyPassword", "", conf, CONF_proxy_password);
+ gpps(sesskey, "ProxyTelnetCommand", "connect %host %port\\n",
+ conf, CONF_proxy_telnet_command);
+ gppmap(sesskey, "Environment", conf, CONF_environmt);
+ gpps(sesskey, "UserName", "", conf, CONF_username);
+ gppi(sesskey, "UserNameFromEnvironment", 0, conf, CONF_username_from_env);
+ gpps(sesskey, "LocalUserName", "", conf, CONF_localusername);
+ gppi(sesskey, "NoPTY", 0, conf, CONF_nopty);
+ gppi(sesskey, "Compression", 0, conf, CONF_compression);
+ gppi(sesskey, "TryAgent", 1, conf, CONF_tryagent);
+ gppi(sesskey, "AgentFwd", 0, conf, CONF_agentfwd);
+ gppi(sesskey, "ChangeUsername", 0, conf, CONF_change_username);
+ gppi(sesskey, "GssapiFwd", 0, conf, CONF_gssapifwd);
+ gprefs(sesskey, "Cipher", "\0",
+ ciphernames, CIPHER_MAX, conf, CONF_ssh_cipherlist);
+ {
+ /* Backward-compatibility: we used to have an option to
+ * 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;
+ i = 2 - gppi_raw(sesskey, "BugDHGEx2", 0);
+ if (i == FORCE_ON)
+ default_kexes = "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";
+ gprefs(sesskey, "KEX", default_kexes,
+ kexnames, KEX_MAX, conf, CONF_ssh_kexlist);
+ }
+ gppi(sesskey, "RekeyTime", 60, conf, CONF_ssh_rekey_time);
+ gpps(sesskey, "RekeyBytes", "1G", conf, CONF_ssh_rekey_data);
+ gppi(sesskey, "SshProt", 2, conf, CONF_sshprot);
+ gpps(sesskey, "LogHost", "", conf, CONF_loghost);
+ gppi(sesskey, "SSH2DES", 0, conf, CONF_ssh2_des_cbc);
+ gppi(sesskey, "SshNoAuth", 0, conf, CONF_ssh_no_userauth);
+ gppi(sesskey, "SshBanner", 1, conf, CONF_ssh_show_banner);
+ gppi(sesskey, "AuthTIS", 0, conf, CONF_try_tis_auth);
+ gppi(sesskey, "AuthKI", 1, conf, CONF_try_ki_auth);
+ gppi(sesskey, "AuthGSSAPI", 1, conf, CONF_try_gssapi_auth);
+#ifndef NO_GSSAPI
+ gprefs(sesskey, "GSSLibs", "\0",
+ gsslibkeywords, ngsslibs, conf, CONF_ssh_gsslist);
+ gppfile(sesskey, "GSSCustom", conf, CONF_ssh_gss_custom);
+#endif
+ gppi(sesskey, "SshNoShell", 0, conf, CONF_ssh_no_shell);
+ gppfile(sesskey, "PublicKeyFile", conf, CONF_keyfile);
+ gpps(sesskey, "RemoteCommand", "", conf, CONF_remote_cmd);
+ gppi(sesskey, "RFCEnviron", 0, conf, CONF_rfc_environ);
+ gppi(sesskey, "PassiveTelnet", 0, conf, CONF_passive_telnet);
+ gppi(sesskey, "BackspaceIsDelete", 1, conf, CONF_bksp_is_delete);
+ gppi(sesskey, "RXVTHomeEnd", 0, conf, CONF_rxvt_homeend);
+ gppi(sesskey, "LinuxFunctionKeys", 0, conf, CONF_funky_type);
+ gppi(sesskey, "NoApplicationKeys", 0, conf, CONF_no_applic_k);
+ gppi(sesskey, "NoApplicationCursors", 0, conf, CONF_no_applic_c);
+ gppi(sesskey, "NoMouseReporting", 0, conf, CONF_no_mouse_rep);
+ gppi(sesskey, "NoRemoteResize", 0, conf, CONF_no_remote_resize);
+ gppi(sesskey, "NoAltScreen", 0, conf, CONF_no_alt_screen);
+ gppi(sesskey, "NoRemoteWinTitle", 0, conf, CONF_no_remote_wintitle);
+ {
+ /* Backward compatibility */
+ int no_remote_qtitle = gppi_raw(sesskey, "NoRemoteQTitle", 1);
+ /* We deliberately interpret the old setting of "no response" as
+ * "empty string". This changes the behaviour, but hopefully for
+ * the better; the user can always recover the old behaviour. */
+ gppi(sesskey, "RemoteQTitleAction",
+ no_remote_qtitle ? TITLE_EMPTY : TITLE_REAL,
+ conf, CONF_remote_qtitle_action);
+ }
+ gppi(sesskey, "NoDBackspace", 0, conf, CONF_no_dbackspace);
+ gppi(sesskey, "NoRemoteCharset", 0, conf, CONF_no_remote_charset);
+ gppi(sesskey, "ApplicationCursorKeys", 0, conf, CONF_app_cursor);
+ gppi(sesskey, "ApplicationKeypad", 0, conf, CONF_app_keypad);
+ gppi(sesskey, "NetHackKeypad", 0, conf, CONF_nethack_keypad);
+ gppi(sesskey, "AltF4", 1, conf, CONF_alt_f4);
+ gppi(sesskey, "AltSpace", 0, conf, CONF_alt_space);
+ gppi(sesskey, "AltOnly", 0, conf, CONF_alt_only);
+ gppi(sesskey, "ComposeKey", 0, conf, CONF_compose_key);
+ gppi(sesskey, "CtrlAltKeys", 1, conf, CONF_ctrlaltkeys);
+ gppi(sesskey, "TelnetKey", 0, conf, CONF_telnet_keyboard);
+ gppi(sesskey, "TelnetRet", 1, conf, CONF_telnet_newline);
+ gppi(sesskey, "LocalEcho", AUTO, conf, CONF_localecho);
+ gppi(sesskey, "LocalEdit", AUTO, conf, CONF_localedit);
+ gpps(sesskey, "Answerback", "PuTTY", conf, CONF_answerback);
+ gppi(sesskey, "AlwaysOnTop", 0, conf, CONF_alwaysontop);
+ gppi(sesskey, "FullScreenOnAltEnter", 0, conf, CONF_fullscreenonaltenter);
+ gppi(sesskey, "HideMousePtr", 0, conf, CONF_hide_mouseptr);
+ gppi(sesskey, "SunkenEdge", 0, conf, CONF_sunken_edge);
+ gppi(sesskey, "WindowBorder", 1, conf, CONF_window_border);
+ gppi(sesskey, "CurType", 0, conf, CONF_cursor_type);
+ gppi(sesskey, "BlinkCur", 0, conf, CONF_blink_cur);
+ /* pedantic compiler tells me I can't use conf, CONF_beep as an int * :-) */
+ gppi(sesskey, "Beep", 1, conf, CONF_beep);
+ gppi(sesskey, "BeepInd", 0, conf, CONF_beep_ind);
+ gppfile(sesskey, "BellWaveFile", conf, CONF_bell_wavefile);
+ gppi(sesskey, "BellOverload", 1, conf, CONF_bellovl);
+ gppi(sesskey, "BellOverloadN", 5, conf, CONF_bellovl_n);
+ i = gppi_raw(sesskey, "BellOverloadT", 2*TICKSPERSEC
+#ifdef PUTTY_UNIX_H
+ *1000
+#endif
+ );
+ conf_set_int(conf, CONF_bellovl_t, i
+#ifdef PUTTY_UNIX_H
+ / 1000
+#endif
+ );
+ i = gppi_raw(sesskey, "BellOverloadS", 5*TICKSPERSEC
+#ifdef PUTTY_UNIX_H
+ *1000
+#endif
+ );
+ conf_set_int(conf, CONF_bellovl_s, i
+#ifdef PUTTY_UNIX_H
+ / 1000
+#endif
+ );
+ gppi(sesskey, "ScrollbackLines", 2000, conf, CONF_savelines);
+ gppi(sesskey, "DECOriginMode", 0, conf, CONF_dec_om);
+ gppi(sesskey, "AutoWrapMode", 1, conf, CONF_wrap_mode);
+ gppi(sesskey, "LFImpliesCR", 0, conf, CONF_lfhascr);
+ gppi(sesskey, "CRImpliesLF", 0, conf, CONF_crhaslf);
+ gppi(sesskey, "DisableArabicShaping", 0, conf, CONF_arabicshaping);
+ gppi(sesskey, "DisableBidi", 0, conf, CONF_bidi);
+ gppi(sesskey, "WinNameAlways", 1, conf, CONF_win_name_always);
+ gpps(sesskey, "WinTitle", "", conf, CONF_wintitle);
+ gppi(sesskey, "TermWidth", 80, conf, CONF_width);
+ gppi(sesskey, "TermHeight", 24, conf, CONF_height);
+ gppfont(sesskey, "Font", conf, CONF_font);
+ gppi(sesskey, "FontQuality", FQ_DEFAULT, conf, CONF_font_quality);
+ gppi(sesskey, "FontVTMode", VT_UNICODE, conf, CONF_vtmode);
+ gppi(sesskey, "UseSystemColours", 0, conf, CONF_system_colour);
+ gppi(sesskey, "TryPalette", 0, conf, CONF_try_palette);
+ gppi(sesskey, "ANSIColour", 1, conf, CONF_ansi_colour);
+ gppi(sesskey, "Xterm256Colour", 1, conf, CONF_xterm_256_colour);
+ i = gppi_raw(sesskey, "BoldAsColour", 1); conf_set_int(conf, CONF_bold_style, i+1);
+
+ for (i = 0; i < 22; i++) {
+ static const char *const defaults[] = {
+ "187,187,187", "255,255,255", "0,0,0", "85,85,85", "0,0,0",
+ "0,255,0", "0,0,0", "85,85,85", "187,0,0", "255,85,85",
+ "0,187,0", "85,255,85", "187,187,0", "255,255,85", "0,0,187",
+ "85,85,255", "187,0,187", "255,85,255", "0,187,187",
+ "85,255,255", "187,187,187", "255,255,255"
+ };
+ char buf[20], *buf2;
+ int c0, c1, c2;
+ sprintf(buf, "Colour%d", i);
+ buf2 = gpps_raw(sesskey, buf, defaults[i]);
+ if (sscanf(buf2, "%d,%d,%d", &c0, &c1, &c2) == 3) {
+ conf_set_int_int(conf, CONF_colours, i*3+0, c0);
+ conf_set_int_int(conf, CONF_colours, i*3+1, c1);
+ conf_set_int_int(conf, CONF_colours, i*3+2, c2);
+ }
+ sfree(buf2);
+ }
+ gppi(sesskey, "RawCNP", 0, conf, CONF_rawcnp);
+ gppi(sesskey, "PasteRTF", 0, conf, CONF_rtf_paste);
+ gppi(sesskey, "MouseIsXterm", 0, conf, CONF_mouse_is_xterm);
+ gppi(sesskey, "RectSelect", 0, conf, CONF_rect_select);
+ gppi(sesskey, "MouseOverride", 1, conf, CONF_mouse_override);
+ for (i = 0; i < 256; i += 32) {
+ static const char *const defaults[] = {
+ "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
+ "0,1,2,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1",
+ "1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,2",
+ "1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1",
+ "1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1",
+ "1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1",
+ "2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2",
+ "2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2"
+ };
+ char buf[20], *buf2, *p;
+ int j;
+ sprintf(buf, "Wordness%d", i);
+ buf2 = gpps_raw(sesskey, buf, defaults[i / 32]);
+ p = buf2;
+ for (j = i; j < i + 32; j++) {
+ char *q = p;
+ while (*p && *p != ',')
+ p++;
+ if (*p == ',')
+ *p++ = '\0';
+ conf_set_int_int(conf, CONF_wordness, j, atoi(q));
+ }
+ sfree(buf2);
+ }
+ /*
+ * The empty default for LineCodePage will be converted later
+ * into a plausible default for the locale.
+ */
+ gpps(sesskey, "LineCodePage", "", conf, CONF_line_codepage);
+ gppi(sesskey, "CJKAmbigWide", 0, conf, CONF_cjk_ambig_wide);
+ gppi(sesskey, "UTF8Override", 1, conf, CONF_utf8_override);
+ gpps(sesskey, "Printer", "", conf, CONF_printer);
+ gppi(sesskey, "CapsLockCyr", 0, conf, CONF_xlat_capslockcyr);
+ gppi(sesskey, "ScrollBar", 1, conf, CONF_scrollbar);
+ gppi(sesskey, "ScrollBarFullScreen", 0, conf, CONF_scrollbar_in_fullscreen);
+ gppi(sesskey, "ScrollOnKey", 0, conf, CONF_scroll_on_key);
+ gppi(sesskey, "ScrollOnDisp", 1, conf, CONF_scroll_on_disp);
+ gppi(sesskey, "EraseToScrollback", 1, conf, CONF_erase_to_scrollback);
+ gppi(sesskey, "LockSize", 0, conf, CONF_resize_action);
+ gppi(sesskey, "BCE", 1, conf, CONF_bce);
+ gppi(sesskey, "BlinkText", 0, conf, CONF_blinktext);
+ gppi(sesskey, "X11Forward", 0, conf, CONF_x11_forward);
+ gpps(sesskey, "X11Display", "", conf, CONF_x11_display);
+ gppi(sesskey, "X11AuthType", X11_MIT, conf, CONF_x11_auth);
+ gppfile(sesskey, "X11AuthFile", conf, CONF_xauthfile);
+
+ gppi(sesskey, "LocalPortAcceptAll", 0, conf, CONF_lport_acceptall);
+ gppi(sesskey, "RemotePortAcceptAll", 0, conf, CONF_rport_acceptall);
+ gppmap(sesskey, "PortForwardings", conf, CONF_portfwd);
+ i = gppi_raw(sesskey, "BugIgnore1", 0); conf_set_int(conf, CONF_sshbug_ignore1, 2-i);
+ i = gppi_raw(sesskey, "BugPlainPW1", 0); conf_set_int(conf, CONF_sshbug_plainpw1, 2-i);
+ i = gppi_raw(sesskey, "BugRSA1", 0); conf_set_int(conf, CONF_sshbug_rsa1, 2-i);
+ i = gppi_raw(sesskey, "BugIgnore2", 0); conf_set_int(conf, CONF_sshbug_ignore2, 2-i);
+ {
+ int i;
+ i = gppi_raw(sesskey, "BugHMAC2", 0); conf_set_int(conf, CONF_sshbug_hmac2, 2-i);
+ if (2-i == AUTO) {
+ i = gppi_raw(sesskey, "BuggyMAC", 0);
+ if (i == 1)
+ conf_set_int(conf, CONF_sshbug_hmac2, FORCE_ON);
+ }
+ }
+ i = gppi_raw(sesskey, "BugDeriveKey2", 0); conf_set_int(conf, CONF_sshbug_derivekey2, 2-i);
+ i = gppi_raw(sesskey, "BugRSAPad2", 0); conf_set_int(conf, CONF_sshbug_rsapad2, 2-i);
+ i = gppi_raw(sesskey, "BugPKSessID2", 0); conf_set_int(conf, CONF_sshbug_pksessid2, 2-i);
+ i = gppi_raw(sesskey, "BugRekey2", 0); conf_set_int(conf, CONF_sshbug_rekey2, 2-i);
+ i = gppi_raw(sesskey, "BugMaxPkt2", 0); conf_set_int(conf, CONF_sshbug_maxpkt2, 2-i);
+ i = gppi_raw(sesskey, "BugWinadj", 0); conf_set_int(conf, CONF_sshbug_winadj, 2-i);
+ conf_set_int(conf, CONF_ssh_simple, FALSE);
+ gppi(sesskey, "StampUtmp", 1, conf, CONF_stamp_utmp);
+ gppi(sesskey, "LoginShell", 1, conf, CONF_login_shell);
+ gppi(sesskey, "ScrollbarOnLeft", 0, conf, CONF_scrollbar_on_left);
+ gppi(sesskey, "ShadowBold", 0, conf, CONF_shadowbold);
+ gppfont(sesskey, "BoldFont", conf, CONF_boldfont);
+ gppfont(sesskey, "WideFont", conf, CONF_widefont);
+ gppfont(sesskey, "WideBoldFont", conf, CONF_wideboldfont);
+ gppi(sesskey, "ShadowBoldOffset", 1, conf, CONF_shadowboldoffset);
+ gpps(sesskey, "SerialLine", "", conf, CONF_serline);
+ gppi(sesskey, "SerialSpeed", 9600, conf, CONF_serspeed);
+ gppi(sesskey, "SerialDataBits", 8, conf, CONF_serdatabits);
+ gppi(sesskey, "SerialStopHalfbits", 2, conf, CONF_serstopbits);
+ gppi(sesskey, "SerialParity", SER_PAR_NONE, conf, CONF_serparity);
+ gppi(sesskey, "SerialFlowControl", SER_FLOW_XONXOFF, conf, CONF_serflow);
+ gpps(sesskey, "WindowClass", "", conf, CONF_winclass);
+ gppi(sesskey, "ConnectionSharing", 0, conf, CONF_ssh_connection_sharing);
+ gppi(sesskey, "ConnectionSharingUpstream", 1, conf, CONF_ssh_connection_sharing_upstream);
+ gppi(sesskey, "ConnectionSharingDownstream", 1, conf, CONF_ssh_connection_sharing_downstream);
+}
+
+void do_defaults(char *session, Conf *conf)
+{
+ load_settings(session, conf);
+}
+
+static int sessioncmp(const void *av, const void *bv)
+{
+ const char *a = *(const char *const *) av;
+ const char *b = *(const char *const *) bv;
+
+ /*
+ * Alphabetical order, except that "Default Settings" is a
+ * special case and comes first.
+ */
+ if (!strcmp(a, "Default Settings"))
+ return -1; /* a comes first */
+ if (!strcmp(b, "Default Settings"))
+ return +1; /* b comes first */
+ /*
+ * FIXME: perhaps we should ignore the first & in determining
+ * sort order.
+ */
+ return strcmp(a, b); /* otherwise, compare normally */
+}
+
+void get_sesslist(struct sesslist *list, int allocate)
+{
+ char otherbuf[2048];
+ int buflen, bufsize, i;
+ char *p, *ret;
+ void *handle;
+
+ if (allocate) {
+
+ buflen = bufsize = 0;
+ list->buffer = NULL;
+ if ((handle = enum_settings_start()) != NULL) {
+ do {
+ ret = enum_settings_next(handle, otherbuf, sizeof(otherbuf));
+ if (ret) {
+ int len = strlen(otherbuf) + 1;
+ if (bufsize < buflen + len) {
+ bufsize = buflen + len + 2048;
+ list->buffer = sresize(list->buffer, bufsize, char);
+ }
+ strcpy(list->buffer + buflen, otherbuf);
+ buflen += strlen(list->buffer + buflen) + 1;
+ }
+ } while (ret);
+ enum_settings_finish(handle);
+ }
+ list->buffer = sresize(list->buffer, buflen + 1, char);
+ list->buffer[buflen] = '\0';
+
+ /*
+ * Now set up the list of sessions. Note that "Default
+ * Settings" must always be claimed to exist, even if it
+ * doesn't really.
+ */
+
+ p = list->buffer;
+ list->nsessions = 1; /* "Default Settings" counts as one */
+ while (*p) {
+ if (strcmp(p, "Default Settings"))
+ list->nsessions++;
+ while (*p)
+ p++;
+ p++;
+ }
+
+ list->sessions = snewn(list->nsessions + 1, char *);
+ list->sessions[0] = "Default Settings";
+ p = list->buffer;
+ i = 1;
+ while (*p) {
+ if (strcmp(p, "Default Settings"))
+ list->sessions[i++] = p;
+ while (*p)
+ p++;
+ p++;
+ }
+
+ qsort(list->sessions, i, sizeof(char *), sessioncmp);
+ } else {
+ sfree(list->buffer);
+ sfree(list->sessions);
+ list->buffer = NULL;
+ list->sessions = NULL;
+ }
+}
diff --git a/tools/plink/ssh.c b/tools/plink/ssh.c
index bdbbc4fd5..cef4455e8 100644..100755
--- a/tools/plink/ssh.c
+++ b/tools/plink/ssh.c
@@ -1,10164 +1,11140 @@
-/*
- * SSH backend.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <assert.h>
-#include <limits.h>
-#include <signal.h>
-
-#include "putty.h"
-#include "tree234.h"
-#include "ssh.h"
-#ifndef NO_GSSAPI
-#include "sshgssc.h"
-#include "sshgss.h"
-#endif
-
-#ifndef FALSE
-#define FALSE 0
-#endif
-#ifndef TRUE
-#define TRUE 1
-#endif
-
-#define SSH1_MSG_DISCONNECT 1 /* 0x1 */
-#define SSH1_SMSG_PUBLIC_KEY 2 /* 0x2 */
-#define SSH1_CMSG_SESSION_KEY 3 /* 0x3 */
-#define SSH1_CMSG_USER 4 /* 0x4 */
-#define SSH1_CMSG_AUTH_RSA 6 /* 0x6 */
-#define SSH1_SMSG_AUTH_RSA_CHALLENGE 7 /* 0x7 */
-#define SSH1_CMSG_AUTH_RSA_RESPONSE 8 /* 0x8 */
-#define SSH1_CMSG_AUTH_PASSWORD 9 /* 0x9 */
-#define SSH1_CMSG_REQUEST_PTY 10 /* 0xa */
-#define SSH1_CMSG_WINDOW_SIZE 11 /* 0xb */
-#define SSH1_CMSG_EXEC_SHELL 12 /* 0xc */
-#define SSH1_CMSG_EXEC_CMD 13 /* 0xd */
-#define SSH1_SMSG_SUCCESS 14 /* 0xe */
-#define SSH1_SMSG_FAILURE 15 /* 0xf */
-#define SSH1_CMSG_STDIN_DATA 16 /* 0x10 */
-#define SSH1_SMSG_STDOUT_DATA 17 /* 0x11 */
-#define SSH1_SMSG_STDERR_DATA 18 /* 0x12 */
-#define SSH1_CMSG_EOF 19 /* 0x13 */
-#define SSH1_SMSG_EXIT_STATUS 20 /* 0x14 */
-#define SSH1_MSG_CHANNEL_OPEN_CONFIRMATION 21 /* 0x15 */
-#define SSH1_MSG_CHANNEL_OPEN_FAILURE 22 /* 0x16 */
-#define SSH1_MSG_CHANNEL_DATA 23 /* 0x17 */
-#define SSH1_MSG_CHANNEL_CLOSE 24 /* 0x18 */
-#define SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION 25 /* 0x19 */
-#define SSH1_SMSG_X11_OPEN 27 /* 0x1b */
-#define SSH1_CMSG_PORT_FORWARD_REQUEST 28 /* 0x1c */
-#define SSH1_MSG_PORT_OPEN 29 /* 0x1d */
-#define SSH1_CMSG_AGENT_REQUEST_FORWARDING 30 /* 0x1e */
-#define SSH1_SMSG_AGENT_OPEN 31 /* 0x1f */
-#define SSH1_MSG_IGNORE 32 /* 0x20 */
-#define SSH1_CMSG_EXIT_CONFIRMATION 33 /* 0x21 */
-#define SSH1_CMSG_X11_REQUEST_FORWARDING 34 /* 0x22 */
-#define SSH1_CMSG_AUTH_RHOSTS_RSA 35 /* 0x23 */
-#define SSH1_MSG_DEBUG 36 /* 0x24 */
-#define SSH1_CMSG_REQUEST_COMPRESSION 37 /* 0x25 */
-#define SSH1_CMSG_AUTH_TIS 39 /* 0x27 */
-#define SSH1_SMSG_AUTH_TIS_CHALLENGE 40 /* 0x28 */
-#define SSH1_CMSG_AUTH_TIS_RESPONSE 41 /* 0x29 */
-#define SSH1_CMSG_AUTH_CCARD 70 /* 0x46 */
-#define SSH1_SMSG_AUTH_CCARD_CHALLENGE 71 /* 0x47 */
-#define SSH1_CMSG_AUTH_CCARD_RESPONSE 72 /* 0x48 */
-
-#define SSH1_AUTH_RHOSTS 1 /* 0x1 */
-#define SSH1_AUTH_RSA 2 /* 0x2 */
-#define SSH1_AUTH_PASSWORD 3 /* 0x3 */
-#define SSH1_AUTH_RHOSTS_RSA 4 /* 0x4 */
-#define SSH1_AUTH_TIS 5 /* 0x5 */
-#define SSH1_AUTH_CCARD 16 /* 0x10 */
-
-#define SSH1_PROTOFLAG_SCREEN_NUMBER 1 /* 0x1 */
-/* Mask for protoflags we will echo back to server if seen */
-#define SSH1_PROTOFLAGS_SUPPORTED 0 /* 0x1 */
-
-#define SSH2_MSG_DISCONNECT 1 /* 0x1 */
-#define SSH2_MSG_IGNORE 2 /* 0x2 */
-#define SSH2_MSG_UNIMPLEMENTED 3 /* 0x3 */
-#define SSH2_MSG_DEBUG 4 /* 0x4 */
-#define SSH2_MSG_SERVICE_REQUEST 5 /* 0x5 */
-#define SSH2_MSG_SERVICE_ACCEPT 6 /* 0x6 */
-#define SSH2_MSG_KEXINIT 20 /* 0x14 */
-#define SSH2_MSG_NEWKEYS 21 /* 0x15 */
-#define SSH2_MSG_KEXDH_INIT 30 /* 0x1e */
-#define SSH2_MSG_KEXDH_REPLY 31 /* 0x1f */
-#define SSH2_MSG_KEX_DH_GEX_REQUEST 30 /* 0x1e */
-#define SSH2_MSG_KEX_DH_GEX_GROUP 31 /* 0x1f */
-#define SSH2_MSG_KEX_DH_GEX_INIT 32 /* 0x20 */
-#define SSH2_MSG_KEX_DH_GEX_REPLY 33 /* 0x21 */
-#define SSH2_MSG_KEXRSA_PUBKEY 30 /* 0x1e */
-#define SSH2_MSG_KEXRSA_SECRET 31 /* 0x1f */
-#define SSH2_MSG_KEXRSA_DONE 32 /* 0x20 */
-#define SSH2_MSG_USERAUTH_REQUEST 50 /* 0x32 */
-#define SSH2_MSG_USERAUTH_FAILURE 51 /* 0x33 */
-#define SSH2_MSG_USERAUTH_SUCCESS 52 /* 0x34 */
-#define SSH2_MSG_USERAUTH_BANNER 53 /* 0x35 */
-#define SSH2_MSG_USERAUTH_PK_OK 60 /* 0x3c */
-#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 /* 0x3c */
-#define SSH2_MSG_USERAUTH_INFO_REQUEST 60 /* 0x3c */
-#define SSH2_MSG_USERAUTH_INFO_RESPONSE 61 /* 0x3d */
-#define SSH2_MSG_GLOBAL_REQUEST 80 /* 0x50 */
-#define SSH2_MSG_REQUEST_SUCCESS 81 /* 0x51 */
-#define SSH2_MSG_REQUEST_FAILURE 82 /* 0x52 */
-#define SSH2_MSG_CHANNEL_OPEN 90 /* 0x5a */
-#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 /* 0x5b */
-#define SSH2_MSG_CHANNEL_OPEN_FAILURE 92 /* 0x5c */
-#define SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 /* 0x5d */
-#define SSH2_MSG_CHANNEL_DATA 94 /* 0x5e */
-#define SSH2_MSG_CHANNEL_EXTENDED_DATA 95 /* 0x5f */
-#define SSH2_MSG_CHANNEL_EOF 96 /* 0x60 */
-#define SSH2_MSG_CHANNEL_CLOSE 97 /* 0x61 */
-#define SSH2_MSG_CHANNEL_REQUEST 98 /* 0x62 */
-#define SSH2_MSG_CHANNEL_SUCCESS 99 /* 0x63 */
-#define SSH2_MSG_CHANNEL_FAILURE 100 /* 0x64 */
-#define SSH2_MSG_USERAUTH_GSSAPI_RESPONSE 60
-#define SSH2_MSG_USERAUTH_GSSAPI_TOKEN 61
-#define SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE 63
-#define SSH2_MSG_USERAUTH_GSSAPI_ERROR 64
-#define SSH2_MSG_USERAUTH_GSSAPI_ERRTOK 65
-#define SSH2_MSG_USERAUTH_GSSAPI_MIC 66
-
-/*
- * Packet type contexts, so that ssh2_pkt_type can correctly decode
- * the ambiguous type numbers back into the correct type strings.
- */
-typedef enum {
- SSH2_PKTCTX_NOKEX,
- SSH2_PKTCTX_DHGROUP,
- SSH2_PKTCTX_DHGEX,
- SSH2_PKTCTX_RSAKEX
-} Pkt_KCtx;
-typedef enum {
- SSH2_PKTCTX_NOAUTH,
- SSH2_PKTCTX_PUBLICKEY,
- SSH2_PKTCTX_PASSWORD,
- SSH2_PKTCTX_GSSAPI,
- SSH2_PKTCTX_KBDINTER
-} Pkt_ACtx;
-
-#define SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 /* 0x1 */
-#define SSH2_DISCONNECT_PROTOCOL_ERROR 2 /* 0x2 */
-#define SSH2_DISCONNECT_KEY_EXCHANGE_FAILED 3 /* 0x3 */
-#define SSH2_DISCONNECT_HOST_AUTHENTICATION_FAILED 4 /* 0x4 */
-#define SSH2_DISCONNECT_MAC_ERROR 5 /* 0x5 */
-#define SSH2_DISCONNECT_COMPRESSION_ERROR 6 /* 0x6 */
-#define SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE 7 /* 0x7 */
-#define SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8 /* 0x8 */
-#define SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9 /* 0x9 */
-#define SSH2_DISCONNECT_CONNECTION_LOST 10 /* 0xa */
-#define SSH2_DISCONNECT_BY_APPLICATION 11 /* 0xb */
-#define SSH2_DISCONNECT_TOO_MANY_CONNECTIONS 12 /* 0xc */
-#define SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER 13 /* 0xd */
-#define SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 /* 0xe */
-#define SSH2_DISCONNECT_ILLEGAL_USER_NAME 15 /* 0xf */
-
-static const char *const ssh2_disconnect_reasons[] = {
- NULL,
- "host not allowed to connect",
- "protocol error",
- "key exchange failed",
- "host authentication failed",
- "MAC error",
- "compression error",
- "service not available",
- "protocol version not supported",
- "host key not verifiable",
- "connection lost",
- "by application",
- "too many connections",
- "auth cancelled by user",
- "no more auth methods available",
- "illegal user name",
-};
-
-#define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED 1 /* 0x1 */
-#define SSH2_OPEN_CONNECT_FAILED 2 /* 0x2 */
-#define SSH2_OPEN_UNKNOWN_CHANNEL_TYPE 3 /* 0x3 */
-#define SSH2_OPEN_RESOURCE_SHORTAGE 4 /* 0x4 */
-
-#define SSH2_EXTENDED_DATA_STDERR 1 /* 0x1 */
-
-/*
- * Various remote-bug flags.
- */
-#define BUG_CHOKES_ON_SSH1_IGNORE 1
-#define BUG_SSH2_HMAC 2
-#define BUG_NEEDS_SSH1_PLAIN_PASSWORD 4
-#define BUG_CHOKES_ON_RSA 8
-#define BUG_SSH2_RSA_PADDING 16
-#define BUG_SSH2_DERIVEKEY 32
-#define BUG_SSH2_REKEY 64
-#define BUG_SSH2_PK_SESSIONID 128
-#define BUG_SSH2_MAXPKT 256
-#define BUG_CHOKES_ON_SSH2_IGNORE 512
-
-/*
- * Codes for terminal modes.
- * Most of these are the same in SSH-1 and SSH-2.
- * This list is derived from RFC 4254 and
- * SSH-1 RFC-1.2.31.
- */
-static const struct {
- const char* const mode;
- int opcode;
- enum { TTY_OP_CHAR, TTY_OP_BOOL } type;
-} ssh_ttymodes[] = {
- /* "V" prefix discarded for special characters relative to SSH specs */
- { "INTR", 1, TTY_OP_CHAR },
- { "QUIT", 2, TTY_OP_CHAR },
- { "ERASE", 3, TTY_OP_CHAR },
- { "KILL", 4, TTY_OP_CHAR },
- { "EOF", 5, TTY_OP_CHAR },
- { "EOL", 6, TTY_OP_CHAR },
- { "EOL2", 7, TTY_OP_CHAR },
- { "START", 8, TTY_OP_CHAR },
- { "STOP", 9, TTY_OP_CHAR },
- { "SUSP", 10, TTY_OP_CHAR },
- { "DSUSP", 11, TTY_OP_CHAR },
- { "REPRINT", 12, TTY_OP_CHAR },
- { "WERASE", 13, TTY_OP_CHAR },
- { "LNEXT", 14, TTY_OP_CHAR },
- { "FLUSH", 15, TTY_OP_CHAR },
- { "SWTCH", 16, TTY_OP_CHAR },
- { "STATUS", 17, TTY_OP_CHAR },
- { "DISCARD", 18, TTY_OP_CHAR },
- { "IGNPAR", 30, TTY_OP_BOOL },
- { "PARMRK", 31, TTY_OP_BOOL },
- { "INPCK", 32, TTY_OP_BOOL },
- { "ISTRIP", 33, TTY_OP_BOOL },
- { "INLCR", 34, TTY_OP_BOOL },
- { "IGNCR", 35, TTY_OP_BOOL },
- { "ICRNL", 36, TTY_OP_BOOL },
- { "IUCLC", 37, TTY_OP_BOOL },
- { "IXON", 38, TTY_OP_BOOL },
- { "IXANY", 39, TTY_OP_BOOL },
- { "IXOFF", 40, TTY_OP_BOOL },
- { "IMAXBEL", 41, TTY_OP_BOOL },
- { "ISIG", 50, TTY_OP_BOOL },
- { "ICANON", 51, TTY_OP_BOOL },
- { "XCASE", 52, TTY_OP_BOOL },
- { "ECHO", 53, TTY_OP_BOOL },
- { "ECHOE", 54, TTY_OP_BOOL },
- { "ECHOK", 55, TTY_OP_BOOL },
- { "ECHONL", 56, TTY_OP_BOOL },
- { "NOFLSH", 57, TTY_OP_BOOL },
- { "TOSTOP", 58, TTY_OP_BOOL },
- { "IEXTEN", 59, TTY_OP_BOOL },
- { "ECHOCTL", 60, TTY_OP_BOOL },
- { "ECHOKE", 61, TTY_OP_BOOL },
- { "PENDIN", 62, TTY_OP_BOOL }, /* XXX is this a real mode? */
- { "OPOST", 70, TTY_OP_BOOL },
- { "OLCUC", 71, TTY_OP_BOOL },
- { "ONLCR", 72, TTY_OP_BOOL },
- { "OCRNL", 73, TTY_OP_BOOL },
- { "ONOCR", 74, TTY_OP_BOOL },
- { "ONLRET", 75, TTY_OP_BOOL },
- { "CS7", 90, TTY_OP_BOOL },
- { "CS8", 91, TTY_OP_BOOL },
- { "PARENB", 92, TTY_OP_BOOL },
- { "PARODD", 93, TTY_OP_BOOL }
-};
-
-/* Miscellaneous other tty-related constants. */
-#define SSH_TTY_OP_END 0
-/* The opcodes for ISPEED/OSPEED differ between SSH-1 and SSH-2. */
-#define SSH1_TTY_OP_ISPEED 192
-#define SSH1_TTY_OP_OSPEED 193
-#define SSH2_TTY_OP_ISPEED 128
-#define SSH2_TTY_OP_OSPEED 129
-
-/* Helper functions for parsing tty-related config. */
-static unsigned int ssh_tty_parse_specchar(char *s)
-{
- unsigned int ret;
- if (*s) {
- char *next = NULL;
- ret = ctrlparse(s, &next);
- if (!next) ret = s[0];
- } else {
- ret = 255; /* special value meaning "don't set" */
- }
- return ret;
-}
-static unsigned int ssh_tty_parse_boolean(char *s)
-{
- if (stricmp(s, "yes") == 0 ||
- stricmp(s, "on") == 0 ||
- stricmp(s, "true") == 0 ||
- stricmp(s, "+") == 0)
- return 1; /* true */
- else if (stricmp(s, "no") == 0 ||
- stricmp(s, "off") == 0 ||
- stricmp(s, "false") == 0 ||
- stricmp(s, "-") == 0)
- return 0; /* false */
- else
- return (atoi(s) != 0);
-}
-
-#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)
-{
- translate(SSH1_MSG_DISCONNECT);
- translate(SSH1_SMSG_PUBLIC_KEY);
- translate(SSH1_CMSG_SESSION_KEY);
- translate(SSH1_CMSG_USER);
- translate(SSH1_CMSG_AUTH_RSA);
- translate(SSH1_SMSG_AUTH_RSA_CHALLENGE);
- translate(SSH1_CMSG_AUTH_RSA_RESPONSE);
- translate(SSH1_CMSG_AUTH_PASSWORD);
- translate(SSH1_CMSG_REQUEST_PTY);
- translate(SSH1_CMSG_WINDOW_SIZE);
- translate(SSH1_CMSG_EXEC_SHELL);
- translate(SSH1_CMSG_EXEC_CMD);
- translate(SSH1_SMSG_SUCCESS);
- translate(SSH1_SMSG_FAILURE);
- translate(SSH1_CMSG_STDIN_DATA);
- translate(SSH1_SMSG_STDOUT_DATA);
- translate(SSH1_SMSG_STDERR_DATA);
- translate(SSH1_CMSG_EOF);
- translate(SSH1_SMSG_EXIT_STATUS);
- translate(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION);
- translate(SSH1_MSG_CHANNEL_OPEN_FAILURE);
- translate(SSH1_MSG_CHANNEL_DATA);
- translate(SSH1_MSG_CHANNEL_CLOSE);
- translate(SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION);
- translate(SSH1_SMSG_X11_OPEN);
- translate(SSH1_CMSG_PORT_FORWARD_REQUEST);
- translate(SSH1_MSG_PORT_OPEN);
- translate(SSH1_CMSG_AGENT_REQUEST_FORWARDING);
- translate(SSH1_SMSG_AGENT_OPEN);
- translate(SSH1_MSG_IGNORE);
- translate(SSH1_CMSG_EXIT_CONFIRMATION);
- translate(SSH1_CMSG_X11_REQUEST_FORWARDING);
- translate(SSH1_CMSG_AUTH_RHOSTS_RSA);
- translate(SSH1_MSG_DEBUG);
- translate(SSH1_CMSG_REQUEST_COMPRESSION);
- translate(SSH1_CMSG_AUTH_TIS);
- translate(SSH1_SMSG_AUTH_TIS_CHALLENGE);
- translate(SSH1_CMSG_AUTH_TIS_RESPONSE);
- translate(SSH1_CMSG_AUTH_CCARD);
- translate(SSH1_SMSG_AUTH_CCARD_CHALLENGE);
- translate(SSH1_CMSG_AUTH_CCARD_RESPONSE);
- return "unknown";
-}
-static 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);
- translatea(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE,SSH2_PKTCTX_GSSAPI);
- translatea(SSH2_MSG_USERAUTH_GSSAPI_ERROR,SSH2_PKTCTX_GSSAPI);
- translatea(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK,SSH2_PKTCTX_GSSAPI);
- translatea(SSH2_MSG_USERAUTH_GSSAPI_MIC, SSH2_PKTCTX_GSSAPI);
- translate(SSH2_MSG_DISCONNECT);
- translate(SSH2_MSG_IGNORE);
- translate(SSH2_MSG_UNIMPLEMENTED);
- translate(SSH2_MSG_DEBUG);
- translate(SSH2_MSG_SERVICE_REQUEST);
- translate(SSH2_MSG_SERVICE_ACCEPT);
- translate(SSH2_MSG_KEXINIT);
- translate(SSH2_MSG_NEWKEYS);
- translatek(SSH2_MSG_KEXDH_INIT, SSH2_PKTCTX_DHGROUP);
- translatek(SSH2_MSG_KEXDH_REPLY, SSH2_PKTCTX_DHGROUP);
- translatek(SSH2_MSG_KEX_DH_GEX_REQUEST, SSH2_PKTCTX_DHGEX);
- translatek(SSH2_MSG_KEX_DH_GEX_GROUP, SSH2_PKTCTX_DHGEX);
- translatek(SSH2_MSG_KEX_DH_GEX_INIT, SSH2_PKTCTX_DHGEX);
- translatek(SSH2_MSG_KEX_DH_GEX_REPLY, SSH2_PKTCTX_DHGEX);
- translatek(SSH2_MSG_KEXRSA_PUBKEY, SSH2_PKTCTX_RSAKEX);
- translatek(SSH2_MSG_KEXRSA_SECRET, SSH2_PKTCTX_RSAKEX);
- translatek(SSH2_MSG_KEXRSA_DONE, SSH2_PKTCTX_RSAKEX);
- translate(SSH2_MSG_USERAUTH_REQUEST);
- translate(SSH2_MSG_USERAUTH_FAILURE);
- translate(SSH2_MSG_USERAUTH_SUCCESS);
- translate(SSH2_MSG_USERAUTH_BANNER);
- translatea(SSH2_MSG_USERAUTH_PK_OK, SSH2_PKTCTX_PUBLICKEY);
- translatea(SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, SSH2_PKTCTX_PASSWORD);
- translatea(SSH2_MSG_USERAUTH_INFO_REQUEST, SSH2_PKTCTX_KBDINTER);
- translatea(SSH2_MSG_USERAUTH_INFO_RESPONSE, SSH2_PKTCTX_KBDINTER);
- translate(SSH2_MSG_GLOBAL_REQUEST);
- translate(SSH2_MSG_REQUEST_SUCCESS);
- translate(SSH2_MSG_REQUEST_FAILURE);
- translate(SSH2_MSG_CHANNEL_OPEN);
- translate(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
- translate(SSH2_MSG_CHANNEL_OPEN_FAILURE);
- translate(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
- translate(SSH2_MSG_CHANNEL_DATA);
- translate(SSH2_MSG_CHANNEL_EXTENDED_DATA);
- translate(SSH2_MSG_CHANNEL_EOF);
- translate(SSH2_MSG_CHANNEL_CLOSE);
- translate(SSH2_MSG_CHANNEL_REQUEST);
- translate(SSH2_MSG_CHANNEL_SUCCESS);
- translate(SSH2_MSG_CHANNEL_FAILURE);
- return "unknown";
-}
-#undef translate
-#undef translatec
-
-/* Enumeration values for fields in SSH-1 packets */
-enum {
- PKT_END, PKT_INT, PKT_CHAR, PKT_DATA, PKT_STR, PKT_BIGNUM,
- /* These values are for communicating relevant semantics of
- * fields to the packet logging code. */
- PKTT_OTHER, PKTT_PASSWORD, PKTT_DATA
-};
-
-/*
- * Coroutine mechanics for the sillier bits of the code. If these
- * macros look impenetrable to you, you might find it helpful to
- * read
- *
- * http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html
- *
- * which explains the theory behind these macros.
- *
- * In particular, if you are getting `case expression not constant'
- * errors when building with MS Visual Studio, this is because MS's
- * Edit and Continue debugging feature causes their compiler to
- * violate ANSI C. To disable Edit and Continue debugging:
- *
- * - right-click ssh.c in the FileView
- * - click Settings
- * - select the C/C++ tab and the General category
- * - under `Debug info:', select anything _other_ than `Program
- * Database for Edit and Continue'.
- */
-#define crBegin(v) { int *crLine = &v; switch(v) { case 0:;
-#define crState(t) \
- struct t *s; \
- if (!ssh->t) ssh->t = snew(struct t); \
- s = ssh->t;
-#define crFinish(z) } *crLine = 0; return (z); }
-#define crFinishV } *crLine = 0; return; }
-#define crReturn(z) \
- do {\
- *crLine =__LINE__; return (z); case __LINE__:;\
- } while (0)
-#define crReturnV \
- do {\
- *crLine=__LINE__; return; case __LINE__:;\
- } while (0)
-#define crStop(z) do{ *crLine = 0; return (z); }while(0)
-#define crStopV do{ *crLine = 0; return; }while(0)
-#define crWaitUntil(c) do { crReturn(0); } while (!(c))
-#define crWaitUntilV(c) do { crReturnV; } while (!(c))
-
-typedef struct ssh_tag *Ssh;
-struct Packet;
-
-static struct Packet *ssh1_pkt_init(int pkt_type);
-static struct Packet *ssh2_pkt_init(int pkt_type);
-static void ssh_pkt_ensure(struct Packet *, int length);
-static void ssh_pkt_adddata(struct Packet *, void *data, int len);
-static void ssh_pkt_addbyte(struct Packet *, unsigned char value);
-static void ssh2_pkt_addbool(struct Packet *, unsigned char value);
-static void ssh_pkt_adduint32(struct Packet *, unsigned long value);
-static void ssh_pkt_addstring_start(struct Packet *);
-static void ssh_pkt_addstring_str(struct Packet *, char *data);
-static void ssh_pkt_addstring_data(struct Packet *, char *data, int len);
-static void ssh_pkt_addstring(struct Packet *, char *data);
-static unsigned char *ssh2_mpint_fmt(Bignum b, int *len);
-static void ssh1_pkt_addmp(struct Packet *, Bignum b);
-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,
- struct Packet *pktin);
-static void do_ssh2_authconn(Ssh ssh, 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);
-
-/*
- * Buffer management constants. There are several of these for
- * various different purposes:
- *
- * - SSH1_BUFFER_LIMIT is the amount of backlog that must build up
- * on a local data stream before we throttle the whole SSH
- * connection (in SSH-1 only). Throttling the whole connection is
- * pretty drastic so we set this high in the hope it won't
- * happen very often.
- *
- * - SSH_MAX_BACKLOG is the amount of backlog that must build up
- * on the SSH connection itself before we defensively throttle
- * _all_ local data streams. This is pretty drastic too (though
- * thankfully unlikely in SSH-2 since the window mechanism should
- * ensure that the server never has any need to throttle its end
- * of the connection), so we set this high as well.
- *
- * - OUR_V2_WINSIZE is the maximum window size we present on SSH-2
- * channels.
- *
- * - OUR_V2_BIGWIN is the window size we advertise for the only
- * channel in a simple connection. It must be <= INT_MAX.
- *
- * - OUR_V2_MAXPKT is the official "maximum packet size" we send
- * to the remote side. This actually has nothing to do with the
- * size of the _packet_, but is instead a limit on the amount
- * of data we're willing to receive in a single SSH2 channel
- * data message.
- *
- * - OUR_V2_PACKETLIMIT is actually the maximum size of SSH
- * _packet_ we're prepared to cope with. It must be a multiple
- * of the cipher block size, and must be at least 35000.
- */
-
-#define SSH1_BUFFER_LIMIT 32768
-#define SSH_MAX_BACKLOG 32768
-#define OUR_V2_WINSIZE 16384
-#define OUR_V2_BIGWIN 0x7fffffff
-#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_mac *macs[] = {
- &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5
-};
-const static struct ssh_mac *buggymacs[] = {
- &ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5
-};
-
-static void *ssh_comp_none_init(void)
-{
- return NULL;
-}
-static void ssh_comp_none_cleanup(void *handle)
-{
-}
-static int ssh_comp_none_block(void *handle, unsigned char *block, int len,
- unsigned char **outblock, int *outlen)
-{
- return 0;
-}
-static int ssh_comp_none_disable(void *handle)
-{
- return 0;
-}
-const static struct ssh_compress ssh_comp_none = {
- "none", NULL,
- ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block,
- ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block,
- ssh_comp_none_disable, NULL
-};
-extern const struct ssh_compress ssh_zlib;
-const static struct ssh_compress *compressions[] = {
- &ssh_zlib, &ssh_comp_none
-};
-
-enum { /* channel types */
- CHAN_MAINSESSION,
- CHAN_X11,
- CHAN_AGENT,
- CHAN_SOCKDATA,
- CHAN_SOCKDATA_DORMANT, /* one the remote hasn't confirmed */
- /*
- * CHAN_ZOMBIE is used to indicate a channel for which we've
- * already destroyed the local data source: for instance, if a
- * forwarded port experiences a socket error on the local side, we
- * immediately destroy its local socket and turn the SSH channel
- * into CHAN_ZOMBIE.
- */
- CHAN_ZOMBIE
-};
-
-/*
- * little structure to keep track of outstanding WINDOW_ADJUSTs
- */
-struct winadj {
- struct winadj *next;
- unsigned size;
-};
-
-/*
- * 2-3-4 tree storing channels.
- */
-struct ssh_channel {
- Ssh ssh; /* pointer back to main context */
- unsigned remoteid, localid;
- int type;
- /* True if we opened this channel but server hasn't confirmed. */
- int halfopen;
- /*
- * In SSH-1, this value contains four bits:
- *
- * 1 We have sent SSH1_MSG_CHANNEL_CLOSE.
- * 2 We have sent SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION.
- * 4 We have received SSH1_MSG_CHANNEL_CLOSE.
- * 8 We have received SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION.
- *
- * A channel is completely finished with when all four bits are set.
- *
- * In SSH-2, the four bits mean:
- *
- * 1 We have sent SSH2_MSG_CHANNEL_EOF.
- * 2 We have sent SSH2_MSG_CHANNEL_CLOSE.
- * 4 We have received SSH2_MSG_CHANNEL_EOF.
- * 8 We have received SSH2_MSG_CHANNEL_CLOSE.
- *
- * A channel is completely finished with when we have both sent
- * and received CLOSE.
- *
- * The symbolic constants below use the SSH-2 terminology, which
- * is a bit confusing in SSH-1, but we have to use _something_.
- */
-#define CLOSES_SENT_EOF 1
-#define CLOSES_SENT_CLOSE 2
-#define CLOSES_RCVD_EOF 4
-#define CLOSES_RCVD_CLOSE 8
- int closes;
-
- /*
- * This flag indicates that an EOF is pending on the outgoing side
- * of the channel: that is, wherever we're getting the data for
- * this channel has sent us some data followed by EOF. We can't
- * actually send the EOF until we've finished sending the data, so
- * we set this flag instead to remind us to do so once our buffer
- * is clear.
- */
- int pending_eof;
-
- /*
- * True if this channel is causing the underlying connection to be
- * throttled.
- */
- int throttling_conn;
- union {
- struct ssh2_data_channel {
- bufchain outbuffer;
- unsigned remwindow, remmaxpkt;
- /* locwindow is signed so we can cope with excess data. */
- int locwindow, locmaxwin;
- /*
- * remlocwin is the amount of local window that we think
- * the remote end had available to it after it sent the
- * last data packet or window adjust ack.
- */
- int remlocwin;
- /*
- * These store the list of window adjusts that haven't
- * been acked.
- */
- struct winadj *winadj_head, *winadj_tail;
- enum { THROTTLED, UNTHROTTLING, UNTHROTTLED } throttle_state;
- } v2;
- } v;
- union {
- struct ssh_agent_channel {
- unsigned char *message;
- unsigned char msglen[4];
- unsigned lensofar, totallen;
- } a;
- struct ssh_x11_channel {
- Socket s;
- } x11;
- struct ssh_pfd_channel {
- Socket s;
- } pfd;
- } u;
-};
-
-/*
- * 2-3-4 tree storing remote->local port forwardings. SSH-1 and SSH-2
- * use this structure in different ways, reflecting SSH-2's
- * altogether saner approach to port forwarding.
- *
- * In SSH-1, you arrange a remote forwarding by sending the server
- * the remote port number, and the local destination host:port.
- * When a connection comes in, the server sends you back that
- * host:port pair, and you connect to it. This is a ready-made
- * security hole if you're not on the ball: a malicious server
- * could send you back _any_ host:port pair, so if you trustingly
- * connect to the address it gives you then you've just opened the
- * entire inside of your corporate network just by connecting
- * through it to a dodgy SSH server. Hence, we must store a list of
- * host:port pairs we _are_ trying to forward to, and reject a
- * connection request from the server if it's not in the list.
- *
- * In SSH-2, each side of the connection minds its own business and
- * doesn't send unnecessary information to the other. You arrange a
- * remote forwarding by sending the server just the remote port
- * number. When a connection comes in, the server tells you which
- * of its ports was connected to; and _you_ have to remember what
- * local host:port pair went with that port number.
- *
- * Hence, in SSH-1 this structure is indexed by destination
- * host:port pair, whereas in SSH-2 it is indexed by source port.
- */
-struct ssh_portfwd; /* forward declaration */
-
-struct ssh_rportfwd {
- unsigned sport, dport;
- char dhost[256];
- char *sportdesc;
- struct ssh_portfwd *pfrec;
-};
-#define free_rportfwd(pf) ( \
- ((pf) ? (sfree((pf)->sportdesc)) : (void)0 ), sfree(pf) )
-
-/*
- * Separately to the rportfwd tree (which is for looking up port
- * open requests from the server), a tree of _these_ structures is
- * used to keep track of all the currently open port forwardings,
- * so that we can reconfigure in mid-session if the user requests
- * it.
- */
-struct ssh_portfwd {
- enum { DESTROY, KEEP, CREATE } status;
- int type;
- unsigned sport, dport;
- char *saddr, *daddr;
- char *sserv, *dserv;
- struct ssh_rportfwd *remote;
- int addressfamily;
- void *local;
-};
-#define free_portfwd(pf) ( \
- ((pf) ? (sfree((pf)->saddr), sfree((pf)->daddr), \
- sfree((pf)->sserv), sfree((pf)->dserv)) : (void)0 ), sfree(pf) )
-
-struct Packet {
- long length; /* length of `data' actually used */
- long forcepad; /* SSH-2: force padding to at least this length */
- int type; /* only used for incoming packets */
- unsigned long sequence; /* SSH-2 incoming sequence number */
- unsigned char *data; /* allocated storage */
- unsigned char *body; /* offset of payload within `data' */
- long savedpos; /* temporary index into `data' (for strings) */
- long maxlen; /* amount of storage allocated for `data' */
- long encrypted_len; /* for SSH-2 total-size counting */
-
- /*
- * State associated with packet logging
- */
- int logmode;
- int nblanks;
- struct logblank_t *blanks;
-};
-
-static void ssh1_protocol(Ssh ssh, void *vin, int inlen,
- struct Packet *pktin);
-static void ssh2_protocol(Ssh ssh, void *vin, int inlen,
- struct Packet *pktin);
-static void ssh1_protocol_setup(Ssh ssh);
-static void ssh2_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 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);
-static int ssh_do_close(Ssh ssh, int notify_exit);
-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, long now);
-static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
- struct Packet *pktin);
-
-struct rdpkt1_state_tag {
- long len, pad, biglen, to_read;
- unsigned long realcrc, gotcrc;
- unsigned char *p;
- int i;
- int chunk;
- struct Packet *pktin;
-};
-
-struct rdpkt2_state_tag {
- long len, pad, payload, packetlen, maclen;
- int i;
- int cipherblk;
- unsigned long incoming_sequence;
- struct Packet *pktin;
-};
-
-typedef void (*handler_fn_t)(Ssh ssh, struct Packet *pktin);
-typedef void (*chandler_fn_t)(Ssh ssh, struct Packet *pktin, void *ctx);
-
-struct queued_handler;
-struct queued_handler {
- int msg1, msg2;
- chandler_fn_t handler;
- void *ctx;
- struct queued_handler *next;
-};
-
-struct ssh_tag {
- const struct plug_function_table *fn;
- /* the above field _must_ be first in the structure */
-
- char *v_c, *v_s;
- void *exhash;
-
- Socket s;
-
- void *ldisc;
- void *logctx;
-
- unsigned char session_key[32];
- int v1_compressing;
- int v1_remote_protoflags;
- int v1_local_protoflags;
- int agentfwd_enabled;
- int X11_fwd_enabled;
- int remote_bugs;
- const struct ssh_cipher *cipher;
- void *v1_cipher_ctx;
- void *crcda_ctx;
- const struct ssh2_cipher *cscipher, *sccipher;
- void *cs_cipher_ctx, *sc_cipher_ctx;
- const struct ssh_mac *csmac, *scmac;
- void *cs_mac_ctx, *sc_mac_ctx;
- const struct ssh_compress *cscomp, *sccomp;
- void *cs_comp_ctx, *sc_comp_ctx;
- const struct ssh_kex *kex;
- const struct ssh_signkey *hostkey;
- unsigned char v2_session_id[SSH2_KEX_MAX_HASH_LEN];
- int v2_session_id_len;
- void *kex_ctx;
-
- char *savedhost;
- int savedport;
- int send_ok;
- int echoing, editing;
-
- void *frontend;
-
- int ospeed, ispeed; /* temporaries */
- int term_width, term_height;
-
- tree234 *channels; /* indexed by local id */
- struct ssh_channel *mainchan; /* primary session channel */
- int ncmode; /* is primary channel direct-tcpip? */
- int exitcode;
- int close_expected;
- int clean_exit;
-
- tree234 *rportfwds, *portfwds;
-
- enum {
- SSH_STATE_PREPACKET,
- SSH_STATE_BEFORE_SIZE,
- SSH_STATE_INTERMED,
- SSH_STATE_SESSION,
- SSH_STATE_CLOSED
- } state;
-
- int size_needed, eof_needed;
- int sent_console_eof;
- int got_pty; /* affects EOF behaviour on main channel */
-
- struct Packet **queue;
- int queuelen, queuesize;
- int queueing;
- unsigned char *deferred_send_data;
- int deferred_len, deferred_size;
-
- /*
- * Gross hack: pscp will try to start SFTP but fall back to
- * scp1 if that fails. This variable is the means by which
- * scp.c can reach into the SSH code and find out which one it
- * got.
- */
- int fallback_cmd;
-
- bufchain banner; /* accumulates banners during do_ssh2_authconn */
-
- Pkt_KCtx pkt_kctx;
- Pkt_ACtx pkt_actx;
-
- struct X11Display *x11disp;
-
- int version;
- int conn_throttle_count;
- int overall_bufsize;
- int throttled_all;
- int v1_stdout_throttling;
- unsigned long v2_outgoing_sequence;
-
- int ssh1_rdpkt_crstate;
- int ssh2_rdpkt_crstate;
- int do_ssh_init_crstate;
- int ssh_gotdata_crstate;
- int do_ssh1_login_crstate;
- int do_ssh1_connection_crstate;
- int do_ssh2_transport_crstate;
- int do_ssh2_authconn_crstate;
-
- void *do_ssh_init_state;
- void *do_ssh1_login_state;
- void *do_ssh2_transport_state;
- void *do_ssh2_authconn_state;
-
- struct rdpkt1_state_tag rdpkt1_state;
- struct rdpkt2_state_tag rdpkt2_state;
-
- /* 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,
- struct Packet *pkt);
- struct Packet *(*s_rdpkt) (Ssh ssh, unsigned char **data, int *datalen);
-
- /*
- * We maintain our own copy of a Conf structure here. That way,
- * when we're passed a new one for reconfiguration, we can check
- * the differences and potentially reconfigure port forwardings
- * etc in mid-session.
- */
- Conf *conf;
-
- /*
- * Values cached out of conf so as to avoid the tree234 lookup
- * cost every time they're used.
- */
- int logomitdata;
-
- /*
- * Dynamically allocated username string created during SSH
- * login. Stored in here rather than in the coroutine state so
- * that it'll be reliably freed if we shut down the SSH session
- * at some unexpected moment.
- */
- char *username;
-
- /*
- * Used to transfer data back from async callbacks.
- */
- void *agent_response;
- int agent_response_len;
- int user_response;
-
- /*
- * The SSH connection can be set as `frozen', meaning we are
- * not currently accepting incoming data from the network. This
- * is slightly more serious than setting the _socket_ as
- * frozen, because we may already have had data passed to us
- * from the network which we need to delay processing until
- * after the freeze is lifted, so we also need a bufchain to
- * store that data.
- */
- int frozen;
- bufchain queued_incoming_data;
-
- /*
- * Dispatch table for packet types that we may have to deal
- * with at any time.
- */
- handler_fn_t packet_dispatch[256];
-
- /*
- * Queues of one-off handler functions for success/failure
- * indications from a request.
- */
- struct queued_handler *qhead, *qtail;
-
- /*
- * This module deals with sending keepalives.
- */
- Pinger pinger;
-
- /*
- * Track incoming and outgoing data sizes and time, for
- * size-based rekeys.
- */
- unsigned long incoming_data_size, outgoing_data_size, deferred_data_size;
- unsigned long max_data_size;
- int kex_in_progress;
- long next_rekey, last_rekey;
- char *deferred_rekey_reason; /* points to STATIC string; don't free */
-
- /*
- * Fully qualified host name, which we need if doing GSSAPI.
- */
- char *fullhostname;
-
-#ifndef NO_GSSAPI
- /*
- * GSSAPI libraries for this session.
- */
- struct ssh_gss_liblist *gsslibs;
-#endif
-};
-
-#define logevent(s) logevent(ssh->frontend, s)
-
-/* logevent, only printf-formatted. */
-static void logeventf(Ssh ssh, const char *fmt, ...)
-{
- va_list ap;
- char *buf;
-
- va_start(ap, fmt);
- buf = dupvprintf(fmt, ap);
- va_end(ap);
- logevent(buf);
- sfree(buf);
-}
-
-#define bombout(msg) \
- do { \
- char *text = dupprintf msg; \
- ssh_do_close(ssh, FALSE); \
- logevent(text); \
- connection_fatal(ssh->frontend, "%s", text); \
- sfree(text); \
- } while (0)
-
-/* Functions to leave bits out of the SSH packet log file. */
-
-static void dont_log_password(Ssh ssh, struct Packet *pkt, int blanktype)
-{
- if (conf_get_int(ssh->conf, CONF_logomitpass))
- pkt->logmode = blanktype;
-}
-
-static void dont_log_data(Ssh ssh, struct Packet *pkt, int blanktype)
-{
- if (ssh->logomitdata)
- pkt->logmode = blanktype;
-}
-
-static void end_log_omission(Ssh ssh, struct Packet *pkt)
-{
- pkt->logmode = PKTLOG_EMIT;
-}
-
-/* Helper function for common bits of parsing ttymodes. */
-static void parse_ttymodes(Ssh ssh,
- void (*do_mode)(void *data, char *mode, char *val),
- void *data)
-{
- char *key, *val;
-
- for (val = conf_get_str_strs(ssh->conf, CONF_ttymodes, NULL, &key);
- val != NULL;
- val = conf_get_str_strs(ssh->conf, CONF_ttymodes, key, &key)) {
- /*
- * val[0] is either 'V', indicating that an explicit value
- * follows it, or 'A' indicating that we should pass the
- * value through from the local environment via get_ttymode.
- */
- if (val[0] == 'A')
- val = get_ttymode(ssh->frontend, key);
- else
- val++; /* skip the 'V' */
- if (val)
- do_mode(data, key, val);
- }
-}
-
-static int ssh_channelcmp(void *av, void *bv)
-{
- struct ssh_channel *a = (struct ssh_channel *) av;
- struct ssh_channel *b = (struct ssh_channel *) bv;
- if (a->localid < b->localid)
- return -1;
- if (a->localid > b->localid)
- return +1;
- return 0;
-}
-static int ssh_channelfind(void *av, void *bv)
-{
- unsigned *a = (unsigned *) av;
- struct ssh_channel *b = (struct ssh_channel *) bv;
- if (*a < b->localid)
- return -1;
- if (*a > b->localid)
- return +1;
- return 0;
-}
-
-static int ssh_rportcmp_ssh1(void *av, void *bv)
-{
- struct ssh_rportfwd *a = (struct ssh_rportfwd *) av;
- struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv;
- int i;
- if ( (i = strcmp(a->dhost, b->dhost)) != 0)
- return i < 0 ? -1 : +1;
- if (a->dport > b->dport)
- return +1;
- if (a->dport < b->dport)
- return -1;
- return 0;
-}
-
-static int ssh_rportcmp_ssh2(void *av, void *bv)
-{
- struct ssh_rportfwd *a = (struct ssh_rportfwd *) av;
- struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv;
-
- if (a->sport > b->sport)
- return +1;
- if (a->sport < b->sport)
- return -1;
- return 0;
-}
-
-/*
- * Special form of strcmp which can cope with NULL inputs. NULL is
- * defined to sort before even the empty string.
- */
-static int nullstrcmp(const char *a, const char *b)
-{
- if (a == NULL && b == NULL)
- return 0;
- if (a == NULL)
- return -1;
- if (b == NULL)
- return +1;
- return strcmp(a, b);
-}
-
-static int ssh_portcmp(void *av, void *bv)
-{
- struct ssh_portfwd *a = (struct ssh_portfwd *) av;
- struct ssh_portfwd *b = (struct ssh_portfwd *) bv;
- int i;
- if (a->type > b->type)
- return +1;
- if (a->type < b->type)
- return -1;
- if (a->addressfamily > b->addressfamily)
- return +1;
- if (a->addressfamily < b->addressfamily)
- return -1;
- if ( (i = nullstrcmp(a->saddr, b->saddr)) != 0)
- return i < 0 ? -1 : +1;
- if (a->sport > b->sport)
- return +1;
- if (a->sport < b->sport)
- return -1;
- if (a->type != 'D') {
- if ( (i = nullstrcmp(a->daddr, b->daddr)) != 0)
- return i < 0 ? -1 : +1;
- if (a->dport > b->dport)
- return +1;
- if (a->dport < b->dport)
- return -1;
- }
- return 0;
-}
-
-static int alloc_channel_id(Ssh ssh)
-{
- const unsigned CHANNEL_NUMBER_OFFSET = 256;
- unsigned low, high, mid;
- int tsize;
- struct ssh_channel *c;
-
- /*
- * First-fit allocation of channel numbers: always pick the
- * lowest unused one. To do this, binary-search using the
- * counted B-tree to find the largest channel ID which is in a
- * contiguous sequence from the beginning. (Precisely
- * everything in that sequence must have ID equal to its tree
- * index plus CHANNEL_NUMBER_OFFSET.)
- */
- tsize = count234(ssh->channels);
-
- low = -1;
- high = tsize;
- while (high - low > 1) {
- mid = (high + low) / 2;
- c = index234(ssh->channels, mid);
- if (c->localid == mid + CHANNEL_NUMBER_OFFSET)
- low = mid; /* this one is fine */
- else
- high = mid; /* this one is past it */
- }
- /*
- * Now low points to either -1, or the tree index of the
- * largest ID in the initial sequence.
- */
- {
- unsigned i = low + 1 + CHANNEL_NUMBER_OFFSET;
- assert(NULL == find234(ssh->channels, &i, ssh_channelfind));
- }
- return low + 1 + CHANNEL_NUMBER_OFFSET;
-}
-
-static void c_write_stderr(int trusted, const char *buf, int len)
-{
- int i;
- for (i = 0; i < len; i++)
- if (buf[i] != '\r' && (trusted || buf[i] == '\n' || (buf[i] & 0x60)))
- fputc(buf[i], stderr);
- fflush(stderr);
-}
-
-static void c_write(Ssh ssh, const char *buf, int len)
-{
- if (flags & FLAG_STDERR)
- c_write_stderr(1, buf, len);
- else
- from_backend(ssh->frontend, 1, buf, len);
-}
-
-static void c_write_untrusted(Ssh ssh, const char *buf, int len)
-{
- if (flags & FLAG_STDERR)
- c_write_stderr(0, buf, len);
- else
- from_backend_untrusted(ssh->frontend, buf, len);
-}
-
-static void c_write_str(Ssh ssh, const char *buf)
-{
- c_write(ssh, buf, strlen(buf));
-}
-
-static void ssh_free_packet(struct Packet *pkt)
-{
- sfree(pkt->data);
- sfree(pkt);
-}
-static struct Packet *ssh_new_packet(void)
-{
- struct Packet *pkt = snew(struct Packet);
-
- pkt->body = pkt->data = NULL;
- pkt->maxlen = 0;
- pkt->logmode = PKTLOG_EMIT;
- pkt->nblanks = 0;
- pkt->blanks = NULL;
-
- return pkt;
-}
-
-/*
- * Collect incoming data in the incoming packet buffer.
- * Decipher and verify the packet when it is completely read.
- * Drop SSH1_MSG_DEBUG and SSH1_MSG_IGNORE packets.
- * 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)
-{
- struct rdpkt1_state_tag *st = &ssh->rdpkt1_state;
-
- crBegin(ssh->ssh1_rdpkt_crstate);
-
- st->pktin = ssh_new_packet();
-
- st->pktin->type = 0;
- st->pktin->length = 0;
-
- for (st->i = st->len = 0; st->i < 4; st->i++) {
- while ((*datalen) == 0)
- crReturn(NULL);
- st->len = (st->len << 8) + **data;
- (*data)++, (*datalen)--;
- }
-
- st->pad = 8 - (st->len % 8);
- st->biglen = st->len + st->pad;
- st->pktin->length = st->len - 5;
-
- if (st->biglen < 0) {
- bombout(("Extremely large packet length from server suggests"
- " data stream corruption"));
- ssh_free_packet(st->pktin);
- crStop(NULL);
- }
-
- st->pktin->maxlen = st->biglen;
- st->pktin->data = snewn(st->biglen + APIEXTRA, unsigned char);
-
- st->to_read = st->biglen;
- st->p = st->pktin->data;
- while (st->to_read > 0) {
- st->chunk = st->to_read;
- while ((*datalen) == 0)
- crReturn(NULL);
- if (st->chunk > (*datalen))
- st->chunk = (*datalen);
- memcpy(st->p, *data, st->chunk);
- *data += st->chunk;
- *datalen -= st->chunk;
- st->p += st->chunk;
- st->to_read -= st->chunk;
- }
-
- if (ssh->cipher && detect_attack(ssh->crcda_ctx, st->pktin->data,
- st->biglen, NULL)) {
- bombout(("Network attack (CRC compensation) detected!"));
- ssh_free_packet(st->pktin);
- crStop(NULL);
- }
-
- if (ssh->cipher)
- ssh->cipher->decrypt(ssh->v1_cipher_ctx, st->pktin->data, st->biglen);
-
- st->realcrc = crc32_compute(st->pktin->data, st->biglen - 4);
- st->gotcrc = GET_32BIT(st->pktin->data + st->biglen - 4);
- if (st->gotcrc != st->realcrc) {
- bombout(("Incorrect CRC received on packet"));
- ssh_free_packet(st->pktin);
- crStop(NULL);
- }
-
- st->pktin->body = st->pktin->data + st->pad + 1;
- st->pktin->savedpos = 0;
-
- if (ssh->v1_compressing) {
- unsigned char *decompblk;
- int decomplen;
- if (!zlib_decompress_block(ssh->sc_comp_ctx,
- st->pktin->body - 1, st->pktin->length + 1,
- &decompblk, &decomplen)) {
- bombout(("Zlib decompression encountered invalid data"));
- ssh_free_packet(st->pktin);
- crStop(NULL);
- }
-
- if (st->pktin->maxlen < st->pad + decomplen) {
- st->pktin->maxlen = st->pad + decomplen;
- st->pktin->data = sresize(st->pktin->data,
- st->pktin->maxlen + APIEXTRA,
- unsigned char);
- st->pktin->body = st->pktin->data + st->pad + 1;
- }
-
- memcpy(st->pktin->body - 1, decompblk, decomplen);
- sfree(decompblk);
- st->pktin->length = decomplen - 1;
- }
-
- st->pktin->type = st->pktin->body[-1];
-
- /*
- * Log incoming packet, possibly omitting sensitive fields.
- */
- if (ssh->logctx) {
- int nblanks = 0;
- struct logblank_t blank;
- if (ssh->logomitdata) {
- int do_blank = FALSE, blank_prefix = 0;
- /* "Session data" packets - omit the data field */
- if ((st->pktin->type == SSH1_SMSG_STDOUT_DATA) ||
- (st->pktin->type == SSH1_SMSG_STDERR_DATA)) {
- do_blank = TRUE; blank_prefix = 4;
- } else if (st->pktin->type == SSH1_MSG_CHANNEL_DATA) {
- do_blank = TRUE; blank_prefix = 8;
- }
- if (do_blank) {
- blank.offset = blank_prefix;
- blank.len = st->pktin->length;
- blank.type = PKTLOG_OMIT;
- nblanks = 1;
- }
- }
- log_packet(ssh->logctx,
- PKT_INCOMING, st->pktin->type,
- ssh1_pkt_type(st->pktin->type),
- st->pktin->body, st->pktin->length,
- nblanks, &blank, NULL);
- }
-
- crFinish(st->pktin);
-}
-
-static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
-{
- struct rdpkt2_state_tag *st = &ssh->rdpkt2_state;
-
- crBegin(ssh->ssh2_rdpkt_crstate);
-
- st->pktin = ssh_new_packet();
-
- st->pktin->type = 0;
- st->pktin->length = 0;
- if (ssh->sccipher)
- st->cipherblk = ssh->sccipher->blksize;
- else
- st->cipherblk = 8;
- if (st->cipherblk < 8)
- st->cipherblk = 8;
- st->maclen = ssh->scmac ? ssh->scmac->len : 0;
-
- if (ssh->sccipher && (ssh->sccipher->flags & SSH_CIPHER_IS_CBC) &&
- ssh->scmac) {
- /*
- * When dealing with a CBC-mode cipher, we want to avoid the
- * possibility of an attacker's tweaking the ciphertext stream
- * so as to cause us to feed the same block to the block
- * cipher more than once and thus leak information
- * (VU#958563). The way we do this is not to take any
- * decisions on the basis of anything we've decrypted until
- * we've verified it with a MAC. That includes the packet
- * 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.
- */
-
- /* May as well allocate the whole lot now. */
- st->pktin->data = snewn(OUR_V2_PACKETLIMIT + st->maclen + APIEXTRA,
- unsigned char);
-
- /* Read an amount corresponding to the MAC. */
- for (st->i = 0; st->i < st->maclen; st->i++) {
- while ((*datalen) == 0)
- crReturn(NULL);
- st->pktin->data[st->i] = *(*data)++;
- (*datalen)--;
- }
-
- st->packetlen = 0;
- {
- unsigned char seq[4];
- ssh->scmac->start(ssh->sc_mac_ctx);
- PUT_32BIT(seq, st->incoming_sequence);
- ssh->scmac->bytes(ssh->sc_mac_ctx, seq, 4);
- }
-
- for (;;) { /* Once around this loop per cipher block. */
- /* Read another cipher-block's worth, and tack it onto the end. */
- for (st->i = 0; st->i < st->cipherblk; st->i++) {
- while ((*datalen) == 0)
- crReturn(NULL);
- st->pktin->data[st->packetlen+st->maclen+st->i] = *(*data)++;
- (*datalen)--;
- }
- /* Decrypt one more block (a little further back in the stream). */
- ssh->sccipher->decrypt(ssh->sc_cipher_ctx,
- st->pktin->data + st->packetlen,
- st->cipherblk);
- /* Feed that block to the MAC. */
- ssh->scmac->bytes(ssh->sc_mac_ctx,
- st->pktin->data + st->packetlen, st->cipherblk);
- st->packetlen += st->cipherblk;
- /* See if that gives us a valid packet. */
- if (ssh->scmac->verresult(ssh->sc_mac_ctx,
- st->pktin->data + st->packetlen) &&
- (st->len = GET_32BIT(st->pktin->data)) + 4 == st->packetlen)
- break;
- if (st->packetlen >= OUR_V2_PACKETLIMIT) {
- bombout(("No valid incoming packet found"));
- ssh_free_packet(st->pktin);
- crStop(NULL);
- }
- }
- st->pktin->maxlen = st->packetlen + st->maclen;
- st->pktin->data = sresize(st->pktin->data,
- st->pktin->maxlen + APIEXTRA,
- unsigned char);
- } else {
- st->pktin->data = snewn(st->cipherblk + APIEXTRA, unsigned char);
-
- /*
- * Acquire and decrypt the first block of the packet. This will
- * contain the length and padding details.
- */
- for (st->i = st->len = 0; st->i < st->cipherblk; st->i++) {
- while ((*datalen) == 0)
- crReturn(NULL);
- st->pktin->data[st->i] = *(*data)++;
- (*datalen)--;
- }
-
- if (ssh->sccipher)
- ssh->sccipher->decrypt(ssh->sc_cipher_ctx,
- st->pktin->data, st->cipherblk);
-
- /*
- * Now get the length figure.
- */
- st->len = 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 + 4) % st->cipherblk != 0) {
- bombout(("Incoming packet was garbled on decryption"));
- 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 and decrypt the remainder of the packet.
- */
- for (st->i = st->cipherblk; st->i < st->packetlen + st->maclen;
- st->i++) {
- while ((*datalen) == 0)
- crReturn(NULL);
- st->pktin->data[st->i] = *(*data)++;
- (*datalen)--;
- }
- /* Decrypt everything _except_ the MAC. */
- if (ssh->sccipher)
- ssh->sccipher->decrypt(ssh->sc_cipher_ctx,
- st->pktin->data + st->cipherblk,
- st->packetlen - st->cipherblk);
-
- /*
- * 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);
- }
- }
- /* Get and sanity-check the amount of random padding. */
- st->pad = st->pktin->data[4];
- if (st->pad < 4 || st->len - st->pad < 1) {
- bombout(("Invalid padding length on received packet"));
- ssh_free_packet(st->pktin);
- crStop(NULL);
- }
- /*
- * This enables us to deduce the payload length.
- */
- st->payload = st->len - st->pad - 1;
-
- st->pktin->length = st->payload + 5;
- st->pktin->encrypted_len = st->packetlen;
-
- st->pktin->sequence = st->incoming_sequence++;
-
- /*
- * Decompress packet payload.
- */
- {
- unsigned char *newpayload;
- int newlen;
- if (ssh->sccomp &&
- ssh->sccomp->decompress(ssh->sc_comp_ctx,
- st->pktin->data + 5, st->pktin->length - 5,
- &newpayload, &newlen)) {
- if (st->pktin->maxlen < newlen + 5) {
- st->pktin->maxlen = newlen + 5;
- st->pktin->data = sresize(st->pktin->data,
- st->pktin->maxlen + APIEXTRA,
- unsigned char);
- }
- st->pktin->length = 5 + newlen;
- memcpy(st->pktin->data + 5, newpayload, newlen);
- sfree(newpayload);
- }
- }
-
- st->pktin->savedpos = 6;
- st->pktin->body = st->pktin->data;
- st->pktin->type = st->pktin->data[5];
-
- /*
- * Log incoming packet, possibly omitting sensitive fields.
- */
- if (ssh->logctx) {
- int nblanks = 0;
- struct logblank_t blank;
- if (ssh->logomitdata) {
- int do_blank = FALSE, blank_prefix = 0;
- /* "Session data" packets - omit the data field */
- if (st->pktin->type == SSH2_MSG_CHANNEL_DATA) {
- do_blank = TRUE; blank_prefix = 8;
- } else if (st->pktin->type == SSH2_MSG_CHANNEL_EXTENDED_DATA) {
- do_blank = TRUE; blank_prefix = 12;
- }
- if (do_blank) {
- blank.offset = blank_prefix;
- blank.len = (st->pktin->length-6) - blank_prefix;
- blank.type = PKTLOG_OMIT;
- nblanks = 1;
- }
- }
- log_packet(ssh->logctx, PKT_INCOMING, st->pktin->type,
- ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx,
- st->pktin->type),
- st->pktin->data+6, st->pktin->length-6,
- nblanks, &blank, &st->pktin->sequence);
- }
-
- crFinish(st->pktin);
-}
-
-static int s_wrpkt_prepare(Ssh ssh, struct Packet *pkt, int *offset_p)
-{
- int pad, biglen, i, pktoffs;
- unsigned long crc;
-#ifdef __SC__
- /*
- * XXX various versions of SC (including 8.8.4) screw up the
- * register allocation in this function and use the same register
- * (D6) for len and as a temporary, with predictable results. The
- * following sledgehammer prevents this.
- */
- volatile
-#endif
- int len;
-
- if (ssh->logctx)
- log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[12],
- ssh1_pkt_type(pkt->data[12]),
- pkt->body, pkt->length - (pkt->body - pkt->data),
- pkt->nblanks, pkt->blanks, NULL);
- sfree(pkt->blanks); pkt->blanks = NULL;
- pkt->nblanks = 0;
-
- if (ssh->v1_compressing) {
- unsigned char *compblk;
- int complen;
- zlib_compress_block(ssh->cs_comp_ctx,
- pkt->data + 12, pkt->length - 12,
- &compblk, &complen);
- ssh_pkt_ensure(pkt, complen + 2); /* just in case it's got bigger */
- memcpy(pkt->data + 12, compblk, complen);
- sfree(compblk);
- pkt->length = complen + 12;
- }
-
- ssh_pkt_ensure(pkt, pkt->length + 4); /* space for CRC */
- pkt->length += 4;
- len = pkt->length - 4 - 8; /* len(type+data+CRC) */
- pad = 8 - (len % 8);
- pktoffs = 8 - pad;
- biglen = len + pad; /* len(padding+type+data+CRC) */
-
- for (i = pktoffs; i < 4+8; i++)
- pkt->data[i] = random_byte();
- crc = crc32_compute(pkt->data + pktoffs + 4, biglen - 4); /* all ex len */
- PUT_32BIT(pkt->data + pktoffs + 4 + biglen - 4, crc);
- PUT_32BIT(pkt->data + pktoffs, len);
-
- if (ssh->cipher)
- ssh->cipher->encrypt(ssh->v1_cipher_ctx,
- pkt->data + pktoffs + 4, biglen);
-
- if (offset_p) *offset_p = pktoffs;
- return biglen + 4; /* len(length+padding+type+data+CRC) */
-}
-
-static int s_write(Ssh ssh, void *data, int len)
-{
- if (ssh->logctx)
- log_packet(ssh->logctx, PKT_OUTGOING, -1, NULL, data, len,
- 0, NULL, NULL);
- return sk_write(ssh->s, (char *)data, len);
-}
-
-static void s_wrpkt(Ssh ssh, struct Packet *pkt)
-{
- int len, backlog, offset;
- len = s_wrpkt_prepare(ssh, pkt, &offset);
- backlog = s_write(ssh, pkt->data + offset, len);
- if (backlog > SSH_MAX_BACKLOG)
- ssh_throttle_all(ssh, 1, backlog);
- ssh_free_packet(pkt);
-}
-
-static void s_wrpkt_defer(Ssh ssh, struct Packet *pkt)
-{
- int len, offset;
- len = s_wrpkt_prepare(ssh, pkt, &offset);
- if (ssh->deferred_len + len > ssh->deferred_size) {
- ssh->deferred_size = ssh->deferred_len + len + 128;
- ssh->deferred_send_data = sresize(ssh->deferred_send_data,
- ssh->deferred_size,
- unsigned char);
- }
- memcpy(ssh->deferred_send_data + ssh->deferred_len,
- pkt->data + offset, len);
- ssh->deferred_len += len;
- ssh_free_packet(pkt);
-}
-
-/*
- * Construct a SSH-1 packet with the specified contents.
- * (This all-at-once interface used to be the only one, but now SSH-1
- * packets can also be constructed incrementally.)
- */
-static struct Packet *construct_packet(Ssh ssh, int pkttype, va_list ap)
-{
- int argtype;
- Bignum bn;
- struct Packet *pkt;
-
- pkt = ssh1_pkt_init(pkttype);
-
- while ((argtype = va_arg(ap, int)) != PKT_END) {
- unsigned char *argp, argchar;
- char *sargp;
- unsigned long argint;
- int arglen;
- switch (argtype) {
- /* Actual fields in the packet */
- case PKT_INT:
- argint = va_arg(ap, int);
- ssh_pkt_adduint32(pkt, argint);
- break;
- case PKT_CHAR:
- argchar = (unsigned char) va_arg(ap, int);
- ssh_pkt_addbyte(pkt, argchar);
- break;
- case PKT_DATA:
- argp = va_arg(ap, unsigned char *);
- arglen = va_arg(ap, int);
- ssh_pkt_adddata(pkt, argp, arglen);
- break;
- case PKT_STR:
- sargp = va_arg(ap, char *);
- ssh_pkt_addstring(pkt, sargp);
- break;
- case PKT_BIGNUM:
- bn = va_arg(ap, Bignum);
- ssh1_pkt_addmp(pkt, bn);
- break;
- /* Tokens for modifications to packet logging */
- case PKTT_PASSWORD:
- dont_log_password(ssh, pkt, PKTLOG_BLANK);
- break;
- case PKTT_DATA:
- dont_log_data(ssh, pkt, PKTLOG_OMIT);
- break;
- case PKTT_OTHER:
- end_log_omission(ssh, pkt);
- break;
- }
- }
-
- return pkt;
-}
-
-static void send_packet(Ssh ssh, int pkttype, ...)
-{
- struct Packet *pkt;
- va_list ap;
- va_start(ap, pkttype);
- pkt = construct_packet(ssh, pkttype, ap);
- va_end(ap);
- s_wrpkt(ssh, pkt);
-}
-
-static void defer_packet(Ssh ssh, int pkttype, ...)
-{
- struct Packet *pkt;
- va_list ap;
- va_start(ap, pkttype);
- pkt = construct_packet(ssh, pkttype, ap);
- va_end(ap);
- s_wrpkt_defer(ssh, pkt);
-}
-
-static int ssh_versioncmp(char *a, char *b)
-{
- char *ae, *be;
- unsigned long av, bv;
-
- av = strtoul(a, &ae, 10);
- bv = strtoul(b, &be, 10);
- if (av != bv)
- return (av < bv ? -1 : +1);
- if (*ae == '.')
- ae++;
- if (*be == '.')
- be++;
- av = strtoul(ae, &ae, 10);
- bv = strtoul(be, &be, 10);
- if (av != bv)
- return (av < bv ? -1 : +1);
- return 0;
-}
-
-/*
- * Utility routines for putting an SSH-protocol `string' and
- * `uint32' into a hash state.
- */
-static void hash_string(const struct ssh_hash *h, void *s, void *str, int len)
-{
- unsigned char lenblk[4];
- PUT_32BIT(lenblk, len);
- h->bytes(s, lenblk, 4);
- h->bytes(s, str, len);
-}
-
-static void hash_uint32(const struct ssh_hash *h, void *s, unsigned i)
-{
- unsigned char intblk[4];
- PUT_32BIT(intblk, i);
- h->bytes(s, intblk, 4);
-}
-
-/*
- * Packet construction functions. Mostly shared between SSH-1 and SSH-2.
- */
-static void ssh_pkt_ensure(struct Packet *pkt, int length)
-{
- if (pkt->maxlen < length) {
- unsigned char *body = pkt->body;
- int offset = body ? body - pkt->data : 0;
- pkt->maxlen = length + 256;
- pkt->data = sresize(pkt->data, pkt->maxlen + APIEXTRA, unsigned char);
- if (body) pkt->body = pkt->data + offset;
- }
-}
-static void ssh_pkt_adddata(struct Packet *pkt, void *data, int len)
-{
- if (pkt->logmode != PKTLOG_EMIT) {
- pkt->nblanks++;
- pkt->blanks = sresize(pkt->blanks, pkt->nblanks, struct logblank_t);
- assert(pkt->body);
- pkt->blanks[pkt->nblanks-1].offset = pkt->length -
- (pkt->body - pkt->data);
- pkt->blanks[pkt->nblanks-1].len = len;
- pkt->blanks[pkt->nblanks-1].type = pkt->logmode;
- }
- pkt->length += len;
- ssh_pkt_ensure(pkt, pkt->length);
- memcpy(pkt->data + pkt->length - len, data, len);
-}
-static void ssh_pkt_addbyte(struct Packet *pkt, unsigned char byte)
-{
- ssh_pkt_adddata(pkt, &byte, 1);
-}
-static void ssh2_pkt_addbool(struct Packet *pkt, unsigned char value)
-{
- ssh_pkt_adddata(pkt, &value, 1);
-}
-static void ssh_pkt_adduint32(struct Packet *pkt, unsigned long value)
-{
- unsigned char x[4];
- PUT_32BIT(x, value);
- ssh_pkt_adddata(pkt, x, 4);
-}
-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, 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, 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(struct Packet *pkt, char *data)
-{
- ssh_pkt_addstring_start(pkt);
- ssh_pkt_addstring_str(pkt, data);
-}
-static void ssh1_pkt_addmp(struct Packet *pkt, Bignum b)
-{
- int len = ssh1_bignum_length(b);
- unsigned char *data = snewn(len, unsigned char);
- (void) ssh1_write_bignum(data, b);
- ssh_pkt_adddata(pkt, data, len);
- sfree(data);
-}
-static unsigned char *ssh2_mpint_fmt(Bignum b, int *len)
-{
- unsigned char *p;
- int i, n = (bignum_bitcount(b) + 7) / 8;
- p = snewn(n + 1, unsigned char);
- p[0] = 0;
- for (i = 1; i <= n; i++)
- p[i] = bignum_byte(b, n - i);
- i = 0;
- while (i <= n && p[i] == 0 && (p[i + 1] & 0x80) == 0)
- i++;
- memmove(p, p + i, n + 1 - i);
- *len = n + 1 - i;
- return p;
-}
-static void ssh2_pkt_addmp(struct Packet *pkt, Bignum b)
-{
- unsigned char *p;
- int len;
- p = ssh2_mpint_fmt(b, &len);
- ssh_pkt_addstring_start(pkt);
- ssh_pkt_addstring_data(pkt, (char *)p, len);
- sfree(p);
-}
-
-static struct Packet *ssh1_pkt_init(int pkt_type)
-{
- struct Packet *pkt = ssh_new_packet();
- pkt->length = 4 + 8; /* space for length + max padding */
- ssh_pkt_addbyte(pkt, pkt_type);
- pkt->body = pkt->data + pkt->length;
- return pkt;
-}
-
-/* For legacy code (SSH-1 and -2 packet construction used to be separate) */
-#define ssh2_pkt_ensure(pkt, length) ssh_pkt_ensure(pkt, length)
-#define ssh2_pkt_adddata(pkt, data, len) ssh_pkt_adddata(pkt, data, len)
-#define ssh2_pkt_addbyte(pkt, byte) ssh_pkt_addbyte(pkt, byte)
-#define ssh2_pkt_adduint32(pkt, value) ssh_pkt_adduint32(pkt, value)
-#define ssh2_pkt_addstring_start(pkt) ssh_pkt_addstring_start(pkt)
-#define ssh2_pkt_addstring_str(pkt, data) ssh_pkt_addstring_str(pkt, data)
-#define ssh2_pkt_addstring_data(pkt, data, len) ssh_pkt_addstring_data(pkt, data, len)
-#define ssh2_pkt_addstring(pkt, data) ssh_pkt_addstring(pkt, data)
-
-static struct Packet *ssh2_pkt_init(int pkt_type)
-{
- struct Packet *pkt = ssh_new_packet();
- pkt->length = 5; /* space for packet length + padding length */
- pkt->forcepad = 0;
- ssh_pkt_addbyte(pkt, (unsigned char) pkt_type);
- pkt->body = pkt->data + pkt->length; /* after packet type */
- return pkt;
-}
-
-/*
- * Construct an SSH-2 final-form packet: compress it, encrypt it,
- * put the MAC on it. Final packet, ready to be sent, is stored in
- * pkt->data. Total length is returned.
- */
-static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt)
-{
- int cipherblk, maclen, padding, i;
-
- if (ssh->logctx)
- log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[5],
- ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, pkt->data[5]),
- pkt->body, pkt->length - (pkt->body - pkt->data),
- pkt->nblanks, pkt->blanks, &ssh->v2_outgoing_sequence);
- sfree(pkt->blanks); pkt->blanks = NULL;
- pkt->nblanks = 0;
-
- /*
- * Compress packet payload.
- */
- {
- unsigned char *newpayload;
- int newlen;
- if (ssh->cscomp &&
- ssh->cscomp->compress(ssh->cs_comp_ctx, pkt->data + 5,
- pkt->length - 5,
- &newpayload, &newlen)) {
- pkt->length = 5;
- ssh2_pkt_adddata(pkt, newpayload, newlen);
- sfree(newpayload);
- }
- }
-
- /*
- * Add padding. At least four bytes, and must also bring total
- * length (minus MAC) up to a multiple of the block size.
- * If pkt->forcepad is set, make sure the packet is at least that size
- * after padding.
- */
- cipherblk = ssh->cscipher ? ssh->cscipher->blksize : 8; /* block size */
- cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */
- padding = 4;
- if (pkt->length + padding < pkt->forcepad)
- padding = pkt->forcepad - pkt->length;
- padding +=
- (cipherblk - (pkt->length + padding) % cipherblk) % cipherblk;
- assert(padding <= 255);
- maclen = ssh->csmac ? ssh->csmac->len : 0;
- ssh2_pkt_ensure(pkt, pkt->length + padding + maclen);
- pkt->data[4] = padding;
- 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);
-
- pkt->encrypted_len = pkt->length + padding;
-
- /* Ready-to-send packet starts at pkt->data. We return length. */
- return pkt->length + padding + maclen;
-}
-
-/*
- * Routines called from the main SSH code to send packets. There
- * are quite a few of these, because we have two separate
- * mechanisms for delaying the sending of packets:
- *
- * - In order to send an IGNORE message and a password message in
- * a single fixed-length blob, we require the ability to
- * concatenate the encrypted forms of those two packets _into_ a
- * single blob and then pass it to our <network.h> transport
- * layer in one go. Hence, there's a deferment mechanism which
- * works after packet encryption.
- *
- * - In order to avoid sending any connection-layer messages
- * during repeat key exchange, we have to queue up any such
- * outgoing messages _before_ they are encrypted (and in
- * particular before they're allocated sequence numbers), and
- * then send them once we've finished.
- *
- * I call these mechanisms `defer' and `queue' respectively, so as
- * to distinguish them reasonably easily.
- *
- * The functions send_noqueue() and defer_noqueue() free the packet
- * structure they are passed. Every outgoing packet goes through
- * precisely one of these functions in its life; packets passed to
- * ssh2_pkt_send() or ssh2_pkt_defer() either go straight to one of
- * these or get queued, and then when the queue is later emptied
- * the packets are all passed to defer_noqueue().
- *
- * When using a CBC-mode cipher, it's necessary to ensure that an
- * attacker can't provide data to be encrypted using an IV that they
- * know. We ensure this by prefixing each packet that might contain
- * user data with an SSH_MSG_IGNORE. This is done using the deferral
- * mechanism, so in this case send_noqueue() ends up redirecting to
- * defer_noqueue(). If you don't like this inefficiency, don't use
- * CBC.
- */
-
-static void ssh2_pkt_defer_noqueue(Ssh, struct Packet *, int);
-static void ssh_pkt_defersend(Ssh);
-
-/*
- * Send an SSH-2 packet immediately, without queuing or deferring.
- */
-static void ssh2_pkt_send_noqueue(Ssh ssh, struct Packet *pkt)
-{
- int len;
- int backlog;
- if (ssh->cscipher != NULL && (ssh->cscipher->flags & SSH_CIPHER_IS_CBC)) {
- /* We need to send two packets, so use the deferral mechanism. */
- ssh2_pkt_defer_noqueue(ssh, pkt, FALSE);
- ssh_pkt_defersend(ssh);
- return;
- }
- len = ssh2_pkt_construct(ssh, pkt);
- backlog = s_write(ssh, pkt->data, len);
- if (backlog > SSH_MAX_BACKLOG)
- ssh_throttle_all(ssh, 1, backlog);
-
- ssh->outgoing_data_size += pkt->encrypted_len;
- if (!ssh->kex_in_progress &&
- ssh->max_data_size != 0 &&
- ssh->outgoing_data_size > ssh->max_data_size)
- do_ssh2_transport(ssh, "too much data sent", -1, NULL);
-
- ssh_free_packet(pkt);
-}
-
-/*
- * Defer an SSH-2 packet.
- */
-static void ssh2_pkt_defer_noqueue(Ssh ssh, struct Packet *pkt, int noignore)
-{
- int len;
- if (ssh->cscipher != NULL && (ssh->cscipher->flags & SSH_CIPHER_IS_CBC) &&
- ssh->deferred_len == 0 && !noignore &&
- !(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) {
- /*
- * Interpose an SSH_MSG_IGNORE to ensure that user data don't
- * get encrypted with a known IV.
- */
- struct Packet *ipkt = ssh2_pkt_init(SSH2_MSG_IGNORE);
- ssh2_pkt_addstring_start(ipkt);
- ssh2_pkt_defer_noqueue(ssh, ipkt, TRUE);
- }
- len = ssh2_pkt_construct(ssh, pkt);
- if (ssh->deferred_len + len > ssh->deferred_size) {
- ssh->deferred_size = ssh->deferred_len + len + 128;
- ssh->deferred_send_data = sresize(ssh->deferred_send_data,
- ssh->deferred_size,
- unsigned char);
- }
- memcpy(ssh->deferred_send_data + ssh->deferred_len, pkt->data, len);
- ssh->deferred_len += len;
- ssh->deferred_data_size += pkt->encrypted_len;
- ssh_free_packet(pkt);
-}
-
-/*
- * Queue an SSH-2 packet.
- */
-static void ssh2_pkt_queue(Ssh ssh, struct Packet *pkt)
-{
- assert(ssh->queueing);
-
- if (ssh->queuelen >= ssh->queuesize) {
- ssh->queuesize = ssh->queuelen + 32;
- ssh->queue = sresize(ssh->queue, ssh->queuesize, struct Packet *);
- }
-
- ssh->queue[ssh->queuelen++] = pkt;
-}
-
-/*
- * Either queue or send a packet, depending on whether queueing is
- * set.
- */
-static void ssh2_pkt_send(Ssh ssh, struct Packet *pkt)
-{
- if (ssh->queueing)
- ssh2_pkt_queue(ssh, pkt);
- else
- ssh2_pkt_send_noqueue(ssh, pkt);
-}
-
-/*
- * Either queue or defer a packet, depending on whether queueing is
- * set.
- */
-static void ssh2_pkt_defer(Ssh ssh, struct Packet *pkt)
-{
- if (ssh->queueing)
- ssh2_pkt_queue(ssh, pkt);
- else
- ssh2_pkt_defer_noqueue(ssh, pkt, FALSE);
-}
-
-/*
- * Send the whole deferred data block constructed by
- * ssh2_pkt_defer() or SSH-1's defer_packet().
- *
- * The expected use of the defer mechanism is that you call
- * ssh2_pkt_defer() a few times, then call ssh_pkt_defersend(). If
- * not currently queueing, this simply sets up deferred_send_data
- * and then sends it. If we _are_ currently queueing, the calls to
- * ssh2_pkt_defer() put the deferred packets on to the queue
- * instead, and therefore ssh_pkt_defersend() has no deferred data
- * to send. Hence, there's no need to make it conditional on
- * ssh->queueing.
- */
-static void ssh_pkt_defersend(Ssh ssh)
-{
- int backlog;
- backlog = s_write(ssh, ssh->deferred_send_data, ssh->deferred_len);
- ssh->deferred_len = ssh->deferred_size = 0;
- sfree(ssh->deferred_send_data);
- ssh->deferred_send_data = NULL;
- if (backlog > SSH_MAX_BACKLOG)
- ssh_throttle_all(ssh, 1, backlog);
-
- ssh->outgoing_data_size += ssh->deferred_data_size;
- if (!ssh->kex_in_progress &&
- ssh->max_data_size != 0 &&
- ssh->outgoing_data_size > ssh->max_data_size)
- do_ssh2_transport(ssh, "too much data sent", -1, NULL);
- ssh->deferred_data_size = 0;
-}
-
-/*
- * Send a packet whose length needs to be disguised (typically
- * passwords or keyboard-interactive responses).
- */
-static void ssh2_pkt_send_with_padding(Ssh ssh, struct Packet *pkt,
- int padsize)
-{
-#if 0
- if (0) {
- /*
- * The simplest way to do this is to adjust the
- * variable-length padding field in the outgoing packet.
- *
- * Currently compiled out, because some Cisco SSH servers
- * don't like excessively padded packets (bah, why's it
- * always Cisco?)
- */
- pkt->forcepad = padsize;
- ssh2_pkt_send(ssh, pkt);
- } else
-#endif
- {
- /*
- * If we can't do that, however, an alternative approach is
- * to use the pkt_defer mechanism to bundle the packet
- * tightly together with an SSH_MSG_IGNORE such that their
- * combined length is a constant. So first we construct the
- * final form of this packet and defer its sending.
- */
- ssh2_pkt_defer(ssh, pkt);
-
- /*
- * Now construct an SSH_MSG_IGNORE which includes a string
- * that's an exact multiple of the cipher block size. (If
- * the cipher is NULL so that the block size is
- * unavailable, we don't do this trick at all, because we
- * gain nothing by it.)
- */
- if (ssh->cscipher &&
- !(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) {
- int stringlen, i;
-
- stringlen = (256 - ssh->deferred_len);
- stringlen += ssh->cscipher->blksize - 1;
- stringlen -= (stringlen % ssh->cscipher->blksize);
- if (ssh->cscomp) {
- /*
- * Temporarily disable actual compression, so we
- * can guarantee to get this string exactly the
- * length we want it. The compression-disabling
- * routine should return an integer indicating how
- * many bytes we should adjust our string length
- * by.
- */
- stringlen -=
- ssh->cscomp->disable_compression(ssh->cs_comp_ctx);
- }
- pkt = ssh2_pkt_init(SSH2_MSG_IGNORE);
- ssh2_pkt_addstring_start(pkt);
- for (i = 0; i < stringlen; i++) {
- char c = (char) random_byte();
- ssh2_pkt_addstring_data(pkt, &c, 1);
- }
- ssh2_pkt_defer(ssh, pkt);
- }
- ssh_pkt_defersend(ssh);
- }
-}
-
-/*
- * Send all queued SSH-2 packets. We send them by means of
- * ssh2_pkt_defer_noqueue(), in case they included a pair of
- * packets that needed to be lumped together.
- */
-static void ssh2_pkt_queuesend(Ssh ssh)
-{
- int i;
-
- assert(!ssh->queueing);
-
- for (i = 0; i < ssh->queuelen; i++)
- ssh2_pkt_defer_noqueue(ssh, ssh->queue[i], FALSE);
- ssh->queuelen = 0;
-
- ssh_pkt_defersend(ssh);
-}
-
-#if 0
-void bndebug(char *string, Bignum b)
-{
- unsigned char *p;
- int i, len;
- p = ssh2_mpint_fmt(b, &len);
- debug(("%s", string));
- for (i = 0; i < len; i++)
- debug((" %02x", p[i]));
- debug(("\n"));
- sfree(p);
-}
-#endif
-
-static void hash_mpint(const struct ssh_hash *h, void *s, Bignum b)
-{
- unsigned char *p;
- int len;
- p = ssh2_mpint_fmt(b, &len);
- hash_string(h, s, p, len);
- sfree(p);
-}
-
-/*
- * Packet decode functions for both SSH-1 and SSH-2.
- */
-static unsigned long ssh_pkt_getuint32(struct Packet *pkt)
-{
- unsigned long value;
- if (pkt->length - pkt->savedpos < 4)
- return 0; /* arrgh, no way to decline (FIXME?) */
- value = GET_32BIT(pkt->body + pkt->savedpos);
- pkt->savedpos += 4;
- return value;
-}
-static int ssh2_pkt_getbool(struct Packet *pkt)
-{
- unsigned long value;
- if (pkt->length - pkt->savedpos < 1)
- return 0; /* arrgh, no way to decline (FIXME?) */
- value = pkt->body[pkt->savedpos] != 0;
- pkt->savedpos++;
- return value;
-}
-static void ssh_pkt_getstring(struct Packet *pkt, char **p, int *length)
-{
- int len;
- *p = NULL;
- *length = 0;
- if (pkt->length - pkt->savedpos < 4)
- return;
- len = GET_32BIT(pkt->body + pkt->savedpos);
- if (len < 0)
- return;
- *length = len;
- pkt->savedpos += 4;
- if (pkt->length - pkt->savedpos < *length)
- return;
- *p = (char *)(pkt->body + pkt->savedpos);
- pkt->savedpos += *length;
-}
-static void *ssh_pkt_getdata(struct Packet *pkt, int length)
-{
- if (pkt->length - pkt->savedpos < length)
- return NULL;
- pkt->savedpos += length;
- return pkt->body + (pkt->savedpos - length);
-}
-static int ssh1_pkt_getrsakey(struct Packet *pkt, struct RSAKey *key,
- unsigned char **keystr)
-{
- int j;
-
- j = makekey(pkt->body + pkt->savedpos,
- pkt->length - pkt->savedpos,
- key, keystr, 0);
-
- if (j < 0)
- return FALSE;
-
- pkt->savedpos += j;
- assert(pkt->savedpos < pkt->length);
-
- return TRUE;
-}
-static Bignum ssh1_pkt_getmp(struct Packet *pkt)
-{
- int j;
- Bignum b;
-
- j = ssh1_read_bignum(pkt->body + pkt->savedpos,
- pkt->length - pkt->savedpos, &b);
-
- if (j < 0)
- return NULL;
-
- pkt->savedpos += j;
- return b;
-}
-static Bignum ssh2_pkt_getmp(struct Packet *pkt)
-{
- char *p;
- int length;
- Bignum b;
-
- ssh_pkt_getstring(pkt, &p, &length);
- if (!p)
- return NULL;
- if (p[0] & 0x80)
- return NULL;
- b = bignum_from_bytes((unsigned char *)p, length);
- return b;
-}
-
-/*
- * Helper function to add an SSH-2 signature blob to a packet.
- * Expects to be shown the public key blob as well as the signature
- * blob. Normally works just like ssh2_pkt_addstring, but will
- * fiddle with the signature packet if necessary for
- * BUG_SSH2_RSA_PADDING.
- */
-static void ssh2_add_sigblob(Ssh ssh, struct Packet *pkt,
- void *pkblob_v, int pkblob_len,
- void *sigblob_v, int sigblob_len)
-{
- unsigned char *pkblob = (unsigned char *)pkblob_v;
- unsigned char *sigblob = (unsigned char *)sigblob_v;
-
- /* dmemdump(pkblob, pkblob_len); */
- /* dmemdump(sigblob, sigblob_len); */
-
- /*
- * See if this is in fact an ssh-rsa signature and a buggy
- * server; otherwise we can just do this the easy way.
- */
- if ((ssh->remote_bugs & BUG_SSH2_RSA_PADDING) &&
- (GET_32BIT(pkblob) == 7 && !memcmp(pkblob+4, "ssh-rsa", 7))) {
- int pos, len, siglen;
-
- /*
- * Find the byte length of the modulus.
- */
-
- pos = 4+7; /* skip over "ssh-rsa" */
- pos += 4 + GET_32BIT(pkblob+pos); /* skip over exponent */
- len = GET_32BIT(pkblob+pos); /* find length of modulus */
- pos += 4; /* find modulus itself */
- while (len > 0 && pkblob[pos] == 0)
- len--, pos++;
- /* debug(("modulus length is %d\n", len)); */
-
- /*
- * Now find the signature integer.
- */
- pos = 4+7; /* skip over "ssh-rsa" */
- siglen = GET_32BIT(sigblob+pos);
- /* debug(("signature length is %d\n", siglen)); */
-
- if (len != siglen) {
- unsigned char newlen[4];
- ssh2_pkt_addstring_start(pkt);
- ssh2_pkt_addstring_data(pkt, (char *)sigblob, pos);
- /* dmemdump(sigblob, pos); */
- pos += 4; /* point to start of actual sig */
- PUT_32BIT(newlen, len);
- ssh2_pkt_addstring_data(pkt, (char *)newlen, 4);
- /* dmemdump(newlen, 4); */
- newlen[0] = 0;
- while (len-- > siglen) {
- ssh2_pkt_addstring_data(pkt, (char *)newlen, 1);
- /* dmemdump(newlen, 1); */
- }
- ssh2_pkt_addstring_data(pkt, (char *)(sigblob+pos), siglen);
- /* dmemdump(sigblob+pos, siglen); */
- return;
- }
-
- /* Otherwise fall through and do it the easy way. */
- }
-
- ssh2_pkt_addstring_start(pkt);
- ssh2_pkt_addstring_data(pkt, (char *)sigblob, sigblob_len);
-}
-
-/*
- * Examine the remote side's version string and compare it against
- * a list of known buggy implementations.
- */
-static void ssh_detect_bugs(Ssh ssh, char *vstring)
-{
- char *imp; /* pointer to implementation part */
- imp = vstring;
- imp += strcspn(imp, "-");
- if (*imp) imp++;
- imp += strcspn(imp, "-");
- if (*imp) imp++;
-
- ssh->remote_bugs = 0;
-
- /*
- * General notes on server version strings:
- * - Not all servers reporting "Cisco-1.25" have all the bugs listed
- * here -- in particular, we've heard of one that's perfectly happy
- * with SSH1_MSG_IGNOREs -- but this string never seems to change,
- * so we can't distinguish them.
- */
- if (conf_get_int(ssh->conf, CONF_sshbug_ignore1) == FORCE_ON ||
- (conf_get_int(ssh->conf, CONF_sshbug_ignore1) == AUTO &&
- (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") ||
- !strcmp(imp, "1.2.20") || !strcmp(imp, "1.2.21") ||
- !strcmp(imp, "1.2.22") || !strcmp(imp, "Cisco-1.25") ||
- !strcmp(imp, "OSU_1.4alpha3") || !strcmp(imp, "OSU_1.5alpha4")))) {
- /*
- * These versions don't support SSH1_MSG_IGNORE, so we have
- * to use a different defence against password length
- * sniffing.
- */
- ssh->remote_bugs |= BUG_CHOKES_ON_SSH1_IGNORE;
- logevent("We believe remote version has SSH-1 ignore bug");
- }
-
- if (conf_get_int(ssh->conf, CONF_sshbug_plainpw1) == FORCE_ON ||
- (conf_get_int(ssh->conf, CONF_sshbug_plainpw1) == AUTO &&
- (!strcmp(imp, "Cisco-1.25") || !strcmp(imp, "OSU_1.4alpha3")))) {
- /*
- * These versions need a plain password sent; they can't
- * handle having a null and a random length of data after
- * the password.
- */
- ssh->remote_bugs |= BUG_NEEDS_SSH1_PLAIN_PASSWORD;
- logevent("We believe remote version needs a plain SSH-1 password");
- }
-
- if (conf_get_int(ssh->conf, CONF_sshbug_rsa1) == FORCE_ON ||
- (conf_get_int(ssh->conf, CONF_sshbug_rsa1) == AUTO &&
- (!strcmp(imp, "Cisco-1.25")))) {
- /*
- * These versions apparently have no clue whatever about
- * RSA authentication and will panic and die if they see
- * an AUTH_RSA message.
- */
- ssh->remote_bugs |= BUG_CHOKES_ON_RSA;
- logevent("We believe remote version can't handle SSH-1 RSA authentication");
- }
-
- if (conf_get_int(ssh->conf, CONF_sshbug_hmac2) == FORCE_ON ||
- (conf_get_int(ssh->conf, CONF_sshbug_hmac2) == AUTO &&
- !wc_match("* VShell", imp) &&
- (wc_match("2.1.0*", imp) || wc_match("2.0.*", imp) ||
- wc_match("2.2.0*", imp) || wc_match("2.3.0*", imp) ||
- wc_match("2.1 *", imp)))) {
- /*
- * These versions have the HMAC bug.
- */
- ssh->remote_bugs |= BUG_SSH2_HMAC;
- logevent("We believe remote version has SSH-2 HMAC bug");
- }
-
- if (conf_get_int(ssh->conf, CONF_sshbug_derivekey2) == FORCE_ON ||
- (conf_get_int(ssh->conf, CONF_sshbug_derivekey2) == AUTO &&
- !wc_match("* VShell", imp) &&
- (wc_match("2.0.0*", imp) || wc_match("2.0.10*", imp) ))) {
- /*
- * These versions have the key-derivation bug (failing to
- * include the literal shared secret in the hashes that
- * generate the keys).
- */
- ssh->remote_bugs |= BUG_SSH2_DERIVEKEY;
- logevent("We believe remote version has SSH-2 key-derivation bug");
- }
-
- if (conf_get_int(ssh->conf, CONF_sshbug_rsapad2) == FORCE_ON ||
- (conf_get_int(ssh->conf, CONF_sshbug_rsapad2) == AUTO &&
- (wc_match("OpenSSH_2.[5-9]*", imp) ||
- wc_match("OpenSSH_3.[0-2]*", imp)))) {
- /*
- * These versions have the SSH-2 RSA padding bug.
- */
- ssh->remote_bugs |= BUG_SSH2_RSA_PADDING;
- logevent("We believe remote version has SSH-2 RSA padding bug");
- }
-
- if (conf_get_int(ssh->conf, CONF_sshbug_pksessid2) == FORCE_ON ||
- (conf_get_int(ssh->conf, CONF_sshbug_pksessid2) == AUTO &&
- wc_match("OpenSSH_2.[0-2]*", imp))) {
- /*
- * These versions have the SSH-2 session-ID bug in
- * public-key authentication.
- */
- ssh->remote_bugs |= BUG_SSH2_PK_SESSIONID;
- logevent("We believe remote version has SSH-2 public-key-session-ID bug");
- }
-
- if (conf_get_int(ssh->conf, CONF_sshbug_rekey2) == FORCE_ON ||
- (conf_get_int(ssh->conf, CONF_sshbug_rekey2) == AUTO &&
- (wc_match("DigiSSH_2.0", imp) ||
- wc_match("OpenSSH_2.[0-4]*", imp) ||
- wc_match("OpenSSH_2.5.[0-3]*", imp) ||
- wc_match("Sun_SSH_1.0", imp) ||
- wc_match("Sun_SSH_1.0.1", imp) ||
- /* All versions <= 1.2.6 (they changed their format in 1.2.7) */
- wc_match("WeOnlyDo-*", imp)))) {
- /*
- * These versions have the SSH-2 rekey bug.
- */
- ssh->remote_bugs |= BUG_SSH2_REKEY;
- logevent("We believe remote version has SSH-2 rekey bug");
- }
-
- if (conf_get_int(ssh->conf, CONF_sshbug_maxpkt2) == FORCE_ON ||
- (conf_get_int(ssh->conf, CONF_sshbug_maxpkt2) == AUTO &&
- (wc_match("1.36_sshlib GlobalSCAPE", imp) ||
- wc_match("1.36 sshlib: GlobalScape", imp)))) {
- /*
- * This version ignores our makpkt and needs to be throttled.
- */
- ssh->remote_bugs |= BUG_SSH2_MAXPKT;
- logevent("We believe remote version ignores SSH-2 maximum packet size");
- }
-
- if (conf_get_int(ssh->conf, CONF_sshbug_ignore2) == FORCE_ON) {
- /*
- * Servers that don't support SSH2_MSG_IGNORE. Currently,
- * none detected automatically.
- */
- ssh->remote_bugs |= BUG_CHOKES_ON_SSH2_IGNORE;
- logevent("We believe remote version has SSH-2 ignore bug");
- }
-}
-
-/*
- * The `software version' part of an SSH version string is required
- * to contain no spaces or minus signs.
- */
-static void ssh_fix_verstring(char *str)
-{
- /* Eat "SSH-<protoversion>-". */
- assert(*str == 'S'); str++;
- assert(*str == 'S'); str++;
- assert(*str == 'H'); str++;
- assert(*str == '-'); str++;
- while (*str && *str != '-') str++;
- assert(*str == '-'); str++;
-
- /* Convert minus signs and spaces in the remaining string into
- * underscores. */
- while (*str) {
- if (*str == '-' || *str == ' ')
- *str = '_';
- str++;
- }
-}
-
-/*
- * Send an appropriate SSH version string.
- */
-static void ssh_send_verstring(Ssh ssh, char *svers)
-{
- char *verstring;
-
- if (ssh->version == 2) {
- /*
- * Construct a v2 version string.
- */
- verstring = dupprintf("SSH-2.0-%s\015\012", sshver);
- } else {
- /*
- * Construct a v1 version string.
- */
- verstring = dupprintf("SSH-%s-%s\012",
- (ssh_versioncmp(svers, "1.5") <= 0 ?
- svers : "1.5"),
- sshver);
- }
-
- ssh_fix_verstring(verstring);
-
- if (ssh->version == 2) {
- size_t len;
- /*
- * Record our version string.
- */
- len = strcspn(verstring, "\015\012");
- ssh->v_c = snewn(len + 1, char);
- memcpy(ssh->v_c, verstring, len);
- ssh->v_c[len] = 0;
- }
-
- logeventf(ssh, "We claim version: %.*s",
- strcspn(verstring, "\015\012"), verstring);
- s_write(ssh, verstring, strlen(verstring));
- sfree(verstring);
-}
-
-static int do_ssh_init(Ssh ssh, unsigned char c)
-{
- struct do_ssh_init_state {
- int vslen;
- char version[10];
- char *vstring;
- int vstrsize;
- int i;
- int proto1, proto2;
- };
- crState(do_ssh_init_state);
-
- crBegin(ssh->do_ssh_init_crstate);
-
- /* Search for a line beginning with the string "SSH-" in the input. */
- for (;;) {
- if (c != 'S') goto no;
- crReturn(1);
- if (c != 'S') goto no;
- crReturn(1);
- if (c != 'H') goto no;
- crReturn(1);
- if (c != '-') goto no;
- break;
- no:
- while (c != '\012')
- crReturn(1);
- crReturn(1);
- }
-
- s->vstrsize = 16;
- s->vstring = snewn(s->vstrsize, char);
- strcpy(s->vstring, "SSH-");
- s->vslen = 4;
- s->i = 0;
- while (1) {
- crReturn(1); /* get another char */
- if (s->vslen >= s->vstrsize - 1) {
- s->vstrsize += 16;
- s->vstring = sresize(s->vstring, s->vstrsize, char);
- }
- s->vstring[s->vslen++] = c;
- if (s->i >= 0) {
- if (c == '-') {
- s->version[s->i] = '\0';
- s->i = -1;
- } else if (s->i < sizeof(s->version) - 1)
- s->version[s->i++] = c;
- } else if (c == '\012')
- break;
- }
-
- ssh->agentfwd_enabled = FALSE;
- ssh->rdpkt2_state.incoming_sequence = 0;
-
- s->vstring[s->vslen] = 0;
- s->vstring[strcspn(s->vstring, "\015\012")] = '\0';/* remove EOL chars */
- logeventf(ssh, "Server version: %s", s->vstring);
- ssh_detect_bugs(ssh, s->vstring);
-
- /*
- * Decide which SSH protocol version to support.
- */
-
- /* Anything strictly below "2.0" means protocol 1 is supported. */
- s->proto1 = ssh_versioncmp(s->version, "2.0") < 0;
- /* Anything greater or equal to "1.99" means protocol 2 is supported. */
- s->proto2 = ssh_versioncmp(s->version, "1.99") >= 0;
-
- if (conf_get_int(ssh->conf, CONF_sshprot) == 0 && !s->proto1) {
- bombout(("SSH protocol version 1 required by user but not provided by server"));
- crStop(0);
- }
- if (conf_get_int(ssh->conf, CONF_sshprot) == 3 && !s->proto2) {
- bombout(("SSH protocol version 2 required by user but not provided by server"));
- crStop(0);
- }
-
- if (s->proto2 && (conf_get_int(ssh->conf, CONF_sshprot) >= 2 || !s->proto1))
- ssh->version = 2;
- else
- ssh->version = 1;
-
- logeventf(ssh, "Using SSH protocol version %d", ssh->version);
-
- /* Send the version string, if we haven't already */
- if (conf_get_int(ssh->conf, CONF_sshprot) != 3)
- ssh_send_verstring(ssh, s->version);
-
- if (ssh->version == 2) {
- size_t len;
- /*
- * Record their version string.
- */
- len = strcspn(s->vstring, "\015\012");
- ssh->v_s = snewn(len + 1, char);
- memcpy(ssh->v_s, s->vstring, len);
- ssh->v_s[len] = 0;
-
- /*
- * Initialise SSH-2 protocol.
- */
- ssh->protocol = ssh2_protocol;
- ssh2_protocol_setup(ssh);
- ssh->s_rdpkt = ssh2_rdpkt;
- } else {
- /*
- * Initialise SSH-1 protocol.
- */
- ssh->protocol = ssh1_protocol;
- ssh1_protocol_setup(ssh);
- ssh->s_rdpkt = ssh1_rdpkt;
- }
- if (ssh->version == 2)
- do_ssh2_transport(ssh, NULL, -1, NULL);
-
- update_specials_menu(ssh->frontend);
- ssh->state = SSH_STATE_BEFORE_SIZE;
- ssh->pinger = pinger_new(ssh->conf, &ssh_backend, ssh);
-
- sfree(s->vstring);
-
- crFinish(0);
-}
-
-static void ssh_process_incoming_data(Ssh ssh,
- unsigned char **data, int *datalen)
-{
- struct Packet *pktin;
-
- pktin = ssh->s_rdpkt(ssh, data, datalen);
- if (pktin) {
- ssh->protocol(ssh, NULL, 0, pktin);
- ssh_free_packet(pktin);
- }
-}
-
-static void ssh_queue_incoming_data(Ssh ssh,
- unsigned char **data, int *datalen)
-{
- bufchain_add(&ssh->queued_incoming_data, *data, *datalen);
- *data += *datalen;
- *datalen = 0;
-}
-
-static void ssh_process_queued_incoming_data(Ssh ssh)
-{
- void *vdata;
- unsigned char *data;
- int len, origlen;
-
- while (!ssh->frozen && bufchain_size(&ssh->queued_incoming_data)) {
- bufchain_prefix(&ssh->queued_incoming_data, &vdata, &len);
- data = vdata;
- origlen = len;
-
- while (!ssh->frozen && len > 0)
- ssh_process_incoming_data(ssh, &data, &len);
-
- if (origlen > len)
- bufchain_consume(&ssh->queued_incoming_data, origlen - len);
- }
-}
-
-static void ssh_set_frozen(Ssh ssh, int frozen)
-{
- if (ssh->s)
- sk_set_frozen(ssh->s, frozen);
- ssh->frozen = frozen;
-}
-
-static void ssh_gotdata(Ssh ssh, unsigned char *data, int datalen)
-{
- /* Log raw data, if we're in that mode. */
- if (ssh->logctx)
- log_packet(ssh->logctx, PKT_INCOMING, -1, NULL, data, datalen,
- 0, NULL, NULL);
-
- crBegin(ssh->ssh_gotdata_crstate);
-
- /*
- * To begin with, feed the characters one by one to the
- * protocol initialisation / selection function do_ssh_init().
- * When that returns 0, we're done with the initial greeting
- * exchange and can move on to packet discipline.
- */
- while (1) {
- int ret; /* need not be kept across crReturn */
- if (datalen == 0)
- crReturnV; /* more data please */
- ret = do_ssh_init(ssh, *data);
- data++;
- datalen--;
- if (ret == 0)
- break;
- }
-
- /*
- * We emerge from that loop when the initial negotiation is
- * over and we have selected an s_rdpkt function. Now pass
- * everything to s_rdpkt, and then pass the resulting packets
- * to the proper protocol handler.
- */
-
- while (1) {
- while (bufchain_size(&ssh->queued_incoming_data) > 0 || datalen > 0) {
- if (ssh->frozen) {
- ssh_queue_incoming_data(ssh, &data, &datalen);
- /* This uses up all data and cannot cause anything interesting
- * to happen; indeed, for anything to happen at all, we must
- * return, so break out. */
- break;
- } else if (bufchain_size(&ssh->queued_incoming_data) > 0) {
- /* This uses up some or all data, and may freeze the
- * session. */
- ssh_process_queued_incoming_data(ssh);
- } else {
- /* This uses up some or all data, and may freeze the
- * session. */
- ssh_process_incoming_data(ssh, &data, &datalen);
- }
- /* FIXME this is probably EBW. */
- if (ssh->state == SSH_STATE_CLOSED)
- return;
- }
- /* We're out of data. Go and get some more. */
- crReturnV;
- }
- crFinishV;
-}
-
-static int ssh_do_close(Ssh ssh, int notify_exit)
-{
- int ret = 0;
- struct ssh_channel *c;
-
- ssh->state = SSH_STATE_CLOSED;
- expire_timer_context(ssh);
- if (ssh->s) {
- sk_close(ssh->s);
- ssh->s = NULL;
- if (notify_exit)
- notify_remote_exit(ssh->frontend);
- else
- ret = 1;
- }
- /*
- * Now we must shut down any port- and X-forwarded channels going
- * through this connection.
- */
- if (ssh->channels) {
- while (NULL != (c = index234(ssh->channels, 0))) {
- switch (c->type) {
- case CHAN_X11:
- x11_close(c->u.x11.s);
- break;
- case CHAN_SOCKDATA:
- case CHAN_SOCKDATA_DORMANT:
- pfd_close(c->u.pfd.s);
- break;
- }
- del234(ssh->channels, c); /* moving next one to index 0 */
- if (ssh->version == 2)
- bufchain_clear(&c->v.v2.outbuffer);
- sfree(c);
- }
- }
- /*
- * Go through port-forwardings, and close any associated
- * listening sockets.
- */
- if (ssh->portfwds) {
- struct ssh_portfwd *pf;
- while (NULL != (pf = index234(ssh->portfwds, 0))) {
- /* Dispose of any listening socket. */
- if (pf->local)
- pfd_terminate(pf->local);
- del234(ssh->portfwds, pf); /* moving next one to index 0 */
- free_portfwd(pf);
- }
- freetree234(ssh->portfwds);
- ssh->portfwds = NULL;
- }
-
- return ret;
-}
-
-static void ssh_log(Plug plug, int type, SockAddr addr, int port,
- const char *error_msg, int error_code)
-{
- Ssh ssh = (Ssh) 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(msg);
- sfree(msg);
-}
-
-static int ssh_closing(Plug plug, const char *error_msg, int error_code,
- int calling_back)
-{
- Ssh ssh = (Ssh) plug;
- int need_notify = ssh_do_close(ssh, FALSE);
-
- if (!error_msg) {
- if (!ssh->close_expected)
- error_msg = "Server unexpectedly closed network connection";
- else
- error_msg = "Server closed network connection";
- }
-
- if (ssh->close_expected && ssh->clean_exit && ssh->exitcode < 0)
- ssh->exitcode = 0;
-
- if (need_notify)
- notify_remote_exit(ssh->frontend);
-
- if (error_msg)
- logevent(error_msg);
- if (!ssh->close_expected || !ssh->clean_exit)
- connection_fatal(ssh->frontend, "%s", error_msg);
- return 0;
-}
-
-static int ssh_receive(Plug plug, int urgent, char *data, int len)
-{
- Ssh ssh = (Ssh) plug;
- ssh_gotdata(ssh, (unsigned char *)data, len);
- if (ssh->state == SSH_STATE_CLOSED) {
- ssh_do_close(ssh, TRUE);
- return 0;
- }
- return 1;
-}
-
-static void ssh_sent(Plug plug, int bufsize)
-{
- Ssh ssh = (Ssh) plug;
- /*
- * If the send backlog on the SSH socket itself clears, we
- * should unthrottle the whole world if it was throttled.
- */
- if (bufsize < SSH_MAX_BACKLOG)
- 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 const struct plug_function_table fn_table = {
- ssh_log,
- ssh_closing,
- ssh_receive,
- ssh_sent,
- NULL
- };
-
- SockAddr addr;
- const char *err;
- char *loghost;
- int addressfamily, sshprot;
-
- loghost = conf_get_str(ssh->conf, CONF_loghost);
- if (*loghost) {
- char *colon;
-
- ssh->savedhost = dupstr(loghost);
- ssh->savedport = 22; /* default ssh port */
-
- /*
- * A colon suffix on savedhost also lets us affect
- * savedport.
- *
- * (FIXME: do something about IPv6 address literals here.)
- */
- colon = strrchr(ssh->savedhost, ':');
- if (colon) {
- *colon++ = '\0';
- if (*colon)
- ssh->savedport = atoi(colon);
- }
- } else {
- ssh->savedhost = dupstr(host);
- if (port < 0)
- port = 22; /* default ssh port */
- ssh->savedport = 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);
- if ((err = sk_addr_error(addr)) != NULL) {
- sk_addr_free(addr);
- return err;
- }
- ssh->fullhostname = dupstr(*realhost); /* save in case of GSSAPI */
-
- /*
- * Open socket.
- */
- ssh->fn = &fn_table;
- ssh->s = new_connection(addr, *realhost, port,
- 0, 1, nodelay, keepalive, (Plug) ssh, ssh->conf);
- if ((err = sk_socket_error(ssh->s)) != NULL) {
- ssh->s = NULL;
- notify_remote_exit(ssh->frontend);
- return err;
- }
-
- /*
- * If the SSH version number's fixed, set it now, and if it's SSH-2,
- * send the version string too.
- */
- sshprot = conf_get_int(ssh->conf, CONF_sshprot);
- if (sshprot == 0)
- ssh->version = 1;
- if (sshprot == 3) {
- ssh->version = 2;
- ssh_send_verstring(ssh, NULL);
- }
-
- /*
- * loghost, if configured, overrides realhost.
- */
- if (*loghost) {
- sfree(*realhost);
- *realhost = dupstr(loghost);
- }
-
- return NULL;
-}
-
-/*
- * Throttle or unthrottle the SSH connection.
- */
-static void ssh_throttle_conn(Ssh ssh, int adjust)
-{
- int old_count = ssh->conn_throttle_count;
- ssh->conn_throttle_count += adjust;
- assert(ssh->conn_throttle_count >= 0);
- if (ssh->conn_throttle_count && !old_count) {
- ssh_set_frozen(ssh, 1);
- } else if (!ssh->conn_throttle_count && old_count) {
- ssh_set_frozen(ssh, 0);
- }
-}
-
-/*
- * Throttle or unthrottle _all_ local data streams (for when sends
- * on the SSH connection itself back up).
- */
-static void ssh_throttle_all(Ssh ssh, int enable, int bufsize)
-{
- int i;
- struct ssh_channel *c;
-
- if (enable == ssh->throttled_all)
- return;
- ssh->throttled_all = enable;
- ssh->overall_bufsize = bufsize;
- if (!ssh->channels)
- return;
- for (i = 0; NULL != (c = index234(ssh->channels, i)); i++) {
- switch (c->type) {
- case CHAN_MAINSESSION:
- /*
- * This is treated separately, outside the switch.
- */
- break;
- case CHAN_X11:
- x11_override_throttle(c->u.x11.s, enable);
- break;
- case CHAN_AGENT:
- /* Agent channels require no buffer management. */
- break;
- case CHAN_SOCKDATA:
- pfd_override_throttle(c->u.pfd.s, enable);
- break;
- }
- }
-}
-
-static void ssh_agent_callback(void *sshv, void *reply, int replylen)
-{
- Ssh ssh = (Ssh) sshv;
-
- ssh->agent_response = reply;
- ssh->agent_response_len = replylen;
-
- if (ssh->version == 1)
- do_ssh1_login(ssh, NULL, -1, NULL);
- else
- do_ssh2_authconn(ssh, NULL, -1, NULL);
-}
-
-static void ssh_dialog_callback(void *sshv, int ret)
-{
- Ssh ssh = (Ssh) sshv;
-
- ssh->user_response = ret;
-
- if (ssh->version == 1)
- do_ssh1_login(ssh, NULL, -1, NULL);
- else
- do_ssh2_transport(ssh, NULL, -1, NULL);
-
- /*
- * This may have unfrozen the SSH connection, so do a
- * queued-data run.
- */
- ssh_process_queued_incoming_data(ssh);
-}
-
-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;
-
- if (!sentreply) {
- /* Fake SSH_AGENT_FAILURE. */
- sentreply = "\0\0\0\1\5";
- replylen = 5;
- }
- if (ssh->version == 2) {
- ssh2_add_channel_data(c, sentreply, replylen);
- ssh2_try_send(c);
- } else {
- send_packet(ssh, SSH1_MSG_CHANNEL_DATA,
- PKT_INT, c->remoteid,
- PKT_INT, replylen,
- PKTT_DATA,
- PKT_DATA, sentreply, replylen,
- PKTT_OTHER,
- PKT_END);
- }
- if (reply)
- sfree(reply);
-}
-
-/*
- * Client-initiated disconnection. Send a DISCONNECT if `wire_reason'
- * 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,
- int code, int clean_exit)
-{
- char *error;
- if (!client_reason)
- client_reason = wire_reason;
- if (client_reason)
- error = dupprintf("Disconnected: %s", client_reason);
- else
- error = dupstr("Disconnected");
- if (wire_reason) {
- if (ssh->version == 1) {
- send_packet(ssh, SSH1_MSG_DISCONNECT, PKT_STR, wire_reason,
- PKT_END);
- } else if (ssh->version == 2) {
- struct Packet *pktout = ssh2_pkt_init(SSH2_MSG_DISCONNECT);
- ssh2_pkt_adduint32(pktout, code);
- ssh2_pkt_addstring(pktout, wire_reason);
- ssh2_pkt_addstring(pktout, "en"); /* language tag */
- ssh2_pkt_send_noqueue(ssh, pktout);
- }
- }
- ssh->close_expected = TRUE;
- ssh->clean_exit = clean_exit;
- ssh_closing((Plug)ssh, error, 0, 0);
- sfree(error);
-}
-
-/*
- * Handle the key exchange and user authentication phases.
- */
-static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
- struct Packet *pktin)
-{
- int i, j, ret;
- unsigned char cookie[8], *ptr;
- struct RSAKey servkey, hostkey;
- struct MD5Context md5c;
- struct do_ssh1_login_state {
- int len;
- unsigned char *rsabuf, *keystr1, *keystr2;
- unsigned long supported_ciphers_mask, supported_auths_mask;
- int tried_publickey, tried_agent;
- int tis_auth_refused, ccard_auth_refused;
- unsigned char session_id[16];
- int cipher_type;
- void *publickey_blob;
- int publickey_bloblen;
- char *publickey_comment;
- int publickey_encrypted;
- prompts_t *cur_prompt;
- char c;
- int pwpkt_type;
- unsigned char request[5], *response, *p;
- int responselen;
- int keyi, nkeys;
- int authed;
- struct RSAKey key;
- Bignum challenge;
- char *commentp;
- int commentlen;
- int dlgret;
- Filename *keyfile;
- };
- crState(do_ssh1_login_state);
-
- crBegin(ssh->do_ssh1_login_crstate);
-
- if (!pktin)
- crWaitUntil(pktin);
-
- if (pktin->type != SSH1_SMSG_PUBLIC_KEY) {
- bombout(("Public key packet not received"));
- crStop(0);
- }
-
- logevent("Received public keys");
-
- ptr = ssh_pkt_getdata(pktin, 8);
- if (!ptr) {
- bombout(("SSH-1 public key packet stopped before random cookie"));
- crStop(0);
- }
- memcpy(cookie, ptr, 8);
-
- if (!ssh1_pkt_getrsakey(pktin, &servkey, &s->keystr1) ||
- !ssh1_pkt_getrsakey(pktin, &hostkey, &s->keystr2)) {
- bombout(("Failed to read SSH-1 public keys from public key packet"));
- crStop(0);
- }
-
- /*
- * Log the host key fingerprint.
- */
- {
- char logmsg[80];
- logevent("Host key fingerprint is:");
- strcpy(logmsg, " ");
- hostkey.comment = NULL;
- rsa_fingerprint(logmsg + strlen(logmsg),
- sizeof(logmsg) - strlen(logmsg), &hostkey);
- logevent(logmsg);
- }
-
- ssh->v1_remote_protoflags = ssh_pkt_getuint32(pktin);
- s->supported_ciphers_mask = ssh_pkt_getuint32(pktin);
- s->supported_auths_mask = ssh_pkt_getuint32(pktin);
- if ((ssh->remote_bugs & BUG_CHOKES_ON_RSA))
- s->supported_auths_mask &= ~(1 << SSH1_AUTH_RSA);
-
- ssh->v1_local_protoflags =
- ssh->v1_remote_protoflags & SSH1_PROTOFLAGS_SUPPORTED;
- ssh->v1_local_protoflags |= SSH1_PROTOFLAG_SCREEN_NUMBER;
-
- MD5Init(&md5c);
- MD5Update(&md5c, s->keystr2, hostkey.bytes);
- MD5Update(&md5c, s->keystr1, servkey.bytes);
- MD5Update(&md5c, cookie, 8);
- MD5Final(s->session_id, &md5c);
-
- for (i = 0; i < 32; i++)
- ssh->session_key[i] = random_byte();
-
- /*
- * Verify that the `bits' and `bytes' parameters match.
- */
- if (hostkey.bits > hostkey.bytes * 8 ||
- servkey.bits > servkey.bytes * 8) {
- bombout(("SSH-1 public keys were badly formatted"));
- crStop(0);
- }
-
- s->len = (hostkey.bytes > servkey.bytes ? hostkey.bytes : servkey.bytes);
-
- s->rsabuf = snewn(s->len, unsigned char);
-
- /*
- * Verify the host key.
- */
- {
- /*
- * First format the key into a string.
- */
- int len = rsastr_len(&hostkey);
- char fingerprint[100];
- char *keystr = snewn(len, char);
- rsastr_fmt(keystr, &hostkey);
- rsa_fingerprint(fingerprint, sizeof(fingerprint), &hostkey);
-
- ssh_set_frozen(ssh, 1);
- s->dlgret = verify_ssh_host_key(ssh->frontend,
- ssh->savedhost, ssh->savedport,
- "rsa", keystr, fingerprint,
- ssh_dialog_callback, ssh);
- sfree(keystr);
- if (s->dlgret < 0) {
- do {
- crReturn(0);
- if (pktin) {
- bombout(("Unexpected data from server while waiting"
- " for user host key response"));
- crStop(0);
- }
- } while (pktin || inlen > 0);
- s->dlgret = ssh->user_response;
- }
- ssh_set_frozen(ssh, 0);
-
- if (s->dlgret == 0) {
- ssh_disconnect(ssh, "User aborted at host key verification",
- NULL, 0, TRUE);
- crStop(0);
- }
- }
-
- for (i = 0; i < 32; i++) {
- s->rsabuf[i] = ssh->session_key[i];
- if (i < 16)
- s->rsabuf[i] ^= s->session_id[i];
- }
-
- if (hostkey.bytes > servkey.bytes) {
- ret = rsaencrypt(s->rsabuf, 32, &servkey);
- if (ret)
- ret = rsaencrypt(s->rsabuf, servkey.bytes, &hostkey);
- } else {
- ret = rsaencrypt(s->rsabuf, 32, &hostkey);
- if (ret)
- ret = rsaencrypt(s->rsabuf, hostkey.bytes, &servkey);
- }
- if (!ret) {
- bombout(("SSH-1 public key encryptions failed due to bad formatting"));
- crStop(0);
- }
-
- logevent("Encrypted session key");
-
- {
- int cipher_chosen = 0, warn = 0;
- char *cipher_string = NULL;
- int i;
- for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) {
- int next_cipher = conf_get_int_int(ssh->conf,
- CONF_ssh_cipherlist, i);
- if (next_cipher == CIPHER_WARN) {
- /* If/when we choose a cipher, warn about it */
- warn = 1;
- } else if (next_cipher == CIPHER_AES) {
- /* XXX Probably don't need to mention this. */
- logevent("AES not supported in SSH-1, skipping");
- } else {
- switch (next_cipher) {
- case CIPHER_3DES: s->cipher_type = SSH_CIPHER_3DES;
- cipher_string = "3DES"; break;
- case CIPHER_BLOWFISH: s->cipher_type = SSH_CIPHER_BLOWFISH;
- cipher_string = "Blowfish"; break;
- case CIPHER_DES: s->cipher_type = SSH_CIPHER_DES;
- cipher_string = "single-DES"; break;
- }
- if (s->supported_ciphers_mask & (1 << s->cipher_type))
- cipher_chosen = 1;
- }
- }
- if (!cipher_chosen) {
- if ((s->supported_ciphers_mask & (1 << SSH_CIPHER_3DES)) == 0)
- bombout(("Server violates SSH-1 protocol by not "
- "supporting 3DES encryption"));
- else
- /* shouldn't happen */
- bombout(("No supported ciphers found"));
- crStop(0);
- }
-
- /* Warn about chosen cipher if necessary. */
- if (warn) {
- ssh_set_frozen(ssh, 1);
- s->dlgret = askalg(ssh->frontend, "cipher", cipher_string,
- ssh_dialog_callback, ssh);
- if (s->dlgret < 0) {
- do {
- crReturn(0);
- if (pktin) {
- bombout(("Unexpected data from server while waiting"
- " for user response"));
- crStop(0);
- }
- } while (pktin || inlen > 0);
- s->dlgret = ssh->user_response;
- }
- ssh_set_frozen(ssh, 0);
- if (s->dlgret == 0) {
- ssh_disconnect(ssh, "User aborted at cipher warning", NULL,
- 0, TRUE);
- crStop(0);
- }
- }
- }
-
- switch (s->cipher_type) {
- case SSH_CIPHER_3DES:
- logevent("Using 3DES encryption");
- break;
- case SSH_CIPHER_DES:
- logevent("Using single-DES encryption");
- break;
- case SSH_CIPHER_BLOWFISH:
- logevent("Using Blowfish encryption");
- break;
- }
-
- send_packet(ssh, SSH1_CMSG_SESSION_KEY,
- PKT_CHAR, s->cipher_type,
- PKT_DATA, cookie, 8,
- PKT_CHAR, (s->len * 8) >> 8, PKT_CHAR, (s->len * 8) & 0xFF,
- PKT_DATA, s->rsabuf, s->len,
- PKT_INT, ssh->v1_local_protoflags, PKT_END);
-
- logevent("Trying to enable encryption...");
-
- sfree(s->rsabuf);
-
- ssh->cipher = (s->cipher_type == SSH_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 :
- s->cipher_type == SSH_CIPHER_DES ? &ssh_des :
- &ssh_3des);
- ssh->v1_cipher_ctx = ssh->cipher->make_context();
- ssh->cipher->sesskey(ssh->v1_cipher_ctx, ssh->session_key);
- logeventf(ssh, "Initialised %s encryption", ssh->cipher->text_name);
-
- ssh->crcda_ctx = crcda_make_context();
- logevent("Installing CRC compensation attack detector");
-
- if (servkey.modulus) {
- sfree(servkey.modulus);
- servkey.modulus = NULL;
- }
- if (servkey.exponent) {
- sfree(servkey.exponent);
- servkey.exponent = NULL;
- }
- if (hostkey.modulus) {
- sfree(hostkey.modulus);
- hostkey.modulus = NULL;
- }
- if (hostkey.exponent) {
- sfree(hostkey.exponent);
- hostkey.exponent = NULL;
- }
- crWaitUntil(pktin);
-
- if (pktin->type != SSH1_SMSG_SUCCESS) {
- bombout(("Encryption not successfully enabled"));
- crStop(0);
- }
-
- logevent("Successfully started encryption");
-
- fflush(stdout); /* FIXME eh? */
- {
- if ((ssh->username = get_remote_username(ssh->conf)) == NULL) {
- int ret; /* need not be kept over crReturn */
- s->cur_prompt = new_prompts(ssh->frontend);
- s->cur_prompt->to_server = TRUE;
- s->cur_prompt->name = dupstr("SSH login name");
- add_prompt(s->cur_prompt, dupstr("login as: "), TRUE);
- ret = get_userpass_input(s->cur_prompt, NULL, 0);
- while (ret < 0) {
- ssh->send_ok = 1;
- crWaitUntil(!pktin);
- ret = get_userpass_input(s->cur_prompt, in, inlen);
- ssh->send_ok = 0;
- }
- if (!ret) {
- /*
- * Failed to get a username. Terminate.
- */
- free_prompts(s->cur_prompt);
- ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE);
- crStop(0);
- }
- ssh->username = dupstr(s->cur_prompt->prompts[0]->result);
- free_prompts(s->cur_prompt);
- }
-
- send_packet(ssh, SSH1_CMSG_USER, PKT_STR, ssh->username, PKT_END);
- {
- char *userlog = dupprintf("Sent username \"%s\"", ssh->username);
- logevent(userlog);
- if (flags & FLAG_INTERACTIVE &&
- (!((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)))) {
- c_write_str(ssh, userlog);
- c_write_str(ssh, "\r\n");
- }
- sfree(userlog);
- }
- }
-
- crWaitUntil(pktin);
-
- if ((s->supported_auths_mask & (1 << SSH1_AUTH_RSA)) == 0) {
- /* We must not attempt PK auth. Pretend we've already tried it. */
- s->tried_publickey = s->tried_agent = 1;
- } else {
- s->tried_publickey = s->tried_agent = 0;
- }
- s->tis_auth_refused = s->ccard_auth_refused = 0;
- /*
- * Load the public half of any configured keyfile for later use.
- */
- s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile);
- if (!filename_is_null(s->keyfile)) {
- int keytype;
- logeventf(ssh, "Reading private key file \"%.150s\"",
- filename_to_str(s->keyfile));
- keytype = key_type(s->keyfile);
- if (keytype == SSH_KEYTYPE_SSH1) {
- 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);
- } else {
- char *msgbuf;
- logeventf(ssh, "Unable to load private key (%s)", error);
- msgbuf = dupprintf("Unable to load private key file "
- "\"%.150s\" (%s)\r\n",
- filename_to_str(s->keyfile),
- error);
- c_write_str(ssh, msgbuf);
- sfree(msgbuf);
- s->publickey_blob = NULL;
- }
- } else {
- char *msgbuf;
- logeventf(ssh, "Unable to use this key file (%s)",
- key_type_to_str(keytype));
- msgbuf = dupprintf("Unable to use key file \"%.150s\""
- " (%s)\r\n",
- filename_to_str(s->keyfile),
- key_type_to_str(keytype));
- c_write_str(ssh, msgbuf);
- sfree(msgbuf);
- s->publickey_blob = NULL;
- }
- } else
- s->publickey_blob = NULL;
-
- while (pktin->type == SSH1_SMSG_FAILURE) {
- s->pwpkt_type = SSH1_CMSG_AUTH_PASSWORD;
-
- if (conf_get_int(ssh->conf, CONF_tryagent) && agent_exists() && !s->tried_agent) {
- /*
- * Attempt RSA authentication using Pageant.
- */
- void *r;
-
- s->authed = FALSE;
- s->tried_agent = 1;
- logevent("Pageant is running. Requesting keys.");
-
- /* Request the keys held by the agent. */
- PUT_32BIT(s->request, 1);
- s->request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
- if (!agent_query(s->request, 5, &r, &s->responselen,
- ssh_agent_callback, ssh)) {
- do {
- crReturn(0);
- if (pktin) {
- bombout(("Unexpected data from server while waiting"
- " for agent response"));
- crStop(0);
- }
- } while (pktin || inlen > 0);
- r = ssh->agent_response;
- s->responselen = ssh->agent_response_len;
- }
- s->response = (unsigned char *) r;
- if (s->response && s->responselen >= 5 &&
- s->response[4] == SSH1_AGENT_RSA_IDENTITIES_ANSWER) {
- s->p = s->response + 5;
- s->nkeys = GET_32BIT(s->p);
- s->p += 4;
- logeventf(ssh, "Pageant has %d SSH-1 keys", s->nkeys);
- for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) {
- unsigned char *pkblob = s->p;
- s->p += 4;
- {
- int n, ok = FALSE;
- do { /* do while (0) to make breaking easy */
- n = ssh1_read_bignum
- (s->p, s->responselen-(s->p-s->response),
- &s->key.exponent);
- if (n < 0)
- break;
- s->p += n;
- n = ssh1_read_bignum
- (s->p, s->responselen-(s->p-s->response),
- &s->key.modulus);
- if (n < 0)
- break;
- s->p += n;
- if (s->responselen - (s->p-s->response) < 4)
- break;
- s->commentlen = GET_32BIT(s->p);
- s->p += 4;
- if (s->responselen - (s->p-s->response) <
- s->commentlen)
- break;
- s->commentp = (char *)s->p;
- s->p += s->commentlen;
- ok = TRUE;
- } while (0);
- if (!ok) {
- logevent("Pageant key list packet was truncated");
- break;
- }
- }
- if (s->publickey_blob) {
- if (!memcmp(pkblob, s->publickey_blob,
- s->publickey_bloblen)) {
- logeventf(ssh, "Pageant key #%d matches "
- "configured key file", s->keyi);
- s->tried_publickey = 1;
- } else
- /* Skip non-configured key */
- continue;
- }
- logeventf(ssh, "Trying Pageant key #%d", s->keyi);
- send_packet(ssh, SSH1_CMSG_AUTH_RSA,
- PKT_BIGNUM, s->key.modulus, PKT_END);
- crWaitUntil(pktin);
- if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
- logevent("Key refused");
- continue;
- }
- logevent("Received RSA challenge");
- if ((s->challenge = ssh1_pkt_getmp(pktin)) == NULL) {
- bombout(("Server's RSA challenge was badly formatted"));
- crStop(0);
- }
-
- {
- char *agentreq, *q, *ret;
- void *vret;
- int len, retlen;
- len = 1 + 4; /* message type, bit count */
- len += ssh1_bignum_length(s->key.exponent);
- len += ssh1_bignum_length(s->key.modulus);
- len += ssh1_bignum_length(s->challenge);
- len += 16; /* session id */
- len += 4; /* response format */
- agentreq = snewn(4 + len, char);
- PUT_32BIT(agentreq, len);
- q = agentreq + 4;
- *q++ = SSH1_AGENTC_RSA_CHALLENGE;
- PUT_32BIT(q, bignum_bitcount(s->key.modulus));
- q += 4;
- q += ssh1_write_bignum(q, s->key.exponent);
- q += ssh1_write_bignum(q, s->key.modulus);
- q += ssh1_write_bignum(q, s->challenge);
- memcpy(q, s->session_id, 16);
- q += 16;
- PUT_32BIT(q, 1); /* response format */
- if (!agent_query(agentreq, len + 4, &vret, &retlen,
- ssh_agent_callback, ssh)) {
- sfree(agentreq);
- do {
- crReturn(0);
- if (pktin) {
- bombout(("Unexpected data from server"
- " while waiting for agent"
- " response"));
- crStop(0);
- }
- } while (pktin || inlen > 0);
- vret = ssh->agent_response;
- retlen = ssh->agent_response_len;
- } else
- sfree(agentreq);
- ret = vret;
- if (ret) {
- if (ret[4] == SSH1_AGENT_RSA_RESPONSE) {
- logevent("Sending Pageant's response");
- send_packet(ssh, SSH1_CMSG_AUTH_RSA_RESPONSE,
- PKT_DATA, ret + 5, 16,
- PKT_END);
- sfree(ret);
- crWaitUntil(pktin);
- if (pktin->type == SSH1_SMSG_SUCCESS) {
- logevent
- ("Pageant's response accepted");
- if (flags & FLAG_VERBOSE) {
- c_write_str(ssh, "Authenticated using"
- " RSA key \"");
- c_write(ssh, s->commentp,
- s->commentlen);
- c_write_str(ssh, "\" from agent\r\n");
- }
- s->authed = TRUE;
- } else
- logevent
- ("Pageant's response not accepted");
- } else {
- logevent
- ("Pageant failed to answer challenge");
- sfree(ret);
- }
- } else {
- logevent("No reply received from Pageant");
- }
- }
- freebn(s->key.exponent);
- freebn(s->key.modulus);
- freebn(s->challenge);
- if (s->authed)
- break;
- }
- sfree(s->response);
- if (s->publickey_blob && !s->tried_publickey)
- logevent("Configured key file not in Pageant");
- } else {
- logevent("Failed to get reply from Pageant");
- }
- if (s->authed)
- break;
- }
- if (s->publickey_blob && !s->tried_publickey) {
- /*
- * Try public key authentication with the specified
- * key file.
- */
- int got_passphrase; /* need not be kept over crReturn */
- if (flags & FLAG_VERBOSE)
- c_write_str(ssh, "Trying public key authentication.\r\n");
- s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile);
- logeventf(ssh, "Trying public key \"%s\"",
- filename_to_str(s->keyfile));
- s->tried_publickey = 1;
- got_passphrase = FALSE;
- while (!got_passphrase) {
- /*
- * Get a passphrase, if necessary.
- */
- char *passphrase = NULL; /* only written after crReturn */
- const char *error;
- if (!s->publickey_encrypted) {
- if (flags & FLAG_VERBOSE)
- c_write_str(ssh, "No passphrase required.\r\n");
- passphrase = NULL;
- } else {
- int ret; /* need not be kept over crReturn */
- s->cur_prompt = new_prompts(ssh->frontend);
- s->cur_prompt->to_server = FALSE;
- s->cur_prompt->name = dupstr("SSH key passphrase");
- add_prompt(s->cur_prompt,
- dupprintf("Passphrase for key \"%.100s\": ",
- s->publickey_comment), FALSE);
- ret = get_userpass_input(s->cur_prompt, NULL, 0);
- while (ret < 0) {
- ssh->send_ok = 1;
- crWaitUntil(!pktin);
- ret = get_userpass_input(s->cur_prompt, in, inlen);
- ssh->send_ok = 0;
- }
- if (!ret) {
- /* Failed to get a passphrase. Terminate. */
- free_prompts(s->cur_prompt);
- ssh_disconnect(ssh, NULL, "Unable to authenticate",
- 0, TRUE);
- crStop(0);
- }
- passphrase = dupstr(s->cur_prompt->prompts[0]->result);
- free_prompts(s->cur_prompt);
- }
- /*
- * Try decrypting key with passphrase.
- */
- s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile);
- ret = loadrsakey(s->keyfile, &s->key, passphrase,
- &error);
- if (passphrase) {
- memset(passphrase, 0, strlen(passphrase));
- sfree(passphrase);
- }
- if (ret == 1) {
- /* Correct passphrase. */
- got_passphrase = TRUE;
- } else if (ret == 0) {
- c_write_str(ssh, "Couldn't load private key from ");
- c_write_str(ssh, filename_to_str(s->keyfile));
- c_write_str(ssh, " (");
- c_write_str(ssh, error);
- c_write_str(ssh, ").\r\n");
- got_passphrase = FALSE;
- break; /* go and try something else */
- } else if (ret == -1) {
- c_write_str(ssh, "Wrong passphrase.\r\n"); /* FIXME */
- got_passphrase = FALSE;
- /* and try again */
- } else {
- assert(0 && "unexpected return from loadrsakey()");
- got_passphrase = FALSE; /* placate optimisers */
- }
- }
-
- if (got_passphrase) {
-
- /*
- * Send a public key attempt.
- */
- send_packet(ssh, SSH1_CMSG_AUTH_RSA,
- PKT_BIGNUM, s->key.modulus, PKT_END);
-
- crWaitUntil(pktin);
- if (pktin->type == SSH1_SMSG_FAILURE) {
- c_write_str(ssh, "Server refused our public key.\r\n");
- continue; /* go and try something else */
- }
- if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
- bombout(("Bizarre response to offer of public key"));
- crStop(0);
- }
-
- {
- int i;
- unsigned char buffer[32];
- Bignum challenge, response;
-
- if ((challenge = ssh1_pkt_getmp(pktin)) == NULL) {
- bombout(("Server's RSA challenge was badly formatted"));
- crStop(0);
- }
- response = rsadecrypt(challenge, &s->key);
- freebn(s->key.private_exponent);/* burn the evidence */
-
- for (i = 0; i < 32; i++) {
- buffer[i] = bignum_byte(response, 31 - i);
- }
-
- MD5Init(&md5c);
- MD5Update(&md5c, buffer, 32);
- MD5Update(&md5c, s->session_id, 16);
- MD5Final(buffer, &md5c);
-
- send_packet(ssh, SSH1_CMSG_AUTH_RSA_RESPONSE,
- PKT_DATA, buffer, 16, PKT_END);
-
- freebn(challenge);
- freebn(response);
- }
-
- crWaitUntil(pktin);
- if (pktin->type == SSH1_SMSG_FAILURE) {
- if (flags & FLAG_VERBOSE)
- c_write_str(ssh, "Failed to authenticate with"
- " our public key.\r\n");
- continue; /* go and try something else */
- } else if (pktin->type != SSH1_SMSG_SUCCESS) {
- bombout(("Bizarre response to RSA authentication response"));
- crStop(0);
- }
-
- break; /* we're through! */
- }
-
- }
-
- /*
- * Otherwise, try various forms of password-like authentication.
- */
- s->cur_prompt = new_prompts(ssh->frontend);
-
- if (conf_get_int(ssh->conf, CONF_try_tis_auth) &&
- (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) &&
- !s->tis_auth_refused) {
- s->pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE;
- logevent("Requested TIS authentication");
- send_packet(ssh, SSH1_CMSG_AUTH_TIS, PKT_END);
- crWaitUntil(pktin);
- if (pktin->type != SSH1_SMSG_AUTH_TIS_CHALLENGE) {
- logevent("TIS authentication declined");
- if (flags & FLAG_INTERACTIVE)
- c_write_str(ssh, "TIS authentication refused.\r\n");
- s->tis_auth_refused = 1;
- continue;
- } else {
- char *challenge;
- int challengelen;
- char *instr_suf, *prompt;
-
- ssh_pkt_getstring(pktin, &challenge, &challengelen);
- if (!challenge) {
- bombout(("TIS challenge packet was badly formed"));
- crStop(0);
- }
- logevent("Received TIS challenge");
- s->cur_prompt->to_server = TRUE;
- s->cur_prompt->name = dupstr("SSH TIS authentication");
- /* Prompt heuristic comes from OpenSSH */
- if (memchr(challenge, '\n', challengelen)) {
- instr_suf = dupstr("");
- prompt = dupprintf("%.*s", challengelen, challenge);
- } else {
- instr_suf = dupprintf("%.*s", challengelen, challenge);
- prompt = dupstr("Response: ");
- }
- s->cur_prompt->instruction =
- dupprintf("Using TIS authentication.%s%s",
- (*instr_suf) ? "\n" : "",
- instr_suf);
- s->cur_prompt->instr_reqd = TRUE;
- add_prompt(s->cur_prompt, prompt, FALSE);
- sfree(instr_suf);
- }
- }
- if (conf_get_int(ssh->conf, CONF_try_tis_auth) &&
- (s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) &&
- !s->ccard_auth_refused) {
- s->pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE;
- logevent("Requested CryptoCard authentication");
- send_packet(ssh, SSH1_CMSG_AUTH_CCARD, PKT_END);
- crWaitUntil(pktin);
- if (pktin->type != SSH1_SMSG_AUTH_CCARD_CHALLENGE) {
- logevent("CryptoCard authentication declined");
- c_write_str(ssh, "CryptoCard authentication refused.\r\n");
- s->ccard_auth_refused = 1;
- continue;
- } else {
- char *challenge;
- int challengelen;
- char *instr_suf, *prompt;
-
- ssh_pkt_getstring(pktin, &challenge, &challengelen);
- if (!challenge) {
- bombout(("CryptoCard challenge packet was badly formed"));
- crStop(0);
- }
- logevent("Received CryptoCard challenge");
- s->cur_prompt->to_server = TRUE;
- s->cur_prompt->name = dupstr("SSH CryptoCard authentication");
- s->cur_prompt->name_reqd = FALSE;
- /* Prompt heuristic comes from OpenSSH */
- if (memchr(challenge, '\n', challengelen)) {
- instr_suf = dupstr("");
- prompt = dupprintf("%.*s", challengelen, challenge);
- } else {
- instr_suf = dupprintf("%.*s", challengelen, challenge);
- prompt = dupstr("Response: ");
- }
- s->cur_prompt->instruction =
- dupprintf("Using CryptoCard authentication.%s%s",
- (*instr_suf) ? "\n" : "",
- instr_suf);
- s->cur_prompt->instr_reqd = TRUE;
- add_prompt(s->cur_prompt, prompt, FALSE);
- sfree(instr_suf);
- }
- }
- if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
- if ((s->supported_auths_mask & (1 << SSH1_AUTH_PASSWORD)) == 0) {
- bombout(("No supported authentication methods available"));
- crStop(0);
- }
- s->cur_prompt->to_server = TRUE;
- s->cur_prompt->name = dupstr("SSH password");
- add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ",
- ssh->username, ssh->savedhost),
- FALSE);
- }
-
- /*
- * Show password prompt, having first obtained it via a TIS
- * or CryptoCard exchange if we're doing TIS or CryptoCard
- * authentication.
- */
- {
- int ret; /* need not be kept over crReturn */
- ret = get_userpass_input(s->cur_prompt, NULL, 0);
- while (ret < 0) {
- ssh->send_ok = 1;
- crWaitUntil(!pktin);
- ret = get_userpass_input(s->cur_prompt, in, inlen);
- ssh->send_ok = 0;
- }
- if (!ret) {
- /*
- * Failed to get a password (for example
- * because one was supplied on the command line
- * which has already failed to work). Terminate.
- */
- free_prompts(s->cur_prompt);
- ssh_disconnect(ssh, NULL, "Unable to authenticate", 0, TRUE);
- crStop(0);
- }
- }
-
- if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
- /*
- * Defence against traffic analysis: we send a
- * whole bunch of packets containing strings of
- * different lengths. One of these strings is the
- * password, in a SSH1_CMSG_AUTH_PASSWORD packet.
- * The others are all random data in
- * SSH1_MSG_IGNORE packets. This way a passive
- * listener can't tell which is the password, and
- * hence can't deduce the password length.
- *
- * Anybody with a password length greater than 16
- * bytes is going to have enough entropy in their
- * password that a listener won't find it _that_
- * much help to know how long it is. So what we'll
- * do is:
- *
- * - if password length < 16, we send 15 packets
- * containing string lengths 1 through 15
- *
- * - otherwise, we let N be the nearest multiple
- * of 8 below the password length, and send 8
- * packets containing string lengths N through
- * N+7. This won't obscure the order of
- * magnitude of the password length, but it will
- * introduce a bit of extra uncertainty.
- *
- * A few servers can't deal with SSH1_MSG_IGNORE, at
- * least in this context. For these servers, we need
- * an alternative defence. We make use of the fact
- * that the password is interpreted as a C string:
- * so we can append a NUL, then some random data.
- *
- * A few servers can deal with neither SSH1_MSG_IGNORE
- * here _nor_ a padded password string.
- * For these servers we are left with no defences
- * against password length sniffing.
- */
- if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) &&
- !(ssh->remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
- /*
- * The server can deal with SSH1_MSG_IGNORE, so
- * we can use the primary defence.
- */
- int bottom, top, pwlen, i;
- char *randomstr;
-
- pwlen = strlen(s->cur_prompt->prompts[0]->result);
- if (pwlen < 16) {
- bottom = 0; /* zero length passwords are OK! :-) */
- top = 15;
- } else {
- bottom = pwlen & ~7;
- top = bottom + 7;
- }
-
- assert(pwlen >= bottom && pwlen <= top);
-
- randomstr = snewn(top + 1, char);
-
- for (i = bottom; i <= top; i++) {
- if (i == pwlen) {
- defer_packet(ssh, s->pwpkt_type,
- PKTT_PASSWORD, PKT_STR,
- s->cur_prompt->prompts[0]->result,
- PKTT_OTHER, PKT_END);
- } else {
- for (j = 0; j < i; j++) {
- do {
- randomstr[j] = random_byte();
- } while (randomstr[j] == '\0');
- }
- randomstr[i] = '\0';
- defer_packet(ssh, SSH1_MSG_IGNORE,
- PKT_STR, randomstr, PKT_END);
- }
- }
- logevent("Sending password with camouflage packets");
- ssh_pkt_defersend(ssh);
- sfree(randomstr);
- }
- else if (!(ssh->remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
- /*
- * The server can't deal with SSH1_MSG_IGNORE
- * but can deal with padded passwords, so we
- * can use the secondary defence.
- */
- char string[64];
- char *ss;
- int len;
-
- len = strlen(s->cur_prompt->prompts[0]->result);
- if (len < sizeof(string)) {
- ss = string;
- strcpy(string, s->cur_prompt->prompts[0]->result);
- len++; /* cover the zero byte */
- while (len < sizeof(string)) {
- string[len++] = (char) random_byte();
- }
- } else {
- ss = s->cur_prompt->prompts[0]->result;
- }
- logevent("Sending length-padded password");
- send_packet(ssh, s->pwpkt_type, PKTT_PASSWORD,
- PKT_INT, len, PKT_DATA, ss, len,
- PKTT_OTHER, PKT_END);
- } else {
- /*
- * The server is believed unable to cope with
- * any of our password camouflage methods.
- */
- int len;
- len = strlen(s->cur_prompt->prompts[0]->result);
- logevent("Sending unpadded password");
- send_packet(ssh, s->pwpkt_type,
- PKTT_PASSWORD, PKT_INT, len,
- PKT_DATA, s->cur_prompt->prompts[0]->result, len,
- PKTT_OTHER, PKT_END);
- }
- } else {
- send_packet(ssh, s->pwpkt_type, PKTT_PASSWORD,
- PKT_STR, s->cur_prompt->prompts[0]->result,
- PKTT_OTHER, PKT_END);
- }
- logevent("Sent password");
- free_prompts(s->cur_prompt);
- crWaitUntil(pktin);
- if (pktin->type == SSH1_SMSG_FAILURE) {
- if (flags & FLAG_VERBOSE)
- c_write_str(ssh, "Access denied\r\n");
- logevent("Authentication refused");
- } else if (pktin->type != SSH1_SMSG_SUCCESS) {
- bombout(("Strange packet received, type %d", pktin->type));
- crStop(0);
- }
- }
-
- /* Clear up */
- if (s->publickey_blob) {
- sfree(s->publickey_blob);
- sfree(s->publickey_comment);
- }
-
- logevent("Authentication successful");
-
- crFinish(1);
-}
-
-static void ssh_channel_try_eof(struct ssh_channel *c)
-{
- Ssh ssh = c->ssh;
- assert(c->pending_eof); /* precondition for calling us */
- if (c->halfopen)
- return; /* can't close: not even opened yet */
- if (ssh->version == 2 && bufchain_size(&c->v.v2.outbuffer) > 0)
- return; /* can't send EOF: pending outgoing data */
-
- if (ssh->version == 1) {
- send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
- PKT_END);
- c->closes |= CLOSES_SENT_EOF;
- } else {
- struct Packet *pktout;
- pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_EOF);
- ssh2_pkt_adduint32(pktout, c->remoteid);
- ssh2_pkt_send(ssh, pktout);
- c->closes |= CLOSES_SENT_EOF;
- if (!((CLOSES_SENT_EOF | CLOSES_RCVD_EOF) & ~c->closes)) {
- /*
- * Also send MSG_CLOSE.
- */
- pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
- ssh2_pkt_adduint32(pktout, c->remoteid);
- ssh2_pkt_send(ssh, pktout);
- c->closes |= CLOSES_SENT_CLOSE;
- }
- }
- c->pending_eof = FALSE; /* we've sent it now */
-}
-
-void sshfwd_write_eof(struct ssh_channel *c)
-{
- Ssh ssh = c->ssh;
-
- if (ssh->state == SSH_STATE_CLOSED)
- return;
-
- if (c->closes & CLOSES_SENT_EOF)
- return;
-
- c->pending_eof = TRUE;
- ssh_channel_try_eof(c);
-}
-
-void sshfwd_unclean_close(struct ssh_channel *c)
-{
- Ssh ssh = c->ssh;
- struct Packet *pktout;
-
- if (ssh->state == SSH_STATE_CLOSED)
- return;
-
- if (c->closes & CLOSES_SENT_CLOSE)
- return;
-
- pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
- ssh2_pkt_adduint32(pktout, c->remoteid);
- ssh2_pkt_send(ssh, pktout);
- c->closes |= CLOSES_SENT_EOF | CLOSES_SENT_CLOSE;
- switch (c->type) {
- case CHAN_X11:
- x11_close(c->u.x11.s);
- break;
- case CHAN_SOCKDATA:
- case CHAN_SOCKDATA_DORMANT:
- pfd_close(c->u.pfd.s);
- break;
- }
- c->type = CHAN_ZOMBIE;
- ssh2_channel_check_close(c);
-}
-
-int sshfwd_write(struct ssh_channel *c, char *buf, int len)
-{
- Ssh ssh = c->ssh;
-
- if (ssh->state == SSH_STATE_CLOSED)
- return 0;
-
- if (ssh->version == 1) {
- send_packet(ssh, SSH1_MSG_CHANNEL_DATA,
- PKT_INT, c->remoteid,
- PKT_INT, len, PKTT_DATA, PKT_DATA, buf, len,
- PKTT_OTHER, PKT_END);
- /*
- * In SSH-1 we can return 0 here - implying that forwarded
- * connections are never individually throttled - because
- * the only circumstance that can cause throttling will be
- * the whole SSH connection backing up, in which case
- * _everything_ will be throttled as a whole.
- */
- return 0;
- } else {
- ssh2_add_channel_data(c, buf, len);
- return ssh2_try_send(c);
- }
-}
-
-void sshfwd_unthrottle(struct ssh_channel *c, int bufsize)
-{
- Ssh ssh = c->ssh;
- int buflimit;
-
- if (ssh->state == SSH_STATE_CLOSED)
- return;
-
- if (ssh->version == 1) {
- buflimit = SSH1_BUFFER_LIMIT;
- } else {
- buflimit = c->v.v2.locmaxwin;
- ssh2_set_window(c, bufsize < buflimit ? buflimit - bufsize : 0);
- }
- if (c->throttling_conn && bufsize <= buflimit) {
- c->throttling_conn = 0;
- ssh_throttle_conn(ssh, -1);
- }
-}
-
-static void ssh_queueing_handler(Ssh ssh, struct Packet *pktin)
-{
- struct queued_handler *qh = ssh->qhead;
-
- assert(qh != NULL);
-
- assert(pktin->type == qh->msg1 || pktin->type == qh->msg2);
-
- if (qh->msg1 > 0) {
- assert(ssh->packet_dispatch[qh->msg1] == ssh_queueing_handler);
- ssh->packet_dispatch[qh->msg1] = NULL;
- }
- if (qh->msg2 > 0) {
- assert(ssh->packet_dispatch[qh->msg2] == ssh_queueing_handler);
- ssh->packet_dispatch[qh->msg2] = NULL;
- }
-
- if (qh->next) {
- ssh->qhead = qh->next;
-
- if (ssh->qhead->msg1 > 0) {
- assert(ssh->packet_dispatch[ssh->qhead->msg1] == NULL);
- ssh->packet_dispatch[ssh->qhead->msg1] = ssh_queueing_handler;
- }
- if (ssh->qhead->msg2 > 0) {
- assert(ssh->packet_dispatch[ssh->qhead->msg2] == NULL);
- ssh->packet_dispatch[ssh->qhead->msg2] = ssh_queueing_handler;
- }
- } else {
- ssh->qhead = ssh->qtail = NULL;
- ssh->packet_dispatch[pktin->type] = NULL;
- }
-
- qh->handler(ssh, pktin, qh->ctx);
-
- sfree(qh);
-}
-
-static void ssh_queue_handler(Ssh ssh, int msg1, int msg2,
- chandler_fn_t handler, void *ctx)
-{
- struct queued_handler *qh;
-
- qh = snew(struct queued_handler);
- qh->msg1 = msg1;
- qh->msg2 = msg2;
- qh->handler = handler;
- qh->ctx = ctx;
- qh->next = NULL;
-
- if (ssh->qtail == NULL) {
- ssh->qhead = qh;
-
- if (qh->msg1 > 0) {
- assert(ssh->packet_dispatch[qh->msg1] == NULL);
- ssh->packet_dispatch[qh->msg1] = ssh_queueing_handler;
- }
- if (qh->msg2 > 0) {
- assert(ssh->packet_dispatch[qh->msg2] == NULL);
- ssh->packet_dispatch[qh->msg2] = ssh_queueing_handler;
- }
- } else {
- ssh->qtail->next = qh;
- }
- ssh->qtail = qh;
-}
-
-static void ssh_rportfwd_succfail(Ssh ssh, struct Packet *pktin, void *ctx)
-{
- struct ssh_rportfwd *rpf, *pf = (struct ssh_rportfwd *)ctx;
-
- if (pktin->type == (ssh->version == 1 ? SSH1_SMSG_SUCCESS :
- SSH2_MSG_REQUEST_SUCCESS)) {
- logeventf(ssh, "Remote port forwarding from %s enabled",
- pf->sportdesc);
- } else {
- logeventf(ssh, "Remote port forwarding from %s refused",
- pf->sportdesc);
-
- rpf = del234(ssh->rportfwds, pf);
- assert(rpf == pf);
- pf->pfrec->remote = NULL;
- free_rportfwd(pf);
- }
-}
-
-static void ssh_setup_portfwd(Ssh ssh, Conf *conf)
-{
- struct ssh_portfwd *epf;
- int i;
- char *key, *val;
-
- if (!ssh->portfwds) {
- ssh->portfwds = newtree234(ssh_portcmp);
- } else {
- /*
- * Go through the existing port forwardings and tag them
- * with status==DESTROY. Any that we want to keep will be
- * re-enabled (status==KEEP) as we go through the
- * configuration and find out which bits are the same as
- * they were before.
- */
- struct ssh_portfwd *epf;
- int i;
- for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++)
- epf->status = DESTROY;
- }
-
- for (val = conf_get_str_strs(conf, CONF_portfwd, NULL, &key);
- val != NULL;
- val = conf_get_str_strs(conf, CONF_portfwd, key, &key)) {
- char *kp, *kp2, *vp, *vp2;
- char address_family, type;
- int sport,dport,sserv,dserv;
- char *sports, *dports, *saddr, *host;
-
- kp = key;
-
- address_family = 'A';
- type = 'L';
- if (*kp == 'A' || *kp == '4' || *kp == '6')
- address_family = *kp++;
- if (*kp == 'L' || *kp == 'R')
- type = *kp++;
-
- if ((kp2 = strchr(kp, ':')) != NULL) {
- /*
- * There's a colon in the middle of the source port
- * string, which means that the part before it is
- * actually a source address.
- */
- saddr = dupprintf("%.*s", (int)(kp2 - kp), kp);
- sports = kp2+1;
- } else {
- saddr = NULL;
- sports = kp;
- }
- sport = atoi(sports);
- sserv = 0;
- if (sport == 0) {
- sserv = 1;
- sport = net_service_lookup(sports);
- if (!sport) {
- logeventf(ssh, "Service lookup failed for source"
- " port \"%s\"", sports);
- }
- }
-
- if (type == 'L' && !strcmp(val, "D")) {
- /* dynamic forwarding */
- host = NULL;
- dports = NULL;
- dport = -1;
- dserv = 0;
- type = 'D';
- } else {
- /* ordinary forwarding */
- vp = val;
- vp2 = vp + strcspn(vp, ":");
- host = dupprintf("%.*s", (int)(vp2 - vp), vp);
- if (vp2)
- vp2++;
- dports = vp2;
- dport = atoi(dports);
- dserv = 0;
- if (dport == 0) {
- dserv = 1;
- dport = net_service_lookup(dports);
- if (!dport) {
- logeventf(ssh, "Service lookup failed for destination"
- " port \"%s\"", dports);
- }
- }
- }
-
- if (sport && dport) {
- /* Set up a description of the source port. */
- struct ssh_portfwd *pfrec, *epfrec;
-
- pfrec = snew(struct ssh_portfwd);
- pfrec->type = type;
- pfrec->saddr = saddr;
- pfrec->sserv = sserv ? dupstr(sports) : NULL;
- pfrec->sport = sport;
- pfrec->daddr = host;
- pfrec->dserv = dserv ? dupstr(dports) : NULL;
- pfrec->dport = dport;
- pfrec->local = NULL;
- pfrec->remote = NULL;
- pfrec->addressfamily = (address_family == '4' ? ADDRTYPE_IPV4 :
- address_family == '6' ? ADDRTYPE_IPV6 :
- ADDRTYPE_UNSPEC);
-
- epfrec = add234(ssh->portfwds, pfrec);
- if (epfrec != pfrec) {
- if (epfrec->status == DESTROY) {
- /*
- * We already have a port forwarding up and running
- * with precisely these parameters. Hence, no need
- * to do anything; simply re-tag the existing one
- * as KEEP.
- */
- epfrec->status = KEEP;
- }
- /*
- * Anything else indicates that there was a duplicate
- * in our input, which we'll silently ignore.
- */
- free_portfwd(pfrec);
- } else {
- pfrec->status = CREATE;
- }
- } else {
- sfree(saddr);
- sfree(host);
- }
- }
-
- /*
- * Now go through and destroy any port forwardings which were
- * not re-enabled.
- */
- for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++)
- if (epf->status == DESTROY) {
- char *message;
-
- message = dupprintf("%s port forwarding from %s%s%d",
- epf->type == 'L' ? "local" :
- epf->type == 'R' ? "remote" : "dynamic",
- epf->saddr ? epf->saddr : "",
- epf->saddr ? ":" : "",
- epf->sport);
-
- if (epf->type != 'D') {
- char *msg2 = dupprintf("%s to %s:%d", message,
- epf->daddr, epf->dport);
- sfree(message);
- message = msg2;
- }
-
- logeventf(ssh, "Cancelling %s", message);
- sfree(message);
-
- /* epf->remote or epf->local may be NULL if setting up a
- * forwarding failed. */
- if (epf->remote) {
- struct ssh_rportfwd *rpf = epf->remote;
- struct Packet *pktout;
-
- /*
- * Cancel the port forwarding at the server
- * end.
- */
- if (ssh->version == 1) {
- /*
- * We cannot cancel listening ports on the
- * server side in SSH-1! There's no message
- * to support it. Instead, we simply remove
- * the rportfwd record from the local end
- * so that any connections the server tries
- * to make on it are rejected.
- */
- } else {
- pktout = ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST);
- ssh2_pkt_addstring(pktout, "cancel-tcpip-forward");
- ssh2_pkt_addbool(pktout, 0);/* _don't_ want reply */
- if (epf->saddr) {
- ssh2_pkt_addstring(pktout, epf->saddr);
- } else if (conf_get_int(conf, CONF_rport_acceptall)) {
- /* XXX: rport_acceptall may not represent
- * what was used to open the original connection,
- * since it's reconfigurable. */
- ssh2_pkt_addstring(pktout, "0.0.0.0");
- } else {
- ssh2_pkt_addstring(pktout, "127.0.0.1");
- }
- ssh2_pkt_adduint32(pktout, epf->sport);
- ssh2_pkt_send(ssh, pktout);
- }
-
- del234(ssh->rportfwds, rpf);
- free_rportfwd(rpf);
- } else if (epf->local) {
- pfd_terminate(epf->local);
- }
-
- delpos234(ssh->portfwds, i);
- free_portfwd(epf);
- i--; /* so we don't skip one in the list */
- }
-
- /*
- * And finally, set up any new port forwardings (status==CREATE).
- */
- for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++)
- if (epf->status == CREATE) {
- char *sportdesc, *dportdesc;
- sportdesc = dupprintf("%s%s%s%s%d%s",
- epf->saddr ? epf->saddr : "",
- epf->saddr ? ":" : "",
- epf->sserv ? epf->sserv : "",
- epf->sserv ? "(" : "",
- epf->sport,
- epf->sserv ? ")" : "");
- if (epf->type == 'D') {
- dportdesc = NULL;
- } else {
- dportdesc = dupprintf("%s:%s%s%d%s",
- epf->daddr,
- epf->dserv ? epf->dserv : "",
- epf->dserv ? "(" : "",
- epf->dport,
- epf->dserv ? ")" : "");
- }
-
- if (epf->type == 'L') {
- const char *err = pfd_addforward(epf->daddr, epf->dport,
- epf->saddr, epf->sport,
- ssh, conf,
- &epf->local,
- epf->addressfamily);
-
- logeventf(ssh, "Local %sport %s forwarding to %s%s%s",
- epf->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " :
- epf->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "",
- sportdesc, dportdesc,
- err ? " failed: " : "", err ? err : "");
- } else if (epf->type == 'D') {
- const char *err = pfd_addforward(NULL, -1,
- epf->saddr, epf->sport,
- ssh, conf,
- &epf->local,
- epf->addressfamily);
-
- logeventf(ssh, "Local %sport %s SOCKS dynamic forwarding%s%s",
- epf->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " :
- epf->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "",
- sportdesc,
- err ? " failed: " : "", err ? err : "");
- } else {
- struct ssh_rportfwd *pf;
-
- /*
- * Ensure the remote port forwardings tree exists.
- */
- if (!ssh->rportfwds) {
- if (ssh->version == 1)
- ssh->rportfwds = newtree234(ssh_rportcmp_ssh1);
- else
- ssh->rportfwds = newtree234(ssh_rportcmp_ssh2);
- }
-
- pf = snew(struct ssh_rportfwd);
- strncpy(pf->dhost, epf->daddr, lenof(pf->dhost)-1);
- pf->dhost[lenof(pf->dhost)-1] = '\0';
- pf->dport = epf->dport;
- pf->sport = epf->sport;
- if (add234(ssh->rportfwds, pf) != pf) {
- logeventf(ssh, "Duplicate remote port forwarding to %s:%d",
- epf->daddr, epf->dport);
- sfree(pf);
- } else {
- logeventf(ssh, "Requesting remote port %s"
- " forward to %s", sportdesc, dportdesc);
-
- pf->sportdesc = sportdesc;
- sportdesc = NULL;
- epf->remote = pf;
- pf->pfrec = epf;
-
- if (ssh->version == 1) {
- send_packet(ssh, SSH1_CMSG_PORT_FORWARD_REQUEST,
- PKT_INT, epf->sport,
- PKT_STR, epf->daddr,
- PKT_INT, epf->dport,
- PKT_END);
- ssh_queue_handler(ssh, SSH1_SMSG_SUCCESS,
- SSH1_SMSG_FAILURE,
- ssh_rportfwd_succfail, pf);
- } else {
- struct Packet *pktout;
- pktout = ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST);
- ssh2_pkt_addstring(pktout, "tcpip-forward");
- ssh2_pkt_addbool(pktout, 1);/* want reply */
- if (epf->saddr) {
- ssh2_pkt_addstring(pktout, epf->saddr);
- } else if (conf_get_int(conf, CONF_rport_acceptall)) {
- ssh2_pkt_addstring(pktout, "0.0.0.0");
- } else {
- ssh2_pkt_addstring(pktout, "127.0.0.1");
- }
- ssh2_pkt_adduint32(pktout, epf->sport);
- ssh2_pkt_send(ssh, pktout);
-
- ssh_queue_handler(ssh, SSH2_MSG_REQUEST_SUCCESS,
- SSH2_MSG_REQUEST_FAILURE,
- ssh_rportfwd_succfail, pf);
- }
- }
- }
- sfree(sportdesc);
- sfree(dportdesc);
- }
-}
-
-static void ssh1_smsg_stdout_stderr_data(Ssh ssh, struct Packet *pktin)
-{
- char *string;
- int stringlen, bufsize;
-
- ssh_pkt_getstring(pktin, &string, &stringlen);
- if (string == NULL) {
- bombout(("Incoming terminal data packet was badly formed"));
- return;
- }
-
- bufsize = from_backend(ssh->frontend, pktin->type == SSH1_SMSG_STDERR_DATA,
- string, stringlen);
- if (!ssh->v1_stdout_throttling && bufsize > SSH1_BUFFER_LIMIT) {
- ssh->v1_stdout_throttling = 1;
- ssh_throttle_conn(ssh, +1);
- }
-}
-
-static void ssh1_smsg_x11_open(Ssh ssh, struct Packet *pktin)
-{
- /* Remote side is trying to open a channel to talk to our
- * X-Server. Give them back a local channel number. */
- struct ssh_channel *c;
- int remoteid = ssh_pkt_getuint32(pktin);
-
- logevent("Received X11 connect request");
- /* Refuse if X11 forwarding is disabled. */
- if (!ssh->X11_fwd_enabled) {
- send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
- PKT_INT, remoteid, PKT_END);
- logevent("Rejected X11 connect request");
- } else {
- c = snew(struct ssh_channel);
- c->ssh = ssh;
-
- if (x11_init(&c->u.x11.s, ssh->x11disp, c,
- NULL, -1, ssh->conf) != NULL) {
- logevent("Opening X11 forward connection failed");
- sfree(c);
- send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
- PKT_INT, remoteid, PKT_END);
- } else {
- logevent
- ("Opening X11 forward connection succeeded");
- c->remoteid = remoteid;
- c->halfopen = FALSE;
- c->localid = alloc_channel_id(ssh);
- c->closes = 0;
- c->pending_eof = FALSE;
- c->throttling_conn = 0;
- c->type = CHAN_X11; /* identify channel type */
- add234(ssh->channels, c);
- send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
- PKT_INT, c->remoteid, PKT_INT,
- c->localid, PKT_END);
- logevent("Opened X11 forward channel");
- }
- }
-}
-
-static void ssh1_smsg_agent_open(Ssh ssh, struct Packet *pktin)
-{
- /* Remote side is trying to open a channel to talk to our
- * agent. Give them back a local channel number. */
- struct ssh_channel *c;
- int remoteid = ssh_pkt_getuint32(pktin);
-
- /* Refuse if agent forwarding is disabled. */
- if (!ssh->agentfwd_enabled) {
- send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
- PKT_INT, remoteid, PKT_END);
- } else {
- c = snew(struct ssh_channel);
- c->ssh = ssh;
- c->remoteid = remoteid;
- c->halfopen = FALSE;
- c->localid = alloc_channel_id(ssh);
- c->closes = 0;
- c->pending_eof = FALSE;
- c->throttling_conn = 0;
- c->type = CHAN_AGENT; /* identify channel type */
- c->u.a.lensofar = 0;
- c->u.a.message = NULL;
- add234(ssh->channels, c);
- send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
- PKT_INT, c->remoteid, PKT_INT, c->localid,
- PKT_END);
- }
-}
-
-static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin)
-{
- /* Remote side is trying to open a channel to talk to a
- * forwarded port. Give them back a local channel number. */
- struct ssh_channel *c;
- struct ssh_rportfwd pf, *pfp;
- int remoteid;
- int hostsize, port;
- char *host;
- const char *e;
- c = snew(struct ssh_channel);
- c->ssh = ssh;
-
- remoteid = ssh_pkt_getuint32(pktin);
- ssh_pkt_getstring(pktin, &host, &hostsize);
- port = ssh_pkt_getuint32(pktin);
-
- if (hostsize >= lenof(pf.dhost))
- hostsize = lenof(pf.dhost)-1;
- memcpy(pf.dhost, host, hostsize);
- pf.dhost[hostsize] = '\0';
- pf.dport = port;
- pfp = find234(ssh->rportfwds, &pf, NULL);
-
- if (pfp == NULL) {
- logeventf(ssh, "Rejected remote port open request for %s:%d",
- pf.dhost, port);
- send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
- PKT_INT, remoteid, PKT_END);
- } else {
- logeventf(ssh, "Received remote port open request for %s:%d",
- pf.dhost, port);
- e = pfd_newconnect(&c->u.pfd.s, pf.dhost, port,
- c, ssh->conf, pfp->pfrec->addressfamily);
- if (e != NULL) {
- logeventf(ssh, "Port open failed: %s", e);
- sfree(c);
- send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
- PKT_INT, remoteid, PKT_END);
- } else {
- c->remoteid = remoteid;
- c->halfopen = FALSE;
- c->localid = alloc_channel_id(ssh);
- c->closes = 0;
- c->pending_eof = FALSE;
- c->throttling_conn = 0;
- c->type = CHAN_SOCKDATA; /* identify channel type */
- add234(ssh->channels, c);
- send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
- PKT_INT, c->remoteid, PKT_INT,
- c->localid, PKT_END);
- logevent("Forwarded port opened successfully");
- }
- }
-}
-
-static void ssh1_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin)
-{
- unsigned int remoteid = ssh_pkt_getuint32(pktin);
- unsigned int localid = ssh_pkt_getuint32(pktin);
- struct ssh_channel *c;
-
- c = find234(ssh->channels, &remoteid, ssh_channelfind);
- if (c && c->type == CHAN_SOCKDATA_DORMANT) {
- c->remoteid = localid;
- c->halfopen = FALSE;
- c->type = CHAN_SOCKDATA;
- c->throttling_conn = 0;
- pfd_confirm(c->u.pfd.s);
- }
-
- if (c && c->pending_eof) {
- /*
- * We have a pending close on this channel,
- * which we decided on before the server acked
- * the channel open. So now we know the
- * remoteid, we can close it again.
- */
- ssh_channel_try_eof(c);
- }
-}
-
-static void ssh1_msg_channel_open_failure(Ssh ssh, struct Packet *pktin)
-{
- unsigned int remoteid = ssh_pkt_getuint32(pktin);
- struct ssh_channel *c;
-
- c = find234(ssh->channels, &remoteid, ssh_channelfind);
- if (c && c->type == CHAN_SOCKDATA_DORMANT) {
- logevent("Forwarded connection refused by server");
- pfd_close(c->u.pfd.s);
- del234(ssh->channels, c);
- sfree(c);
- }
-}
-
-static void ssh1_msg_channel_close(Ssh ssh, struct Packet *pktin)
-{
- /* Remote side closes a channel. */
- unsigned i = ssh_pkt_getuint32(pktin);
- struct ssh_channel *c;
- c = find234(ssh->channels, &i, ssh_channelfind);
- if (c && !c->halfopen) {
-
- if (pktin->type == SSH1_MSG_CHANNEL_CLOSE &&
- !(c->closes & CLOSES_RCVD_EOF)) {
- /*
- * Received CHANNEL_CLOSE, which we translate into
- * outgoing EOF.
- */
- int send_close = FALSE;
-
- c->closes |= CLOSES_RCVD_EOF;
-
- switch (c->type) {
- case CHAN_X11:
- if (c->u.x11.s)
- x11_send_eof(c->u.x11.s);
- else
- send_close = TRUE;
- case CHAN_SOCKDATA:
- if (c->u.pfd.s)
- x11_send_eof(c->u.pfd.s);
- else
- send_close = TRUE;
- case CHAN_AGENT:
- send_close = TRUE;
- }
-
- if (send_close && !(c->closes & CLOSES_SENT_EOF)) {
- send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
- PKT_END);
- c->closes |= CLOSES_SENT_EOF;
- }
- }
-
- if (pktin->type == SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION &&
- !(c->closes & CLOSES_RCVD_CLOSE)) {
-
- if (!(c->closes & CLOSES_SENT_EOF)) {
- bombout(("Received CHANNEL_CLOSE_CONFIRMATION for channel %d"
- " for which we never sent CHANNEL_CLOSE\n", i));
- }
-
- c->closes |= CLOSES_RCVD_CLOSE;
- }
-
- if (!((CLOSES_SENT_EOF | CLOSES_RCVD_EOF) & ~c->closes) &&
- !(c->closes & CLOSES_SENT_CLOSE)) {
- send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION,
- PKT_INT, c->remoteid, PKT_END);
- c->closes |= CLOSES_SENT_CLOSE;
- }
-
- if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes))
- ssh_channel_destroy(c);
- } else {
- bombout(("Received CHANNEL_CLOSE%s for %s channel %d\n",
- pktin->type == SSH1_MSG_CHANNEL_CLOSE ? "" :
- "_CONFIRMATION", c ? "half-open" : "nonexistent",
- i));
- }
-}
-
-static void ssh1_msg_channel_data(Ssh ssh, struct Packet *pktin)
-{
- /* Data sent down one of our channels. */
- int i = ssh_pkt_getuint32(pktin);
- char *p;
- int len;
- struct ssh_channel *c;
-
- ssh_pkt_getstring(pktin, &p, &len);
-
- c = find234(ssh->channels, &i, ssh_channelfind);
- if (c) {
- int bufsize = 0;
- switch (c->type) {
- case CHAN_X11:
- bufsize = x11_send(c->u.x11.s, p, len);
- break;
- case CHAN_SOCKDATA:
- bufsize = pfd_send(c->u.pfd.s, p, len);
- break;
- case CHAN_AGENT:
- /* Data for an agent message. Buffer it. */
- while (len > 0) {
- if (c->u.a.lensofar < 4) {
- unsigned int l = min(4 - c->u.a.lensofar, (unsigned)len);
- memcpy(c->u.a.msglen + c->u.a.lensofar, p,
- l);
- p += l;
- len -= l;
- c->u.a.lensofar += l;
- }
- if (c->u.a.lensofar == 4) {
- c->u.a.totallen =
- 4 + GET_32BIT(c->u.a.msglen);
- c->u.a.message = snewn(c->u.a.totallen,
- unsigned char);
- memcpy(c->u.a.message, c->u.a.msglen, 4);
- }
- if (c->u.a.lensofar >= 4 && len > 0) {
- unsigned int l =
- min(c->u.a.totallen - c->u.a.lensofar,
- (unsigned)len);
- memcpy(c->u.a.message + c->u.a.lensofar, p,
- l);
- p += l;
- len -= l;
- c->u.a.lensofar += l;
- }
- if (c->u.a.lensofar == c->u.a.totallen) {
- void *reply;
- int replylen;
- if (agent_query(c->u.a.message,
- c->u.a.totallen,
- &reply, &replylen,
- ssh_agentf_callback, c))
- ssh_agentf_callback(c, reply, replylen);
- sfree(c->u.a.message);
- c->u.a.lensofar = 0;
- }
- }
- bufsize = 0; /* agent channels never back up */
- break;
- }
- if (!c->throttling_conn && bufsize > SSH1_BUFFER_LIMIT) {
- c->throttling_conn = 1;
- ssh_throttle_conn(ssh, +1);
- }
- }
-}
-
-static void ssh1_smsg_exit_status(Ssh ssh, struct Packet *pktin)
-{
- ssh->exitcode = ssh_pkt_getuint32(pktin);
- logeventf(ssh, "Server sent command exit status %d", ssh->exitcode);
- send_packet(ssh, SSH1_CMSG_EXIT_CONFIRMATION, PKT_END);
- /*
- * In case `helpful' firewalls or proxies tack
- * extra human-readable text on the end of the
- * session which we might mistake for another
- * encrypted packet, we close the session once
- * we've sent EXIT_CONFIRMATION.
- */
- ssh_disconnect(ssh, NULL, NULL, 0, TRUE);
-}
-
-/* Helper function to deal with sending tty modes for REQUEST_PTY */
-static void ssh1_send_ttymode(void *data, char *mode, char *val)
-{
- struct Packet *pktout = (struct Packet *)data;
- int i = 0;
- unsigned int arg = 0;
- while (strcmp(mode, ssh_ttymodes[i].mode) != 0) i++;
- if (i == lenof(ssh_ttymodes)) return;
- switch (ssh_ttymodes[i].type) {
- case TTY_OP_CHAR:
- arg = ssh_tty_parse_specchar(val);
- break;
- case TTY_OP_BOOL:
- arg = ssh_tty_parse_boolean(val);
- break;
- }
- ssh2_pkt_addbyte(pktout, ssh_ttymodes[i].opcode);
- ssh2_pkt_addbyte(pktout, arg);
-}
-
-
-static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen,
- struct Packet *pktin)
-{
- crBegin(ssh->do_ssh1_connection_crstate);
-
- ssh->packet_dispatch[SSH1_SMSG_STDOUT_DATA] =
- ssh->packet_dispatch[SSH1_SMSG_STDERR_DATA] =
- ssh1_smsg_stdout_stderr_data;
-
- ssh->packet_dispatch[SSH1_MSG_CHANNEL_OPEN_CONFIRMATION] =
- ssh1_msg_channel_open_confirmation;
- ssh->packet_dispatch[SSH1_MSG_CHANNEL_OPEN_FAILURE] =
- ssh1_msg_channel_open_failure;
- ssh->packet_dispatch[SSH1_MSG_CHANNEL_CLOSE] =
- ssh->packet_dispatch[SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION] =
- ssh1_msg_channel_close;
- ssh->packet_dispatch[SSH1_MSG_CHANNEL_DATA] = ssh1_msg_channel_data;
- ssh->packet_dispatch[SSH1_SMSG_EXIT_STATUS] = ssh1_smsg_exit_status;
-
- if (conf_get_int(ssh->conf, CONF_agentfwd) && agent_exists()) {
- logevent("Requesting agent forwarding");
- send_packet(ssh, SSH1_CMSG_AGENT_REQUEST_FORWARDING, PKT_END);
- do {
- crReturnV;
- } while (!pktin);
- if (pktin->type != SSH1_SMSG_SUCCESS
- && pktin->type != SSH1_SMSG_FAILURE) {
- bombout(("Protocol confusion"));
- crStopV;
- } else if (pktin->type == SSH1_SMSG_FAILURE) {
- logevent("Agent forwarding refused");
- } else {
- logevent("Agent forwarding enabled");
- ssh->agentfwd_enabled = TRUE;
- ssh->packet_dispatch[SSH1_SMSG_AGENT_OPEN] = ssh1_smsg_agent_open;
- }
- }
-
- if (conf_get_int(ssh->conf, CONF_x11_forward) &&
- (ssh->x11disp = x11_setup_display(conf_get_str(ssh->conf, CONF_x11_display),
- conf_get_int(ssh->conf, CONF_x11_auth), ssh->conf))) {
- logevent("Requesting X11 forwarding");
- /*
- * Note that while we blank the X authentication data here, we don't
- * take any special action to blank the start of an X11 channel,
- * so using MIT-MAGIC-COOKIE-1 and actually opening an X connection
- * without having session blanking enabled is likely to leak your
- * cookie into the log.
- */
- if (ssh->v1_local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) {
- send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING,
- PKT_STR, ssh->x11disp->remoteauthprotoname,
- PKTT_PASSWORD,
- PKT_STR, ssh->x11disp->remoteauthdatastring,
- PKTT_OTHER,
- PKT_INT, ssh->x11disp->screennum,
- PKT_END);
- } else {
- send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING,
- PKT_STR, ssh->x11disp->remoteauthprotoname,
- PKTT_PASSWORD,
- PKT_STR, ssh->x11disp->remoteauthdatastring,
- PKTT_OTHER,
- PKT_END);
- }
- do {
- crReturnV;
- } while (!pktin);
- if (pktin->type != SSH1_SMSG_SUCCESS
- && pktin->type != SSH1_SMSG_FAILURE) {
- bombout(("Protocol confusion"));
- crStopV;
- } else if (pktin->type == SSH1_SMSG_FAILURE) {
- logevent("X11 forwarding refused");
- } else {
- logevent("X11 forwarding enabled");
- ssh->X11_fwd_enabled = TRUE;
- ssh->packet_dispatch[SSH1_SMSG_X11_OPEN] = ssh1_smsg_x11_open;
- }
- }
-
- ssh_setup_portfwd(ssh, ssh->conf);
- ssh->packet_dispatch[SSH1_MSG_PORT_OPEN] = ssh1_msg_port_open;
-
- if (!conf_get_int(ssh->conf, CONF_nopty)) {
- struct Packet *pkt;
- /* Unpick the terminal-speed string. */
- /* XXX perhaps we should allow no speeds to be sent. */
- ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */
- sscanf(conf_get_str(ssh->conf, CONF_termspeed), "%d,%d", &ssh->ospeed, &ssh->ispeed);
- /* Send the pty request. */
- pkt = ssh1_pkt_init(SSH1_CMSG_REQUEST_PTY);
- ssh_pkt_addstring(pkt, conf_get_str(ssh->conf, CONF_termtype));
- ssh_pkt_adduint32(pkt, ssh->term_height);
- ssh_pkt_adduint32(pkt, ssh->term_width);
- ssh_pkt_adduint32(pkt, 0); /* width in pixels */
- ssh_pkt_adduint32(pkt, 0); /* height in pixels */
- parse_ttymodes(ssh, ssh1_send_ttymode, (void *)pkt);
- ssh_pkt_addbyte(pkt, SSH1_TTY_OP_ISPEED);
- ssh_pkt_adduint32(pkt, ssh->ispeed);
- ssh_pkt_addbyte(pkt, SSH1_TTY_OP_OSPEED);
- ssh_pkt_adduint32(pkt, ssh->ospeed);
- ssh_pkt_addbyte(pkt, SSH_TTY_OP_END);
- s_wrpkt(ssh, pkt);
- ssh->state = SSH_STATE_INTERMED;
- do {
- crReturnV;
- } while (!pktin);
- if (pktin->type != SSH1_SMSG_SUCCESS
- && pktin->type != SSH1_SMSG_FAILURE) {
- bombout(("Protocol confusion"));
- crStopV;
- } else if (pktin->type == SSH1_SMSG_FAILURE) {
- c_write_str(ssh, "Server refused to allocate pty\r\n");
- ssh->editing = ssh->echoing = 1;
- } else {
- logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)",
- ssh->ospeed, ssh->ispeed);
- ssh->got_pty = TRUE;
- }
- } else {
- ssh->editing = ssh->echoing = 1;
- }
-
- if (conf_get_int(ssh->conf, CONF_compression)) {
- send_packet(ssh, SSH1_CMSG_REQUEST_COMPRESSION, PKT_INT, 6, PKT_END);
- do {
- crReturnV;
- } while (!pktin);
- if (pktin->type != SSH1_SMSG_SUCCESS
- && pktin->type != SSH1_SMSG_FAILURE) {
- bombout(("Protocol confusion"));
- crStopV;
- } else if (pktin->type == SSH1_SMSG_FAILURE) {
- c_write_str(ssh, "Server refused to compress\r\n");
- }
- logevent("Started compression");
- ssh->v1_compressing = TRUE;
- ssh->cs_comp_ctx = zlib_compress_init();
- logevent("Initialised zlib (RFC1950) compression");
- ssh->sc_comp_ctx = zlib_decompress_init();
- logevent("Initialised zlib (RFC1950) decompression");
- }
-
- /*
- * Start the shell or command.
- *
- * Special case: if the first-choice command is an SSH-2
- * subsystem (hence not usable here) and the second choice
- * exists, we fall straight back to that.
- */
- {
- char *cmd = conf_get_str(ssh->conf, CONF_remote_cmd);
-
- if (conf_get_int(ssh->conf, CONF_ssh_subsys) &&
- conf_get_str(ssh->conf, CONF_remote_cmd2)) {
- cmd = conf_get_str(ssh->conf, CONF_remote_cmd2);
- ssh->fallback_cmd = TRUE;
- }
- if (*cmd)
- send_packet(ssh, SSH1_CMSG_EXEC_CMD, PKT_STR, cmd, PKT_END);
- else
- send_packet(ssh, SSH1_CMSG_EXEC_SHELL, PKT_END);
- logevent("Started session");
- }
-
- ssh->state = SSH_STATE_SESSION;
- if (ssh->size_needed)
- ssh_size(ssh, ssh->term_width, ssh->term_height);
- if (ssh->eof_needed)
- ssh_special(ssh, TS_EOF);
-
- if (ssh->ldisc)
- ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */
- ssh->send_ok = 1;
- ssh->channels = newtree234(ssh_channelcmp);
- while (1) {
-
- /*
- * By this point, most incoming packets are already being
- * handled by the dispatch table, and we need only pay
- * attention to the unusual ones.
- */
-
- crReturnV;
- if (pktin) {
- if (pktin->type == SSH1_SMSG_SUCCESS) {
- /* may be from EXEC_SHELL on some servers */
- } else if (pktin->type == SSH1_SMSG_FAILURE) {
- /* may be from EXEC_SHELL on some servers
- * if no pty is available or in other odd cases. Ignore */
- } else {
- bombout(("Strange packet received: type %d", pktin->type));
- crStopV;
- }
- } else {
- while (inlen > 0) {
- int len = min(inlen, 512);
- send_packet(ssh, SSH1_CMSG_STDIN_DATA,
- PKT_INT, len, PKTT_DATA, PKT_DATA, in, len,
- PKTT_OTHER, PKT_END);
- in += len;
- inlen -= len;
- }
- }
- }
-
- crFinishV;
-}
-
-/*
- * Handle the top-level SSH-2 protocol.
- */
-static void ssh1_msg_debug(Ssh ssh, struct Packet *pktin)
-{
- char *msg;
- int msglen;
-
- ssh_pkt_getstring(pktin, &msg, &msglen);
- logeventf(ssh, "Remote debug message: %.*s", msglen, msg);
-}
-
-static void ssh1_msg_disconnect(Ssh ssh, struct Packet *pktin)
-{
- /* log reason code in disconnect message */
- char *msg;
- int msglen;
-
- ssh_pkt_getstring(pktin, &msg, &msglen);
- bombout(("Server sent disconnect message:\n\"%.*s\"", msglen, msg));
-}
-
-static void ssh_msg_ignore(Ssh ssh, struct Packet *pktin)
-{
- /* Do nothing, because we're ignoring it! Duhh. */
-}
-
-static void ssh1_protocol_setup(Ssh ssh)
-{
- int i;
-
- /*
- * Most messages are handled by the coroutines.
- */
- for (i = 0; i < 256; i++)
- ssh->packet_dispatch[i] = NULL;
-
- /*
- * These special message types we install handlers for.
- */
- ssh->packet_dispatch[SSH1_MSG_DISCONNECT] = ssh1_msg_disconnect;
- ssh->packet_dispatch[SSH1_MSG_IGNORE] = ssh_msg_ignore;
- ssh->packet_dispatch[SSH1_MSG_DEBUG] = ssh1_msg_debug;
-}
-
-static void ssh1_protocol(Ssh ssh, void *vin, int inlen,
- struct Packet *pktin)
-{
- unsigned char *in=(unsigned char*)vin;
- if (ssh->state == SSH_STATE_CLOSED)
- return;
-
- if (pktin && ssh->packet_dispatch[pktin->type]) {
- ssh->packet_dispatch[pktin->type](ssh, pktin);
- return;
- }
-
- if (!ssh->protocol_initial_phase_done) {
- if (do_ssh1_login(ssh, in, inlen, pktin))
- ssh->protocol_initial_phase_done = TRUE;
- else
- return;
- }
-
- do_ssh1_connection(ssh, in, inlen, pktin);
-}
-
-/*
- * Utility routine for decoding comma-separated strings in KEXINIT.
- */
-static int in_commasep_string(char *needle, char *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 */
- }
-}
-
-/*
- * 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)
-{
- int needlen;
- 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 */
- )
- return 1;
- return 0;
-}
-
-
-/*
- * 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.)
- */
-#define SSH2_MKKEY_ITERS (2)
-static void ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H, char chr,
- unsigned char *keyspace)
-{
- const struct ssh_hash *h = ssh->kex->hash;
- void *s;
- /* First 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, &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);
-}
-
-/*
- * Handle the SSH-2 transport layer.
- */
-static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
- struct Packet *pktin)
-{
- unsigned char *in = (unsigned char *)vin;
- struct do_ssh2_transport_state {
- int nbits, pbits, warn_kex, warn_cscipher, warn_sccipher;
- Bignum p, g, e, f, K;
- void *our_kexinit;
- int our_kexinitlen;
- int kex_init_value, kex_reply_value;
- const struct ssh_mac **maclist;
- int nmacs;
- const struct ssh2_cipher *cscipher_tobe;
- const struct ssh2_cipher *sccipher_tobe;
- const struct ssh_mac *csmac_tobe;
- const struct ssh_mac *scmac_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 */
- unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN];
- int n_preferred_kex;
- const struct ssh_kexes *preferred_kex[KEX_MAX];
- int n_preferred_ciphers;
- const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
- const struct ssh_compress *preferred_comp;
- int userauth_succeeded; /* for delayed compression */
- int pending_compression;
- int got_session_id, activated_authconn;
- struct Packet *pktout;
- int dlgret;
- int guessok;
- int ignorepkt;
- };
- crState(do_ssh2_transport_state);
-
- crBegin(ssh->do_ssh2_transport_crstate);
-
- s->cscipher_tobe = s->sccipher_tobe = NULL;
- s->csmac_tobe = s->scmac_tobe = NULL;
- s->cscomp_tobe = s->sccomp_tobe = NULL;
-
- s->got_session_id = s->activated_authconn = FALSE;
- s->userauth_succeeded = FALSE;
- s->pending_compression = FALSE;
-
- /*
- * Be prepared to work around the buggy MAC problem.
- */
- if (ssh->remote_bugs & BUG_SSH2_HMAC)
- s->maclist = buggymacs, s->nmacs = lenof(buggymacs);
- else
- s->maclist = macs, s->nmacs = lenof(macs);
-
- begin_key_exchange:
- ssh->pkt_kctx = SSH2_PKTCTX_NOKEX;
- {
- int i, j, commalist_started;
-
- /*
- * Set up the preferred key exchange. (NULL => warn below here)
- */
- s->n_preferred_kex = 0;
- for (i = 0; i < KEX_MAX; i++) {
- switch (conf_get_int_int(ssh->conf, CONF_ssh_kexlist, i)) {
- case KEX_DHGEX:
- s->preferred_kex[s->n_preferred_kex++] =
- &ssh_diffiehellman_gex;
- break;
- case KEX_DHGROUP14:
- s->preferred_kex[s->n_preferred_kex++] =
- &ssh_diffiehellman_group14;
- break;
- case KEX_DHGROUP1:
- s->preferred_kex[s->n_preferred_kex++] =
- &ssh_diffiehellman_group1;
- break;
- case KEX_RSA:
- s->preferred_kex[s->n_preferred_kex++] =
- &ssh_rsa_kex;
- break;
- case KEX_WARN:
- /* Flag for later. Don't bother if it's the last in
- * the list. */
- if (i < KEX_MAX - 1) {
- s->preferred_kex[s->n_preferred_kex++] = NULL;
- }
- break;
- }
- }
-
- /*
- * Set up the preferred ciphers. (NULL => warn below here)
- */
- s->n_preferred_ciphers = 0;
- for (i = 0; i < CIPHER_MAX; i++) {
- switch (conf_get_int_int(ssh->conf, CONF_ssh_cipherlist, i)) {
- case CIPHER_BLOWFISH:
- s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_blowfish;
- break;
- case CIPHER_DES:
- if (conf_get_int(ssh->conf, CONF_ssh2_des_cbc)) {
- s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_des;
- }
- break;
- case CIPHER_3DES:
- s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_3des;
- break;
- case CIPHER_AES:
- s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_aes;
- break;
- case CIPHER_ARCFOUR:
- s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_arcfour;
- break;
- case CIPHER_WARN:
- /* Flag for later. Don't bother if it's the last in
- * the list. */
- if (i < CIPHER_MAX - 1) {
- s->preferred_ciphers[s->n_preferred_ciphers++] = NULL;
- }
- break;
- }
- }
-
- /*
- * Set up preferred compression.
- */
- if (conf_get_int(ssh->conf, CONF_compression))
- s->preferred_comp = &ssh_zlib;
- else
- s->preferred_comp = &ssh_comp_none;
-
- /*
- * Enable queueing of outgoing auth- or connection-layer
- * packets while we are in the middle of a key exchange.
- */
- ssh->queueing = TRUE;
-
- /*
- * Flag that KEX is in progress.
- */
- 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());
- /* List key exchange algorithms. */
- ssh2_pkt_addstring_start(s->pktout);
- commalist_started = 0;
- 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;
- }
- }
- /* List server host key algorithms. */
- 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, ",");
- }
- /* List client->server encryption algorithms. */
- ssh2_pkt_addstring_start(s->pktout);
- commalist_started = 0;
- 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;
- }
- }
- /* List server->client encryption algorithms. */
- ssh2_pkt_addstring_start(s->pktout);
- commalist_started = 0;
- 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;
- }
- }
- /* List client->server MAC algorithms. */
- ssh2_pkt_addstring_start(s->pktout);
- 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, ",");
- }
- /* List server->client MAC algorithms. */
- ssh2_pkt_addstring_start(s->pktout);
- 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, ",");
- }
- /* 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);
- assert(lenof(compressions) > 1);
- /* Prefer non-delayed versions */
- ssh2_pkt_addstring_str(s->pktout, s->preferred_comp->name);
- /* 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);
- }
- 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);
- }
- }
- }
- }
- /* List client->server languages. Empty list. */
- ssh2_pkt_addstring_start(s->pktout);
- /* List server->client languages. Empty list. */
- ssh2_pkt_addstring_start(s->pktout);
- /* First KEX packet does _not_ follow, because we're not that brave. */
- ssh2_pkt_addbool(s->pktout, FALSE);
- /* Reserved. */
- ssh2_pkt_adduint32(s->pktout, 0);
- }
-
- s->our_kexinitlen = s->pktout->length - 5;
- s->our_kexinit = snewn(s->our_kexinitlen, unsigned char);
- memcpy(s->our_kexinit, s->pktout->data + 5, s->our_kexinitlen);
-
- ssh2_pkt_send_noqueue(ssh, s->pktout);
-
- if (!pktin)
- crWaitUntil(pktin);
-
- /*
- * Now examine the other side's KEXINIT to see what we're up
- * to.
- */
- {
- char *str, *preferred;
- int i, j, len;
-
- if (pktin->type != SSH2_MSG_KEXINIT) {
- bombout(("expected key exchange packet from server"));
- crStop(0);
- }
- ssh->kex = NULL;
- ssh->hostkey = NULL;
- s->cscipher_tobe = NULL;
- s->sccipher_tobe = NULL;
- s->csmac_tobe = NULL;
- s->scmac_tobe = NULL;
- s->cscomp_tobe = NULL;
- s->sccomp_tobe = NULL;
- 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 */
-
- 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)",
- str ? str : "(null)"));
- crStop(0);
- }
- /*
- * 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 */
- 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 = s->guessok &&
- first_in_commasep_string(hostkey_algs[0]->name, str, len);
- ssh_pkt_getstring(pktin, &str, &len); /* client->server cipher */
- 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)",
- str ? str : "(null)"));
- crStop(0);
- }
-
- ssh_pkt_getstring(pktin, &str, &len); /* server->client cipher */
- 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;
- }
- }
- }
- if (s->sccipher_tobe)
- break;
- }
- if (!s->sccipher_tobe) {
- bombout(("Couldn't agree a server-to-client cipher (available: %s)",
- str ? str : "(null)"));
- crStop(0);
- }
-
- ssh_pkt_getstring(pktin, &str, &len); /* client->server mac */
- 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 */
- 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 */
- 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 */
- 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");
- }
- ssh_pkt_getstring(pktin, &str, &len); /* client->server language */
- ssh_pkt_getstring(pktin, &str, &len); /* server->client language */
- s->ignorepkt = ssh2_pkt_getbool(pktin) && !s->guessok;
-
- if (s->warn_kex) {
- ssh_set_frozen(ssh, 1);
- s->dlgret = askalg(ssh->frontend, "key-exchange algorithm",
- ssh->kex->name,
- ssh_dialog_callback, ssh);
- if (s->dlgret < 0) {
- do {
- crReturn(0);
- if (pktin) {
- bombout(("Unexpected data from server while"
- " waiting for user response"));
- crStop(0);
- }
- } while (pktin || inlen > 0);
- s->dlgret = ssh->user_response;
- }
- ssh_set_frozen(ssh, 0);
- if (s->dlgret == 0) {
- ssh_disconnect(ssh, "User aborted at kex warning", NULL,
- 0, TRUE);
- crStop(0);
- }
- }
-
- if (s->warn_cscipher) {
- ssh_set_frozen(ssh, 1);
- s->dlgret = askalg(ssh->frontend,
- "client-to-server cipher",
- s->cscipher_tobe->name,
- ssh_dialog_callback, ssh);
- if (s->dlgret < 0) {
- do {
- crReturn(0);
- if (pktin) {
- bombout(("Unexpected data from server while"
- " waiting for user response"));
- crStop(0);
- }
- } while (pktin || inlen > 0);
- s->dlgret = ssh->user_response;
- }
- ssh_set_frozen(ssh, 0);
- if (s->dlgret == 0) {
- ssh_disconnect(ssh, "User aborted at cipher warning", NULL,
- 0, TRUE);
- crStop(0);
- }
- }
-
- if (s->warn_sccipher) {
- ssh_set_frozen(ssh, 1);
- s->dlgret = askalg(ssh->frontend,
- "server-to-client cipher",
- s->sccipher_tobe->name,
- ssh_dialog_callback, ssh);
- if (s->dlgret < 0) {
- do {
- crReturn(0);
- if (pktin) {
- bombout(("Unexpected data from server while"
- " waiting for user response"));
- crStop(0);
- }
- } while (pktin || inlen > 0);
- s->dlgret = ssh->user_response;
- }
- ssh_set_frozen(ssh, 0);
- if (s->dlgret == 0) {
- ssh_disconnect(ssh, "User aborted at cipher warning", NULL,
- 0, TRUE);
- crStop(0);
- }
- }
-
- ssh->exhash = ssh->kex->hash->init();
- hash_string(ssh->kex->hash, ssh->exhash, ssh->v_c, strlen(ssh->v_c));
- hash_string(ssh->kex->hash, ssh->exhash, ssh->v_s, strlen(ssh->v_s));
- hash_string(ssh->kex->hash, ssh->exhash,
- s->our_kexinit, s->our_kexinitlen);
- sfree(s->our_kexinit);
- if (pktin->length > 5)
- hash_string(ssh->kex->hash, ssh->exhash,
- pktin->data + 5, pktin->length - 5);
-
- if (s->ignorepkt) /* first_kex_packet_follows */
- crWaitUntil(pktin); /* Ignore packet */
- }
-
- if (ssh->kex->main_type == KEXTYPE_DH) {
- /*
- * Work out the number of bits of key we will need from the
- * key exchange. We start with the maximum key length of
- * either cipher...
- */
- {
- int csbits, scbits;
-
- csbits = s->cscipher_tobe->keylen;
- scbits = s->sccipher_tobe->keylen;
- s->nbits = (csbits > scbits ? csbits : scbits);
- }
- /* The keys only have hlen-bit entropy, since they're based on
- * a hash. So cap the key size at hlen bits. */
- if (s->nbits > ssh->kex->hash->hlen * 8)
- s->nbits = ssh->kex->hash->hlen * 8;
-
- /*
- * If we're doing Diffie-Hellman group exchange, start by
- * requesting a group.
- */
- if (!ssh->kex->pdata) {
- logevent("Doing Diffie-Hellman group exchange");
- ssh->pkt_kctx = SSH2_PKTCTX_DHGEX;
- /*
- * Work out how big a DH group we will need to allow that
- * much data.
- */
- s->pbits = 512 << ((s->nbits - 1) / 64);
- s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_DH_GEX_REQUEST);
- ssh2_pkt_adduint32(s->pktout, s->pbits);
- ssh2_pkt_send_noqueue(ssh, s->pktout);
-
- crWaitUntil(pktin);
- if (pktin->type != SSH2_MSG_KEX_DH_GEX_GROUP) {
- bombout(("expected key exchange group packet from server"));
- crStop(0);
- }
- s->p = ssh2_pkt_getmp(pktin);
- s->g = ssh2_pkt_getmp(pktin);
- if (!s->p || !s->g) {
- bombout(("unable to read mp-ints from incoming group packet"));
- crStop(0);
- }
- ssh->kex_ctx = dh_setup_gex(s->p, s->g);
- s->kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT;
- s->kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY;
- } else {
- ssh->pkt_kctx = SSH2_PKTCTX_DHGROUP;
- ssh->kex_ctx = dh_setup_group(ssh->kex);
- s->kex_init_value = SSH2_MSG_KEXDH_INIT;
- s->kex_reply_value = SSH2_MSG_KEXDH_REPLY;
- logeventf(ssh, "Using Diffie-Hellman with standard group \"%s\"",
- ssh->kex->groupname);
- }
-
- logeventf(ssh, "Doing Diffie-Hellman key exchange with hash %s",
- ssh->kex->hash->text_name);
- /*
- * Now generate and send e for Diffie-Hellman.
- */
- set_busy_status(ssh->frontend, BUSY_CPU); /* this can take a while */
- s->e = dh_create_e(ssh->kex_ctx, s->nbits * 2);
- s->pktout = ssh2_pkt_init(s->kex_init_value);
- ssh2_pkt_addmp(s->pktout, s->e);
- ssh2_pkt_send_noqueue(ssh, s->pktout);
-
- set_busy_status(ssh->frontend, BUSY_WAITING); /* wait for server */
- crWaitUntil(pktin);
- if (pktin->type != s->kex_reply_value) {
- bombout(("expected key exchange reply packet from server"));
- crStop(0);
- }
- set_busy_status(ssh->frontend, BUSY_CPU); /* cogitate */
- ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen);
- s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen);
- s->f = ssh2_pkt_getmp(pktin);
- if (!s->f) {
- bombout(("unable to parse key exchange reply packet"));
- crStop(0);
- }
- ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen);
-
- s->K = dh_find_K(ssh->kex_ctx, s->f);
-
- /* We assume everything from now on will be quick, and it might
- * involve user interaction. */
- set_busy_status(ssh->frontend, BUSY_NOT);
-
- hash_string(ssh->kex->hash, ssh->exhash, s->hostkeydata, s->hostkeylen);
- if (!ssh->kex->pdata) {
- hash_uint32(ssh->kex->hash, ssh->exhash, s->pbits);
- hash_mpint(ssh->kex->hash, ssh->exhash, s->p);
- hash_mpint(ssh->kex->hash, ssh->exhash, s->g);
- }
- hash_mpint(ssh->kex->hash, ssh->exhash, s->e);
- hash_mpint(ssh->kex->hash, ssh->exhash, s->f);
-
- dh_cleanup(ssh->kex_ctx);
- freebn(s->f);
- if (!ssh->kex->pdata) {
- freebn(s->g);
- freebn(s->p);
- }
- } else {
- logeventf(ssh, "Doing RSA key exchange with hash %s",
- ssh->kex->hash->text_name);
- ssh->pkt_kctx = SSH2_PKTCTX_RSAKEX;
- /*
- * RSA key exchange. First expect a KEXRSA_PUBKEY packet
- * from the server.
- */
- crWaitUntil(pktin);
- if (pktin->type != SSH2_MSG_KEXRSA_PUBKEY) {
- bombout(("expected RSA public key packet from server"));
- crStop(0);
- }
-
- ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen);
- hash_string(ssh->kex->hash, ssh->exhash,
- s->hostkeydata, s->hostkeylen);
- s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen);
-
- {
- char *keydata;
- ssh_pkt_getstring(pktin, &keydata, &s->rsakeylen);
- s->rsakeydata = snewn(s->rsakeylen, char);
- memcpy(s->rsakeydata, keydata, s->rsakeylen);
- }
-
- s->rsakey = ssh_rsakex_newkey(s->rsakeydata, s->rsakeylen);
- if (!s->rsakey) {
- sfree(s->rsakeydata);
- bombout(("unable to parse RSA public key from server"));
- crStop(0);
- }
-
- hash_string(ssh->kex->hash, ssh->exhash, s->rsakeydata, s->rsakeylen);
-
- /*
- * Next, set up a shared secret K, of precisely KLEN -
- * 2*HLEN - 49 bits, where KLEN is the bit length of the
- * RSA key modulus and HLEN is the bit length of the hash
- * we're using.
- */
- {
- int klen = ssh_rsakex_klen(s->rsakey);
- int nbits = klen - (2*ssh->kex->hash->hlen*8 + 49);
- int i, byte = 0;
- unsigned char *kstr1, *kstr2, *outstr;
- int kstr1len, kstr2len, outstrlen;
-
- s->K = bn_power_2(nbits - 1);
-
- for (i = 0; i < nbits; i++) {
- if ((i & 7) == 0) {
- byte = random_byte();
- }
- bignum_set_bit(s->K, i, (byte >> (i & 7)) & 1);
- }
-
- /*
- * Encode this as an mpint.
- */
- kstr1 = ssh2_mpint_fmt(s->K, &kstr1len);
- kstr2 = snewn(kstr2len = 4 + kstr1len, unsigned char);
- PUT_32BIT(kstr2, kstr1len);
- memcpy(kstr2 + 4, kstr1, kstr1len);
-
- /*
- * Encrypt it with the given RSA key.
- */
- outstrlen = (klen + 7) / 8;
- outstr = snewn(outstrlen, unsigned char);
- ssh_rsakex_encrypt(ssh->kex->hash, kstr2, kstr2len,
- outstr, outstrlen, s->rsakey);
-
- /*
- * And send it off in a return packet.
- */
- s->pktout = ssh2_pkt_init(SSH2_MSG_KEXRSA_SECRET);
- ssh2_pkt_addstring_start(s->pktout);
- ssh2_pkt_addstring_data(s->pktout, (char *)outstr, outstrlen);
- ssh2_pkt_send_noqueue(ssh, s->pktout);
-
- hash_string(ssh->kex->hash, ssh->exhash, outstr, outstrlen);
-
- sfree(kstr2);
- sfree(kstr1);
- sfree(outstr);
- }
-
- ssh_rsakex_freekey(s->rsakey);
-
- crWaitUntil(pktin);
- if (pktin->type != SSH2_MSG_KEXRSA_DONE) {
- sfree(s->rsakeydata);
- bombout(("expected signature packet from server"));
- crStop(0);
- }
-
- ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen);
-
- sfree(s->rsakeydata);
- }
-
- hash_mpint(ssh->kex->hash, ssh->exhash, s->K);
- assert(ssh->kex->hash->hlen <= sizeof(s->exchange_hash));
- ssh->kex->hash->final(ssh->exhash, s->exchange_hash);
-
- ssh->kex_ctx = NULL;
-
-#if 0
- debug(("Exchange hash is:\n"));
- dmemdump(s->exchange_hash, ssh->kex->hash->hlen);
-#endif
-
- if (!s->hkey ||
- !ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen,
- (char *)s->exchange_hash,
- ssh->kex->hash->hlen)) {
- bombout(("Server's host key did not match the signature supplied"));
- crStop(0);
- }
-
- /*
- * Authenticate remote host: verify host key. (We've already
- * checked the signature of the exchange hash.)
- */
- s->keystr = ssh->hostkey->fmtkey(s->hkey);
- s->fingerprint = ssh->hostkey->fingerprint(s->hkey);
- ssh_set_frozen(ssh, 1);
- s->dlgret = verify_ssh_host_key(ssh->frontend,
- ssh->savedhost, ssh->savedport,
- ssh->hostkey->keytype, s->keystr,
- s->fingerprint,
- ssh_dialog_callback, ssh);
- if (s->dlgret < 0) {
- do {
- crReturn(0);
- if (pktin) {
- bombout(("Unexpected data from server while waiting"
- " for user host key response"));
- crStop(0);
- }
- } while (pktin || inlen > 0);
- s->dlgret = ssh->user_response;
- }
- ssh_set_frozen(ssh, 0);
- if (s->dlgret == 0) {
- ssh_disconnect(ssh, "User aborted at host key verification", NULL,
- 0, TRUE);
- crStop(0);
- }
- if (!s->got_session_id) { /* don't bother logging this in rekeys */
- logevent("Host key fingerprint is:");
- logevent(s->fingerprint);
- }
- sfree(s->fingerprint);
- sfree(s->keystr);
- ssh->hostkey->freekey(s->hkey);
-
- /*
- * The exchange hash from the very first key exchange is also
- * the session id, used in session key construction and
- * authentication.
- */
- if (!s->got_session_id) {
- assert(sizeof(s->exchange_hash) <= sizeof(ssh->v2_session_id));
- memcpy(ssh->v2_session_id, s->exchange_hash,
- sizeof(s->exchange_hash));
- ssh->v2_session_id_len = ssh->kex->hash->hlen;
- assert(ssh->v2_session_id_len <= sizeof(ssh->v2_session_id));
- s->got_session_id = TRUE;
- }
-
- /*
- * Send SSH2_MSG_NEWKEYS.
- */
- s->pktout = ssh2_pkt_init(SSH2_MSG_NEWKEYS);
- ssh2_pkt_send_noqueue(ssh, s->pktout);
- ssh->outgoing_data_size = 0; /* start counting from here */
-
- /*
- * We've sent client NEWKEYS, so create and initialise
- * client-to-server session keys.
- */
- 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->cs_mac_ctx)
- ssh->csmac->free_context(ssh->cs_mac_ctx);
- ssh->csmac = s->csmac_tobe;
- ssh->cs_mac_ctx = ssh->csmac->make_context();
-
- if (ssh->cs_comp_ctx)
- ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx);
- ssh->cscomp = s->cscomp_tobe;
- ssh->cs_comp_ctx = ssh->cscomp->compress_init();
-
- /*
- * 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);
- memset(keyspace, 0, 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->cscomp->text_name)
- logeventf(ssh, "Initialised %s compression",
- ssh->cscomp->text_name);
-
- /*
- * Now our end of the key exchange is complete, we can send all
- * our queued higher-layer packets.
- */
- ssh->queueing = FALSE;
- ssh2_pkt_queuesend(ssh);
-
- /*
- * Expect SSH2_MSG_NEWKEYS from server.
- */
- crWaitUntil(pktin);
- if (pktin->type != SSH2_MSG_NEWKEYS) {
- bombout(("expected new-keys packet from server"));
- crStop(0);
- }
- ssh->incoming_data_size = 0; /* start counting from here */
-
- /*
- * We've seen server NEWKEYS, so create and initialise
- * server-to-client session keys.
- */
- 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 (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 (ssh->sc_comp_ctx)
- ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx);
- ssh->sccomp = s->sccomp_tobe;
- ssh->sc_comp_ctx = ssh->sccomp->decompress_init();
-
- /*
- * 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);
- memset(keyspace, 0, 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->sccomp->text_name)
- logeventf(ssh, "Initialised %s decompression",
- ssh->sccomp->text_name);
-
- /*
- * Free shared secret.
- */
- freebn(s->K);
-
- /*
- * Key exchange is over. Loop straight back round if we have a
- * deferred rekey reason.
- */
- if (ssh->deferred_rekey_reason) {
- logevent(ssh->deferred_rekey_reason);
- pktin = NULL;
- ssh->deferred_rekey_reason = NULL;
- goto begin_key_exchange;
- }
-
- /*
- * Otherwise, schedule a timer for our next rekey.
- */
- ssh->kex_in_progress = FALSE;
- ssh->last_rekey = GETTICKCOUNT();
- if (conf_get_int(ssh->conf, CONF_ssh_rekey_time) != 0)
- ssh->next_rekey = schedule_timer(conf_get_int(ssh->conf, CONF_ssh_rekey_time)*60*TICKSPERSEC,
- ssh2_timer, ssh);
-
- /*
- * If this is the first key exchange phase, we must pass the
- * SSH2_MSG_NEWKEYS packet to the next layer, not because it
- * wants to see it but because it will need time to initialise
- * itself before it sees an actual packet. In subsequent key
- * exchange phases, we don't pass SSH2_MSG_NEWKEYS on, because
- * it would only confuse the layer above.
- */
- if (s->activated_authconn) {
- crReturn(0);
- }
- s->activated_authconn = TRUE;
-
- /*
- * Now we're encrypting. Begin returning 1 to the protocol main
- * function so that other things can run on top of the
- * transport. If we ever see a KEXINIT, we must go back to the
- * start.
- *
- * We _also_ go back to the start if we see pktin==NULL and
- * inlen negative, because this is a special signal meaning
- * `initiate client-driven rekey', and `in' contains a message
- * giving the reason for the rekey.
- *
- * inlen==-1 means always initiate a rekey;
- * inlen==-2 means that userauth has completed successfully and
- * we should consider rekeying (for delayed compression).
- */
- while (!((pktin && pktin->type == SSH2_MSG_KEXINIT) ||
- (!pktin && inlen < 0))) {
- wait_for_rekey:
- crReturn(1);
- }
- if (pktin) {
- logevent("Server initiated key re-exchange");
- } else {
- if (inlen == -2) {
- /*
- * authconn has seen a USERAUTH_SUCCEEDED. Time to enable
- * delayed compression, if it's available.
- *
- * draft-miller-secsh-compression-delayed-00 says that you
- * negotiate delayed compression in the first key exchange, and
- * both sides start compressing when the server has sent
- * USERAUTH_SUCCESS. This has a race condition -- the server
- * can't know when the client has seen it, and thus which incoming
- * packets it should treat as compressed.
- *
- * Instead, we do the initial key exchange without offering the
- * delayed methods, but note if the server offers them; when we
- * get here, if a delayed method was available that was higher
- * on our list than what we got, we initiate a rekey in which we
- * _do_ list the delayed methods (and hopefully get it as a
- * result). Subsequent rekeys will do the same.
- */
- assert(!s->userauth_succeeded); /* should only happen once */
- s->userauth_succeeded = TRUE;
- if (!s->pending_compression)
- /* Can't see any point rekeying. */
- goto wait_for_rekey; /* this is utterly horrid */
- /* else fall through to rekey... */
- s->pending_compression = FALSE;
- }
- /*
- * Now we've decided to rekey.
- *
- * Special case: if the server bug is set that doesn't
- * allow rekeying, we give a different log message and
- * continue waiting. (If such a server _initiates_ a rekey,
- * we process it anyway!)
- */
- if ((ssh->remote_bugs & BUG_SSH2_REKEY)) {
- logeventf(ssh, "Server bug prevents key re-exchange (%s)",
- (char *)in);
- /* Reset the counters, so that at least this message doesn't
- * hit the event log _too_ often. */
- ssh->outgoing_data_size = 0;
- ssh->incoming_data_size = 0;
- if (conf_get_int(ssh->conf, CONF_ssh_rekey_time) != 0) {
- ssh->next_rekey =
- schedule_timer(conf_get_int(ssh->conf, CONF_ssh_rekey_time)*60*TICKSPERSEC,
- ssh2_timer, ssh);
- }
- goto wait_for_rekey; /* this is still utterly horrid */
- } else {
- logeventf(ssh, "Initiating key re-exchange (%s)", (char *)in);
- }
- }
- goto begin_key_exchange;
-
- crFinish(1);
-}
-
-/*
- * Add data to an SSH-2 channel output buffer.
- */
-static void ssh2_add_channel_data(struct ssh_channel *c, char *buf,
- int len)
-{
- bufchain_add(&c->v.v2.outbuffer, buf, len);
-}
-
-/*
- * Attempt to send data on an SSH-2 channel.
- */
-static int ssh2_try_send(struct ssh_channel *c)
-{
- Ssh ssh = c->ssh;
- struct Packet *pktout;
- int ret;
-
- while (c->v.v2.remwindow > 0 && bufchain_size(&c->v.v2.outbuffer) > 0) {
- int len;
- void *data;
- bufchain_prefix(&c->v.v2.outbuffer, &data, &len);
- if ((unsigned)len > c->v.v2.remwindow)
- len = c->v.v2.remwindow;
- if ((unsigned)len > c->v.v2.remmaxpkt)
- len = c->v.v2.remmaxpkt;
- pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_DATA);
- ssh2_pkt_adduint32(pktout, c->remoteid);
- ssh2_pkt_addstring_start(pktout);
- dont_log_data(ssh, pktout, PKTLOG_OMIT);
- ssh2_pkt_addstring_data(pktout, data, len);
- end_log_omission(ssh, pktout);
- ssh2_pkt_send(ssh, pktout);
- bufchain_consume(&c->v.v2.outbuffer, len);
- c->v.v2.remwindow -= len;
- }
-
- /*
- * After having sent as much data as we can, return the amount
- * still buffered.
- */
- ret = bufchain_size(&c->v.v2.outbuffer);
-
- /*
- * And if there's no data pending but we need to send an EOF, send
- * it.
- */
- if (!ret && c->pending_eof)
- ssh_channel_try_eof(c);
-
- return ret;
-}
-
-static void ssh2_try_send_and_unthrottle(Ssh ssh, struct ssh_channel *c)
-{
- int bufsize;
- if (c->closes & CLOSES_SENT_EOF)
- return; /* don't send on channels we've EOFed */
- bufsize = ssh2_try_send(c);
- if (bufsize == 0) {
- switch (c->type) {
- case CHAN_MAINSESSION:
- /* stdin need not receive an unthrottle
- * notification since it will be polled */
- break;
- case CHAN_X11:
- x11_unthrottle(c->u.x11.s);
- break;
- case CHAN_AGENT:
- /* agent sockets are request/response and need no
- * buffer management */
- break;
- case CHAN_SOCKDATA:
- pfd_unthrottle(c->u.pfd.s);
- break;
- }
- }
-}
-
-/*
- * Set up most of a new ssh_channel for SSH-2.
- */
-static void ssh2_channel_init(struct ssh_channel *c)
-{
- Ssh ssh = c->ssh;
- c->localid = alloc_channel_id(ssh);
- c->closes = 0;
- c->pending_eof = FALSE;
- c->throttling_conn = FALSE;
- c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin =
- conf_get_int(ssh->conf, CONF_ssh_simple) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE;
- c->v.v2.winadj_head = c->v.v2.winadj_tail = NULL;
- c->v.v2.throttle_state = UNTHROTTLED;
- bufchain_init(&c->v.v2.outbuffer);
-}
-
-/*
- * Potentially enlarge the window on an SSH-2 channel.
- */
-static void ssh2_set_window(struct ssh_channel *c, int newwin)
-{
- Ssh ssh = c->ssh;
-
- /*
- * Never send WINDOW_ADJUST for a channel that the remote side has
- * already sent EOF on; there's no point, since it won't be
- * sending any more data anyway. Ditto if _we've_ already sent
- * CLOSE.
- */
- if (c->closes & (CLOSES_RCVD_EOF | CLOSES_SENT_CLOSE))
- return;
-
- /*
- * If the remote end has a habit of ignoring maxpkt, limit the
- * window so that it has no choice (assuming it doesn't ignore the
- * window as well).
- */
- if ((ssh->remote_bugs & BUG_SSH2_MAXPKT) && newwin > OUR_V2_MAXPKT)
- newwin = OUR_V2_MAXPKT;
-
- /*
- * Only send a WINDOW_ADJUST if there's significantly more window
- * available than the other end thinks there is. This saves us
- * sending a WINDOW_ADJUST for every character in a shell session.
- *
- * "Significant" is arbitrarily defined as half the window size.
- */
- if (newwin / 2 >= c->v.v2.locwindow) {
- struct Packet *pktout;
- struct winadj *wa;
-
- /*
- * In order to keep track of how much window the client
- * actually has available, we'd like it to acknowledge each
- * WINDOW_ADJUST. We can't do that directly, so we accompany
- * it with a CHANNEL_REQUEST that has to be acknowledged.
- *
- * This is only necessary if we're opening the window wide.
- * If we're not, then throughput is being constrained by
- * something other than the maximum window size anyway.
- *
- * We also only send this if the main channel has finished its
- * initial CHANNEL_REQUESTs and installed the default
- * CHANNEL_FAILURE handler, so as not to risk giving it
- * unexpected CHANNEL_FAILUREs.
- */
- if (newwin == c->v.v2.locmaxwin &&
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE]) {
- pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
- ssh2_pkt_adduint32(pktout, c->remoteid);
- ssh2_pkt_addstring(pktout, "winadj@putty.projects.tartarus.org");
- ssh2_pkt_addbool(pktout, TRUE);
- ssh2_pkt_send(ssh, pktout);
-
- /*
- * CHANNEL_FAILURE doesn't come with any indication of
- * what message caused it, so we have to keep track of the
- * outstanding CHANNEL_REQUESTs ourselves.
- */
- wa = snew(struct winadj);
- wa->size = newwin - c->v.v2.locwindow;
- wa->next = NULL;
- if (!c->v.v2.winadj_head)
- c->v.v2.winadj_head = wa;
- else
- c->v.v2.winadj_tail->next = wa;
- c->v.v2.winadj_tail = wa;
- if (c->v.v2.throttle_state != UNTHROTTLED)
- c->v.v2.throttle_state = UNTHROTTLING;
- } else {
- /* Pretend the WINDOW_ADJUST was acked immediately. */
- c->v.v2.remlocwin = newwin;
- c->v.v2.throttle_state = THROTTLED;
- }
- pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
- ssh2_pkt_adduint32(pktout, c->remoteid);
- ssh2_pkt_adduint32(pktout, newwin - c->v.v2.locwindow);
- ssh2_pkt_send(ssh, pktout);
- c->v.v2.locwindow = newwin;
- }
-}
-
-/*
- * Find the channel associated with a message. If there's no channel,
- * or it's not properly open, make a noise about it and return NULL.
- */
-static struct ssh_channel *ssh2_channel_msg(Ssh ssh, struct Packet *pktin)
-{
- unsigned localid = ssh_pkt_getuint32(pktin);
- struct ssh_channel *c;
-
- c = find234(ssh->channels, &localid, ssh_channelfind);
- if (!c ||
- (c->halfopen && pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION &&
- pktin->type != SSH2_MSG_CHANNEL_OPEN_FAILURE)) {
- char *buf = dupprintf("Received %s for %s channel %u",
- ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx,
- pktin->type),
- c ? "half-open" : "nonexistent", localid);
- ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
- sfree(buf);
- return NULL;
- }
- return c;
-}
-
-static int ssh2_handle_winadj_response(struct ssh_channel *c)
-{
- struct winadj *wa = c->v.v2.winadj_head;
- if (!wa)
- return FALSE;
- c->v.v2.winadj_head = wa->next;
- c->v.v2.remlocwin += wa->size;
- sfree(wa);
- /*
- * winadj messages are only sent when the window is fully open, so
- * if we get an ack of one, we know any pending unthrottle is
- * complete.
- */
- if (c->v.v2.throttle_state == UNTHROTTLING)
- c->v.v2.throttle_state = UNTHROTTLED;
- /*
- * We may now initiate channel-closing procedures, if that winadj
- * was the last thing outstanding before we send CHANNEL_CLOSE.
- */
- ssh2_channel_check_close(c);
- return TRUE;
-}
-
-static void ssh2_msg_channel_success(Ssh ssh, struct Packet *pktin)
-{
- /*
- * This should never get called. All channel requests are either
- * sent with want_reply false, are sent before this handler gets
- * installed, or are "winadj@putty" requests, which servers should
- * never respond to with success.
- *
- * However, at least one server ("boks_sshd") is known to return
- * SUCCESS for channel requests it's never heard of, such as
- * "winadj@putty". Raised with foxt.com as bug 090916-090424, but
- * for the sake of a quiet life, we handle it just the same as the
- * expected FAILURE.
- */
- struct ssh_channel *c;
-
- c = ssh2_channel_msg(ssh, pktin);
- if (!c)
- return;
- if (!ssh2_handle_winadj_response(c))
- ssh_disconnect(ssh, NULL,
- "Received unsolicited SSH_MSG_CHANNEL_SUCCESS",
- SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
-}
-
-static void ssh2_msg_channel_failure(Ssh ssh, struct Packet *pktin)
-{
- /*
- * The only time this should get called is for "winadj@putty"
- * messages sent above. All other channel requests are either
- * sent with want_reply false or are sent before this handler gets
- * installed.
- */
- struct ssh_channel *c;
-
- c = ssh2_channel_msg(ssh, pktin);
- if (!c)
- return;
- if (!ssh2_handle_winadj_response(c))
- ssh_disconnect(ssh, NULL,
- "Received unsolicited SSH_MSG_CHANNEL_FAILURE",
- SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
-}
-
-static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin)
-{
- struct ssh_channel *c;
- c = ssh2_channel_msg(ssh, pktin);
- if (!c)
- return;
- if (!(c->closes & CLOSES_SENT_EOF)) {
- c->v.v2.remwindow += ssh_pkt_getuint32(pktin);
- ssh2_try_send_and_unthrottle(ssh, c);
- }
-}
-
-static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
-{
- char *data;
- int length;
- struct ssh_channel *c;
- c = ssh2_channel_msg(ssh, pktin);
- if (!c)
- return;
- if (pktin->type == SSH2_MSG_CHANNEL_EXTENDED_DATA &&
- ssh_pkt_getuint32(pktin) != SSH2_EXTENDED_DATA_STDERR)
- return; /* extended but not stderr */
- ssh_pkt_getstring(pktin, &data, &length);
- if (data) {
- int bufsize = 0;
- c->v.v2.locwindow -= length;
- c->v.v2.remlocwin -= length;
- switch (c->type) {
- case CHAN_MAINSESSION:
- bufsize =
- from_backend(ssh->frontend, pktin->type ==
- SSH2_MSG_CHANNEL_EXTENDED_DATA,
- data, length);
- break;
- case CHAN_X11:
- bufsize = x11_send(c->u.x11.s, data, length);
- break;
- case CHAN_SOCKDATA:
- bufsize = pfd_send(c->u.pfd.s, data, length);
- break;
- case CHAN_AGENT:
- while (length > 0) {
- if (c->u.a.lensofar < 4) {
- unsigned int l = min(4 - c->u.a.lensofar,
- (unsigned)length);
- memcpy(c->u.a.msglen + c->u.a.lensofar,
- data, l);
- data += l;
- length -= l;
- c->u.a.lensofar += l;
- }
- if (c->u.a.lensofar == 4) {
- c->u.a.totallen =
- 4 + GET_32BIT(c->u.a.msglen);
- c->u.a.message = snewn(c->u.a.totallen,
- unsigned char);
- memcpy(c->u.a.message, c->u.a.msglen, 4);
- }
- if (c->u.a.lensofar >= 4 && length > 0) {
- unsigned int l =
- min(c->u.a.totallen - c->u.a.lensofar,
- (unsigned)length);
- memcpy(c->u.a.message + c->u.a.lensofar,
- data, l);
- data += l;
- length -= l;
- c->u.a.lensofar += l;
- }
- if (c->u.a.lensofar == c->u.a.totallen) {
- void *reply;
- int replylen;
- if (agent_query(c->u.a.message,
- c->u.a.totallen,
- &reply, &replylen,
- ssh_agentf_callback, c))
- ssh_agentf_callback(c, reply, replylen);
- sfree(c->u.a.message);
- c->u.a.message = NULL;
- c->u.a.lensofar = 0;
- }
- }
- bufsize = 0;
- break;
- }
- /*
- * If it looks like the remote end hit the end of its window,
- * and we didn't want it to do that, think about using a
- * larger window.
- */
- if (c->v.v2.remlocwin <= 0 && c->v.v2.throttle_state == UNTHROTTLED &&
- c->v.v2.locmaxwin < 0x40000000)
- c->v.v2.locmaxwin += OUR_V2_WINSIZE;
- /*
- * If we are not buffering too much data,
- * enlarge the window again at the remote side.
- * If we are buffering too much, we may still
- * need to adjust the window if the server's
- * sent excess data.
- */
- ssh2_set_window(c, bufsize < c->v.v2.locmaxwin ?
- c->v.v2.locmaxwin - bufsize : 0);
- /*
- * If we're either buffering way too much data, or if we're
- * buffering anything at all and we're in "simple" mode,
- * throttle the whole channel.
- */
- if ((bufsize > c->v.v2.locmaxwin ||
- (conf_get_int(ssh->conf, CONF_ssh_simple) && bufsize > 0)) &&
- !c->throttling_conn) {
- c->throttling_conn = 1;
- ssh_throttle_conn(ssh, +1);
- }
- }
-}
-
-static void ssh_channel_destroy(struct ssh_channel *c)
-{
- Ssh ssh = c->ssh;
-
- switch (c->type) {
- case CHAN_MAINSESSION:
- ssh->mainchan = NULL;
- update_specials_menu(ssh->frontend);
- break;
- case CHAN_X11:
- if (c->u.x11.s != NULL)
- x11_close(c->u.x11.s);
- logevent("Forwarded X11 connection terminated");
- break;
- case CHAN_AGENT:
- sfree(c->u.a.message);
- break;
- case CHAN_SOCKDATA:
- if (c->u.pfd.s != NULL)
- pfd_close(c->u.pfd.s);
- logevent("Forwarded port closed");
- break;
- }
-
- del234(ssh->channels, c);
- if (ssh->version == 2)
- bufchain_clear(&c->v.v2.outbuffer);
- sfree(c);
-
- /*
- * See if that was the last channel left open.
- * (This is only our termination condition if we're
- * not running in -N mode.)
- */
- if (ssh->version == 2 &&
- !conf_get_int(ssh->conf, CONF_ssh_no_shell) &&
- count234(ssh->channels) == 0) {
- /*
- * We used to send SSH_MSG_DISCONNECT here,
- * because I'd believed that _every_ conforming
- * SSH-2 connection had to end with a disconnect
- * being sent by at least one side; apparently
- * I was wrong and it's perfectly OK to
- * unceremoniously slam the connection shut
- * when you're done, and indeed OpenSSH feels
- * this is more polite than sending a
- * DISCONNECT. So now we don't.
- */
- ssh_disconnect(ssh, "All channels closed", NULL, 0, TRUE);
- }
-}
-
-static void ssh2_channel_check_close(struct ssh_channel *c)
-{
- Ssh ssh = c->ssh;
- struct Packet *pktout;
-
- if ((c->closes & (CLOSES_SENT_EOF | CLOSES_RCVD_EOF | CLOSES_SENT_CLOSE))
- == (CLOSES_SENT_EOF | CLOSES_RCVD_EOF) && !c->v.v2.winadj_head) {
- /*
- * We have both sent and received EOF, and we have no
- * outstanding winadj channel requests, which means the
- * channel is in final wind-up. But we haven't sent CLOSE, so
- * let's do so now.
- */
- pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
- ssh2_pkt_adduint32(pktout, c->remoteid);
- ssh2_pkt_send(ssh, pktout);
- c->closes |= CLOSES_SENT_CLOSE;
- }
-
- if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) {
- /*
- * We have both sent and received CLOSE, which means we're
- * completely done with the channel.
- */
- ssh_channel_destroy(c);
- }
-}
-
-static void ssh2_channel_got_eof(struct ssh_channel *c)
-{
- if (c->closes & CLOSES_RCVD_EOF)
- return; /* already seen EOF */
- c->closes |= CLOSES_RCVD_EOF;
-
- if (c->type == CHAN_X11) {
- x11_send_eof(c->u.x11.s);
- } else if (c->type == CHAN_AGENT) {
- /* Manufacture an outgoing EOF in response to the incoming one. */
- sshfwd_write_eof(c);
- } else if (c->type == CHAN_SOCKDATA) {
- pfd_send_eof(c->u.pfd.s);
- } else if (c->type == CHAN_MAINSESSION) {
- Ssh ssh = c->ssh;
-
- if (!ssh->sent_console_eof &&
- (from_backend_eof(ssh->frontend) || ssh->got_pty)) {
- /*
- * Either from_backend_eof told us that the front end
- * wants us to close the outgoing side of the connection
- * as soon as we see EOF from the far end, or else we've
- * unilaterally decided to do that because we've allocated
- * a remote pty and hence EOF isn't a particularly
- * meaningful concept.
- */
- sshfwd_write_eof(c);
- }
- ssh->sent_console_eof = TRUE;
- }
-
- ssh2_channel_check_close(c);
-}
-
-static void ssh2_msg_channel_eof(Ssh ssh, struct Packet *pktin)
-{
- struct ssh_channel *c;
-
- c = ssh2_channel_msg(ssh, pktin);
- if (!c)
- return;
- ssh2_channel_got_eof(c);
-}
-
-static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin)
-{
- struct ssh_channel *c;
-
- c = ssh2_channel_msg(ssh, pktin);
- if (!c)
- return;
-
- /*
- * When we receive CLOSE on a channel, we assume it comes with an
- * implied EOF if we haven't seen EOF yet.
- */
- ssh2_channel_got_eof(c);
-
- /*
- * And we also send an outgoing EOF, if we haven't already, on the
- * assumption that CLOSE is a pretty forceful announcement that
- * the remote side is doing away with the entire channel. (If it
- * had wanted to send us EOF and continue receiving data from us,
- * it would have just sent CHANNEL_EOF.)
- */
- if (!(c->closes & CLOSES_SENT_EOF)) {
- /*
- * Make sure we don't read any more from whatever our local
- * data source is for this channel.
- */
- switch (c->type) {
- case CHAN_MAINSESSION:
- ssh->send_ok = 0; /* stop trying to read from stdin */
- break;
- case CHAN_X11:
- x11_override_throttle(c->u.x11.s, 1);
- break;
- case CHAN_SOCKDATA:
- pfd_override_throttle(c->u.pfd.s, 1);
- break;
- }
-
- /*
- * Send outgoing EOF.
- */
- sshfwd_write_eof(c);
- }
-
- /*
- * Now process the actual close.
- */
- if (!(c->closes & CLOSES_RCVD_CLOSE)) {
- c->closes |= CLOSES_RCVD_CLOSE;
- ssh2_channel_check_close(c);
- }
-}
-
-static void ssh2_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin)
-{
- struct ssh_channel *c;
-
- c = ssh2_channel_msg(ssh, pktin);
- if (!c)
- return;
- if (c->type != CHAN_SOCKDATA_DORMANT)
- return; /* dunno why they're confirming this */
- c->remoteid = ssh_pkt_getuint32(pktin);
- c->halfopen = FALSE;
- c->type = CHAN_SOCKDATA;
- c->v.v2.remwindow = ssh_pkt_getuint32(pktin);
- c->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin);
- if (c->u.pfd.s)
- pfd_confirm(c->u.pfd.s);
- if (c->pending_eof)
- ssh_channel_try_eof(c);
-}
-
-static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin)
-{
- static const char *const reasons[] = {
- "<unknown reason code>",
- "Administratively prohibited",
- "Connect failed",
- "Unknown channel type",
- "Resource shortage",
- };
- unsigned reason_code;
- char *reason_string;
- int reason_length;
- struct ssh_channel *c;
- c = ssh2_channel_msg(ssh, pktin);
- if (!c)
- return;
- if (c->type != CHAN_SOCKDATA_DORMANT)
- return; /* dunno why they're failing this */
-
- reason_code = ssh_pkt_getuint32(pktin);
- if (reason_code >= lenof(reasons))
- reason_code = 0; /* ensure reasons[reason_code] in range */
- ssh_pkt_getstring(pktin, &reason_string, &reason_length);
- logeventf(ssh, "Forwarded connection refused by server: %s [%.*s]",
- reasons[reason_code], reason_length, reason_string);
-
- pfd_close(c->u.pfd.s);
-
- del234(ssh->channels, c);
- sfree(c);
-}
-
-static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin)
-{
- char *type;
- int typelen, want_reply;
- int reply = SSH2_MSG_CHANNEL_FAILURE; /* default */
- struct ssh_channel *c;
- struct Packet *pktout;
-
- c = ssh2_channel_msg(ssh, pktin);
- if (!c)
- return;
- ssh_pkt_getstring(pktin, &type, &typelen);
- want_reply = ssh2_pkt_getbool(pktin);
-
- /*
- * Having got the channel number, we now look at
- * the request type string to see if it's something
- * we recognise.
- */
- if (c == ssh->mainchan) {
- /*
- * We recognise "exit-status" and "exit-signal" on
- * the primary channel.
- */
- if (typelen == 11 &&
- !memcmp(type, "exit-status", 11)) {
-
- ssh->exitcode = ssh_pkt_getuint32(pktin);
- logeventf(ssh, "Server sent command exit status %d",
- ssh->exitcode);
- reply = SSH2_MSG_CHANNEL_SUCCESS;
-
- } else if (typelen == 11 &&
- !memcmp(type, "exit-signal", 11)) {
-
- int is_plausible = TRUE, is_int = FALSE;
- char *fmt_sig = "", *fmt_msg = "";
- char *msg;
- int msglen = 0, core = FALSE;
- /* ICK: older versions of OpenSSH (e.g. 3.4p1)
- * provide an `int' for the signal, despite its
- * having been a `string' in the drafts of RFC 4254 since at
- * least 2001. (Fixed in session.c 1.147.) Try to
- * infer which we can safely parse it as. */
- {
- unsigned char *p = pktin->body +
- pktin->savedpos;
- long len = pktin->length - pktin->savedpos;
- unsigned long num = GET_32BIT(p); /* what is it? */
- /* If it's 0, it hardly matters; assume string */
- if (num == 0) {
- is_int = FALSE;
- } else {
- int maybe_int = FALSE, maybe_str = FALSE;
-#define CHECK_HYPOTHESIS(offset, result) \
- do { \
- long q = offset; \
- if (q >= 0 && q+4 <= len) { \
- q = q + 4 + GET_32BIT(p+q); \
- if (q >= 0 && q+4 <= len && \
- ((q = q + 4 + GET_32BIT(p+q))!= 0) && q == len) \
- result = TRUE; \
- } \
- } while(0)
- CHECK_HYPOTHESIS(4+1, maybe_int);
- CHECK_HYPOTHESIS(4+num+1, maybe_str);
-#undef CHECK_HYPOTHESIS
- if (maybe_int && !maybe_str)
- is_int = TRUE;
- else if (!maybe_int && maybe_str)
- is_int = FALSE;
- else
- /* Crikey. Either or neither. Panic. */
- is_plausible = FALSE;
- }
- }
- ssh->exitcode = 128; /* means `unknown signal' */
- if (is_plausible) {
- if (is_int) {
- /* Old non-standard OpenSSH. */
- int signum = ssh_pkt_getuint32(pktin);
- fmt_sig = dupprintf(" %d", signum);
- ssh->exitcode = 128 + signum;
- } else {
- /* As per RFC 4254. */
- char *sig;
- int siglen;
- ssh_pkt_getstring(pktin, &sig, &siglen);
- /* Signal name isn't supposed to be blank, but
- * let's cope gracefully if it is. */
- if (siglen) {
- fmt_sig = dupprintf(" \"%.*s\"",
- siglen, sig);
- }
-
- /*
- * Really hideous method of translating the
- * signal description back into a locally
- * meaningful number.
- */
-
- if (0)
- ;
-#define TRANSLATE_SIGNAL(s) \
- else if (siglen == lenof(#s)-1 && !memcmp(sig, #s, siglen)) \
- ssh->exitcode = 128 + SIG ## s
-#ifdef SIGABRT
- TRANSLATE_SIGNAL(ABRT);
-#endif
-#ifdef SIGALRM
- TRANSLATE_SIGNAL(ALRM);
-#endif
-#ifdef SIGFPE
- TRANSLATE_SIGNAL(FPE);
-#endif
-#ifdef SIGHUP
- TRANSLATE_SIGNAL(HUP);
-#endif
-#ifdef SIGILL
- TRANSLATE_SIGNAL(ILL);
-#endif
-#ifdef SIGINT
- TRANSLATE_SIGNAL(INT);
-#endif
-#ifdef SIGKILL
- TRANSLATE_SIGNAL(KILL);
-#endif
-#ifdef SIGPIPE
- TRANSLATE_SIGNAL(PIPE);
-#endif
-#ifdef SIGQUIT
- TRANSLATE_SIGNAL(QUIT);
-#endif
-#ifdef SIGSEGV
- TRANSLATE_SIGNAL(SEGV);
-#endif
-#ifdef SIGTERM
- TRANSLATE_SIGNAL(TERM);
-#endif
-#ifdef SIGUSR1
- TRANSLATE_SIGNAL(USR1);
-#endif
-#ifdef SIGUSR2
- TRANSLATE_SIGNAL(USR2);
-#endif
-#undef TRANSLATE_SIGNAL
- else
- ssh->exitcode = 128;
- }
- core = ssh2_pkt_getbool(pktin);
- ssh_pkt_getstring(pktin, &msg, &msglen);
- if (msglen) {
- fmt_msg = dupprintf(" (\"%.*s\")", msglen, msg);
- }
- /* 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);
- reply = SSH2_MSG_CHANNEL_SUCCESS;
-
- }
- } else {
- /*
- * This is a channel request we don't know
- * about, so we now either ignore the request
- * or respond with CHANNEL_FAILURE, depending
- * on want_reply.
- */
- reply = SSH2_MSG_CHANNEL_FAILURE;
- }
- if (want_reply) {
- pktout = ssh2_pkt_init(reply);
- ssh2_pkt_adduint32(pktout, c->remoteid);
- ssh2_pkt_send(ssh, pktout);
- }
-}
-
-static void ssh2_msg_global_request(Ssh ssh, struct Packet *pktin)
-{
- char *type;
- int typelen, want_reply;
- struct Packet *pktout;
-
- ssh_pkt_getstring(pktin, &type, &typelen);
- want_reply = ssh2_pkt_getbool(pktin);
-
- /*
- * We currently don't support any global requests
- * at all, so we either ignore the request or
- * respond with REQUEST_FAILURE, depending on
- * want_reply.
- */
- if (want_reply) {
- pktout = ssh2_pkt_init(SSH2_MSG_REQUEST_FAILURE);
- ssh2_pkt_send(ssh, pktout);
- }
-}
-
-static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
-{
- char *type;
- int typelen;
- char *peeraddr;
- int peeraddrlen;
- int peerport;
- char *error = NULL;
- struct ssh_channel *c;
- unsigned remid, winsize, pktsize;
- struct Packet *pktout;
-
- ssh_pkt_getstring(pktin, &type, &typelen);
- c = snew(struct ssh_channel);
- c->ssh = ssh;
-
- remid = ssh_pkt_getuint32(pktin);
- winsize = ssh_pkt_getuint32(pktin);
- pktsize = ssh_pkt_getuint32(pktin);
-
- if (typelen == 3 && !memcmp(type, "x11", 3)) {
- char *addrstr;
- const char *x11err;
-
- ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen);
- addrstr = snewn(peeraddrlen+1, char);
- memcpy(addrstr, peeraddr, peeraddrlen);
- addrstr[peeraddrlen] = '\0';
- peerport = ssh_pkt_getuint32(pktin);
-
- logeventf(ssh, "Received X11 connect request from %s:%d",
- addrstr, peerport);
-
- if (!ssh->X11_fwd_enabled)
- error = "X11 forwarding is not enabled";
- else if ((x11err = x11_init(&c->u.x11.s, ssh->x11disp, c,
- addrstr, peerport, ssh->conf)) != NULL) {
- logeventf(ssh, "Local X11 connection failed: %s", x11err);
- error = "Unable to open an X11 connection";
- } else {
- logevent("Opening X11 forward connection succeeded");
- c->type = CHAN_X11;
- }
-
- sfree(addrstr);
- } else if (typelen == 15 &&
- !memcmp(type, "forwarded-tcpip", 15)) {
- struct ssh_rportfwd pf, *realpf;
- char *dummy;
- int dummylen;
- ssh_pkt_getstring(pktin, &dummy, &dummylen);/* skip address */
- pf.sport = ssh_pkt_getuint32(pktin);
- ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen);
- peerport = ssh_pkt_getuint32(pktin);
- realpf = find234(ssh->rportfwds, &pf, NULL);
- logeventf(ssh, "Received remote port %d open request "
- "from %s:%d", pf.sport, peeraddr, peerport);
- if (realpf == NULL) {
- error = "Remote port is not recognised";
- } else {
- const char *e = pfd_newconnect(&c->u.pfd.s,
- realpf->dhost,
- realpf->dport, c,
- ssh->conf,
- realpf->pfrec->addressfamily);
- logeventf(ssh, "Attempting to forward remote port to "
- "%s:%d", realpf->dhost, realpf->dport);
- if (e != NULL) {
- logeventf(ssh, "Port open failed: %s", e);
- error = "Port open failed";
- } else {
- logevent("Forwarded port opened successfully");
- c->type = CHAN_SOCKDATA;
- }
- }
- } else if (typelen == 22 &&
- !memcmp(type, "auth-agent@openssh.com", 22)) {
- if (!ssh->agentfwd_enabled)
- error = "Agent forwarding is not enabled";
- else {
- c->type = CHAN_AGENT; /* identify channel type */
- c->u.a.lensofar = 0;
- }
- } else {
- error = "Unsupported channel type requested";
- }
-
- c->remoteid = remid;
- c->halfopen = FALSE;
- if (error) {
- pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_FAILURE);
- ssh2_pkt_adduint32(pktout, c->remoteid);
- ssh2_pkt_adduint32(pktout, SSH2_OPEN_CONNECT_FAILED);
- ssh2_pkt_addstring(pktout, error);
- ssh2_pkt_addstring(pktout, "en"); /* language tag */
- ssh2_pkt_send(ssh, pktout);
- logeventf(ssh, "Rejected channel open: %s", error);
- sfree(c);
- } else {
- ssh2_channel_init(c);
- c->v.v2.remwindow = winsize;
- c->v.v2.remmaxpkt = pktsize;
- add234(ssh->channels, c);
- pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
- ssh2_pkt_adduint32(pktout, c->remoteid);
- ssh2_pkt_adduint32(pktout, c->localid);
- ssh2_pkt_adduint32(pktout, c->v.v2.locwindow);
- ssh2_pkt_adduint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */
- ssh2_pkt_send(ssh, pktout);
- }
-}
-
-/*
- * Buffer banner messages for later display at some convenient point,
- * if we're going to display them.
- */
-static void ssh2_msg_userauth_banner(Ssh ssh, struct Packet *pktin)
-{
- /* Arbitrary limit to prevent unbounded inflation of buffer */
- if (conf_get_int(ssh->conf, CONF_ssh_show_banner) &&
- bufchain_size(&ssh->banner) <= 131072) {
- char *banner = NULL;
- int size = 0;
- ssh_pkt_getstring(pktin, &banner, &size);
- if (banner)
- bufchain_add(&ssh->banner, banner, size);
- }
-}
-
-/* Helper function to deal with sending tty modes for "pty-req" */
-static void ssh2_send_ttymode(void *data, char *mode, char *val)
-{
- struct Packet *pktout = (struct Packet *)data;
- int i = 0;
- unsigned int arg = 0;
- while (strcmp(mode, ssh_ttymodes[i].mode) != 0) i++;
- if (i == lenof(ssh_ttymodes)) return;
- switch (ssh_ttymodes[i].type) {
- case TTY_OP_CHAR:
- arg = ssh_tty_parse_specchar(val);
- break;
- case TTY_OP_BOOL:
- arg = ssh_tty_parse_boolean(val);
- break;
- }
- ssh2_pkt_addbyte(pktout, ssh_ttymodes[i].opcode);
- ssh2_pkt_adduint32(pktout, arg);
-}
-
-/*
- * Handle the SSH-2 userauth and connection layers.
- */
-static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
- struct Packet *pktin)
-{
- struct do_ssh2_authconn_state {
- enum {
- AUTH_TYPE_NONE,
- AUTH_TYPE_PUBLICKEY,
- AUTH_TYPE_PUBLICKEY_OFFER_LOUD,
- AUTH_TYPE_PUBLICKEY_OFFER_QUIET,
- AUTH_TYPE_PASSWORD,
- AUTH_TYPE_GSSAPI, /* always QUIET */
- AUTH_TYPE_KEYBOARD_INTERACTIVE,
- AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET
- } type;
- int done_service_req;
- int gotit, need_pw, can_pubkey, can_passwd, can_keyb_inter;
- int tried_pubkey_config, done_agent;
-#ifndef NO_GSSAPI
- int can_gssapi;
- int tried_gssapi;
-#endif
- int kbd_inter_refused;
- int we_are_in, userauth_success;
- prompts_t *cur_prompt;
- int num_prompts;
- char *username;
- char *password;
- int got_username;
- void *publickey_blob;
- int publickey_bloblen;
- int publickey_encrypted;
- char *publickey_algorithm;
- char *publickey_comment;
- unsigned char agent_request[5], *agent_response, *agentp;
- int agent_responselen;
- unsigned char *pkblob_in_agent;
- int keyi, nkeys;
- char *pkblob, *alg, *commentp;
- int pklen, alglen, commentlen;
- int siglen, retlen, len;
- char *q, *agentreq, *ret;
- int try_send;
- int num_env, env_left, env_ok;
- struct Packet *pktout;
- Filename *keyfile;
-#ifndef NO_GSSAPI
- struct ssh_gss_library *gsslib;
- Ssh_gss_ctx gss_ctx;
- Ssh_gss_buf gss_buf;
- Ssh_gss_buf gss_rcvtok, gss_sndtok;
- Ssh_gss_name gss_srv_name;
- Ssh_gss_stat gss_stat;
-#endif
- };
- crState(do_ssh2_authconn_state);
-
- crBegin(ssh->do_ssh2_authconn_crstate);
-
- s->done_service_req = FALSE;
- s->we_are_in = s->userauth_success = FALSE;
-#ifndef NO_GSSAPI
- s->tried_gssapi = FALSE;
-#endif
-
- if (!conf_get_int(ssh->conf, CONF_ssh_no_userauth)) {
- /*
- * Request userauth protocol, and await a response to it.
- */
- s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST);
- ssh2_pkt_addstring(s->pktout, "ssh-userauth");
- ssh2_pkt_send(ssh, s->pktout);
- crWaitUntilV(pktin);
- if (pktin->type == SSH2_MSG_SERVICE_ACCEPT)
- s->done_service_req = TRUE;
- }
- if (!s->done_service_req) {
- /*
- * Request connection protocol directly, without authentication.
- */
- s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST);
- ssh2_pkt_addstring(s->pktout, "ssh-connection");
- ssh2_pkt_send(ssh, s->pktout);
- crWaitUntilV(pktin);
- if (pktin->type == SSH2_MSG_SERVICE_ACCEPT) {
- s->we_are_in = TRUE; /* no auth required */
- } else {
- bombout(("Server refused service request"));
- crStopV;
- }
- }
-
- /* Arrange to be able to deal with any BANNERs that come in.
- * (We do this now as packets may come in during the next bit.) */
- bufchain_init(&ssh->banner);
- ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] =
- ssh2_msg_userauth_banner;
-
- /*
- * Misc one-time setup for authentication.
- */
- s->publickey_blob = NULL;
- if (!s->we_are_in) {
-
- /*
- * Load the public half of any configured public key file
- * for later use.
- */
- s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile);
- if (!filename_is_null(s->keyfile)) {
- int keytype;
- logeventf(ssh, "Reading private key file \"%.150s\"",
- filename_to_str(s->keyfile));
- keytype = key_type(s->keyfile);
- if (keytype == SSH_KEYTYPE_SSH2) {
- const char *error;
- s->publickey_blob =
- ssh2_userkey_loadpub(s->keyfile,
- &s->publickey_algorithm,
- &s->publickey_bloblen,
- &s->publickey_comment, &error);
- if (s->publickey_blob) {
- s->publickey_encrypted =
- ssh2_userkey_encrypted(s->keyfile, NULL);
- } else {
- char *msgbuf;
- logeventf(ssh, "Unable to load private key (%s)",
- error);
- msgbuf = dupprintf("Unable to load private key file "
- "\"%.150s\" (%s)\r\n",
- filename_to_str(s->keyfile),
- error);
- c_write_str(ssh, msgbuf);
- sfree(msgbuf);
- }
- } else {
- char *msgbuf;
- logeventf(ssh, "Unable to use this key file (%s)",
- key_type_to_str(keytype));
- msgbuf = dupprintf("Unable to use key file \"%.150s\""
- " (%s)\r\n",
- filename_to_str(s->keyfile),
- key_type_to_str(keytype));
- c_write_str(ssh, msgbuf);
- sfree(msgbuf);
- s->publickey_blob = NULL;
- }
- }
-
- /*
- * Find out about any keys Pageant has (but if there's a
- * public key configured, filter out all others).
- */
- s->nkeys = 0;
- s->agent_response = NULL;
- s->pkblob_in_agent = NULL;
- if (conf_get_int(ssh->conf, CONF_tryagent) && agent_exists()) {
-
- void *r;
-
- logevent("Pageant is running. Requesting keys.");
-
- /* Request the keys held by the agent. */
- PUT_32BIT(s->agent_request, 1);
- s->agent_request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
- if (!agent_query(s->agent_request, 5, &r, &s->agent_responselen,
- ssh_agent_callback, ssh)) {
- do {
- crReturnV;
- if (pktin) {
- bombout(("Unexpected data from server while"
- " waiting for agent response"));
- crStopV;
- }
- } while (pktin || inlen > 0);
- r = ssh->agent_response;
- s->agent_responselen = ssh->agent_response_len;
- }
- s->agent_response = (unsigned char *) r;
- if (s->agent_response && s->agent_responselen >= 5 &&
- s->agent_response[4] == SSH2_AGENT_IDENTITIES_ANSWER) {
- int keyi;
- unsigned char *p;
- p = s->agent_response + 5;
- s->nkeys = GET_32BIT(p);
- p += 4;
- logeventf(ssh, "Pageant has %d SSH-2 keys", s->nkeys);
- if (s->publickey_blob) {
- /* See if configured key is in agent. */
- for (keyi = 0; keyi < s->nkeys; keyi++) {
- s->pklen = GET_32BIT(p);
- if (s->pklen == s->publickey_bloblen &&
- !memcmp(p+4, s->publickey_blob,
- s->publickey_bloblen)) {
- logeventf(ssh, "Pageant key #%d matches "
- "configured key file", keyi);
- s->keyi = keyi;
- s->pkblob_in_agent = p;
- break;
- }
- p += 4 + s->pklen;
- p += GET_32BIT(p) + 4; /* comment */
- }
- if (!s->pkblob_in_agent) {
- logevent("Configured key file not in Pageant");
- s->nkeys = 0;
- }
- }
- } else {
- logevent("Failed to get reply from Pageant");
- }
- }
-
- }
-
- /*
- * We repeat this whole loop, including the username prompt,
- * until we manage a successful authentication. If the user
- * types the wrong _password_, they can be sent back to the
- * beginning to try another username, if this is configured on.
- * (If they specify a username in the config, they are never
- * asked, even if they do give a wrong password.)
- *
- * I think this best serves the needs of
- *
- * - the people who have no configuration, no keys, and just
- * want to try repeated (username,password) pairs until they
- * type both correctly
- *
- * - people who have keys and configuration but occasionally
- * need to fall back to passwords
- *
- * - people with a key held in Pageant, who might not have
- * logged in to a particular machine before; so they want to
- * type a username, and then _either_ their key will be
- * accepted, _or_ they will type a password. If they mistype
- * the username they will want to be able to get back and
- * retype it!
- */
- s->got_username = FALSE;
- while (!s->we_are_in) {
- /*
- * Get a username.
- */
- if (s->got_username && !conf_get_int(ssh->conf, CONF_change_username)) {
- /*
- * We got a username last time round this loop, and
- * with change_username turned off we don't try to get
- * it again.
- */
- } else if ((ssh->username = get_remote_username(ssh->conf)) == NULL) {
- int ret; /* need not be kept over crReturn */
- s->cur_prompt = new_prompts(ssh->frontend);
- s->cur_prompt->to_server = TRUE;
- s->cur_prompt->name = dupstr("SSH login name");
- add_prompt(s->cur_prompt, dupstr("login as: "), TRUE);
- ret = get_userpass_input(s->cur_prompt, NULL, 0);
- while (ret < 0) {
- ssh->send_ok = 1;
- crWaitUntilV(!pktin);
- ret = get_userpass_input(s->cur_prompt, in, inlen);
- ssh->send_ok = 0;
- }
- if (!ret) {
- /*
- * get_userpass_input() failed to get a username.
- * Terminate.
- */
- free_prompts(s->cur_prompt);
- ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE);
- crStopV;
- }
- ssh->username = dupstr(s->cur_prompt->prompts[0]->result);
- free_prompts(s->cur_prompt);
- } else {
- char *stuff;
- if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) {
- stuff = dupprintf("Using username \"%s\".\r\n", ssh->username);
- c_write_str(ssh, stuff);
- sfree(stuff);
- }
- }
- s->got_username = TRUE;
-
- /*
- * Send an authentication request using method "none": (a)
- * just in case it succeeds, and (b) so that we know what
- * authentication methods we can usefully try next.
- */
- ssh->pkt_actx = SSH2_PKTCTX_NOAUTH;
-
- s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
- ssh2_pkt_addstring(s->pktout, ssh->username);
- ssh2_pkt_addstring(s->pktout, "ssh-connection");/* service requested */
- ssh2_pkt_addstring(s->pktout, "none"); /* method */
- ssh2_pkt_send(ssh, s->pktout);
- s->type = AUTH_TYPE_NONE;
- s->gotit = FALSE;
- s->we_are_in = FALSE;
-
- s->tried_pubkey_config = FALSE;
- s->kbd_inter_refused = FALSE;
-
- /* Reset agent request state. */
- s->done_agent = FALSE;
- if (s->agent_response) {
- if (s->pkblob_in_agent) {
- s->agentp = s->pkblob_in_agent;
- } else {
- s->agentp = s->agent_response + 5 + 4;
- s->keyi = 0;
- }
- }
-
- while (1) {
- char *methods = NULL;
- int methlen = 0;
-
- /*
- * Wait for the result of the last authentication request.
- */
- if (!s->gotit)
- crWaitUntilV(pktin);
- /*
- * Now is a convenient point to spew any banner material
- * that we've accumulated. (This should ensure that when
- * we exit the auth loop, we haven't any left to deal
- * with.)
- */
- {
- int size = bufchain_size(&ssh->banner);
- /*
- * Don't show the banner if we're operating in
- * non-verbose non-interactive mode. (It's probably
- * a script, which means nobody will read the
- * banner _anyway_, and moreover the printing of
- * the banner will screw up processing on the
- * output of (say) plink.)
- */
- if (size && (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE))) {
- char *banner = snewn(size, char);
- bufchain_fetch(&ssh->banner, banner, size);
- c_write_untrusted(ssh, banner, size);
- sfree(banner);
- }
- bufchain_clear(&ssh->banner);
- }
- if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) {
- logevent("Access granted");
- s->we_are_in = s->userauth_success = TRUE;
- break;
- }
-
- if (pktin->type != SSH2_MSG_USERAUTH_FAILURE && s->type != AUTH_TYPE_GSSAPI) {
- bombout(("Strange packet received during authentication: "
- "type %d", pktin->type));
- crStopV;
- }
-
- s->gotit = FALSE;
-
- /*
- * OK, we're now sitting on a USERAUTH_FAILURE message, so
- * we can look at the string in it and know what we can
- * helpfully try next.
- */
- if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) {
- ssh_pkt_getstring(pktin, &methods, &methlen);
- if (!ssh2_pkt_getbool(pktin)) {
- /*
- * We have received an unequivocal Access
- * Denied. This can translate to a variety of
- * messages, or no message at all.
- *
- * For forms of authentication which are attempted
- * implicitly, by which I mean without printing
- * anything in the window indicating that we're
- * trying them, we should never print 'Access
- * denied'.
- *
- * If we do print a message saying that we're
- * attempting some kind of authentication, it's OK
- * to print a followup message saying it failed -
- * but the message may sometimes be more specific
- * than simply 'Access denied'.
- *
- * Additionally, if we'd just tried password
- * authentication, we should break out of this
- * whole loop so as to go back to the username
- * prompt (iff we're configured to allow
- * username change attempts).
- */
- if (s->type == AUTH_TYPE_NONE) {
- /* do nothing */
- } else if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD ||
- s->type == AUTH_TYPE_PUBLICKEY_OFFER_QUIET) {
- if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD)
- c_write_str(ssh, "Server refused our key\r\n");
- logevent("Server refused our key");
- } else if (s->type == AUTH_TYPE_PUBLICKEY) {
- /* This _shouldn't_ happen except by a
- * protocol bug causing client and server to
- * disagree on what is a correct signature. */
- c_write_str(ssh, "Server refused public-key signature"
- " despite accepting key!\r\n");
- logevent("Server refused public-key signature"
- " despite accepting key!");
- } else if (s->type==AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET) {
- /* quiet, so no c_write */
- logevent("Server refused keyboard-interactive authentication");
- } else if (s->type==AUTH_TYPE_GSSAPI) {
- /* always quiet, so no c_write */
- /* also, the code down in the GSSAPI block has
- * already logged this in the Event Log */
- } else if (s->type == AUTH_TYPE_KEYBOARD_INTERACTIVE) {
- logevent("Keyboard-interactive authentication failed");
- c_write_str(ssh, "Access denied\r\n");
- } else {
- assert(s->type == AUTH_TYPE_PASSWORD);
- logevent("Password authentication failed");
- c_write_str(ssh, "Access denied\r\n");
-
- if (conf_get_int(ssh->conf, CONF_change_username)) {
- /* XXX perhaps we should allow
- * keyboard-interactive to do this too? */
- s->we_are_in = FALSE;
- break;
- }
- }
- } else {
- c_write_str(ssh, "Further authentication required\r\n");
- logevent("Further authentication required");
- }
-
- s->can_pubkey =
- in_commasep_string("publickey", methods, methlen);
- s->can_passwd =
- in_commasep_string("password", methods, methlen);
- s->can_keyb_inter = conf_get_int(ssh->conf, CONF_try_ki_auth) &&
- in_commasep_string("keyboard-interactive", methods, methlen);
-#ifndef NO_GSSAPI
- if (!ssh->gsslibs)
- ssh->gsslibs = ssh_gss_setup(ssh->conf);
- s->can_gssapi = conf_get_int(ssh->conf, CONF_try_gssapi_auth) &&
- in_commasep_string("gssapi-with-mic", methods, methlen) &&
- ssh->gsslibs->nlibraries > 0;
-#endif
- }
-
- ssh->pkt_actx = SSH2_PKTCTX_NOAUTH;
-
- if (s->can_pubkey && !s->done_agent && s->nkeys) {
-
- /*
- * Attempt public-key authentication using a key from Pageant.
- */
-
- ssh->pkt_actx = SSH2_PKTCTX_PUBLICKEY;
-
- logeventf(ssh, "Trying Pageant key #%d", s->keyi);
-
- /* Unpack key from agent response */
- s->pklen = GET_32BIT(s->agentp);
- s->agentp += 4;
- s->pkblob = (char *)s->agentp;
- s->agentp += s->pklen;
- s->alglen = GET_32BIT(s->pkblob);
- s->alg = s->pkblob + 4;
- s->commentlen = GET_32BIT(s->agentp);
- s->agentp += 4;
- s->commentp = (char *)s->agentp;
- s->agentp += s->commentlen;
- /* s->agentp now points at next key, if any */
-
- /* See if server will accept it */
- s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
- ssh2_pkt_addstring(s->pktout, ssh->username);
- ssh2_pkt_addstring(s->pktout, "ssh-connection");
- /* service requested */
- ssh2_pkt_addstring(s->pktout, "publickey");
- /* method */
- ssh2_pkt_addbool(s->pktout, FALSE); /* no signature included */
- ssh2_pkt_addstring_start(s->pktout);
- ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen);
- ssh2_pkt_addstring_start(s->pktout);
- ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen);
- ssh2_pkt_send(ssh, s->pktout);
- s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET;
-
- crWaitUntilV(pktin);
- if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) {
-
- /* Offer of key refused. */
- s->gotit = TRUE;
-
- } else {
-
- void *vret;
-
- if (flags & FLAG_VERBOSE) {
- c_write_str(ssh, "Authenticating with "
- "public key \"");
- c_write(ssh, s->commentp, s->commentlen);
- c_write_str(ssh, "\" from agent\r\n");
- }
-
- /*
- * Server is willing to accept the key.
- * Construct a SIGN_REQUEST.
- */
- s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
- ssh2_pkt_addstring(s->pktout, ssh->username);
- ssh2_pkt_addstring(s->pktout, "ssh-connection");
- /* service requested */
- ssh2_pkt_addstring(s->pktout, "publickey");
- /* method */
- ssh2_pkt_addbool(s->pktout, TRUE); /* signature included */
- ssh2_pkt_addstring_start(s->pktout);
- ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen);
- ssh2_pkt_addstring_start(s->pktout);
- ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen);
-
- /* Ask agent for signature. */
- s->siglen = s->pktout->length - 5 + 4 +
- ssh->v2_session_id_len;
- if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)
- s->siglen -= 4;
- s->len = 1; /* message type */
- s->len += 4 + s->pklen; /* key blob */
- s->len += 4 + s->siglen; /* data to sign */
- s->len += 4; /* flags */
- s->agentreq = snewn(4 + s->len, char);
- PUT_32BIT(s->agentreq, s->len);
- s->q = s->agentreq + 4;
- *s->q++ = SSH2_AGENTC_SIGN_REQUEST;
- PUT_32BIT(s->q, s->pklen);
- s->q += 4;
- memcpy(s->q, s->pkblob, s->pklen);
- s->q += s->pklen;
- PUT_32BIT(s->q, s->siglen);
- s->q += 4;
- /* Now the data to be signed... */
- if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) {
- PUT_32BIT(s->q, ssh->v2_session_id_len);
- s->q += 4;
- }
- memcpy(s->q, ssh->v2_session_id,
- ssh->v2_session_id_len);
- s->q += ssh->v2_session_id_len;
- memcpy(s->q, s->pktout->data + 5,
- s->pktout->length - 5);
- s->q += s->pktout->length - 5;
- /* And finally the (zero) flags word. */
- PUT_32BIT(s->q, 0);
- if (!agent_query(s->agentreq, s->len + 4,
- &vret, &s->retlen,
- ssh_agent_callback, ssh)) {
- do {
- crReturnV;
- if (pktin) {
- bombout(("Unexpected data from server"
- " while waiting for agent"
- " response"));
- crStopV;
- }
- } while (pktin || inlen > 0);
- vret = ssh->agent_response;
- s->retlen = ssh->agent_response_len;
- }
- s->ret = vret;
- sfree(s->agentreq);
- if (s->ret) {
- if (s->ret[4] == SSH2_AGENT_SIGN_RESPONSE) {
- logevent("Sending Pageant's response");
- ssh2_add_sigblob(ssh, s->pktout,
- s->pkblob, s->pklen,
- s->ret + 9,
- GET_32BIT(s->ret + 5));
- ssh2_pkt_send(ssh, s->pktout);
- s->type = AUTH_TYPE_PUBLICKEY;
- } else {
- /* FIXME: less drastic response */
- bombout(("Pageant failed to answer challenge"));
- crStopV;
- }
- }
- }
-
- /* Do we have any keys left to try? */
- if (s->pkblob_in_agent) {
- s->done_agent = TRUE;
- s->tried_pubkey_config = TRUE;
- } else {
- s->keyi++;
- if (s->keyi >= s->nkeys)
- s->done_agent = TRUE;
- }
-
- } else if (s->can_pubkey && s->publickey_blob &&
- !s->tried_pubkey_config) {
-
- struct ssh2_userkey *key; /* not live over crReturn */
- char *passphrase; /* not live over crReturn */
-
- ssh->pkt_actx = SSH2_PKTCTX_PUBLICKEY;
-
- s->tried_pubkey_config = TRUE;
-
- /*
- * Try the public key supplied in the configuration.
- *
- * First, offer the public blob to see if the server is
- * willing to accept it.
- */
- s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
- ssh2_pkt_addstring(s->pktout, ssh->username);
- ssh2_pkt_addstring(s->pktout, "ssh-connection");
- /* service requested */
- ssh2_pkt_addstring(s->pktout, "publickey"); /* method */
- ssh2_pkt_addbool(s->pktout, FALSE);
- /* no signature included */
- ssh2_pkt_addstring(s->pktout, s->publickey_algorithm);
- ssh2_pkt_addstring_start(s->pktout);
- ssh2_pkt_addstring_data(s->pktout,
- (char *)s->publickey_blob,
- s->publickey_bloblen);
- ssh2_pkt_send(ssh, s->pktout);
- logevent("Offered public key");
-
- crWaitUntilV(pktin);
- if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) {
- /* Key refused. Give up. */
- s->gotit = TRUE; /* reconsider message next loop */
- s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD;
- continue; /* process this new message */
- }
- logevent("Offer of public key accepted");
-
- /*
- * Actually attempt a serious authentication using
- * the key.
- */
- if (flags & FLAG_VERBOSE) {
- c_write_str(ssh, "Authenticating with public key \"");
- c_write_str(ssh, s->publickey_comment);
- c_write_str(ssh, "\"\r\n");
- }
- key = NULL;
- while (!key) {
- const char *error; /* not live over crReturn */
- if (s->publickey_encrypted) {
- /*
- * Get a passphrase from the user.
- */
- int ret; /* need not be kept over crReturn */
- s->cur_prompt = new_prompts(ssh->frontend);
- s->cur_prompt->to_server = FALSE;
- s->cur_prompt->name = dupstr("SSH key passphrase");
- add_prompt(s->cur_prompt,
- dupprintf("Passphrase for key \"%.100s\": ",
- s->publickey_comment),
- FALSE);
- ret = get_userpass_input(s->cur_prompt, NULL, 0);
- while (ret < 0) {
- ssh->send_ok = 1;
- crWaitUntilV(!pktin);
- ret = get_userpass_input(s->cur_prompt,
- in, inlen);
- ssh->send_ok = 0;
- }
- if (!ret) {
- /* Failed to get a passphrase. Terminate. */
- free_prompts(s->cur_prompt);
- ssh_disconnect(ssh, NULL,
- "Unable to authenticate",
- SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER,
- TRUE);
- crStopV;
- }
- passphrase =
- dupstr(s->cur_prompt->prompts[0]->result);
- free_prompts(s->cur_prompt);
- } else {
- passphrase = NULL; /* no passphrase needed */
- }
-
- /*
- * Try decrypting the key.
- */
- s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile);
- key = ssh2_load_userkey(s->keyfile, passphrase, &error);
- if (passphrase) {
- /* burn the evidence */
- memset(passphrase, 0, strlen(passphrase));
- sfree(passphrase);
- }
- if (key == SSH2_WRONG_PASSPHRASE || key == NULL) {
- if (passphrase &&
- (key == SSH2_WRONG_PASSPHRASE)) {
- c_write_str(ssh, "Wrong passphrase\r\n");
- key = NULL;
- /* and loop again */
- } else {
- c_write_str(ssh, "Unable to load private key (");
- c_write_str(ssh, error);
- c_write_str(ssh, ")\r\n");
- key = NULL;
- break; /* try something else */
- }
- }
- }
-
- if (key) {
- unsigned char *pkblob, *sigblob, *sigdata;
- int pkblob_len, sigblob_len, sigdata_len;
- int p;
-
- /*
- * We have loaded the private key and the server
- * has announced that it's willing to accept it.
- * Hallelujah. Generate a signature and send it.
- */
- s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
- ssh2_pkt_addstring(s->pktout, ssh->username);
- ssh2_pkt_addstring(s->pktout, "ssh-connection");
- /* service requested */
- ssh2_pkt_addstring(s->pktout, "publickey");
- /* method */
- ssh2_pkt_addbool(s->pktout, TRUE);
- /* signature follows */
- ssh2_pkt_addstring(s->pktout, key->alg->name);
- pkblob = key->alg->public_blob(key->data,
- &pkblob_len);
- ssh2_pkt_addstring_start(s->pktout);
- ssh2_pkt_addstring_data(s->pktout, (char *)pkblob,
- pkblob_len);
-
- /*
- * The data to be signed is:
- *
- * string session-id
- *
- * followed by everything so far placed in the
- * outgoing packet.
- */
- sigdata_len = s->pktout->length - 5 + 4 +
- ssh->v2_session_id_len;
- if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)
- sigdata_len -= 4;
- sigdata = snewn(sigdata_len, unsigned char);
- p = 0;
- if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) {
- PUT_32BIT(sigdata+p, ssh->v2_session_id_len);
- p += 4;
- }
- memcpy(sigdata+p, ssh->v2_session_id,
- ssh->v2_session_id_len);
- p += ssh->v2_session_id_len;
- memcpy(sigdata+p, s->pktout->data + 5,
- s->pktout->length - 5);
- p += s->pktout->length - 5;
- assert(p == sigdata_len);
- sigblob = key->alg->sign(key->data, (char *)sigdata,
- sigdata_len, &sigblob_len);
- ssh2_add_sigblob(ssh, s->pktout, pkblob, pkblob_len,
- sigblob, sigblob_len);
- sfree(pkblob);
- sfree(sigblob);
- sfree(sigdata);
-
- ssh2_pkt_send(ssh, s->pktout);
- logevent("Sent public key signature");
- s->type = AUTH_TYPE_PUBLICKEY;
- key->alg->freekey(key->data);
- }
-
-#ifndef NO_GSSAPI
- } else if (s->can_gssapi && !s->tried_gssapi) {
-
- /* GSSAPI Authentication */
-
- int micoffset, len;
- char *data;
- Ssh_gss_buf mic;
- s->type = AUTH_TYPE_GSSAPI;
- s->tried_gssapi = TRUE;
- s->gotit = TRUE;
- ssh->pkt_actx = SSH2_PKTCTX_GSSAPI;
-
- /*
- * Pick the highest GSS library on the preference
- * list.
- */
- {
- int i, j;
- s->gsslib = NULL;
- for (i = 0; i < ngsslibs; i++) {
- int want_id = conf_get_int_int(ssh->conf,
- CONF_ssh_gsslist, i);
- for (j = 0; j < ssh->gsslibs->nlibraries; j++)
- if (ssh->gsslibs->libraries[j].id == want_id) {
- s->gsslib = &ssh->gsslibs->libraries[j];
- goto got_gsslib; /* double break */
- }
- }
- got_gsslib:
- /*
- * We always expect to have found something in
- * the above loop: we only came here if there
- * was at least one viable GSS library, and the
- * preference list should always mention
- * everything and only change the order.
- */
- assert(s->gsslib);
- }
-
- if (s->gsslib->gsslogmsg)
- logevent(s->gsslib->gsslogmsg);
-
- /* Sending USERAUTH_REQUEST with "gssapi-with-mic" method */
- s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
- ssh2_pkt_addstring(s->pktout, ssh->username);
- ssh2_pkt_addstring(s->pktout, "ssh-connection");
- ssh2_pkt_addstring(s->pktout, "gssapi-with-mic");
- logevent("Attempting GSSAPI authentication");
-
- /* add mechanism info */
- s->gsslib->indicate_mech(s->gsslib, &s->gss_buf);
-
- /* number of GSSAPI mechanisms */
- ssh2_pkt_adduint32(s->pktout,1);
-
- /* length of OID + 2 */
- ssh2_pkt_adduint32(s->pktout, s->gss_buf.length + 2);
- ssh2_pkt_addbyte(s->pktout, SSH2_GSS_OIDTYPE);
-
- /* length of OID */
- ssh2_pkt_addbyte(s->pktout, (unsigned char) s->gss_buf.length);
-
- ssh_pkt_adddata(s->pktout, s->gss_buf.value,
- s->gss_buf.length);
- ssh2_pkt_send(ssh, s->pktout);
- crWaitUntilV(pktin);
- if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_RESPONSE) {
- logevent("GSSAPI authentication request refused");
- continue;
- }
-
- /* check returned packet ... */
-
- ssh_pkt_getstring(pktin, &data, &len);
- s->gss_rcvtok.value = data;
- s->gss_rcvtok.length = len;
- if (s->gss_rcvtok.length != s->gss_buf.length + 2 ||
- ((char *)s->gss_rcvtok.value)[0] != SSH2_GSS_OIDTYPE ||
- ((char *)s->gss_rcvtok.value)[1] != s->gss_buf.length ||
- memcmp((char *)s->gss_rcvtok.value + 2,
- s->gss_buf.value,s->gss_buf.length) ) {
- logevent("GSSAPI authentication - wrong response from server");
- continue;
- }
-
- /* now start running */
- s->gss_stat = s->gsslib->import_name(s->gsslib,
- ssh->fullhostname,
- &s->gss_srv_name);
- if (s->gss_stat != SSH_GSS_OK) {
- if (s->gss_stat == SSH_GSS_BAD_HOST_NAME)
- logevent("GSSAPI import name failed - Bad service name");
- else
- logevent("GSSAPI import name failed");
- continue;
- }
-
- /* fetch TGT into GSS engine */
- s->gss_stat = s->gsslib->acquire_cred(s->gsslib, &s->gss_ctx);
-
- if (s->gss_stat != SSH_GSS_OK) {
- logevent("GSSAPI authentication failed to get credentials");
- s->gsslib->release_name(s->gsslib, &s->gss_srv_name);
- continue;
- }
-
- /* initial tokens are empty */
- SSH_GSS_CLEAR_BUF(&s->gss_rcvtok);
- SSH_GSS_CLEAR_BUF(&s->gss_sndtok);
-
- /* now enter the loop */
- do {
- s->gss_stat = s->gsslib->init_sec_context
- (s->gsslib,
- &s->gss_ctx,
- s->gss_srv_name,
- conf_get_int(ssh->conf, CONF_gssapifwd),
- &s->gss_rcvtok,
- &s->gss_sndtok);
-
- if (s->gss_stat!=SSH_GSS_S_COMPLETE &&
- s->gss_stat!=SSH_GSS_S_CONTINUE_NEEDED) {
- logevent("GSSAPI authentication initialisation failed");
-
- if (s->gsslib->display_status(s->gsslib, s->gss_ctx,
- &s->gss_buf) == SSH_GSS_OK) {
- logevent(s->gss_buf.value);
- sfree(s->gss_buf.value);
- }
-
- break;
- }
- logevent("GSSAPI authentication initialised");
-
- /* Client and server now exchange tokens until GSSAPI
- * no longer says CONTINUE_NEEDED */
-
- if (s->gss_sndtok.length != 0) {
- s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_GSSAPI_TOKEN);
- ssh_pkt_addstring_start(s->pktout);
- ssh_pkt_addstring_data(s->pktout,s->gss_sndtok.value,s->gss_sndtok.length);
- ssh2_pkt_send(ssh, s->pktout);
- s->gsslib->free_tok(s->gsslib, &s->gss_sndtok);
- }
-
- if (s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED) {
- crWaitUntilV(pktin);
- if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_TOKEN) {
- logevent("GSSAPI authentication - bad server response");
- s->gss_stat = SSH_GSS_FAILURE;
- break;
- }
- ssh_pkt_getstring(pktin, &data, &len);
- s->gss_rcvtok.value = data;
- s->gss_rcvtok.length = len;
- }
- } while (s-> gss_stat == SSH_GSS_S_CONTINUE_NEEDED);
-
- if (s->gss_stat != SSH_GSS_OK) {
- s->gsslib->release_name(s->gsslib, &s->gss_srv_name);
- s->gsslib->release_cred(s->gsslib, &s->gss_ctx);
- continue;
- }
- logevent("GSSAPI authentication loop finished OK");
-
- /* Now send the MIC */
-
- s->pktout = ssh2_pkt_init(0);
- micoffset = s->pktout->length;
- ssh_pkt_addstring_start(s->pktout);
- ssh_pkt_addstring_data(s->pktout, (char *)ssh->v2_session_id, ssh->v2_session_id_len);
- ssh_pkt_addbyte(s->pktout, SSH2_MSG_USERAUTH_REQUEST);
- ssh_pkt_addstring(s->pktout, ssh->username);
- ssh_pkt_addstring(s->pktout, "ssh-connection");
- ssh_pkt_addstring(s->pktout, "gssapi-with-mic");
-
- s->gss_buf.value = (char *)s->pktout->data + micoffset;
- s->gss_buf.length = s->pktout->length - micoffset;
-
- s->gsslib->get_mic(s->gsslib, s->gss_ctx, &s->gss_buf, &mic);
- s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_GSSAPI_MIC);
- ssh_pkt_addstring_start(s->pktout);
- ssh_pkt_addstring_data(s->pktout, mic.value, mic.length);
- ssh2_pkt_send(ssh, s->pktout);
- s->gsslib->free_mic(s->gsslib, &mic);
-
- s->gotit = FALSE;
-
- s->gsslib->release_name(s->gsslib, &s->gss_srv_name);
- s->gsslib->release_cred(s->gsslib, &s->gss_ctx);
- continue;
-#endif
- } else if (s->can_keyb_inter && !s->kbd_inter_refused) {
-
- /*
- * Keyboard-interactive authentication.
- */
-
- s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
-
- ssh->pkt_actx = SSH2_PKTCTX_KBDINTER;
-
- s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
- ssh2_pkt_addstring(s->pktout, ssh->username);
- ssh2_pkt_addstring(s->pktout, "ssh-connection");
- /* service requested */
- ssh2_pkt_addstring(s->pktout, "keyboard-interactive");
- /* method */
- ssh2_pkt_addstring(s->pktout, ""); /* lang */
- ssh2_pkt_addstring(s->pktout, ""); /* submethods */
- ssh2_pkt_send(ssh, s->pktout);
-
- logevent("Attempting keyboard-interactive authentication");
-
- crWaitUntilV(pktin);
- if (pktin->type != SSH2_MSG_USERAUTH_INFO_REQUEST) {
- /* Server is not willing to do keyboard-interactive
- * at all (or, bizarrely but legally, accepts the
- * user without actually issuing any prompts).
- * Give up on it entirely. */
- s->gotit = TRUE;
- s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET;
- s->kbd_inter_refused = TRUE; /* don't try it again */
- continue;
- }
-
- /*
- * Loop while the server continues to send INFO_REQUESTs.
- */
- while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) {
-
- char *name, *inst, *lang;
- int name_len, inst_len, lang_len;
- int i;
-
- /*
- * We've got a fresh USERAUTH_INFO_REQUEST.
- * Get the preamble and start building a prompt.
- */
- ssh_pkt_getstring(pktin, &name, &name_len);
- ssh_pkt_getstring(pktin, &inst, &inst_len);
- ssh_pkt_getstring(pktin, &lang, &lang_len);
- s->cur_prompt = new_prompts(ssh->frontend);
- s->cur_prompt->to_server = TRUE;
-
- /*
- * Get any prompt(s) from the packet.
- */
- s->num_prompts = ssh_pkt_getuint32(pktin);
- for (i = 0; i < s->num_prompts; i++) {
- char *prompt;
- int prompt_len;
- int echo;
- static char noprompt[] =
- "<server failed to send prompt>: ";
-
- ssh_pkt_getstring(pktin, &prompt, &prompt_len);
- echo = ssh2_pkt_getbool(pktin);
- if (!prompt_len) {
- prompt = noprompt;
- prompt_len = lenof(noprompt)-1;
- }
- add_prompt(s->cur_prompt,
- dupprintf("%.*s", prompt_len, prompt),
- echo);
- }
-
- if (name_len) {
- /* FIXME: better prefix to distinguish from
- * local prompts? */
- s->cur_prompt->name =
- dupprintf("SSH server: %.*s", name_len, name);
- s->cur_prompt->name_reqd = TRUE;
- } else {
- s->cur_prompt->name =
- dupstr("SSH server authentication");
- s->cur_prompt->name_reqd = FALSE;
- }
- /* We add a prefix to try to make it clear that a prompt
- * has come from the server.
- * FIXME: ugly to print "Using..." in prompt _every_
- * time round. Can this be done more subtly? */
- /* Special case: for reasons best known to themselves,
- * some servers send k-i requests with no prompts and
- * nothing to display. Keep quiet in this case. */
- if (s->num_prompts || name_len || inst_len) {
- s->cur_prompt->instruction =
- dupprintf("Using keyboard-interactive authentication.%s%.*s",
- inst_len ? "\n" : "", inst_len, inst);
- s->cur_prompt->instr_reqd = TRUE;
- } else {
- s->cur_prompt->instr_reqd = FALSE;
- }
-
- /*
- * Display any instructions, and get the user's
- * response(s).
- */
- {
- int ret; /* not live over crReturn */
- ret = get_userpass_input(s->cur_prompt, NULL, 0);
- while (ret < 0) {
- ssh->send_ok = 1;
- crWaitUntilV(!pktin);
- ret = get_userpass_input(s->cur_prompt, in, inlen);
- ssh->send_ok = 0;
- }
- if (!ret) {
- /*
- * Failed to get responses. Terminate.
- */
- free_prompts(s->cur_prompt);
- ssh_disconnect(ssh, NULL, "Unable to authenticate",
- SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER,
- TRUE);
- crStopV;
- }
- }
-
- /*
- * Send the response(s) to the server.
- */
- s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE);
- ssh2_pkt_adduint32(s->pktout, s->num_prompts);
- for (i=0; i < s->num_prompts; i++) {
- dont_log_password(ssh, s->pktout, PKTLOG_BLANK);
- ssh2_pkt_addstring(s->pktout,
- s->cur_prompt->prompts[i]->result);
- end_log_omission(ssh, s->pktout);
- }
- ssh2_pkt_send_with_padding(ssh, s->pktout, 256);
-
- /*
- * Free the prompts structure from this iteration.
- * If there's another, a new one will be allocated
- * when we return to the top of this while loop.
- */
- free_prompts(s->cur_prompt);
-
- /*
- * Get the next packet in case it's another
- * INFO_REQUEST.
- */
- crWaitUntilV(pktin);
-
- }
-
- /*
- * We should have SUCCESS or FAILURE now.
- */
- s->gotit = TRUE;
-
- } else if (s->can_passwd) {
-
- /*
- * Plain old password authentication.
- */
- int ret; /* not live over crReturn */
- int changereq_first_time; /* not live over crReturn */
-
- ssh->pkt_actx = SSH2_PKTCTX_PASSWORD;
-
- s->cur_prompt = new_prompts(ssh->frontend);
- s->cur_prompt->to_server = TRUE;
- s->cur_prompt->name = dupstr("SSH password");
- add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ",
- ssh->username,
- ssh->savedhost),
- FALSE);
-
- ret = get_userpass_input(s->cur_prompt, NULL, 0);
- while (ret < 0) {
- ssh->send_ok = 1;
- crWaitUntilV(!pktin);
- ret = get_userpass_input(s->cur_prompt, in, inlen);
- ssh->send_ok = 0;
- }
- if (!ret) {
- /*
- * Failed to get responses. Terminate.
- */
- free_prompts(s->cur_prompt);
- ssh_disconnect(ssh, NULL, "Unable to authenticate",
- SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER,
- TRUE);
- crStopV;
- }
- /*
- * Squirrel away the password. (We may need it later if
- * asked to change it.)
- */
- s->password = dupstr(s->cur_prompt->prompts[0]->result);
- free_prompts(s->cur_prompt);
-
- /*
- * Send the password packet.
- *
- * We pad out the password packet to 256 bytes to make
- * it harder for an attacker to find the length of the
- * user's password.
- *
- * Anyone using a password longer than 256 bytes
- * probably doesn't have much to worry about from
- * people who find out how long their password is!
- */
- s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
- ssh2_pkt_addstring(s->pktout, ssh->username);
- ssh2_pkt_addstring(s->pktout, "ssh-connection");
- /* service requested */
- ssh2_pkt_addstring(s->pktout, "password");
- ssh2_pkt_addbool(s->pktout, FALSE);
- dont_log_password(ssh, s->pktout, PKTLOG_BLANK);
- ssh2_pkt_addstring(s->pktout, s->password);
- end_log_omission(ssh, s->pktout);
- ssh2_pkt_send_with_padding(ssh, s->pktout, 256);
- logevent("Sent password");
- s->type = AUTH_TYPE_PASSWORD;
-
- /*
- * Wait for next packet, in case it's a password change
- * request.
- */
- crWaitUntilV(pktin);
- changereq_first_time = TRUE;
-
- while (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) {
-
- /*
- * We're being asked for a new password
- * (perhaps not for the first time).
- * Loop until the server accepts it.
- */
-
- int got_new = FALSE; /* not live over crReturn */
- char *prompt; /* not live over crReturn */
- int prompt_len; /* not live over crReturn */
-
- {
- char *msg;
- if (changereq_first_time)
- msg = "Server requested password change";
- else
- msg = "Server rejected new password";
- logevent(msg);
- c_write_str(ssh, msg);
- c_write_str(ssh, "\r\n");
- }
-
- ssh_pkt_getstring(pktin, &prompt, &prompt_len);
-
- s->cur_prompt = new_prompts(ssh->frontend);
- s->cur_prompt->to_server = TRUE;
- s->cur_prompt->name = dupstr("New SSH password");
- s->cur_prompt->instruction =
- dupprintf("%.*s", prompt_len, prompt);
- s->cur_prompt->instr_reqd = TRUE;
- /*
- * There's no explicit requirement in the protocol
- * for the "old" passwords in the original and
- * password-change messages to be the same, and
- * apparently some Cisco kit supports password change
- * by the user entering a blank password originally
- * and the real password subsequently, so,
- * reluctantly, we prompt for the old password again.
- *
- * (On the other hand, some servers don't even bother
- * to check this field.)
- */
- add_prompt(s->cur_prompt,
- dupstr("Current password (blank for previously entered password): "),
- FALSE);
- add_prompt(s->cur_prompt, dupstr("Enter new password: "),
- FALSE);
- add_prompt(s->cur_prompt, dupstr("Confirm new password: "),
- FALSE);
-
- /*
- * Loop until the user manages to enter the same
- * password twice.
- */
- while (!got_new) {
-
- ret = get_userpass_input(s->cur_prompt, NULL, 0);
- while (ret < 0) {
- ssh->send_ok = 1;
- crWaitUntilV(!pktin);
- ret = get_userpass_input(s->cur_prompt, in, inlen);
- ssh->send_ok = 0;
- }
- if (!ret) {
- /*
- * Failed to get responses. Terminate.
- */
- /* burn the evidence */
- free_prompts(s->cur_prompt);
- memset(s->password, 0, strlen(s->password));
- sfree(s->password);
- ssh_disconnect(ssh, NULL, "Unable to authenticate",
- SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER,
- TRUE);
- crStopV;
- }
-
- /*
- * If the user specified a new original password
- * (IYSWIM), overwrite any previously specified
- * one.
- * (A side effect is that the user doesn't have to
- * re-enter it if they louse up the new password.)
- */
- if (s->cur_prompt->prompts[0]->result[0]) {
- memset(s->password, 0, strlen(s->password));
- /* burn the evidence */
- sfree(s->password);
- s->password =
- dupstr(s->cur_prompt->prompts[0]->result);
- }
-
- /*
- * Check the two new passwords match.
- */
- got_new = (strcmp(s->cur_prompt->prompts[1]->result,
- s->cur_prompt->prompts[2]->result)
- == 0);
- if (!got_new)
- /* They don't. Silly user. */
- c_write_str(ssh, "Passwords do not match\r\n");
-
- }
-
- /*
- * Send the new password (along with the old one).
- * (see above for padding rationale)
- */
- s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
- ssh2_pkt_addstring(s->pktout, ssh->username);
- ssh2_pkt_addstring(s->pktout, "ssh-connection");
- /* service requested */
- ssh2_pkt_addstring(s->pktout, "password");
- ssh2_pkt_addbool(s->pktout, TRUE);
- dont_log_password(ssh, s->pktout, PKTLOG_BLANK);
- ssh2_pkt_addstring(s->pktout, s->password);
- ssh2_pkt_addstring(s->pktout,
- s->cur_prompt->prompts[1]->result);
- free_prompts(s->cur_prompt);
- end_log_omission(ssh, s->pktout);
- ssh2_pkt_send_with_padding(ssh, s->pktout, 256);
- logevent("Sent new password");
-
- /*
- * Now see what the server has to say about it.
- * (If it's CHANGEREQ again, it's not happy with the
- * new password.)
- */
- crWaitUntilV(pktin);
- changereq_first_time = FALSE;
-
- }
-
- /*
- * We need to reexamine the current pktin at the top
- * of the loop. Either:
- * - we weren't asked to change password at all, in
- * which case it's a SUCCESS or FAILURE with the
- * usual meaning
- * - we sent a new password, and the server was
- * either OK with it (SUCCESS or FAILURE w/partial
- * success) or unhappy with the _old_ password
- * (FAILURE w/o partial success)
- * In any of these cases, we go back to the top of
- * the loop and start again.
- */
- s->gotit = TRUE;
-
- /*
- * We don't need the old password any more, in any
- * case. Burn the evidence.
- */
- memset(s->password, 0, strlen(s->password));
- sfree(s->password);
-
- } else {
- char *str = dupprintf("No supported authentication methods available"
- " (server sent: %.*s)",
- methlen, methods);
-
- ssh_disconnect(ssh, str,
- "No supported authentication methods available",
- SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
- FALSE);
- sfree(str);
-
- crStopV;
-
- }
-
- }
- }
- ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = NULL;
-
- /* Clear up various bits and pieces from authentication. */
- if (s->publickey_blob) {
- sfree(s->publickey_blob);
- sfree(s->publickey_comment);
- }
- if (s->agent_response)
- sfree(s->agent_response);
-
- if (s->userauth_success) {
- /*
- * We've just received USERAUTH_SUCCESS, and we haven't sent any
- * packets since. Signal the transport layer to consider enacting
- * delayed compression.
- *
- * (Relying on we_are_in is not sufficient, as
- * draft-miller-secsh-compression-delayed is quite clear that it
- * triggers on USERAUTH_SUCCESS specifically, and we_are_in can
- * become set for other reasons.)
- */
- do_ssh2_transport(ssh, "enabling delayed compression", -2, NULL);
- }
-
- /*
- * Now the connection protocol has started, one way or another.
- */
-
- ssh->channels = newtree234(ssh_channelcmp);
-
- /*
- * Set up handlers for some connection protocol messages, so we
- * don't have to handle them repeatedly in this coroutine.
- */
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] =
- ssh2_msg_channel_window_adjust;
- ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] =
- ssh2_msg_global_request;
-
- /*
- * Create the main session channel.
- */
- if (conf_get_int(ssh->conf, CONF_ssh_no_shell)) {
- ssh->mainchan = NULL;
- } else if (*conf_get_str(ssh->conf, CONF_ssh_nc_host)) {
- /*
- * Just start a direct-tcpip channel and use it as the main
- * channel.
- */
- ssh->mainchan = snew(struct ssh_channel);
- ssh->mainchan->ssh = ssh;
- ssh2_channel_init(ssh->mainchan);
- logeventf(ssh,
- "Opening direct-tcpip channel to %s:%d in place of session",
- conf_get_str(ssh->conf, CONF_ssh_nc_host),
- conf_get_int(ssh->conf, CONF_ssh_nc_port));
- s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
- ssh2_pkt_addstring(s->pktout, "direct-tcpip");
- ssh2_pkt_adduint32(s->pktout, ssh->mainchan->localid);
- ssh2_pkt_adduint32(s->pktout, ssh->mainchan->v.v2.locwindow);/* our window size */
- ssh2_pkt_adduint32(s->pktout, OUR_V2_MAXPKT); /* our max pkt size */
- ssh2_pkt_addstring(s->pktout, conf_get_str(ssh->conf, CONF_ssh_nc_host));
- ssh2_pkt_adduint32(s->pktout, conf_get_int(ssh->conf, CONF_ssh_nc_port));
- /*
- * There's nothing meaningful to put in the originator
- * fields, but some servers insist on syntactically correct
- * information.
- */
- ssh2_pkt_addstring(s->pktout, "0.0.0.0");
- ssh2_pkt_adduint32(s->pktout, 0);
- ssh2_pkt_send(ssh, s->pktout);
-
- crWaitUntilV(pktin);
- if (pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
- bombout(("Server refused to open a direct-tcpip channel"));
- crStopV;
- /* FIXME: error data comes back in FAILURE packet */
- }
- if (ssh_pkt_getuint32(pktin) != ssh->mainchan->localid) {
- bombout(("Server's channel confirmation cited wrong channel"));
- crStopV;
- }
- ssh->mainchan->remoteid = ssh_pkt_getuint32(pktin);
- ssh->mainchan->halfopen = FALSE;
- ssh->mainchan->type = CHAN_MAINSESSION;
- ssh->mainchan->v.v2.remwindow = ssh_pkt_getuint32(pktin);
- ssh->mainchan->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin);
- add234(ssh->channels, ssh->mainchan);
- update_specials_menu(ssh->frontend);
- logevent("Opened direct-tcpip channel");
- ssh->ncmode = TRUE;
- } else {
- ssh->mainchan = snew(struct ssh_channel);
- ssh->mainchan->ssh = ssh;
- ssh2_channel_init(ssh->mainchan);
- s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
- ssh2_pkt_addstring(s->pktout, "session");
- ssh2_pkt_adduint32(s->pktout, ssh->mainchan->localid);
- ssh2_pkt_adduint32(s->pktout, ssh->mainchan->v.v2.locwindow);/* our window size */
- ssh2_pkt_adduint32(s->pktout, OUR_V2_MAXPKT); /* our max pkt size */
- ssh2_pkt_send(ssh, s->pktout);
- crWaitUntilV(pktin);
- if (pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
- bombout(("Server refused to open a session"));
- crStopV;
- /* FIXME: error data comes back in FAILURE packet */
- }
- if (ssh_pkt_getuint32(pktin) != ssh->mainchan->localid) {
- bombout(("Server's channel confirmation cited wrong channel"));
- crStopV;
- }
- ssh->mainchan->remoteid = ssh_pkt_getuint32(pktin);
- ssh->mainchan->halfopen = FALSE;
- ssh->mainchan->type = CHAN_MAINSESSION;
- ssh->mainchan->v.v2.remwindow = ssh_pkt_getuint32(pktin);
- ssh->mainchan->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin);
- add234(ssh->channels, ssh->mainchan);
- update_specials_menu(ssh->frontend);
- logevent("Opened channel for session");
- ssh->ncmode = FALSE;
- }
-
- /*
- * Now we have a channel, make dispatch table entries for
- * general channel-based messages.
- */
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] =
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] =
- ssh2_msg_channel_data;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_channel_eof;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_channel_close;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] =
- ssh2_msg_channel_open_confirmation;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] =
- ssh2_msg_channel_open_failure;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] =
- ssh2_msg_channel_request;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] =
- ssh2_msg_channel_open;
-
- if (ssh->mainchan && conf_get_int(ssh->conf, CONF_ssh_simple)) {
- /*
- * This message indicates to the server that we promise
- * not to try to run any other channel in parallel with
- * this one, so it's safe for it to advertise a very large
- * window and leave the flow control to TCP.
- */
- s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
- ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid);
- ssh2_pkt_addstring(s->pktout, "simple@putty.projects.tartarus.org");
- ssh2_pkt_addbool(s->pktout, 0); /* no reply */
- ssh2_pkt_send(ssh, s->pktout);
- }
-
- /*
- * Potentially enable X11 forwarding.
- */
- if (ssh->mainchan && !ssh->ncmode && conf_get_int(ssh->conf, CONF_x11_forward) &&
- (ssh->x11disp = x11_setup_display(conf_get_str(ssh->conf, CONF_x11_display),
- conf_get_int(ssh->conf, CONF_x11_auth), ssh->conf))) {
- logevent("Requesting X11 forwarding");
- s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
- ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid);
- ssh2_pkt_addstring(s->pktout, "x11-req");
- ssh2_pkt_addbool(s->pktout, 1); /* want reply */
- ssh2_pkt_addbool(s->pktout, 0); /* many connections */
- ssh2_pkt_addstring(s->pktout, ssh->x11disp->remoteauthprotoname);
- /*
- * Note that while we blank the X authentication data here, we don't
- * take any special action to blank the start of an X11 channel,
- * so using MIT-MAGIC-COOKIE-1 and actually opening an X connection
- * without having session blanking enabled is likely to leak your
- * cookie into the log.
- */
- dont_log_password(ssh, s->pktout, PKTLOG_BLANK);
- ssh2_pkt_addstring(s->pktout, ssh->x11disp->remoteauthdatastring);
- end_log_omission(ssh, s->pktout);
- ssh2_pkt_adduint32(s->pktout, ssh->x11disp->screennum);
- ssh2_pkt_send(ssh, s->pktout);
-
- crWaitUntilV(pktin);
-
- if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
- if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
- bombout(("Unexpected response to X11 forwarding request:"
- " packet type %d", pktin->type));
- crStopV;
- }
- logevent("X11 forwarding refused");
- } else {
- logevent("X11 forwarding enabled");
- ssh->X11_fwd_enabled = TRUE;
- }
- }
-
- /*
- * Enable port forwardings.
- */
- ssh_setup_portfwd(ssh, ssh->conf);
-
- /*
- * Potentially enable agent forwarding.
- */
- if (ssh->mainchan && !ssh->ncmode && conf_get_int(ssh->conf, CONF_agentfwd) && agent_exists()) {
- logevent("Requesting OpenSSH-style agent forwarding");
- s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
- ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid);
- ssh2_pkt_addstring(s->pktout, "auth-agent-req@openssh.com");
- ssh2_pkt_addbool(s->pktout, 1); /* want reply */
- ssh2_pkt_send(ssh, s->pktout);
-
- crWaitUntilV(pktin);
-
- if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
- if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
- bombout(("Unexpected response to agent forwarding request:"
- " packet type %d", pktin->type));
- crStopV;
- }
- logevent("Agent forwarding refused");
- } else {
- logevent("Agent forwarding enabled");
- ssh->agentfwd_enabled = TRUE;
- }
- }
-
- /*
- * Now allocate a pty for the session.
- */
- if (ssh->mainchan && !ssh->ncmode && !conf_get_int(ssh->conf, CONF_nopty)) {
- /* Unpick the terminal-speed string. */
- /* XXX perhaps we should allow no speeds to be sent. */
- ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */
- sscanf(conf_get_str(ssh->conf, CONF_termspeed), "%d,%d", &ssh->ospeed, &ssh->ispeed);
- /* Build the pty request. */
- s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
- ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid); /* recipient channel */
- ssh2_pkt_addstring(s->pktout, "pty-req");
- ssh2_pkt_addbool(s->pktout, 1); /* want reply */
- ssh2_pkt_addstring(s->pktout, conf_get_str(ssh->conf, CONF_termtype));
- ssh2_pkt_adduint32(s->pktout, ssh->term_width);
- ssh2_pkt_adduint32(s->pktout, ssh->term_height);
- ssh2_pkt_adduint32(s->pktout, 0); /* pixel width */
- ssh2_pkt_adduint32(s->pktout, 0); /* pixel height */
- ssh2_pkt_addstring_start(s->pktout);
- parse_ttymodes(ssh, ssh2_send_ttymode, (void *)s->pktout);
- ssh2_pkt_addbyte(s->pktout, SSH2_TTY_OP_ISPEED);
- ssh2_pkt_adduint32(s->pktout, ssh->ispeed);
- ssh2_pkt_addbyte(s->pktout, SSH2_TTY_OP_OSPEED);
- ssh2_pkt_adduint32(s->pktout, ssh->ospeed);
- ssh2_pkt_addstring_data(s->pktout, "\0", 1); /* TTY_OP_END */
- ssh2_pkt_send(ssh, s->pktout);
- ssh->state = SSH_STATE_INTERMED;
-
- crWaitUntilV(pktin);
-
- if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
- if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
- bombout(("Unexpected response to pty request:"
- " packet type %d", pktin->type));
- crStopV;
- }
- c_write_str(ssh, "Server refused to allocate pty\r\n");
- ssh->editing = ssh->echoing = 1;
- } else {
- logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)",
- ssh->ospeed, ssh->ispeed);
- ssh->got_pty = TRUE;
- }
- } else {
- ssh->editing = ssh->echoing = 1;
- }
-
- /*
- * Send environment variables.
- *
- * Simplest thing here is to send all the requests at once, and
- * then wait for a whole bunch of successes or failures.
- */
- if (ssh->mainchan && !ssh->ncmode) {
- char *key, *val;
-
- s->num_env = 0;
-
- for (val = conf_get_str_strs(ssh->conf, CONF_environmt, NULL, &key);
- val != NULL;
- val = conf_get_str_strs(ssh->conf, CONF_environmt, key, &key)) {
- s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
- ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid);
- ssh2_pkt_addstring(s->pktout, "env");
- ssh2_pkt_addbool(s->pktout, 1); /* want reply */
- ssh2_pkt_addstring(s->pktout, key);
- ssh2_pkt_addstring(s->pktout, val);
- ssh2_pkt_send(ssh, s->pktout);
-
- s->num_env++;
- }
-
- if (s->num_env) {
- logeventf(ssh, "Sent %d environment variables", s->num_env);
-
- s->env_ok = 0;
- s->env_left = s->num_env;
-
- while (s->env_left > 0) {
- crWaitUntilV(pktin);
-
- if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
- if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
- bombout(("Unexpected response to environment request:"
- " packet type %d", pktin->type));
- crStopV;
- }
- } else {
- s->env_ok++;
- }
-
- s->env_left--;
- }
-
- if (s->env_ok == s->num_env) {
- logevent("All environment variables successfully set");
- } else if (s->env_ok == 0) {
- logevent("All environment variables refused");
- c_write_str(ssh, "Server refused to set environment variables\r\n");
- } else {
- logeventf(ssh, "%d environment variables refused",
- s->num_env - s->env_ok);
- c_write_str(ssh, "Server refused to set all environment variables\r\n");
- }
- }
- }
-
- /*
- * Start a shell or a remote command. We may have to attempt
- * this twice if the config data has provided a second choice
- * of command.
- */
- if (ssh->mainchan && !ssh->ncmode) while (1) {
- int subsys;
- char *cmd;
-
- if (ssh->fallback_cmd) {
- subsys = conf_get_int(ssh->conf, CONF_ssh_subsys2);
- cmd = conf_get_str(ssh->conf, CONF_remote_cmd2);
- } else {
- subsys = conf_get_int(ssh->conf, CONF_ssh_subsys);
- cmd = conf_get_str(ssh->conf, CONF_remote_cmd);
- }
-
- s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
- ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid); /* recipient channel */
- if (subsys) {
- ssh2_pkt_addstring(s->pktout, "subsystem");
- ssh2_pkt_addbool(s->pktout, 1); /* want reply */
- ssh2_pkt_addstring(s->pktout, cmd);
- } else if (*cmd) {
- ssh2_pkt_addstring(s->pktout, "exec");
- ssh2_pkt_addbool(s->pktout, 1); /* want reply */
- ssh2_pkt_addstring(s->pktout, cmd);
- } else {
- ssh2_pkt_addstring(s->pktout, "shell");
- ssh2_pkt_addbool(s->pktout, 1); /* want reply */
- }
- ssh2_pkt_send(ssh, s->pktout);
-
- crWaitUntilV(pktin);
-
- if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
- if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
- bombout(("Unexpected response to shell/command request:"
- " packet type %d", pktin->type));
- crStopV;
- }
- /*
- * We failed to start the command. If this is the
- * fallback command, we really are finished; if it's
- * not, and if the fallback command exists, try falling
- * back to it before complaining.
- */
- if (!ssh->fallback_cmd &&
- *conf_get_str(ssh->conf, CONF_remote_cmd2)) {
- logevent("Primary command failed; attempting fallback");
- ssh->fallback_cmd = TRUE;
- continue;
- }
- bombout(("Server refused to start a shell/command"));
- crStopV;
- } else {
- logevent("Started a shell/command");
- }
- break;
- }
-
- ssh->state = SSH_STATE_SESSION;
- if (ssh->size_needed)
- ssh_size(ssh, ssh->term_width, ssh->term_height);
- if (ssh->eof_needed)
- ssh_special(ssh, TS_EOF);
-
- /*
- * All the initial channel requests are done, so install the default
- * failure handler.
- */
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_channel_success;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_channel_failure;
-
- /*
- * Transfer data!
- */
- if (ssh->ldisc)
- ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */
- if (ssh->mainchan)
- ssh->send_ok = 1;
- while (1) {
- crReturnV;
- s->try_send = FALSE;
- if (pktin) {
-
- /*
- * _All_ the connection-layer packets we expect to
- * receive are now handled by the dispatch table.
- * Anything that reaches here must be bogus.
- */
-
- bombout(("Strange packet received: type %d", pktin->type));
- crStopV;
- } else if (ssh->mainchan) {
- /*
- * We have spare data. Add it to the channel buffer.
- */
- ssh2_add_channel_data(ssh->mainchan, (char *)in, inlen);
- s->try_send = TRUE;
- }
- if (s->try_send) {
- int i;
- struct ssh_channel *c;
- /*
- * Try to send data on all channels if we can.
- */
- for (i = 0; NULL != (c = index234(ssh->channels, i)); i++)
- ssh2_try_send_and_unthrottle(ssh, c);
- }
- }
-
- crFinishV;
-}
-
-/*
- * Handlers for SSH-2 messages that might arrive at any moment.
- */
-static void ssh2_msg_disconnect(Ssh ssh, struct Packet *pktin)
-{
- /* log reason code in disconnect message */
- char *buf, *msg;
- int reason, msglen;
-
- reason = ssh_pkt_getuint32(pktin);
- ssh_pkt_getstring(pktin, &msg, &msglen);
-
- if (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) {
- buf = dupprintf("Received disconnect message (%s)",
- ssh2_disconnect_reasons[reason]);
- } else {
- buf = dupprintf("Received disconnect message (unknown"
- " type %d)", reason);
- }
- logevent(buf);
- sfree(buf);
- buf = dupprintf("Disconnection message text: %.*s",
- msglen, msg);
- logevent(buf);
- bombout(("Server sent disconnect message\ntype %d (%s):\n\"%.*s\"",
- reason,
- (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ?
- ssh2_disconnect_reasons[reason] : "unknown",
- msglen, msg));
- sfree(buf);
-}
-
-static void ssh2_msg_debug(Ssh ssh, struct Packet *pktin)
-{
- /* log the debug message */
- char *msg;
- int msglen;
-
- /* XXX maybe we should actually take notice of the return value */
- ssh2_pkt_getbool(pktin);
- ssh_pkt_getstring(pktin, &msg, &msglen);
-
- logeventf(ssh, "Remote debug message: %.*s", msglen, msg);
-}
-
-static void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin)
-{
- struct Packet *pktout;
- pktout = ssh2_pkt_init(SSH2_MSG_UNIMPLEMENTED);
- ssh2_pkt_adduint32(pktout, pktin->sequence);
- /*
- * UNIMPLEMENTED messages MUST appear in the same order as the
- * messages they respond to. Hence, never queue them.
- */
- ssh2_pkt_send_noqueue(ssh, pktout);
-}
-
-/*
- * Handle the top-level SSH-2 protocol.
- */
-static void ssh2_protocol_setup(Ssh ssh)
-{
- int i;
-
- /*
- * Most messages cause SSH2_MSG_UNIMPLEMENTED.
- */
- for (i = 0; i < 256; i++)
- ssh->packet_dispatch[i] = ssh2_msg_something_unimplemented;
-
- /*
- * Any message we actually understand, we set to NULL so that
- * the coroutines will get it.
- */
- ssh->packet_dispatch[SSH2_MSG_UNIMPLEMENTED] = NULL;
- ssh->packet_dispatch[SSH2_MSG_SERVICE_REQUEST] = NULL;
- ssh->packet_dispatch[SSH2_MSG_SERVICE_ACCEPT] = NULL;
- ssh->packet_dispatch[SSH2_MSG_KEXINIT] = NULL;
- ssh->packet_dispatch[SSH2_MSG_NEWKEYS] = NULL;
- ssh->packet_dispatch[SSH2_MSG_KEXDH_INIT] = NULL;
- ssh->packet_dispatch[SSH2_MSG_KEXDH_REPLY] = NULL;
- /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REQUEST] = NULL; duplicate case value */
- /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_GROUP] = NULL; duplicate case value */
- ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_INIT] = NULL;
- ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REPLY] = NULL;
- ssh->packet_dispatch[SSH2_MSG_USERAUTH_REQUEST] = NULL;
- ssh->packet_dispatch[SSH2_MSG_USERAUTH_FAILURE] = NULL;
- ssh->packet_dispatch[SSH2_MSG_USERAUTH_SUCCESS] = NULL;
- ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = NULL;
- ssh->packet_dispatch[SSH2_MSG_USERAUTH_PK_OK] = NULL;
- /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ] = NULL; duplicate case value */
- /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_REQUEST] = NULL; duplicate case value */
- ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_RESPONSE] = NULL;
- ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = NULL;
- ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = NULL;
- ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = NULL;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = NULL;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = NULL;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = NULL;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = NULL;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = NULL;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = NULL;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = NULL;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = NULL;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = NULL;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = NULL;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = NULL;
-
- /*
- * These special message types we install handlers for.
- */
- ssh->packet_dispatch[SSH2_MSG_DISCONNECT] = ssh2_msg_disconnect;
- ssh->packet_dispatch[SSH2_MSG_IGNORE] = ssh_msg_ignore; /* shared with SSH-1 */
- ssh->packet_dispatch[SSH2_MSG_DEBUG] = ssh2_msg_debug;
-}
-
-static void ssh2_timer(void *ctx, long now)
-{
- Ssh ssh = (Ssh)ctx;
-
- if (ssh->state == SSH_STATE_CLOSED)
- return;
-
- if (!ssh->kex_in_progress && conf_get_int(ssh->conf, CONF_ssh_rekey_time) != 0 &&
- now - ssh->next_rekey >= 0) {
- do_ssh2_transport(ssh, "timeout", -1, NULL);
- }
-}
-
-static void ssh2_protocol(Ssh ssh, void *vin, int inlen,
- struct Packet *pktin)
-{
- unsigned char *in = (unsigned char *)vin;
- if (ssh->state == SSH_STATE_CLOSED)
- return;
-
- if (pktin) {
- ssh->incoming_data_size += pktin->encrypted_len;
- if (!ssh->kex_in_progress &&
- ssh->max_data_size != 0 &&
- ssh->incoming_data_size > ssh->max_data_size)
- do_ssh2_transport(ssh, "too much data received", -1, NULL);
- }
-
- if (pktin && ssh->packet_dispatch[pktin->type]) {
- ssh->packet_dispatch[pktin->type](ssh, pktin);
- return;
- }
-
- if (!ssh->protocol_initial_phase_done ||
- (pktin && pktin->type >= 20 && pktin->type < 50)) {
- if (do_ssh2_transport(ssh, in, inlen, pktin) &&
- !ssh->protocol_initial_phase_done) {
- ssh->protocol_initial_phase_done = TRUE;
- /*
- * Allow authconn to initialise itself.
- */
- do_ssh2_authconn(ssh, NULL, 0, NULL);
- }
- } else {
- do_ssh2_authconn(ssh, in, inlen, pktin);
- }
-}
-
-static void ssh_cache_conf_values(Ssh ssh)
-{
- ssh->logomitdata = conf_get_int(ssh->conf, CONF_logomitdata);
-}
-
-/*
- * Called to set up the connection.
- *
- * 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,
- int nodelay, int keepalive)
-{
- const char *p;
- Ssh ssh;
-
- ssh = snew(struct ssh_tag);
- ssh->conf = conf_copy(conf);
- ssh_cache_conf_values(ssh);
- ssh->version = 0; /* when not ready yet */
- ssh->s = NULL;
- ssh->cipher = NULL;
- ssh->v1_cipher_ctx = NULL;
- ssh->crcda_ctx = NULL;
- ssh->cscipher = NULL;
- ssh->cs_cipher_ctx = NULL;
- ssh->sccipher = NULL;
- ssh->sc_cipher_ctx = NULL;
- ssh->csmac = NULL;
- ssh->cs_mac_ctx = NULL;
- ssh->scmac = NULL;
- ssh->sc_mac_ctx = NULL;
- ssh->cscomp = NULL;
- ssh->cs_comp_ctx = NULL;
- ssh->sccomp = NULL;
- ssh->sc_comp_ctx = NULL;
- ssh->kex = NULL;
- ssh->kex_ctx = NULL;
- ssh->hostkey = NULL;
- ssh->exitcode = -1;
- ssh->close_expected = FALSE;
- ssh->clean_exit = FALSE;
- ssh->state = SSH_STATE_PREPACKET;
- ssh->size_needed = FALSE;
- ssh->eof_needed = FALSE;
- ssh->ldisc = NULL;
- ssh->logctx = NULL;
- ssh->deferred_send_data = NULL;
- ssh->deferred_len = 0;
- ssh->deferred_size = 0;
- ssh->fallback_cmd = 0;
- ssh->pkt_kctx = SSH2_PKTCTX_NOKEX;
- ssh->pkt_actx = SSH2_PKTCTX_NOAUTH;
- ssh->x11disp = NULL;
- ssh->v1_compressing = FALSE;
- ssh->v2_outgoing_sequence = 0;
- ssh->ssh1_rdpkt_crstate = 0;
- ssh->ssh2_rdpkt_crstate = 0;
- ssh->do_ssh_init_crstate = 0;
- ssh->ssh_gotdata_crstate = 0;
- ssh->do_ssh1_connection_crstate = 0;
- ssh->do_ssh1_login_crstate = 0;
- ssh->do_ssh2_transport_crstate = 0;
- ssh->do_ssh2_authconn_crstate = 0;
- ssh->do_ssh_init_state = NULL;
- ssh->do_ssh1_login_state = NULL;
- ssh->do_ssh2_transport_state = NULL;
- ssh->do_ssh2_authconn_state = NULL;
- ssh->v_c = NULL;
- ssh->v_s = NULL;
- ssh->mainchan = NULL;
- ssh->throttled_all = 0;
- ssh->v1_stdout_throttling = 0;
- ssh->queue = NULL;
- ssh->queuelen = ssh->queuesize = 0;
- ssh->queueing = FALSE;
- ssh->qhead = ssh->qtail = NULL;
- ssh->deferred_rekey_reason = NULL;
- bufchain_init(&ssh->queued_incoming_data);
- ssh->frozen = FALSE;
- ssh->username = NULL;
- ssh->sent_console_eof = FALSE;
- ssh->got_pty = FALSE;
-
- *backend_handle = ssh;
-
-#ifdef MSCRYPTOAPI
- if (crypto_startup() == 0)
- return "Microsoft high encryption pack not installed!";
-#endif
-
- ssh->frontend = frontend_handle;
- ssh->term_width = conf_get_int(ssh->conf, CONF_width);
- ssh->term_height = conf_get_int(ssh->conf, CONF_height);
-
- ssh->channels = NULL;
- ssh->rportfwds = NULL;
- ssh->portfwds = NULL;
-
- ssh->send_ok = 0;
- ssh->editing = 0;
- ssh->echoing = 0;
- ssh->conn_throttle_count = 0;
- ssh->overall_bufsize = 0;
- ssh->fallback_cmd = 0;
-
- ssh->protocol = NULL;
-
- ssh->protocol_initial_phase_done = FALSE;
-
- ssh->pinger = NULL;
-
- ssh->incoming_data_size = ssh->outgoing_data_size =
- ssh->deferred_data_size = 0L;
- ssh->max_data_size = parse_blocksize(conf_get_str(ssh->conf,
- CONF_ssh_rekey_data));
- ssh->kex_in_progress = FALSE;
-
-#ifndef NO_GSSAPI
- ssh->gsslibs = NULL;
-#endif
-
- p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive);
- if (p != NULL)
- return p;
-
- random_ref();
-
- return NULL;
-}
-
-static void ssh_free(void *handle)
-{
- Ssh ssh = (Ssh) handle;
- struct ssh_channel *c;
- struct ssh_rportfwd *pf;
-
- if (ssh->v1_cipher_ctx)
- ssh->cipher->free_context(ssh->v1_cipher_ctx);
- if (ssh->cs_cipher_ctx)
- ssh->cscipher->free_context(ssh->cs_cipher_ctx);
- if (ssh->sc_cipher_ctx)
- ssh->sccipher->free_context(ssh->sc_cipher_ctx);
- if (ssh->cs_mac_ctx)
- ssh->csmac->free_context(ssh->cs_mac_ctx);
- if (ssh->sc_mac_ctx)
- ssh->scmac->free_context(ssh->sc_mac_ctx);
- if (ssh->cs_comp_ctx) {
- if (ssh->cscomp)
- ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx);
- else
- zlib_compress_cleanup(ssh->cs_comp_ctx);
- }
- if (ssh->sc_comp_ctx) {
- if (ssh->sccomp)
- ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx);
- else
- zlib_decompress_cleanup(ssh->sc_comp_ctx);
- }
- if (ssh->kex_ctx)
- dh_cleanup(ssh->kex_ctx);
- sfree(ssh->savedhost);
-
- while (ssh->queuelen-- > 0)
- ssh_free_packet(ssh->queue[ssh->queuelen]);
- sfree(ssh->queue);
-
- while (ssh->qhead) {
- struct queued_handler *qh = ssh->qhead;
- ssh->qhead = qh->next;
- sfree(ssh->qhead);
- }
- ssh->qhead = ssh->qtail = NULL;
-
- if (ssh->channels) {
- while ((c = delpos234(ssh->channels, 0)) != NULL) {
- switch (c->type) {
- case CHAN_X11:
- if (c->u.x11.s != NULL)
- x11_close(c->u.x11.s);
- break;
- case CHAN_SOCKDATA:
- case CHAN_SOCKDATA_DORMANT:
- if (c->u.pfd.s != NULL)
- pfd_close(c->u.pfd.s);
- break;
- }
- sfree(c);
- }
- freetree234(ssh->channels);
- ssh->channels = NULL;
- }
-
- if (ssh->rportfwds) {
- while ((pf = delpos234(ssh->rportfwds, 0)) != NULL)
- free_rportfwd(pf);
- freetree234(ssh->rportfwds);
- ssh->rportfwds = NULL;
- }
- sfree(ssh->deferred_send_data);
- if (ssh->x11disp)
- x11_free_display(ssh->x11disp);
- sfree(ssh->do_ssh_init_state);
- sfree(ssh->do_ssh1_login_state);
- sfree(ssh->do_ssh2_transport_state);
- sfree(ssh->do_ssh2_authconn_state);
- sfree(ssh->v_c);
- sfree(ssh->v_s);
- sfree(ssh->fullhostname);
- if (ssh->crcda_ctx) {
- crcda_free_context(ssh->crcda_ctx);
- ssh->crcda_ctx = NULL;
- }
- if (ssh->s)
- ssh_do_close(ssh, TRUE);
- expire_timer_context(ssh);
- if (ssh->pinger)
- pinger_free(ssh->pinger);
- bufchain_clear(&ssh->queued_incoming_data);
- sfree(ssh->username);
- conf_free(ssh->conf);
-#ifndef NO_GSSAPI
- if (ssh->gsslibs)
- ssh_gss_cleanup(ssh->gsslibs);
-#endif
- sfree(ssh);
-
- random_unref();
-}
-
-/*
- * Reconfigure the SSH backend.
- */
-static void ssh_reconfig(void *handle, Conf *conf)
-{
- Ssh ssh = (Ssh) handle;
- char *rekeying = NULL, rekey_mandatory = FALSE;
- unsigned long old_max_data_size;
- int i, rekey_time;
-
- pinger_reconfig(ssh->pinger, ssh->conf, conf);
- if (ssh->portfwds)
- ssh_setup_portfwd(ssh, conf);
-
- rekey_time = conf_get_int(conf, CONF_ssh_rekey_time);
- if (conf_get_int(ssh->conf, CONF_ssh_rekey_time) != rekey_time &&
- rekey_time != 0) {
- long new_next = ssh->last_rekey + rekey_time*60*TICKSPERSEC;
- long now = GETTICKCOUNT();
-
- if (new_next - now < 0) {
- rekeying = "timeout shortened";
- } else {
- ssh->next_rekey = schedule_timer(new_next - now, ssh2_timer, ssh);
- }
- }
-
- old_max_data_size = ssh->max_data_size;
- ssh->max_data_size = parse_blocksize(conf_get_str(ssh->conf,
- CONF_ssh_rekey_data));
- if (old_max_data_size != ssh->max_data_size &&
- ssh->max_data_size != 0) {
- if (ssh->outgoing_data_size > ssh->max_data_size ||
- ssh->incoming_data_size > ssh->max_data_size)
- rekeying = "data limit lowered";
- }
-
- if (conf_get_int(ssh->conf, CONF_compression) !=
- conf_get_int(conf, CONF_compression)) {
- rekeying = "compression setting changed";
- rekey_mandatory = TRUE;
- }
-
- for (i = 0; i < CIPHER_MAX; i++)
- if (conf_get_int_int(ssh->conf, CONF_ssh_cipherlist, i) !=
- conf_get_int_int(conf, CONF_ssh_cipherlist, i)) {
- rekeying = "cipher settings changed";
- rekey_mandatory = TRUE;
- }
- if (conf_get_int(ssh->conf, CONF_ssh2_des_cbc) !=
- conf_get_int(conf, CONF_ssh2_des_cbc)) {
- rekeying = "cipher settings changed";
- rekey_mandatory = TRUE;
- }
-
- conf_free(ssh->conf);
- ssh->conf = conf_copy(conf);
- ssh_cache_conf_values(ssh);
-
- if (rekeying) {
- if (!ssh->kex_in_progress) {
- do_ssh2_transport(ssh, rekeying, -1, NULL);
- } else if (rekey_mandatory) {
- ssh->deferred_rekey_reason = rekeying;
- }
- }
-}
-
-/*
- * Called to send data down the SSH connection.
- */
-static int ssh_send(void *handle, 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);
-
- return ssh_sendbuffer(ssh);
-}
-
-/*
- * Called to query the current amount of buffered stdin data.
- */
-static int ssh_sendbuffer(void *handle)
-{
- Ssh ssh = (Ssh) handle;
- int override_value;
-
- if (ssh == NULL || ssh->s == NULL || ssh->protocol == NULL)
- return 0;
-
- /*
- * If the SSH socket itself has backed up, add the total backup
- * size on that to any individual buffer on the stdin channel.
- */
- override_value = 0;
- if (ssh->throttled_all)
- override_value = ssh->overall_bufsize;
-
- if (ssh->version == 1) {
- return override_value;
- } else if (ssh->version == 2) {
- if (!ssh->mainchan)
- return override_value;
- else
- return (override_value +
- bufchain_size(&ssh->mainchan->v.v2.outbuffer));
- }
-
- return 0;
-}
-
-/*
- * Called to set the size of the window from SSH's POV.
- */
-static void ssh_size(void *handle, int width, int height)
-{
- Ssh ssh = (Ssh) handle;
- struct Packet *pktout;
-
- ssh->term_width = width;
- ssh->term_height = height;
-
- switch (ssh->state) {
- case SSH_STATE_BEFORE_SIZE:
- case SSH_STATE_PREPACKET:
- case SSH_STATE_CLOSED:
- break; /* do nothing */
- case SSH_STATE_INTERMED:
- ssh->size_needed = TRUE; /* buffer for later */
- break;
- case SSH_STATE_SESSION:
- if (!conf_get_int(ssh->conf, CONF_nopty)) {
- if (ssh->version == 1) {
- send_packet(ssh, SSH1_CMSG_WINDOW_SIZE,
- PKT_INT, ssh->term_height,
- PKT_INT, ssh->term_width,
- PKT_INT, 0, PKT_INT, 0, PKT_END);
- } else if (ssh->mainchan) {
- pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
- ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);
- ssh2_pkt_addstring(pktout, "window-change");
- ssh2_pkt_addbool(pktout, 0);
- ssh2_pkt_adduint32(pktout, ssh->term_width);
- ssh2_pkt_adduint32(pktout, ssh->term_height);
- ssh2_pkt_adduint32(pktout, 0);
- ssh2_pkt_adduint32(pktout, 0);
- ssh2_pkt_send(ssh, pktout);
- }
- }
- break;
- }
-}
-
-/*
- * Return a list of the special codes that make sense in this
- * protocol.
- */
-static const struct telnet_special *ssh_get_specials(void *handle)
-{
- static const struct telnet_special ssh1_ignore_special[] = {
- {"IGNORE message", TS_NOP}
- };
- static const struct telnet_special ssh2_ignore_special[] = {
- {"IGNORE message", TS_NOP},
- };
- static const struct telnet_special ssh2_rekey_special[] = {
- {"Repeat key exchange", TS_REKEY},
- };
- static const struct telnet_special ssh2_session_specials[] = {
- {NULL, TS_SEP},
- {"Break", TS_BRK},
- /* These are the signal names defined by RFC 4254.
- * They include all the ISO C signals, but are a subset of the POSIX
- * required signals. */
- {"SIGINT (Interrupt)", TS_SIGINT},
- {"SIGTERM (Terminate)", TS_SIGTERM},
- {"SIGKILL (Kill)", TS_SIGKILL},
- {"SIGQUIT (Quit)", TS_SIGQUIT},
- {"SIGHUP (Hangup)", TS_SIGHUP},
- {"More signals", TS_SUBMENU},
- {"SIGABRT", TS_SIGABRT}, {"SIGALRM", TS_SIGALRM},
- {"SIGFPE", TS_SIGFPE}, {"SIGILL", TS_SIGILL},
- {"SIGPIPE", TS_SIGPIPE}, {"SIGSEGV", TS_SIGSEGV},
- {"SIGUSR1", TS_SIGUSR1}, {"SIGUSR2", TS_SIGUSR2},
- {NULL, TS_EXITMENU}
- };
- static const struct telnet_special specials_end[] = {
- {NULL, TS_EXITMENU}
- };
- /* XXX review this length for any changes: */
- static struct telnet_special ssh_specials[lenof(ssh2_ignore_special) +
- lenof(ssh2_rekey_special) +
- lenof(ssh2_session_specials) +
- lenof(specials_end)];
- Ssh ssh = (Ssh) handle;
- int i = 0;
-#define ADD_SPECIALS(name) \
- do { \
- assert((i + lenof(name)) <= lenof(ssh_specials)); \
- memcpy(&ssh_specials[i], name, sizeof name); \
- i += lenof(name); \
- } while(0)
-
- if (ssh->version == 1) {
- /* Don't bother offering IGNORE if we've decided the remote
- * won't cope with it, since we wouldn't bother sending it if
- * asked anyway. */
- if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE))
- ADD_SPECIALS(ssh1_ignore_special);
- } else if (ssh->version == 2) {
- if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE))
- ADD_SPECIALS(ssh2_ignore_special);
- if (!(ssh->remote_bugs & BUG_SSH2_REKEY))
- ADD_SPECIALS(ssh2_rekey_special);
- if (ssh->mainchan)
- ADD_SPECIALS(ssh2_session_specials);
- } /* else we're not ready yet */
-
- if (i) {
- ADD_SPECIALS(specials_end);
- return ssh_specials;
- } else {
- return NULL;
- }
-#undef ADD_SPECIALS
-}
-
-/*
- * Send special codes. TS_EOF is useful for `plink', so you
- * can send an EOF and collect resulting output (e.g. `plink
- * hostname sort').
- */
-static void ssh_special(void *handle, Telnet_Special code)
-{
- Ssh ssh = (Ssh) handle;
- struct Packet *pktout;
-
- if (code == TS_EOF) {
- if (ssh->state != SSH_STATE_SESSION) {
- /*
- * Buffer the EOF in case we are pre-SESSION, so we can
- * send it as soon as we reach SESSION.
- */
- if (code == TS_EOF)
- ssh->eof_needed = TRUE;
- return;
- }
- if (ssh->version == 1) {
- send_packet(ssh, SSH1_CMSG_EOF, PKT_END);
- } else if (ssh->mainchan) {
- sshfwd_write_eof(ssh->mainchan);
- ssh->send_ok = 0; /* now stop trying to read from stdin */
- }
- logevent("Sent EOF message");
- } else if (code == TS_PING || code == TS_NOP) {
- if (ssh->state == SSH_STATE_CLOSED
- || ssh->state == SSH_STATE_PREPACKET) return;
- if (ssh->version == 1) {
- if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE))
- send_packet(ssh, SSH1_MSG_IGNORE, PKT_STR, "", PKT_END);
- } else {
- if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) {
- pktout = ssh2_pkt_init(SSH2_MSG_IGNORE);
- ssh2_pkt_addstring_start(pktout);
- ssh2_pkt_send_noqueue(ssh, pktout);
- }
- }
- } else if (code == TS_REKEY) {
- if (!ssh->kex_in_progress && ssh->version == 2) {
- do_ssh2_transport(ssh, "at user request", -1, NULL);
- }
- } else if (code == TS_BRK) {
- if (ssh->state == SSH_STATE_CLOSED
- || ssh->state == SSH_STATE_PREPACKET) return;
- if (ssh->version == 1) {
- logevent("Unable to send BREAK signal in SSH-1");
- } else if (ssh->mainchan) {
- pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
- ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);
- ssh2_pkt_addstring(pktout, "break");
- ssh2_pkt_addbool(pktout, 0);
- ssh2_pkt_adduint32(pktout, 0); /* default break length */
- ssh2_pkt_send(ssh, pktout);
- }
- } else {
- /* Is is a POSIX signal? */
- char *signame = NULL;
- if (code == TS_SIGABRT) signame = "ABRT";
- if (code == TS_SIGALRM) signame = "ALRM";
- if (code == TS_SIGFPE) signame = "FPE";
- if (code == TS_SIGHUP) signame = "HUP";
- if (code == TS_SIGILL) signame = "ILL";
- if (code == TS_SIGINT) signame = "INT";
- if (code == TS_SIGKILL) signame = "KILL";
- if (code == TS_SIGPIPE) signame = "PIPE";
- if (code == TS_SIGQUIT) signame = "QUIT";
- if (code == TS_SIGSEGV) signame = "SEGV";
- if (code == TS_SIGTERM) signame = "TERM";
- if (code == TS_SIGUSR1) signame = "USR1";
- if (code == TS_SIGUSR2) signame = "USR2";
- /* The SSH-2 protocol does in principle support arbitrary named
- * signals, including signame@domain, but we don't support those. */
- if (signame) {
- /* It's a signal. */
- if (ssh->version == 2 && ssh->mainchan) {
- pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
- ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);
- ssh2_pkt_addstring(pktout, "signal");
- ssh2_pkt_addbool(pktout, 0);
- ssh2_pkt_addstring(pktout, signame);
- ssh2_pkt_send(ssh, pktout);
- logeventf(ssh, "Sent signal SIG%s", signame);
- }
- } else {
- /* Never heard of it. Do nothing */
- }
- }
-}
-
-void *new_sock_channel(void *handle, Socket s)
-{
- Ssh ssh = (Ssh) handle;
- struct ssh_channel *c;
- c = snew(struct ssh_channel);
-
- c->ssh = ssh;
- ssh2_channel_init(c);
- c->halfopen = TRUE;
- c->type = CHAN_SOCKDATA_DORMANT;/* identify channel type */
- c->u.pfd.s = s;
- add234(ssh->channels, c);
- return c;
-}
-
-/*
- * This is called when stdout/stderr (the entity to which
- * from_backend sends data) manages to clear some backlog.
- */
-static void ssh_unthrottle(void *handle, int bufsize)
-{
- Ssh ssh = (Ssh) handle;
- int buflimit;
-
- if (ssh->version == 1) {
- if (ssh->v1_stdout_throttling && bufsize < SSH1_BUFFER_LIMIT) {
- ssh->v1_stdout_throttling = 0;
- ssh_throttle_conn(ssh, -1);
- }
- } else {
- if (ssh->mainchan) {
- ssh2_set_window(ssh->mainchan,
- bufsize < ssh->mainchan->v.v2.locmaxwin ?
- ssh->mainchan->v.v2.locmaxwin - bufsize : 0);
- if (conf_get_int(ssh->conf, CONF_ssh_simple))
- buflimit = 0;
- else
- buflimit = ssh->mainchan->v.v2.locmaxwin;
- if (ssh->mainchan->throttling_conn && bufsize <= buflimit) {
- ssh->mainchan->throttling_conn = 0;
- ssh_throttle_conn(ssh, -1);
- }
- }
- }
-}
-
-void ssh_send_port_open(void *channel, char *hostname, int port, char *org)
-{
- struct ssh_channel *c = (struct ssh_channel *)channel;
- Ssh ssh = c->ssh;
- struct Packet *pktout;
-
- logeventf(ssh, "Opening forwarded connection to %s:%d", hostname, port);
-
- if (ssh->version == 1) {
- send_packet(ssh, SSH1_MSG_PORT_OPEN,
- PKT_INT, c->localid,
- PKT_STR, hostname,
- PKT_INT, port,
- /* PKT_STR, <org:orgport>, */
- PKT_END);
- } else {
- pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
- ssh2_pkt_addstring(pktout, "direct-tcpip");
- ssh2_pkt_adduint32(pktout, c->localid);
- ssh2_pkt_adduint32(pktout, c->v.v2.locwindow);/* our window size */
- ssh2_pkt_adduint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */
- ssh2_pkt_addstring(pktout, hostname);
- ssh2_pkt_adduint32(pktout, port);
- /*
- * We make up values for the originator data; partly it's
- * too much hassle to keep track, and partly I'm not
- * convinced the server should be told details like that
- * about my local network configuration.
- * The "originator IP address" is syntactically a numeric
- * IP address, and some servers (e.g., Tectia) get upset
- * if it doesn't match this syntax.
- */
- ssh2_pkt_addstring(pktout, "0.0.0.0");
- ssh2_pkt_adduint32(pktout, 0);
- ssh2_pkt_send(ssh, pktout);
- }
-}
-
-static int ssh_connected(void *handle)
-{
- Ssh ssh = (Ssh) handle;
- return ssh->s != NULL;
-}
-
-static int ssh_sendok(void *handle)
-{
- Ssh ssh = (Ssh) handle;
- return ssh->send_ok;
-}
-
-static int ssh_ldisc(void *handle, int option)
-{
- Ssh ssh = (Ssh) handle;
- if (option == LD_ECHO)
- return ssh->echoing;
- if (option == LD_EDIT)
- return ssh->editing;
- return FALSE;
-}
-
-static void ssh_provide_ldisc(void *handle, void *ldisc)
-{
- Ssh ssh = (Ssh) handle;
- ssh->ldisc = ldisc;
-}
-
-static void ssh_provide_logctx(void *handle, void *logctx)
-{
- Ssh ssh = (Ssh) handle;
- ssh->logctx = logctx;
-}
-
-static int ssh_return_exitcode(void *handle)
-{
- Ssh ssh = (Ssh) handle;
- if (ssh->s != NULL)
- return -1;
- else
- return (ssh->exitcode >= 0 ? ssh->exitcode : INT_MAX);
-}
-
-/*
- * cfg_info for SSH is the currently running version of the
- * protocol. (1 for 1; 2 for 2; 0 for not-decided-yet.)
- */
-static int ssh_cfg_info(void *handle)
-{
- Ssh ssh = (Ssh) handle;
- return ssh->version;
-}
-
-/*
- * Gross hack: pscp will try to start SFTP but fall back to scp1 if
- * that fails. This variable is the means by which scp.c can reach
- * into the SSH code and find out which one it got.
- */
-extern int ssh_fallback_cmd(void *handle)
-{
- Ssh ssh = (Ssh) handle;
- return ssh->fallback_cmd;
-}
-
-Backend ssh_backend = {
- ssh_init,
- ssh_free,
- ssh_reconfig,
- ssh_send,
- ssh_sendbuffer,
- ssh_size,
- ssh_special,
- ssh_get_specials,
- ssh_connected,
- ssh_return_exitcode,
- ssh_sendok,
- ssh_ldisc,
- ssh_provide_ldisc,
- ssh_provide_logctx,
- ssh_unthrottle,
- ssh_cfg_info,
- "ssh",
- PROT_SSH,
- 22
-};
+/*
+ * SSH backend.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <limits.h>
+#include <signal.h>
+
+#include "putty.h"
+#include "tree234.h"
+#include "ssh.h"
+#ifndef NO_GSSAPI
+#include "sshgssc.h"
+#include "sshgss.h"
+#endif
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+/*
+ * Packet type contexts, so that ssh2_pkt_type can correctly decode
+ * the ambiguous type numbers back into the correct type strings.
+ */
+typedef enum {
+ SSH2_PKTCTX_NOKEX,
+ SSH2_PKTCTX_DHGROUP,
+ SSH2_PKTCTX_DHGEX,
+ SSH2_PKTCTX_RSAKEX
+} Pkt_KCtx;
+typedef enum {
+ SSH2_PKTCTX_NOAUTH,
+ SSH2_PKTCTX_PUBLICKEY,
+ SSH2_PKTCTX_PASSWORD,
+ SSH2_PKTCTX_GSSAPI,
+ SSH2_PKTCTX_KBDINTER
+} Pkt_ACtx;
+
+static const char *const ssh2_disconnect_reasons[] = {
+ NULL,
+ "host not allowed to connect",
+ "protocol error",
+ "key exchange failed",
+ "host authentication failed",
+ "MAC error",
+ "compression error",
+ "service not available",
+ "protocol version not supported",
+ "host key not verifiable",
+ "connection lost",
+ "by application",
+ "too many connections",
+ "auth cancelled by user",
+ "no more auth methods available",
+ "illegal user name",
+};
+
+/*
+ * Various remote-bug flags.
+ */
+#define BUG_CHOKES_ON_SSH1_IGNORE 1
+#define BUG_SSH2_HMAC 2
+#define BUG_NEEDS_SSH1_PLAIN_PASSWORD 4
+#define BUG_CHOKES_ON_RSA 8
+#define BUG_SSH2_RSA_PADDING 16
+#define BUG_SSH2_DERIVEKEY 32
+#define BUG_SSH2_REKEY 64
+#define BUG_SSH2_PK_SESSIONID 128
+#define BUG_SSH2_MAXPKT 256
+#define BUG_CHOKES_ON_SSH2_IGNORE 512
+#define BUG_CHOKES_ON_WINADJ 1024
+
+/*
+ * Codes for terminal modes.
+ * Most of these are the same in SSH-1 and SSH-2.
+ * This list is derived from RFC 4254 and
+ * SSH-1 RFC-1.2.31.
+ */
+static const struct {
+ const char* const mode;
+ int opcode;
+ enum { TTY_OP_CHAR, TTY_OP_BOOL } type;
+} ssh_ttymodes[] = {
+ /* "V" prefix discarded for special characters relative to SSH specs */
+ { "INTR", 1, TTY_OP_CHAR },
+ { "QUIT", 2, TTY_OP_CHAR },
+ { "ERASE", 3, TTY_OP_CHAR },
+ { "KILL", 4, TTY_OP_CHAR },
+ { "EOF", 5, TTY_OP_CHAR },
+ { "EOL", 6, TTY_OP_CHAR },
+ { "EOL2", 7, TTY_OP_CHAR },
+ { "START", 8, TTY_OP_CHAR },
+ { "STOP", 9, TTY_OP_CHAR },
+ { "SUSP", 10, TTY_OP_CHAR },
+ { "DSUSP", 11, TTY_OP_CHAR },
+ { "REPRINT", 12, TTY_OP_CHAR },
+ { "WERASE", 13, TTY_OP_CHAR },
+ { "LNEXT", 14, TTY_OP_CHAR },
+ { "FLUSH", 15, TTY_OP_CHAR },
+ { "SWTCH", 16, TTY_OP_CHAR },
+ { "STATUS", 17, TTY_OP_CHAR },
+ { "DISCARD", 18, TTY_OP_CHAR },
+ { "IGNPAR", 30, TTY_OP_BOOL },
+ { "PARMRK", 31, TTY_OP_BOOL },
+ { "INPCK", 32, TTY_OP_BOOL },
+ { "ISTRIP", 33, TTY_OP_BOOL },
+ { "INLCR", 34, TTY_OP_BOOL },
+ { "IGNCR", 35, TTY_OP_BOOL },
+ { "ICRNL", 36, TTY_OP_BOOL },
+ { "IUCLC", 37, TTY_OP_BOOL },
+ { "IXON", 38, TTY_OP_BOOL },
+ { "IXANY", 39, TTY_OP_BOOL },
+ { "IXOFF", 40, TTY_OP_BOOL },
+ { "IMAXBEL", 41, TTY_OP_BOOL },
+ { "ISIG", 50, TTY_OP_BOOL },
+ { "ICANON", 51, TTY_OP_BOOL },
+ { "XCASE", 52, TTY_OP_BOOL },
+ { "ECHO", 53, TTY_OP_BOOL },
+ { "ECHOE", 54, TTY_OP_BOOL },
+ { "ECHOK", 55, TTY_OP_BOOL },
+ { "ECHONL", 56, TTY_OP_BOOL },
+ { "NOFLSH", 57, TTY_OP_BOOL },
+ { "TOSTOP", 58, TTY_OP_BOOL },
+ { "IEXTEN", 59, TTY_OP_BOOL },
+ { "ECHOCTL", 60, TTY_OP_BOOL },
+ { "ECHOKE", 61, TTY_OP_BOOL },
+ { "PENDIN", 62, TTY_OP_BOOL }, /* XXX is this a real mode? */
+ { "OPOST", 70, TTY_OP_BOOL },
+ { "OLCUC", 71, TTY_OP_BOOL },
+ { "ONLCR", 72, TTY_OP_BOOL },
+ { "OCRNL", 73, TTY_OP_BOOL },
+ { "ONOCR", 74, TTY_OP_BOOL },
+ { "ONLRET", 75, TTY_OP_BOOL },
+ { "CS7", 90, TTY_OP_BOOL },
+ { "CS8", 91, TTY_OP_BOOL },
+ { "PARENB", 92, TTY_OP_BOOL },
+ { "PARODD", 93, TTY_OP_BOOL }
+};
+
+/* Miscellaneous other tty-related constants. */
+#define SSH_TTY_OP_END 0
+/* The opcodes for ISPEED/OSPEED differ between SSH-1 and SSH-2. */
+#define SSH1_TTY_OP_ISPEED 192
+#define SSH1_TTY_OP_OSPEED 193
+#define SSH2_TTY_OP_ISPEED 128
+#define SSH2_TTY_OP_OSPEED 129
+
+/* Helper functions for parsing tty-related config. */
+static unsigned int ssh_tty_parse_specchar(char *s)
+{
+ unsigned int ret;
+ if (*s) {
+ char *next = NULL;
+ ret = ctrlparse(s, &next);
+ if (!next) ret = s[0];
+ } else {
+ ret = 255; /* special value meaning "don't set" */
+ }
+ return ret;
+}
+static unsigned int ssh_tty_parse_boolean(char *s)
+{
+ if (stricmp(s, "yes") == 0 ||
+ stricmp(s, "on") == 0 ||
+ stricmp(s, "true") == 0 ||
+ stricmp(s, "+") == 0)
+ return 1; /* true */
+ else if (stricmp(s, "no") == 0 ||
+ stricmp(s, "off") == 0 ||
+ stricmp(s, "false") == 0 ||
+ stricmp(s, "-") == 0)
+ return 0; /* false */
+ else
+ return (atoi(s) != 0);
+}
+
+#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)
+{
+ translate(SSH1_MSG_DISCONNECT);
+ translate(SSH1_SMSG_PUBLIC_KEY);
+ translate(SSH1_CMSG_SESSION_KEY);
+ translate(SSH1_CMSG_USER);
+ translate(SSH1_CMSG_AUTH_RSA);
+ translate(SSH1_SMSG_AUTH_RSA_CHALLENGE);
+ translate(SSH1_CMSG_AUTH_RSA_RESPONSE);
+ translate(SSH1_CMSG_AUTH_PASSWORD);
+ translate(SSH1_CMSG_REQUEST_PTY);
+ translate(SSH1_CMSG_WINDOW_SIZE);
+ translate(SSH1_CMSG_EXEC_SHELL);
+ translate(SSH1_CMSG_EXEC_CMD);
+ translate(SSH1_SMSG_SUCCESS);
+ translate(SSH1_SMSG_FAILURE);
+ translate(SSH1_CMSG_STDIN_DATA);
+ translate(SSH1_SMSG_STDOUT_DATA);
+ translate(SSH1_SMSG_STDERR_DATA);
+ translate(SSH1_CMSG_EOF);
+ translate(SSH1_SMSG_EXIT_STATUS);
+ translate(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION);
+ translate(SSH1_MSG_CHANNEL_OPEN_FAILURE);
+ translate(SSH1_MSG_CHANNEL_DATA);
+ translate(SSH1_MSG_CHANNEL_CLOSE);
+ translate(SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION);
+ translate(SSH1_SMSG_X11_OPEN);
+ translate(SSH1_CMSG_PORT_FORWARD_REQUEST);
+ translate(SSH1_MSG_PORT_OPEN);
+ translate(SSH1_CMSG_AGENT_REQUEST_FORWARDING);
+ translate(SSH1_SMSG_AGENT_OPEN);
+ translate(SSH1_MSG_IGNORE);
+ translate(SSH1_CMSG_EXIT_CONFIRMATION);
+ translate(SSH1_CMSG_X11_REQUEST_FORWARDING);
+ translate(SSH1_CMSG_AUTH_RHOSTS_RSA);
+ translate(SSH1_MSG_DEBUG);
+ translate(SSH1_CMSG_REQUEST_COMPRESSION);
+ translate(SSH1_CMSG_AUTH_TIS);
+ translate(SSH1_SMSG_AUTH_TIS_CHALLENGE);
+ translate(SSH1_CMSG_AUTH_TIS_RESPONSE);
+ translate(SSH1_CMSG_AUTH_CCARD);
+ translate(SSH1_SMSG_AUTH_CCARD_CHALLENGE);
+ translate(SSH1_CMSG_AUTH_CCARD_RESPONSE);
+ return "unknown";
+}
+static 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);
+ translatea(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE,SSH2_PKTCTX_GSSAPI);
+ translatea(SSH2_MSG_USERAUTH_GSSAPI_ERROR,SSH2_PKTCTX_GSSAPI);
+ translatea(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK,SSH2_PKTCTX_GSSAPI);
+ translatea(SSH2_MSG_USERAUTH_GSSAPI_MIC, SSH2_PKTCTX_GSSAPI);
+ translate(SSH2_MSG_DISCONNECT);
+ translate(SSH2_MSG_IGNORE);
+ translate(SSH2_MSG_UNIMPLEMENTED);
+ translate(SSH2_MSG_DEBUG);
+ translate(SSH2_MSG_SERVICE_REQUEST);
+ translate(SSH2_MSG_SERVICE_ACCEPT);
+ translate(SSH2_MSG_KEXINIT);
+ translate(SSH2_MSG_NEWKEYS);
+ translatek(SSH2_MSG_KEXDH_INIT, SSH2_PKTCTX_DHGROUP);
+ translatek(SSH2_MSG_KEXDH_REPLY, SSH2_PKTCTX_DHGROUP);
+ translatek(SSH2_MSG_KEX_DH_GEX_REQUEST, SSH2_PKTCTX_DHGEX);
+ translatek(SSH2_MSG_KEX_DH_GEX_GROUP, SSH2_PKTCTX_DHGEX);
+ translatek(SSH2_MSG_KEX_DH_GEX_INIT, SSH2_PKTCTX_DHGEX);
+ translatek(SSH2_MSG_KEX_DH_GEX_REPLY, SSH2_PKTCTX_DHGEX);
+ translatek(SSH2_MSG_KEXRSA_PUBKEY, SSH2_PKTCTX_RSAKEX);
+ translatek(SSH2_MSG_KEXRSA_SECRET, SSH2_PKTCTX_RSAKEX);
+ translatek(SSH2_MSG_KEXRSA_DONE, SSH2_PKTCTX_RSAKEX);
+ translate(SSH2_MSG_USERAUTH_REQUEST);
+ translate(SSH2_MSG_USERAUTH_FAILURE);
+ translate(SSH2_MSG_USERAUTH_SUCCESS);
+ translate(SSH2_MSG_USERAUTH_BANNER);
+ translatea(SSH2_MSG_USERAUTH_PK_OK, SSH2_PKTCTX_PUBLICKEY);
+ translatea(SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, SSH2_PKTCTX_PASSWORD);
+ translatea(SSH2_MSG_USERAUTH_INFO_REQUEST, SSH2_PKTCTX_KBDINTER);
+ translatea(SSH2_MSG_USERAUTH_INFO_RESPONSE, SSH2_PKTCTX_KBDINTER);
+ translate(SSH2_MSG_GLOBAL_REQUEST);
+ translate(SSH2_MSG_REQUEST_SUCCESS);
+ translate(SSH2_MSG_REQUEST_FAILURE);
+ translate(SSH2_MSG_CHANNEL_OPEN);
+ translate(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
+ translate(SSH2_MSG_CHANNEL_OPEN_FAILURE);
+ translate(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+ translate(SSH2_MSG_CHANNEL_DATA);
+ translate(SSH2_MSG_CHANNEL_EXTENDED_DATA);
+ translate(SSH2_MSG_CHANNEL_EOF);
+ translate(SSH2_MSG_CHANNEL_CLOSE);
+ translate(SSH2_MSG_CHANNEL_REQUEST);
+ translate(SSH2_MSG_CHANNEL_SUCCESS);
+ translate(SSH2_MSG_CHANNEL_FAILURE);
+ return "unknown";
+}
+#undef translate
+#undef translatec
+
+/* Enumeration values for fields in SSH-1 packets */
+enum {
+ PKT_END, PKT_INT, PKT_CHAR, PKT_DATA, PKT_STR, PKT_BIGNUM,
+};
+
+/*
+ * Coroutine mechanics for the sillier bits of the code. If these
+ * macros look impenetrable to you, you might find it helpful to
+ * read
+ *
+ * http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html
+ *
+ * which explains the theory behind these macros.
+ *
+ * In particular, if you are getting `case expression not constant'
+ * errors when building with MS Visual Studio, this is because MS's
+ * Edit and Continue debugging feature causes their compiler to
+ * violate ANSI C. To disable Edit and Continue debugging:
+ *
+ * - right-click ssh.c in the FileView
+ * - click Settings
+ * - select the C/C++ tab and the General category
+ * - under `Debug info:', select anything _other_ than `Program
+ * Database for Edit and Continue'.
+ */
+#define crBegin(v) { int *crLine = &v; switch(v) { case 0:;
+#define crBeginState crBegin(s->crLine)
+#define crStateP(t, v) \
+ struct t *s; \
+ if (!(v)) { s = (v) = snew(struct t); s->crLine = 0; } \
+ s = (v);
+#define crState(t) crStateP(t, ssh->t)
+#define crFinish(z) } *crLine = 0; return (z); }
+#define crFinishV } *crLine = 0; return; }
+#define crFinishFree(z) } sfree(s); return (z); }
+#define crFinishFreeV } sfree(s); return; }
+#define crReturn(z) \
+ do {\
+ *crLine =__LINE__; return (z); case __LINE__:;\
+ } while (0)
+#define crReturnV \
+ do {\
+ *crLine=__LINE__; return; case __LINE__:;\
+ } while (0)
+#define crStop(z) do{ *crLine = 0; return (z); }while(0)
+#define crStopV do{ *crLine = 0; return; }while(0)
+#define crWaitUntil(c) do { crReturn(0); } while (!(c))
+#define crWaitUntilV(c) do { crReturnV; } while (!(c))
+
+struct Packet;
+
+static struct Packet *ssh1_pkt_init(int pkt_type);
+static struct Packet *ssh2_pkt_init(int pkt_type);
+static void ssh_pkt_ensure(struct Packet *, int length);
+static void ssh_pkt_adddata(struct Packet *, const void *data, int len);
+static void ssh_pkt_addbyte(struct Packet *, unsigned char value);
+static void ssh2_pkt_addbool(struct Packet *, unsigned char value);
+static void ssh_pkt_adduint32(struct Packet *, unsigned long value);
+static void ssh_pkt_addstring_start(struct Packet *);
+static void ssh_pkt_addstring_str(struct Packet *, const char *data);
+static void ssh_pkt_addstring_data(struct Packet *, const char *data, int len);
+static void ssh_pkt_addstring(struct Packet *, const char *data);
+static unsigned char *ssh2_mpint_fmt(Bignum b, int *len);
+static void ssh1_pkt_addmp(struct Packet *, Bignum b);
+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,
+ struct Packet *pktin);
+static void do_ssh2_authconn(Ssh ssh, 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);
+
+/*
+ * Buffer management constants. There are several of these for
+ * various different purposes:
+ *
+ * - SSH1_BUFFER_LIMIT is the amount of backlog that must build up
+ * on a local data stream before we throttle the whole SSH
+ * connection (in SSH-1 only). Throttling the whole connection is
+ * pretty drastic so we set this high in the hope it won't
+ * happen very often.
+ *
+ * - SSH_MAX_BACKLOG is the amount of backlog that must build up
+ * on the SSH connection itself before we defensively throttle
+ * _all_ local data streams. This is pretty drastic too (though
+ * thankfully unlikely in SSH-2 since the window mechanism should
+ * ensure that the server never has any need to throttle its end
+ * of the connection), so we set this high as well.
+ *
+ * - OUR_V2_WINSIZE is the maximum window size we present on SSH-2
+ * channels.
+ *
+ * - OUR_V2_BIGWIN is the window size we advertise for the only
+ * channel in a simple connection. It must be <= INT_MAX.
+ *
+ * - OUR_V2_MAXPKT is the official "maximum packet size" we send
+ * to the remote side. This actually has nothing to do with the
+ * size of the _packet_, but is instead a limit on the amount
+ * of data we're willing to receive in a single SSH2 channel
+ * data message.
+ *
+ * - OUR_V2_PACKETLIMIT is actually the maximum size of SSH
+ * _packet_ we're prepared to cope with. It must be a multiple
+ * of the cipher block size, and must be at least 35000.
+ */
+
+#define SSH1_BUFFER_LIMIT 32768
+#define SSH_MAX_BACKLOG 32768
+#define OUR_V2_WINSIZE 16384
+#define OUR_V2_BIGWIN 0x7fffffff
+#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_mac *macs[] = {
+ &ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5
+};
+const static struct ssh_mac *buggymacs[] = {
+ &ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5
+};
+
+static void *ssh_comp_none_init(void)
+{
+ return NULL;
+}
+static void ssh_comp_none_cleanup(void *handle)
+{
+}
+static int ssh_comp_none_block(void *handle, unsigned char *block, int len,
+ unsigned char **outblock, int *outlen)
+{
+ return 0;
+}
+static int ssh_comp_none_disable(void *handle)
+{
+ return 0;
+}
+const static struct ssh_compress ssh_comp_none = {
+ "none", NULL,
+ ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block,
+ ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block,
+ ssh_comp_none_disable, NULL
+};
+extern const struct ssh_compress ssh_zlib;
+const static struct ssh_compress *compressions[] = {
+ &ssh_zlib, &ssh_comp_none
+};
+
+enum { /* channel types */
+ CHAN_MAINSESSION,
+ CHAN_X11,
+ CHAN_AGENT,
+ CHAN_SOCKDATA,
+ CHAN_SOCKDATA_DORMANT, /* one the remote hasn't confirmed */
+ /*
+ * CHAN_SHARING indicates a channel which is tracked here on
+ * behalf of a connection-sharing downstream. We do almost nothing
+ * with these channels ourselves: all messages relating to them
+ * get thrown straight to sshshare.c and passed on almost
+ * unmodified to downstream.
+ */
+ CHAN_SHARING,
+ /*
+ * CHAN_ZOMBIE is used to indicate a channel for which we've
+ * already destroyed the local data source: for instance, if a
+ * forwarded port experiences a socket error on the local side, we
+ * immediately destroy its local socket and turn the SSH channel
+ * into CHAN_ZOMBIE.
+ */
+ CHAN_ZOMBIE
+};
+
+typedef void (*handler_fn_t)(Ssh ssh, struct Packet *pktin);
+typedef void (*chandler_fn_t)(Ssh ssh, struct Packet *pktin, void *ctx);
+typedef void (*cchandler_fn_t)(struct ssh_channel *, struct Packet *, void *);
+
+/*
+ * Each channel has a queue of outstanding CHANNEL_REQUESTS and their
+ * handlers.
+ */
+struct outstanding_channel_request {
+ cchandler_fn_t handler;
+ void *ctx;
+ struct outstanding_channel_request *next;
+};
+
+/*
+ * 2-3-4 tree storing channels.
+ */
+struct ssh_channel {
+ Ssh ssh; /* pointer back to main context */
+ unsigned remoteid, localid;
+ int type;
+ /* True if we opened this channel but server hasn't confirmed. */
+ int halfopen;
+ /*
+ * In SSH-1, this value contains four bits:
+ *
+ * 1 We have sent SSH1_MSG_CHANNEL_CLOSE.
+ * 2 We have sent SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION.
+ * 4 We have received SSH1_MSG_CHANNEL_CLOSE.
+ * 8 We have received SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION.
+ *
+ * A channel is completely finished with when all four bits are set.
+ *
+ * In SSH-2, the four bits mean:
+ *
+ * 1 We have sent SSH2_MSG_CHANNEL_EOF.
+ * 2 We have sent SSH2_MSG_CHANNEL_CLOSE.
+ * 4 We have received SSH2_MSG_CHANNEL_EOF.
+ * 8 We have received SSH2_MSG_CHANNEL_CLOSE.
+ *
+ * A channel is completely finished with when we have both sent
+ * and received CLOSE.
+ *
+ * The symbolic constants below use the SSH-2 terminology, which
+ * is a bit confusing in SSH-1, but we have to use _something_.
+ */
+#define CLOSES_SENT_EOF 1
+#define CLOSES_SENT_CLOSE 2
+#define CLOSES_RCVD_EOF 4
+#define CLOSES_RCVD_CLOSE 8
+ int closes;
+
+ /*
+ * This flag indicates that an EOF is pending on the outgoing side
+ * of the channel: that is, wherever we're getting the data for
+ * this channel has sent us some data followed by EOF. We can't
+ * actually send the EOF until we've finished sending the data, so
+ * we set this flag instead to remind us to do so once our buffer
+ * is clear.
+ */
+ int pending_eof;
+
+ /*
+ * True if this channel is causing the underlying connection to be
+ * throttled.
+ */
+ int throttling_conn;
+ union {
+ struct ssh2_data_channel {
+ bufchain outbuffer;
+ unsigned remwindow, remmaxpkt;
+ /* locwindow is signed so we can cope with excess data. */
+ int locwindow, locmaxwin;
+ /*
+ * remlocwin is the amount of local window that we think
+ * the remote end had available to it after it sent the
+ * last data packet or window adjust ack.
+ */
+ int remlocwin;
+ /*
+ * These store the list of channel requests that haven't
+ * been acked.
+ */
+ struct outstanding_channel_request *chanreq_head, *chanreq_tail;
+ enum { THROTTLED, UNTHROTTLING, UNTHROTTLED } throttle_state;
+ } v2;
+ } v;
+ union {
+ struct ssh_agent_channel {
+ unsigned char *message;
+ unsigned char msglen[4];
+ unsigned lensofar, totallen;
+ int outstanding_requests;
+ } a;
+ struct ssh_x11_channel {
+ struct X11Connection *xconn;
+ int initial;
+ } x11;
+ struct ssh_pfd_channel {
+ struct PortForwarding *pf;
+ } pfd;
+ struct ssh_sharing_channel {
+ void *ctx;
+ } sharing;
+ } u;
+};
+
+/*
+ * 2-3-4 tree storing remote->local port forwardings. SSH-1 and SSH-2
+ * use this structure in different ways, reflecting SSH-2's
+ * altogether saner approach to port forwarding.
+ *
+ * In SSH-1, you arrange a remote forwarding by sending the server
+ * the remote port number, and the local destination host:port.
+ * When a connection comes in, the server sends you back that
+ * host:port pair, and you connect to it. This is a ready-made
+ * security hole if you're not on the ball: a malicious server
+ * could send you back _any_ host:port pair, so if you trustingly
+ * connect to the address it gives you then you've just opened the
+ * entire inside of your corporate network just by connecting
+ * through it to a dodgy SSH server. Hence, we must store a list of
+ * host:port pairs we _are_ trying to forward to, and reject a
+ * connection request from the server if it's not in the list.
+ *
+ * In SSH-2, each side of the connection minds its own business and
+ * doesn't send unnecessary information to the other. You arrange a
+ * remote forwarding by sending the server just the remote port
+ * number. When a connection comes in, the server tells you which
+ * of its ports was connected to; and _you_ have to remember what
+ * local host:port pair went with that port number.
+ *
+ * Hence, in SSH-1 this structure is indexed by destination
+ * host:port pair, whereas in SSH-2 it is indexed by source port.
+ */
+struct ssh_portfwd; /* forward declaration */
+
+struct ssh_rportfwd {
+ unsigned sport, dport;
+ char *shost, *dhost;
+ char *sportdesc;
+ void *share_ctx;
+ struct ssh_portfwd *pfrec;
+};
+
+static void free_rportfwd(struct ssh_rportfwd *pf)
+{
+ if (pf) {
+ sfree(pf->sportdesc);
+ sfree(pf->shost);
+ sfree(pf->dhost);
+ sfree(pf);
+ }
+}
+
+/*
+ * Separately to the rportfwd tree (which is for looking up port
+ * open requests from the server), a tree of _these_ structures is
+ * used to keep track of all the currently open port forwardings,
+ * so that we can reconfigure in mid-session if the user requests
+ * it.
+ */
+struct ssh_portfwd {
+ enum { DESTROY, KEEP, CREATE } status;
+ int type;
+ unsigned sport, dport;
+ char *saddr, *daddr;
+ char *sserv, *dserv;
+ struct ssh_rportfwd *remote;
+ int addressfamily;
+ struct PortListener *local;
+};
+#define free_portfwd(pf) ( \
+ ((pf) ? (sfree((pf)->saddr), sfree((pf)->daddr), \
+ sfree((pf)->sserv), sfree((pf)->dserv)) : (void)0 ), sfree(pf) )
+
+struct Packet {
+ long length; /* length of packet: see below */
+ long forcepad; /* SSH-2: force padding to at least this length */
+ int type; /* only used for incoming packets */
+ unsigned long sequence; /* SSH-2 incoming sequence number */
+ unsigned char *data; /* allocated storage */
+ unsigned char *body; /* offset of payload within `data' */
+ long savedpos; /* dual-purpose saved packet position: see below */
+ long maxlen; /* amount of storage allocated for `data' */
+ long encrypted_len; /* for SSH-2 total-size counting */
+
+ /*
+ * A note on the 'length' and 'savedpos' fields above.
+ *
+ * Incoming packets are set up so that pkt->length is measured
+ * relative to pkt->body, which itself points to a few bytes after
+ * pkt->data (skipping some uninteresting header fields including
+ * the packet type code). The ssh_pkt_get* functions all expect
+ * this setup, and they also use pkt->savedpos to indicate how far
+ * through the packet being decoded they've got - and that, too,
+ * is an offset from pkt->body rather than pkt->data.
+ *
+ * During construction of an outgoing packet, however, pkt->length
+ * is measured relative to the base pointer pkt->data, and
+ * pkt->body is not really used for anything until the packet is
+ * ready for sending. In this mode, pkt->savedpos is reused as a
+ * temporary variable by the addstring functions, which write out
+ * a string length field and then keep going back and updating it
+ * as more data is appended to the subsequent string data field;
+ * pkt->savedpos stores the offset (again relative to pkt->data)
+ * of the start of the string data field.
+ */
+
+ /* Extra metadata used in SSH packet logging mode, allowing us to
+ * log in the packet header line that the packet came from a
+ * connection-sharing downstream and what if anything unusual was
+ * done to it. The additional_log_text field is expected to be a
+ * static string - it will not be freed. */
+ unsigned downstream_id;
+ const char *additional_log_text;
+};
+
+static void ssh1_protocol(Ssh ssh, void *vin, int inlen,
+ struct Packet *pktin);
+static void ssh2_protocol(Ssh ssh, void *vin, int inlen,
+ struct Packet *pktin);
+static void ssh2_bare_connection_protocol(Ssh ssh, void *vin, int inlen,
+ struct Packet *pktin);
+static void ssh1_protocol_setup(Ssh ssh);
+static void ssh2_protocol_setup(Ssh ssh);
+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 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);
+static int ssh_do_close(Ssh ssh, int notify_exit);
+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,
+ struct Packet *pktin);
+static void ssh2_msg_unexpected(Ssh ssh, struct Packet *pktin);
+
+struct rdpkt1_state_tag {
+ long len, pad, biglen, to_read;
+ unsigned long realcrc, gotcrc;
+ unsigned char *p;
+ int i;
+ int chunk;
+ struct Packet *pktin;
+};
+
+struct rdpkt2_state_tag {
+ long len, pad, payload, packetlen, maclen;
+ int i;
+ int cipherblk;
+ unsigned long incoming_sequence;
+ struct Packet *pktin;
+};
+
+struct rdpkt2_bare_state_tag {
+ char length[4];
+ long packetlen;
+ int i;
+ unsigned long incoming_sequence;
+ struct Packet *pktin;
+};
+
+struct queued_handler;
+struct queued_handler {
+ int msg1, msg2;
+ chandler_fn_t handler;
+ void *ctx;
+ struct queued_handler *next;
+};
+
+struct ssh_tag {
+ const struct plug_function_table *fn;
+ /* the above field _must_ be first in the structure */
+
+ char *v_c, *v_s;
+ void *exhash;
+
+ Socket s;
+
+ void *ldisc;
+ void *logctx;
+
+ unsigned char session_key[32];
+ int v1_compressing;
+ int v1_remote_protoflags;
+ int v1_local_protoflags;
+ int agentfwd_enabled;
+ int X11_fwd_enabled;
+ int remote_bugs;
+ const struct ssh_cipher *cipher;
+ void *v1_cipher_ctx;
+ void *crcda_ctx;
+ const struct ssh2_cipher *cscipher, *sccipher;
+ void *cs_cipher_ctx, *sc_cipher_ctx;
+ const struct ssh_mac *csmac, *scmac;
+ void *cs_mac_ctx, *sc_mac_ctx;
+ const struct ssh_compress *cscomp, *sccomp;
+ void *cs_comp_ctx, *sc_comp_ctx;
+ const struct ssh_kex *kex;
+ const struct ssh_signkey *hostkey;
+ char *hostkey_str; /* string representation, for easy checking in rekeys */
+ unsigned char v2_session_id[SSH2_KEX_MAX_HASH_LEN];
+ int v2_session_id_len;
+ void *kex_ctx;
+
+ int bare_connection;
+ int attempting_connshare;
+ void *connshare;
+
+ char *savedhost;
+ int savedport;
+ int send_ok;
+ int echoing, editing;
+
+ void *frontend;
+
+ int ospeed, ispeed; /* temporaries */
+ int term_width, term_height;
+
+ tree234 *channels; /* indexed by local id */
+ struct ssh_channel *mainchan; /* primary session channel */
+ int ncmode; /* is primary channel direct-tcpip? */
+ int exitcode;
+ int close_expected;
+ int clean_exit;
+
+ tree234 *rportfwds, *portfwds;
+
+ enum {
+ SSH_STATE_PREPACKET,
+ SSH_STATE_BEFORE_SIZE,
+ SSH_STATE_INTERMED,
+ SSH_STATE_SESSION,
+ SSH_STATE_CLOSED
+ } state;
+
+ int size_needed, eof_needed;
+ int sent_console_eof;
+ int got_pty; /* affects EOF behaviour on main channel */
+
+ struct Packet **queue;
+ int queuelen, queuesize;
+ int queueing;
+ unsigned char *deferred_send_data;
+ int deferred_len, deferred_size;
+
+ /*
+ * Gross hack: pscp will try to start SFTP but fall back to
+ * scp1 if that fails. This variable is the means by which
+ * scp.c can reach into the SSH code and find out which one it
+ * got.
+ */
+ int fallback_cmd;
+
+ bufchain banner; /* accumulates banners during do_ssh2_authconn */
+
+ Pkt_KCtx pkt_kctx;
+ Pkt_ACtx pkt_actx;
+
+ struct X11Display *x11disp;
+ struct X11FakeAuth *x11auth;
+ tree234 *x11authtree;
+
+ int version;
+ int conn_throttle_count;
+ int overall_bufsize;
+ int throttled_all;
+ int v1_stdout_throttling;
+ unsigned long v2_outgoing_sequence;
+
+ int ssh1_rdpkt_crstate;
+ int ssh2_rdpkt_crstate;
+ int ssh2_bare_rdpkt_crstate;
+ int ssh_gotdata_crstate;
+ int do_ssh1_connection_crstate;
+
+ void *do_ssh_init_state;
+ void *do_ssh1_login_state;
+ void *do_ssh2_transport_state;
+ void *do_ssh2_authconn_state;
+ void *do_ssh_connection_init_state;
+
+ struct rdpkt1_state_tag rdpkt1_state;
+ struct rdpkt2_state_tag rdpkt2_state;
+ struct rdpkt2_bare_state_tag rdpkt2_bare_state;
+
+ /* 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,
+ struct Packet *pkt);
+ struct Packet *(*s_rdpkt) (Ssh ssh, unsigned char **data, int *datalen);
+ int (*do_ssh_init)(Ssh ssh, unsigned char c);
+
+ /*
+ * We maintain our own copy of a Conf structure here. That way,
+ * when we're passed a new one for reconfiguration, we can check
+ * the differences and potentially reconfigure port forwardings
+ * etc in mid-session.
+ */
+ Conf *conf;
+
+ /*
+ * Values cached out of conf so as to avoid the tree234 lookup
+ * cost every time they're used.
+ */
+ int logomitdata;
+
+ /*
+ * Dynamically allocated username string created during SSH
+ * login. Stored in here rather than in the coroutine state so
+ * that it'll be reliably freed if we shut down the SSH session
+ * at some unexpected moment.
+ */
+ char *username;
+
+ /*
+ * Used to transfer data back from async callbacks.
+ */
+ void *agent_response;
+ int agent_response_len;
+ int user_response;
+
+ /*
+ * The SSH connection can be set as `frozen', meaning we are
+ * not currently accepting incoming data from the network. This
+ * is slightly more serious than setting the _socket_ as
+ * frozen, because we may already have had data passed to us
+ * from the network which we need to delay processing until
+ * after the freeze is lifted, so we also need a bufchain to
+ * store that data.
+ */
+ int frozen;
+ bufchain queued_incoming_data;
+
+ /*
+ * Dispatch table for packet types that we may have to deal
+ * with at any time.
+ */
+ handler_fn_t packet_dispatch[256];
+
+ /*
+ * Queues of one-off handler functions for success/failure
+ * indications from a request.
+ */
+ struct queued_handler *qhead, *qtail;
+ handler_fn_t q_saved_handler1, q_saved_handler2;
+
+ /*
+ * This module deals with sending keepalives.
+ */
+ Pinger pinger;
+
+ /*
+ * Track incoming and outgoing data sizes and time, for
+ * size-based rekeys.
+ */
+ unsigned long incoming_data_size, outgoing_data_size, deferred_data_size;
+ 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 */
+
+ /*
+ * Fully qualified host name, which we need if doing GSSAPI.
+ */
+ char *fullhostname;
+
+#ifndef NO_GSSAPI
+ /*
+ * GSSAPI libraries for this session.
+ */
+ struct ssh_gss_liblist *gsslibs;
+#endif
+};
+
+#define logevent(s) logevent(ssh->frontend, s)
+
+/* logevent, only printf-formatted. */
+static void logeventf(Ssh ssh, const char *fmt, ...)
+{
+ va_list ap;
+ char *buf;
+
+ va_start(ap, fmt);
+ buf = dupvprintf(fmt, ap);
+ va_end(ap);
+ logevent(buf);
+ sfree(buf);
+}
+
+static void bomb_out(Ssh ssh, char *text)
+{
+ ssh_do_close(ssh, FALSE);
+ logevent(text);
+ connection_fatal(ssh->frontend, "%s", text);
+ sfree(text);
+}
+
+#define bombout(msg) bomb_out(ssh, dupprintf msg)
+
+/* Helper function for common bits of parsing ttymodes. */
+static void parse_ttymodes(Ssh ssh,
+ void (*do_mode)(void *data, char *mode, char *val),
+ void *data)
+{
+ char *key, *val;
+
+ for (val = conf_get_str_strs(ssh->conf, CONF_ttymodes, NULL, &key);
+ val != NULL;
+ val = conf_get_str_strs(ssh->conf, CONF_ttymodes, key, &key)) {
+ /*
+ * val[0] is either 'V', indicating that an explicit value
+ * follows it, or 'A' indicating that we should pass the
+ * value through from the local environment via get_ttymode.
+ */
+ if (val[0] == 'A') {
+ val = get_ttymode(ssh->frontend, key);
+ if (val) {
+ do_mode(data, key, val);
+ sfree(val);
+ }
+ } else
+ do_mode(data, key, val + 1); /* skip the 'V' */
+ }
+}
+
+static int ssh_channelcmp(void *av, void *bv)
+{
+ struct ssh_channel *a = (struct ssh_channel *) av;
+ struct ssh_channel *b = (struct ssh_channel *) bv;
+ if (a->localid < b->localid)
+ return -1;
+ if (a->localid > b->localid)
+ return +1;
+ return 0;
+}
+static int ssh_channelfind(void *av, void *bv)
+{
+ unsigned *a = (unsigned *) av;
+ struct ssh_channel *b = (struct ssh_channel *) bv;
+ if (*a < b->localid)
+ return -1;
+ if (*a > b->localid)
+ return +1;
+ return 0;
+}
+
+static int ssh_rportcmp_ssh1(void *av, void *bv)
+{
+ struct ssh_rportfwd *a = (struct ssh_rportfwd *) av;
+ struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv;
+ int i;
+ if ( (i = strcmp(a->dhost, b->dhost)) != 0)
+ return i < 0 ? -1 : +1;
+ if (a->dport > b->dport)
+ return +1;
+ if (a->dport < b->dport)
+ return -1;
+ return 0;
+}
+
+static int ssh_rportcmp_ssh2(void *av, void *bv)
+{
+ struct ssh_rportfwd *a = (struct ssh_rportfwd *) av;
+ struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv;
+ int i;
+ if ( (i = strcmp(a->shost, b->shost)) != 0)
+ return i < 0 ? -1 : +1;
+ if (a->sport > b->sport)
+ return +1;
+ if (a->sport < b->sport)
+ return -1;
+ return 0;
+}
+
+/*
+ * Special form of strcmp which can cope with NULL inputs. NULL is
+ * defined to sort before even the empty string.
+ */
+static int nullstrcmp(const char *a, const char *b)
+{
+ if (a == NULL && b == NULL)
+ return 0;
+ if (a == NULL)
+ return -1;
+ if (b == NULL)
+ return +1;
+ return strcmp(a, b);
+}
+
+static int ssh_portcmp(void *av, void *bv)
+{
+ struct ssh_portfwd *a = (struct ssh_portfwd *) av;
+ struct ssh_portfwd *b = (struct ssh_portfwd *) bv;
+ int i;
+ if (a->type > b->type)
+ return +1;
+ if (a->type < b->type)
+ return -1;
+ if (a->addressfamily > b->addressfamily)
+ return +1;
+ if (a->addressfamily < b->addressfamily)
+ return -1;
+ if ( (i = nullstrcmp(a->saddr, b->saddr)) != 0)
+ return i < 0 ? -1 : +1;
+ if (a->sport > b->sport)
+ return +1;
+ if (a->sport < b->sport)
+ return -1;
+ if (a->type != 'D') {
+ if ( (i = nullstrcmp(a->daddr, b->daddr)) != 0)
+ return i < 0 ? -1 : +1;
+ if (a->dport > b->dport)
+ return +1;
+ if (a->dport < b->dport)
+ return -1;
+ }
+ return 0;
+}
+
+static int alloc_channel_id(Ssh ssh)
+{
+ const unsigned CHANNEL_NUMBER_OFFSET = 256;
+ unsigned low, high, mid;
+ int tsize;
+ struct ssh_channel *c;
+
+ /*
+ * First-fit allocation of channel numbers: always pick the
+ * lowest unused one. To do this, binary-search using the
+ * counted B-tree to find the largest channel ID which is in a
+ * contiguous sequence from the beginning. (Precisely
+ * everything in that sequence must have ID equal to its tree
+ * index plus CHANNEL_NUMBER_OFFSET.)
+ */
+ tsize = count234(ssh->channels);
+
+ low = -1;
+ high = tsize;
+ while (high - low > 1) {
+ mid = (high + low) / 2;
+ c = index234(ssh->channels, mid);
+ if (c->localid == mid + CHANNEL_NUMBER_OFFSET)
+ low = mid; /* this one is fine */
+ else
+ high = mid; /* this one is past it */
+ }
+ /*
+ * Now low points to either -1, or the tree index of the
+ * largest ID in the initial sequence.
+ */
+ {
+ unsigned i = low + 1 + CHANNEL_NUMBER_OFFSET;
+ assert(NULL == find234(ssh->channels, &i, ssh_channelfind));
+ }
+ return low + 1 + CHANNEL_NUMBER_OFFSET;
+}
+
+static void c_write_stderr(int trusted, const char *buf, int len)
+{
+ int i;
+ for (i = 0; i < len; i++)
+ if (buf[i] != '\r' && (trusted || buf[i] == '\n' || (buf[i] & 0x60)))
+ fputc(buf[i], stderr);
+ fflush(stderr);
+}
+
+static void c_write(Ssh ssh, const char *buf, int len)
+{
+ if (flags & FLAG_STDERR)
+ c_write_stderr(1, buf, len);
+ else
+ from_backend(ssh->frontend, 1, buf, len);
+}
+
+static void c_write_untrusted(Ssh ssh, const char *buf, int len)
+{
+ if (flags & FLAG_STDERR)
+ c_write_stderr(0, buf, len);
+ else
+ from_backend_untrusted(ssh->frontend, buf, len);
+}
+
+static void c_write_str(Ssh ssh, const char *buf)
+{
+ c_write(ssh, buf, strlen(buf));
+}
+
+static void ssh_free_packet(struct Packet *pkt)
+{
+ sfree(pkt->data);
+ sfree(pkt);
+}
+static struct Packet *ssh_new_packet(void)
+{
+ struct Packet *pkt = snew(struct Packet);
+
+ pkt->body = pkt->data = NULL;
+ pkt->maxlen = 0;
+
+ return pkt;
+}
+
+static void ssh1_log_incoming_packet(Ssh ssh, struct Packet *pkt)
+{
+ int nblanks = 0;
+ struct logblank_t blanks[4];
+ char *str;
+ int slen;
+
+ pkt->savedpos = 0;
+
+ if (ssh->logomitdata &&
+ (pkt->type == SSH1_SMSG_STDOUT_DATA ||
+ pkt->type == SSH1_SMSG_STDERR_DATA ||
+ pkt->type == SSH1_MSG_CHANNEL_DATA)) {
+ /* "Session data" packets - omit the data string. */
+ if (pkt->type == SSH1_MSG_CHANNEL_DATA)
+ ssh_pkt_getuint32(pkt); /* skip channel id */
+ blanks[nblanks].offset = pkt->savedpos + 4;
+ blanks[nblanks].type = PKTLOG_OMIT;
+ ssh_pkt_getstring(pkt, &str, &slen);
+ if (str) {
+ blanks[nblanks].len = slen;
+ nblanks++;
+ }
+ }
+ log_packet(ssh->logctx, PKT_INCOMING, pkt->type,
+ ssh1_pkt_type(pkt->type),
+ pkt->body, pkt->length, nblanks, blanks, NULL,
+ 0, NULL);
+}
+
+static void ssh1_log_outgoing_packet(Ssh ssh, struct Packet *pkt)
+{
+ int nblanks = 0;
+ struct logblank_t blanks[4];
+ char *str;
+ int slen;
+
+ /*
+ * For outgoing packets, pkt->length represents the length of the
+ * whole packet starting at pkt->data (including some header), and
+ * pkt->body refers to the point within that where the log-worthy
+ * payload begins. However, incoming packets expect pkt->length to
+ * represent only the payload length (that is, it's measured from
+ * pkt->body not from pkt->data). Temporarily adjust our outgoing
+ * packet to conform to the incoming-packet semantics, so that we
+ * can analyse it with the ssh_pkt_get functions.
+ */
+ pkt->length -= (pkt->body - pkt->data);
+ pkt->savedpos = 0;
+
+ if (ssh->logomitdata &&
+ (pkt->type == SSH1_CMSG_STDIN_DATA ||
+ pkt->type == SSH1_MSG_CHANNEL_DATA)) {
+ /* "Session data" packets - omit the data string. */
+ if (pkt->type == SSH1_MSG_CHANNEL_DATA)
+ ssh_pkt_getuint32(pkt); /* skip channel id */
+ blanks[nblanks].offset = pkt->savedpos + 4;
+ blanks[nblanks].type = PKTLOG_OMIT;
+ ssh_pkt_getstring(pkt, &str, &slen);
+ if (str) {
+ blanks[nblanks].len = slen;
+ nblanks++;
+ }
+ }
+
+ if ((pkt->type == SSH1_CMSG_AUTH_PASSWORD ||
+ pkt->type == SSH1_CMSG_AUTH_TIS_RESPONSE ||
+ pkt->type == SSH1_CMSG_AUTH_CCARD_RESPONSE) &&
+ conf_get_int(ssh->conf, CONF_logomitpass)) {
+ /* If this is a password or similar packet, blank the password(s). */
+ blanks[nblanks].offset = 0;
+ blanks[nblanks].len = pkt->length;
+ blanks[nblanks].type = PKTLOG_BLANK;
+ nblanks++;
+ } else if (pkt->type == SSH1_CMSG_X11_REQUEST_FORWARDING &&
+ conf_get_int(ssh->conf, CONF_logomitpass)) {
+ /*
+ * If this is an X forwarding request packet, blank the fake
+ * auth data.
+ *
+ * Note that while we blank the X authentication data here, we
+ * don't take any special action to blank the start of an X11
+ * channel, so using MIT-MAGIC-COOKIE-1 and actually opening
+ * an X connection without having session blanking enabled is
+ * likely to leak your cookie into the log.
+ */
+ pkt->savedpos = 0;
+ ssh_pkt_getstring(pkt, &str, &slen);
+ blanks[nblanks].offset = pkt->savedpos;
+ blanks[nblanks].type = PKTLOG_BLANK;
+ ssh_pkt_getstring(pkt, &str, &slen);
+ if (str) {
+ blanks[nblanks].len = pkt->savedpos - blanks[nblanks].offset;
+ nblanks++;
+ }
+ }
+
+ log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[12],
+ ssh1_pkt_type(pkt->data[12]),
+ pkt->body, pkt->length,
+ nblanks, blanks, NULL, 0, NULL);
+
+ /*
+ * Undo the above adjustment of pkt->length, to put the packet
+ * back in the state we found it.
+ */
+ pkt->length += (pkt->body - pkt->data);
+}
+
+/*
+ * Collect incoming data in the incoming packet buffer.
+ * Decipher and verify the packet when it is completely read.
+ * Drop SSH1_MSG_DEBUG and SSH1_MSG_IGNORE packets.
+ * 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)
+{
+ struct rdpkt1_state_tag *st = &ssh->rdpkt1_state;
+
+ crBegin(ssh->ssh1_rdpkt_crstate);
+
+ st->pktin = ssh_new_packet();
+
+ st->pktin->type = 0;
+ st->pktin->length = 0;
+
+ for (st->i = st->len = 0; st->i < 4; st->i++) {
+ while ((*datalen) == 0)
+ crReturn(NULL);
+ st->len = (st->len << 8) + **data;
+ (*data)++, (*datalen)--;
+ }
+
+ st->pad = 8 - (st->len % 8);
+ st->biglen = st->len + st->pad;
+ st->pktin->length = st->len - 5;
+
+ if (st->biglen < 0) {
+ bombout(("Extremely large packet length from server suggests"
+ " data stream corruption"));
+ ssh_free_packet(st->pktin);
+ crStop(NULL);
+ }
+
+ st->pktin->maxlen = st->biglen;
+ st->pktin->data = snewn(st->biglen + APIEXTRA, unsigned char);
+
+ st->to_read = st->biglen;
+ st->p = st->pktin->data;
+ while (st->to_read > 0) {
+ st->chunk = st->to_read;
+ while ((*datalen) == 0)
+ crReturn(NULL);
+ if (st->chunk > (*datalen))
+ st->chunk = (*datalen);
+ memcpy(st->p, *data, st->chunk);
+ *data += st->chunk;
+ *datalen -= st->chunk;
+ st->p += st->chunk;
+ st->to_read -= st->chunk;
+ }
+
+ if (ssh->cipher && detect_attack(ssh->crcda_ctx, st->pktin->data,
+ st->biglen, NULL)) {
+ bombout(("Network attack (CRC compensation) detected!"));
+ ssh_free_packet(st->pktin);
+ crStop(NULL);
+ }
+
+ if (ssh->cipher)
+ ssh->cipher->decrypt(ssh->v1_cipher_ctx, st->pktin->data, st->biglen);
+
+ st->realcrc = crc32_compute(st->pktin->data, st->biglen - 4);
+ st->gotcrc = GET_32BIT(st->pktin->data + st->biglen - 4);
+ if (st->gotcrc != st->realcrc) {
+ bombout(("Incorrect CRC received on packet"));
+ ssh_free_packet(st->pktin);
+ crStop(NULL);
+ }
+
+ st->pktin->body = st->pktin->data + st->pad + 1;
+
+ if (ssh->v1_compressing) {
+ unsigned char *decompblk;
+ int decomplen;
+ if (!zlib_decompress_block(ssh->sc_comp_ctx,
+ st->pktin->body - 1, st->pktin->length + 1,
+ &decompblk, &decomplen)) {
+ bombout(("Zlib decompression encountered invalid data"));
+ ssh_free_packet(st->pktin);
+ crStop(NULL);
+ }
+
+ if (st->pktin->maxlen < st->pad + decomplen) {
+ st->pktin->maxlen = st->pad + decomplen;
+ st->pktin->data = sresize(st->pktin->data,
+ st->pktin->maxlen + APIEXTRA,
+ unsigned char);
+ st->pktin->body = st->pktin->data + st->pad + 1;
+ }
+
+ memcpy(st->pktin->body - 1, decompblk, decomplen);
+ sfree(decompblk);
+ st->pktin->length = decomplen - 1;
+ }
+
+ st->pktin->type = st->pktin->body[-1];
+
+ /*
+ * Now pktin->body and pktin->length identify the semantic content
+ * of the packet, excluding the initial type byte.
+ */
+
+ if (ssh->logctx)
+ ssh1_log_incoming_packet(ssh, st->pktin);
+
+ st->pktin->savedpos = 0;
+
+ crFinish(st->pktin);
+}
+
+static void ssh2_log_incoming_packet(Ssh ssh, struct Packet *pkt)
+{
+ int nblanks = 0;
+ struct logblank_t blanks[4];
+ char *str;
+ int slen;
+
+ pkt->savedpos = 0;
+
+ if (ssh->logomitdata &&
+ (pkt->type == SSH2_MSG_CHANNEL_DATA ||
+ pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA)) {
+ /* "Session data" packets - omit the data string. */
+ ssh_pkt_getuint32(pkt); /* skip channel id */
+ if (pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA)
+ ssh_pkt_getuint32(pkt); /* skip extended data type */
+ blanks[nblanks].offset = pkt->savedpos + 4;
+ blanks[nblanks].type = PKTLOG_OMIT;
+ ssh_pkt_getstring(pkt, &str, &slen);
+ if (str) {
+ blanks[nblanks].len = slen;
+ nblanks++;
+ }
+ }
+
+ log_packet(ssh->logctx, PKT_INCOMING, pkt->type,
+ ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, pkt->type),
+ pkt->body, pkt->length, nblanks, blanks, &pkt->sequence,
+ 0, NULL);
+}
+
+static void ssh2_log_outgoing_packet(Ssh ssh, struct Packet *pkt)
+{
+ int nblanks = 0;
+ struct logblank_t blanks[4];
+ char *str;
+ int slen;
+
+ /*
+ * For outgoing packets, pkt->length represents the length of the
+ * whole packet starting at pkt->data (including some header), and
+ * pkt->body refers to the point within that where the log-worthy
+ * payload begins. However, incoming packets expect pkt->length to
+ * represent only the payload length (that is, it's measured from
+ * pkt->body not from pkt->data). Temporarily adjust our outgoing
+ * packet to conform to the incoming-packet semantics, so that we
+ * can analyse it with the ssh_pkt_get functions.
+ */
+ pkt->length -= (pkt->body - pkt->data);
+ pkt->savedpos = 0;
+
+ if (ssh->logomitdata &&
+ (pkt->type == SSH2_MSG_CHANNEL_DATA ||
+ pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA)) {
+ /* "Session data" packets - omit the data string. */
+ ssh_pkt_getuint32(pkt); /* skip channel id */
+ if (pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA)
+ ssh_pkt_getuint32(pkt); /* skip extended data type */
+ blanks[nblanks].offset = pkt->savedpos + 4;
+ blanks[nblanks].type = PKTLOG_OMIT;
+ ssh_pkt_getstring(pkt, &str, &slen);
+ if (str) {
+ blanks[nblanks].len = slen;
+ nblanks++;
+ }
+ }
+
+ if (pkt->type == SSH2_MSG_USERAUTH_REQUEST &&
+ conf_get_int(ssh->conf, CONF_logomitpass)) {
+ /* If this is a password packet, blank the password(s). */
+ pkt->savedpos = 0;
+ ssh_pkt_getstring(pkt, &str, &slen);
+ ssh_pkt_getstring(pkt, &str, &slen);
+ ssh_pkt_getstring(pkt, &str, &slen);
+ if (slen == 8 && !memcmp(str, "password", 8)) {
+ ssh2_pkt_getbool(pkt);
+ /* Blank the password field. */
+ blanks[nblanks].offset = pkt->savedpos;
+ blanks[nblanks].type = PKTLOG_BLANK;
+ ssh_pkt_getstring(pkt, &str, &slen);
+ if (str) {
+ blanks[nblanks].len = pkt->savedpos - blanks[nblanks].offset;
+ nblanks++;
+ /* If there's another password field beyond it (change of
+ * password), blank that too. */
+ ssh_pkt_getstring(pkt, &str, &slen);
+ if (str)
+ blanks[nblanks-1].len =
+ pkt->savedpos - blanks[nblanks].offset;
+ }
+ }
+ } else if (ssh->pkt_actx == SSH2_PKTCTX_KBDINTER &&
+ pkt->type == SSH2_MSG_USERAUTH_INFO_RESPONSE &&
+ conf_get_int(ssh->conf, CONF_logomitpass)) {
+ /* If this is a keyboard-interactive response packet, blank
+ * the responses. */
+ pkt->savedpos = 0;
+ ssh_pkt_getuint32(pkt);
+ blanks[nblanks].offset = pkt->savedpos;
+ blanks[nblanks].type = PKTLOG_BLANK;
+ while (1) {
+ ssh_pkt_getstring(pkt, &str, &slen);
+ if (!str)
+ break;
+ }
+ blanks[nblanks].len = pkt->savedpos - blanks[nblanks].offset;
+ nblanks++;
+ } else if (pkt->type == SSH2_MSG_CHANNEL_REQUEST &&
+ conf_get_int(ssh->conf, CONF_logomitpass)) {
+ /*
+ * If this is an X forwarding request packet, blank the fake
+ * auth data.
+ *
+ * Note that while we blank the X authentication data here, we
+ * don't take any special action to blank the start of an X11
+ * channel, so using MIT-MAGIC-COOKIE-1 and actually opening
+ * an X connection without having session blanking enabled is
+ * likely to leak your cookie into the log.
+ */
+ pkt->savedpos = 0;
+ ssh_pkt_getuint32(pkt);
+ ssh_pkt_getstring(pkt, &str, &slen);
+ if (slen == 7 && !memcmp(str, "x11-req", 0)) {
+ ssh2_pkt_getbool(pkt);
+ ssh2_pkt_getbool(pkt);
+ ssh_pkt_getstring(pkt, &str, &slen);
+ blanks[nblanks].offset = pkt->savedpos;
+ blanks[nblanks].type = PKTLOG_BLANK;
+ ssh_pkt_getstring(pkt, &str, &slen);
+ if (str) {
+ blanks[nblanks].len = pkt->savedpos - blanks[nblanks].offset;
+ nblanks++;
+ }
+ }
+ }
+
+ log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[5],
+ ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, pkt->data[5]),
+ pkt->body, pkt->length, nblanks, blanks,
+ &ssh->v2_outgoing_sequence,
+ pkt->downstream_id, pkt->additional_log_text);
+
+ /*
+ * Undo the above adjustment of pkt->length, to put the packet
+ * back in the state we found it.
+ */
+ pkt->length += (pkt->body - pkt->data);
+}
+
+static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
+{
+ struct rdpkt2_state_tag *st = &ssh->rdpkt2_state;
+
+ crBegin(ssh->ssh2_rdpkt_crstate);
+
+ st->pktin = ssh_new_packet();
+
+ st->pktin->type = 0;
+ st->pktin->length = 0;
+ if (ssh->sccipher)
+ st->cipherblk = ssh->sccipher->blksize;
+ else
+ st->cipherblk = 8;
+ if (st->cipherblk < 8)
+ st->cipherblk = 8;
+ st->maclen = ssh->scmac ? ssh->scmac->len : 0;
+
+ if (ssh->sccipher && (ssh->sccipher->flags & SSH_CIPHER_IS_CBC) &&
+ ssh->scmac) {
+ /*
+ * When dealing with a CBC-mode cipher, we want to avoid the
+ * possibility of an attacker's tweaking the ciphertext stream
+ * so as to cause us to feed the same block to the block
+ * cipher more than once and thus leak information
+ * (VU#958563). The way we do this is not to take any
+ * decisions on the basis of anything we've decrypted until
+ * we've verified it with a MAC. That includes the packet
+ * 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.
+ */
+
+ /* May as well allocate the whole lot now. */
+ st->pktin->data = snewn(OUR_V2_PACKETLIMIT + st->maclen + APIEXTRA,
+ unsigned char);
+
+ /* Read an amount corresponding to the MAC. */
+ for (st->i = 0; st->i < st->maclen; st->i++) {
+ while ((*datalen) == 0)
+ crReturn(NULL);
+ st->pktin->data[st->i] = *(*data)++;
+ (*datalen)--;
+ }
+
+ st->packetlen = 0;
+ {
+ unsigned char seq[4];
+ ssh->scmac->start(ssh->sc_mac_ctx);
+ PUT_32BIT(seq, st->incoming_sequence);
+ ssh->scmac->bytes(ssh->sc_mac_ctx, seq, 4);
+ }
+
+ for (;;) { /* Once around this loop per cipher block. */
+ /* Read another cipher-block's worth, and tack it onto the end. */
+ for (st->i = 0; st->i < st->cipherblk; st->i++) {
+ while ((*datalen) == 0)
+ crReturn(NULL);
+ st->pktin->data[st->packetlen+st->maclen+st->i] = *(*data)++;
+ (*datalen)--;
+ }
+ /* Decrypt one more block (a little further back in the stream). */
+ ssh->sccipher->decrypt(ssh->sc_cipher_ctx,
+ st->pktin->data + st->packetlen,
+ st->cipherblk);
+ /* Feed that block to the MAC. */
+ ssh->scmac->bytes(ssh->sc_mac_ctx,
+ st->pktin->data + st->packetlen, st->cipherblk);
+ st->packetlen += st->cipherblk;
+ /* See if that gives us a valid packet. */
+ if (ssh->scmac->verresult(ssh->sc_mac_ctx,
+ st->pktin->data + st->packetlen) &&
+ ((st->len = toint(GET_32BIT(st->pktin->data))) ==
+ st->packetlen-4))
+ break;
+ if (st->packetlen >= OUR_V2_PACKETLIMIT) {
+ bombout(("No valid incoming packet found"));
+ ssh_free_packet(st->pktin);
+ crStop(NULL);
+ }
+ }
+ st->pktin->maxlen = st->packetlen + st->maclen;
+ st->pktin->data = sresize(st->pktin->data,
+ st->pktin->maxlen + APIEXTRA,
+ unsigned char);
+ } else {
+ st->pktin->data = snewn(st->cipherblk + APIEXTRA, unsigned char);
+
+ /*
+ * Acquire and decrypt the first block of the packet. This will
+ * contain the length and padding details.
+ */
+ for (st->i = st->len = 0; st->i < st->cipherblk; st->i++) {
+ while ((*datalen) == 0)
+ crReturn(NULL);
+ st->pktin->data[st->i] = *(*data)++;
+ (*datalen)--;
+ }
+
+ if (ssh->sccipher)
+ ssh->sccipher->decrypt(ssh->sc_cipher_ctx,
+ st->pktin->data, st->cipherblk);
+
+ /*
+ * Now get the length figure.
+ */
+ 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 + 4) % st->cipherblk != 0) {
+ bombout(("Incoming packet was garbled on decryption"));
+ 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 and decrypt the remainder of the packet.
+ */
+ for (st->i = st->cipherblk; st->i < st->packetlen + st->maclen;
+ st->i++) {
+ while ((*datalen) == 0)
+ crReturn(NULL);
+ st->pktin->data[st->i] = *(*data)++;
+ (*datalen)--;
+ }
+ /* Decrypt everything _except_ the MAC. */
+ if (ssh->sccipher)
+ ssh->sccipher->decrypt(ssh->sc_cipher_ctx,
+ st->pktin->data + st->cipherblk,
+ st->packetlen - st->cipherblk);
+
+ /*
+ * 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);
+ }
+ }
+ /* Get and sanity-check the amount of random padding. */
+ st->pad = st->pktin->data[4];
+ if (st->pad < 4 || st->len - st->pad < 1) {
+ bombout(("Invalid padding length on received packet"));
+ ssh_free_packet(st->pktin);
+ crStop(NULL);
+ }
+ /*
+ * This enables us to deduce the payload length.
+ */
+ st->payload = st->len - st->pad - 1;
+
+ st->pktin->length = st->payload + 5;
+ st->pktin->encrypted_len = st->packetlen;
+
+ st->pktin->sequence = st->incoming_sequence++;
+
+ st->pktin->length = st->packetlen - st->pad;
+ assert(st->pktin->length >= 0);
+
+ /*
+ * Decompress packet payload.
+ */
+ {
+ unsigned char *newpayload;
+ int newlen;
+ if (ssh->sccomp &&
+ ssh->sccomp->decompress(ssh->sc_comp_ctx,
+ st->pktin->data + 5, st->pktin->length - 5,
+ &newpayload, &newlen)) {
+ if (st->pktin->maxlen < newlen + 5) {
+ st->pktin->maxlen = newlen + 5;
+ st->pktin->data = sresize(st->pktin->data,
+ st->pktin->maxlen + APIEXTRA,
+ unsigned char);
+ }
+ st->pktin->length = 5 + newlen;
+ memcpy(st->pktin->data + 5, newpayload, newlen);
+ sfree(newpayload);
+ }
+ }
+
+ /*
+ * pktin->body and pktin->length should identify the semantic
+ * content of the packet, excluding the initial type byte.
+ */
+ st->pktin->type = st->pktin->data[5];
+ st->pktin->body = st->pktin->data + 6;
+ st->pktin->length -= 6;
+ assert(st->pktin->length >= 0); /* one last double-check */
+
+ if (ssh->logctx)
+ ssh2_log_incoming_packet(ssh, st->pktin);
+
+ st->pktin->savedpos = 0;
+
+ crFinish(st->pktin);
+}
+
+static struct Packet *ssh2_bare_connection_rdpkt(Ssh ssh, unsigned char **data,
+ int *datalen)
+{
+ struct rdpkt2_bare_state_tag *st = &ssh->rdpkt2_bare_state;
+
+ crBegin(ssh->ssh2_bare_rdpkt_crstate);
+
+ /*
+ * Read the packet length field.
+ */
+ for (st->i = 0; st->i < 4; st->i++) {
+ while ((*datalen) == 0)
+ crReturn(NULL);
+ st->length[st->i] = *(*data)++;
+ (*datalen)--;
+ }
+
+ st->packetlen = toint(GET_32BIT_MSB_FIRST(st->length));
+ if (st->packetlen <= 0 || st->packetlen >= OUR_V2_PACKETLIMIT) {
+ bombout(("Invalid packet length received"));
+ crStop(NULL);
+ }
+
+ st->pktin = ssh_new_packet();
+ st->pktin->data = snewn(st->packetlen, unsigned char);
+
+ st->pktin->encrypted_len = st->packetlen;
+
+ st->pktin->sequence = st->incoming_sequence++;
+
+ /*
+ * Read the remainder of the packet.
+ */
+ for (st->i = 0; st->i < st->packetlen; st->i++) {
+ while ((*datalen) == 0)
+ crReturn(NULL);
+ st->pktin->data[st->i] = *(*data)++;
+ (*datalen)--;
+ }
+
+ /*
+ * pktin->body and pktin->length should identify the semantic
+ * content of the packet, excluding the initial type byte.
+ */
+ st->pktin->type = st->pktin->data[0];
+ st->pktin->body = st->pktin->data + 1;
+ st->pktin->length = st->packetlen - 1;
+
+ /*
+ * Log incoming packet, possibly omitting sensitive fields.
+ */
+ if (ssh->logctx)
+ ssh2_log_incoming_packet(ssh, st->pktin);
+
+ st->pktin->savedpos = 0;
+
+ crFinish(st->pktin);
+}
+
+static int s_wrpkt_prepare(Ssh ssh, struct Packet *pkt, int *offset_p)
+{
+ int pad, biglen, i, pktoffs;
+ unsigned long crc;
+#ifdef __SC__
+ /*
+ * XXX various versions of SC (including 8.8.4) screw up the
+ * register allocation in this function and use the same register
+ * (D6) for len and as a temporary, with predictable results. The
+ * following sledgehammer prevents this.
+ */
+ volatile
+#endif
+ int len;
+
+ if (ssh->logctx)
+ ssh1_log_outgoing_packet(ssh, pkt);
+
+ if (ssh->v1_compressing) {
+ unsigned char *compblk;
+ int complen;
+ zlib_compress_block(ssh->cs_comp_ctx,
+ pkt->data + 12, pkt->length - 12,
+ &compblk, &complen);
+ ssh_pkt_ensure(pkt, complen + 2); /* just in case it's got bigger */
+ memcpy(pkt->data + 12, compblk, complen);
+ sfree(compblk);
+ pkt->length = complen + 12;
+ }
+
+ ssh_pkt_ensure(pkt, pkt->length + 4); /* space for CRC */
+ pkt->length += 4;
+ len = pkt->length - 4 - 8; /* len(type+data+CRC) */
+ pad = 8 - (len % 8);
+ pktoffs = 8 - pad;
+ biglen = len + pad; /* len(padding+type+data+CRC) */
+
+ for (i = pktoffs; i < 4+8; i++)
+ pkt->data[i] = random_byte();
+ crc = crc32_compute(pkt->data + pktoffs + 4, biglen - 4); /* all ex len */
+ PUT_32BIT(pkt->data + pktoffs + 4 + biglen - 4, crc);
+ PUT_32BIT(pkt->data + pktoffs, len);
+
+ if (ssh->cipher)
+ ssh->cipher->encrypt(ssh->v1_cipher_ctx,
+ pkt->data + pktoffs + 4, biglen);
+
+ if (offset_p) *offset_p = pktoffs;
+ return biglen + 4; /* len(length+padding+type+data+CRC) */
+}
+
+static int s_write(Ssh ssh, void *data, int len)
+{
+ if (ssh->logctx)
+ log_packet(ssh->logctx, PKT_OUTGOING, -1, NULL, data, len,
+ 0, NULL, NULL, 0, NULL);
+ if (!ssh->s)
+ return 0;
+ return sk_write(ssh->s, (char *)data, len);
+}
+
+static void s_wrpkt(Ssh ssh, struct Packet *pkt)
+{
+ int len, backlog, offset;
+ len = s_wrpkt_prepare(ssh, pkt, &offset);
+ backlog = s_write(ssh, pkt->data + offset, len);
+ if (backlog > SSH_MAX_BACKLOG)
+ ssh_throttle_all(ssh, 1, backlog);
+ ssh_free_packet(pkt);
+}
+
+static void s_wrpkt_defer(Ssh ssh, struct Packet *pkt)
+{
+ int len, offset;
+ len = s_wrpkt_prepare(ssh, pkt, &offset);
+ if (ssh->deferred_len + len > ssh->deferred_size) {
+ ssh->deferred_size = ssh->deferred_len + len + 128;
+ ssh->deferred_send_data = sresize(ssh->deferred_send_data,
+ ssh->deferred_size,
+ unsigned char);
+ }
+ memcpy(ssh->deferred_send_data + ssh->deferred_len,
+ pkt->data + offset, len);
+ ssh->deferred_len += len;
+ ssh_free_packet(pkt);
+}
+
+/*
+ * Construct a SSH-1 packet with the specified contents.
+ * (This all-at-once interface used to be the only one, but now SSH-1
+ * packets can also be constructed incrementally.)
+ */
+static struct Packet *construct_packet(Ssh ssh, int pkttype, va_list ap)
+{
+ int argtype;
+ Bignum bn;
+ struct Packet *pkt;
+
+ pkt = ssh1_pkt_init(pkttype);
+
+ while ((argtype = va_arg(ap, int)) != PKT_END) {
+ unsigned char *argp, argchar;
+ char *sargp;
+ unsigned long argint;
+ int arglen;
+ switch (argtype) {
+ /* Actual fields in the packet */
+ case PKT_INT:
+ argint = va_arg(ap, int);
+ ssh_pkt_adduint32(pkt, argint);
+ break;
+ case PKT_CHAR:
+ argchar = (unsigned char) va_arg(ap, int);
+ ssh_pkt_addbyte(pkt, argchar);
+ break;
+ case PKT_DATA:
+ argp = va_arg(ap, unsigned char *);
+ arglen = va_arg(ap, int);
+ ssh_pkt_adddata(pkt, argp, arglen);
+ break;
+ case PKT_STR:
+ sargp = va_arg(ap, char *);
+ ssh_pkt_addstring(pkt, sargp);
+ break;
+ case PKT_BIGNUM:
+ bn = va_arg(ap, Bignum);
+ ssh1_pkt_addmp(pkt, bn);
+ break;
+ }
+ }
+
+ return pkt;
+}
+
+static void send_packet(Ssh ssh, int pkttype, ...)
+{
+ struct Packet *pkt;
+ va_list ap;
+ va_start(ap, pkttype);
+ pkt = construct_packet(ssh, pkttype, ap);
+ va_end(ap);
+ s_wrpkt(ssh, pkt);
+}
+
+static void defer_packet(Ssh ssh, int pkttype, ...)
+{
+ struct Packet *pkt;
+ va_list ap;
+ va_start(ap, pkttype);
+ pkt = construct_packet(ssh, pkttype, ap);
+ va_end(ap);
+ s_wrpkt_defer(ssh, pkt);
+}
+
+static int ssh_versioncmp(char *a, char *b)
+{
+ char *ae, *be;
+ unsigned long av, bv;
+
+ av = strtoul(a, &ae, 10);
+ bv = strtoul(b, &be, 10);
+ if (av != bv)
+ return (av < bv ? -1 : +1);
+ if (*ae == '.')
+ ae++;
+ if (*be == '.')
+ be++;
+ av = strtoul(ae, &ae, 10);
+ bv = strtoul(be, &be, 10);
+ if (av != bv)
+ return (av < bv ? -1 : +1);
+ return 0;
+}
+
+/*
+ * Utility routines for putting an SSH-protocol `string' and
+ * `uint32' into a hash state.
+ */
+static void hash_string(const struct ssh_hash *h, void *s, void *str, int len)
+{
+ unsigned char lenblk[4];
+ PUT_32BIT(lenblk, len);
+ h->bytes(s, lenblk, 4);
+ h->bytes(s, str, len);
+}
+
+static void hash_uint32(const struct ssh_hash *h, void *s, unsigned i)
+{
+ unsigned char intblk[4];
+ PUT_32BIT(intblk, i);
+ h->bytes(s, intblk, 4);
+}
+
+/*
+ * Packet construction functions. Mostly shared between SSH-1 and SSH-2.
+ */
+static void ssh_pkt_ensure(struct Packet *pkt, int length)
+{
+ if (pkt->maxlen < length) {
+ unsigned char *body = pkt->body;
+ int offset = body ? body - pkt->data : 0;
+ pkt->maxlen = length + 256;
+ pkt->data = sresize(pkt->data, pkt->maxlen + APIEXTRA, unsigned char);
+ if (body) pkt->body = pkt->data + offset;
+ }
+}
+static void ssh_pkt_adddata(struct Packet *pkt, const void *data, int len)
+{
+ pkt->length += len;
+ ssh_pkt_ensure(pkt, pkt->length);
+ memcpy(pkt->data + pkt->length - len, data, len);
+}
+static void ssh_pkt_addbyte(struct Packet *pkt, unsigned char byte)
+{
+ ssh_pkt_adddata(pkt, &byte, 1);
+}
+static void ssh2_pkt_addbool(struct Packet *pkt, unsigned char value)
+{
+ ssh_pkt_adddata(pkt, &value, 1);
+}
+static void ssh_pkt_adduint32(struct Packet *pkt, unsigned long value)
+{
+ unsigned char x[4];
+ PUT_32BIT(x, value);
+ ssh_pkt_adddata(pkt, x, 4);
+}
+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(struct Packet *pkt, const char *data)
+{
+ ssh_pkt_addstring_start(pkt);
+ ssh_pkt_addstring_str(pkt, data);
+}
+static void ssh1_pkt_addmp(struct Packet *pkt, Bignum b)
+{
+ int len = ssh1_bignum_length(b);
+ unsigned char *data = snewn(len, unsigned char);
+ (void) ssh1_write_bignum(data, b);
+ ssh_pkt_adddata(pkt, data, len);
+ sfree(data);
+}
+static unsigned char *ssh2_mpint_fmt(Bignum b, int *len)
+{
+ unsigned char *p;
+ int i, n = (bignum_bitcount(b) + 7) / 8;
+ p = snewn(n + 1, unsigned char);
+ p[0] = 0;
+ for (i = 1; i <= n; i++)
+ p[i] = bignum_byte(b, n - i);
+ i = 0;
+ while (i <= n && p[i] == 0 && (p[i + 1] & 0x80) == 0)
+ i++;
+ memmove(p, p + i, n + 1 - i);
+ *len = n + 1 - i;
+ return p;
+}
+static void ssh2_pkt_addmp(struct Packet *pkt, Bignum b)
+{
+ unsigned char *p;
+ int len;
+ p = ssh2_mpint_fmt(b, &len);
+ ssh_pkt_addstring_start(pkt);
+ ssh_pkt_addstring_data(pkt, (char *)p, len);
+ sfree(p);
+}
+
+static struct Packet *ssh1_pkt_init(int pkt_type)
+{
+ struct Packet *pkt = ssh_new_packet();
+ pkt->length = 4 + 8; /* space for length + max padding */
+ ssh_pkt_addbyte(pkt, pkt_type);
+ pkt->body = pkt->data + pkt->length;
+ pkt->type = pkt_type;
+ pkt->downstream_id = 0;
+ pkt->additional_log_text = NULL;
+ return pkt;
+}
+
+/* For legacy code (SSH-1 and -2 packet construction used to be separate) */
+#define ssh2_pkt_ensure(pkt, length) ssh_pkt_ensure(pkt, length)
+#define ssh2_pkt_adddata(pkt, data, len) ssh_pkt_adddata(pkt, data, len)
+#define ssh2_pkt_addbyte(pkt, byte) ssh_pkt_addbyte(pkt, byte)
+#define ssh2_pkt_adduint32(pkt, value) ssh_pkt_adduint32(pkt, value)
+#define ssh2_pkt_addstring_start(pkt) ssh_pkt_addstring_start(pkt)
+#define ssh2_pkt_addstring_str(pkt, data) ssh_pkt_addstring_str(pkt, data)
+#define ssh2_pkt_addstring_data(pkt, data, len) ssh_pkt_addstring_data(pkt, data, len)
+#define ssh2_pkt_addstring(pkt, data) ssh_pkt_addstring(pkt, data)
+
+static struct Packet *ssh2_pkt_init(int pkt_type)
+{
+ struct Packet *pkt = ssh_new_packet();
+ pkt->length = 5; /* space for packet length + padding length */
+ pkt->forcepad = 0;
+ pkt->type = pkt_type;
+ ssh_pkt_addbyte(pkt, (unsigned char) pkt_type);
+ pkt->body = pkt->data + pkt->length; /* after packet type */
+ pkt->downstream_id = 0;
+ pkt->additional_log_text = NULL;
+ return pkt;
+}
+
+/*
+ * Construct an SSH-2 final-form packet: compress it, encrypt it,
+ * put the MAC on it. Final packet, ready to be sent, is stored in
+ * pkt->data. Total length is returned.
+ */
+static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt)
+{
+ int cipherblk, maclen, padding, i;
+
+ if (ssh->logctx)
+ ssh2_log_outgoing_packet(ssh, pkt);
+
+ if (ssh->bare_connection) {
+ /*
+ * Trivial packet construction for the bare connection
+ * protocol.
+ */
+ PUT_32BIT(pkt->data + 1, pkt->length - 5);
+ pkt->body = pkt->data + 1;
+ ssh->v2_outgoing_sequence++; /* only for diagnostics, really */
+ return pkt->length - 1;
+ }
+
+ /*
+ * Compress packet payload.
+ */
+ {
+ unsigned char *newpayload;
+ int newlen;
+ if (ssh->cscomp &&
+ ssh->cscomp->compress(ssh->cs_comp_ctx, pkt->data + 5,
+ pkt->length - 5,
+ &newpayload, &newlen)) {
+ pkt->length = 5;
+ ssh2_pkt_adddata(pkt, newpayload, newlen);
+ sfree(newpayload);
+ }
+ }
+
+ /*
+ * Add padding. At least four bytes, and must also bring total
+ * length (minus MAC) up to a multiple of the block size.
+ * If pkt->forcepad is set, make sure the packet is at least that size
+ * after padding.
+ */
+ cipherblk = ssh->cscipher ? ssh->cscipher->blksize : 8; /* block size */
+ cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */
+ padding = 4;
+ if (pkt->length + padding < pkt->forcepad)
+ padding = pkt->forcepad - pkt->length;
+ padding +=
+ (cipherblk - (pkt->length + padding) % cipherblk) % cipherblk;
+ assert(padding <= 255);
+ maclen = ssh->csmac ? ssh->csmac->len : 0;
+ ssh2_pkt_ensure(pkt, pkt->length + padding + maclen);
+ pkt->data[4] = padding;
+ 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);
+
+ pkt->encrypted_len = pkt->length + padding;
+
+ /* Ready-to-send packet starts at pkt->data. We return length. */
+ pkt->body = pkt->data;
+ return pkt->length + padding + maclen;
+}
+
+/*
+ * Routines called from the main SSH code to send packets. There
+ * are quite a few of these, because we have two separate
+ * mechanisms for delaying the sending of packets:
+ *
+ * - In order to send an IGNORE message and a password message in
+ * a single fixed-length blob, we require the ability to
+ * concatenate the encrypted forms of those two packets _into_ a
+ * single blob and then pass it to our <network.h> transport
+ * layer in one go. Hence, there's a deferment mechanism which
+ * works after packet encryption.
+ *
+ * - In order to avoid sending any connection-layer messages
+ * during repeat key exchange, we have to queue up any such
+ * outgoing messages _before_ they are encrypted (and in
+ * particular before they're allocated sequence numbers), and
+ * then send them once we've finished.
+ *
+ * I call these mechanisms `defer' and `queue' respectively, so as
+ * to distinguish them reasonably easily.
+ *
+ * The functions send_noqueue() and defer_noqueue() free the packet
+ * structure they are passed. Every outgoing packet goes through
+ * precisely one of these functions in its life; packets passed to
+ * ssh2_pkt_send() or ssh2_pkt_defer() either go straight to one of
+ * these or get queued, and then when the queue is later emptied
+ * the packets are all passed to defer_noqueue().
+ *
+ * When using a CBC-mode cipher, it's necessary to ensure that an
+ * attacker can't provide data to be encrypted using an IV that they
+ * know. We ensure this by prefixing each packet that might contain
+ * user data with an SSH_MSG_IGNORE. This is done using the deferral
+ * mechanism, so in this case send_noqueue() ends up redirecting to
+ * defer_noqueue(). If you don't like this inefficiency, don't use
+ * CBC.
+ */
+
+static void ssh2_pkt_defer_noqueue(Ssh, struct Packet *, int);
+static void ssh_pkt_defersend(Ssh);
+
+/*
+ * Send an SSH-2 packet immediately, without queuing or deferring.
+ */
+static void ssh2_pkt_send_noqueue(Ssh ssh, struct Packet *pkt)
+{
+ int len;
+ int backlog;
+ if (ssh->cscipher != NULL && (ssh->cscipher->flags & SSH_CIPHER_IS_CBC)) {
+ /* We need to send two packets, so use the deferral mechanism. */
+ ssh2_pkt_defer_noqueue(ssh, pkt, FALSE);
+ ssh_pkt_defersend(ssh);
+ return;
+ }
+ len = ssh2_pkt_construct(ssh, pkt);
+ backlog = s_write(ssh, pkt->body, len);
+ if (backlog > SSH_MAX_BACKLOG)
+ ssh_throttle_all(ssh, 1, backlog);
+
+ ssh->outgoing_data_size += pkt->encrypted_len;
+ if (!ssh->kex_in_progress &&
+ !ssh->bare_connection &&
+ ssh->max_data_size != 0 &&
+ ssh->outgoing_data_size > ssh->max_data_size)
+ do_ssh2_transport(ssh, "too much data sent", -1, NULL);
+
+ ssh_free_packet(pkt);
+}
+
+/*
+ * Defer an SSH-2 packet.
+ */
+static void ssh2_pkt_defer_noqueue(Ssh ssh, struct Packet *pkt, int noignore)
+{
+ int len;
+ if (ssh->cscipher != NULL && (ssh->cscipher->flags & SSH_CIPHER_IS_CBC) &&
+ ssh->deferred_len == 0 && !noignore &&
+ !(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) {
+ /*
+ * Interpose an SSH_MSG_IGNORE to ensure that user data don't
+ * get encrypted with a known IV.
+ */
+ struct Packet *ipkt = ssh2_pkt_init(SSH2_MSG_IGNORE);
+ ssh2_pkt_addstring_start(ipkt);
+ ssh2_pkt_defer_noqueue(ssh, ipkt, TRUE);
+ }
+ len = ssh2_pkt_construct(ssh, pkt);
+ if (ssh->deferred_len + len > ssh->deferred_size) {
+ ssh->deferred_size = ssh->deferred_len + len + 128;
+ ssh->deferred_send_data = sresize(ssh->deferred_send_data,
+ ssh->deferred_size,
+ unsigned char);
+ }
+ memcpy(ssh->deferred_send_data + ssh->deferred_len, pkt->body, len);
+ ssh->deferred_len += len;
+ ssh->deferred_data_size += pkt->encrypted_len;
+ ssh_free_packet(pkt);
+}
+
+/*
+ * Queue an SSH-2 packet.
+ */
+static void ssh2_pkt_queue(Ssh ssh, struct Packet *pkt)
+{
+ assert(ssh->queueing);
+
+ if (ssh->queuelen >= ssh->queuesize) {
+ ssh->queuesize = ssh->queuelen + 32;
+ ssh->queue = sresize(ssh->queue, ssh->queuesize, struct Packet *);
+ }
+
+ ssh->queue[ssh->queuelen++] = pkt;
+}
+
+/*
+ * Either queue or send a packet, depending on whether queueing is
+ * set.
+ */
+static void ssh2_pkt_send(Ssh ssh, struct Packet *pkt)
+{
+ if (ssh->queueing)
+ ssh2_pkt_queue(ssh, pkt);
+ else
+ ssh2_pkt_send_noqueue(ssh, pkt);
+}
+
+/*
+ * Either queue or defer a packet, depending on whether queueing is
+ * set.
+ */
+static void ssh2_pkt_defer(Ssh ssh, struct Packet *pkt)
+{
+ if (ssh->queueing)
+ ssh2_pkt_queue(ssh, pkt);
+ else
+ ssh2_pkt_defer_noqueue(ssh, pkt, FALSE);
+}
+
+/*
+ * Send the whole deferred data block constructed by
+ * ssh2_pkt_defer() or SSH-1's defer_packet().
+ *
+ * The expected use of the defer mechanism is that you call
+ * ssh2_pkt_defer() a few times, then call ssh_pkt_defersend(). If
+ * not currently queueing, this simply sets up deferred_send_data
+ * and then sends it. If we _are_ currently queueing, the calls to
+ * ssh2_pkt_defer() put the deferred packets on to the queue
+ * instead, and therefore ssh_pkt_defersend() has no deferred data
+ * to send. Hence, there's no need to make it conditional on
+ * ssh->queueing.
+ */
+static void ssh_pkt_defersend(Ssh ssh)
+{
+ int backlog;
+ backlog = s_write(ssh, ssh->deferred_send_data, ssh->deferred_len);
+ ssh->deferred_len = ssh->deferred_size = 0;
+ sfree(ssh->deferred_send_data);
+ ssh->deferred_send_data = NULL;
+ if (backlog > SSH_MAX_BACKLOG)
+ ssh_throttle_all(ssh, 1, backlog);
+
+ ssh->outgoing_data_size += ssh->deferred_data_size;
+ if (!ssh->kex_in_progress &&
+ !ssh->bare_connection &&
+ ssh->max_data_size != 0 &&
+ ssh->outgoing_data_size > ssh->max_data_size)
+ do_ssh2_transport(ssh, "too much data sent", -1, NULL);
+ ssh->deferred_data_size = 0;
+}
+
+/*
+ * Send a packet whose length needs to be disguised (typically
+ * passwords or keyboard-interactive responses).
+ */
+static void ssh2_pkt_send_with_padding(Ssh ssh, struct Packet *pkt,
+ int padsize)
+{
+#if 0
+ if (0) {
+ /*
+ * The simplest way to do this is to adjust the
+ * variable-length padding field in the outgoing packet.
+ *
+ * Currently compiled out, because some Cisco SSH servers
+ * don't like excessively padded packets (bah, why's it
+ * always Cisco?)
+ */
+ pkt->forcepad = padsize;
+ ssh2_pkt_send(ssh, pkt);
+ } else
+#endif
+ {
+ /*
+ * If we can't do that, however, an alternative approach is
+ * to use the pkt_defer mechanism to bundle the packet
+ * tightly together with an SSH_MSG_IGNORE such that their
+ * combined length is a constant. So first we construct the
+ * final form of this packet and defer its sending.
+ */
+ ssh2_pkt_defer(ssh, pkt);
+
+ /*
+ * Now construct an SSH_MSG_IGNORE which includes a string
+ * that's an exact multiple of the cipher block size. (If
+ * the cipher is NULL so that the block size is
+ * unavailable, we don't do this trick at all, because we
+ * gain nothing by it.)
+ */
+ if (ssh->cscipher &&
+ !(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) {
+ int stringlen, i;
+
+ stringlen = (256 - ssh->deferred_len);
+ stringlen += ssh->cscipher->blksize - 1;
+ stringlen -= (stringlen % ssh->cscipher->blksize);
+ if (ssh->cscomp) {
+ /*
+ * Temporarily disable actual compression, so we
+ * can guarantee to get this string exactly the
+ * length we want it. The compression-disabling
+ * routine should return an integer indicating how
+ * many bytes we should adjust our string length
+ * by.
+ */
+ stringlen -=
+ ssh->cscomp->disable_compression(ssh->cs_comp_ctx);
+ }
+ pkt = ssh2_pkt_init(SSH2_MSG_IGNORE);
+ ssh2_pkt_addstring_start(pkt);
+ for (i = 0; i < stringlen; i++) {
+ char c = (char) random_byte();
+ ssh2_pkt_addstring_data(pkt, &c, 1);
+ }
+ ssh2_pkt_defer(ssh, pkt);
+ }
+ ssh_pkt_defersend(ssh);
+ }
+}
+
+/*
+ * Send all queued SSH-2 packets. We send them by means of
+ * ssh2_pkt_defer_noqueue(), in case they included a pair of
+ * packets that needed to be lumped together.
+ */
+static void ssh2_pkt_queuesend(Ssh ssh)
+{
+ int i;
+
+ assert(!ssh->queueing);
+
+ for (i = 0; i < ssh->queuelen; i++)
+ ssh2_pkt_defer_noqueue(ssh, ssh->queue[i], FALSE);
+ ssh->queuelen = 0;
+
+ ssh_pkt_defersend(ssh);
+}
+
+#if 0
+void bndebug(char *string, Bignum b)
+{
+ unsigned char *p;
+ int i, len;
+ p = ssh2_mpint_fmt(b, &len);
+ debug(("%s", string));
+ for (i = 0; i < len; i++)
+ debug((" %02x", p[i]));
+ debug(("\n"));
+ sfree(p);
+}
+#endif
+
+static void hash_mpint(const struct ssh_hash *h, void *s, Bignum b)
+{
+ unsigned char *p;
+ int len;
+ p = ssh2_mpint_fmt(b, &len);
+ hash_string(h, s, p, len);
+ sfree(p);
+}
+
+/*
+ * Packet decode functions for both SSH-1 and SSH-2.
+ */
+static unsigned long ssh_pkt_getuint32(struct Packet *pkt)
+{
+ unsigned long value;
+ if (pkt->length - pkt->savedpos < 4)
+ return 0; /* arrgh, no way to decline (FIXME?) */
+ value = GET_32BIT(pkt->body + pkt->savedpos);
+ pkt->savedpos += 4;
+ return value;
+}
+static int ssh2_pkt_getbool(struct Packet *pkt)
+{
+ unsigned long value;
+ if (pkt->length - pkt->savedpos < 1)
+ return 0; /* arrgh, no way to decline (FIXME?) */
+ value = pkt->body[pkt->savedpos] != 0;
+ pkt->savedpos++;
+ return value;
+}
+static void ssh_pkt_getstring(struct Packet *pkt, char **p, int *length)
+{
+ int len;
+ *p = NULL;
+ *length = 0;
+ if (pkt->length - pkt->savedpos < 4)
+ return;
+ len = toint(GET_32BIT(pkt->body + pkt->savedpos));
+ if (len < 0)
+ return;
+ *length = len;
+ pkt->savedpos += 4;
+ if (pkt->length - pkt->savedpos < *length)
+ return;
+ *p = (char *)(pkt->body + pkt->savedpos);
+ pkt->savedpos += *length;
+}
+static void *ssh_pkt_getdata(struct Packet *pkt, int length)
+{
+ if (pkt->length - pkt->savedpos < length)
+ return NULL;
+ pkt->savedpos += length;
+ return pkt->body + (pkt->savedpos - length);
+}
+static int ssh1_pkt_getrsakey(struct Packet *pkt, struct RSAKey *key,
+ unsigned char **keystr)
+{
+ int j;
+
+ j = makekey(pkt->body + pkt->savedpos,
+ pkt->length - pkt->savedpos,
+ key, keystr, 0);
+
+ if (j < 0)
+ return FALSE;
+
+ pkt->savedpos += j;
+ assert(pkt->savedpos < pkt->length);
+
+ return TRUE;
+}
+static Bignum ssh1_pkt_getmp(struct Packet *pkt)
+{
+ int j;
+ Bignum b;
+
+ j = ssh1_read_bignum(pkt->body + pkt->savedpos,
+ pkt->length - pkt->savedpos, &b);
+
+ if (j < 0)
+ return NULL;
+
+ pkt->savedpos += j;
+ return b;
+}
+static Bignum ssh2_pkt_getmp(struct Packet *pkt)
+{
+ char *p;
+ int length;
+ Bignum b;
+
+ ssh_pkt_getstring(pkt, &p, &length);
+ if (!p)
+ return NULL;
+ if (p[0] & 0x80)
+ return NULL;
+ b = bignum_from_bytes((unsigned char *)p, length);
+ return b;
+}
+
+/*
+ * Helper function to add an SSH-2 signature blob to a packet.
+ * Expects to be shown the public key blob as well as the signature
+ * blob. Normally works just like ssh2_pkt_addstring, but will
+ * fiddle with the signature packet if necessary for
+ * BUG_SSH2_RSA_PADDING.
+ */
+static void ssh2_add_sigblob(Ssh ssh, struct Packet *pkt,
+ void *pkblob_v, int pkblob_len,
+ void *sigblob_v, int sigblob_len)
+{
+ unsigned char *pkblob = (unsigned char *)pkblob_v;
+ unsigned char *sigblob = (unsigned char *)sigblob_v;
+
+ /* dmemdump(pkblob, pkblob_len); */
+ /* dmemdump(sigblob, sigblob_len); */
+
+ /*
+ * See if this is in fact an ssh-rsa signature and a buggy
+ * server; otherwise we can just do this the easy way.
+ */
+ if ((ssh->remote_bugs & BUG_SSH2_RSA_PADDING) && pkblob_len > 4+7+4 &&
+ (GET_32BIT(pkblob) == 7 && !memcmp(pkblob+4, "ssh-rsa", 7))) {
+ int pos, len, siglen;
+
+ /*
+ * Find the byte length of the modulus.
+ */
+
+ pos = 4+7; /* skip over "ssh-rsa" */
+ len = toint(GET_32BIT(pkblob+pos)); /* get length of exponent */
+ if (len < 0 || len > pkblob_len - pos - 4)
+ goto give_up;
+ pos += 4 + len; /* skip over exponent */
+ if (pkblob_len - pos < 4)
+ goto give_up;
+ len = toint(GET_32BIT(pkblob+pos)); /* find length of modulus */
+ if (len < 0 || len > pkblob_len - pos - 4)
+ goto give_up;
+ pos += 4; /* find modulus itself */
+ while (len > 0 && pkblob[pos] == 0)
+ len--, pos++;
+ /* debug(("modulus length is %d\n", len)); */
+
+ /*
+ * Now find the signature integer.
+ */
+ pos = 4+7; /* skip over "ssh-rsa" */
+ if (sigblob_len < pos+4)
+ goto give_up;
+ siglen = toint(GET_32BIT(sigblob+pos));
+ if (siglen != sigblob_len - pos - 4)
+ goto give_up;
+ /* debug(("signature length is %d\n", siglen)); */
+
+ if (len != siglen) {
+ unsigned char newlen[4];
+ ssh2_pkt_addstring_start(pkt);
+ ssh2_pkt_addstring_data(pkt, (char *)sigblob, pos);
+ /* dmemdump(sigblob, pos); */
+ pos += 4; /* point to start of actual sig */
+ PUT_32BIT(newlen, len);
+ ssh2_pkt_addstring_data(pkt, (char *)newlen, 4);
+ /* dmemdump(newlen, 4); */
+ newlen[0] = 0;
+ while (len-- > siglen) {
+ ssh2_pkt_addstring_data(pkt, (char *)newlen, 1);
+ /* dmemdump(newlen, 1); */
+ }
+ ssh2_pkt_addstring_data(pkt, (char *)(sigblob+pos), siglen);
+ /* dmemdump(sigblob+pos, siglen); */
+ return;
+ }
+
+ /* Otherwise fall through and do it the easy way. We also come
+ * here as a fallback if we discover above that the key blob
+ * is misformatted in some way. */
+ give_up:;
+ }
+
+ ssh2_pkt_addstring_start(pkt);
+ ssh2_pkt_addstring_data(pkt, (char *)sigblob, sigblob_len);
+}
+
+/*
+ * Examine the remote side's version string and compare it against
+ * a list of known buggy implementations.
+ */
+static void ssh_detect_bugs(Ssh ssh, char *vstring)
+{
+ char *imp; /* pointer to implementation part */
+ imp = vstring;
+ imp += strcspn(imp, "-");
+ if (*imp) imp++;
+ imp += strcspn(imp, "-");
+ if (*imp) imp++;
+
+ ssh->remote_bugs = 0;
+
+ /*
+ * General notes on server version strings:
+ * - Not all servers reporting "Cisco-1.25" have all the bugs listed
+ * here -- in particular, we've heard of one that's perfectly happy
+ * with SSH1_MSG_IGNOREs -- but this string never seems to change,
+ * so we can't distinguish them.
+ */
+ if (conf_get_int(ssh->conf, CONF_sshbug_ignore1) == FORCE_ON ||
+ (conf_get_int(ssh->conf, CONF_sshbug_ignore1) == AUTO &&
+ (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") ||
+ !strcmp(imp, "1.2.20") || !strcmp(imp, "1.2.21") ||
+ !strcmp(imp, "1.2.22") || !strcmp(imp, "Cisco-1.25") ||
+ !strcmp(imp, "OSU_1.4alpha3") || !strcmp(imp, "OSU_1.5alpha4")))) {
+ /*
+ * These versions don't support SSH1_MSG_IGNORE, so we have
+ * to use a different defence against password length
+ * sniffing.
+ */
+ ssh->remote_bugs |= BUG_CHOKES_ON_SSH1_IGNORE;
+ logevent("We believe remote version has SSH-1 ignore bug");
+ }
+
+ if (conf_get_int(ssh->conf, CONF_sshbug_plainpw1) == FORCE_ON ||
+ (conf_get_int(ssh->conf, CONF_sshbug_plainpw1) == AUTO &&
+ (!strcmp(imp, "Cisco-1.25") || !strcmp(imp, "OSU_1.4alpha3")))) {
+ /*
+ * These versions need a plain password sent; they can't
+ * handle having a null and a random length of data after
+ * the password.
+ */
+ ssh->remote_bugs |= BUG_NEEDS_SSH1_PLAIN_PASSWORD;
+ logevent("We believe remote version needs a plain SSH-1 password");
+ }
+
+ if (conf_get_int(ssh->conf, CONF_sshbug_rsa1) == FORCE_ON ||
+ (conf_get_int(ssh->conf, CONF_sshbug_rsa1) == AUTO &&
+ (!strcmp(imp, "Cisco-1.25")))) {
+ /*
+ * These versions apparently have no clue whatever about
+ * RSA authentication and will panic and die if they see
+ * an AUTH_RSA message.
+ */
+ ssh->remote_bugs |= BUG_CHOKES_ON_RSA;
+ logevent("We believe remote version can't handle SSH-1 RSA authentication");
+ }
+
+ if (conf_get_int(ssh->conf, CONF_sshbug_hmac2) == FORCE_ON ||
+ (conf_get_int(ssh->conf, CONF_sshbug_hmac2) == AUTO &&
+ !wc_match("* VShell", imp) &&
+ (wc_match("2.1.0*", imp) || wc_match("2.0.*", imp) ||
+ wc_match("2.2.0*", imp) || wc_match("2.3.0*", imp) ||
+ wc_match("2.1 *", imp)))) {
+ /*
+ * These versions have the HMAC bug.
+ */
+ ssh->remote_bugs |= BUG_SSH2_HMAC;
+ logevent("We believe remote version has SSH-2 HMAC bug");
+ }
+
+ if (conf_get_int(ssh->conf, CONF_sshbug_derivekey2) == FORCE_ON ||
+ (conf_get_int(ssh->conf, CONF_sshbug_derivekey2) == AUTO &&
+ !wc_match("* VShell", imp) &&
+ (wc_match("2.0.0*", imp) || wc_match("2.0.10*", imp) ))) {
+ /*
+ * These versions have the key-derivation bug (failing to
+ * include the literal shared secret in the hashes that
+ * generate the keys).
+ */
+ ssh->remote_bugs |= BUG_SSH2_DERIVEKEY;
+ logevent("We believe remote version has SSH-2 key-derivation bug");
+ }
+
+ if (conf_get_int(ssh->conf, CONF_sshbug_rsapad2) == FORCE_ON ||
+ (conf_get_int(ssh->conf, CONF_sshbug_rsapad2) == AUTO &&
+ (wc_match("OpenSSH_2.[5-9]*", imp) ||
+ wc_match("OpenSSH_3.[0-2]*", imp) ||
+ wc_match("mod_sftp/0.[0-8]*", imp) ||
+ wc_match("mod_sftp/0.9.[0-8]", imp)))) {
+ /*
+ * These versions have the SSH-2 RSA padding bug.
+ */
+ ssh->remote_bugs |= BUG_SSH2_RSA_PADDING;
+ logevent("We believe remote version has SSH-2 RSA padding bug");
+ }
+
+ if (conf_get_int(ssh->conf, CONF_sshbug_pksessid2) == FORCE_ON ||
+ (conf_get_int(ssh->conf, CONF_sshbug_pksessid2) == AUTO &&
+ wc_match("OpenSSH_2.[0-2]*", imp))) {
+ /*
+ * These versions have the SSH-2 session-ID bug in
+ * public-key authentication.
+ */
+ ssh->remote_bugs |= BUG_SSH2_PK_SESSIONID;
+ logevent("We believe remote version has SSH-2 public-key-session-ID bug");
+ }
+
+ if (conf_get_int(ssh->conf, CONF_sshbug_rekey2) == FORCE_ON ||
+ (conf_get_int(ssh->conf, CONF_sshbug_rekey2) == AUTO &&
+ (wc_match("DigiSSH_2.0", imp) ||
+ wc_match("OpenSSH_2.[0-4]*", imp) ||
+ wc_match("OpenSSH_2.5.[0-3]*", imp) ||
+ wc_match("Sun_SSH_1.0", imp) ||
+ wc_match("Sun_SSH_1.0.1", imp) ||
+ /* All versions <= 1.2.6 (they changed their format in 1.2.7) */
+ wc_match("WeOnlyDo-*", imp)))) {
+ /*
+ * These versions have the SSH-2 rekey bug.
+ */
+ ssh->remote_bugs |= BUG_SSH2_REKEY;
+ logevent("We believe remote version has SSH-2 rekey bug");
+ }
+
+ if (conf_get_int(ssh->conf, CONF_sshbug_maxpkt2) == FORCE_ON ||
+ (conf_get_int(ssh->conf, CONF_sshbug_maxpkt2) == AUTO &&
+ (wc_match("1.36_sshlib GlobalSCAPE", imp) ||
+ wc_match("1.36 sshlib: GlobalScape", imp)))) {
+ /*
+ * This version ignores our makpkt and needs to be throttled.
+ */
+ ssh->remote_bugs |= BUG_SSH2_MAXPKT;
+ logevent("We believe remote version ignores SSH-2 maximum packet size");
+ }
+
+ if (conf_get_int(ssh->conf, CONF_sshbug_ignore2) == FORCE_ON) {
+ /*
+ * Servers that don't support SSH2_MSG_IGNORE. Currently,
+ * none detected automatically.
+ */
+ ssh->remote_bugs |= BUG_CHOKES_ON_SSH2_IGNORE;
+ logevent("We believe remote version has SSH-2 ignore bug");
+ }
+
+ if (conf_get_int(ssh->conf, CONF_sshbug_winadj) == FORCE_ON) {
+ /*
+ * Servers that don't support our winadj request for one
+ * reason or another. Currently, none detected automatically.
+ */
+ ssh->remote_bugs |= BUG_CHOKES_ON_WINADJ;
+ logevent("We believe remote version has winadj bug");
+ }
+}
+
+/*
+ * The `software version' part of an SSH version string is required
+ * to contain no spaces or minus signs.
+ */
+static void ssh_fix_verstring(char *str)
+{
+ /* Eat "<protoversion>-". */
+ while (*str && *str != '-') str++;
+ assert(*str == '-'); str++;
+
+ /* Convert minus signs and spaces in the remaining string into
+ * underscores. */
+ while (*str) {
+ if (*str == '-' || *str == ' ')
+ *str = '_';
+ str++;
+ }
+}
+
+/*
+ * Send an appropriate SSH version string.
+ */
+static void ssh_send_verstring(Ssh ssh, const char *protoname, char *svers)
+{
+ char *verstring;
+
+ if (ssh->version == 2) {
+ /*
+ * Construct a v2 version string.
+ */
+ verstring = dupprintf("%s2.0-%s\015\012", protoname, sshver);
+ } else {
+ /*
+ * Construct a v1 version string.
+ */
+ assert(!strcmp(protoname, "SSH-")); /* no v1 bare connection protocol */
+ verstring = dupprintf("SSH-%s-%s\012",
+ (ssh_versioncmp(svers, "1.5") <= 0 ?
+ svers : "1.5"),
+ sshver);
+ }
+
+ ssh_fix_verstring(verstring + strlen(protoname));
+
+ if (ssh->version == 2) {
+ size_t len;
+ /*
+ * Record our version string.
+ */
+ len = strcspn(verstring, "\015\012");
+ ssh->v_c = snewn(len + 1, char);
+ memcpy(ssh->v_c, verstring, len);
+ ssh->v_c[len] = 0;
+ }
+
+ logeventf(ssh, "We claim version: %.*s",
+ strcspn(verstring, "\015\012"), verstring);
+ s_write(ssh, verstring, strlen(verstring));
+ sfree(verstring);
+}
+
+static int do_ssh_init(Ssh ssh, unsigned char c)
+{
+ static const char protoname[] = "SSH-";
+
+ struct do_ssh_init_state {
+ int crLine;
+ int vslen;
+ char version[10];
+ char *vstring;
+ int vstrsize;
+ int i;
+ int proto1, proto2;
+ };
+ crState(do_ssh_init_state);
+
+ crBeginState;
+
+ /* Search for a line beginning with the protocol name prefix in
+ * the input. */
+ for (;;) {
+ for (s->i = 0; protoname[s->i]; s->i++) {
+ if ((char)c != protoname[s->i]) goto no;
+ crReturn(1);
+ }
+ break;
+ no:
+ while (c != '\012')
+ crReturn(1);
+ crReturn(1);
+ }
+
+ s->vstrsize = sizeof(protoname) + 16;
+ s->vstring = snewn(s->vstrsize, char);
+ strcpy(s->vstring, protoname);
+ s->vslen = strlen(protoname);
+ s->i = 0;
+ while (1) {
+ if (s->vslen >= s->vstrsize - 1) {
+ s->vstrsize += 16;
+ s->vstring = sresize(s->vstring, s->vstrsize, char);
+ }
+ s->vstring[s->vslen++] = c;
+ if (s->i >= 0) {
+ if (c == '-') {
+ s->version[s->i] = '\0';
+ s->i = -1;
+ } else if (s->i < sizeof(s->version) - 1)
+ s->version[s->i++] = c;
+ } else if (c == '\012')
+ break;
+ crReturn(1); /* get another char */
+ }
+
+ ssh->agentfwd_enabled = FALSE;
+ ssh->rdpkt2_state.incoming_sequence = 0;
+
+ s->vstring[s->vslen] = 0;
+ s->vstring[strcspn(s->vstring, "\015\012")] = '\0';/* remove EOL chars */
+ logeventf(ssh, "Server version: %s", s->vstring);
+ ssh_detect_bugs(ssh, s->vstring);
+
+ /*
+ * Decide which SSH protocol version to support.
+ */
+
+ /* Anything strictly below "2.0" means protocol 1 is supported. */
+ s->proto1 = ssh_versioncmp(s->version, "2.0") < 0;
+ /* Anything greater or equal to "1.99" means protocol 2 is supported. */
+ s->proto2 = ssh_versioncmp(s->version, "1.99") >= 0;
+
+ if (conf_get_int(ssh->conf, CONF_sshprot) == 0 && !s->proto1) {
+ bombout(("SSH protocol version 1 required by user but not provided by server"));
+ crStop(0);
+ }
+ if (conf_get_int(ssh->conf, CONF_sshprot) == 3 && !s->proto2) {
+ bombout(("SSH protocol version 2 required by user but not provided by server"));
+ crStop(0);
+ }
+
+ if (s->proto2 && (conf_get_int(ssh->conf, CONF_sshprot) >= 2 || !s->proto1))
+ ssh->version = 2;
+ else
+ ssh->version = 1;
+
+ logeventf(ssh, "Using SSH protocol version %d", ssh->version);
+
+ /* Send the version string, if we haven't already */
+ if (conf_get_int(ssh->conf, CONF_sshprot) != 3)
+ ssh_send_verstring(ssh, protoname, s->version);
+
+ if (ssh->version == 2) {
+ size_t len;
+ /*
+ * Record their version string.
+ */
+ len = strcspn(s->vstring, "\015\012");
+ ssh->v_s = snewn(len + 1, char);
+ memcpy(ssh->v_s, s->vstring, len);
+ ssh->v_s[len] = 0;
+
+ /*
+ * Initialise SSH-2 protocol.
+ */
+ ssh->protocol = ssh2_protocol;
+ ssh2_protocol_setup(ssh);
+ ssh->s_rdpkt = ssh2_rdpkt;
+ } else {
+ /*
+ * Initialise SSH-1 protocol.
+ */
+ ssh->protocol = ssh1_protocol;
+ ssh1_protocol_setup(ssh);
+ ssh->s_rdpkt = ssh1_rdpkt;
+ }
+ if (ssh->version == 2)
+ do_ssh2_transport(ssh, NULL, -1, NULL);
+
+ update_specials_menu(ssh->frontend);
+ ssh->state = SSH_STATE_BEFORE_SIZE;
+ ssh->pinger = pinger_new(ssh->conf, &ssh_backend, ssh);
+
+ sfree(s->vstring);
+
+ crFinish(0);
+}
+
+static int do_ssh_connection_init(Ssh ssh, unsigned char c)
+{
+ /*
+ * Ordinary SSH begins with the banner "SSH-x.y-...". This is just
+ * the ssh-connection part, extracted and given a trivial binary
+ * packet protocol, so we replace 'SSH-' at the start with a new
+ * name. In proper SSH style (though of course this part of the
+ * proper SSH protocol _isn't_ subject to this kind of
+ * DNS-domain-based extension), we define the new name in our
+ * extension space.
+ */
+ static const char protoname[] =
+ "SSHCONNECTION@putty.projects.tartarus.org-";
+
+ struct do_ssh_connection_init_state {
+ int crLine;
+ int vslen;
+ char version[10];
+ char *vstring;
+ int vstrsize;
+ int i;
+ };
+ crState(do_ssh_connection_init_state);
+
+ crBeginState;
+
+ /* Search for a line beginning with the protocol name prefix in
+ * the input. */
+ for (;;) {
+ for (s->i = 0; protoname[s->i]; s->i++) {
+ if ((char)c != protoname[s->i]) goto no;
+ crReturn(1);
+ }
+ break;
+ no:
+ while (c != '\012')
+ crReturn(1);
+ crReturn(1);
+ }
+
+ s->vstrsize = sizeof(protoname) + 16;
+ s->vstring = snewn(s->vstrsize, char);
+ strcpy(s->vstring, protoname);
+ s->vslen = strlen(protoname);
+ s->i = 0;
+ while (1) {
+ if (s->vslen >= s->vstrsize - 1) {
+ s->vstrsize += 16;
+ s->vstring = sresize(s->vstring, s->vstrsize, char);
+ }
+ s->vstring[s->vslen++] = c;
+ if (s->i >= 0) {
+ if (c == '-') {
+ s->version[s->i] = '\0';
+ s->i = -1;
+ } else if (s->i < sizeof(s->version) - 1)
+ s->version[s->i++] = c;
+ } else if (c == '\012')
+ break;
+ crReturn(1); /* get another char */
+ }
+
+ ssh->agentfwd_enabled = FALSE;
+ ssh->rdpkt2_bare_state.incoming_sequence = 0;
+
+ s->vstring[s->vslen] = 0;
+ s->vstring[strcspn(s->vstring, "\015\012")] = '\0';/* remove EOL chars */
+ logeventf(ssh, "Server version: %s", s->vstring);
+ ssh_detect_bugs(ssh, s->vstring);
+
+ /*
+ * Decide which SSH protocol version to support. This is easy in
+ * bare ssh-connection mode: only 2.0 is legal.
+ */
+ if (ssh_versioncmp(s->version, "2.0") < 0) {
+ bombout(("Server announces compatibility with SSH-1 in bare ssh-connection protocol"));
+ crStop(0);
+ }
+ if (conf_get_int(ssh->conf, CONF_sshprot) == 0) {
+ bombout(("Bare ssh-connection protocol cannot be run in SSH-1-only mode"));
+ crStop(0);
+ }
+
+ ssh->version = 2;
+
+ logeventf(ssh, "Using bare ssh-connection protocol");
+
+ /* Send the version string, if we haven't already */
+ ssh_send_verstring(ssh, protoname, s->version);
+
+ /*
+ * Initialise bare connection protocol.
+ */
+ ssh->protocol = ssh2_bare_connection_protocol;
+ ssh2_bare_connection_protocol_setup(ssh);
+ ssh->s_rdpkt = ssh2_bare_connection_rdpkt;
+
+ update_specials_menu(ssh->frontend);
+ ssh->state = SSH_STATE_BEFORE_SIZE;
+ ssh->pinger = pinger_new(ssh->conf, &ssh_backend, ssh);
+
+ /*
+ * Get authconn (really just conn) under way.
+ */
+ do_ssh2_authconn(ssh, NULL, 0, NULL);
+
+ sfree(s->vstring);
+
+ crFinish(0);
+}
+
+static void ssh_process_incoming_data(Ssh ssh,
+ unsigned char **data, int *datalen)
+{
+ struct Packet *pktin;
+
+ pktin = ssh->s_rdpkt(ssh, data, datalen);
+ if (pktin) {
+ ssh->protocol(ssh, NULL, 0, pktin);
+ ssh_free_packet(pktin);
+ }
+}
+
+static void ssh_queue_incoming_data(Ssh ssh,
+ unsigned char **data, int *datalen)
+{
+ bufchain_add(&ssh->queued_incoming_data, *data, *datalen);
+ *data += *datalen;
+ *datalen = 0;
+}
+
+static void ssh_process_queued_incoming_data(Ssh ssh)
+{
+ void *vdata;
+ unsigned char *data;
+ int len, origlen;
+
+ while (!ssh->frozen && bufchain_size(&ssh->queued_incoming_data)) {
+ bufchain_prefix(&ssh->queued_incoming_data, &vdata, &len);
+ data = vdata;
+ origlen = len;
+
+ while (!ssh->frozen && len > 0)
+ ssh_process_incoming_data(ssh, &data, &len);
+
+ if (origlen > len)
+ bufchain_consume(&ssh->queued_incoming_data, origlen - len);
+ }
+}
+
+static void ssh_set_frozen(Ssh ssh, int frozen)
+{
+ if (ssh->s)
+ sk_set_frozen(ssh->s, frozen);
+ ssh->frozen = frozen;
+}
+
+static void ssh_gotdata(Ssh ssh, unsigned char *data, int datalen)
+{
+ /* Log raw data, if we're in that mode. */
+ if (ssh->logctx)
+ log_packet(ssh->logctx, PKT_INCOMING, -1, NULL, data, datalen,
+ 0, NULL, NULL, 0, NULL);
+
+ crBegin(ssh->ssh_gotdata_crstate);
+
+ /*
+ * To begin with, feed the characters one by one to the
+ * protocol initialisation / selection function do_ssh_init().
+ * When that returns 0, we're done with the initial greeting
+ * exchange and can move on to packet discipline.
+ */
+ while (1) {
+ int ret; /* need not be kept across crReturn */
+ if (datalen == 0)
+ crReturnV; /* more data please */
+ ret = ssh->do_ssh_init(ssh, *data);
+ data++;
+ datalen--;
+ if (ret == 0)
+ break;
+ }
+
+ /*
+ * We emerge from that loop when the initial negotiation is
+ * over and we have selected an s_rdpkt function. Now pass
+ * everything to s_rdpkt, and then pass the resulting packets
+ * to the proper protocol handler.
+ */
+
+ while (1) {
+ while (bufchain_size(&ssh->queued_incoming_data) > 0 || datalen > 0) {
+ if (ssh->frozen) {
+ ssh_queue_incoming_data(ssh, &data, &datalen);
+ /* This uses up all data and cannot cause anything interesting
+ * to happen; indeed, for anything to happen at all, we must
+ * return, so break out. */
+ break;
+ } else if (bufchain_size(&ssh->queued_incoming_data) > 0) {
+ /* This uses up some or all data, and may freeze the
+ * session. */
+ ssh_process_queued_incoming_data(ssh);
+ } else {
+ /* This uses up some or all data, and may freeze the
+ * session. */
+ ssh_process_incoming_data(ssh, &data, &datalen);
+ }
+ /* FIXME this is probably EBW. */
+ if (ssh->state == SSH_STATE_CLOSED)
+ return;
+ }
+ /* We're out of data. Go and get some more. */
+ crReturnV;
+ }
+ crFinishV;
+}
+
+static int ssh_do_close(Ssh ssh, int notify_exit)
+{
+ int ret = 0;
+ struct ssh_channel *c;
+
+ ssh->state = SSH_STATE_CLOSED;
+ expire_timer_context(ssh);
+ if (ssh->s) {
+ sk_close(ssh->s);
+ ssh->s = NULL;
+ if (notify_exit)
+ notify_remote_exit(ssh->frontend);
+ else
+ ret = 1;
+ }
+ /*
+ * Now we must shut down any port- and X-forwarded channels going
+ * through this connection.
+ */
+ if (ssh->channels) {
+ while (NULL != (c = index234(ssh->channels, 0))) {
+ switch (c->type) {
+ case CHAN_X11:
+ x11_close(c->u.x11.xconn);
+ break;
+ case CHAN_SOCKDATA:
+ case CHAN_SOCKDATA_DORMANT:
+ pfd_close(c->u.pfd.pf);
+ break;
+ }
+ del234(ssh->channels, c); /* moving next one to index 0 */
+ if (ssh->version == 2)
+ bufchain_clear(&c->v.v2.outbuffer);
+ sfree(c);
+ }
+ }
+ /*
+ * Go through port-forwardings, and close any associated
+ * listening sockets.
+ */
+ if (ssh->portfwds) {
+ struct ssh_portfwd *pf;
+ while (NULL != (pf = index234(ssh->portfwds, 0))) {
+ /* Dispose of any listening socket. */
+ if (pf->local)
+ pfl_terminate(pf->local);
+ del234(ssh->portfwds, pf); /* moving next one to index 0 */
+ free_portfwd(pf);
+ }
+ freetree234(ssh->portfwds);
+ ssh->portfwds = NULL;
+ }
+
+ return ret;
+}
+
+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);
+ }
+
+ logevent(msg);
+ sfree(msg);
+ }
+}
+
+void ssh_connshare_log(Ssh ssh, int event, const char *logtext,
+ const char *ds_err, const char *us_err)
+{
+ if (event == SHARE_NONE) {
+ /* In this case, 'logtext' is an error message indicating a
+ * reason why connection sharing couldn't be set up _at all_.
+ * Failing that, ds_err and us_err indicate why we couldn't be
+ * a downstream and an upstream respectively. */
+ if (logtext) {
+ logeventf(ssh, "Could not set up connection sharing: %s", logtext);
+ } else {
+ if (ds_err)
+ logeventf(ssh, "Could not set up connection sharing"
+ " as downstream: %s", ds_err);
+ if (us_err)
+ logeventf(ssh, "Could not set up connection sharing"
+ " as upstream: %s", us_err);
+ }
+ } else if (event == SHARE_DOWNSTREAM) {
+ /* In this case, 'logtext' is a local endpoint address */
+ logeventf(ssh, "Using existing shared connection at %s", logtext);
+ /* Also we should mention this in the console window to avoid
+ * confusing users as to why this window doesn't behave the
+ * usual way. */
+ if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) {
+ c_write_str(ssh,"Reusing a shared connection to this server.\r\n");
+ }
+ } else if (event == SHARE_UPSTREAM) {
+ /* In this case, 'logtext' is a local endpoint address too */
+ logeventf(ssh, "Sharing this connection at %s", logtext);
+ }
+}
+
+static int ssh_closing(Plug plug, const char *error_msg, int error_code,
+ int calling_back)
+{
+ Ssh ssh = (Ssh) plug;
+ int need_notify = ssh_do_close(ssh, FALSE);
+
+ if (!error_msg) {
+ if (!ssh->close_expected)
+ error_msg = "Server unexpectedly closed network connection";
+ else
+ error_msg = "Server closed network connection";
+ }
+
+ if (ssh->close_expected && ssh->clean_exit && ssh->exitcode < 0)
+ ssh->exitcode = 0;
+
+ if (need_notify)
+ notify_remote_exit(ssh->frontend);
+
+ if (error_msg)
+ logevent(error_msg);
+ if (!ssh->close_expected || !ssh->clean_exit)
+ connection_fatal(ssh->frontend, "%s", error_msg);
+ return 0;
+}
+
+static int ssh_receive(Plug plug, int urgent, char *data, int len)
+{
+ Ssh ssh = (Ssh) plug;
+ ssh_gotdata(ssh, (unsigned char *)data, len);
+ if (ssh->state == SSH_STATE_CLOSED) {
+ ssh_do_close(ssh, TRUE);
+ return 0;
+ }
+ return 1;
+}
+
+static void ssh_sent(Plug plug, int bufsize)
+{
+ Ssh ssh = (Ssh) plug;
+ /*
+ * If the send backlog on the SSH socket itself clears, we
+ * should unthrottle the whole world if it was throttled.
+ */
+ if (bufsize < SSH_MAX_BACKLOG)
+ 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 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;
+
+ loghost = conf_get_str(ssh->conf, CONF_loghost);
+ if (*loghost) {
+ char *tmphost;
+ char *colon;
+
+ tmphost = dupstr(loghost);
+ ssh->savedport = 22; /* default ssh port */
+
+ /*
+ * A colon suffix on the hostname string also lets us affect
+ * savedport. (Unless there are multiple colons, in which case
+ * we assume this is an unbracketed IPv6 literal.)
+ */
+ colon = host_strrchr(tmphost, ':');
+ if (colon && colon == host_strchr(tmphost, ':')) {
+ *colon++ = '\0';
+ if (*colon)
+ ssh->savedport = atoi(colon);
+ }
+
+ ssh->savedhost = host_strduptrim(tmphost);
+ sfree(tmphost);
+ } else {
+ ssh->savedhost = host_strduptrim(host);
+ if (port < 0)
+ port = 22; /* default ssh port */
+ ssh->savedport = port;
+ }
+
+ ssh->fn = &fn_table; /* make 'ssh' usable as a Plug */
+
+ /*
+ * Try connection-sharing, in case that means we don't open a
+ * socket after all. ssh_connection_sharing_init will connect to a
+ * previously established upstream if it can, and failing that,
+ * establish a listening socket for _us_ to be the upstream. In
+ * the latter case it will return NULL just as if it had done
+ * nothing, because here we only need to care if we're a
+ * downstream and need to do our connection setup differently.
+ */
+ ssh->connshare = NULL;
+ ssh->attempting_connshare = TRUE; /* affects socket logging behaviour */
+ ssh->s = ssh_connection_sharing_init(ssh->savedhost, ssh->savedport,
+ ssh->conf, ssh, &ssh->connshare);
+ ssh->attempting_connshare = FALSE;
+ if (ssh->s != NULL) {
+ /*
+ * We are a downstream.
+ */
+ ssh->bare_connection = TRUE;
+ ssh->do_ssh_init = do_ssh_connection_init;
+ ssh->fullhostname = NULL;
+ *realhost = dupstr(host); /* best we can do */
+ } else {
+ /*
+ * We're not a downstream, so open a normal socket.
+ */
+ ssh->do_ssh_init = do_ssh_init;
+
+ /*
+ * 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);
+ if ((err = sk_addr_error(addr)) != NULL) {
+ sk_addr_free(addr);
+ return err;
+ }
+ ssh->fullhostname = dupstr(*realhost); /* save in case of GSSAPI */
+
+ ssh->s = new_connection(addr, *realhost, port,
+ 0, 1, nodelay, keepalive,
+ (Plug) ssh, ssh->conf);
+ if ((err = sk_socket_error(ssh->s)) != NULL) {
+ ssh->s = NULL;
+ notify_remote_exit(ssh->frontend);
+ return err;
+ }
+ }
+
+ /*
+ * If the SSH version number's fixed, set it now, and if it's SSH-2,
+ * send the version string too.
+ */
+ sshprot = conf_get_int(ssh->conf, CONF_sshprot);
+ if (sshprot == 0)
+ ssh->version = 1;
+ if (sshprot == 3 && !ssh->bare_connection) {
+ ssh->version = 2;
+ ssh_send_verstring(ssh, "SSH-", NULL);
+ }
+
+ /*
+ * loghost, if configured, overrides realhost.
+ */
+ if (*loghost) {
+ sfree(*realhost);
+ *realhost = dupstr(loghost);
+ }
+
+ return NULL;
+}
+
+/*
+ * Throttle or unthrottle the SSH connection.
+ */
+static void ssh_throttle_conn(Ssh ssh, int adjust)
+{
+ int old_count = ssh->conn_throttle_count;
+ ssh->conn_throttle_count += adjust;
+ assert(ssh->conn_throttle_count >= 0);
+ if (ssh->conn_throttle_count && !old_count) {
+ ssh_set_frozen(ssh, 1);
+ } else if (!ssh->conn_throttle_count && old_count) {
+ ssh_set_frozen(ssh, 0);
+ }
+}
+
+/*
+ * Throttle or unthrottle _all_ local data streams (for when sends
+ * on the SSH connection itself back up).
+ */
+static void ssh_throttle_all(Ssh ssh, int enable, int bufsize)
+{
+ int i;
+ struct ssh_channel *c;
+
+ if (enable == ssh->throttled_all)
+ return;
+ ssh->throttled_all = enable;
+ ssh->overall_bufsize = bufsize;
+ if (!ssh->channels)
+ return;
+ for (i = 0; NULL != (c = index234(ssh->channels, i)); i++) {
+ switch (c->type) {
+ case CHAN_MAINSESSION:
+ /*
+ * This is treated separately, outside the switch.
+ */
+ break;
+ case CHAN_X11:
+ x11_override_throttle(c->u.x11.xconn, enable);
+ break;
+ case CHAN_AGENT:
+ /* Agent channels require no buffer management. */
+ break;
+ case CHAN_SOCKDATA:
+ pfd_override_throttle(c->u.pfd.pf, enable);
+ break;
+ }
+ }
+}
+
+static void ssh_agent_callback(void *sshv, void *reply, int replylen)
+{
+ Ssh ssh = (Ssh) sshv;
+
+ ssh->agent_response = reply;
+ ssh->agent_response_len = replylen;
+
+ if (ssh->version == 1)
+ do_ssh1_login(ssh, NULL, -1, NULL);
+ else
+ do_ssh2_authconn(ssh, NULL, -1, NULL);
+}
+
+static void ssh_dialog_callback(void *sshv, int ret)
+{
+ Ssh ssh = (Ssh) sshv;
+
+ ssh->user_response = ret;
+
+ if (ssh->version == 1)
+ do_ssh1_login(ssh, NULL, -1, NULL);
+ else
+ do_ssh2_transport(ssh, NULL, -1, NULL);
+
+ /*
+ * This may have unfrozen the SSH connection, so do a
+ * queued-data run.
+ */
+ ssh_process_queued_incoming_data(ssh);
+}
+
+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;
+
+ c->u.a.outstanding_requests--;
+ if (!sentreply) {
+ /* Fake SSH_AGENT_FAILURE. */
+ sentreply = "\0\0\0\1\5";
+ replylen = 5;
+ }
+ if (ssh->version == 2) {
+ ssh2_add_channel_data(c, sentreply, replylen);
+ ssh2_try_send(c);
+ } else {
+ send_packet(ssh, SSH1_MSG_CHANNEL_DATA,
+ PKT_INT, c->remoteid,
+ PKT_INT, replylen,
+ PKT_DATA, sentreply, replylen,
+ PKT_END);
+ }
+ if (reply)
+ sfree(reply);
+ /*
+ * If we've already seen an incoming EOF but haven't sent an
+ * outgoing one, this may be the moment to send it.
+ */
+ if (c->u.a.outstanding_requests == 0 && (c->closes & CLOSES_RCVD_EOF))
+ sshfwd_write_eof(c);
+}
+
+/*
+ * Client-initiated disconnection. Send a DISCONNECT if `wire_reason'
+ * 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,
+ int code, int clean_exit)
+{
+ char *error;
+ if (!client_reason)
+ client_reason = wire_reason;
+ if (client_reason)
+ error = dupprintf("Disconnected: %s", client_reason);
+ else
+ error = dupstr("Disconnected");
+ if (wire_reason) {
+ if (ssh->version == 1) {
+ send_packet(ssh, SSH1_MSG_DISCONNECT, PKT_STR, wire_reason,
+ PKT_END);
+ } else if (ssh->version == 2) {
+ struct Packet *pktout = ssh2_pkt_init(SSH2_MSG_DISCONNECT);
+ ssh2_pkt_adduint32(pktout, code);
+ ssh2_pkt_addstring(pktout, wire_reason);
+ ssh2_pkt_addstring(pktout, "en"); /* language tag */
+ ssh2_pkt_send_noqueue(ssh, pktout);
+ }
+ }
+ ssh->close_expected = TRUE;
+ ssh->clean_exit = clean_exit;
+ ssh_closing((Plug)ssh, error, 0, 0);
+ sfree(error);
+}
+
+/*
+ * Handle the key exchange and user authentication phases.
+ */
+static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
+ struct Packet *pktin)
+{
+ int i, j, ret;
+ unsigned char cookie[8], *ptr;
+ struct MD5Context md5c;
+ struct do_ssh1_login_state {
+ int crLine;
+ int len;
+ unsigned char *rsabuf, *keystr1, *keystr2;
+ unsigned long supported_ciphers_mask, supported_auths_mask;
+ int tried_publickey, tried_agent;
+ int tis_auth_refused, ccard_auth_refused;
+ unsigned char session_id[16];
+ int cipher_type;
+ void *publickey_blob;
+ int publickey_bloblen;
+ char *publickey_comment;
+ int publickey_encrypted;
+ prompts_t *cur_prompt;
+ char c;
+ int pwpkt_type;
+ unsigned char request[5], *response, *p;
+ int responselen;
+ int keyi, nkeys;
+ int authed;
+ struct RSAKey key;
+ Bignum challenge;
+ char *commentp;
+ int commentlen;
+ int dlgret;
+ Filename *keyfile;
+ struct RSAKey servkey, hostkey;
+ };
+ crState(do_ssh1_login_state);
+
+ crBeginState;
+
+ if (!pktin)
+ crWaitUntil(pktin);
+
+ if (pktin->type != SSH1_SMSG_PUBLIC_KEY) {
+ bombout(("Public key packet not received"));
+ crStop(0);
+ }
+
+ logevent("Received public keys");
+
+ ptr = ssh_pkt_getdata(pktin, 8);
+ if (!ptr) {
+ bombout(("SSH-1 public key packet stopped before random cookie"));
+ crStop(0);
+ }
+ memcpy(cookie, ptr, 8);
+
+ if (!ssh1_pkt_getrsakey(pktin, &s->servkey, &s->keystr1) ||
+ !ssh1_pkt_getrsakey(pktin, &s->hostkey, &s->keystr2)) {
+ bombout(("Failed to read SSH-1 public keys from public key packet"));
+ crStop(0);
+ }
+
+ /*
+ * Log the host key fingerprint.
+ */
+ {
+ char logmsg[80];
+ logevent("Host key fingerprint is:");
+ strcpy(logmsg, " ");
+ s->hostkey.comment = NULL;
+ rsa_fingerprint(logmsg + strlen(logmsg),
+ sizeof(logmsg) - strlen(logmsg), &s->hostkey);
+ logevent(logmsg);
+ }
+
+ ssh->v1_remote_protoflags = ssh_pkt_getuint32(pktin);
+ s->supported_ciphers_mask = ssh_pkt_getuint32(pktin);
+ s->supported_auths_mask = ssh_pkt_getuint32(pktin);
+ if ((ssh->remote_bugs & BUG_CHOKES_ON_RSA))
+ s->supported_auths_mask &= ~(1 << SSH1_AUTH_RSA);
+
+ ssh->v1_local_protoflags =
+ ssh->v1_remote_protoflags & SSH1_PROTOFLAGS_SUPPORTED;
+ ssh->v1_local_protoflags |= SSH1_PROTOFLAG_SCREEN_NUMBER;
+
+ MD5Init(&md5c);
+ MD5Update(&md5c, s->keystr2, s->hostkey.bytes);
+ MD5Update(&md5c, s->keystr1, s->servkey.bytes);
+ MD5Update(&md5c, cookie, 8);
+ MD5Final(s->session_id, &md5c);
+
+ for (i = 0; i < 32; i++)
+ ssh->session_key[i] = random_byte();
+
+ /*
+ * Verify that the `bits' and `bytes' parameters match.
+ */
+ if (s->hostkey.bits > s->hostkey.bytes * 8 ||
+ s->servkey.bits > s->servkey.bytes * 8) {
+ bombout(("SSH-1 public keys were badly formatted"));
+ crStop(0);
+ }
+
+ s->len = (s->hostkey.bytes > s->servkey.bytes ?
+ s->hostkey.bytes : s->servkey.bytes);
+
+ s->rsabuf = snewn(s->len, unsigned char);
+
+ /*
+ * Verify the host key.
+ */
+ {
+ /*
+ * First format the key into a string.
+ */
+ int len = rsastr_len(&s->hostkey);
+ char fingerprint[100];
+ char *keystr = snewn(len, char);
+ rsastr_fmt(keystr, &s->hostkey);
+ rsa_fingerprint(fingerprint, sizeof(fingerprint), &s->hostkey);
+
+ ssh_set_frozen(ssh, 1);
+ s->dlgret = verify_ssh_host_key(ssh->frontend,
+ ssh->savedhost, ssh->savedport,
+ "rsa", keystr, fingerprint,
+ ssh_dialog_callback, ssh);
+ sfree(keystr);
+ if (s->dlgret < 0) {
+ do {
+ crReturn(0);
+ if (pktin) {
+ bombout(("Unexpected data from server while waiting"
+ " for user host key response"));
+ crStop(0);
+ }
+ } while (pktin || inlen > 0);
+ s->dlgret = ssh->user_response;
+ }
+ ssh_set_frozen(ssh, 0);
+
+ if (s->dlgret == 0) {
+ ssh_disconnect(ssh, "User aborted at host key verification",
+ NULL, 0, TRUE);
+ crStop(0);
+ }
+ }
+
+ for (i = 0; i < 32; i++) {
+ s->rsabuf[i] = ssh->session_key[i];
+ if (i < 16)
+ s->rsabuf[i] ^= s->session_id[i];
+ }
+
+ if (s->hostkey.bytes > s->servkey.bytes) {
+ ret = rsaencrypt(s->rsabuf, 32, &s->servkey);
+ if (ret)
+ ret = rsaencrypt(s->rsabuf, s->servkey.bytes, &s->hostkey);
+ } else {
+ ret = rsaencrypt(s->rsabuf, 32, &s->hostkey);
+ if (ret)
+ ret = rsaencrypt(s->rsabuf, s->hostkey.bytes, &s->servkey);
+ }
+ if (!ret) {
+ bombout(("SSH-1 public key encryptions failed due to bad formatting"));
+ crStop(0);
+ }
+
+ logevent("Encrypted session key");
+
+ {
+ int cipher_chosen = 0, warn = 0;
+ char *cipher_string = NULL;
+ int i;
+ for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) {
+ int next_cipher = conf_get_int_int(ssh->conf,
+ CONF_ssh_cipherlist, i);
+ if (next_cipher == CIPHER_WARN) {
+ /* If/when we choose a cipher, warn about it */
+ warn = 1;
+ } else if (next_cipher == CIPHER_AES) {
+ /* XXX Probably don't need to mention this. */
+ logevent("AES not supported in SSH-1, skipping");
+ } else {
+ switch (next_cipher) {
+ case CIPHER_3DES: s->cipher_type = SSH_CIPHER_3DES;
+ cipher_string = "3DES"; break;
+ case CIPHER_BLOWFISH: s->cipher_type = SSH_CIPHER_BLOWFISH;
+ cipher_string = "Blowfish"; break;
+ case CIPHER_DES: s->cipher_type = SSH_CIPHER_DES;
+ cipher_string = "single-DES"; break;
+ }
+ if (s->supported_ciphers_mask & (1 << s->cipher_type))
+ cipher_chosen = 1;
+ }
+ }
+ if (!cipher_chosen) {
+ if ((s->supported_ciphers_mask & (1 << SSH_CIPHER_3DES)) == 0)
+ bombout(("Server violates SSH-1 protocol by not "
+ "supporting 3DES encryption"));
+ else
+ /* shouldn't happen */
+ bombout(("No supported ciphers found"));
+ crStop(0);
+ }
+
+ /* Warn about chosen cipher if necessary. */
+ if (warn) {
+ ssh_set_frozen(ssh, 1);
+ s->dlgret = askalg(ssh->frontend, "cipher", cipher_string,
+ ssh_dialog_callback, ssh);
+ if (s->dlgret < 0) {
+ do {
+ crReturn(0);
+ if (pktin) {
+ bombout(("Unexpected data from server while waiting"
+ " for user response"));
+ crStop(0);
+ }
+ } while (pktin || inlen > 0);
+ s->dlgret = ssh->user_response;
+ }
+ ssh_set_frozen(ssh, 0);
+ if (s->dlgret == 0) {
+ ssh_disconnect(ssh, "User aborted at cipher warning", NULL,
+ 0, TRUE);
+ crStop(0);
+ }
+ }
+ }
+
+ switch (s->cipher_type) {
+ case SSH_CIPHER_3DES:
+ logevent("Using 3DES encryption");
+ break;
+ case SSH_CIPHER_DES:
+ logevent("Using single-DES encryption");
+ break;
+ case SSH_CIPHER_BLOWFISH:
+ logevent("Using Blowfish encryption");
+ break;
+ }
+
+ send_packet(ssh, SSH1_CMSG_SESSION_KEY,
+ PKT_CHAR, s->cipher_type,
+ PKT_DATA, cookie, 8,
+ PKT_CHAR, (s->len * 8) >> 8, PKT_CHAR, (s->len * 8) & 0xFF,
+ PKT_DATA, s->rsabuf, s->len,
+ PKT_INT, ssh->v1_local_protoflags, PKT_END);
+
+ logevent("Trying to enable encryption...");
+
+ sfree(s->rsabuf);
+
+ ssh->cipher = (s->cipher_type == SSH_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 :
+ s->cipher_type == SSH_CIPHER_DES ? &ssh_des :
+ &ssh_3des);
+ ssh->v1_cipher_ctx = ssh->cipher->make_context();
+ ssh->cipher->sesskey(ssh->v1_cipher_ctx, ssh->session_key);
+ logeventf(ssh, "Initialised %s encryption", ssh->cipher->text_name);
+
+ ssh->crcda_ctx = crcda_make_context();
+ logevent("Installing CRC compensation attack detector");
+
+ if (s->servkey.modulus) {
+ sfree(s->servkey.modulus);
+ s->servkey.modulus = NULL;
+ }
+ if (s->servkey.exponent) {
+ sfree(s->servkey.exponent);
+ s->servkey.exponent = NULL;
+ }
+ if (s->hostkey.modulus) {
+ sfree(s->hostkey.modulus);
+ s->hostkey.modulus = NULL;
+ }
+ if (s->hostkey.exponent) {
+ sfree(s->hostkey.exponent);
+ s->hostkey.exponent = NULL;
+ }
+ crWaitUntil(pktin);
+
+ if (pktin->type != SSH1_SMSG_SUCCESS) {
+ bombout(("Encryption not successfully enabled"));
+ crStop(0);
+ }
+
+ logevent("Successfully started encryption");
+
+ fflush(stdout); /* FIXME eh? */
+ {
+ if ((ssh->username = get_remote_username(ssh->conf)) == NULL) {
+ int ret; /* need not be kept over crReturn */
+ s->cur_prompt = new_prompts(ssh->frontend);
+ s->cur_prompt->to_server = TRUE;
+ s->cur_prompt->name = dupstr("SSH login name");
+ add_prompt(s->cur_prompt, dupstr("login as: "), TRUE);
+ ret = get_userpass_input(s->cur_prompt, NULL, 0);
+ while (ret < 0) {
+ ssh->send_ok = 1;
+ crWaitUntil(!pktin);
+ ret = get_userpass_input(s->cur_prompt, in, inlen);
+ ssh->send_ok = 0;
+ }
+ if (!ret) {
+ /*
+ * Failed to get a username. Terminate.
+ */
+ free_prompts(s->cur_prompt);
+ ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE);
+ crStop(0);
+ }
+ ssh->username = dupstr(s->cur_prompt->prompts[0]->result);
+ free_prompts(s->cur_prompt);
+ }
+
+ send_packet(ssh, SSH1_CMSG_USER, PKT_STR, ssh->username, PKT_END);
+ {
+ char *userlog = dupprintf("Sent username \"%s\"", ssh->username);
+ logevent(userlog);
+ if (flags & FLAG_INTERACTIVE &&
+ (!((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)))) {
+ c_write_str(ssh, userlog);
+ c_write_str(ssh, "\r\n");
+ }
+ sfree(userlog);
+ }
+ }
+
+ crWaitUntil(pktin);
+
+ if ((s->supported_auths_mask & (1 << SSH1_AUTH_RSA)) == 0) {
+ /* We must not attempt PK auth. Pretend we've already tried it. */
+ s->tried_publickey = s->tried_agent = 1;
+ } else {
+ s->tried_publickey = s->tried_agent = 0;
+ }
+ s->tis_auth_refused = s->ccard_auth_refused = 0;
+ /*
+ * Load the public half of any configured keyfile for later use.
+ */
+ s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile);
+ if (!filename_is_null(s->keyfile)) {
+ int keytype;
+ logeventf(ssh, "Reading private key file \"%.150s\"",
+ filename_to_str(s->keyfile));
+ keytype = key_type(s->keyfile);
+ if (keytype == SSH_KEYTYPE_SSH1) {
+ 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);
+ } else {
+ char *msgbuf;
+ logeventf(ssh, "Unable to load private key (%s)", error);
+ msgbuf = dupprintf("Unable to load private key file "
+ "\"%.150s\" (%s)\r\n",
+ filename_to_str(s->keyfile),
+ error);
+ c_write_str(ssh, msgbuf);
+ sfree(msgbuf);
+ s->publickey_blob = NULL;
+ }
+ } else {
+ char *msgbuf;
+ logeventf(ssh, "Unable to use this key file (%s)",
+ key_type_to_str(keytype));
+ msgbuf = dupprintf("Unable to use key file \"%.150s\""
+ " (%s)\r\n",
+ filename_to_str(s->keyfile),
+ key_type_to_str(keytype));
+ c_write_str(ssh, msgbuf);
+ sfree(msgbuf);
+ s->publickey_blob = NULL;
+ }
+ } else
+ s->publickey_blob = NULL;
+
+ while (pktin->type == SSH1_SMSG_FAILURE) {
+ s->pwpkt_type = SSH1_CMSG_AUTH_PASSWORD;
+
+ if (conf_get_int(ssh->conf, CONF_tryagent) && agent_exists() && !s->tried_agent) {
+ /*
+ * Attempt RSA authentication using Pageant.
+ */
+ void *r;
+
+ s->authed = FALSE;
+ s->tried_agent = 1;
+ logevent("Pageant is running. Requesting keys.");
+
+ /* Request the keys held by the agent. */
+ PUT_32BIT(s->request, 1);
+ s->request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
+ if (!agent_query(s->request, 5, &r, &s->responselen,
+ ssh_agent_callback, ssh)) {
+ do {
+ crReturn(0);
+ if (pktin) {
+ bombout(("Unexpected data from server while waiting"
+ " for agent response"));
+ crStop(0);
+ }
+ } while (pktin || inlen > 0);
+ r = ssh->agent_response;
+ s->responselen = ssh->agent_response_len;
+ }
+ s->response = (unsigned char *) r;
+ if (s->response && s->responselen >= 5 &&
+ s->response[4] == SSH1_AGENT_RSA_IDENTITIES_ANSWER) {
+ s->p = s->response + 5;
+ s->nkeys = toint(GET_32BIT(s->p));
+ if (s->nkeys < 0) {
+ logeventf(ssh, "Pageant reported negative key count %d",
+ s->nkeys);
+ s->nkeys = 0;
+ }
+ s->p += 4;
+ logeventf(ssh, "Pageant has %d SSH-1 keys", s->nkeys);
+ for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) {
+ unsigned char *pkblob = s->p;
+ s->p += 4;
+ {
+ int n, ok = FALSE;
+ do { /* do while (0) to make breaking easy */
+ n = ssh1_read_bignum
+ (s->p, toint(s->responselen-(s->p-s->response)),
+ &s->key.exponent);
+ if (n < 0)
+ break;
+ s->p += n;
+ n = ssh1_read_bignum
+ (s->p, toint(s->responselen-(s->p-s->response)),
+ &s->key.modulus);
+ if (n < 0)
+ break;
+ s->p += n;
+ if (s->responselen - (s->p-s->response) < 4)
+ break;
+ s->commentlen = toint(GET_32BIT(s->p));
+ s->p += 4;
+ if (s->commentlen < 0 ||
+ toint(s->responselen - (s->p-s->response)) <
+ s->commentlen)
+ break;
+ s->commentp = (char *)s->p;
+ s->p += s->commentlen;
+ ok = TRUE;
+ } while (0);
+ if (!ok) {
+ logevent("Pageant key list packet was truncated");
+ break;
+ }
+ }
+ if (s->publickey_blob) {
+ if (!memcmp(pkblob, s->publickey_blob,
+ s->publickey_bloblen)) {
+ logeventf(ssh, "Pageant key #%d matches "
+ "configured key file", s->keyi);
+ s->tried_publickey = 1;
+ } else
+ /* Skip non-configured key */
+ continue;
+ }
+ logeventf(ssh, "Trying Pageant key #%d", s->keyi);
+ send_packet(ssh, SSH1_CMSG_AUTH_RSA,
+ PKT_BIGNUM, s->key.modulus, PKT_END);
+ crWaitUntil(pktin);
+ if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
+ logevent("Key refused");
+ continue;
+ }
+ logevent("Received RSA challenge");
+ if ((s->challenge = ssh1_pkt_getmp(pktin)) == NULL) {
+ bombout(("Server's RSA challenge was badly formatted"));
+ crStop(0);
+ }
+
+ {
+ char *agentreq, *q, *ret;
+ void *vret;
+ int len, retlen;
+ len = 1 + 4; /* message type, bit count */
+ len += ssh1_bignum_length(s->key.exponent);
+ len += ssh1_bignum_length(s->key.modulus);
+ len += ssh1_bignum_length(s->challenge);
+ len += 16; /* session id */
+ len += 4; /* response format */
+ agentreq = snewn(4 + len, char);
+ PUT_32BIT(agentreq, len);
+ q = agentreq + 4;
+ *q++ = SSH1_AGENTC_RSA_CHALLENGE;
+ PUT_32BIT(q, bignum_bitcount(s->key.modulus));
+ q += 4;
+ q += ssh1_write_bignum(q, s->key.exponent);
+ q += ssh1_write_bignum(q, s->key.modulus);
+ q += ssh1_write_bignum(q, s->challenge);
+ memcpy(q, s->session_id, 16);
+ q += 16;
+ PUT_32BIT(q, 1); /* response format */
+ if (!agent_query(agentreq, len + 4, &vret, &retlen,
+ ssh_agent_callback, ssh)) {
+ sfree(agentreq);
+ do {
+ crReturn(0);
+ if (pktin) {
+ bombout(("Unexpected data from server"
+ " while waiting for agent"
+ " response"));
+ crStop(0);
+ }
+ } while (pktin || inlen > 0);
+ vret = ssh->agent_response;
+ retlen = ssh->agent_response_len;
+ } else
+ sfree(agentreq);
+ ret = vret;
+ if (ret) {
+ if (ret[4] == SSH1_AGENT_RSA_RESPONSE) {
+ logevent("Sending Pageant's response");
+ send_packet(ssh, SSH1_CMSG_AUTH_RSA_RESPONSE,
+ PKT_DATA, ret + 5, 16,
+ PKT_END);
+ sfree(ret);
+ crWaitUntil(pktin);
+ if (pktin->type == SSH1_SMSG_SUCCESS) {
+ logevent
+ ("Pageant's response accepted");
+ if (flags & FLAG_VERBOSE) {
+ c_write_str(ssh, "Authenticated using"
+ " RSA key \"");
+ c_write(ssh, s->commentp,
+ s->commentlen);
+ c_write_str(ssh, "\" from agent\r\n");
+ }
+ s->authed = TRUE;
+ } else
+ logevent
+ ("Pageant's response not accepted");
+ } else {
+ logevent
+ ("Pageant failed to answer challenge");
+ sfree(ret);
+ }
+ } else {
+ logevent("No reply received from Pageant");
+ }
+ }
+ freebn(s->key.exponent);
+ freebn(s->key.modulus);
+ freebn(s->challenge);
+ if (s->authed)
+ break;
+ }
+ sfree(s->response);
+ if (s->publickey_blob && !s->tried_publickey)
+ logevent("Configured key file not in Pageant");
+ } else {
+ logevent("Failed to get reply from Pageant");
+ }
+ if (s->authed)
+ break;
+ }
+ if (s->publickey_blob && !s->tried_publickey) {
+ /*
+ * Try public key authentication with the specified
+ * key file.
+ */
+ int got_passphrase; /* need not be kept over crReturn */
+ if (flags & FLAG_VERBOSE)
+ c_write_str(ssh, "Trying public key authentication.\r\n");
+ s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile);
+ logeventf(ssh, "Trying public key \"%s\"",
+ filename_to_str(s->keyfile));
+ s->tried_publickey = 1;
+ got_passphrase = FALSE;
+ while (!got_passphrase) {
+ /*
+ * Get a passphrase, if necessary.
+ */
+ char *passphrase = NULL; /* only written after crReturn */
+ const char *error;
+ if (!s->publickey_encrypted) {
+ if (flags & FLAG_VERBOSE)
+ c_write_str(ssh, "No passphrase required.\r\n");
+ passphrase = NULL;
+ } else {
+ int ret; /* need not be kept over crReturn */
+ s->cur_prompt = new_prompts(ssh->frontend);
+ s->cur_prompt->to_server = FALSE;
+ s->cur_prompt->name = dupstr("SSH key passphrase");
+ add_prompt(s->cur_prompt,
+ dupprintf("Passphrase for key \"%.100s\": ",
+ s->publickey_comment), FALSE);
+ ret = get_userpass_input(s->cur_prompt, NULL, 0);
+ while (ret < 0) {
+ ssh->send_ok = 1;
+ crWaitUntil(!pktin);
+ ret = get_userpass_input(s->cur_prompt, in, inlen);
+ ssh->send_ok = 0;
+ }
+ if (!ret) {
+ /* Failed to get a passphrase. Terminate. */
+ free_prompts(s->cur_prompt);
+ ssh_disconnect(ssh, NULL, "Unable to authenticate",
+ 0, TRUE);
+ crStop(0);
+ }
+ passphrase = dupstr(s->cur_prompt->prompts[0]->result);
+ free_prompts(s->cur_prompt);
+ }
+ /*
+ * Try decrypting key with passphrase.
+ */
+ s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile);
+ ret = loadrsakey(s->keyfile, &s->key, passphrase,
+ &error);
+ if (passphrase) {
+ smemclr(passphrase, strlen(passphrase));
+ sfree(passphrase);
+ }
+ if (ret == 1) {
+ /* Correct passphrase. */
+ got_passphrase = TRUE;
+ } else if (ret == 0) {
+ c_write_str(ssh, "Couldn't load private key from ");
+ c_write_str(ssh, filename_to_str(s->keyfile));
+ c_write_str(ssh, " (");
+ c_write_str(ssh, error);
+ c_write_str(ssh, ").\r\n");
+ got_passphrase = FALSE;
+ break; /* go and try something else */
+ } else if (ret == -1) {
+ c_write_str(ssh, "Wrong passphrase.\r\n"); /* FIXME */
+ got_passphrase = FALSE;
+ /* and try again */
+ } else {
+ assert(0 && "unexpected return from loadrsakey()");
+ got_passphrase = FALSE; /* placate optimisers */
+ }
+ }
+
+ if (got_passphrase) {
+
+ /*
+ * Send a public key attempt.
+ */
+ send_packet(ssh, SSH1_CMSG_AUTH_RSA,
+ PKT_BIGNUM, s->key.modulus, PKT_END);
+
+ crWaitUntil(pktin);
+ if (pktin->type == SSH1_SMSG_FAILURE) {
+ c_write_str(ssh, "Server refused our public key.\r\n");
+ continue; /* go and try something else */
+ }
+ if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
+ bombout(("Bizarre response to offer of public key"));
+ crStop(0);
+ }
+
+ {
+ int i;
+ unsigned char buffer[32];
+ Bignum challenge, response;
+
+ if ((challenge = ssh1_pkt_getmp(pktin)) == NULL) {
+ bombout(("Server's RSA challenge was badly formatted"));
+ crStop(0);
+ }
+ response = rsadecrypt(challenge, &s->key);
+ freebn(s->key.private_exponent);/* burn the evidence */
+
+ for (i = 0; i < 32; i++) {
+ buffer[i] = bignum_byte(response, 31 - i);
+ }
+
+ MD5Init(&md5c);
+ MD5Update(&md5c, buffer, 32);
+ MD5Update(&md5c, s->session_id, 16);
+ MD5Final(buffer, &md5c);
+
+ send_packet(ssh, SSH1_CMSG_AUTH_RSA_RESPONSE,
+ PKT_DATA, buffer, 16, PKT_END);
+
+ freebn(challenge);
+ freebn(response);
+ }
+
+ crWaitUntil(pktin);
+ if (pktin->type == SSH1_SMSG_FAILURE) {
+ if (flags & FLAG_VERBOSE)
+ c_write_str(ssh, "Failed to authenticate with"
+ " our public key.\r\n");
+ continue; /* go and try something else */
+ } else if (pktin->type != SSH1_SMSG_SUCCESS) {
+ bombout(("Bizarre response to RSA authentication response"));
+ crStop(0);
+ }
+
+ break; /* we're through! */
+ }
+
+ }
+
+ /*
+ * Otherwise, try various forms of password-like authentication.
+ */
+ s->cur_prompt = new_prompts(ssh->frontend);
+
+ if (conf_get_int(ssh->conf, CONF_try_tis_auth) &&
+ (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) &&
+ !s->tis_auth_refused) {
+ s->pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE;
+ logevent("Requested TIS authentication");
+ send_packet(ssh, SSH1_CMSG_AUTH_TIS, PKT_END);
+ crWaitUntil(pktin);
+ if (pktin->type != SSH1_SMSG_AUTH_TIS_CHALLENGE) {
+ logevent("TIS authentication declined");
+ if (flags & FLAG_INTERACTIVE)
+ c_write_str(ssh, "TIS authentication refused.\r\n");
+ s->tis_auth_refused = 1;
+ continue;
+ } else {
+ char *challenge;
+ int challengelen;
+ char *instr_suf, *prompt;
+
+ ssh_pkt_getstring(pktin, &challenge, &challengelen);
+ if (!challenge) {
+ bombout(("TIS challenge packet was badly formed"));
+ crStop(0);
+ }
+ logevent("Received TIS challenge");
+ s->cur_prompt->to_server = TRUE;
+ s->cur_prompt->name = dupstr("SSH TIS authentication");
+ /* Prompt heuristic comes from OpenSSH */
+ if (memchr(challenge, '\n', challengelen)) {
+ instr_suf = dupstr("");
+ prompt = dupprintf("%.*s", challengelen, challenge);
+ } else {
+ instr_suf = dupprintf("%.*s", challengelen, challenge);
+ prompt = dupstr("Response: ");
+ }
+ s->cur_prompt->instruction =
+ dupprintf("Using TIS authentication.%s%s",
+ (*instr_suf) ? "\n" : "",
+ instr_suf);
+ s->cur_prompt->instr_reqd = TRUE;
+ add_prompt(s->cur_prompt, prompt, FALSE);
+ sfree(instr_suf);
+ }
+ }
+ if (conf_get_int(ssh->conf, CONF_try_tis_auth) &&
+ (s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) &&
+ !s->ccard_auth_refused) {
+ s->pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE;
+ logevent("Requested CryptoCard authentication");
+ send_packet(ssh, SSH1_CMSG_AUTH_CCARD, PKT_END);
+ crWaitUntil(pktin);
+ if (pktin->type != SSH1_SMSG_AUTH_CCARD_CHALLENGE) {
+ logevent("CryptoCard authentication declined");
+ c_write_str(ssh, "CryptoCard authentication refused.\r\n");
+ s->ccard_auth_refused = 1;
+ continue;
+ } else {
+ char *challenge;
+ int challengelen;
+ char *instr_suf, *prompt;
+
+ ssh_pkt_getstring(pktin, &challenge, &challengelen);
+ if (!challenge) {
+ bombout(("CryptoCard challenge packet was badly formed"));
+ crStop(0);
+ }
+ logevent("Received CryptoCard challenge");
+ s->cur_prompt->to_server = TRUE;
+ s->cur_prompt->name = dupstr("SSH CryptoCard authentication");
+ s->cur_prompt->name_reqd = FALSE;
+ /* Prompt heuristic comes from OpenSSH */
+ if (memchr(challenge, '\n', challengelen)) {
+ instr_suf = dupstr("");
+ prompt = dupprintf("%.*s", challengelen, challenge);
+ } else {
+ instr_suf = dupprintf("%.*s", challengelen, challenge);
+ prompt = dupstr("Response: ");
+ }
+ s->cur_prompt->instruction =
+ dupprintf("Using CryptoCard authentication.%s%s",
+ (*instr_suf) ? "\n" : "",
+ instr_suf);
+ s->cur_prompt->instr_reqd = TRUE;
+ add_prompt(s->cur_prompt, prompt, FALSE);
+ sfree(instr_suf);
+ }
+ }
+ if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
+ if ((s->supported_auths_mask & (1 << SSH1_AUTH_PASSWORD)) == 0) {
+ bombout(("No supported authentication methods available"));
+ crStop(0);
+ }
+ s->cur_prompt->to_server = TRUE;
+ s->cur_prompt->name = dupstr("SSH password");
+ add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ",
+ ssh->username, ssh->savedhost),
+ FALSE);
+ }
+
+ /*
+ * Show password prompt, having first obtained it via a TIS
+ * or CryptoCard exchange if we're doing TIS or CryptoCard
+ * authentication.
+ */
+ {
+ int ret; /* need not be kept over crReturn */
+ ret = get_userpass_input(s->cur_prompt, NULL, 0);
+ while (ret < 0) {
+ ssh->send_ok = 1;
+ crWaitUntil(!pktin);
+ ret = get_userpass_input(s->cur_prompt, in, inlen);
+ ssh->send_ok = 0;
+ }
+ if (!ret) {
+ /*
+ * Failed to get a password (for example
+ * because one was supplied on the command line
+ * which has already failed to work). Terminate.
+ */
+ free_prompts(s->cur_prompt);
+ ssh_disconnect(ssh, NULL, "Unable to authenticate", 0, TRUE);
+ crStop(0);
+ }
+ }
+
+ if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
+ /*
+ * Defence against traffic analysis: we send a
+ * whole bunch of packets containing strings of
+ * different lengths. One of these strings is the
+ * password, in a SSH1_CMSG_AUTH_PASSWORD packet.
+ * The others are all random data in
+ * SSH1_MSG_IGNORE packets. This way a passive
+ * listener can't tell which is the password, and
+ * hence can't deduce the password length.
+ *
+ * Anybody with a password length greater than 16
+ * bytes is going to have enough entropy in their
+ * password that a listener won't find it _that_
+ * much help to know how long it is. So what we'll
+ * do is:
+ *
+ * - if password length < 16, we send 15 packets
+ * containing string lengths 1 through 15
+ *
+ * - otherwise, we let N be the nearest multiple
+ * of 8 below the password length, and send 8
+ * packets containing string lengths N through
+ * N+7. This won't obscure the order of
+ * magnitude of the password length, but it will
+ * introduce a bit of extra uncertainty.
+ *
+ * A few servers can't deal with SSH1_MSG_IGNORE, at
+ * least in this context. For these servers, we need
+ * an alternative defence. We make use of the fact
+ * that the password is interpreted as a C string:
+ * so we can append a NUL, then some random data.
+ *
+ * A few servers can deal with neither SSH1_MSG_IGNORE
+ * here _nor_ a padded password string.
+ * For these servers we are left with no defences
+ * against password length sniffing.
+ */
+ if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) &&
+ !(ssh->remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
+ /*
+ * The server can deal with SSH1_MSG_IGNORE, so
+ * we can use the primary defence.
+ */
+ int bottom, top, pwlen, i;
+ char *randomstr;
+
+ pwlen = strlen(s->cur_prompt->prompts[0]->result);
+ if (pwlen < 16) {
+ bottom = 0; /* zero length passwords are OK! :-) */
+ top = 15;
+ } else {
+ bottom = pwlen & ~7;
+ top = bottom + 7;
+ }
+
+ assert(pwlen >= bottom && pwlen <= top);
+
+ randomstr = snewn(top + 1, char);
+
+ for (i = bottom; i <= top; i++) {
+ if (i == pwlen) {
+ defer_packet(ssh, s->pwpkt_type,
+ PKT_STR,s->cur_prompt->prompts[0]->result,
+ PKT_END);
+ } else {
+ for (j = 0; j < i; j++) {
+ do {
+ randomstr[j] = random_byte();
+ } while (randomstr[j] == '\0');
+ }
+ randomstr[i] = '\0';
+ defer_packet(ssh, SSH1_MSG_IGNORE,
+ PKT_STR, randomstr, PKT_END);
+ }
+ }
+ logevent("Sending password with camouflage packets");
+ ssh_pkt_defersend(ssh);
+ sfree(randomstr);
+ }
+ else if (!(ssh->remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
+ /*
+ * The server can't deal with SSH1_MSG_IGNORE
+ * but can deal with padded passwords, so we
+ * can use the secondary defence.
+ */
+ char string[64];
+ char *ss;
+ int len;
+
+ len = strlen(s->cur_prompt->prompts[0]->result);
+ if (len < sizeof(string)) {
+ ss = string;
+ strcpy(string, s->cur_prompt->prompts[0]->result);
+ len++; /* cover the zero byte */
+ while (len < sizeof(string)) {
+ string[len++] = (char) random_byte();
+ }
+ } else {
+ ss = s->cur_prompt->prompts[0]->result;
+ }
+ logevent("Sending length-padded password");
+ send_packet(ssh, s->pwpkt_type,
+ PKT_INT, len, PKT_DATA, ss, len,
+ PKT_END);
+ } else {
+ /*
+ * The server is believed unable to cope with
+ * any of our password camouflage methods.
+ */
+ int len;
+ len = strlen(s->cur_prompt->prompts[0]->result);
+ logevent("Sending unpadded password");
+ send_packet(ssh, s->pwpkt_type,
+ PKT_INT, len,
+ PKT_DATA, s->cur_prompt->prompts[0]->result, len,
+ PKT_END);
+ }
+ } else {
+ send_packet(ssh, s->pwpkt_type,
+ PKT_STR, s->cur_prompt->prompts[0]->result,
+ PKT_END);
+ }
+ logevent("Sent password");
+ free_prompts(s->cur_prompt);
+ crWaitUntil(pktin);
+ if (pktin->type == SSH1_SMSG_FAILURE) {
+ if (flags & FLAG_VERBOSE)
+ c_write_str(ssh, "Access denied\r\n");
+ logevent("Authentication refused");
+ } else if (pktin->type != SSH1_SMSG_SUCCESS) {
+ bombout(("Strange packet received, type %d", pktin->type));
+ crStop(0);
+ }
+ }
+
+ /* Clear up */
+ if (s->publickey_blob) {
+ sfree(s->publickey_blob);
+ sfree(s->publickey_comment);
+ }
+
+ logevent("Authentication successful");
+
+ crFinish(1);
+}
+
+static void ssh_channel_try_eof(struct ssh_channel *c)
+{
+ Ssh ssh = c->ssh;
+ assert(c->pending_eof); /* precondition for calling us */
+ if (c->halfopen)
+ return; /* can't close: not even opened yet */
+ if (ssh->version == 2 && bufchain_size(&c->v.v2.outbuffer) > 0)
+ return; /* can't send EOF: pending outgoing data */
+
+ c->pending_eof = FALSE; /* we're about to send it */
+ if (ssh->version == 1) {
+ send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
+ PKT_END);
+ c->closes |= CLOSES_SENT_EOF;
+ } else {
+ struct Packet *pktout;
+ pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_EOF);
+ ssh2_pkt_adduint32(pktout, c->remoteid);
+ ssh2_pkt_send(ssh, pktout);
+ c->closes |= CLOSES_SENT_EOF;
+ ssh2_channel_check_close(c);
+ }
+}
+
+Conf *sshfwd_get_conf(struct ssh_channel *c)
+{
+ Ssh ssh = c->ssh;
+ return ssh->conf;
+}
+
+void sshfwd_write_eof(struct ssh_channel *c)
+{
+ Ssh ssh = c->ssh;
+
+ if (ssh->state == SSH_STATE_CLOSED)
+ return;
+
+ if (c->closes & CLOSES_SENT_EOF)
+ return;
+
+ c->pending_eof = TRUE;
+ ssh_channel_try_eof(c);
+}
+
+void sshfwd_unclean_close(struct ssh_channel *c, const char *err)
+{
+ Ssh ssh = c->ssh;
+
+ if (ssh->state == SSH_STATE_CLOSED)
+ return;
+
+ switch (c->type) {
+ case CHAN_X11:
+ x11_close(c->u.x11.xconn);
+ logeventf(ssh, "Forwarded X11 connection terminated due to local "
+ "error: %s", err);
+ break;
+ case CHAN_SOCKDATA:
+ case CHAN_SOCKDATA_DORMANT:
+ pfd_close(c->u.pfd.pf);
+ logeventf(ssh, "Forwarded port closed due to local error: %s", err);
+ break;
+ }
+ c->type = CHAN_ZOMBIE;
+ c->pending_eof = FALSE; /* this will confuse a zombie channel */
+
+ ssh2_channel_check_close(c);
+}
+
+int sshfwd_write(struct ssh_channel *c, char *buf, int len)
+{
+ Ssh ssh = c->ssh;
+
+ if (ssh->state == SSH_STATE_CLOSED)
+ return 0;
+
+ if (ssh->version == 1) {
+ send_packet(ssh, SSH1_MSG_CHANNEL_DATA,
+ PKT_INT, c->remoteid,
+ PKT_INT, len, PKT_DATA, buf, len,
+ PKT_END);
+ /*
+ * In SSH-1 we can return 0 here - implying that forwarded
+ * connections are never individually throttled - because
+ * the only circumstance that can cause throttling will be
+ * the whole SSH connection backing up, in which case
+ * _everything_ will be throttled as a whole.
+ */
+ return 0;
+ } else {
+ ssh2_add_channel_data(c, buf, len);
+ return ssh2_try_send(c);
+ }
+}
+
+void sshfwd_unthrottle(struct ssh_channel *c, int bufsize)
+{
+ Ssh ssh = c->ssh;
+ int buflimit;
+
+ if (ssh->state == SSH_STATE_CLOSED)
+ return;
+
+ if (ssh->version == 1) {
+ buflimit = SSH1_BUFFER_LIMIT;
+ } else {
+ buflimit = c->v.v2.locmaxwin;
+ ssh2_set_window(c, bufsize < buflimit ? buflimit - bufsize : 0);
+ }
+ if (c->throttling_conn && bufsize <= buflimit) {
+ c->throttling_conn = 0;
+ ssh_throttle_conn(ssh, -1);
+ }
+}
+
+static void ssh_queueing_handler(Ssh ssh, struct Packet *pktin)
+{
+ struct queued_handler *qh = ssh->qhead;
+
+ assert(qh != NULL);
+
+ assert(pktin->type == qh->msg1 || pktin->type == qh->msg2);
+
+ if (qh->msg1 > 0) {
+ assert(ssh->packet_dispatch[qh->msg1] == ssh_queueing_handler);
+ ssh->packet_dispatch[qh->msg1] = ssh->q_saved_handler1;
+ }
+ if (qh->msg2 > 0) {
+ assert(ssh->packet_dispatch[qh->msg2] == ssh_queueing_handler);
+ ssh->packet_dispatch[qh->msg2] = ssh->q_saved_handler2;
+ }
+
+ if (qh->next) {
+ ssh->qhead = qh->next;
+
+ if (ssh->qhead->msg1 > 0) {
+ ssh->q_saved_handler1 = ssh->packet_dispatch[ssh->qhead->msg1];
+ ssh->packet_dispatch[ssh->qhead->msg1] = ssh_queueing_handler;
+ }
+ if (ssh->qhead->msg2 > 0) {
+ ssh->q_saved_handler2 = ssh->packet_dispatch[ssh->qhead->msg2];
+ ssh->packet_dispatch[ssh->qhead->msg2] = ssh_queueing_handler;
+ }
+ } else {
+ ssh->qhead = ssh->qtail = NULL;
+ }
+
+ qh->handler(ssh, pktin, qh->ctx);
+
+ sfree(qh);
+}
+
+static void ssh_queue_handler(Ssh ssh, int msg1, int msg2,
+ chandler_fn_t handler, void *ctx)
+{
+ struct queued_handler *qh;
+
+ qh = snew(struct queued_handler);
+ qh->msg1 = msg1;
+ qh->msg2 = msg2;
+ qh->handler = handler;
+ qh->ctx = ctx;
+ qh->next = NULL;
+
+ if (ssh->qtail == NULL) {
+ ssh->qhead = qh;
+
+ if (qh->msg1 > 0) {
+ ssh->q_saved_handler1 = ssh->packet_dispatch[ssh->qhead->msg1];
+ ssh->packet_dispatch[qh->msg1] = ssh_queueing_handler;
+ }
+ if (qh->msg2 > 0) {
+ ssh->q_saved_handler2 = ssh->packet_dispatch[ssh->qhead->msg2];
+ ssh->packet_dispatch[qh->msg2] = ssh_queueing_handler;
+ }
+ } else {
+ ssh->qtail->next = qh;
+ }
+ ssh->qtail = qh;
+}
+
+static void ssh_rportfwd_succfail(Ssh ssh, struct Packet *pktin, void *ctx)
+{
+ struct ssh_rportfwd *rpf, *pf = (struct ssh_rportfwd *)ctx;
+
+ if (pktin->type == (ssh->version == 1 ? SSH1_SMSG_SUCCESS :
+ SSH2_MSG_REQUEST_SUCCESS)) {
+ logeventf(ssh, "Remote port forwarding from %s enabled",
+ pf->sportdesc);
+ } else {
+ logeventf(ssh, "Remote port forwarding from %s refused",
+ pf->sportdesc);
+
+ rpf = del234(ssh->rportfwds, pf);
+ assert(rpf == pf);
+ pf->pfrec->remote = NULL;
+ free_rportfwd(pf);
+ }
+}
+
+int ssh_alloc_sharing_rportfwd(Ssh ssh, const char *shost, int sport,
+ void *share_ctx)
+{
+ struct ssh_rportfwd *pf = snew(struct ssh_rportfwd);
+ pf->dhost = NULL;
+ pf->dport = 0;
+ pf->share_ctx = share_ctx;
+ pf->shost = dupstr(shost);
+ pf->sport = sport;
+ pf->sportdesc = NULL;
+ if (!ssh->rportfwds) {
+ assert(ssh->version == 2);
+ ssh->rportfwds = newtree234(ssh_rportcmp_ssh2);
+ }
+ if (add234(ssh->rportfwds, pf) != pf) {
+ sfree(pf->shost);
+ sfree(pf);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void ssh_sharing_global_request_response(Ssh ssh, struct Packet *pktin,
+ void *ctx)
+{
+ share_got_pkt_from_server(ctx, pktin->type,
+ pktin->body, pktin->length);
+}
+
+void ssh_sharing_queue_global_request(Ssh ssh, void *share_ctx)
+{
+ ssh_queue_handler(ssh, SSH2_MSG_REQUEST_SUCCESS, SSH2_MSG_REQUEST_FAILURE,
+ ssh_sharing_global_request_response, share_ctx);
+}
+
+static void ssh_setup_portfwd(Ssh ssh, Conf *conf)
+{
+ struct ssh_portfwd *epf;
+ int i;
+ char *key, *val;
+
+ if (!ssh->portfwds) {
+ ssh->portfwds = newtree234(ssh_portcmp);
+ } else {
+ /*
+ * Go through the existing port forwardings and tag them
+ * with status==DESTROY. Any that we want to keep will be
+ * re-enabled (status==KEEP) as we go through the
+ * configuration and find out which bits are the same as
+ * they were before.
+ */
+ struct ssh_portfwd *epf;
+ int i;
+ for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++)
+ epf->status = DESTROY;
+ }
+
+ for (val = conf_get_str_strs(conf, CONF_portfwd, NULL, &key);
+ val != NULL;
+ val = conf_get_str_strs(conf, CONF_portfwd, key, &key)) {
+ char *kp, *kp2, *vp, *vp2;
+ char address_family, type;
+ int sport,dport,sserv,dserv;
+ char *sports, *dports, *saddr, *host;
+
+ kp = key;
+
+ address_family = 'A';
+ type = 'L';
+ if (*kp == 'A' || *kp == '4' || *kp == '6')
+ address_family = *kp++;
+ if (*kp == 'L' || *kp == 'R')
+ type = *kp++;
+
+ if ((kp2 = host_strchr(kp, ':')) != NULL) {
+ /*
+ * There's a colon in the middle of the source port
+ * string, which means that the part before it is
+ * actually a source address.
+ */
+ char *saddr_tmp = dupprintf("%.*s", (int)(kp2 - kp), kp);
+ saddr = host_strduptrim(saddr_tmp);
+ sfree(saddr_tmp);
+ sports = kp2+1;
+ } else {
+ saddr = NULL;
+ sports = kp;
+ }
+ sport = atoi(sports);
+ sserv = 0;
+ if (sport == 0) {
+ sserv = 1;
+ sport = net_service_lookup(sports);
+ if (!sport) {
+ logeventf(ssh, "Service lookup failed for source"
+ " port \"%s\"", sports);
+ }
+ }
+
+ if (type == 'L' && !strcmp(val, "D")) {
+ /* dynamic forwarding */
+ host = NULL;
+ dports = NULL;
+ dport = -1;
+ dserv = 0;
+ type = 'D';
+ } else {
+ /* ordinary forwarding */
+ vp = val;
+ vp2 = vp + host_strcspn(vp, ":");
+ host = dupprintf("%.*s", (int)(vp2 - vp), vp);
+ if (*vp2)
+ vp2++;
+ dports = vp2;
+ dport = atoi(dports);
+ dserv = 0;
+ if (dport == 0) {
+ dserv = 1;
+ dport = net_service_lookup(dports);
+ if (!dport) {
+ logeventf(ssh, "Service lookup failed for destination"
+ " port \"%s\"", dports);
+ }
+ }
+ }
+
+ if (sport && dport) {
+ /* Set up a description of the source port. */
+ struct ssh_portfwd *pfrec, *epfrec;
+
+ pfrec = snew(struct ssh_portfwd);
+ pfrec->type = type;
+ pfrec->saddr = saddr;
+ pfrec->sserv = sserv ? dupstr(sports) : NULL;
+ pfrec->sport = sport;
+ pfrec->daddr = host;
+ pfrec->dserv = dserv ? dupstr(dports) : NULL;
+ pfrec->dport = dport;
+ pfrec->local = NULL;
+ pfrec->remote = NULL;
+ pfrec->addressfamily = (address_family == '4' ? ADDRTYPE_IPV4 :
+ address_family == '6' ? ADDRTYPE_IPV6 :
+ ADDRTYPE_UNSPEC);
+
+ epfrec = add234(ssh->portfwds, pfrec);
+ if (epfrec != pfrec) {
+ if (epfrec->status == DESTROY) {
+ /*
+ * We already have a port forwarding up and running
+ * with precisely these parameters. Hence, no need
+ * to do anything; simply re-tag the existing one
+ * as KEEP.
+ */
+ epfrec->status = KEEP;
+ }
+ /*
+ * Anything else indicates that there was a duplicate
+ * in our input, which we'll silently ignore.
+ */
+ free_portfwd(pfrec);
+ } else {
+ pfrec->status = CREATE;
+ }
+ } else {
+ sfree(saddr);
+ sfree(host);
+ }
+ }
+
+ /*
+ * Now go through and destroy any port forwardings which were
+ * not re-enabled.
+ */
+ for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++)
+ if (epf->status == DESTROY) {
+ char *message;
+
+ message = dupprintf("%s port forwarding from %s%s%d",
+ epf->type == 'L' ? "local" :
+ epf->type == 'R' ? "remote" : "dynamic",
+ epf->saddr ? epf->saddr : "",
+ epf->saddr ? ":" : "",
+ epf->sport);
+
+ if (epf->type != 'D') {
+ char *msg2 = dupprintf("%s to %s:%d", message,
+ epf->daddr, epf->dport);
+ sfree(message);
+ message = msg2;
+ }
+
+ logeventf(ssh, "Cancelling %s", message);
+ sfree(message);
+
+ /* epf->remote or epf->local may be NULL if setting up a
+ * forwarding failed. */
+ if (epf->remote) {
+ struct ssh_rportfwd *rpf = epf->remote;
+ struct Packet *pktout;
+
+ /*
+ * Cancel the port forwarding at the server
+ * end.
+ */
+ if (ssh->version == 1) {
+ /*
+ * We cannot cancel listening ports on the
+ * server side in SSH-1! There's no message
+ * to support it. Instead, we simply remove
+ * the rportfwd record from the local end
+ * so that any connections the server tries
+ * to make on it are rejected.
+ */
+ } else {
+ pktout = ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST);
+ ssh2_pkt_addstring(pktout, "cancel-tcpip-forward");
+ ssh2_pkt_addbool(pktout, 0);/* _don't_ want reply */
+ if (epf->saddr) {
+ ssh2_pkt_addstring(pktout, epf->saddr);
+ } else if (conf_get_int(conf, CONF_rport_acceptall)) {
+ /* XXX: rport_acceptall may not represent
+ * what was used to open the original connection,
+ * since it's reconfigurable. */
+ ssh2_pkt_addstring(pktout, "");
+ } else {
+ ssh2_pkt_addstring(pktout, "localhost");
+ }
+ ssh2_pkt_adduint32(pktout, epf->sport);
+ ssh2_pkt_send(ssh, pktout);
+ }
+
+ del234(ssh->rportfwds, rpf);
+ free_rportfwd(rpf);
+ } else if (epf->local) {
+ pfl_terminate(epf->local);
+ }
+
+ delpos234(ssh->portfwds, i);
+ free_portfwd(epf);
+ i--; /* so we don't skip one in the list */
+ }
+
+ /*
+ * And finally, set up any new port forwardings (status==CREATE).
+ */
+ for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++)
+ if (epf->status == CREATE) {
+ char *sportdesc, *dportdesc;
+ sportdesc = dupprintf("%s%s%s%s%d%s",
+ epf->saddr ? epf->saddr : "",
+ epf->saddr ? ":" : "",
+ epf->sserv ? epf->sserv : "",
+ epf->sserv ? "(" : "",
+ epf->sport,
+ epf->sserv ? ")" : "");
+ if (epf->type == 'D') {
+ dportdesc = NULL;
+ } else {
+ dportdesc = dupprintf("%s:%s%s%d%s",
+ epf->daddr,
+ epf->dserv ? epf->dserv : "",
+ epf->dserv ? "(" : "",
+ epf->dport,
+ epf->dserv ? ")" : "");
+ }
+
+ if (epf->type == 'L') {
+ char *err = pfl_listen(epf->daddr, epf->dport,
+ epf->saddr, epf->sport,
+ ssh, conf, &epf->local,
+ epf->addressfamily);
+
+ logeventf(ssh, "Local %sport %s forwarding to %s%s%s",
+ epf->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " :
+ epf->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "",
+ sportdesc, dportdesc,
+ err ? " failed: " : "", err ? err : "");
+ if (err)
+ sfree(err);
+ } else if (epf->type == 'D') {
+ char *err = pfl_listen(NULL, -1, epf->saddr, epf->sport,
+ ssh, conf, &epf->local,
+ epf->addressfamily);
+
+ logeventf(ssh, "Local %sport %s SOCKS dynamic forwarding%s%s",
+ epf->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " :
+ epf->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "",
+ sportdesc,
+ err ? " failed: " : "", err ? err : "");
+
+ if (err)
+ sfree(err);
+ } else {
+ struct ssh_rportfwd *pf;
+
+ /*
+ * Ensure the remote port forwardings tree exists.
+ */
+ if (!ssh->rportfwds) {
+ if (ssh->version == 1)
+ ssh->rportfwds = newtree234(ssh_rportcmp_ssh1);
+ else
+ ssh->rportfwds = newtree234(ssh_rportcmp_ssh2);
+ }
+
+ pf = snew(struct ssh_rportfwd);
+ pf->share_ctx = NULL;
+ pf->dhost = dupstr(epf->daddr);
+ pf->dport = epf->dport;
+ if (epf->saddr) {
+ pf->shost = dupstr(epf->saddr);
+ } else if (conf_get_int(conf, CONF_rport_acceptall)) {
+ pf->shost = dupstr("");
+ } else {
+ pf->shost = dupstr("localhost");
+ }
+ pf->sport = epf->sport;
+ if (add234(ssh->rportfwds, pf) != pf) {
+ logeventf(ssh, "Duplicate remote port forwarding to %s:%d",
+ epf->daddr, epf->dport);
+ sfree(pf);
+ } else {
+ logeventf(ssh, "Requesting remote port %s"
+ " forward to %s", sportdesc, dportdesc);
+
+ pf->sportdesc = sportdesc;
+ sportdesc = NULL;
+ epf->remote = pf;
+ pf->pfrec = epf;
+
+ if (ssh->version == 1) {
+ send_packet(ssh, SSH1_CMSG_PORT_FORWARD_REQUEST,
+ PKT_INT, epf->sport,
+ PKT_STR, epf->daddr,
+ PKT_INT, epf->dport,
+ PKT_END);
+ ssh_queue_handler(ssh, SSH1_SMSG_SUCCESS,
+ SSH1_SMSG_FAILURE,
+ ssh_rportfwd_succfail, pf);
+ } else {
+ struct Packet *pktout;
+ pktout = ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST);
+ ssh2_pkt_addstring(pktout, "tcpip-forward");
+ ssh2_pkt_addbool(pktout, 1);/* want reply */
+ ssh2_pkt_addstring(pktout, pf->shost);
+ ssh2_pkt_adduint32(pktout, pf->sport);
+ ssh2_pkt_send(ssh, pktout);
+
+ ssh_queue_handler(ssh, SSH2_MSG_REQUEST_SUCCESS,
+ SSH2_MSG_REQUEST_FAILURE,
+ ssh_rportfwd_succfail, pf);
+ }
+ }
+ }
+ sfree(sportdesc);
+ sfree(dportdesc);
+ }
+}
+
+static void ssh1_smsg_stdout_stderr_data(Ssh ssh, struct Packet *pktin)
+{
+ char *string;
+ int stringlen, bufsize;
+
+ ssh_pkt_getstring(pktin, &string, &stringlen);
+ if (string == NULL) {
+ bombout(("Incoming terminal data packet was badly formed"));
+ return;
+ }
+
+ bufsize = from_backend(ssh->frontend, pktin->type == SSH1_SMSG_STDERR_DATA,
+ string, stringlen);
+ if (!ssh->v1_stdout_throttling && bufsize > SSH1_BUFFER_LIMIT) {
+ ssh->v1_stdout_throttling = 1;
+ ssh_throttle_conn(ssh, +1);
+ }
+}
+
+static void ssh1_smsg_x11_open(Ssh ssh, struct Packet *pktin)
+{
+ /* Remote side is trying to open a channel to talk to our
+ * X-Server. Give them back a local channel number. */
+ struct ssh_channel *c;
+ int remoteid = ssh_pkt_getuint32(pktin);
+
+ logevent("Received X11 connect request");
+ /* Refuse if X11 forwarding is disabled. */
+ if (!ssh->X11_fwd_enabled) {
+ send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
+ PKT_INT, remoteid, PKT_END);
+ logevent("Rejected X11 connect request");
+ } else {
+ c = snew(struct ssh_channel);
+ c->ssh = ssh;
+
+ c->u.x11.xconn = x11_init(ssh->x11authtree, c, NULL, -1);
+ c->remoteid = remoteid;
+ c->halfopen = FALSE;
+ c->localid = alloc_channel_id(ssh);
+ c->closes = 0;
+ c->pending_eof = FALSE;
+ c->throttling_conn = 0;
+ c->type = CHAN_X11; /* identify channel type */
+ add234(ssh->channels, c);
+ send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
+ PKT_INT, c->remoteid, PKT_INT,
+ c->localid, PKT_END);
+ logevent("Opened X11 forward channel");
+ }
+}
+
+static void ssh1_smsg_agent_open(Ssh ssh, struct Packet *pktin)
+{
+ /* Remote side is trying to open a channel to talk to our
+ * agent. Give them back a local channel number. */
+ struct ssh_channel *c;
+ int remoteid = ssh_pkt_getuint32(pktin);
+
+ /* Refuse if agent forwarding is disabled. */
+ if (!ssh->agentfwd_enabled) {
+ send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
+ PKT_INT, remoteid, PKT_END);
+ } else {
+ c = snew(struct ssh_channel);
+ c->ssh = ssh;
+ c->remoteid = remoteid;
+ c->halfopen = FALSE;
+ c->localid = alloc_channel_id(ssh);
+ c->closes = 0;
+ c->pending_eof = FALSE;
+ c->throttling_conn = 0;
+ c->type = CHAN_AGENT; /* identify channel type */
+ c->u.a.lensofar = 0;
+ c->u.a.message = NULL;
+ c->u.a.outstanding_requests = 0;
+ add234(ssh->channels, c);
+ send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
+ PKT_INT, c->remoteid, PKT_INT, c->localid,
+ PKT_END);
+ }
+}
+
+static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin)
+{
+ /* Remote side is trying to open a channel to talk to a
+ * forwarded port. Give them back a local channel number. */
+ struct ssh_rportfwd pf, *pfp;
+ int remoteid;
+ int hostsize, port;
+ char *host;
+ char *err;
+
+ remoteid = ssh_pkt_getuint32(pktin);
+ ssh_pkt_getstring(pktin, &host, &hostsize);
+ port = ssh_pkt_getuint32(pktin);
+
+ pf.dhost = dupprintf("%.*s", hostsize, host);
+ pf.dport = port;
+ pfp = find234(ssh->rportfwds, &pf, NULL);
+
+ if (pfp == NULL) {
+ logeventf(ssh, "Rejected remote port open request for %s:%d",
+ pf.dhost, port);
+ send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
+ PKT_INT, remoteid, PKT_END);
+ } else {
+ struct ssh_channel *c = snew(struct ssh_channel);
+ c->ssh = ssh;
+
+ logeventf(ssh, "Received remote port open request for %s:%d",
+ pf.dhost, port);
+ err = pfd_connect(&c->u.pfd.pf, pf.dhost, port,
+ c, ssh->conf, pfp->pfrec->addressfamily);
+ if (err != NULL) {
+ logeventf(ssh, "Port open failed: %s", err);
+ sfree(err);
+ sfree(c);
+ send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
+ PKT_INT, remoteid, PKT_END);
+ } else {
+ c->remoteid = remoteid;
+ c->halfopen = FALSE;
+ c->localid = alloc_channel_id(ssh);
+ c->closes = 0;
+ c->pending_eof = FALSE;
+ c->throttling_conn = 0;
+ c->type = CHAN_SOCKDATA; /* identify channel type */
+ add234(ssh->channels, c);
+ send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
+ PKT_INT, c->remoteid, PKT_INT,
+ c->localid, PKT_END);
+ logevent("Forwarded port opened successfully");
+ }
+ }
+
+ sfree(pf.dhost);
+}
+
+static void ssh1_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin)
+{
+ unsigned int remoteid = ssh_pkt_getuint32(pktin);
+ unsigned int localid = ssh_pkt_getuint32(pktin);
+ struct ssh_channel *c;
+
+ c = find234(ssh->channels, &remoteid, ssh_channelfind);
+ if (c && c->type == CHAN_SOCKDATA_DORMANT) {
+ c->remoteid = localid;
+ c->halfopen = FALSE;
+ c->type = CHAN_SOCKDATA;
+ c->throttling_conn = 0;
+ pfd_confirm(c->u.pfd.pf);
+ }
+
+ if (c && c->pending_eof) {
+ /*
+ * We have a pending close on this channel,
+ * which we decided on before the server acked
+ * the channel open. So now we know the
+ * remoteid, we can close it again.
+ */
+ ssh_channel_try_eof(c);
+ }
+}
+
+static void ssh1_msg_channel_open_failure(Ssh ssh, struct Packet *pktin)
+{
+ unsigned int remoteid = ssh_pkt_getuint32(pktin);
+ struct ssh_channel *c;
+
+ c = find234(ssh->channels, &remoteid, ssh_channelfind);
+ if (c && c->type == CHAN_SOCKDATA_DORMANT) {
+ logevent("Forwarded connection refused by server");
+ pfd_close(c->u.pfd.pf);
+ del234(ssh->channels, c);
+ sfree(c);
+ }
+}
+
+static void ssh1_msg_channel_close(Ssh ssh, struct Packet *pktin)
+{
+ /* Remote side closes a channel. */
+ unsigned i = ssh_pkt_getuint32(pktin);
+ struct ssh_channel *c;
+ c = find234(ssh->channels, &i, ssh_channelfind);
+ if (c && !c->halfopen) {
+
+ if (pktin->type == SSH1_MSG_CHANNEL_CLOSE &&
+ !(c->closes & CLOSES_RCVD_EOF)) {
+ /*
+ * Received CHANNEL_CLOSE, which we translate into
+ * outgoing EOF.
+ */
+ int send_close = FALSE;
+
+ c->closes |= CLOSES_RCVD_EOF;
+
+ switch (c->type) {
+ case CHAN_X11:
+ if (c->u.x11.xconn)
+ x11_send_eof(c->u.x11.xconn);
+ else
+ send_close = TRUE;
+ break;
+ case CHAN_SOCKDATA:
+ if (c->u.pfd.pf)
+ pfd_send_eof(c->u.pfd.pf);
+ else
+ send_close = TRUE;
+ break;
+ case CHAN_AGENT:
+ send_close = TRUE;
+ break;
+ }
+
+ if (send_close && !(c->closes & CLOSES_SENT_EOF)) {
+ send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
+ PKT_END);
+ c->closes |= CLOSES_SENT_EOF;
+ }
+ }
+
+ if (pktin->type == SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION &&
+ !(c->closes & CLOSES_RCVD_CLOSE)) {
+
+ if (!(c->closes & CLOSES_SENT_EOF)) {
+ bombout(("Received CHANNEL_CLOSE_CONFIRMATION for channel %d"
+ " for which we never sent CHANNEL_CLOSE\n", i));
+ }
+
+ c->closes |= CLOSES_RCVD_CLOSE;
+ }
+
+ if (!((CLOSES_SENT_EOF | CLOSES_RCVD_EOF) & ~c->closes) &&
+ !(c->closes & CLOSES_SENT_CLOSE)) {
+ send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION,
+ PKT_INT, c->remoteid, PKT_END);
+ c->closes |= CLOSES_SENT_CLOSE;
+ }
+
+ if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes))
+ ssh_channel_destroy(c);
+ } else {
+ bombout(("Received CHANNEL_CLOSE%s for %s channel %d\n",
+ pktin->type == SSH1_MSG_CHANNEL_CLOSE ? "" :
+ "_CONFIRMATION", c ? "half-open" : "nonexistent",
+ i));
+ }
+}
+
+static void ssh1_msg_channel_data(Ssh ssh, struct Packet *pktin)
+{
+ /* Data sent down one of our channels. */
+ int i = ssh_pkt_getuint32(pktin);
+ char *p;
+ int len;
+ struct ssh_channel *c;
+
+ ssh_pkt_getstring(pktin, &p, &len);
+
+ c = find234(ssh->channels, &i, ssh_channelfind);
+ if (c) {
+ int bufsize = 0;
+ switch (c->type) {
+ case CHAN_X11:
+ bufsize = x11_send(c->u.x11.xconn, p, len);
+ break;
+ case CHAN_SOCKDATA:
+ bufsize = pfd_send(c->u.pfd.pf, p, len);
+ break;
+ case CHAN_AGENT:
+ /* Data for an agent message. Buffer it. */
+ while (len > 0) {
+ if (c->u.a.lensofar < 4) {
+ unsigned int l = min(4 - c->u.a.lensofar, (unsigned)len);
+ memcpy(c->u.a.msglen + c->u.a.lensofar, p,
+ l);
+ p += l;
+ len -= l;
+ c->u.a.lensofar += l;
+ }
+ if (c->u.a.lensofar == 4) {
+ c->u.a.totallen =
+ 4 + GET_32BIT(c->u.a.msglen);
+ c->u.a.message = snewn(c->u.a.totallen,
+ unsigned char);
+ memcpy(c->u.a.message, c->u.a.msglen, 4);
+ }
+ if (c->u.a.lensofar >= 4 && len > 0) {
+ unsigned int l =
+ min(c->u.a.totallen - c->u.a.lensofar,
+ (unsigned)len);
+ memcpy(c->u.a.message + c->u.a.lensofar, p,
+ l);
+ p += l;
+ len -= l;
+ c->u.a.lensofar += l;
+ }
+ if (c->u.a.lensofar == c->u.a.totallen) {
+ void *reply;
+ int replylen;
+ c->u.a.outstanding_requests++;
+ if (agent_query(c->u.a.message,
+ c->u.a.totallen,
+ &reply, &replylen,
+ ssh_agentf_callback, c))
+ ssh_agentf_callback(c, reply, replylen);
+ sfree(c->u.a.message);
+ c->u.a.lensofar = 0;
+ }
+ }
+ bufsize = 0; /* agent channels never back up */
+ break;
+ }
+ if (!c->throttling_conn && bufsize > SSH1_BUFFER_LIMIT) {
+ c->throttling_conn = 1;
+ ssh_throttle_conn(ssh, +1);
+ }
+ }
+}
+
+static void ssh1_smsg_exit_status(Ssh ssh, struct Packet *pktin)
+{
+ ssh->exitcode = ssh_pkt_getuint32(pktin);
+ logeventf(ssh, "Server sent command exit status %d", ssh->exitcode);
+ send_packet(ssh, SSH1_CMSG_EXIT_CONFIRMATION, PKT_END);
+ /*
+ * In case `helpful' firewalls or proxies tack
+ * extra human-readable text on the end of the
+ * session which we might mistake for another
+ * encrypted packet, we close the session once
+ * we've sent EXIT_CONFIRMATION.
+ */
+ ssh_disconnect(ssh, NULL, NULL, 0, TRUE);
+}
+
+/* Helper function to deal with sending tty modes for REQUEST_PTY */
+static void ssh1_send_ttymode(void *data, char *mode, char *val)
+{
+ struct Packet *pktout = (struct Packet *)data;
+ int i = 0;
+ unsigned int arg = 0;
+ while (strcmp(mode, ssh_ttymodes[i].mode) != 0) i++;
+ if (i == lenof(ssh_ttymodes)) return;
+ switch (ssh_ttymodes[i].type) {
+ case TTY_OP_CHAR:
+ arg = ssh_tty_parse_specchar(val);
+ break;
+ case TTY_OP_BOOL:
+ arg = ssh_tty_parse_boolean(val);
+ break;
+ }
+ ssh2_pkt_addbyte(pktout, ssh_ttymodes[i].opcode);
+ ssh2_pkt_addbyte(pktout, arg);
+}
+
+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,
+ struct Packet *pktin)
+{
+ crBegin(ssh->do_ssh1_connection_crstate);
+
+ ssh->packet_dispatch[SSH1_SMSG_STDOUT_DATA] =
+ ssh->packet_dispatch[SSH1_SMSG_STDERR_DATA] =
+ ssh1_smsg_stdout_stderr_data;
+
+ ssh->packet_dispatch[SSH1_MSG_CHANNEL_OPEN_CONFIRMATION] =
+ ssh1_msg_channel_open_confirmation;
+ ssh->packet_dispatch[SSH1_MSG_CHANNEL_OPEN_FAILURE] =
+ ssh1_msg_channel_open_failure;
+ ssh->packet_dispatch[SSH1_MSG_CHANNEL_CLOSE] =
+ ssh->packet_dispatch[SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION] =
+ ssh1_msg_channel_close;
+ ssh->packet_dispatch[SSH1_MSG_CHANNEL_DATA] = ssh1_msg_channel_data;
+ ssh->packet_dispatch[SSH1_SMSG_EXIT_STATUS] = ssh1_smsg_exit_status;
+
+ if (ssh_agent_forwarding_permitted(ssh)) {
+ logevent("Requesting agent forwarding");
+ send_packet(ssh, SSH1_CMSG_AGENT_REQUEST_FORWARDING, PKT_END);
+ do {
+ crReturnV;
+ } while (!pktin);
+ if (pktin->type != SSH1_SMSG_SUCCESS
+ && pktin->type != SSH1_SMSG_FAILURE) {
+ bombout(("Protocol confusion"));
+ crStopV;
+ } else if (pktin->type == SSH1_SMSG_FAILURE) {
+ logevent("Agent forwarding refused");
+ } else {
+ logevent("Agent forwarding enabled");
+ ssh->agentfwd_enabled = TRUE;
+ ssh->packet_dispatch[SSH1_SMSG_AGENT_OPEN] = ssh1_smsg_agent_open;
+ }
+ }
+
+ if (conf_get_int(ssh->conf, CONF_x11_forward)) {
+ ssh->x11disp =
+ x11_setup_display(conf_get_str(ssh->conf, CONF_x11_display),
+ ssh->conf);
+ if (!ssh->x11disp) {
+ /* FIXME: return an error message from x11_setup_display */
+ logevent("X11 forwarding not enabled: unable to"
+ " initialise X display");
+ } else {
+ ssh->x11auth = x11_invent_fake_auth
+ (ssh->x11authtree, conf_get_int(ssh->conf, CONF_x11_auth));
+ ssh->x11auth->disp = ssh->x11disp;
+
+ logevent("Requesting X11 forwarding");
+ if (ssh->v1_local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) {
+ send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING,
+ PKT_STR, ssh->x11auth->protoname,
+ PKT_STR, ssh->x11auth->datastring,
+ PKT_INT, ssh->x11disp->screennum,
+ PKT_END);
+ } else {
+ send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING,
+ PKT_STR, ssh->x11auth->protoname,
+ PKT_STR, ssh->x11auth->datastring,
+ PKT_END);
+ }
+ do {
+ crReturnV;
+ } while (!pktin);
+ if (pktin->type != SSH1_SMSG_SUCCESS
+ && pktin->type != SSH1_SMSG_FAILURE) {
+ bombout(("Protocol confusion"));
+ crStopV;
+ } else if (pktin->type == SSH1_SMSG_FAILURE) {
+ logevent("X11 forwarding refused");
+ } else {
+ logevent("X11 forwarding enabled");
+ ssh->X11_fwd_enabled = TRUE;
+ ssh->packet_dispatch[SSH1_SMSG_X11_OPEN] = ssh1_smsg_x11_open;
+ }
+ }
+ }
+
+ ssh_setup_portfwd(ssh, ssh->conf);
+ ssh->packet_dispatch[SSH1_MSG_PORT_OPEN] = ssh1_msg_port_open;
+
+ if (!conf_get_int(ssh->conf, CONF_nopty)) {
+ struct Packet *pkt;
+ /* Unpick the terminal-speed string. */
+ /* XXX perhaps we should allow no speeds to be sent. */
+ ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */
+ sscanf(conf_get_str(ssh->conf, CONF_termspeed), "%d,%d", &ssh->ospeed, &ssh->ispeed);
+ /* Send the pty request. */
+ pkt = ssh1_pkt_init(SSH1_CMSG_REQUEST_PTY);
+ ssh_pkt_addstring(pkt, conf_get_str(ssh->conf, CONF_termtype));
+ ssh_pkt_adduint32(pkt, ssh->term_height);
+ ssh_pkt_adduint32(pkt, ssh->term_width);
+ ssh_pkt_adduint32(pkt, 0); /* width in pixels */
+ ssh_pkt_adduint32(pkt, 0); /* height in pixels */
+ parse_ttymodes(ssh, ssh1_send_ttymode, (void *)pkt);
+ ssh_pkt_addbyte(pkt, SSH1_TTY_OP_ISPEED);
+ ssh_pkt_adduint32(pkt, ssh->ispeed);
+ ssh_pkt_addbyte(pkt, SSH1_TTY_OP_OSPEED);
+ ssh_pkt_adduint32(pkt, ssh->ospeed);
+ ssh_pkt_addbyte(pkt, SSH_TTY_OP_END);
+ s_wrpkt(ssh, pkt);
+ ssh->state = SSH_STATE_INTERMED;
+ do {
+ crReturnV;
+ } while (!pktin);
+ if (pktin->type != SSH1_SMSG_SUCCESS
+ && pktin->type != SSH1_SMSG_FAILURE) {
+ bombout(("Protocol confusion"));
+ crStopV;
+ } else if (pktin->type == SSH1_SMSG_FAILURE) {
+ c_write_str(ssh, "Server refused to allocate pty\r\n");
+ ssh->editing = ssh->echoing = 1;
+ } else {
+ logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)",
+ ssh->ospeed, ssh->ispeed);
+ ssh->got_pty = TRUE;
+ }
+ } else {
+ ssh->editing = ssh->echoing = 1;
+ }
+
+ if (conf_get_int(ssh->conf, CONF_compression)) {
+ send_packet(ssh, SSH1_CMSG_REQUEST_COMPRESSION, PKT_INT, 6, PKT_END);
+ do {
+ crReturnV;
+ } while (!pktin);
+ if (pktin->type != SSH1_SMSG_SUCCESS
+ && pktin->type != SSH1_SMSG_FAILURE) {
+ bombout(("Protocol confusion"));
+ crStopV;
+ } else if (pktin->type == SSH1_SMSG_FAILURE) {
+ c_write_str(ssh, "Server refused to compress\r\n");
+ }
+ logevent("Started compression");
+ ssh->v1_compressing = TRUE;
+ ssh->cs_comp_ctx = zlib_compress_init();
+ logevent("Initialised zlib (RFC1950) compression");
+ ssh->sc_comp_ctx = zlib_decompress_init();
+ logevent("Initialised zlib (RFC1950) decompression");
+ }
+
+ /*
+ * Start the shell or command.
+ *
+ * Special case: if the first-choice command is an SSH-2
+ * subsystem (hence not usable here) and the second choice
+ * exists, we fall straight back to that.
+ */
+ {
+ char *cmd = conf_get_str(ssh->conf, CONF_remote_cmd);
+
+ if (conf_get_int(ssh->conf, CONF_ssh_subsys) &&
+ conf_get_str(ssh->conf, CONF_remote_cmd2)) {
+ cmd = conf_get_str(ssh->conf, CONF_remote_cmd2);
+ ssh->fallback_cmd = TRUE;
+ }
+ if (*cmd)
+ send_packet(ssh, SSH1_CMSG_EXEC_CMD, PKT_STR, cmd, PKT_END);
+ else
+ send_packet(ssh, SSH1_CMSG_EXEC_SHELL, PKT_END);
+ logevent("Started session");
+ }
+
+ ssh->state = SSH_STATE_SESSION;
+ if (ssh->size_needed)
+ ssh_size(ssh, ssh->term_width, ssh->term_height);
+ if (ssh->eof_needed)
+ ssh_special(ssh, TS_EOF);
+
+ if (ssh->ldisc)
+ ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */
+ ssh->send_ok = 1;
+ ssh->channels = newtree234(ssh_channelcmp);
+ while (1) {
+
+ /*
+ * By this point, most incoming packets are already being
+ * handled by the dispatch table, and we need only pay
+ * attention to the unusual ones.
+ */
+
+ crReturnV;
+ if (pktin) {
+ if (pktin->type == SSH1_SMSG_SUCCESS) {
+ /* may be from EXEC_SHELL on some servers */
+ } else if (pktin->type == SSH1_SMSG_FAILURE) {
+ /* may be from EXEC_SHELL on some servers
+ * if no pty is available or in other odd cases. Ignore */
+ } else {
+ bombout(("Strange packet received: type %d", pktin->type));
+ crStopV;
+ }
+ } else {
+ while (inlen > 0) {
+ int len = min(inlen, 512);
+ send_packet(ssh, SSH1_CMSG_STDIN_DATA,
+ PKT_INT, len, PKT_DATA, in, len,
+ PKT_END);
+ in += len;
+ inlen -= len;
+ }
+ }
+ }
+
+ crFinishV;
+}
+
+/*
+ * Handle the top-level SSH-2 protocol.
+ */
+static void ssh1_msg_debug(Ssh ssh, struct Packet *pktin)
+{
+ char *msg;
+ int msglen;
+
+ ssh_pkt_getstring(pktin, &msg, &msglen);
+ logeventf(ssh, "Remote debug message: %.*s", msglen, msg);
+}
+
+static void ssh1_msg_disconnect(Ssh ssh, struct Packet *pktin)
+{
+ /* log reason code in disconnect message */
+ char *msg;
+ int msglen;
+
+ ssh_pkt_getstring(pktin, &msg, &msglen);
+ bombout(("Server sent disconnect message:\n\"%.*s\"", msglen, msg));
+}
+
+static void ssh_msg_ignore(Ssh ssh, struct Packet *pktin)
+{
+ /* Do nothing, because we're ignoring it! Duhh. */
+}
+
+static void ssh1_protocol_setup(Ssh ssh)
+{
+ int i;
+
+ /*
+ * Most messages are handled by the coroutines.
+ */
+ for (i = 0; i < 256; i++)
+ ssh->packet_dispatch[i] = NULL;
+
+ /*
+ * These special message types we install handlers for.
+ */
+ ssh->packet_dispatch[SSH1_MSG_DISCONNECT] = ssh1_msg_disconnect;
+ ssh->packet_dispatch[SSH1_MSG_IGNORE] = ssh_msg_ignore;
+ ssh->packet_dispatch[SSH1_MSG_DEBUG] = ssh1_msg_debug;
+}
+
+static void ssh1_protocol(Ssh ssh, void *vin, int inlen,
+ struct Packet *pktin)
+{
+ unsigned char *in=(unsigned char*)vin;
+ if (ssh->state == SSH_STATE_CLOSED)
+ return;
+
+ if (pktin && ssh->packet_dispatch[pktin->type]) {
+ ssh->packet_dispatch[pktin->type](ssh, pktin);
+ return;
+ }
+
+ if (!ssh->protocol_initial_phase_done) {
+ if (do_ssh1_login(ssh, in, inlen, pktin))
+ ssh->protocol_initial_phase_done = TRUE;
+ else
+ return;
+ }
+
+ do_ssh1_connection(ssh, in, inlen, pktin);
+}
+
+/*
+ * Utility routine for decoding comma-separated strings in KEXINIT.
+ */
+static int in_commasep_string(char *needle, char *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 */
+ }
+}
+
+/*
+ * 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)
+{
+ int needlen;
+ 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 */
+ )
+ return 1;
+ return 0;
+}
+
+
+/*
+ * 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.)
+ */
+#define SSH2_MKKEY_ITERS (2)
+static void ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H, char chr,
+ unsigned char *keyspace)
+{
+ const struct ssh_hash *h = ssh->kex->hash;
+ void *s;
+ /* First 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, &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);
+}
+
+/*
+ * Handle the SSH-2 transport layer.
+ */
+static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
+ struct Packet *pktin)
+{
+ unsigned char *in = (unsigned char *)vin;
+ struct do_ssh2_transport_state {
+ int crLine;
+ int nbits, pbits, warn_kex, warn_cscipher, warn_sccipher;
+ Bignum p, g, e, f, K;
+ void *our_kexinit;
+ int our_kexinitlen;
+ int kex_init_value, kex_reply_value;
+ const struct ssh_mac **maclist;
+ int nmacs;
+ const struct ssh2_cipher *cscipher_tobe;
+ const struct ssh2_cipher *sccipher_tobe;
+ const struct ssh_mac *csmac_tobe;
+ const struct ssh_mac *scmac_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 */
+ unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN];
+ int n_preferred_kex;
+ const struct ssh_kexes *preferred_kex[KEX_MAX];
+ int n_preferred_ciphers;
+ const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
+ const struct ssh_compress *preferred_comp;
+ int userauth_succeeded; /* for delayed compression */
+ int pending_compression;
+ int got_session_id, activated_authconn;
+ struct Packet *pktout;
+ int dlgret;
+ int guessok;
+ int ignorepkt;
+ };
+ crState(do_ssh2_transport_state);
+
+ assert(!ssh->bare_connection);
+
+ crBeginState;
+
+ s->cscipher_tobe = s->sccipher_tobe = NULL;
+ s->csmac_tobe = s->scmac_tobe = NULL;
+ s->cscomp_tobe = s->sccomp_tobe = NULL;
+
+ s->got_session_id = s->activated_authconn = FALSE;
+ s->userauth_succeeded = FALSE;
+ s->pending_compression = FALSE;
+
+ /*
+ * Be prepared to work around the buggy MAC problem.
+ */
+ if (ssh->remote_bugs & BUG_SSH2_HMAC)
+ s->maclist = buggymacs, s->nmacs = lenof(buggymacs);
+ else
+ s->maclist = macs, s->nmacs = lenof(macs);
+
+ begin_key_exchange:
+ ssh->pkt_kctx = SSH2_PKTCTX_NOKEX;
+ {
+ int i, j, k, commalist_started;
+
+ /*
+ * Set up the preferred key exchange. (NULL => warn below here)
+ */
+ s->n_preferred_kex = 0;
+ for (i = 0; i < KEX_MAX; i++) {
+ switch (conf_get_int_int(ssh->conf, CONF_ssh_kexlist, i)) {
+ case KEX_DHGEX:
+ s->preferred_kex[s->n_preferred_kex++] =
+ &ssh_diffiehellman_gex;
+ break;
+ case KEX_DHGROUP14:
+ s->preferred_kex[s->n_preferred_kex++] =
+ &ssh_diffiehellman_group14;
+ break;
+ case KEX_DHGROUP1:
+ s->preferred_kex[s->n_preferred_kex++] =
+ &ssh_diffiehellman_group1;
+ break;
+ case KEX_RSA:
+ s->preferred_kex[s->n_preferred_kex++] =
+ &ssh_rsa_kex;
+ break;
+ case KEX_WARN:
+ /* Flag for later. Don't bother if it's the last in
+ * the list. */
+ if (i < KEX_MAX - 1) {
+ s->preferred_kex[s->n_preferred_kex++] = NULL;
+ }
+ break;
+ }
+ }
+
+ /*
+ * Set up the preferred ciphers. (NULL => warn below here)
+ */
+ s->n_preferred_ciphers = 0;
+ for (i = 0; i < CIPHER_MAX; i++) {
+ switch (conf_get_int_int(ssh->conf, CONF_ssh_cipherlist, i)) {
+ case CIPHER_BLOWFISH:
+ s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_blowfish;
+ break;
+ case CIPHER_DES:
+ if (conf_get_int(ssh->conf, CONF_ssh2_des_cbc)) {
+ s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_des;
+ }
+ break;
+ case CIPHER_3DES:
+ s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_3des;
+ break;
+ case CIPHER_AES:
+ s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_aes;
+ break;
+ case CIPHER_ARCFOUR:
+ s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_arcfour;
+ break;
+ case CIPHER_WARN:
+ /* Flag for later. Don't bother if it's the last in
+ * the list. */
+ if (i < CIPHER_MAX - 1) {
+ s->preferred_ciphers[s->n_preferred_ciphers++] = NULL;
+ }
+ break;
+ }
+ }
+
+ /*
+ * Set up preferred compression.
+ */
+ if (conf_get_int(ssh->conf, CONF_compression))
+ s->preferred_comp = &ssh_zlib;
+ else
+ s->preferred_comp = &ssh_comp_none;
+
+ /*
+ * Enable queueing of outgoing auth- or connection-layer
+ * packets while we are in the middle of a key exchange.
+ */
+ ssh->queueing = TRUE;
+
+ /*
+ * Flag that KEX is in progress.
+ */
+ 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());
+ /* List key exchange algorithms. */
+ ssh2_pkt_addstring_start(s->pktout);
+ commalist_started = 0;
+ 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;
+ }
+ }
+ /* 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.
+ */
+ 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, ",");
+ }
+ } else {
+ /*
+ * In subsequent key exchanges, we list only the kex
+ * algorithm that was selected in the first key exchange,
+ * so that we keep getting the same host key and hence
+ * don't have to interrupt the user's session to ask for
+ * reverification.
+ */
+ assert(ssh->kex);
+ ssh2_pkt_addstring(s->pktout, ssh->hostkey->name);
+ }
+ /* List encryption algorithms (client->server then server->client). */
+ for (k = 0; k < 2; k++) {
+ ssh2_pkt_addstring_start(s->pktout);
+ commalist_started = 0;
+ 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;
+ }
+ }
+ }
+ /* List MAC algorithms (client->server then server->client). */
+ for (j = 0; j < 2; j++) {
+ ssh2_pkt_addstring_start(s->pktout);
+ 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, ",");
+ }
+ }
+ /* 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);
+ assert(lenof(compressions) > 1);
+ /* Prefer non-delayed versions */
+ ssh2_pkt_addstring_str(s->pktout, s->preferred_comp->name);
+ /* 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);
+ }
+ 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);
+ }
+ }
+ }
+ }
+ /* List client->server languages. Empty list. */
+ ssh2_pkt_addstring_start(s->pktout);
+ /* List server->client languages. Empty list. */
+ ssh2_pkt_addstring_start(s->pktout);
+ /* First KEX packet does _not_ follow, because we're not that brave. */
+ ssh2_pkt_addbool(s->pktout, FALSE);
+ /* Reserved. */
+ ssh2_pkt_adduint32(s->pktout, 0);
+ }
+
+ s->our_kexinitlen = s->pktout->length - 5;
+ s->our_kexinit = snewn(s->our_kexinitlen, unsigned char);
+ memcpy(s->our_kexinit, s->pktout->data + 5, s->our_kexinitlen);
+
+ ssh2_pkt_send_noqueue(ssh, s->pktout);
+
+ if (!pktin)
+ crWaitUntilV(pktin);
+
+ /*
+ * Now examine the other side's KEXINIT to see what we're up
+ * to.
+ */
+ {
+ char *str, *preferred;
+ int i, j, len;
+
+ if (pktin->type != SSH2_MSG_KEXINIT) {
+ bombout(("expected key exchange packet from server"));
+ crStopV;
+ }
+ ssh->kex = NULL;
+ ssh->hostkey = NULL;
+ s->cscipher_tobe = NULL;
+ s->sccipher_tobe = NULL;
+ s->csmac_tobe = NULL;
+ s->scmac_tobe = NULL;
+ s->cscomp_tobe = NULL;
+ s->sccomp_tobe = NULL;
+ 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 */
+
+ 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)",
+ str ? str : "(null)"));
+ 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 */
+ for (i = 0; i < lenof(hostkey_algs); i++) {
+ if (in_commasep_string(hostkey_algs[i]->name, str, len)) {
+ ssh->hostkey = hostkey_algs[i];
+ break;
+ }
+ }
+ if (!ssh->hostkey) {
+ bombout(("Couldn't agree a host key algorithm (available: %s)",
+ str ? str : "(null)"));
+ crStopV;
+ }
+
+ s->guessok = s->guessok &&
+ first_in_commasep_string(hostkey_algs[0]->name, str, len);
+ ssh_pkt_getstring(pktin, &str, &len); /* client->server cipher */
+ 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)",
+ str ? str : "(null)"));
+ crStopV;
+ }
+
+ ssh_pkt_getstring(pktin, &str, &len); /* server->client cipher */
+ 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;
+ }
+ }
+ }
+ if (s->sccipher_tobe)
+ break;
+ }
+ if (!s->sccipher_tobe) {
+ bombout(("Couldn't agree a server-to-client cipher (available: %s)",
+ str ? str : "(null)"));
+ crStopV;
+ }
+
+ ssh_pkt_getstring(pktin, &str, &len); /* client->server mac */
+ 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 */
+ 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 */
+ 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 */
+ 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");
+ }
+ ssh_pkt_getstring(pktin, &str, &len); /* client->server language */
+ ssh_pkt_getstring(pktin, &str, &len); /* server->client language */
+ s->ignorepkt = ssh2_pkt_getbool(pktin) && !s->guessok;
+
+ ssh->exhash = ssh->kex->hash->init();
+ hash_string(ssh->kex->hash, ssh->exhash, ssh->v_c, strlen(ssh->v_c));
+ hash_string(ssh->kex->hash, ssh->exhash, ssh->v_s, strlen(ssh->v_s));
+ hash_string(ssh->kex->hash, ssh->exhash,
+ s->our_kexinit, s->our_kexinitlen);
+ sfree(s->our_kexinit);
+ /* Include the type byte in the hash of server's KEXINIT */
+ hash_string(ssh->kex->hash, ssh->exhash,
+ pktin->body - 1, pktin->length + 1);
+
+ if (s->warn_kex) {
+ ssh_set_frozen(ssh, 1);
+ s->dlgret = askalg(ssh->frontend, "key-exchange algorithm",
+ ssh->kex->name,
+ ssh_dialog_callback, ssh);
+ if (s->dlgret < 0) {
+ do {
+ crReturnV;
+ if (pktin) {
+ bombout(("Unexpected data from server while"
+ " waiting for user response"));
+ crStopV;
+ }
+ } while (pktin || inlen > 0);
+ s->dlgret = ssh->user_response;
+ }
+ ssh_set_frozen(ssh, 0);
+ if (s->dlgret == 0) {
+ ssh_disconnect(ssh, "User aborted at kex warning", NULL,
+ 0, TRUE);
+ crStopV;
+ }
+ }
+
+ if (s->warn_cscipher) {
+ ssh_set_frozen(ssh, 1);
+ s->dlgret = askalg(ssh->frontend,
+ "client-to-server cipher",
+ s->cscipher_tobe->name,
+ ssh_dialog_callback, ssh);
+ if (s->dlgret < 0) {
+ do {
+ crReturnV;
+ if (pktin) {
+ bombout(("Unexpected data from server while"
+ " waiting for user response"));
+ crStopV;
+ }
+ } while (pktin || inlen > 0);
+ s->dlgret = ssh->user_response;
+ }
+ ssh_set_frozen(ssh, 0);
+ if (s->dlgret == 0) {
+ ssh_disconnect(ssh, "User aborted at cipher warning", NULL,
+ 0, TRUE);
+ crStopV;
+ }
+ }
+
+ if (s->warn_sccipher) {
+ ssh_set_frozen(ssh, 1);
+ s->dlgret = askalg(ssh->frontend,
+ "server-to-client cipher",
+ s->sccipher_tobe->name,
+ ssh_dialog_callback, ssh);
+ if (s->dlgret < 0) {
+ do {
+ crReturnV;
+ if (pktin) {
+ bombout(("Unexpected data from server while"
+ " waiting for user response"));
+ crStopV;
+ }
+ } while (pktin || inlen > 0);
+ s->dlgret = ssh->user_response;
+ }
+ ssh_set_frozen(ssh, 0);
+ if (s->dlgret == 0) {
+ ssh_disconnect(ssh, "User aborted at cipher warning", NULL,
+ 0, TRUE);
+ crStopV;
+ }
+ }
+
+ if (s->ignorepkt) /* first_kex_packet_follows */
+ crWaitUntilV(pktin); /* Ignore packet */
+ }
+
+ if (ssh->kex->main_type == KEXTYPE_DH) {
+ /*
+ * Work out the number of bits of key we will need from the
+ * key exchange. We start with the maximum key length of
+ * either cipher...
+ */
+ {
+ int csbits, scbits;
+
+ csbits = s->cscipher_tobe->keylen;
+ scbits = s->sccipher_tobe->keylen;
+ s->nbits = (csbits > scbits ? csbits : scbits);
+ }
+ /* The keys only have hlen-bit entropy, since they're based on
+ * a hash. So cap the key size at hlen bits. */
+ if (s->nbits > ssh->kex->hash->hlen * 8)
+ s->nbits = ssh->kex->hash->hlen * 8;
+
+ /*
+ * If we're doing Diffie-Hellman group exchange, start by
+ * requesting a group.
+ */
+ if (!ssh->kex->pdata) {
+ logevent("Doing Diffie-Hellman group exchange");
+ ssh->pkt_kctx = SSH2_PKTCTX_DHGEX;
+ /*
+ * Work out how big a DH group we will need to allow that
+ * much data.
+ */
+ s->pbits = 512 << ((s->nbits - 1) / 64);
+ s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_DH_GEX_REQUEST);
+ ssh2_pkt_adduint32(s->pktout, s->pbits);
+ ssh2_pkt_send_noqueue(ssh, s->pktout);
+
+ crWaitUntilV(pktin);
+ if (pktin->type != SSH2_MSG_KEX_DH_GEX_GROUP) {
+ bombout(("expected key exchange group packet from server"));
+ crStopV;
+ }
+ s->p = ssh2_pkt_getmp(pktin);
+ s->g = ssh2_pkt_getmp(pktin);
+ if (!s->p || !s->g) {
+ bombout(("unable to read mp-ints from incoming group packet"));
+ crStopV;
+ }
+ ssh->kex_ctx = dh_setup_gex(s->p, s->g);
+ s->kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT;
+ s->kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY;
+ } else {
+ ssh->pkt_kctx = SSH2_PKTCTX_DHGROUP;
+ ssh->kex_ctx = dh_setup_group(ssh->kex);
+ s->kex_init_value = SSH2_MSG_KEXDH_INIT;
+ s->kex_reply_value = SSH2_MSG_KEXDH_REPLY;
+ logeventf(ssh, "Using Diffie-Hellman with standard group \"%s\"",
+ ssh->kex->groupname);
+ }
+
+ logeventf(ssh, "Doing Diffie-Hellman key exchange with hash %s",
+ ssh->kex->hash->text_name);
+ /*
+ * Now generate and send e for Diffie-Hellman.
+ */
+ set_busy_status(ssh->frontend, BUSY_CPU); /* this can take a while */
+ s->e = dh_create_e(ssh->kex_ctx, s->nbits * 2);
+ s->pktout = ssh2_pkt_init(s->kex_init_value);
+ ssh2_pkt_addmp(s->pktout, s->e);
+ ssh2_pkt_send_noqueue(ssh, s->pktout);
+
+ set_busy_status(ssh->frontend, BUSY_WAITING); /* wait for server */
+ crWaitUntilV(pktin);
+ if (pktin->type != s->kex_reply_value) {
+ bombout(("expected key exchange reply packet from server"));
+ crStopV;
+ }
+ set_busy_status(ssh->frontend, BUSY_CPU); /* cogitate */
+ ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen);
+ s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen);
+ s->f = ssh2_pkt_getmp(pktin);
+ if (!s->f) {
+ bombout(("unable to parse key exchange reply packet"));
+ crStopV;
+ }
+ ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen);
+
+ s->K = dh_find_K(ssh->kex_ctx, s->f);
+
+ /* We assume everything from now on will be quick, and it might
+ * involve user interaction. */
+ set_busy_status(ssh->frontend, BUSY_NOT);
+
+ hash_string(ssh->kex->hash, ssh->exhash, s->hostkeydata, s->hostkeylen);
+ if (!ssh->kex->pdata) {
+ hash_uint32(ssh->kex->hash, ssh->exhash, s->pbits);
+ hash_mpint(ssh->kex->hash, ssh->exhash, s->p);
+ hash_mpint(ssh->kex->hash, ssh->exhash, s->g);
+ }
+ hash_mpint(ssh->kex->hash, ssh->exhash, s->e);
+ hash_mpint(ssh->kex->hash, ssh->exhash, s->f);
+
+ dh_cleanup(ssh->kex_ctx);
+ freebn(s->f);
+ if (!ssh->kex->pdata) {
+ freebn(s->g);
+ freebn(s->p);
+ }
+ } else {
+ logeventf(ssh, "Doing RSA key exchange with hash %s",
+ ssh->kex->hash->text_name);
+ ssh->pkt_kctx = SSH2_PKTCTX_RSAKEX;
+ /*
+ * RSA key exchange. First expect a KEXRSA_PUBKEY packet
+ * from the server.
+ */
+ crWaitUntilV(pktin);
+ if (pktin->type != SSH2_MSG_KEXRSA_PUBKEY) {
+ bombout(("expected RSA public key packet from server"));
+ crStopV;
+ }
+
+ ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen);
+ hash_string(ssh->kex->hash, ssh->exhash,
+ s->hostkeydata, s->hostkeylen);
+ s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen);
+
+ {
+ char *keydata;
+ ssh_pkt_getstring(pktin, &keydata, &s->rsakeylen);
+ s->rsakeydata = snewn(s->rsakeylen, char);
+ memcpy(s->rsakeydata, keydata, s->rsakeylen);
+ }
+
+ s->rsakey = ssh_rsakex_newkey(s->rsakeydata, s->rsakeylen);
+ if (!s->rsakey) {
+ sfree(s->rsakeydata);
+ bombout(("unable to parse RSA public key from server"));
+ crStopV;
+ }
+
+ hash_string(ssh->kex->hash, ssh->exhash, s->rsakeydata, s->rsakeylen);
+
+ /*
+ * Next, set up a shared secret K, of precisely KLEN -
+ * 2*HLEN - 49 bits, where KLEN is the bit length of the
+ * RSA key modulus and HLEN is the bit length of the hash
+ * we're using.
+ */
+ {
+ int klen = ssh_rsakex_klen(s->rsakey);
+ int nbits = klen - (2*ssh->kex->hash->hlen*8 + 49);
+ int i, byte = 0;
+ unsigned char *kstr1, *kstr2, *outstr;
+ int kstr1len, kstr2len, outstrlen;
+
+ s->K = bn_power_2(nbits - 1);
+
+ for (i = 0; i < nbits; i++) {
+ if ((i & 7) == 0) {
+ byte = random_byte();
+ }
+ bignum_set_bit(s->K, i, (byte >> (i & 7)) & 1);
+ }
+
+ /*
+ * Encode this as an mpint.
+ */
+ kstr1 = ssh2_mpint_fmt(s->K, &kstr1len);
+ kstr2 = snewn(kstr2len = 4 + kstr1len, unsigned char);
+ PUT_32BIT(kstr2, kstr1len);
+ memcpy(kstr2 + 4, kstr1, kstr1len);
+
+ /*
+ * Encrypt it with the given RSA key.
+ */
+ outstrlen = (klen + 7) / 8;
+ outstr = snewn(outstrlen, unsigned char);
+ ssh_rsakex_encrypt(ssh->kex->hash, kstr2, kstr2len,
+ outstr, outstrlen, s->rsakey);
+
+ /*
+ * And send it off in a return packet.
+ */
+ s->pktout = ssh2_pkt_init(SSH2_MSG_KEXRSA_SECRET);
+ ssh2_pkt_addstring_start(s->pktout);
+ ssh2_pkt_addstring_data(s->pktout, (char *)outstr, outstrlen);
+ ssh2_pkt_send_noqueue(ssh, s->pktout);
+
+ hash_string(ssh->kex->hash, ssh->exhash, outstr, outstrlen);
+
+ sfree(kstr2);
+ sfree(kstr1);
+ sfree(outstr);
+ }
+
+ ssh_rsakex_freekey(s->rsakey);
+
+ crWaitUntilV(pktin);
+ if (pktin->type != SSH2_MSG_KEXRSA_DONE) {
+ sfree(s->rsakeydata);
+ bombout(("expected signature packet from server"));
+ crStopV;
+ }
+
+ ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen);
+
+ sfree(s->rsakeydata);
+ }
+
+ hash_mpint(ssh->kex->hash, ssh->exhash, s->K);
+ assert(ssh->kex->hash->hlen <= sizeof(s->exchange_hash));
+ ssh->kex->hash->final(ssh->exhash, s->exchange_hash);
+
+ ssh->kex_ctx = NULL;
+
+#if 0
+ debug(("Exchange hash is:\n"));
+ dmemdump(s->exchange_hash, ssh->kex->hash->hlen);
+#endif
+
+ if (!s->hkey ||
+ !ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen,
+ (char *)s->exchange_hash,
+ ssh->kex->hash->hlen)) {
+ bombout(("Server's host key did not match the signature supplied"));
+ crStopV;
+ }
+
+ s->keystr = ssh->hostkey->fmtkey(s->hkey);
+ if (!s->got_session_id) {
+ /*
+ * Authenticate remote host: verify host key. (We've already
+ * checked the signature of the exchange hash.)
+ */
+ s->fingerprint = ssh->hostkey->fingerprint(s->hkey);
+ ssh_set_frozen(ssh, 1);
+ s->dlgret = verify_ssh_host_key(ssh->frontend,
+ ssh->savedhost, ssh->savedport,
+ ssh->hostkey->keytype, s->keystr,
+ s->fingerprint,
+ ssh_dialog_callback, ssh);
+ if (s->dlgret < 0) {
+ do {
+ crReturnV;
+ if (pktin) {
+ bombout(("Unexpected data from server while waiting"
+ " for user host key response"));
+ crStopV;
+ }
+ } while (pktin || inlen > 0);
+ s->dlgret = ssh->user_response;
+ }
+ ssh_set_frozen(ssh, 0);
+ if (s->dlgret == 0) {
+ ssh_disconnect(ssh, "User aborted at host key verification", NULL,
+ 0, TRUE);
+ crStopV;
+ }
+ logevent("Host key fingerprint is:");
+ logevent(s->fingerprint);
+ sfree(s->fingerprint);
+ /*
+ * Save this host key, to check against the one presented in
+ * subsequent rekeys.
+ */
+ ssh->hostkey_str = s->keystr;
+ } else {
+ /*
+ * In a rekey, we never present an interactive host key
+ * verification request to the user. Instead, we simply
+ * enforce that the key we're seeing this time is identical to
+ * the one we saw before.
+ */
+ if (strcmp(ssh->hostkey_str, s->keystr)) {
+ bombout(("Host key was different in repeat key exchange"));
+ crStopV;
+ }
+ sfree(s->keystr);
+ }
+ ssh->hostkey->freekey(s->hkey);
+
+ /*
+ * The exchange hash from the very first key exchange is also
+ * the session id, used in session key construction and
+ * authentication.
+ */
+ if (!s->got_session_id) {
+ assert(sizeof(s->exchange_hash) <= sizeof(ssh->v2_session_id));
+ memcpy(ssh->v2_session_id, s->exchange_hash,
+ sizeof(s->exchange_hash));
+ ssh->v2_session_id_len = ssh->kex->hash->hlen;
+ assert(ssh->v2_session_id_len <= sizeof(ssh->v2_session_id));
+ s->got_session_id = TRUE;
+ }
+
+ /*
+ * Send SSH2_MSG_NEWKEYS.
+ */
+ s->pktout = ssh2_pkt_init(SSH2_MSG_NEWKEYS);
+ ssh2_pkt_send_noqueue(ssh, s->pktout);
+ ssh->outgoing_data_size = 0; /* start counting from here */
+
+ /*
+ * We've sent client NEWKEYS, so create and initialise
+ * client-to-server session keys.
+ */
+ 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->cs_mac_ctx)
+ ssh->csmac->free_context(ssh->cs_mac_ctx);
+ ssh->csmac = s->csmac_tobe;
+ ssh->cs_mac_ctx = ssh->csmac->make_context();
+
+ if (ssh->cs_comp_ctx)
+ ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx);
+ ssh->cscomp = s->cscomp_tobe;
+ ssh->cs_comp_ctx = ssh->cscomp->compress_init();
+
+ /*
+ * 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->cscomp->text_name)
+ logeventf(ssh, "Initialised %s compression",
+ ssh->cscomp->text_name);
+
+ /*
+ * Now our end of the key exchange is complete, we can send all
+ * our queued higher-layer packets.
+ */
+ ssh->queueing = FALSE;
+ ssh2_pkt_queuesend(ssh);
+
+ /*
+ * Expect SSH2_MSG_NEWKEYS from server.
+ */
+ crWaitUntilV(pktin);
+ if (pktin->type != SSH2_MSG_NEWKEYS) {
+ bombout(("expected new-keys packet from server"));
+ crStopV;
+ }
+ ssh->incoming_data_size = 0; /* start counting from here */
+
+ /*
+ * We've seen server NEWKEYS, so create and initialise
+ * server-to-client session keys.
+ */
+ 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 (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 (ssh->sc_comp_ctx)
+ ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx);
+ ssh->sccomp = s->sccomp_tobe;
+ ssh->sc_comp_ctx = ssh->sccomp->decompress_init();
+
+ /*
+ * 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->sccomp->text_name)
+ logeventf(ssh, "Initialised %s decompression",
+ ssh->sccomp->text_name);
+
+ /*
+ * Free shared secret.
+ */
+ freebn(s->K);
+
+ /*
+ * Key exchange is over. Loop straight back round if we have a
+ * deferred rekey reason.
+ */
+ if (ssh->deferred_rekey_reason) {
+ logevent(ssh->deferred_rekey_reason);
+ pktin = NULL;
+ ssh->deferred_rekey_reason = NULL;
+ goto begin_key_exchange;
+ }
+
+ /*
+ * Otherwise, schedule a timer for our next rekey.
+ */
+ ssh->kex_in_progress = FALSE;
+ ssh->last_rekey = GETTICKCOUNT();
+ if (conf_get_int(ssh->conf, CONF_ssh_rekey_time) != 0)
+ ssh->next_rekey = schedule_timer(conf_get_int(ssh->conf, CONF_ssh_rekey_time)*60*TICKSPERSEC,
+ ssh2_timer, ssh);
+
+ /*
+ * Now we're encrypting. Begin returning 1 to the protocol main
+ * function so that other things can run on top of the
+ * transport. If we ever see a KEXINIT, we must go back to the
+ * start.
+ *
+ * We _also_ go back to the start if we see pktin==NULL and
+ * inlen negative, because this is a special signal meaning
+ * `initiate client-driven rekey', and `in' contains a message
+ * giving the reason for the rekey.
+ *
+ * inlen==-1 means always initiate a rekey;
+ * inlen==-2 means that userauth has completed successfully and
+ * we should consider rekeying (for delayed compression).
+ */
+ while (!((pktin && pktin->type == SSH2_MSG_KEXINIT) ||
+ (!pktin && inlen < 0))) {
+ wait_for_rekey:
+ if (!ssh->protocol_initial_phase_done) {
+ ssh->protocol_initial_phase_done = TRUE;
+ /*
+ * Allow authconn to initialise itself.
+ */
+ do_ssh2_authconn(ssh, NULL, 0, NULL);
+ }
+ crReturnV;
+ }
+ if (pktin) {
+ logevent("Server initiated key re-exchange");
+ } else {
+ if (inlen == -2) {
+ /*
+ * authconn has seen a USERAUTH_SUCCEEDED. Time to enable
+ * delayed compression, if it's available.
+ *
+ * draft-miller-secsh-compression-delayed-00 says that you
+ * negotiate delayed compression in the first key exchange, and
+ * both sides start compressing when the server has sent
+ * USERAUTH_SUCCESS. This has a race condition -- the server
+ * can't know when the client has seen it, and thus which incoming
+ * packets it should treat as compressed.
+ *
+ * Instead, we do the initial key exchange without offering the
+ * delayed methods, but note if the server offers them; when we
+ * get here, if a delayed method was available that was higher
+ * on our list than what we got, we initiate a rekey in which we
+ * _do_ list the delayed methods (and hopefully get it as a
+ * result). Subsequent rekeys will do the same.
+ */
+ assert(!s->userauth_succeeded); /* should only happen once */
+ s->userauth_succeeded = TRUE;
+ if (!s->pending_compression)
+ /* Can't see any point rekeying. */
+ goto wait_for_rekey; /* this is utterly horrid */
+ /* else fall through to rekey... */
+ s->pending_compression = FALSE;
+ }
+ /*
+ * Now we've decided to rekey.
+ *
+ * Special case: if the server bug is set that doesn't
+ * allow rekeying, we give a different log message and
+ * continue waiting. (If such a server _initiates_ a rekey,
+ * we process it anyway!)
+ */
+ if ((ssh->remote_bugs & BUG_SSH2_REKEY)) {
+ logeventf(ssh, "Server bug prevents key re-exchange (%s)",
+ (char *)in);
+ /* Reset the counters, so that at least this message doesn't
+ * hit the event log _too_ often. */
+ ssh->outgoing_data_size = 0;
+ ssh->incoming_data_size = 0;
+ if (conf_get_int(ssh->conf, CONF_ssh_rekey_time) != 0) {
+ ssh->next_rekey =
+ schedule_timer(conf_get_int(ssh->conf, CONF_ssh_rekey_time)*60*TICKSPERSEC,
+ ssh2_timer, ssh);
+ }
+ goto wait_for_rekey; /* this is still utterly horrid */
+ } else {
+ logeventf(ssh, "Initiating key re-exchange (%s)", (char *)in);
+ }
+ }
+ goto begin_key_exchange;
+
+ crFinishV;
+}
+
+/*
+ * Add data to an SSH-2 channel output buffer.
+ */
+static void ssh2_add_channel_data(struct ssh_channel *c, char *buf,
+ int len)
+{
+ bufchain_add(&c->v.v2.outbuffer, buf, len);
+}
+
+/*
+ * Attempt to send data on an SSH-2 channel.
+ */
+static int ssh2_try_send(struct ssh_channel *c)
+{
+ Ssh ssh = c->ssh;
+ struct Packet *pktout;
+ int ret;
+
+ while (c->v.v2.remwindow > 0 && bufchain_size(&c->v.v2.outbuffer) > 0) {
+ int len;
+ void *data;
+ bufchain_prefix(&c->v.v2.outbuffer, &data, &len);
+ if ((unsigned)len > c->v.v2.remwindow)
+ len = c->v.v2.remwindow;
+ if ((unsigned)len > c->v.v2.remmaxpkt)
+ len = c->v.v2.remmaxpkt;
+ pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_DATA);
+ ssh2_pkt_adduint32(pktout, c->remoteid);
+ ssh2_pkt_addstring_start(pktout);
+ ssh2_pkt_addstring_data(pktout, data, len);
+ ssh2_pkt_send(ssh, pktout);
+ bufchain_consume(&c->v.v2.outbuffer, len);
+ c->v.v2.remwindow -= len;
+ }
+
+ /*
+ * After having sent as much data as we can, return the amount
+ * still buffered.
+ */
+ ret = bufchain_size(&c->v.v2.outbuffer);
+
+ /*
+ * And if there's no data pending but we need to send an EOF, send
+ * it.
+ */
+ if (!ret && c->pending_eof)
+ ssh_channel_try_eof(c);
+
+ return ret;
+}
+
+static void ssh2_try_send_and_unthrottle(Ssh ssh, struct ssh_channel *c)
+{
+ int bufsize;
+ if (c->closes & CLOSES_SENT_EOF)
+ return; /* don't send on channels we've EOFed */
+ bufsize = ssh2_try_send(c);
+ if (bufsize == 0) {
+ switch (c->type) {
+ case CHAN_MAINSESSION:
+ /* stdin need not receive an unthrottle
+ * notification since it will be polled */
+ break;
+ case CHAN_X11:
+ x11_unthrottle(c->u.x11.xconn);
+ break;
+ case CHAN_AGENT:
+ /* agent sockets are request/response and need no
+ * buffer management */
+ break;
+ case CHAN_SOCKDATA:
+ pfd_unthrottle(c->u.pfd.pf);
+ break;
+ }
+ }
+}
+
+static int ssh_is_simple(Ssh ssh)
+{
+ /*
+ * We use the 'simple' variant of the SSH protocol if we're asked
+ * to, except not if we're also doing connection-sharing (either
+ * tunnelling our packets over an upstream or expecting to be
+ * tunnelled over ourselves), since then the assumption that we
+ * have only one channel to worry about is not true after all.
+ */
+ return (conf_get_int(ssh->conf, CONF_ssh_simple) &&
+ !ssh->bare_connection && !ssh->connshare);
+}
+
+/*
+ * Set up most of a new ssh_channel for SSH-2.
+ */
+static void ssh2_channel_init(struct ssh_channel *c)
+{
+ Ssh ssh = c->ssh;
+ c->localid = alloc_channel_id(ssh);
+ c->closes = 0;
+ c->pending_eof = FALSE;
+ c->throttling_conn = FALSE;
+ c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin =
+ ssh_is_simple(ssh) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE;
+ c->v.v2.chanreq_head = NULL;
+ c->v.v2.throttle_state = UNTHROTTLED;
+ bufchain_init(&c->v.v2.outbuffer);
+}
+
+/*
+ * Construct the common parts of a CHANNEL_OPEN.
+ */
+static struct Packet *ssh2_chanopen_init(struct ssh_channel *c, char *type)
+{
+ struct Packet *pktout;
+
+ pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
+ ssh2_pkt_addstring(pktout, type);
+ ssh2_pkt_adduint32(pktout, c->localid);
+ ssh2_pkt_adduint32(pktout, c->v.v2.locwindow);/* our window size */
+ ssh2_pkt_adduint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */
+ return pktout;
+}
+
+/*
+ * CHANNEL_FAILURE doesn't come with any indication of what message
+ * caused it, so we have to keep track of the outstanding
+ * CHANNEL_REQUESTs ourselves.
+ */
+static void ssh2_queue_chanreq_handler(struct ssh_channel *c,
+ cchandler_fn_t handler, void *ctx)
+{
+ struct outstanding_channel_request *ocr =
+ snew(struct outstanding_channel_request);
+
+ assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE)));
+ ocr->handler = handler;
+ ocr->ctx = ctx;
+ ocr->next = NULL;
+ if (!c->v.v2.chanreq_head)
+ c->v.v2.chanreq_head = ocr;
+ else
+ c->v.v2.chanreq_tail->next = ocr;
+ c->v.v2.chanreq_tail = ocr;
+}
+
+/*
+ * Construct the common parts of a CHANNEL_REQUEST. If handler is not
+ * NULL then a reply will be requested and the handler will be called
+ * when it arrives. The returned packet is ready to have any
+ * request-specific data added and be sent. Note that if a handler is
+ * provided, it's essential that the request actually be sent.
+ *
+ * The handler will usually be passed the response packet in pktin.
+ * If pktin is NULL, this means that no reply will ever be forthcoming
+ * (e.g. because the entire connection is being destroyed) and the
+ * handler should free any storage it's holding.
+ */
+static struct Packet *ssh2_chanreq_init(struct ssh_channel *c, char *type,
+ cchandler_fn_t handler, void *ctx)
+{
+ struct Packet *pktout;
+
+ assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE)));
+ pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
+ ssh2_pkt_adduint32(pktout, c->remoteid);
+ ssh2_pkt_addstring(pktout, type);
+ ssh2_pkt_addbool(pktout, handler != NULL);
+ if (handler != NULL)
+ ssh2_queue_chanreq_handler(c, handler, ctx);
+ return pktout;
+}
+
+/*
+ * Potentially enlarge the window on an SSH-2 channel.
+ */
+static void ssh2_handle_winadj_response(struct ssh_channel *, struct Packet *,
+ void *);
+static void ssh2_set_window(struct ssh_channel *c, int newwin)
+{
+ Ssh ssh = c->ssh;
+
+ /*
+ * Never send WINDOW_ADJUST for a channel that the remote side has
+ * already sent EOF on; there's no point, since it won't be
+ * sending any more data anyway. Ditto if _we've_ already sent
+ * CLOSE.
+ */
+ if (c->closes & (CLOSES_RCVD_EOF | CLOSES_SENT_CLOSE))
+ return;
+
+ /*
+ * Also, never widen the window for an X11 channel when we're
+ * still waiting to see its initial auth and may yet hand it off
+ * to a downstream.
+ */
+ if (c->type == CHAN_X11 && c->u.x11.initial)
+ return;
+
+ /*
+ * If the remote end has a habit of ignoring maxpkt, limit the
+ * window so that it has no choice (assuming it doesn't ignore the
+ * window as well).
+ */
+ if ((ssh->remote_bugs & BUG_SSH2_MAXPKT) && newwin > OUR_V2_MAXPKT)
+ newwin = OUR_V2_MAXPKT;
+
+ /*
+ * Only send a WINDOW_ADJUST if there's significantly more window
+ * available than the other end thinks there is. This saves us
+ * sending a WINDOW_ADJUST for every character in a shell session.
+ *
+ * "Significant" is arbitrarily defined as half the window size.
+ */
+ if (newwin / 2 >= c->v.v2.locwindow) {
+ struct Packet *pktout;
+ unsigned *up;
+
+ /*
+ * In order to keep track of how much window the client
+ * actually has available, we'd like it to acknowledge each
+ * WINDOW_ADJUST. We can't do that directly, so we accompany
+ * it with a CHANNEL_REQUEST that has to be acknowledged.
+ *
+ * This is only necessary if we're opening the window wide.
+ * If we're not, then throughput is being constrained by
+ * something other than the maximum window size anyway.
+ */
+ if (newwin == c->v.v2.locmaxwin &&
+ !(ssh->remote_bugs & BUG_CHOKES_ON_WINADJ)) {
+ up = snew(unsigned);
+ *up = newwin - c->v.v2.locwindow;
+ pktout = ssh2_chanreq_init(c, "winadj@putty.projects.tartarus.org",
+ ssh2_handle_winadj_response, up);
+ ssh2_pkt_send(ssh, pktout);
+
+ if (c->v.v2.throttle_state != UNTHROTTLED)
+ c->v.v2.throttle_state = UNTHROTTLING;
+ } else {
+ /* Pretend the WINDOW_ADJUST was acked immediately. */
+ c->v.v2.remlocwin = newwin;
+ c->v.v2.throttle_state = THROTTLED;
+ }
+ pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+ ssh2_pkt_adduint32(pktout, c->remoteid);
+ ssh2_pkt_adduint32(pktout, newwin - c->v.v2.locwindow);
+ ssh2_pkt_send(ssh, pktout);
+ c->v.v2.locwindow = newwin;
+ }
+}
+
+/*
+ * Find the channel associated with a message. If there's no channel,
+ * or it's not properly open, make a noise about it and return NULL.
+ */
+static struct ssh_channel *ssh2_channel_msg(Ssh ssh, struct Packet *pktin)
+{
+ unsigned localid = ssh_pkt_getuint32(pktin);
+ struct ssh_channel *c;
+
+ c = find234(ssh->channels, &localid, ssh_channelfind);
+ if (!c ||
+ (c->type != CHAN_SHARING && c->halfopen &&
+ pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION &&
+ pktin->type != SSH2_MSG_CHANNEL_OPEN_FAILURE)) {
+ char *buf = dupprintf("Received %s for %s channel %u",
+ ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx,
+ pktin->type),
+ c ? "half-open" : "nonexistent", localid);
+ ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
+ sfree(buf);
+ return NULL;
+ }
+ return c;
+}
+
+static void ssh2_handle_winadj_response(struct ssh_channel *c,
+ struct Packet *pktin, void *ctx)
+{
+ unsigned *sizep = ctx;
+
+ /*
+ * Winadj responses should always be failures. However, at least
+ * one server ("boks_sshd") is known to return SUCCESS for channel
+ * requests it's never heard of, such as "winadj@putty". Raised
+ * with foxt.com as bug 090916-090424, but for the sake of a quiet
+ * life, we don't worry about what kind of response we got.
+ */
+
+ c->v.v2.remlocwin += *sizep;
+ sfree(sizep);
+ /*
+ * winadj messages are only sent when the window is fully open, so
+ * if we get an ack of one, we know any pending unthrottle is
+ * complete.
+ */
+ if (c->v.v2.throttle_state == UNTHROTTLING)
+ c->v.v2.throttle_state = UNTHROTTLED;
+}
+
+static void ssh2_msg_channel_response(Ssh ssh, struct Packet *pktin)
+{
+ struct ssh_channel *c = ssh2_channel_msg(ssh, pktin);
+ struct outstanding_channel_request *ocr;
+
+ if (!c) return;
+ if (c->type == CHAN_SHARING) {
+ share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+ pktin->body, pktin->length);
+ return;
+ }
+ ocr = c->v.v2.chanreq_head;
+ if (!ocr) {
+ ssh2_msg_unexpected(ssh, pktin);
+ return;
+ }
+ ocr->handler(c, pktin, ocr->ctx);
+ c->v.v2.chanreq_head = ocr->next;
+ sfree(ocr);
+ /*
+ * We may now initiate channel-closing procedures, if that
+ * CHANNEL_REQUEST was the last thing outstanding before we send
+ * CHANNEL_CLOSE.
+ */
+ ssh2_channel_check_close(c);
+}
+
+static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin)
+{
+ struct ssh_channel *c;
+ c = ssh2_channel_msg(ssh, pktin);
+ if (!c)
+ return;
+ if (c->type == CHAN_SHARING) {
+ share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+ pktin->body, pktin->length);
+ return;
+ }
+ if (!(c->closes & CLOSES_SENT_EOF)) {
+ c->v.v2.remwindow += ssh_pkt_getuint32(pktin);
+ ssh2_try_send_and_unthrottle(ssh, c);
+ }
+}
+
+static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin)
+{
+ char *data;
+ int length;
+ struct ssh_channel *c;
+ c = ssh2_channel_msg(ssh, pktin);
+ if (!c)
+ return;
+ if (c->type == CHAN_SHARING) {
+ share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+ pktin->body, pktin->length);
+ return;
+ }
+ if (pktin->type == SSH2_MSG_CHANNEL_EXTENDED_DATA &&
+ ssh_pkt_getuint32(pktin) != SSH2_EXTENDED_DATA_STDERR)
+ return; /* extended but not stderr */
+ ssh_pkt_getstring(pktin, &data, &length);
+ if (data) {
+ int bufsize = 0;
+ c->v.v2.locwindow -= length;
+ c->v.v2.remlocwin -= length;
+ switch (c->type) {
+ case CHAN_MAINSESSION:
+ bufsize =
+ from_backend(ssh->frontend, pktin->type ==
+ SSH2_MSG_CHANNEL_EXTENDED_DATA,
+ data, length);
+ break;
+ case CHAN_X11:
+ bufsize = x11_send(c->u.x11.xconn, data, length);
+ break;
+ case CHAN_SOCKDATA:
+ bufsize = pfd_send(c->u.pfd.pf, data, length);
+ break;
+ case CHAN_AGENT:
+ while (length > 0) {
+ if (c->u.a.lensofar < 4) {
+ unsigned int l = min(4 - c->u.a.lensofar,
+ (unsigned)length);
+ memcpy(c->u.a.msglen + c->u.a.lensofar,
+ data, l);
+ data += l;
+ length -= l;
+ c->u.a.lensofar += l;
+ }
+ if (c->u.a.lensofar == 4) {
+ c->u.a.totallen =
+ 4 + GET_32BIT(c->u.a.msglen);
+ c->u.a.message = snewn(c->u.a.totallen,
+ unsigned char);
+ memcpy(c->u.a.message, c->u.a.msglen, 4);
+ }
+ if (c->u.a.lensofar >= 4 && length > 0) {
+ unsigned int l =
+ min(c->u.a.totallen - c->u.a.lensofar,
+ (unsigned)length);
+ memcpy(c->u.a.message + c->u.a.lensofar,
+ data, l);
+ data += l;
+ length -= l;
+ c->u.a.lensofar += l;
+ }
+ if (c->u.a.lensofar == c->u.a.totallen) {
+ void *reply;
+ int replylen;
+ c->u.a.outstanding_requests++;
+ if (agent_query(c->u.a.message,
+ c->u.a.totallen,
+ &reply, &replylen,
+ ssh_agentf_callback, c))
+ ssh_agentf_callback(c, reply, replylen);
+ sfree(c->u.a.message);
+ c->u.a.message = NULL;
+ c->u.a.lensofar = 0;
+ }
+ }
+ bufsize = 0;
+ break;
+ }
+ /*
+ * If it looks like the remote end hit the end of its window,
+ * and we didn't want it to do that, think about using a
+ * larger window.
+ */
+ if (c->v.v2.remlocwin <= 0 && c->v.v2.throttle_state == UNTHROTTLED &&
+ c->v.v2.locmaxwin < 0x40000000)
+ c->v.v2.locmaxwin += OUR_V2_WINSIZE;
+ /*
+ * If we are not buffering too much data,
+ * enlarge the window again at the remote side.
+ * If we are buffering too much, we may still
+ * need to adjust the window if the server's
+ * sent excess data.
+ */
+ ssh2_set_window(c, bufsize < c->v.v2.locmaxwin ?
+ c->v.v2.locmaxwin - bufsize : 0);
+ /*
+ * If we're either buffering way too much data, or if we're
+ * buffering anything at all and we're in "simple" mode,
+ * throttle the whole channel.
+ */
+ if ((bufsize > c->v.v2.locmaxwin || (ssh_is_simple(ssh) && bufsize>0))
+ && !c->throttling_conn) {
+ c->throttling_conn = 1;
+ ssh_throttle_conn(ssh, +1);
+ }
+ }
+}
+
+static void ssh_check_termination(Ssh ssh)
+{
+ if (ssh->version == 2 &&
+ !conf_get_int(ssh->conf, CONF_ssh_no_shell) &&
+ count234(ssh->channels) == 0 &&
+ !(ssh->connshare && share_ndownstreams(ssh->connshare) > 0)) {
+ /*
+ * We used to send SSH_MSG_DISCONNECT here, because I'd
+ * believed that _every_ conforming SSH-2 connection had to
+ * end with a disconnect being sent by at least one side;
+ * apparently I was wrong and it's perfectly OK to
+ * unceremoniously slam the connection shut when you're done,
+ * and indeed OpenSSH feels this is more polite than sending a
+ * DISCONNECT. So now we don't.
+ */
+ ssh_disconnect(ssh, "All channels closed", NULL, 0, TRUE);
+ }
+}
+
+void ssh_sharing_downstream_connected(Ssh ssh, unsigned id)
+{
+ logeventf(ssh, "Connection sharing downstream #%u connected", id);
+}
+
+void ssh_sharing_downstream_disconnected(Ssh ssh, unsigned id)
+{
+ logeventf(ssh, "Connection sharing downstream #%u disconnected", id);
+ ssh_check_termination(ssh);
+}
+
+void ssh_sharing_logf(Ssh ssh, unsigned id, const char *logfmt, ...)
+{
+ va_list ap;
+ char *buf;
+
+ va_start(ap, logfmt);
+ buf = dupvprintf(logfmt, ap);
+ va_end(ap);
+ if (id)
+ logeventf(ssh, "Connection sharing downstream #%u: %s", id, buf);
+ else
+ logeventf(ssh, "Connection sharing: %s", buf);
+ sfree(buf);
+}
+
+static void ssh_channel_destroy(struct ssh_channel *c)
+{
+ Ssh ssh = c->ssh;
+
+ switch (c->type) {
+ case CHAN_MAINSESSION:
+ ssh->mainchan = NULL;
+ update_specials_menu(ssh->frontend);
+ break;
+ case CHAN_X11:
+ if (c->u.x11.xconn != NULL)
+ x11_close(c->u.x11.xconn);
+ logevent("Forwarded X11 connection terminated");
+ break;
+ case CHAN_AGENT:
+ sfree(c->u.a.message);
+ break;
+ case CHAN_SOCKDATA:
+ if (c->u.pfd.pf != NULL)
+ pfd_close(c->u.pfd.pf);
+ logevent("Forwarded port closed");
+ break;
+ }
+
+ del234(ssh->channels, c);
+ if (ssh->version == 2) {
+ bufchain_clear(&c->v.v2.outbuffer);
+ assert(c->v.v2.chanreq_head == NULL);
+ }
+ sfree(c);
+
+ /*
+ * If that was the last channel left open, we might need to
+ * terminate.
+ */
+ ssh_check_termination(ssh);
+}
+
+static void ssh2_channel_check_close(struct ssh_channel *c)
+{
+ Ssh ssh = c->ssh;
+ struct Packet *pktout;
+
+ if (c->halfopen) {
+ /*
+ * If we've sent out our own CHANNEL_OPEN but not yet seen
+ * either OPEN_CONFIRMATION or OPEN_FAILURE in response, then
+ * it's too early to be sending close messages of any kind.
+ */
+ return;
+ }
+
+ if ((!((CLOSES_SENT_EOF | CLOSES_RCVD_EOF) & ~c->closes) ||
+ c->type == CHAN_ZOMBIE) &&
+ !c->v.v2.chanreq_head &&
+ !(c->closes & CLOSES_SENT_CLOSE)) {
+ /*
+ * We have both sent and received EOF (or the channel is a
+ * zombie), and we have no outstanding channel requests, which
+ * means the channel is in final wind-up. But we haven't sent
+ * CLOSE, so let's do so now.
+ */
+ pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
+ ssh2_pkt_adduint32(pktout, c->remoteid);
+ ssh2_pkt_send(ssh, pktout);
+ c->closes |= CLOSES_SENT_EOF | CLOSES_SENT_CLOSE;
+ }
+
+ if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) {
+ assert(c->v.v2.chanreq_head == NULL);
+ /*
+ * We have both sent and received CLOSE, which means we're
+ * completely done with the channel.
+ */
+ ssh_channel_destroy(c);
+ }
+}
+
+static void ssh2_channel_got_eof(struct ssh_channel *c)
+{
+ if (c->closes & CLOSES_RCVD_EOF)
+ return; /* already seen EOF */
+ c->closes |= CLOSES_RCVD_EOF;
+
+ if (c->type == CHAN_X11) {
+ x11_send_eof(c->u.x11.xconn);
+ } else if (c->type == CHAN_AGENT) {
+ if (c->u.a.outstanding_requests == 0) {
+ /* Manufacture an outgoing EOF in response to the incoming one. */
+ sshfwd_write_eof(c);
+ }
+ } else if (c->type == CHAN_SOCKDATA) {
+ pfd_send_eof(c->u.pfd.pf);
+ } else if (c->type == CHAN_MAINSESSION) {
+ Ssh ssh = c->ssh;
+
+ if (!ssh->sent_console_eof &&
+ (from_backend_eof(ssh->frontend) || ssh->got_pty)) {
+ /*
+ * Either from_backend_eof told us that the front end
+ * wants us to close the outgoing side of the connection
+ * as soon as we see EOF from the far end, or else we've
+ * unilaterally decided to do that because we've allocated
+ * a remote pty and hence EOF isn't a particularly
+ * meaningful concept.
+ */
+ sshfwd_write_eof(c);
+ }
+ ssh->sent_console_eof = TRUE;
+ }
+
+ ssh2_channel_check_close(c);
+}
+
+static void ssh2_msg_channel_eof(Ssh ssh, struct Packet *pktin)
+{
+ struct ssh_channel *c;
+
+ c = ssh2_channel_msg(ssh, pktin);
+ if (!c)
+ return;
+ if (c->type == CHAN_SHARING) {
+ share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+ pktin->body, pktin->length);
+ return;
+ }
+ ssh2_channel_got_eof(c);
+}
+
+static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin)
+{
+ struct ssh_channel *c;
+
+ c = ssh2_channel_msg(ssh, pktin);
+ if (!c)
+ return;
+ if (c->type == CHAN_SHARING) {
+ share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+ pktin->body, pktin->length);
+ return;
+ }
+
+ /*
+ * When we receive CLOSE on a channel, we assume it comes with an
+ * implied EOF if we haven't seen EOF yet.
+ */
+ ssh2_channel_got_eof(c);
+
+ /*
+ * And we also send an outgoing EOF, if we haven't already, on the
+ * assumption that CLOSE is a pretty forceful announcement that
+ * the remote side is doing away with the entire channel. (If it
+ * had wanted to send us EOF and continue receiving data from us,
+ * it would have just sent CHANNEL_EOF.)
+ */
+ if (!(c->closes & CLOSES_SENT_EOF)) {
+ /*
+ * Make sure we don't read any more from whatever our local
+ * data source is for this channel.
+ */
+ switch (c->type) {
+ case CHAN_MAINSESSION:
+ ssh->send_ok = 0; /* stop trying to read from stdin */
+ break;
+ case CHAN_X11:
+ x11_override_throttle(c->u.x11.xconn, 1);
+ break;
+ case CHAN_SOCKDATA:
+ pfd_override_throttle(c->u.pfd.pf, 1);
+ break;
+ }
+
+ /*
+ * Abandon any buffered data we still wanted to send to this
+ * channel. Receiving a CHANNEL_CLOSE is an indication that
+ * the server really wants to get on and _destroy_ this
+ * channel, and it isn't going to send us any further
+ * WINDOW_ADJUSTs to permit us to send pending stuff.
+ */
+ bufchain_clear(&c->v.v2.outbuffer);
+
+ /*
+ * Send outgoing EOF.
+ */
+ sshfwd_write_eof(c);
+ }
+
+ /*
+ * Now process the actual close.
+ */
+ if (!(c->closes & CLOSES_RCVD_CLOSE)) {
+ c->closes |= CLOSES_RCVD_CLOSE;
+ ssh2_channel_check_close(c);
+ }
+}
+
+static void ssh2_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin)
+{
+ struct ssh_channel *c;
+
+ c = ssh2_channel_msg(ssh, pktin);
+ if (!c)
+ return;
+ if (c->type == CHAN_SHARING) {
+ share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+ pktin->body, pktin->length);
+ return;
+ }
+ assert(c->halfopen); /* ssh2_channel_msg will have enforced this */
+ c->remoteid = ssh_pkt_getuint32(pktin);
+ c->halfopen = FALSE;
+ c->v.v2.remwindow = ssh_pkt_getuint32(pktin);
+ c->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin);
+
+ if (c->type == CHAN_SOCKDATA_DORMANT) {
+ c->type = CHAN_SOCKDATA;
+ if (c->u.pfd.pf)
+ pfd_confirm(c->u.pfd.pf);
+ } else if (c->type == CHAN_ZOMBIE) {
+ /*
+ * This case can occur if a local socket error occurred
+ * between us sending out CHANNEL_OPEN and receiving
+ * OPEN_CONFIRMATION. In this case, all we can do is
+ * immediately initiate close proceedings now that we know the
+ * server's id to put in the close message.
+ */
+ ssh2_channel_check_close(c);
+ } else {
+ /*
+ * We never expect to receive OPEN_CONFIRMATION for any
+ * *other* channel type (since only local-to-remote port
+ * forwardings cause us to send CHANNEL_OPEN after the main
+ * channel is live - all other auxiliary channel types are
+ * initiated from the server end). It's safe to enforce this
+ * by assertion rather than by ssh_disconnect, because the
+ * real point is that we never constructed a half-open channel
+ * structure in the first place with any type other than the
+ * above.
+ */
+ assert(!"Funny channel type in ssh2_msg_channel_open_confirmation");
+ }
+
+ if (c->pending_eof)
+ ssh_channel_try_eof(c); /* in case we had a pending EOF */
+}
+
+static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin)
+{
+ static const char *const reasons[] = {
+ "<unknown reason code>",
+ "Administratively prohibited",
+ "Connect failed",
+ "Unknown channel type",
+ "Resource shortage",
+ };
+ unsigned reason_code;
+ char *reason_string;
+ int reason_length;
+ struct ssh_channel *c;
+
+ c = ssh2_channel_msg(ssh, pktin);
+ if (!c)
+ return;
+ if (c->type == CHAN_SHARING) {
+ share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+ pktin->body, pktin->length);
+ return;
+ }
+ assert(c->halfopen); /* ssh2_channel_msg will have enforced this */
+
+ if (c->type == CHAN_SOCKDATA_DORMANT) {
+ reason_code = ssh_pkt_getuint32(pktin);
+ if (reason_code >= lenof(reasons))
+ reason_code = 0; /* ensure reasons[reason_code] in range */
+ ssh_pkt_getstring(pktin, &reason_string, &reason_length);
+ logeventf(ssh, "Forwarded connection refused by server: %s [%.*s]",
+ reasons[reason_code], reason_length, reason_string);
+
+ pfd_close(c->u.pfd.pf);
+ } else if (c->type == CHAN_ZOMBIE) {
+ /*
+ * This case can occur if a local socket error occurred
+ * between us sending out CHANNEL_OPEN and receiving
+ * OPEN_FAILURE. In this case, we need do nothing except allow
+ * the code below to throw the half-open channel away.
+ */
+ } else {
+ /*
+ * We never expect to receive OPEN_FAILURE for any *other*
+ * channel type (since only local-to-remote port forwardings
+ * cause us to send CHANNEL_OPEN after the main channel is
+ * live - all other auxiliary channel types are initiated from
+ * the server end). It's safe to enforce this by assertion
+ * rather than by ssh_disconnect, because the real point is
+ * that we never constructed a half-open channel structure in
+ * the first place with any type other than the above.
+ */
+ assert(!"Funny channel type in ssh2_msg_channel_open_failure");
+ }
+
+ del234(ssh->channels, c);
+ sfree(c);
+}
+
+static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin)
+{
+ char *type;
+ int typelen, want_reply;
+ int reply = SSH2_MSG_CHANNEL_FAILURE; /* default */
+ struct ssh_channel *c;
+ struct Packet *pktout;
+
+ c = ssh2_channel_msg(ssh, pktin);
+ if (!c)
+ return;
+ if (c->type == CHAN_SHARING) {
+ share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+ pktin->body, pktin->length);
+ return;
+ }
+ ssh_pkt_getstring(pktin, &type, &typelen);
+ want_reply = ssh2_pkt_getbool(pktin);
+
+ /*
+ * Having got the channel number, we now look at
+ * the request type string to see if it's something
+ * we recognise.
+ */
+ if (c == ssh->mainchan) {
+ /*
+ * We recognise "exit-status" and "exit-signal" on
+ * the primary channel.
+ */
+ if (typelen == 11 &&
+ !memcmp(type, "exit-status", 11)) {
+
+ ssh->exitcode = ssh_pkt_getuint32(pktin);
+ logeventf(ssh, "Server sent command exit status %d",
+ ssh->exitcode);
+ reply = SSH2_MSG_CHANNEL_SUCCESS;
+
+ } else if (typelen == 11 &&
+ !memcmp(type, "exit-signal", 11)) {
+
+ int is_plausible = TRUE, is_int = FALSE;
+ char *fmt_sig = "", *fmt_msg = "";
+ char *msg;
+ int msglen = 0, core = FALSE;
+ /* ICK: older versions of OpenSSH (e.g. 3.4p1)
+ * provide an `int' for the signal, despite its
+ * having been a `string' in the drafts of RFC 4254 since at
+ * least 2001. (Fixed in session.c 1.147.) Try to
+ * infer which we can safely parse it as. */
+ {
+ unsigned char *p = pktin->body +
+ pktin->savedpos;
+ long len = pktin->length - pktin->savedpos;
+ unsigned long num = GET_32BIT(p); /* what is it? */
+ /* If it's 0, it hardly matters; assume string */
+ if (num == 0) {
+ is_int = FALSE;
+ } else {
+ int maybe_int = FALSE, maybe_str = FALSE;
+#define CHECK_HYPOTHESIS(offset, result) \
+ do \
+ { \
+ int q = toint(offset); \
+ if (q >= 0 && q+4 <= len) { \
+ q = toint(q + 4 + GET_32BIT(p+q)); \
+ if (q >= 0 && q+4 <= len && \
+ ((q = toint(q + 4 + GET_32BIT(p+q))) != 0) && \
+ q == len) \
+ result = TRUE; \
+ } \
+ } while(0)
+ CHECK_HYPOTHESIS(4+1, maybe_int);
+ CHECK_HYPOTHESIS(4+num+1, maybe_str);
+#undef CHECK_HYPOTHESIS
+ if (maybe_int && !maybe_str)
+ is_int = TRUE;
+ else if (!maybe_int && maybe_str)
+ is_int = FALSE;
+ else
+ /* Crikey. Either or neither. Panic. */
+ is_plausible = FALSE;
+ }
+ }
+ ssh->exitcode = 128; /* means `unknown signal' */
+ if (is_plausible) {
+ if (is_int) {
+ /* Old non-standard OpenSSH. */
+ int signum = ssh_pkt_getuint32(pktin);
+ fmt_sig = dupprintf(" %d", signum);
+ ssh->exitcode = 128 + signum;
+ } else {
+ /* As per RFC 4254. */
+ char *sig;
+ int siglen;
+ ssh_pkt_getstring(pktin, &sig, &siglen);
+ /* Signal name isn't supposed to be blank, but
+ * let's cope gracefully if it is. */
+ if (siglen) {
+ fmt_sig = dupprintf(" \"%.*s\"",
+ siglen, sig);
+ }
+
+ /*
+ * Really hideous method of translating the
+ * signal description back into a locally
+ * meaningful number.
+ */
+
+ if (0)
+ ;
+#define TRANSLATE_SIGNAL(s) \
+ else if (siglen == lenof(#s)-1 && !memcmp(sig, #s, siglen)) \
+ ssh->exitcode = 128 + SIG ## s
+#ifdef SIGABRT
+ TRANSLATE_SIGNAL(ABRT);
+#endif
+#ifdef SIGALRM
+ TRANSLATE_SIGNAL(ALRM);
+#endif
+#ifdef SIGFPE
+ TRANSLATE_SIGNAL(FPE);
+#endif
+#ifdef SIGHUP
+ TRANSLATE_SIGNAL(HUP);
+#endif
+#ifdef SIGILL
+ TRANSLATE_SIGNAL(ILL);
+#endif
+#ifdef SIGINT
+ TRANSLATE_SIGNAL(INT);
+#endif
+#ifdef SIGKILL
+ TRANSLATE_SIGNAL(KILL);
+#endif
+#ifdef SIGPIPE
+ TRANSLATE_SIGNAL(PIPE);
+#endif
+#ifdef SIGQUIT
+ TRANSLATE_SIGNAL(QUIT);
+#endif
+#ifdef SIGSEGV
+ TRANSLATE_SIGNAL(SEGV);
+#endif
+#ifdef SIGTERM
+ TRANSLATE_SIGNAL(TERM);
+#endif
+#ifdef SIGUSR1
+ TRANSLATE_SIGNAL(USR1);
+#endif
+#ifdef SIGUSR2
+ TRANSLATE_SIGNAL(USR2);
+#endif
+#undef TRANSLATE_SIGNAL
+ else
+ ssh->exitcode = 128;
+ }
+ core = ssh2_pkt_getbool(pktin);
+ ssh_pkt_getstring(pktin, &msg, &msglen);
+ if (msglen) {
+ fmt_msg = dupprintf(" (\"%.*s\")", msglen, msg);
+ }
+ /* 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);
+ reply = SSH2_MSG_CHANNEL_SUCCESS;
+
+ }
+ } else {
+ /*
+ * This is a channel request we don't know
+ * about, so we now either ignore the request
+ * or respond with CHANNEL_FAILURE, depending
+ * on want_reply.
+ */
+ reply = SSH2_MSG_CHANNEL_FAILURE;
+ }
+ if (want_reply) {
+ pktout = ssh2_pkt_init(reply);
+ ssh2_pkt_adduint32(pktout, c->remoteid);
+ ssh2_pkt_send(ssh, pktout);
+ }
+}
+
+static void ssh2_msg_global_request(Ssh ssh, struct Packet *pktin)
+{
+ char *type;
+ int typelen, want_reply;
+ struct Packet *pktout;
+
+ ssh_pkt_getstring(pktin, &type, &typelen);
+ want_reply = ssh2_pkt_getbool(pktin);
+
+ /*
+ * We currently don't support any global requests
+ * at all, so we either ignore the request or
+ * respond with REQUEST_FAILURE, depending on
+ * want_reply.
+ */
+ if (want_reply) {
+ pktout = ssh2_pkt_init(SSH2_MSG_REQUEST_FAILURE);
+ ssh2_pkt_send(ssh, pktout);
+ }
+}
+
+struct X11FakeAuth *ssh_sharing_add_x11_display(Ssh ssh, int authtype,
+ void *share_cs,
+ void *share_chan)
+{
+ struct X11FakeAuth *auth;
+
+ /*
+ * Make up a new set of fake X11 auth data, and add it to the tree
+ * of currently valid ones with an indication of the sharing
+ * context that it's relevant to.
+ */
+ auth = x11_invent_fake_auth(ssh->x11authtree, authtype);
+ auth->share_cs = share_cs;
+ auth->share_chan = share_chan;
+
+ return auth;
+}
+
+void ssh_sharing_remove_x11_display(Ssh ssh, struct X11FakeAuth *auth)
+{
+ del234(ssh->x11authtree, auth);
+ x11_free_fake_auth(auth);
+}
+
+static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
+{
+ char *type;
+ int typelen;
+ char *peeraddr;
+ int peeraddrlen;
+ int peerport;
+ char *error = NULL;
+ struct ssh_channel *c;
+ unsigned remid, winsize, pktsize;
+ unsigned our_winsize_override = 0;
+ struct Packet *pktout;
+
+ ssh_pkt_getstring(pktin, &type, &typelen);
+ c = snew(struct ssh_channel);
+ c->ssh = ssh;
+
+ remid = ssh_pkt_getuint32(pktin);
+ winsize = ssh_pkt_getuint32(pktin);
+ pktsize = ssh_pkt_getuint32(pktin);
+
+ if (typelen == 3 && !memcmp(type, "x11", 3)) {
+ char *addrstr;
+
+ ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen);
+ addrstr = snewn(peeraddrlen+1, char);
+ memcpy(addrstr, peeraddr, peeraddrlen);
+ addrstr[peeraddrlen] = '\0';
+ peerport = ssh_pkt_getuint32(pktin);
+
+ logeventf(ssh, "Received X11 connect request from %s:%d",
+ addrstr, peerport);
+
+ if (!ssh->X11_fwd_enabled && !ssh->connshare)
+ error = "X11 forwarding is not enabled";
+ else {
+ c->u.x11.xconn = x11_init(ssh->x11authtree, c,
+ addrstr, peerport);
+ c->type = CHAN_X11;
+ c->u.x11.initial = TRUE;
+
+ /*
+ * If we are a connection-sharing upstream, then we should
+ * initially present a very small window, adequate to take
+ * the X11 initial authorisation packet but not much more.
+ * Downstream will then present us a larger window (by
+ * fiat of the connection-sharing protocol) and we can
+ * guarantee to send a positive-valued WINDOW_ADJUST.
+ */
+ if (ssh->connshare)
+ our_winsize_override = 128;
+
+ logevent("Opened X11 forward channel");
+ }
+
+ sfree(addrstr);
+ } else if (typelen == 15 &&
+ !memcmp(type, "forwarded-tcpip", 15)) {
+ struct ssh_rportfwd pf, *realpf;
+ char *shost;
+ int shostlen;
+ ssh_pkt_getstring(pktin, &shost, &shostlen);/* skip address */
+ pf.shost = dupprintf("%.*s", shostlen, shost);
+ pf.sport = ssh_pkt_getuint32(pktin);
+ ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen);
+ peerport = ssh_pkt_getuint32(pktin);
+ realpf = find234(ssh->rportfwds, &pf, NULL);
+ logeventf(ssh, "Received remote port %s:%d open request "
+ "from %s:%d", pf.shost, pf.sport, peeraddr, peerport);
+ sfree(pf.shost);
+
+ if (realpf == NULL) {
+ error = "Remote port is not recognised";
+ } else {
+ char *err;
+
+ if (realpf->share_ctx) {
+ /*
+ * This port forwarding is on behalf of a
+ * connection-sharing downstream, so abandon our own
+ * channel-open procedure and just pass the message on
+ * to sshshare.c.
+ */
+ share_got_pkt_from_server(realpf->share_ctx, pktin->type,
+ pktin->body, pktin->length);
+ sfree(c);
+ return;
+ }
+
+ err = pfd_connect(&c->u.pfd.pf, realpf->dhost, realpf->dport,
+ c, ssh->conf, realpf->pfrec->addressfamily);
+ logeventf(ssh, "Attempting to forward remote port to "
+ "%s:%d", realpf->dhost, realpf->dport);
+ if (err != NULL) {
+ logeventf(ssh, "Port open failed: %s", err);
+ sfree(err);
+ error = "Port open failed";
+ } else {
+ logevent("Forwarded port opened successfully");
+ c->type = CHAN_SOCKDATA;
+ }
+ }
+ } else if (typelen == 22 &&
+ !memcmp(type, "auth-agent@openssh.com", 22)) {
+ if (!ssh->agentfwd_enabled)
+ error = "Agent forwarding is not enabled";
+ else {
+ c->type = CHAN_AGENT; /* identify channel type */
+ c->u.a.lensofar = 0;
+ c->u.a.message = NULL;
+ c->u.a.outstanding_requests = 0;
+ }
+ } else {
+ error = "Unsupported channel type requested";
+ }
+
+ c->remoteid = remid;
+ c->halfopen = FALSE;
+ if (error) {
+ pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_FAILURE);
+ ssh2_pkt_adduint32(pktout, c->remoteid);
+ ssh2_pkt_adduint32(pktout, SSH2_OPEN_CONNECT_FAILED);
+ ssh2_pkt_addstring(pktout, error);
+ ssh2_pkt_addstring(pktout, "en"); /* language tag */
+ ssh2_pkt_send(ssh, pktout);
+ logeventf(ssh, "Rejected channel open: %s", error);
+ sfree(c);
+ } else {
+ ssh2_channel_init(c);
+ c->v.v2.remwindow = winsize;
+ c->v.v2.remmaxpkt = pktsize;
+ if (our_winsize_override) {
+ c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin =
+ our_winsize_override;
+ }
+ add234(ssh->channels, c);
+ pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
+ ssh2_pkt_adduint32(pktout, c->remoteid);
+ ssh2_pkt_adduint32(pktout, c->localid);
+ ssh2_pkt_adduint32(pktout, c->v.v2.locwindow);
+ ssh2_pkt_adduint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */
+ ssh2_pkt_send(ssh, pktout);
+ }
+}
+
+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)
+{
+ /*
+ * This function is called when we've just discovered that an X
+ * forwarding channel on which we'd been handling the initial auth
+ * ourselves turns out to be destined for a connection-sharing
+ * downstream. So we turn the channel into a CHAN_SHARING, meaning
+ * that we completely stop tracking windows and buffering data and
+ * just pass more or less unmodified SSH messages back and forth.
+ */
+ c->type = CHAN_SHARING;
+ c->u.sharing.ctx = share_cs;
+ share_setup_x11_channel(share_cs, share_chan,
+ c->localid, c->remoteid, c->v.v2.remwindow,
+ c->v.v2.remmaxpkt, c->v.v2.locwindow,
+ peer_addr, peer_port, endian,
+ protomajor, protominor,
+ initial_data, initial_len);
+}
+
+void sshfwd_x11_is_local(struct ssh_channel *c)
+{
+ /*
+ * This function is called when we've just discovered that an X
+ * forwarding channel is _not_ destined for a connection-sharing
+ * downstream but we're going to handle it ourselves. We stop
+ * presenting a cautiously small window and go into ordinary data
+ * exchange mode.
+ */
+ c->u.x11.initial = FALSE;
+ ssh2_set_window(c, ssh_is_simple(c->ssh) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE);
+}
+
+/*
+ * Buffer banner messages for later display at some convenient point,
+ * if we're going to display them.
+ */
+static void ssh2_msg_userauth_banner(Ssh ssh, struct Packet *pktin)
+{
+ /* Arbitrary limit to prevent unbounded inflation of buffer */
+ if (conf_get_int(ssh->conf, CONF_ssh_show_banner) &&
+ bufchain_size(&ssh->banner) <= 131072) {
+ char *banner = NULL;
+ int size = 0;
+ ssh_pkt_getstring(pktin, &banner, &size);
+ if (banner)
+ bufchain_add(&ssh->banner, banner, size);
+ }
+}
+
+/* Helper function to deal with sending tty modes for "pty-req" */
+static void ssh2_send_ttymode(void *data, char *mode, char *val)
+{
+ struct Packet *pktout = (struct Packet *)data;
+ int i = 0;
+ unsigned int arg = 0;
+ while (strcmp(mode, ssh_ttymodes[i].mode) != 0) i++;
+ if (i == lenof(ssh_ttymodes)) return;
+ switch (ssh_ttymodes[i].type) {
+ case TTY_OP_CHAR:
+ arg = ssh_tty_parse_specchar(val);
+ break;
+ case TTY_OP_BOOL:
+ arg = ssh_tty_parse_boolean(val);
+ break;
+ }
+ ssh2_pkt_addbyte(pktout, ssh_ttymodes[i].opcode);
+ ssh2_pkt_adduint32(pktout, arg);
+}
+
+static void ssh2_setup_x11(struct ssh_channel *c, struct Packet *pktin,
+ void *ctx)
+{
+ struct ssh2_setup_x11_state {
+ int crLine;
+ };
+ Ssh ssh = c->ssh;
+ struct Packet *pktout;
+ crStateP(ssh2_setup_x11_state, ctx);
+
+ crBeginState;
+
+ logevent("Requesting X11 forwarding");
+ pktout = ssh2_chanreq_init(ssh->mainchan, "x11-req",
+ ssh2_setup_x11, s);
+ ssh2_pkt_addbool(pktout, 0); /* many connections */
+ ssh2_pkt_addstring(pktout, ssh->x11auth->protoname);
+ ssh2_pkt_addstring(pktout, ssh->x11auth->datastring);
+ ssh2_pkt_adduint32(pktout, ssh->x11disp->screennum);
+ ssh2_pkt_send(ssh, pktout);
+
+ /* Wait to be called back with either a response packet, or NULL
+ * meaning clean up and free our data */
+ crReturnV;
+
+ if (pktin) {
+ if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) {
+ logevent("X11 forwarding enabled");
+ ssh->X11_fwd_enabled = TRUE;
+ } else
+ logevent("X11 forwarding refused");
+ }
+
+ crFinishFreeV;
+}
+
+static void ssh2_setup_agent(struct ssh_channel *c, struct Packet *pktin,
+ void *ctx)
+{
+ struct ssh2_setup_agent_state {
+ int crLine;
+ };
+ Ssh ssh = c->ssh;
+ struct Packet *pktout;
+ crStateP(ssh2_setup_agent_state, ctx);
+
+ crBeginState;
+
+ logevent("Requesting OpenSSH-style agent forwarding");
+ pktout = ssh2_chanreq_init(ssh->mainchan, "auth-agent-req@openssh.com",
+ ssh2_setup_agent, s);
+ ssh2_pkt_send(ssh, pktout);
+
+ /* Wait to be called back with either a response packet, or NULL
+ * meaning clean up and free our data */
+ crReturnV;
+
+ if (pktin) {
+ if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) {
+ logevent("Agent forwarding enabled");
+ ssh->agentfwd_enabled = TRUE;
+ } else
+ logevent("Agent forwarding refused");
+ }
+
+ crFinishFreeV;
+}
+
+static void ssh2_setup_pty(struct ssh_channel *c, struct Packet *pktin,
+ void *ctx)
+{
+ struct ssh2_setup_pty_state {
+ int crLine;
+ };
+ Ssh ssh = c->ssh;
+ struct Packet *pktout;
+ crStateP(ssh2_setup_pty_state, ctx);
+
+ crBeginState;
+
+ /* Unpick the terminal-speed string. */
+ /* XXX perhaps we should allow no speeds to be sent. */
+ ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */
+ sscanf(conf_get_str(ssh->conf, CONF_termspeed), "%d,%d", &ssh->ospeed, &ssh->ispeed);
+ /* Build the pty request. */
+ pktout = ssh2_chanreq_init(ssh->mainchan, "pty-req",
+ ssh2_setup_pty, s);
+ ssh2_pkt_addstring(pktout, conf_get_str(ssh->conf, CONF_termtype));
+ ssh2_pkt_adduint32(pktout, ssh->term_width);
+ ssh2_pkt_adduint32(pktout, ssh->term_height);
+ ssh2_pkt_adduint32(pktout, 0); /* pixel width */
+ ssh2_pkt_adduint32(pktout, 0); /* pixel height */
+ ssh2_pkt_addstring_start(pktout);
+ parse_ttymodes(ssh, ssh2_send_ttymode, (void *)pktout);
+ ssh2_pkt_addbyte(pktout, SSH2_TTY_OP_ISPEED);
+ ssh2_pkt_adduint32(pktout, ssh->ispeed);
+ ssh2_pkt_addbyte(pktout, SSH2_TTY_OP_OSPEED);
+ ssh2_pkt_adduint32(pktout, ssh->ospeed);
+ ssh2_pkt_addstring_data(pktout, "\0", 1); /* TTY_OP_END */
+ ssh2_pkt_send(ssh, pktout);
+ ssh->state = SSH_STATE_INTERMED;
+
+ /* Wait to be called back with either a response packet, or NULL
+ * meaning clean up and free our data */
+ crReturnV;
+
+ if (pktin) {
+ if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) {
+ logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)",
+ ssh->ospeed, ssh->ispeed);
+ ssh->got_pty = TRUE;
+ } else {
+ c_write_str(ssh, "Server refused to allocate pty\r\n");
+ ssh->editing = ssh->echoing = 1;
+ }
+ }
+
+ crFinishFreeV;
+}
+
+static void ssh2_setup_env(struct ssh_channel *c, struct Packet *pktin,
+ void *ctx)
+{
+ struct ssh2_setup_env_state {
+ int crLine;
+ int num_env, env_left, env_ok;
+ };
+ Ssh ssh = c->ssh;
+ struct Packet *pktout;
+ crStateP(ssh2_setup_env_state, ctx);
+
+ crBeginState;
+
+ /*
+ * Send environment variables.
+ *
+ * Simplest thing here is to send all the requests at once, and
+ * then wait for a whole bunch of successes or failures.
+ */
+ s->num_env = 0;
+ {
+ char *key, *val;
+
+ for (val = conf_get_str_strs(ssh->conf, CONF_environmt, NULL, &key);
+ val != NULL;
+ val = conf_get_str_strs(ssh->conf, CONF_environmt, key, &key)) {
+ pktout = ssh2_chanreq_init(ssh->mainchan, "env", ssh2_setup_env, s);
+ ssh2_pkt_addstring(pktout, key);
+ ssh2_pkt_addstring(pktout, val);
+ ssh2_pkt_send(ssh, pktout);
+
+ s->num_env++;
+ }
+ if (s->num_env)
+ logeventf(ssh, "Sent %d environment variables", s->num_env);
+ }
+
+ if (s->num_env) {
+ s->env_ok = 0;
+ s->env_left = s->num_env;
+
+ while (s->env_left > 0) {
+ /* Wait to be called back with either a response packet,
+ * or NULL meaning clean up and free our data */
+ crReturnV;
+ if (!pktin) goto out;
+ if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS)
+ s->env_ok++;
+ s->env_left--;
+ }
+
+ if (s->env_ok == s->num_env) {
+ logevent("All environment variables successfully set");
+ } else if (s->env_ok == 0) {
+ logevent("All environment variables refused");
+ c_write_str(ssh, "Server refused to set environment variables\r\n");
+ } else {
+ logeventf(ssh, "%d environment variables refused",
+ s->num_env - s->env_ok);
+ c_write_str(ssh, "Server refused to set all environment variables\r\n");
+ }
+ }
+ out:;
+ crFinishFreeV;
+}
+
+/*
+ * Handle the SSH-2 userauth and connection layers.
+ */
+static void ssh2_msg_authconn(Ssh ssh, struct Packet *pktin)
+{
+ do_ssh2_authconn(ssh, NULL, 0, pktin);
+}
+
+static void ssh2_response_authconn(struct ssh_channel *c, struct Packet *pktin,
+ void *ctx)
+{
+ do_ssh2_authconn(c->ssh, NULL, 0, pktin);
+}
+
+static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
+ struct Packet *pktin)
+{
+ struct do_ssh2_authconn_state {
+ int crLine;
+ enum {
+ AUTH_TYPE_NONE,
+ AUTH_TYPE_PUBLICKEY,
+ AUTH_TYPE_PUBLICKEY_OFFER_LOUD,
+ AUTH_TYPE_PUBLICKEY_OFFER_QUIET,
+ AUTH_TYPE_PASSWORD,
+ AUTH_TYPE_GSSAPI, /* always QUIET */
+ AUTH_TYPE_KEYBOARD_INTERACTIVE,
+ AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET
+ } type;
+ int done_service_req;
+ int gotit, need_pw, can_pubkey, can_passwd, can_keyb_inter;
+ int tried_pubkey_config, done_agent;
+#ifndef NO_GSSAPI
+ int can_gssapi;
+ int tried_gssapi;
+#endif
+ int kbd_inter_refused;
+ int we_are_in, userauth_success;
+ prompts_t *cur_prompt;
+ int num_prompts;
+ char *username;
+ char *password;
+ int got_username;
+ void *publickey_blob;
+ int publickey_bloblen;
+ int publickey_encrypted;
+ char *publickey_algorithm;
+ char *publickey_comment;
+ unsigned char agent_request[5], *agent_response, *agentp;
+ int agent_responselen;
+ unsigned char *pkblob_in_agent;
+ int keyi, nkeys;
+ char *pkblob, *alg, *commentp;
+ int pklen, alglen, commentlen;
+ int siglen, retlen, len;
+ char *q, *agentreq, *ret;
+ int try_send;
+ struct Packet *pktout;
+ Filename *keyfile;
+#ifndef NO_GSSAPI
+ struct ssh_gss_library *gsslib;
+ Ssh_gss_ctx gss_ctx;
+ Ssh_gss_buf gss_buf;
+ Ssh_gss_buf gss_rcvtok, gss_sndtok;
+ Ssh_gss_name gss_srv_name;
+ Ssh_gss_stat gss_stat;
+#endif
+ };
+ crState(do_ssh2_authconn_state);
+
+ crBeginState;
+
+ /* Register as a handler for all the messages this coroutine handles. */
+ ssh->packet_dispatch[SSH2_MSG_SERVICE_ACCEPT] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_REQUEST] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_FAILURE] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_SUCCESS] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_PK_OK] = ssh2_msg_authconn;
+ /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ] = ssh2_msg_authconn; duplicate case value */
+ /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_REQUEST] = ssh2_msg_authconn; duplicate case value */
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_RESPONSE] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_authconn;
+
+ s->done_service_req = FALSE;
+ s->we_are_in = s->userauth_success = FALSE;
+ s->agent_response = NULL;
+#ifndef NO_GSSAPI
+ s->tried_gssapi = FALSE;
+#endif
+
+ if (!ssh->bare_connection) {
+ if (!conf_get_int(ssh->conf, CONF_ssh_no_userauth)) {
+ /*
+ * Request userauth protocol, and await a response to it.
+ */
+ s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST);
+ ssh2_pkt_addstring(s->pktout, "ssh-userauth");
+ ssh2_pkt_send(ssh, s->pktout);
+ crWaitUntilV(pktin);
+ if (pktin->type == SSH2_MSG_SERVICE_ACCEPT)
+ s->done_service_req = TRUE;
+ }
+ if (!s->done_service_req) {
+ /*
+ * Request connection protocol directly, without authentication.
+ */
+ s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST);
+ ssh2_pkt_addstring(s->pktout, "ssh-connection");
+ ssh2_pkt_send(ssh, s->pktout);
+ crWaitUntilV(pktin);
+ if (pktin->type == SSH2_MSG_SERVICE_ACCEPT) {
+ s->we_are_in = TRUE; /* no auth required */
+ } else {
+ bombout(("Server refused service request"));
+ crStopV;
+ }
+ }
+ } else {
+ s->we_are_in = TRUE;
+ }
+
+ /* Arrange to be able to deal with any BANNERs that come in.
+ * (We do this now as packets may come in during the next bit.) */
+ bufchain_init(&ssh->banner);
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] =
+ ssh2_msg_userauth_banner;
+
+ /*
+ * Misc one-time setup for authentication.
+ */
+ s->publickey_blob = NULL;
+ if (!s->we_are_in) {
+
+ /*
+ * Load the public half of any configured public key file
+ * for later use.
+ */
+ s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile);
+ if (!filename_is_null(s->keyfile)) {
+ int keytype;
+ logeventf(ssh, "Reading private key file \"%.150s\"",
+ filename_to_str(s->keyfile));
+ keytype = key_type(s->keyfile);
+ if (keytype == SSH_KEYTYPE_SSH2) {
+ const char *error;
+ s->publickey_blob =
+ ssh2_userkey_loadpub(s->keyfile,
+ &s->publickey_algorithm,
+ &s->publickey_bloblen,
+ &s->publickey_comment, &error);
+ if (s->publickey_blob) {
+ s->publickey_encrypted =
+ ssh2_userkey_encrypted(s->keyfile, NULL);
+ } else {
+ char *msgbuf;
+ logeventf(ssh, "Unable to load private key (%s)",
+ error);
+ msgbuf = dupprintf("Unable to load private key file "
+ "\"%.150s\" (%s)\r\n",
+ filename_to_str(s->keyfile),
+ error);
+ c_write_str(ssh, msgbuf);
+ sfree(msgbuf);
+ }
+ } else {
+ char *msgbuf;
+ logeventf(ssh, "Unable to use this key file (%s)",
+ key_type_to_str(keytype));
+ msgbuf = dupprintf("Unable to use key file \"%.150s\""
+ " (%s)\r\n",
+ filename_to_str(s->keyfile),
+ key_type_to_str(keytype));
+ c_write_str(ssh, msgbuf);
+ sfree(msgbuf);
+ s->publickey_blob = NULL;
+ }
+ }
+
+ /*
+ * Find out about any keys Pageant has (but if there's a
+ * public key configured, filter out all others).
+ */
+ s->nkeys = 0;
+ s->agent_response = NULL;
+ s->pkblob_in_agent = NULL;
+ if (conf_get_int(ssh->conf, CONF_tryagent) && agent_exists()) {
+
+ void *r;
+
+ logevent("Pageant is running. Requesting keys.");
+
+ /* Request the keys held by the agent. */
+ PUT_32BIT(s->agent_request, 1);
+ s->agent_request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
+ if (!agent_query(s->agent_request, 5, &r, &s->agent_responselen,
+ ssh_agent_callback, ssh)) {
+ do {
+ crReturnV;
+ if (pktin) {
+ bombout(("Unexpected data from server while"
+ " waiting for agent response"));
+ crStopV;
+ }
+ } while (pktin || inlen > 0);
+ r = ssh->agent_response;
+ s->agent_responselen = ssh->agent_response_len;
+ }
+ s->agent_response = (unsigned char *) r;
+ if (s->agent_response && s->agent_responselen >= 5 &&
+ s->agent_response[4] == SSH2_AGENT_IDENTITIES_ANSWER) {
+ int keyi;
+ unsigned char *p;
+ p = s->agent_response + 5;
+ s->nkeys = toint(GET_32BIT(p));
+
+ /*
+ * Vet the Pageant response to ensure that the key
+ * count and blob lengths make sense.
+ */
+ if (s->nkeys < 0) {
+ logeventf(ssh, "Pageant response contained a negative"
+ " key count %d", s->nkeys);
+ s->nkeys = 0;
+ goto done_agent_query;
+ } else {
+ unsigned char *q = p + 4;
+ int lenleft = s->agent_responselen - 5 - 4;
+
+ for (keyi = 0; keyi < s->nkeys; keyi++) {
+ int bloblen, commentlen;
+ if (lenleft < 4) {
+ logeventf(ssh, "Pageant response was truncated");
+ s->nkeys = 0;
+ goto done_agent_query;
+ }
+ bloblen = toint(GET_32BIT(q));
+ if (bloblen < 0 || bloblen > lenleft) {
+ logeventf(ssh, "Pageant response was truncated");
+ s->nkeys = 0;
+ goto done_agent_query;
+ }
+ lenleft -= 4 + bloblen;
+ q += 4 + bloblen;
+ commentlen = toint(GET_32BIT(q));
+ if (commentlen < 0 || commentlen > lenleft) {
+ logeventf(ssh, "Pageant response was truncated");
+ s->nkeys = 0;
+ goto done_agent_query;
+ }
+ lenleft -= 4 + commentlen;
+ q += 4 + commentlen;
+ }
+ }
+
+ p += 4;
+ logeventf(ssh, "Pageant has %d SSH-2 keys", s->nkeys);
+ if (s->publickey_blob) {
+ /* See if configured key is in agent. */
+ for (keyi = 0; keyi < s->nkeys; keyi++) {
+ s->pklen = toint(GET_32BIT(p));
+ if (s->pklen == s->publickey_bloblen &&
+ !memcmp(p+4, s->publickey_blob,
+ s->publickey_bloblen)) {
+ logeventf(ssh, "Pageant key #%d matches "
+ "configured key file", keyi);
+ s->keyi = keyi;
+ s->pkblob_in_agent = p;
+ break;
+ }
+ p += 4 + s->pklen;
+ p += toint(GET_32BIT(p)) + 4; /* comment */
+ }
+ if (!s->pkblob_in_agent) {
+ logevent("Configured key file not in Pageant");
+ s->nkeys = 0;
+ }
+ }
+ } else {
+ logevent("Failed to get reply from Pageant");
+ }
+ done_agent_query:;
+ }
+
+ }
+
+ /*
+ * We repeat this whole loop, including the username prompt,
+ * until we manage a successful authentication. If the user
+ * types the wrong _password_, they can be sent back to the
+ * beginning to try another username, if this is configured on.
+ * (If they specify a username in the config, they are never
+ * asked, even if they do give a wrong password.)
+ *
+ * I think this best serves the needs of
+ *
+ * - the people who have no configuration, no keys, and just
+ * want to try repeated (username,password) pairs until they
+ * type both correctly
+ *
+ * - people who have keys and configuration but occasionally
+ * need to fall back to passwords
+ *
+ * - people with a key held in Pageant, who might not have
+ * logged in to a particular machine before; so they want to
+ * type a username, and then _either_ their key will be
+ * accepted, _or_ they will type a password. If they mistype
+ * the username they will want to be able to get back and
+ * retype it!
+ */
+ s->got_username = FALSE;
+ while (!s->we_are_in) {
+ /*
+ * Get a username.
+ */
+ if (s->got_username && !conf_get_int(ssh->conf, CONF_change_username)) {
+ /*
+ * We got a username last time round this loop, and
+ * with change_username turned off we don't try to get
+ * it again.
+ */
+ } else if ((ssh->username = get_remote_username(ssh->conf)) == NULL) {
+ int ret; /* need not be kept over crReturn */
+ s->cur_prompt = new_prompts(ssh->frontend);
+ s->cur_prompt->to_server = TRUE;
+ s->cur_prompt->name = dupstr("SSH login name");
+ add_prompt(s->cur_prompt, dupstr("login as: "), TRUE);
+ ret = get_userpass_input(s->cur_prompt, NULL, 0);
+ while (ret < 0) {
+ ssh->send_ok = 1;
+ crWaitUntilV(!pktin);
+ ret = get_userpass_input(s->cur_prompt, in, inlen);
+ ssh->send_ok = 0;
+ }
+ if (!ret) {
+ /*
+ * get_userpass_input() failed to get a username.
+ * Terminate.
+ */
+ free_prompts(s->cur_prompt);
+ ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE);
+ crStopV;
+ }
+ ssh->username = dupstr(s->cur_prompt->prompts[0]->result);
+ free_prompts(s->cur_prompt);
+ } else {
+ char *stuff;
+ if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) {
+ stuff = dupprintf("Using username \"%s\".\r\n", ssh->username);
+ c_write_str(ssh, stuff);
+ sfree(stuff);
+ }
+ }
+ s->got_username = TRUE;
+
+ /*
+ * Send an authentication request using method "none": (a)
+ * just in case it succeeds, and (b) so that we know what
+ * authentication methods we can usefully try next.
+ */
+ ssh->pkt_actx = SSH2_PKTCTX_NOAUTH;
+
+ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
+ ssh2_pkt_addstring(s->pktout, ssh->username);
+ ssh2_pkt_addstring(s->pktout, "ssh-connection");/* service requested */
+ ssh2_pkt_addstring(s->pktout, "none"); /* method */
+ ssh2_pkt_send(ssh, s->pktout);
+ s->type = AUTH_TYPE_NONE;
+ s->gotit = FALSE;
+ s->we_are_in = FALSE;
+
+ s->tried_pubkey_config = FALSE;
+ s->kbd_inter_refused = FALSE;
+
+ /* Reset agent request state. */
+ s->done_agent = FALSE;
+ if (s->agent_response) {
+ if (s->pkblob_in_agent) {
+ s->agentp = s->pkblob_in_agent;
+ } else {
+ s->agentp = s->agent_response + 5 + 4;
+ s->keyi = 0;
+ }
+ }
+
+ while (1) {
+ char *methods = NULL;
+ int methlen = 0;
+
+ /*
+ * Wait for the result of the last authentication request.
+ */
+ if (!s->gotit)
+ crWaitUntilV(pktin);
+ /*
+ * Now is a convenient point to spew any banner material
+ * that we've accumulated. (This should ensure that when
+ * we exit the auth loop, we haven't any left to deal
+ * with.)
+ */
+ {
+ int size = bufchain_size(&ssh->banner);
+ /*
+ * Don't show the banner if we're operating in
+ * non-verbose non-interactive mode. (It's probably
+ * a script, which means nobody will read the
+ * banner _anyway_, and moreover the printing of
+ * the banner will screw up processing on the
+ * output of (say) plink.)
+ */
+ if (size && (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE))) {
+ char *banner = snewn(size, char);
+ bufchain_fetch(&ssh->banner, banner, size);
+ c_write_untrusted(ssh, banner, size);
+ sfree(banner);
+ }
+ bufchain_clear(&ssh->banner);
+ }
+ if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) {
+ logevent("Access granted");
+ s->we_are_in = s->userauth_success = TRUE;
+ break;
+ }
+
+ if (pktin->type != SSH2_MSG_USERAUTH_FAILURE && s->type != AUTH_TYPE_GSSAPI) {
+ bombout(("Strange packet received during authentication: "
+ "type %d", pktin->type));
+ crStopV;
+ }
+
+ s->gotit = FALSE;
+
+ /*
+ * OK, we're now sitting on a USERAUTH_FAILURE message, so
+ * we can look at the string in it and know what we can
+ * helpfully try next.
+ */
+ if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) {
+ ssh_pkt_getstring(pktin, &methods, &methlen);
+ if (!ssh2_pkt_getbool(pktin)) {
+ /*
+ * We have received an unequivocal Access
+ * Denied. This can translate to a variety of
+ * messages, or no message at all.
+ *
+ * For forms of authentication which are attempted
+ * implicitly, by which I mean without printing
+ * anything in the window indicating that we're
+ * trying them, we should never print 'Access
+ * denied'.
+ *
+ * If we do print a message saying that we're
+ * attempting some kind of authentication, it's OK
+ * to print a followup message saying it failed -
+ * but the message may sometimes be more specific
+ * than simply 'Access denied'.
+ *
+ * Additionally, if we'd just tried password
+ * authentication, we should break out of this
+ * whole loop so as to go back to the username
+ * prompt (iff we're configured to allow
+ * username change attempts).
+ */
+ if (s->type == AUTH_TYPE_NONE) {
+ /* do nothing */
+ } else if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD ||
+ s->type == AUTH_TYPE_PUBLICKEY_OFFER_QUIET) {
+ if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD)
+ c_write_str(ssh, "Server refused our key\r\n");
+ logevent("Server refused our key");
+ } else if (s->type == AUTH_TYPE_PUBLICKEY) {
+ /* This _shouldn't_ happen except by a
+ * protocol bug causing client and server to
+ * disagree on what is a correct signature. */
+ c_write_str(ssh, "Server refused public-key signature"
+ " despite accepting key!\r\n");
+ logevent("Server refused public-key signature"
+ " despite accepting key!");
+ } else if (s->type==AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET) {
+ /* quiet, so no c_write */
+ logevent("Server refused keyboard-interactive authentication");
+ } else if (s->type==AUTH_TYPE_GSSAPI) {
+ /* always quiet, so no c_write */
+ /* also, the code down in the GSSAPI block has
+ * already logged this in the Event Log */
+ } else if (s->type == AUTH_TYPE_KEYBOARD_INTERACTIVE) {
+ logevent("Keyboard-interactive authentication failed");
+ c_write_str(ssh, "Access denied\r\n");
+ } else {
+ assert(s->type == AUTH_TYPE_PASSWORD);
+ logevent("Password authentication failed");
+ c_write_str(ssh, "Access denied\r\n");
+
+ if (conf_get_int(ssh->conf, CONF_change_username)) {
+ /* XXX perhaps we should allow
+ * keyboard-interactive to do this too? */
+ s->we_are_in = FALSE;
+ break;
+ }
+ }
+ } else {
+ c_write_str(ssh, "Further authentication required\r\n");
+ logevent("Further authentication required");
+ }
+
+ s->can_pubkey =
+ in_commasep_string("publickey", methods, methlen);
+ s->can_passwd =
+ in_commasep_string("password", methods, methlen);
+ s->can_keyb_inter = conf_get_int(ssh->conf, CONF_try_ki_auth) &&
+ in_commasep_string("keyboard-interactive", methods, methlen);
+#ifndef NO_GSSAPI
+ if (!ssh->gsslibs)
+ ssh->gsslibs = ssh_gss_setup(ssh->conf);
+ s->can_gssapi = conf_get_int(ssh->conf, CONF_try_gssapi_auth) &&
+ in_commasep_string("gssapi-with-mic", methods, methlen) &&
+ ssh->gsslibs->nlibraries > 0;
+#endif
+ }
+
+ ssh->pkt_actx = SSH2_PKTCTX_NOAUTH;
+
+ if (s->can_pubkey && !s->done_agent && s->nkeys) {
+
+ /*
+ * Attempt public-key authentication using a key from Pageant.
+ */
+
+ ssh->pkt_actx = SSH2_PKTCTX_PUBLICKEY;
+
+ logeventf(ssh, "Trying Pageant key #%d", s->keyi);
+
+ /* Unpack key from agent response */
+ s->pklen = toint(GET_32BIT(s->agentp));
+ s->agentp += 4;
+ s->pkblob = (char *)s->agentp;
+ s->agentp += s->pklen;
+ s->alglen = toint(GET_32BIT(s->pkblob));
+ s->alg = s->pkblob + 4;
+ s->commentlen = toint(GET_32BIT(s->agentp));
+ s->agentp += 4;
+ s->commentp = (char *)s->agentp;
+ s->agentp += s->commentlen;
+ /* s->agentp now points at next key, if any */
+
+ /* See if server will accept it */
+ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
+ ssh2_pkt_addstring(s->pktout, ssh->username);
+ ssh2_pkt_addstring(s->pktout, "ssh-connection");
+ /* service requested */
+ ssh2_pkt_addstring(s->pktout, "publickey");
+ /* method */
+ ssh2_pkt_addbool(s->pktout, FALSE); /* no signature included */
+ ssh2_pkt_addstring_start(s->pktout);
+ ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen);
+ ssh2_pkt_addstring_start(s->pktout);
+ ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen);
+ ssh2_pkt_send(ssh, s->pktout);
+ s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET;
+
+ crWaitUntilV(pktin);
+ if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) {
+
+ /* Offer of key refused. */
+ s->gotit = TRUE;
+
+ } else {
+
+ void *vret;
+
+ if (flags & FLAG_VERBOSE) {
+ c_write_str(ssh, "Authenticating with "
+ "public key \"");
+ c_write(ssh, s->commentp, s->commentlen);
+ c_write_str(ssh, "\" from agent\r\n");
+ }
+
+ /*
+ * Server is willing to accept the key.
+ * Construct a SIGN_REQUEST.
+ */
+ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
+ ssh2_pkt_addstring(s->pktout, ssh->username);
+ ssh2_pkt_addstring(s->pktout, "ssh-connection");
+ /* service requested */
+ ssh2_pkt_addstring(s->pktout, "publickey");
+ /* method */
+ ssh2_pkt_addbool(s->pktout, TRUE); /* signature included */
+ ssh2_pkt_addstring_start(s->pktout);
+ ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen);
+ ssh2_pkt_addstring_start(s->pktout);
+ ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen);
+
+ /* Ask agent for signature. */
+ s->siglen = s->pktout->length - 5 + 4 +
+ ssh->v2_session_id_len;
+ if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)
+ s->siglen -= 4;
+ s->len = 1; /* message type */
+ s->len += 4 + s->pklen; /* key blob */
+ s->len += 4 + s->siglen; /* data to sign */
+ s->len += 4; /* flags */
+ s->agentreq = snewn(4 + s->len, char);
+ PUT_32BIT(s->agentreq, s->len);
+ s->q = s->agentreq + 4;
+ *s->q++ = SSH2_AGENTC_SIGN_REQUEST;
+ PUT_32BIT(s->q, s->pklen);
+ s->q += 4;
+ memcpy(s->q, s->pkblob, s->pklen);
+ s->q += s->pklen;
+ PUT_32BIT(s->q, s->siglen);
+ s->q += 4;
+ /* Now the data to be signed... */
+ if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) {
+ PUT_32BIT(s->q, ssh->v2_session_id_len);
+ s->q += 4;
+ }
+ memcpy(s->q, ssh->v2_session_id,
+ ssh->v2_session_id_len);
+ s->q += ssh->v2_session_id_len;
+ memcpy(s->q, s->pktout->data + 5,
+ s->pktout->length - 5);
+ s->q += s->pktout->length - 5;
+ /* And finally the (zero) flags word. */
+ PUT_32BIT(s->q, 0);
+ if (!agent_query(s->agentreq, s->len + 4,
+ &vret, &s->retlen,
+ ssh_agent_callback, ssh)) {
+ do {
+ crReturnV;
+ if (pktin) {
+ bombout(("Unexpected data from server"
+ " while waiting for agent"
+ " response"));
+ crStopV;
+ }
+ } while (pktin || inlen > 0);
+ vret = ssh->agent_response;
+ s->retlen = ssh->agent_response_len;
+ }
+ s->ret = vret;
+ sfree(s->agentreq);
+ if (s->ret) {
+ if (s->retlen >= 9 &&
+ s->ret[4] == SSH2_AGENT_SIGN_RESPONSE &&
+ GET_32BIT(s->ret + 5) <= (unsigned)(s->retlen-9)) {
+ logevent("Sending Pageant's response");
+ ssh2_add_sigblob(ssh, s->pktout,
+ s->pkblob, s->pklen,
+ s->ret + 9,
+ GET_32BIT(s->ret + 5));
+ ssh2_pkt_send(ssh, s->pktout);
+ s->type = AUTH_TYPE_PUBLICKEY;
+ } else {
+ /* FIXME: less drastic response */
+ bombout(("Pageant failed to answer challenge"));
+ crStopV;
+ }
+ }
+ }
+
+ /* Do we have any keys left to try? */
+ if (s->pkblob_in_agent) {
+ s->done_agent = TRUE;
+ s->tried_pubkey_config = TRUE;
+ } else {
+ s->keyi++;
+ if (s->keyi >= s->nkeys)
+ s->done_agent = TRUE;
+ }
+
+ } else if (s->can_pubkey && s->publickey_blob &&
+ !s->tried_pubkey_config) {
+
+ struct ssh2_userkey *key; /* not live over crReturn */
+ char *passphrase; /* not live over crReturn */
+
+ ssh->pkt_actx = SSH2_PKTCTX_PUBLICKEY;
+
+ s->tried_pubkey_config = TRUE;
+
+ /*
+ * Try the public key supplied in the configuration.
+ *
+ * First, offer the public blob to see if the server is
+ * willing to accept it.
+ */
+ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
+ ssh2_pkt_addstring(s->pktout, ssh->username);
+ ssh2_pkt_addstring(s->pktout, "ssh-connection");
+ /* service requested */
+ ssh2_pkt_addstring(s->pktout, "publickey"); /* method */
+ ssh2_pkt_addbool(s->pktout, FALSE);
+ /* no signature included */
+ ssh2_pkt_addstring(s->pktout, s->publickey_algorithm);
+ ssh2_pkt_addstring_start(s->pktout);
+ ssh2_pkt_addstring_data(s->pktout,
+ (char *)s->publickey_blob,
+ s->publickey_bloblen);
+ ssh2_pkt_send(ssh, s->pktout);
+ logevent("Offered public key");
+
+ crWaitUntilV(pktin);
+ if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) {
+ /* Key refused. Give up. */
+ s->gotit = TRUE; /* reconsider message next loop */
+ s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD;
+ continue; /* process this new message */
+ }
+ logevent("Offer of public key accepted");
+
+ /*
+ * Actually attempt a serious authentication using
+ * the key.
+ */
+ if (flags & FLAG_VERBOSE) {
+ c_write_str(ssh, "Authenticating with public key \"");
+ c_write_str(ssh, s->publickey_comment);
+ c_write_str(ssh, "\"\r\n");
+ }
+ key = NULL;
+ while (!key) {
+ const char *error; /* not live over crReturn */
+ if (s->publickey_encrypted) {
+ /*
+ * Get a passphrase from the user.
+ */
+ int ret; /* need not be kept over crReturn */
+ s->cur_prompt = new_prompts(ssh->frontend);
+ s->cur_prompt->to_server = FALSE;
+ s->cur_prompt->name = dupstr("SSH key passphrase");
+ add_prompt(s->cur_prompt,
+ dupprintf("Passphrase for key \"%.100s\": ",
+ s->publickey_comment),
+ FALSE);
+ ret = get_userpass_input(s->cur_prompt, NULL, 0);
+ while (ret < 0) {
+ ssh->send_ok = 1;
+ crWaitUntilV(!pktin);
+ ret = get_userpass_input(s->cur_prompt,
+ in, inlen);
+ ssh->send_ok = 0;
+ }
+ if (!ret) {
+ /* Failed to get a passphrase. Terminate. */
+ free_prompts(s->cur_prompt);
+ ssh_disconnect(ssh, NULL,
+ "Unable to authenticate",
+ SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER,
+ TRUE);
+ crStopV;
+ }
+ passphrase =
+ dupstr(s->cur_prompt->prompts[0]->result);
+ free_prompts(s->cur_prompt);
+ } else {
+ passphrase = NULL; /* no passphrase needed */
+ }
+
+ /*
+ * Try decrypting the key.
+ */
+ s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile);
+ key = ssh2_load_userkey(s->keyfile, passphrase, &error);
+ if (passphrase) {
+ /* burn the evidence */
+ smemclr(passphrase, strlen(passphrase));
+ sfree(passphrase);
+ }
+ if (key == SSH2_WRONG_PASSPHRASE || key == NULL) {
+ if (passphrase &&
+ (key == SSH2_WRONG_PASSPHRASE)) {
+ c_write_str(ssh, "Wrong passphrase\r\n");
+ key = NULL;
+ /* and loop again */
+ } else {
+ c_write_str(ssh, "Unable to load private key (");
+ c_write_str(ssh, error);
+ c_write_str(ssh, ")\r\n");
+ key = NULL;
+ break; /* try something else */
+ }
+ }
+ }
+
+ if (key) {
+ unsigned char *pkblob, *sigblob, *sigdata;
+ int pkblob_len, sigblob_len, sigdata_len;
+ int p;
+
+ /*
+ * We have loaded the private key and the server
+ * has announced that it's willing to accept it.
+ * Hallelujah. Generate a signature and send it.
+ */
+ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
+ ssh2_pkt_addstring(s->pktout, ssh->username);
+ ssh2_pkt_addstring(s->pktout, "ssh-connection");
+ /* service requested */
+ ssh2_pkt_addstring(s->pktout, "publickey");
+ /* method */
+ ssh2_pkt_addbool(s->pktout, TRUE);
+ /* signature follows */
+ ssh2_pkt_addstring(s->pktout, key->alg->name);
+ pkblob = key->alg->public_blob(key->data,
+ &pkblob_len);
+ ssh2_pkt_addstring_start(s->pktout);
+ ssh2_pkt_addstring_data(s->pktout, (char *)pkblob,
+ pkblob_len);
+
+ /*
+ * The data to be signed is:
+ *
+ * string session-id
+ *
+ * followed by everything so far placed in the
+ * outgoing packet.
+ */
+ sigdata_len = s->pktout->length - 5 + 4 +
+ ssh->v2_session_id_len;
+ if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)
+ sigdata_len -= 4;
+ sigdata = snewn(sigdata_len, unsigned char);
+ p = 0;
+ if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) {
+ PUT_32BIT(sigdata+p, ssh->v2_session_id_len);
+ p += 4;
+ }
+ memcpy(sigdata+p, ssh->v2_session_id,
+ ssh->v2_session_id_len);
+ p += ssh->v2_session_id_len;
+ memcpy(sigdata+p, s->pktout->data + 5,
+ s->pktout->length - 5);
+ p += s->pktout->length - 5;
+ assert(p == sigdata_len);
+ sigblob = key->alg->sign(key->data, (char *)sigdata,
+ sigdata_len, &sigblob_len);
+ ssh2_add_sigblob(ssh, s->pktout, pkblob, pkblob_len,
+ sigblob, sigblob_len);
+ sfree(pkblob);
+ sfree(sigblob);
+ sfree(sigdata);
+
+ ssh2_pkt_send(ssh, s->pktout);
+ logevent("Sent public key signature");
+ s->type = AUTH_TYPE_PUBLICKEY;
+ key->alg->freekey(key->data);
+ }
+
+#ifndef NO_GSSAPI
+ } else if (s->can_gssapi && !s->tried_gssapi) {
+
+ /* GSSAPI Authentication */
+
+ int micoffset, len;
+ char *data;
+ Ssh_gss_buf mic;
+ s->type = AUTH_TYPE_GSSAPI;
+ s->tried_gssapi = TRUE;
+ s->gotit = TRUE;
+ ssh->pkt_actx = SSH2_PKTCTX_GSSAPI;
+
+ /*
+ * Pick the highest GSS library on the preference
+ * list.
+ */
+ {
+ int i, j;
+ s->gsslib = NULL;
+ for (i = 0; i < ngsslibs; i++) {
+ int want_id = conf_get_int_int(ssh->conf,
+ CONF_ssh_gsslist, i);
+ for (j = 0; j < ssh->gsslibs->nlibraries; j++)
+ if (ssh->gsslibs->libraries[j].id == want_id) {
+ s->gsslib = &ssh->gsslibs->libraries[j];
+ goto got_gsslib; /* double break */
+ }
+ }
+ got_gsslib:
+ /*
+ * We always expect to have found something in
+ * the above loop: we only came here if there
+ * was at least one viable GSS library, and the
+ * preference list should always mention
+ * everything and only change the order.
+ */
+ assert(s->gsslib);
+ }
+
+ if (s->gsslib->gsslogmsg)
+ logevent(s->gsslib->gsslogmsg);
+
+ /* Sending USERAUTH_REQUEST with "gssapi-with-mic" method */
+ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
+ ssh2_pkt_addstring(s->pktout, ssh->username);
+ ssh2_pkt_addstring(s->pktout, "ssh-connection");
+ ssh2_pkt_addstring(s->pktout, "gssapi-with-mic");
+ logevent("Attempting GSSAPI authentication");
+
+ /* add mechanism info */
+ s->gsslib->indicate_mech(s->gsslib, &s->gss_buf);
+
+ /* number of GSSAPI mechanisms */
+ ssh2_pkt_adduint32(s->pktout,1);
+
+ /* length of OID + 2 */
+ ssh2_pkt_adduint32(s->pktout, s->gss_buf.length + 2);
+ ssh2_pkt_addbyte(s->pktout, SSH2_GSS_OIDTYPE);
+
+ /* length of OID */
+ ssh2_pkt_addbyte(s->pktout, (unsigned char) s->gss_buf.length);
+
+ ssh_pkt_adddata(s->pktout, s->gss_buf.value,
+ s->gss_buf.length);
+ ssh2_pkt_send(ssh, s->pktout);
+ crWaitUntilV(pktin);
+ if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_RESPONSE) {
+ logevent("GSSAPI authentication request refused");
+ continue;
+ }
+
+ /* check returned packet ... */
+
+ ssh_pkt_getstring(pktin, &data, &len);
+ s->gss_rcvtok.value = data;
+ s->gss_rcvtok.length = len;
+ if (s->gss_rcvtok.length != s->gss_buf.length + 2 ||
+ ((char *)s->gss_rcvtok.value)[0] != SSH2_GSS_OIDTYPE ||
+ ((char *)s->gss_rcvtok.value)[1] != s->gss_buf.length ||
+ memcmp((char *)s->gss_rcvtok.value + 2,
+ s->gss_buf.value,s->gss_buf.length) ) {
+ logevent("GSSAPI authentication - wrong response from server");
+ continue;
+ }
+
+ /* now start running */
+ s->gss_stat = s->gsslib->import_name(s->gsslib,
+ ssh->fullhostname,
+ &s->gss_srv_name);
+ if (s->gss_stat != SSH_GSS_OK) {
+ if (s->gss_stat == SSH_GSS_BAD_HOST_NAME)
+ logevent("GSSAPI import name failed - Bad service name");
+ else
+ logevent("GSSAPI import name failed");
+ continue;
+ }
+
+ /* fetch TGT into GSS engine */
+ s->gss_stat = s->gsslib->acquire_cred(s->gsslib, &s->gss_ctx);
+
+ if (s->gss_stat != SSH_GSS_OK) {
+ logevent("GSSAPI authentication failed to get credentials");
+ s->gsslib->release_name(s->gsslib, &s->gss_srv_name);
+ continue;
+ }
+
+ /* initial tokens are empty */
+ SSH_GSS_CLEAR_BUF(&s->gss_rcvtok);
+ SSH_GSS_CLEAR_BUF(&s->gss_sndtok);
+
+ /* now enter the loop */
+ do {
+ s->gss_stat = s->gsslib->init_sec_context
+ (s->gsslib,
+ &s->gss_ctx,
+ s->gss_srv_name,
+ conf_get_int(ssh->conf, CONF_gssapifwd),
+ &s->gss_rcvtok,
+ &s->gss_sndtok);
+
+ if (s->gss_stat!=SSH_GSS_S_COMPLETE &&
+ s->gss_stat!=SSH_GSS_S_CONTINUE_NEEDED) {
+ logevent("GSSAPI authentication initialisation failed");
+
+ if (s->gsslib->display_status(s->gsslib, s->gss_ctx,
+ &s->gss_buf) == SSH_GSS_OK) {
+ logevent(s->gss_buf.value);
+ sfree(s->gss_buf.value);
+ }
+
+ break;
+ }
+ logevent("GSSAPI authentication initialised");
+
+ /* Client and server now exchange tokens until GSSAPI
+ * no longer says CONTINUE_NEEDED */
+
+ if (s->gss_sndtok.length != 0) {
+ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_GSSAPI_TOKEN);
+ ssh_pkt_addstring_start(s->pktout);
+ ssh_pkt_addstring_data(s->pktout,s->gss_sndtok.value,s->gss_sndtok.length);
+ ssh2_pkt_send(ssh, s->pktout);
+ s->gsslib->free_tok(s->gsslib, &s->gss_sndtok);
+ }
+
+ if (s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED) {
+ crWaitUntilV(pktin);
+ if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_TOKEN) {
+ logevent("GSSAPI authentication - bad server response");
+ s->gss_stat = SSH_GSS_FAILURE;
+ break;
+ }
+ ssh_pkt_getstring(pktin, &data, &len);
+ s->gss_rcvtok.value = data;
+ s->gss_rcvtok.length = len;
+ }
+ } while (s-> gss_stat == SSH_GSS_S_CONTINUE_NEEDED);
+
+ if (s->gss_stat != SSH_GSS_OK) {
+ s->gsslib->release_name(s->gsslib, &s->gss_srv_name);
+ s->gsslib->release_cred(s->gsslib, &s->gss_ctx);
+ continue;
+ }
+ logevent("GSSAPI authentication loop finished OK");
+
+ /* Now send the MIC */
+
+ s->pktout = ssh2_pkt_init(0);
+ micoffset = s->pktout->length;
+ ssh_pkt_addstring_start(s->pktout);
+ ssh_pkt_addstring_data(s->pktout, (char *)ssh->v2_session_id, ssh->v2_session_id_len);
+ ssh_pkt_addbyte(s->pktout, SSH2_MSG_USERAUTH_REQUEST);
+ ssh_pkt_addstring(s->pktout, ssh->username);
+ ssh_pkt_addstring(s->pktout, "ssh-connection");
+ ssh_pkt_addstring(s->pktout, "gssapi-with-mic");
+
+ s->gss_buf.value = (char *)s->pktout->data + micoffset;
+ s->gss_buf.length = s->pktout->length - micoffset;
+
+ s->gsslib->get_mic(s->gsslib, s->gss_ctx, &s->gss_buf, &mic);
+ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_GSSAPI_MIC);
+ ssh_pkt_addstring_start(s->pktout);
+ ssh_pkt_addstring_data(s->pktout, mic.value, mic.length);
+ ssh2_pkt_send(ssh, s->pktout);
+ s->gsslib->free_mic(s->gsslib, &mic);
+
+ s->gotit = FALSE;
+
+ s->gsslib->release_name(s->gsslib, &s->gss_srv_name);
+ s->gsslib->release_cred(s->gsslib, &s->gss_ctx);
+ continue;
+#endif
+ } else if (s->can_keyb_inter && !s->kbd_inter_refused) {
+
+ /*
+ * Keyboard-interactive authentication.
+ */
+
+ s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
+
+ ssh->pkt_actx = SSH2_PKTCTX_KBDINTER;
+
+ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
+ ssh2_pkt_addstring(s->pktout, ssh->username);
+ ssh2_pkt_addstring(s->pktout, "ssh-connection");
+ /* service requested */
+ ssh2_pkt_addstring(s->pktout, "keyboard-interactive");
+ /* method */
+ ssh2_pkt_addstring(s->pktout, ""); /* lang */
+ ssh2_pkt_addstring(s->pktout, ""); /* submethods */
+ ssh2_pkt_send(ssh, s->pktout);
+
+ logevent("Attempting keyboard-interactive authentication");
+
+ crWaitUntilV(pktin);
+ if (pktin->type != SSH2_MSG_USERAUTH_INFO_REQUEST) {
+ /* Server is not willing to do keyboard-interactive
+ * at all (or, bizarrely but legally, accepts the
+ * user without actually issuing any prompts).
+ * Give up on it entirely. */
+ s->gotit = TRUE;
+ s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET;
+ s->kbd_inter_refused = TRUE; /* don't try it again */
+ continue;
+ }
+
+ /*
+ * Loop while the server continues to send INFO_REQUESTs.
+ */
+ while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) {
+
+ char *name, *inst, *lang;
+ int name_len, inst_len, lang_len;
+ int i;
+
+ /*
+ * We've got a fresh USERAUTH_INFO_REQUEST.
+ * Get the preamble and start building a prompt.
+ */
+ ssh_pkt_getstring(pktin, &name, &name_len);
+ ssh_pkt_getstring(pktin, &inst, &inst_len);
+ ssh_pkt_getstring(pktin, &lang, &lang_len);
+ s->cur_prompt = new_prompts(ssh->frontend);
+ s->cur_prompt->to_server = TRUE;
+
+ /*
+ * Get any prompt(s) from the packet.
+ */
+ s->num_prompts = ssh_pkt_getuint32(pktin);
+ for (i = 0; i < s->num_prompts; i++) {
+ char *prompt;
+ int prompt_len;
+ int echo;
+ static char noprompt[] =
+ "<server failed to send prompt>: ";
+
+ ssh_pkt_getstring(pktin, &prompt, &prompt_len);
+ echo = ssh2_pkt_getbool(pktin);
+ if (!prompt_len) {
+ prompt = noprompt;
+ prompt_len = lenof(noprompt)-1;
+ }
+ add_prompt(s->cur_prompt,
+ dupprintf("%.*s", prompt_len, prompt),
+ echo);
+ }
+
+ if (name_len) {
+ /* FIXME: better prefix to distinguish from
+ * local prompts? */
+ s->cur_prompt->name =
+ dupprintf("SSH server: %.*s", name_len, name);
+ s->cur_prompt->name_reqd = TRUE;
+ } else {
+ s->cur_prompt->name =
+ dupstr("SSH server authentication");
+ s->cur_prompt->name_reqd = FALSE;
+ }
+ /* We add a prefix to try to make it clear that a prompt
+ * has come from the server.
+ * FIXME: ugly to print "Using..." in prompt _every_
+ * time round. Can this be done more subtly? */
+ /* Special case: for reasons best known to themselves,
+ * some servers send k-i requests with no prompts and
+ * nothing to display. Keep quiet in this case. */
+ if (s->num_prompts || name_len || inst_len) {
+ s->cur_prompt->instruction =
+ dupprintf("Using keyboard-interactive authentication.%s%.*s",
+ inst_len ? "\n" : "", inst_len, inst);
+ s->cur_prompt->instr_reqd = TRUE;
+ } else {
+ s->cur_prompt->instr_reqd = FALSE;
+ }
+
+ /*
+ * Display any instructions, and get the user's
+ * response(s).
+ */
+ {
+ int ret; /* not live over crReturn */
+ ret = get_userpass_input(s->cur_prompt, NULL, 0);
+ while (ret < 0) {
+ ssh->send_ok = 1;
+ crWaitUntilV(!pktin);
+ ret = get_userpass_input(s->cur_prompt, in, inlen);
+ ssh->send_ok = 0;
+ }
+ if (!ret) {
+ /*
+ * Failed to get responses. Terminate.
+ */
+ free_prompts(s->cur_prompt);
+ ssh_disconnect(ssh, NULL, "Unable to authenticate",
+ SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER,
+ TRUE);
+ crStopV;
+ }
+ }
+
+ /*
+ * Send the response(s) to the server.
+ */
+ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE);
+ ssh2_pkt_adduint32(s->pktout, s->num_prompts);
+ for (i=0; i < s->num_prompts; i++) {
+ ssh2_pkt_addstring(s->pktout,
+ s->cur_prompt->prompts[i]->result);
+ }
+ ssh2_pkt_send_with_padding(ssh, s->pktout, 256);
+
+ /*
+ * Free the prompts structure from this iteration.
+ * If there's another, a new one will be allocated
+ * when we return to the top of this while loop.
+ */
+ free_prompts(s->cur_prompt);
+
+ /*
+ * Get the next packet in case it's another
+ * INFO_REQUEST.
+ */
+ crWaitUntilV(pktin);
+
+ }
+
+ /*
+ * We should have SUCCESS or FAILURE now.
+ */
+ s->gotit = TRUE;
+
+ } else if (s->can_passwd) {
+
+ /*
+ * Plain old password authentication.
+ */
+ int ret; /* not live over crReturn */
+ int changereq_first_time; /* not live over crReturn */
+
+ ssh->pkt_actx = SSH2_PKTCTX_PASSWORD;
+
+ s->cur_prompt = new_prompts(ssh->frontend);
+ s->cur_prompt->to_server = TRUE;
+ s->cur_prompt->name = dupstr("SSH password");
+ add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ",
+ ssh->username,
+ ssh->savedhost),
+ FALSE);
+
+ ret = get_userpass_input(s->cur_prompt, NULL, 0);
+ while (ret < 0) {
+ ssh->send_ok = 1;
+ crWaitUntilV(!pktin);
+ ret = get_userpass_input(s->cur_prompt, in, inlen);
+ ssh->send_ok = 0;
+ }
+ if (!ret) {
+ /*
+ * Failed to get responses. Terminate.
+ */
+ free_prompts(s->cur_prompt);
+ ssh_disconnect(ssh, NULL, "Unable to authenticate",
+ SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER,
+ TRUE);
+ crStopV;
+ }
+ /*
+ * Squirrel away the password. (We may need it later if
+ * asked to change it.)
+ */
+ s->password = dupstr(s->cur_prompt->prompts[0]->result);
+ free_prompts(s->cur_prompt);
+
+ /*
+ * Send the password packet.
+ *
+ * We pad out the password packet to 256 bytes to make
+ * it harder for an attacker to find the length of the
+ * user's password.
+ *
+ * Anyone using a password longer than 256 bytes
+ * probably doesn't have much to worry about from
+ * people who find out how long their password is!
+ */
+ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
+ ssh2_pkt_addstring(s->pktout, ssh->username);
+ ssh2_pkt_addstring(s->pktout, "ssh-connection");
+ /* service requested */
+ ssh2_pkt_addstring(s->pktout, "password");
+ ssh2_pkt_addbool(s->pktout, FALSE);
+ ssh2_pkt_addstring(s->pktout, s->password);
+ ssh2_pkt_send_with_padding(ssh, s->pktout, 256);
+ logevent("Sent password");
+ s->type = AUTH_TYPE_PASSWORD;
+
+ /*
+ * Wait for next packet, in case it's a password change
+ * request.
+ */
+ crWaitUntilV(pktin);
+ changereq_first_time = TRUE;
+
+ while (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) {
+
+ /*
+ * We're being asked for a new password
+ * (perhaps not for the first time).
+ * Loop until the server accepts it.
+ */
+
+ int got_new = FALSE; /* not live over crReturn */
+ char *prompt; /* not live over crReturn */
+ int prompt_len; /* not live over crReturn */
+
+ {
+ char *msg;
+ if (changereq_first_time)
+ msg = "Server requested password change";
+ else
+ msg = "Server rejected new password";
+ logevent(msg);
+ c_write_str(ssh, msg);
+ c_write_str(ssh, "\r\n");
+ }
+
+ ssh_pkt_getstring(pktin, &prompt, &prompt_len);
+
+ s->cur_prompt = new_prompts(ssh->frontend);
+ s->cur_prompt->to_server = TRUE;
+ s->cur_prompt->name = dupstr("New SSH password");
+ s->cur_prompt->instruction =
+ dupprintf("%.*s", prompt_len, prompt);
+ s->cur_prompt->instr_reqd = TRUE;
+ /*
+ * There's no explicit requirement in the protocol
+ * for the "old" passwords in the original and
+ * password-change messages to be the same, and
+ * apparently some Cisco kit supports password change
+ * by the user entering a blank password originally
+ * and the real password subsequently, so,
+ * reluctantly, we prompt for the old password again.
+ *
+ * (On the other hand, some servers don't even bother
+ * to check this field.)
+ */
+ add_prompt(s->cur_prompt,
+ dupstr("Current password (blank for previously entered password): "),
+ FALSE);
+ add_prompt(s->cur_prompt, dupstr("Enter new password: "),
+ FALSE);
+ add_prompt(s->cur_prompt, dupstr("Confirm new password: "),
+ FALSE);
+
+ /*
+ * Loop until the user manages to enter the same
+ * password twice.
+ */
+ while (!got_new) {
+
+ ret = get_userpass_input(s->cur_prompt, NULL, 0);
+ while (ret < 0) {
+ ssh->send_ok = 1;
+ crWaitUntilV(!pktin);
+ ret = get_userpass_input(s->cur_prompt, in, inlen);
+ ssh->send_ok = 0;
+ }
+ if (!ret) {
+ /*
+ * Failed to get responses. Terminate.
+ */
+ /* burn the evidence */
+ free_prompts(s->cur_prompt);
+ smemclr(s->password, strlen(s->password));
+ sfree(s->password);
+ ssh_disconnect(ssh, NULL, "Unable to authenticate",
+ SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER,
+ TRUE);
+ crStopV;
+ }
+
+ /*
+ * If the user specified a new original password
+ * (IYSWIM), overwrite any previously specified
+ * one.
+ * (A side effect is that the user doesn't have to
+ * re-enter it if they louse up the new password.)
+ */
+ if (s->cur_prompt->prompts[0]->result[0]) {
+ smemclr(s->password, strlen(s->password));
+ /* burn the evidence */
+ sfree(s->password);
+ s->password =
+ dupstr(s->cur_prompt->prompts[0]->result);
+ }
+
+ /*
+ * Check the two new passwords match.
+ */
+ got_new = (strcmp(s->cur_prompt->prompts[1]->result,
+ s->cur_prompt->prompts[2]->result)
+ == 0);
+ if (!got_new)
+ /* They don't. Silly user. */
+ c_write_str(ssh, "Passwords do not match\r\n");
+
+ }
+
+ /*
+ * Send the new password (along with the old one).
+ * (see above for padding rationale)
+ */
+ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
+ ssh2_pkt_addstring(s->pktout, ssh->username);
+ ssh2_pkt_addstring(s->pktout, "ssh-connection");
+ /* service requested */
+ ssh2_pkt_addstring(s->pktout, "password");
+ ssh2_pkt_addbool(s->pktout, TRUE);
+ ssh2_pkt_addstring(s->pktout, s->password);
+ ssh2_pkt_addstring(s->pktout,
+ s->cur_prompt->prompts[1]->result);
+ free_prompts(s->cur_prompt);
+ ssh2_pkt_send_with_padding(ssh, s->pktout, 256);
+ logevent("Sent new password");
+
+ /*
+ * Now see what the server has to say about it.
+ * (If it's CHANGEREQ again, it's not happy with the
+ * new password.)
+ */
+ crWaitUntilV(pktin);
+ changereq_first_time = FALSE;
+
+ }
+
+ /*
+ * We need to reexamine the current pktin at the top
+ * of the loop. Either:
+ * - we weren't asked to change password at all, in
+ * which case it's a SUCCESS or FAILURE with the
+ * usual meaning
+ * - we sent a new password, and the server was
+ * either OK with it (SUCCESS or FAILURE w/partial
+ * success) or unhappy with the _old_ password
+ * (FAILURE w/o partial success)
+ * In any of these cases, we go back to the top of
+ * the loop and start again.
+ */
+ s->gotit = TRUE;
+
+ /*
+ * We don't need the old password any more, in any
+ * case. Burn the evidence.
+ */
+ smemclr(s->password, strlen(s->password));
+ sfree(s->password);
+
+ } else {
+ char *str = dupprintf("No supported authentication methods available"
+ " (server sent: %.*s)",
+ methlen, methods);
+
+ ssh_disconnect(ssh, str,
+ "No supported authentication methods available",
+ SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
+ FALSE);
+ sfree(str);
+
+ crStopV;
+
+ }
+
+ }
+ }
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = NULL;
+
+ /* Clear up various bits and pieces from authentication. */
+ if (s->publickey_blob) {
+ sfree(s->publickey_blob);
+ sfree(s->publickey_comment);
+ }
+ if (s->agent_response)
+ sfree(s->agent_response);
+
+ if (s->userauth_success && !ssh->bare_connection) {
+ /*
+ * We've just received USERAUTH_SUCCESS, and we haven't sent any
+ * packets since. Signal the transport layer to consider enacting
+ * delayed compression.
+ *
+ * (Relying on we_are_in is not sufficient, as
+ * draft-miller-secsh-compression-delayed is quite clear that it
+ * triggers on USERAUTH_SUCCESS specifically, and we_are_in can
+ * become set for other reasons.)
+ */
+ do_ssh2_transport(ssh, "enabling delayed compression", -2, NULL);
+ }
+
+ ssh->channels = newtree234(ssh_channelcmp);
+
+ /*
+ * Set up handlers for some connection protocol messages, so we
+ * don't have to handle them repeatedly in this coroutine.
+ */
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] =
+ ssh2_msg_channel_window_adjust;
+ ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] =
+ ssh2_msg_global_request;
+
+ /*
+ * Create the main session channel.
+ */
+ if (conf_get_int(ssh->conf, CONF_ssh_no_shell)) {
+ ssh->mainchan = NULL;
+ } else {
+ ssh->mainchan = snew(struct ssh_channel);
+ ssh->mainchan->ssh = ssh;
+ ssh2_channel_init(ssh->mainchan);
+
+ if (*conf_get_str(ssh->conf, CONF_ssh_nc_host)) {
+ /*
+ * Just start a direct-tcpip channel and use it as the main
+ * channel.
+ */
+ ssh_send_port_open(ssh->mainchan,
+ conf_get_str(ssh->conf, CONF_ssh_nc_host),
+ conf_get_int(ssh->conf, CONF_ssh_nc_port),
+ "main channel");
+ ssh->ncmode = TRUE;
+ } else {
+ s->pktout = ssh2_chanopen_init(ssh->mainchan, "session");
+ logevent("Opening session as main channel");
+ ssh2_pkt_send(ssh, s->pktout);
+ ssh->ncmode = FALSE;
+ }
+ crWaitUntilV(pktin);
+ if (pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
+ bombout(("Server refused to open channel"));
+ crStopV;
+ /* FIXME: error data comes back in FAILURE packet */
+ }
+ if (ssh_pkt_getuint32(pktin) != ssh->mainchan->localid) {
+ bombout(("Server's channel confirmation cited wrong channel"));
+ crStopV;
+ }
+ ssh->mainchan->remoteid = ssh_pkt_getuint32(pktin);
+ ssh->mainchan->halfopen = FALSE;
+ ssh->mainchan->type = CHAN_MAINSESSION;
+ ssh->mainchan->v.v2.remwindow = ssh_pkt_getuint32(pktin);
+ ssh->mainchan->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin);
+ add234(ssh->channels, ssh->mainchan);
+ update_specials_menu(ssh->frontend);
+ logevent("Opened main channel");
+ }
+
+ /*
+ * Now we have a channel, make dispatch table entries for
+ * general channel-based messages.
+ */
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] =
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] =
+ ssh2_msg_channel_data;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_channel_eof;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_channel_close;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] =
+ ssh2_msg_channel_open_confirmation;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] =
+ ssh2_msg_channel_open_failure;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] =
+ ssh2_msg_channel_request;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] =
+ ssh2_msg_channel_open;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_channel_response;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_channel_response;
+
+ /*
+ * Now the connection protocol is properly up and running, with
+ * all those dispatch table entries, so it's safe to let
+ * downstreams start trying to open extra channels through us.
+ */
+ if (ssh->connshare)
+ share_activate(ssh->connshare, ssh->v_s);
+
+ if (ssh->mainchan && ssh_is_simple(ssh)) {
+ /*
+ * This message indicates to the server that we promise
+ * not to try to run any other channel in parallel with
+ * this one, so it's safe for it to advertise a very large
+ * window and leave the flow control to TCP.
+ */
+ s->pktout = ssh2_chanreq_init(ssh->mainchan,
+ "simple@putty.projects.tartarus.org",
+ NULL, NULL);
+ ssh2_pkt_send(ssh, s->pktout);
+ }
+
+ /*
+ * Enable port forwardings.
+ */
+ ssh_setup_portfwd(ssh, ssh->conf);
+
+ if (ssh->mainchan && !ssh->ncmode) {
+ /*
+ * Send the CHANNEL_REQUESTS for the main session channel.
+ * Each one is handled by its own little asynchronous
+ * co-routine.
+ */
+
+ /* Potentially enable X11 forwarding. */
+ if (conf_get_int(ssh->conf, CONF_x11_forward)) {
+ ssh->x11disp =
+ x11_setup_display(conf_get_str(ssh->conf, CONF_x11_display),
+ ssh->conf);
+ if (!ssh->x11disp) {
+ /* FIXME: return an error message from x11_setup_display */
+ logevent("X11 forwarding not enabled: unable to"
+ " initialise X display");
+ } else {
+ ssh->x11auth = x11_invent_fake_auth
+ (ssh->x11authtree, conf_get_int(ssh->conf, CONF_x11_auth));
+ ssh->x11auth->disp = ssh->x11disp;
+
+ ssh2_setup_x11(ssh->mainchan, NULL, NULL);
+ }
+ }
+
+ /* Potentially enable agent forwarding. */
+ if (ssh_agent_forwarding_permitted(ssh))
+ ssh2_setup_agent(ssh->mainchan, NULL, NULL);
+
+ /* Now allocate a pty for the session. */
+ if (!conf_get_int(ssh->conf, CONF_nopty))
+ ssh2_setup_pty(ssh->mainchan, NULL, NULL);
+
+ /* Send environment variables. */
+ ssh2_setup_env(ssh->mainchan, NULL, NULL);
+
+ /*
+ * Start a shell or a remote command. We may have to attempt
+ * this twice if the config data has provided a second choice
+ * of command.
+ */
+ while (1) {
+ int subsys;
+ char *cmd;
+
+ if (ssh->fallback_cmd) {
+ subsys = conf_get_int(ssh->conf, CONF_ssh_subsys2);
+ cmd = conf_get_str(ssh->conf, CONF_remote_cmd2);
+ } else {
+ subsys = conf_get_int(ssh->conf, CONF_ssh_subsys);
+ cmd = conf_get_str(ssh->conf, CONF_remote_cmd);
+ }
+
+ if (subsys) {
+ s->pktout = ssh2_chanreq_init(ssh->mainchan, "subsystem",
+ ssh2_response_authconn, NULL);
+ ssh2_pkt_addstring(s->pktout, cmd);
+ } else if (*cmd) {
+ s->pktout = ssh2_chanreq_init(ssh->mainchan, "exec",
+ ssh2_response_authconn, NULL);
+ ssh2_pkt_addstring(s->pktout, cmd);
+ } else {
+ s->pktout = ssh2_chanreq_init(ssh->mainchan, "shell",
+ ssh2_response_authconn, NULL);
+ }
+ ssh2_pkt_send(ssh, s->pktout);
+
+ crWaitUntilV(pktin);
+
+ if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
+ if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
+ bombout(("Unexpected response to shell/command request:"
+ " packet type %d", pktin->type));
+ crStopV;
+ }
+ /*
+ * We failed to start the command. If this is the
+ * fallback command, we really are finished; if it's
+ * not, and if the fallback command exists, try falling
+ * back to it before complaining.
+ */
+ if (!ssh->fallback_cmd &&
+ *conf_get_str(ssh->conf, CONF_remote_cmd2)) {
+ logevent("Primary command failed; attempting fallback");
+ ssh->fallback_cmd = TRUE;
+ continue;
+ }
+ bombout(("Server refused to start a shell/command"));
+ crStopV;
+ } else {
+ logevent("Started a shell/command");
+ }
+ break;
+ }
+ } else {
+ ssh->editing = ssh->echoing = TRUE;
+ }
+
+ ssh->state = SSH_STATE_SESSION;
+ if (ssh->size_needed)
+ ssh_size(ssh, ssh->term_width, ssh->term_height);
+ if (ssh->eof_needed)
+ ssh_special(ssh, TS_EOF);
+
+ /*
+ * Transfer data!
+ */
+ if (ssh->ldisc)
+ ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */
+ if (ssh->mainchan)
+ ssh->send_ok = 1;
+ while (1) {
+ crReturnV;
+ s->try_send = FALSE;
+ if (pktin) {
+
+ /*
+ * _All_ the connection-layer packets we expect to
+ * receive are now handled by the dispatch table.
+ * Anything that reaches here must be bogus.
+ */
+
+ bombout(("Strange packet received: type %d", pktin->type));
+ crStopV;
+ } else if (ssh->mainchan) {
+ /*
+ * We have spare data. Add it to the channel buffer.
+ */
+ ssh2_add_channel_data(ssh->mainchan, (char *)in, inlen);
+ s->try_send = TRUE;
+ }
+ if (s->try_send) {
+ int i;
+ struct ssh_channel *c;
+ /*
+ * Try to send data on all channels if we can.
+ */
+ for (i = 0; NULL != (c = index234(ssh->channels, i)); i++)
+ ssh2_try_send_and_unthrottle(ssh, c);
+ }
+ }
+
+ crFinishV;
+}
+
+/*
+ * Handlers for SSH-2 messages that might arrive at any moment.
+ */
+static void ssh2_msg_disconnect(Ssh ssh, struct Packet *pktin)
+{
+ /* log reason code in disconnect message */
+ char *buf, *msg;
+ int reason, msglen;
+
+ reason = ssh_pkt_getuint32(pktin);
+ ssh_pkt_getstring(pktin, &msg, &msglen);
+
+ if (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) {
+ buf = dupprintf("Received disconnect message (%s)",
+ ssh2_disconnect_reasons[reason]);
+ } else {
+ buf = dupprintf("Received disconnect message (unknown"
+ " type %d)", reason);
+ }
+ logevent(buf);
+ sfree(buf);
+ buf = dupprintf("Disconnection message text: %.*s",
+ msglen, msg);
+ logevent(buf);
+ bombout(("Server sent disconnect message\ntype %d (%s):\n\"%.*s\"",
+ reason,
+ (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ?
+ ssh2_disconnect_reasons[reason] : "unknown",
+ msglen, msg));
+ sfree(buf);
+}
+
+static void ssh2_msg_debug(Ssh ssh, struct Packet *pktin)
+{
+ /* log the debug message */
+ char *msg;
+ int msglen;
+
+ /* XXX maybe we should actually take notice of the return value */
+ ssh2_pkt_getbool(pktin);
+ ssh_pkt_getstring(pktin, &msg, &msglen);
+
+ logeventf(ssh, "Remote debug message: %.*s", msglen, msg);
+}
+
+static void ssh2_msg_transport(Ssh ssh, struct Packet *pktin)
+{
+ do_ssh2_transport(ssh, NULL, 0, pktin);
+}
+
+/*
+ * Called if we receive a packet that isn't allowed by the protocol.
+ * This only applies to packets whose meaning PuTTY understands.
+ * Entirely unknown packets are handled below.
+ */
+static void ssh2_msg_unexpected(Ssh ssh, struct Packet *pktin)
+{
+ char *buf = dupprintf("Server protocol violation: unexpected %s packet",
+ ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx,
+ pktin->type));
+ ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
+ sfree(buf);
+}
+
+static void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin)
+{
+ struct Packet *pktout;
+ pktout = ssh2_pkt_init(SSH2_MSG_UNIMPLEMENTED);
+ ssh2_pkt_adduint32(pktout, pktin->sequence);
+ /*
+ * UNIMPLEMENTED messages MUST appear in the same order as the
+ * messages they respond to. Hence, never queue them.
+ */
+ ssh2_pkt_send_noqueue(ssh, pktout);
+}
+
+/*
+ * Handle the top-level SSH-2 protocol.
+ */
+static void ssh2_protocol_setup(Ssh ssh)
+{
+ int i;
+
+ /*
+ * Most messages cause SSH2_MSG_UNIMPLEMENTED.
+ */
+ for (i = 0; i < 256; i++)
+ ssh->packet_dispatch[i] = ssh2_msg_something_unimplemented;
+
+ /*
+ * Initially, we only accept transport messages (and a few generic
+ * ones). do_ssh2_authconn will add more when it starts.
+ * Messages that are understood but not currently acceptable go to
+ * ssh2_msg_unexpected.
+ */
+ ssh->packet_dispatch[SSH2_MSG_UNIMPLEMENTED] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_SERVICE_REQUEST] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_SERVICE_ACCEPT] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_KEXINIT] = ssh2_msg_transport;
+ ssh->packet_dispatch[SSH2_MSG_NEWKEYS] = ssh2_msg_transport;
+ ssh->packet_dispatch[SSH2_MSG_KEXDH_INIT] = ssh2_msg_transport;
+ ssh->packet_dispatch[SSH2_MSG_KEXDH_REPLY] = ssh2_msg_transport;
+ /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REQUEST] = ssh2_msg_transport; duplicate case value */
+ /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_GROUP] = ssh2_msg_transport; duplicate case value */
+ ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_INIT] = ssh2_msg_transport;
+ ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REPLY] = ssh2_msg_transport;
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_REQUEST] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_FAILURE] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_SUCCESS] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_PK_OK] = ssh2_msg_unexpected;
+ /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ] = ssh2_msg_unexpected; duplicate case value */
+ /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_REQUEST] = ssh2_msg_unexpected; duplicate case value */
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_RESPONSE] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_unexpected;
+
+ /*
+ * These messages have a special handler from the start.
+ */
+ ssh->packet_dispatch[SSH2_MSG_DISCONNECT] = ssh2_msg_disconnect;
+ ssh->packet_dispatch[SSH2_MSG_IGNORE] = ssh_msg_ignore; /* shared with SSH-1 */
+ ssh->packet_dispatch[SSH2_MSG_DEBUG] = ssh2_msg_debug;
+}
+
+static void ssh2_bare_connection_protocol_setup(Ssh ssh)
+{
+ int i;
+
+ /*
+ * Most messages cause SSH2_MSG_UNIMPLEMENTED.
+ */
+ for (i = 0; i < 256; i++)
+ ssh->packet_dispatch[i] = ssh2_msg_something_unimplemented;
+
+ /*
+ * Initially, we set all ssh-connection messages to 'unexpected';
+ * do_ssh2_authconn will fill things in properly. We also handle a
+ * couple of messages from the transport protocol which aren't
+ * related to key exchange (UNIMPLEMENTED, IGNORE, DEBUG,
+ * DISCONNECT).
+ */
+ ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_unexpected;
+
+ ssh->packet_dispatch[SSH2_MSG_UNIMPLEMENTED] = ssh2_msg_unexpected;
+
+ /*
+ * These messages have a special handler from the start.
+ */
+ ssh->packet_dispatch[SSH2_MSG_DISCONNECT] = ssh2_msg_disconnect;
+ ssh->packet_dispatch[SSH2_MSG_IGNORE] = ssh_msg_ignore;
+ ssh->packet_dispatch[SSH2_MSG_DEBUG] = ssh2_msg_debug;
+}
+
+static void ssh2_timer(void *ctx, unsigned long now)
+{
+ Ssh ssh = (Ssh)ctx;
+
+ if (ssh->state == SSH_STATE_CLOSED)
+ return;
+
+ if (!ssh->kex_in_progress && !ssh->bare_connection &&
+ conf_get_int(ssh->conf, CONF_ssh_rekey_time) != 0 &&
+ now == ssh->next_rekey) {
+ do_ssh2_transport(ssh, "timeout", -1, NULL);
+ }
+}
+
+static void ssh2_protocol(Ssh ssh, void *vin, int inlen,
+ struct Packet *pktin)
+{
+ unsigned char *in = (unsigned char *)vin;
+ if (ssh->state == SSH_STATE_CLOSED)
+ return;
+
+ if (pktin) {
+ ssh->incoming_data_size += pktin->encrypted_len;
+ if (!ssh->kex_in_progress &&
+ ssh->max_data_size != 0 &&
+ ssh->incoming_data_size > ssh->max_data_size)
+ do_ssh2_transport(ssh, "too much data received", -1, NULL);
+ }
+
+ if (pktin)
+ ssh->packet_dispatch[pktin->type](ssh, pktin);
+ else if (!ssh->protocol_initial_phase_done)
+ do_ssh2_transport(ssh, in, inlen, pktin);
+ else
+ do_ssh2_authconn(ssh, in, inlen, pktin);
+}
+
+static void ssh2_bare_connection_protocol(Ssh ssh, void *vin, int inlen,
+ struct Packet *pktin)
+{
+ unsigned char *in = (unsigned char *)vin;
+ if (ssh->state == SSH_STATE_CLOSED)
+ return;
+
+ if (pktin)
+ ssh->packet_dispatch[pktin->type](ssh, pktin);
+ else
+ do_ssh2_authconn(ssh, in, inlen, pktin);
+}
+
+static void ssh_cache_conf_values(Ssh ssh)
+{
+ ssh->logomitdata = conf_get_int(ssh->conf, CONF_logomitdata);
+}
+
+/*
+ * Called to set up the connection.
+ *
+ * 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,
+ int nodelay, int keepalive)
+{
+ const char *p;
+ Ssh ssh;
+
+ ssh = snew(struct ssh_tag);
+ ssh->conf = conf_copy(conf);
+ ssh_cache_conf_values(ssh);
+ ssh->version = 0; /* when not ready yet */
+ ssh->s = NULL;
+ ssh->cipher = NULL;
+ ssh->v1_cipher_ctx = NULL;
+ ssh->crcda_ctx = NULL;
+ ssh->cscipher = NULL;
+ ssh->cs_cipher_ctx = NULL;
+ ssh->sccipher = NULL;
+ ssh->sc_cipher_ctx = NULL;
+ ssh->csmac = NULL;
+ ssh->cs_mac_ctx = NULL;
+ ssh->scmac = NULL;
+ ssh->sc_mac_ctx = NULL;
+ ssh->cscomp = NULL;
+ ssh->cs_comp_ctx = NULL;
+ ssh->sccomp = NULL;
+ ssh->sc_comp_ctx = NULL;
+ ssh->kex = NULL;
+ ssh->kex_ctx = NULL;
+ ssh->hostkey = NULL;
+ ssh->hostkey_str = NULL;
+ ssh->exitcode = -1;
+ ssh->close_expected = FALSE;
+ ssh->clean_exit = FALSE;
+ ssh->state = SSH_STATE_PREPACKET;
+ ssh->size_needed = FALSE;
+ ssh->eof_needed = FALSE;
+ ssh->ldisc = NULL;
+ ssh->logctx = NULL;
+ ssh->deferred_send_data = NULL;
+ ssh->deferred_len = 0;
+ ssh->deferred_size = 0;
+ ssh->fallback_cmd = 0;
+ ssh->pkt_kctx = SSH2_PKTCTX_NOKEX;
+ ssh->pkt_actx = SSH2_PKTCTX_NOAUTH;
+ ssh->x11disp = NULL;
+ ssh->x11auth = NULL;
+ ssh->x11authtree = newtree234(x11_authcmp);
+ ssh->v1_compressing = FALSE;
+ ssh->v2_outgoing_sequence = 0;
+ ssh->ssh1_rdpkt_crstate = 0;
+ ssh->ssh2_rdpkt_crstate = 0;
+ ssh->ssh2_bare_rdpkt_crstate = 0;
+ ssh->ssh_gotdata_crstate = 0;
+ ssh->do_ssh1_connection_crstate = 0;
+ ssh->do_ssh_init_state = NULL;
+ ssh->do_ssh_connection_init_state = NULL;
+ ssh->do_ssh1_login_state = NULL;
+ ssh->do_ssh2_transport_state = NULL;
+ ssh->do_ssh2_authconn_state = NULL;
+ ssh->v_c = NULL;
+ ssh->v_s = NULL;
+ ssh->mainchan = NULL;
+ ssh->throttled_all = 0;
+ ssh->v1_stdout_throttling = 0;
+ ssh->queue = NULL;
+ ssh->queuelen = ssh->queuesize = 0;
+ ssh->queueing = FALSE;
+ ssh->qhead = ssh->qtail = NULL;
+ ssh->deferred_rekey_reason = NULL;
+ bufchain_init(&ssh->queued_incoming_data);
+ ssh->frozen = FALSE;
+ ssh->username = NULL;
+ ssh->sent_console_eof = FALSE;
+ ssh->got_pty = FALSE;
+ ssh->bare_connection = FALSE;
+ ssh->attempting_connshare = FALSE;
+
+ *backend_handle = ssh;
+
+#ifdef MSCRYPTOAPI
+ if (crypto_startup() == 0)
+ return "Microsoft high encryption pack not installed!";
+#endif
+
+ ssh->frontend = frontend_handle;
+ ssh->term_width = conf_get_int(ssh->conf, CONF_width);
+ ssh->term_height = conf_get_int(ssh->conf, CONF_height);
+
+ ssh->channels = NULL;
+ ssh->rportfwds = NULL;
+ ssh->portfwds = NULL;
+
+ ssh->send_ok = 0;
+ ssh->editing = 0;
+ ssh->echoing = 0;
+ ssh->conn_throttle_count = 0;
+ ssh->overall_bufsize = 0;
+ ssh->fallback_cmd = 0;
+
+ ssh->protocol = NULL;
+
+ ssh->protocol_initial_phase_done = FALSE;
+
+ ssh->pinger = NULL;
+
+ ssh->incoming_data_size = ssh->outgoing_data_size =
+ ssh->deferred_data_size = 0L;
+ ssh->max_data_size = parse_blocksize(conf_get_str(ssh->conf,
+ CONF_ssh_rekey_data));
+ ssh->kex_in_progress = FALSE;
+
+#ifndef NO_GSSAPI
+ ssh->gsslibs = NULL;
+#endif
+
+ p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive);
+ if (p != NULL)
+ return p;
+
+ random_ref();
+
+ return NULL;
+}
+
+static void ssh_free(void *handle)
+{
+ Ssh ssh = (Ssh) handle;
+ struct ssh_channel *c;
+ struct ssh_rportfwd *pf;
+ struct X11FakeAuth *auth;
+
+ if (ssh->v1_cipher_ctx)
+ ssh->cipher->free_context(ssh->v1_cipher_ctx);
+ if (ssh->cs_cipher_ctx)
+ ssh->cscipher->free_context(ssh->cs_cipher_ctx);
+ if (ssh->sc_cipher_ctx)
+ ssh->sccipher->free_context(ssh->sc_cipher_ctx);
+ if (ssh->cs_mac_ctx)
+ ssh->csmac->free_context(ssh->cs_mac_ctx);
+ if (ssh->sc_mac_ctx)
+ ssh->scmac->free_context(ssh->sc_mac_ctx);
+ if (ssh->cs_comp_ctx) {
+ if (ssh->cscomp)
+ ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx);
+ else
+ zlib_compress_cleanup(ssh->cs_comp_ctx);
+ }
+ if (ssh->sc_comp_ctx) {
+ if (ssh->sccomp)
+ ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx);
+ else
+ zlib_decompress_cleanup(ssh->sc_comp_ctx);
+ }
+ if (ssh->kex_ctx)
+ dh_cleanup(ssh->kex_ctx);
+ sfree(ssh->savedhost);
+
+ while (ssh->queuelen-- > 0)
+ ssh_free_packet(ssh->queue[ssh->queuelen]);
+ sfree(ssh->queue);
+
+ while (ssh->qhead) {
+ struct queued_handler *qh = ssh->qhead;
+ ssh->qhead = qh->next;
+ sfree(qh);
+ }
+ ssh->qhead = ssh->qtail = NULL;
+
+ if (ssh->channels) {
+ while ((c = delpos234(ssh->channels, 0)) != NULL) {
+ switch (c->type) {
+ case CHAN_X11:
+ if (c->u.x11.xconn != NULL)
+ x11_close(c->u.x11.xconn);
+ break;
+ case CHAN_SOCKDATA:
+ case CHAN_SOCKDATA_DORMANT:
+ if (c->u.pfd.pf != NULL)
+ pfd_close(c->u.pfd.pf);
+ break;
+ }
+ if (ssh->version == 2) {
+ struct outstanding_channel_request *ocr, *nocr;
+ ocr = c->v.v2.chanreq_head;
+ while (ocr) {
+ ocr->handler(c, NULL, ocr->ctx);
+ nocr = ocr->next;
+ sfree(ocr);
+ ocr = nocr;
+ }
+ bufchain_clear(&c->v.v2.outbuffer);
+ }
+ sfree(c);
+ }
+ freetree234(ssh->channels);
+ ssh->channels = NULL;
+ }
+
+ if (ssh->connshare)
+ sharestate_free(ssh->connshare);
+
+ if (ssh->rportfwds) {
+ while ((pf = delpos234(ssh->rportfwds, 0)) != NULL)
+ free_rportfwd(pf);
+ freetree234(ssh->rportfwds);
+ ssh->rportfwds = NULL;
+ }
+ sfree(ssh->deferred_send_data);
+ if (ssh->x11disp)
+ x11_free_display(ssh->x11disp);
+ while ((auth = delpos234(ssh->x11authtree, 0)) != NULL)
+ x11_free_fake_auth(auth);
+ freetree234(ssh->x11authtree);
+ sfree(ssh->do_ssh_init_state);
+ sfree(ssh->do_ssh1_login_state);
+ sfree(ssh->do_ssh2_transport_state);
+ sfree(ssh->do_ssh2_authconn_state);
+ sfree(ssh->v_c);
+ sfree(ssh->v_s);
+ sfree(ssh->fullhostname);
+ sfree(ssh->hostkey_str);
+ if (ssh->crcda_ctx) {
+ crcda_free_context(ssh->crcda_ctx);
+ ssh->crcda_ctx = NULL;
+ }
+ if (ssh->s)
+ ssh_do_close(ssh, TRUE);
+ expire_timer_context(ssh);
+ if (ssh->pinger)
+ pinger_free(ssh->pinger);
+ bufchain_clear(&ssh->queued_incoming_data);
+ sfree(ssh->username);
+ conf_free(ssh->conf);
+#ifndef NO_GSSAPI
+ if (ssh->gsslibs)
+ ssh_gss_cleanup(ssh->gsslibs);
+#endif
+ sfree(ssh);
+
+ random_unref();
+}
+
+/*
+ * Reconfigure the SSH backend.
+ */
+static void ssh_reconfig(void *handle, Conf *conf)
+{
+ Ssh ssh = (Ssh) handle;
+ char *rekeying = NULL, rekey_mandatory = FALSE;
+ unsigned long old_max_data_size;
+ int i, rekey_time;
+
+ pinger_reconfig(ssh->pinger, ssh->conf, conf);
+ if (ssh->portfwds)
+ ssh_setup_portfwd(ssh, conf);
+
+ rekey_time = conf_get_int(conf, CONF_ssh_rekey_time);
+ if (conf_get_int(ssh->conf, CONF_ssh_rekey_time) != rekey_time &&
+ rekey_time != 0) {
+ unsigned long new_next = ssh->last_rekey + rekey_time*60*TICKSPERSEC;
+ unsigned long now = GETTICKCOUNT();
+
+ if (now - ssh->last_rekey > rekey_time*60*TICKSPERSEC) {
+ rekeying = "timeout shortened";
+ } else {
+ ssh->next_rekey = schedule_timer(new_next - now, ssh2_timer, ssh);
+ }
+ }
+
+ old_max_data_size = ssh->max_data_size;
+ ssh->max_data_size = parse_blocksize(conf_get_str(ssh->conf,
+ CONF_ssh_rekey_data));
+ if (old_max_data_size != ssh->max_data_size &&
+ ssh->max_data_size != 0) {
+ if (ssh->outgoing_data_size > ssh->max_data_size ||
+ ssh->incoming_data_size > ssh->max_data_size)
+ rekeying = "data limit lowered";
+ }
+
+ if (conf_get_int(ssh->conf, CONF_compression) !=
+ conf_get_int(conf, CONF_compression)) {
+ rekeying = "compression setting changed";
+ rekey_mandatory = TRUE;
+ }
+
+ for (i = 0; i < CIPHER_MAX; i++)
+ if (conf_get_int_int(ssh->conf, CONF_ssh_cipherlist, i) !=
+ conf_get_int_int(conf, CONF_ssh_cipherlist, i)) {
+ rekeying = "cipher settings changed";
+ rekey_mandatory = TRUE;
+ }
+ if (conf_get_int(ssh->conf, CONF_ssh2_des_cbc) !=
+ conf_get_int(conf, CONF_ssh2_des_cbc)) {
+ rekeying = "cipher settings changed";
+ rekey_mandatory = TRUE;
+ }
+
+ conf_free(ssh->conf);
+ ssh->conf = conf_copy(conf);
+ ssh_cache_conf_values(ssh);
+
+ if (!ssh->bare_connection && rekeying) {
+ if (!ssh->kex_in_progress) {
+ do_ssh2_transport(ssh, rekeying, -1, NULL);
+ } else if (rekey_mandatory) {
+ ssh->deferred_rekey_reason = rekeying;
+ }
+ }
+}
+
+/*
+ * Called to send data down the SSH connection.
+ */
+static int ssh_send(void *handle, 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);
+
+ return ssh_sendbuffer(ssh);
+}
+
+/*
+ * Called to query the current amount of buffered stdin data.
+ */
+static int ssh_sendbuffer(void *handle)
+{
+ Ssh ssh = (Ssh) handle;
+ int override_value;
+
+ if (ssh == NULL || ssh->s == NULL || ssh->protocol == NULL)
+ return 0;
+
+ /*
+ * If the SSH socket itself has backed up, add the total backup
+ * size on that to any individual buffer on the stdin channel.
+ */
+ override_value = 0;
+ if (ssh->throttled_all)
+ override_value = ssh->overall_bufsize;
+
+ if (ssh->version == 1) {
+ return override_value;
+ } else if (ssh->version == 2) {
+ if (!ssh->mainchan)
+ return override_value;
+ else
+ return (override_value +
+ bufchain_size(&ssh->mainchan->v.v2.outbuffer));
+ }
+
+ return 0;
+}
+
+/*
+ * Called to set the size of the window from SSH's POV.
+ */
+static void ssh_size(void *handle, int width, int height)
+{
+ Ssh ssh = (Ssh) handle;
+ struct Packet *pktout;
+
+ ssh->term_width = width;
+ ssh->term_height = height;
+
+ switch (ssh->state) {
+ case SSH_STATE_BEFORE_SIZE:
+ case SSH_STATE_PREPACKET:
+ case SSH_STATE_CLOSED:
+ break; /* do nothing */
+ case SSH_STATE_INTERMED:
+ ssh->size_needed = TRUE; /* buffer for later */
+ break;
+ case SSH_STATE_SESSION:
+ if (!conf_get_int(ssh->conf, CONF_nopty)) {
+ if (ssh->version == 1) {
+ send_packet(ssh, SSH1_CMSG_WINDOW_SIZE,
+ PKT_INT, ssh->term_height,
+ PKT_INT, ssh->term_width,
+ PKT_INT, 0, PKT_INT, 0, PKT_END);
+ } else if (ssh->mainchan) {
+ pktout = ssh2_chanreq_init(ssh->mainchan, "window-change",
+ NULL, NULL);
+ ssh2_pkt_adduint32(pktout, ssh->term_width);
+ ssh2_pkt_adduint32(pktout, ssh->term_height);
+ ssh2_pkt_adduint32(pktout, 0);
+ ssh2_pkt_adduint32(pktout, 0);
+ ssh2_pkt_send(ssh, pktout);
+ }
+ }
+ break;
+ }
+}
+
+/*
+ * Return a list of the special codes that make sense in this
+ * protocol.
+ */
+static const struct telnet_special *ssh_get_specials(void *handle)
+{
+ static const struct telnet_special ssh1_ignore_special[] = {
+ {"IGNORE message", TS_NOP}
+ };
+ static const struct telnet_special ssh2_ignore_special[] = {
+ {"IGNORE message", TS_NOP},
+ };
+ static const struct telnet_special ssh2_rekey_special[] = {
+ {"Repeat key exchange", TS_REKEY},
+ };
+ static const struct telnet_special ssh2_session_specials[] = {
+ {NULL, TS_SEP},
+ {"Break", TS_BRK},
+ /* These are the signal names defined by RFC 4254.
+ * They include all the ISO C signals, but are a subset of the POSIX
+ * required signals. */
+ {"SIGINT (Interrupt)", TS_SIGINT},
+ {"SIGTERM (Terminate)", TS_SIGTERM},
+ {"SIGKILL (Kill)", TS_SIGKILL},
+ {"SIGQUIT (Quit)", TS_SIGQUIT},
+ {"SIGHUP (Hangup)", TS_SIGHUP},
+ {"More signals", TS_SUBMENU},
+ {"SIGABRT", TS_SIGABRT}, {"SIGALRM", TS_SIGALRM},
+ {"SIGFPE", TS_SIGFPE}, {"SIGILL", TS_SIGILL},
+ {"SIGPIPE", TS_SIGPIPE}, {"SIGSEGV", TS_SIGSEGV},
+ {"SIGUSR1", TS_SIGUSR1}, {"SIGUSR2", TS_SIGUSR2},
+ {NULL, TS_EXITMENU}
+ };
+ static const struct telnet_special specials_end[] = {
+ {NULL, TS_EXITMENU}
+ };
+ /* XXX review this length for any changes: */
+ static struct telnet_special ssh_specials[lenof(ssh2_ignore_special) +
+ lenof(ssh2_rekey_special) +
+ lenof(ssh2_session_specials) +
+ lenof(specials_end)];
+ Ssh ssh = (Ssh) handle;
+ int i = 0;
+#define ADD_SPECIALS(name) \
+ do { \
+ assert((i + lenof(name)) <= lenof(ssh_specials)); \
+ memcpy(&ssh_specials[i], name, sizeof name); \
+ i += lenof(name); \
+ } while(0)
+
+ if (ssh->version == 1) {
+ /* Don't bother offering IGNORE if we've decided the remote
+ * won't cope with it, since we wouldn't bother sending it if
+ * asked anyway. */
+ if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE))
+ ADD_SPECIALS(ssh1_ignore_special);
+ } else if (ssh->version == 2) {
+ if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE))
+ ADD_SPECIALS(ssh2_ignore_special);
+ if (!(ssh->remote_bugs & BUG_SSH2_REKEY) && !ssh->bare_connection)
+ ADD_SPECIALS(ssh2_rekey_special);
+ if (ssh->mainchan)
+ ADD_SPECIALS(ssh2_session_specials);
+ } /* else we're not ready yet */
+
+ if (i) {
+ ADD_SPECIALS(specials_end);
+ return ssh_specials;
+ } else {
+ return NULL;
+ }
+#undef ADD_SPECIALS
+}
+
+/*
+ * Send special codes. TS_EOF is useful for `plink', so you
+ * can send an EOF and collect resulting output (e.g. `plink
+ * hostname sort').
+ */
+static void ssh_special(void *handle, Telnet_Special code)
+{
+ Ssh ssh = (Ssh) handle;
+ struct Packet *pktout;
+
+ if (code == TS_EOF) {
+ if (ssh->state != SSH_STATE_SESSION) {
+ /*
+ * Buffer the EOF in case we are pre-SESSION, so we can
+ * send it as soon as we reach SESSION.
+ */
+ if (code == TS_EOF)
+ ssh->eof_needed = TRUE;
+ return;
+ }
+ if (ssh->version == 1) {
+ send_packet(ssh, SSH1_CMSG_EOF, PKT_END);
+ } else if (ssh->mainchan) {
+ sshfwd_write_eof(ssh->mainchan);
+ ssh->send_ok = 0; /* now stop trying to read from stdin */
+ }
+ logevent("Sent EOF message");
+ } else if (code == TS_PING || code == TS_NOP) {
+ if (ssh->state == SSH_STATE_CLOSED
+ || ssh->state == SSH_STATE_PREPACKET) return;
+ if (ssh->version == 1) {
+ if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE))
+ send_packet(ssh, SSH1_MSG_IGNORE, PKT_STR, "", PKT_END);
+ } else {
+ if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) {
+ pktout = ssh2_pkt_init(SSH2_MSG_IGNORE);
+ ssh2_pkt_addstring_start(pktout);
+ ssh2_pkt_send_noqueue(ssh, pktout);
+ }
+ }
+ } else if (code == TS_REKEY) {
+ if (!ssh->kex_in_progress && !ssh->bare_connection &&
+ ssh->version == 2) {
+ do_ssh2_transport(ssh, "at user request", -1, NULL);
+ }
+ } else if (code == TS_BRK) {
+ if (ssh->state == SSH_STATE_CLOSED
+ || ssh->state == SSH_STATE_PREPACKET) return;
+ if (ssh->version == 1) {
+ logevent("Unable to send BREAK signal in SSH-1");
+ } else if (ssh->mainchan) {
+ pktout = ssh2_chanreq_init(ssh->mainchan, "break", NULL, NULL);
+ ssh2_pkt_adduint32(pktout, 0); /* default break length */
+ ssh2_pkt_send(ssh, pktout);
+ }
+ } else {
+ /* Is is a POSIX signal? */
+ char *signame = NULL;
+ if (code == TS_SIGABRT) signame = "ABRT";
+ if (code == TS_SIGALRM) signame = "ALRM";
+ if (code == TS_SIGFPE) signame = "FPE";
+ if (code == TS_SIGHUP) signame = "HUP";
+ if (code == TS_SIGILL) signame = "ILL";
+ if (code == TS_SIGINT) signame = "INT";
+ if (code == TS_SIGKILL) signame = "KILL";
+ if (code == TS_SIGPIPE) signame = "PIPE";
+ if (code == TS_SIGQUIT) signame = "QUIT";
+ if (code == TS_SIGSEGV) signame = "SEGV";
+ if (code == TS_SIGTERM) signame = "TERM";
+ if (code == TS_SIGUSR1) signame = "USR1";
+ if (code == TS_SIGUSR2) signame = "USR2";
+ /* The SSH-2 protocol does in principle support arbitrary named
+ * signals, including signame@domain, but we don't support those. */
+ if (signame) {
+ /* It's a signal. */
+ if (ssh->version == 2 && ssh->mainchan) {
+ pktout = ssh2_chanreq_init(ssh->mainchan, "signal", NULL, NULL);
+ ssh2_pkt_addstring(pktout, signame);
+ ssh2_pkt_send(ssh, pktout);
+ logeventf(ssh, "Sent signal SIG%s", signame);
+ }
+ } else {
+ /* Never heard of it. Do nothing */
+ }
+ }
+}
+
+void *new_sock_channel(void *handle, struct PortForwarding *pf)
+{
+ Ssh ssh = (Ssh) handle;
+ struct ssh_channel *c;
+ c = snew(struct ssh_channel);
+
+ c->ssh = ssh;
+ ssh2_channel_init(c);
+ c->halfopen = TRUE;
+ c->type = CHAN_SOCKDATA_DORMANT;/* identify channel type */
+ c->u.pfd.pf = pf;
+ add234(ssh->channels, c);
+ return c;
+}
+
+unsigned ssh_alloc_sharing_channel(Ssh ssh, void *sharing_ctx)
+{
+ struct ssh_channel *c;
+ c = snew(struct ssh_channel);
+
+ c->ssh = ssh;
+ ssh2_channel_init(c);
+ c->type = CHAN_SHARING;
+ c->u.sharing.ctx = sharing_ctx;
+ add234(ssh->channels, c);
+ return c->localid;
+}
+
+void ssh_delete_sharing_channel(Ssh ssh, unsigned localid)
+{
+ struct ssh_channel *c;
+
+ c = find234(ssh->channels, &localid, ssh_channelfind);
+ if (c)
+ ssh_channel_destroy(c);
+}
+
+void ssh_send_packet_from_downstream(Ssh ssh, unsigned id, int type,
+ const void *data, int datalen,
+ const char *additional_log_text)
+{
+ struct Packet *pkt;
+
+ pkt = ssh2_pkt_init(type);
+ pkt->downstream_id = id;
+ pkt->additional_log_text = additional_log_text;
+ ssh2_pkt_adddata(pkt, data, datalen);
+ ssh2_pkt_send(ssh, pkt);
+}
+
+/*
+ * This is called when stdout/stderr (the entity to which
+ * from_backend sends data) manages to clear some backlog.
+ */
+static void ssh_unthrottle(void *handle, int bufsize)
+{
+ Ssh ssh = (Ssh) handle;
+ int buflimit;
+
+ if (ssh->version == 1) {
+ if (ssh->v1_stdout_throttling && bufsize < SSH1_BUFFER_LIMIT) {
+ ssh->v1_stdout_throttling = 0;
+ ssh_throttle_conn(ssh, -1);
+ }
+ } else {
+ if (ssh->mainchan) {
+ ssh2_set_window(ssh->mainchan,
+ bufsize < ssh->mainchan->v.v2.locmaxwin ?
+ ssh->mainchan->v.v2.locmaxwin - bufsize : 0);
+ if (ssh_is_simple(ssh))
+ buflimit = 0;
+ else
+ buflimit = ssh->mainchan->v.v2.locmaxwin;
+ if (ssh->mainchan->throttling_conn && bufsize <= buflimit) {
+ ssh->mainchan->throttling_conn = 0;
+ ssh_throttle_conn(ssh, -1);
+ }
+ }
+ }
+
+ /*
+ * Now process any SSH connection data that was stashed in our
+ * queue while we were frozen.
+ */
+ ssh_process_queued_incoming_data(ssh);
+}
+
+void ssh_send_port_open(void *channel, char *hostname, int port, char *org)
+{
+ struct ssh_channel *c = (struct ssh_channel *)channel;
+ Ssh ssh = c->ssh;
+ struct Packet *pktout;
+
+ logeventf(ssh, "Opening connection to %s:%d for %s", hostname, port, org);
+
+ if (ssh->version == 1) {
+ send_packet(ssh, SSH1_MSG_PORT_OPEN,
+ PKT_INT, c->localid,
+ PKT_STR, hostname,
+ PKT_INT, port,
+ /* PKT_STR, <org:orgport>, */
+ PKT_END);
+ } else {
+ pktout = ssh2_chanopen_init(c, "direct-tcpip");
+ {
+ char *trimmed_host = host_strduptrim(hostname);
+ ssh2_pkt_addstring(pktout, trimmed_host);
+ sfree(trimmed_host);
+ }
+ ssh2_pkt_adduint32(pktout, port);
+ /*
+ * We make up values for the originator data; partly it's
+ * too much hassle to keep track, and partly I'm not
+ * convinced the server should be told details like that
+ * about my local network configuration.
+ * The "originator IP address" is syntactically a numeric
+ * IP address, and some servers (e.g., Tectia) get upset
+ * if it doesn't match this syntax.
+ */
+ ssh2_pkt_addstring(pktout, "0.0.0.0");
+ ssh2_pkt_adduint32(pktout, 0);
+ ssh2_pkt_send(ssh, pktout);
+ }
+}
+
+static int ssh_connected(void *handle)
+{
+ Ssh ssh = (Ssh) handle;
+ return ssh->s != NULL;
+}
+
+static int ssh_sendok(void *handle)
+{
+ Ssh ssh = (Ssh) handle;
+ return ssh->send_ok;
+}
+
+static int ssh_ldisc(void *handle, int option)
+{
+ Ssh ssh = (Ssh) handle;
+ if (option == LD_ECHO)
+ return ssh->echoing;
+ if (option == LD_EDIT)
+ return ssh->editing;
+ return FALSE;
+}
+
+static void ssh_provide_ldisc(void *handle, void *ldisc)
+{
+ Ssh ssh = (Ssh) handle;
+ ssh->ldisc = ldisc;
+}
+
+static void ssh_provide_logctx(void *handle, void *logctx)
+{
+ Ssh ssh = (Ssh) handle;
+ ssh->logctx = logctx;
+}
+
+static int ssh_return_exitcode(void *handle)
+{
+ Ssh ssh = (Ssh) handle;
+ if (ssh->s != NULL)
+ return -1;
+ else
+ return (ssh->exitcode >= 0 ? ssh->exitcode : INT_MAX);
+}
+
+/*
+ * cfg_info for SSH is the currently running version of the
+ * protocol. (1 for 1; 2 for 2; 0 for not-decided-yet.)
+ */
+static int ssh_cfg_info(void *handle)
+{
+ Ssh ssh = (Ssh) handle;
+ return ssh->version;
+}
+
+/*
+ * Gross hack: pscp will try to start SFTP but fall back to scp1 if
+ * that fails. This variable is the means by which scp.c can reach
+ * into the SSH code and find out which one it got.
+ */
+extern int ssh_fallback_cmd(void *handle)
+{
+ Ssh ssh = (Ssh) handle;
+ return ssh->fallback_cmd;
+}
+
+Backend ssh_backend = {
+ ssh_init,
+ ssh_free,
+ ssh_reconfig,
+ ssh_send,
+ ssh_sendbuffer,
+ ssh_size,
+ ssh_special,
+ ssh_get_specials,
+ ssh_connected,
+ ssh_return_exitcode,
+ ssh_sendok,
+ ssh_ldisc,
+ ssh_provide_ldisc,
+ ssh_provide_logctx,
+ ssh_unthrottle,
+ ssh_cfg_info,
+ "ssh",
+ PROT_SSH,
+ 22
+};
diff --git a/tools/plink/ssh.h b/tools/plink/ssh.h
index 3431842aa..89f772d66 100644
--- a/tools/plink/ssh.h
+++ b/tools/plink/ssh.h
@@ -1,600 +1,816 @@
-#include <stdio.h>
-#include <string.h>
-
-#include "puttymem.h"
-#include "tree234.h"
-#include "network.h"
-#include "int64.h"
-#include "misc.h"
-
-struct ssh_channel;
-
-extern int sshfwd_write(struct ssh_channel *c, char *, int);
-extern void sshfwd_write_eof(struct ssh_channel *c);
-extern void sshfwd_unclean_close(struct ssh_channel *c);
-extern void sshfwd_unthrottle(struct ssh_channel *c, int bufsize);
-
-/*
- * Useful thing.
- */
-#ifndef lenof
-#define lenof(x) ( (sizeof((x))) / (sizeof(*(x))))
-#endif
-
-#define SSH_CIPHER_IDEA 1
-#define SSH_CIPHER_DES 2
-#define SSH_CIPHER_3DES 3
-#define SSH_CIPHER_BLOWFISH 6
-
-#ifdef MSCRYPTOAPI
-#define APIEXTRA 8
-#else
-#define APIEXTRA 0
-#endif
-
-#ifndef BIGNUM_INTERNAL
-typedef void *Bignum;
-#endif
-
-struct RSAKey {
- int bits;
- int bytes;
-#ifdef MSCRYPTOAPI
- unsigned long exponent;
- unsigned char *modulus;
-#else
- Bignum modulus;
- Bignum exponent;
- Bignum private_exponent;
- Bignum p;
- Bignum q;
- Bignum iqmp;
-#endif
- char *comment;
-};
-
-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);
-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);
-void rsasanitise(struct RSAKey *key);
-int rsastr_len(struct RSAKey *key);
-void rsastr_fmt(char *str, struct RSAKey *key);
-void rsa_fingerprint(char *str, int len, struct RSAKey *key);
-int rsa_verify(struct RSAKey *key);
-unsigned char *rsa_public_blob(struct RSAKey *key, int *len);
-int rsa_public_blob_len(void *data, int maxlen);
-void freersakey(struct RSAKey *key);
-
-#ifndef PUTTY_UINT32_DEFINED
-/* This makes assumptions about the int type. */
-typedef unsigned int uint32;
-#define PUTTY_UINT32_DEFINED
-#endif
-typedef uint32 word32;
-
-unsigned long crc32_compute(const void *s, size_t len);
-unsigned long crc32_update(unsigned long crc_input, const void *s, size_t len);
-
-/* SSH CRC compensation attack detector */
-void *crcda_make_context(void);
-void crcda_free_context(void *handle);
-int detect_attack(void *handle, unsigned char *buf, uint32 len,
- unsigned char *IV);
-
-/*
- * SSH2 RSA key exchange functions
- */
-struct ssh_hash;
-void *ssh_rsakex_newkey(char *data, int len);
-void ssh_rsakex_freekey(void *key);
-int ssh_rsakex_klen(void *key);
-void ssh_rsakex_encrypt(const struct ssh_hash *h, unsigned char *in, int inlen,
- unsigned char *out, int outlen,
- void *key);
-
-typedef struct {
- uint32 h[4];
-} MD5_Core_State;
-
-struct MD5Context {
-#ifdef MSCRYPTOAPI
- unsigned long hHash;
-#else
- MD5_Core_State core;
- unsigned char block[64];
- int blkused;
- uint32 lenhi, lenlo;
-#endif
-};
-
-void MD5Init(struct MD5Context *context);
-void MD5Update(struct MD5Context *context, unsigned char const *buf,
- unsigned len);
-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_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,
- unsigned char *hmac);
-
-typedef struct {
- uint32 h[5];
- unsigned char block[64];
- int blkused;
- uint32 lenhi, lenlo;
-} SHA_State;
-void SHA_Init(SHA_State * s);
-void SHA_Bytes(SHA_State * s, void *p, int len);
-void SHA_Final(SHA_State * s, unsigned char *output);
-void SHA_Simple(void *p, int len, unsigned char *output);
-
-void hmac_sha1_simple(void *key, int keylen, void *data, int datalen,
- unsigned char *output);
-typedef struct {
- uint32 h[8];
- unsigned char block[64];
- int blkused;
- uint32 lenhi, lenlo;
-} SHA256_State;
-void SHA256_Init(SHA256_State * s);
-void SHA256_Bytes(SHA256_State * s, const void *p, int len);
-void SHA256_Final(SHA256_State * s, unsigned char *output);
-void SHA256_Simple(const void *p, int len, unsigned char *output);
-
-typedef struct {
- uint64 h[8];
- unsigned char block[128];
- int blkused;
- uint32 len[4];
-} 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);
-
-struct ssh_cipher {
- void *(*make_context)(void);
- void (*free_context)(void *);
- void (*sesskey) (void *, unsigned char *key); /* for SSH-1 */
- void (*encrypt) (void *, unsigned char *blk, int len);
- void (*decrypt) (void *, unsigned char *blk, int len);
- int blksize;
- char *text_name;
-};
-
-struct ssh2_cipher {
- void *(*make_context)(void);
- void (*free_context)(void *);
- void (*setiv) (void *, unsigned char *key); /* for SSH-2 */
- 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;
- int blksize;
- int keylen;
- unsigned int flags;
-#define SSH_CIPHER_IS_CBC 1
- char *text_name;
-};
-
-struct ssh2_ciphers {
- int nciphers;
- const struct ssh2_cipher *const *list;
-};
-
-struct ssh_mac {
- void *(*make_context)(void);
- void (*free_context)(void *);
- void (*setkey) (void *, unsigned char *key);
- /* whole-packet operations */
- void (*generate) (void *, unsigned char *blk, int len, unsigned long seq);
- int (*verify) (void *, unsigned char *blk, int len, unsigned long seq);
- /* partial-packet operations */
- void (*start) (void *);
- 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;
-};
-
-struct ssh_hash {
- void *(*init)(void); /* also allocates context */
- void (*bytes)(void *, void *, int);
- void (*final)(void *, unsigned char *); /* also frees context */
- int hlen; /* output length in bytes */
- 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 struct ssh_hash *hash;
-};
-
-struct ssh_kexes {
- int nkexes;
- const struct ssh_kex *const *list;
-};
-
-struct ssh_signkey {
- void *(*newkey) (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);
- 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,
- int *siglen);
- char *name;
- char *keytype; /* for host key cache */
-};
-
-struct ssh_compress {
- char *name;
- /* For zlib@openssh.com: if non-NULL, this name will be considered once
- * userauth has completed successfully. */
- char *delayed_name;
- void *(*compress_init) (void);
- void (*compress_cleanup) (void *);
- int (*compress) (void *, unsigned char *block, int len,
- unsigned char **outblock, int *outlen);
- void *(*decompress_init) (void);
- void (*decompress_cleanup) (void *);
- int (*decompress) (void *, unsigned char *block, int len,
- unsigned char **outblock, int *outlen);
- int (*disable_compression) (void *);
- char *text_name;
-};
-
-struct ssh2_userkey {
- const struct ssh_signkey *alg; /* the key algorithm */
- void *data; /* the key data */
- char *comment; /* the key comment */
-};
-
-/* The maximum length of any hash algorithm used in kex. (bytes) */
-#define SSH2_KEX_MAX_HASH_LEN (32) /* SHA-256 */
-
-extern const struct ssh_cipher ssh_3des;
-extern const struct ssh_cipher ssh_des;
-extern const struct ssh_cipher ssh_blowfish_ssh1;
-extern const struct ssh2_ciphers ssh2_3des;
-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 ssh_hash ssh_sha1;
-extern const struct ssh_hash ssh_sha256;
-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_signkey ssh_dss;
-extern const struct ssh_signkey ssh_rsa;
-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;
-extern const struct ssh_mac ssh_hmac_sha1_96;
-extern const struct ssh_mac ssh_hmac_sha1_96_buggy;
-
-void *aes_make_context(void);
-void aes_free_context(void *handle);
-void aes128_key(void *handle, unsigned char *key);
-void aes192_key(void *handle, unsigned char *key);
-void aes256_key(void *handle, unsigned char *key);
-void aes_iv(void *handle, unsigned char *iv);
-void aes_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len);
-void aes_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len);
-
-/*
- * PuTTY version number formatted as an SSH version string.
- */
-extern char sshver[];
-
-/*
- * Gross hack: pscp will try to start SFTP but fall back to scp1 if
- * that fails. This variable is the means by which scp.c can reach
- * into the SSH code and find out which one it got.
- */
-extern int ssh_fallback_cmd(void *handle);
-
-#ifndef MSCRYPTOAPI
-void SHATransform(word32 * digest, word32 * data);
-#endif
-
-int random_byte(void);
-void random_add_noise(void *noise, int length);
-void random_add_heavynoise(void *noise, int length);
-
-void logevent(void *, const char *);
-
-/* Allocate and register a new channel for port forwarding */
-void *new_sock_channel(void *handle, Socket s);
-void ssh_send_port_open(void *channel, char *hostname, int port, char *org);
-
-/* Exports from portfwd.c */
-extern const char *pfd_newconnect(Socket * s, char *hostname, int port,
- void *c, Conf *conf, int addressfamily);
-/* desthost == NULL indicates dynamic (SOCKS) port forwarding */
-extern const char *pfd_addforward(char *desthost, int destport, char *srcaddr,
- int port, void *backhandle, Conf *conf,
- void **sockdata, int address_family);
-extern void pfd_close(Socket s);
-extern void pfd_terminate(void *sockdata);
-extern int pfd_send(Socket s, char *data, int len);
-extern void pfd_send_eof(Socket s);
-extern void pfd_confirm(Socket s);
-extern void pfd_unthrottle(Socket s);
-extern void pfd_override_throttle(Socket s, int enable);
-
-/* Exports from x11fwd.c */
-enum {
- X11_TRANS_IPV4 = 0, X11_TRANS_IPV6 = 6, X11_TRANS_UNIX = 256
-};
-struct X11Display {
- /* Broken-down components of the display name itself */
- int unixdomain;
- char *hostname;
- int displaynum;
- int screennum;
- /* OSX sometimes replaces all the above with a full Unix-socket pathname */
- char *unixsocketpath;
-
- /* PuTTY networking SockAddr to connect to the display, and associated
- * gubbins */
- SockAddr addr;
- int port;
- char *realhost;
-
- /* Auth details we invented for the virtual display on the SSH server. */
- int remoteauthproto;
- unsigned char *remoteauthdata;
- int remoteauthdatalen;
- char *remoteauthprotoname;
- char *remoteauthdatastring;
-
- /* Our local auth details for talking to the real X display. */
- int localauthproto;
- unsigned char *localauthdata;
- int localauthdatalen;
-
- /*
- * Used inside x11fwd.c to remember recently seen
- * XDM-AUTHORIZATION-1 strings, to avoid replay attacks.
- */
- tree234 *xdmseen;
-};
-/*
- * x11_setup_display() parses the display variable and fills in an
- * X11Display structure. Some remote auth details are invented;
- * the supplied authtype parameter configures the preferred
- * 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, int authtype,
- Conf *);
-void x11_free_display(struct X11Display *disp);
-extern const char *x11_init(Socket *, struct X11Display *, void *,
- const char *, int, Conf *);
-extern void x11_close(Socket);
-extern int x11_send(Socket, char *, int);
-extern void x11_send_eof(Socket s);
-extern void x11_unthrottle(Socket s);
-extern void x11_override_throttle(Socket s, int enable);
-char *x11_display(const char *display);
-/* Platform-dependent X11 functions */
-extern void platform_get_x11_auth(struct X11Display *display, Conf *);
- /* examine a mostly-filled-in X11Display and fill in localauth* */
-extern const int platform_uses_x11_unix_by_default;
- /* choose default X transport in the absence of a specified one */
-SockAddr platform_get_x11_unix_address(const char *path, int displaynum);
- /* make up a SockAddr naming the address for displaynum */
-char *platform_get_x_display(void);
- /* allocated local X display string, if any */
-/* Callbacks in x11.c usable _by_ platform X11 functions */
-/*
- * This function does the job of platform_get_x11_auth, provided
- * it is told where to find a normally formatted .Xauthority file:
- * it opens that file, parses it to find an auth record which
- * matches the display details in "display", and fills in the
- * localauth fields.
- *
- * It is expected that most implementations of
- * platform_get_x11_auth() will work by finding their system's
- * .Xauthority file, adjusting the display details if necessary
- * for local oddities like Unix-domain socket transport, and
- * calling this function to do the rest of the work.
- */
-void x11_get_auth_from_authfile(struct X11Display *display,
- const char *authfilename);
-
-Bignum copybn(Bignum b);
-Bignum bn_power_2(int n);
-void bn_restore_invariant(Bignum b);
-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);
-void decbn(Bignum n);
-extern Bignum Zero, One;
-Bignum bignum_from_bytes(const unsigned char *data, int nbytes);
-int ssh1_read_bignum(const unsigned char *data, int len, Bignum * result);
-int bignum_bitcount(Bignum bn);
-int ssh1_bignum_length(Bignum bn);
-int ssh2_bignum_length(Bignum bn);
-int bignum_byte(Bignum bn, int i);
-int bignum_bit(Bignum bn, int i);
-void bignum_set_bit(Bignum bn, int i, int value);
-int ssh1_write_bignum(void *data, Bignum bn);
-Bignum biggcd(Bignum a, Bignum b);
-unsigned short bignum_mod_short(Bignum number, unsigned short modulus);
-Bignum bignum_add_long(Bignum number, unsigned long addend);
-Bignum bigadd(Bignum a, Bignum b);
-Bignum bigsub(Bignum a, Bignum b);
-Bignum bigmul(Bignum a, Bignum b);
-Bignum bigmuladd(Bignum a, Bignum b, Bignum addend);
-Bignum bigdiv(Bignum a, Bignum b);
-Bignum bigmod(Bignum a, Bignum b);
-Bignum modinv(Bignum number, Bignum modulus);
-Bignum bignum_bitmask(Bignum number);
-Bignum bignum_rshift(Bignum number, int shift);
-int bignum_cmp(Bignum a, Bignum b);
-char *bignum_decimal(Bignum x);
-
-#ifdef DEBUG
-void diagbn(char *prefix, Bignum md);
-#endif
-
-void *dh_setup_group(const struct ssh_kex *kex);
-void *dh_setup_gex(Bignum pval, Bignum gval);
-void dh_cleanup(void *);
-Bignum dh_create_e(void *, int nbits);
-Bignum dh_find_K(void *, Bignum f);
-
-int loadrsakey(const Filename *filename, struct RSAKey *key,
- 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_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);
-
-/* ssh2_load_userkey can return this as an error */
-extern struct ssh2_userkey ssh2_wrong_passphrase;
-#define SSH2_WRONG_PASSPHRASE (&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);
-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);
-
-enum {
- SSH_KEYTYPE_UNOPENABLE,
- SSH_KEYTYPE_UNKNOWN,
- SSH_KEYTYPE_SSH1, SSH_KEYTYPE_SSH2,
- SSH_KEYTYPE_OPENSSH, SSH_KEYTYPE_SSHCOM
-};
-int key_type(const Filename *filename);
-char *key_type_to_str(int type);
-
-int import_possible(int type);
-int import_target_type(int type);
-int import_encrypted(const Filename *filename, int type, char **comment);
-int import_ssh1(const Filename *filename, int type,
- struct RSAKey *key, char *passphrase, const char **errmsg_p);
-struct ssh2_userkey *import_ssh2(const Filename *filename, int type,
- char *passphrase, const char **errmsg_p);
-int export_ssh1(const Filename *filename, int type,
- struct RSAKey *key, char *passphrase);
-int export_ssh2(const Filename *filename, int type,
- struct ssh2_userkey *key, char *passphrase);
-
-void des3_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len);
-void des3_encrypt_pubkey(unsigned char *key, unsigned char *blk, int len);
-void des3_decrypt_pubkey_ossh(unsigned char *key, unsigned char *iv,
- unsigned char *blk, int len);
-void des3_encrypt_pubkey_ossh(unsigned char *key, unsigned char *iv,
- unsigned char *blk, int len);
-void aes256_encrypt_pubkey(unsigned char *key, unsigned char *blk,
- int len);
-void aes256_decrypt_pubkey(unsigned char *key, unsigned char *blk,
- int len);
-
-void des_encrypt_xdmauth(unsigned char *key, unsigned char *blk, int len);
-void des_decrypt_xdmauth(unsigned char *key, unsigned char *blk, int len);
-
-/*
- * For progress updates in the key generation utility.
- */
-#define PROGFN_INITIALISE 1
-#define PROGFN_LIN_PHASE 2
-#define PROGFN_EXP_PHASE 3
-#define PROGFN_PHASE_EXTENT 4
-#define PROGFN_READY 5
-#define PROGFN_PROGRESS 6
-typedef void (*progfn_t) (void *param, int action, int phase, int progress);
-
-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);
-Bignum primegen(int bits, int modulus, int residue, Bignum factor,
- int phase, progfn_t pfn, void *pfnparam);
-
-
-/*
- * zlib compression.
- */
-void *zlib_compress_init(void);
-void zlib_compress_cleanup(void *);
-void *zlib_decompress_init(void);
-void zlib_decompress_cleanup(void *);
-int zlib_compress_block(void *, unsigned char *block, int len,
- unsigned char **outblock, int *outlen);
-int zlib_decompress_block(void *, unsigned char *block, int len,
- unsigned char **outblock, int *outlen);
-
-/*
- * SSH-1 agent messages.
- */
-#define SSH1_AGENTC_REQUEST_RSA_IDENTITIES 1
-#define SSH1_AGENT_RSA_IDENTITIES_ANSWER 2
-#define SSH1_AGENTC_RSA_CHALLENGE 3
-#define SSH1_AGENT_RSA_RESPONSE 4
-#define SSH1_AGENTC_ADD_RSA_IDENTITY 7
-#define SSH1_AGENTC_REMOVE_RSA_IDENTITY 8
-#define SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES 9 /* openssh private? */
-
-/*
- * Messages common to SSH-1 and OpenSSH's SSH-2.
- */
-#define SSH_AGENT_FAILURE 5
-#define SSH_AGENT_SUCCESS 6
-
-/*
- * OpenSSH's SSH-2 agent messages.
- */
-#define SSH2_AGENTC_REQUEST_IDENTITIES 11
-#define SSH2_AGENT_IDENTITIES_ANSWER 12
-#define SSH2_AGENTC_SIGN_REQUEST 13
-#define SSH2_AGENT_SIGN_RESPONSE 14
-#define SSH2_AGENTC_ADD_IDENTITY 17
-#define SSH2_AGENTC_REMOVE_IDENTITY 18
-#define SSH2_AGENTC_REMOVE_ALL_IDENTITIES 19
-
-/*
- * Need this to warn about support for the original SSH-2 keyfile
- * format.
- */
-void old_keyfile_warning(void);
+#include <stdio.h>
+#include <string.h>
+
+#include "puttymem.h"
+#include "tree234.h"
+#include "network.h"
+#include "int64.h"
+#include "misc.h"
+
+struct ssh_channel;
+typedef struct ssh_tag *Ssh;
+
+extern int sshfwd_write(struct ssh_channel *c, char *, int);
+extern void sshfwd_write_eof(struct ssh_channel *c);
+extern void sshfwd_unclean_close(struct ssh_channel *c, const char *err);
+extern void sshfwd_unthrottle(struct ssh_channel *c, int bufsize);
+Conf *sshfwd_get_conf(struct ssh_channel *c);
+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);
+
+extern Socket ssh_connection_sharing_init(const char *host, int port,
+ Conf *conf, Ssh ssh, void **state);
+void share_got_pkt_from_server(void *ctx, int type,
+ unsigned char *pkt, int pktlen);
+void share_activate(void *state, const char *server_verstring);
+void sharestate_free(void *state);
+int share_ndownstreams(void *state);
+
+void ssh_connshare_log(Ssh ssh, int event, const char *logtext,
+ const char *ds_err, const char *us_err);
+unsigned ssh_alloc_sharing_channel(Ssh ssh, void *sharing_ctx);
+void ssh_delete_sharing_channel(Ssh ssh, unsigned localid);
+int ssh_alloc_sharing_rportfwd(Ssh ssh, const char *shost, int sport,
+ void *share_ctx);
+void ssh_sharing_queue_global_request(Ssh ssh, void *share_ctx);
+struct X11FakeAuth *ssh_sharing_add_x11_display(Ssh ssh, int authtype,
+ void *share_cs,
+ void *share_chan);
+void ssh_sharing_remove_x11_display(Ssh ssh, struct X11FakeAuth *auth);
+void ssh_send_packet_from_downstream(Ssh ssh, unsigned id, int type,
+ const void *pkt, int pktlen,
+ const char *additional_log_text);
+void ssh_sharing_downstream_connected(Ssh ssh, unsigned id);
+void ssh_sharing_downstream_disconnected(Ssh ssh, unsigned id);
+void ssh_sharing_logf(Ssh ssh, unsigned id, const char *logfmt, ...);
+int ssh_agent_forwarding_permitted(Ssh ssh);
+void share_setup_x11_channel(void *csv, void *chanv,
+ unsigned upstream_id, unsigned server_id,
+ unsigned server_currwin, unsigned server_maxpkt,
+ unsigned client_adjusted_window,
+ const char *peer_addr, int peer_port, int endian,
+ int protomajor, int protominor,
+ const void *initial_data, int initial_len);
+
+/*
+ * Useful thing.
+ */
+#ifndef lenof
+#define lenof(x) ( (sizeof((x))) / (sizeof(*(x))))
+#endif
+
+#define SSH_CIPHER_IDEA 1
+#define SSH_CIPHER_DES 2
+#define SSH_CIPHER_3DES 3
+#define SSH_CIPHER_BLOWFISH 6
+
+#ifdef MSCRYPTOAPI
+#define APIEXTRA 8
+#else
+#define APIEXTRA 0
+#endif
+
+#ifndef BIGNUM_INTERNAL
+typedef void *Bignum;
+#endif
+
+struct RSAKey {
+ int bits;
+ int bytes;
+#ifdef MSCRYPTOAPI
+ unsigned long exponent;
+ unsigned char *modulus;
+#else
+ Bignum modulus;
+ Bignum exponent;
+ Bignum private_exponent;
+ Bignum p;
+ Bignum q;
+ Bignum iqmp;
+#endif
+ char *comment;
+};
+
+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);
+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);
+void rsasanitise(struct RSAKey *key);
+int rsastr_len(struct RSAKey *key);
+void rsastr_fmt(char *str, struct RSAKey *key);
+void rsa_fingerprint(char *str, int len, struct RSAKey *key);
+int rsa_verify(struct RSAKey *key);
+unsigned char *rsa_public_blob(struct RSAKey *key, int *len);
+int rsa_public_blob_len(void *data, int maxlen);
+void freersakey(struct RSAKey *key);
+
+#ifndef PUTTY_UINT32_DEFINED
+/* This makes assumptions about the int type. */
+typedef unsigned int uint32;
+#define PUTTY_UINT32_DEFINED
+#endif
+typedef uint32 word32;
+
+unsigned long crc32_compute(const void *s, size_t len);
+unsigned long crc32_update(unsigned long crc_input, const void *s, size_t len);
+
+/* SSH CRC compensation attack detector */
+void *crcda_make_context(void);
+void crcda_free_context(void *handle);
+int detect_attack(void *handle, unsigned char *buf, uint32 len,
+ unsigned char *IV);
+
+/*
+ * SSH2 RSA key exchange functions
+ */
+struct ssh_hash;
+void *ssh_rsakex_newkey(char *data, int len);
+void ssh_rsakex_freekey(void *key);
+int ssh_rsakex_klen(void *key);
+void ssh_rsakex_encrypt(const struct ssh_hash *h, unsigned char *in, int inlen,
+ unsigned char *out, int outlen,
+ void *key);
+
+typedef struct {
+ uint32 h[4];
+} MD5_Core_State;
+
+struct MD5Context {
+#ifdef MSCRYPTOAPI
+ unsigned long hHash;
+#else
+ MD5_Core_State core;
+ unsigned char block[64];
+ int blkused;
+ uint32 lenhi, lenlo;
+#endif
+};
+
+void MD5Init(struct MD5Context *context);
+void MD5Update(struct MD5Context *context, unsigned char const *buf,
+ unsigned len);
+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_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,
+ unsigned char *hmac);
+
+typedef struct {
+ uint32 h[5];
+ unsigned char block[64];
+ int blkused;
+ uint32 lenhi, lenlo;
+} SHA_State;
+void SHA_Init(SHA_State * s);
+void SHA_Bytes(SHA_State * s, const void *p, int len);
+void SHA_Final(SHA_State * s, unsigned char *output);
+void SHA_Simple(const void *p, int len, unsigned char *output);
+
+void hmac_sha1_simple(void *key, int keylen, void *data, int datalen,
+ unsigned char *output);
+typedef struct {
+ uint32 h[8];
+ unsigned char block[64];
+ int blkused;
+ uint32 lenhi, lenlo;
+} SHA256_State;
+void SHA256_Init(SHA256_State * s);
+void SHA256_Bytes(SHA256_State * s, const void *p, int len);
+void SHA256_Final(SHA256_State * s, unsigned char *output);
+void SHA256_Simple(const void *p, int len, unsigned char *output);
+
+typedef struct {
+ uint64 h[8];
+ unsigned char block[128];
+ int blkused;
+ uint32 len[4];
+} 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);
+
+struct ssh_cipher {
+ void *(*make_context)(void);
+ void (*free_context)(void *);
+ void (*sesskey) (void *, unsigned char *key); /* for SSH-1 */
+ void (*encrypt) (void *, unsigned char *blk, int len);
+ void (*decrypt) (void *, unsigned char *blk, int len);
+ int blksize;
+ char *text_name;
+};
+
+struct ssh2_cipher {
+ void *(*make_context)(void);
+ void (*free_context)(void *);
+ void (*setiv) (void *, unsigned char *key); /* for SSH-2 */
+ 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;
+ int blksize;
+ int keylen;
+ unsigned int flags;
+#define SSH_CIPHER_IS_CBC 1
+ char *text_name;
+};
+
+struct ssh2_ciphers {
+ int nciphers;
+ const struct ssh2_cipher *const *list;
+};
+
+struct ssh_mac {
+ void *(*make_context)(void);
+ void (*free_context)(void *);
+ void (*setkey) (void *, unsigned char *key);
+ /* whole-packet operations */
+ void (*generate) (void *, unsigned char *blk, int len, unsigned long seq);
+ int (*verify) (void *, unsigned char *blk, int len, unsigned long seq);
+ /* partial-packet operations */
+ void (*start) (void *);
+ 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;
+};
+
+struct ssh_hash {
+ void *(*init)(void); /* also allocates context */
+ void (*bytes)(void *, void *, int);
+ void (*final)(void *, unsigned char *); /* also frees context */
+ int hlen; /* output length in bytes */
+ 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 struct ssh_hash *hash;
+};
+
+struct ssh_kexes {
+ int nkexes;
+ const struct ssh_kex *const *list;
+};
+
+struct ssh_signkey {
+ void *(*newkey) (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);
+ 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,
+ int *siglen);
+ char *name;
+ char *keytype; /* for host key cache */
+};
+
+struct ssh_compress {
+ char *name;
+ /* For zlib@openssh.com: if non-NULL, this name will be considered once
+ * userauth has completed successfully. */
+ char *delayed_name;
+ void *(*compress_init) (void);
+ void (*compress_cleanup) (void *);
+ int (*compress) (void *, unsigned char *block, int len,
+ unsigned char **outblock, int *outlen);
+ void *(*decompress_init) (void);
+ void (*decompress_cleanup) (void *);
+ int (*decompress) (void *, unsigned char *block, int len,
+ unsigned char **outblock, int *outlen);
+ int (*disable_compression) (void *);
+ char *text_name;
+};
+
+struct ssh2_userkey {
+ const struct ssh_signkey *alg; /* the key algorithm */
+ void *data; /* the key data */
+ char *comment; /* the key comment */
+};
+
+/* The maximum length of any hash algorithm used in kex. (bytes) */
+#define SSH2_KEX_MAX_HASH_LEN (32) /* SHA-256 */
+
+extern const struct ssh_cipher ssh_3des;
+extern const struct ssh_cipher ssh_des;
+extern const struct ssh_cipher ssh_blowfish_ssh1;
+extern const struct ssh2_ciphers ssh2_3des;
+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 ssh_hash ssh_sha1;
+extern const struct ssh_hash ssh_sha256;
+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_signkey ssh_dss;
+extern const struct ssh_signkey ssh_rsa;
+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;
+extern const struct ssh_mac ssh_hmac_sha1_96;
+extern const struct ssh_mac ssh_hmac_sha1_96_buggy;
+extern const struct ssh_mac ssh_hmac_sha256;
+
+void *aes_make_context(void);
+void aes_free_context(void *handle);
+void aes128_key(void *handle, unsigned char *key);
+void aes192_key(void *handle, unsigned char *key);
+void aes256_key(void *handle, unsigned char *key);
+void aes_iv(void *handle, unsigned char *iv);
+void aes_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len);
+void aes_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len);
+
+/*
+ * PuTTY version number formatted as an SSH version string.
+ */
+extern char sshver[];
+
+/*
+ * Gross hack: pscp will try to start SFTP but fall back to scp1 if
+ * that fails. This variable is the means by which scp.c can reach
+ * into the SSH code and find out which one it got.
+ */
+extern int ssh_fallback_cmd(void *handle);
+
+#ifndef MSCRYPTOAPI
+void SHATransform(word32 * digest, word32 * data);
+#endif
+
+int random_byte(void);
+void random_add_noise(void *noise, int length);
+void random_add_heavynoise(void *noise, int length);
+
+void logevent(void *, const char *);
+
+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);
+
+/* Exports from portfwd.c */
+extern char *pfd_connect(struct PortForwarding **pf, char *hostname, int port,
+ void *c, Conf *conf, int addressfamily);
+extern void pfd_close(struct PortForwarding *);
+extern int pfd_send(struct PortForwarding *, char *data, int len);
+extern void pfd_send_eof(struct PortForwarding *);
+extern void pfd_confirm(struct PortForwarding *);
+extern void pfd_unthrottle(struct PortForwarding *);
+extern void pfd_override_throttle(struct PortForwarding *, int enable);
+struct PortListener;
+/* desthost == NULL indicates dynamic (SOCKS) port forwarding */
+extern char *pfl_listen(char *desthost, int destport, char *srcaddr,
+ int port, void *backhandle, Conf *conf,
+ struct PortListener **pl, int address_family);
+extern void pfl_terminate(struct PortListener *);
+
+/* Exports from x11fwd.c */
+enum {
+ X11_TRANS_IPV4 = 0, X11_TRANS_IPV6 = 6, X11_TRANS_UNIX = 256
+};
+struct X11Display {
+ /* Broken-down components of the display name itself */
+ int unixdomain;
+ char *hostname;
+ int displaynum;
+ int screennum;
+ /* OSX sometimes replaces all the above with a full Unix-socket pathname */
+ char *unixsocketpath;
+
+ /* PuTTY networking SockAddr to connect to the display, and associated
+ * gubbins */
+ SockAddr addr;
+ int port;
+ char *realhost;
+
+ /* Our local auth details for talking to the real X display. */
+ int localauthproto;
+ unsigned char *localauthdata;
+ int localauthdatalen;
+};
+struct X11FakeAuth {
+ /* Auth details we invented for a virtual display on the SSH server. */
+ int proto;
+ unsigned char *data;
+ int datalen;
+ char *protoname;
+ char *datastring;
+
+ /* The encrypted form of the first block, in XDM-AUTHORIZATION-1.
+ * Used as part of the key when these structures are organised
+ * into a tree. See x11_invent_fake_auth for explanation. */
+ unsigned char *xa1_firstblock;
+
+ /*
+ * Used inside x11fwd.c to remember recently seen
+ * XDM-AUTHORIZATION-1 strings, to avoid replay attacks.
+ */
+ tree234 *xdmseen;
+
+ /*
+ * What to do with an X connection matching this auth data.
+ */
+ struct X11Display *disp;
+ void *share_cs, *share_chan;
+};
+void *x11_make_greeting(int endian, int protomajor, int protominor,
+ int auth_proto, const void *auth_data, int auth_len,
+ const char *peer_ip, int peer_port,
+ int *outlen);
+int x11_authcmp(void *av, void *bv); /* for putting X11FakeAuth in a tree234 */
+/*
+ * x11_setup_display() parses the display variable and fills in an
+ * X11Display structure. Some remote auth details are invented;
+ * the supplied authtype parameter configures the preferred
+ * 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 *);
+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);
+struct X11Connection; /* opaque outside x11fwd.c */
+struct X11Connection *x11_init(tree234 *authtree, void *, const char *, int);
+extern void x11_close(struct X11Connection *);
+extern int x11_send(struct X11Connection *, char *, int);
+extern void x11_send_eof(struct X11Connection *s);
+extern void x11_unthrottle(struct X11Connection *s);
+extern void x11_override_throttle(struct X11Connection *s, int enable);
+char *x11_display(const char *display);
+/* Platform-dependent X11 functions */
+extern void platform_get_x11_auth(struct X11Display *display, Conf *);
+ /* examine a mostly-filled-in X11Display and fill in localauth* */
+extern const int platform_uses_x11_unix_by_default;
+ /* choose default X transport in the absence of a specified one */
+SockAddr platform_get_x11_unix_address(const char *path, int displaynum);
+ /* make up a SockAddr naming the address for displaynum */
+char *platform_get_x_display(void);
+ /* allocated local X display string, if any */
+/* Callbacks in x11.c usable _by_ platform X11 functions */
+/*
+ * This function does the job of platform_get_x11_auth, provided
+ * it is told where to find a normally formatted .Xauthority file:
+ * it opens that file, parses it to find an auth record which
+ * matches the display details in "display", and fills in the
+ * localauth fields.
+ *
+ * It is expected that most implementations of
+ * platform_get_x11_auth() will work by finding their system's
+ * .Xauthority file, adjusting the display details if necessary
+ * for local oddities like Unix-domain socket transport, and
+ * calling this function to do the rest of the work.
+ */
+void x11_get_auth_from_authfile(struct X11Display *display,
+ const char *authfilename);
+int x11_identify_auth_proto(const char *proto);
+void *x11_dehexify(const char *hex, int *outlen);
+
+Bignum copybn(Bignum b);
+Bignum bn_power_2(int n);
+void bn_restore_invariant(Bignum b);
+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);
+void decbn(Bignum n);
+extern Bignum Zero, One;
+Bignum bignum_from_bytes(const unsigned char *data, int nbytes);
+int ssh1_read_bignum(const unsigned char *data, int len, Bignum * result);
+int bignum_bitcount(Bignum bn);
+int ssh1_bignum_length(Bignum bn);
+int ssh2_bignum_length(Bignum bn);
+int bignum_byte(Bignum bn, int i);
+int bignum_bit(Bignum bn, int i);
+void bignum_set_bit(Bignum bn, int i, int value);
+int ssh1_write_bignum(void *data, Bignum bn);
+Bignum biggcd(Bignum a, Bignum b);
+unsigned short bignum_mod_short(Bignum number, unsigned short modulus);
+Bignum bignum_add_long(Bignum number, unsigned long addend);
+Bignum bigadd(Bignum a, Bignum b);
+Bignum bigsub(Bignum a, Bignum b);
+Bignum bigmul(Bignum a, Bignum b);
+Bignum bigmuladd(Bignum a, Bignum b, Bignum addend);
+Bignum bigdiv(Bignum a, Bignum b);
+Bignum bigmod(Bignum a, Bignum b);
+Bignum modinv(Bignum number, Bignum modulus);
+Bignum bignum_bitmask(Bignum number);
+Bignum bignum_rshift(Bignum number, int shift);
+int bignum_cmp(Bignum a, Bignum b);
+char *bignum_decimal(Bignum x);
+
+#ifdef DEBUG
+void diagbn(char *prefix, Bignum md);
+#endif
+
+void *dh_setup_group(const struct ssh_kex *kex);
+void *dh_setup_gex(Bignum pval, Bignum gval);
+void dh_cleanup(void *);
+Bignum dh_create_e(void *, int nbits);
+Bignum dh_find_K(void *, Bignum f);
+
+int loadrsakey(const Filename *filename, struct RSAKey *key,
+ 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_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);
+
+/* ssh2_load_userkey can return this as an error */
+extern struct ssh2_userkey ssh2_wrong_passphrase;
+#define SSH2_WRONG_PASSPHRASE (&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);
+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);
+
+enum {
+ SSH_KEYTYPE_UNOPENABLE,
+ SSH_KEYTYPE_UNKNOWN,
+ SSH_KEYTYPE_SSH1, SSH_KEYTYPE_SSH2,
+ SSH_KEYTYPE_OPENSSH, SSH_KEYTYPE_SSHCOM
+};
+int key_type(const Filename *filename);
+char *key_type_to_str(int type);
+
+int import_possible(int type);
+int import_target_type(int type);
+int import_encrypted(const Filename *filename, int type, char **comment);
+int import_ssh1(const Filename *filename, int type,
+ struct RSAKey *key, char *passphrase, const char **errmsg_p);
+struct ssh2_userkey *import_ssh2(const Filename *filename, int type,
+ char *passphrase, const char **errmsg_p);
+int export_ssh1(const Filename *filename, int type,
+ struct RSAKey *key, char *passphrase);
+int export_ssh2(const Filename *filename, int type,
+ struct ssh2_userkey *key, char *passphrase);
+
+void des3_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len);
+void des3_encrypt_pubkey(unsigned char *key, unsigned char *blk, int len);
+void des3_decrypt_pubkey_ossh(unsigned char *key, unsigned char *iv,
+ unsigned char *blk, int len);
+void des3_encrypt_pubkey_ossh(unsigned char *key, unsigned char *iv,
+ unsigned char *blk, int len);
+void aes256_encrypt_pubkey(unsigned char *key, unsigned char *blk,
+ int len);
+void aes256_decrypt_pubkey(unsigned char *key, unsigned char *blk,
+ int len);
+
+void des_encrypt_xdmauth(const unsigned char *key,
+ unsigned char *blk, int len);
+void des_decrypt_xdmauth(const unsigned char *key,
+ unsigned char *blk, int len);
+
+/*
+ * For progress updates in the key generation utility.
+ */
+#define PROGFN_INITIALISE 1
+#define PROGFN_LIN_PHASE 2
+#define PROGFN_EXP_PHASE 3
+#define PROGFN_PHASE_EXTENT 4
+#define PROGFN_READY 5
+#define PROGFN_PROGRESS 6
+typedef void (*progfn_t) (void *param, int action, int phase, int progress);
+
+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);
+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);
+
+
+/*
+ * zlib compression.
+ */
+void *zlib_compress_init(void);
+void zlib_compress_cleanup(void *);
+void *zlib_decompress_init(void);
+void zlib_decompress_cleanup(void *);
+int zlib_compress_block(void *, unsigned char *block, int len,
+ unsigned char **outblock, int *outlen);
+int zlib_decompress_block(void *, unsigned char *block, int len,
+ unsigned char **outblock, int *outlen);
+
+/*
+ * Connection-sharing API provided by platforms. This function must
+ * either:
+ * - return SHARE_NONE and do nothing
+ * - return SHARE_DOWNSTREAM and set *sock to a Socket connected to
+ * downplug
+ * - return SHARE_UPSTREAM and set *sock to a Socket connected to
+ * upplug.
+ */
+enum { SHARE_NONE, SHARE_DOWNSTREAM, SHARE_UPSTREAM };
+int platform_ssh_share(const char *name, Conf *conf,
+ Plug downplug, Plug upplug, Socket *sock,
+ char **logtext, char **ds_err, char **us_err,
+ int can_upstream, int can_downstream);
+void platform_ssh_share_cleanup(const char *name);
+
+/*
+ * SSH-1 message type codes.
+ */
+#define SSH1_MSG_DISCONNECT 1 /* 0x1 */
+#define SSH1_SMSG_PUBLIC_KEY 2 /* 0x2 */
+#define SSH1_CMSG_SESSION_KEY 3 /* 0x3 */
+#define SSH1_CMSG_USER 4 /* 0x4 */
+#define SSH1_CMSG_AUTH_RSA 6 /* 0x6 */
+#define SSH1_SMSG_AUTH_RSA_CHALLENGE 7 /* 0x7 */
+#define SSH1_CMSG_AUTH_RSA_RESPONSE 8 /* 0x8 */
+#define SSH1_CMSG_AUTH_PASSWORD 9 /* 0x9 */
+#define SSH1_CMSG_REQUEST_PTY 10 /* 0xa */
+#define SSH1_CMSG_WINDOW_SIZE 11 /* 0xb */
+#define SSH1_CMSG_EXEC_SHELL 12 /* 0xc */
+#define SSH1_CMSG_EXEC_CMD 13 /* 0xd */
+#define SSH1_SMSG_SUCCESS 14 /* 0xe */
+#define SSH1_SMSG_FAILURE 15 /* 0xf */
+#define SSH1_CMSG_STDIN_DATA 16 /* 0x10 */
+#define SSH1_SMSG_STDOUT_DATA 17 /* 0x11 */
+#define SSH1_SMSG_STDERR_DATA 18 /* 0x12 */
+#define SSH1_CMSG_EOF 19 /* 0x13 */
+#define SSH1_SMSG_EXIT_STATUS 20 /* 0x14 */
+#define SSH1_MSG_CHANNEL_OPEN_CONFIRMATION 21 /* 0x15 */
+#define SSH1_MSG_CHANNEL_OPEN_FAILURE 22 /* 0x16 */
+#define SSH1_MSG_CHANNEL_DATA 23 /* 0x17 */
+#define SSH1_MSG_CHANNEL_CLOSE 24 /* 0x18 */
+#define SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION 25 /* 0x19 */
+#define SSH1_SMSG_X11_OPEN 27 /* 0x1b */
+#define SSH1_CMSG_PORT_FORWARD_REQUEST 28 /* 0x1c */
+#define SSH1_MSG_PORT_OPEN 29 /* 0x1d */
+#define SSH1_CMSG_AGENT_REQUEST_FORWARDING 30 /* 0x1e */
+#define SSH1_SMSG_AGENT_OPEN 31 /* 0x1f */
+#define SSH1_MSG_IGNORE 32 /* 0x20 */
+#define SSH1_CMSG_EXIT_CONFIRMATION 33 /* 0x21 */
+#define SSH1_CMSG_X11_REQUEST_FORWARDING 34 /* 0x22 */
+#define SSH1_CMSG_AUTH_RHOSTS_RSA 35 /* 0x23 */
+#define SSH1_MSG_DEBUG 36 /* 0x24 */
+#define SSH1_CMSG_REQUEST_COMPRESSION 37 /* 0x25 */
+#define SSH1_CMSG_AUTH_TIS 39 /* 0x27 */
+#define SSH1_SMSG_AUTH_TIS_CHALLENGE 40 /* 0x28 */
+#define SSH1_CMSG_AUTH_TIS_RESPONSE 41 /* 0x29 */
+#define SSH1_CMSG_AUTH_CCARD 70 /* 0x46 */
+#define SSH1_SMSG_AUTH_CCARD_CHALLENGE 71 /* 0x47 */
+#define SSH1_CMSG_AUTH_CCARD_RESPONSE 72 /* 0x48 */
+
+#define SSH1_AUTH_RHOSTS 1 /* 0x1 */
+#define SSH1_AUTH_RSA 2 /* 0x2 */
+#define SSH1_AUTH_PASSWORD 3 /* 0x3 */
+#define SSH1_AUTH_RHOSTS_RSA 4 /* 0x4 */
+#define SSH1_AUTH_TIS 5 /* 0x5 */
+#define SSH1_AUTH_CCARD 16 /* 0x10 */
+
+#define SSH1_PROTOFLAG_SCREEN_NUMBER 1 /* 0x1 */
+/* Mask for protoflags we will echo back to server if seen */
+#define SSH1_PROTOFLAGS_SUPPORTED 0 /* 0x1 */
+
+/*
+ * SSH-2 message type codes.
+ */
+#define SSH2_MSG_DISCONNECT 1 /* 0x1 */
+#define SSH2_MSG_IGNORE 2 /* 0x2 */
+#define SSH2_MSG_UNIMPLEMENTED 3 /* 0x3 */
+#define SSH2_MSG_DEBUG 4 /* 0x4 */
+#define SSH2_MSG_SERVICE_REQUEST 5 /* 0x5 */
+#define SSH2_MSG_SERVICE_ACCEPT 6 /* 0x6 */
+#define SSH2_MSG_KEXINIT 20 /* 0x14 */
+#define SSH2_MSG_NEWKEYS 21 /* 0x15 */
+#define SSH2_MSG_KEXDH_INIT 30 /* 0x1e */
+#define SSH2_MSG_KEXDH_REPLY 31 /* 0x1f */
+#define SSH2_MSG_KEX_DH_GEX_REQUEST 30 /* 0x1e */
+#define SSH2_MSG_KEX_DH_GEX_GROUP 31 /* 0x1f */
+#define SSH2_MSG_KEX_DH_GEX_INIT 32 /* 0x20 */
+#define SSH2_MSG_KEX_DH_GEX_REPLY 33 /* 0x21 */
+#define SSH2_MSG_KEXRSA_PUBKEY 30 /* 0x1e */
+#define SSH2_MSG_KEXRSA_SECRET 31 /* 0x1f */
+#define SSH2_MSG_KEXRSA_DONE 32 /* 0x20 */
+#define SSH2_MSG_USERAUTH_REQUEST 50 /* 0x32 */
+#define SSH2_MSG_USERAUTH_FAILURE 51 /* 0x33 */
+#define SSH2_MSG_USERAUTH_SUCCESS 52 /* 0x34 */
+#define SSH2_MSG_USERAUTH_BANNER 53 /* 0x35 */
+#define SSH2_MSG_USERAUTH_PK_OK 60 /* 0x3c */
+#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 /* 0x3c */
+#define SSH2_MSG_USERAUTH_INFO_REQUEST 60 /* 0x3c */
+#define SSH2_MSG_USERAUTH_INFO_RESPONSE 61 /* 0x3d */
+#define SSH2_MSG_GLOBAL_REQUEST 80 /* 0x50 */
+#define SSH2_MSG_REQUEST_SUCCESS 81 /* 0x51 */
+#define SSH2_MSG_REQUEST_FAILURE 82 /* 0x52 */
+#define SSH2_MSG_CHANNEL_OPEN 90 /* 0x5a */
+#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 /* 0x5b */
+#define SSH2_MSG_CHANNEL_OPEN_FAILURE 92 /* 0x5c */
+#define SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 /* 0x5d */
+#define SSH2_MSG_CHANNEL_DATA 94 /* 0x5e */
+#define SSH2_MSG_CHANNEL_EXTENDED_DATA 95 /* 0x5f */
+#define SSH2_MSG_CHANNEL_EOF 96 /* 0x60 */
+#define SSH2_MSG_CHANNEL_CLOSE 97 /* 0x61 */
+#define SSH2_MSG_CHANNEL_REQUEST 98 /* 0x62 */
+#define SSH2_MSG_CHANNEL_SUCCESS 99 /* 0x63 */
+#define SSH2_MSG_CHANNEL_FAILURE 100 /* 0x64 */
+#define SSH2_MSG_USERAUTH_GSSAPI_RESPONSE 60
+#define SSH2_MSG_USERAUTH_GSSAPI_TOKEN 61
+#define SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE 63
+#define SSH2_MSG_USERAUTH_GSSAPI_ERROR 64
+#define SSH2_MSG_USERAUTH_GSSAPI_ERRTOK 65
+#define SSH2_MSG_USERAUTH_GSSAPI_MIC 66
+
+/*
+ * SSH-1 agent messages.
+ */
+#define SSH1_AGENTC_REQUEST_RSA_IDENTITIES 1
+#define SSH1_AGENT_RSA_IDENTITIES_ANSWER 2
+#define SSH1_AGENTC_RSA_CHALLENGE 3
+#define SSH1_AGENT_RSA_RESPONSE 4
+#define SSH1_AGENTC_ADD_RSA_IDENTITY 7
+#define SSH1_AGENTC_REMOVE_RSA_IDENTITY 8
+#define SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES 9 /* openssh private? */
+
+/*
+ * Messages common to SSH-1 and OpenSSH's SSH-2.
+ */
+#define SSH_AGENT_FAILURE 5
+#define SSH_AGENT_SUCCESS 6
+
+/*
+ * OpenSSH's SSH-2 agent messages.
+ */
+#define SSH2_AGENTC_REQUEST_IDENTITIES 11
+#define SSH2_AGENT_IDENTITIES_ANSWER 12
+#define SSH2_AGENTC_SIGN_REQUEST 13
+#define SSH2_AGENT_SIGN_RESPONSE 14
+#define SSH2_AGENTC_ADD_IDENTITY 17
+#define SSH2_AGENTC_REMOVE_IDENTITY 18
+#define SSH2_AGENTC_REMOVE_ALL_IDENTITIES 19
+
+/*
+ * Assorted other SSH-related enumerations.
+ */
+#define SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 /* 0x1 */
+#define SSH2_DISCONNECT_PROTOCOL_ERROR 2 /* 0x2 */
+#define SSH2_DISCONNECT_KEY_EXCHANGE_FAILED 3 /* 0x3 */
+#define SSH2_DISCONNECT_HOST_AUTHENTICATION_FAILED 4 /* 0x4 */
+#define SSH2_DISCONNECT_MAC_ERROR 5 /* 0x5 */
+#define SSH2_DISCONNECT_COMPRESSION_ERROR 6 /* 0x6 */
+#define SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE 7 /* 0x7 */
+#define SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8 /* 0x8 */
+#define SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9 /* 0x9 */
+#define SSH2_DISCONNECT_CONNECTION_LOST 10 /* 0xa */
+#define SSH2_DISCONNECT_BY_APPLICATION 11 /* 0xb */
+#define SSH2_DISCONNECT_TOO_MANY_CONNECTIONS 12 /* 0xc */
+#define SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER 13 /* 0xd */
+#define SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 /* 0xe */
+#define SSH2_DISCONNECT_ILLEGAL_USER_NAME 15 /* 0xf */
+
+#define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED 1 /* 0x1 */
+#define SSH2_OPEN_CONNECT_FAILED 2 /* 0x2 */
+#define SSH2_OPEN_UNKNOWN_CHANNEL_TYPE 3 /* 0x3 */
+#define SSH2_OPEN_RESOURCE_SHORTAGE 4 /* 0x4 */
+
+#define SSH2_EXTENDED_DATA_STDERR 1 /* 0x1 */
+
+/*
+ * Need this to warn about support for the original SSH-2 keyfile
+ * format.
+ */
+void old_keyfile_warning(void);
diff --git a/tools/plink/sshaes.c b/tools/plink/sshaes.c
index 7684cd949..97935b7f1 100644
--- a/tools/plink/sshaes.c
+++ b/tools/plink/sshaes.c
@@ -1,1234 +1,1234 @@
-/*
- * aes.c - implementation of AES / Rijndael
- *
- * AES is a flexible algorithm as regards endianness: it has no
- * inherent preference as to which way round you should form words
- * from the input byte stream. It talks endlessly of four-byte
- * _vectors_, but never of 32-bit _words_ - there's no 32-bit
- * addition at all, which would force an endianness by means of
- * which way the carries went. So it would be possible to write a
- * working AES that read words big-endian, and another working one
- * that read them little-endian, just by computing a different set
- * of tables - with no speed drop.
- *
- * It's therefore tempting to do just that, and remove the overhead
- * of GET_32BIT_MSB_FIRST() et al, allowing every system to use its
- * own endianness-native code; but I decided not to, partly for
- * ease of testing, and mostly because I like the flexibility that
- * allows you to encrypt a non-word-aligned block of memory (which
- * many systems would stop being able to do if I went the
- * endianness-dependent route).
- *
- * This implementation reads and stores words big-endian, but
- * that's a minor implementation detail. By flipping the endianness
- * of everything in the E0..E3, D0..D3 tables, and substituting
- * GET_32BIT_LSB_FIRST for GET_32BIT_MSB_FIRST, I could create an
- * implementation that worked internally little-endian and gave the
- * same answers at the same speed.
- */
-
-#include <assert.h>
-#include <stdlib.h>
-
-#include "ssh.h"
-
-#define MAX_NR 14 /* max no of rounds */
-#define MAX_NK 8 /* max no of words in input key */
-#define MAX_NB 8 /* max no of words in cipher blk */
-
-#define mulby2(x) ( ((x&0x7F) << 1) ^ (x & 0x80 ? 0x1B : 0) )
-
-typedef struct AESContext AESContext;
-
-struct AESContext {
- word32 keysched[(MAX_NR + 1) * MAX_NB];
- word32 invkeysched[(MAX_NR + 1) * MAX_NB];
- void (*encrypt) (AESContext * ctx, word32 * block);
- void (*decrypt) (AESContext * ctx, word32 * block);
- word32 iv[MAX_NB];
- int Nb, Nr;
-};
-
-static const unsigned char Sbox[256] = {
- 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
- 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
- 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
- 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
- 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc,
- 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
- 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a,
- 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
- 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
- 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
- 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
- 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
- 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85,
- 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
- 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
- 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
- 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17,
- 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
- 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88,
- 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
- 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
- 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
- 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9,
- 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
- 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6,
- 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
- 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
- 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
- 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94,
- 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
- 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68,
- 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
-};
-
-static const unsigned char Sboxinv[256] = {
- 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38,
- 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
- 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87,
- 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
- 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d,
- 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
- 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2,
- 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
- 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16,
- 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
- 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda,
- 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
- 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a,
- 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
- 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02,
- 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
- 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea,
- 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
- 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85,
- 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
- 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89,
- 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
- 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20,
- 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
- 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31,
- 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
- 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d,
- 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
- 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0,
- 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
- 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26,
- 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d
-};
-
-static const word32 E0[256] = {
- 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d,
- 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554,
- 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d,
- 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a,
- 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87,
- 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b,
- 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea,
- 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b,
- 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a,
- 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f,
- 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108,
- 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f,
- 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e,
- 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5,
- 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d,
- 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f,
- 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e,
- 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb,
- 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce,
- 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497,
- 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c,
- 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed,
- 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b,
- 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a,
- 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16,
- 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594,
- 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81,
- 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3,
- 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a,
- 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504,
- 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163,
- 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d,
- 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f,
- 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739,
- 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47,
- 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395,
- 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f,
- 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883,
- 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c,
- 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76,
- 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e,
- 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4,
- 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6,
- 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b,
- 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7,
- 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0,
- 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25,
- 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818,
- 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72,
- 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651,
- 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21,
- 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85,
- 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa,
- 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12,
- 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0,
- 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9,
- 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133,
- 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7,
- 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920,
- 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a,
- 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17,
- 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8,
- 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11,
- 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a,
-};
-static const word32 E1[256] = {
- 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b,
- 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5,
- 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b,
- 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676,
- 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d,
- 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0,
- 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf,
- 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0,
- 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626,
- 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc,
- 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1,
- 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515,
- 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3,
- 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a,
- 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2,
- 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575,
- 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a,
- 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0,
- 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3,
- 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484,
- 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded,
- 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b,
- 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939,
- 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf,
- 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb,
- 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585,
- 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f,
- 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8,
- 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f,
- 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5,
- 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121,
- 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2,
- 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec,
- 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717,
- 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d,
- 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373,
- 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc,
- 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888,
- 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414,
- 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb,
- 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a,
- 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c,
- 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262,
- 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979,
- 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d,
- 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9,
- 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea,
- 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808,
- 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e,
- 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6,
- 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f,
- 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a,
- 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666,
- 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e,
- 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9,
- 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e,
- 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111,
- 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494,
- 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9,
- 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf,
- 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d,
- 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868,
- 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f,
- 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616,
-};
-static const word32 E2[256] = {
- 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b,
- 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5,
- 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b,
- 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76,
- 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d,
- 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0,
- 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af,
- 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0,
- 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26,
- 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc,
- 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1,
- 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15,
- 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3,
- 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a,
- 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2,
- 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75,
- 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a,
- 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0,
- 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3,
- 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384,
- 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed,
- 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b,
- 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239,
- 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf,
- 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb,
- 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185,
- 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f,
- 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8,
- 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f,
- 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5,
- 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221,
- 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2,
- 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec,
- 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17,
- 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d,
- 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673,
- 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc,
- 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88,
- 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814,
- 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb,
- 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a,
- 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c,
- 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462,
- 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279,
- 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d,
- 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9,
- 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea,
- 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008,
- 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e,
- 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6,
- 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f,
- 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a,
- 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66,
- 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e,
- 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9,
- 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e,
- 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211,
- 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394,
- 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9,
- 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df,
- 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d,
- 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068,
- 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f,
- 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16,
-};
-static const word32 E3[256] = {
- 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6,
- 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491,
- 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56,
- 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec,
- 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa,
- 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb,
- 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45,
- 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b,
- 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c,
- 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83,
- 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9,
- 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a,
- 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d,
- 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f,
- 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf,
- 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea,
- 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34,
- 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b,
- 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d,
- 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713,
- 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1,
- 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6,
- 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72,
- 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85,
- 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed,
- 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411,
- 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe,
- 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b,
- 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05,
- 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1,
- 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342,
- 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf,
- 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3,
- 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e,
- 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a,
- 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6,
- 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3,
- 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b,
- 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28,
- 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad,
- 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14,
- 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8,
- 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4,
- 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2,
- 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da,
- 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049,
- 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf,
- 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810,
- 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c,
- 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197,
- 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e,
- 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f,
- 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc,
- 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c,
- 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069,
- 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927,
- 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322,
- 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733,
- 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9,
- 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5,
- 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a,
- 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0,
- 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e,
- 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c,
-};
-static const word32 D0[256] = {
- 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96,
- 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393,
- 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25,
- 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f,
- 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1,
- 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6,
- 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da,
- 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844,
- 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd,
- 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4,
- 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45,
- 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94,
- 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7,
- 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a,
- 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5,
- 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c,
- 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1,
- 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a,
- 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75,
- 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051,
- 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46,
- 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff,
- 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77,
- 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb,
- 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000,
- 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e,
- 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927,
- 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a,
- 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e,
- 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16,
- 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d,
- 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8,
- 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd,
- 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34,
- 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163,
- 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120,
- 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d,
- 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0,
- 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422,
- 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef,
- 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36,
- 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4,
- 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662,
- 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5,
- 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3,
- 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b,
- 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8,
- 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6,
- 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6,
- 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0,
- 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815,
- 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f,
- 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df,
- 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f,
- 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e,
- 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713,
- 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89,
- 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c,
- 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf,
- 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86,
- 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f,
- 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541,
- 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190,
- 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742,
-};
-static const word32 D1[256] = {
- 0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e,
- 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303,
- 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c,
- 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3,
- 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0,
- 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9,
- 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259,
- 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8,
- 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971,
- 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a,
- 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f,
- 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b,
- 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8,
- 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab,
- 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708,
- 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682,
- 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2,
- 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe,
- 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb,
- 0x390b83ec, 0xaa4060ef, 0x065e719f, 0x51bd6e10,
- 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd,
- 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015,
- 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e,
- 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee,
- 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000,
- 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72,
- 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39,
- 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e,
- 0xb10c0a67, 0x0f9357e7, 0xd2b4ee96, 0x9e1b9b91,
- 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a,
- 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17,
- 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9,
- 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60,
- 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e,
- 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1,
- 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611,
- 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1,
- 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3,
- 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964,
- 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390,
- 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b,
- 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf,
- 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46,
- 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af,
- 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512,
- 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb,
- 0x09cd2678, 0xf46e5918, 0x01ec9ab7, 0xa8834f9a,
- 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8,
- 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c,
- 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266,
- 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8,
- 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6,
- 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604,
- 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551,
- 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41,
- 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647,
- 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c,
- 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1,
- 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737,
- 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db,
- 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340,
- 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95,
- 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1,
- 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857,
-};
-static const word32 D2[256] = {
- 0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27,
- 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3,
- 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502,
- 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562,
- 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe,
- 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3,
- 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552,
- 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9,
- 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9,
- 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce,
- 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253,
- 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908,
- 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b,
- 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655,
- 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337,
- 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16,
- 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69,
- 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6,
- 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6,
- 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e,
- 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6,
- 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050,
- 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9,
- 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8,
- 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000,
- 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a,
- 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d,
- 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436,
- 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b,
- 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12,
- 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b,
- 0x0d0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e,
- 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f,
- 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb,
- 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4,
- 0xdccad731, 0x85104263, 0x22401397, 0x112084c6,
- 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729,
- 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1,
- 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9,
- 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233,
- 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4,
- 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad,
- 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e,
- 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3,
- 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25,
- 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b,
- 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f,
- 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15,
- 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0,
- 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2,
- 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7,
- 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791,
- 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496,
- 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665,
- 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b,
- 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6,
- 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13,
- 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47,
- 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7,
- 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844,
- 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3,
- 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d,
- 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456,
- 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8,
-};
-static const word32 D3[256] = {
- 0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a,
- 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b,
- 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5,
- 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5,
- 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d,
- 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b,
- 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95,
- 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e,
- 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27,
- 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d,
- 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562,
- 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9,
- 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752,
- 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66,
- 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3,
- 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced,
- 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e,
- 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4,
- 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4,
- 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd,
- 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d,
- 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60,
- 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767,
- 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79,
- 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000,
- 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c,
- 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736,
- 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24,
- 0x0a67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b,
- 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c,
- 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12,
- 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814,
- 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3,
- 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b,
- 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8,
- 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084,
- 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7,
- 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077,
- 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247,
- 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22,
- 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698,
- 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f,
- 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254,
- 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582,
- 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf,
- 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb,
- 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883,
- 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef,
- 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629,
- 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035,
- 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533,
- 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17,
- 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4,
- 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46,
- 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb,
- 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d,
- 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb,
- 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a,
- 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73,
- 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678,
- 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2,
- 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff,
- 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064,
- 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0,
-};
-
-/*
- * Common macros in both the encryption and decryption routines.
- */
-#define ADD_ROUND_KEY_4 (block[0]^=*keysched++, block[1]^=*keysched++, \
- block[2]^=*keysched++, block[3]^=*keysched++)
-#define ADD_ROUND_KEY_6 (block[0]^=*keysched++, block[1]^=*keysched++, \
- block[2]^=*keysched++, block[3]^=*keysched++, \
- block[4]^=*keysched++, block[5]^=*keysched++)
-#define ADD_ROUND_KEY_8 (block[0]^=*keysched++, block[1]^=*keysched++, \
- block[2]^=*keysched++, block[3]^=*keysched++, \
- block[4]^=*keysched++, block[5]^=*keysched++, \
- block[6]^=*keysched++, block[7]^=*keysched++)
-#define MOVEWORD(i) ( block[i] = newstate[i] )
-
-/*
- * Macros for the encryption routine. There are three encryption
- * cores, for Nb=4,6,8.
- */
-#define MAKEWORD(i) ( newstate[i] = (E0[(block[i] >> 24) & 0xFF] ^ \
- E1[(block[(i+C1)%Nb] >> 16) & 0xFF] ^ \
- E2[(block[(i+C2)%Nb] >> 8) & 0xFF] ^ \
- E3[block[(i+C3)%Nb] & 0xFF]) )
-#define LASTWORD(i) ( newstate[i] = (Sbox[(block[i] >> 24) & 0xFF] << 24) | \
- (Sbox[(block[(i+C1)%Nb] >> 16) & 0xFF] << 16) | \
- (Sbox[(block[(i+C2)%Nb] >> 8) & 0xFF] << 8) | \
- (Sbox[(block[(i+C3)%Nb] ) & 0xFF] ) )
-
-/*
- * Core encrypt routines, expecting word32 inputs read big-endian
- * from the byte-oriented input stream.
- */
-static void aes_encrypt_nb_4(AESContext * ctx, word32 * block)
-{
- int i;
- static const int C1 = 1, C2 = 2, C3 = 3, Nb = 4;
- word32 *keysched = ctx->keysched;
- word32 newstate[4];
- for (i = 0; i < ctx->Nr - 1; i++) {
- ADD_ROUND_KEY_4;
- MAKEWORD(0);
- MAKEWORD(1);
- MAKEWORD(2);
- MAKEWORD(3);
- MOVEWORD(0);
- MOVEWORD(1);
- MOVEWORD(2);
- MOVEWORD(3);
- }
- ADD_ROUND_KEY_4;
- LASTWORD(0);
- LASTWORD(1);
- LASTWORD(2);
- LASTWORD(3);
- MOVEWORD(0);
- MOVEWORD(1);
- MOVEWORD(2);
- MOVEWORD(3);
- ADD_ROUND_KEY_4;
-}
-static void aes_encrypt_nb_6(AESContext * ctx, word32 * block)
-{
- int i;
- static const int C1 = 1, C2 = 2, C3 = 3, Nb = 6;
- word32 *keysched = ctx->keysched;
- word32 newstate[6];
- for (i = 0; i < ctx->Nr - 1; i++) {
- ADD_ROUND_KEY_6;
- MAKEWORD(0);
- MAKEWORD(1);
- MAKEWORD(2);
- MAKEWORD(3);
- MAKEWORD(4);
- MAKEWORD(5);
- MOVEWORD(0);
- MOVEWORD(1);
- MOVEWORD(2);
- MOVEWORD(3);
- MOVEWORD(4);
- MOVEWORD(5);
- }
- ADD_ROUND_KEY_6;
- LASTWORD(0);
- LASTWORD(1);
- LASTWORD(2);
- LASTWORD(3);
- LASTWORD(4);
- LASTWORD(5);
- MOVEWORD(0);
- MOVEWORD(1);
- MOVEWORD(2);
- MOVEWORD(3);
- MOVEWORD(4);
- MOVEWORD(5);
- ADD_ROUND_KEY_6;
-}
-static void aes_encrypt_nb_8(AESContext * ctx, word32 * block)
-{
- int i;
- static const int C1 = 1, C2 = 3, C3 = 4, Nb = 8;
- word32 *keysched = ctx->keysched;
- word32 newstate[8];
- for (i = 0; i < ctx->Nr - 1; i++) {
- ADD_ROUND_KEY_8;
- MAKEWORD(0);
- MAKEWORD(1);
- MAKEWORD(2);
- MAKEWORD(3);
- MAKEWORD(4);
- MAKEWORD(5);
- MAKEWORD(6);
- MAKEWORD(7);
- MOVEWORD(0);
- MOVEWORD(1);
- MOVEWORD(2);
- MOVEWORD(3);
- MOVEWORD(4);
- MOVEWORD(5);
- MOVEWORD(6);
- MOVEWORD(7);
- }
- ADD_ROUND_KEY_8;
- LASTWORD(0);
- LASTWORD(1);
- LASTWORD(2);
- LASTWORD(3);
- LASTWORD(4);
- LASTWORD(5);
- LASTWORD(6);
- LASTWORD(7);
- MOVEWORD(0);
- MOVEWORD(1);
- MOVEWORD(2);
- MOVEWORD(3);
- MOVEWORD(4);
- MOVEWORD(5);
- MOVEWORD(6);
- MOVEWORD(7);
- ADD_ROUND_KEY_8;
-}
-
-#undef MAKEWORD
-#undef LASTWORD
-
-/*
- * Macros for the decryption routine. There are three decryption
- * cores, for Nb=4,6,8.
- */
-#define MAKEWORD(i) ( newstate[i] = (D0[(block[i] >> 24) & 0xFF] ^ \
- D1[(block[(i+C1)%Nb] >> 16) & 0xFF] ^ \
- D2[(block[(i+C2)%Nb] >> 8) & 0xFF] ^ \
- D3[block[(i+C3)%Nb] & 0xFF]) )
-#define LASTWORD(i) (newstate[i] = (Sboxinv[(block[i] >> 24) & 0xFF] << 24) | \
- (Sboxinv[(block[(i+C1)%Nb] >> 16) & 0xFF] << 16) | \
- (Sboxinv[(block[(i+C2)%Nb] >> 8) & 0xFF] << 8) | \
- (Sboxinv[(block[(i+C3)%Nb] ) & 0xFF] ) )
-
-/*
- * Core decrypt routines, expecting word32 inputs read big-endian
- * from the byte-oriented input stream.
- */
-static void aes_decrypt_nb_4(AESContext * ctx, word32 * block)
-{
- int i;
- static const int C1 = 4 - 1, C2 = 4 - 2, C3 = 4 - 3, Nb = 4;
- word32 *keysched = ctx->invkeysched;
- word32 newstate[4];
- for (i = 0; i < ctx->Nr - 1; i++) {
- ADD_ROUND_KEY_4;
- MAKEWORD(0);
- MAKEWORD(1);
- MAKEWORD(2);
- MAKEWORD(3);
- MOVEWORD(0);
- MOVEWORD(1);
- MOVEWORD(2);
- MOVEWORD(3);
- }
- ADD_ROUND_KEY_4;
- LASTWORD(0);
- LASTWORD(1);
- LASTWORD(2);
- LASTWORD(3);
- MOVEWORD(0);
- MOVEWORD(1);
- MOVEWORD(2);
- MOVEWORD(3);
- ADD_ROUND_KEY_4;
-}
-static void aes_decrypt_nb_6(AESContext * ctx, word32 * block)
-{
- int i;
- static const int C1 = 6 - 1, C2 = 6 - 2, C3 = 6 - 3, Nb = 6;
- word32 *keysched = ctx->invkeysched;
- word32 newstate[6];
- for (i = 0; i < ctx->Nr - 1; i++) {
- ADD_ROUND_KEY_6;
- MAKEWORD(0);
- MAKEWORD(1);
- MAKEWORD(2);
- MAKEWORD(3);
- MAKEWORD(4);
- MAKEWORD(5);
- MOVEWORD(0);
- MOVEWORD(1);
- MOVEWORD(2);
- MOVEWORD(3);
- MOVEWORD(4);
- MOVEWORD(5);
- }
- ADD_ROUND_KEY_6;
- LASTWORD(0);
- LASTWORD(1);
- LASTWORD(2);
- LASTWORD(3);
- LASTWORD(4);
- LASTWORD(5);
- MOVEWORD(0);
- MOVEWORD(1);
- MOVEWORD(2);
- MOVEWORD(3);
- MOVEWORD(4);
- MOVEWORD(5);
- ADD_ROUND_KEY_6;
-}
-static void aes_decrypt_nb_8(AESContext * ctx, word32 * block)
-{
- int i;
- static const int C1 = 8 - 1, C2 = 8 - 3, C3 = 8 - 4, Nb = 8;
- word32 *keysched = ctx->invkeysched;
- word32 newstate[8];
- for (i = 0; i < ctx->Nr - 1; i++) {
- ADD_ROUND_KEY_8;
- MAKEWORD(0);
- MAKEWORD(1);
- MAKEWORD(2);
- MAKEWORD(3);
- MAKEWORD(4);
- MAKEWORD(5);
- MAKEWORD(6);
- MAKEWORD(7);
- MOVEWORD(0);
- MOVEWORD(1);
- MOVEWORD(2);
- MOVEWORD(3);
- MOVEWORD(4);
- MOVEWORD(5);
- MOVEWORD(6);
- MOVEWORD(7);
- }
- ADD_ROUND_KEY_8;
- LASTWORD(0);
- LASTWORD(1);
- LASTWORD(2);
- LASTWORD(3);
- LASTWORD(4);
- LASTWORD(5);
- LASTWORD(6);
- LASTWORD(7);
- MOVEWORD(0);
- MOVEWORD(1);
- MOVEWORD(2);
- MOVEWORD(3);
- MOVEWORD(4);
- MOVEWORD(5);
- MOVEWORD(6);
- MOVEWORD(7);
- ADD_ROUND_KEY_8;
-}
-
-#undef MAKEWORD
-#undef LASTWORD
-
-
-/*
- * Set up an AESContext. `keylen' and `blocklen' are measured in
- * bytes; each can be either 16 (128-bit), 24 (192-bit), or 32
- * (256-bit).
- */
-static void aes_setup(AESContext * ctx, int blocklen,
- unsigned char *key, int keylen)
-{
- int i, j, Nk, rconst;
-
- assert(blocklen == 16 || blocklen == 24 || blocklen == 32);
- assert(keylen == 16 || keylen == 24 || keylen == 32);
-
- /*
- * Basic parameters. Words per block, words in key, rounds.
- */
- Nk = keylen / 4;
- ctx->Nb = blocklen / 4;
- ctx->Nr = 6 + (ctx->Nb > Nk ? ctx->Nb : Nk);
-
- /*
- * Assign core-function pointers.
- */
- if (ctx->Nb == 8)
- ctx->encrypt = aes_encrypt_nb_8, ctx->decrypt = aes_decrypt_nb_8;
- else if (ctx->Nb == 6)
- ctx->encrypt = aes_encrypt_nb_6, ctx->decrypt = aes_decrypt_nb_6;
- else if (ctx->Nb == 4)
- ctx->encrypt = aes_encrypt_nb_4, ctx->decrypt = aes_decrypt_nb_4;
-
- /*
- * Now do the key setup itself.
- */
- rconst = 1;
- for (i = 0; i < (ctx->Nr + 1) * ctx->Nb; i++) {
- if (i < Nk)
- ctx->keysched[i] = GET_32BIT_MSB_FIRST(key + 4 * i);
- else {
- word32 temp = ctx->keysched[i - 1];
- if (i % Nk == 0) {
- int a, b, c, d;
- a = (temp >> 16) & 0xFF;
- b = (temp >> 8) & 0xFF;
- c = (temp >> 0) & 0xFF;
- d = (temp >> 24) & 0xFF;
- temp = Sbox[a] ^ rconst;
- temp = (temp << 8) | Sbox[b];
- temp = (temp << 8) | Sbox[c];
- temp = (temp << 8) | Sbox[d];
- rconst = mulby2(rconst);
- } else if (i % Nk == 4 && Nk > 6) {
- int a, b, c, d;
- a = (temp >> 24) & 0xFF;
- b = (temp >> 16) & 0xFF;
- c = (temp >> 8) & 0xFF;
- d = (temp >> 0) & 0xFF;
- temp = Sbox[a];
- temp = (temp << 8) | Sbox[b];
- temp = (temp << 8) | Sbox[c];
- temp = (temp << 8) | Sbox[d];
- }
- ctx->keysched[i] = ctx->keysched[i - Nk] ^ temp;
- }
- }
-
- /*
- * Now prepare the modified keys for the inverse cipher.
- */
- for (i = 0; i <= ctx->Nr; i++) {
- for (j = 0; j < ctx->Nb; j++) {
- word32 temp;
- temp = ctx->keysched[(ctx->Nr - i) * ctx->Nb + j];
- if (i != 0 && i != ctx->Nr) {
- /*
- * Perform the InvMixColumn operation on i. The D
- * tables give the result of InvMixColumn applied
- * to Sboxinv on individual bytes, so we should
- * compose Sbox with the D tables for this.
- */
- int a, b, c, d;
- a = (temp >> 24) & 0xFF;
- b = (temp >> 16) & 0xFF;
- c = (temp >> 8) & 0xFF;
- d = (temp >> 0) & 0xFF;
- temp = D0[Sbox[a]];
- temp ^= D1[Sbox[b]];
- temp ^= D2[Sbox[c]];
- temp ^= D3[Sbox[d]];
- }
- ctx->invkeysched[i * ctx->Nb + j] = temp;
- }
- }
-}
-
-static void aes_encrypt(AESContext * ctx, word32 * block)
-{
- ctx->encrypt(ctx, block);
-}
-
-static void aes_decrypt(AESContext * ctx, word32 * block)
-{
- ctx->decrypt(ctx, block);
-}
-
-static void aes_encrypt_cbc(unsigned char *blk, int len, AESContext * ctx)
-{
- word32 iv[4];
- int i;
-
- assert((len & 15) == 0);
-
- memcpy(iv, ctx->iv, sizeof(iv));
-
- while (len > 0) {
- for (i = 0; i < 4; i++)
- iv[i] ^= GET_32BIT_MSB_FIRST(blk + 4 * i);
- aes_encrypt(ctx, iv);
- for (i = 0; i < 4; i++)
- PUT_32BIT_MSB_FIRST(blk + 4 * i, iv[i]);
- blk += 16;
- len -= 16;
- }
-
- memcpy(ctx->iv, iv, sizeof(iv));
-}
-
-static void aes_decrypt_cbc(unsigned char *blk, int len, AESContext * ctx)
-{
- word32 iv[4], x[4], ct[4];
- int i;
-
- assert((len & 15) == 0);
-
- memcpy(iv, ctx->iv, sizeof(iv));
-
- while (len > 0) {
- for (i = 0; i < 4; i++)
- x[i] = ct[i] = GET_32BIT_MSB_FIRST(blk + 4 * i);
- aes_decrypt(ctx, x);
- for (i = 0; i < 4; i++) {
- PUT_32BIT_MSB_FIRST(blk + 4 * i, iv[i] ^ x[i]);
- iv[i] = ct[i];
- }
- blk += 16;
- len -= 16;
- }
-
- memcpy(ctx->iv, iv, sizeof(iv));
-}
-
-static void aes_sdctr(unsigned char *blk, int len, AESContext *ctx)
-{
- word32 iv[4], b[4], tmp;
- int i;
-
- assert((len & 15) == 0);
-
- memcpy(iv, ctx->iv, sizeof(iv));
-
- while (len > 0) {
- memcpy(b, iv, sizeof(b));
- aes_encrypt(ctx, b);
- for (i = 0; i < 4; i++) {
- tmp = GET_32BIT_MSB_FIRST(blk + 4 * i);
- PUT_32BIT_MSB_FIRST(blk + 4 * i, tmp ^ b[i]);
- }
- for (i = 3; i >= 0; i--)
- if ((iv[i] = (iv[i] + 1) & 0xffffffff) != 0)
- break;
- blk += 16;
- len -= 16;
- }
-
- memcpy(ctx->iv, iv, sizeof(iv));
-}
-
-void *aes_make_context(void)
-{
- return snew(AESContext);
-}
-
-void aes_free_context(void *handle)
-{
- sfree(handle);
-}
-
-void aes128_key(void *handle, unsigned char *key)
-{
- AESContext *ctx = (AESContext *)handle;
- aes_setup(ctx, 16, key, 16);
-}
-
-void aes192_key(void *handle, unsigned char *key)
-{
- AESContext *ctx = (AESContext *)handle;
- aes_setup(ctx, 16, key, 24);
-}
-
-void aes256_key(void *handle, unsigned char *key)
-{
- AESContext *ctx = (AESContext *)handle;
- aes_setup(ctx, 16, key, 32);
-}
-
-void aes_iv(void *handle, unsigned char *iv)
-{
- AESContext *ctx = (AESContext *)handle;
- int i;
- for (i = 0; i < 4; i++)
- ctx->iv[i] = GET_32BIT_MSB_FIRST(iv + 4 * i);
-}
-
-void aes_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len)
-{
- AESContext *ctx = (AESContext *)handle;
- aes_encrypt_cbc(blk, len, ctx);
-}
-
-void aes_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len)
-{
- AESContext *ctx = (AESContext *)handle;
- aes_decrypt_cbc(blk, len, ctx);
-}
-
-static void aes_ssh2_sdctr(void *handle, unsigned char *blk, int len)
-{
- AESContext *ctx = (AESContext *)handle;
- aes_sdctr(blk, len, ctx);
-}
-
-void aes256_encrypt_pubkey(unsigned char *key, unsigned char *blk, int len)
-{
- AESContext ctx;
- aes_setup(&ctx, 16, key, 32);
- memset(ctx.iv, 0, sizeof(ctx.iv));
- aes_encrypt_cbc(blk, len, &ctx);
- memset(&ctx, 0, sizeof(ctx));
-}
-
-void aes256_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len)
-{
- AESContext ctx;
- aes_setup(&ctx, 16, key, 32);
- memset(ctx.iv, 0, sizeof(ctx.iv));
- aes_decrypt_cbc(blk, len, &ctx);
- memset(&ctx, 0, sizeof(ctx));
-}
-
-static const struct ssh2_cipher ssh_aes128_ctr = {
- aes_make_context, aes_free_context, aes_iv, aes128_key,
- aes_ssh2_sdctr, aes_ssh2_sdctr,
- "aes128-ctr",
- 16, 128, 0, "AES-128 SDCTR"
-};
-
-static const struct ssh2_cipher ssh_aes192_ctr = {
- aes_make_context, aes_free_context, aes_iv, aes192_key,
- aes_ssh2_sdctr, aes_ssh2_sdctr,
- "aes192-ctr",
- 16, 192, 0, "AES-192 SDCTR"
-};
-
-static const struct ssh2_cipher ssh_aes256_ctr = {
- aes_make_context, aes_free_context, aes_iv, aes256_key,
- aes_ssh2_sdctr, aes_ssh2_sdctr,
- "aes256-ctr",
- 16, 256, 0, "AES-256 SDCTR"
-};
-
-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,
- "aes128-cbc",
- 16, 128, SSH_CIPHER_IS_CBC, "AES-128 CBC"
-};
-
-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,
- "aes192-cbc",
- 16, 192, SSH_CIPHER_IS_CBC, "AES-192 CBC"
-};
-
-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,
- "aes256-cbc",
- 16, 256, SSH_CIPHER_IS_CBC, "AES-256 CBC"
-};
-
-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,
- "rijndael-cbc@lysator.liu.se",
- 16, 256, SSH_CIPHER_IS_CBC, "AES-256 CBC"
-};
-
-static const struct ssh2_cipher *const aes_list[] = {
- &ssh_aes256_ctr,
- &ssh_aes256,
- &ssh_rijndael_lysator,
- &ssh_aes192_ctr,
- &ssh_aes192,
- &ssh_aes128_ctr,
- &ssh_aes128,
-};
-
-const struct ssh2_ciphers ssh2_aes = {
- sizeof(aes_list) / sizeof(*aes_list),
- aes_list
-};
+/*
+ * aes.c - implementation of AES / Rijndael
+ *
+ * AES is a flexible algorithm as regards endianness: it has no
+ * inherent preference as to which way round you should form words
+ * from the input byte stream. It talks endlessly of four-byte
+ * _vectors_, but never of 32-bit _words_ - there's no 32-bit
+ * addition at all, which would force an endianness by means of
+ * which way the carries went. So it would be possible to write a
+ * working AES that read words big-endian, and another working one
+ * that read them little-endian, just by computing a different set
+ * of tables - with no speed drop.
+ *
+ * It's therefore tempting to do just that, and remove the overhead
+ * of GET_32BIT_MSB_FIRST() et al, allowing every system to use its
+ * own endianness-native code; but I decided not to, partly for
+ * ease of testing, and mostly because I like the flexibility that
+ * allows you to encrypt a non-word-aligned block of memory (which
+ * many systems would stop being able to do if I went the
+ * endianness-dependent route).
+ *
+ * This implementation reads and stores words big-endian, but
+ * that's a minor implementation detail. By flipping the endianness
+ * of everything in the E0..E3, D0..D3 tables, and substituting
+ * GET_32BIT_LSB_FIRST for GET_32BIT_MSB_FIRST, I could create an
+ * implementation that worked internally little-endian and gave the
+ * same answers at the same speed.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "ssh.h"
+
+#define MAX_NR 14 /* max no of rounds */
+#define MAX_NK 8 /* max no of words in input key */
+#define MAX_NB 8 /* max no of words in cipher blk */
+
+#define mulby2(x) ( ((x&0x7F) << 1) ^ (x & 0x80 ? 0x1B : 0) )
+
+typedef struct AESContext AESContext;
+
+struct AESContext {
+ word32 keysched[(MAX_NR + 1) * MAX_NB];
+ word32 invkeysched[(MAX_NR + 1) * MAX_NB];
+ void (*encrypt) (AESContext * ctx, word32 * block);
+ void (*decrypt) (AESContext * ctx, word32 * block);
+ word32 iv[MAX_NB];
+ int Nb, Nr;
+};
+
+static const unsigned char Sbox[256] = {
+ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
+ 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
+ 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
+ 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
+ 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc,
+ 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
+ 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a,
+ 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
+ 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
+ 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
+ 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
+ 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
+ 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85,
+ 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
+ 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
+ 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
+ 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17,
+ 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
+ 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88,
+ 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
+ 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
+ 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
+ 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9,
+ 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
+ 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6,
+ 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
+ 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
+ 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
+ 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94,
+ 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
+ 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68,
+ 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
+};
+
+static const unsigned char Sboxinv[256] = {
+ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38,
+ 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
+ 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87,
+ 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
+ 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d,
+ 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
+ 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2,
+ 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
+ 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16,
+ 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
+ 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda,
+ 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
+ 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a,
+ 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
+ 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02,
+ 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
+ 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea,
+ 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
+ 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85,
+ 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
+ 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89,
+ 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
+ 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20,
+ 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
+ 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31,
+ 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
+ 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d,
+ 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
+ 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0,
+ 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
+ 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26,
+ 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d
+};
+
+static const word32 E0[256] = {
+ 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d,
+ 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554,
+ 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d,
+ 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a,
+ 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87,
+ 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b,
+ 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea,
+ 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b,
+ 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a,
+ 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f,
+ 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108,
+ 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f,
+ 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e,
+ 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5,
+ 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d,
+ 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f,
+ 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e,
+ 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb,
+ 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce,
+ 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497,
+ 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c,
+ 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed,
+ 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b,
+ 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a,
+ 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16,
+ 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594,
+ 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81,
+ 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3,
+ 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a,
+ 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504,
+ 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163,
+ 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d,
+ 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f,
+ 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739,
+ 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47,
+ 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395,
+ 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f,
+ 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883,
+ 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c,
+ 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76,
+ 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e,
+ 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4,
+ 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6,
+ 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b,
+ 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7,
+ 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0,
+ 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25,
+ 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818,
+ 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72,
+ 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651,
+ 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21,
+ 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85,
+ 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa,
+ 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12,
+ 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0,
+ 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9,
+ 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133,
+ 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7,
+ 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920,
+ 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a,
+ 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17,
+ 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8,
+ 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11,
+ 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a,
+};
+static const word32 E1[256] = {
+ 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b,
+ 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5,
+ 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b,
+ 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676,
+ 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d,
+ 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0,
+ 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf,
+ 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0,
+ 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626,
+ 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc,
+ 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1,
+ 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515,
+ 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3,
+ 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a,
+ 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2,
+ 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575,
+ 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a,
+ 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0,
+ 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3,
+ 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484,
+ 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded,
+ 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b,
+ 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939,
+ 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf,
+ 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb,
+ 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585,
+ 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f,
+ 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8,
+ 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f,
+ 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5,
+ 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121,
+ 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2,
+ 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec,
+ 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717,
+ 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d,
+ 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373,
+ 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc,
+ 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888,
+ 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414,
+ 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb,
+ 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a,
+ 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c,
+ 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262,
+ 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979,
+ 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d,
+ 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9,
+ 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea,
+ 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808,
+ 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e,
+ 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6,
+ 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f,
+ 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a,
+ 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666,
+ 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e,
+ 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9,
+ 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e,
+ 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111,
+ 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494,
+ 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9,
+ 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf,
+ 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d,
+ 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868,
+ 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f,
+ 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616,
+};
+static const word32 E2[256] = {
+ 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b,
+ 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5,
+ 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b,
+ 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76,
+ 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d,
+ 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0,
+ 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af,
+ 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0,
+ 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26,
+ 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc,
+ 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1,
+ 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15,
+ 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3,
+ 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a,
+ 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2,
+ 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75,
+ 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a,
+ 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0,
+ 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3,
+ 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384,
+ 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed,
+ 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b,
+ 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239,
+ 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf,
+ 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb,
+ 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185,
+ 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f,
+ 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8,
+ 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f,
+ 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5,
+ 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221,
+ 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2,
+ 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec,
+ 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17,
+ 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d,
+ 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673,
+ 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc,
+ 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88,
+ 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814,
+ 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb,
+ 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a,
+ 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c,
+ 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462,
+ 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279,
+ 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d,
+ 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9,
+ 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea,
+ 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008,
+ 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e,
+ 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6,
+ 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f,
+ 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a,
+ 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66,
+ 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e,
+ 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9,
+ 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e,
+ 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211,
+ 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394,
+ 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9,
+ 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df,
+ 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d,
+ 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068,
+ 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f,
+ 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16,
+};
+static const word32 E3[256] = {
+ 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6,
+ 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491,
+ 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56,
+ 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec,
+ 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa,
+ 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb,
+ 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45,
+ 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b,
+ 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c,
+ 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83,
+ 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9,
+ 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a,
+ 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d,
+ 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f,
+ 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf,
+ 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea,
+ 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34,
+ 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b,
+ 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d,
+ 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713,
+ 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1,
+ 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6,
+ 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72,
+ 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85,
+ 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed,
+ 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411,
+ 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe,
+ 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b,
+ 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05,
+ 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1,
+ 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342,
+ 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf,
+ 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3,
+ 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e,
+ 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a,
+ 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6,
+ 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3,
+ 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b,
+ 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28,
+ 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad,
+ 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14,
+ 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8,
+ 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4,
+ 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2,
+ 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da,
+ 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049,
+ 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf,
+ 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810,
+ 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c,
+ 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197,
+ 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e,
+ 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f,
+ 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc,
+ 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c,
+ 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069,
+ 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927,
+ 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322,
+ 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733,
+ 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9,
+ 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5,
+ 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a,
+ 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0,
+ 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e,
+ 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c,
+};
+static const word32 D0[256] = {
+ 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96,
+ 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393,
+ 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25,
+ 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f,
+ 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1,
+ 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6,
+ 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da,
+ 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844,
+ 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd,
+ 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4,
+ 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45,
+ 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94,
+ 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7,
+ 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a,
+ 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5,
+ 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c,
+ 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1,
+ 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a,
+ 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75,
+ 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051,
+ 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46,
+ 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff,
+ 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77,
+ 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb,
+ 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000,
+ 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e,
+ 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927,
+ 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a,
+ 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e,
+ 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16,
+ 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d,
+ 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8,
+ 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd,
+ 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34,
+ 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163,
+ 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120,
+ 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d,
+ 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0,
+ 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422,
+ 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef,
+ 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36,
+ 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4,
+ 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662,
+ 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5,
+ 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3,
+ 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b,
+ 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8,
+ 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6,
+ 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6,
+ 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0,
+ 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815,
+ 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f,
+ 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df,
+ 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f,
+ 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e,
+ 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713,
+ 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89,
+ 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c,
+ 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf,
+ 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86,
+ 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f,
+ 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541,
+ 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190,
+ 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742,
+};
+static const word32 D1[256] = {
+ 0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e,
+ 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303,
+ 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c,
+ 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3,
+ 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0,
+ 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9,
+ 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259,
+ 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8,
+ 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971,
+ 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a,
+ 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f,
+ 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b,
+ 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8,
+ 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab,
+ 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708,
+ 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682,
+ 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2,
+ 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe,
+ 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb,
+ 0x390b83ec, 0xaa4060ef, 0x065e719f, 0x51bd6e10,
+ 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd,
+ 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015,
+ 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e,
+ 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee,
+ 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000,
+ 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72,
+ 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39,
+ 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e,
+ 0xb10c0a67, 0x0f9357e7, 0xd2b4ee96, 0x9e1b9b91,
+ 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a,
+ 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17,
+ 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9,
+ 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60,
+ 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e,
+ 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1,
+ 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611,
+ 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1,
+ 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3,
+ 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964,
+ 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390,
+ 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b,
+ 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf,
+ 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46,
+ 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af,
+ 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512,
+ 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb,
+ 0x09cd2678, 0xf46e5918, 0x01ec9ab7, 0xa8834f9a,
+ 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8,
+ 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c,
+ 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266,
+ 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8,
+ 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6,
+ 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604,
+ 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551,
+ 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41,
+ 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647,
+ 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c,
+ 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1,
+ 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737,
+ 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db,
+ 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340,
+ 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95,
+ 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1,
+ 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857,
+};
+static const word32 D2[256] = {
+ 0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27,
+ 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3,
+ 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502,
+ 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562,
+ 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe,
+ 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3,
+ 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552,
+ 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9,
+ 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9,
+ 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce,
+ 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253,
+ 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908,
+ 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b,
+ 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655,
+ 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337,
+ 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16,
+ 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69,
+ 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6,
+ 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6,
+ 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e,
+ 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6,
+ 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050,
+ 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9,
+ 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8,
+ 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000,
+ 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a,
+ 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d,
+ 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436,
+ 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b,
+ 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12,
+ 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b,
+ 0x0d0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e,
+ 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f,
+ 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb,
+ 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4,
+ 0xdccad731, 0x85104263, 0x22401397, 0x112084c6,
+ 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729,
+ 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1,
+ 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9,
+ 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233,
+ 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4,
+ 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad,
+ 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e,
+ 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3,
+ 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25,
+ 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b,
+ 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f,
+ 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15,
+ 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0,
+ 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2,
+ 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7,
+ 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791,
+ 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496,
+ 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665,
+ 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b,
+ 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6,
+ 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13,
+ 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47,
+ 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7,
+ 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844,
+ 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3,
+ 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d,
+ 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456,
+ 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8,
+};
+static const word32 D3[256] = {
+ 0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a,
+ 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b,
+ 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5,
+ 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5,
+ 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d,
+ 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b,
+ 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95,
+ 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e,
+ 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27,
+ 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d,
+ 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562,
+ 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9,
+ 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752,
+ 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66,
+ 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3,
+ 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced,
+ 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e,
+ 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4,
+ 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4,
+ 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd,
+ 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d,
+ 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60,
+ 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767,
+ 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79,
+ 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000,
+ 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c,
+ 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736,
+ 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24,
+ 0x0a67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b,
+ 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c,
+ 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12,
+ 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814,
+ 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3,
+ 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b,
+ 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8,
+ 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084,
+ 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7,
+ 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077,
+ 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247,
+ 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22,
+ 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698,
+ 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f,
+ 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254,
+ 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582,
+ 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf,
+ 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb,
+ 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883,
+ 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef,
+ 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629,
+ 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035,
+ 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533,
+ 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17,
+ 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4,
+ 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46,
+ 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb,
+ 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d,
+ 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb,
+ 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a,
+ 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73,
+ 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678,
+ 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2,
+ 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff,
+ 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064,
+ 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0,
+};
+
+/*
+ * Common macros in both the encryption and decryption routines.
+ */
+#define ADD_ROUND_KEY_4 (block[0]^=*keysched++, block[1]^=*keysched++, \
+ block[2]^=*keysched++, block[3]^=*keysched++)
+#define ADD_ROUND_KEY_6 (block[0]^=*keysched++, block[1]^=*keysched++, \
+ block[2]^=*keysched++, block[3]^=*keysched++, \
+ block[4]^=*keysched++, block[5]^=*keysched++)
+#define ADD_ROUND_KEY_8 (block[0]^=*keysched++, block[1]^=*keysched++, \
+ block[2]^=*keysched++, block[3]^=*keysched++, \
+ block[4]^=*keysched++, block[5]^=*keysched++, \
+ block[6]^=*keysched++, block[7]^=*keysched++)
+#define MOVEWORD(i) ( block[i] = newstate[i] )
+
+/*
+ * Macros for the encryption routine. There are three encryption
+ * cores, for Nb=4,6,8.
+ */
+#define MAKEWORD(i) ( newstate[i] = (E0[(block[i] >> 24) & 0xFF] ^ \
+ E1[(block[(i+C1)%Nb] >> 16) & 0xFF] ^ \
+ E2[(block[(i+C2)%Nb] >> 8) & 0xFF] ^ \
+ E3[block[(i+C3)%Nb] & 0xFF]) )
+#define LASTWORD(i) ( newstate[i] = (Sbox[(block[i] >> 24) & 0xFF] << 24) | \
+ (Sbox[(block[(i+C1)%Nb] >> 16) & 0xFF] << 16) | \
+ (Sbox[(block[(i+C2)%Nb] >> 8) & 0xFF] << 8) | \
+ (Sbox[(block[(i+C3)%Nb] ) & 0xFF] ) )
+
+/*
+ * Core encrypt routines, expecting word32 inputs read big-endian
+ * from the byte-oriented input stream.
+ */
+static void aes_encrypt_nb_4(AESContext * ctx, word32 * block)
+{
+ int i;
+ static const int C1 = 1, C2 = 2, C3 = 3, Nb = 4;
+ word32 *keysched = ctx->keysched;
+ word32 newstate[4];
+ for (i = 0; i < ctx->Nr - 1; i++) {
+ ADD_ROUND_KEY_4;
+ MAKEWORD(0);
+ MAKEWORD(1);
+ MAKEWORD(2);
+ MAKEWORD(3);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ }
+ ADD_ROUND_KEY_4;
+ LASTWORD(0);
+ LASTWORD(1);
+ LASTWORD(2);
+ LASTWORD(3);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ ADD_ROUND_KEY_4;
+}
+static void aes_encrypt_nb_6(AESContext * ctx, word32 * block)
+{
+ int i;
+ static const int C1 = 1, C2 = 2, C3 = 3, Nb = 6;
+ word32 *keysched = ctx->keysched;
+ word32 newstate[6];
+ for (i = 0; i < ctx->Nr - 1; i++) {
+ ADD_ROUND_KEY_6;
+ MAKEWORD(0);
+ MAKEWORD(1);
+ MAKEWORD(2);
+ MAKEWORD(3);
+ MAKEWORD(4);
+ MAKEWORD(5);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ MOVEWORD(4);
+ MOVEWORD(5);
+ }
+ ADD_ROUND_KEY_6;
+ LASTWORD(0);
+ LASTWORD(1);
+ LASTWORD(2);
+ LASTWORD(3);
+ LASTWORD(4);
+ LASTWORD(5);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ MOVEWORD(4);
+ MOVEWORD(5);
+ ADD_ROUND_KEY_6;
+}
+static void aes_encrypt_nb_8(AESContext * ctx, word32 * block)
+{
+ int i;
+ static const int C1 = 1, C2 = 3, C3 = 4, Nb = 8;
+ word32 *keysched = ctx->keysched;
+ word32 newstate[8];
+ for (i = 0; i < ctx->Nr - 1; i++) {
+ ADD_ROUND_KEY_8;
+ MAKEWORD(0);
+ MAKEWORD(1);
+ MAKEWORD(2);
+ MAKEWORD(3);
+ MAKEWORD(4);
+ MAKEWORD(5);
+ MAKEWORD(6);
+ MAKEWORD(7);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ MOVEWORD(4);
+ MOVEWORD(5);
+ MOVEWORD(6);
+ MOVEWORD(7);
+ }
+ ADD_ROUND_KEY_8;
+ LASTWORD(0);
+ LASTWORD(1);
+ LASTWORD(2);
+ LASTWORD(3);
+ LASTWORD(4);
+ LASTWORD(5);
+ LASTWORD(6);
+ LASTWORD(7);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ MOVEWORD(4);
+ MOVEWORD(5);
+ MOVEWORD(6);
+ MOVEWORD(7);
+ ADD_ROUND_KEY_8;
+}
+
+#undef MAKEWORD
+#undef LASTWORD
+
+/*
+ * Macros for the decryption routine. There are three decryption
+ * cores, for Nb=4,6,8.
+ */
+#define MAKEWORD(i) ( newstate[i] = (D0[(block[i] >> 24) & 0xFF] ^ \
+ D1[(block[(i+C1)%Nb] >> 16) & 0xFF] ^ \
+ D2[(block[(i+C2)%Nb] >> 8) & 0xFF] ^ \
+ D3[block[(i+C3)%Nb] & 0xFF]) )
+#define LASTWORD(i) (newstate[i] = (Sboxinv[(block[i] >> 24) & 0xFF] << 24) | \
+ (Sboxinv[(block[(i+C1)%Nb] >> 16) & 0xFF] << 16) | \
+ (Sboxinv[(block[(i+C2)%Nb] >> 8) & 0xFF] << 8) | \
+ (Sboxinv[(block[(i+C3)%Nb] ) & 0xFF] ) )
+
+/*
+ * Core decrypt routines, expecting word32 inputs read big-endian
+ * from the byte-oriented input stream.
+ */
+static void aes_decrypt_nb_4(AESContext * ctx, word32 * block)
+{
+ int i;
+ static const int C1 = 4 - 1, C2 = 4 - 2, C3 = 4 - 3, Nb = 4;
+ word32 *keysched = ctx->invkeysched;
+ word32 newstate[4];
+ for (i = 0; i < ctx->Nr - 1; i++) {
+ ADD_ROUND_KEY_4;
+ MAKEWORD(0);
+ MAKEWORD(1);
+ MAKEWORD(2);
+ MAKEWORD(3);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ }
+ ADD_ROUND_KEY_4;
+ LASTWORD(0);
+ LASTWORD(1);
+ LASTWORD(2);
+ LASTWORD(3);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ ADD_ROUND_KEY_4;
+}
+static void aes_decrypt_nb_6(AESContext * ctx, word32 * block)
+{
+ int i;
+ static const int C1 = 6 - 1, C2 = 6 - 2, C3 = 6 - 3, Nb = 6;
+ word32 *keysched = ctx->invkeysched;
+ word32 newstate[6];
+ for (i = 0; i < ctx->Nr - 1; i++) {
+ ADD_ROUND_KEY_6;
+ MAKEWORD(0);
+ MAKEWORD(1);
+ MAKEWORD(2);
+ MAKEWORD(3);
+ MAKEWORD(4);
+ MAKEWORD(5);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ MOVEWORD(4);
+ MOVEWORD(5);
+ }
+ ADD_ROUND_KEY_6;
+ LASTWORD(0);
+ LASTWORD(1);
+ LASTWORD(2);
+ LASTWORD(3);
+ LASTWORD(4);
+ LASTWORD(5);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ MOVEWORD(4);
+ MOVEWORD(5);
+ ADD_ROUND_KEY_6;
+}
+static void aes_decrypt_nb_8(AESContext * ctx, word32 * block)
+{
+ int i;
+ static const int C1 = 8 - 1, C2 = 8 - 3, C3 = 8 - 4, Nb = 8;
+ word32 *keysched = ctx->invkeysched;
+ word32 newstate[8];
+ for (i = 0; i < ctx->Nr - 1; i++) {
+ ADD_ROUND_KEY_8;
+ MAKEWORD(0);
+ MAKEWORD(1);
+ MAKEWORD(2);
+ MAKEWORD(3);
+ MAKEWORD(4);
+ MAKEWORD(5);
+ MAKEWORD(6);
+ MAKEWORD(7);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ MOVEWORD(4);
+ MOVEWORD(5);
+ MOVEWORD(6);
+ MOVEWORD(7);
+ }
+ ADD_ROUND_KEY_8;
+ LASTWORD(0);
+ LASTWORD(1);
+ LASTWORD(2);
+ LASTWORD(3);
+ LASTWORD(4);
+ LASTWORD(5);
+ LASTWORD(6);
+ LASTWORD(7);
+ MOVEWORD(0);
+ MOVEWORD(1);
+ MOVEWORD(2);
+ MOVEWORD(3);
+ MOVEWORD(4);
+ MOVEWORD(5);
+ MOVEWORD(6);
+ MOVEWORD(7);
+ ADD_ROUND_KEY_8;
+}
+
+#undef MAKEWORD
+#undef LASTWORD
+
+
+/*
+ * Set up an AESContext. `keylen' and `blocklen' are measured in
+ * bytes; each can be either 16 (128-bit), 24 (192-bit), or 32
+ * (256-bit).
+ */
+static void aes_setup(AESContext * ctx, int blocklen,
+ unsigned char *key, int keylen)
+{
+ int i, j, Nk, rconst;
+
+ assert(blocklen == 16 || blocklen == 24 || blocklen == 32);
+ assert(keylen == 16 || keylen == 24 || keylen == 32);
+
+ /*
+ * Basic parameters. Words per block, words in key, rounds.
+ */
+ Nk = keylen / 4;
+ ctx->Nb = blocklen / 4;
+ ctx->Nr = 6 + (ctx->Nb > Nk ? ctx->Nb : Nk);
+
+ /*
+ * Assign core-function pointers.
+ */
+ if (ctx->Nb == 8)
+ ctx->encrypt = aes_encrypt_nb_8, ctx->decrypt = aes_decrypt_nb_8;
+ else if (ctx->Nb == 6)
+ ctx->encrypt = aes_encrypt_nb_6, ctx->decrypt = aes_decrypt_nb_6;
+ else if (ctx->Nb == 4)
+ ctx->encrypt = aes_encrypt_nb_4, ctx->decrypt = aes_decrypt_nb_4;
+
+ /*
+ * Now do the key setup itself.
+ */
+ rconst = 1;
+ for (i = 0; i < (ctx->Nr + 1) * ctx->Nb; i++) {
+ if (i < Nk)
+ ctx->keysched[i] = GET_32BIT_MSB_FIRST(key + 4 * i);
+ else {
+ word32 temp = ctx->keysched[i - 1];
+ if (i % Nk == 0) {
+ int a, b, c, d;
+ a = (temp >> 16) & 0xFF;
+ b = (temp >> 8) & 0xFF;
+ c = (temp >> 0) & 0xFF;
+ d = (temp >> 24) & 0xFF;
+ temp = Sbox[a] ^ rconst;
+ temp = (temp << 8) | Sbox[b];
+ temp = (temp << 8) | Sbox[c];
+ temp = (temp << 8) | Sbox[d];
+ rconst = mulby2(rconst);
+ } else if (i % Nk == 4 && Nk > 6) {
+ int a, b, c, d;
+ a = (temp >> 24) & 0xFF;
+ b = (temp >> 16) & 0xFF;
+ c = (temp >> 8) & 0xFF;
+ d = (temp >> 0) & 0xFF;
+ temp = Sbox[a];
+ temp = (temp << 8) | Sbox[b];
+ temp = (temp << 8) | Sbox[c];
+ temp = (temp << 8) | Sbox[d];
+ }
+ ctx->keysched[i] = ctx->keysched[i - Nk] ^ temp;
+ }
+ }
+
+ /*
+ * Now prepare the modified keys for the inverse cipher.
+ */
+ for (i = 0; i <= ctx->Nr; i++) {
+ for (j = 0; j < ctx->Nb; j++) {
+ word32 temp;
+ temp = ctx->keysched[(ctx->Nr - i) * ctx->Nb + j];
+ if (i != 0 && i != ctx->Nr) {
+ /*
+ * Perform the InvMixColumn operation on i. The D
+ * tables give the result of InvMixColumn applied
+ * to Sboxinv on individual bytes, so we should
+ * compose Sbox with the D tables for this.
+ */
+ int a, b, c, d;
+ a = (temp >> 24) & 0xFF;
+ b = (temp >> 16) & 0xFF;
+ c = (temp >> 8) & 0xFF;
+ d = (temp >> 0) & 0xFF;
+ temp = D0[Sbox[a]];
+ temp ^= D1[Sbox[b]];
+ temp ^= D2[Sbox[c]];
+ temp ^= D3[Sbox[d]];
+ }
+ ctx->invkeysched[i * ctx->Nb + j] = temp;
+ }
+ }
+}
+
+static void aes_encrypt(AESContext * ctx, word32 * block)
+{
+ ctx->encrypt(ctx, block);
+}
+
+static void aes_decrypt(AESContext * ctx, word32 * block)
+{
+ ctx->decrypt(ctx, block);
+}
+
+static void aes_encrypt_cbc(unsigned char *blk, int len, AESContext * ctx)
+{
+ word32 iv[4];
+ int i;
+
+ assert((len & 15) == 0);
+
+ memcpy(iv, ctx->iv, sizeof(iv));
+
+ while (len > 0) {
+ for (i = 0; i < 4; i++)
+ iv[i] ^= GET_32BIT_MSB_FIRST(blk + 4 * i);
+ aes_encrypt(ctx, iv);
+ for (i = 0; i < 4; i++)
+ PUT_32BIT_MSB_FIRST(blk + 4 * i, iv[i]);
+ blk += 16;
+ len -= 16;
+ }
+
+ memcpy(ctx->iv, iv, sizeof(iv));
+}
+
+static void aes_decrypt_cbc(unsigned char *blk, int len, AESContext * ctx)
+{
+ word32 iv[4], x[4], ct[4];
+ int i;
+
+ assert((len & 15) == 0);
+
+ memcpy(iv, ctx->iv, sizeof(iv));
+
+ while (len > 0) {
+ for (i = 0; i < 4; i++)
+ x[i] = ct[i] = GET_32BIT_MSB_FIRST(blk + 4 * i);
+ aes_decrypt(ctx, x);
+ for (i = 0; i < 4; i++) {
+ PUT_32BIT_MSB_FIRST(blk + 4 * i, iv[i] ^ x[i]);
+ iv[i] = ct[i];
+ }
+ blk += 16;
+ len -= 16;
+ }
+
+ memcpy(ctx->iv, iv, sizeof(iv));
+}
+
+static void aes_sdctr(unsigned char *blk, int len, AESContext *ctx)
+{
+ word32 iv[4], b[4], tmp;
+ int i;
+
+ assert((len & 15) == 0);
+
+ memcpy(iv, ctx->iv, sizeof(iv));
+
+ while (len > 0) {
+ memcpy(b, iv, sizeof(b));
+ aes_encrypt(ctx, b);
+ for (i = 0; i < 4; i++) {
+ tmp = GET_32BIT_MSB_FIRST(blk + 4 * i);
+ PUT_32BIT_MSB_FIRST(blk + 4 * i, tmp ^ b[i]);
+ }
+ for (i = 3; i >= 0; i--)
+ if ((iv[i] = (iv[i] + 1) & 0xffffffff) != 0)
+ break;
+ blk += 16;
+ len -= 16;
+ }
+
+ memcpy(ctx->iv, iv, sizeof(iv));
+}
+
+void *aes_make_context(void)
+{
+ return snew(AESContext);
+}
+
+void aes_free_context(void *handle)
+{
+ sfree(handle);
+}
+
+void aes128_key(void *handle, unsigned char *key)
+{
+ AESContext *ctx = (AESContext *)handle;
+ aes_setup(ctx, 16, key, 16);
+}
+
+void aes192_key(void *handle, unsigned char *key)
+{
+ AESContext *ctx = (AESContext *)handle;
+ aes_setup(ctx, 16, key, 24);
+}
+
+void aes256_key(void *handle, unsigned char *key)
+{
+ AESContext *ctx = (AESContext *)handle;
+ aes_setup(ctx, 16, key, 32);
+}
+
+void aes_iv(void *handle, unsigned char *iv)
+{
+ AESContext *ctx = (AESContext *)handle;
+ int i;
+ for (i = 0; i < 4; i++)
+ ctx->iv[i] = GET_32BIT_MSB_FIRST(iv + 4 * i);
+}
+
+void aes_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len)
+{
+ AESContext *ctx = (AESContext *)handle;
+ aes_encrypt_cbc(blk, len, ctx);
+}
+
+void aes_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len)
+{
+ AESContext *ctx = (AESContext *)handle;
+ aes_decrypt_cbc(blk, len, ctx);
+}
+
+static void aes_ssh2_sdctr(void *handle, unsigned char *blk, int len)
+{
+ AESContext *ctx = (AESContext *)handle;
+ aes_sdctr(blk, len, ctx);
+}
+
+void aes256_encrypt_pubkey(unsigned char *key, unsigned char *blk, int len)
+{
+ AESContext ctx;
+ aes_setup(&ctx, 16, key, 32);
+ memset(ctx.iv, 0, sizeof(ctx.iv));
+ aes_encrypt_cbc(blk, len, &ctx);
+ smemclr(&ctx, sizeof(ctx));
+}
+
+void aes256_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len)
+{
+ AESContext ctx;
+ aes_setup(&ctx, 16, key, 32);
+ memset(ctx.iv, 0, sizeof(ctx.iv));
+ aes_decrypt_cbc(blk, len, &ctx);
+ smemclr(&ctx, sizeof(ctx));
+}
+
+static const struct ssh2_cipher ssh_aes128_ctr = {
+ aes_make_context, aes_free_context, aes_iv, aes128_key,
+ aes_ssh2_sdctr, aes_ssh2_sdctr,
+ "aes128-ctr",
+ 16, 128, 0, "AES-128 SDCTR"
+};
+
+static const struct ssh2_cipher ssh_aes192_ctr = {
+ aes_make_context, aes_free_context, aes_iv, aes192_key,
+ aes_ssh2_sdctr, aes_ssh2_sdctr,
+ "aes192-ctr",
+ 16, 192, 0, "AES-192 SDCTR"
+};
+
+static const struct ssh2_cipher ssh_aes256_ctr = {
+ aes_make_context, aes_free_context, aes_iv, aes256_key,
+ aes_ssh2_sdctr, aes_ssh2_sdctr,
+ "aes256-ctr",
+ 16, 256, 0, "AES-256 SDCTR"
+};
+
+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,
+ "aes128-cbc",
+ 16, 128, SSH_CIPHER_IS_CBC, "AES-128 CBC"
+};
+
+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,
+ "aes192-cbc",
+ 16, 192, SSH_CIPHER_IS_CBC, "AES-192 CBC"
+};
+
+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,
+ "aes256-cbc",
+ 16, 256, SSH_CIPHER_IS_CBC, "AES-256 CBC"
+};
+
+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,
+ "rijndael-cbc@lysator.liu.se",
+ 16, 256, SSH_CIPHER_IS_CBC, "AES-256 CBC"
+};
+
+static const struct ssh2_cipher *const aes_list[] = {
+ &ssh_aes256_ctr,
+ &ssh_aes256,
+ &ssh_rijndael_lysator,
+ &ssh_aes192_ctr,
+ &ssh_aes192,
+ &ssh_aes128_ctr,
+ &ssh_aes128,
+};
+
+const struct ssh2_ciphers ssh2_aes = {
+ sizeof(aes_list) / sizeof(*aes_list),
+ aes_list
+};
diff --git a/tools/plink/ssharcf.c b/tools/plink/ssharcf.c
index e0b247e51..06f235c5b 100644
--- a/tools/plink/ssharcf.c
+++ b/tools/plink/ssharcf.c
@@ -1,123 +1,123 @@
-/*
- * Arcfour (RC4) implementation for PuTTY.
- *
- * Coded from Schneier.
- */
-
-#include <assert.h>
-#include "ssh.h"
-
-typedef struct {
- unsigned char i, j, s[256];
-} ArcfourContext;
-
-static void arcfour_block(void *handle, unsigned char *blk, int len)
-{
- ArcfourContext *ctx = (ArcfourContext *)handle;
- unsigned k;
- unsigned char tmp, i, j, *s;
-
- s = ctx->s;
- i = ctx->i; j = ctx->j;
- for (k = 0; (int)k < len; k++) {
- i = (i + 1) & 0xff;
- j = (j + s[i]) & 0xff;
- tmp = s[i]; s[i] = s[j]; s[j] = tmp;
- blk[k] ^= s[(s[i]+s[j]) & 0xff];
- }
- ctx->i = i; ctx->j = j;
-}
-
-static void arcfour_setkey(ArcfourContext *ctx, unsigned char const *key,
- unsigned keybytes)
-{
- unsigned char tmp, k[256], *s;
- unsigned i, j;
-
- s = ctx->s;
- assert(keybytes <= 256);
- ctx->i = ctx->j = 0;
- for (i = 0; i < 256; i++) {
- s[i] = i;
- k[i] = key[i % keybytes];
- }
- j = 0;
- for (i = 0; i < 256; i++) {
- j = (j + s[i] + k[i]) & 0xff;
- tmp = s[i]; s[i] = s[j]; s[j] = tmp;
- }
-}
-
-/* -- Interface with PuTTY -- */
-
-/*
- * We don't implement Arcfour in SSH-1 because it's utterly insecure in
- * several ways. See CERT Vulnerability Notes VU#25309, VU#665372,
- * and VU#565052.
- *
- * We don't implement the "arcfour" algorithm in SSH-2 because it doesn't
- * stir the cipher state before emitting keystream, and hence is likely
- * to leak data about the key.
- */
-
-static void *arcfour_make_context(void)
-{
- return snew(ArcfourContext);
-}
-
-static void arcfour_free_context(void *handle)
-{
- sfree(handle);
-}
-
-static void arcfour_stir(ArcfourContext *ctx)
-{
- unsigned char *junk = snewn(1536, unsigned char);
- memset(junk, 0, 1536);
- arcfour_block(ctx, junk, 1536);
- memset(junk, 0, 1536);
- sfree(junk);
-}
-
-static void arcfour128_key(void *handle, unsigned char *key)
-{
- ArcfourContext *ctx = (ArcfourContext *)handle;
- arcfour_setkey(ctx, key, 16);
- arcfour_stir(ctx);
-}
-
-static void arcfour256_key(void *handle, unsigned char *key)
-{
- ArcfourContext *ctx = (ArcfourContext *)handle;
- arcfour_setkey(ctx, key, 32);
- arcfour_stir(ctx);
-}
-
-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,
- "arcfour128",
- 1, 128, 0, "Arcfour-128"
-};
-
-const struct ssh2_cipher ssh_arcfour256_ssh2 = {
- arcfour_make_context, arcfour_free_context, arcfour_iv, arcfour256_key,
- arcfour_block, arcfour_block,
- "arcfour256",
- 1, 256, 0, "Arcfour-256"
-};
-
-static const struct ssh2_cipher *const arcfour_list[] = {
- &ssh_arcfour256_ssh2,
- &ssh_arcfour128_ssh2,
-};
-
-const struct ssh2_ciphers ssh2_arcfour = {
- sizeof(arcfour_list) / sizeof(*arcfour_list),
- arcfour_list
-};
+/*
+ * Arcfour (RC4) implementation for PuTTY.
+ *
+ * Coded from Schneier.
+ */
+
+#include <assert.h>
+#include "ssh.h"
+
+typedef struct {
+ unsigned char i, j, s[256];
+} ArcfourContext;
+
+static void arcfour_block(void *handle, unsigned char *blk, int len)
+{
+ ArcfourContext *ctx = (ArcfourContext *)handle;
+ unsigned k;
+ unsigned char tmp, i, j, *s;
+
+ s = ctx->s;
+ i = ctx->i; j = ctx->j;
+ for (k = 0; (int)k < len; k++) {
+ i = (i + 1) & 0xff;
+ j = (j + s[i]) & 0xff;
+ tmp = s[i]; s[i] = s[j]; s[j] = tmp;
+ blk[k] ^= s[(s[i]+s[j]) & 0xff];
+ }
+ ctx->i = i; ctx->j = j;
+}
+
+static void arcfour_setkey(ArcfourContext *ctx, unsigned char const *key,
+ unsigned keybytes)
+{
+ unsigned char tmp, k[256], *s;
+ unsigned i, j;
+
+ s = ctx->s;
+ assert(keybytes <= 256);
+ ctx->i = ctx->j = 0;
+ for (i = 0; i < 256; i++) {
+ s[i] = i;
+ k[i] = key[i % keybytes];
+ }
+ j = 0;
+ for (i = 0; i < 256; i++) {
+ j = (j + s[i] + k[i]) & 0xff;
+ tmp = s[i]; s[i] = s[j]; s[j] = tmp;
+ }
+}
+
+/* -- Interface with PuTTY -- */
+
+/*
+ * We don't implement Arcfour in SSH-1 because it's utterly insecure in
+ * several ways. See CERT Vulnerability Notes VU#25309, VU#665372,
+ * and VU#565052.
+ *
+ * We don't implement the "arcfour" algorithm in SSH-2 because it doesn't
+ * stir the cipher state before emitting keystream, and hence is likely
+ * to leak data about the key.
+ */
+
+static void *arcfour_make_context(void)
+{
+ return snew(ArcfourContext);
+}
+
+static void arcfour_free_context(void *handle)
+{
+ sfree(handle);
+}
+
+static void arcfour_stir(ArcfourContext *ctx)
+{
+ unsigned char *junk = snewn(1536, unsigned char);
+ memset(junk, 0, 1536);
+ arcfour_block(ctx, junk, 1536);
+ smemclr(junk, 1536);
+ sfree(junk);
+}
+
+static void arcfour128_key(void *handle, unsigned char *key)
+{
+ ArcfourContext *ctx = (ArcfourContext *)handle;
+ arcfour_setkey(ctx, key, 16);
+ arcfour_stir(ctx);
+}
+
+static void arcfour256_key(void *handle, unsigned char *key)
+{
+ ArcfourContext *ctx = (ArcfourContext *)handle;
+ arcfour_setkey(ctx, key, 32);
+ arcfour_stir(ctx);
+}
+
+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,
+ "arcfour128",
+ 1, 128, 0, "Arcfour-128"
+};
+
+const struct ssh2_cipher ssh_arcfour256_ssh2 = {
+ arcfour_make_context, arcfour_free_context, arcfour_iv, arcfour256_key,
+ arcfour_block, arcfour_block,
+ "arcfour256",
+ 1, 256, 0, "Arcfour-256"
+};
+
+static const struct ssh2_cipher *const arcfour_list[] = {
+ &ssh_arcfour256_ssh2,
+ &ssh_arcfour128_ssh2,
+};
+
+const struct ssh2_ciphers ssh2_arcfour = {
+ sizeof(arcfour_list) / sizeof(*arcfour_list),
+ arcfour_list
+};
diff --git a/tools/plink/sshbn.c b/tools/plink/sshbn.c
index 5c1870876..e71940e31 100644..100755
--- a/tools/plink/sshbn.c
+++ b/tools/plink/sshbn.c
@@ -1,1918 +1,2001 @@
-/*
- * Bignum routines for RSA and DH and stuff.
- */
-
-#include <stdio.h>
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "misc.h"
-
-/*
- * 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.
- */
-
-#if 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))
-#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)
-#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)
-#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)
-#endif
-
-#define BIGNUM_INT_BYTES (BIGNUM_INT_BITS / 8)
-
-#define BIGNUM_INTERNAL
-typedef BignumInt *Bignum;
-
-#include "ssh.h"
-
-BignumInt bnZero[1] = { 0 };
-BignumInt bnOne[2] = { 1, 1 };
-
-/*
- * The Bignum format is an array of `BignumInt'. The first
- * element of the array counts the remaining elements. The
- * remaining elements express the actual number, base 2^BIGNUM_INT_BITS, _least_
- * significant digit first. (So it's trivial to extract the bit
- * with value 2^n for any n.)
- *
- * All Bignums in this module are positive. Negative numbers must
- * be dealt with outside it.
- *
- * INVARIANT: the most significant word of any Bignum must be
- * nonzero.
- */
-
-Bignum Zero = bnZero, One = bnOne;
-
-static Bignum newbn(int length)
-{
- Bignum b = snewn(length + 1, BignumInt);
- if (!b)
- abort(); /* FIXME */
- memset(b, 0, (length + 1) * sizeof(*b));
- b[0] = length;
- return b;
-}
-
-void bn_restore_invariant(Bignum b)
-{
- while (b[0] > 1 && b[b[0]] == 0)
- b[0]--;
-}
-
-Bignum copybn(Bignum orig)
-{
- Bignum b = snewn(orig[0] + 1, BignumInt);
- if (!b)
- abort(); /* FIXME */
- memcpy(b, orig, (orig[0] + 1) * sizeof(*b));
- return b;
-}
-
-void freebn(Bignum b)
-{
- /*
- * Burn the evidence, just in case.
- */
- memset(b, 0, sizeof(b[0]) * (b[0] + 1));
- sfree(b);
-}
-
-Bignum bn_power_2(int n)
-{
- Bignum ret = newbn(n / BIGNUM_INT_BITS + 1);
- bignum_set_bit(ret, n, 1);
- return ret;
-}
-
-/*
- * 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.
- */
-static BignumInt internal_add(const BignumInt *a, const BignumInt *b,
- BignumInt *c, int len)
-{
- int i;
- BignumDblInt carry = 0;
-
- for (i = len-1; i >= 0; i--) {
- carry += (BignumDblInt)a[i] + b[i];
- c[i] = (BignumInt)(carry&BIGNUM_INT_MASK);
- carry >>= BIGNUM_INT_BITS;
- }
-
- return (BignumInt)carry;
-}
-
-/*
- * Internal subtraction. Sets c = a - b, where 'a', 'b' and 'c' are
- * all big-endian arrays of 'len' BignumInts. Any borrow from the top
- * is ignored.
- */
-static void internal_sub(const BignumInt *a, const BignumInt *b,
- BignumInt *c, int len)
-{
- int i;
- BignumDblInt carry = 1;
-
- for (i = len-1; i >= 0; i--) {
- carry += (BignumDblInt)a[i] + (b[i] ^ BIGNUM_INT_MASK);
- c[i] = (BignumInt)(carry&BIGNUM_INT_MASK);
- carry >>= BIGNUM_INT_BITS;
- }
-}
-
-/*
- * Compute c = a * b.
- * Input is in the first len words of a and b.
- * Result is returned in the first 2*len words of c.
- *
- * 'scratch' must point to an array of BignumInt of size at least
- * mul_compute_scratch(len). (This covers the needs of internal_mul
- * and all its recursive calls to itself.)
- */
-#define KARATSUBA_THRESHOLD 50
-static int mul_compute_scratch(int len)
-{
- int ret = 0;
- while (len > KARATSUBA_THRESHOLD) {
- int toplen = len/2, botlen = len - toplen; /* botlen is the bigger */
- int midlen = botlen + 1;
- ret += 4*midlen;
- len = midlen;
- }
- return ret;
-}
-static void internal_mul(const BignumInt *a, const BignumInt *b,
- BignumInt *c, int len, BignumInt *scratch)
-{
- if (len > KARATSUBA_THRESHOLD) {
- int i;
-
- /*
- * Karatsuba divide-and-conquer algorithm. Cut each input in
- * half, so that it's expressed as two big 'digits' in a giant
- * base D:
- *
- * a = a_1 D + a_0
- * b = b_1 D + b_0
- *
- * Then the product is of course
- *
- * ab = a_1 b_1 D^2 + (a_1 b_0 + a_0 b_1) D + a_0 b_0
- *
- * and we compute the three coefficients by recursively
- * calling ourself to do half-length multiplications.
- *
- * The clever bit that makes this worth doing is that we only
- * need _one_ half-length multiplication for the central
- * coefficient rather than the two that it obviouly looks
- * like, because we can use a single multiplication to compute
- *
- * (a_1 + a_0) (b_1 + b_0) = a_1 b_1 + a_1 b_0 + a_0 b_1 + a_0 b_0
- *
- * and then we subtract the other two coefficients (a_1 b_1
- * and a_0 b_0) which we were computing anyway.
- *
- * Hence we get to multiply two numbers of length N in about
- * three times as much work as it takes to multiply numbers of
- * length N/2, which is obviously better than the four times
- * as much work it would take if we just did a long
- * conventional multiply.
- */
-
- int toplen = len/2, botlen = len - toplen; /* botlen is the bigger */
- int midlen = botlen + 1;
- BignumDblInt carry;
-#ifdef KARA_DEBUG
- int i;
-#endif
-
- /*
- * The coefficients a_1 b_1 and a_0 b_0 just avoid overlapping
- * in the output array, so we can compute them immediately in
- * place.
- */
-
-#ifdef KARA_DEBUG
- printf("a1,a0 = 0x");
- for (i = 0; i < len; i++) {
- if (i == toplen) printf(", 0x");
- printf("%0*x", BIGNUM_INT_BITS/4, a[i]);
- }
- printf("\n");
- printf("b1,b0 = 0x");
- for (i = 0; i < len; i++) {
- if (i == toplen) printf(", 0x");
- printf("%0*x", BIGNUM_INT_BITS/4, b[i]);
- }
- printf("\n");
-#endif
-
- /* a_1 b_1 */
- internal_mul(a, b, c, toplen, scratch);
-#ifdef KARA_DEBUG
- printf("a1b1 = 0x");
- for (i = 0; i < 2*toplen; i++) {
- printf("%0*x", BIGNUM_INT_BITS/4, c[i]);
- }
- printf("\n");
-#endif
-
- /* a_0 b_0 */
- internal_mul(a + toplen, b + toplen, c + 2*toplen, botlen, scratch);
-#ifdef KARA_DEBUG
- printf("a0b0 = 0x");
- for (i = 0; i < 2*botlen; i++) {
- printf("%0*x", BIGNUM_INT_BITS/4, c[2*toplen+i]);
- }
- printf("\n");
-#endif
-
- /* Zero padding. midlen exceeds toplen by at most 2, so just
- * zero the first two words of each input and the rest will be
- * copied over. */
- scratch[0] = scratch[1] = scratch[midlen] = scratch[midlen+1] = 0;
-
- for (i = 0; i < toplen; i++) {
- scratch[midlen - toplen + i] = a[i]; /* a_1 */
- scratch[2*midlen - toplen + i] = b[i]; /* b_1 */
- }
-
- /* compute a_1 + a_0 */
- scratch[0] = internal_add(scratch+1, a+toplen, scratch+1, botlen);
-#ifdef KARA_DEBUG
- printf("a1plusa0 = 0x");
- for (i = 0; i < midlen; i++) {
- printf("%0*x", BIGNUM_INT_BITS/4, scratch[i]);
- }
- printf("\n");
-#endif
- /* compute b_1 + b_0 */
- scratch[midlen] = internal_add(scratch+midlen+1, b+toplen,
- scratch+midlen+1, botlen);
-#ifdef KARA_DEBUG
- printf("b1plusb0 = 0x");
- for (i = 0; i < midlen; i++) {
- printf("%0*x", BIGNUM_INT_BITS/4, scratch[midlen+i]);
- }
- printf("\n");
-#endif
-
- /*
- * Now we can do the third multiplication.
- */
- internal_mul(scratch, scratch + midlen, scratch + 2*midlen, midlen,
- scratch + 4*midlen);
-#ifdef KARA_DEBUG
- printf("a1plusa0timesb1plusb0 = 0x");
- for (i = 0; i < 2*midlen; i++) {
- printf("%0*x", BIGNUM_INT_BITS/4, scratch[2*midlen+i]);
- }
- printf("\n");
-#endif
-
- /*
- * Now we can reuse the first half of 'scratch' to compute the
- * sum of the outer two coefficients, to subtract from that
- * product to obtain the middle one.
- */
- scratch[0] = scratch[1] = scratch[2] = scratch[3] = 0;
- for (i = 0; i < 2*toplen; i++)
- scratch[2*midlen - 2*toplen + i] = c[i];
- scratch[1] = internal_add(scratch+2, c + 2*toplen,
- scratch+2, 2*botlen);
-#ifdef KARA_DEBUG
- printf("a1b1plusa0b0 = 0x");
- for (i = 0; i < 2*midlen; i++) {
- printf("%0*x", BIGNUM_INT_BITS/4, scratch[i]);
- }
- printf("\n");
-#endif
-
- internal_sub(scratch + 2*midlen, scratch,
- scratch + 2*midlen, 2*midlen);
-#ifdef KARA_DEBUG
- printf("a1b0plusa0b1 = 0x");
- for (i = 0; i < 2*midlen; i++) {
- printf("%0*x", BIGNUM_INT_BITS/4, scratch[2*midlen+i]);
- }
- printf("\n");
-#endif
-
- /*
- * And now all we need to do is to add that middle coefficient
- * back into the output. We may have to propagate a carry
- * further up the output, but we can be sure it won't
- * propagate right the way off the top.
- */
- carry = internal_add(c + 2*len - botlen - 2*midlen,
- scratch + 2*midlen,
- c + 2*len - botlen - 2*midlen, 2*midlen);
- i = 2*len - botlen - 2*midlen - 1;
- while (carry) {
- assert(i >= 0);
- carry += c[i];
- c[i] = (BignumInt)carry;
- carry >>= BIGNUM_INT_BITS;
- i--;
- }
-#ifdef KARA_DEBUG
- printf("ab = 0x");
- for (i = 0; i < 2*len; i++) {
- printf("%0*x", BIGNUM_INT_BITS/4, c[i]);
- }
- printf("\n");
-#endif
-
- } else {
- int i;
- BignumInt carry;
- BignumDblInt t;
- const BignumInt *ap, *bp;
- BignumInt *cp, *cps;
-
- /*
- * Multiply in the ordinary O(N^2) way.
- */
-
- for (i = 0; i < 2 * len; i++)
- c[i] = 0;
-
- 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 & 0xffffffff);
- carry = (BignumInt)(t >> BIGNUM_INT_BITS);
- }
- *cp = carry;
- }
- }
-}
-
-/*
- * Variant form of internal_mul used for the initial step of
- * Montgomery reduction. Only bothers outputting 'len' words
- * (everything above that is thrown away).
- */
-static void internal_mul_low(const BignumInt *a, const BignumInt *b,
- BignumInt *c, int len, BignumInt *scratch)
-{
- if (len > KARATSUBA_THRESHOLD) {
- int i;
-
- /*
- * Karatsuba-aware version of internal_mul_low. As before, we
- * express each input value as a shifted combination of two
- * halves:
- *
- * a = a_1 D + a_0
- * b = b_1 D + b_0
- *
- * Then the full product is, as before,
- *
- * ab = a_1 b_1 D^2 + (a_1 b_0 + a_0 b_1) D + a_0 b_0
- *
- * Provided we choose D on the large side (so that a_0 and b_0
- * are _at least_ as long as a_1 and b_1), we don't need the
- * topmost term at all, and we only need half of the middle
- * term. So there's no point in doing the proper Karatsuba
- * optimisation which computes the middle term using the top
- * one, because we'd take as long computing the top one as
- * just computing the middle one directly.
- *
- * So instead, we do a much more obvious thing: we call the
- * fully optimised internal_mul to compute a_0 b_0, and we
- * recursively call ourself to compute the _bottom halves_ of
- * a_1 b_0 and a_0 b_1, each of which we add into the result
- * in the obvious way.
- *
- * In other words, there's no actual Karatsuba _optimisation_
- * in this function; the only benefit in doing it this way is
- * that we call internal_mul proper for a large part of the
- * work, and _that_ can optimise its operation.
- */
-
- int toplen = len/2, botlen = len - toplen; /* botlen is the bigger */
-
- /*
- * Scratch space for the various bits and pieces we're going
- * to be adding together: we need botlen*2 words for a_0 b_0
- * (though we may end up throwing away its topmost word), and
- * toplen words for each of a_1 b_0 and a_0 b_1. That adds up
- * to exactly 2*len.
- */
-
- /* a_0 b_0 */
- internal_mul(a + toplen, b + toplen, scratch + 2*toplen, botlen,
- scratch + 2*len);
-
- /* a_1 b_0 */
- internal_mul_low(a, b + len - toplen, scratch + toplen, toplen,
- scratch + 2*len);
-
- /* a_0 b_1 */
- internal_mul_low(a + len - toplen, b, scratch, toplen,
- scratch + 2*len);
-
- /* Copy the bottom half of the big coefficient into place */
- for (i = 0; i < botlen; i++)
- c[toplen + i] = scratch[2*toplen + botlen + i];
-
- /* Add the two small coefficients, throwing away the returned carry */
- internal_add(scratch, scratch + toplen, scratch, toplen);
-
- /* And add that to the large coefficient, leaving the result in c. */
- internal_add(scratch, scratch + 2*toplen + botlen - toplen,
- c, toplen);
-
- } else {
- int i;
- BignumInt carry;
- BignumDblInt t;
- const BignumInt *ap, *bp;
- BignumInt *cp, *cps;
-
- /*
- * Multiply in the ordinary O(N^2) way.
- */
-
- for (i = 0; i < len; i++)
- c[i] = 0;
-
- 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&BIGNUM_INT_MASK);
- carry = (BignumInt)(t >> BIGNUM_INT_BITS);
- }
- }
- }
-}
-
-/*
- * Montgomery reduction. Expects x to be a big-endian array of 2*len
- * BignumInts whose value satisfies 0 <= x < rn (where r = 2^(len *
- * BIGNUM_INT_BITS) is the Montgomery base). Returns in the same array
- * a value x' which is congruent to xr^{-1} mod n, and satisfies 0 <=
- * x' < n.
- *
- * 'n' and 'mninv' should be big-endian arrays of 'len' BignumInts
- * each, containing respectively n and the multiplicative inverse of
- * -n mod r.
- *
- * 'tmp' is an array of BignumInt used as scratch space, of length at
- * least 3*len + mul_compute_scratch(len).
- */
-static void monty_reduce(BignumInt *x, const BignumInt *n,
- const BignumInt *mninv, BignumInt *tmp, int len)
-{
- int i;
- BignumInt carry;
-
- /*
- * Multiply x by (-n)^{-1} mod r. This gives us a value m such
- * that mn is congruent to -x mod r. Hence, mn+x is an exact
- * multiple of r, and is also (obviously) congruent to x mod n.
- */
- internal_mul_low(x + len, mninv, tmp, len, tmp + 3*len);
-
- /*
- * Compute t = (mn+x)/r in ordinary, non-modular, integer
- * arithmetic. By construction this is exact, and is congruent mod
- * n to x * r^{-1}, i.e. the answer we want.
- *
- * The following multiply leaves that answer in the _most_
- * significant half of the 'x' array, so then we must shift it
- * down.
- */
- internal_mul(tmp, n, tmp+len, len, tmp + 3*len);
- carry = internal_add(x, tmp+len, x, 2*len);
- for (i = 0; i < len; i++)
- x[len + i] = x[i], x[i] = 0;
-
- /*
- * Reduce t mod n. This doesn't require a full-on division by n,
- * but merely a test and single optional subtraction, since we can
- * show that 0 <= t < 2n.
- *
- * Proof:
- * + we computed m mod r, so 0 <= m < r.
- * + so 0 <= mn < rn, obviously
- * + hence we only need 0 <= x < rn to guarantee that 0 <= mn+x < 2rn
- * + yielding 0 <= (mn+x)/r < 2n as required.
- */
- if (!carry) {
- for (i = 0; i < len; i++)
- if (x[len + i] != n[i])
- break;
- }
- if (carry || i >= len || x[len + i] > n[i])
- internal_sub(x+len, n, x+len, len);
-}
-
-static void internal_add_shifted(BignumInt *number,
- unsigned n, int shift)
-{
- int word = 1 + (shift / BIGNUM_INT_BITS);
- int bshift = shift % BIGNUM_INT_BITS;
- BignumDblInt addend;
-
- addend = (BignumDblInt)n << bshift;
-
- while (addend) {
- addend += number[word];
- number[word] = (BignumInt) (addend & BIGNUM_INT_MASK);
- addend >>= BIGNUM_INT_BITS;
- word++;
- }
-}
-
-/*
- * 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.
- */
-static void internal_mod(BignumInt *a, int alen,
- BignumInt *m, int mlen,
- BignumInt *quot, int qshift)
-{
- BignumInt m0, m1;
- unsigned int h;
- int i, k;
-
- m0 = m[0];
- if (mlen > 1)
- m1 = m[1];
- else
- m1 = 0;
-
- for (i = 0; i <= alen - mlen; i++) {
- BignumDblInt t;
- unsigned int q, r, c, ai1;
-
- if (i == 0) {
- h = 0;
- } else {
- h = a[i - 1];
- a[i - 1] = 0;
- }
-
- 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--;
- }
- }
-
- /* Subtract q * m from a[i...] */
- c = 0;
- for (k = mlen - 1; k >= 0; k--) {
- t = MUL_WORD(q, m[k]);
- t += c;
- c = (unsigned)(t >> BIGNUM_INT_BITS);
- if (((BignumInt)(t&0xffffffff)) > a[i + k])
- c++;
- a[i + k] -= (BignumInt) (t&0xffffffff);
- }
-
- /* 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));
- }
-}
-
-/*
- * Compute (base ^ exp) % mod, the pedestrian way.
- */
-Bignum modpow_simple(Bignum base_in, Bignum exp, Bignum mod)
-{
- BignumInt *a, *b, *n, *m, *scratch;
- int mshift;
- int mlen, scratchlen, i, j;
- Bignum base, result;
-
- /*
- * The most significant word of mod needs to be non-zero. It
- * should already be, but let's make sure.
- */
- assert(mod[mod[0]] != 0);
-
- /*
- * Make sure the base is smaller than the modulus, by reducing
- * it modulo the modulus if not.
- */
- base = bigmod(base_in, mod);
-
- /* Allocate m of size mlen, copy mod to m */
- /* We use big endian internally */
- mlen = mod[0];
- m = snewn(mlen, BignumInt);
- 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];
- for (j = 0; j < i; j++)
- n[j] = 0;
- for (j = 0; j < (int)base[0]; j++)
- n[i + j] = base[base[0] - j];
-
- /* Allocate a and b of size 2*mlen. Set a = 1 */
- a = snewn(2 * mlen, BignumInt);
- b = snewn(2 * mlen, BignumInt);
- for (i = 0; i < 2 * mlen; i++)
- a[i] = 0;
- a[2 * mlen - 1] = 1;
-
- /* Scratch space for multiplies */
- scratchlen = mul_compute_scratch(mlen);
- scratch = snewn(scratchlen, BignumInt);
-
- /* Skip leading zero bits of exp. */
- i = 0;
- j = BIGNUM_INT_BITS-1;
- while (i < (int)exp[0] && (exp[exp[0] - i] & (1 << j)) == 0) {
- j--;
- if (j < 0) {
- i++;
- j = BIGNUM_INT_BITS-1;
- }
- }
-
- /* 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);
- if ((exp[exp[0] - i] & (1 << j)) != 0) {
- internal_mul(b + mlen, n, a, mlen, scratch);
- internal_mod(a, mlen * 2, m, mlen, NULL, 0);
- } else {
- BignumInt *t;
- t = a;
- a = b;
- b = t;
- }
- j--;
- }
- i++;
- 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++)
- result[result[0] - i] = a[i + mlen];
- while (result[0] > 1 && result[result[0]] == 0)
- result[0]--;
-
- /* Free temporary arrays */
- for (i = 0; i < 2 * mlen; i++)
- a[i] = 0;
- sfree(a);
- for (i = 0; i < scratchlen; i++)
- scratch[i] = 0;
- sfree(scratch);
- for (i = 0; i < 2 * mlen; i++)
- b[i] = 0;
- sfree(b);
- for (i = 0; i < mlen; i++)
- m[i] = 0;
- sfree(m);
- for (i = 0; i < mlen; i++)
- n[i] = 0;
- sfree(n);
-
- freebn(base);
-
- return result;
-}
-
-/*
- * Compute (base ^ exp) % mod. Uses the Montgomery multiplication
- * technique where possible, falling back to modpow_simple otherwise.
- */
-Bignum modpow(Bignum base_in, Bignum exp, Bignum mod)
-{
- BignumInt *a, *b, *x, *n, *mninv, *scratch;
- int len, scratchlen, i, j;
- Bignum base, base2, r, rn, inv, result;
-
- /*
- * The most significant word of mod needs to be non-zero. It
- * should already be, but let's make sure.
- */
- assert(mod[mod[0]] != 0);
-
- /*
- * mod had better be odd, or we can't do Montgomery multiplication
- * using a power of two at all.
- */
- if (!(mod[1] & 1))
- return modpow_simple(base_in, exp, mod);
-
- /*
- * Make sure the base is smaller than the modulus, by reducing
- * it modulo the modulus if not.
- */
- base = bigmod(base_in, mod);
-
- /*
- * Compute the inverse of n mod r, for monty_reduce. (In fact we
- * want the inverse of _minus_ n mod r, but we'll sort that out
- * below.)
- */
- len = mod[0];
- r = bn_power_2(BIGNUM_INT_BITS * len);
- inv = modinv(mod, r);
-
- /*
- * Multiply the base by r mod n, to get it into Montgomery
- * representation.
- */
- base2 = modmul(base, r, mod);
- freebn(base);
- base = base2;
-
- rn = bigmod(r, mod); /* r mod n, i.e. Montgomerified 1 */
-
- freebn(r); /* won't need this any more */
-
- /*
- * Set up internal arrays of the right lengths, in big-endian
- * format, containing the base, the modulus, and the modulus's
- * inverse.
- */
- n = snewn(len, BignumInt);
- for (j = 0; j < len; j++)
- n[len - 1 - j] = mod[j + 1];
-
- mninv = snewn(len, BignumInt);
- for (j = 0; j < len; j++)
- mninv[len - 1 - j] = (j < (int)inv[0] ? inv[j + 1] : 0);
- freebn(inv); /* we don't need this copy of it any more */
- /* Now negate mninv mod r, so it's the inverse of -n rather than +n. */
- x = snewn(len, BignumInt);
- for (j = 0; j < len; j++)
- x[j] = 0;
- internal_sub(x, mninv, mninv, len);
-
- /* x = snewn(len, BignumInt); */ /* already done above */
- for (j = 0; j < len; j++)
- x[len - 1 - j] = (j < (int)base[0] ? base[j + 1] : 0);
- freebn(base); /* we don't need this copy of it any more */
-
- a = snewn(2*len, BignumInt);
- b = snewn(2*len, BignumInt);
- for (j = 0; j < len; j++)
- a[2*len - 1 - j] = (j < (int)rn[0] ? rn[j + 1] : 0);
- freebn(rn);
-
- /* Scratch space for multiplies */
- scratchlen = 3*len + mul_compute_scratch(len);
- scratch = snewn(scratchlen, BignumInt);
-
- /* Skip leading zero bits of exp. */
- i = 0;
- j = BIGNUM_INT_BITS-1;
- while (i < (int)exp[0] && (exp[exp[0] - i] & (1 << j)) == 0) {
- j--;
- if (j < 0) {
- i++;
- j = BIGNUM_INT_BITS-1;
- }
- }
-
- /* Main computation */
- while (i < (int)exp[0]) {
- while (j >= 0) {
- internal_mul(a + len, a + len, b, len, scratch);
- monty_reduce(b, n, mninv, scratch, len);
- if ((exp[exp[0] - i] & (1 << j)) != 0) {
- internal_mul(b + len, x, a, len, scratch);
- monty_reduce(a, n, mninv, scratch, len);
- } else {
- BignumInt *t;
- t = a;
- a = b;
- b = t;
- }
- j--;
- }
- i++;
- j = BIGNUM_INT_BITS-1;
- }
-
- /*
- * Final monty_reduce to get back from the adjusted Montgomery
- * representation.
- */
- monty_reduce(a, n, mninv, scratch, len);
-
- /* Copy result to buffer */
- result = newbn(mod[0]);
- for (i = 0; i < len; i++)
- result[result[0] - i] = a[i + len];
- while (result[0] > 1 && result[result[0]] == 0)
- result[0]--;
-
- /* Free temporary arrays */
- for (i = 0; i < scratchlen; i++)
- scratch[i] = 0;
- sfree(scratch);
- for (i = 0; i < 2 * len; i++)
- a[i] = 0;
- sfree(a);
- for (i = 0; i < 2 * len; i++)
- b[i] = 0;
- sfree(b);
- for (i = 0; i < len; i++)
- mninv[i] = 0;
- sfree(mninv);
- for (i = 0; i < len; i++)
- n[i] = 0;
- sfree(n);
- for (i = 0; i < len; i++)
- x[i] = 0;
- sfree(x);
-
- return result;
-}
-
-/*
- * Compute (p * q) % mod.
- * The most significant word of mod MUST be non-zero.
- * We assume that the result array is the same size as the mod array.
- */
-Bignum modmul(Bignum p, Bignum q, Bignum mod)
-{
- BignumInt *a, *n, *m, *o, *scratch;
- int mshift, scratchlen;
- int pqlen, mlen, rlen, i, j;
- Bignum result;
-
- /* Allocate m of size mlen, copy mod to m */
- /* We use big endian internally */
- mlen = mod[0];
- m = snewn(mlen, BignumInt);
- 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]);
-
- /* Allocate n of size pqlen, copy p to n */
- n = snewn(pqlen, BignumInt);
- i = pqlen - p[0];
- for (j = 0; j < i; j++)
- n[j] = 0;
- for (j = 0; j < (int)p[0]; j++)
- n[i + j] = p[p[0] - j];
-
- /* Allocate o of size pqlen, copy q to o */
- o = snewn(pqlen, BignumInt);
- i = pqlen - q[0];
- for (j = 0; j < i; j++)
- o[j] = 0;
- for (j = 0; j < (int)q[0]; j++)
- o[i + j] = q[q[0] - j];
-
- /* Allocate a of size 2*pqlen for result */
- a = snewn(2 * pqlen, BignumInt);
-
- /* Scratch space for multiplies */
- scratchlen = mul_compute_scratch(pqlen);
- scratch = snewn(scratchlen, BignumInt);
-
- /* 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));
- }
-
- /* Copy result to buffer */
- rlen = (mlen < pqlen * 2 ? mlen : pqlen * 2);
- result = newbn(rlen);
- for (i = 0; i < rlen; i++)
- result[result[0] - i] = a[i + 2 * pqlen - rlen];
- while (result[0] > 1 && result[result[0]] == 0)
- result[0]--;
-
- /* Free temporary arrays */
- for (i = 0; i < scratchlen; i++)
- scratch[i] = 0;
- sfree(scratch);
- for (i = 0; i < 2 * pqlen; i++)
- a[i] = 0;
- sfree(a);
- for (i = 0; i < mlen; i++)
- m[i] = 0;
- sfree(m);
- for (i = 0; i < pqlen; i++)
- n[i] = 0;
- sfree(n);
- for (i = 0; i < pqlen; i++)
- o[i] = 0;
- sfree(o);
-
- return result;
-}
-
-/*
- * Compute p % mod.
- * The most significant word of mod MUST be non-zero.
- * We assume that the result array is the same size as the mod array.
- * We optionally write out a quotient if `quotient' is non-NULL.
- * We can avoid writing out the result if `result' is NULL.
- */
-static void bigdivmod(Bignum p, Bignum mod, Bignum result, Bignum quotient)
-{
- BignumInt *n, *m;
- int mshift;
- int plen, mlen, i, j;
-
- /* Allocate m of size mlen, copy mod to m */
- /* We use big endian internally */
- mlen = mod[0];
- m = snewn(mlen, BignumInt);
- 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)
- plen = mlen + 1;
-
- /* Allocate n of size plen, copy p to n */
- n = snewn(plen, BignumInt);
- for (j = 0; j < plen; j++)
- n[j] = 0;
- 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));
- }
-
- /* Copy result to buffer */
- if (result) {
- for (i = 1; i <= (int)result[0]; i++) {
- int j = plen - i;
- result[i] = j >= 0 ? n[j] : 0;
- }
- }
-
- /* Free temporary arrays */
- for (i = 0; i < mlen; i++)
- m[i] = 0;
- sfree(m);
- for (i = 0; i < plen; i++)
- n[i] = 0;
- sfree(n);
-}
-
-/*
- * Decrement a number.
- */
-void decbn(Bignum bn)
-{
- int i = 1;
- while (i < (int)bn[0] && bn[i] == 0)
- bn[i++] = BIGNUM_INT_MASK;
- bn[i]--;
-}
-
-Bignum bignum_from_bytes(const unsigned char *data, int nbytes)
-{
- Bignum result;
- int w, i;
-
- 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 = nbytes; i--;) {
- unsigned char byte = *data++;
- result[1 + i / BIGNUM_INT_BYTES] |= byte << (8*i % BIGNUM_INT_BITS);
- }
-
- while (result[0] > 1 && result[result[0]] == 0)
- result[0]--;
- return result;
-}
-
-/*
- * Read an SSH-1-format bignum from a data buffer. Return the number
- * of bytes consumed, or -1 if there wasn't enough data.
- */
-int ssh1_read_bignum(const unsigned char *data, int len, Bignum * result)
-{
- const unsigned char *p = data;
- int i;
- int w, b;
-
- if (len < 2)
- return -1;
-
- w = 0;
- for (i = 0; i < 2; i++)
- w = (w << 8) + *p++;
- b = (w + 7) / 8; /* bits -> bytes */
-
- if (len < b+2)
- return -1;
-
- if (!result) /* just return length */
- return b + 2;
-
- *result = bignum_from_bytes(p, b);
-
- return p + b - data;
-}
-
-/*
- * Return the bit count of a bignum, for SSH-1 encoding.
- */
-int bignum_bitcount(Bignum bn)
-{
- int bitcount = bn[0] * BIGNUM_INT_BITS - 1;
- while (bitcount >= 0
- && (bn[bitcount / BIGNUM_INT_BITS + 1] >> (bitcount % BIGNUM_INT_BITS)) == 0) bitcount--;
- return bitcount + 1;
-}
-
-/*
- * Return the byte length of a bignum when SSH-1 encoded.
- */
-int ssh1_bignum_length(Bignum bn)
-{
- return 2 + (bignum_bitcount(bn) + 7) / 8;
-}
-
-/*
- * Return the byte length of a bignum when SSH-2 encoded.
- */
-int ssh2_bignum_length(Bignum bn)
-{
- return 4 + (bignum_bitcount(bn) + 8) / 8;
-}
-
-/*
- * Return a byte from a bignum; 0 is least significant, etc.
- */
-int bignum_byte(Bignum bn, int i)
-{
- if (i >= (int)(BIGNUM_INT_BYTES * bn[0]))
- return 0; /* beyond the end */
- else
- return (bn[i / BIGNUM_INT_BYTES + 1] >>
- ((i % BIGNUM_INT_BYTES)*8)) & 0xFF;
-}
-
-/*
- * Return a bit from a bignum; 0 is least significant, etc.
- */
-int bignum_bit(Bignum bn, int i)
-{
- if (i >= (int)(BIGNUM_INT_BITS * bn[0]))
- return 0; /* beyond the end */
- else
- return (bn[i / BIGNUM_INT_BITS + 1] >> (i % BIGNUM_INT_BITS)) & 1;
-}
-
-/*
- * Set a bit in a bignum; 0 is least significant, etc.
- */
-void bignum_set_bit(Bignum bn, int bitnum, int value)
-{
- if (bitnum >= (int)(BIGNUM_INT_BITS * bn[0]))
- abort(); /* beyond the end */
- else {
- int v = bitnum / BIGNUM_INT_BITS + 1;
- int mask = 1 << (bitnum % BIGNUM_INT_BITS);
- if (value)
- bn[v] |= mask;
- else
- bn[v] &= ~mask;
- }
-}
-
-/*
- * Write a SSH-1-format bignum into a buffer. It is assumed the
- * buffer is big enough. Returns the number of bytes used.
- */
-int ssh1_write_bignum(void *data, Bignum bn)
-{
- unsigned char *p = data;
- int len = ssh1_bignum_length(bn);
- int i;
- int bitc = bignum_bitcount(bn);
-
- *p++ = (bitc >> 8) & 0xFF;
- *p++ = (bitc) & 0xFF;
- for (i = len - 2; i--;)
- *p++ = bignum_byte(bn, i);
- return len;
-}
-
-/*
- * Compare two bignums. Returns like strcmp.
- */
-int bignum_cmp(Bignum a, Bignum b)
-{
- int amax = a[0], bmax = b[0];
- int i = (amax > bmax ? amax : bmax);
- while (i) {
- BignumInt aval = (i > amax ? 0 : a[i]);
- BignumInt bval = (i > bmax ? 0 : b[i]);
- if (aval < bval)
- return -1;
- if (aval > bval)
- return +1;
- i--;
- }
- return 0;
-}
-
-/*
- * Right-shift one bignum to form another.
- */
-Bignum bignum_rshift(Bignum a, int shift)
-{
- Bignum ret;
- int i, shiftw, shiftb, shiftbb, bits;
- BignumInt ai, ai1;
-
- bits = bignum_bitcount(a) - shift;
- ret = newbn((bits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS);
-
- if (ret) {
- shiftw = shift / BIGNUM_INT_BITS;
- shiftb = shift % BIGNUM_INT_BITS;
- shiftbb = BIGNUM_INT_BITS - shiftb;
-
- ai1 = a[shiftw + 1];
- for (i = 1; i <= (int)ret[0]; i++) {
- ai = ai1;
- ai1 = (i + shiftw + 1 <= (int)a[0] ? a[i + shiftw + 1] : 0);
- ret[i] = ((ai >> shiftb) | (ai1 << shiftbb)) & BIGNUM_INT_MASK;
- }
- }
-
- return ret;
-}
-
-/*
- * Non-modular multiplication and addition.
- */
-Bignum bigmuladd(Bignum a, Bignum b, Bignum addend)
-{
- int alen = a[0], blen = b[0];
- int mlen = (alen > blen ? alen : blen);
- int rlen, i, maxspot;
- int wslen;
- BignumInt *workspace;
- Bignum ret;
-
- /* mlen space for a, mlen space for b, 2*mlen for result,
- * plus scratch space for multiplication */
- wslen = mlen * 4 + mul_compute_scratch(mlen);
- workspace = snewn(wslen, BignumInt);
- for (i = 0; i < mlen; i++) {
- workspace[0 * mlen + i] = (mlen - i <= (int)a[0] ? a[mlen - i] : 0);
- workspace[1 * mlen + i] = (mlen - i <= (int)b[0] ? b[mlen - i] : 0);
- }
-
- internal_mul(workspace + 0 * mlen, workspace + 1 * mlen,
- workspace + 2 * mlen, mlen, workspace + 4 * mlen);
-
- /* now just copy the result back */
- rlen = alen + blen + 1;
- if (addend && rlen <= (int)addend[0])
- rlen = addend[0] + 1;
- ret = newbn(rlen);
- maxspot = 0;
- for (i = 1; i <= (int)ret[0]; i++) {
- ret[i] = (i <= 2 * mlen ? workspace[4 * mlen - i] : 0);
- if (ret[i] != 0)
- maxspot = i;
- }
- ret[0] = maxspot;
-
- /* now add in the addend, if any */
- if (addend) {
- BignumDblInt 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;
- if (ret[i] != 0 && i > maxspot)
- maxspot = i;
- }
- }
- ret[0] = maxspot;
-
- for (i = 0; i < wslen; i++)
- workspace[i] = 0;
- sfree(workspace);
- return ret;
-}
-
-/*
- * Non-modular multiplication.
- */
-Bignum bigmul(Bignum a, Bignum b)
-{
- return bigmuladd(a, b, NULL);
-}
-
-/*
- * Simple addition.
- */
-Bignum bigadd(Bignum a, Bignum b)
-{
- int alen = a[0], blen = b[0];
- int rlen = (alen > blen ? alen : blen) + 1;
- int i, maxspot;
- Bignum ret;
- BignumDblInt 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;
- if (ret[i] != 0 && i > maxspot)
- maxspot = i;
- }
- ret[0] = maxspot;
-
- return ret;
-}
-
-/*
- * Subtraction. Returns a-b, or NULL if the result would come out
- * negative (recall that this entire bignum module only handles
- * positive numbers).
- */
-Bignum bigsub(Bignum a, Bignum b)
-{
- int alen = a[0], blen = b[0];
- int rlen = (alen > blen ? alen : blen);
- int i, maxspot;
- Bignum ret;
- BignumDblInt 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;
- if (ret[i] != 0 && i > maxspot)
- maxspot = i;
- }
- ret[0] = maxspot;
-
- if (!carry) {
- freebn(ret);
- return NULL;
- }
-
- return ret;
-}
-
-/*
- * Create a bignum which is the bitmask covering another one. That
- * is, the smallest integer which is >= N and is also one less than
- * a power of two.
- */
-Bignum bignum_bitmask(Bignum n)
-{
- Bignum ret = copybn(n);
- int i;
- BignumInt j;
-
- i = ret[0];
- while (n[i] == 0 && i > 0)
- i--;
- if (i <= 0)
- return ret; /* input was zero */
- j = 1;
- while (j < n[i])
- j = 2 * j + 1;
- ret[i] = j;
- while (--i > 0)
- ret[i] = BIGNUM_INT_MASK;
- return ret;
-}
-
-/*
- * Convert a (max 32-bit) long into a bignum.
- */
-Bignum bignum_from_long(unsigned long nn)
-{
- Bignum ret;
- BignumDblInt n = nn;
-
- 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 ret = newbn(number[0] + 1);
- int i, maxspot = 0;
- BignumDblInt carry = 0, addend = addendx;
-
- 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;
- }
- ret[0] = maxspot;
- return ret;
-}
-
-/*
- * Compute the residue of a bignum, modulo a (max 16-bit) short.
- */
-unsigned short bignum_mod_short(Bignum number, unsigned short modulus)
-{
- BignumDblInt mod, r;
- int i;
-
- r = 0;
- mod = modulus;
- for (i = number[0]; i > 0; i--)
- r = (r * (BIGNUM_TOP_BIT % mod) * 2 + number[i] % mod) % mod;
- return (unsigned short) r;
-}
-
-#ifdef DEBUG
-void diagbn(char *prefix, Bignum md)
-{
- int i, nibbles, morenibbles;
- static const char hex[] = "0123456789ABCDEF";
-
- debug(("%s0x", prefix ? prefix : ""));
-
- nibbles = (3 + bignum_bitcount(md)) / 4;
- if (nibbles < 1)
- nibbles = 1;
- morenibbles = 4 * md[0] - nibbles;
- for (i = 0; i < morenibbles; i++)
- debug(("-"));
- for (i = nibbles; i--;)
- debug(("%c",
- hex[(bignum_byte(md, i / 2) >> (4 * (i % 2))) & 0xF]));
-
- if (prefix)
- debug(("\n"));
-}
-#endif
-
-/*
- * Simple division.
- */
-Bignum bigdiv(Bignum a, Bignum b)
-{
- Bignum q = newbn(a[0]);
- bigdivmod(a, b, NULL, q);
- return q;
-}
-
-/*
- * Simple remainder.
- */
-Bignum bigmod(Bignum a, Bignum b)
-{
- Bignum r = newbn(b[0]);
- bigdivmod(a, b, r, NULL);
- return r;
-}
-
-/*
- * Greatest common divisor.
- */
-Bignum biggcd(Bignum av, Bignum bv)
-{
- Bignum a = copybn(av);
- Bignum b = copybn(bv);
-
- while (bignum_cmp(b, Zero) != 0) {
- Bignum t = newbn(b[0]);
- bigdivmod(a, b, t, NULL);
- while (t[0] > 1 && t[t[0]] == 0)
- t[0]--;
- freebn(a);
- a = b;
- b = t;
- }
-
- freebn(b);
- return a;
-}
-
-/*
- * Modular inverse, using Euclid's extended algorithm.
- */
-Bignum modinv(Bignum number, Bignum modulus)
-{
- Bignum a = copybn(modulus);
- Bignum b = copybn(number);
- Bignum xp = copybn(Zero);
- Bignum x = copybn(One);
- int sign = +1;
-
- while (bignum_cmp(b, One) != 0) {
- Bignum t = newbn(b[0]);
- Bignum q = newbn(a[0]);
- bigdivmod(a, b, t, q);
- while (t[0] > 1 && t[t[0]] == 0)
- t[0]--;
- freebn(a);
- a = b;
- b = t;
- t = xp;
- xp = x;
- x = bigmuladd(q, xp, t);
- sign = -sign;
- freebn(t);
- freebn(q);
- }
-
- freebn(b);
- freebn(a);
- freebn(xp);
-
- /* now we know that sign * x == 1, and that x < modulus */
- if (sign < 0) {
- /* set a new x to be modulus - x */
- Bignum newx = newbn(modulus[0]);
- BignumInt carry = 0;
- int maxspot = 1;
- int i;
-
- for (i = 1; i <= (int)newx[0]; i++) {
- BignumInt aword = (i <= (int)modulus[0] ? modulus[i] : 0);
- BignumInt bword = (i <= (int)x[0] ? x[i] : 0);
- newx[i] = aword - bword - carry;
- bword = ~bword;
- carry = carry ? (newx[i] >= bword) : (newx[i] > bword);
- if (newx[i] != 0)
- maxspot = i;
- }
- newx[0] = maxspot;
- freebn(x);
- x = newx;
- }
-
- /* and return. */
- return x;
-}
-
-/*
- * Render a bignum into decimal. Return a malloced string holding
- * the decimal representation.
- */
-char *bignum_decimal(Bignum x)
-{
- int ndigits, ndigit;
- int i, iszero;
- BignumDblInt carry;
- char *ret;
- BignumInt *workspace;
-
- /*
- * First, estimate the number of digits. Since log(10)/log(2)
- * is just greater than 93/28 (the joys of continued fraction
- * approximations...) we know that for every 93 bits, we need
- * at most 28 digits. This will tell us how much to malloc.
- *
- * Formally: if x has i bits, that means x is strictly less
- * than 2^i. Since 2 is less than 10^(28/93), this is less than
- * 10^(28i/93). We need an integer power of ten, so we must
- * round up (rounding down might make it less than x again).
- * Therefore if we multiply the bit count by 28/93, rounding
- * up, we will have enough digits.
- *
- * i=0 (i.e., x=0) is an irritating special case.
- */
- i = bignum_bitcount(x);
- if (!i)
- ndigits = 1; /* x = 0 */
- else
- ndigits = (28 * i + 92) / 93; /* multiply by 28/93 and round up */
- ndigits++; /* allow for trailing \0 */
- ret = snewn(ndigits, char);
-
- /*
- * Now allocate some workspace to hold the binary form as we
- * repeatedly divide it by ten. Initialise this to the
- * big-endian form of the number.
- */
- workspace = snewn(x[0], BignumInt);
- for (i = 0; i < (int)x[0]; i++)
- workspace[i] = x[x[0] - i];
-
- /*
- * Next, write the decimal number starting with the last digit.
- * We use ordinary short division, dividing 10 into the
- * workspace.
- */
- ndigit = ndigits - 1;
- ret[ndigit] = '\0';
- do {
- iszero = 1;
- carry = 0;
- for (i = 0; i < (int)x[0]; i++) {
- carry = (carry << BIGNUM_INT_BITS) + workspace[i];
- workspace[i] = (BignumInt) (carry / 10);
- if (workspace[i])
- iszero = 0;
- carry %= 10;
- }
- ret[--ndigit] = (char) (carry + '0');
- } while (!iszero);
-
- /*
- * There's a chance we've fallen short of the start of the
- * string. Correct if so.
- */
- if (ndigit > 0)
- memmove(ret, ret + ndigit, ndigits - ndigit);
-
- /*
- * Done.
- */
- sfree(workspace);
- return ret;
-}
-
-#ifdef TESTBN
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <ctype.h>
-
-/*
- * gcc -g -O0 -DTESTBN -o testbn sshbn.c misc.c -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);
-}
-
-#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);
- 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, "pow")) {
- Bignum base, expt, modulus, expected, answer;
-
- if (ptrnum != 4) {
- printf("%d: mul with %d parameters, expected 3\n", line);
- 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
+/*
+ * Bignum routines for RSA and DH and stuff.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include "misc.h"
+
+/*
+ * 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.
+ */
+
+#if 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))
+#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)
+#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)
+#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)
+#endif
+
+#define BIGNUM_INT_BYTES (BIGNUM_INT_BITS / 8)
+
+#define BIGNUM_INTERNAL
+typedef BignumInt *Bignum;
+
+#include "ssh.h"
+
+BignumInt bnZero[1] = { 0 };
+BignumInt bnOne[2] = { 1, 1 };
+
+/*
+ * The Bignum format is an array of `BignumInt'. The first
+ * element of the array counts the remaining elements. The
+ * remaining elements express the actual number, base 2^BIGNUM_INT_BITS, _least_
+ * significant digit first. (So it's trivial to extract the bit
+ * with value 2^n for any n.)
+ *
+ * All Bignums in this module are positive. Negative numbers must
+ * be dealt with outside it.
+ *
+ * INVARIANT: the most significant word of any Bignum must be
+ * nonzero.
+ */
+
+Bignum Zero = bnZero, One = bnOne;
+
+static Bignum newbn(int length)
+{
+ Bignum b;
+
+ 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;
+}
+
+void bn_restore_invariant(Bignum b)
+{
+ while (b[0] > 1 && b[b[0]] == 0)
+ b[0]--;
+}
+
+Bignum copybn(Bignum orig)
+{
+ Bignum b = snewn(orig[0] + 1, BignumInt);
+ if (!b)
+ abort(); /* FIXME */
+ memcpy(b, orig, (orig[0] + 1) * sizeof(*b));
+ return b;
+}
+
+void freebn(Bignum b)
+{
+ /*
+ * Burn the evidence, just in case.
+ */
+ smemclr(b, sizeof(b[0]) * (b[0] + 1));
+ sfree(b);
+}
+
+Bignum bn_power_2(int n)
+{
+ Bignum ret;
+
+ assert(n >= 0);
+
+ ret = newbn(n / BIGNUM_INT_BITS + 1);
+ bignum_set_bit(ret, n, 1);
+ return ret;
+}
+
+/*
+ * 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.
+ */
+static BignumInt internal_add(const BignumInt *a, const BignumInt *b,
+ BignumInt *c, int len)
+{
+ int i;
+ BignumDblInt carry = 0;
+
+ for (i = len-1; i >= 0; i--) {
+ carry += (BignumDblInt)a[i] + b[i];
+ c[i] = (BignumInt)(carry&BIGNUM_INT_MASK);
+ carry >>= BIGNUM_INT_BITS;
+ }
+
+ return (BignumInt)carry;
+}
+
+/*
+ * Internal subtraction. Sets c = a - b, where 'a', 'b' and 'c' are
+ * all big-endian arrays of 'len' BignumInts. Any borrow from the top
+ * is ignored.
+ */
+static void internal_sub(const BignumInt *a, const BignumInt *b,
+ BignumInt *c, int len)
+{
+ int i;
+ BignumDblInt carry = 1;
+
+ for (i = len-1; i >= 0; i--) {
+ carry += (BignumDblInt)a[i] + (b[i] ^ BIGNUM_INT_MASK);
+ c[i] = (BignumInt)(carry&BIGNUM_INT_MASK);
+ carry >>= BIGNUM_INT_BITS;
+ }
+}
+
+/*
+ * Compute c = a * b.
+ * Input is in the first len words of a and b.
+ * Result is returned in the first 2*len words of c.
+ *
+ * 'scratch' must point to an array of BignumInt of size at least
+ * mul_compute_scratch(len). (This covers the needs of internal_mul
+ * and all its recursive calls to itself.)
+ */
+#define KARATSUBA_THRESHOLD 50
+static int mul_compute_scratch(int len)
+{
+ int ret = 0;
+ while (len > KARATSUBA_THRESHOLD) {
+ int toplen = len/2, botlen = len - toplen; /* botlen is the bigger */
+ int midlen = botlen + 1;
+ ret += 4*midlen;
+ len = midlen;
+ }
+ return ret;
+}
+static void internal_mul(const BignumInt *a, const BignumInt *b,
+ BignumInt *c, int len, BignumInt *scratch)
+{
+ if (len > KARATSUBA_THRESHOLD) {
+ int i;
+
+ /*
+ * Karatsuba divide-and-conquer algorithm. Cut each input in
+ * half, so that it's expressed as two big 'digits' in a giant
+ * base D:
+ *
+ * a = a_1 D + a_0
+ * b = b_1 D + b_0
+ *
+ * Then the product is of course
+ *
+ * ab = a_1 b_1 D^2 + (a_1 b_0 + a_0 b_1) D + a_0 b_0
+ *
+ * and we compute the three coefficients by recursively
+ * calling ourself to do half-length multiplications.
+ *
+ * The clever bit that makes this worth doing is that we only
+ * need _one_ half-length multiplication for the central
+ * coefficient rather than the two that it obviouly looks
+ * like, because we can use a single multiplication to compute
+ *
+ * (a_1 + a_0) (b_1 + b_0) = a_1 b_1 + a_1 b_0 + a_0 b_1 + a_0 b_0
+ *
+ * and then we subtract the other two coefficients (a_1 b_1
+ * and a_0 b_0) which we were computing anyway.
+ *
+ * Hence we get to multiply two numbers of length N in about
+ * three times as much work as it takes to multiply numbers of
+ * length N/2, which is obviously better than the four times
+ * as much work it would take if we just did a long
+ * conventional multiply.
+ */
+
+ int toplen = len/2, botlen = len - toplen; /* botlen is the bigger */
+ int midlen = botlen + 1;
+ BignumDblInt carry;
+#ifdef KARA_DEBUG
+ int i;
+#endif
+
+ /*
+ * The coefficients a_1 b_1 and a_0 b_0 just avoid overlapping
+ * in the output array, so we can compute them immediately in
+ * place.
+ */
+
+#ifdef KARA_DEBUG
+ printf("a1,a0 = 0x");
+ for (i = 0; i < len; i++) {
+ if (i == toplen) printf(", 0x");
+ printf("%0*x", BIGNUM_INT_BITS/4, a[i]);
+ }
+ printf("\n");
+ printf("b1,b0 = 0x");
+ for (i = 0; i < len; i++) {
+ if (i == toplen) printf(", 0x");
+ printf("%0*x", BIGNUM_INT_BITS/4, b[i]);
+ }
+ printf("\n");
+#endif
+
+ /* a_1 b_1 */
+ internal_mul(a, b, c, toplen, scratch);
+#ifdef KARA_DEBUG
+ printf("a1b1 = 0x");
+ for (i = 0; i < 2*toplen; i++) {
+ printf("%0*x", BIGNUM_INT_BITS/4, c[i]);
+ }
+ printf("\n");
+#endif
+
+ /* a_0 b_0 */
+ internal_mul(a + toplen, b + toplen, c + 2*toplen, botlen, scratch);
+#ifdef KARA_DEBUG
+ printf("a0b0 = 0x");
+ for (i = 0; i < 2*botlen; i++) {
+ printf("%0*x", BIGNUM_INT_BITS/4, c[2*toplen+i]);
+ }
+ printf("\n");
+#endif
+
+ /* Zero padding. midlen exceeds toplen by at most 2, so just
+ * zero the first two words of each input and the rest will be
+ * copied over. */
+ scratch[0] = scratch[1] = scratch[midlen] = scratch[midlen+1] = 0;
+
+ for (i = 0; i < toplen; i++) {
+ scratch[midlen - toplen + i] = a[i]; /* a_1 */
+ scratch[2*midlen - toplen + i] = b[i]; /* b_1 */
+ }
+
+ /* compute a_1 + a_0 */
+ scratch[0] = internal_add(scratch+1, a+toplen, scratch+1, botlen);
+#ifdef KARA_DEBUG
+ printf("a1plusa0 = 0x");
+ for (i = 0; i < midlen; i++) {
+ printf("%0*x", BIGNUM_INT_BITS/4, scratch[i]);
+ }
+ printf("\n");
+#endif
+ /* compute b_1 + b_0 */
+ scratch[midlen] = internal_add(scratch+midlen+1, b+toplen,
+ scratch+midlen+1, botlen);
+#ifdef KARA_DEBUG
+ printf("b1plusb0 = 0x");
+ for (i = 0; i < midlen; i++) {
+ printf("%0*x", BIGNUM_INT_BITS/4, scratch[midlen+i]);
+ }
+ printf("\n");
+#endif
+
+ /*
+ * Now we can do the third multiplication.
+ */
+ internal_mul(scratch, scratch + midlen, scratch + 2*midlen, midlen,
+ scratch + 4*midlen);
+#ifdef KARA_DEBUG
+ printf("a1plusa0timesb1plusb0 = 0x");
+ for (i = 0; i < 2*midlen; i++) {
+ printf("%0*x", BIGNUM_INT_BITS/4, scratch[2*midlen+i]);
+ }
+ printf("\n");
+#endif
+
+ /*
+ * Now we can reuse the first half of 'scratch' to compute the
+ * sum of the outer two coefficients, to subtract from that
+ * product to obtain the middle one.
+ */
+ scratch[0] = scratch[1] = scratch[2] = scratch[3] = 0;
+ for (i = 0; i < 2*toplen; i++)
+ scratch[2*midlen - 2*toplen + i] = c[i];
+ scratch[1] = internal_add(scratch+2, c + 2*toplen,
+ scratch+2, 2*botlen);
+#ifdef KARA_DEBUG
+ printf("a1b1plusa0b0 = 0x");
+ for (i = 0; i < 2*midlen; i++) {
+ printf("%0*x", BIGNUM_INT_BITS/4, scratch[i]);
+ }
+ printf("\n");
+#endif
+
+ internal_sub(scratch + 2*midlen, scratch,
+ scratch + 2*midlen, 2*midlen);
+#ifdef KARA_DEBUG
+ printf("a1b0plusa0b1 = 0x");
+ for (i = 0; i < 2*midlen; i++) {
+ printf("%0*x", BIGNUM_INT_BITS/4, scratch[2*midlen+i]);
+ }
+ printf("\n");
+#endif
+
+ /*
+ * And now all we need to do is to add that middle coefficient
+ * back into the output. We may have to propagate a carry
+ * further up the output, but we can be sure it won't
+ * propagate right the way off the top.
+ */
+ carry = internal_add(c + 2*len - botlen - 2*midlen,
+ scratch + 2*midlen,
+ c + 2*len - botlen - 2*midlen, 2*midlen);
+ i = 2*len - botlen - 2*midlen - 1;
+ while (carry) {
+ assert(i >= 0);
+ carry += c[i];
+ c[i] = (BignumInt)carry;
+ carry >>= BIGNUM_INT_BITS;
+ i--;
+ }
+#ifdef KARA_DEBUG
+ printf("ab = 0x");
+ for (i = 0; i < 2*len; i++) {
+ printf("%0*x", BIGNUM_INT_BITS/4, c[i]);
+ }
+ printf("\n");
+#endif
+
+ } else {
+ int i;
+ BignumInt carry;
+ BignumDblInt t;
+ const BignumInt *ap, *bp;
+ BignumInt *cp, *cps;
+
+ /*
+ * Multiply in the ordinary O(N^2) way.
+ */
+
+ for (i = 0; i < 2 * len; i++)
+ c[i] = 0;
+
+ 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 & 0xffffffff);
+ carry = (BignumInt)(t >> BIGNUM_INT_BITS);
+ }
+ *cp = carry;
+ }
+ }
+}
+
+/*
+ * Variant form of internal_mul used for the initial step of
+ * Montgomery reduction. Only bothers outputting 'len' words
+ * (everything above that is thrown away).
+ */
+static void internal_mul_low(const BignumInt *a, const BignumInt *b,
+ BignumInt *c, int len, BignumInt *scratch)
+{
+ if (len > KARATSUBA_THRESHOLD) {
+ int i;
+
+ /*
+ * Karatsuba-aware version of internal_mul_low. As before, we
+ * express each input value as a shifted combination of two
+ * halves:
+ *
+ * a = a_1 D + a_0
+ * b = b_1 D + b_0
+ *
+ * Then the full product is, as before,
+ *
+ * ab = a_1 b_1 D^2 + (a_1 b_0 + a_0 b_1) D + a_0 b_0
+ *
+ * Provided we choose D on the large side (so that a_0 and b_0
+ * are _at least_ as long as a_1 and b_1), we don't need the
+ * topmost term at all, and we only need half of the middle
+ * term. So there's no point in doing the proper Karatsuba
+ * optimisation which computes the middle term using the top
+ * one, because we'd take as long computing the top one as
+ * just computing the middle one directly.
+ *
+ * So instead, we do a much more obvious thing: we call the
+ * fully optimised internal_mul to compute a_0 b_0, and we
+ * recursively call ourself to compute the _bottom halves_ of
+ * a_1 b_0 and a_0 b_1, each of which we add into the result
+ * in the obvious way.
+ *
+ * In other words, there's no actual Karatsuba _optimisation_
+ * in this function; the only benefit in doing it this way is
+ * that we call internal_mul proper for a large part of the
+ * work, and _that_ can optimise its operation.
+ */
+
+ int toplen = len/2, botlen = len - toplen; /* botlen is the bigger */
+
+ /*
+ * Scratch space for the various bits and pieces we're going
+ * to be adding together: we need botlen*2 words for a_0 b_0
+ * (though we may end up throwing away its topmost word), and
+ * toplen words for each of a_1 b_0 and a_0 b_1. That adds up
+ * to exactly 2*len.
+ */
+
+ /* a_0 b_0 */
+ internal_mul(a + toplen, b + toplen, scratch + 2*toplen, botlen,
+ scratch + 2*len);
+
+ /* a_1 b_0 */
+ internal_mul_low(a, b + len - toplen, scratch + toplen, toplen,
+ scratch + 2*len);
+
+ /* a_0 b_1 */
+ internal_mul_low(a + len - toplen, b, scratch, toplen,
+ scratch + 2*len);
+
+ /* Copy the bottom half of the big coefficient into place */
+ for (i = 0; i < botlen; i++)
+ c[toplen + i] = scratch[2*toplen + botlen + i];
+
+ /* Add the two small coefficients, throwing away the returned carry */
+ internal_add(scratch, scratch + toplen, scratch, toplen);
+
+ /* And add that to the large coefficient, leaving the result in c. */
+ internal_add(scratch, scratch + 2*toplen + botlen - toplen,
+ c, toplen);
+
+ } else {
+ int i;
+ BignumInt carry;
+ BignumDblInt t;
+ const BignumInt *ap, *bp;
+ BignumInt *cp, *cps;
+
+ /*
+ * Multiply in the ordinary O(N^2) way.
+ */
+
+ for (i = 0; i < len; i++)
+ c[i] = 0;
+
+ 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&BIGNUM_INT_MASK);
+ carry = (BignumInt)(t >> BIGNUM_INT_BITS);
+ }
+ }
+ }
+}
+
+/*
+ * Montgomery reduction. Expects x to be a big-endian array of 2*len
+ * BignumInts whose value satisfies 0 <= x < rn (where r = 2^(len *
+ * BIGNUM_INT_BITS) is the Montgomery base). Returns in the same array
+ * a value x' which is congruent to xr^{-1} mod n, and satisfies 0 <=
+ * x' < n.
+ *
+ * 'n' and 'mninv' should be big-endian arrays of 'len' BignumInts
+ * each, containing respectively n and the multiplicative inverse of
+ * -n mod r.
+ *
+ * 'tmp' is an array of BignumInt used as scratch space, of length at
+ * least 3*len + mul_compute_scratch(len).
+ */
+static void monty_reduce(BignumInt *x, const BignumInt *n,
+ const BignumInt *mninv, BignumInt *tmp, int len)
+{
+ int i;
+ BignumInt carry;
+
+ /*
+ * Multiply x by (-n)^{-1} mod r. This gives us a value m such
+ * that mn is congruent to -x mod r. Hence, mn+x is an exact
+ * multiple of r, and is also (obviously) congruent to x mod n.
+ */
+ internal_mul_low(x + len, mninv, tmp, len, tmp + 3*len);
+
+ /*
+ * Compute t = (mn+x)/r in ordinary, non-modular, integer
+ * arithmetic. By construction this is exact, and is congruent mod
+ * n to x * r^{-1}, i.e. the answer we want.
+ *
+ * The following multiply leaves that answer in the _most_
+ * significant half of the 'x' array, so then we must shift it
+ * down.
+ */
+ internal_mul(tmp, n, tmp+len, len, tmp + 3*len);
+ carry = internal_add(x, tmp+len, x, 2*len);
+ for (i = 0; i < len; i++)
+ x[len + i] = x[i], x[i] = 0;
+
+ /*
+ * Reduce t mod n. This doesn't require a full-on division by n,
+ * but merely a test and single optional subtraction, since we can
+ * show that 0 <= t < 2n.
+ *
+ * Proof:
+ * + we computed m mod r, so 0 <= m < r.
+ * + so 0 <= mn < rn, obviously
+ * + hence we only need 0 <= x < rn to guarantee that 0 <= mn+x < 2rn
+ * + yielding 0 <= (mn+x)/r < 2n as required.
+ */
+ if (!carry) {
+ for (i = 0; i < len; i++)
+ if (x[len + i] != n[i])
+ break;
+ }
+ if (carry || i >= len || x[len + i] > n[i])
+ internal_sub(x+len, n, x+len, len);
+}
+
+static void internal_add_shifted(BignumInt *number,
+ unsigned n, int shift)
+{
+ int word = 1 + (shift / BIGNUM_INT_BITS);
+ int bshift = shift % BIGNUM_INT_BITS;
+ BignumDblInt addend;
+
+ addend = (BignumDblInt)n << bshift;
+
+ while (addend) {
+ assert(word <= number[0]);
+ addend += number[word];
+ number[word] = (BignumInt) (addend & BIGNUM_INT_MASK);
+ addend >>= BIGNUM_INT_BITS;
+ word++;
+ }
+}
+
+/*
+ * 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.
+ */
+static void internal_mod(BignumInt *a, int alen,
+ BignumInt *m, int mlen,
+ BignumInt *quot, int qshift)
+{
+ BignumInt m0, m1;
+ unsigned int h;
+ int i, k;
+
+ m0 = m[0];
+ assert(m0 >> (BIGNUM_INT_BITS-1) == 1);
+ if (mlen > 1)
+ m1 = m[1];
+ else
+ m1 = 0;
+
+ for (i = 0; i <= alen - mlen; i++) {
+ BignumDblInt t;
+ unsigned int q, r, c, ai1;
+
+ if (i == 0) {
+ h = 0;
+ } else {
+ h = a[i - 1];
+ a[i - 1] = 0;
+ }
+
+ 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--;
+ }
+ }
+
+ /* Subtract q * m from a[i...] */
+ c = 0;
+ for (k = mlen - 1; k >= 0; k--) {
+ t = MUL_WORD(q, m[k]);
+ t += c;
+ c = (unsigned)(t >> BIGNUM_INT_BITS);
+ if (((BignumInt)(t&0xffffffff)) > a[i + k])
+ c++;
+ a[i + k] -= (BignumInt) (t&0xffffffff);
+ }
+
+ /* 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));
+ }
+}
+
+/*
+ * Compute (base ^ exp) % mod, the pedestrian way.
+ */
+Bignum modpow_simple(Bignum base_in, Bignum exp, Bignum mod)
+{
+ BignumInt *a, *b, *n, *m, *scratch;
+ int mshift;
+ int mlen, scratchlen, i, j;
+ Bignum base, result;
+
+ /*
+ * The most significant word of mod needs to be non-zero. It
+ * should already be, but let's make sure.
+ */
+ assert(mod[mod[0]] != 0);
+
+ /*
+ * Make sure the base is smaller than the modulus, by reducing
+ * it modulo the modulus if not.
+ */
+ base = bigmod(base_in, mod);
+
+ /* Allocate m of size mlen, copy mod to m */
+ /* We use big endian internally */
+ mlen = mod[0];
+ m = snewn(mlen, BignumInt);
+ 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];
+ for (j = 0; j < i; j++)
+ n[j] = 0;
+ for (j = 0; j < (int)base[0]; j++)
+ n[i + j] = base[base[0] - j];
+
+ /* Allocate a and b of size 2*mlen. Set a = 1 */
+ a = snewn(2 * mlen, BignumInt);
+ b = snewn(2 * mlen, BignumInt);
+ for (i = 0; i < 2 * mlen; i++)
+ a[i] = 0;
+ a[2 * mlen - 1] = 1;
+
+ /* Scratch space for multiplies */
+ scratchlen = mul_compute_scratch(mlen);
+ scratch = snewn(scratchlen, BignumInt);
+
+ /* Skip leading zero bits of exp. */
+ i = 0;
+ j = BIGNUM_INT_BITS-1;
+ while (i < (int)exp[0] && (exp[exp[0] - i] & (1 << j)) == 0) {
+ j--;
+ if (j < 0) {
+ i++;
+ j = BIGNUM_INT_BITS-1;
+ }
+ }
+
+ /* 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);
+ if ((exp[exp[0] - i] & (1 << j)) != 0) {
+ internal_mul(b + mlen, n, a, mlen, scratch);
+ internal_mod(a, mlen * 2, m, mlen, NULL, 0);
+ } else {
+ BignumInt *t;
+ t = a;
+ a = b;
+ b = t;
+ }
+ j--;
+ }
+ i++;
+ 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++)
+ result[result[0] - i] = a[i + mlen];
+ while (result[0] > 1 && result[result[0]] == 0)
+ result[0]--;
+
+ /* Free temporary arrays */
+ smemclr(a, 2 * mlen * sizeof(*a));
+ sfree(a);
+ smemclr(scratch, scratchlen * sizeof(*scratch));
+ sfree(scratch);
+ smemclr(b, 2 * mlen * sizeof(*b));
+ sfree(b);
+ smemclr(m, mlen * sizeof(*m));
+ sfree(m);
+ smemclr(n, mlen * sizeof(*n));
+ sfree(n);
+
+ freebn(base);
+
+ return result;
+}
+
+/*
+ * Compute (base ^ exp) % mod. Uses the Montgomery multiplication
+ * technique where possible, falling back to modpow_simple otherwise.
+ */
+Bignum modpow(Bignum base_in, Bignum exp, Bignum mod)
+{
+ BignumInt *a, *b, *x, *n, *mninv, *scratch;
+ int len, scratchlen, i, j;
+ Bignum base, base2, r, rn, inv, result;
+
+ /*
+ * The most significant word of mod needs to be non-zero. It
+ * should already be, but let's make sure.
+ */
+ assert(mod[mod[0]] != 0);
+
+ /*
+ * mod had better be odd, or we can't do Montgomery multiplication
+ * using a power of two at all.
+ */
+ if (!(mod[1] & 1))
+ return modpow_simple(base_in, exp, mod);
+
+ /*
+ * Make sure the base is smaller than the modulus, by reducing
+ * it modulo the modulus if not.
+ */
+ base = bigmod(base_in, mod);
+
+ /*
+ * Compute the inverse of n mod r, for monty_reduce. (In fact we
+ * want the inverse of _minus_ n mod r, but we'll sort that out
+ * below.)
+ */
+ len = mod[0];
+ r = bn_power_2(BIGNUM_INT_BITS * len);
+ inv = modinv(mod, r);
+ assert(inv); /* cannot fail, since mod is odd and r is a power of 2 */
+
+ /*
+ * Multiply the base by r mod n, to get it into Montgomery
+ * representation.
+ */
+ base2 = modmul(base, r, mod);
+ freebn(base);
+ base = base2;
+
+ rn = bigmod(r, mod); /* r mod n, i.e. Montgomerified 1 */
+
+ freebn(r); /* won't need this any more */
+
+ /*
+ * Set up internal arrays of the right lengths, in big-endian
+ * format, containing the base, the modulus, and the modulus's
+ * inverse.
+ */
+ n = snewn(len, BignumInt);
+ for (j = 0; j < len; j++)
+ n[len - 1 - j] = mod[j + 1];
+
+ mninv = snewn(len, BignumInt);
+ for (j = 0; j < len; j++)
+ mninv[len - 1 - j] = (j < (int)inv[0] ? inv[j + 1] : 0);
+ freebn(inv); /* we don't need this copy of it any more */
+ /* Now negate mninv mod r, so it's the inverse of -n rather than +n. */
+ x = snewn(len, BignumInt);
+ for (j = 0; j < len; j++)
+ x[j] = 0;
+ internal_sub(x, mninv, mninv, len);
+
+ /* x = snewn(len, BignumInt); */ /* already done above */
+ for (j = 0; j < len; j++)
+ x[len - 1 - j] = (j < (int)base[0] ? base[j + 1] : 0);
+ freebn(base); /* we don't need this copy of it any more */
+
+ a = snewn(2*len, BignumInt);
+ b = snewn(2*len, BignumInt);
+ for (j = 0; j < len; j++)
+ a[2*len - 1 - j] = (j < (int)rn[0] ? rn[j + 1] : 0);
+ freebn(rn);
+
+ /* Scratch space for multiplies */
+ scratchlen = 3*len + mul_compute_scratch(len);
+ scratch = snewn(scratchlen, BignumInt);
+
+ /* Skip leading zero bits of exp. */
+ i = 0;
+ j = BIGNUM_INT_BITS-1;
+ while (i < (int)exp[0] && (exp[exp[0] - i] & (1 << j)) == 0) {
+ j--;
+ if (j < 0) {
+ i++;
+ j = BIGNUM_INT_BITS-1;
+ }
+ }
+
+ /* Main computation */
+ while (i < (int)exp[0]) {
+ while (j >= 0) {
+ internal_mul(a + len, a + len, b, len, scratch);
+ monty_reduce(b, n, mninv, scratch, len);
+ if ((exp[exp[0] - i] & (1 << j)) != 0) {
+ internal_mul(b + len, x, a, len, scratch);
+ monty_reduce(a, n, mninv, scratch, len);
+ } else {
+ BignumInt *t;
+ t = a;
+ a = b;
+ b = t;
+ }
+ j--;
+ }
+ i++;
+ j = BIGNUM_INT_BITS-1;
+ }
+
+ /*
+ * Final monty_reduce to get back from the adjusted Montgomery
+ * representation.
+ */
+ monty_reduce(a, n, mninv, scratch, len);
+
+ /* Copy result to buffer */
+ result = newbn(mod[0]);
+ for (i = 0; i < len; i++)
+ result[result[0] - i] = a[i + len];
+ while (result[0] > 1 && result[result[0]] == 0)
+ result[0]--;
+
+ /* Free temporary arrays */
+ smemclr(scratch, scratchlen * sizeof(*scratch));
+ sfree(scratch);
+ smemclr(a, 2 * len * sizeof(*a));
+ sfree(a);
+ smemclr(b, 2 * len * sizeof(*b));
+ sfree(b);
+ smemclr(mninv, len * sizeof(*mninv));
+ sfree(mninv);
+ smemclr(n, len * sizeof(*n));
+ sfree(n);
+ smemclr(x, len * sizeof(*x));
+ sfree(x);
+
+ return result;
+}
+
+/*
+ * Compute (p * q) % mod.
+ * The most significant word of mod MUST be non-zero.
+ * We assume that the result array is the same size as the mod array.
+ */
+Bignum modmul(Bignum p, Bignum q, Bignum mod)
+{
+ BignumInt *a, *n, *m, *o, *scratch;
+ int mshift, scratchlen;
+ int pqlen, mlen, rlen, i, j;
+ Bignum result;
+
+ /*
+ * The most significant word of mod needs to be non-zero. It
+ * should already be, but let's make sure.
+ */
+ assert(mod[mod[0]] != 0);
+
+ /* Allocate m of size mlen, copy mod to m */
+ /* We use big endian internally */
+ mlen = mod[0];
+ m = snewn(mlen, BignumInt);
+ 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]);
+
+ /*
+ * Make sure that we're allowing enough space. The shifting below
+ * will underflow the vectors we allocate if pqlen is too small.
+ */
+ if (2*pqlen <= mlen)
+ pqlen = mlen/2 + 1;
+
+ /* Allocate n of size pqlen, copy p to n */
+ n = snewn(pqlen, BignumInt);
+ i = pqlen - p[0];
+ for (j = 0; j < i; j++)
+ n[j] = 0;
+ for (j = 0; j < (int)p[0]; j++)
+ n[i + j] = p[p[0] - j];
+
+ /* Allocate o of size pqlen, copy q to o */
+ o = snewn(pqlen, BignumInt);
+ i = pqlen - q[0];
+ for (j = 0; j < i; j++)
+ o[j] = 0;
+ for (j = 0; j < (int)q[0]; j++)
+ o[i + j] = q[q[0] - j];
+
+ /* Allocate a of size 2*pqlen for result */
+ a = snewn(2 * pqlen, BignumInt);
+
+ /* Scratch space for multiplies */
+ scratchlen = mul_compute_scratch(pqlen);
+ scratch = snewn(scratchlen, BignumInt);
+
+ /* 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));
+ }
+
+ /* Copy result to buffer */
+ rlen = (mlen < pqlen * 2 ? mlen : pqlen * 2);
+ result = newbn(rlen);
+ for (i = 0; i < rlen; i++)
+ result[result[0] - i] = a[i + 2 * pqlen - rlen];
+ while (result[0] > 1 && result[result[0]] == 0)
+ result[0]--;
+
+ /* Free temporary arrays */
+ smemclr(scratch, scratchlen * sizeof(*scratch));
+ sfree(scratch);
+ smemclr(a, 2 * pqlen * sizeof(*a));
+ sfree(a);
+ smemclr(m, mlen * sizeof(*m));
+ sfree(m);
+ smemclr(n, pqlen * sizeof(*n));
+ sfree(n);
+ smemclr(o, pqlen * sizeof(*o));
+ sfree(o);
+
+ return result;
+}
+
+/*
+ * Compute p % mod.
+ * The most significant word of mod MUST be non-zero.
+ * We assume that the result array is the same size as the mod array.
+ * We optionally write out a quotient if `quotient' is non-NULL.
+ * We can avoid writing out the result if `result' is NULL.
+ */
+static void bigdivmod(Bignum p, Bignum mod, Bignum result, Bignum quotient)
+{
+ BignumInt *n, *m;
+ int mshift;
+ int plen, mlen, i, j;
+
+ /*
+ * The most significant word of mod needs to be non-zero. It
+ * should already be, but let's make sure.
+ */
+ assert(mod[mod[0]] != 0);
+
+ /* Allocate m of size mlen, copy mod to m */
+ /* We use big endian internally */
+ mlen = mod[0];
+ m = snewn(mlen, BignumInt);
+ 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)
+ plen = mlen + 1;
+
+ /* Allocate n of size plen, copy p to n */
+ n = snewn(plen, BignumInt);
+ for (j = 0; j < plen; j++)
+ n[j] = 0;
+ 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));
+ }
+
+ /* Copy result to buffer */
+ if (result) {
+ for (i = 1; i <= (int)result[0]; i++) {
+ int j = plen - i;
+ result[i] = j >= 0 ? n[j] : 0;
+ }
+ }
+
+ /* Free temporary arrays */
+ smemclr(m, mlen * sizeof(*m));
+ sfree(m);
+ smemclr(n, plen * sizeof(*n));
+ sfree(n);
+}
+
+/*
+ * Decrement a number.
+ */
+void decbn(Bignum bn)
+{
+ int i = 1;
+ while (i < (int)bn[0] && bn[i] == 0)
+ bn[i++] = BIGNUM_INT_MASK;
+ bn[i]--;
+}
+
+Bignum bignum_from_bytes(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 = nbytes; i--;) {
+ unsigned char byte = *data++;
+ result[1 + i / BIGNUM_INT_BYTES] |= byte << (8*i % BIGNUM_INT_BITS);
+ }
+
+ while (result[0] > 1 && result[result[0]] == 0)
+ result[0]--;
+ return result;
+}
+
+/*
+ * Read an SSH-1-format bignum from a data buffer. Return the number
+ * of bytes consumed, or -1 if there wasn't enough data.
+ */
+int ssh1_read_bignum(const unsigned char *data, int len, Bignum * result)
+{
+ const unsigned char *p = data;
+ int i;
+ int w, b;
+
+ if (len < 2)
+ return -1;
+
+ w = 0;
+ for (i = 0; i < 2; i++)
+ w = (w << 8) + *p++;
+ b = (w + 7) / 8; /* bits -> bytes */
+
+ if (len < b+2)
+ return -1;
+
+ if (!result) /* just return length */
+ return b + 2;
+
+ *result = bignum_from_bytes(p, b);
+
+ return p + b - data;
+}
+
+/*
+ * Return the bit count of a bignum, for SSH-1 encoding.
+ */
+int bignum_bitcount(Bignum bn)
+{
+ int bitcount = bn[0] * BIGNUM_INT_BITS - 1;
+ while (bitcount >= 0
+ && (bn[bitcount / BIGNUM_INT_BITS + 1] >> (bitcount % BIGNUM_INT_BITS)) == 0) bitcount--;
+ return bitcount + 1;
+}
+
+/*
+ * Return the byte length of a bignum when SSH-1 encoded.
+ */
+int ssh1_bignum_length(Bignum bn)
+{
+ return 2 + (bignum_bitcount(bn) + 7) / 8;
+}
+
+/*
+ * Return the byte length of a bignum when SSH-2 encoded.
+ */
+int ssh2_bignum_length(Bignum bn)
+{
+ return 4 + (bignum_bitcount(bn) + 8) / 8;
+}
+
+/*
+ * Return a byte from a bignum; 0 is least significant, etc.
+ */
+int bignum_byte(Bignum bn, int i)
+{
+ if (i < 0 || i >= (int)(BIGNUM_INT_BYTES * bn[0]))
+ return 0; /* beyond the end */
+ else
+ return (bn[i / BIGNUM_INT_BYTES + 1] >>
+ ((i % BIGNUM_INT_BYTES)*8)) & 0xFF;
+}
+
+/*
+ * Return a bit from a bignum; 0 is least significant, etc.
+ */
+int bignum_bit(Bignum bn, int i)
+{
+ if (i < 0 || i >= (int)(BIGNUM_INT_BITS * bn[0]))
+ return 0; /* beyond the end */
+ else
+ return (bn[i / BIGNUM_INT_BITS + 1] >> (i % BIGNUM_INT_BITS)) & 1;
+}
+
+/*
+ * Set a bit in a bignum; 0 is least significant, etc.
+ */
+void bignum_set_bit(Bignum bn, int bitnum, int value)
+{
+ if (bitnum < 0 || bitnum >= (int)(BIGNUM_INT_BITS * bn[0]))
+ abort(); /* beyond the end */
+ else {
+ int v = bitnum / BIGNUM_INT_BITS + 1;
+ int mask = 1 << (bitnum % BIGNUM_INT_BITS);
+ if (value)
+ bn[v] |= mask;
+ else
+ bn[v] &= ~mask;
+ }
+}
+
+/*
+ * Write a SSH-1-format bignum into a buffer. It is assumed the
+ * buffer is big enough. Returns the number of bytes used.
+ */
+int ssh1_write_bignum(void *data, Bignum bn)
+{
+ unsigned char *p = data;
+ int len = ssh1_bignum_length(bn);
+ int i;
+ int bitc = bignum_bitcount(bn);
+
+ *p++ = (bitc >> 8) & 0xFF;
+ *p++ = (bitc) & 0xFF;
+ for (i = len - 2; i--;)
+ *p++ = bignum_byte(bn, i);
+ return len;
+}
+
+/*
+ * Compare two bignums. Returns like strcmp.
+ */
+int bignum_cmp(Bignum a, Bignum b)
+{
+ int amax = a[0], bmax = b[0];
+ int i;
+
+ /* Annoyingly we have two representations of zero */
+ if (amax == 1 && a[amax] == 0)
+ amax = 0;
+ if (bmax == 1 && b[bmax] == 0)
+ bmax = 0;
+
+ assert(amax == 0 || a[amax] != 0);
+ assert(bmax == 0 || b[bmax] != 0);
+
+ i = (amax > bmax ? amax : bmax);
+ while (i) {
+ BignumInt aval = (i > amax ? 0 : a[i]);
+ BignumInt bval = (i > bmax ? 0 : b[i]);
+ if (aval < bval)
+ return -1;
+ if (aval > bval)
+ return +1;
+ i--;
+ }
+ return 0;
+}
+
+/*
+ * Right-shift one bignum to form another.
+ */
+Bignum bignum_rshift(Bignum a, int shift)
+{
+ Bignum ret;
+ int i, shiftw, shiftb, shiftbb, bits;
+ BignumInt ai, ai1;
+
+ assert(shift >= 0);
+
+ bits = bignum_bitcount(a) - shift;
+ ret = newbn((bits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS);
+
+ if (ret) {
+ shiftw = shift / BIGNUM_INT_BITS;
+ shiftb = shift % BIGNUM_INT_BITS;
+ shiftbb = BIGNUM_INT_BITS - shiftb;
+
+ ai1 = a[shiftw + 1];
+ for (i = 1; i <= (int)ret[0]; i++) {
+ ai = ai1;
+ ai1 = (i + shiftw + 1 <= (int)a[0] ? a[i + shiftw + 1] : 0);
+ ret[i] = ((ai >> shiftb) | (ai1 << shiftbb)) & BIGNUM_INT_MASK;
+ }
+ }
+
+ return ret;
+}
+
+/*
+ * Non-modular multiplication and addition.
+ */
+Bignum bigmuladd(Bignum a, Bignum b, Bignum addend)
+{
+ int alen = a[0], blen = b[0];
+ int mlen = (alen > blen ? alen : blen);
+ int rlen, i, maxspot;
+ int wslen;
+ BignumInt *workspace;
+ Bignum ret;
+
+ /* mlen space for a, mlen space for b, 2*mlen for result,
+ * plus scratch space for multiplication */
+ wslen = mlen * 4 + mul_compute_scratch(mlen);
+ workspace = snewn(wslen, BignumInt);
+ for (i = 0; i < mlen; i++) {
+ workspace[0 * mlen + i] = (mlen - i <= (int)a[0] ? a[mlen - i] : 0);
+ workspace[1 * mlen + i] = (mlen - i <= (int)b[0] ? b[mlen - i] : 0);
+ }
+
+ internal_mul(workspace + 0 * mlen, workspace + 1 * mlen,
+ workspace + 2 * mlen, mlen, workspace + 4 * mlen);
+
+ /* now just copy the result back */
+ rlen = alen + blen + 1;
+ if (addend && rlen <= (int)addend[0])
+ rlen = addend[0] + 1;
+ ret = newbn(rlen);
+ maxspot = 0;
+ for (i = 1; i <= (int)ret[0]; i++) {
+ ret[i] = (i <= 2 * mlen ? workspace[4 * mlen - i] : 0);
+ if (ret[i] != 0)
+ maxspot = i;
+ }
+ ret[0] = maxspot;
+
+ /* now add in the addend, if any */
+ if (addend) {
+ BignumDblInt 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;
+ if (ret[i] != 0 && i > maxspot)
+ maxspot = i;
+ }
+ }
+ ret[0] = maxspot;
+
+ smemclr(workspace, wslen * sizeof(*workspace));
+ sfree(workspace);
+ return ret;
+}
+
+/*
+ * Non-modular multiplication.
+ */
+Bignum bigmul(Bignum a, Bignum b)
+{
+ return bigmuladd(a, b, NULL);
+}
+
+/*
+ * Simple addition.
+ */
+Bignum bigadd(Bignum a, Bignum b)
+{
+ int alen = a[0], blen = b[0];
+ int rlen = (alen > blen ? alen : blen) + 1;
+ int i, maxspot;
+ Bignum ret;
+ BignumDblInt 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;
+ if (ret[i] != 0 && i > maxspot)
+ maxspot = i;
+ }
+ ret[0] = maxspot;
+
+ return ret;
+}
+
+/*
+ * Subtraction. Returns a-b, or NULL if the result would come out
+ * negative (recall that this entire bignum module only handles
+ * positive numbers).
+ */
+Bignum bigsub(Bignum a, Bignum b)
+{
+ int alen = a[0], blen = b[0];
+ int rlen = (alen > blen ? alen : blen);
+ int i, maxspot;
+ Bignum ret;
+ BignumDblInt 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;
+ if (ret[i] != 0 && i > maxspot)
+ maxspot = i;
+ }
+ ret[0] = maxspot;
+
+ if (!carry) {
+ freebn(ret);
+ return NULL;
+ }
+
+ return ret;
+}
+
+/*
+ * Create a bignum which is the bitmask covering another one. That
+ * is, the smallest integer which is >= N and is also one less than
+ * a power of two.
+ */
+Bignum bignum_bitmask(Bignum n)
+{
+ Bignum ret = copybn(n);
+ int i;
+ BignumInt j;
+
+ i = ret[0];
+ while (n[i] == 0 && i > 0)
+ i--;
+ if (i <= 0)
+ return ret; /* input was zero */
+ j = 1;
+ while (j < n[i])
+ j = 2 * j + 1;
+ ret[i] = j;
+ while (--i > 0)
+ ret[i] = BIGNUM_INT_MASK;
+ return ret;
+}
+
+/*
+ * Convert a (max 32-bit) long into a bignum.
+ */
+Bignum bignum_from_long(unsigned long nn)
+{
+ Bignum ret;
+ BignumDblInt n = nn;
+
+ 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 ret = newbn(number[0] + 1);
+ int i, maxspot = 0;
+ BignumDblInt carry = 0, addend = addendx;
+
+ 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;
+ }
+ ret[0] = maxspot;
+ return ret;
+}
+
+/*
+ * Compute the residue of a bignum, modulo a (max 16-bit) short.
+ */
+unsigned short bignum_mod_short(Bignum number, unsigned short modulus)
+{
+ BignumDblInt mod, r;
+ int i;
+
+ r = 0;
+ mod = modulus;
+ for (i = number[0]; i > 0; i--)
+ r = (r * (BIGNUM_TOP_BIT % mod) * 2 + number[i] % mod) % mod;
+ return (unsigned short) r;
+}
+
+#ifdef DEBUG
+void diagbn(char *prefix, Bignum md)
+{
+ int i, nibbles, morenibbles;
+ static const char hex[] = "0123456789ABCDEF";
+
+ debug(("%s0x", prefix ? prefix : ""));
+
+ nibbles = (3 + bignum_bitcount(md)) / 4;
+ if (nibbles < 1)
+ nibbles = 1;
+ morenibbles = 4 * md[0] - nibbles;
+ for (i = 0; i < morenibbles; i++)
+ debug(("-"));
+ for (i = nibbles; i--;)
+ debug(("%c",
+ hex[(bignum_byte(md, i / 2) >> (4 * (i % 2))) & 0xF]));
+
+ if (prefix)
+ debug(("\n"));
+}
+#endif
+
+/*
+ * Simple division.
+ */
+Bignum bigdiv(Bignum a, Bignum b)
+{
+ Bignum q = newbn(a[0]);
+ bigdivmod(a, b, NULL, q);
+ return q;
+}
+
+/*
+ * Simple remainder.
+ */
+Bignum bigmod(Bignum a, Bignum b)
+{
+ Bignum r = newbn(b[0]);
+ bigdivmod(a, b, r, NULL);
+ return r;
+}
+
+/*
+ * Greatest common divisor.
+ */
+Bignum biggcd(Bignum av, Bignum bv)
+{
+ Bignum a = copybn(av);
+ Bignum b = copybn(bv);
+
+ while (bignum_cmp(b, Zero) != 0) {
+ Bignum t = newbn(b[0]);
+ bigdivmod(a, b, t, NULL);
+ while (t[0] > 1 && t[t[0]] == 0)
+ t[0]--;
+ freebn(a);
+ a = b;
+ b = t;
+ }
+
+ freebn(b);
+ return a;
+}
+
+/*
+ * Modular inverse, using Euclid's extended algorithm.
+ */
+Bignum modinv(Bignum number, Bignum modulus)
+{
+ Bignum a = copybn(modulus);
+ Bignum b = copybn(number);
+ Bignum xp = copybn(Zero);
+ Bignum x = copybn(One);
+ int sign = +1;
+
+ assert(number[number[0]] != 0);
+ assert(modulus[modulus[0]] != 0);
+
+ while (bignum_cmp(b, One) != 0) {
+ Bignum t, q;
+
+ if (bignum_cmp(b, Zero) == 0) {
+ /*
+ * Found a common factor between the inputs, so we cannot
+ * return a modular inverse at all.
+ */
+ freebn(b);
+ freebn(a);
+ freebn(xp);
+ freebn(x);
+ return NULL;
+ }
+
+ t = newbn(b[0]);
+ q = newbn(a[0]);
+ bigdivmod(a, b, t, q);
+ while (t[0] > 1 && t[t[0]] == 0)
+ t[0]--;
+ freebn(a);
+ a = b;
+ b = t;
+ t = xp;
+ xp = x;
+ x = bigmuladd(q, xp, t);
+ sign = -sign;
+ freebn(t);
+ freebn(q);
+ }
+
+ freebn(b);
+ freebn(a);
+ freebn(xp);
+
+ /* now we know that sign * x == 1, and that x < modulus */
+ if (sign < 0) {
+ /* set a new x to be modulus - x */
+ Bignum newx = newbn(modulus[0]);
+ BignumInt carry = 0;
+ int maxspot = 1;
+ int i;
+
+ for (i = 1; i <= (int)newx[0]; i++) {
+ BignumInt aword = (i <= (int)modulus[0] ? modulus[i] : 0);
+ BignumInt bword = (i <= (int)x[0] ? x[i] : 0);
+ newx[i] = aword - bword - carry;
+ bword = ~bword;
+ carry = carry ? (newx[i] >= bword) : (newx[i] > bword);
+ if (newx[i] != 0)
+ maxspot = i;
+ }
+ newx[0] = maxspot;
+ freebn(x);
+ x = newx;
+ }
+
+ /* and return. */
+ return x;
+}
+
+/*
+ * Render a bignum into decimal. Return a malloced string holding
+ * the decimal representation.
+ */
+char *bignum_decimal(Bignum x)
+{
+ int ndigits, ndigit;
+ int i, iszero;
+ BignumDblInt carry;
+ char *ret;
+ BignumInt *workspace;
+
+ /*
+ * First, estimate the number of digits. Since log(10)/log(2)
+ * is just greater than 93/28 (the joys of continued fraction
+ * approximations...) we know that for every 93 bits, we need
+ * at most 28 digits. This will tell us how much to malloc.
+ *
+ * Formally: if x has i bits, that means x is strictly less
+ * than 2^i. Since 2 is less than 10^(28/93), this is less than
+ * 10^(28i/93). We need an integer power of ten, so we must
+ * round up (rounding down might make it less than x again).
+ * Therefore if we multiply the bit count by 28/93, rounding
+ * up, we will have enough digits.
+ *
+ * i=0 (i.e., x=0) is an irritating special case.
+ */
+ i = bignum_bitcount(x);
+ if (!i)
+ ndigits = 1; /* x = 0 */
+ else
+ ndigits = (28 * i + 92) / 93; /* multiply by 28/93 and round up */
+ ndigits++; /* allow for trailing \0 */
+ ret = snewn(ndigits, char);
+
+ /*
+ * Now allocate some workspace to hold the binary form as we
+ * repeatedly divide it by ten. Initialise this to the
+ * big-endian form of the number.
+ */
+ workspace = snewn(x[0], BignumInt);
+ for (i = 0; i < (int)x[0]; i++)
+ workspace[i] = x[x[0] - i];
+
+ /*
+ * Next, write the decimal number starting with the last digit.
+ * We use ordinary short division, dividing 10 into the
+ * workspace.
+ */
+ ndigit = ndigits - 1;
+ ret[ndigit] = '\0';
+ do {
+ iszero = 1;
+ carry = 0;
+ for (i = 0; i < (int)x[0]; i++) {
+ carry = (carry << BIGNUM_INT_BITS) + workspace[i];
+ workspace[i] = (BignumInt) (carry / 10);
+ if (workspace[i])
+ iszero = 0;
+ carry %= 10;
+ }
+ ret[--ndigit] = (char) (carry + '0');
+ } while (!iszero);
+
+ /*
+ * There's a chance we've fallen short of the start of the
+ * string. Correct if so.
+ */
+ if (ndigit > 0)
+ memmove(ret, ret + ndigit, ndigits - ndigit);
+
+ /*
+ * Done.
+ */
+ smemclr(workspace, x[0] * sizeof(*workspace));
+ 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);
+}
+
+#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/tools/plink/sshdes.c b/tools/plink/sshdes.c
index 0beb273ce..f54990e7c 100644
--- a/tools/plink/sshdes.c
+++ b/tools/plink/sshdes.c
@@ -1,1031 +1,1088 @@
-#include <assert.h>
-#include "ssh.h"
-
-
-/* des.c - implementation of DES
- */
-
-/*
- * Description of DES
- * ------------------
- *
- * Unlike the description in FIPS 46, I'm going to use _sensible_ indices:
- * bits in an n-bit word are numbered from 0 at the LSB to n-1 at the MSB.
- * And S-boxes are indexed by six consecutive bits, not by the outer two
- * followed by the middle four.
- *
- * The DES encryption routine requires a 64-bit input, and a key schedule K
- * containing 16 48-bit elements.
- *
- * First the input is permuted by the initial permutation IP.
- * Then the input is split into 32-bit words L and R. (L is the MSW.)
- * Next, 16 rounds. In each round:
- * (L, R) <- (R, L xor f(R, K[i]))
- * Then the pre-output words L and R are swapped.
- * Then L and R are glued back together into a 64-bit word. (L is the MSW,
- * again, but since we just swapped them, the MSW is the R that came out
- * of the last round.)
- * The 64-bit output block is permuted by the inverse of IP and returned.
- *
- * Decryption is identical except that the elements of K are used in the
- * opposite order. (This wouldn't work if that word swap didn't happen.)
- *
- * The function f, used in each round, accepts a 32-bit word R and a
- * 48-bit key block K. It produces a 32-bit output.
- *
- * First R is expanded to 48 bits using the bit-selection function E.
- * The resulting 48-bit block is XORed with the key block K to produce
- * a 48-bit block X.
- * This block X is split into eight groups of 6 bits. Each group of 6
- * bits is then looked up in one of the eight S-boxes to convert
- * it to 4 bits. These eight groups of 4 bits are glued back
- * together to produce a 32-bit preoutput block.
- * The preoutput block is permuted using the permutation P and returned.
- *
- * Key setup maps a 64-bit key word into a 16x48-bit key schedule. Although
- * the approved input format for the key is a 64-bit word, eight of the
- * bits are discarded, so the actual quantity of key used is 56 bits.
- *
- * First the input key is converted to two 28-bit words C and D using
- * the bit-selection function PC1.
- * Then 16 rounds of key setup occur. In each round, C and D are each
- * rotated left by either 1 or 2 bits (depending on which round), and
- * then converted into a key schedule element using the bit-selection
- * function PC2.
- *
- * That's the actual algorithm. Now for the tedious details: all those
- * painful permutations and lookup tables.
- *
- * IP is a 64-to-64 bit permutation. Its output contains the following
- * bits of its input (listed in order MSB to LSB of output).
- *
- * 6 14 22 30 38 46 54 62 4 12 20 28 36 44 52 60
- * 2 10 18 26 34 42 50 58 0 8 16 24 32 40 48 56
- * 7 15 23 31 39 47 55 63 5 13 21 29 37 45 53 61
- * 3 11 19 27 35 43 51 59 1 9 17 25 33 41 49 57
- *
- * E is a 32-to-48 bit selection function. Its output contains the following
- * bits of its input (listed in order MSB to LSB of output).
- *
- * 0 31 30 29 28 27 28 27 26 25 24 23 24 23 22 21 20 19 20 19 18 17 16 15
- * 16 15 14 13 12 11 12 11 10 9 8 7 8 7 6 5 4 3 4 3 2 1 0 31
- *
- * The S-boxes are arbitrary table-lookups each mapping a 6-bit input to a
- * 4-bit output. In other words, each S-box is an array[64] of 4-bit numbers.
- * The S-boxes are listed below. The first S-box listed is applied to the
- * most significant six bits of the block X; the last one is applied to the
- * least significant.
- *
- * 14 0 4 15 13 7 1 4 2 14 15 2 11 13 8 1
- * 3 10 10 6 6 12 12 11 5 9 9 5 0 3 7 8
- * 4 15 1 12 14 8 8 2 13 4 6 9 2 1 11 7
- * 15 5 12 11 9 3 7 14 3 10 10 0 5 6 0 13
- *
- * 15 3 1 13 8 4 14 7 6 15 11 2 3 8 4 14
- * 9 12 7 0 2 1 13 10 12 6 0 9 5 11 10 5
- * 0 13 14 8 7 10 11 1 10 3 4 15 13 4 1 2
- * 5 11 8 6 12 7 6 12 9 0 3 5 2 14 15 9
- *
- * 10 13 0 7 9 0 14 9 6 3 3 4 15 6 5 10
- * 1 2 13 8 12 5 7 14 11 12 4 11 2 15 8 1
- * 13 1 6 10 4 13 9 0 8 6 15 9 3 8 0 7
- * 11 4 1 15 2 14 12 3 5 11 10 5 14 2 7 12
- *
- * 7 13 13 8 14 11 3 5 0 6 6 15 9 0 10 3
- * 1 4 2 7 8 2 5 12 11 1 12 10 4 14 15 9
- * 10 3 6 15 9 0 0 6 12 10 11 1 7 13 13 8
- * 15 9 1 4 3 5 14 11 5 12 2 7 8 2 4 14
- *
- * 2 14 12 11 4 2 1 12 7 4 10 7 11 13 6 1
- * 8 5 5 0 3 15 15 10 13 3 0 9 14 8 9 6
- * 4 11 2 8 1 12 11 7 10 1 13 14 7 2 8 13
- * 15 6 9 15 12 0 5 9 6 10 3 4 0 5 14 3
- *
- * 12 10 1 15 10 4 15 2 9 7 2 12 6 9 8 5
- * 0 6 13 1 3 13 4 14 14 0 7 11 5 3 11 8
- * 9 4 14 3 15 2 5 12 2 9 8 5 12 15 3 10
- * 7 11 0 14 4 1 10 7 1 6 13 0 11 8 6 13
- *
- * 4 13 11 0 2 11 14 7 15 4 0 9 8 1 13 10
- * 3 14 12 3 9 5 7 12 5 2 10 15 6 8 1 6
- * 1 6 4 11 11 13 13 8 12 1 3 4 7 10 14 7
- * 10 9 15 5 6 0 8 15 0 14 5 2 9 3 2 12
- *
- * 13 1 2 15 8 13 4 8 6 10 15 3 11 7 1 4
- * 10 12 9 5 3 6 14 11 5 0 0 14 12 9 7 2
- * 7 2 11 1 4 14 1 7 9 4 12 10 14 8 2 13
- * 0 15 6 12 10 9 13 0 15 3 3 5 5 6 8 11
- *
- * P is a 32-to-32 bit permutation. Its output contains the following
- * bits of its input (listed in order MSB to LSB of output).
- *
- * 16 25 12 11 3 20 4 15 31 17 9 6 27 14 1 22
- * 30 24 8 18 0 5 29 23 13 19 2 26 10 21 28 7
- *
- * PC1 is a 64-to-56 bit selection function. Its output is in two words,
- * C and D. The word C contains the following bits of its input (listed
- * in order MSB to LSB of output).
- *
- * 7 15 23 31 39 47 55 63 6 14 22 30 38 46
- * 54 62 5 13 21 29 37 45 53 61 4 12 20 28
- *
- * And the word D contains these bits.
- *
- * 1 9 17 25 33 41 49 57 2 10 18 26 34 42
- * 50 58 3 11 19 27 35 43 51 59 36 44 52 60
- *
- * PC2 is a 56-to-48 bit selection function. Its input is in two words,
- * C and D. These are treated as one 56-bit word (with C more significant,
- * so that bits 55 to 28 of the word are bits 27 to 0 of C, and bits 27 to
- * 0 of the word are bits 27 to 0 of D). The output contains the following
- * bits of this 56-bit input word (listed in order MSB to LSB of output).
- *
- * 42 39 45 32 55 51 53 28 41 50 35 46 33 37 44 52 30 48 40 49 29 36 43 54
- * 15 4 25 19 9 1 26 16 5 11 23 8 12 7 17 0 22 3 10 14 6 20 27 24
- */
-
-/*
- * Implementation details
- * ----------------------
- *
- * If you look at the code in this module, you'll find it looks
- * nothing _like_ the above algorithm. Here I explain the
- * differences...
- *
- * Key setup has not been heavily optimised here. We are not
- * concerned with key agility: we aren't codebreakers. We don't
- * mind a little delay (and it really is a little one; it may be a
- * factor of five or so slower than it could be but it's still not
- * an appreciable length of time) while setting up. The only tweaks
- * in the key setup are ones which change the format of the key
- * schedule to speed up the actual encryption. I'll describe those
- * below.
- *
- * The first and most obvious optimisation is the S-boxes. Since
- * each S-box always targets the same four bits in the final 32-bit
- * word, so the output from (for example) S-box 0 must always be
- * shifted left 28 bits, we can store the already-shifted outputs
- * in the lookup tables. This reduces lookup-and-shift to lookup,
- * so the S-box step is now just a question of ORing together eight
- * table lookups.
- *
- * The permutation P is just a bit order change; it's invariant
- * with respect to OR, in that P(x)|P(y) = P(x|y). Therefore, we
- * can apply P to every entry of the S-box tables and then we don't
- * have to do it in the code of f(). This yields a set of tables
- * which might be called SP-boxes.
- *
- * The bit-selection function E is our next target. Note that E is
- * immediately followed by the operation of splitting into 6-bit
- * chunks. Examining the 6-bit chunks coming out of E we notice
- * they're all contiguous within the word (speaking cyclically -
- * the end two wrap round); so we can extract those bit strings
- * individually rather than explicitly running E. This would yield
- * code such as
- *
- * y |= SPboxes[0][ (rotl(R, 5) ^ top6bitsofK) & 0x3F ];
- * t |= SPboxes[1][ (rotl(R,11) ^ next6bitsofK) & 0x3F ];
- *
- * and so on; and the key schedule preparation would have to
- * provide each 6-bit chunk separately.
- *
- * Really we'd like to XOR in the key schedule element before
- * looking up bit strings in R. This we can't do, naively, because
- * the 6-bit strings we want overlap. But look at the strings:
- *
- * 3322222222221111111111
- * bit 10987654321098765432109876543210
- *
- * box0 XXXXX X
- * box1 XXXXXX
- * box2 XXXXXX
- * box3 XXXXXX
- * box4 XXXXXX
- * box5 XXXXXX
- * box6 XXXXXX
- * box7 X XXXXX
- *
- * The bit strings we need to XOR in for boxes 0, 2, 4 and 6 don't
- * overlap with each other. Neither do the ones for boxes 1, 3, 5
- * and 7. So we could provide the key schedule in the form of two
- * words that we can separately XOR into R, and then every S-box
- * index is available as a (cyclically) contiguous 6-bit substring
- * of one or the other of the results.
- *
- * The comments in Eric Young's libdes implementation point out
- * that two of these bit strings require a rotation (rather than a
- * simple shift) to extract. It's unavoidable that at least _one_
- * must do; but we can actually run the whole inner algorithm (all
- * 16 rounds) rotated one bit to the left, so that what the `real'
- * DES description sees as L=0x80000001 we see as L=0x00000003.
- * This requires rotating all our SP-box entries one bit to the
- * left, and rotating each word of the key schedule elements one to
- * the left, and rotating L and R one bit left just after IP and
- * one bit right again just before FP. And in each round we convert
- * a rotate into a shift, so we've saved a few per cent.
- *
- * That's about it for the inner loop; the SP-box tables as listed
- * below are what I've described here (the original S value,
- * shifted to its final place in the input to P, run through P, and
- * then rotated one bit left). All that remains is to optimise the
- * initial permutation IP.
- *
- * IP is not an arbitrary permutation. It has the nice property
- * that if you take any bit number, write it in binary (6 bits),
- * permute those 6 bits and invert some of them, you get the final
- * position of that bit. Specifically, the bit whose initial
- * position is given (in binary) as fedcba ends up in position
- * AcbFED (where a capital letter denotes the inverse of a bit).
- *
- * We have the 64-bit data in two 32-bit words L and R, where bits
- * in L are those with f=1 and bits in R are those with f=0. We
- * note that we can do a simple transformation: suppose we exchange
- * the bits with f=1,c=0 and the bits with f=0,c=1. This will cause
- * the bit fedcba to be in position cedfba - we've `swapped' bits c
- * and f in the position of each bit!
- *
- * Better still, this transformation is easy. In the example above,
- * bits in L with c=0 are bits 0x0F0F0F0F, and those in R with c=1
- * are 0xF0F0F0F0. So we can do
- *
- * difference = ((R >> 4) ^ L) & 0x0F0F0F0F
- * R ^= (difference << 4)
- * L ^= difference
- *
- * to perform the swap. Let's denote this by bitswap(4,0x0F0F0F0F).
- * Also, we can invert the bit at the top just by exchanging L and
- * R. So in a few swaps and a few of these bit operations we can
- * do:
- *
- * Initially the position of bit fedcba is fedcba
- * Swap L with R to make it Fedcba
- * Perform bitswap( 4,0x0F0F0F0F) to make it cedFba
- * Perform bitswap(16,0x0000FFFF) to make it ecdFba
- * Swap L with R to make it EcdFba
- * Perform bitswap( 2,0x33333333) to make it bcdFEa
- * Perform bitswap( 8,0x00FF00FF) to make it dcbFEa
- * Swap L with R to make it DcbFEa
- * Perform bitswap( 1,0x55555555) to make it acbFED
- * Swap L with R to make it AcbFED
- *
- * (In the actual code the four swaps are implicit: R and L are
- * simply used the other way round in the first, second and last
- * bitswap operations.)
- *
- * The final permutation is just the inverse of IP, so it can be
- * performed by a similar set of operations.
- */
-
-typedef struct {
- word32 k0246[16], k1357[16];
- word32 iv0, iv1;
-} DESContext;
-
-#define rotl(x, c) ( (x << c) | (x >> (32-c)) )
-#define rotl28(x, c) ( ( (x << c) | (x >> (28-c)) ) & 0x0FFFFFFF)
-
-static word32 bitsel(word32 * input, const int *bitnums, int size)
-{
- word32 ret = 0;
- while (size--) {
- int bitpos = *bitnums++;
- ret <<= 1;
- if (bitpos >= 0)
- ret |= 1 & (input[bitpos / 32] >> (bitpos % 32));
- }
- return ret;
-}
-
-static void des_key_setup(word32 key_msw, word32 key_lsw, DESContext * sched)
-{
-
- static const int PC1_Cbits[] = {
- 7, 15, 23, 31, 39, 47, 55, 63, 6, 14, 22, 30, 38, 46,
- 54, 62, 5, 13, 21, 29, 37, 45, 53, 61, 4, 12, 20, 28
- };
- static const int PC1_Dbits[] = {
- 1, 9, 17, 25, 33, 41, 49, 57, 2, 10, 18, 26, 34, 42,
- 50, 58, 3, 11, 19, 27, 35, 43, 51, 59, 36, 44, 52, 60
- };
- /*
- * The bit numbers in the two lists below don't correspond to
- * the ones in the above description of PC2, because in the
- * above description C and D are concatenated so `bit 28' means
- * bit 0 of C. In this implementation we're using the standard
- * `bitsel' function above and C is in the second word, so bit
- * 0 of C is addressed by writing `32' here.
- */
- static const int PC2_0246[] = {
- 49, 36, 59, 55, -1, -1, 37, 41, 48, 56, 34, 52, -1, -1, 15, 4,
- 25, 19, 9, 1, -1, -1, 12, 7, 17, 0, 22, 3, -1, -1, 46, 43
- };
- static const int PC2_1357[] = {
- -1, -1, 57, 32, 45, 54, 39, 50, -1, -1, 44, 53, 33, 40, 47, 58,
- -1, -1, 26, 16, 5, 11, 23, 8, -1, -1, 10, 14, 6, 20, 27, 24
- };
- static const int leftshifts[] =
- { 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 };
-
- word32 C, D;
- word32 buf[2];
- int i;
-
- buf[0] = key_lsw;
- buf[1] = key_msw;
-
- C = bitsel(buf, PC1_Cbits, 28);
- D = bitsel(buf, PC1_Dbits, 28);
-
- for (i = 0; i < 16; i++) {
- C = rotl28(C, leftshifts[i]);
- D = rotl28(D, leftshifts[i]);
- buf[0] = D;
- buf[1] = C;
- sched->k0246[i] = bitsel(buf, PC2_0246, 32);
- sched->k1357[i] = bitsel(buf, PC2_1357, 32);
- }
-
- sched->iv0 = sched->iv1 = 0;
-}
-
-static const word32 SPboxes[8][64] = {
- {0x01010400, 0x00000000, 0x00010000, 0x01010404,
- 0x01010004, 0x00010404, 0x00000004, 0x00010000,
- 0x00000400, 0x01010400, 0x01010404, 0x00000400,
- 0x01000404, 0x01010004, 0x01000000, 0x00000004,
- 0x00000404, 0x01000400, 0x01000400, 0x00010400,
- 0x00010400, 0x01010000, 0x01010000, 0x01000404,
- 0x00010004, 0x01000004, 0x01000004, 0x00010004,
- 0x00000000, 0x00000404, 0x00010404, 0x01000000,
- 0x00010000, 0x01010404, 0x00000004, 0x01010000,
- 0x01010400, 0x01000000, 0x01000000, 0x00000400,
- 0x01010004, 0x00010000, 0x00010400, 0x01000004,
- 0x00000400, 0x00000004, 0x01000404, 0x00010404,
- 0x01010404, 0x00010004, 0x01010000, 0x01000404,
- 0x01000004, 0x00000404, 0x00010404, 0x01010400,
- 0x00000404, 0x01000400, 0x01000400, 0x00000000,
- 0x00010004, 0x00010400, 0x00000000, 0x01010004L},
-
- {0x80108020, 0x80008000, 0x00008000, 0x00108020,
- 0x00100000, 0x00000020, 0x80100020, 0x80008020,
- 0x80000020, 0x80108020, 0x80108000, 0x80000000,
- 0x80008000, 0x00100000, 0x00000020, 0x80100020,
- 0x00108000, 0x00100020, 0x80008020, 0x00000000,
- 0x80000000, 0x00008000, 0x00108020, 0x80100000,
- 0x00100020, 0x80000020, 0x00000000, 0x00108000,
- 0x00008020, 0x80108000, 0x80100000, 0x00008020,
- 0x00000000, 0x00108020, 0x80100020, 0x00100000,
- 0x80008020, 0x80100000, 0x80108000, 0x00008000,
- 0x80100000, 0x80008000, 0x00000020, 0x80108020,
- 0x00108020, 0x00000020, 0x00008000, 0x80000000,
- 0x00008020, 0x80108000, 0x00100000, 0x80000020,
- 0x00100020, 0x80008020, 0x80000020, 0x00100020,
- 0x00108000, 0x00000000, 0x80008000, 0x00008020,
- 0x80000000, 0x80100020, 0x80108020, 0x00108000L},
-
- {0x00000208, 0x08020200, 0x00000000, 0x08020008,
- 0x08000200, 0x00000000, 0x00020208, 0x08000200,
- 0x00020008, 0x08000008, 0x08000008, 0x00020000,
- 0x08020208, 0x00020008, 0x08020000, 0x00000208,
- 0x08000000, 0x00000008, 0x08020200, 0x00000200,
- 0x00020200, 0x08020000, 0x08020008, 0x00020208,
- 0x08000208, 0x00020200, 0x00020000, 0x08000208,
- 0x00000008, 0x08020208, 0x00000200, 0x08000000,
- 0x08020200, 0x08000000, 0x00020008, 0x00000208,
- 0x00020000, 0x08020200, 0x08000200, 0x00000000,
- 0x00000200, 0x00020008, 0x08020208, 0x08000200,
- 0x08000008, 0x00000200, 0x00000000, 0x08020008,
- 0x08000208, 0x00020000, 0x08000000, 0x08020208,
- 0x00000008, 0x00020208, 0x00020200, 0x08000008,
- 0x08020000, 0x08000208, 0x00000208, 0x08020000,
- 0x00020208, 0x00000008, 0x08020008, 0x00020200L},
-
- {0x00802001, 0x00002081, 0x00002081, 0x00000080,
- 0x00802080, 0x00800081, 0x00800001, 0x00002001,
- 0x00000000, 0x00802000, 0x00802000, 0x00802081,
- 0x00000081, 0x00000000, 0x00800080, 0x00800001,
- 0x00000001, 0x00002000, 0x00800000, 0x00802001,
- 0x00000080, 0x00800000, 0x00002001, 0x00002080,
- 0x00800081, 0x00000001, 0x00002080, 0x00800080,
- 0x00002000, 0x00802080, 0x00802081, 0x00000081,
- 0x00800080, 0x00800001, 0x00802000, 0x00802081,
- 0x00000081, 0x00000000, 0x00000000, 0x00802000,
- 0x00002080, 0x00800080, 0x00800081, 0x00000001,
- 0x00802001, 0x00002081, 0x00002081, 0x00000080,
- 0x00802081, 0x00000081, 0x00000001, 0x00002000,
- 0x00800001, 0x00002001, 0x00802080, 0x00800081,
- 0x00002001, 0x00002080, 0x00800000, 0x00802001,
- 0x00000080, 0x00800000, 0x00002000, 0x00802080L},
-
- {0x00000100, 0x02080100, 0x02080000, 0x42000100,
- 0x00080000, 0x00000100, 0x40000000, 0x02080000,
- 0x40080100, 0x00080000, 0x02000100, 0x40080100,
- 0x42000100, 0x42080000, 0x00080100, 0x40000000,
- 0x02000000, 0x40080000, 0x40080000, 0x00000000,
- 0x40000100, 0x42080100, 0x42080100, 0x02000100,
- 0x42080000, 0x40000100, 0x00000000, 0x42000000,
- 0x02080100, 0x02000000, 0x42000000, 0x00080100,
- 0x00080000, 0x42000100, 0x00000100, 0x02000000,
- 0x40000000, 0x02080000, 0x42000100, 0x40080100,
- 0x02000100, 0x40000000, 0x42080000, 0x02080100,
- 0x40080100, 0x00000100, 0x02000000, 0x42080000,
- 0x42080100, 0x00080100, 0x42000000, 0x42080100,
- 0x02080000, 0x00000000, 0x40080000, 0x42000000,
- 0x00080100, 0x02000100, 0x40000100, 0x00080000,
- 0x00000000, 0x40080000, 0x02080100, 0x40000100L},
-
- {0x20000010, 0x20400000, 0x00004000, 0x20404010,
- 0x20400000, 0x00000010, 0x20404010, 0x00400000,
- 0x20004000, 0x00404010, 0x00400000, 0x20000010,
- 0x00400010, 0x20004000, 0x20000000, 0x00004010,
- 0x00000000, 0x00400010, 0x20004010, 0x00004000,
- 0x00404000, 0x20004010, 0x00000010, 0x20400010,
- 0x20400010, 0x00000000, 0x00404010, 0x20404000,
- 0x00004010, 0x00404000, 0x20404000, 0x20000000,
- 0x20004000, 0x00000010, 0x20400010, 0x00404000,
- 0x20404010, 0x00400000, 0x00004010, 0x20000010,
- 0x00400000, 0x20004000, 0x20000000, 0x00004010,
- 0x20000010, 0x20404010, 0x00404000, 0x20400000,
- 0x00404010, 0x20404000, 0x00000000, 0x20400010,
- 0x00000010, 0x00004000, 0x20400000, 0x00404010,
- 0x00004000, 0x00400010, 0x20004010, 0x00000000,
- 0x20404000, 0x20000000, 0x00400010, 0x20004010L},
-
- {0x00200000, 0x04200002, 0x04000802, 0x00000000,
- 0x00000800, 0x04000802, 0x00200802, 0x04200800,
- 0x04200802, 0x00200000, 0x00000000, 0x04000002,
- 0x00000002, 0x04000000, 0x04200002, 0x00000802,
- 0x04000800, 0x00200802, 0x00200002, 0x04000800,
- 0x04000002, 0x04200000, 0x04200800, 0x00200002,
- 0x04200000, 0x00000800, 0x00000802, 0x04200802,
- 0x00200800, 0x00000002, 0x04000000, 0x00200800,
- 0x04000000, 0x00200800, 0x00200000, 0x04000802,
- 0x04000802, 0x04200002, 0x04200002, 0x00000002,
- 0x00200002, 0x04000000, 0x04000800, 0x00200000,
- 0x04200800, 0x00000802, 0x00200802, 0x04200800,
- 0x00000802, 0x04000002, 0x04200802, 0x04200000,
- 0x00200800, 0x00000000, 0x00000002, 0x04200802,
- 0x00000000, 0x00200802, 0x04200000, 0x00000800,
- 0x04000002, 0x04000800, 0x00000800, 0x00200002L},
-
- {0x10001040, 0x00001000, 0x00040000, 0x10041040,
- 0x10000000, 0x10001040, 0x00000040, 0x10000000,
- 0x00040040, 0x10040000, 0x10041040, 0x00041000,
- 0x10041000, 0x00041040, 0x00001000, 0x00000040,
- 0x10040000, 0x10000040, 0x10001000, 0x00001040,
- 0x00041000, 0x00040040, 0x10040040, 0x10041000,
- 0x00001040, 0x00000000, 0x00000000, 0x10040040,
- 0x10000040, 0x10001000, 0x00041040, 0x00040000,
- 0x00041040, 0x00040000, 0x10041000, 0x00001000,
- 0x00000040, 0x10040040, 0x00001000, 0x00041040,
- 0x10001000, 0x00000040, 0x10000040, 0x10040000,
- 0x10040040, 0x10000000, 0x00040000, 0x10001040,
- 0x00000000, 0x10041040, 0x00040040, 0x10000040,
- 0x10040000, 0x10001000, 0x10001040, 0x00000000,
- 0x10041040, 0x00041000, 0x00041000, 0x00001040,
- 0x00001040, 0x00040040, 0x10000000, 0x10041000L}
-};
-
-#define f(R, K0246, K1357) (\
- s0246 = R ^ K0246, \
- s1357 = R ^ K1357, \
- s0246 = rotl(s0246, 28), \
- SPboxes[0] [(s0246 >> 24) & 0x3F] | \
- SPboxes[1] [(s1357 >> 24) & 0x3F] | \
- SPboxes[2] [(s0246 >> 16) & 0x3F] | \
- SPboxes[3] [(s1357 >> 16) & 0x3F] | \
- SPboxes[4] [(s0246 >> 8) & 0x3F] | \
- SPboxes[5] [(s1357 >> 8) & 0x3F] | \
- SPboxes[6] [(s0246 ) & 0x3F] | \
- SPboxes[7] [(s1357 ) & 0x3F])
-
-#define bitswap(L, R, n, mask) (\
- swap = mask & ( (R >> n) ^ L ), \
- R ^= swap << n, \
- L ^= swap)
-
-/* Initial permutation */
-#define IP(L, R) (\
- bitswap(R, L, 4, 0x0F0F0F0F), \
- bitswap(R, L, 16, 0x0000FFFF), \
- bitswap(L, R, 2, 0x33333333), \
- bitswap(L, R, 8, 0x00FF00FF), \
- bitswap(R, L, 1, 0x55555555))
-
-/* Final permutation */
-#define FP(L, R) (\
- bitswap(R, L, 1, 0x55555555), \
- bitswap(L, R, 8, 0x00FF00FF), \
- bitswap(L, R, 2, 0x33333333), \
- bitswap(R, L, 16, 0x0000FFFF), \
- bitswap(R, L, 4, 0x0F0F0F0F))
-
-static void des_encipher(word32 * output, word32 L, word32 R,
- DESContext * sched)
-{
- word32 swap, s0246, s1357;
-
- IP(L, R);
-
- L = rotl(L, 1);
- R = rotl(R, 1);
-
- L ^= f(R, sched->k0246[0], sched->k1357[0]);
- R ^= f(L, sched->k0246[1], sched->k1357[1]);
- L ^= f(R, sched->k0246[2], sched->k1357[2]);
- R ^= f(L, sched->k0246[3], sched->k1357[3]);
- L ^= f(R, sched->k0246[4], sched->k1357[4]);
- R ^= f(L, sched->k0246[5], sched->k1357[5]);
- L ^= f(R, sched->k0246[6], sched->k1357[6]);
- R ^= f(L, sched->k0246[7], sched->k1357[7]);
- L ^= f(R, sched->k0246[8], sched->k1357[8]);
- R ^= f(L, sched->k0246[9], sched->k1357[9]);
- L ^= f(R, sched->k0246[10], sched->k1357[10]);
- R ^= f(L, sched->k0246[11], sched->k1357[11]);
- L ^= f(R, sched->k0246[12], sched->k1357[12]);
- R ^= f(L, sched->k0246[13], sched->k1357[13]);
- L ^= f(R, sched->k0246[14], sched->k1357[14]);
- R ^= f(L, sched->k0246[15], sched->k1357[15]);
-
- L = rotl(L, 31);
- R = rotl(R, 31);
-
- swap = L;
- L = R;
- R = swap;
-
- FP(L, R);
-
- output[0] = L;
- output[1] = R;
-}
-
-static void des_decipher(word32 * output, word32 L, word32 R,
- DESContext * sched)
-{
- word32 swap, s0246, s1357;
-
- IP(L, R);
-
- L = rotl(L, 1);
- R = rotl(R, 1);
-
- L ^= f(R, sched->k0246[15], sched->k1357[15]);
- R ^= f(L, sched->k0246[14], sched->k1357[14]);
- L ^= f(R, sched->k0246[13], sched->k1357[13]);
- R ^= f(L, sched->k0246[12], sched->k1357[12]);
- L ^= f(R, sched->k0246[11], sched->k1357[11]);
- R ^= f(L, sched->k0246[10], sched->k1357[10]);
- L ^= f(R, sched->k0246[9], sched->k1357[9]);
- R ^= f(L, sched->k0246[8], sched->k1357[8]);
- L ^= f(R, sched->k0246[7], sched->k1357[7]);
- R ^= f(L, sched->k0246[6], sched->k1357[6]);
- L ^= f(R, sched->k0246[5], sched->k1357[5]);
- R ^= f(L, sched->k0246[4], sched->k1357[4]);
- L ^= f(R, sched->k0246[3], sched->k1357[3]);
- R ^= f(L, sched->k0246[2], sched->k1357[2]);
- L ^= f(R, sched->k0246[1], sched->k1357[1]);
- R ^= f(L, sched->k0246[0], sched->k1357[0]);
-
- L = rotl(L, 31);
- R = rotl(R, 31);
-
- swap = L;
- L = R;
- R = swap;
-
- FP(L, R);
-
- output[0] = L;
- output[1] = R;
-}
-
-static void des_cbc_encrypt(unsigned char *blk,
- unsigned int len, DESContext * sched)
-{
- word32 out[2], iv0, iv1;
- unsigned int i;
-
- assert((len & 7) == 0);
-
- iv0 = sched->iv0;
- iv1 = sched->iv1;
- for (i = 0; i < len; i += 8) {
- iv0 ^= GET_32BIT_MSB_FIRST(blk);
- iv1 ^= GET_32BIT_MSB_FIRST(blk + 4);
- des_encipher(out, iv0, iv1, sched);
- iv0 = out[0];
- iv1 = out[1];
- PUT_32BIT_MSB_FIRST(blk, iv0);
- PUT_32BIT_MSB_FIRST(blk + 4, iv1);
- blk += 8;
- }
- sched->iv0 = iv0;
- sched->iv1 = iv1;
-}
-
-static void des_cbc_decrypt(unsigned char *blk,
- unsigned int len, DESContext * sched)
-{
- word32 out[2], iv0, iv1, xL, xR;
- unsigned int i;
-
- assert((len & 7) == 0);
-
- iv0 = sched->iv0;
- iv1 = sched->iv1;
- for (i = 0; i < len; i += 8) {
- xL = GET_32BIT_MSB_FIRST(blk);
- xR = GET_32BIT_MSB_FIRST(blk + 4);
- des_decipher(out, xL, xR, sched);
- iv0 ^= out[0];
- iv1 ^= out[1];
- PUT_32BIT_MSB_FIRST(blk, iv0);
- PUT_32BIT_MSB_FIRST(blk + 4, iv1);
- blk += 8;
- iv0 = xL;
- iv1 = xR;
- }
- sched->iv0 = iv0;
- sched->iv1 = iv1;
-}
-
-static void des_3cbc_encrypt(unsigned char *blk,
- unsigned int len, DESContext * scheds)
-{
- des_cbc_encrypt(blk, len, &scheds[0]);
- des_cbc_decrypt(blk, len, &scheds[1]);
- des_cbc_encrypt(blk, len, &scheds[2]);
-}
-
-static void des_cbc3_encrypt(unsigned char *blk,
- unsigned int len, DESContext * scheds)
-{
- word32 out[2], iv0, iv1;
- unsigned int i;
-
- assert((len & 7) == 0);
-
- iv0 = scheds->iv0;
- iv1 = scheds->iv1;
- for (i = 0; i < len; i += 8) {
- iv0 ^= GET_32BIT_MSB_FIRST(blk);
- iv1 ^= GET_32BIT_MSB_FIRST(blk + 4);
- des_encipher(out, iv0, iv1, &scheds[0]);
- des_decipher(out, out[0], out[1], &scheds[1]);
- des_encipher(out, out[0], out[1], &scheds[2]);
- iv0 = out[0];
- iv1 = out[1];
- PUT_32BIT_MSB_FIRST(blk, iv0);
- PUT_32BIT_MSB_FIRST(blk + 4, iv1);
- blk += 8;
- }
- scheds->iv0 = iv0;
- scheds->iv1 = iv1;
-}
-
-static void des_3cbc_decrypt(unsigned char *blk,
- unsigned int len, DESContext * scheds)
-{
- des_cbc_decrypt(blk, len, &scheds[2]);
- des_cbc_encrypt(blk, len, &scheds[1]);
- des_cbc_decrypt(blk, len, &scheds[0]);
-}
-
-static void des_cbc3_decrypt(unsigned char *blk,
- unsigned int len, DESContext * scheds)
-{
- word32 out[2], iv0, iv1, xL, xR;
- unsigned int i;
-
- assert((len & 7) == 0);
-
- iv0 = scheds->iv0;
- iv1 = scheds->iv1;
- for (i = 0; i < len; i += 8) {
- xL = GET_32BIT_MSB_FIRST(blk);
- xR = GET_32BIT_MSB_FIRST(blk + 4);
- des_decipher(out, xL, xR, &scheds[2]);
- des_encipher(out, out[0], out[1], &scheds[1]);
- des_decipher(out, out[0], out[1], &scheds[0]);
- iv0 ^= out[0];
- iv1 ^= out[1];
- PUT_32BIT_MSB_FIRST(blk, iv0);
- PUT_32BIT_MSB_FIRST(blk + 4, iv1);
- blk += 8;
- iv0 = xL;
- iv1 = xR;
- }
- scheds->iv0 = iv0;
- scheds->iv1 = iv1;
-}
-
-static void des_sdctr3(unsigned char *blk,
- unsigned int len, DESContext * scheds)
-{
- word32 b[2], iv0, iv1, tmp;
- unsigned int i;
-
- assert((len & 7) == 0);
-
- iv0 = scheds->iv0;
- iv1 = scheds->iv1;
- for (i = 0; i < len; i += 8) {
- des_encipher(b, iv0, iv1, &scheds[0]);
- des_decipher(b, b[0], b[1], &scheds[1]);
- des_encipher(b, b[0], b[1], &scheds[2]);
- tmp = GET_32BIT_MSB_FIRST(blk);
- PUT_32BIT_MSB_FIRST(blk, tmp ^ b[0]);
- blk += 4;
- tmp = GET_32BIT_MSB_FIRST(blk);
- PUT_32BIT_MSB_FIRST(blk, tmp ^ b[1]);
- blk += 4;
- if ((iv1 = (iv1 + 1) & 0xffffffff) == 0)
- iv0 = (iv0 + 1) & 0xffffffff;
- }
- scheds->iv0 = iv0;
- scheds->iv1 = iv1;
-}
-
-static void *des3_make_context(void)
-{
- return snewn(3, DESContext);
-}
-
-static void *des3_ssh1_make_context(void)
-{
- /* Need 3 keys for each direction, in SSH-1 */
- return snewn(6, DESContext);
-}
-
-static void *des_make_context(void)
-{
- return snew(DESContext);
-}
-
-static void *des_ssh1_make_context(void)
-{
- /* Need one key for each direction, in SSH-1 */
- return snewn(2, DESContext);
-}
-
-static void des3_free_context(void *handle) /* used for both 3DES and DES */
-{
- sfree(handle);
-}
-
-static void des3_key(void *handle, unsigned char *key)
-{
- DESContext *keys = (DESContext *) handle;
- des_key_setup(GET_32BIT_MSB_FIRST(key),
- GET_32BIT_MSB_FIRST(key + 4), &keys[0]);
- des_key_setup(GET_32BIT_MSB_FIRST(key + 8),
- GET_32BIT_MSB_FIRST(key + 12), &keys[1]);
- des_key_setup(GET_32BIT_MSB_FIRST(key + 16),
- GET_32BIT_MSB_FIRST(key + 20), &keys[2]);
-}
-
-static void des3_iv(void *handle, unsigned char *key)
-{
- DESContext *keys = (DESContext *) handle;
- keys[0].iv0 = GET_32BIT_MSB_FIRST(key);
- keys[0].iv1 = GET_32BIT_MSB_FIRST(key + 4);
-}
-
-static void des_key(void *handle, unsigned char *key)
-{
- DESContext *keys = (DESContext *) handle;
- des_key_setup(GET_32BIT_MSB_FIRST(key),
- GET_32BIT_MSB_FIRST(key + 4), &keys[0]);
-}
-
-static void des3_sesskey(void *handle, unsigned char *key)
-{
- DESContext *keys = (DESContext *) handle;
- des3_key(keys, key);
- des3_key(keys+3, key);
-}
-
-static void des3_encrypt_blk(void *handle, unsigned char *blk, int len)
-{
- DESContext *keys = (DESContext *) handle;
- des_3cbc_encrypt(blk, len, keys);
-}
-
-static void des3_decrypt_blk(void *handle, unsigned char *blk, int len)
-{
- DESContext *keys = (DESContext *) handle;
- des_3cbc_decrypt(blk, len, keys+3);
-}
-
-static void des3_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len)
-{
- DESContext *keys = (DESContext *) handle;
- des_cbc3_encrypt(blk, len, keys);
-}
-
-static void des3_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len)
-{
- DESContext *keys = (DESContext *) handle;
- des_cbc3_decrypt(blk, len, keys);
-}
-
-static void des3_ssh2_sdctr(void *handle, unsigned char *blk, int len)
-{
- DESContext *keys = (DESContext *) handle;
- des_sdctr3(blk, len, keys);
-}
-
-static void des_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len)
-{
- DESContext *keys = (DESContext *) handle;
- des_cbc_encrypt(blk, len, keys);
-}
-
-static void des_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len)
-{
- DESContext *keys = (DESContext *) handle;
- des_cbc_decrypt(blk, len, keys);
-}
-
-void des3_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len)
-{
- DESContext ourkeys[3];
- des_key_setup(GET_32BIT_MSB_FIRST(key),
- GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]);
- des_key_setup(GET_32BIT_MSB_FIRST(key + 8),
- GET_32BIT_MSB_FIRST(key + 12), &ourkeys[1]);
- des_key_setup(GET_32BIT_MSB_FIRST(key),
- GET_32BIT_MSB_FIRST(key + 4), &ourkeys[2]);
- des_3cbc_decrypt(blk, len, ourkeys);
- memset(ourkeys, 0, sizeof(ourkeys));
-}
-
-void des3_encrypt_pubkey(unsigned char *key, unsigned char *blk, int len)
-{
- DESContext ourkeys[3];
- des_key_setup(GET_32BIT_MSB_FIRST(key),
- GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]);
- des_key_setup(GET_32BIT_MSB_FIRST(key + 8),
- GET_32BIT_MSB_FIRST(key + 12), &ourkeys[1]);
- des_key_setup(GET_32BIT_MSB_FIRST(key),
- GET_32BIT_MSB_FIRST(key + 4), &ourkeys[2]);
- des_3cbc_encrypt(blk, len, ourkeys);
- memset(ourkeys, 0, sizeof(ourkeys));
-}
-
-void des3_decrypt_pubkey_ossh(unsigned char *key, unsigned char *iv,
- unsigned char *blk, int len)
-{
- DESContext ourkeys[3];
- des_key_setup(GET_32BIT_MSB_FIRST(key),
- GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]);
- des_key_setup(GET_32BIT_MSB_FIRST(key + 8),
- GET_32BIT_MSB_FIRST(key + 12), &ourkeys[1]);
- des_key_setup(GET_32BIT_MSB_FIRST(key + 16),
- GET_32BIT_MSB_FIRST(key + 20), &ourkeys[2]);
- ourkeys[0].iv0 = GET_32BIT_MSB_FIRST(iv);
- ourkeys[0].iv1 = GET_32BIT_MSB_FIRST(iv+4);
- des_cbc3_decrypt(blk, len, ourkeys);
- memset(ourkeys, 0, sizeof(ourkeys));
-}
-
-void des3_encrypt_pubkey_ossh(unsigned char *key, unsigned char *iv,
- unsigned char *blk, int len)
-{
- DESContext ourkeys[3];
- des_key_setup(GET_32BIT_MSB_FIRST(key),
- GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]);
- des_key_setup(GET_32BIT_MSB_FIRST(key + 8),
- GET_32BIT_MSB_FIRST(key + 12), &ourkeys[1]);
- des_key_setup(GET_32BIT_MSB_FIRST(key + 16),
- GET_32BIT_MSB_FIRST(key + 20), &ourkeys[2]);
- ourkeys[0].iv0 = GET_32BIT_MSB_FIRST(iv);
- ourkeys[0].iv1 = GET_32BIT_MSB_FIRST(iv+4);
- des_cbc3_encrypt(blk, len, ourkeys);
- memset(ourkeys, 0, sizeof(ourkeys));
-}
-
-static void des_keysetup_xdmauth(unsigned char *keydata, DESContext *dc)
-{
- unsigned char key[8];
- int i, nbits, j;
- unsigned int bits;
-
- bits = 0;
- nbits = 0;
- j = 0;
- for (i = 0; i < 8; i++) {
- if (nbits < 7) {
- bits = (bits << 8) | keydata[j];
- nbits += 8;
- j++;
- }
- key[i] = (bits >> (nbits - 7)) << 1;
- bits &= ~(0x7F << (nbits - 7));
- nbits -= 7;
- }
-
- des_key_setup(GET_32BIT_MSB_FIRST(key), GET_32BIT_MSB_FIRST(key + 4), dc);
-}
-
-void des_encrypt_xdmauth(unsigned char *keydata, unsigned char *blk, int len)
-{
- DESContext dc;
- des_keysetup_xdmauth(keydata, &dc);
- des_cbc_encrypt(blk, 24, &dc);
-}
-
-void des_decrypt_xdmauth(unsigned char *keydata, unsigned char *blk, int len)
-{
- DESContext dc;
- des_keysetup_xdmauth(keydata, &dc);
- des_cbc_decrypt(blk, 24, &dc);
-}
-
-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,
- "3des-cbc",
- 8, 168, SSH_CIPHER_IS_CBC, "triple-DES CBC"
-};
-
-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,
- "3des-ctr",
- 8, 168, 0, "triple-DES SDCTR"
-};
-
-/*
- * Single DES in SSH-2. "des-cbc" is marked as HISTORIC in
- * RFC 4250, referring to
- * FIPS-46-3. ("Single DES (i.e., DES) will be permitted
- * for legacy systems only.") , but ssh.com support it and
- * apparently aren't the only people to do so, so we sigh
- * and implement it anyway.
- */
-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-cbc",
- 8, 56, SSH_CIPHER_IS_CBC, "single-DES CBC"
-};
-
-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-cbc@ssh.com",
- 8, 56, SSH_CIPHER_IS_CBC, "single-DES CBC"
-};
-
-static const struct ssh2_cipher *const des3_list[] = {
- &ssh_3des_ssh2_ctr,
- &ssh_3des_ssh2
-};
-
-const struct ssh2_ciphers ssh2_3des = {
- sizeof(des3_list) / sizeof(*des3_list),
- des3_list
-};
-
-static const struct ssh2_cipher *const des_list[] = {
- &ssh_des_ssh2,
- &ssh_des_sshcom_ssh2
-};
-
-const struct ssh2_ciphers ssh2_des = {
- sizeof(des_list) / sizeof(*des_list),
- des_list
-};
-
-const struct ssh_cipher ssh_3des = {
- des3_ssh1_make_context, des3_free_context, des3_sesskey,
- des3_encrypt_blk, des3_decrypt_blk,
- 8, "triple-DES inner-CBC"
-};
-
-static void des_sesskey(void *handle, unsigned char *key)
-{
- DESContext *keys = (DESContext *) handle;
- des_key(keys, key);
- des_key(keys+1, key);
-}
-
-static void des_encrypt_blk(void *handle, unsigned char *blk, int len)
-{
- DESContext *keys = (DESContext *) handle;
- des_cbc_encrypt(blk, len, keys);
-}
-
-static void des_decrypt_blk(void *handle, unsigned char *blk, int len)
-{
- DESContext *keys = (DESContext *) handle;
- des_cbc_decrypt(blk, len, keys+1);
-}
-
-const struct ssh_cipher ssh_des = {
- des_ssh1_make_context, des3_free_context, des_sesskey,
- des_encrypt_blk, des_decrypt_blk,
- 8, "single-DES CBC"
-};
+#include <assert.h>
+#include "ssh.h"
+
+
+/* des.c - implementation of DES
+ */
+
+/*
+ * Description of DES
+ * ------------------
+ *
+ * Unlike the description in FIPS 46, I'm going to use _sensible_ indices:
+ * bits in an n-bit word are numbered from 0 at the LSB to n-1 at the MSB.
+ * And S-boxes are indexed by six consecutive bits, not by the outer two
+ * followed by the middle four.
+ *
+ * The DES encryption routine requires a 64-bit input, and a key schedule K
+ * containing 16 48-bit elements.
+ *
+ * First the input is permuted by the initial permutation IP.
+ * Then the input is split into 32-bit words L and R. (L is the MSW.)
+ * Next, 16 rounds. In each round:
+ * (L, R) <- (R, L xor f(R, K[i]))
+ * Then the pre-output words L and R are swapped.
+ * Then L and R are glued back together into a 64-bit word. (L is the MSW,
+ * again, but since we just swapped them, the MSW is the R that came out
+ * of the last round.)
+ * The 64-bit output block is permuted by the inverse of IP and returned.
+ *
+ * Decryption is identical except that the elements of K are used in the
+ * opposite order. (This wouldn't work if that word swap didn't happen.)
+ *
+ * The function f, used in each round, accepts a 32-bit word R and a
+ * 48-bit key block K. It produces a 32-bit output.
+ *
+ * First R is expanded to 48 bits using the bit-selection function E.
+ * The resulting 48-bit block is XORed with the key block K to produce
+ * a 48-bit block X.
+ * This block X is split into eight groups of 6 bits. Each group of 6
+ * bits is then looked up in one of the eight S-boxes to convert
+ * it to 4 bits. These eight groups of 4 bits are glued back
+ * together to produce a 32-bit preoutput block.
+ * The preoutput block is permuted using the permutation P and returned.
+ *
+ * Key setup maps a 64-bit key word into a 16x48-bit key schedule. Although
+ * the approved input format for the key is a 64-bit word, eight of the
+ * bits are discarded, so the actual quantity of key used is 56 bits.
+ *
+ * First the input key is converted to two 28-bit words C and D using
+ * the bit-selection function PC1.
+ * Then 16 rounds of key setup occur. In each round, C and D are each
+ * rotated left by either 1 or 2 bits (depending on which round), and
+ * then converted into a key schedule element using the bit-selection
+ * function PC2.
+ *
+ * That's the actual algorithm. Now for the tedious details: all those
+ * painful permutations and lookup tables.
+ *
+ * IP is a 64-to-64 bit permutation. Its output contains the following
+ * bits of its input (listed in order MSB to LSB of output).
+ *
+ * 6 14 22 30 38 46 54 62 4 12 20 28 36 44 52 60
+ * 2 10 18 26 34 42 50 58 0 8 16 24 32 40 48 56
+ * 7 15 23 31 39 47 55 63 5 13 21 29 37 45 53 61
+ * 3 11 19 27 35 43 51 59 1 9 17 25 33 41 49 57
+ *
+ * E is a 32-to-48 bit selection function. Its output contains the following
+ * bits of its input (listed in order MSB to LSB of output).
+ *
+ * 0 31 30 29 28 27 28 27 26 25 24 23 24 23 22 21 20 19 20 19 18 17 16 15
+ * 16 15 14 13 12 11 12 11 10 9 8 7 8 7 6 5 4 3 4 3 2 1 0 31
+ *
+ * The S-boxes are arbitrary table-lookups each mapping a 6-bit input to a
+ * 4-bit output. In other words, each S-box is an array[64] of 4-bit numbers.
+ * The S-boxes are listed below. The first S-box listed is applied to the
+ * most significant six bits of the block X; the last one is applied to the
+ * least significant.
+ *
+ * 14 0 4 15 13 7 1 4 2 14 15 2 11 13 8 1
+ * 3 10 10 6 6 12 12 11 5 9 9 5 0 3 7 8
+ * 4 15 1 12 14 8 8 2 13 4 6 9 2 1 11 7
+ * 15 5 12 11 9 3 7 14 3 10 10 0 5 6 0 13
+ *
+ * 15 3 1 13 8 4 14 7 6 15 11 2 3 8 4 14
+ * 9 12 7 0 2 1 13 10 12 6 0 9 5 11 10 5
+ * 0 13 14 8 7 10 11 1 10 3 4 15 13 4 1 2
+ * 5 11 8 6 12 7 6 12 9 0 3 5 2 14 15 9
+ *
+ * 10 13 0 7 9 0 14 9 6 3 3 4 15 6 5 10
+ * 1 2 13 8 12 5 7 14 11 12 4 11 2 15 8 1
+ * 13 1 6 10 4 13 9 0 8 6 15 9 3 8 0 7
+ * 11 4 1 15 2 14 12 3 5 11 10 5 14 2 7 12
+ *
+ * 7 13 13 8 14 11 3 5 0 6 6 15 9 0 10 3
+ * 1 4 2 7 8 2 5 12 11 1 12 10 4 14 15 9
+ * 10 3 6 15 9 0 0 6 12 10 11 1 7 13 13 8
+ * 15 9 1 4 3 5 14 11 5 12 2 7 8 2 4 14
+ *
+ * 2 14 12 11 4 2 1 12 7 4 10 7 11 13 6 1
+ * 8 5 5 0 3 15 15 10 13 3 0 9 14 8 9 6
+ * 4 11 2 8 1 12 11 7 10 1 13 14 7 2 8 13
+ * 15 6 9 15 12 0 5 9 6 10 3 4 0 5 14 3
+ *
+ * 12 10 1 15 10 4 15 2 9 7 2 12 6 9 8 5
+ * 0 6 13 1 3 13 4 14 14 0 7 11 5 3 11 8
+ * 9 4 14 3 15 2 5 12 2 9 8 5 12 15 3 10
+ * 7 11 0 14 4 1 10 7 1 6 13 0 11 8 6 13
+ *
+ * 4 13 11 0 2 11 14 7 15 4 0 9 8 1 13 10
+ * 3 14 12 3 9 5 7 12 5 2 10 15 6 8 1 6
+ * 1 6 4 11 11 13 13 8 12 1 3 4 7 10 14 7
+ * 10 9 15 5 6 0 8 15 0 14 5 2 9 3 2 12
+ *
+ * 13 1 2 15 8 13 4 8 6 10 15 3 11 7 1 4
+ * 10 12 9 5 3 6 14 11 5 0 0 14 12 9 7 2
+ * 7 2 11 1 4 14 1 7 9 4 12 10 14 8 2 13
+ * 0 15 6 12 10 9 13 0 15 3 3 5 5 6 8 11
+ *
+ * P is a 32-to-32 bit permutation. Its output contains the following
+ * bits of its input (listed in order MSB to LSB of output).
+ *
+ * 16 25 12 11 3 20 4 15 31 17 9 6 27 14 1 22
+ * 30 24 8 18 0 5 29 23 13 19 2 26 10 21 28 7
+ *
+ * PC1 is a 64-to-56 bit selection function. Its output is in two words,
+ * C and D. The word C contains the following bits of its input (listed
+ * in order MSB to LSB of output).
+ *
+ * 7 15 23 31 39 47 55 63 6 14 22 30 38 46
+ * 54 62 5 13 21 29 37 45 53 61 4 12 20 28
+ *
+ * And the word D contains these bits.
+ *
+ * 1 9 17 25 33 41 49 57 2 10 18 26 34 42
+ * 50 58 3 11 19 27 35 43 51 59 36 44 52 60
+ *
+ * PC2 is a 56-to-48 bit selection function. Its input is in two words,
+ * C and D. These are treated as one 56-bit word (with C more significant,
+ * so that bits 55 to 28 of the word are bits 27 to 0 of C, and bits 27 to
+ * 0 of the word are bits 27 to 0 of D). The output contains the following
+ * bits of this 56-bit input word (listed in order MSB to LSB of output).
+ *
+ * 42 39 45 32 55 51 53 28 41 50 35 46 33 37 44 52 30 48 40 49 29 36 43 54
+ * 15 4 25 19 9 1 26 16 5 11 23 8 12 7 17 0 22 3 10 14 6 20 27 24
+ */
+
+/*
+ * Implementation details
+ * ----------------------
+ *
+ * If you look at the code in this module, you'll find it looks
+ * nothing _like_ the above algorithm. Here I explain the
+ * differences...
+ *
+ * Key setup has not been heavily optimised here. We are not
+ * concerned with key agility: we aren't codebreakers. We don't
+ * mind a little delay (and it really is a little one; it may be a
+ * factor of five or so slower than it could be but it's still not
+ * an appreciable length of time) while setting up. The only tweaks
+ * in the key setup are ones which change the format of the key
+ * schedule to speed up the actual encryption. I'll describe those
+ * below.
+ *
+ * The first and most obvious optimisation is the S-boxes. Since
+ * each S-box always targets the same four bits in the final 32-bit
+ * word, so the output from (for example) S-box 0 must always be
+ * shifted left 28 bits, we can store the already-shifted outputs
+ * in the lookup tables. This reduces lookup-and-shift to lookup,
+ * so the S-box step is now just a question of ORing together eight
+ * table lookups.
+ *
+ * The permutation P is just a bit order change; it's invariant
+ * with respect to OR, in that P(x)|P(y) = P(x|y). Therefore, we
+ * can apply P to every entry of the S-box tables and then we don't
+ * have to do it in the code of f(). This yields a set of tables
+ * which might be called SP-boxes.
+ *
+ * The bit-selection function E is our next target. Note that E is
+ * immediately followed by the operation of splitting into 6-bit
+ * chunks. Examining the 6-bit chunks coming out of E we notice
+ * they're all contiguous within the word (speaking cyclically -
+ * the end two wrap round); so we can extract those bit strings
+ * individually rather than explicitly running E. This would yield
+ * code such as
+ *
+ * y |= SPboxes[0][ (rotl(R, 5) ^ top6bitsofK) & 0x3F ];
+ * t |= SPboxes[1][ (rotl(R,11) ^ next6bitsofK) & 0x3F ];
+ *
+ * and so on; and the key schedule preparation would have to
+ * provide each 6-bit chunk separately.
+ *
+ * Really we'd like to XOR in the key schedule element before
+ * looking up bit strings in R. This we can't do, naively, because
+ * the 6-bit strings we want overlap. But look at the strings:
+ *
+ * 3322222222221111111111
+ * bit 10987654321098765432109876543210
+ *
+ * box0 XXXXX X
+ * box1 XXXXXX
+ * box2 XXXXXX
+ * box3 XXXXXX
+ * box4 XXXXXX
+ * box5 XXXXXX
+ * box6 XXXXXX
+ * box7 X XXXXX
+ *
+ * The bit strings we need to XOR in for boxes 0, 2, 4 and 6 don't
+ * overlap with each other. Neither do the ones for boxes 1, 3, 5
+ * and 7. So we could provide the key schedule in the form of two
+ * words that we can separately XOR into R, and then every S-box
+ * index is available as a (cyclically) contiguous 6-bit substring
+ * of one or the other of the results.
+ *
+ * The comments in Eric Young's libdes implementation point out
+ * that two of these bit strings require a rotation (rather than a
+ * simple shift) to extract. It's unavoidable that at least _one_
+ * must do; but we can actually run the whole inner algorithm (all
+ * 16 rounds) rotated one bit to the left, so that what the `real'
+ * DES description sees as L=0x80000001 we see as L=0x00000003.
+ * This requires rotating all our SP-box entries one bit to the
+ * left, and rotating each word of the key schedule elements one to
+ * the left, and rotating L and R one bit left just after IP and
+ * one bit right again just before FP. And in each round we convert
+ * a rotate into a shift, so we've saved a few per cent.
+ *
+ * That's about it for the inner loop; the SP-box tables as listed
+ * below are what I've described here (the original S value,
+ * shifted to its final place in the input to P, run through P, and
+ * then rotated one bit left). All that remains is to optimise the
+ * initial permutation IP.
+ *
+ * IP is not an arbitrary permutation. It has the nice property
+ * that if you take any bit number, write it in binary (6 bits),
+ * permute those 6 bits and invert some of them, you get the final
+ * position of that bit. Specifically, the bit whose initial
+ * position is given (in binary) as fedcba ends up in position
+ * AcbFED (where a capital letter denotes the inverse of a bit).
+ *
+ * We have the 64-bit data in two 32-bit words L and R, where bits
+ * in L are those with f=1 and bits in R are those with f=0. We
+ * note that we can do a simple transformation: suppose we exchange
+ * the bits with f=1,c=0 and the bits with f=0,c=1. This will cause
+ * the bit fedcba to be in position cedfba - we've `swapped' bits c
+ * and f in the position of each bit!
+ *
+ * Better still, this transformation is easy. In the example above,
+ * bits in L with c=0 are bits 0x0F0F0F0F, and those in R with c=1
+ * are 0xF0F0F0F0. So we can do
+ *
+ * difference = ((R >> 4) ^ L) & 0x0F0F0F0F
+ * R ^= (difference << 4)
+ * L ^= difference
+ *
+ * to perform the swap. Let's denote this by bitswap(4,0x0F0F0F0F).
+ * Also, we can invert the bit at the top just by exchanging L and
+ * R. So in a few swaps and a few of these bit operations we can
+ * do:
+ *
+ * Initially the position of bit fedcba is fedcba
+ * Swap L with R to make it Fedcba
+ * Perform bitswap( 4,0x0F0F0F0F) to make it cedFba
+ * Perform bitswap(16,0x0000FFFF) to make it ecdFba
+ * Swap L with R to make it EcdFba
+ * Perform bitswap( 2,0x33333333) to make it bcdFEa
+ * Perform bitswap( 8,0x00FF00FF) to make it dcbFEa
+ * Swap L with R to make it DcbFEa
+ * Perform bitswap( 1,0x55555555) to make it acbFED
+ * Swap L with R to make it AcbFED
+ *
+ * (In the actual code the four swaps are implicit: R and L are
+ * simply used the other way round in the first, second and last
+ * bitswap operations.)
+ *
+ * The final permutation is just the inverse of IP, so it can be
+ * performed by a similar set of operations.
+ */
+
+typedef struct {
+ word32 k0246[16], k1357[16];
+ word32 iv0, iv1;
+} DESContext;
+
+#define rotl(x, c) ( (x << c) | (x >> (32-c)) )
+#define rotl28(x, c) ( ( (x << c) | (x >> (28-c)) ) & 0x0FFFFFFF)
+
+static word32 bitsel(word32 * input, const int *bitnums, int size)
+{
+ word32 ret = 0;
+ while (size--) {
+ int bitpos = *bitnums++;
+ ret <<= 1;
+ if (bitpos >= 0)
+ ret |= 1 & (input[bitpos / 32] >> (bitpos % 32));
+ }
+ return ret;
+}
+
+static void des_key_setup(word32 key_msw, word32 key_lsw, DESContext * sched)
+{
+
+ static const int PC1_Cbits[] = {
+ 7, 15, 23, 31, 39, 47, 55, 63, 6, 14, 22, 30, 38, 46,
+ 54, 62, 5, 13, 21, 29, 37, 45, 53, 61, 4, 12, 20, 28
+ };
+ static const int PC1_Dbits[] = {
+ 1, 9, 17, 25, 33, 41, 49, 57, 2, 10, 18, 26, 34, 42,
+ 50, 58, 3, 11, 19, 27, 35, 43, 51, 59, 36, 44, 52, 60
+ };
+ /*
+ * The bit numbers in the two lists below don't correspond to
+ * the ones in the above description of PC2, because in the
+ * above description C and D are concatenated so `bit 28' means
+ * bit 0 of C. In this implementation we're using the standard
+ * `bitsel' function above and C is in the second word, so bit
+ * 0 of C is addressed by writing `32' here.
+ */
+ static const int PC2_0246[] = {
+ 49, 36, 59, 55, -1, -1, 37, 41, 48, 56, 34, 52, -1, -1, 15, 4,
+ 25, 19, 9, 1, -1, -1, 12, 7, 17, 0, 22, 3, -1, -1, 46, 43
+ };
+ static const int PC2_1357[] = {
+ -1, -1, 57, 32, 45, 54, 39, 50, -1, -1, 44, 53, 33, 40, 47, 58,
+ -1, -1, 26, 16, 5, 11, 23, 8, -1, -1, 10, 14, 6, 20, 27, 24
+ };
+ static const int leftshifts[] =
+ { 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 };
+
+ word32 C, D;
+ word32 buf[2];
+ int i;
+
+ buf[0] = key_lsw;
+ buf[1] = key_msw;
+
+ C = bitsel(buf, PC1_Cbits, 28);
+ D = bitsel(buf, PC1_Dbits, 28);
+
+ for (i = 0; i < 16; i++) {
+ C = rotl28(C, leftshifts[i]);
+ D = rotl28(D, leftshifts[i]);
+ buf[0] = D;
+ buf[1] = C;
+ sched->k0246[i] = bitsel(buf, PC2_0246, 32);
+ sched->k1357[i] = bitsel(buf, PC2_1357, 32);
+ }
+
+ sched->iv0 = sched->iv1 = 0;
+}
+
+static const word32 SPboxes[8][64] = {
+ {0x01010400, 0x00000000, 0x00010000, 0x01010404,
+ 0x01010004, 0x00010404, 0x00000004, 0x00010000,
+ 0x00000400, 0x01010400, 0x01010404, 0x00000400,
+ 0x01000404, 0x01010004, 0x01000000, 0x00000004,
+ 0x00000404, 0x01000400, 0x01000400, 0x00010400,
+ 0x00010400, 0x01010000, 0x01010000, 0x01000404,
+ 0x00010004, 0x01000004, 0x01000004, 0x00010004,
+ 0x00000000, 0x00000404, 0x00010404, 0x01000000,
+ 0x00010000, 0x01010404, 0x00000004, 0x01010000,
+ 0x01010400, 0x01000000, 0x01000000, 0x00000400,
+ 0x01010004, 0x00010000, 0x00010400, 0x01000004,
+ 0x00000400, 0x00000004, 0x01000404, 0x00010404,
+ 0x01010404, 0x00010004, 0x01010000, 0x01000404,
+ 0x01000004, 0x00000404, 0x00010404, 0x01010400,
+ 0x00000404, 0x01000400, 0x01000400, 0x00000000,
+ 0x00010004, 0x00010400, 0x00000000, 0x01010004L},
+
+ {0x80108020, 0x80008000, 0x00008000, 0x00108020,
+ 0x00100000, 0x00000020, 0x80100020, 0x80008020,
+ 0x80000020, 0x80108020, 0x80108000, 0x80000000,
+ 0x80008000, 0x00100000, 0x00000020, 0x80100020,
+ 0x00108000, 0x00100020, 0x80008020, 0x00000000,
+ 0x80000000, 0x00008000, 0x00108020, 0x80100000,
+ 0x00100020, 0x80000020, 0x00000000, 0x00108000,
+ 0x00008020, 0x80108000, 0x80100000, 0x00008020,
+ 0x00000000, 0x00108020, 0x80100020, 0x00100000,
+ 0x80008020, 0x80100000, 0x80108000, 0x00008000,
+ 0x80100000, 0x80008000, 0x00000020, 0x80108020,
+ 0x00108020, 0x00000020, 0x00008000, 0x80000000,
+ 0x00008020, 0x80108000, 0x00100000, 0x80000020,
+ 0x00100020, 0x80008020, 0x80000020, 0x00100020,
+ 0x00108000, 0x00000000, 0x80008000, 0x00008020,
+ 0x80000000, 0x80100020, 0x80108020, 0x00108000L},
+
+ {0x00000208, 0x08020200, 0x00000000, 0x08020008,
+ 0x08000200, 0x00000000, 0x00020208, 0x08000200,
+ 0x00020008, 0x08000008, 0x08000008, 0x00020000,
+ 0x08020208, 0x00020008, 0x08020000, 0x00000208,
+ 0x08000000, 0x00000008, 0x08020200, 0x00000200,
+ 0x00020200, 0x08020000, 0x08020008, 0x00020208,
+ 0x08000208, 0x00020200, 0x00020000, 0x08000208,
+ 0x00000008, 0x08020208, 0x00000200, 0x08000000,
+ 0x08020200, 0x08000000, 0x00020008, 0x00000208,
+ 0x00020000, 0x08020200, 0x08000200, 0x00000000,
+ 0x00000200, 0x00020008, 0x08020208, 0x08000200,
+ 0x08000008, 0x00000200, 0x00000000, 0x08020008,
+ 0x08000208, 0x00020000, 0x08000000, 0x08020208,
+ 0x00000008, 0x00020208, 0x00020200, 0x08000008,
+ 0x08020000, 0x08000208, 0x00000208, 0x08020000,
+ 0x00020208, 0x00000008, 0x08020008, 0x00020200L},
+
+ {0x00802001, 0x00002081, 0x00002081, 0x00000080,
+ 0x00802080, 0x00800081, 0x00800001, 0x00002001,
+ 0x00000000, 0x00802000, 0x00802000, 0x00802081,
+ 0x00000081, 0x00000000, 0x00800080, 0x00800001,
+ 0x00000001, 0x00002000, 0x00800000, 0x00802001,
+ 0x00000080, 0x00800000, 0x00002001, 0x00002080,
+ 0x00800081, 0x00000001, 0x00002080, 0x00800080,
+ 0x00002000, 0x00802080, 0x00802081, 0x00000081,
+ 0x00800080, 0x00800001, 0x00802000, 0x00802081,
+ 0x00000081, 0x00000000, 0x00000000, 0x00802000,
+ 0x00002080, 0x00800080, 0x00800081, 0x00000001,
+ 0x00802001, 0x00002081, 0x00002081, 0x00000080,
+ 0x00802081, 0x00000081, 0x00000001, 0x00002000,
+ 0x00800001, 0x00002001, 0x00802080, 0x00800081,
+ 0x00002001, 0x00002080, 0x00800000, 0x00802001,
+ 0x00000080, 0x00800000, 0x00002000, 0x00802080L},
+
+ {0x00000100, 0x02080100, 0x02080000, 0x42000100,
+ 0x00080000, 0x00000100, 0x40000000, 0x02080000,
+ 0x40080100, 0x00080000, 0x02000100, 0x40080100,
+ 0x42000100, 0x42080000, 0x00080100, 0x40000000,
+ 0x02000000, 0x40080000, 0x40080000, 0x00000000,
+ 0x40000100, 0x42080100, 0x42080100, 0x02000100,
+ 0x42080000, 0x40000100, 0x00000000, 0x42000000,
+ 0x02080100, 0x02000000, 0x42000000, 0x00080100,
+ 0x00080000, 0x42000100, 0x00000100, 0x02000000,
+ 0x40000000, 0x02080000, 0x42000100, 0x40080100,
+ 0x02000100, 0x40000000, 0x42080000, 0x02080100,
+ 0x40080100, 0x00000100, 0x02000000, 0x42080000,
+ 0x42080100, 0x00080100, 0x42000000, 0x42080100,
+ 0x02080000, 0x00000000, 0x40080000, 0x42000000,
+ 0x00080100, 0x02000100, 0x40000100, 0x00080000,
+ 0x00000000, 0x40080000, 0x02080100, 0x40000100L},
+
+ {0x20000010, 0x20400000, 0x00004000, 0x20404010,
+ 0x20400000, 0x00000010, 0x20404010, 0x00400000,
+ 0x20004000, 0x00404010, 0x00400000, 0x20000010,
+ 0x00400010, 0x20004000, 0x20000000, 0x00004010,
+ 0x00000000, 0x00400010, 0x20004010, 0x00004000,
+ 0x00404000, 0x20004010, 0x00000010, 0x20400010,
+ 0x20400010, 0x00000000, 0x00404010, 0x20404000,
+ 0x00004010, 0x00404000, 0x20404000, 0x20000000,
+ 0x20004000, 0x00000010, 0x20400010, 0x00404000,
+ 0x20404010, 0x00400000, 0x00004010, 0x20000010,
+ 0x00400000, 0x20004000, 0x20000000, 0x00004010,
+ 0x20000010, 0x20404010, 0x00404000, 0x20400000,
+ 0x00404010, 0x20404000, 0x00000000, 0x20400010,
+ 0x00000010, 0x00004000, 0x20400000, 0x00404010,
+ 0x00004000, 0x00400010, 0x20004010, 0x00000000,
+ 0x20404000, 0x20000000, 0x00400010, 0x20004010L},
+
+ {0x00200000, 0x04200002, 0x04000802, 0x00000000,
+ 0x00000800, 0x04000802, 0x00200802, 0x04200800,
+ 0x04200802, 0x00200000, 0x00000000, 0x04000002,
+ 0x00000002, 0x04000000, 0x04200002, 0x00000802,
+ 0x04000800, 0x00200802, 0x00200002, 0x04000800,
+ 0x04000002, 0x04200000, 0x04200800, 0x00200002,
+ 0x04200000, 0x00000800, 0x00000802, 0x04200802,
+ 0x00200800, 0x00000002, 0x04000000, 0x00200800,
+ 0x04000000, 0x00200800, 0x00200000, 0x04000802,
+ 0x04000802, 0x04200002, 0x04200002, 0x00000002,
+ 0x00200002, 0x04000000, 0x04000800, 0x00200000,
+ 0x04200800, 0x00000802, 0x00200802, 0x04200800,
+ 0x00000802, 0x04000002, 0x04200802, 0x04200000,
+ 0x00200800, 0x00000000, 0x00000002, 0x04200802,
+ 0x00000000, 0x00200802, 0x04200000, 0x00000800,
+ 0x04000002, 0x04000800, 0x00000800, 0x00200002L},
+
+ {0x10001040, 0x00001000, 0x00040000, 0x10041040,
+ 0x10000000, 0x10001040, 0x00000040, 0x10000000,
+ 0x00040040, 0x10040000, 0x10041040, 0x00041000,
+ 0x10041000, 0x00041040, 0x00001000, 0x00000040,
+ 0x10040000, 0x10000040, 0x10001000, 0x00001040,
+ 0x00041000, 0x00040040, 0x10040040, 0x10041000,
+ 0x00001040, 0x00000000, 0x00000000, 0x10040040,
+ 0x10000040, 0x10001000, 0x00041040, 0x00040000,
+ 0x00041040, 0x00040000, 0x10041000, 0x00001000,
+ 0x00000040, 0x10040040, 0x00001000, 0x00041040,
+ 0x10001000, 0x00000040, 0x10000040, 0x10040000,
+ 0x10040040, 0x10000000, 0x00040000, 0x10001040,
+ 0x00000000, 0x10041040, 0x00040040, 0x10000040,
+ 0x10040000, 0x10001000, 0x10001040, 0x00000000,
+ 0x10041040, 0x00041000, 0x00041000, 0x00001040,
+ 0x00001040, 0x00040040, 0x10000000, 0x10041000L}
+};
+
+#define f(R, K0246, K1357) (\
+ s0246 = R ^ K0246, \
+ s1357 = R ^ K1357, \
+ s0246 = rotl(s0246, 28), \
+ SPboxes[0] [(s0246 >> 24) & 0x3F] | \
+ SPboxes[1] [(s1357 >> 24) & 0x3F] | \
+ SPboxes[2] [(s0246 >> 16) & 0x3F] | \
+ SPboxes[3] [(s1357 >> 16) & 0x3F] | \
+ SPboxes[4] [(s0246 >> 8) & 0x3F] | \
+ SPboxes[5] [(s1357 >> 8) & 0x3F] | \
+ SPboxes[6] [(s0246 ) & 0x3F] | \
+ SPboxes[7] [(s1357 ) & 0x3F])
+
+#define bitswap(L, R, n, mask) (\
+ swap = mask & ( (R >> n) ^ L ), \
+ R ^= swap << n, \
+ L ^= swap)
+
+/* Initial permutation */
+#define IP(L, R) (\
+ bitswap(R, L, 4, 0x0F0F0F0F), \
+ bitswap(R, L, 16, 0x0000FFFF), \
+ bitswap(L, R, 2, 0x33333333), \
+ bitswap(L, R, 8, 0x00FF00FF), \
+ bitswap(R, L, 1, 0x55555555))
+
+/* Final permutation */
+#define FP(L, R) (\
+ bitswap(R, L, 1, 0x55555555), \
+ bitswap(L, R, 8, 0x00FF00FF), \
+ bitswap(L, R, 2, 0x33333333), \
+ bitswap(R, L, 16, 0x0000FFFF), \
+ bitswap(R, L, 4, 0x0F0F0F0F))
+
+static void des_encipher(word32 * output, word32 L, word32 R,
+ DESContext * sched)
+{
+ word32 swap, s0246, s1357;
+
+ IP(L, R);
+
+ L = rotl(L, 1);
+ R = rotl(R, 1);
+
+ L ^= f(R, sched->k0246[0], sched->k1357[0]);
+ R ^= f(L, sched->k0246[1], sched->k1357[1]);
+ L ^= f(R, sched->k0246[2], sched->k1357[2]);
+ R ^= f(L, sched->k0246[3], sched->k1357[3]);
+ L ^= f(R, sched->k0246[4], sched->k1357[4]);
+ R ^= f(L, sched->k0246[5], sched->k1357[5]);
+ L ^= f(R, sched->k0246[6], sched->k1357[6]);
+ R ^= f(L, sched->k0246[7], sched->k1357[7]);
+ L ^= f(R, sched->k0246[8], sched->k1357[8]);
+ R ^= f(L, sched->k0246[9], sched->k1357[9]);
+ L ^= f(R, sched->k0246[10], sched->k1357[10]);
+ R ^= f(L, sched->k0246[11], sched->k1357[11]);
+ L ^= f(R, sched->k0246[12], sched->k1357[12]);
+ R ^= f(L, sched->k0246[13], sched->k1357[13]);
+ L ^= f(R, sched->k0246[14], sched->k1357[14]);
+ R ^= f(L, sched->k0246[15], sched->k1357[15]);
+
+ L = rotl(L, 31);
+ R = rotl(R, 31);
+
+ swap = L;
+ L = R;
+ R = swap;
+
+ FP(L, R);
+
+ output[0] = L;
+ output[1] = R;
+}
+
+static void des_decipher(word32 * output, word32 L, word32 R,
+ DESContext * sched)
+{
+ word32 swap, s0246, s1357;
+
+ IP(L, R);
+
+ L = rotl(L, 1);
+ R = rotl(R, 1);
+
+ L ^= f(R, sched->k0246[15], sched->k1357[15]);
+ R ^= f(L, sched->k0246[14], sched->k1357[14]);
+ L ^= f(R, sched->k0246[13], sched->k1357[13]);
+ R ^= f(L, sched->k0246[12], sched->k1357[12]);
+ L ^= f(R, sched->k0246[11], sched->k1357[11]);
+ R ^= f(L, sched->k0246[10], sched->k1357[10]);
+ L ^= f(R, sched->k0246[9], sched->k1357[9]);
+ R ^= f(L, sched->k0246[8], sched->k1357[8]);
+ L ^= f(R, sched->k0246[7], sched->k1357[7]);
+ R ^= f(L, sched->k0246[6], sched->k1357[6]);
+ L ^= f(R, sched->k0246[5], sched->k1357[5]);
+ R ^= f(L, sched->k0246[4], sched->k1357[4]);
+ L ^= f(R, sched->k0246[3], sched->k1357[3]);
+ R ^= f(L, sched->k0246[2], sched->k1357[2]);
+ L ^= f(R, sched->k0246[1], sched->k1357[1]);
+ R ^= f(L, sched->k0246[0], sched->k1357[0]);
+
+ L = rotl(L, 31);
+ R = rotl(R, 31);
+
+ swap = L;
+ L = R;
+ R = swap;
+
+ FP(L, R);
+
+ output[0] = L;
+ output[1] = R;
+}
+
+static void des_cbc_encrypt(unsigned char *blk,
+ unsigned int len, DESContext * sched)
+{
+ word32 out[2], iv0, iv1;
+ unsigned int i;
+
+ assert((len & 7) == 0);
+
+ iv0 = sched->iv0;
+ iv1 = sched->iv1;
+ for (i = 0; i < len; i += 8) {
+ iv0 ^= GET_32BIT_MSB_FIRST(blk);
+ iv1 ^= GET_32BIT_MSB_FIRST(blk + 4);
+ des_encipher(out, iv0, iv1, sched);
+ iv0 = out[0];
+ iv1 = out[1];
+ PUT_32BIT_MSB_FIRST(blk, iv0);
+ PUT_32BIT_MSB_FIRST(blk + 4, iv1);
+ blk += 8;
+ }
+ sched->iv0 = iv0;
+ sched->iv1 = iv1;
+}
+
+static void des_cbc_decrypt(unsigned char *blk,
+ unsigned int len, DESContext * sched)
+{
+ word32 out[2], iv0, iv1, xL, xR;
+ unsigned int i;
+
+ assert((len & 7) == 0);
+
+ iv0 = sched->iv0;
+ iv1 = sched->iv1;
+ for (i = 0; i < len; i += 8) {
+ xL = GET_32BIT_MSB_FIRST(blk);
+ xR = GET_32BIT_MSB_FIRST(blk + 4);
+ des_decipher(out, xL, xR, sched);
+ iv0 ^= out[0];
+ iv1 ^= out[1];
+ PUT_32BIT_MSB_FIRST(blk, iv0);
+ PUT_32BIT_MSB_FIRST(blk + 4, iv1);
+ blk += 8;
+ iv0 = xL;
+ iv1 = xR;
+ }
+ sched->iv0 = iv0;
+ sched->iv1 = iv1;
+}
+
+static void des_3cbc_encrypt(unsigned char *blk,
+ unsigned int len, DESContext * scheds)
+{
+ des_cbc_encrypt(blk, len, &scheds[0]);
+ des_cbc_decrypt(blk, len, &scheds[1]);
+ des_cbc_encrypt(blk, len, &scheds[2]);
+}
+
+static void des_cbc3_encrypt(unsigned char *blk,
+ unsigned int len, DESContext * scheds)
+{
+ word32 out[2], iv0, iv1;
+ unsigned int i;
+
+ assert((len & 7) == 0);
+
+ iv0 = scheds->iv0;
+ iv1 = scheds->iv1;
+ for (i = 0; i < len; i += 8) {
+ iv0 ^= GET_32BIT_MSB_FIRST(blk);
+ iv1 ^= GET_32BIT_MSB_FIRST(blk + 4);
+ des_encipher(out, iv0, iv1, &scheds[0]);
+ des_decipher(out, out[0], out[1], &scheds[1]);
+ des_encipher(out, out[0], out[1], &scheds[2]);
+ iv0 = out[0];
+ iv1 = out[1];
+ PUT_32BIT_MSB_FIRST(blk, iv0);
+ PUT_32BIT_MSB_FIRST(blk + 4, iv1);
+ blk += 8;
+ }
+ scheds->iv0 = iv0;
+ scheds->iv1 = iv1;
+}
+
+static void des_3cbc_decrypt(unsigned char *blk,
+ unsigned int len, DESContext * scheds)
+{
+ des_cbc_decrypt(blk, len, &scheds[2]);
+ des_cbc_encrypt(blk, len, &scheds[1]);
+ des_cbc_decrypt(blk, len, &scheds[0]);
+}
+
+static void des_cbc3_decrypt(unsigned char *blk,
+ unsigned int len, DESContext * scheds)
+{
+ word32 out[2], iv0, iv1, xL, xR;
+ unsigned int i;
+
+ assert((len & 7) == 0);
+
+ iv0 = scheds->iv0;
+ iv1 = scheds->iv1;
+ for (i = 0; i < len; i += 8) {
+ xL = GET_32BIT_MSB_FIRST(blk);
+ xR = GET_32BIT_MSB_FIRST(blk + 4);
+ des_decipher(out, xL, xR, &scheds[2]);
+ des_encipher(out, out[0], out[1], &scheds[1]);
+ des_decipher(out, out[0], out[1], &scheds[0]);
+ iv0 ^= out[0];
+ iv1 ^= out[1];
+ PUT_32BIT_MSB_FIRST(blk, iv0);
+ PUT_32BIT_MSB_FIRST(blk + 4, iv1);
+ blk += 8;
+ iv0 = xL;
+ iv1 = xR;
+ }
+ scheds->iv0 = iv0;
+ scheds->iv1 = iv1;
+}
+
+static void des_sdctr3(unsigned char *blk,
+ unsigned int len, DESContext * scheds)
+{
+ word32 b[2], iv0, iv1, tmp;
+ unsigned int i;
+
+ assert((len & 7) == 0);
+
+ iv0 = scheds->iv0;
+ iv1 = scheds->iv1;
+ for (i = 0; i < len; i += 8) {
+ des_encipher(b, iv0, iv1, &scheds[0]);
+ des_decipher(b, b[0], b[1], &scheds[1]);
+ des_encipher(b, b[0], b[1], &scheds[2]);
+ tmp = GET_32BIT_MSB_FIRST(blk);
+ PUT_32BIT_MSB_FIRST(blk, tmp ^ b[0]);
+ blk += 4;
+ tmp = GET_32BIT_MSB_FIRST(blk);
+ PUT_32BIT_MSB_FIRST(blk, tmp ^ b[1]);
+ blk += 4;
+ if ((iv1 = (iv1 + 1) & 0xffffffff) == 0)
+ iv0 = (iv0 + 1) & 0xffffffff;
+ }
+ scheds->iv0 = iv0;
+ scheds->iv1 = iv1;
+}
+
+static void *des3_make_context(void)
+{
+ return snewn(3, DESContext);
+}
+
+static void *des3_ssh1_make_context(void)
+{
+ /* Need 3 keys for each direction, in SSH-1 */
+ return snewn(6, DESContext);
+}
+
+static void *des_make_context(void)
+{
+ return snew(DESContext);
+}
+
+static void *des_ssh1_make_context(void)
+{
+ /* Need one key for each direction, in SSH-1 */
+ return snewn(2, DESContext);
+}
+
+static void des3_free_context(void *handle) /* used for both 3DES and DES */
+{
+ sfree(handle);
+}
+
+static void des3_key(void *handle, unsigned char *key)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_key_setup(GET_32BIT_MSB_FIRST(key),
+ GET_32BIT_MSB_FIRST(key + 4), &keys[0]);
+ des_key_setup(GET_32BIT_MSB_FIRST(key + 8),
+ GET_32BIT_MSB_FIRST(key + 12), &keys[1]);
+ des_key_setup(GET_32BIT_MSB_FIRST(key + 16),
+ GET_32BIT_MSB_FIRST(key + 20), &keys[2]);
+}
+
+static void des3_iv(void *handle, unsigned char *key)
+{
+ DESContext *keys = (DESContext *) handle;
+ keys[0].iv0 = GET_32BIT_MSB_FIRST(key);
+ keys[0].iv1 = GET_32BIT_MSB_FIRST(key + 4);
+}
+
+static void des_key(void *handle, unsigned char *key)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_key_setup(GET_32BIT_MSB_FIRST(key),
+ GET_32BIT_MSB_FIRST(key + 4), &keys[0]);
+}
+
+static void des3_sesskey(void *handle, unsigned char *key)
+{
+ DESContext *keys = (DESContext *) handle;
+ des3_key(keys, key);
+ des3_key(keys+3, key);
+}
+
+static void des3_encrypt_blk(void *handle, unsigned char *blk, int len)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_3cbc_encrypt(blk, len, keys);
+}
+
+static void des3_decrypt_blk(void *handle, unsigned char *blk, int len)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_3cbc_decrypt(blk, len, keys+3);
+}
+
+static void des3_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_cbc3_encrypt(blk, len, keys);
+}
+
+static void des3_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_cbc3_decrypt(blk, len, keys);
+}
+
+static void des3_ssh2_sdctr(void *handle, unsigned char *blk, int len)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_sdctr3(blk, len, keys);
+}
+
+static void des_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_cbc_encrypt(blk, len, keys);
+}
+
+static void des_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_cbc_decrypt(blk, len, keys);
+}
+
+void des3_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len)
+{
+ DESContext ourkeys[3];
+ des_key_setup(GET_32BIT_MSB_FIRST(key),
+ GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]);
+ des_key_setup(GET_32BIT_MSB_FIRST(key + 8),
+ GET_32BIT_MSB_FIRST(key + 12), &ourkeys[1]);
+ des_key_setup(GET_32BIT_MSB_FIRST(key),
+ GET_32BIT_MSB_FIRST(key + 4), &ourkeys[2]);
+ des_3cbc_decrypt(blk, len, ourkeys);
+ smemclr(ourkeys, sizeof(ourkeys));
+}
+
+void des3_encrypt_pubkey(unsigned char *key, unsigned char *blk, int len)
+{
+ DESContext ourkeys[3];
+ des_key_setup(GET_32BIT_MSB_FIRST(key),
+ GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]);
+ des_key_setup(GET_32BIT_MSB_FIRST(key + 8),
+ GET_32BIT_MSB_FIRST(key + 12), &ourkeys[1]);
+ des_key_setup(GET_32BIT_MSB_FIRST(key),
+ GET_32BIT_MSB_FIRST(key + 4), &ourkeys[2]);
+ des_3cbc_encrypt(blk, len, ourkeys);
+ smemclr(ourkeys, sizeof(ourkeys));
+}
+
+void des3_decrypt_pubkey_ossh(unsigned char *key, unsigned char *iv,
+ unsigned char *blk, int len)
+{
+ DESContext ourkeys[3];
+ des_key_setup(GET_32BIT_MSB_FIRST(key),
+ GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]);
+ des_key_setup(GET_32BIT_MSB_FIRST(key + 8),
+ GET_32BIT_MSB_FIRST(key + 12), &ourkeys[1]);
+ des_key_setup(GET_32BIT_MSB_FIRST(key + 16),
+ GET_32BIT_MSB_FIRST(key + 20), &ourkeys[2]);
+ ourkeys[0].iv0 = GET_32BIT_MSB_FIRST(iv);
+ ourkeys[0].iv1 = GET_32BIT_MSB_FIRST(iv+4);
+ des_cbc3_decrypt(blk, len, ourkeys);
+ smemclr(ourkeys, sizeof(ourkeys));
+}
+
+void des3_encrypt_pubkey_ossh(unsigned char *key, unsigned char *iv,
+ unsigned char *blk, int len)
+{
+ DESContext ourkeys[3];
+ des_key_setup(GET_32BIT_MSB_FIRST(key),
+ GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]);
+ des_key_setup(GET_32BIT_MSB_FIRST(key + 8),
+ GET_32BIT_MSB_FIRST(key + 12), &ourkeys[1]);
+ des_key_setup(GET_32BIT_MSB_FIRST(key + 16),
+ GET_32BIT_MSB_FIRST(key + 20), &ourkeys[2]);
+ ourkeys[0].iv0 = GET_32BIT_MSB_FIRST(iv);
+ ourkeys[0].iv1 = GET_32BIT_MSB_FIRST(iv+4);
+ des_cbc3_encrypt(blk, len, ourkeys);
+ smemclr(ourkeys, sizeof(ourkeys));
+}
+
+static void des_keysetup_xdmauth(const unsigned char *keydata, DESContext *dc)
+{
+ unsigned char key[8];
+ int i, nbits, j;
+ unsigned int bits;
+
+ bits = 0;
+ nbits = 0;
+ j = 0;
+ for (i = 0; i < 8; i++) {
+ if (nbits < 7) {
+ bits = (bits << 8) | keydata[j];
+ nbits += 8;
+ j++;
+ }
+ key[i] = (bits >> (nbits - 7)) << 1;
+ bits &= ~(0x7F << (nbits - 7));
+ nbits -= 7;
+ }
+
+ des_key_setup(GET_32BIT_MSB_FIRST(key), GET_32BIT_MSB_FIRST(key + 4), dc);
+}
+
+void des_encrypt_xdmauth(const unsigned char *keydata,
+ unsigned char *blk, int len)
+{
+ DESContext dc;
+ des_keysetup_xdmauth(keydata, &dc);
+ des_cbc_encrypt(blk, len, &dc);
+}
+
+void des_decrypt_xdmauth(const unsigned char *keydata,
+ unsigned char *blk, int len)
+{
+ DESContext dc;
+ des_keysetup_xdmauth(keydata, &dc);
+ des_cbc_decrypt(blk, len, &dc);
+}
+
+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,
+ "3des-cbc",
+ 8, 168, SSH_CIPHER_IS_CBC, "triple-DES CBC"
+};
+
+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,
+ "3des-ctr",
+ 8, 168, 0, "triple-DES SDCTR"
+};
+
+/*
+ * Single DES in SSH-2. "des-cbc" is marked as HISTORIC in
+ * RFC 4250, referring to
+ * FIPS-46-3. ("Single DES (i.e., DES) will be permitted
+ * for legacy systems only.") , but ssh.com support it and
+ * apparently aren't the only people to do so, so we sigh
+ * and implement it anyway.
+ */
+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-cbc",
+ 8, 56, SSH_CIPHER_IS_CBC, "single-DES CBC"
+};
+
+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-cbc@ssh.com",
+ 8, 56, SSH_CIPHER_IS_CBC, "single-DES CBC"
+};
+
+static const struct ssh2_cipher *const des3_list[] = {
+ &ssh_3des_ssh2_ctr,
+ &ssh_3des_ssh2
+};
+
+const struct ssh2_ciphers ssh2_3des = {
+ sizeof(des3_list) / sizeof(*des3_list),
+ des3_list
+};
+
+static const struct ssh2_cipher *const des_list[] = {
+ &ssh_des_ssh2,
+ &ssh_des_sshcom_ssh2
+};
+
+const struct ssh2_ciphers ssh2_des = {
+ sizeof(des_list) / sizeof(*des_list),
+ des_list
+};
+
+const struct ssh_cipher ssh_3des = {
+ des3_ssh1_make_context, des3_free_context, des3_sesskey,
+ des3_encrypt_blk, des3_decrypt_blk,
+ 8, "triple-DES inner-CBC"
+};
+
+static void des_sesskey(void *handle, unsigned char *key)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_key(keys, key);
+ des_key(keys+1, key);
+}
+
+static void des_encrypt_blk(void *handle, unsigned char *blk, int len)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_cbc_encrypt(blk, len, keys);
+}
+
+static void des_decrypt_blk(void *handle, unsigned char *blk, int len)
+{
+ DESContext *keys = (DESContext *) handle;
+ des_cbc_decrypt(blk, len, keys+1);
+}
+
+const struct ssh_cipher ssh_des = {
+ des_ssh1_make_context, des3_free_context, des_sesskey,
+ des_encrypt_blk, des_decrypt_blk,
+ 8, "single-DES CBC"
+};
+
+#ifdef TEST_XDM_AUTH
+
+/*
+ * Small standalone utility which allows encryption and decryption of
+ * single cipher blocks in the XDM-AUTHORIZATION-1 style. Written
+ * during the rework of X authorisation for connection sharing, to
+ * check the corner case when xa1_firstblock matches but the rest of
+ * the authorisation is bogus.
+ *
+ * Just compile this file on its own with the above ifdef symbol
+ * predefined:
+
+gcc -DTEST_XDM_AUTH -o sshdes sshdes.c
+
+ */
+
+#include <stdlib.h>
+void *safemalloc(size_t n, size_t size) { return calloc(n, size); }
+void safefree(void *p) { return free(p); }
+void smemclr(void *p, size_t size) { memset(p, 0, size); }
+int main(int argc, char **argv)
+{
+ unsigned char words[2][8];
+ unsigned char out[8];
+ int i, j;
+
+ memset(words, 0, sizeof(words));
+
+ for (i = 0; i < 2; i++) {
+ for (j = 0; j < 8 && argv[i+1][2*j]; j++) {
+ char x[3];
+ unsigned u;
+ x[0] = argv[i+1][2*j];
+ x[1] = argv[i+1][2*j+1];
+ x[2] = 0;
+ sscanf(x, "%02x", &u);
+ words[i][j] = u;
+ }
+ }
+
+ memcpy(out, words[0], 8);
+ des_decrypt_xdmauth(words[1], out, 8);
+ printf("decrypt(%s,%s) = ", argv[1], argv[2]);
+ for (i = 0; i < 8; i++) printf("%02x", out[i]);
+ printf("\n");
+
+ memcpy(out, words[0], 8);
+ des_encrypt_xdmauth(words[1], out, 8);
+ printf("encrypt(%s,%s) = ", argv[1], argv[2]);
+ for (i = 0; i < 8; i++) printf("%02x", out[i]);
+ printf("\n");
+}
+
+#endif
diff --git a/tools/plink/sshdss.c b/tools/plink/sshdss.c
index dba1db1b4..84fcdac79 100644
--- a/tools/plink/sshdss.c
+++ b/tools/plink/sshdss.c
@@ -1,643 +1,700 @@
-/*
- * Digital Signature Standard implementation for PuTTY.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-
-#include "ssh.h"
-#include "misc.h"
-
-static void sha_mpint(SHA_State * s, Bignum b)
-{
- unsigned char lenbuf[4];
- int len;
- len = (bignum_bitcount(b) + 8) / 8;
- PUT_32BIT(lenbuf, len);
- SHA_Bytes(s, lenbuf, 4);
- while (len-- > 0) {
- lenbuf[0] = bignum_byte(b, len);
- SHA_Bytes(s, lenbuf, 1);
- }
- memset(lenbuf, 0, sizeof(lenbuf));
-}
-
-static void sha512_mpint(SHA512_State * s, Bignum b)
-{
- unsigned char lenbuf[4];
- int len;
- len = (bignum_bitcount(b) + 8) / 8;
- PUT_32BIT(lenbuf, len);
- SHA512_Bytes(s, lenbuf, 4);
- while (len-- > 0) {
- lenbuf[0] = bignum_byte(b, len);
- SHA512_Bytes(s, lenbuf, 1);
- }
- memset(lenbuf, 0, sizeof(lenbuf));
-}
-
-static void getstring(char **data, int *datalen, char **p, int *length)
-{
- *p = NULL;
- if (*datalen < 4)
- return;
- *length = GET_32BIT(*data);
- *datalen -= 4;
- *data += 4;
- if (*datalen < *length)
- return;
- *p = *data;
- *data += *length;
- *datalen -= *length;
-}
-static Bignum getmp(char **data, int *datalen)
-{
- char *p;
- int length;
- Bignum b;
-
- getstring(data, datalen, &p, &length);
- if (!p)
- return NULL;
- if (p[0] & 0x80)
- return NULL; /* negative mp */
- b = bignum_from_bytes((unsigned char *)p, length);
- return b;
-}
-
-static Bignum get160(char **data, int *datalen)
-{
- Bignum b;
-
- b = bignum_from_bytes((unsigned char *)*data, 20);
- *data += 20;
- *datalen -= 20;
-
- return b;
-}
-
-static void *dss_newkey(char *data, int len)
-{
- char *p;
- int slen;
- struct dss_key *dss;
-
- dss = snew(struct dss_key);
- if (!dss)
- return NULL;
- getstring(&data, &len, &p, &slen);
-
-#ifdef DEBUG_DSS
- {
- int i;
- printf("key:");
- for (i = 0; i < len; i++)
- printf(" %02x", (unsigned char) (data[i]));
- printf("\n");
- }
-#endif
-
- if (!p || memcmp(p, "ssh-dss", 7)) {
- sfree(dss);
- return NULL;
- }
- dss->p = getmp(&data, &len);
- dss->q = getmp(&data, &len);
- dss->g = getmp(&data, &len);
- dss->y = getmp(&data, &len);
-
- return dss;
-}
-
-static void dss_freekey(void *key)
-{
- struct dss_key *dss = (struct dss_key *) key;
- freebn(dss->p);
- freebn(dss->q);
- freebn(dss->g);
- freebn(dss->y);
- sfree(dss);
-}
-
-static char *dss_fmtkey(void *key)
-{
- struct dss_key *dss = (struct dss_key *) key;
- char *p;
- int len, i, pos, nibbles;
- static const char hex[] = "0123456789abcdef";
- if (!dss->p)
- return NULL;
- len = 8 + 4 + 1; /* 4 x "0x", punctuation, \0 */
- len += 4 * (bignum_bitcount(dss->p) + 15) / 16;
- len += 4 * (bignum_bitcount(dss->q) + 15) / 16;
- len += 4 * (bignum_bitcount(dss->g) + 15) / 16;
- len += 4 * (bignum_bitcount(dss->y) + 15) / 16;
- p = snewn(len, char);
- if (!p)
- return NULL;
-
- pos = 0;
- pos += sprintf(p + pos, "0x");
- nibbles = (3 + bignum_bitcount(dss->p)) / 4;
- if (nibbles < 1)
- nibbles = 1;
- for (i = nibbles; i--;)
- p[pos++] =
- hex[(bignum_byte(dss->p, i / 2) >> (4 * (i % 2))) & 0xF];
- pos += sprintf(p + pos, ",0x");
- nibbles = (3 + bignum_bitcount(dss->q)) / 4;
- if (nibbles < 1)
- nibbles = 1;
- for (i = nibbles; i--;)
- p[pos++] =
- hex[(bignum_byte(dss->q, i / 2) >> (4 * (i % 2))) & 0xF];
- pos += sprintf(p + pos, ",0x");
- nibbles = (3 + bignum_bitcount(dss->g)) / 4;
- if (nibbles < 1)
- nibbles = 1;
- for (i = nibbles; i--;)
- p[pos++] =
- hex[(bignum_byte(dss->g, i / 2) >> (4 * (i % 2))) & 0xF];
- pos += sprintf(p + pos, ",0x");
- nibbles = (3 + bignum_bitcount(dss->y)) / 4;
- if (nibbles < 1)
- nibbles = 1;
- for (i = nibbles; i--;)
- p[pos++] =
- hex[(bignum_byte(dss->y, i / 2) >> (4 * (i % 2))) & 0xF];
- p[pos] = '\0';
- 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)
-{
- struct dss_key *dss = (struct dss_key *) key;
- char *p;
- int slen;
- char hash[20];
- Bignum r, s, w, gu1p, yu2p, gu1yu2p, u1, u2, sha, v;
- int ret;
-
- if (!dss->p)
- return 0;
-
-#ifdef DEBUG_DSS
- {
- int i;
- printf("sig:");
- for (i = 0; i < siglen; i++)
- printf(" %02x", (unsigned char) (sig[i]));
- printf("\n");
- }
-#endif
- /*
- * Commercial SSH (2.0.13) and OpenSSH disagree over the format
- * of a DSA signature. OpenSSH is in line with RFC 4253:
- * it uses a string "ssh-dss", followed by a 40-byte string
- * containing two 160-bit integers end-to-end. Commercial SSH
- * can't be bothered with the header bit, and considers a DSA
- * signature blob to be _just_ the 40-byte string containing
- * the two 160-bit integers. We tell them apart by measuring
- * the length: length 40 means the commercial-SSH bug, anything
- * else is assumed to be RFC-compliant.
- */
- if (siglen != 40) { /* bug not present; read admin fields */
- getstring(&sig, &siglen, &p, &slen);
- if (!p || slen != 7 || memcmp(p, "ssh-dss", 7)) {
- return 0;
- }
- sig += 4, siglen -= 4; /* skip yet another length field */
- }
- r = get160(&sig, &siglen);
- s = get160(&sig, &siglen);
- if (!r || !s)
- return 0;
-
- /*
- * Step 1. w <- s^-1 mod q.
- */
- w = modinv(s, dss->q);
-
- /*
- * Step 2. u1 <- SHA(message) * w mod q.
- */
- SHA_Simple(data, datalen, (unsigned char *)hash);
- p = hash;
- slen = 20;
- sha = get160(&p, &slen);
- u1 = modmul(sha, w, dss->q);
-
- /*
- * Step 3. u2 <- r * w mod q.
- */
- u2 = modmul(r, w, dss->q);
-
- /*
- * Step 4. v <- (g^u1 * y^u2 mod p) mod q.
- */
- gu1p = modpow(dss->g, u1, dss->p);
- yu2p = modpow(dss->y, u2, dss->p);
- gu1yu2p = modmul(gu1p, yu2p, dss->p);
- v = modmul(gu1yu2p, One, dss->q);
-
- /*
- * Step 5. v should now be equal to r.
- */
-
- ret = !bignum_cmp(v, r);
-
- freebn(w);
- freebn(sha);
- freebn(gu1p);
- freebn(yu2p);
- freebn(gu1yu2p);
- freebn(v);
- freebn(r);
- freebn(s);
-
- return ret;
-}
-
-static unsigned char *dss_public_blob(void *key, int *len)
-{
- struct dss_key *dss = (struct dss_key *) key;
- int plen, qlen, glen, ylen, bloblen;
- int i;
- unsigned char *blob, *p;
-
- plen = (bignum_bitcount(dss->p) + 8) / 8;
- qlen = (bignum_bitcount(dss->q) + 8) / 8;
- glen = (bignum_bitcount(dss->g) + 8) / 8;
- ylen = (bignum_bitcount(dss->y) + 8) / 8;
-
- /*
- * string "ssh-dss", mpint p, mpint q, mpint g, mpint y. Total
- * 27 + sum of lengths. (five length fields, 20+7=27).
- */
- bloblen = 27 + plen + qlen + glen + ylen;
- blob = snewn(bloblen, unsigned char);
- p = blob;
- PUT_32BIT(p, 7);
- p += 4;
- memcpy(p, "ssh-dss", 7);
- p += 7;
- PUT_32BIT(p, plen);
- p += 4;
- for (i = plen; i--;)
- *p++ = bignum_byte(dss->p, i);
- PUT_32BIT(p, qlen);
- p += 4;
- for (i = qlen; i--;)
- *p++ = bignum_byte(dss->q, i);
- PUT_32BIT(p, glen);
- p += 4;
- for (i = glen; i--;)
- *p++ = bignum_byte(dss->g, i);
- PUT_32BIT(p, ylen);
- p += 4;
- for (i = ylen; i--;)
- *p++ = bignum_byte(dss->y, i);
- assert(p == blob + bloblen);
- *len = bloblen;
- return blob;
-}
-
-static unsigned char *dss_private_blob(void *key, int *len)
-{
- struct dss_key *dss = (struct dss_key *) key;
- int xlen, bloblen;
- int i;
- unsigned char *blob, *p;
-
- xlen = (bignum_bitcount(dss->x) + 8) / 8;
-
- /*
- * mpint x, string[20] the SHA of p||q||g. Total 4 + xlen.
- */
- bloblen = 4 + xlen;
- blob = snewn(bloblen, unsigned char);
- p = blob;
- PUT_32BIT(p, xlen);
- p += 4;
- for (i = xlen; i--;)
- *p++ = bignum_byte(dss->x, i);
- assert(p == blob + bloblen);
- *len = bloblen;
- return blob;
-}
-
-static void *dss_createkey(unsigned char *pub_blob, int pub_len,
- unsigned char *priv_blob, int priv_len)
-{
- struct dss_key *dss;
- char *pb = (char *) priv_blob;
- char *hash;
- int hashlen;
- SHA_State s;
- unsigned char digest[20];
- Bignum ytest;
-
- dss = dss_newkey((char *) pub_blob, pub_len);
- dss->x = getmp(&pb, &priv_len);
-
- /*
- * Check the obsolete hash in the old DSS key format.
- */
- hashlen = -1;
- getstring(&pb, &priv_len, &hash, &hashlen);
- if (hashlen == 20) {
- SHA_Init(&s);
- sha_mpint(&s, dss->p);
- sha_mpint(&s, dss->q);
- sha_mpint(&s, dss->g);
- SHA_Final(&s, digest);
- if (0 != memcmp(hash, digest, 20)) {
- dss_freekey(dss);
- return NULL;
- }
- }
-
- /*
- * Now ensure g^x mod p really is y.
- */
- ytest = modpow(dss->g, dss->x, dss->p);
- if (0 != bignum_cmp(ytest, dss->y)) {
- dss_freekey(dss);
- return NULL;
- }
- freebn(ytest);
-
- return dss;
-}
-
-static void *dss_openssh_createkey(unsigned char **blob, int *len)
-{
- char **b = (char **) blob;
- struct dss_key *dss;
-
- dss = snew(struct dss_key);
- if (!dss)
- return NULL;
-
- dss->p = getmp(b, len);
- dss->q = getmp(b, len);
- dss->g = getmp(b, len);
- dss->y = getmp(b, len);
- dss->x = getmp(b, len);
-
- if (!dss->p || !dss->q || !dss->g || !dss->y || !dss->x) {
- sfree(dss->p);
- sfree(dss->q);
- sfree(dss->g);
- sfree(dss->y);
- sfree(dss->x);
- sfree(dss);
- return NULL;
- }
-
- return dss;
-}
-
-static int dss_openssh_fmtkey(void *key, unsigned char *blob, int len)
-{
- struct dss_key *dss = (struct dss_key *) key;
- int bloblen, i;
-
- bloblen =
- ssh2_bignum_length(dss->p) +
- ssh2_bignum_length(dss->q) +
- ssh2_bignum_length(dss->g) +
- ssh2_bignum_length(dss->y) +
- ssh2_bignum_length(dss->x);
-
- if (bloblen > len)
- return bloblen;
-
- bloblen = 0;
-#define ENC(x) \
- PUT_32BIT(blob+bloblen, ssh2_bignum_length((x))-4); bloblen += 4; \
- for (i = ssh2_bignum_length((x))-4; i-- ;) blob[bloblen++]=bignum_byte((x),i);
- ENC(dss->p);
- ENC(dss->q);
- ENC(dss->g);
- ENC(dss->y);
- ENC(dss->x);
-
- return bloblen;
-}
-
-static int dss_pubkey_bits(void *blob, int len)
-{
- struct dss_key *dss;
- int ret;
-
- dss = dss_newkey((char *) blob, len);
- ret = bignum_bitcount(dss->p);
- dss_freekey(dss);
-
- return ret;
-}
-
-static unsigned char *dss_sign(void *key, char *data, int datalen, int *siglen)
-{
- /*
- * The basic DSS signing algorithm is:
- *
- * - invent a random k between 1 and q-1 (exclusive).
- * - Compute r = (g^k mod p) mod q.
- * - Compute s = k^-1 * (hash + x*r) mod q.
- *
- * This has the dangerous properties that:
- *
- * - if an attacker in possession of the public key _and_ the
- * signature (for example, the host you just authenticated
- * to) can guess your k, he can reverse the computation of s
- * and work out x = r^-1 * (s*k - hash) mod q. That is, he
- * can deduce the private half of your key, and masquerade
- * as you for as long as the key is still valid.
- *
- * - since r is a function purely of k and the public key, if
- * the attacker only has a _range of possibilities_ for k
- * it's easy for him to work through them all and check each
- * one against r; he'll never be unsure of whether he's got
- * the right one.
- *
- * - if you ever sign two different hashes with the same k, it
- * will be immediately obvious because the two signatures
- * will have the same r, and moreover an attacker in
- * possession of both signatures (and the public key of
- * course) can compute k = (hash1-hash2) * (s1-s2)^-1 mod q,
- * and from there deduce x as before.
- *
- * - the Bleichenbacher attack on DSA makes use of methods of
- * generating k which are significantly non-uniformly
- * distributed; in particular, generating a 160-bit random
- * number and reducing it mod q is right out.
- *
- * For this reason we must be pretty careful about how we
- * generate our k. Since this code runs on Windows, with no
- * particularly good system entropy sources, we can't trust our
- * RNG itself to produce properly unpredictable data. Hence, we
- * use a totally different scheme instead.
- *
- * What we do is to take a SHA-512 (_big_) hash of the private
- * key x, and then feed this into another SHA-512 hash that
- * also includes the message hash being signed. That is:
- *
- * proto_k = SHA512 ( SHA512(x) || SHA160(message) )
- *
- * This number is 512 bits long, so reducing it mod q won't be
- * noticeably non-uniform. So
- *
- * k = proto_k mod q
- *
- * This has the interesting property that it's _deterministic_:
- * signing the same hash twice with the same key yields the
- * same signature.
- *
- * Despite this determinism, it's still not predictable to an
- * attacker, because in order to repeat the SHA-512
- * construction that created it, the attacker would have to
- * know the private key value x - and by assumption he doesn't,
- * because if he knew that he wouldn't be attacking k!
- *
- * (This trick doesn't, _per se_, protect against reuse of k.
- * Reuse of k is left to chance; all it does is prevent
- * _excessively high_ chances of reuse of k due to entropy
- * problems.)
- *
- * Thanks to Colin Plumb for the general idea of using x to
- * ensure k is hard to guess, and to the Cambridge University
- * 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);
-
- /*
- * Hash some identifying text plus x.
- */
- SHA512_Init(&ss);
- SHA512_Bytes(&ss, "DSA deterministic k generator", 30);
- sha512_mpint(&ss, dss->x);
- SHA512_Final(&ss, digest512);
-
- /*
- * Now hash that digest plus the message hash.
- */
- SHA512_Init(&ss);
- SHA512_Bytes(&ss, digest512, sizeof(digest512));
- SHA512_Bytes(&ss, digest, sizeof(digest));
- SHA512_Final(&ss, digest512);
-
- memset(&ss, 0, sizeof(ss));
-
- /*
- * 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);
- freebn(proto_k);
-
- memset(digest512, 0, sizeof(digest512));
-
- /*
- * Now we have k, so just go ahead and compute the signature.
- */
- gkp = modpow(dss->g, k, dss->p); /* g^k mod p */
- r = bigmod(gkp, dss->q); /* r = (g^k mod p) mod q */
- freebn(gkp);
-
- hash = bignum_from_bytes(digest, 20);
- kinv = modinv(k, dss->q); /* k^-1 mod q */
- hxr = bigmuladd(dss->x, r, hash); /* hash + x*r */
- s = modmul(kinv, hxr, dss->q); /* s = k^-1 * (hash + x*r) mod q */
- freebn(hxr);
- freebn(kinv);
- freebn(hash);
-
- /*
- * Signature blob is
- *
- * string "ssh-dss"
- * string two 20-byte numbers r and s, end to end
- *
- * i.e. 4+7 + 4+40 bytes.
- */
- nbytes = 4 + 7 + 4 + 40;
- bytes = snewn(nbytes, unsigned char);
- PUT_32BIT(bytes, 7);
- memcpy(bytes + 4, "ssh-dss", 7);
- PUT_32BIT(bytes + 4 + 7, 40);
- for (i = 0; i < 20; i++) {
- bytes[4 + 7 + 4 + i] = bignum_byte(r, 19 - i);
- bytes[4 + 7 + 4 + 20 + i] = bignum_byte(s, 19 - i);
- }
- freebn(r);
- freebn(s);
-
- *siglen = nbytes;
- return bytes;
-}
-
-const struct ssh_signkey ssh_dss = {
- dss_newkey,
- dss_freekey,
- dss_fmtkey,
- dss_public_blob,
- dss_private_blob,
- dss_createkey,
- dss_openssh_createkey,
- dss_openssh_fmtkey,
- dss_pubkey_bits,
- dss_fingerprint,
- dss_verifysig,
- dss_sign,
- "ssh-dss",
- "dss"
-};
+/*
+ * Digital Signature Standard implementation for PuTTY.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "ssh.h"
+#include "misc.h"
+
+static void sha_mpint(SHA_State * s, Bignum b)
+{
+ unsigned char lenbuf[4];
+ int len;
+ len = (bignum_bitcount(b) + 8) / 8;
+ PUT_32BIT(lenbuf, len);
+ SHA_Bytes(s, lenbuf, 4);
+ while (len-- > 0) {
+ lenbuf[0] = bignum_byte(b, len);
+ SHA_Bytes(s, lenbuf, 1);
+ }
+ smemclr(lenbuf, sizeof(lenbuf));
+}
+
+static void sha512_mpint(SHA512_State * s, Bignum b)
+{
+ unsigned char lenbuf[4];
+ int len;
+ len = (bignum_bitcount(b) + 8) / 8;
+ PUT_32BIT(lenbuf, len);
+ SHA512_Bytes(s, lenbuf, 4);
+ while (len-- > 0) {
+ lenbuf[0] = bignum_byte(b, len);
+ SHA512_Bytes(s, lenbuf, 1);
+ }
+ smemclr(lenbuf, sizeof(lenbuf));
+}
+
+static void getstring(char **data, int *datalen, 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(char **data, int *datalen)
+{
+ char *p;
+ int length;
+ Bignum b;
+
+ getstring(data, datalen, &p, &length);
+ if (!p)
+ return NULL;
+ if (p[0] & 0x80)
+ return NULL; /* negative mp */
+ b = bignum_from_bytes((unsigned char *)p, length);
+ return b;
+}
+
+static Bignum get160(char **data, int *datalen)
+{
+ Bignum b;
+
+ if (*datalen < 20)
+ return NULL;
+
+ b = bignum_from_bytes((unsigned char *)*data, 20);
+ *data += 20;
+ *datalen -= 20;
+
+ return b;
+}
+
+static void dss_freekey(void *key); /* forward reference */
+
+static void *dss_newkey(char *data, int len)
+{
+ char *p;
+ int slen;
+ struct dss_key *dss;
+
+ dss = snew(struct dss_key);
+ getstring(&data, &len, &p, &slen);
+
+#ifdef DEBUG_DSS
+ {
+ int i;
+ printf("key:");
+ for (i = 0; i < len; i++)
+ printf(" %02x", (unsigned char) (data[i]));
+ printf("\n");
+ }
+#endif
+
+ if (!p || slen != 7 || memcmp(p, "ssh-dss", 7)) {
+ sfree(dss);
+ return NULL;
+ }
+ dss->p = getmp(&data, &len);
+ dss->q = getmp(&data, &len);
+ dss->g = getmp(&data, &len);
+ dss->y = getmp(&data, &len);
+ dss->x = NULL;
+
+ if (!dss->p || !dss->q || !dss->g || !dss->y ||
+ !bignum_cmp(dss->q, Zero) || !bignum_cmp(dss->p, Zero)) {
+ /* Invalid key. */
+ dss_freekey(dss);
+ return NULL;
+ }
+
+ return dss;
+}
+
+static void dss_freekey(void *key)
+{
+ struct dss_key *dss = (struct dss_key *) key;
+ if (dss->p)
+ freebn(dss->p);
+ if (dss->q)
+ freebn(dss->q);
+ if (dss->g)
+ freebn(dss->g);
+ if (dss->y)
+ freebn(dss->y);
+ if (dss->x)
+ freebn(dss->x);
+ sfree(dss);
+}
+
+static char *dss_fmtkey(void *key)
+{
+ struct dss_key *dss = (struct dss_key *) key;
+ char *p;
+ int len, i, pos, nibbles;
+ static const char hex[] = "0123456789abcdef";
+ if (!dss->p)
+ return NULL;
+ len = 8 + 4 + 1; /* 4 x "0x", punctuation, \0 */
+ len += 4 * (bignum_bitcount(dss->p) + 15) / 16;
+ len += 4 * (bignum_bitcount(dss->q) + 15) / 16;
+ len += 4 * (bignum_bitcount(dss->g) + 15) / 16;
+ len += 4 * (bignum_bitcount(dss->y) + 15) / 16;
+ p = snewn(len, char);
+ if (!p)
+ return NULL;
+
+ pos = 0;
+ pos += sprintf(p + pos, "0x");
+ nibbles = (3 + bignum_bitcount(dss->p)) / 4;
+ if (nibbles < 1)
+ nibbles = 1;
+ for (i = nibbles; i--;)
+ p[pos++] =
+ hex[(bignum_byte(dss->p, i / 2) >> (4 * (i % 2))) & 0xF];
+ pos += sprintf(p + pos, ",0x");
+ nibbles = (3 + bignum_bitcount(dss->q)) / 4;
+ if (nibbles < 1)
+ nibbles = 1;
+ for (i = nibbles; i--;)
+ p[pos++] =
+ hex[(bignum_byte(dss->q, i / 2) >> (4 * (i % 2))) & 0xF];
+ pos += sprintf(p + pos, ",0x");
+ nibbles = (3 + bignum_bitcount(dss->g)) / 4;
+ if (nibbles < 1)
+ nibbles = 1;
+ for (i = nibbles; i--;)
+ p[pos++] =
+ hex[(bignum_byte(dss->g, i / 2) >> (4 * (i % 2))) & 0xF];
+ pos += sprintf(p + pos, ",0x");
+ nibbles = (3 + bignum_bitcount(dss->y)) / 4;
+ if (nibbles < 1)
+ nibbles = 1;
+ for (i = nibbles; i--;)
+ p[pos++] =
+ hex[(bignum_byte(dss->y, i / 2) >> (4 * (i % 2))) & 0xF];
+ p[pos] = '\0';
+ 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)
+{
+ struct dss_key *dss = (struct dss_key *) key;
+ char *p;
+ int slen;
+ char hash[20];
+ Bignum r, s, w, gu1p, yu2p, gu1yu2p, u1, u2, sha, v;
+ int ret;
+
+ if (!dss->p)
+ return 0;
+
+#ifdef DEBUG_DSS
+ {
+ int i;
+ printf("sig:");
+ for (i = 0; i < siglen; i++)
+ printf(" %02x", (unsigned char) (sig[i]));
+ printf("\n");
+ }
+#endif
+ /*
+ * Commercial SSH (2.0.13) and OpenSSH disagree over the format
+ * of a DSA signature. OpenSSH is in line with RFC 4253:
+ * it uses a string "ssh-dss", followed by a 40-byte string
+ * containing two 160-bit integers end-to-end. Commercial SSH
+ * can't be bothered with the header bit, and considers a DSA
+ * signature blob to be _just_ the 40-byte string containing
+ * the two 160-bit integers. We tell them apart by measuring
+ * the length: length 40 means the commercial-SSH bug, anything
+ * else is assumed to be RFC-compliant.
+ */
+ if (siglen != 40) { /* bug not present; read admin fields */
+ getstring(&sig, &siglen, &p, &slen);
+ if (!p || slen != 7 || memcmp(p, "ssh-dss", 7)) {
+ return 0;
+ }
+ sig += 4, siglen -= 4; /* skip yet another length field */
+ }
+ r = get160(&sig, &siglen);
+ s = get160(&sig, &siglen);
+ if (!r || !s) {
+ if (r)
+ freebn(r);
+ if (s)
+ freebn(s);
+ return 0;
+ }
+
+ if (!bignum_cmp(s, Zero)) {
+ freebn(r);
+ freebn(s);
+ return 0;
+ }
+
+ /*
+ * Step 1. w <- s^-1 mod q.
+ */
+ w = modinv(s, dss->q);
+ if (!w) {
+ freebn(r);
+ freebn(s);
+ return 0;
+ }
+
+ /*
+ * Step 2. u1 <- SHA(message) * w mod q.
+ */
+ SHA_Simple(data, datalen, (unsigned char *)hash);
+ p = hash;
+ slen = 20;
+ sha = get160(&p, &slen);
+ u1 = modmul(sha, w, dss->q);
+
+ /*
+ * Step 3. u2 <- r * w mod q.
+ */
+ u2 = modmul(r, w, dss->q);
+
+ /*
+ * Step 4. v <- (g^u1 * y^u2 mod p) mod q.
+ */
+ gu1p = modpow(dss->g, u1, dss->p);
+ yu2p = modpow(dss->y, u2, dss->p);
+ gu1yu2p = modmul(gu1p, yu2p, dss->p);
+ v = modmul(gu1yu2p, One, dss->q);
+
+ /*
+ * Step 5. v should now be equal to r.
+ */
+
+ ret = !bignum_cmp(v, r);
+
+ freebn(w);
+ freebn(sha);
+ freebn(u1);
+ freebn(u2);
+ freebn(gu1p);
+ freebn(yu2p);
+ freebn(gu1yu2p);
+ freebn(v);
+ freebn(r);
+ freebn(s);
+
+ return ret;
+}
+
+static unsigned char *dss_public_blob(void *key, int *len)
+{
+ struct dss_key *dss = (struct dss_key *) key;
+ int plen, qlen, glen, ylen, bloblen;
+ int i;
+ unsigned char *blob, *p;
+
+ plen = (bignum_bitcount(dss->p) + 8) / 8;
+ qlen = (bignum_bitcount(dss->q) + 8) / 8;
+ glen = (bignum_bitcount(dss->g) + 8) / 8;
+ ylen = (bignum_bitcount(dss->y) + 8) / 8;
+
+ /*
+ * string "ssh-dss", mpint p, mpint q, mpint g, mpint y. Total
+ * 27 + sum of lengths. (five length fields, 20+7=27).
+ */
+ bloblen = 27 + plen + qlen + glen + ylen;
+ blob = snewn(bloblen, unsigned char);
+ p = blob;
+ PUT_32BIT(p, 7);
+ p += 4;
+ memcpy(p, "ssh-dss", 7);
+ p += 7;
+ PUT_32BIT(p, plen);
+ p += 4;
+ for (i = plen; i--;)
+ *p++ = bignum_byte(dss->p, i);
+ PUT_32BIT(p, qlen);
+ p += 4;
+ for (i = qlen; i--;)
+ *p++ = bignum_byte(dss->q, i);
+ PUT_32BIT(p, glen);
+ p += 4;
+ for (i = glen; i--;)
+ *p++ = bignum_byte(dss->g, i);
+ PUT_32BIT(p, ylen);
+ p += 4;
+ for (i = ylen; i--;)
+ *p++ = bignum_byte(dss->y, i);
+ assert(p == blob + bloblen);
+ *len = bloblen;
+ return blob;
+}
+
+static unsigned char *dss_private_blob(void *key, int *len)
+{
+ struct dss_key *dss = (struct dss_key *) key;
+ int xlen, bloblen;
+ int i;
+ unsigned char *blob, *p;
+
+ xlen = (bignum_bitcount(dss->x) + 8) / 8;
+
+ /*
+ * mpint x, string[20] the SHA of p||q||g. Total 4 + xlen.
+ */
+ bloblen = 4 + xlen;
+ blob = snewn(bloblen, unsigned char);
+ p = blob;
+ PUT_32BIT(p, xlen);
+ p += 4;
+ for (i = xlen; i--;)
+ *p++ = bignum_byte(dss->x, i);
+ assert(p == blob + bloblen);
+ *len = bloblen;
+ return blob;
+}
+
+static void *dss_createkey(unsigned char *pub_blob, int pub_len,
+ unsigned char *priv_blob, int priv_len)
+{
+ struct dss_key *dss;
+ char *pb = (char *) priv_blob;
+ char *hash;
+ int hashlen;
+ SHA_State s;
+ unsigned char digest[20];
+ Bignum ytest;
+
+ dss = dss_newkey((char *) pub_blob, pub_len);
+ if (!dss)
+ return NULL;
+ dss->x = getmp(&pb, &priv_len);
+ if (!dss->x) {
+ dss_freekey(dss);
+ return NULL;
+ }
+
+ /*
+ * Check the obsolete hash in the old DSS key format.
+ */
+ hashlen = -1;
+ getstring(&pb, &priv_len, &hash, &hashlen);
+ if (hashlen == 20) {
+ SHA_Init(&s);
+ sha_mpint(&s, dss->p);
+ sha_mpint(&s, dss->q);
+ sha_mpint(&s, dss->g);
+ SHA_Final(&s, digest);
+ if (0 != memcmp(hash, digest, 20)) {
+ dss_freekey(dss);
+ return NULL;
+ }
+ }
+
+ /*
+ * Now ensure g^x mod p really is y.
+ */
+ ytest = modpow(dss->g, dss->x, dss->p);
+ if (0 != bignum_cmp(ytest, dss->y)) {
+ dss_freekey(dss);
+ freebn(ytest);
+ return NULL;
+ }
+ freebn(ytest);
+
+ return dss;
+}
+
+static void *dss_openssh_createkey(unsigned char **blob, int *len)
+{
+ char **b = (char **) blob;
+ struct dss_key *dss;
+
+ dss = snew(struct dss_key);
+
+ dss->p = getmp(b, len);
+ dss->q = getmp(b, len);
+ dss->g = getmp(b, len);
+ dss->y = getmp(b, len);
+ dss->x = getmp(b, len);
+
+ if (!dss->p || !dss->q || !dss->g || !dss->y || !dss->x ||
+ !bignum_cmp(dss->q, Zero) || !bignum_cmp(dss->p, Zero)) {
+ /* Invalid key. */
+ dss_freekey(dss);
+ return NULL;
+ }
+
+ return dss;
+}
+
+static int dss_openssh_fmtkey(void *key, unsigned char *blob, int len)
+{
+ struct dss_key *dss = (struct dss_key *) key;
+ int bloblen, i;
+
+ bloblen =
+ ssh2_bignum_length(dss->p) +
+ ssh2_bignum_length(dss->q) +
+ ssh2_bignum_length(dss->g) +
+ ssh2_bignum_length(dss->y) +
+ ssh2_bignum_length(dss->x);
+
+ if (bloblen > len)
+ return bloblen;
+
+ bloblen = 0;
+#define ENC(x) \
+ PUT_32BIT(blob+bloblen, ssh2_bignum_length((x))-4); bloblen += 4; \
+ for (i = ssh2_bignum_length((x))-4; i-- ;) blob[bloblen++]=bignum_byte((x),i);
+ ENC(dss->p);
+ ENC(dss->q);
+ ENC(dss->g);
+ ENC(dss->y);
+ ENC(dss->x);
+
+ return bloblen;
+}
+
+static int dss_pubkey_bits(void *blob, int len)
+{
+ struct dss_key *dss;
+ int ret;
+
+ dss = dss_newkey((char *) blob, len);
+ if (!dss)
+ return -1;
+ ret = bignum_bitcount(dss->p);
+ dss_freekey(dss);
+
+ return ret;
+}
+
+static unsigned char *dss_sign(void *key, char *data, int datalen, int *siglen)
+{
+ /*
+ * The basic DSS signing algorithm is:
+ *
+ * - invent a random k between 1 and q-1 (exclusive).
+ * - Compute r = (g^k mod p) mod q.
+ * - Compute s = k^-1 * (hash + x*r) mod q.
+ *
+ * This has the dangerous properties that:
+ *
+ * - if an attacker in possession of the public key _and_ the
+ * signature (for example, the host you just authenticated
+ * to) can guess your k, he can reverse the computation of s
+ * and work out x = r^-1 * (s*k - hash) mod q. That is, he
+ * can deduce the private half of your key, and masquerade
+ * as you for as long as the key is still valid.
+ *
+ * - since r is a function purely of k and the public key, if
+ * the attacker only has a _range of possibilities_ for k
+ * it's easy for him to work through them all and check each
+ * one against r; he'll never be unsure of whether he's got
+ * the right one.
+ *
+ * - if you ever sign two different hashes with the same k, it
+ * will be immediately obvious because the two signatures
+ * will have the same r, and moreover an attacker in
+ * possession of both signatures (and the public key of
+ * course) can compute k = (hash1-hash2) * (s1-s2)^-1 mod q,
+ * and from there deduce x as before.
+ *
+ * - the Bleichenbacher attack on DSA makes use of methods of
+ * generating k which are significantly non-uniformly
+ * distributed; in particular, generating a 160-bit random
+ * number and reducing it mod q is right out.
+ *
+ * For this reason we must be pretty careful about how we
+ * generate our k. Since this code runs on Windows, with no
+ * particularly good system entropy sources, we can't trust our
+ * RNG itself to produce properly unpredictable data. Hence, we
+ * use a totally different scheme instead.
+ *
+ * What we do is to take a SHA-512 (_big_) hash of the private
+ * key x, and then feed this into another SHA-512 hash that
+ * also includes the message hash being signed. That is:
+ *
+ * proto_k = SHA512 ( SHA512(x) || SHA160(message) )
+ *
+ * This number is 512 bits long, so reducing it mod q won't be
+ * noticeably non-uniform. So
+ *
+ * k = proto_k mod q
+ *
+ * This has the interesting property that it's _deterministic_:
+ * signing the same hash twice with the same key yields the
+ * same signature.
+ *
+ * Despite this determinism, it's still not predictable to an
+ * attacker, because in order to repeat the SHA-512
+ * construction that created it, the attacker would have to
+ * know the private key value x - and by assumption he doesn't,
+ * because if he knew that he wouldn't be attacking k!
+ *
+ * (This trick doesn't, _per se_, protect against reuse of k.
+ * Reuse of k is left to chance; all it does is prevent
+ * _excessively high_ chances of reuse of k due to entropy
+ * problems.)
+ *
+ * Thanks to Colin Plumb for the general idea of using x to
+ * ensure k is hard to guess, and to the Cambridge University
+ * 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);
+
+ /*
+ * Hash some identifying text plus x.
+ */
+ SHA512_Init(&ss);
+ SHA512_Bytes(&ss, "DSA deterministic k generator", 30);
+ sha512_mpint(&ss, dss->x);
+ SHA512_Final(&ss, digest512);
+
+ /*
+ * Now hash that digest plus the message hash.
+ */
+ SHA512_Init(&ss);
+ SHA512_Bytes(&ss, digest512, sizeof(digest512));
+ SHA512_Bytes(&ss, digest, sizeof(digest));
+
+ while (1) {
+ SHA512_State ss2 = ss; /* structure copy */
+ SHA512_Final(&ss2, digest512);
+
+ smemclr(&ss2, sizeof(ss2));
+
+ /*
+ * 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);
+ 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;
+ }
+
+ break;
+ }
+
+ smemclr(&ss, sizeof(ss));
+
+ smemclr(digest512, sizeof(digest512));
+
+ /*
+ * Now we have k, so just go ahead and compute the signature.
+ */
+ gkp = modpow(dss->g, k, dss->p); /* g^k mod p */
+ r = bigmod(gkp, dss->q); /* r = (g^k mod p) mod q */
+ freebn(gkp);
+
+ hash = bignum_from_bytes(digest, 20);
+ hxr = bigmuladd(dss->x, r, hash); /* hash + x*r */
+ s = modmul(kinv, hxr, dss->q); /* s = k^-1 * (hash + x*r) mod q */
+ freebn(hxr);
+ freebn(kinv);
+ freebn(k);
+ freebn(hash);
+
+ /*
+ * Signature blob is
+ *
+ * string "ssh-dss"
+ * string two 20-byte numbers r and s, end to end
+ *
+ * i.e. 4+7 + 4+40 bytes.
+ */
+ nbytes = 4 + 7 + 4 + 40;
+ bytes = snewn(nbytes, unsigned char);
+ PUT_32BIT(bytes, 7);
+ memcpy(bytes + 4, "ssh-dss", 7);
+ PUT_32BIT(bytes + 4 + 7, 40);
+ for (i = 0; i < 20; i++) {
+ bytes[4 + 7 + 4 + i] = bignum_byte(r, 19 - i);
+ bytes[4 + 7 + 4 + 20 + i] = bignum_byte(s, 19 - i);
+ }
+ freebn(r);
+ freebn(s);
+
+ *siglen = nbytes;
+ return bytes;
+}
+
+const struct ssh_signkey ssh_dss = {
+ dss_newkey,
+ dss_freekey,
+ dss_fmtkey,
+ dss_public_blob,
+ dss_private_blob,
+ dss_createkey,
+ dss_openssh_createkey,
+ dss_openssh_fmtkey,
+ dss_pubkey_bits,
+ dss_fingerprint,
+ dss_verifysig,
+ dss_sign,
+ "ssh-dss",
+ "dss"
+};
diff --git a/tools/plink/sshmd5.c b/tools/plink/sshmd5.c
index 80474dfad..2fdb59007 100644
--- a/tools/plink/sshmd5.c
+++ b/tools/plink/sshmd5.c
@@ -1,344 +1,340 @@
-#include "ssh.h"
-
-/*
- * MD5 implementation for PuTTY. Written directly from the spec by
- * Simon Tatham.
- */
-
-/* ----------------------------------------------------------------------
- * Core MD5 algorithm: processes 16-word blocks into a message digest.
- */
-
-#define F(x,y,z) ( ((x) & (y)) | ((~(x)) & (z)) )
-#define G(x,y,z) ( ((x) & (z)) | ((~(z)) & (y)) )
-#define H(x,y,z) ( (x) ^ (y) ^ (z) )
-#define I(x,y,z) ( (y) ^ ( (x) | ~(z) ) )
-
-#define rol(x,y) ( ((x) << (y)) | (((uint32)x) >> (32-y)) )
-
-#define subround(f,w,x,y,z,k,s,ti) \
- w = x + rol(w + f(x,y,z) + block[k] + ti, s)
-
-static void MD5_Core_Init(MD5_Core_State * s)
-{
- s->h[0] = 0x67452301;
- s->h[1] = 0xefcdab89;
- s->h[2] = 0x98badcfe;
- s->h[3] = 0x10325476;
-}
-
-static void MD5_Block(MD5_Core_State * s, uint32 * block)
-{
- uint32 a, b, c, d;
-
- a = s->h[0];
- b = s->h[1];
- c = s->h[2];
- d = s->h[3];
-
- subround(F, a, b, c, d, 0, 7, 0xd76aa478);
- subround(F, d, a, b, c, 1, 12, 0xe8c7b756);
- subround(F, c, d, a, b, 2, 17, 0x242070db);
- subround(F, b, c, d, a, 3, 22, 0xc1bdceee);
- subround(F, a, b, c, d, 4, 7, 0xf57c0faf);
- subround(F, d, a, b, c, 5, 12, 0x4787c62a);
- subround(F, c, d, a, b, 6, 17, 0xa8304613);
- subround(F, b, c, d, a, 7, 22, 0xfd469501);
- subround(F, a, b, c, d, 8, 7, 0x698098d8);
- subround(F, d, a, b, c, 9, 12, 0x8b44f7af);
- subround(F, c, d, a, b, 10, 17, 0xffff5bb1);
- subround(F, b, c, d, a, 11, 22, 0x895cd7be);
- subround(F, a, b, c, d, 12, 7, 0x6b901122);
- subround(F, d, a, b, c, 13, 12, 0xfd987193);
- subround(F, c, d, a, b, 14, 17, 0xa679438e);
- subround(F, b, c, d, a, 15, 22, 0x49b40821);
- subround(G, a, b, c, d, 1, 5, 0xf61e2562);
- subround(G, d, a, b, c, 6, 9, 0xc040b340);
- subround(G, c, d, a, b, 11, 14, 0x265e5a51);
- subround(G, b, c, d, a, 0, 20, 0xe9b6c7aa);
- subround(G, a, b, c, d, 5, 5, 0xd62f105d);
- subround(G, d, a, b, c, 10, 9, 0x02441453);
- subround(G, c, d, a, b, 15, 14, 0xd8a1e681);
- subround(G, b, c, d, a, 4, 20, 0xe7d3fbc8);
- subround(G, a, b, c, d, 9, 5, 0x21e1cde6);
- subround(G, d, a, b, c, 14, 9, 0xc33707d6);
- subround(G, c, d, a, b, 3, 14, 0xf4d50d87);
- subround(G, b, c, d, a, 8, 20, 0x455a14ed);
- subround(G, a, b, c, d, 13, 5, 0xa9e3e905);
- subround(G, d, a, b, c, 2, 9, 0xfcefa3f8);
- subround(G, c, d, a, b, 7, 14, 0x676f02d9);
- subround(G, b, c, d, a, 12, 20, 0x8d2a4c8a);
- subround(H, a, b, c, d, 5, 4, 0xfffa3942);
- subround(H, d, a, b, c, 8, 11, 0x8771f681);
- subround(H, c, d, a, b, 11, 16, 0x6d9d6122);
- subround(H, b, c, d, a, 14, 23, 0xfde5380c);
- subround(H, a, b, c, d, 1, 4, 0xa4beea44);
- subround(H, d, a, b, c, 4, 11, 0x4bdecfa9);
- subround(H, c, d, a, b, 7, 16, 0xf6bb4b60);
- subround(H, b, c, d, a, 10, 23, 0xbebfbc70);
- subround(H, a, b, c, d, 13, 4, 0x289b7ec6);
- subround(H, d, a, b, c, 0, 11, 0xeaa127fa);
- subround(H, c, d, a, b, 3, 16, 0xd4ef3085);
- subround(H, b, c, d, a, 6, 23, 0x04881d05);
- subround(H, a, b, c, d, 9, 4, 0xd9d4d039);
- subround(H, d, a, b, c, 12, 11, 0xe6db99e5);
- subround(H, c, d, a, b, 15, 16, 0x1fa27cf8);
- subround(H, b, c, d, a, 2, 23, 0xc4ac5665);
- subround(I, a, b, c, d, 0, 6, 0xf4292244);
- subround(I, d, a, b, c, 7, 10, 0x432aff97);
- subround(I, c, d, a, b, 14, 15, 0xab9423a7);
- subround(I, b, c, d, a, 5, 21, 0xfc93a039);
- subround(I, a, b, c, d, 12, 6, 0x655b59c3);
- subround(I, d, a, b, c, 3, 10, 0x8f0ccc92);
- subround(I, c, d, a, b, 10, 15, 0xffeff47d);
- subround(I, b, c, d, a, 1, 21, 0x85845dd1);
- subround(I, a, b, c, d, 8, 6, 0x6fa87e4f);
- subround(I, d, a, b, c, 15, 10, 0xfe2ce6e0);
- subround(I, c, d, a, b, 6, 15, 0xa3014314);
- subround(I, b, c, d, a, 13, 21, 0x4e0811a1);
- subround(I, a, b, c, d, 4, 6, 0xf7537e82);
- subround(I, d, a, b, c, 11, 10, 0xbd3af235);
- subround(I, c, d, a, b, 2, 15, 0x2ad7d2bb);
- subround(I, b, c, d, a, 9, 21, 0xeb86d391);
-
- s->h[0] += a;
- s->h[1] += b;
- s->h[2] += c;
- s->h[3] += d;
-}
-
-/* ----------------------------------------------------------------------
- * Outer MD5 algorithm: take an arbitrary length byte string,
- * convert it into 16-word blocks with the prescribed padding at
- * the end, and pass those blocks to the core MD5 algorithm.
- */
-
-#define BLKSIZE 64
-
-void MD5Init(struct MD5Context *s)
-{
- MD5_Core_Init(&s->core);
- s->blkused = 0;
- s->lenhi = s->lenlo = 0;
-}
-
-void MD5Update(struct MD5Context *s, unsigned char const *p, unsigned len)
-{
- unsigned char *q = (unsigned char *) p;
- uint32 wordblock[16];
- uint32 lenw = len;
- int i;
-
- /*
- * Update the length field.
- */
- s->lenlo += lenw;
- s->lenhi += (s->lenlo < lenw);
-
- if (s->blkused + len < BLKSIZE) {
- /*
- * Trivial case: just add to the block.
- */
- memcpy(s->block + s->blkused, q, len);
- s->blkused += len;
- } else {
- /*
- * We must complete and process at least one block.
- */
- while (s->blkused + len >= BLKSIZE) {
- memcpy(s->block + s->blkused, q, BLKSIZE - s->blkused);
- q += BLKSIZE - s->blkused;
- len -= BLKSIZE - s->blkused;
- /* Now process the block. Gather bytes little-endian into words */
- for (i = 0; i < 16; i++) {
- wordblock[i] =
- (((uint32) s->block[i * 4 + 3]) << 24) |
- (((uint32) s->block[i * 4 + 2]) << 16) |
- (((uint32) s->block[i * 4 + 1]) << 8) |
- (((uint32) s->block[i * 4 + 0]) << 0);
- }
- MD5_Block(&s->core, wordblock);
- s->blkused = 0;
- }
- memcpy(s->block, q, len);
- s->blkused = len;
- }
-}
-
-void MD5Final(unsigned char output[16], struct MD5Context *s)
-{
- int i;
- unsigned pad;
- unsigned char c[64];
- uint32 lenhi, lenlo;
-
- if (s->blkused >= 56)
- pad = 56 + 64 - s->blkused;
- else
- pad = 56 - s->blkused;
-
- lenhi = (s->lenhi << 3) | (s->lenlo >> (32 - 3));
- lenlo = (s->lenlo << 3);
-
- memset(c, 0, pad);
- c[0] = 0x80;
- MD5Update(s, c, pad);
-
- c[7] = (lenhi >> 24) & 0xFF;
- c[6] = (lenhi >> 16) & 0xFF;
- c[5] = (lenhi >> 8) & 0xFF;
- c[4] = (lenhi >> 0) & 0xFF;
- c[3] = (lenlo >> 24) & 0xFF;
- c[2] = (lenlo >> 16) & 0xFF;
- c[1] = (lenlo >> 8) & 0xFF;
- c[0] = (lenlo >> 0) & 0xFF;
-
- MD5Update(s, c, 8);
-
- for (i = 0; i < 4; i++) {
- output[4 * i + 3] = (s->core.h[i] >> 24) & 0xFF;
- output[4 * i + 2] = (s->core.h[i] >> 16) & 0xFF;
- output[4 * i + 1] = (s->core.h[i] >> 8) & 0xFF;
- output[4 * i + 0] = (s->core.h[i] >> 0) & 0xFF;
- }
-}
-
-void MD5Simple(void const *p, unsigned len, unsigned char output[16])
-{
- struct MD5Context s;
-
- MD5Init(&s);
- MD5Update(&s, (unsigned char const *)p, len);
- MD5Final(output, &s);
-}
-
-/* ----------------------------------------------------------------------
- * The above is the MD5 algorithm itself. Now we implement the
- * HMAC wrapper on it.
- *
- * Some of these functions are exported directly, because they are
- * useful elsewhere (SOCKS5 CHAP authentication uses HMAC-MD5).
- */
-
-void *hmacmd5_make_context(void)
-{
- return snewn(3, struct MD5Context);
-}
-
-void hmacmd5_free_context(void *handle)
-{
- sfree(handle);
-}
-
-void hmacmd5_key(void *handle, void const *keyv, int len)
-{
- struct MD5Context *keys = (struct MD5Context *)handle;
- unsigned char foo[64];
- unsigned char const *key = (unsigned char const *)keyv;
- int i;
-
- memset(foo, 0x36, 64);
- for (i = 0; i < len && i < 64; i++)
- foo[i] ^= key[i];
- MD5Init(&keys[0]);
- MD5Update(&keys[0], foo, 64);
-
- memset(foo, 0x5C, 64);
- for (i = 0; i < len && i < 64; i++)
- foo[i] ^= key[i];
- MD5Init(&keys[1]);
- MD5Update(&keys[1], foo, 64);
-
- memset(foo, 0, 64); /* burn the evidence */
-}
-
-static void hmacmd5_key_16(void *handle, unsigned char *key)
-{
- hmacmd5_key(handle, key, 16);
-}
-
-static void hmacmd5_start(void *handle)
-{
- struct MD5Context *keys = (struct MD5Context *)handle;
-
- keys[2] = keys[0]; /* structure copy */
-}
-
-static void hmacmd5_bytes(void *handle, unsigned char const *blk, int len)
-{
- struct MD5Context *keys = (struct MD5Context *)handle;
- MD5Update(&keys[2], blk, len);
-}
-
-static void hmacmd5_genresult(void *handle, unsigned char *hmac)
-{
- struct MD5Context *keys = (struct MD5Context *)handle;
- struct MD5Context s;
- unsigned char intermediate[16];
-
- s = keys[2]; /* structure copy */
- MD5Final(intermediate, &s);
- s = keys[1]; /* structure copy */
- MD5Update(&s, intermediate, 16);
- MD5Final(hmac, &s);
-}
-
-static int hmacmd5_verresult(void *handle, unsigned char const *hmac)
-{
- unsigned char correct[16];
- hmacmd5_genresult(handle, correct);
- return !memcmp(correct, hmac, 16);
-}
-
-static void hmacmd5_do_hmac_internal(void *handle,
- unsigned char const *blk, int len,
- unsigned char const *blk2, int len2,
- unsigned char *hmac)
-{
- hmacmd5_start(handle);
- hmacmd5_bytes(handle, blk, len);
- if (blk2) hmacmd5_bytes(handle, blk2, len2);
- hmacmd5_genresult(handle, hmac);
-}
-
-void hmacmd5_do_hmac(void *handle, unsigned char const *blk, int len,
- unsigned char *hmac)
-{
- hmacmd5_do_hmac_internal(handle, blk, len, NULL, 0, hmac);
-}
-
-static void hmacmd5_do_hmac_ssh(void *handle, unsigned char const *blk, int len,
- unsigned long seq, unsigned char *hmac)
-{
- unsigned char seqbuf[16];
-
- seqbuf[0] = (unsigned char) ((seq >> 24) & 0xFF);
- seqbuf[1] = (unsigned char) ((seq >> 16) & 0xFF);
- seqbuf[2] = (unsigned char) ((seq >> 8) & 0xFF);
- seqbuf[3] = (unsigned char) ((seq) & 0xFF);
-
- hmacmd5_do_hmac_internal(handle, seqbuf, 4, blk, len, hmac);
-}
-
-static void hmacmd5_generate(void *handle, unsigned char *blk, int len,
- unsigned long seq)
-{
- hmacmd5_do_hmac_ssh(handle, blk, len, seq, blk + len);
-}
-
-static int hmacmd5_verify(void *handle, unsigned char *blk, int len,
- unsigned long seq)
-{
- unsigned char correct[16];
- hmacmd5_do_hmac_ssh(handle, blk, len, seq, correct);
- return !memcmp(correct, blk + len, 16);
-}
-
-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"
-};
+#include "ssh.h"
+
+/*
+ * MD5 implementation for PuTTY. Written directly from the spec by
+ * Simon Tatham.
+ */
+
+/* ----------------------------------------------------------------------
+ * Core MD5 algorithm: processes 16-word blocks into a message digest.
+ */
+
+#define F(x,y,z) ( ((x) & (y)) | ((~(x)) & (z)) )
+#define G(x,y,z) ( ((x) & (z)) | ((~(z)) & (y)) )
+#define H(x,y,z) ( (x) ^ (y) ^ (z) )
+#define I(x,y,z) ( (y) ^ ( (x) | ~(z) ) )
+
+#define rol(x,y) ( ((x) << (y)) | (((uint32)x) >> (32-y)) )
+
+#define subround(f,w,x,y,z,k,s,ti) \
+ w = x + rol(w + f(x,y,z) + block[k] + ti, s)
+
+static void MD5_Core_Init(MD5_Core_State * s)
+{
+ s->h[0] = 0x67452301;
+ s->h[1] = 0xefcdab89;
+ s->h[2] = 0x98badcfe;
+ s->h[3] = 0x10325476;
+}
+
+static void MD5_Block(MD5_Core_State * s, uint32 * block)
+{
+ uint32 a, b, c, d;
+
+ a = s->h[0];
+ b = s->h[1];
+ c = s->h[2];
+ d = s->h[3];
+
+ subround(F, a, b, c, d, 0, 7, 0xd76aa478);
+ subround(F, d, a, b, c, 1, 12, 0xe8c7b756);
+ subround(F, c, d, a, b, 2, 17, 0x242070db);
+ subround(F, b, c, d, a, 3, 22, 0xc1bdceee);
+ subround(F, a, b, c, d, 4, 7, 0xf57c0faf);
+ subround(F, d, a, b, c, 5, 12, 0x4787c62a);
+ subround(F, c, d, a, b, 6, 17, 0xa8304613);
+ subround(F, b, c, d, a, 7, 22, 0xfd469501);
+ subround(F, a, b, c, d, 8, 7, 0x698098d8);
+ subround(F, d, a, b, c, 9, 12, 0x8b44f7af);
+ subround(F, c, d, a, b, 10, 17, 0xffff5bb1);
+ subround(F, b, c, d, a, 11, 22, 0x895cd7be);
+ subround(F, a, b, c, d, 12, 7, 0x6b901122);
+ subround(F, d, a, b, c, 13, 12, 0xfd987193);
+ subround(F, c, d, a, b, 14, 17, 0xa679438e);
+ subround(F, b, c, d, a, 15, 22, 0x49b40821);
+ subround(G, a, b, c, d, 1, 5, 0xf61e2562);
+ subround(G, d, a, b, c, 6, 9, 0xc040b340);
+ subround(G, c, d, a, b, 11, 14, 0x265e5a51);
+ subround(G, b, c, d, a, 0, 20, 0xe9b6c7aa);
+ subround(G, a, b, c, d, 5, 5, 0xd62f105d);
+ subround(G, d, a, b, c, 10, 9, 0x02441453);
+ subround(G, c, d, a, b, 15, 14, 0xd8a1e681);
+ subround(G, b, c, d, a, 4, 20, 0xe7d3fbc8);
+ subround(G, a, b, c, d, 9, 5, 0x21e1cde6);
+ subround(G, d, a, b, c, 14, 9, 0xc33707d6);
+ subround(G, c, d, a, b, 3, 14, 0xf4d50d87);
+ subround(G, b, c, d, a, 8, 20, 0x455a14ed);
+ subround(G, a, b, c, d, 13, 5, 0xa9e3e905);
+ subround(G, d, a, b, c, 2, 9, 0xfcefa3f8);
+ subround(G, c, d, a, b, 7, 14, 0x676f02d9);
+ subround(G, b, c, d, a, 12, 20, 0x8d2a4c8a);
+ subround(H, a, b, c, d, 5, 4, 0xfffa3942);
+ subround(H, d, a, b, c, 8, 11, 0x8771f681);
+ subround(H, c, d, a, b, 11, 16, 0x6d9d6122);
+ subround(H, b, c, d, a, 14, 23, 0xfde5380c);
+ subround(H, a, b, c, d, 1, 4, 0xa4beea44);
+ subround(H, d, a, b, c, 4, 11, 0x4bdecfa9);
+ subround(H, c, d, a, b, 7, 16, 0xf6bb4b60);
+ subround(H, b, c, d, a, 10, 23, 0xbebfbc70);
+ subround(H, a, b, c, d, 13, 4, 0x289b7ec6);
+ subround(H, d, a, b, c, 0, 11, 0xeaa127fa);
+ subround(H, c, d, a, b, 3, 16, 0xd4ef3085);
+ subround(H, b, c, d, a, 6, 23, 0x04881d05);
+ subround(H, a, b, c, d, 9, 4, 0xd9d4d039);
+ subround(H, d, a, b, c, 12, 11, 0xe6db99e5);
+ subround(H, c, d, a, b, 15, 16, 0x1fa27cf8);
+ subround(H, b, c, d, a, 2, 23, 0xc4ac5665);
+ subround(I, a, b, c, d, 0, 6, 0xf4292244);
+ subround(I, d, a, b, c, 7, 10, 0x432aff97);
+ subround(I, c, d, a, b, 14, 15, 0xab9423a7);
+ subround(I, b, c, d, a, 5, 21, 0xfc93a039);
+ subround(I, a, b, c, d, 12, 6, 0x655b59c3);
+ subround(I, d, a, b, c, 3, 10, 0x8f0ccc92);
+ subround(I, c, d, a, b, 10, 15, 0xffeff47d);
+ subround(I, b, c, d, a, 1, 21, 0x85845dd1);
+ subround(I, a, b, c, d, 8, 6, 0x6fa87e4f);
+ subround(I, d, a, b, c, 15, 10, 0xfe2ce6e0);
+ subround(I, c, d, a, b, 6, 15, 0xa3014314);
+ subround(I, b, c, d, a, 13, 21, 0x4e0811a1);
+ subround(I, a, b, c, d, 4, 6, 0xf7537e82);
+ subround(I, d, a, b, c, 11, 10, 0xbd3af235);
+ subround(I, c, d, a, b, 2, 15, 0x2ad7d2bb);
+ subround(I, b, c, d, a, 9, 21, 0xeb86d391);
+
+ s->h[0] += a;
+ s->h[1] += b;
+ s->h[2] += c;
+ s->h[3] += d;
+}
+
+/* ----------------------------------------------------------------------
+ * Outer MD5 algorithm: take an arbitrary length byte string,
+ * convert it into 16-word blocks with the prescribed padding at
+ * the end, and pass those blocks to the core MD5 algorithm.
+ */
+
+#define BLKSIZE 64
+
+void MD5Init(struct MD5Context *s)
+{
+ MD5_Core_Init(&s->core);
+ s->blkused = 0;
+ s->lenhi = s->lenlo = 0;
+}
+
+void MD5Update(struct MD5Context *s, unsigned char const *p, unsigned len)
+{
+ unsigned char *q = (unsigned char *) p;
+ uint32 wordblock[16];
+ uint32 lenw = len;
+ int i;
+
+ /*
+ * Update the length field.
+ */
+ s->lenlo += lenw;
+ s->lenhi += (s->lenlo < lenw);
+
+ if (s->blkused + len < BLKSIZE) {
+ /*
+ * Trivial case: just add to the block.
+ */
+ memcpy(s->block + s->blkused, q, len);
+ s->blkused += len;
+ } else {
+ /*
+ * We must complete and process at least one block.
+ */
+ while (s->blkused + len >= BLKSIZE) {
+ memcpy(s->block + s->blkused, q, BLKSIZE - s->blkused);
+ q += BLKSIZE - s->blkused;
+ len -= BLKSIZE - s->blkused;
+ /* Now process the block. Gather bytes little-endian into words */
+ for (i = 0; i < 16; i++) {
+ wordblock[i] =
+ (((uint32) s->block[i * 4 + 3]) << 24) |
+ (((uint32) s->block[i * 4 + 2]) << 16) |
+ (((uint32) s->block[i * 4 + 1]) << 8) |
+ (((uint32) s->block[i * 4 + 0]) << 0);
+ }
+ MD5_Block(&s->core, wordblock);
+ s->blkused = 0;
+ }
+ memcpy(s->block, q, len);
+ s->blkused = len;
+ }
+}
+
+void MD5Final(unsigned char output[16], struct MD5Context *s)
+{
+ int i;
+ unsigned pad;
+ unsigned char c[64];
+ uint32 lenhi, lenlo;
+
+ if (s->blkused >= 56)
+ pad = 56 + 64 - s->blkused;
+ else
+ pad = 56 - s->blkused;
+
+ lenhi = (s->lenhi << 3) | (s->lenlo >> (32 - 3));
+ lenlo = (s->lenlo << 3);
+
+ memset(c, 0, pad);
+ c[0] = 0x80;
+ MD5Update(s, c, pad);
+
+ c[7] = (lenhi >> 24) & 0xFF;
+ c[6] = (lenhi >> 16) & 0xFF;
+ c[5] = (lenhi >> 8) & 0xFF;
+ c[4] = (lenhi >> 0) & 0xFF;
+ c[3] = (lenlo >> 24) & 0xFF;
+ c[2] = (lenlo >> 16) & 0xFF;
+ c[1] = (lenlo >> 8) & 0xFF;
+ c[0] = (lenlo >> 0) & 0xFF;
+
+ MD5Update(s, c, 8);
+
+ for (i = 0; i < 4; i++) {
+ output[4 * i + 3] = (s->core.h[i] >> 24) & 0xFF;
+ output[4 * i + 2] = (s->core.h[i] >> 16) & 0xFF;
+ output[4 * i + 1] = (s->core.h[i] >> 8) & 0xFF;
+ output[4 * i + 0] = (s->core.h[i] >> 0) & 0xFF;
+ }
+}
+
+void MD5Simple(void const *p, unsigned len, unsigned char output[16])
+{
+ struct MD5Context s;
+
+ MD5Init(&s);
+ MD5Update(&s, (unsigned char const *)p, len);
+ MD5Final(output, &s);
+}
+
+/* ----------------------------------------------------------------------
+ * The above is the MD5 algorithm itself. Now we implement the
+ * HMAC wrapper on it.
+ *
+ * Some of these functions are exported directly, because they are
+ * useful elsewhere (SOCKS5 CHAP authentication uses HMAC-MD5).
+ */
+
+void *hmacmd5_make_context(void)
+{
+ return snewn(3, struct MD5Context);
+}
+
+void hmacmd5_free_context(void *handle)
+{
+ sfree(handle);
+}
+
+void hmacmd5_key(void *handle, void const *keyv, int len)
+{
+ struct MD5Context *keys = (struct MD5Context *)handle;
+ unsigned char foo[64];
+ unsigned char const *key = (unsigned char const *)keyv;
+ int i;
+
+ memset(foo, 0x36, 64);
+ for (i = 0; i < len && i < 64; i++)
+ foo[i] ^= key[i];
+ MD5Init(&keys[0]);
+ MD5Update(&keys[0], foo, 64);
+
+ memset(foo, 0x5C, 64);
+ for (i = 0; i < len && i < 64; i++)
+ foo[i] ^= key[i];
+ MD5Init(&keys[1]);
+ MD5Update(&keys[1], foo, 64);
+
+ smemclr(foo, 64); /* burn the evidence */
+}
+
+static void hmacmd5_key_16(void *handle, unsigned char *key)
+{
+ hmacmd5_key(handle, key, 16);
+}
+
+static void hmacmd5_start(void *handle)
+{
+ struct MD5Context *keys = (struct MD5Context *)handle;
+
+ keys[2] = keys[0]; /* structure copy */
+}
+
+static void hmacmd5_bytes(void *handle, unsigned char const *blk, int len)
+{
+ struct MD5Context *keys = (struct MD5Context *)handle;
+ MD5Update(&keys[2], blk, len);
+}
+
+static void hmacmd5_genresult(void *handle, unsigned char *hmac)
+{
+ struct MD5Context *keys = (struct MD5Context *)handle;
+ struct MD5Context s;
+ unsigned char intermediate[16];
+
+ s = keys[2]; /* structure copy */
+ MD5Final(intermediate, &s);
+ s = keys[1]; /* structure copy */
+ MD5Update(&s, intermediate, 16);
+ MD5Final(hmac, &s);
+}
+
+static int hmacmd5_verresult(void *handle, unsigned char const *hmac)
+{
+ unsigned char correct[16];
+ hmacmd5_genresult(handle, correct);
+ return !memcmp(correct, hmac, 16);
+}
+
+static void hmacmd5_do_hmac_internal(void *handle,
+ unsigned char const *blk, int len,
+ unsigned char const *blk2, int len2,
+ unsigned char *hmac)
+{
+ hmacmd5_start(handle);
+ hmacmd5_bytes(handle, blk, len);
+ if (blk2) hmacmd5_bytes(handle, blk2, len2);
+ hmacmd5_genresult(handle, hmac);
+}
+
+void hmacmd5_do_hmac(void *handle, unsigned char const *blk, int len,
+ unsigned char *hmac)
+{
+ hmacmd5_do_hmac_internal(handle, blk, len, NULL, 0, hmac);
+}
+
+static void hmacmd5_do_hmac_ssh(void *handle, unsigned char const *blk, int len,
+ unsigned long seq, unsigned char *hmac)
+{
+ unsigned char seqbuf[16];
+
+ PUT_32BIT_MSB_FIRST(seqbuf, seq);
+ hmacmd5_do_hmac_internal(handle, seqbuf, 4, blk, len, hmac);
+}
+
+static void hmacmd5_generate(void *handle, unsigned char *blk, int len,
+ unsigned long seq)
+{
+ hmacmd5_do_hmac_ssh(handle, blk, len, seq, blk + len);
+}
+
+static int hmacmd5_verify(void *handle, unsigned char *blk, int len,
+ unsigned long seq)
+{
+ unsigned char correct[16];
+ hmacmd5_do_hmac_ssh(handle, blk, len, seq, correct);
+ return !memcmp(correct, blk + len, 16);
+}
+
+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"
+};
diff --git a/tools/plink/sshpubk.c b/tools/plink/sshpubk.c
index c1386108a..ac9e0fa7e 100644
--- a/tools/plink/sshpubk.c
+++ b/tools/plink/sshpubk.c
@@ -1,1217 +1,1227 @@
-/*
- * Generic SSH public-key handling operations. In particular,
- * reading of SSH public-key files, and also the generic `sign'
- * operation for SSH-2 (which checks the type of the key and
- * dispatches to the appropriate key-type specific function).
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "misc.h"
-
-#define rsa_signature "SSH PRIVATE KEY FILE FORMAT 1.1\n"
-
-#define BASE64_TOINT(x) ( (x)-'A'<26 ? (x)-'A'+0 :\
- (x)-'a'<26 ? (x)-'a'+26 :\
- (x)-'0'<10 ? (x)-'0'+52 :\
- (x)=='+' ? 62 : \
- (x)=='/' ? 63 : 0 )
-
-static int loadrsakey_main(FILE * fp, struct RSAKey *key, int pub_only,
- char **commentptr, char *passphrase,
- const char **error)
-{
- unsigned char buf[16384];
- unsigned char keybuf[16];
- int len;
- int i, j, ciphertype;
- int ret = 0;
- struct MD5Context md5c;
- char *comment;
-
- *error = NULL;
-
- /* Slurp the whole file (minus the header) into a buffer. */
- len = fread(buf, 1, sizeof(buf), fp);
- fclose(fp);
- if (len < 0 || len == sizeof(buf)) {
- *error = "error reading file";
- goto end; /* file too big or not read */
- }
-
- i = 0;
- *error = "file format error";
-
- /*
- * A zero byte. (The signature includes a terminating NUL.)
- */
- if (len - i < 1 || buf[i] != 0)
- goto end;
- i++;
-
- /* One byte giving encryption type, and one reserved uint32. */
- if (len - i < 1)
- goto end;
- ciphertype = buf[i];
- if (ciphertype != 0 && ciphertype != SSH_CIPHER_3DES)
- goto end;
- i++;
- if (len - i < 4)
- goto end; /* reserved field not present */
- if (buf[i] != 0 || buf[i + 1] != 0 || buf[i + 2] != 0
- || buf[i + 3] != 0) goto end; /* reserved field nonzero, panic! */
- i += 4;
-
- /* Now the serious stuff. An ordinary SSH-1 public key. */
- i += makekey(buf + i, len, key, NULL, 1);
- if (i < 0)
- goto end; /* overran */
-
- /* Next, the comment field. */
- j = GET_32BIT(buf + i);
- i += 4;
- if (len - i < j)
- goto end;
- comment = snewn(j + 1, char);
- if (comment) {
- memcpy(comment, buf + i, j);
- comment[j] = '\0';
- }
- i += j;
- if (commentptr)
- *commentptr = dupstr(comment);
- if (key)
- key->comment = comment;
- else
- sfree(comment);
-
- if (pub_only) {
- ret = 1;
- goto end;
- }
-
- if (!key) {
- ret = ciphertype != 0;
- *error = NULL;
- goto end;
- }
-
- /*
- * Decrypt remainder of buffer.
- */
- if (ciphertype) {
- MD5Init(&md5c);
- MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase));
- MD5Final(keybuf, &md5c);
- des3_decrypt_pubkey(keybuf, buf + i, (len - i + 7) & ~7);
- memset(keybuf, 0, sizeof(keybuf)); /* burn the evidence */
- }
-
- /*
- * We are now in the secret part of the key. The first four
- * bytes should be of the form a, b, a, b.
- */
- if (len - i < 4)
- goto end;
- if (buf[i] != buf[i + 2] || buf[i + 1] != buf[i + 3]) {
- *error = "wrong passphrase";
- ret = -1;
- goto end;
- }
- i += 4;
-
- /*
- * After that, we have one further bignum which is our
- * decryption exponent, and then the three auxiliary values
- * (iqmp, q, p).
- */
- j = makeprivate(buf + i, len - i, key);
- if (j < 0) goto end;
- i += j;
- j = ssh1_read_bignum(buf + i, len - i, &key->iqmp);
- if (j < 0) goto end;
- i += j;
- j = ssh1_read_bignum(buf + i, len - i, &key->q);
- if (j < 0) goto end;
- i += j;
- j = ssh1_read_bignum(buf + i, len - i, &key->p);
- if (j < 0) goto end;
- i += j;
-
- if (!rsa_verify(key)) {
- *error = "rsa_verify failed";
- freersakey(key);
- ret = 0;
- } else
- ret = 1;
-
- end:
- memset(buf, 0, sizeof(buf)); /* burn the evidence */
- return ret;
-}
-
-int loadrsakey(const Filename *filename, struct RSAKey *key, char *passphrase,
- const char **errorstr)
-{
- FILE *fp;
- char buf[64];
- int ret = 0;
- const char *error = NULL;
-
- fp = f_open(filename, "rb", FALSE);
- if (!fp) {
- error = "can't open file";
- goto end;
- }
-
- /*
- * Read the first line of the file and see if it's a v1 private
- * key file.
- */
- if (fgets(buf, sizeof(buf), fp) && !strcmp(buf, rsa_signature)) {
- /*
- * This routine will take care of calling fclose() for us.
- */
- ret = loadrsakey_main(fp, key, FALSE, NULL, passphrase, &error);
- fp = NULL;
- goto end;
- }
-
- /*
- * Otherwise, we have nothing. Return empty-handed.
- */
- error = "not an SSH-1 RSA file";
-
- end:
- if (fp)
- fclose(fp);
- if ((ret != 1) && errorstr)
- *errorstr = error;
- return ret;
-}
-
-/*
- * See whether an RSA key is encrypted. Return its comment field as
- * well.
- */
-int rsakey_encrypted(const Filename *filename, char **comment)
-{
- FILE *fp;
- char buf[64];
-
- fp = f_open(filename, "rb", FALSE);
- if (!fp)
- return 0; /* doesn't even exist */
-
- /*
- * Read the first line of the file and see if it's a v1 private
- * key file.
- */
- if (fgets(buf, sizeof(buf), fp) && !strcmp(buf, rsa_signature)) {
- const char *dummy;
- /*
- * This routine will take care of calling fclose() for us.
- */
- return loadrsakey_main(fp, NULL, FALSE, comment, NULL, &dummy);
- }
- fclose(fp);
- return 0; /* wasn't the right kind of file */
-}
-
-/*
- * Return a malloc'ed chunk of memory containing the public blob of
- * an RSA key, as given in the agent protocol (modulus bits,
- * exponent, modulus).
- */
-int rsakey_pubblob(const Filename *filename, void **blob, int *bloblen,
- char **commentptr, const char **errorstr)
-{
- FILE *fp;
- char buf[64];
- struct RSAKey key;
- int ret;
- const char *error = NULL;
-
- /* Default return if we fail. */
- *blob = NULL;
- *bloblen = 0;
- ret = 0;
-
- fp = f_open(filename, "rb", FALSE);
- if (!fp) {
- error = "can't open file";
- goto end;
- }
-
- /*
- * Read the first line of the file and see if it's a v1 private
- * key file.
- */
- if (fgets(buf, sizeof(buf), fp) && !strcmp(buf, rsa_signature)) {
- memset(&key, 0, sizeof(key));
- if (loadrsakey_main(fp, &key, TRUE, commentptr, NULL, &error)) {
- *blob = rsa_public_blob(&key, bloblen);
- freersakey(&key);
- ret = 1;
- fp = NULL;
- }
- } else {
- error = "not an SSH-1 RSA file";
- }
-
- end:
- if (fp)
- fclose(fp);
- if ((ret != 1) && errorstr)
- *errorstr = error;
- return ret;
-}
-
-/*
- * Save an RSA key file. Return nonzero on success.
- */
-int saversakey(const Filename *filename, struct RSAKey *key, char *passphrase)
-{
- unsigned char buf[16384];
- unsigned char keybuf[16];
- struct MD5Context md5c;
- unsigned char *p, *estart;
- FILE *fp;
-
- /*
- * Write the initial signature.
- */
- p = buf;
- memcpy(p, rsa_signature, sizeof(rsa_signature));
- p += sizeof(rsa_signature);
-
- /*
- * One byte giving encryption type, and one reserved (zero)
- * uint32.
- */
- *p++ = (passphrase ? SSH_CIPHER_3DES : 0);
- PUT_32BIT(p, 0);
- p += 4;
-
- /*
- * An ordinary SSH-1 public key consists of: a uint32
- * containing the bit count, then two bignums containing the
- * modulus and exponent respectively.
- */
- PUT_32BIT(p, bignum_bitcount(key->modulus));
- p += 4;
- p += ssh1_write_bignum(p, key->modulus);
- p += ssh1_write_bignum(p, key->exponent);
-
- /*
- * A string containing the comment field.
- */
- if (key->comment) {
- PUT_32BIT(p, strlen(key->comment));
- p += 4;
- memcpy(p, key->comment, strlen(key->comment));
- p += strlen(key->comment);
- } else {
- PUT_32BIT(p, 0);
- p += 4;
- }
-
- /*
- * The encrypted portion starts here.
- */
- estart = p;
-
- /*
- * Two bytes, then the same two bytes repeated.
- */
- *p++ = random_byte();
- *p++ = random_byte();
- p[0] = p[-2];
- p[1] = p[-1];
- p += 2;
-
- /*
- * Four more bignums: the decryption exponent, then iqmp, then
- * q, then p.
- */
- p += ssh1_write_bignum(p, key->private_exponent);
- p += ssh1_write_bignum(p, key->iqmp);
- p += ssh1_write_bignum(p, key->q);
- p += ssh1_write_bignum(p, key->p);
-
- /*
- * Now write zeros until the encrypted portion is a multiple of
- * 8 bytes.
- */
- while ((p - estart) % 8)
- *p++ = '\0';
-
- /*
- * Now encrypt the encrypted portion.
- */
- if (passphrase) {
- MD5Init(&md5c);
- MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase));
- MD5Final(keybuf, &md5c);
- des3_encrypt_pubkey(keybuf, estart, p - estart);
- memset(keybuf, 0, sizeof(keybuf)); /* burn the evidence */
- }
-
- /*
- * Done. Write the result to the file.
- */
- fp = f_open(filename, "wb", TRUE);
- if (fp) {
- int ret = (fwrite(buf, 1, p - buf, fp) == (size_t) (p - buf));
- if (fclose(fp))
- ret = 0;
- return ret;
- } else
- return 0;
-}
-
-/* ----------------------------------------------------------------------
- * SSH-2 private key load/store functions.
- */
-
-/*
- * PuTTY's own format for SSH-2 keys is as follows:
- *
- * The file is text. Lines are terminated by CRLF, although CR-only
- * and LF-only are tolerated on input.
- *
- * The first line says "PuTTY-User-Key-File-2: " plus the name of the
- * algorithm ("ssh-dss", "ssh-rsa" etc).
- *
- * The next line says "Encryption: " plus an encryption type.
- * Currently the only supported encryption types are "aes256-cbc"
- * and "none".
- *
- * The next line says "Comment: " plus the comment string.
- *
- * Next there is a line saying "Public-Lines: " plus a number N.
- * The following N lines contain a base64 encoding of the public
- * part of the key. This is encoded as the standard SSH-2 public key
- * blob (with no initial length): so for RSA, for example, it will
- * read
- *
- * string "ssh-rsa"
- * mpint exponent
- * mpint modulus
- *
- * Next, there is a line saying "Private-Lines: " plus a number N,
- * and then N lines containing the (potentially encrypted) private
- * part of the key. For the key type "ssh-rsa", this will be
- * composed of
- *
- * mpint private_exponent
- * mpint p (the larger of the two primes)
- * mpint q (the smaller prime)
- * mpint iqmp (the inverse of q modulo p)
- * data padding (to reach a multiple of the cipher block size)
- *
- * And for "ssh-dss", it will be composed of
- *
- * mpint x (the private key parameter)
- * [ string hash 20-byte hash of mpints p || q || g only in old format ]
- *
- * Finally, there is a line saying "Private-MAC: " plus a hex
- * representation of a HMAC-SHA-1 of:
- *
- * string name of algorithm ("ssh-dss", "ssh-rsa")
- * string encryption type
- * string comment
- * string public-blob
- * string private-plaintext (the plaintext version of the
- * private part, including the final
- * padding)
- *
- * The key to the MAC is itself a SHA-1 hash of:
- *
- * data "putty-private-key-file-mac-key"
- * data passphrase
- *
- * (An empty passphrase is used for unencrypted keys.)
- *
- * If the key is encrypted, the encryption key is derived from the
- * passphrase by means of a succession of SHA-1 hashes. Each hash
- * is the hash of:
- *
- * uint32 sequence-number
- * data passphrase
- *
- * where the sequence-number increases from zero. As many of these
- * hashes are used as necessary.
- *
- * For backwards compatibility with snapshots between 0.51 and
- * 0.52, we also support the older key file format, which begins
- * with "PuTTY-User-Key-File-1" (version number differs). In this
- * format the Private-MAC: field only covers the private-plaintext
- * field and nothing else (and without the 4-byte string length on
- * the front too). Moreover, the Private-MAC: field can be replaced
- * with a Private-Hash: field which is a plain SHA-1 hash instead of
- * an HMAC (this was generated for unencrypted keys).
- */
-
-static int read_header(FILE * fp, char *header)
-{
- int len = 39;
- int c;
-
- while (len > 0) {
- c = fgetc(fp);
- if (c == '\n' || c == '\r' || c == EOF)
- return 0; /* failure */
- if (c == ':') {
- c = fgetc(fp);
- if (c != ' ')
- return 0;
- *header = '\0';
- return 1; /* success! */
- }
- if (len == 0)
- return 0; /* failure */
- *header++ = c;
- len--;
- }
- return 0; /* failure */
-}
-
-static char *read_body(FILE * fp)
-{
- char *text;
- int len;
- int size;
- int c;
-
- size = 128;
- text = snewn(size, char);
- len = 0;
- text[len] = '\0';
-
- while (1) {
- c = fgetc(fp);
- if (c == '\r' || c == '\n' || c == EOF) {
- if (c != EOF) {
- c = fgetc(fp);
- if (c != '\r' && c != '\n')
- ungetc(c, fp);
- }
- return text;
- }
- if (len + 1 >= size) {
- size += 128;
- text = sresize(text, size, char);
- }
- text[len++] = c;
- text[len] = '\0';
- }
-}
-
-int base64_decode_atom(char *atom, unsigned char *out)
-{
- int vals[4];
- int i, v, len;
- unsigned word;
- char c;
-
- for (i = 0; i < 4; i++) {
- c = atom[i];
- if (c >= 'A' && c <= 'Z')
- v = c - 'A';
- else if (c >= 'a' && c <= 'z')
- v = c - 'a' + 26;
- else if (c >= '0' && c <= '9')
- v = c - '0' + 52;
- else if (c == '+')
- v = 62;
- else if (c == '/')
- v = 63;
- else if (c == '=')
- v = -1;
- else
- return 0; /* invalid atom */
- vals[i] = v;
- }
-
- if (vals[0] == -1 || vals[1] == -1)
- return 0;
- if (vals[2] == -1 && vals[3] != -1)
- return 0;
-
- if (vals[3] != -1)
- len = 3;
- else if (vals[2] != -1)
- len = 2;
- else
- len = 1;
-
- word = ((vals[0] << 18) |
- (vals[1] << 12) | ((vals[2] & 0x3F) << 6) | (vals[3] & 0x3F));
- out[0] = (word >> 16) & 0xFF;
- if (len > 1)
- out[1] = (word >> 8) & 0xFF;
- if (len > 2)
- out[2] = word & 0xFF;
- return len;
-}
-
-static unsigned char *read_blob(FILE * fp, int nlines, int *bloblen)
-{
- unsigned char *blob;
- char *line;
- int linelen, len;
- int i, j, k;
-
- /* We expect at most 64 base64 characters, ie 48 real bytes, per line. */
- blob = snewn(48 * nlines, unsigned char);
- len = 0;
- for (i = 0; i < nlines; i++) {
- line = read_body(fp);
- if (!line) {
- sfree(blob);
- return NULL;
- }
- linelen = strlen(line);
- if (linelen % 4 != 0 || linelen > 64) {
- sfree(blob);
- sfree(line);
- return NULL;
- }
- for (j = 0; j < linelen; j += 4) {
- k = base64_decode_atom(line + j, blob + len);
- if (!k) {
- sfree(line);
- sfree(blob);
- return NULL;
- }
- len += k;
- }
- sfree(line);
- }
- *bloblen = len;
- return blob;
-}
-
-/*
- * Magic error return value for when the passphrase is wrong.
- */
-struct ssh2_userkey ssh2_wrong_passphrase = {
- NULL, NULL, NULL
-};
-
-const struct ssh_signkey *find_pubkey_alg(const char *name)
-{
- if (!strcmp(name, "ssh-rsa"))
- return &ssh_rsa;
- else if (!strcmp(name, "ssh-dss"))
- return &ssh_dss;
- else
- return NULL;
-}
-
-struct ssh2_userkey *ssh2_load_userkey(const Filename *filename,
- char *passphrase, const char **errorstr)
-{
- FILE *fp;
- char header[40], *b, *encryption, *comment, *mac;
- const struct ssh_signkey *alg;
- struct ssh2_userkey *ret;
- int cipher, cipherblk;
- unsigned char *public_blob, *private_blob;
- int public_blob_len, private_blob_len;
- int i, is_mac, old_fmt;
- int passlen = passphrase ? strlen(passphrase) : 0;
- const char *error = NULL;
-
- ret = NULL; /* return NULL for most errors */
- encryption = comment = mac = NULL;
- public_blob = private_blob = NULL;
-
- fp = f_open(filename, "rb", FALSE);
- if (!fp) {
- error = "can't open file";
- goto error;
- }
-
- /* Read the first header line which contains the key type. */
- if (!read_header(fp, header))
- goto error;
- if (0 == strcmp(header, "PuTTY-User-Key-File-2")) {
- old_fmt = 0;
- } else if (0 == strcmp(header, "PuTTY-User-Key-File-1")) {
- /* this is an old key file; warn and then continue */
- old_keyfile_warning();
- old_fmt = 1;
- } else {
- error = "not a PuTTY SSH-2 private key";
- goto error;
- }
- error = "file format error";
- if ((b = read_body(fp)) == NULL)
- goto error;
- /* Select key algorithm structure. */
- alg = find_pubkey_alg(b);
- if (!alg) {
- sfree(b);
- goto error;
- }
- sfree(b);
-
- /* Read the Encryption header line. */
- if (!read_header(fp, header) || 0 != strcmp(header, "Encryption"))
- goto error;
- if ((encryption = read_body(fp)) == NULL)
- goto error;
- if (!strcmp(encryption, "aes256-cbc")) {
- cipher = 1;
- cipherblk = 16;
- } else if (!strcmp(encryption, "none")) {
- cipher = 0;
- cipherblk = 1;
- } else {
- sfree(encryption);
- goto error;
- }
-
- /* Read the Comment header line. */
- if (!read_header(fp, header) || 0 != strcmp(header, "Comment"))
- goto error;
- if ((comment = read_body(fp)) == NULL)
- goto error;
-
- /* Read the Public-Lines header line and the public blob. */
- if (!read_header(fp, header) || 0 != strcmp(header, "Public-Lines"))
- goto error;
- if ((b = read_body(fp)) == NULL)
- goto error;
- i = atoi(b);
- sfree(b);
- if ((public_blob = read_blob(fp, i, &public_blob_len)) == NULL)
- goto error;
-
- /* Read the Private-Lines header line and the Private blob. */
- if (!read_header(fp, header) || 0 != strcmp(header, "Private-Lines"))
- goto error;
- if ((b = read_body(fp)) == NULL)
- goto error;
- i = atoi(b);
- sfree(b);
- if ((private_blob = read_blob(fp, i, &private_blob_len)) == NULL)
- goto error;
-
- /* Read the Private-MAC or Private-Hash header line. */
- if (!read_header(fp, header))
- goto error;
- if (0 == strcmp(header, "Private-MAC")) {
- if ((mac = read_body(fp)) == NULL)
- goto error;
- is_mac = 1;
- } else if (0 == strcmp(header, "Private-Hash") && old_fmt) {
- if ((mac = read_body(fp)) == NULL)
- goto error;
- is_mac = 0;
- } else
- goto error;
-
- fclose(fp);
- fp = NULL;
-
- /*
- * Decrypt the private blob.
- */
- if (cipher) {
- unsigned char key[40];
- SHA_State s;
-
- if (!passphrase)
- goto error;
- if (private_blob_len % cipherblk)
- goto error;
-
- SHA_Init(&s);
- SHA_Bytes(&s, "\0\0\0\0", 4);
- SHA_Bytes(&s, passphrase, passlen);
- SHA_Final(&s, key + 0);
- SHA_Init(&s);
- SHA_Bytes(&s, "\0\0\0\1", 4);
- SHA_Bytes(&s, passphrase, passlen);
- SHA_Final(&s, key + 20);
- aes256_decrypt_pubkey(key, private_blob, private_blob_len);
- }
-
- /*
- * Verify the MAC.
- */
- {
- char realmac[41];
- unsigned char binary[20];
- unsigned char *macdata;
- int maclen;
- int free_macdata;
-
- if (old_fmt) {
- /* MAC (or hash) only covers the private blob. */
- macdata = private_blob;
- maclen = private_blob_len;
- free_macdata = 0;
- } else {
- unsigned char *p;
- int namelen = strlen(alg->name);
- int enclen = strlen(encryption);
- int commlen = strlen(comment);
- maclen = (4 + namelen +
- 4 + enclen +
- 4 + commlen +
- 4 + public_blob_len +
- 4 + private_blob_len);
- macdata = snewn(maclen, unsigned char);
- p = macdata;
-#define DO_STR(s,len) PUT_32BIT(p,(len));memcpy(p+4,(s),(len));p+=4+(len)
- DO_STR(alg->name, namelen);
- DO_STR(encryption, enclen);
- DO_STR(comment, commlen);
- DO_STR(public_blob, public_blob_len);
- DO_STR(private_blob, private_blob_len);
-
- free_macdata = 1;
- }
-
- if (is_mac) {
- SHA_State s;
- unsigned char mackey[20];
- char header[] = "putty-private-key-file-mac-key";
-
- SHA_Init(&s);
- SHA_Bytes(&s, header, sizeof(header)-1);
- if (cipher && passphrase)
- SHA_Bytes(&s, passphrase, passlen);
- SHA_Final(&s, mackey);
-
- hmac_sha1_simple(mackey, 20, macdata, maclen, binary);
-
- memset(mackey, 0, sizeof(mackey));
- memset(&s, 0, sizeof(s));
- } else {
- SHA_Simple(macdata, maclen, binary);
- }
-
- if (free_macdata) {
- memset(macdata, 0, maclen);
- sfree(macdata);
- }
-
- for (i = 0; i < 20; i++)
- sprintf(realmac + 2 * i, "%02x", binary[i]);
-
- if (strcmp(mac, realmac)) {
- /* An incorrect MAC is an unconditional Error if the key is
- * unencrypted. Otherwise, it means Wrong Passphrase. */
- if (cipher) {
- error = "wrong passphrase";
- ret = SSH2_WRONG_PASSPHRASE;
- } else {
- error = "MAC failed";
- ret = NULL;
- }
- goto error;
- }
- }
- sfree(mac);
-
- /*
- * Create and return the key.
- */
- ret = snew(struct ssh2_userkey);
- ret->alg = alg;
- ret->comment = comment;
- ret->data = alg->createkey(public_blob, public_blob_len,
- private_blob, private_blob_len);
- if (!ret->data) {
- sfree(ret->comment);
- sfree(ret);
- ret = NULL;
- error = "createkey failed";
- goto error;
- }
- sfree(public_blob);
- sfree(private_blob);
- sfree(encryption);
- if (errorstr)
- *errorstr = NULL;
- return ret;
-
- /*
- * Error processing.
- */
- error:
- if (fp)
- fclose(fp);
- if (comment)
- sfree(comment);
- if (encryption)
- sfree(encryption);
- if (mac)
- sfree(mac);
- if (public_blob)
- sfree(public_blob);
- if (private_blob)
- sfree(private_blob);
- if (errorstr)
- *errorstr = error;
- return ret;
-}
-
-unsigned char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm,
- int *pub_blob_len, char **commentptr,
- const char **errorstr)
-{
- FILE *fp;
- char header[40], *b;
- const struct ssh_signkey *alg;
- unsigned char *public_blob;
- int public_blob_len;
- int i;
- const char *error = NULL;
- char *comment;
-
- public_blob = NULL;
-
- fp = f_open(filename, "rb", FALSE);
- if (!fp) {
- error = "can't open file";
- 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") &&
- 0 != strcmp(header, "PuTTY-User-Key-File-1"))) {
- error = "not a PuTTY SSH-2 private key";
- goto error;
- }
- error = "file format error";
- if ((b = read_body(fp)) == NULL)
- goto error;
- /* Select key algorithm structure. */
- alg = find_pubkey_alg(b);
- if (!alg) {
- sfree(b);
- goto error;
- }
- sfree(b);
-
- /* Read the Encryption header line. */
- if (!read_header(fp, header) || 0 != strcmp(header, "Encryption"))
- goto error;
- if ((b = read_body(fp)) == NULL)
- goto error;
- sfree(b); /* we don't care */
-
- /* Read the Comment header line. */
- if (!read_header(fp, header) || 0 != strcmp(header, "Comment"))
- goto error;
- if ((comment = read_body(fp)) == NULL)
- goto error;
-
- if (commentptr)
- *commentptr = comment;
- else
- sfree(comment);
-
- /* Read the Public-Lines header line and the public blob. */
- if (!read_header(fp, header) || 0 != strcmp(header, "Public-Lines"))
- goto error;
- if ((b = read_body(fp)) == NULL)
- goto error;
- i = atoi(b);
- sfree(b);
- if ((public_blob = read_blob(fp, i, &public_blob_len)) == NULL)
- goto error;
-
- fclose(fp);
- if (pub_blob_len)
- *pub_blob_len = public_blob_len;
- if (algorithm)
- *algorithm = alg->name;
- return public_blob;
-
- /*
- * Error processing.
- */
- error:
- if (fp)
- fclose(fp);
- if (public_blob)
- sfree(public_blob);
- if (errorstr)
- *errorstr = error;
- return NULL;
-}
-
-int ssh2_userkey_encrypted(const Filename *filename, char **commentptr)
-{
- FILE *fp;
- char header[40], *b, *comment;
- int ret;
-
- if (commentptr)
- *commentptr = NULL;
-
- fp = f_open(filename, "rb", FALSE);
- if (!fp)
- return 0;
- if (!read_header(fp, header)
- || (0 != strcmp(header, "PuTTY-User-Key-File-2") &&
- 0 != strcmp(header, "PuTTY-User-Key-File-1"))) {
- fclose(fp);
- return 0;
- }
- if ((b = read_body(fp)) == NULL) {
- fclose(fp);
- return 0;
- }
- sfree(b); /* we don't care about key type here */
- /* Read the Encryption header line. */
- if (!read_header(fp, header) || 0 != strcmp(header, "Encryption")) {
- fclose(fp);
- return 0;
- }
- if ((b = read_body(fp)) == NULL) {
- fclose(fp);
- return 0;
- }
-
- /* Read the Comment header line. */
- if (!read_header(fp, header) || 0 != strcmp(header, "Comment")) {
- fclose(fp);
- sfree(b);
- return 1;
- }
- if ((comment = read_body(fp)) == NULL) {
- fclose(fp);
- sfree(b);
- return 1;
- }
-
- if (commentptr)
- *commentptr = comment;
-
- fclose(fp);
- if (!strcmp(b, "aes256-cbc"))
- ret = 1;
- else
- ret = 0;
- sfree(b);
- return ret;
-}
-
-int base64_lines(int datalen)
-{
- /* When encoding, we use 64 chars/line, which equals 48 real chars. */
- return (datalen + 47) / 48;
-}
-
-void base64_encode(FILE * fp, unsigned char *data, int datalen, int cpl)
-{
- int linelen = 0;
- char out[4];
- int n, i;
-
- while (datalen > 0) {
- n = (datalen < 3 ? datalen : 3);
- base64_encode_atom(data, n, out);
- data += n;
- datalen -= n;
- for (i = 0; i < 4; i++) {
- if (linelen >= cpl) {
- linelen = 0;
- fputc('\n', fp);
- }
- fputc(out[i], fp);
- linelen++;
- }
- }
- fputc('\n', fp);
-}
-
-int ssh2_save_userkey(const Filename *filename, struct ssh2_userkey *key,
- char *passphrase)
-{
- FILE *fp;
- unsigned char *pub_blob, *priv_blob, *priv_blob_encrypted;
- int pub_blob_len, priv_blob_len, priv_encrypted_len;
- int passlen;
- int cipherblk;
- int i;
- char *cipherstr;
- unsigned char priv_mac[20];
-
- /*
- * Fetch the key component blobs.
- */
- pub_blob = key->alg->public_blob(key->data, &pub_blob_len);
- priv_blob = key->alg->private_blob(key->data, &priv_blob_len);
- if (!pub_blob || !priv_blob) {
- sfree(pub_blob);
- sfree(priv_blob);
- return 0;
- }
-
- /*
- * Determine encryption details, and encrypt the private blob.
- */
- if (passphrase) {
- cipherstr = "aes256-cbc";
- cipherblk = 16;
- } else {
- cipherstr = "none";
- cipherblk = 1;
- }
- priv_encrypted_len = priv_blob_len + cipherblk - 1;
- priv_encrypted_len -= priv_encrypted_len % cipherblk;
- priv_blob_encrypted = snewn(priv_encrypted_len, unsigned char);
- memset(priv_blob_encrypted, 0, priv_encrypted_len);
- memcpy(priv_blob_encrypted, priv_blob, priv_blob_len);
- /* Create padding based on the SHA hash of the unpadded blob. This prevents
- * too easy a known-plaintext attack on the last block. */
- SHA_Simple(priv_blob, priv_blob_len, priv_mac);
- assert(priv_encrypted_len - priv_blob_len < 20);
- memcpy(priv_blob_encrypted + priv_blob_len, priv_mac,
- priv_encrypted_len - priv_blob_len);
-
- /* Now create the MAC. */
- {
- unsigned char *macdata;
- int maclen;
- unsigned char *p;
- int namelen = strlen(key->alg->name);
- int enclen = strlen(cipherstr);
- int commlen = strlen(key->comment);
- SHA_State s;
- unsigned char mackey[20];
- char header[] = "putty-private-key-file-mac-key";
-
- maclen = (4 + namelen +
- 4 + enclen +
- 4 + commlen +
- 4 + pub_blob_len +
- 4 + priv_encrypted_len);
- macdata = snewn(maclen, unsigned char);
- p = macdata;
-#define DO_STR(s,len) PUT_32BIT(p,(len));memcpy(p+4,(s),(len));p+=4+(len)
- DO_STR(key->alg->name, namelen);
- DO_STR(cipherstr, enclen);
- DO_STR(key->comment, commlen);
- DO_STR(pub_blob, pub_blob_len);
- DO_STR(priv_blob_encrypted, priv_encrypted_len);
-
- SHA_Init(&s);
- SHA_Bytes(&s, header, sizeof(header)-1);
- if (passphrase)
- SHA_Bytes(&s, passphrase, strlen(passphrase));
- SHA_Final(&s, mackey);
- hmac_sha1_simple(mackey, 20, macdata, maclen, priv_mac);
- memset(macdata, 0, maclen);
- sfree(macdata);
- memset(mackey, 0, sizeof(mackey));
- memset(&s, 0, sizeof(s));
- }
-
- if (passphrase) {
- unsigned char key[40];
- SHA_State s;
-
- passlen = strlen(passphrase);
-
- SHA_Init(&s);
- SHA_Bytes(&s, "\0\0\0\0", 4);
- SHA_Bytes(&s, passphrase, passlen);
- SHA_Final(&s, key + 0);
- SHA_Init(&s);
- SHA_Bytes(&s, "\0\0\0\1", 4);
- SHA_Bytes(&s, passphrase, passlen);
- SHA_Final(&s, key + 20);
- aes256_encrypt_pubkey(key, priv_blob_encrypted,
- priv_encrypted_len);
-
- memset(key, 0, sizeof(key));
- memset(&s, 0, sizeof(s));
- }
-
- fp = f_open(filename, "w", TRUE);
- if (!fp)
- return 0;
- fprintf(fp, "PuTTY-User-Key-File-2: %s\n", key->alg->name);
- fprintf(fp, "Encryption: %s\n", cipherstr);
- fprintf(fp, "Comment: %s\n", key->comment);
- fprintf(fp, "Public-Lines: %d\n", base64_lines(pub_blob_len));
- base64_encode(fp, pub_blob, pub_blob_len, 64);
- fprintf(fp, "Private-Lines: %d\n", base64_lines(priv_encrypted_len));
- base64_encode(fp, priv_blob_encrypted, priv_encrypted_len, 64);
- fprintf(fp, "Private-MAC: ");
- for (i = 0; i < 20; i++)
- fprintf(fp, "%02x", priv_mac[i]);
- fprintf(fp, "\n");
- fclose(fp);
-
- sfree(pub_blob);
- memset(priv_blob, 0, priv_blob_len);
- sfree(priv_blob);
- sfree(priv_blob_encrypted);
- return 1;
-}
-
-/* ----------------------------------------------------------------------
- * A function to determine the type of a private key file. Returns
- * 0 on failure, 1 or 2 on success.
- */
-int key_type(const Filename *filename)
-{
- FILE *fp;
- char buf[32];
- const char putty2_sig[] = "PuTTY-User-Key-File-";
- const char sshcom_sig[] = "---- BEGIN SSH2 ENCRYPTED PRIVAT";
- const char openssh_sig[] = "-----BEGIN ";
- int i;
-
- 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;
- if (!memcmp(buf, rsa_signature, sizeof(rsa_signature)-1))
- return SSH_KEYTYPE_SSH1;
- if (!memcmp(buf, putty2_sig, sizeof(putty2_sig)-1))
- return SSH_KEYTYPE_SSH2;
- if (!memcmp(buf, openssh_sig, sizeof(openssh_sig)-1))
- return SSH_KEYTYPE_OPENSSH;
- if (!memcmp(buf, sshcom_sig, sizeof(sshcom_sig)-1))
- return SSH_KEYTYPE_SSHCOM;
- return SSH_KEYTYPE_UNKNOWN; /* unrecognised or EOF */
-}
-
-/*
- * Convert the type word to a string, for `wrong type' error
- * messages.
- */
-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_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_SSHCOM: return "ssh.com SSH-2 private key"; break;
- default: return "INTERNAL ERROR"; break;
- }
-}
+/*
+ * Generic SSH public-key handling operations. In particular,
+ * reading of SSH public-key files, and also the generic `sign'
+ * operation for SSH-2 (which checks the type of the key and
+ * dispatches to the appropriate key-type specific function).
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "misc.h"
+
+#define rsa_signature "SSH PRIVATE KEY FILE FORMAT 1.1\n"
+
+#define BASE64_TOINT(x) ( (x)-'A'<26 ? (x)-'A'+0 :\
+ (x)-'a'<26 ? (x)-'a'+26 :\
+ (x)-'0'<10 ? (x)-'0'+52 :\
+ (x)=='+' ? 62 : \
+ (x)=='/' ? 63 : 0 )
+
+static int loadrsakey_main(FILE * fp, struct RSAKey *key, int pub_only,
+ char **commentptr, char *passphrase,
+ const char **error)
+{
+ unsigned char buf[16384];
+ unsigned char keybuf[16];
+ int len;
+ int i, j, ciphertype;
+ int ret = 0;
+ struct MD5Context md5c;
+ char *comment;
+
+ *error = NULL;
+
+ /* Slurp the whole file (minus the header) into a buffer. */
+ len = fread(buf, 1, sizeof(buf), fp);
+ fclose(fp);
+ if (len < 0 || len == sizeof(buf)) {
+ *error = "error reading file";
+ goto end; /* file too big or not read */
+ }
+
+ i = 0;
+ *error = "file format error";
+
+ /*
+ * A zero byte. (The signature includes a terminating NUL.)
+ */
+ if (len - i < 1 || buf[i] != 0)
+ goto end;
+ i++;
+
+ /* One byte giving encryption type, and one reserved uint32. */
+ if (len - i < 1)
+ goto end;
+ ciphertype = buf[i];
+ if (ciphertype != 0 && ciphertype != SSH_CIPHER_3DES)
+ goto end;
+ i++;
+ if (len - i < 4)
+ goto end; /* reserved field not present */
+ if (buf[i] != 0 || buf[i + 1] != 0 || buf[i + 2] != 0
+ || buf[i + 3] != 0) goto end; /* reserved field nonzero, panic! */
+ i += 4;
+
+ /* Now the serious stuff. An ordinary SSH-1 public key. */
+ j = makekey(buf + i, len, key, NULL, 1);
+ if (j < 0)
+ goto end; /* overran */
+ i += j;
+
+ /* Next, the comment field. */
+ j = toint(GET_32BIT(buf + i));
+ i += 4;
+ if (j < 0 || len - i < j)
+ goto end;
+ comment = snewn(j + 1, char);
+ if (comment) {
+ memcpy(comment, buf + i, j);
+ comment[j] = '\0';
+ }
+ i += j;
+ if (commentptr)
+ *commentptr = dupstr(comment);
+ if (key)
+ key->comment = comment;
+ else
+ sfree(comment);
+
+ if (pub_only) {
+ ret = 1;
+ goto end;
+ }
+
+ if (!key) {
+ ret = ciphertype != 0;
+ *error = NULL;
+ goto end;
+ }
+
+ /*
+ * Decrypt remainder of buffer.
+ */
+ if (ciphertype) {
+ MD5Init(&md5c);
+ MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase));
+ MD5Final(keybuf, &md5c);
+ des3_decrypt_pubkey(keybuf, buf + i, (len - i + 7) & ~7);
+ smemclr(keybuf, sizeof(keybuf)); /* burn the evidence */
+ }
+
+ /*
+ * We are now in the secret part of the key. The first four
+ * bytes should be of the form a, b, a, b.
+ */
+ if (len - i < 4)
+ goto end;
+ if (buf[i] != buf[i + 2] || buf[i + 1] != buf[i + 3]) {
+ *error = "wrong passphrase";
+ ret = -1;
+ goto end;
+ }
+ i += 4;
+
+ /*
+ * After that, we have one further bignum which is our
+ * decryption exponent, and then the three auxiliary values
+ * (iqmp, q, p).
+ */
+ j = makeprivate(buf + i, len - i, key);
+ if (j < 0) goto end;
+ i += j;
+ j = ssh1_read_bignum(buf + i, len - i, &key->iqmp);
+ if (j < 0) goto end;
+ i += j;
+ j = ssh1_read_bignum(buf + i, len - i, &key->q);
+ if (j < 0) goto end;
+ i += j;
+ j = ssh1_read_bignum(buf + i, len - i, &key->p);
+ if (j < 0) goto end;
+ i += j;
+
+ if (!rsa_verify(key)) {
+ *error = "rsa_verify failed";
+ freersakey(key);
+ ret = 0;
+ } else
+ ret = 1;
+
+ end:
+ smemclr(buf, sizeof(buf)); /* burn the evidence */
+ return ret;
+}
+
+int loadrsakey(const Filename *filename, struct RSAKey *key, char *passphrase,
+ const char **errorstr)
+{
+ FILE *fp;
+ char buf[64];
+ int ret = 0;
+ const char *error = NULL;
+
+ fp = f_open(filename, "rb", FALSE);
+ if (!fp) {
+ error = "can't open file";
+ goto end;
+ }
+
+ /*
+ * Read the first line of the file and see if it's a v1 private
+ * key file.
+ */
+ if (fgets(buf, sizeof(buf), fp) && !strcmp(buf, rsa_signature)) {
+ /*
+ * This routine will take care of calling fclose() for us.
+ */
+ ret = loadrsakey_main(fp, key, FALSE, NULL, passphrase, &error);
+ fp = NULL;
+ goto end;
+ }
+
+ /*
+ * Otherwise, we have nothing. Return empty-handed.
+ */
+ error = "not an SSH-1 RSA file";
+
+ end:
+ if (fp)
+ fclose(fp);
+ if ((ret != 1) && errorstr)
+ *errorstr = error;
+ return ret;
+}
+
+/*
+ * See whether an RSA key is encrypted. Return its comment field as
+ * well.
+ */
+int rsakey_encrypted(const Filename *filename, char **comment)
+{
+ FILE *fp;
+ char buf[64];
+
+ fp = f_open(filename, "rb", FALSE);
+ if (!fp)
+ return 0; /* doesn't even exist */
+
+ /*
+ * Read the first line of the file and see if it's a v1 private
+ * key file.
+ */
+ if (fgets(buf, sizeof(buf), fp) && !strcmp(buf, rsa_signature)) {
+ const char *dummy;
+ /*
+ * This routine will take care of calling fclose() for us.
+ */
+ return loadrsakey_main(fp, NULL, FALSE, comment, NULL, &dummy);
+ }
+ fclose(fp);
+ return 0; /* wasn't the right kind of file */
+}
+
+/*
+ * Return a malloc'ed chunk of memory containing the public blob of
+ * an RSA key, as given in the agent protocol (modulus bits,
+ * exponent, modulus).
+ */
+int rsakey_pubblob(const Filename *filename, void **blob, int *bloblen,
+ char **commentptr, const char **errorstr)
+{
+ FILE *fp;
+ char buf[64];
+ struct RSAKey key;
+ int ret;
+ const char *error = NULL;
+
+ /* Default return if we fail. */
+ *blob = NULL;
+ *bloblen = 0;
+ ret = 0;
+
+ fp = f_open(filename, "rb", FALSE);
+ if (!fp) {
+ error = "can't open file";
+ goto end;
+ }
+
+ /*
+ * Read the first line of the file and see if it's a v1 private
+ * key file.
+ */
+ if (fgets(buf, sizeof(buf), fp) && !strcmp(buf, rsa_signature)) {
+ memset(&key, 0, sizeof(key));
+ if (loadrsakey_main(fp, &key, TRUE, commentptr, NULL, &error)) {
+ *blob = rsa_public_blob(&key, bloblen);
+ freersakey(&key);
+ ret = 1;
+ }
+ fp = NULL; /* loadrsakey_main unconditionally closes fp */
+ } else {
+ error = "not an SSH-1 RSA file";
+ }
+
+ end:
+ if (fp)
+ fclose(fp);
+ if ((ret != 1) && errorstr)
+ *errorstr = error;
+ return ret;
+}
+
+/*
+ * Save an RSA key file. Return nonzero on success.
+ */
+int saversakey(const Filename *filename, struct RSAKey *key, char *passphrase)
+{
+ unsigned char buf[16384];
+ unsigned char keybuf[16];
+ struct MD5Context md5c;
+ unsigned char *p, *estart;
+ FILE *fp;
+
+ /*
+ * Write the initial signature.
+ */
+ p = buf;
+ memcpy(p, rsa_signature, sizeof(rsa_signature));
+ p += sizeof(rsa_signature);
+
+ /*
+ * One byte giving encryption type, and one reserved (zero)
+ * uint32.
+ */
+ *p++ = (passphrase ? SSH_CIPHER_3DES : 0);
+ PUT_32BIT(p, 0);
+ p += 4;
+
+ /*
+ * An ordinary SSH-1 public key consists of: a uint32
+ * containing the bit count, then two bignums containing the
+ * modulus and exponent respectively.
+ */
+ PUT_32BIT(p, bignum_bitcount(key->modulus));
+ p += 4;
+ p += ssh1_write_bignum(p, key->modulus);
+ p += ssh1_write_bignum(p, key->exponent);
+
+ /*
+ * A string containing the comment field.
+ */
+ if (key->comment) {
+ PUT_32BIT(p, strlen(key->comment));
+ p += 4;
+ memcpy(p, key->comment, strlen(key->comment));
+ p += strlen(key->comment);
+ } else {
+ PUT_32BIT(p, 0);
+ p += 4;
+ }
+
+ /*
+ * The encrypted portion starts here.
+ */
+ estart = p;
+
+ /*
+ * Two bytes, then the same two bytes repeated.
+ */
+ *p++ = random_byte();
+ *p++ = random_byte();
+ p[0] = p[-2];
+ p[1] = p[-1];
+ p += 2;
+
+ /*
+ * Four more bignums: the decryption exponent, then iqmp, then
+ * q, then p.
+ */
+ p += ssh1_write_bignum(p, key->private_exponent);
+ p += ssh1_write_bignum(p, key->iqmp);
+ p += ssh1_write_bignum(p, key->q);
+ p += ssh1_write_bignum(p, key->p);
+
+ /*
+ * Now write zeros until the encrypted portion is a multiple of
+ * 8 bytes.
+ */
+ while ((p - estart) % 8)
+ *p++ = '\0';
+
+ /*
+ * Now encrypt the encrypted portion.
+ */
+ if (passphrase) {
+ MD5Init(&md5c);
+ MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase));
+ MD5Final(keybuf, &md5c);
+ des3_encrypt_pubkey(keybuf, estart, p - estart);
+ smemclr(keybuf, sizeof(keybuf)); /* burn the evidence */
+ }
+
+ /*
+ * Done. Write the result to the file.
+ */
+ fp = f_open(filename, "wb", TRUE);
+ if (fp) {
+ int ret = (fwrite(buf, 1, p - buf, fp) == (size_t) (p - buf));
+ if (fclose(fp))
+ ret = 0;
+ return ret;
+ } else
+ return 0;
+}
+
+/* ----------------------------------------------------------------------
+ * SSH-2 private key load/store functions.
+ */
+
+/*
+ * PuTTY's own format for SSH-2 keys is as follows:
+ *
+ * The file is text. Lines are terminated by CRLF, although CR-only
+ * and LF-only are tolerated on input.
+ *
+ * The first line says "PuTTY-User-Key-File-2: " plus the name of the
+ * algorithm ("ssh-dss", "ssh-rsa" etc).
+ *
+ * The next line says "Encryption: " plus an encryption type.
+ * Currently the only supported encryption types are "aes256-cbc"
+ * and "none".
+ *
+ * The next line says "Comment: " plus the comment string.
+ *
+ * Next there is a line saying "Public-Lines: " plus a number N.
+ * The following N lines contain a base64 encoding of the public
+ * part of the key. This is encoded as the standard SSH-2 public key
+ * blob (with no initial length): so for RSA, for example, it will
+ * read
+ *
+ * string "ssh-rsa"
+ * mpint exponent
+ * mpint modulus
+ *
+ * Next, there is a line saying "Private-Lines: " plus a number N,
+ * and then N lines containing the (potentially encrypted) private
+ * part of the key. For the key type "ssh-rsa", this will be
+ * composed of
+ *
+ * mpint private_exponent
+ * mpint p (the larger of the two primes)
+ * mpint q (the smaller prime)
+ * mpint iqmp (the inverse of q modulo p)
+ * data padding (to reach a multiple of the cipher block size)
+ *
+ * And for "ssh-dss", it will be composed of
+ *
+ * mpint x (the private key parameter)
+ * [ string hash 20-byte hash of mpints p || q || g only in old format ]
+ *
+ * Finally, there is a line saying "Private-MAC: " plus a hex
+ * representation of a HMAC-SHA-1 of:
+ *
+ * string name of algorithm ("ssh-dss", "ssh-rsa")
+ * string encryption type
+ * string comment
+ * string public-blob
+ * string private-plaintext (the plaintext version of the
+ * private part, including the final
+ * padding)
+ *
+ * The key to the MAC is itself a SHA-1 hash of:
+ *
+ * data "putty-private-key-file-mac-key"
+ * data passphrase
+ *
+ * (An empty passphrase is used for unencrypted keys.)
+ *
+ * If the key is encrypted, the encryption key is derived from the
+ * passphrase by means of a succession of SHA-1 hashes. Each hash
+ * is the hash of:
+ *
+ * uint32 sequence-number
+ * data passphrase
+ *
+ * where the sequence-number increases from zero. As many of these
+ * hashes are used as necessary.
+ *
+ * For backwards compatibility with snapshots between 0.51 and
+ * 0.52, we also support the older key file format, which begins
+ * with "PuTTY-User-Key-File-1" (version number differs). In this
+ * format the Private-MAC: field only covers the private-plaintext
+ * field and nothing else (and without the 4-byte string length on
+ * the front too). Moreover, the Private-MAC: field can be replaced
+ * with a Private-Hash: field which is a plain SHA-1 hash instead of
+ * an HMAC (this was generated for unencrypted keys).
+ */
+
+static int read_header(FILE * fp, char *header)
+{
+ int len = 39;
+ int c;
+
+ while (1) {
+ c = fgetc(fp);
+ if (c == '\n' || c == '\r' || c == EOF)
+ return 0; /* failure */
+ if (c == ':') {
+ c = fgetc(fp);
+ if (c != ' ')
+ return 0;
+ *header = '\0';
+ return 1; /* success! */
+ }
+ if (len == 0)
+ return 0; /* failure */
+ *header++ = c;
+ len--;
+ }
+ return 0; /* failure */
+}
+
+static char *read_body(FILE * fp)
+{
+ char *text;
+ int len;
+ int size;
+ int c;
+
+ size = 128;
+ text = snewn(size, char);
+ len = 0;
+ text[len] = '\0';
+
+ while (1) {
+ c = fgetc(fp);
+ if (c == '\r' || c == '\n' || c == EOF) {
+ if (c != EOF) {
+ c = fgetc(fp);
+ if (c != '\r' && c != '\n')
+ ungetc(c, fp);
+ }
+ return text;
+ }
+ if (len + 1 >= size) {
+ size += 128;
+ text = sresize(text, size, char);
+ }
+ text[len++] = c;
+ text[len] = '\0';
+ }
+}
+
+int base64_decode_atom(char *atom, unsigned char *out)
+{
+ int vals[4];
+ int i, v, len;
+ unsigned word;
+ char c;
+
+ for (i = 0; i < 4; i++) {
+ c = atom[i];
+ if (c >= 'A' && c <= 'Z')
+ v = c - 'A';
+ else if (c >= 'a' && c <= 'z')
+ v = c - 'a' + 26;
+ else if (c >= '0' && c <= '9')
+ v = c - '0' + 52;
+ else if (c == '+')
+ v = 62;
+ else if (c == '/')
+ v = 63;
+ else if (c == '=')
+ v = -1;
+ else
+ return 0; /* invalid atom */
+ vals[i] = v;
+ }
+
+ if (vals[0] == -1 || vals[1] == -1)
+ return 0;
+ if (vals[2] == -1 && vals[3] != -1)
+ return 0;
+
+ if (vals[3] != -1)
+ len = 3;
+ else if (vals[2] != -1)
+ len = 2;
+ else
+ len = 1;
+
+ word = ((vals[0] << 18) |
+ (vals[1] << 12) | ((vals[2] & 0x3F) << 6) | (vals[3] & 0x3F));
+ out[0] = (word >> 16) & 0xFF;
+ if (len > 1)
+ out[1] = (word >> 8) & 0xFF;
+ if (len > 2)
+ out[2] = word & 0xFF;
+ return len;
+}
+
+static unsigned char *read_blob(FILE * fp, int nlines, int *bloblen)
+{
+ unsigned char *blob;
+ char *line;
+ int linelen, len;
+ int i, j, k;
+
+ /* We expect at most 64 base64 characters, ie 48 real bytes, per line. */
+ blob = snewn(48 * nlines, unsigned char);
+ len = 0;
+ for (i = 0; i < nlines; i++) {
+ line = read_body(fp);
+ if (!line) {
+ sfree(blob);
+ return NULL;
+ }
+ linelen = strlen(line);
+ if (linelen % 4 != 0 || linelen > 64) {
+ sfree(blob);
+ sfree(line);
+ return NULL;
+ }
+ for (j = 0; j < linelen; j += 4) {
+ k = base64_decode_atom(line + j, blob + len);
+ if (!k) {
+ sfree(line);
+ sfree(blob);
+ return NULL;
+ }
+ len += k;
+ }
+ sfree(line);
+ }
+ *bloblen = len;
+ return blob;
+}
+
+/*
+ * Magic error return value for when the passphrase is wrong.
+ */
+struct ssh2_userkey ssh2_wrong_passphrase = {
+ NULL, NULL, NULL
+};
+
+const struct ssh_signkey *find_pubkey_alg(const char *name)
+{
+ if (!strcmp(name, "ssh-rsa"))
+ return &ssh_rsa;
+ else if (!strcmp(name, "ssh-dss"))
+ return &ssh_dss;
+ else
+ return NULL;
+}
+
+struct ssh2_userkey *ssh2_load_userkey(const Filename *filename,
+ char *passphrase, const char **errorstr)
+{
+ FILE *fp;
+ char header[40], *b, *encryption, *comment, *mac;
+ const struct ssh_signkey *alg;
+ struct ssh2_userkey *ret;
+ int cipher, cipherblk;
+ unsigned char *public_blob, *private_blob;
+ int public_blob_len, private_blob_len;
+ int i, is_mac, old_fmt;
+ int passlen = passphrase ? strlen(passphrase) : 0;
+ const char *error = NULL;
+
+ ret = NULL; /* return NULL for most errors */
+ encryption = comment = mac = NULL;
+ public_blob = private_blob = NULL;
+
+ fp = f_open(filename, "rb", FALSE);
+ if (!fp) {
+ error = "can't open file";
+ goto error;
+ }
+
+ /* Read the first header line which contains the key type. */
+ if (!read_header(fp, header))
+ goto error;
+ if (0 == strcmp(header, "PuTTY-User-Key-File-2")) {
+ old_fmt = 0;
+ } else if (0 == strcmp(header, "PuTTY-User-Key-File-1")) {
+ /* this is an old key file; warn and then continue */
+ old_keyfile_warning();
+ old_fmt = 1;
+ } else if (0 == strncmp(header, "PuTTY-User-Key-File-", 20)) {
+ /* this is a key file FROM THE FUTURE; refuse it, but with a
+ * more specific error message than the generic one below */
+ error = "PuTTY key format too new";
+ goto error;
+ } else {
+ error = "not a PuTTY SSH-2 private key";
+ goto error;
+ }
+ error = "file format error";
+ if ((b = read_body(fp)) == NULL)
+ goto error;
+ /* Select key algorithm structure. */
+ alg = find_pubkey_alg(b);
+ if (!alg) {
+ sfree(b);
+ goto error;
+ }
+ sfree(b);
+
+ /* Read the Encryption header line. */
+ if (!read_header(fp, header) || 0 != strcmp(header, "Encryption"))
+ goto error;
+ if ((encryption = read_body(fp)) == NULL)
+ goto error;
+ if (!strcmp(encryption, "aes256-cbc")) {
+ cipher = 1;
+ cipherblk = 16;
+ } else if (!strcmp(encryption, "none")) {
+ cipher = 0;
+ cipherblk = 1;
+ } else {
+ goto error;
+ }
+
+ /* Read the Comment header line. */
+ if (!read_header(fp, header) || 0 != strcmp(header, "Comment"))
+ goto error;
+ if ((comment = read_body(fp)) == NULL)
+ goto error;
+
+ /* Read the Public-Lines header line and the public blob. */
+ if (!read_header(fp, header) || 0 != strcmp(header, "Public-Lines"))
+ goto error;
+ if ((b = read_body(fp)) == NULL)
+ goto error;
+ i = atoi(b);
+ sfree(b);
+ if ((public_blob = read_blob(fp, i, &public_blob_len)) == NULL)
+ goto error;
+
+ /* Read the Private-Lines header line and the Private blob. */
+ if (!read_header(fp, header) || 0 != strcmp(header, "Private-Lines"))
+ goto error;
+ if ((b = read_body(fp)) == NULL)
+ goto error;
+ i = atoi(b);
+ sfree(b);
+ if ((private_blob = read_blob(fp, i, &private_blob_len)) == NULL)
+ goto error;
+
+ /* Read the Private-MAC or Private-Hash header line. */
+ if (!read_header(fp, header))
+ goto error;
+ if (0 == strcmp(header, "Private-MAC")) {
+ if ((mac = read_body(fp)) == NULL)
+ goto error;
+ is_mac = 1;
+ } else if (0 == strcmp(header, "Private-Hash") && old_fmt) {
+ if ((mac = read_body(fp)) == NULL)
+ goto error;
+ is_mac = 0;
+ } else
+ goto error;
+
+ fclose(fp);
+ fp = NULL;
+
+ /*
+ * Decrypt the private blob.
+ */
+ if (cipher) {
+ unsigned char key[40];
+ SHA_State s;
+
+ if (!passphrase)
+ goto error;
+ if (private_blob_len % cipherblk)
+ goto error;
+
+ SHA_Init(&s);
+ SHA_Bytes(&s, "\0\0\0\0", 4);
+ SHA_Bytes(&s, passphrase, passlen);
+ SHA_Final(&s, key + 0);
+ SHA_Init(&s);
+ SHA_Bytes(&s, "\0\0\0\1", 4);
+ SHA_Bytes(&s, passphrase, passlen);
+ SHA_Final(&s, key + 20);
+ aes256_decrypt_pubkey(key, private_blob, private_blob_len);
+ }
+
+ /*
+ * Verify the MAC.
+ */
+ {
+ char realmac[41];
+ unsigned char binary[20];
+ unsigned char *macdata;
+ int maclen;
+ int free_macdata;
+
+ if (old_fmt) {
+ /* MAC (or hash) only covers the private blob. */
+ macdata = private_blob;
+ maclen = private_blob_len;
+ free_macdata = 0;
+ } else {
+ unsigned char *p;
+ int namelen = strlen(alg->name);
+ int enclen = strlen(encryption);
+ int commlen = strlen(comment);
+ maclen = (4 + namelen +
+ 4 + enclen +
+ 4 + commlen +
+ 4 + public_blob_len +
+ 4 + private_blob_len);
+ macdata = snewn(maclen, unsigned char);
+ p = macdata;
+#define DO_STR(s,len) PUT_32BIT(p,(len));memcpy(p+4,(s),(len));p+=4+(len)
+ DO_STR(alg->name, namelen);
+ DO_STR(encryption, enclen);
+ DO_STR(comment, commlen);
+ DO_STR(public_blob, public_blob_len);
+ DO_STR(private_blob, private_blob_len);
+
+ free_macdata = 1;
+ }
+
+ if (is_mac) {
+ SHA_State s;
+ unsigned char mackey[20];
+ char header[] = "putty-private-key-file-mac-key";
+
+ SHA_Init(&s);
+ SHA_Bytes(&s, header, sizeof(header)-1);
+ if (cipher && passphrase)
+ SHA_Bytes(&s, passphrase, passlen);
+ SHA_Final(&s, mackey);
+
+ hmac_sha1_simple(mackey, 20, macdata, maclen, binary);
+
+ smemclr(mackey, sizeof(mackey));
+ smemclr(&s, sizeof(s));
+ } else {
+ SHA_Simple(macdata, maclen, binary);
+ }
+
+ if (free_macdata) {
+ smemclr(macdata, maclen);
+ sfree(macdata);
+ }
+
+ for (i = 0; i < 20; i++)
+ sprintf(realmac + 2 * i, "%02x", binary[i]);
+
+ if (strcmp(mac, realmac)) {
+ /* An incorrect MAC is an unconditional Error if the key is
+ * unencrypted. Otherwise, it means Wrong Passphrase. */
+ if (cipher) {
+ error = "wrong passphrase";
+ ret = SSH2_WRONG_PASSPHRASE;
+ } else {
+ error = "MAC failed";
+ ret = NULL;
+ }
+ goto error;
+ }
+ }
+ sfree(mac);
+
+ /*
+ * Create and return the key.
+ */
+ ret = snew(struct ssh2_userkey);
+ ret->alg = alg;
+ ret->comment = comment;
+ ret->data = alg->createkey(public_blob, public_blob_len,
+ private_blob, private_blob_len);
+ if (!ret->data) {
+ sfree(ret->comment);
+ sfree(ret);
+ ret = NULL;
+ error = "createkey failed";
+ goto error;
+ }
+ sfree(public_blob);
+ sfree(private_blob);
+ sfree(encryption);
+ if (errorstr)
+ *errorstr = NULL;
+ return ret;
+
+ /*
+ * Error processing.
+ */
+ error:
+ if (fp)
+ fclose(fp);
+ if (comment)
+ sfree(comment);
+ if (encryption)
+ sfree(encryption);
+ if (mac)
+ sfree(mac);
+ if (public_blob)
+ sfree(public_blob);
+ if (private_blob)
+ sfree(private_blob);
+ if (errorstr)
+ *errorstr = error;
+ return ret;
+}
+
+unsigned char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm,
+ int *pub_blob_len, char **commentptr,
+ const char **errorstr)
+{
+ FILE *fp;
+ char header[40], *b;
+ const struct ssh_signkey *alg;
+ unsigned char *public_blob;
+ int public_blob_len;
+ int i;
+ const char *error = NULL;
+ char *comment;
+
+ public_blob = NULL;
+
+ fp = f_open(filename, "rb", FALSE);
+ if (!fp) {
+ error = "can't open file";
+ 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") &&
+ 0 != strcmp(header, "PuTTY-User-Key-File-1"))) {
+ if (0 == strncmp(header, "PuTTY-User-Key-File-", 20))
+ error = "PuTTY key format too new";
+ else
+ error = "not a PuTTY SSH-2 private key";
+ goto error;
+ }
+ error = "file format error";
+ if ((b = read_body(fp)) == NULL)
+ goto error;
+ /* Select key algorithm structure. */
+ alg = find_pubkey_alg(b);
+ if (!alg) {
+ sfree(b);
+ goto error;
+ }
+ sfree(b);
+
+ /* Read the Encryption header line. */
+ if (!read_header(fp, header) || 0 != strcmp(header, "Encryption"))
+ goto error;
+ if ((b = read_body(fp)) == NULL)
+ goto error;
+ sfree(b); /* we don't care */
+
+ /* Read the Comment header line. */
+ if (!read_header(fp, header) || 0 != strcmp(header, "Comment"))
+ goto error;
+ if ((comment = read_body(fp)) == NULL)
+ goto error;
+
+ if (commentptr)
+ *commentptr = comment;
+ else
+ sfree(comment);
+
+ /* Read the Public-Lines header line and the public blob. */
+ if (!read_header(fp, header) || 0 != strcmp(header, "Public-Lines"))
+ goto error;
+ if ((b = read_body(fp)) == NULL)
+ goto error;
+ i = atoi(b);
+ sfree(b);
+ if ((public_blob = read_blob(fp, i, &public_blob_len)) == NULL)
+ goto error;
+
+ fclose(fp);
+ if (pub_blob_len)
+ *pub_blob_len = public_blob_len;
+ if (algorithm)
+ *algorithm = alg->name;
+ return public_blob;
+
+ /*
+ * Error processing.
+ */
+ error:
+ if (fp)
+ fclose(fp);
+ if (public_blob)
+ sfree(public_blob);
+ if (errorstr)
+ *errorstr = error;
+ return NULL;
+}
+
+int ssh2_userkey_encrypted(const Filename *filename, char **commentptr)
+{
+ FILE *fp;
+ char header[40], *b, *comment;
+ int ret;
+
+ if (commentptr)
+ *commentptr = NULL;
+
+ fp = f_open(filename, "rb", FALSE);
+ if (!fp)
+ return 0;
+ if (!read_header(fp, header)
+ || (0 != strcmp(header, "PuTTY-User-Key-File-2") &&
+ 0 != strcmp(header, "PuTTY-User-Key-File-1"))) {
+ fclose(fp);
+ return 0;
+ }
+ if ((b = read_body(fp)) == NULL) {
+ fclose(fp);
+ return 0;
+ }
+ sfree(b); /* we don't care about key type here */
+ /* Read the Encryption header line. */
+ if (!read_header(fp, header) || 0 != strcmp(header, "Encryption")) {
+ fclose(fp);
+ return 0;
+ }
+ if ((b = read_body(fp)) == NULL) {
+ fclose(fp);
+ return 0;
+ }
+
+ /* Read the Comment header line. */
+ if (!read_header(fp, header) || 0 != strcmp(header, "Comment")) {
+ fclose(fp);
+ sfree(b);
+ return 1;
+ }
+ if ((comment = read_body(fp)) == NULL) {
+ fclose(fp);
+ sfree(b);
+ return 1;
+ }
+
+ if (commentptr)
+ *commentptr = comment;
+ else
+ sfree(comment);
+
+ fclose(fp);
+ if (!strcmp(b, "aes256-cbc"))
+ ret = 1;
+ else
+ ret = 0;
+ sfree(b);
+ return ret;
+}
+
+int base64_lines(int datalen)
+{
+ /* When encoding, we use 64 chars/line, which equals 48 real chars. */
+ return (datalen + 47) / 48;
+}
+
+void base64_encode(FILE * fp, unsigned char *data, int datalen, int cpl)
+{
+ int linelen = 0;
+ char out[4];
+ int n, i;
+
+ while (datalen > 0) {
+ n = (datalen < 3 ? datalen : 3);
+ base64_encode_atom(data, n, out);
+ data += n;
+ datalen -= n;
+ for (i = 0; i < 4; i++) {
+ if (linelen >= cpl) {
+ linelen = 0;
+ fputc('\n', fp);
+ }
+ fputc(out[i], fp);
+ linelen++;
+ }
+ }
+ fputc('\n', fp);
+}
+
+int ssh2_save_userkey(const Filename *filename, struct ssh2_userkey *key,
+ char *passphrase)
+{
+ FILE *fp;
+ unsigned char *pub_blob, *priv_blob, *priv_blob_encrypted;
+ int pub_blob_len, priv_blob_len, priv_encrypted_len;
+ int passlen;
+ int cipherblk;
+ int i;
+ char *cipherstr;
+ unsigned char priv_mac[20];
+
+ /*
+ * Fetch the key component blobs.
+ */
+ pub_blob = key->alg->public_blob(key->data, &pub_blob_len);
+ priv_blob = key->alg->private_blob(key->data, &priv_blob_len);
+ if (!pub_blob || !priv_blob) {
+ sfree(pub_blob);
+ sfree(priv_blob);
+ return 0;
+ }
+
+ /*
+ * Determine encryption details, and encrypt the private blob.
+ */
+ if (passphrase) {
+ cipherstr = "aes256-cbc";
+ cipherblk = 16;
+ } else {
+ cipherstr = "none";
+ cipherblk = 1;
+ }
+ priv_encrypted_len = priv_blob_len + cipherblk - 1;
+ priv_encrypted_len -= priv_encrypted_len % cipherblk;
+ priv_blob_encrypted = snewn(priv_encrypted_len, unsigned char);
+ memset(priv_blob_encrypted, 0, priv_encrypted_len);
+ memcpy(priv_blob_encrypted, priv_blob, priv_blob_len);
+ /* Create padding based on the SHA hash of the unpadded blob. This prevents
+ * too easy a known-plaintext attack on the last block. */
+ SHA_Simple(priv_blob, priv_blob_len, priv_mac);
+ assert(priv_encrypted_len - priv_blob_len < 20);
+ memcpy(priv_blob_encrypted + priv_blob_len, priv_mac,
+ priv_encrypted_len - priv_blob_len);
+
+ /* Now create the MAC. */
+ {
+ unsigned char *macdata;
+ int maclen;
+ unsigned char *p;
+ int namelen = strlen(key->alg->name);
+ int enclen = strlen(cipherstr);
+ int commlen = strlen(key->comment);
+ SHA_State s;
+ unsigned char mackey[20];
+ char header[] = "putty-private-key-file-mac-key";
+
+ maclen = (4 + namelen +
+ 4 + enclen +
+ 4 + commlen +
+ 4 + pub_blob_len +
+ 4 + priv_encrypted_len);
+ macdata = snewn(maclen, unsigned char);
+ p = macdata;
+#define DO_STR(s,len) PUT_32BIT(p,(len));memcpy(p+4,(s),(len));p+=4+(len)
+ DO_STR(key->alg->name, namelen);
+ DO_STR(cipherstr, enclen);
+ DO_STR(key->comment, commlen);
+ DO_STR(pub_blob, pub_blob_len);
+ DO_STR(priv_blob_encrypted, priv_encrypted_len);
+
+ SHA_Init(&s);
+ SHA_Bytes(&s, header, sizeof(header)-1);
+ if (passphrase)
+ SHA_Bytes(&s, passphrase, strlen(passphrase));
+ SHA_Final(&s, mackey);
+ hmac_sha1_simple(mackey, 20, macdata, maclen, priv_mac);
+ smemclr(macdata, maclen);
+ sfree(macdata);
+ smemclr(mackey, sizeof(mackey));
+ smemclr(&s, sizeof(s));
+ }
+
+ if (passphrase) {
+ unsigned char key[40];
+ SHA_State s;
+
+ passlen = strlen(passphrase);
+
+ SHA_Init(&s);
+ SHA_Bytes(&s, "\0\0\0\0", 4);
+ SHA_Bytes(&s, passphrase, passlen);
+ SHA_Final(&s, key + 0);
+ SHA_Init(&s);
+ SHA_Bytes(&s, "\0\0\0\1", 4);
+ SHA_Bytes(&s, passphrase, passlen);
+ SHA_Final(&s, key + 20);
+ aes256_encrypt_pubkey(key, priv_blob_encrypted,
+ priv_encrypted_len);
+
+ smemclr(key, sizeof(key));
+ smemclr(&s, sizeof(s));
+ }
+
+ fp = f_open(filename, "w", TRUE);
+ if (!fp)
+ return 0;
+ fprintf(fp, "PuTTY-User-Key-File-2: %s\n", key->alg->name);
+ fprintf(fp, "Encryption: %s\n", cipherstr);
+ fprintf(fp, "Comment: %s\n", key->comment);
+ fprintf(fp, "Public-Lines: %d\n", base64_lines(pub_blob_len));
+ base64_encode(fp, pub_blob, pub_blob_len, 64);
+ fprintf(fp, "Private-Lines: %d\n", base64_lines(priv_encrypted_len));
+ base64_encode(fp, priv_blob_encrypted, priv_encrypted_len, 64);
+ fprintf(fp, "Private-MAC: ");
+ for (i = 0; i < 20; i++)
+ fprintf(fp, "%02x", priv_mac[i]);
+ fprintf(fp, "\n");
+ fclose(fp);
+
+ sfree(pub_blob);
+ smemclr(priv_blob, priv_blob_len);
+ sfree(priv_blob);
+ sfree(priv_blob_encrypted);
+ return 1;
+}
+
+/* ----------------------------------------------------------------------
+ * A function to determine the type of a private key file. Returns
+ * 0 on failure, 1 or 2 on success.
+ */
+int key_type(const Filename *filename)
+{
+ FILE *fp;
+ char buf[32];
+ const char putty2_sig[] = "PuTTY-User-Key-File-";
+ const char sshcom_sig[] = "---- BEGIN SSH2 ENCRYPTED PRIVAT";
+ const char openssh_sig[] = "-----BEGIN ";
+ int i;
+
+ 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;
+ if (!memcmp(buf, rsa_signature, sizeof(rsa_signature)-1))
+ return SSH_KEYTYPE_SSH1;
+ if (!memcmp(buf, putty2_sig, sizeof(putty2_sig)-1))
+ return SSH_KEYTYPE_SSH2;
+ if (!memcmp(buf, openssh_sig, sizeof(openssh_sig)-1))
+ return SSH_KEYTYPE_OPENSSH;
+ if (!memcmp(buf, sshcom_sig, sizeof(sshcom_sig)-1))
+ return SSH_KEYTYPE_SSHCOM;
+ return SSH_KEYTYPE_UNKNOWN; /* unrecognised or EOF */
+}
+
+/*
+ * Convert the type word to a string, for `wrong type' error
+ * messages.
+ */
+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_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_SSHCOM: return "ssh.com SSH-2 private key"; break;
+ default: return "INTERNAL ERROR"; break;
+ }
+}
diff --git a/tools/plink/sshrand.c b/tools/plink/sshrand.c
index 91d9b3772..ead39a9bd 100644
--- a/tools/plink/sshrand.c
+++ b/tools/plink/sshrand.c
@@ -1,251 +1,328 @@
-/*
- * cryptographic random number generator for PuTTY's ssh client
- */
-
-#include "putty.h"
-#include "ssh.h"
-#include <assert.h>
-
-/* Collect environmental noise every 5 minutes */
-#define NOISE_REGULAR_INTERVAL (5*60*TICKSPERSEC)
-
-void noise_get_heavy(void (*func) (void *, int));
-void noise_get_light(void (*func) (void *, int));
-
-/*
- * `pool' itself is a pool of random data which we actually use: we
- * return bytes from `pool', at position `poolpos', until `poolpos'
- * reaches the end of the pool. At this point we generate more
- * random data, by adding noise, stirring well, and resetting
- * `poolpos' to point to just past the beginning of the pool (not
- * _the_ beginning, since otherwise we'd give away the whole
- * contents of our pool, and attackers would just have to guess the
- * next lot of noise).
- *
- * `incomingb' buffers acquired noise data, until it gets full, at
- * which point the acquired noise is SHA'ed into `incoming' and
- * `incomingb' is cleared. The noise in `incoming' is used as part
- * of the noise for each stirring of the pool, in addition to local
- * time, process listings, and other such stuff.
- */
-
-#define HASHINPUT 64 /* 64 bytes SHA input */
-#define HASHSIZE 20 /* 160 bits SHA output */
-#define POOLSIZE 1200 /* size of random pool */
-
-struct RandPool {
- unsigned char pool[POOLSIZE];
- int poolpos;
-
- unsigned char incoming[HASHSIZE];
-
- unsigned char incomingb[HASHINPUT];
- int incomingpos;
-
- int stir_pending;
-};
-
-static struct RandPool pool;
-int random_active = 0;
-long next_noise_collection;
-
-static void random_stir(void)
-{
- word32 block[HASHINPUT / sizeof(word32)];
- word32 digest[HASHSIZE / sizeof(word32)];
- int i, j, k;
-
- /*
- * noise_get_light will call random_add_noise, which may call
- * back to here. Prevent recursive stirs.
- */
- if (pool.stir_pending)
- return;
- pool.stir_pending = TRUE;
-
- noise_get_light(random_add_noise);
-
- SHATransform((word32 *) pool.incoming, (word32 *) pool.incomingb);
- pool.incomingpos = 0;
-
- /*
- * Chunks of this code are blatantly endianness-dependent, but
- * as it's all random bits anyway, WHO CARES?
- */
- memcpy(digest, pool.incoming, sizeof(digest));
-
- /*
- * Make two passes over the pool.
- */
- for (i = 0; i < 2; i++) {
-
- /*
- * We operate SHA in CFB mode, repeatedly adding the same
- * block of data to the digest. But we're also fiddling
- * with the digest-so-far, so this shouldn't be Bad or
- * anything.
- */
- memcpy(block, pool.pool, sizeof(block));
-
- /*
- * Each pass processes the pool backwards in blocks of
- * HASHSIZE, just so that in general we get the output of
- * SHA before the corresponding input, in the hope that
- * things will be that much less predictable that way
- * round, when we subsequently return bytes ...
- */
- for (j = POOLSIZE; (j -= HASHSIZE) >= 0;) {
- /*
- * XOR the bit of the pool we're processing into the
- * digest.
- */
-
- for (k = 0; k < sizeof(digest) / sizeof(*digest); k++)
- digest[k] ^= ((word32 *) (pool.pool + j))[k];
-
- /*
- * Munge our unrevealed first block of the pool into
- * it.
- */
- SHATransform(digest, block);
-
- /*
- * Stick the result back into the pool.
- */
-
- for (k = 0; k < sizeof(digest) / sizeof(*digest); k++)
- ((word32 *) (pool.pool + j))[k] = digest[k];
- }
- }
-
- /*
- * Might as well save this value back into `incoming', just so
- * there'll be some extra bizarreness there.
- */
- SHATransform(digest, block);
- memcpy(pool.incoming, digest, sizeof(digest));
-
- pool.poolpos = sizeof(pool.incoming);
-
- pool.stir_pending = FALSE;
-}
-
-void random_add_noise(void *noise, int length)
-{
- unsigned char *p = noise;
- int i;
-
- if (!random_active)
- return;
-
- /*
- * This function processes HASHINPUT bytes into only HASHSIZE
- * bytes, so _if_ we were getting incredibly high entropy
- * sources then we would be throwing away valuable stuff.
- */
- while (length >= (HASHINPUT - pool.incomingpos)) {
- memcpy(pool.incomingb + pool.incomingpos, p,
- HASHINPUT - pool.incomingpos);
- p += HASHINPUT - pool.incomingpos;
- length -= HASHINPUT - pool.incomingpos;
- SHATransform((word32 *) pool.incoming, (word32 *) pool.incomingb);
- for (i = 0; i < HASHSIZE; i++) {
- pool.pool[pool.poolpos++] ^= pool.incomingb[i];
- if (pool.poolpos >= POOLSIZE)
- pool.poolpos = 0;
- }
- if (pool.poolpos < HASHSIZE)
- random_stir();
-
- pool.incomingpos = 0;
- }
-
- memcpy(pool.incomingb + pool.incomingpos, p, length);
- pool.incomingpos += length;
-}
-
-void random_add_heavynoise(void *noise, int length)
-{
- unsigned char *p = noise;
- int i;
-
- while (length >= POOLSIZE) {
- for (i = 0; i < POOLSIZE; i++)
- pool.pool[i] ^= *p++;
- random_stir();
- length -= POOLSIZE;
- }
-
- for (i = 0; i < length; i++)
- pool.pool[i] ^= *p++;
- random_stir();
-}
-
-static void random_add_heavynoise_bitbybit(void *noise, int length)
-{
- unsigned char *p = noise;
- int i;
-
- while (length >= POOLSIZE - pool.poolpos) {
- for (i = 0; i < POOLSIZE - pool.poolpos; i++)
- pool.pool[pool.poolpos + i] ^= *p++;
- random_stir();
- length -= POOLSIZE - pool.poolpos;
- pool.poolpos = 0;
- }
-
- for (i = 0; i < length; i++)
- pool.pool[i] ^= *p++;
- pool.poolpos = i;
-}
-
-static void random_timer(void *ctx, long now)
-{
- if (random_active > 0 && now - next_noise_collection >= 0) {
- noise_regular();
- next_noise_collection =
- schedule_timer(NOISE_REGULAR_INTERVAL, random_timer, &pool);
- }
-}
-
-void random_ref(void)
-{
- if (!random_active) {
- memset(&pool, 0, sizeof(pool)); /* just to start with */
-
- noise_get_heavy(random_add_heavynoise_bitbybit);
- random_stir();
-
- next_noise_collection =
- schedule_timer(NOISE_REGULAR_INTERVAL, random_timer, &pool);
- }
-
- random_active++;
-}
-
-void random_unref(void)
-{
- random_active--;
- assert(random_active >= 0);
- if (random_active) return;
-
- expire_timer_context(&pool);
-}
-
-int random_byte(void)
-{
- if (pool.poolpos >= POOLSIZE)
- random_stir();
-
- return pool.pool[pool.poolpos++];
-}
-
-void random_get_savedata(void **data, int *len)
-{
- void *buf = snewn(POOLSIZE / 2, char);
- random_stir();
- memcpy(buf, pool.pool + pool.poolpos, POOLSIZE / 2);
- *len = POOLSIZE / 2;
- *data = buf;
- random_stir();
-}
+/*
+ * cryptographic random number generator for PuTTY's ssh client
+ */
+
+#include "putty.h"
+#include "ssh.h"
+#include <assert.h>
+
+/* Collect environmental noise every 5 minutes */
+#define NOISE_REGULAR_INTERVAL (5*60*TICKSPERSEC)
+
+void noise_get_heavy(void (*func) (void *, int));
+void noise_get_light(void (*func) (void *, int));
+
+/*
+ * `pool' itself is a pool of random data which we actually use: we
+ * return bytes from `pool', at position `poolpos', until `poolpos'
+ * reaches the end of the pool. At this point we generate more
+ * random data, by adding noise, stirring well, and resetting
+ * `poolpos' to point to just past the beginning of the pool (not
+ * _the_ beginning, since otherwise we'd give away the whole
+ * contents of our pool, and attackers would just have to guess the
+ * next lot of noise).
+ *
+ * `incomingb' buffers acquired noise data, until it gets full, at
+ * which point the acquired noise is SHA'ed into `incoming' and
+ * `incomingb' is cleared. The noise in `incoming' is used as part
+ * of the noise for each stirring of the pool, in addition to local
+ * time, process listings, and other such stuff.
+ */
+
+#define HASHINPUT 64 /* 64 bytes SHA input */
+#define HASHSIZE 20 /* 160 bits SHA output */
+#define POOLSIZE 1200 /* size of random pool */
+
+struct RandPool {
+ unsigned char pool[POOLSIZE];
+ int poolpos;
+
+ unsigned char incoming[HASHSIZE];
+
+ unsigned char incomingb[HASHINPUT];
+ int incomingpos;
+
+ int stir_pending;
+};
+
+static struct RandPool pool;
+int random_active = 0;
+long next_noise_collection;
+
+#ifdef RANDOM_DIAGNOSTICS
+int random_diagnostics = 0;
+#endif
+
+static void random_stir(void)
+{
+ word32 block[HASHINPUT / sizeof(word32)];
+ word32 digest[HASHSIZE / sizeof(word32)];
+ int i, j, k;
+
+ /*
+ * noise_get_light will call random_add_noise, which may call
+ * back to here. Prevent recursive stirs.
+ */
+ if (pool.stir_pending)
+ return;
+ pool.stir_pending = TRUE;
+
+ noise_get_light(random_add_noise);
+
+#ifdef RANDOM_DIAGNOSTICS
+ {
+ int p, q;
+ printf("random stir starting\npool:\n");
+ for (p = 0; p < POOLSIZE; p += HASHSIZE) {
+ printf(" ");
+ for (q = 0; q < HASHSIZE; q += 4) {
+ printf(" %08x", *(word32 *)(pool.pool + p + q));
+ }
+ printf("\n");
+ }
+ printf("incoming:\n ");
+ for (q = 0; q < HASHSIZE; q += 4) {
+ printf(" %08x", *(word32 *)(pool.incoming + q));
+ }
+ printf("\nincomingb:\n ");
+ for (q = 0; q < HASHINPUT; q += 4) {
+ printf(" %08x", *(word32 *)(pool.incomingb + q));
+ }
+ printf("\n");
+ random_diagnostics++;
+ }
+#endif
+
+ SHATransform((word32 *) pool.incoming, (word32 *) pool.incomingb);
+ pool.incomingpos = 0;
+
+ /*
+ * Chunks of this code are blatantly endianness-dependent, but
+ * as it's all random bits anyway, WHO CARES?
+ */
+ memcpy(digest, pool.incoming, sizeof(digest));
+
+ /*
+ * Make two passes over the pool.
+ */
+ for (i = 0; i < 2; i++) {
+
+ /*
+ * We operate SHA in CFB mode, repeatedly adding the same
+ * block of data to the digest. But we're also fiddling
+ * with the digest-so-far, so this shouldn't be Bad or
+ * anything.
+ */
+ memcpy(block, pool.pool, sizeof(block));
+
+ /*
+ * Each pass processes the pool backwards in blocks of
+ * HASHSIZE, just so that in general we get the output of
+ * SHA before the corresponding input, in the hope that
+ * things will be that much less predictable that way
+ * round, when we subsequently return bytes ...
+ */
+ for (j = POOLSIZE; (j -= HASHSIZE) >= 0;) {
+ /*
+ * XOR the bit of the pool we're processing into the
+ * digest.
+ */
+
+ for (k = 0; k < sizeof(digest) / sizeof(*digest); k++)
+ digest[k] ^= ((word32 *) (pool.pool + j))[k];
+
+ /*
+ * Munge our unrevealed first block of the pool into
+ * it.
+ */
+ SHATransform(digest, block);
+
+ /*
+ * Stick the result back into the pool.
+ */
+
+ for (k = 0; k < sizeof(digest) / sizeof(*digest); k++)
+ ((word32 *) (pool.pool + j))[k] = digest[k];
+ }
+
+#ifdef RANDOM_DIAGNOSTICS
+ if (i == 0) {
+ int p, q;
+ printf("random stir midpoint\npool:\n");
+ for (p = 0; p < POOLSIZE; p += HASHSIZE) {
+ printf(" ");
+ for (q = 0; q < HASHSIZE; q += 4) {
+ printf(" %08x", *(word32 *)(pool.pool + p + q));
+ }
+ printf("\n");
+ }
+ printf("incoming:\n ");
+ for (q = 0; q < HASHSIZE; q += 4) {
+ printf(" %08x", *(word32 *)(pool.incoming + q));
+ }
+ printf("\nincomingb:\n ");
+ for (q = 0; q < HASHINPUT; q += 4) {
+ printf(" %08x", *(word32 *)(pool.incomingb + q));
+ }
+ printf("\n");
+ }
+#endif
+ }
+
+ /*
+ * Might as well save this value back into `incoming', just so
+ * there'll be some extra bizarreness there.
+ */
+ SHATransform(digest, block);
+ memcpy(pool.incoming, digest, sizeof(digest));
+
+ pool.poolpos = sizeof(pool.incoming);
+
+ pool.stir_pending = FALSE;
+
+#ifdef RANDOM_DIAGNOSTICS
+ {
+ int p, q;
+ printf("random stir done\npool:\n");
+ for (p = 0; p < POOLSIZE; p += HASHSIZE) {
+ printf(" ");
+ for (q = 0; q < HASHSIZE; q += 4) {
+ printf(" %08x", *(word32 *)(pool.pool + p + q));
+ }
+ printf("\n");
+ }
+ printf("incoming:\n ");
+ for (q = 0; q < HASHSIZE; q += 4) {
+ printf(" %08x", *(word32 *)(pool.incoming + q));
+ }
+ printf("\nincomingb:\n ");
+ for (q = 0; q < HASHINPUT; q += 4) {
+ printf(" %08x", *(word32 *)(pool.incomingb + q));
+ }
+ printf("\n");
+ random_diagnostics--;
+ }
+#endif
+}
+
+void random_add_noise(void *noise, int length)
+{
+ unsigned char *p = noise;
+ int i;
+
+ if (!random_active)
+ return;
+
+ /*
+ * This function processes HASHINPUT bytes into only HASHSIZE
+ * bytes, so _if_ we were getting incredibly high entropy
+ * sources then we would be throwing away valuable stuff.
+ */
+ while (length >= (HASHINPUT - pool.incomingpos)) {
+ memcpy(pool.incomingb + pool.incomingpos, p,
+ HASHINPUT - pool.incomingpos);
+ p += HASHINPUT - pool.incomingpos;
+ length -= HASHINPUT - pool.incomingpos;
+ SHATransform((word32 *) pool.incoming, (word32 *) pool.incomingb);
+ for (i = 0; i < HASHSIZE; i++) {
+ pool.pool[pool.poolpos++] ^= pool.incomingb[i];
+ if (pool.poolpos >= POOLSIZE)
+ pool.poolpos = 0;
+ }
+ if (pool.poolpos < HASHSIZE)
+ random_stir();
+
+ pool.incomingpos = 0;
+ }
+
+ memcpy(pool.incomingb + pool.incomingpos, p, length);
+ pool.incomingpos += length;
+}
+
+void random_add_heavynoise(void *noise, int length)
+{
+ unsigned char *p = noise;
+ int i;
+
+ while (length >= POOLSIZE) {
+ for (i = 0; i < POOLSIZE; i++)
+ pool.pool[i] ^= *p++;
+ random_stir();
+ length -= POOLSIZE;
+ }
+
+ for (i = 0; i < length; i++)
+ pool.pool[i] ^= *p++;
+ random_stir();
+}
+
+static void random_add_heavynoise_bitbybit(void *noise, int length)
+{
+ unsigned char *p = noise;
+ int i;
+
+ while (length >= POOLSIZE - pool.poolpos) {
+ for (i = 0; i < POOLSIZE - pool.poolpos; i++)
+ pool.pool[pool.poolpos + i] ^= *p++;
+ random_stir();
+ length -= POOLSIZE - pool.poolpos;
+ pool.poolpos = 0;
+ }
+
+ for (i = 0; i < length; i++)
+ pool.pool[i] ^= *p++;
+ pool.poolpos = i;
+}
+
+static void random_timer(void *ctx, unsigned long now)
+{
+ if (random_active > 0 && now == next_noise_collection) {
+ noise_regular();
+ next_noise_collection =
+ schedule_timer(NOISE_REGULAR_INTERVAL, random_timer, &pool);
+ }
+}
+
+void random_ref(void)
+{
+ if (!random_active) {
+ memset(&pool, 0, sizeof(pool)); /* just to start with */
+
+ noise_get_heavy(random_add_heavynoise_bitbybit);
+ random_stir();
+
+ next_noise_collection =
+ schedule_timer(NOISE_REGULAR_INTERVAL, random_timer, &pool);
+ }
+ random_active++;
+}
+
+void random_unref(void)
+{
+ assert(random_active > 0);
+ if (random_active == 1) {
+ random_save_seed();
+ expire_timer_context(&pool);
+ }
+ random_active--;
+}
+
+int random_byte(void)
+{
+ assert(random_active);
+
+ if (pool.poolpos >= POOLSIZE)
+ random_stir();
+
+ return pool.pool[pool.poolpos++];
+}
+
+void random_get_savedata(void **data, int *len)
+{
+ void *buf = snewn(POOLSIZE / 2, char);
+ random_stir();
+ memcpy(buf, pool.pool + pool.poolpos, POOLSIZE / 2);
+ *len = POOLSIZE / 2;
+ *data = buf;
+ random_stir();
+}
diff --git a/tools/plink/sshrsa.c b/tools/plink/sshrsa.c
index ea6440bc5..25f9cf7e6 100644
--- a/tools/plink/sshrsa.c
+++ b/tools/plink/sshrsa.c
@@ -1,1086 +1,1105 @@
-/*
- * RSA implementation for PuTTY.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-
-#include "ssh.h"
-#include "misc.h"
-
-int makekey(unsigned char *data, int len, struct RSAKey *result,
- unsigned char **keystr, int order)
-{
- unsigned char *p = data;
- int i, n;
-
- if (len < 4)
- return -1;
-
- if (result) {
- result->bits = 0;
- for (i = 0; i < 4; i++)
- result->bits = (result->bits << 8) + *p++;
- } else
- p += 4;
-
- len -= 4;
-
- /*
- * order=0 means exponent then modulus (the keys sent by the
- * server). order=1 means modulus then exponent (the keys
- * stored in a keyfile).
- */
-
- if (order == 0) {
- n = ssh1_read_bignum(p, len, result ? &result->exponent : NULL);
- if (n < 0) return -1;
- p += n;
- len -= n;
- }
-
- n = ssh1_read_bignum(p, len, result ? &result->modulus : NULL);
- if (n < 0 || (result && bignum_bitcount(result->modulus) == 0)) return -1;
- if (result)
- result->bytes = n - 2;
- if (keystr)
- *keystr = p + 2;
- p += n;
- len -= n;
-
- if (order == 1) {
- n = ssh1_read_bignum(p, len, result ? &result->exponent : NULL);
- if (n < 0) return -1;
- p += n;
- len -= n;
- }
- return p - data;
-}
-
-int makeprivate(unsigned char *data, int len, struct RSAKey *result)
-{
- return ssh1_read_bignum(data, len, &result->private_exponent);
-}
-
-int rsaencrypt(unsigned char *data, int length, struct RSAKey *key)
-{
- Bignum b1, b2;
- int i;
- unsigned char *p;
-
- if (key->bytes < length + 4)
- return 0; /* RSA key too short! */
-
- memmove(data + key->bytes - length, data, length);
- data[0] = 0;
- data[1] = 2;
-
- for (i = 2; i < key->bytes - length - 1; i++) {
- do {
- data[i] = random_byte();
- } while (data[i] == 0);
- }
- data[key->bytes - length - 1] = 0;
-
- b1 = bignum_from_bytes(data, key->bytes);
-
- b2 = modpow(b1, key->exponent, key->modulus);
-
- p = data;
- for (i = key->bytes; i--;) {
- *p++ = bignum_byte(b2, i);
- }
-
- freebn(b1);
- freebn(b2);
-
- return 1;
-}
-
-static void sha512_mpint(SHA512_State * s, Bignum b)
-{
- unsigned char lenbuf[4];
- int len;
- len = (bignum_bitcount(b) + 8) / 8;
- PUT_32BIT(lenbuf, len);
- SHA512_Bytes(s, lenbuf, 4);
- while (len-- > 0) {
- lenbuf[0] = bignum_byte(b, len);
- SHA512_Bytes(s, lenbuf, 1);
- }
- memset(lenbuf, 0, sizeof(lenbuf));
-}
-
-/*
- * Compute (base ^ exp) % mod, provided mod == p * q, with p,q
- * distinct primes, and iqmp is the multiplicative inverse of q mod p.
- * Uses Chinese Remainder Theorem to speed computation up over the
- * obvious implementation of a single big modpow.
- */
-Bignum crt_modpow(Bignum base, Bignum exp, Bignum mod,
- Bignum p, Bignum q, Bignum iqmp)
-{
- Bignum pm1, qm1, pexp, qexp, presult, qresult, diff, multiplier, ret0, ret;
-
- /*
- * Reduce the exponent mod phi(p) and phi(q), to save time when
- * exponentiating mod p and mod q respectively. Of course, since p
- * and q are prime, phi(p) == p-1 and similarly for q.
- */
- pm1 = copybn(p);
- decbn(pm1);
- qm1 = copybn(q);
- decbn(qm1);
- pexp = bigmod(exp, pm1);
- qexp = bigmod(exp, qm1);
-
- /*
- * Do the two modpows.
- */
- presult = modpow(base, pexp, p);
- qresult = modpow(base, qexp, q);
-
- /*
- * Recombine the results. We want a value which is congruent to
- * qresult mod q, and to presult mod p.
- *
- * We know that iqmp * q is congruent to 1 * mod p (by definition
- * of iqmp) and to 0 mod q (obviously). So we start with qresult
- * (which is congruent to qresult mod both primes), and add on
- * (presult-qresult) * (iqmp * q) which adjusts it to be congruent
- * to presult mod p without affecting its value mod q.
- */
- if (bignum_cmp(presult, qresult) < 0) {
- /*
- * Can't subtract presult from qresult without first adding on
- * p.
- */
- Bignum tmp = presult;
- presult = bigadd(presult, p);
- freebn(tmp);
- }
- diff = bigsub(presult, qresult);
- multiplier = bigmul(iqmp, q);
- ret0 = bigmuladd(multiplier, diff, qresult);
-
- /*
- * Finally, reduce the result mod n.
- */
- ret = bigmod(ret0, mod);
-
- /*
- * Free all the intermediate results before returning.
- */
- freebn(pm1);
- freebn(qm1);
- freebn(pexp);
- freebn(qexp);
- freebn(presult);
- freebn(qresult);
- freebn(diff);
- freebn(multiplier);
- freebn(ret0);
-
- return ret;
-}
-
-/*
- * This function is a wrapper on modpow(). It has the same effect as
- * modpow(), but employs RSA blinding to protect against timing
- * attacks and also uses the Chinese Remainder Theorem (implemented
- * above, in crt_modpow()) to speed up the main operation.
- */
-static Bignum rsa_privkey_op(Bignum input, struct RSAKey *key)
-{
- Bignum random, random_encrypted, random_inverse;
- Bignum input_blinded, ret_blinded;
- Bignum ret;
-
- SHA512_State ss;
- unsigned char digest512[64];
- int digestused = lenof(digest512);
- int hashseq = 0;
-
- /*
- * Start by inventing a random number chosen uniformly from the
- * range 2..modulus-1. (We do this by preparing a random number
- * of the right length and retrying if it's greater than the
- * modulus, to prevent any potential Bleichenbacher-like
- * attacks making use of the uneven distribution within the
- * range that would arise from just reducing our number mod n.
- * There are timing implications to the potential retries, of
- * course, but all they tell you is the modulus, which you
- * already knew.)
- *
- * To preserve determinism and avoid Pageant needing to share
- * the random number pool, we actually generate this `random'
- * number by hashing stuff with the private key.
- */
- while (1) {
- int bits, byte, bitsleft, v;
- random = copybn(key->modulus);
- /*
- * Find the topmost set bit. (This function will return its
- * index plus one.) Then we'll set all bits from that one
- * downwards randomly.
- */
- bits = bignum_bitcount(random);
- byte = 0;
- bitsleft = 0;
- while (bits--) {
- if (bitsleft <= 0) {
- bitsleft = 8;
- /*
- * Conceptually the following few lines are equivalent to
- * byte = random_byte();
- */
- if (digestused >= lenof(digest512)) {
- unsigned char seqbuf[4];
- PUT_32BIT(seqbuf, hashseq);
- SHA512_Init(&ss);
- SHA512_Bytes(&ss, "RSA deterministic blinding", 26);
- SHA512_Bytes(&ss, seqbuf, sizeof(seqbuf));
- sha512_mpint(&ss, key->private_exponent);
- SHA512_Final(&ss, digest512);
- hashseq++;
-
- /*
- * Now hash that digest plus the signature
- * input.
- */
- SHA512_Init(&ss);
- SHA512_Bytes(&ss, digest512, sizeof(digest512));
- sha512_mpint(&ss, input);
- SHA512_Final(&ss, digest512);
-
- digestused = 0;
- }
- byte = digest512[digestused++];
- }
- v = byte & 1;
- byte >>= 1;
- bitsleft--;
- bignum_set_bit(random, bits, v);
- }
-
- /*
- * Now check that this number is strictly greater than
- * zero, and strictly less than modulus.
- */
- if (bignum_cmp(random, Zero) <= 0 ||
- bignum_cmp(random, key->modulus) >= 0) {
- freebn(random);
- continue;
- } else {
- break;
- }
- }
-
- /*
- * RSA blinding relies on the fact that (xy)^d mod n is equal
- * to (x^d mod n) * (y^d mod n) mod n. We invent a random pair
- * y and y^d; then we multiply x by y, raise to the power d mod
- * n as usual, and divide by y^d to recover x^d. Thus an
- * attacker can't correlate the timing of the modpow with the
- * input, because they don't know anything about the number
- * that was input to the actual modpow.
- *
- * The clever bit is that we don't have to do a huge modpow to
- * get y and y^d; we will use the number we just invented as
- * _y^d_, and use the _public_ exponent to compute (y^d)^e = y
- * from it, which is much faster to do.
- */
- random_encrypted = crt_modpow(random, key->exponent,
- key->modulus, key->p, key->q, key->iqmp);
- random_inverse = modinv(random, key->modulus);
- input_blinded = modmul(input, random_encrypted, key->modulus);
- ret_blinded = crt_modpow(input_blinded, key->private_exponent,
- key->modulus, key->p, key->q, key->iqmp);
- ret = modmul(ret_blinded, random_inverse, key->modulus);
-
- freebn(ret_blinded);
- freebn(input_blinded);
- freebn(random_inverse);
- freebn(random_encrypted);
- freebn(random);
-
- return ret;
-}
-
-Bignum rsadecrypt(Bignum input, struct RSAKey *key)
-{
- return rsa_privkey_op(input, key);
-}
-
-int rsastr_len(struct RSAKey *key)
-{
- Bignum md, ex;
- int mdlen, exlen;
-
- md = key->modulus;
- ex = key->exponent;
- mdlen = (bignum_bitcount(md) + 15) / 16;
- exlen = (bignum_bitcount(ex) + 15) / 16;
- return 4 * (mdlen + exlen) + 20;
-}
-
-void rsastr_fmt(char *str, struct RSAKey *key)
-{
- Bignum md, ex;
- int len = 0, i, nibbles;
- static const char hex[] = "0123456789abcdef";
-
- md = key->modulus;
- ex = key->exponent;
-
- len += sprintf(str + len, "0x");
-
- nibbles = (3 + bignum_bitcount(ex)) / 4;
- if (nibbles < 1)
- nibbles = 1;
- for (i = nibbles; i--;)
- str[len++] = hex[(bignum_byte(ex, i / 2) >> (4 * (i % 2))) & 0xF];
-
- len += sprintf(str + len, ",0x");
-
- nibbles = (3 + bignum_bitcount(md)) / 4;
- if (nibbles < 1)
- nibbles = 1;
- for (i = nibbles; i--;)
- str[len++] = hex[(bignum_byte(md, i / 2) >> (4 * (i % 2))) & 0xF];
-
- str[len] = '\0';
-}
-
-/*
- * Generate a fingerprint string for the key. Compatible with the
- * OpenSSH fingerprint code.
- */
-void rsa_fingerprint(char *str, int len, struct RSAKey *key)
-{
- struct MD5Context md5c;
- unsigned char digest[16];
- char buffer[16 * 3 + 40];
- int numlen, slen, i;
-
- MD5Init(&md5c);
- numlen = ssh1_bignum_length(key->modulus) - 2;
- for (i = numlen; i--;) {
- unsigned char c = bignum_byte(key->modulus, i);
- MD5Update(&md5c, &c, 1);
- }
- numlen = ssh1_bignum_length(key->exponent) - 2;
- for (i = numlen; i--;) {
- unsigned char c = bignum_byte(key->exponent, i);
- MD5Update(&md5c, &c, 1);
- }
- MD5Final(digest, &md5c);
-
- sprintf(buffer, "%d ", bignum_bitcount(key->modulus));
- for (i = 0; i < 16; i++)
- sprintf(buffer + strlen(buffer), "%s%02x", i ? ":" : "",
- digest[i]);
- strncpy(str, buffer, len);
- str[len - 1] = '\0';
- slen = strlen(str);
- if (key->comment && slen < len - 1) {
- str[slen] = ' ';
- strncpy(str + slen + 1, key->comment, len - slen - 1);
- str[len - 1] = '\0';
- }
-}
-
-/*
- * Verify that the public data in an RSA key matches the private
- * data. We also check the private data itself: we ensure that p >
- * q and that iqmp really is the inverse of q mod p.
- */
-int rsa_verify(struct RSAKey *key)
-{
- Bignum n, ed, pm1, qm1;
- int cmp;
-
- /* n must equal pq. */
- n = bigmul(key->p, key->q);
- cmp = bignum_cmp(n, key->modulus);
- freebn(n);
- if (cmp != 0)
- return 0;
-
- /* e * d must be congruent to 1, modulo (p-1) and modulo (q-1). */
- pm1 = copybn(key->p);
- decbn(pm1);
- ed = modmul(key->exponent, key->private_exponent, pm1);
- cmp = bignum_cmp(ed, One);
- sfree(ed);
- if (cmp != 0)
- return 0;
-
- qm1 = copybn(key->q);
- decbn(qm1);
- ed = modmul(key->exponent, key->private_exponent, qm1);
- cmp = bignum_cmp(ed, One);
- sfree(ed);
- if (cmp != 0)
- return 0;
-
- /*
- * Ensure p > q.
- *
- * I have seen key blobs in the wild which were generated with
- * p < q, so instead of rejecting the key in this case we
- * should instead flip them round into the canonical order of
- * p > q. This also involves regenerating iqmp.
- */
- if (bignum_cmp(key->p, key->q) <= 0) {
- Bignum tmp = key->p;
- key->p = key->q;
- key->q = tmp;
-
- freebn(key->iqmp);
- key->iqmp = modinv(key->q, key->p);
- }
-
- /*
- * Ensure iqmp * q is congruent to 1, modulo p.
- */
- n = modmul(key->iqmp, key->q, key->p);
- cmp = bignum_cmp(n, One);
- sfree(n);
- if (cmp != 0)
- return 0;
-
- return 1;
-}
-
-/* Public key blob as used by Pageant: exponent before modulus. */
-unsigned char *rsa_public_blob(struct RSAKey *key, int *len)
-{
- int length, pos;
- unsigned char *ret;
-
- length = (ssh1_bignum_length(key->modulus) +
- ssh1_bignum_length(key->exponent) + 4);
- ret = snewn(length, unsigned char);
-
- PUT_32BIT(ret, bignum_bitcount(key->modulus));
- pos = 4;
- pos += ssh1_write_bignum(ret + pos, key->exponent);
- pos += ssh1_write_bignum(ret + pos, key->modulus);
-
- *len = length;
- return ret;
-}
-
-/* Given a public blob, determine its length. */
-int rsa_public_blob_len(void *data, int maxlen)
-{
- unsigned char *p = (unsigned char *)data;
- int n;
-
- if (maxlen < 4)
- return -1;
- p += 4; /* length word */
- maxlen -= 4;
-
- n = ssh1_read_bignum(p, maxlen, NULL); /* exponent */
- if (n < 0)
- return -1;
- p += n;
-
- n = ssh1_read_bignum(p, maxlen, NULL); /* modulus */
- if (n < 0)
- return -1;
- p += n;
-
- return p - (unsigned char *)data;
-}
-
-void freersakey(struct RSAKey *key)
-{
- if (key->modulus)
- freebn(key->modulus);
- if (key->exponent)
- freebn(key->exponent);
- if (key->private_exponent)
- freebn(key->private_exponent);
- if (key->p)
- freebn(key->p);
- if (key->q)
- freebn(key->q);
- if (key->iqmp)
- freebn(key->iqmp);
- if (key->comment)
- sfree(key->comment);
-}
-
-/* ----------------------------------------------------------------------
- * Implementation of the ssh-rsa signing key type.
- */
-
-static void getstring(char **data, int *datalen, char **p, int *length)
-{
- *p = NULL;
- if (*datalen < 4)
- return;
- *length = GET_32BIT(*data);
- *datalen -= 4;
- *data += 4;
- if (*datalen < *length)
- return;
- *p = *data;
- *data += *length;
- *datalen -= *length;
-}
-static Bignum getmp(char **data, int *datalen)
-{
- char *p;
- int length;
- Bignum b;
-
- getstring(data, datalen, &p, &length);
- if (!p)
- return NULL;
- b = bignum_from_bytes((unsigned char *)p, length);
- return b;
-}
-
-static void *rsa2_newkey(char *data, int len)
-{
- char *p;
- int slen;
- struct RSAKey *rsa;
-
- rsa = snew(struct RSAKey);
- if (!rsa)
- return NULL;
- getstring(&data, &len, &p, &slen);
-
- if (!p || slen != 7 || memcmp(p, "ssh-rsa", 7)) {
- sfree(rsa);
- return NULL;
- }
- rsa->exponent = getmp(&data, &len);
- rsa->modulus = getmp(&data, &len);
- rsa->private_exponent = NULL;
- rsa->p = rsa->q = rsa->iqmp = NULL;
- rsa->comment = NULL;
-
- return rsa;
-}
-
-static void rsa2_freekey(void *key)
-{
- struct RSAKey *rsa = (struct RSAKey *) key;
- freersakey(rsa);
- sfree(rsa);
-}
-
-static char *rsa2_fmtkey(void *key)
-{
- struct RSAKey *rsa = (struct RSAKey *) key;
- char *p;
- int len;
-
- len = rsastr_len(rsa);
- p = snewn(len, char);
- rsastr_fmt(p, rsa);
- return p;
-}
-
-static unsigned char *rsa2_public_blob(void *key, int *len)
-{
- struct RSAKey *rsa = (struct RSAKey *) key;
- int elen, mlen, bloblen;
- int i;
- unsigned char *blob, *p;
-
- elen = (bignum_bitcount(rsa->exponent) + 8) / 8;
- mlen = (bignum_bitcount(rsa->modulus) + 8) / 8;
-
- /*
- * string "ssh-rsa", mpint exp, mpint mod. Total 19+elen+mlen.
- * (three length fields, 12+7=19).
- */
- bloblen = 19 + elen + mlen;
- blob = snewn(bloblen, unsigned char);
- p = blob;
- PUT_32BIT(p, 7);
- p += 4;
- memcpy(p, "ssh-rsa", 7);
- p += 7;
- PUT_32BIT(p, elen);
- p += 4;
- for (i = elen; i--;)
- *p++ = bignum_byte(rsa->exponent, i);
- PUT_32BIT(p, mlen);
- p += 4;
- for (i = mlen; i--;)
- *p++ = bignum_byte(rsa->modulus, i);
- assert(p == blob + bloblen);
- *len = bloblen;
- return blob;
-}
-
-static unsigned char *rsa2_private_blob(void *key, int *len)
-{
- struct RSAKey *rsa = (struct RSAKey *) key;
- int dlen, plen, qlen, ulen, bloblen;
- int i;
- unsigned char *blob, *p;
-
- dlen = (bignum_bitcount(rsa->private_exponent) + 8) / 8;
- plen = (bignum_bitcount(rsa->p) + 8) / 8;
- qlen = (bignum_bitcount(rsa->q) + 8) / 8;
- ulen = (bignum_bitcount(rsa->iqmp) + 8) / 8;
-
- /*
- * mpint private_exp, mpint p, mpint q, mpint iqmp. Total 16 +
- * sum of lengths.
- */
- bloblen = 16 + dlen + plen + qlen + ulen;
- blob = snewn(bloblen, unsigned char);
- p = blob;
- PUT_32BIT(p, dlen);
- p += 4;
- for (i = dlen; i--;)
- *p++ = bignum_byte(rsa->private_exponent, i);
- PUT_32BIT(p, plen);
- p += 4;
- for (i = plen; i--;)
- *p++ = bignum_byte(rsa->p, i);
- PUT_32BIT(p, qlen);
- p += 4;
- for (i = qlen; i--;)
- *p++ = bignum_byte(rsa->q, i);
- PUT_32BIT(p, ulen);
- p += 4;
- for (i = ulen; i--;)
- *p++ = bignum_byte(rsa->iqmp, i);
- assert(p == blob + bloblen);
- *len = bloblen;
- return blob;
-}
-
-static void *rsa2_createkey(unsigned char *pub_blob, int pub_len,
- unsigned char *priv_blob, int priv_len)
-{
- struct RSAKey *rsa;
- char *pb = (char *) priv_blob;
-
- rsa = rsa2_newkey((char *) pub_blob, pub_len);
- rsa->private_exponent = getmp(&pb, &priv_len);
- rsa->p = getmp(&pb, &priv_len);
- rsa->q = getmp(&pb, &priv_len);
- rsa->iqmp = getmp(&pb, &priv_len);
-
- if (!rsa_verify(rsa)) {
- rsa2_freekey(rsa);
- return NULL;
- }
-
- return rsa;
-}
-
-static void *rsa2_openssh_createkey(unsigned char **blob, int *len)
-{
- char **b = (char **) blob;
- struct RSAKey *rsa;
-
- rsa = snew(struct RSAKey);
- if (!rsa)
- return NULL;
- rsa->comment = NULL;
-
- rsa->modulus = getmp(b, len);
- rsa->exponent = getmp(b, len);
- rsa->private_exponent = getmp(b, len);
- rsa->iqmp = getmp(b, len);
- rsa->p = getmp(b, len);
- rsa->q = getmp(b, len);
-
- if (!rsa->modulus || !rsa->exponent || !rsa->private_exponent ||
- !rsa->iqmp || !rsa->p || !rsa->q) {
- sfree(rsa->modulus);
- sfree(rsa->exponent);
- sfree(rsa->private_exponent);
- sfree(rsa->iqmp);
- sfree(rsa->p);
- sfree(rsa->q);
- sfree(rsa);
- return NULL;
- }
-
- return rsa;
-}
-
-static int rsa2_openssh_fmtkey(void *key, unsigned char *blob, int len)
-{
- struct RSAKey *rsa = (struct RSAKey *) key;
- int bloblen, i;
-
- bloblen =
- ssh2_bignum_length(rsa->modulus) +
- ssh2_bignum_length(rsa->exponent) +
- ssh2_bignum_length(rsa->private_exponent) +
- ssh2_bignum_length(rsa->iqmp) +
- ssh2_bignum_length(rsa->p) + ssh2_bignum_length(rsa->q);
-
- if (bloblen > len)
- return bloblen;
-
- bloblen = 0;
-#define ENC(x) \
- PUT_32BIT(blob+bloblen, ssh2_bignum_length((x))-4); bloblen += 4; \
- for (i = ssh2_bignum_length((x))-4; i-- ;) blob[bloblen++]=bignum_byte((x),i);
- ENC(rsa->modulus);
- ENC(rsa->exponent);
- ENC(rsa->private_exponent);
- ENC(rsa->iqmp);
- ENC(rsa->p);
- ENC(rsa->q);
-
- return bloblen;
-}
-
-static int rsa2_pubkey_bits(void *blob, int len)
-{
- struct RSAKey *rsa;
- int ret;
-
- rsa = rsa2_newkey((char *) blob, len);
- ret = bignum_bitcount(rsa->modulus);
- rsa2_freekey(rsa);
-
- 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
- * value. The meaning of it is:
- *
- * 00 -- this marks the end of the FFs; not part of the ASN.1 bit itself
- *
- * 30 21 -- a constructed SEQUENCE of length 0x21
- * 30 09 -- a constructed sub-SEQUENCE of length 9
- * 06 05 -- an object identifier, length 5
- * 2B 0E 03 02 1A -- object id { 1 3 14 3 2 26 }
- * (the 1,3 comes from 0x2B = 43 = 40*1+3)
- * 05 00 -- NULL
- * 04 14 -- a primitive OCTET STRING of length 0x14
- * [0x14 bytes of hash data follows]
- *
- * The object id in the middle there is listed as `id-sha1' in
- * ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1d2.asn (the
- * ASN module for PKCS #1) and its expanded form is as follows:
- *
- * id-sha1 OBJECT IDENTIFIER ::= {
- * iso(1) identified-organization(3) oiw(14) secsig(3)
- * algorithms(2) 26 }
- */
-static const unsigned char asn1_weird_stuff[] = {
- 0x00, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B,
- 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14,
-};
-
-#define ASN1_LEN ( (int) sizeof(asn1_weird_stuff) )
-
-static int rsa2_verifysig(void *key, char *sig, int siglen,
- char *data, int datalen)
-{
- struct RSAKey *rsa = (struct RSAKey *) key;
- Bignum in, out;
- char *p;
- int slen;
- int bytes, i, j, ret;
- unsigned char hash[20];
-
- getstring(&sig, &siglen, &p, &slen);
- if (!p || slen != 7 || memcmp(p, "ssh-rsa", 7)) {
- return 0;
- }
- in = getmp(&sig, &siglen);
- out = modpow(in, rsa->exponent, rsa->modulus);
- freebn(in);
-
- ret = 1;
-
- bytes = (bignum_bitcount(rsa->modulus)+7) / 8;
- /* Top (partial) byte should be zero. */
- if (bignum_byte(out, bytes - 1) != 0)
- ret = 0;
- /* First whole byte should be 1. */
- if (bignum_byte(out, bytes - 2) != 1)
- ret = 0;
- /* Most of the rest should be FF. */
- for (i = bytes - 3; i >= 20 + ASN1_LEN; i--) {
- if (bignum_byte(out, i) != 0xFF)
- ret = 0;
- }
- /* Then we expect to see the asn1_weird_stuff. */
- for (i = 20 + ASN1_LEN - 1, j = 0; i >= 20; i--, j++) {
- if (bignum_byte(out, i) != asn1_weird_stuff[j])
- ret = 0;
- }
- /* Finally, we expect to see the SHA-1 hash of the signed data. */
- SHA_Simple(data, datalen, hash);
- for (i = 19, j = 0; i >= 0; i--, j++) {
- if (bignum_byte(out, i) != hash[j])
- ret = 0;
- }
- freebn(out);
-
- return ret;
-}
-
-static unsigned char *rsa2_sign(void *key, char *data, int datalen,
- int *siglen)
-{
- struct RSAKey *rsa = (struct RSAKey *) key;
- unsigned char *bytes;
- int nbytes;
- unsigned char hash[20];
- Bignum in, out;
- int i, j;
-
- SHA_Simple(data, datalen, hash);
-
- nbytes = (bignum_bitcount(rsa->modulus) - 1) / 8;
- assert(1 <= nbytes - 20 - ASN1_LEN);
- bytes = snewn(nbytes, unsigned char);
-
- bytes[0] = 1;
- for (i = 1; i < nbytes - 20 - ASN1_LEN; i++)
- bytes[i] = 0xFF;
- for (i = nbytes - 20 - ASN1_LEN, j = 0; i < nbytes - 20; i++, j++)
- bytes[i] = asn1_weird_stuff[j];
- for (i = nbytes - 20, j = 0; i < nbytes; i++, j++)
- bytes[i] = hash[j];
-
- in = bignum_from_bytes(bytes, nbytes);
- sfree(bytes);
-
- out = rsa_privkey_op(in, rsa);
- freebn(in);
-
- nbytes = (bignum_bitcount(out) + 7) / 8;
- bytes = snewn(4 + 7 + 4 + nbytes, unsigned char);
- PUT_32BIT(bytes, 7);
- memcpy(bytes + 4, "ssh-rsa", 7);
- PUT_32BIT(bytes + 4 + 7, nbytes);
- for (i = 0; i < nbytes; i++)
- bytes[4 + 7 + 4 + i] = bignum_byte(out, nbytes - 1 - i);
- freebn(out);
-
- *siglen = 4 + 7 + 4 + nbytes;
- return bytes;
-}
-
-const struct ssh_signkey ssh_rsa = {
- rsa2_newkey,
- rsa2_freekey,
- rsa2_fmtkey,
- rsa2_public_blob,
- rsa2_private_blob,
- rsa2_createkey,
- rsa2_openssh_createkey,
- rsa2_openssh_fmtkey,
- rsa2_pubkey_bits,
- rsa2_fingerprint,
- rsa2_verifysig,
- rsa2_sign,
- "ssh-rsa",
- "rsa2"
-};
-
-void *ssh_rsakex_newkey(char *data, int len)
-{
- return rsa2_newkey(data, len);
-}
-
-void ssh_rsakex_freekey(void *key)
-{
- rsa2_freekey(key);
-}
-
-int ssh_rsakex_klen(void *key)
-{
- struct RSAKey *rsa = (struct RSAKey *) key;
-
- return bignum_bitcount(rsa->modulus);
-}
-
-static void oaep_mask(const struct ssh_hash *h, void *seed, int seedlen,
- void *vdata, int datalen)
-{
- unsigned char *data = (unsigned char *)vdata;
- unsigned count = 0;
-
- while (datalen > 0) {
- int i, max = (datalen > h->hlen ? h->hlen : datalen);
- void *s;
- unsigned char counter[4], hash[SSH2_KEX_MAX_HASH_LEN];
-
- assert(h->hlen <= SSH2_KEX_MAX_HASH_LEN);
- PUT_32BIT(counter, count);
- s = h->init();
- h->bytes(s, seed, seedlen);
- h->bytes(s, counter, 4);
- h->final(s, hash);
- count++;
-
- for (i = 0; i < max; i++)
- data[i] ^= hash[i];
-
- data += max;
- datalen -= max;
- }
-}
-
-void ssh_rsakex_encrypt(const struct ssh_hash *h, unsigned char *in, int inlen,
- unsigned char *out, int outlen,
- void *key)
-{
- Bignum b1, b2;
- struct RSAKey *rsa = (struct RSAKey *) key;
- int k, i;
- char *p;
- const int HLEN = h->hlen;
-
- /*
- * Here we encrypt using RSAES-OAEP. Essentially this means:
- *
- * - we have a SHA-based `mask generation function' which
- * creates a pseudo-random stream of mask data
- * deterministically from an input chunk of data.
- *
- * - we have a random chunk of data called a seed.
- *
- * - we use the seed to generate a mask which we XOR with our
- * plaintext.
- *
- * - then we use _the masked plaintext_ to generate a mask
- * which we XOR with the seed.
- *
- * - then we concatenate the masked seed and the masked
- * plaintext, and RSA-encrypt that lot.
- *
- * The result is that the data input to the encryption function
- * is random-looking and (hopefully) contains no exploitable
- * structure such as PKCS1-v1_5 does.
- *
- * For a precise specification, see RFC 3447, section 7.1.1.
- * Some of the variable names below are derived from that, so
- * it'd probably help to read it anyway.
- */
-
- /* k denotes the length in octets of the RSA modulus. */
- k = (7 + bignum_bitcount(rsa->modulus)) / 8;
-
- /* The length of the input data must be at most k - 2hLen - 2. */
- assert(inlen > 0 && inlen <= k - 2*HLEN - 2);
-
- /* The length of the output data wants to be precisely k. */
- assert(outlen == k);
-
- /*
- * Now perform EME-OAEP encoding. First set up all the unmasked
- * output data.
- */
- /* Leading byte zero. */
- out[0] = 0;
- /* At position 1, the seed: HLEN bytes of random data. */
- for (i = 0; i < HLEN; i++)
- out[i + 1] = random_byte();
- /* At position 1+HLEN, the data block DB, consisting of: */
- /* The hash of the label (we only support an empty label here) */
- h->final(h->init(), out + HLEN + 1);
- /* A bunch of zero octets */
- memset(out + 2*HLEN + 1, 0, outlen - (2*HLEN + 1));
- /* A single 1 octet, followed by the input message data. */
- out[outlen - inlen - 1] = 1;
- memcpy(out + outlen - inlen, in, inlen);
-
- /*
- * Now use the seed data to mask the block DB.
- */
- oaep_mask(h, out+1, HLEN, out+HLEN+1, outlen-HLEN-1);
-
- /*
- * And now use the masked DB to mask the seed itself.
- */
- oaep_mask(h, out+HLEN+1, outlen-HLEN-1, out+1, HLEN);
-
- /*
- * Now `out' contains precisely the data we want to
- * RSA-encrypt.
- */
- b1 = bignum_from_bytes(out, outlen);
- b2 = modpow(b1, rsa->exponent, rsa->modulus);
- p = (char *)out;
- for (i = outlen; i--;) {
- *p++ = bignum_byte(b2, i);
- }
- freebn(b1);
- freebn(b2);
-
- /*
- * And we're done.
- */
-}
-
-static const struct ssh_kex ssh_rsa_kex_sha1 = {
- "rsa1024-sha1", NULL, KEXTYPE_RSA, NULL, NULL, 0, 0, &ssh_sha1
-};
-
-static const struct ssh_kex ssh_rsa_kex_sha256 = {
- "rsa2048-sha256", NULL, KEXTYPE_RSA, NULL, NULL, 0, 0, &ssh_sha256
-};
-
-static const struct ssh_kex *const rsa_kex_list[] = {
- &ssh_rsa_kex_sha256,
- &ssh_rsa_kex_sha1
-};
-
-const struct ssh_kexes ssh_rsa_kex = {
- sizeof(rsa_kex_list) / sizeof(*rsa_kex_list),
- rsa_kex_list
-};
+/*
+ * RSA implementation for PuTTY.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "ssh.h"
+#include "misc.h"
+
+int makekey(unsigned char *data, int len, struct RSAKey *result,
+ unsigned char **keystr, int order)
+{
+ unsigned char *p = data;
+ int i, n;
+
+ if (len < 4)
+ return -1;
+
+ if (result) {
+ result->bits = 0;
+ for (i = 0; i < 4; i++)
+ result->bits = (result->bits << 8) + *p++;
+ } else
+ p += 4;
+
+ len -= 4;
+
+ /*
+ * order=0 means exponent then modulus (the keys sent by the
+ * server). order=1 means modulus then exponent (the keys
+ * stored in a keyfile).
+ */
+
+ if (order == 0) {
+ n = ssh1_read_bignum(p, len, result ? &result->exponent : NULL);
+ if (n < 0) return -1;
+ p += n;
+ len -= n;
+ }
+
+ n = ssh1_read_bignum(p, len, result ? &result->modulus : NULL);
+ if (n < 0 || (result && bignum_bitcount(result->modulus) == 0)) return -1;
+ if (result)
+ result->bytes = n - 2;
+ if (keystr)
+ *keystr = p + 2;
+ p += n;
+ len -= n;
+
+ if (order == 1) {
+ n = ssh1_read_bignum(p, len, result ? &result->exponent : NULL);
+ if (n < 0) return -1;
+ p += n;
+ len -= n;
+ }
+ return p - data;
+}
+
+int makeprivate(unsigned char *data, int len, struct RSAKey *result)
+{
+ return ssh1_read_bignum(data, len, &result->private_exponent);
+}
+
+int rsaencrypt(unsigned char *data, int length, struct RSAKey *key)
+{
+ Bignum b1, b2;
+ int i;
+ unsigned char *p;
+
+ if (key->bytes < length + 4)
+ return 0; /* RSA key too short! */
+
+ memmove(data + key->bytes - length, data, length);
+ data[0] = 0;
+ data[1] = 2;
+
+ for (i = 2; i < key->bytes - length - 1; i++) {
+ do {
+ data[i] = random_byte();
+ } while (data[i] == 0);
+ }
+ data[key->bytes - length - 1] = 0;
+
+ b1 = bignum_from_bytes(data, key->bytes);
+
+ b2 = modpow(b1, key->exponent, key->modulus);
+
+ p = data;
+ for (i = key->bytes; i--;) {
+ *p++ = bignum_byte(b2, i);
+ }
+
+ freebn(b1);
+ freebn(b2);
+
+ return 1;
+}
+
+static void sha512_mpint(SHA512_State * s, Bignum b)
+{
+ unsigned char lenbuf[4];
+ int len;
+ len = (bignum_bitcount(b) + 8) / 8;
+ PUT_32BIT(lenbuf, len);
+ SHA512_Bytes(s, lenbuf, 4);
+ while (len-- > 0) {
+ lenbuf[0] = bignum_byte(b, len);
+ SHA512_Bytes(s, lenbuf, 1);
+ }
+ smemclr(lenbuf, sizeof(lenbuf));
+}
+
+/*
+ * Compute (base ^ exp) % mod, provided mod == p * q, with p,q
+ * distinct primes, and iqmp is the multiplicative inverse of q mod p.
+ * Uses Chinese Remainder Theorem to speed computation up over the
+ * obvious implementation of a single big modpow.
+ */
+Bignum crt_modpow(Bignum base, Bignum exp, Bignum mod,
+ Bignum p, Bignum q, Bignum iqmp)
+{
+ Bignum pm1, qm1, pexp, qexp, presult, qresult, diff, multiplier, ret0, ret;
+
+ /*
+ * Reduce the exponent mod phi(p) and phi(q), to save time when
+ * exponentiating mod p and mod q respectively. Of course, since p
+ * and q are prime, phi(p) == p-1 and similarly for q.
+ */
+ pm1 = copybn(p);
+ decbn(pm1);
+ qm1 = copybn(q);
+ decbn(qm1);
+ pexp = bigmod(exp, pm1);
+ qexp = bigmod(exp, qm1);
+
+ /*
+ * Do the two modpows.
+ */
+ presult = modpow(base, pexp, p);
+ qresult = modpow(base, qexp, q);
+
+ /*
+ * Recombine the results. We want a value which is congruent to
+ * qresult mod q, and to presult mod p.
+ *
+ * We know that iqmp * q is congruent to 1 * mod p (by definition
+ * of iqmp) and to 0 mod q (obviously). So we start with qresult
+ * (which is congruent to qresult mod both primes), and add on
+ * (presult-qresult) * (iqmp * q) which adjusts it to be congruent
+ * to presult mod p without affecting its value mod q.
+ */
+ if (bignum_cmp(presult, qresult) < 0) {
+ /*
+ * Can't subtract presult from qresult without first adding on
+ * p.
+ */
+ Bignum tmp = presult;
+ presult = bigadd(presult, p);
+ freebn(tmp);
+ }
+ diff = bigsub(presult, qresult);
+ multiplier = bigmul(iqmp, q);
+ ret0 = bigmuladd(multiplier, diff, qresult);
+
+ /*
+ * Finally, reduce the result mod n.
+ */
+ ret = bigmod(ret0, mod);
+
+ /*
+ * Free all the intermediate results before returning.
+ */
+ freebn(pm1);
+ freebn(qm1);
+ freebn(pexp);
+ freebn(qexp);
+ freebn(presult);
+ freebn(qresult);
+ freebn(diff);
+ freebn(multiplier);
+ freebn(ret0);
+
+ return ret;
+}
+
+/*
+ * This function is a wrapper on modpow(). It has the same effect as
+ * modpow(), but employs RSA blinding to protect against timing
+ * attacks and also uses the Chinese Remainder Theorem (implemented
+ * above, in crt_modpow()) to speed up the main operation.
+ */
+static Bignum rsa_privkey_op(Bignum input, struct RSAKey *key)
+{
+ Bignum random, random_encrypted, random_inverse;
+ Bignum input_blinded, ret_blinded;
+ Bignum ret;
+
+ SHA512_State ss;
+ unsigned char digest512[64];
+ int digestused = lenof(digest512);
+ int hashseq = 0;
+
+ /*
+ * Start by inventing a random number chosen uniformly from the
+ * range 2..modulus-1. (We do this by preparing a random number
+ * of the right length and retrying if it's greater than the
+ * modulus, to prevent any potential Bleichenbacher-like
+ * attacks making use of the uneven distribution within the
+ * range that would arise from just reducing our number mod n.
+ * There are timing implications to the potential retries, of
+ * course, but all they tell you is the modulus, which you
+ * already knew.)
+ *
+ * To preserve determinism and avoid Pageant needing to share
+ * the random number pool, we actually generate this `random'
+ * number by hashing stuff with the private key.
+ */
+ while (1) {
+ int bits, byte, bitsleft, v;
+ random = copybn(key->modulus);
+ /*
+ * Find the topmost set bit. (This function will return its
+ * index plus one.) Then we'll set all bits from that one
+ * downwards randomly.
+ */
+ bits = bignum_bitcount(random);
+ byte = 0;
+ bitsleft = 0;
+ while (bits--) {
+ if (bitsleft <= 0) {
+ bitsleft = 8;
+ /*
+ * Conceptually the following few lines are equivalent to
+ * byte = random_byte();
+ */
+ if (digestused >= lenof(digest512)) {
+ unsigned char seqbuf[4];
+ PUT_32BIT(seqbuf, hashseq);
+ SHA512_Init(&ss);
+ SHA512_Bytes(&ss, "RSA deterministic blinding", 26);
+ SHA512_Bytes(&ss, seqbuf, sizeof(seqbuf));
+ sha512_mpint(&ss, key->private_exponent);
+ SHA512_Final(&ss, digest512);
+ hashseq++;
+
+ /*
+ * Now hash that digest plus the signature
+ * input.
+ */
+ SHA512_Init(&ss);
+ SHA512_Bytes(&ss, digest512, sizeof(digest512));
+ sha512_mpint(&ss, input);
+ SHA512_Final(&ss, digest512);
+
+ digestused = 0;
+ }
+ byte = digest512[digestused++];
+ }
+ v = byte & 1;
+ byte >>= 1;
+ bitsleft--;
+ bignum_set_bit(random, bits, v);
+ }
+ bn_restore_invariant(random);
+
+ /*
+ * Now check that this number is strictly greater than
+ * zero, and strictly less than modulus.
+ */
+ if (bignum_cmp(random, Zero) <= 0 ||
+ bignum_cmp(random, key->modulus) >= 0) {
+ freebn(random);
+ continue;
+ }
+
+ /*
+ * Also, make sure it has an inverse mod modulus.
+ */
+ random_inverse = modinv(random, key->modulus);
+ if (!random_inverse) {
+ freebn(random);
+ continue;
+ }
+
+ break;
+ }
+
+ /*
+ * RSA blinding relies on the fact that (xy)^d mod n is equal
+ * to (x^d mod n) * (y^d mod n) mod n. We invent a random pair
+ * y and y^d; then we multiply x by y, raise to the power d mod
+ * n as usual, and divide by y^d to recover x^d. Thus an
+ * attacker can't correlate the timing of the modpow with the
+ * input, because they don't know anything about the number
+ * that was input to the actual modpow.
+ *
+ * The clever bit is that we don't have to do a huge modpow to
+ * get y and y^d; we will use the number we just invented as
+ * _y^d_, and use the _public_ exponent to compute (y^d)^e = y
+ * from it, which is much faster to do.
+ */
+ random_encrypted = crt_modpow(random, key->exponent,
+ key->modulus, key->p, key->q, key->iqmp);
+ input_blinded = modmul(input, random_encrypted, key->modulus);
+ ret_blinded = crt_modpow(input_blinded, key->private_exponent,
+ key->modulus, key->p, key->q, key->iqmp);
+ ret = modmul(ret_blinded, random_inverse, key->modulus);
+
+ freebn(ret_blinded);
+ freebn(input_blinded);
+ freebn(random_inverse);
+ freebn(random_encrypted);
+ freebn(random);
+
+ return ret;
+}
+
+Bignum rsadecrypt(Bignum input, struct RSAKey *key)
+{
+ return rsa_privkey_op(input, key);
+}
+
+int rsastr_len(struct RSAKey *key)
+{
+ Bignum md, ex;
+ int mdlen, exlen;
+
+ md = key->modulus;
+ ex = key->exponent;
+ mdlen = (bignum_bitcount(md) + 15) / 16;
+ exlen = (bignum_bitcount(ex) + 15) / 16;
+ return 4 * (mdlen + exlen) + 20;
+}
+
+void rsastr_fmt(char *str, struct RSAKey *key)
+{
+ Bignum md, ex;
+ int len = 0, i, nibbles;
+ static const char hex[] = "0123456789abcdef";
+
+ md = key->modulus;
+ ex = key->exponent;
+
+ len += sprintf(str + len, "0x");
+
+ nibbles = (3 + bignum_bitcount(ex)) / 4;
+ if (nibbles < 1)
+ nibbles = 1;
+ for (i = nibbles; i--;)
+ str[len++] = hex[(bignum_byte(ex, i / 2) >> (4 * (i % 2))) & 0xF];
+
+ len += sprintf(str + len, ",0x");
+
+ nibbles = (3 + bignum_bitcount(md)) / 4;
+ if (nibbles < 1)
+ nibbles = 1;
+ for (i = nibbles; i--;)
+ str[len++] = hex[(bignum_byte(md, i / 2) >> (4 * (i % 2))) & 0xF];
+
+ str[len] = '\0';
+}
+
+/*
+ * Generate a fingerprint string for the key. Compatible with the
+ * OpenSSH fingerprint code.
+ */
+void rsa_fingerprint(char *str, int len, struct RSAKey *key)
+{
+ struct MD5Context md5c;
+ unsigned char digest[16];
+ char buffer[16 * 3 + 40];
+ int numlen, slen, i;
+
+ MD5Init(&md5c);
+ numlen = ssh1_bignum_length(key->modulus) - 2;
+ for (i = numlen; i--;) {
+ unsigned char c = bignum_byte(key->modulus, i);
+ MD5Update(&md5c, &c, 1);
+ }
+ numlen = ssh1_bignum_length(key->exponent) - 2;
+ for (i = numlen; i--;) {
+ unsigned char c = bignum_byte(key->exponent, i);
+ MD5Update(&md5c, &c, 1);
+ }
+ MD5Final(digest, &md5c);
+
+ sprintf(buffer, "%d ", bignum_bitcount(key->modulus));
+ for (i = 0; i < 16; i++)
+ sprintf(buffer + strlen(buffer), "%s%02x", i ? ":" : "",
+ digest[i]);
+ strncpy(str, buffer, len);
+ str[len - 1] = '\0';
+ slen = strlen(str);
+ if (key->comment && slen < len - 1) {
+ str[slen] = ' ';
+ strncpy(str + slen + 1, key->comment, len - slen - 1);
+ str[len - 1] = '\0';
+ }
+}
+
+/*
+ * Verify that the public data in an RSA key matches the private
+ * data. We also check the private data itself: we ensure that p >
+ * q and that iqmp really is the inverse of q mod p.
+ */
+int rsa_verify(struct RSAKey *key)
+{
+ Bignum n, ed, pm1, qm1;
+ int cmp;
+
+ /* n must equal pq. */
+ n = bigmul(key->p, key->q);
+ cmp = bignum_cmp(n, key->modulus);
+ freebn(n);
+ if (cmp != 0)
+ return 0;
+
+ /* e * d must be congruent to 1, modulo (p-1) and modulo (q-1). */
+ pm1 = copybn(key->p);
+ decbn(pm1);
+ ed = modmul(key->exponent, key->private_exponent, pm1);
+ freebn(pm1);
+ cmp = bignum_cmp(ed, One);
+ freebn(ed);
+ if (cmp != 0)
+ return 0;
+
+ qm1 = copybn(key->q);
+ decbn(qm1);
+ ed = modmul(key->exponent, key->private_exponent, qm1);
+ freebn(qm1);
+ cmp = bignum_cmp(ed, One);
+ freebn(ed);
+ if (cmp != 0)
+ return 0;
+
+ /*
+ * Ensure p > q.
+ *
+ * I have seen key blobs in the wild which were generated with
+ * p < q, so instead of rejecting the key in this case we
+ * should instead flip them round into the canonical order of
+ * p > q. This also involves regenerating iqmp.
+ */
+ if (bignum_cmp(key->p, key->q) <= 0) {
+ Bignum tmp = key->p;
+ key->p = key->q;
+ key->q = tmp;
+
+ freebn(key->iqmp);
+ key->iqmp = modinv(key->q, key->p);
+ if (!key->iqmp)
+ return 0;
+ }
+
+ /*
+ * Ensure iqmp * q is congruent to 1, modulo p.
+ */
+ n = modmul(key->iqmp, key->q, key->p);
+ cmp = bignum_cmp(n, One);
+ freebn(n);
+ if (cmp != 0)
+ return 0;
+
+ return 1;
+}
+
+/* Public key blob as used by Pageant: exponent before modulus. */
+unsigned char *rsa_public_blob(struct RSAKey *key, int *len)
+{
+ int length, pos;
+ unsigned char *ret;
+
+ length = (ssh1_bignum_length(key->modulus) +
+ ssh1_bignum_length(key->exponent) + 4);
+ ret = snewn(length, unsigned char);
+
+ PUT_32BIT(ret, bignum_bitcount(key->modulus));
+ pos = 4;
+ pos += ssh1_write_bignum(ret + pos, key->exponent);
+ pos += ssh1_write_bignum(ret + pos, key->modulus);
+
+ *len = length;
+ return ret;
+}
+
+/* Given a public blob, determine its length. */
+int rsa_public_blob_len(void *data, int maxlen)
+{
+ unsigned char *p = (unsigned char *)data;
+ int n;
+
+ if (maxlen < 4)
+ return -1;
+ p += 4; /* length word */
+ maxlen -= 4;
+
+ n = ssh1_read_bignum(p, maxlen, NULL); /* exponent */
+ if (n < 0)
+ return -1;
+ p += n;
+
+ n = ssh1_read_bignum(p, maxlen, NULL); /* modulus */
+ if (n < 0)
+ return -1;
+ p += n;
+
+ return p - (unsigned char *)data;
+}
+
+void freersakey(struct RSAKey *key)
+{
+ if (key->modulus)
+ freebn(key->modulus);
+ if (key->exponent)
+ freebn(key->exponent);
+ if (key->private_exponent)
+ freebn(key->private_exponent);
+ if (key->p)
+ freebn(key->p);
+ if (key->q)
+ freebn(key->q);
+ if (key->iqmp)
+ freebn(key->iqmp);
+ if (key->comment)
+ sfree(key->comment);
+}
+
+/* ----------------------------------------------------------------------
+ * Implementation of the ssh-rsa signing key type.
+ */
+
+static void getstring(char **data, int *datalen, 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(char **data, int *datalen)
+{
+ char *p;
+ int length;
+ Bignum b;
+
+ getstring(data, datalen, &p, &length);
+ if (!p)
+ return NULL;
+ b = bignum_from_bytes((unsigned char *)p, length);
+ return b;
+}
+
+static void rsa2_freekey(void *key); /* forward reference */
+
+static void *rsa2_newkey(char *data, int len)
+{
+ char *p;
+ int slen;
+ struct RSAKey *rsa;
+
+ rsa = snew(struct RSAKey);
+ getstring(&data, &len, &p, &slen);
+
+ if (!p || slen != 7 || memcmp(p, "ssh-rsa", 7)) {
+ sfree(rsa);
+ return NULL;
+ }
+ rsa->exponent = getmp(&data, &len);
+ rsa->modulus = getmp(&data, &len);
+ rsa->private_exponent = NULL;
+ rsa->p = rsa->q = rsa->iqmp = NULL;
+ rsa->comment = NULL;
+
+ if (!rsa->exponent || !rsa->modulus) {
+ rsa2_freekey(rsa);
+ return NULL;
+ }
+
+ return rsa;
+}
+
+static void rsa2_freekey(void *key)
+{
+ struct RSAKey *rsa = (struct RSAKey *) key;
+ freersakey(rsa);
+ sfree(rsa);
+}
+
+static char *rsa2_fmtkey(void *key)
+{
+ struct RSAKey *rsa = (struct RSAKey *) key;
+ char *p;
+ int len;
+
+ len = rsastr_len(rsa);
+ p = snewn(len, char);
+ rsastr_fmt(p, rsa);
+ return p;
+}
+
+static unsigned char *rsa2_public_blob(void *key, int *len)
+{
+ struct RSAKey *rsa = (struct RSAKey *) key;
+ int elen, mlen, bloblen;
+ int i;
+ unsigned char *blob, *p;
+
+ elen = (bignum_bitcount(rsa->exponent) + 8) / 8;
+ mlen = (bignum_bitcount(rsa->modulus) + 8) / 8;
+
+ /*
+ * string "ssh-rsa", mpint exp, mpint mod. Total 19+elen+mlen.
+ * (three length fields, 12+7=19).
+ */
+ bloblen = 19 + elen + mlen;
+ blob = snewn(bloblen, unsigned char);
+ p = blob;
+ PUT_32BIT(p, 7);
+ p += 4;
+ memcpy(p, "ssh-rsa", 7);
+ p += 7;
+ PUT_32BIT(p, elen);
+ p += 4;
+ for (i = elen; i--;)
+ *p++ = bignum_byte(rsa->exponent, i);
+ PUT_32BIT(p, mlen);
+ p += 4;
+ for (i = mlen; i--;)
+ *p++ = bignum_byte(rsa->modulus, i);
+ assert(p == blob + bloblen);
+ *len = bloblen;
+ return blob;
+}
+
+static unsigned char *rsa2_private_blob(void *key, int *len)
+{
+ struct RSAKey *rsa = (struct RSAKey *) key;
+ int dlen, plen, qlen, ulen, bloblen;
+ int i;
+ unsigned char *blob, *p;
+
+ dlen = (bignum_bitcount(rsa->private_exponent) + 8) / 8;
+ plen = (bignum_bitcount(rsa->p) + 8) / 8;
+ qlen = (bignum_bitcount(rsa->q) + 8) / 8;
+ ulen = (bignum_bitcount(rsa->iqmp) + 8) / 8;
+
+ /*
+ * mpint private_exp, mpint p, mpint q, mpint iqmp. Total 16 +
+ * sum of lengths.
+ */
+ bloblen = 16 + dlen + plen + qlen + ulen;
+ blob = snewn(bloblen, unsigned char);
+ p = blob;
+ PUT_32BIT(p, dlen);
+ p += 4;
+ for (i = dlen; i--;)
+ *p++ = bignum_byte(rsa->private_exponent, i);
+ PUT_32BIT(p, plen);
+ p += 4;
+ for (i = plen; i--;)
+ *p++ = bignum_byte(rsa->p, i);
+ PUT_32BIT(p, qlen);
+ p += 4;
+ for (i = qlen; i--;)
+ *p++ = bignum_byte(rsa->q, i);
+ PUT_32BIT(p, ulen);
+ p += 4;
+ for (i = ulen; i--;)
+ *p++ = bignum_byte(rsa->iqmp, i);
+ assert(p == blob + bloblen);
+ *len = bloblen;
+ return blob;
+}
+
+static void *rsa2_createkey(unsigned char *pub_blob, int pub_len,
+ unsigned char *priv_blob, int priv_len)
+{
+ struct RSAKey *rsa;
+ char *pb = (char *) priv_blob;
+
+ rsa = rsa2_newkey((char *) pub_blob, pub_len);
+ rsa->private_exponent = getmp(&pb, &priv_len);
+ rsa->p = getmp(&pb, &priv_len);
+ rsa->q = getmp(&pb, &priv_len);
+ rsa->iqmp = getmp(&pb, &priv_len);
+
+ if (!rsa_verify(rsa)) {
+ rsa2_freekey(rsa);
+ return NULL;
+ }
+
+ return rsa;
+}
+
+static void *rsa2_openssh_createkey(unsigned char **blob, int *len)
+{
+ char **b = (char **) blob;
+ struct RSAKey *rsa;
+
+ rsa = snew(struct RSAKey);
+ rsa->comment = NULL;
+
+ rsa->modulus = getmp(b, len);
+ rsa->exponent = getmp(b, len);
+ rsa->private_exponent = getmp(b, len);
+ rsa->iqmp = getmp(b, len);
+ rsa->p = getmp(b, len);
+ rsa->q = getmp(b, len);
+
+ if (!rsa->modulus || !rsa->exponent || !rsa->private_exponent ||
+ !rsa->iqmp || !rsa->p || !rsa->q) {
+ rsa2_freekey(rsa);
+ return NULL;
+ }
+
+ if (!rsa_verify(rsa)) {
+ rsa2_freekey(rsa);
+ return NULL;
+ }
+
+ return rsa;
+}
+
+static int rsa2_openssh_fmtkey(void *key, unsigned char *blob, int len)
+{
+ struct RSAKey *rsa = (struct RSAKey *) key;
+ int bloblen, i;
+
+ bloblen =
+ ssh2_bignum_length(rsa->modulus) +
+ ssh2_bignum_length(rsa->exponent) +
+ ssh2_bignum_length(rsa->private_exponent) +
+ ssh2_bignum_length(rsa->iqmp) +
+ ssh2_bignum_length(rsa->p) + ssh2_bignum_length(rsa->q);
+
+ if (bloblen > len)
+ return bloblen;
+
+ bloblen = 0;
+#define ENC(x) \
+ PUT_32BIT(blob+bloblen, ssh2_bignum_length((x))-4); bloblen += 4; \
+ for (i = ssh2_bignum_length((x))-4; i-- ;) blob[bloblen++]=bignum_byte((x),i);
+ ENC(rsa->modulus);
+ ENC(rsa->exponent);
+ ENC(rsa->private_exponent);
+ ENC(rsa->iqmp);
+ ENC(rsa->p);
+ ENC(rsa->q);
+
+ return bloblen;
+}
+
+static int rsa2_pubkey_bits(void *blob, int len)
+{
+ struct RSAKey *rsa;
+ int ret;
+
+ rsa = rsa2_newkey((char *) blob, len);
+ ret = bignum_bitcount(rsa->modulus);
+ rsa2_freekey(rsa);
+
+ 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
+ * value. The meaning of it is:
+ *
+ * 00 -- this marks the end of the FFs; not part of the ASN.1 bit itself
+ *
+ * 30 21 -- a constructed SEQUENCE of length 0x21
+ * 30 09 -- a constructed sub-SEQUENCE of length 9
+ * 06 05 -- an object identifier, length 5
+ * 2B 0E 03 02 1A -- object id { 1 3 14 3 2 26 }
+ * (the 1,3 comes from 0x2B = 43 = 40*1+3)
+ * 05 00 -- NULL
+ * 04 14 -- a primitive OCTET STRING of length 0x14
+ * [0x14 bytes of hash data follows]
+ *
+ * The object id in the middle there is listed as `id-sha1' in
+ * ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1d2.asn (the
+ * ASN module for PKCS #1) and its expanded form is as follows:
+ *
+ * id-sha1 OBJECT IDENTIFIER ::= {
+ * iso(1) identified-organization(3) oiw(14) secsig(3)
+ * algorithms(2) 26 }
+ */
+static const unsigned char asn1_weird_stuff[] = {
+ 0x00, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B,
+ 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14,
+};
+
+#define ASN1_LEN ( (int) sizeof(asn1_weird_stuff) )
+
+static int rsa2_verifysig(void *key, char *sig, int siglen,
+ char *data, int datalen)
+{
+ struct RSAKey *rsa = (struct RSAKey *) key;
+ Bignum in, out;
+ char *p;
+ int slen;
+ int bytes, i, j, ret;
+ unsigned char hash[20];
+
+ getstring(&sig, &siglen, &p, &slen);
+ if (!p || slen != 7 || memcmp(p, "ssh-rsa", 7)) {
+ return 0;
+ }
+ in = getmp(&sig, &siglen);
+ if (!in)
+ return 0;
+ out = modpow(in, rsa->exponent, rsa->modulus);
+ freebn(in);
+
+ ret = 1;
+
+ bytes = (bignum_bitcount(rsa->modulus)+7) / 8;
+ /* Top (partial) byte should be zero. */
+ if (bignum_byte(out, bytes - 1) != 0)
+ ret = 0;
+ /* First whole byte should be 1. */
+ if (bignum_byte(out, bytes - 2) != 1)
+ ret = 0;
+ /* Most of the rest should be FF. */
+ for (i = bytes - 3; i >= 20 + ASN1_LEN; i--) {
+ if (bignum_byte(out, i) != 0xFF)
+ ret = 0;
+ }
+ /* Then we expect to see the asn1_weird_stuff. */
+ for (i = 20 + ASN1_LEN - 1, j = 0; i >= 20; i--, j++) {
+ if (bignum_byte(out, i) != asn1_weird_stuff[j])
+ ret = 0;
+ }
+ /* Finally, we expect to see the SHA-1 hash of the signed data. */
+ SHA_Simple(data, datalen, hash);
+ for (i = 19, j = 0; i >= 0; i--, j++) {
+ if (bignum_byte(out, i) != hash[j])
+ ret = 0;
+ }
+ freebn(out);
+
+ return ret;
+}
+
+static unsigned char *rsa2_sign(void *key, char *data, int datalen,
+ int *siglen)
+{
+ struct RSAKey *rsa = (struct RSAKey *) key;
+ unsigned char *bytes;
+ int nbytes;
+ unsigned char hash[20];
+ Bignum in, out;
+ int i, j;
+
+ SHA_Simple(data, datalen, hash);
+
+ nbytes = (bignum_bitcount(rsa->modulus) - 1) / 8;
+ assert(1 <= nbytes - 20 - ASN1_LEN);
+ bytes = snewn(nbytes, unsigned char);
+
+ bytes[0] = 1;
+ for (i = 1; i < nbytes - 20 - ASN1_LEN; i++)
+ bytes[i] = 0xFF;
+ for (i = nbytes - 20 - ASN1_LEN, j = 0; i < nbytes - 20; i++, j++)
+ bytes[i] = asn1_weird_stuff[j];
+ for (i = nbytes - 20, j = 0; i < nbytes; i++, j++)
+ bytes[i] = hash[j];
+
+ in = bignum_from_bytes(bytes, nbytes);
+ sfree(bytes);
+
+ out = rsa_privkey_op(in, rsa);
+ freebn(in);
+
+ nbytes = (bignum_bitcount(out) + 7) / 8;
+ bytes = snewn(4 + 7 + 4 + nbytes, unsigned char);
+ PUT_32BIT(bytes, 7);
+ memcpy(bytes + 4, "ssh-rsa", 7);
+ PUT_32BIT(bytes + 4 + 7, nbytes);
+ for (i = 0; i < nbytes; i++)
+ bytes[4 + 7 + 4 + i] = bignum_byte(out, nbytes - 1 - i);
+ freebn(out);
+
+ *siglen = 4 + 7 + 4 + nbytes;
+ return bytes;
+}
+
+const struct ssh_signkey ssh_rsa = {
+ rsa2_newkey,
+ rsa2_freekey,
+ rsa2_fmtkey,
+ rsa2_public_blob,
+ rsa2_private_blob,
+ rsa2_createkey,
+ rsa2_openssh_createkey,
+ rsa2_openssh_fmtkey,
+ rsa2_pubkey_bits,
+ rsa2_fingerprint,
+ rsa2_verifysig,
+ rsa2_sign,
+ "ssh-rsa",
+ "rsa2"
+};
+
+void *ssh_rsakex_newkey(char *data, int len)
+{
+ return rsa2_newkey(data, len);
+}
+
+void ssh_rsakex_freekey(void *key)
+{
+ rsa2_freekey(key);
+}
+
+int ssh_rsakex_klen(void *key)
+{
+ struct RSAKey *rsa = (struct RSAKey *) key;
+
+ return bignum_bitcount(rsa->modulus);
+}
+
+static void oaep_mask(const struct ssh_hash *h, void *seed, int seedlen,
+ void *vdata, int datalen)
+{
+ unsigned char *data = (unsigned char *)vdata;
+ unsigned count = 0;
+
+ while (datalen > 0) {
+ int i, max = (datalen > h->hlen ? h->hlen : datalen);
+ void *s;
+ unsigned char counter[4], hash[SSH2_KEX_MAX_HASH_LEN];
+
+ assert(h->hlen <= SSH2_KEX_MAX_HASH_LEN);
+ PUT_32BIT(counter, count);
+ s = h->init();
+ h->bytes(s, seed, seedlen);
+ h->bytes(s, counter, 4);
+ h->final(s, hash);
+ count++;
+
+ for (i = 0; i < max; i++)
+ data[i] ^= hash[i];
+
+ data += max;
+ datalen -= max;
+ }
+}
+
+void ssh_rsakex_encrypt(const struct ssh_hash *h, unsigned char *in, int inlen,
+ unsigned char *out, int outlen,
+ void *key)
+{
+ Bignum b1, b2;
+ struct RSAKey *rsa = (struct RSAKey *) key;
+ int k, i;
+ char *p;
+ const int HLEN = h->hlen;
+
+ /*
+ * Here we encrypt using RSAES-OAEP. Essentially this means:
+ *
+ * - we have a SHA-based `mask generation function' which
+ * creates a pseudo-random stream of mask data
+ * deterministically from an input chunk of data.
+ *
+ * - we have a random chunk of data called a seed.
+ *
+ * - we use the seed to generate a mask which we XOR with our
+ * plaintext.
+ *
+ * - then we use _the masked plaintext_ to generate a mask
+ * which we XOR with the seed.
+ *
+ * - then we concatenate the masked seed and the masked
+ * plaintext, and RSA-encrypt that lot.
+ *
+ * The result is that the data input to the encryption function
+ * is random-looking and (hopefully) contains no exploitable
+ * structure such as PKCS1-v1_5 does.
+ *
+ * For a precise specification, see RFC 3447, section 7.1.1.
+ * Some of the variable names below are derived from that, so
+ * it'd probably help to read it anyway.
+ */
+
+ /* k denotes the length in octets of the RSA modulus. */
+ k = (7 + bignum_bitcount(rsa->modulus)) / 8;
+
+ /* The length of the input data must be at most k - 2hLen - 2. */
+ assert(inlen > 0 && inlen <= k - 2*HLEN - 2);
+
+ /* The length of the output data wants to be precisely k. */
+ assert(outlen == k);
+
+ /*
+ * Now perform EME-OAEP encoding. First set up all the unmasked
+ * output data.
+ */
+ /* Leading byte zero. */
+ out[0] = 0;
+ /* At position 1, the seed: HLEN bytes of random data. */
+ for (i = 0; i < HLEN; i++)
+ out[i + 1] = random_byte();
+ /* At position 1+HLEN, the data block DB, consisting of: */
+ /* The hash of the label (we only support an empty label here) */
+ h->final(h->init(), out + HLEN + 1);
+ /* A bunch of zero octets */
+ memset(out + 2*HLEN + 1, 0, outlen - (2*HLEN + 1));
+ /* A single 1 octet, followed by the input message data. */
+ out[outlen - inlen - 1] = 1;
+ memcpy(out + outlen - inlen, in, inlen);
+
+ /*
+ * Now use the seed data to mask the block DB.
+ */
+ oaep_mask(h, out+1, HLEN, out+HLEN+1, outlen-HLEN-1);
+
+ /*
+ * And now use the masked DB to mask the seed itself.
+ */
+ oaep_mask(h, out+HLEN+1, outlen-HLEN-1, out+1, HLEN);
+
+ /*
+ * Now `out' contains precisely the data we want to
+ * RSA-encrypt.
+ */
+ b1 = bignum_from_bytes(out, outlen);
+ b2 = modpow(b1, rsa->exponent, rsa->modulus);
+ p = (char *)out;
+ for (i = outlen; i--;) {
+ *p++ = bignum_byte(b2, i);
+ }
+ freebn(b1);
+ freebn(b2);
+
+ /*
+ * And we're done.
+ */
+}
+
+static const struct ssh_kex ssh_rsa_kex_sha1 = {
+ "rsa1024-sha1", NULL, KEXTYPE_RSA, NULL, NULL, 0, 0, &ssh_sha1
+};
+
+static const struct ssh_kex ssh_rsa_kex_sha256 = {
+ "rsa2048-sha256", NULL, KEXTYPE_RSA, NULL, NULL, 0, 0, &ssh_sha256
+};
+
+static const struct ssh_kex *const rsa_kex_list[] = {
+ &ssh_rsa_kex_sha256,
+ &ssh_rsa_kex_sha1
+};
+
+const struct ssh_kexes ssh_rsa_kex = {
+ sizeof(rsa_kex_list) / sizeof(*rsa_kex_list),
+ rsa_kex_list
+};
diff --git a/tools/plink/sshsh256.c b/tools/plink/sshsh256.c
index 538982a89..60c5217f5 100644
--- a/tools/plink/sshsh256.c
+++ b/tools/plink/sshsh256.c
@@ -1,269 +1,379 @@
-/*
- * SHA-256 algorithm as described at
- *
- * http://csrc.nist.gov/cryptval/shs.html
- */
-
-#include "ssh.h"
-
-/* ----------------------------------------------------------------------
- * Core SHA256 algorithm: processes 16-word blocks into a message digest.
- */
-
-#define ror(x,y) ( ((x) << (32-y)) | (((uint32)(x)) >> (y)) )
-#define shr(x,y) ( (((uint32)(x)) >> (y)) )
-#define Ch(x,y,z) ( ((x) & (y)) ^ (~(x) & (z)) )
-#define Maj(x,y,z) ( ((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)) )
-#define bigsigma0(x) ( ror((x),2) ^ ror((x),13) ^ ror((x),22) )
-#define bigsigma1(x) ( ror((x),6) ^ ror((x),11) ^ ror((x),25) )
-#define smallsigma0(x) ( ror((x),7) ^ ror((x),18) ^ shr((x),3) )
-#define smallsigma1(x) ( ror((x),17) ^ ror((x),19) ^ shr((x),10) )
-
-void SHA256_Core_Init(SHA256_State *s) {
- s->h[0] = 0x6a09e667;
- s->h[1] = 0xbb67ae85;
- s->h[2] = 0x3c6ef372;
- s->h[3] = 0xa54ff53a;
- s->h[4] = 0x510e527f;
- s->h[5] = 0x9b05688c;
- s->h[6] = 0x1f83d9ab;
- s->h[7] = 0x5be0cd19;
-}
-
-void SHA256_Block(SHA256_State *s, uint32 *block) {
- uint32 w[80];
- uint32 a,b,c,d,e,f,g,h;
- static const int k[] = {
- 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
- 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
- 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
- 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
- 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
- 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
- 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
- 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
- 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
- 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
- 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
- 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
- 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
- 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
- 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
- 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
- };
-
- int t;
-
- for (t = 0; t < 16; t++)
- w[t] = block[t];
-
- for (t = 16; t < 64; t++)
- w[t] = smallsigma1(w[t-2]) + w[t-7] + smallsigma0(w[t-15]) + w[t-16];
-
- a = s->h[0]; b = s->h[1]; c = s->h[2]; d = s->h[3];
- e = s->h[4]; f = s->h[5]; g = s->h[6]; h = s->h[7];
-
- for (t = 0; t < 64; t+=8) {
- uint32 t1, t2;
-
-#define ROUND(j,a,b,c,d,e,f,g,h) \
- t1 = h + bigsigma1(e) + Ch(e,f,g) + k[j] + w[j]; \
- t2 = bigsigma0(a) + Maj(a,b,c); \
- d = d + t1; h = t1 + t2;
-
- ROUND(t+0, a,b,c,d,e,f,g,h);
- ROUND(t+1, h,a,b,c,d,e,f,g);
- ROUND(t+2, g,h,a,b,c,d,e,f);
- ROUND(t+3, f,g,h,a,b,c,d,e);
- ROUND(t+4, e,f,g,h,a,b,c,d);
- ROUND(t+5, d,e,f,g,h,a,b,c);
- ROUND(t+6, c,d,e,f,g,h,a,b);
- ROUND(t+7, b,c,d,e,f,g,h,a);
- }
-
- s->h[0] += a; s->h[1] += b; s->h[2] += c; s->h[3] += d;
- s->h[4] += e; s->h[5] += f; s->h[6] += g; s->h[7] += h;
-}
-
-/* ----------------------------------------------------------------------
- * Outer SHA256 algorithm: take an arbitrary length byte string,
- * convert it into 16-word blocks with the prescribed padding at
- * the end, and pass those blocks to the core SHA256 algorithm.
- */
-
-#define BLKSIZE 64
-
-void SHA256_Init(SHA256_State *s) {
- SHA256_Core_Init(s);
- s->blkused = 0;
- s->lenhi = s->lenlo = 0;
-}
-
-void SHA256_Bytes(SHA256_State *s, const void *p, int len) {
- unsigned char *q = (unsigned char *)p;
- uint32 wordblock[16];
- uint32 lenw = len;
- int i;
-
- /*
- * Update the length field.
- */
- s->lenlo += lenw;
- s->lenhi += (s->lenlo < lenw);
-
- if (s->blkused && s->blkused+len < BLKSIZE) {
- /*
- * Trivial case: just add to the block.
- */
- memcpy(s->block + s->blkused, q, len);
- s->blkused += len;
- } else {
- /*
- * We must complete and process at least one block.
- */
- while (s->blkused + len >= BLKSIZE) {
- memcpy(s->block + s->blkused, q, BLKSIZE - s->blkused);
- q += BLKSIZE - s->blkused;
- len -= BLKSIZE - s->blkused;
- /* Now process the block. Gather bytes big-endian into words */
- for (i = 0; i < 16; i++) {
- wordblock[i] =
- ( ((uint32)s->block[i*4+0]) << 24 ) |
- ( ((uint32)s->block[i*4+1]) << 16 ) |
- ( ((uint32)s->block[i*4+2]) << 8 ) |
- ( ((uint32)s->block[i*4+3]) << 0 );
- }
- SHA256_Block(s, wordblock);
- s->blkused = 0;
- }
- memcpy(s->block, q, len);
- s->blkused = len;
- }
-}
-
-void SHA256_Final(SHA256_State *s, unsigned char *digest) {
- int i;
- int pad;
- unsigned char c[64];
- uint32 lenhi, lenlo;
-
- if (s->blkused >= 56)
- pad = 56 + 64 - s->blkused;
- else
- pad = 56 - s->blkused;
-
- lenhi = (s->lenhi << 3) | (s->lenlo >> (32-3));
- lenlo = (s->lenlo << 3);
-
- memset(c, 0, pad);
- c[0] = 0x80;
- SHA256_Bytes(s, &c, pad);
-
- c[0] = (lenhi >> 24) & 0xFF;
- c[1] = (lenhi >> 16) & 0xFF;
- c[2] = (lenhi >> 8) & 0xFF;
- c[3] = (lenhi >> 0) & 0xFF;
- c[4] = (lenlo >> 24) & 0xFF;
- c[5] = (lenlo >> 16) & 0xFF;
- c[6] = (lenlo >> 8) & 0xFF;
- c[7] = (lenlo >> 0) & 0xFF;
-
- SHA256_Bytes(s, &c, 8);
-
- for (i = 0; i < 8; i++) {
- digest[i*4+0] = (s->h[i] >> 24) & 0xFF;
- digest[i*4+1] = (s->h[i] >> 16) & 0xFF;
- digest[i*4+2] = (s->h[i] >> 8) & 0xFF;
- digest[i*4+3] = (s->h[i] >> 0) & 0xFF;
- }
-}
-
-void SHA256_Simple(const void *p, int len, unsigned char *output) {
- SHA256_State s;
-
- SHA256_Init(&s);
- SHA256_Bytes(&s, p, len);
- SHA256_Final(&s, output);
-}
-
-/*
- * Thin abstraction for things where hashes are pluggable.
- */
-
-static void *sha256_init(void)
-{
- SHA256_State *s;
-
- s = snew(SHA256_State);
- SHA256_Init(s);
- return s;
-}
-
-static void sha256_bytes(void *handle, void *p, int len)
-{
- SHA256_State *s = handle;
-
- SHA256_Bytes(s, p, len);
-}
-
-static void sha256_final(void *handle, unsigned char *output)
-{
- SHA256_State *s = handle;
-
- SHA256_Final(s, output);
- sfree(s);
-}
-
-const struct ssh_hash ssh_sha256 = {
- sha256_init, sha256_bytes, sha256_final, 32, "SHA-256"
-};
-
-#ifdef TEST
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-
-int main(void) {
- unsigned char digest[32];
- int i, j, errors;
-
- struct {
- const char *teststring;
- unsigned char digest[32];
- } tests[] = {
- { "abc", {
- 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea,
- 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23,
- 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c,
- 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad,
- } },
- { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", {
- 0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8,
- 0xe5, 0xc0, 0x26, 0x93, 0x0c, 0x3e, 0x60, 0x39,
- 0xa3, 0x3c, 0xe4, 0x59, 0x64, 0xff, 0x21, 0x67,
- 0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1,
- } },
- };
-
- errors = 0;
-
- for (i = 0; i < sizeof(tests) / sizeof(*tests); i++) {
- SHA256_Simple(tests[i].teststring,
- strlen(tests[i].teststring), digest);
- for (j = 0; j < 32; j++) {
- if (digest[j] != tests[i].digest[j]) {
- fprintf(stderr,
- "\"%s\" digest byte %d should be 0x%02x, is 0x%02x\n",
- tests[i].teststring, j, tests[i].digest[j], digest[j]);
- errors++;
- }
- }
- }
-
- printf("%d errors\n", errors);
-
- return 0;
-}
-
-#endif
+/*
+ * SHA-256 algorithm as described at
+ *
+ * http://csrc.nist.gov/cryptval/shs.html
+ */
+
+#include "ssh.h"
+
+/* ----------------------------------------------------------------------
+ * Core SHA256 algorithm: processes 16-word blocks into a message digest.
+ */
+
+#define ror(x,y) ( ((x) << (32-y)) | (((uint32)(x)) >> (y)) )
+#define shr(x,y) ( (((uint32)(x)) >> (y)) )
+#define Ch(x,y,z) ( ((x) & (y)) ^ (~(x) & (z)) )
+#define Maj(x,y,z) ( ((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)) )
+#define bigsigma0(x) ( ror((x),2) ^ ror((x),13) ^ ror((x),22) )
+#define bigsigma1(x) ( ror((x),6) ^ ror((x),11) ^ ror((x),25) )
+#define smallsigma0(x) ( ror((x),7) ^ ror((x),18) ^ shr((x),3) )
+#define smallsigma1(x) ( ror((x),17) ^ ror((x),19) ^ shr((x),10) )
+
+void SHA256_Core_Init(SHA256_State *s) {
+ s->h[0] = 0x6a09e667;
+ s->h[1] = 0xbb67ae85;
+ s->h[2] = 0x3c6ef372;
+ s->h[3] = 0xa54ff53a;
+ s->h[4] = 0x510e527f;
+ s->h[5] = 0x9b05688c;
+ s->h[6] = 0x1f83d9ab;
+ s->h[7] = 0x5be0cd19;
+}
+
+void SHA256_Block(SHA256_State *s, uint32 *block) {
+ uint32 w[80];
+ uint32 a,b,c,d,e,f,g,h;
+ static const int k[] = {
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+ 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+ 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+ 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+ 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+ 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
+ };
+
+ int t;
+
+ for (t = 0; t < 16; t++)
+ w[t] = block[t];
+
+ for (t = 16; t < 64; t++)
+ w[t] = smallsigma1(w[t-2]) + w[t-7] + smallsigma0(w[t-15]) + w[t-16];
+
+ a = s->h[0]; b = s->h[1]; c = s->h[2]; d = s->h[3];
+ e = s->h[4]; f = s->h[5]; g = s->h[6]; h = s->h[7];
+
+ for (t = 0; t < 64; t+=8) {
+ uint32 t1, t2;
+
+#define ROUND(j,a,b,c,d,e,f,g,h) \
+ t1 = h + bigsigma1(e) + Ch(e,f,g) + k[j] + w[j]; \
+ t2 = bigsigma0(a) + Maj(a,b,c); \
+ d = d + t1; h = t1 + t2;
+
+ ROUND(t+0, a,b,c,d,e,f,g,h);
+ ROUND(t+1, h,a,b,c,d,e,f,g);
+ ROUND(t+2, g,h,a,b,c,d,e,f);
+ ROUND(t+3, f,g,h,a,b,c,d,e);
+ ROUND(t+4, e,f,g,h,a,b,c,d);
+ ROUND(t+5, d,e,f,g,h,a,b,c);
+ ROUND(t+6, c,d,e,f,g,h,a,b);
+ ROUND(t+7, b,c,d,e,f,g,h,a);
+ }
+
+ s->h[0] += a; s->h[1] += b; s->h[2] += c; s->h[3] += d;
+ s->h[4] += e; s->h[5] += f; s->h[6] += g; s->h[7] += h;
+}
+
+/* ----------------------------------------------------------------------
+ * Outer SHA256 algorithm: take an arbitrary length byte string,
+ * convert it into 16-word blocks with the prescribed padding at
+ * the end, and pass those blocks to the core SHA256 algorithm.
+ */
+
+#define BLKSIZE 64
+
+void SHA256_Init(SHA256_State *s) {
+ SHA256_Core_Init(s);
+ s->blkused = 0;
+ s->lenhi = s->lenlo = 0;
+}
+
+void SHA256_Bytes(SHA256_State *s, const void *p, int len) {
+ unsigned char *q = (unsigned char *)p;
+ uint32 wordblock[16];
+ uint32 lenw = len;
+ int i;
+
+ /*
+ * Update the length field.
+ */
+ s->lenlo += lenw;
+ s->lenhi += (s->lenlo < lenw);
+
+ if (s->blkused && s->blkused+len < BLKSIZE) {
+ /*
+ * Trivial case: just add to the block.
+ */
+ memcpy(s->block + s->blkused, q, len);
+ s->blkused += len;
+ } else {
+ /*
+ * We must complete and process at least one block.
+ */
+ while (s->blkused + len >= BLKSIZE) {
+ memcpy(s->block + s->blkused, q, BLKSIZE - s->blkused);
+ q += BLKSIZE - s->blkused;
+ len -= BLKSIZE - s->blkused;
+ /* Now process the block. Gather bytes big-endian into words */
+ for (i = 0; i < 16; i++) {
+ wordblock[i] =
+ ( ((uint32)s->block[i*4+0]) << 24 ) |
+ ( ((uint32)s->block[i*4+1]) << 16 ) |
+ ( ((uint32)s->block[i*4+2]) << 8 ) |
+ ( ((uint32)s->block[i*4+3]) << 0 );
+ }
+ SHA256_Block(s, wordblock);
+ s->blkused = 0;
+ }
+ memcpy(s->block, q, len);
+ s->blkused = len;
+ }
+}
+
+void SHA256_Final(SHA256_State *s, unsigned char *digest) {
+ int i;
+ int pad;
+ unsigned char c[64];
+ uint32 lenhi, lenlo;
+
+ if (s->blkused >= 56)
+ pad = 56 + 64 - s->blkused;
+ else
+ pad = 56 - s->blkused;
+
+ lenhi = (s->lenhi << 3) | (s->lenlo >> (32-3));
+ lenlo = (s->lenlo << 3);
+
+ memset(c, 0, pad);
+ c[0] = 0x80;
+ SHA256_Bytes(s, &c, pad);
+
+ c[0] = (lenhi >> 24) & 0xFF;
+ c[1] = (lenhi >> 16) & 0xFF;
+ c[2] = (lenhi >> 8) & 0xFF;
+ c[3] = (lenhi >> 0) & 0xFF;
+ c[4] = (lenlo >> 24) & 0xFF;
+ c[5] = (lenlo >> 16) & 0xFF;
+ c[6] = (lenlo >> 8) & 0xFF;
+ c[7] = (lenlo >> 0) & 0xFF;
+
+ SHA256_Bytes(s, &c, 8);
+
+ for (i = 0; i < 8; i++) {
+ digest[i*4+0] = (s->h[i] >> 24) & 0xFF;
+ digest[i*4+1] = (s->h[i] >> 16) & 0xFF;
+ digest[i*4+2] = (s->h[i] >> 8) & 0xFF;
+ digest[i*4+3] = (s->h[i] >> 0) & 0xFF;
+ }
+}
+
+void SHA256_Simple(const void *p, int len, unsigned char *output) {
+ SHA256_State s;
+
+ SHA256_Init(&s);
+ SHA256_Bytes(&s, p, len);
+ SHA256_Final(&s, output);
+}
+
+/*
+ * Thin abstraction for things where hashes are pluggable.
+ */
+
+static void *sha256_init(void)
+{
+ SHA256_State *s;
+
+ s = snew(SHA256_State);
+ SHA256_Init(s);
+ return s;
+}
+
+static void sha256_bytes(void *handle, void *p, int len)
+{
+ SHA256_State *s = handle;
+
+ SHA256_Bytes(s, p, len);
+}
+
+static void sha256_final(void *handle, unsigned char *output)
+{
+ SHA256_State *s = handle;
+
+ SHA256_Final(s, output);
+ sfree(s);
+}
+
+const struct ssh_hash ssh_sha256 = {
+ sha256_init, sha256_bytes, sha256_final, 32, "SHA-256"
+};
+
+/* ----------------------------------------------------------------------
+ * The above is the SHA-256 algorithm itself. Now we implement the
+ * HMAC wrapper on it.
+ */
+
+static void *sha256_make_context(void)
+{
+ return snewn(3, SHA256_State);
+}
+
+static void sha256_free_context(void *handle)
+{
+ sfree(handle);
+}
+
+static void sha256_key_internal(void *handle, unsigned char *key, int len)
+{
+ SHA256_State *keys = (SHA256_State *)handle;
+ unsigned char foo[64];
+ int i;
+
+ memset(foo, 0x36, 64);
+ for (i = 0; i < len && i < 64; i++)
+ foo[i] ^= key[i];
+ SHA256_Init(&keys[0]);
+ SHA256_Bytes(&keys[0], foo, 64);
+
+ memset(foo, 0x5C, 64);
+ for (i = 0; i < len && i < 64; i++)
+ foo[i] ^= key[i];
+ SHA256_Init(&keys[1]);
+ SHA256_Bytes(&keys[1], foo, 64);
+
+ smemclr(foo, 64); /* burn the evidence */
+}
+
+static void sha256_key(void *handle, unsigned char *key)
+{
+ sha256_key_internal(handle, key, 32);
+}
+
+static void hmacsha256_start(void *handle)
+{
+ SHA256_State *keys = (SHA256_State *)handle;
+
+ keys[2] = keys[0]; /* structure copy */
+}
+
+static void hmacsha256_bytes(void *handle, unsigned char const *blk, int len)
+{
+ SHA256_State *keys = (SHA256_State *)handle;
+ SHA256_Bytes(&keys[2], (void *)blk, len);
+}
+
+static void hmacsha256_genresult(void *handle, unsigned char *hmac)
+{
+ SHA256_State *keys = (SHA256_State *)handle;
+ SHA256_State s;
+ unsigned char intermediate[32];
+
+ s = keys[2]; /* structure copy */
+ SHA256_Final(&s, intermediate);
+ s = keys[1]; /* structure copy */
+ SHA256_Bytes(&s, intermediate, 32);
+ SHA256_Final(&s, hmac);
+}
+
+static void sha256_do_hmac(void *handle, unsigned char *blk, int len,
+ unsigned long seq, unsigned char *hmac)
+{
+ unsigned char seqbuf[4];
+
+ PUT_32BIT_MSB_FIRST(seqbuf, seq);
+ hmacsha256_start(handle);
+ hmacsha256_bytes(handle, seqbuf, 4);
+ hmacsha256_bytes(handle, blk, len);
+ hmacsha256_genresult(handle, hmac);
+}
+
+static void sha256_generate(void *handle, unsigned char *blk, int len,
+ unsigned long seq)
+{
+ sha256_do_hmac(handle, blk, len, seq, blk + len);
+}
+
+static int hmacsha256_verresult(void *handle, unsigned char const *hmac)
+{
+ unsigned char correct[32];
+ hmacsha256_genresult(handle, correct);
+ return !memcmp(correct, hmac, 32);
+}
+
+static int sha256_verify(void *handle, unsigned char *blk, int len,
+ unsigned long seq)
+{
+ unsigned char correct[32];
+ sha256_do_hmac(handle, blk, len, seq, correct);
+ return !memcmp(correct, blk + len, 32);
+}
+
+const struct ssh_mac ssh_hmac_sha256 = {
+ sha256_make_context, sha256_free_context, sha256_key,
+ sha256_generate, sha256_verify,
+ hmacsha256_start, hmacsha256_bytes,
+ hmacsha256_genresult, hmacsha256_verresult,
+ "hmac-sha2-256",
+ 32,
+ "HMAC-SHA-256"
+};
+
+#ifdef TEST
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+int main(void) {
+ unsigned char digest[32];
+ int i, j, errors;
+
+ struct {
+ const char *teststring;
+ unsigned char digest[32];
+ } tests[] = {
+ { "abc", {
+ 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea,
+ 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23,
+ 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c,
+ 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad,
+ } },
+ { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", {
+ 0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8,
+ 0xe5, 0xc0, 0x26, 0x93, 0x0c, 0x3e, 0x60, 0x39,
+ 0xa3, 0x3c, 0xe4, 0x59, 0x64, 0xff, 0x21, 0x67,
+ 0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1,
+ } },
+ };
+
+ errors = 0;
+
+ for (i = 0; i < sizeof(tests) / sizeof(*tests); i++) {
+ SHA256_Simple(tests[i].teststring,
+ strlen(tests[i].teststring), digest);
+ for (j = 0; j < 32; j++) {
+ if (digest[j] != tests[i].digest[j]) {
+ fprintf(stderr,
+ "\"%s\" digest byte %d should be 0x%02x, is 0x%02x\n",
+ tests[i].teststring, j, tests[i].digest[j], digest[j]);
+ errors++;
+ }
+ }
+ }
+
+ printf("%d errors\n", errors);
+
+ return 0;
+}
+
+#endif
diff --git a/tools/plink/sshsha.c b/tools/plink/sshsha.c
index d1c798126..a5b3a60c8 100644
--- a/tools/plink/sshsha.c
+++ b/tools/plink/sshsha.c
@@ -1,411 +1,435 @@
-/*
- * SHA1 hash algorithm. Used in SSH-2 as a MAC, and the transform is
- * also used as a `stirring' function for the PuTTY random number
- * pool. Implemented directly from the specification by Simon
- * Tatham.
- */
-
-#include "ssh.h"
-
-/* ----------------------------------------------------------------------
- * Core SHA algorithm: processes 16-word blocks into a message digest.
- */
-
-#define rol(x,y) ( ((x) << (y)) | (((uint32)x) >> (32-y)) )
-
-static void SHA_Core_Init(uint32 h[5])
-{
- h[0] = 0x67452301;
- h[1] = 0xefcdab89;
- h[2] = 0x98badcfe;
- h[3] = 0x10325476;
- h[4] = 0xc3d2e1f0;
-}
-
-void SHATransform(word32 * digest, word32 * block)
-{
- word32 w[80];
- word32 a, b, c, d, e;
- int t;
-
- for (t = 0; t < 16; t++)
- w[t] = block[t];
-
- for (t = 16; t < 80; t++) {
- word32 tmp = w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16];
- w[t] = rol(tmp, 1);
- }
-
- a = digest[0];
- b = digest[1];
- c = digest[2];
- d = digest[3];
- e = digest[4];
-
- for (t = 0; t < 20; t++) {
- word32 tmp =
- rol(a, 5) + ((b & c) | (d & ~b)) + e + w[t] + 0x5a827999;
- e = d;
- d = c;
- c = rol(b, 30);
- b = a;
- a = tmp;
- }
- for (t = 20; t < 40; t++) {
- word32 tmp = rol(a, 5) + (b ^ c ^ d) + e + w[t] + 0x6ed9eba1;
- e = d;
- d = c;
- c = rol(b, 30);
- b = a;
- a = tmp;
- }
- for (t = 40; t < 60; t++) {
- word32 tmp = rol(a,
- 5) + ((b & c) | (b & d) | (c & d)) + e + w[t] +
- 0x8f1bbcdc;
- e = d;
- d = c;
- c = rol(b, 30);
- b = a;
- a = tmp;
- }
- for (t = 60; t < 80; t++) {
- word32 tmp = rol(a, 5) + (b ^ c ^ d) + e + w[t] + 0xca62c1d6;
- e = d;
- d = c;
- c = rol(b, 30);
- b = a;
- a = tmp;
- }
-
- digest[0] += a;
- digest[1] += b;
- digest[2] += c;
- digest[3] += d;
- digest[4] += e;
-}
-
-/* ----------------------------------------------------------------------
- * Outer SHA algorithm: take an arbitrary length byte string,
- * convert it into 16-word blocks with the prescribed padding at
- * the end, and pass those blocks to the core SHA algorithm.
- */
-
-void SHA_Init(SHA_State * s)
-{
- SHA_Core_Init(s->h);
- s->blkused = 0;
- s->lenhi = s->lenlo = 0;
-}
-
-void SHA_Bytes(SHA_State * s, void *p, int len)
-{
- unsigned char *q = (unsigned char *) p;
- uint32 wordblock[16];
- uint32 lenw = len;
- int i;
-
- /*
- * Update the length field.
- */
- s->lenlo += lenw;
- s->lenhi += (s->lenlo < lenw);
-
- if (s->blkused && s->blkused + len < 64) {
- /*
- * Trivial case: just add to the block.
- */
- memcpy(s->block + s->blkused, q, len);
- s->blkused += len;
- } else {
- /*
- * We must complete and process at least one block.
- */
- while (s->blkused + len >= 64) {
- memcpy(s->block + s->blkused, q, 64 - s->blkused);
- q += 64 - s->blkused;
- len -= 64 - s->blkused;
- /* Now process the block. Gather bytes big-endian into words */
- for (i = 0; i < 16; i++) {
- wordblock[i] =
- (((uint32) s->block[i * 4 + 0]) << 24) |
- (((uint32) s->block[i * 4 + 1]) << 16) |
- (((uint32) s->block[i * 4 + 2]) << 8) |
- (((uint32) s->block[i * 4 + 3]) << 0);
- }
- SHATransform(s->h, wordblock);
- s->blkused = 0;
- }
- memcpy(s->block, q, len);
- s->blkused = len;
- }
-}
-
-void SHA_Final(SHA_State * s, unsigned char *output)
-{
- int i;
- int pad;
- unsigned char c[64];
- uint32 lenhi, lenlo;
-
- if (s->blkused >= 56)
- pad = 56 + 64 - s->blkused;
- else
- pad = 56 - s->blkused;
-
- lenhi = (s->lenhi << 3) | (s->lenlo >> (32 - 3));
- lenlo = (s->lenlo << 3);
-
- memset(c, 0, pad);
- c[0] = 0x80;
- SHA_Bytes(s, &c, pad);
-
- c[0] = (lenhi >> 24) & 0xFF;
- c[1] = (lenhi >> 16) & 0xFF;
- c[2] = (lenhi >> 8) & 0xFF;
- c[3] = (lenhi >> 0) & 0xFF;
- c[4] = (lenlo >> 24) & 0xFF;
- c[5] = (lenlo >> 16) & 0xFF;
- c[6] = (lenlo >> 8) & 0xFF;
- c[7] = (lenlo >> 0) & 0xFF;
-
- SHA_Bytes(s, &c, 8);
-
- for (i = 0; i < 5; i++) {
- output[i * 4] = (s->h[i] >> 24) & 0xFF;
- output[i * 4 + 1] = (s->h[i] >> 16) & 0xFF;
- output[i * 4 + 2] = (s->h[i] >> 8) & 0xFF;
- output[i * 4 + 3] = (s->h[i]) & 0xFF;
- }
-}
-
-void SHA_Simple(void *p, int len, unsigned char *output)
-{
- SHA_State s;
-
- SHA_Init(&s);
- SHA_Bytes(&s, p, len);
- SHA_Final(&s, output);
-}
-
-/*
- * Thin abstraction for things where hashes are pluggable.
- */
-
-static void *sha1_init(void)
-{
- SHA_State *s;
-
- s = snew(SHA_State);
- SHA_Init(s);
- return s;
-}
-
-static void sha1_bytes(void *handle, void *p, int len)
-{
- SHA_State *s = handle;
-
- SHA_Bytes(s, p, len);
-}
-
-static void sha1_final(void *handle, unsigned char *output)
-{
- SHA_State *s = handle;
-
- SHA_Final(s, output);
- sfree(s);
-}
-
-const struct ssh_hash ssh_sha1 = {
- sha1_init, sha1_bytes, sha1_final, 20, "SHA-1"
-};
-
-/* ----------------------------------------------------------------------
- * The above is the SHA-1 algorithm itself. Now we implement the
- * HMAC wrapper on it.
- */
-
-static void *sha1_make_context(void)
-{
- return snewn(3, SHA_State);
-}
-
-static void sha1_free_context(void *handle)
-{
- sfree(handle);
-}
-
-static void sha1_key_internal(void *handle, unsigned char *key, int len)
-{
- SHA_State *keys = (SHA_State *)handle;
- unsigned char foo[64];
- int i;
-
- memset(foo, 0x36, 64);
- for (i = 0; i < len && i < 64; i++)
- foo[i] ^= key[i];
- SHA_Init(&keys[0]);
- SHA_Bytes(&keys[0], foo, 64);
-
- memset(foo, 0x5C, 64);
- for (i = 0; i < len && i < 64; i++)
- foo[i] ^= key[i];
- SHA_Init(&keys[1]);
- SHA_Bytes(&keys[1], foo, 64);
-
- memset(foo, 0, 64); /* burn the evidence */
-}
-
-static void sha1_key(void *handle, unsigned char *key)
-{
- sha1_key_internal(handle, key, 20);
-}
-
-static void sha1_key_buggy(void *handle, unsigned char *key)
-{
- sha1_key_internal(handle, key, 16);
-}
-
-static void hmacsha1_start(void *handle)
-{
- SHA_State *keys = (SHA_State *)handle;
-
- keys[2] = keys[0]; /* structure copy */
-}
-
-static void hmacsha1_bytes(void *handle, unsigned char const *blk, int len)
-{
- SHA_State *keys = (SHA_State *)handle;
- SHA_Bytes(&keys[2], (void *)blk, len);
-}
-
-static void hmacsha1_genresult(void *handle, unsigned char *hmac)
-{
- SHA_State *keys = (SHA_State *)handle;
- SHA_State s;
- unsigned char intermediate[20];
-
- s = keys[2]; /* structure copy */
- SHA_Final(&s, intermediate);
- s = keys[1]; /* structure copy */
- SHA_Bytes(&s, intermediate, 20);
- SHA_Final(&s, hmac);
-}
-
-static void sha1_do_hmac(void *handle, unsigned char *blk, int len,
- unsigned long seq, unsigned char *hmac)
-{
- unsigned char seqbuf[4];
-
- seqbuf[0] = (unsigned char) ((seq >> 24) & 0xFF);
- seqbuf[1] = (unsigned char) ((seq >> 16) & 0xFF);
- seqbuf[2] = (unsigned char) ((seq >> 8) & 0xFF);
- seqbuf[3] = (unsigned char) ((seq) & 0xFF);
-
- hmacsha1_start(handle);
- hmacsha1_bytes(handle, seqbuf, 4);
- hmacsha1_bytes(handle, blk, len);
- hmacsha1_genresult(handle, hmac);
-}
-
-static void sha1_generate(void *handle, unsigned char *blk, int len,
- unsigned long seq)
-{
- sha1_do_hmac(handle, blk, len, seq, blk + len);
-}
-
-static int hmacsha1_verresult(void *handle, unsigned char const *hmac)
-{
- unsigned char correct[20];
- hmacsha1_genresult(handle, correct);
- return !memcmp(correct, hmac, 20);
-}
-
-static int sha1_verify(void *handle, unsigned char *blk, int len,
- unsigned long seq)
-{
- unsigned char correct[20];
- sha1_do_hmac(handle, blk, len, seq, correct);
- return !memcmp(correct, blk + len, 20);
-}
-
-static void hmacsha1_96_genresult(void *handle, unsigned char *hmac)
-{
- unsigned char full[20];
- hmacsha1_genresult(handle, full);
- memcpy(hmac, full, 12);
-}
-
-static void sha1_96_generate(void *handle, unsigned char *blk, int len,
- unsigned long seq)
-{
- unsigned char full[20];
- sha1_do_hmac(handle, blk, len, seq, full);
- memcpy(blk + len, full, 12);
-}
-
-static int hmacsha1_96_verresult(void *handle, unsigned char const *hmac)
-{
- unsigned char correct[20];
- hmacsha1_genresult(handle, correct);
- return !memcmp(correct, hmac, 12);
-}
-
-static int sha1_96_verify(void *handle, unsigned char *blk, int len,
- unsigned long seq)
-{
- unsigned char correct[20];
- sha1_do_hmac(handle, blk, len, seq, correct);
- return !memcmp(correct, blk + len, 12);
-}
-
-void hmac_sha1_simple(void *key, int keylen, void *data, int datalen,
- unsigned char *output) {
- SHA_State states[2];
- unsigned char intermediate[20];
-
- sha1_key_internal(states, key, keylen);
- SHA_Bytes(&states[0], data, datalen);
- SHA_Final(&states[0], intermediate);
-
- SHA_Bytes(&states[1], intermediate, 20);
- SHA_Final(&states[1], output);
-}
-
-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"
-};
-
-const struct ssh_mac ssh_hmac_sha1_96 = {
- sha1_make_context, sha1_free_context, sha1_key,
- sha1_96_generate, sha1_96_verify,
- hmacsha1_start, hmacsha1_bytes,
- hmacsha1_96_genresult, hmacsha1_96_verresult,
- "hmac-sha1-96",
- 12,
- "HMAC-SHA1-96"
-};
-
-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,
- "bug-compatible HMAC-SHA1"
-};
-
-const struct ssh_mac ssh_hmac_sha1_96_buggy = {
- sha1_make_context, sha1_free_context, sha1_key_buggy,
- sha1_96_generate, sha1_96_verify,
- hmacsha1_start, hmacsha1_bytes,
- hmacsha1_96_genresult, hmacsha1_96_verresult,
- "hmac-sha1-96",
- 12,
- "bug-compatible HMAC-SHA1-96"
-};
+/*
+ * SHA1 hash algorithm. Used in SSH-2 as a MAC, and the transform is
+ * also used as a `stirring' function for the PuTTY random number
+ * pool. Implemented directly from the specification by Simon
+ * Tatham.
+ */
+
+#include "ssh.h"
+
+/* ----------------------------------------------------------------------
+ * Core SHA algorithm: processes 16-word blocks into a message digest.
+ */
+
+#define rol(x,y) ( ((x) << (y)) | (((uint32)x) >> (32-y)) )
+
+static void SHA_Core_Init(uint32 h[5])
+{
+ h[0] = 0x67452301;
+ h[1] = 0xefcdab89;
+ h[2] = 0x98badcfe;
+ h[3] = 0x10325476;
+ h[4] = 0xc3d2e1f0;
+}
+
+void SHATransform(word32 * digest, word32 * block)
+{
+ word32 w[80];
+ word32 a, b, c, d, e;
+ int t;
+
+#ifdef RANDOM_DIAGNOSTICS
+ {
+ extern int random_diagnostics;
+ if (random_diagnostics) {
+ int i;
+ printf("SHATransform:");
+ for (i = 0; i < 5; i++)
+ printf(" %08x", digest[i]);
+ printf(" +");
+ for (i = 0; i < 16; i++)
+ printf(" %08x", block[i]);
+ }
+ }
+#endif
+
+ for (t = 0; t < 16; t++)
+ w[t] = block[t];
+
+ for (t = 16; t < 80; t++) {
+ word32 tmp = w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16];
+ w[t] = rol(tmp, 1);
+ }
+
+ a = digest[0];
+ b = digest[1];
+ c = digest[2];
+ d = digest[3];
+ e = digest[4];
+
+ for (t = 0; t < 20; t++) {
+ word32 tmp =
+ rol(a, 5) + ((b & c) | (d & ~b)) + e + w[t] + 0x5a827999;
+ e = d;
+ d = c;
+ c = rol(b, 30);
+ b = a;
+ a = tmp;
+ }
+ for (t = 20; t < 40; t++) {
+ word32 tmp = rol(a, 5) + (b ^ c ^ d) + e + w[t] + 0x6ed9eba1;
+ e = d;
+ d = c;
+ c = rol(b, 30);
+ b = a;
+ a = tmp;
+ }
+ for (t = 40; t < 60; t++) {
+ word32 tmp = rol(a,
+ 5) + ((b & c) | (b & d) | (c & d)) + e + w[t] +
+ 0x8f1bbcdc;
+ e = d;
+ d = c;
+ c = rol(b, 30);
+ b = a;
+ a = tmp;
+ }
+ for (t = 60; t < 80; t++) {
+ word32 tmp = rol(a, 5) + (b ^ c ^ d) + e + w[t] + 0xca62c1d6;
+ e = d;
+ d = c;
+ c = rol(b, 30);
+ b = a;
+ a = tmp;
+ }
+
+ digest[0] += a;
+ digest[1] += b;
+ digest[2] += c;
+ digest[3] += d;
+ digest[4] += e;
+
+#ifdef RANDOM_DIAGNOSTICS
+ {
+ extern int random_diagnostics;
+ if (random_diagnostics) {
+ int i;
+ printf(" =");
+ for (i = 0; i < 5; i++)
+ printf(" %08x", digest[i]);
+ printf("\n");
+ }
+ }
+#endif
+}
+
+/* ----------------------------------------------------------------------
+ * Outer SHA algorithm: take an arbitrary length byte string,
+ * convert it into 16-word blocks with the prescribed padding at
+ * the end, and pass those blocks to the core SHA algorithm.
+ */
+
+void SHA_Init(SHA_State * s)
+{
+ SHA_Core_Init(s->h);
+ s->blkused = 0;
+ s->lenhi = s->lenlo = 0;
+}
+
+void SHA_Bytes(SHA_State * s, const void *p, int len)
+{
+ const unsigned char *q = (const unsigned char *) p;
+ uint32 wordblock[16];
+ uint32 lenw = len;
+ int i;
+
+ /*
+ * Update the length field.
+ */
+ s->lenlo += lenw;
+ s->lenhi += (s->lenlo < lenw);
+
+ if (s->blkused && s->blkused + len < 64) {
+ /*
+ * Trivial case: just add to the block.
+ */
+ memcpy(s->block + s->blkused, q, len);
+ s->blkused += len;
+ } else {
+ /*
+ * We must complete and process at least one block.
+ */
+ while (s->blkused + len >= 64) {
+ memcpy(s->block + s->blkused, q, 64 - s->blkused);
+ q += 64 - s->blkused;
+ len -= 64 - s->blkused;
+ /* Now process the block. Gather bytes big-endian into words */
+ for (i = 0; i < 16; i++) {
+ wordblock[i] =
+ (((uint32) s->block[i * 4 + 0]) << 24) |
+ (((uint32) s->block[i * 4 + 1]) << 16) |
+ (((uint32) s->block[i * 4 + 2]) << 8) |
+ (((uint32) s->block[i * 4 + 3]) << 0);
+ }
+ SHATransform(s->h, wordblock);
+ s->blkused = 0;
+ }
+ memcpy(s->block, q, len);
+ s->blkused = len;
+ }
+}
+
+void SHA_Final(SHA_State * s, unsigned char *output)
+{
+ int i;
+ int pad;
+ unsigned char c[64];
+ uint32 lenhi, lenlo;
+
+ if (s->blkused >= 56)
+ pad = 56 + 64 - s->blkused;
+ else
+ pad = 56 - s->blkused;
+
+ lenhi = (s->lenhi << 3) | (s->lenlo >> (32 - 3));
+ lenlo = (s->lenlo << 3);
+
+ memset(c, 0, pad);
+ c[0] = 0x80;
+ SHA_Bytes(s, &c, pad);
+
+ c[0] = (lenhi >> 24) & 0xFF;
+ c[1] = (lenhi >> 16) & 0xFF;
+ c[2] = (lenhi >> 8) & 0xFF;
+ c[3] = (lenhi >> 0) & 0xFF;
+ c[4] = (lenlo >> 24) & 0xFF;
+ c[5] = (lenlo >> 16) & 0xFF;
+ c[6] = (lenlo >> 8) & 0xFF;
+ c[7] = (lenlo >> 0) & 0xFF;
+
+ SHA_Bytes(s, &c, 8);
+
+ for (i = 0; i < 5; i++) {
+ output[i * 4] = (s->h[i] >> 24) & 0xFF;
+ output[i * 4 + 1] = (s->h[i] >> 16) & 0xFF;
+ output[i * 4 + 2] = (s->h[i] >> 8) & 0xFF;
+ output[i * 4 + 3] = (s->h[i]) & 0xFF;
+ }
+}
+
+void SHA_Simple(const void *p, int len, unsigned char *output)
+{
+ SHA_State s;
+
+ SHA_Init(&s);
+ SHA_Bytes(&s, p, len);
+ SHA_Final(&s, output);
+}
+
+/*
+ * Thin abstraction for things where hashes are pluggable.
+ */
+
+static void *sha1_init(void)
+{
+ SHA_State *s;
+
+ s = snew(SHA_State);
+ SHA_Init(s);
+ return s;
+}
+
+static void sha1_bytes(void *handle, void *p, int len)
+{
+ SHA_State *s = handle;
+
+ SHA_Bytes(s, p, len);
+}
+
+static void sha1_final(void *handle, unsigned char *output)
+{
+ SHA_State *s = handle;
+
+ SHA_Final(s, output);
+ sfree(s);
+}
+
+const struct ssh_hash ssh_sha1 = {
+ sha1_init, sha1_bytes, sha1_final, 20, "SHA-1"
+};
+
+/* ----------------------------------------------------------------------
+ * The above is the SHA-1 algorithm itself. Now we implement the
+ * HMAC wrapper on it.
+ */
+
+static void *sha1_make_context(void)
+{
+ return snewn(3, SHA_State);
+}
+
+static void sha1_free_context(void *handle)
+{
+ sfree(handle);
+}
+
+static void sha1_key_internal(void *handle, unsigned char *key, int len)
+{
+ SHA_State *keys = (SHA_State *)handle;
+ unsigned char foo[64];
+ int i;
+
+ memset(foo, 0x36, 64);
+ for (i = 0; i < len && i < 64; i++)
+ foo[i] ^= key[i];
+ SHA_Init(&keys[0]);
+ SHA_Bytes(&keys[0], foo, 64);
+
+ memset(foo, 0x5C, 64);
+ for (i = 0; i < len && i < 64; i++)
+ foo[i] ^= key[i];
+ SHA_Init(&keys[1]);
+ SHA_Bytes(&keys[1], foo, 64);
+
+ smemclr(foo, 64); /* burn the evidence */
+}
+
+static void sha1_key(void *handle, unsigned char *key)
+{
+ sha1_key_internal(handle, key, 20);
+}
+
+static void sha1_key_buggy(void *handle, unsigned char *key)
+{
+ sha1_key_internal(handle, key, 16);
+}
+
+static void hmacsha1_start(void *handle)
+{
+ SHA_State *keys = (SHA_State *)handle;
+
+ keys[2] = keys[0]; /* structure copy */
+}
+
+static void hmacsha1_bytes(void *handle, unsigned char const *blk, int len)
+{
+ SHA_State *keys = (SHA_State *)handle;
+ SHA_Bytes(&keys[2], (void *)blk, len);
+}
+
+static void hmacsha1_genresult(void *handle, unsigned char *hmac)
+{
+ SHA_State *keys = (SHA_State *)handle;
+ SHA_State s;
+ unsigned char intermediate[20];
+
+ s = keys[2]; /* structure copy */
+ SHA_Final(&s, intermediate);
+ s = keys[1]; /* structure copy */
+ SHA_Bytes(&s, intermediate, 20);
+ SHA_Final(&s, hmac);
+}
+
+static void sha1_do_hmac(void *handle, unsigned char *blk, int len,
+ unsigned long seq, unsigned char *hmac)
+{
+ unsigned char seqbuf[4];
+
+ PUT_32BIT_MSB_FIRST(seqbuf, seq);
+ hmacsha1_start(handle);
+ hmacsha1_bytes(handle, seqbuf, 4);
+ hmacsha1_bytes(handle, blk, len);
+ hmacsha1_genresult(handle, hmac);
+}
+
+static void sha1_generate(void *handle, unsigned char *blk, int len,
+ unsigned long seq)
+{
+ sha1_do_hmac(handle, blk, len, seq, blk + len);
+}
+
+static int hmacsha1_verresult(void *handle, unsigned char const *hmac)
+{
+ unsigned char correct[20];
+ hmacsha1_genresult(handle, correct);
+ return !memcmp(correct, hmac, 20);
+}
+
+static int sha1_verify(void *handle, unsigned char *blk, int len,
+ unsigned long seq)
+{
+ unsigned char correct[20];
+ sha1_do_hmac(handle, blk, len, seq, correct);
+ return !memcmp(correct, blk + len, 20);
+}
+
+static void hmacsha1_96_genresult(void *handle, unsigned char *hmac)
+{
+ unsigned char full[20];
+ hmacsha1_genresult(handle, full);
+ memcpy(hmac, full, 12);
+}
+
+static void sha1_96_generate(void *handle, unsigned char *blk, int len,
+ unsigned long seq)
+{
+ unsigned char full[20];
+ sha1_do_hmac(handle, blk, len, seq, full);
+ memcpy(blk + len, full, 12);
+}
+
+static int hmacsha1_96_verresult(void *handle, unsigned char const *hmac)
+{
+ unsigned char correct[20];
+ hmacsha1_genresult(handle, correct);
+ return !memcmp(correct, hmac, 12);
+}
+
+static int sha1_96_verify(void *handle, unsigned char *blk, int len,
+ unsigned long seq)
+{
+ unsigned char correct[20];
+ sha1_do_hmac(handle, blk, len, seq, correct);
+ return !memcmp(correct, blk + len, 12);
+}
+
+void hmac_sha1_simple(void *key, int keylen, void *data, int datalen,
+ unsigned char *output) {
+ SHA_State states[2];
+ unsigned char intermediate[20];
+
+ sha1_key_internal(states, key, keylen);
+ SHA_Bytes(&states[0], data, datalen);
+ SHA_Final(&states[0], intermediate);
+
+ SHA_Bytes(&states[1], intermediate, 20);
+ SHA_Final(&states[1], output);
+}
+
+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"
+};
+
+const struct ssh_mac ssh_hmac_sha1_96 = {
+ sha1_make_context, sha1_free_context, sha1_key,
+ sha1_96_generate, sha1_96_verify,
+ hmacsha1_start, hmacsha1_bytes,
+ hmacsha1_96_genresult, hmacsha1_96_verresult,
+ "hmac-sha1-96",
+ 12,
+ "HMAC-SHA1-96"
+};
+
+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,
+ "bug-compatible HMAC-SHA1"
+};
+
+const struct ssh_mac ssh_hmac_sha1_96_buggy = {
+ sha1_make_context, sha1_free_context, sha1_key_buggy,
+ sha1_96_generate, sha1_96_verify,
+ hmacsha1_start, hmacsha1_bytes,
+ hmacsha1_96_genresult, hmacsha1_96_verresult,
+ "hmac-sha1-96",
+ 12,
+ "bug-compatible HMAC-SHA1-96"
+};
diff --git a/tools/plink/sshzlib.c b/tools/plink/sshzlib.c
index 9c780a41f..8a64e3563 100644
--- a/tools/plink/sshzlib.c
+++ b/tools/plink/sshzlib.c
@@ -1,1385 +1,1394 @@
-/*
- * Zlib (RFC1950 / RFC1951) compression for PuTTY.
- *
- * There will no doubt be criticism of my decision to reimplement
- * Zlib compression from scratch instead of using the existing zlib
- * code. People will cry `reinventing the wheel'; they'll claim
- * that the `fundamental basis of OSS' is code reuse; they'll want
- * to see a really good reason for me having chosen not to use the
- * existing code.
- *
- * Well, here are my reasons. Firstly, I don't want to link the
- * whole of zlib into the PuTTY binary; PuTTY is justifiably proud
- * of its small size and I think zlib contains a lot of unnecessary
- * baggage for the kind of compression that SSH requires.
- *
- * Secondly, I also don't like the alternative of using zlib.dll.
- * Another thing PuTTY is justifiably proud of is its ease of
- * installation, and the last thing I want to do is to start
- * mandating DLLs. Not only that, but there are two _kinds_ of
- * zlib.dll kicking around, one with C calling conventions on the
- * exported functions and another with WINAPI conventions, and
- * there would be a significant danger of getting the wrong one.
- *
- * Thirdly, there seems to be a difference of opinion on the IETF
- * secsh mailing list about the correct way to round off a
- * compressed packet and start the next. In particular, there's
- * some talk of switching to a mechanism zlib isn't currently
- * capable of supporting (see below for an explanation). Given that
- * sort of uncertainty, I thought it might be better to have code
- * that will support even the zlib-incompatible worst case.
- *
- * Fourthly, it's a _second implementation_. Second implementations
- * are fundamentally a Good Thing in standardisation efforts. The
- * difference of opinion mentioned above has arisen _precisely_
- * because there has been only one zlib implementation and
- * everybody has used it. I don't intend that this should happen
- * again.
- */
-
-#include <stdlib.h>
-#include <assert.h>
-
-#ifdef ZLIB_STANDALONE
-
-/*
- * This module also makes a handy zlib decoding tool for when
- * you're picking apart Zip files or PDFs or PNGs. If you compile
- * it with ZLIB_STANDALONE defined, it builds on its own and
- * becomes a command-line utility.
- *
- * Therefore, here I provide a self-contained implementation of the
- * macros required from the rest of the PuTTY sources.
- */
-#define snew(type) ( (type *) malloc(sizeof(type)) )
-#define snewn(n, type) ( (type *) malloc((n) * sizeof(type)) )
-#define sresize(x, n, type) ( (type *) realloc((x), (n) * sizeof(type)) )
-#define sfree(x) ( free((x)) )
-
-#else
-#include "ssh.h"
-#endif
-
-#ifndef FALSE
-#define FALSE 0
-#define TRUE (!FALSE)
-#endif
-
-/* ----------------------------------------------------------------------
- * Basic LZ77 code. This bit is designed modularly, so it could be
- * ripped out and used in a different LZ77 compressor. Go to it,
- * and good luck :-)
- */
-
-struct LZ77InternalContext;
-struct LZ77Context {
- struct LZ77InternalContext *ictx;
- void *userdata;
- void (*literal) (struct LZ77Context * ctx, unsigned char c);
- void (*match) (struct LZ77Context * ctx, int distance, int len);
-};
-
-/*
- * Initialise the private fields of an LZ77Context. It's up to the
- * user to initialise the public fields.
- */
-static int lz77_init(struct LZ77Context *ctx);
-
-/*
- * Supply data to be compressed. Will update the private fields of
- * the LZ77Context, and will call literal() and match() to output.
- * If `compress' is FALSE, it will never emit a match, but will
- * instead call literal() for everything.
- */
-static void lz77_compress(struct LZ77Context *ctx,
- unsigned char *data, int len, int compress);
-
-/*
- * Modifiable parameters.
- */
-#define WINSIZE 32768 /* window size. Must be power of 2! */
-#define HASHMAX 2039 /* one more than max hash value */
-#define MAXMATCH 32 /* how many matches we track */
-#define HASHCHARS 3 /* how many chars make a hash */
-
-/*
- * This compressor takes a less slapdash approach than the
- * gzip/zlib one. Rather than allowing our hash chains to fall into
- * disuse near the far end, we keep them doubly linked so we can
- * _find_ the far end, and then every time we add a new byte to the
- * window (thus rolling round by one and removing the previous
- * byte), we can carefully remove the hash chain entry.
- */
-
-#define INVALID -1 /* invalid hash _and_ invalid offset */
-struct WindowEntry {
- short next, prev; /* array indices within the window */
- short hashval;
-};
-
-struct HashEntry {
- short first; /* window index of first in chain */
-};
-
-struct Match {
- int distance, len;
-};
-
-struct LZ77InternalContext {
- struct WindowEntry win[WINSIZE];
- unsigned char data[WINSIZE];
- int winpos;
- struct HashEntry hashtab[HASHMAX];
- unsigned char pending[HASHCHARS];
- int npending;
-};
-
-static int lz77_hash(unsigned char *data)
-{
- return (257 * data[0] + 263 * data[1] + 269 * data[2]) % HASHMAX;
-}
-
-static int lz77_init(struct LZ77Context *ctx)
-{
- struct LZ77InternalContext *st;
- int i;
-
- st = snew(struct LZ77InternalContext);
- if (!st)
- return 0;
-
- ctx->ictx = st;
-
- for (i = 0; i < WINSIZE; i++)
- st->win[i].next = st->win[i].prev = st->win[i].hashval = INVALID;
- for (i = 0; i < HASHMAX; i++)
- st->hashtab[i].first = INVALID;
- st->winpos = 0;
-
- st->npending = 0;
-
- return 1;
-}
-
-static void lz77_advance(struct LZ77InternalContext *st,
- unsigned char c, int hash)
-{
- int off;
-
- /*
- * Remove the hash entry at winpos from the tail of its chain,
- * or empty the chain if it's the only thing on the chain.
- */
- if (st->win[st->winpos].prev != INVALID) {
- st->win[st->win[st->winpos].prev].next = INVALID;
- } else if (st->win[st->winpos].hashval != INVALID) {
- st->hashtab[st->win[st->winpos].hashval].first = INVALID;
- }
-
- /*
- * Create a new entry at winpos and add it to the head of its
- * hash chain.
- */
- st->win[st->winpos].hashval = hash;
- st->win[st->winpos].prev = INVALID;
- off = st->win[st->winpos].next = st->hashtab[hash].first;
- st->hashtab[hash].first = st->winpos;
- if (off != INVALID)
- st->win[off].prev = st->winpos;
- st->data[st->winpos] = c;
-
- /*
- * Advance the window pointer.
- */
- st->winpos = (st->winpos + 1) & (WINSIZE - 1);
-}
-
-#define CHARAT(k) ( (k)<0 ? st->data[(st->winpos+k)&(WINSIZE-1)] : data[k] )
-
-static void lz77_compress(struct LZ77Context *ctx,
- unsigned char *data, int len, int compress)
-{
- struct LZ77InternalContext *st = ctx->ictx;
- int i, hash, distance, off, nmatch, matchlen, advance;
- struct Match defermatch, matches[MAXMATCH];
- int deferchr;
-
- /*
- * Add any pending characters from last time to the window. (We
- * might not be able to.)
- */
- for (i = 0; i < st->npending; i++) {
- unsigned char foo[HASHCHARS];
- int j;
- if (len + st->npending - i < HASHCHARS) {
- /* Update the pending array. */
- for (j = i; j < st->npending; j++)
- st->pending[j - i] = st->pending[j];
- break;
- }
- for (j = 0; j < HASHCHARS; j++)
- foo[j] = (i + j < st->npending ? st->pending[i + j] :
- data[i + j - st->npending]);
- lz77_advance(st, foo[0], lz77_hash(foo));
- }
- st->npending -= i;
-
- defermatch.distance = 0; /* appease compiler */
- defermatch.len = 0;
- deferchr = '\0';
- while (len > 0) {
-
- /* Don't even look for a match, if we're not compressing. */
- if (compress && len >= HASHCHARS) {
- /*
- * Hash the next few characters.
- */
- hash = lz77_hash(data);
-
- /*
- * Look the hash up in the corresponding hash chain and see
- * what we can find.
- */
- nmatch = 0;
- for (off = st->hashtab[hash].first;
- off != INVALID; off = st->win[off].next) {
- /* distance = 1 if off == st->winpos-1 */
- /* distance = WINSIZE if off == st->winpos */
- distance =
- WINSIZE - (off + WINSIZE - st->winpos) % WINSIZE;
- for (i = 0; i < HASHCHARS; i++)
- if (CHARAT(i) != CHARAT(i - distance))
- break;
- if (i == HASHCHARS) {
- matches[nmatch].distance = distance;
- matches[nmatch].len = 3;
- if (++nmatch >= MAXMATCH)
- break;
- }
- }
- } else {
- nmatch = 0;
- hash = INVALID;
- }
-
- if (nmatch > 0) {
- /*
- * We've now filled up matches[] with nmatch potential
- * matches. Follow them down to find the longest. (We
- * assume here that it's always worth favouring a
- * longer match over a shorter one.)
- */
- matchlen = HASHCHARS;
- while (matchlen < len) {
- int j;
- for (i = j = 0; i < nmatch; i++) {
- if (CHARAT(matchlen) ==
- CHARAT(matchlen - matches[i].distance)) {
- matches[j++] = matches[i];
- }
- }
- if (j == 0)
- break;
- matchlen++;
- nmatch = j;
- }
-
- /*
- * We've now got all the longest matches. We favour the
- * shorter distances, which means we go with matches[0].
- * So see if we want to defer it or throw it away.
- */
- matches[0].len = matchlen;
- if (defermatch.len > 0) {
- if (matches[0].len > defermatch.len + 1) {
- /* We have a better match. Emit the deferred char,
- * and defer this match. */
- ctx->literal(ctx, (unsigned char) deferchr);
- defermatch = matches[0];
- deferchr = data[0];
- advance = 1;
- } else {
- /* We don't have a better match. Do the deferred one. */
- ctx->match(ctx, defermatch.distance, defermatch.len);
- advance = defermatch.len - 1;
- defermatch.len = 0;
- }
- } else {
- /* There was no deferred match. Defer this one. */
- defermatch = matches[0];
- deferchr = data[0];
- advance = 1;
- }
- } else {
- /*
- * We found no matches. Emit the deferred match, if
- * any; otherwise emit a literal.
- */
- if (defermatch.len > 0) {
- ctx->match(ctx, defermatch.distance, defermatch.len);
- advance = defermatch.len - 1;
- defermatch.len = 0;
- } else {
- ctx->literal(ctx, data[0]);
- advance = 1;
- }
- }
-
- /*
- * Now advance the position by `advance' characters,
- * keeping the window and hash chains consistent.
- */
- while (advance > 0) {
- if (len >= HASHCHARS) {
- lz77_advance(st, *data, lz77_hash(data));
- } else {
- st->pending[st->npending++] = *data;
- }
- data++;
- len--;
- advance--;
- }
- }
-}
-
-/* ----------------------------------------------------------------------
- * Zlib compression. We always use the static Huffman tree option.
- * Mostly this is because it's hard to scan a block in advance to
- * work out better trees; dynamic trees are great when you're
- * compressing a large file under no significant time constraint,
- * but when you're compressing little bits in real time, things get
- * hairier.
- *
- * I suppose it's possible that I could compute Huffman trees based
- * on the frequencies in the _previous_ block, as a sort of
- * heuristic, but I'm not confident that the gain would balance out
- * having to transmit the trees.
- */
-
-struct Outbuf {
- unsigned char *outbuf;
- int outlen, outsize;
- unsigned long outbits;
- int noutbits;
- int firstblock;
- int comp_disabled;
-};
-
-static void outbits(struct Outbuf *out, unsigned long bits, int nbits)
-{
- assert(out->noutbits + nbits <= 32);
- out->outbits |= bits << out->noutbits;
- out->noutbits += nbits;
- while (out->noutbits >= 8) {
- if (out->outlen >= out->outsize) {
- out->outsize = out->outlen + 64;
- out->outbuf = sresize(out->outbuf, out->outsize, unsigned char);
- }
- out->outbuf[out->outlen++] = (unsigned char) (out->outbits & 0xFF);
- out->outbits >>= 8;
- out->noutbits -= 8;
- }
-}
-
-static const unsigned char mirrorbytes[256] = {
- 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
- 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
- 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
- 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
- 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
- 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
- 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
- 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
- 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
- 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
- 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
- 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
- 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
- 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
- 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
- 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
- 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
- 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
- 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
- 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
- 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
- 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
- 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
- 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
- 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
- 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
- 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
- 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
- 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
- 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
- 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
- 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
-};
-
-typedef struct {
- short code, extrabits;
- int min, max;
-} coderecord;
-
-static const coderecord lencodes[] = {
- {257, 0, 3, 3},
- {258, 0, 4, 4},
- {259, 0, 5, 5},
- {260, 0, 6, 6},
- {261, 0, 7, 7},
- {262, 0, 8, 8},
- {263, 0, 9, 9},
- {264, 0, 10, 10},
- {265, 1, 11, 12},
- {266, 1, 13, 14},
- {267, 1, 15, 16},
- {268, 1, 17, 18},
- {269, 2, 19, 22},
- {270, 2, 23, 26},
- {271, 2, 27, 30},
- {272, 2, 31, 34},
- {273, 3, 35, 42},
- {274, 3, 43, 50},
- {275, 3, 51, 58},
- {276, 3, 59, 66},
- {277, 4, 67, 82},
- {278, 4, 83, 98},
- {279, 4, 99, 114},
- {280, 4, 115, 130},
- {281, 5, 131, 162},
- {282, 5, 163, 194},
- {283, 5, 195, 226},
- {284, 5, 227, 257},
- {285, 0, 258, 258},
-};
-
-static const coderecord distcodes[] = {
- {0, 0, 1, 1},
- {1, 0, 2, 2},
- {2, 0, 3, 3},
- {3, 0, 4, 4},
- {4, 1, 5, 6},
- {5, 1, 7, 8},
- {6, 2, 9, 12},
- {7, 2, 13, 16},
- {8, 3, 17, 24},
- {9, 3, 25, 32},
- {10, 4, 33, 48},
- {11, 4, 49, 64},
- {12, 5, 65, 96},
- {13, 5, 97, 128},
- {14, 6, 129, 192},
- {15, 6, 193, 256},
- {16, 7, 257, 384},
- {17, 7, 385, 512},
- {18, 8, 513, 768},
- {19, 8, 769, 1024},
- {20, 9, 1025, 1536},
- {21, 9, 1537, 2048},
- {22, 10, 2049, 3072},
- {23, 10, 3073, 4096},
- {24, 11, 4097, 6144},
- {25, 11, 6145, 8192},
- {26, 12, 8193, 12288},
- {27, 12, 12289, 16384},
- {28, 13, 16385, 24576},
- {29, 13, 24577, 32768},
-};
-
-static void zlib_literal(struct LZ77Context *ectx, unsigned char c)
-{
- struct Outbuf *out = (struct Outbuf *) ectx->userdata;
-
- if (out->comp_disabled) {
- /*
- * We're in an uncompressed block, so just output the byte.
- */
- outbits(out, c, 8);
- return;
- }
-
- if (c <= 143) {
- /* 0 through 143 are 8 bits long starting at 00110000. */
- outbits(out, mirrorbytes[0x30 + c], 8);
- } else {
- /* 144 through 255 are 9 bits long starting at 110010000. */
- outbits(out, 1 + 2 * mirrorbytes[0x90 - 144 + c], 9);
- }
-}
-
-static void zlib_match(struct LZ77Context *ectx, int distance, int len)
-{
- const coderecord *d, *l;
- int i, j, k;
- struct Outbuf *out = (struct Outbuf *) ectx->userdata;
-
- assert(!out->comp_disabled);
-
- while (len > 0) {
- int thislen;
-
- /*
- * We can transmit matches of lengths 3 through 258
- * inclusive. So if len exceeds 258, we must transmit in
- * several steps, with 258 or less in each step.
- *
- * Specifically: if len >= 261, we can transmit 258 and be
- * sure of having at least 3 left for the next step. And if
- * len <= 258, we can just transmit len. But if len == 259
- * or 260, we must transmit len-3.
- */
- thislen = (len > 260 ? 258 : len <= 258 ? len : len - 3);
- len -= thislen;
-
- /*
- * Binary-search to find which length code we're
- * transmitting.
- */
- i = -1;
- j = sizeof(lencodes) / sizeof(*lencodes);
- while (1) {
- assert(j - i >= 2);
- k = (j + i) / 2;
- if (thislen < lencodes[k].min)
- j = k;
- else if (thislen > lencodes[k].max)
- i = k;
- else {
- l = &lencodes[k];
- break; /* found it! */
- }
- }
-
- /*
- * Transmit the length code. 256-279 are seven bits
- * starting at 0000000; 280-287 are eight bits starting at
- * 11000000.
- */
- if (l->code <= 279) {
- outbits(out, mirrorbytes[(l->code - 256) * 2], 7);
- } else {
- outbits(out, mirrorbytes[0xc0 - 280 + l->code], 8);
- }
-
- /*
- * Transmit the extra bits.
- */
- if (l->extrabits)
- outbits(out, thislen - l->min, l->extrabits);
-
- /*
- * Binary-search to find which distance code we're
- * transmitting.
- */
- i = -1;
- j = sizeof(distcodes) / sizeof(*distcodes);
- while (1) {
- assert(j - i >= 2);
- k = (j + i) / 2;
- if (distance < distcodes[k].min)
- j = k;
- else if (distance > distcodes[k].max)
- i = k;
- else {
- d = &distcodes[k];
- break; /* found it! */
- }
- }
-
- /*
- * Transmit the distance code. Five bits starting at 00000.
- */
- outbits(out, mirrorbytes[d->code * 8], 5);
-
- /*
- * Transmit the extra bits.
- */
- if (d->extrabits)
- outbits(out, distance - d->min, d->extrabits);
- }
-}
-
-void *zlib_compress_init(void)
-{
- struct Outbuf *out;
- struct LZ77Context *ectx = snew(struct LZ77Context);
-
- lz77_init(ectx);
- ectx->literal = zlib_literal;
- ectx->match = zlib_match;
-
- out = snew(struct Outbuf);
- out->outbits = out->noutbits = 0;
- out->firstblock = 1;
- out->comp_disabled = FALSE;
- ectx->userdata = out;
-
- return ectx;
-}
-
-void zlib_compress_cleanup(void *handle)
-{
- struct LZ77Context *ectx = (struct LZ77Context *)handle;
- sfree(ectx->userdata);
- sfree(ectx->ictx);
- sfree(ectx);
-}
-
-/*
- * Turn off actual LZ77 analysis for one block, to facilitate
- * construction of a precise-length IGNORE packet. Returns the
- * length adjustment (which is only valid for packets < 65536
- * bytes, but that seems reasonable enough).
- */
-static int zlib_disable_compression(void *handle)
-{
- struct LZ77Context *ectx = (struct LZ77Context *)handle;
- struct Outbuf *out = (struct Outbuf *) ectx->userdata;
- int n;
-
- out->comp_disabled = TRUE;
-
- n = 0;
- /*
- * If this is the first block, we will start by outputting two
- * header bytes, and then three bits to begin an uncompressed
- * block. This will cost three bytes (because we will start on
- * a byte boundary, this is certain).
- */
- if (out->firstblock) {
- n = 3;
- } else {
- /*
- * Otherwise, we will output seven bits to close the
- * previous static block, and _then_ three bits to begin an
- * uncompressed block, and then flush the current byte.
- * This may cost two bytes or three, depending on noutbits.
- */
- n += (out->noutbits + 10) / 8;
- }
-
- /*
- * Now we output four bytes for the length / ~length pair in
- * the uncompressed block.
- */
- n += 4;
-
- return n;
-}
-
-int zlib_compress_block(void *handle, unsigned char *block, int len,
- unsigned char **outblock, int *outlen)
-{
- struct LZ77Context *ectx = (struct LZ77Context *)handle;
- struct Outbuf *out = (struct Outbuf *) ectx->userdata;
- int in_block;
-
- out->outbuf = NULL;
- out->outlen = out->outsize = 0;
-
- /*
- * If this is the first block, output the Zlib (RFC1950) header
- * bytes 78 9C. (Deflate compression, 32K window size, default
- * algorithm.)
- */
- if (out->firstblock) {
- outbits(out, 0x9C78, 16);
- out->firstblock = 0;
-
- in_block = FALSE;
- } else
- in_block = TRUE;
-
- if (out->comp_disabled) {
- if (in_block)
- outbits(out, 0, 7); /* close static block */
-
- while (len > 0) {
- int blen = (len < 65535 ? len : 65535);
-
- /*
- * Start a Deflate (RFC1951) uncompressed block. We
- * transmit a zero bit (BFINAL=0), followed by two more
- * zero bits (BTYPE=00). Of course these are in the
- * wrong order (00 0), not that it matters.
- */
- outbits(out, 0, 3);
-
- /*
- * Output zero bits to align to a byte boundary.
- */
- if (out->noutbits)
- outbits(out, 0, 8 - out->noutbits);
-
- /*
- * Output the block length, and then its one's
- * complement. They're little-endian, so all we need to
- * do is pass them straight to outbits() with bit count
- * 16.
- */
- outbits(out, blen, 16);
- outbits(out, blen ^ 0xFFFF, 16);
-
- /*
- * Do the `compression': we need to pass the data to
- * lz77_compress so that it will be taken into account
- * for subsequent (distance,length) pairs. But
- * lz77_compress is passed FALSE, which means it won't
- * actually find (or even look for) any matches; so
- * every character will be passed straight to
- * zlib_literal which will spot out->comp_disabled and
- * emit in the uncompressed format.
- */
- lz77_compress(ectx, block, blen, FALSE);
-
- len -= blen;
- block += blen;
- }
- outbits(out, 2, 3); /* open new block */
- } else {
- if (!in_block) {
- /*
- * Start a Deflate (RFC1951) fixed-trees block. We
- * transmit a zero bit (BFINAL=0), followed by a zero
- * bit and a one bit (BTYPE=01). Of course these are in
- * the wrong order (01 0).
- */
- outbits(out, 2, 3);
- }
-
- /*
- * Do the compression.
- */
- lz77_compress(ectx, block, len, TRUE);
-
- /*
- * End the block (by transmitting code 256, which is
- * 0000000 in fixed-tree mode), and transmit some empty
- * blocks to ensure we have emitted the byte containing the
- * last piece of genuine data. There are three ways we can
- * do this:
- *
- * - Minimal flush. Output end-of-block and then open a
- * new static block. This takes 9 bits, which is
- * guaranteed to flush out the last genuine code in the
- * closed block; but allegedly zlib can't handle it.
- *
- * - Zlib partial flush. Output EOB, open and close an
- * empty static block, and _then_ open the new block.
- * This is the best zlib can handle.
- *
- * - Zlib sync flush. Output EOB, then an empty
- * _uncompressed_ block (000, then sync to byte
- * boundary, then send bytes 00 00 FF FF). Then open the
- * new block.
- *
- * For the moment, we will use Zlib partial flush.
- */
- outbits(out, 0, 7); /* close block */
- outbits(out, 2, 3 + 7); /* empty static block */
- outbits(out, 2, 3); /* open new block */
- }
-
- out->comp_disabled = FALSE;
-
- *outblock = out->outbuf;
- *outlen = out->outlen;
-
- return 1;
-}
-
-/* ----------------------------------------------------------------------
- * Zlib decompression. Of course, even though our compressor always
- * uses static trees, our _decompressor_ has to be capable of
- * handling dynamic trees if it sees them.
- */
-
-/*
- * The way we work the Huffman decode is to have a table lookup on
- * the first N bits of the input stream (in the order they arrive,
- * of course, i.e. the first bit of the Huffman code is in bit 0).
- * Each table entry lists the number of bits to consume, plus
- * either an output code or a pointer to a secondary table.
- */
-struct zlib_table;
-struct zlib_tableentry;
-
-struct zlib_tableentry {
- unsigned char nbits;
- short code;
- struct zlib_table *nexttable;
-};
-
-struct zlib_table {
- int mask; /* mask applied to input bit stream */
- struct zlib_tableentry *table;
-};
-
-#define MAXCODELEN 16
-#define MAXSYMS 288
-
-/*
- * Build a single-level decode table for elements
- * [minlength,maxlength) of the provided code/length tables, and
- * recurse to build subtables.
- */
-static struct zlib_table *zlib_mkonetab(int *codes, unsigned char *lengths,
- int nsyms,
- int pfx, int pfxbits, int bits)
-{
- struct zlib_table *tab = snew(struct zlib_table);
- int pfxmask = (1 << pfxbits) - 1;
- int nbits, i, j, code;
-
- tab->table = snewn(1 << bits, struct zlib_tableentry);
- tab->mask = (1 << bits) - 1;
-
- for (code = 0; code <= tab->mask; code++) {
- tab->table[code].code = -1;
- tab->table[code].nbits = 0;
- tab->table[code].nexttable = NULL;
- }
-
- for (i = 0; i < nsyms; i++) {
- if (lengths[i] <= pfxbits || (codes[i] & pfxmask) != pfx)
- continue;
- code = (codes[i] >> pfxbits) & tab->mask;
- for (j = code; j <= tab->mask; j += 1 << (lengths[i] - pfxbits)) {
- tab->table[j].code = i;
- nbits = lengths[i] - pfxbits;
- if (tab->table[j].nbits < nbits)
- tab->table[j].nbits = nbits;
- }
- }
- for (code = 0; code <= tab->mask; code++) {
- if (tab->table[code].nbits <= bits)
- continue;
- /* Generate a subtable. */
- tab->table[code].code = -1;
- nbits = tab->table[code].nbits - bits;
- if (nbits > 7)
- nbits = 7;
- tab->table[code].nbits = bits;
- tab->table[code].nexttable = zlib_mkonetab(codes, lengths, nsyms,
- pfx | (code << pfxbits),
- pfxbits + bits, nbits);
- }
-
- return tab;
-}
-
-/*
- * Build a decode table, given a set of Huffman tree lengths.
- */
-static struct zlib_table *zlib_mktable(unsigned char *lengths,
- int nlengths)
-{
- int count[MAXCODELEN], startcode[MAXCODELEN], codes[MAXSYMS];
- int code, maxlen;
- int i, j;
-
- /* Count the codes of each length. */
- maxlen = 0;
- for (i = 1; i < MAXCODELEN; i++)
- count[i] = 0;
- for (i = 0; i < nlengths; i++) {
- count[lengths[i]]++;
- if (maxlen < lengths[i])
- maxlen = lengths[i];
- }
- /* Determine the starting code for each length block. */
- code = 0;
- for (i = 1; i < MAXCODELEN; i++) {
- startcode[i] = code;
- code += count[i];
- code <<= 1;
- }
- /* Determine the code for each symbol. Mirrored, of course. */
- for (i = 0; i < nlengths; i++) {
- code = startcode[lengths[i]]++;
- codes[i] = 0;
- for (j = 0; j < lengths[i]; j++) {
- codes[i] = (codes[i] << 1) | (code & 1);
- code >>= 1;
- }
- }
-
- /*
- * Now we have the complete list of Huffman codes. Build a
- * table.
- */
- return zlib_mkonetab(codes, lengths, nlengths, 0, 0,
- maxlen < 9 ? maxlen : 9);
-}
-
-static int zlib_freetable(struct zlib_table **ztab)
-{
- struct zlib_table *tab;
- int code;
-
- if (ztab == NULL)
- return -1;
-
- if (*ztab == NULL)
- return 0;
-
- tab = *ztab;
-
- for (code = 0; code <= tab->mask; code++)
- if (tab->table[code].nexttable != NULL)
- zlib_freetable(&tab->table[code].nexttable);
-
- sfree(tab->table);
- tab->table = NULL;
-
- sfree(tab);
- *ztab = NULL;
-
- return (0);
-}
-
-struct zlib_decompress_ctx {
- struct zlib_table *staticlentable, *staticdisttable;
- struct zlib_table *currlentable, *currdisttable, *lenlentable;
- enum {
- START, OUTSIDEBLK,
- TREES_HDR, TREES_LENLEN, TREES_LEN, TREES_LENREP,
- INBLK, GOTLENSYM, GOTLEN, GOTDISTSYM,
- UNCOMP_LEN, UNCOMP_NLEN, UNCOMP_DATA
- } state;
- int sym, hlit, hdist, hclen, lenptr, lenextrabits, lenaddon, len,
- lenrep;
- int uncomplen;
- unsigned char lenlen[19];
- unsigned char lengths[286 + 32];
- unsigned long bits;
- int nbits;
- unsigned char window[WINSIZE];
- int winpos;
- unsigned char *outblk;
- int outlen, outsize;
-};
-
-void *zlib_decompress_init(void)
-{
- struct zlib_decompress_ctx *dctx = snew(struct zlib_decompress_ctx);
- unsigned char lengths[288];
-
- memset(lengths, 8, 144);
- memset(lengths + 144, 9, 256 - 144);
- memset(lengths + 256, 7, 280 - 256);
- memset(lengths + 280, 8, 288 - 280);
- dctx->staticlentable = zlib_mktable(lengths, 288);
- memset(lengths, 5, 32);
- dctx->staticdisttable = zlib_mktable(lengths, 32);
- dctx->state = START; /* even before header */
- dctx->currlentable = dctx->currdisttable = dctx->lenlentable = NULL;
- dctx->bits = 0;
- dctx->nbits = 0;
- dctx->winpos = 0;
-
- return dctx;
-}
-
-void zlib_decompress_cleanup(void *handle)
-{
- struct zlib_decompress_ctx *dctx = (struct zlib_decompress_ctx *)handle;
-
- if (dctx->currlentable && dctx->currlentable != dctx->staticlentable)
- zlib_freetable(&dctx->currlentable);
- if (dctx->currdisttable && dctx->currdisttable != dctx->staticdisttable)
- zlib_freetable(&dctx->currdisttable);
- if (dctx->lenlentable)
- zlib_freetable(&dctx->lenlentable);
- zlib_freetable(&dctx->staticlentable);
- zlib_freetable(&dctx->staticdisttable);
- sfree(dctx);
-}
-
-static int zlib_huflookup(unsigned long *bitsp, int *nbitsp,
- struct zlib_table *tab)
-{
- unsigned long bits = *bitsp;
- int nbits = *nbitsp;
- while (1) {
- struct zlib_tableentry *ent;
- ent = &tab->table[bits & tab->mask];
- if (ent->nbits > nbits)
- return -1; /* not enough data */
- bits >>= ent->nbits;
- nbits -= ent->nbits;
- if (ent->code == -1)
- tab = ent->nexttable;
- else {
- *bitsp = bits;
- *nbitsp = nbits;
- return ent->code;
- }
-
- if (!tab) {
- /*
- * There was a missing entry in the table, presumably
- * due to an invalid Huffman table description, and the
- * subsequent data has attempted to use the missing
- * entry. Return a decoding failure.
- */
- return -2;
- }
- }
-}
-
-static void zlib_emit_char(struct zlib_decompress_ctx *dctx, int c)
-{
- dctx->window[dctx->winpos] = c;
- dctx->winpos = (dctx->winpos + 1) & (WINSIZE - 1);
- if (dctx->outlen >= dctx->outsize) {
- dctx->outsize = dctx->outlen + 512;
- dctx->outblk = sresize(dctx->outblk, dctx->outsize, unsigned char);
- }
- dctx->outblk[dctx->outlen++] = c;
-}
-
-#define EATBITS(n) ( dctx->nbits -= (n), dctx->bits >>= (n) )
-
-int zlib_decompress_block(void *handle, unsigned char *block, int len,
- unsigned char **outblock, int *outlen)
-{
- struct zlib_decompress_ctx *dctx = (struct zlib_decompress_ctx *)handle;
- const coderecord *rec;
- int code, blktype, rep, dist, nlen, header;
- static const unsigned char lenlenmap[] = {
- 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
- };
-
- dctx->outblk = snewn(256, unsigned char);
- dctx->outsize = 256;
- dctx->outlen = 0;
-
- while (len > 0 || dctx->nbits > 0) {
- while (dctx->nbits < 24 && len > 0) {
- dctx->bits |= (*block++) << dctx->nbits;
- dctx->nbits += 8;
- len--;
- }
- switch (dctx->state) {
- case START:
- /* Expect 16-bit zlib header. */
- if (dctx->nbits < 16)
- goto finished; /* done all we can */
-
- /*
- * The header is stored as a big-endian 16-bit integer,
- * in contrast to the general little-endian policy in
- * the rest of the format :-(
- */
- header = (((dctx->bits & 0xFF00) >> 8) |
- ((dctx->bits & 0x00FF) << 8));
- EATBITS(16);
-
- /*
- * Check the header:
- *
- * - bits 8-11 should be 1000 (Deflate/RFC1951)
- * - bits 12-15 should be at most 0111 (window size)
- * - bit 5 should be zero (no dictionary present)
- * - we don't care about bits 6-7 (compression rate)
- * - bits 0-4 should be set up to make the whole thing
- * a multiple of 31 (checksum).
- */
- if ((header & 0x0F00) != 0x0800 ||
- (header & 0xF000) > 0x7000 ||
- (header & 0x0020) != 0x0000 ||
- (header % 31) != 0)
- goto decode_error;
-
- dctx->state = OUTSIDEBLK;
- break;
- case OUTSIDEBLK:
- /* Expect 3-bit block header. */
- if (dctx->nbits < 3)
- goto finished; /* done all we can */
- EATBITS(1);
- blktype = dctx->bits & 3;
- EATBITS(2);
- if (blktype == 0) {
- int to_eat = dctx->nbits & 7;
- dctx->state = UNCOMP_LEN;
- EATBITS(to_eat); /* align to byte boundary */
- } else if (blktype == 1) {
- dctx->currlentable = dctx->staticlentable;
- dctx->currdisttable = dctx->staticdisttable;
- dctx->state = INBLK;
- } else if (blktype == 2) {
- dctx->state = TREES_HDR;
- }
- break;
- case TREES_HDR:
- /*
- * Dynamic block header. Five bits of HLIT, five of
- * HDIST, four of HCLEN.
- */
- if (dctx->nbits < 5 + 5 + 4)
- goto finished; /* done all we can */
- dctx->hlit = 257 + (dctx->bits & 31);
- EATBITS(5);
- dctx->hdist = 1 + (dctx->bits & 31);
- EATBITS(5);
- dctx->hclen = 4 + (dctx->bits & 15);
- EATBITS(4);
- dctx->lenptr = 0;
- dctx->state = TREES_LENLEN;
- memset(dctx->lenlen, 0, sizeof(dctx->lenlen));
- break;
- case TREES_LENLEN:
- if (dctx->nbits < 3)
- goto finished;
- while (dctx->lenptr < dctx->hclen && dctx->nbits >= 3) {
- dctx->lenlen[lenlenmap[dctx->lenptr++]] =
- (unsigned char) (dctx->bits & 7);
- EATBITS(3);
- }
- if (dctx->lenptr == dctx->hclen) {
- dctx->lenlentable = zlib_mktable(dctx->lenlen, 19);
- dctx->state = TREES_LEN;
- dctx->lenptr = 0;
- }
- break;
- case TREES_LEN:
- if (dctx->lenptr >= dctx->hlit + dctx->hdist) {
- dctx->currlentable = zlib_mktable(dctx->lengths, dctx->hlit);
- dctx->currdisttable = zlib_mktable(dctx->lengths + dctx->hlit,
- dctx->hdist);
- zlib_freetable(&dctx->lenlentable);
- dctx->lenlentable = NULL;
- dctx->state = INBLK;
- break;
- }
- code =
- zlib_huflookup(&dctx->bits, &dctx->nbits, dctx->lenlentable);
- if (code == -1)
- goto finished;
- if (code == -2)
- goto decode_error;
- if (code < 16)
- dctx->lengths[dctx->lenptr++] = code;
- else {
- dctx->lenextrabits = (code == 16 ? 2 : code == 17 ? 3 : 7);
- dctx->lenaddon = (code == 18 ? 11 : 3);
- dctx->lenrep = (code == 16 && dctx->lenptr > 0 ?
- dctx->lengths[dctx->lenptr - 1] : 0);
- dctx->state = TREES_LENREP;
- }
- break;
- case TREES_LENREP:
- if (dctx->nbits < dctx->lenextrabits)
- goto finished;
- rep =
- dctx->lenaddon +
- (dctx->bits & ((1 << dctx->lenextrabits) - 1));
- EATBITS(dctx->lenextrabits);
- while (rep > 0 && dctx->lenptr < dctx->hlit + dctx->hdist) {
- dctx->lengths[dctx->lenptr] = dctx->lenrep;
- dctx->lenptr++;
- rep--;
- }
- dctx->state = TREES_LEN;
- break;
- case INBLK:
- code =
- zlib_huflookup(&dctx->bits, &dctx->nbits, dctx->currlentable);
- if (code == -1)
- goto finished;
- if (code == -2)
- goto decode_error;
- if (code < 256)
- zlib_emit_char(dctx, code);
- else if (code == 256) {
- dctx->state = OUTSIDEBLK;
- if (dctx->currlentable != dctx->staticlentable) {
- zlib_freetable(&dctx->currlentable);
- dctx->currlentable = NULL;
- }
- if (dctx->currdisttable != dctx->staticdisttable) {
- zlib_freetable(&dctx->currdisttable);
- dctx->currdisttable = NULL;
- }
- } else if (code < 286) { /* static tree can give >285; ignore */
- dctx->state = GOTLENSYM;
- dctx->sym = code;
- }
- break;
- case GOTLENSYM:
- rec = &lencodes[dctx->sym - 257];
- if (dctx->nbits < rec->extrabits)
- goto finished;
- dctx->len =
- rec->min + (dctx->bits & ((1 << rec->extrabits) - 1));
- EATBITS(rec->extrabits);
- dctx->state = GOTLEN;
- break;
- case GOTLEN:
- code =
- zlib_huflookup(&dctx->bits, &dctx->nbits,
- dctx->currdisttable);
- if (code == -1)
- goto finished;
- if (code == -2)
- goto decode_error;
- dctx->state = GOTDISTSYM;
- dctx->sym = code;
- break;
- case GOTDISTSYM:
- rec = &distcodes[dctx->sym];
- if (dctx->nbits < rec->extrabits)
- goto finished;
- dist = rec->min + (dctx->bits & ((1 << rec->extrabits) - 1));
- EATBITS(rec->extrabits);
- dctx->state = INBLK;
- while (dctx->len--)
- zlib_emit_char(dctx, dctx->window[(dctx->winpos - dist) &
- (WINSIZE - 1)]);
- break;
- case UNCOMP_LEN:
- /*
- * Uncompressed block. We expect to see a 16-bit LEN.
- */
- if (dctx->nbits < 16)
- goto finished;
- dctx->uncomplen = dctx->bits & 0xFFFF;
- EATBITS(16);
- dctx->state = UNCOMP_NLEN;
- break;
- case UNCOMP_NLEN:
- /*
- * Uncompressed block. We expect to see a 16-bit NLEN,
- * which should be the one's complement of the previous
- * LEN.
- */
- if (dctx->nbits < 16)
- goto finished;
- nlen = dctx->bits & 0xFFFF;
- EATBITS(16);
- if (dctx->uncomplen != (nlen ^ 0xFFFF))
- goto decode_error;
- if (dctx->uncomplen == 0)
- dctx->state = OUTSIDEBLK; /* block is empty */
- else
- dctx->state = UNCOMP_DATA;
- break;
- case UNCOMP_DATA:
- if (dctx->nbits < 8)
- goto finished;
- zlib_emit_char(dctx, dctx->bits & 0xFF);
- EATBITS(8);
- if (--dctx->uncomplen == 0)
- dctx->state = OUTSIDEBLK; /* end of uncompressed block */
- break;
- }
- }
-
- finished:
- *outblock = dctx->outblk;
- *outlen = dctx->outlen;
- return 1;
-
- decode_error:
- sfree(dctx->outblk);
- *outblock = dctx->outblk = NULL;
- *outlen = 0;
- return 0;
-}
-
-#ifdef ZLIB_STANDALONE
-
-#include <stdio.h>
-#include <string.h>
-
-int main(int argc, char **argv)
-{
- unsigned char buf[16], *outbuf;
- int ret, outlen;
- void *handle;
- int noheader = FALSE, opts = TRUE;
- char *filename = NULL;
- FILE *fp;
-
- while (--argc) {
- char *p = *++argv;
-
- if (p[0] == '-' && opts) {
- if (!strcmp(p, "-d"))
- noheader = TRUE;
- else if (!strcmp(p, "--"))
- opts = FALSE; /* next thing is filename */
- else {
- fprintf(stderr, "unknown command line option '%s'\n", p);
- return 1;
- }
- } else if (!filename) {
- filename = p;
- } else {
- fprintf(stderr, "can only handle one filename\n");
- return 1;
- }
- }
-
- handle = zlib_decompress_init();
-
- if (noheader) {
- /*
- * Provide missing zlib header if -d was specified.
- */
- zlib_decompress_block(handle, "\x78\x9C", 2, &outbuf, &outlen);
- assert(outlen == 0);
- }
-
- if (filename)
- fp = fopen(filename, "rb");
- else
- fp = stdin;
-
- if (!fp) {
- assert(filename);
- fprintf(stderr, "unable to open '%s'\n", filename);
- return 1;
- }
-
- while (1) {
- ret = fread(buf, 1, sizeof(buf), fp);
- if (ret <= 0)
- break;
- zlib_decompress_block(handle, buf, ret, &outbuf, &outlen);
- if (outbuf) {
- if (outlen)
- fwrite(outbuf, 1, outlen, stdout);
- sfree(outbuf);
- } else {
- fprintf(stderr, "decoding error\n");
- return 1;
- }
- }
-
- zlib_decompress_cleanup(handle);
-
- if (filename)
- fclose(fp);
-
- return 0;
-}
-
-#else
-
-const struct ssh_compress ssh_zlib = {
- "zlib",
- "zlib@openssh.com", /* delayed version */
- zlib_compress_init,
- zlib_compress_cleanup,
- zlib_compress_block,
- zlib_decompress_init,
- zlib_decompress_cleanup,
- zlib_decompress_block,
- zlib_disable_compression,
- "zlib (RFC1950)"
-};
-
-#endif
+/*
+ * Zlib (RFC1950 / RFC1951) compression for PuTTY.
+ *
+ * There will no doubt be criticism of my decision to reimplement
+ * Zlib compression from scratch instead of using the existing zlib
+ * code. People will cry `reinventing the wheel'; they'll claim
+ * that the `fundamental basis of OSS' is code reuse; they'll want
+ * to see a really good reason for me having chosen not to use the
+ * existing code.
+ *
+ * Well, here are my reasons. Firstly, I don't want to link the
+ * whole of zlib into the PuTTY binary; PuTTY is justifiably proud
+ * of its small size and I think zlib contains a lot of unnecessary
+ * baggage for the kind of compression that SSH requires.
+ *
+ * Secondly, I also don't like the alternative of using zlib.dll.
+ * Another thing PuTTY is justifiably proud of is its ease of
+ * installation, and the last thing I want to do is to start
+ * mandating DLLs. Not only that, but there are two _kinds_ of
+ * zlib.dll kicking around, one with C calling conventions on the
+ * exported functions and another with WINAPI conventions, and
+ * there would be a significant danger of getting the wrong one.
+ *
+ * Thirdly, there seems to be a difference of opinion on the IETF
+ * secsh mailing list about the correct way to round off a
+ * compressed packet and start the next. In particular, there's
+ * some talk of switching to a mechanism zlib isn't currently
+ * capable of supporting (see below for an explanation). Given that
+ * sort of uncertainty, I thought it might be better to have code
+ * that will support even the zlib-incompatible worst case.
+ *
+ * Fourthly, it's a _second implementation_. Second implementations
+ * are fundamentally a Good Thing in standardisation efforts. The
+ * difference of opinion mentioned above has arisen _precisely_
+ * because there has been only one zlib implementation and
+ * everybody has used it. I don't intend that this should happen
+ * again.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#ifdef ZLIB_STANDALONE
+
+/*
+ * This module also makes a handy zlib decoding tool for when
+ * you're picking apart Zip files or PDFs or PNGs. If you compile
+ * it with ZLIB_STANDALONE defined, it builds on its own and
+ * becomes a command-line utility.
+ *
+ * Therefore, here I provide a self-contained implementation of the
+ * macros required from the rest of the PuTTY sources.
+ */
+#define snew(type) ( (type *) malloc(sizeof(type)) )
+#define snewn(n, type) ( (type *) malloc((n) * sizeof(type)) )
+#define sresize(x, n, type) ( (type *) realloc((x), (n) * sizeof(type)) )
+#define sfree(x) ( free((x)) )
+
+#else
+#include "ssh.h"
+#endif
+
+#ifndef FALSE
+#define FALSE 0
+#define TRUE (!FALSE)
+#endif
+
+/* ----------------------------------------------------------------------
+ * Basic LZ77 code. This bit is designed modularly, so it could be
+ * ripped out and used in a different LZ77 compressor. Go to it,
+ * and good luck :-)
+ */
+
+struct LZ77InternalContext;
+struct LZ77Context {
+ struct LZ77InternalContext *ictx;
+ void *userdata;
+ void (*literal) (struct LZ77Context * ctx, unsigned char c);
+ void (*match) (struct LZ77Context * ctx, int distance, int len);
+};
+
+/*
+ * Initialise the private fields of an LZ77Context. It's up to the
+ * user to initialise the public fields.
+ */
+static int lz77_init(struct LZ77Context *ctx);
+
+/*
+ * Supply data to be compressed. Will update the private fields of
+ * the LZ77Context, and will call literal() and match() to output.
+ * If `compress' is FALSE, it will never emit a match, but will
+ * instead call literal() for everything.
+ */
+static void lz77_compress(struct LZ77Context *ctx,
+ unsigned char *data, int len, int compress);
+
+/*
+ * Modifiable parameters.
+ */
+#define WINSIZE 32768 /* window size. Must be power of 2! */
+#define HASHMAX 2039 /* one more than max hash value */
+#define MAXMATCH 32 /* how many matches we track */
+#define HASHCHARS 3 /* how many chars make a hash */
+
+/*
+ * This compressor takes a less slapdash approach than the
+ * gzip/zlib one. Rather than allowing our hash chains to fall into
+ * disuse near the far end, we keep them doubly linked so we can
+ * _find_ the far end, and then every time we add a new byte to the
+ * window (thus rolling round by one and removing the previous
+ * byte), we can carefully remove the hash chain entry.
+ */
+
+#define INVALID -1 /* invalid hash _and_ invalid offset */
+struct WindowEntry {
+ short next, prev; /* array indices within the window */
+ short hashval;
+};
+
+struct HashEntry {
+ short first; /* window index of first in chain */
+};
+
+struct Match {
+ int distance, len;
+};
+
+struct LZ77InternalContext {
+ struct WindowEntry win[WINSIZE];
+ unsigned char data[WINSIZE];
+ int winpos;
+ struct HashEntry hashtab[HASHMAX];
+ unsigned char pending[HASHCHARS];
+ int npending;
+};
+
+static int lz77_hash(unsigned char *data)
+{
+ return (257 * data[0] + 263 * data[1] + 269 * data[2]) % HASHMAX;
+}
+
+static int lz77_init(struct LZ77Context *ctx)
+{
+ struct LZ77InternalContext *st;
+ int i;
+
+ st = snew(struct LZ77InternalContext);
+ if (!st)
+ return 0;
+
+ ctx->ictx = st;
+
+ for (i = 0; i < WINSIZE; i++)
+ st->win[i].next = st->win[i].prev = st->win[i].hashval = INVALID;
+ for (i = 0; i < HASHMAX; i++)
+ st->hashtab[i].first = INVALID;
+ st->winpos = 0;
+
+ st->npending = 0;
+
+ return 1;
+}
+
+static void lz77_advance(struct LZ77InternalContext *st,
+ unsigned char c, int hash)
+{
+ int off;
+
+ /*
+ * Remove the hash entry at winpos from the tail of its chain,
+ * or empty the chain if it's the only thing on the chain.
+ */
+ if (st->win[st->winpos].prev != INVALID) {
+ st->win[st->win[st->winpos].prev].next = INVALID;
+ } else if (st->win[st->winpos].hashval != INVALID) {
+ st->hashtab[st->win[st->winpos].hashval].first = INVALID;
+ }
+
+ /*
+ * Create a new entry at winpos and add it to the head of its
+ * hash chain.
+ */
+ st->win[st->winpos].hashval = hash;
+ st->win[st->winpos].prev = INVALID;
+ off = st->win[st->winpos].next = st->hashtab[hash].first;
+ st->hashtab[hash].first = st->winpos;
+ if (off != INVALID)
+ st->win[off].prev = st->winpos;
+ st->data[st->winpos] = c;
+
+ /*
+ * Advance the window pointer.
+ */
+ st->winpos = (st->winpos + 1) & (WINSIZE - 1);
+}
+
+#define CHARAT(k) ( (k)<0 ? st->data[(st->winpos+k)&(WINSIZE-1)] : data[k] )
+
+static void lz77_compress(struct LZ77Context *ctx,
+ unsigned char *data, int len, int compress)
+{
+ struct LZ77InternalContext *st = ctx->ictx;
+ int i, hash, distance, off, nmatch, matchlen, advance;
+ struct Match defermatch, matches[MAXMATCH];
+ int deferchr;
+
+ assert(st->npending <= HASHCHARS);
+
+ /*
+ * Add any pending characters from last time to the window. (We
+ * might not be able to.)
+ *
+ * This leaves st->pending empty in the usual case (when len >=
+ * HASHCHARS); otherwise it leaves st->pending empty enough that
+ * adding all the remaining 'len' characters will not push it past
+ * HASHCHARS in size.
+ */
+ for (i = 0; i < st->npending; i++) {
+ unsigned char foo[HASHCHARS];
+ int j;
+ if (len + st->npending - i < HASHCHARS) {
+ /* Update the pending array. */
+ for (j = i; j < st->npending; j++)
+ st->pending[j - i] = st->pending[j];
+ break;
+ }
+ for (j = 0; j < HASHCHARS; j++)
+ foo[j] = (i + j < st->npending ? st->pending[i + j] :
+ data[i + j - st->npending]);
+ lz77_advance(st, foo[0], lz77_hash(foo));
+ }
+ st->npending -= i;
+
+ defermatch.distance = 0; /* appease compiler */
+ defermatch.len = 0;
+ deferchr = '\0';
+ while (len > 0) {
+
+ /* Don't even look for a match, if we're not compressing. */
+ if (compress && len >= HASHCHARS) {
+ /*
+ * Hash the next few characters.
+ */
+ hash = lz77_hash(data);
+
+ /*
+ * Look the hash up in the corresponding hash chain and see
+ * what we can find.
+ */
+ nmatch = 0;
+ for (off = st->hashtab[hash].first;
+ off != INVALID; off = st->win[off].next) {
+ /* distance = 1 if off == st->winpos-1 */
+ /* distance = WINSIZE if off == st->winpos */
+ distance =
+ WINSIZE - (off + WINSIZE - st->winpos) % WINSIZE;
+ for (i = 0; i < HASHCHARS; i++)
+ if (CHARAT(i) != CHARAT(i - distance))
+ break;
+ if (i == HASHCHARS) {
+ matches[nmatch].distance = distance;
+ matches[nmatch].len = 3;
+ if (++nmatch >= MAXMATCH)
+ break;
+ }
+ }
+ } else {
+ nmatch = 0;
+ hash = INVALID;
+ }
+
+ if (nmatch > 0) {
+ /*
+ * We've now filled up matches[] with nmatch potential
+ * matches. Follow them down to find the longest. (We
+ * assume here that it's always worth favouring a
+ * longer match over a shorter one.)
+ */
+ matchlen = HASHCHARS;
+ while (matchlen < len) {
+ int j;
+ for (i = j = 0; i < nmatch; i++) {
+ if (CHARAT(matchlen) ==
+ CHARAT(matchlen - matches[i].distance)) {
+ matches[j++] = matches[i];
+ }
+ }
+ if (j == 0)
+ break;
+ matchlen++;
+ nmatch = j;
+ }
+
+ /*
+ * We've now got all the longest matches. We favour the
+ * shorter distances, which means we go with matches[0].
+ * So see if we want to defer it or throw it away.
+ */
+ matches[0].len = matchlen;
+ if (defermatch.len > 0) {
+ if (matches[0].len > defermatch.len + 1) {
+ /* We have a better match. Emit the deferred char,
+ * and defer this match. */
+ ctx->literal(ctx, (unsigned char) deferchr);
+ defermatch = matches[0];
+ deferchr = data[0];
+ advance = 1;
+ } else {
+ /* We don't have a better match. Do the deferred one. */
+ ctx->match(ctx, defermatch.distance, defermatch.len);
+ advance = defermatch.len - 1;
+ defermatch.len = 0;
+ }
+ } else {
+ /* There was no deferred match. Defer this one. */
+ defermatch = matches[0];
+ deferchr = data[0];
+ advance = 1;
+ }
+ } else {
+ /*
+ * We found no matches. Emit the deferred match, if
+ * any; otherwise emit a literal.
+ */
+ if (defermatch.len > 0) {
+ ctx->match(ctx, defermatch.distance, defermatch.len);
+ advance = defermatch.len - 1;
+ defermatch.len = 0;
+ } else {
+ ctx->literal(ctx, data[0]);
+ advance = 1;
+ }
+ }
+
+ /*
+ * Now advance the position by `advance' characters,
+ * keeping the window and hash chains consistent.
+ */
+ while (advance > 0) {
+ if (len >= HASHCHARS) {
+ lz77_advance(st, *data, lz77_hash(data));
+ } else {
+ assert(st->npending < HASHCHARS);
+ st->pending[st->npending++] = *data;
+ }
+ data++;
+ len--;
+ advance--;
+ }
+ }
+}
+
+/* ----------------------------------------------------------------------
+ * Zlib compression. We always use the static Huffman tree option.
+ * Mostly this is because it's hard to scan a block in advance to
+ * work out better trees; dynamic trees are great when you're
+ * compressing a large file under no significant time constraint,
+ * but when you're compressing little bits in real time, things get
+ * hairier.
+ *
+ * I suppose it's possible that I could compute Huffman trees based
+ * on the frequencies in the _previous_ block, as a sort of
+ * heuristic, but I'm not confident that the gain would balance out
+ * having to transmit the trees.
+ */
+
+struct Outbuf {
+ unsigned char *outbuf;
+ int outlen, outsize;
+ unsigned long outbits;
+ int noutbits;
+ int firstblock;
+ int comp_disabled;
+};
+
+static void outbits(struct Outbuf *out, unsigned long bits, int nbits)
+{
+ assert(out->noutbits + nbits <= 32);
+ out->outbits |= bits << out->noutbits;
+ out->noutbits += nbits;
+ while (out->noutbits >= 8) {
+ if (out->outlen >= out->outsize) {
+ out->outsize = out->outlen + 64;
+ out->outbuf = sresize(out->outbuf, out->outsize, unsigned char);
+ }
+ out->outbuf[out->outlen++] = (unsigned char) (out->outbits & 0xFF);
+ out->outbits >>= 8;
+ out->noutbits -= 8;
+ }
+}
+
+static const unsigned char mirrorbytes[256] = {
+ 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
+ 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
+ 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
+ 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
+ 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
+ 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+ 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
+ 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
+ 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
+ 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
+ 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
+ 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+ 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
+ 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
+ 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
+ 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
+ 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
+ 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+ 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
+ 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
+ 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
+ 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
+ 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
+ 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+ 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
+ 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
+ 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
+ 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
+ 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
+ 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+ 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
+ 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
+};
+
+typedef struct {
+ short code, extrabits;
+ int min, max;
+} coderecord;
+
+static const coderecord lencodes[] = {
+ {257, 0, 3, 3},
+ {258, 0, 4, 4},
+ {259, 0, 5, 5},
+ {260, 0, 6, 6},
+ {261, 0, 7, 7},
+ {262, 0, 8, 8},
+ {263, 0, 9, 9},
+ {264, 0, 10, 10},
+ {265, 1, 11, 12},
+ {266, 1, 13, 14},
+ {267, 1, 15, 16},
+ {268, 1, 17, 18},
+ {269, 2, 19, 22},
+ {270, 2, 23, 26},
+ {271, 2, 27, 30},
+ {272, 2, 31, 34},
+ {273, 3, 35, 42},
+ {274, 3, 43, 50},
+ {275, 3, 51, 58},
+ {276, 3, 59, 66},
+ {277, 4, 67, 82},
+ {278, 4, 83, 98},
+ {279, 4, 99, 114},
+ {280, 4, 115, 130},
+ {281, 5, 131, 162},
+ {282, 5, 163, 194},
+ {283, 5, 195, 226},
+ {284, 5, 227, 257},
+ {285, 0, 258, 258},
+};
+
+static const coderecord distcodes[] = {
+ {0, 0, 1, 1},
+ {1, 0, 2, 2},
+ {2, 0, 3, 3},
+ {3, 0, 4, 4},
+ {4, 1, 5, 6},
+ {5, 1, 7, 8},
+ {6, 2, 9, 12},
+ {7, 2, 13, 16},
+ {8, 3, 17, 24},
+ {9, 3, 25, 32},
+ {10, 4, 33, 48},
+ {11, 4, 49, 64},
+ {12, 5, 65, 96},
+ {13, 5, 97, 128},
+ {14, 6, 129, 192},
+ {15, 6, 193, 256},
+ {16, 7, 257, 384},
+ {17, 7, 385, 512},
+ {18, 8, 513, 768},
+ {19, 8, 769, 1024},
+ {20, 9, 1025, 1536},
+ {21, 9, 1537, 2048},
+ {22, 10, 2049, 3072},
+ {23, 10, 3073, 4096},
+ {24, 11, 4097, 6144},
+ {25, 11, 6145, 8192},
+ {26, 12, 8193, 12288},
+ {27, 12, 12289, 16384},
+ {28, 13, 16385, 24576},
+ {29, 13, 24577, 32768},
+};
+
+static void zlib_literal(struct LZ77Context *ectx, unsigned char c)
+{
+ struct Outbuf *out = (struct Outbuf *) ectx->userdata;
+
+ if (out->comp_disabled) {
+ /*
+ * We're in an uncompressed block, so just output the byte.
+ */
+ outbits(out, c, 8);
+ return;
+ }
+
+ if (c <= 143) {
+ /* 0 through 143 are 8 bits long starting at 00110000. */
+ outbits(out, mirrorbytes[0x30 + c], 8);
+ } else {
+ /* 144 through 255 are 9 bits long starting at 110010000. */
+ outbits(out, 1 + 2 * mirrorbytes[0x90 - 144 + c], 9);
+ }
+}
+
+static void zlib_match(struct LZ77Context *ectx, int distance, int len)
+{
+ const coderecord *d, *l;
+ int i, j, k;
+ struct Outbuf *out = (struct Outbuf *) ectx->userdata;
+
+ assert(!out->comp_disabled);
+
+ while (len > 0) {
+ int thislen;
+
+ /*
+ * We can transmit matches of lengths 3 through 258
+ * inclusive. So if len exceeds 258, we must transmit in
+ * several steps, with 258 or less in each step.
+ *
+ * Specifically: if len >= 261, we can transmit 258 and be
+ * sure of having at least 3 left for the next step. And if
+ * len <= 258, we can just transmit len. But if len == 259
+ * or 260, we must transmit len-3.
+ */
+ thislen = (len > 260 ? 258 : len <= 258 ? len : len - 3);
+ len -= thislen;
+
+ /*
+ * Binary-search to find which length code we're
+ * transmitting.
+ */
+ i = -1;
+ j = sizeof(lencodes) / sizeof(*lencodes);
+ while (1) {
+ assert(j - i >= 2);
+ k = (j + i) / 2;
+ if (thislen < lencodes[k].min)
+ j = k;
+ else if (thislen > lencodes[k].max)
+ i = k;
+ else {
+ l = &lencodes[k];
+ break; /* found it! */
+ }
+ }
+
+ /*
+ * Transmit the length code. 256-279 are seven bits
+ * starting at 0000000; 280-287 are eight bits starting at
+ * 11000000.
+ */
+ if (l->code <= 279) {
+ outbits(out, mirrorbytes[(l->code - 256) * 2], 7);
+ } else {
+ outbits(out, mirrorbytes[0xc0 - 280 + l->code], 8);
+ }
+
+ /*
+ * Transmit the extra bits.
+ */
+ if (l->extrabits)
+ outbits(out, thislen - l->min, l->extrabits);
+
+ /*
+ * Binary-search to find which distance code we're
+ * transmitting.
+ */
+ i = -1;
+ j = sizeof(distcodes) / sizeof(*distcodes);
+ while (1) {
+ assert(j - i >= 2);
+ k = (j + i) / 2;
+ if (distance < distcodes[k].min)
+ j = k;
+ else if (distance > distcodes[k].max)
+ i = k;
+ else {
+ d = &distcodes[k];
+ break; /* found it! */
+ }
+ }
+
+ /*
+ * Transmit the distance code. Five bits starting at 00000.
+ */
+ outbits(out, mirrorbytes[d->code * 8], 5);
+
+ /*
+ * Transmit the extra bits.
+ */
+ if (d->extrabits)
+ outbits(out, distance - d->min, d->extrabits);
+ }
+}
+
+void *zlib_compress_init(void)
+{
+ struct Outbuf *out;
+ struct LZ77Context *ectx = snew(struct LZ77Context);
+
+ lz77_init(ectx);
+ ectx->literal = zlib_literal;
+ ectx->match = zlib_match;
+
+ out = snew(struct Outbuf);
+ out->outbits = out->noutbits = 0;
+ out->firstblock = 1;
+ out->comp_disabled = FALSE;
+ ectx->userdata = out;
+
+ return ectx;
+}
+
+void zlib_compress_cleanup(void *handle)
+{
+ struct LZ77Context *ectx = (struct LZ77Context *)handle;
+ sfree(ectx->userdata);
+ sfree(ectx->ictx);
+ sfree(ectx);
+}
+
+/*
+ * Turn off actual LZ77 analysis for one block, to facilitate
+ * construction of a precise-length IGNORE packet. Returns the
+ * length adjustment (which is only valid for packets < 65536
+ * bytes, but that seems reasonable enough).
+ */
+static int zlib_disable_compression(void *handle)
+{
+ struct LZ77Context *ectx = (struct LZ77Context *)handle;
+ struct Outbuf *out = (struct Outbuf *) ectx->userdata;
+ int n;
+
+ out->comp_disabled = TRUE;
+
+ n = 0;
+ /*
+ * If this is the first block, we will start by outputting two
+ * header bytes, and then three bits to begin an uncompressed
+ * block. This will cost three bytes (because we will start on
+ * a byte boundary, this is certain).
+ */
+ if (out->firstblock) {
+ n = 3;
+ } else {
+ /*
+ * Otherwise, we will output seven bits to close the
+ * previous static block, and _then_ three bits to begin an
+ * uncompressed block, and then flush the current byte.
+ * This may cost two bytes or three, depending on noutbits.
+ */
+ n += (out->noutbits + 10) / 8;
+ }
+
+ /*
+ * Now we output four bytes for the length / ~length pair in
+ * the uncompressed block.
+ */
+ n += 4;
+
+ return n;
+}
+
+int zlib_compress_block(void *handle, unsigned char *block, int len,
+ unsigned char **outblock, int *outlen)
+{
+ struct LZ77Context *ectx = (struct LZ77Context *)handle;
+ struct Outbuf *out = (struct Outbuf *) ectx->userdata;
+ int in_block;
+
+ out->outbuf = NULL;
+ out->outlen = out->outsize = 0;
+
+ /*
+ * If this is the first block, output the Zlib (RFC1950) header
+ * bytes 78 9C. (Deflate compression, 32K window size, default
+ * algorithm.)
+ */
+ if (out->firstblock) {
+ outbits(out, 0x9C78, 16);
+ out->firstblock = 0;
+
+ in_block = FALSE;
+ } else
+ in_block = TRUE;
+
+ if (out->comp_disabled) {
+ if (in_block)
+ outbits(out, 0, 7); /* close static block */
+
+ while (len > 0) {
+ int blen = (len < 65535 ? len : 65535);
+
+ /*
+ * Start a Deflate (RFC1951) uncompressed block. We
+ * transmit a zero bit (BFINAL=0), followed by two more
+ * zero bits (BTYPE=00). Of course these are in the
+ * wrong order (00 0), not that it matters.
+ */
+ outbits(out, 0, 3);
+
+ /*
+ * Output zero bits to align to a byte boundary.
+ */
+ if (out->noutbits)
+ outbits(out, 0, 8 - out->noutbits);
+
+ /*
+ * Output the block length, and then its one's
+ * complement. They're little-endian, so all we need to
+ * do is pass them straight to outbits() with bit count
+ * 16.
+ */
+ outbits(out, blen, 16);
+ outbits(out, blen ^ 0xFFFF, 16);
+
+ /*
+ * Do the `compression': we need to pass the data to
+ * lz77_compress so that it will be taken into account
+ * for subsequent (distance,length) pairs. But
+ * lz77_compress is passed FALSE, which means it won't
+ * actually find (or even look for) any matches; so
+ * every character will be passed straight to
+ * zlib_literal which will spot out->comp_disabled and
+ * emit in the uncompressed format.
+ */
+ lz77_compress(ectx, block, blen, FALSE);
+
+ len -= blen;
+ block += blen;
+ }
+ outbits(out, 2, 3); /* open new block */
+ } else {
+ if (!in_block) {
+ /*
+ * Start a Deflate (RFC1951) fixed-trees block. We
+ * transmit a zero bit (BFINAL=0), followed by a zero
+ * bit and a one bit (BTYPE=01). Of course these are in
+ * the wrong order (01 0).
+ */
+ outbits(out, 2, 3);
+ }
+
+ /*
+ * Do the compression.
+ */
+ lz77_compress(ectx, block, len, TRUE);
+
+ /*
+ * End the block (by transmitting code 256, which is
+ * 0000000 in fixed-tree mode), and transmit some empty
+ * blocks to ensure we have emitted the byte containing the
+ * last piece of genuine data. There are three ways we can
+ * do this:
+ *
+ * - Minimal flush. Output end-of-block and then open a
+ * new static block. This takes 9 bits, which is
+ * guaranteed to flush out the last genuine code in the
+ * closed block; but allegedly zlib can't handle it.
+ *
+ * - Zlib partial flush. Output EOB, open and close an
+ * empty static block, and _then_ open the new block.
+ * This is the best zlib can handle.
+ *
+ * - Zlib sync flush. Output EOB, then an empty
+ * _uncompressed_ block (000, then sync to byte
+ * boundary, then send bytes 00 00 FF FF). Then open the
+ * new block.
+ *
+ * For the moment, we will use Zlib partial flush.
+ */
+ outbits(out, 0, 7); /* close block */
+ outbits(out, 2, 3 + 7); /* empty static block */
+ outbits(out, 2, 3); /* open new block */
+ }
+
+ out->comp_disabled = FALSE;
+
+ *outblock = out->outbuf;
+ *outlen = out->outlen;
+
+ return 1;
+}
+
+/* ----------------------------------------------------------------------
+ * Zlib decompression. Of course, even though our compressor always
+ * uses static trees, our _decompressor_ has to be capable of
+ * handling dynamic trees if it sees them.
+ */
+
+/*
+ * The way we work the Huffman decode is to have a table lookup on
+ * the first N bits of the input stream (in the order they arrive,
+ * of course, i.e. the first bit of the Huffman code is in bit 0).
+ * Each table entry lists the number of bits to consume, plus
+ * either an output code or a pointer to a secondary table.
+ */
+struct zlib_table;
+struct zlib_tableentry;
+
+struct zlib_tableentry {
+ unsigned char nbits;
+ short code;
+ struct zlib_table *nexttable;
+};
+
+struct zlib_table {
+ int mask; /* mask applied to input bit stream */
+ struct zlib_tableentry *table;
+};
+
+#define MAXCODELEN 16
+#define MAXSYMS 288
+
+/*
+ * Build a single-level decode table for elements
+ * [minlength,maxlength) of the provided code/length tables, and
+ * recurse to build subtables.
+ */
+static struct zlib_table *zlib_mkonetab(int *codes, unsigned char *lengths,
+ int nsyms,
+ int pfx, int pfxbits, int bits)
+{
+ struct zlib_table *tab = snew(struct zlib_table);
+ int pfxmask = (1 << pfxbits) - 1;
+ int nbits, i, j, code;
+
+ tab->table = snewn(1 << bits, struct zlib_tableentry);
+ tab->mask = (1 << bits) - 1;
+
+ for (code = 0; code <= tab->mask; code++) {
+ tab->table[code].code = -1;
+ tab->table[code].nbits = 0;
+ tab->table[code].nexttable = NULL;
+ }
+
+ for (i = 0; i < nsyms; i++) {
+ if (lengths[i] <= pfxbits || (codes[i] & pfxmask) != pfx)
+ continue;
+ code = (codes[i] >> pfxbits) & tab->mask;
+ for (j = code; j <= tab->mask; j += 1 << (lengths[i] - pfxbits)) {
+ tab->table[j].code = i;
+ nbits = lengths[i] - pfxbits;
+ if (tab->table[j].nbits < nbits)
+ tab->table[j].nbits = nbits;
+ }
+ }
+ for (code = 0; code <= tab->mask; code++) {
+ if (tab->table[code].nbits <= bits)
+ continue;
+ /* Generate a subtable. */
+ tab->table[code].code = -1;
+ nbits = tab->table[code].nbits - bits;
+ if (nbits > 7)
+ nbits = 7;
+ tab->table[code].nbits = bits;
+ tab->table[code].nexttable = zlib_mkonetab(codes, lengths, nsyms,
+ pfx | (code << pfxbits),
+ pfxbits + bits, nbits);
+ }
+
+ return tab;
+}
+
+/*
+ * Build a decode table, given a set of Huffman tree lengths.
+ */
+static struct zlib_table *zlib_mktable(unsigned char *lengths,
+ int nlengths)
+{
+ int count[MAXCODELEN], startcode[MAXCODELEN], codes[MAXSYMS];
+ int code, maxlen;
+ int i, j;
+
+ /* Count the codes of each length. */
+ maxlen = 0;
+ for (i = 1; i < MAXCODELEN; i++)
+ count[i] = 0;
+ for (i = 0; i < nlengths; i++) {
+ count[lengths[i]]++;
+ if (maxlen < lengths[i])
+ maxlen = lengths[i];
+ }
+ /* Determine the starting code for each length block. */
+ code = 0;
+ for (i = 1; i < MAXCODELEN; i++) {
+ startcode[i] = code;
+ code += count[i];
+ code <<= 1;
+ }
+ /* Determine the code for each symbol. Mirrored, of course. */
+ for (i = 0; i < nlengths; i++) {
+ code = startcode[lengths[i]]++;
+ codes[i] = 0;
+ for (j = 0; j < lengths[i]; j++) {
+ codes[i] = (codes[i] << 1) | (code & 1);
+ code >>= 1;
+ }
+ }
+
+ /*
+ * Now we have the complete list of Huffman codes. Build a
+ * table.
+ */
+ return zlib_mkonetab(codes, lengths, nlengths, 0, 0,
+ maxlen < 9 ? maxlen : 9);
+}
+
+static int zlib_freetable(struct zlib_table **ztab)
+{
+ struct zlib_table *tab;
+ int code;
+
+ if (ztab == NULL)
+ return -1;
+
+ if (*ztab == NULL)
+ return 0;
+
+ tab = *ztab;
+
+ for (code = 0; code <= tab->mask; code++)
+ if (tab->table[code].nexttable != NULL)
+ zlib_freetable(&tab->table[code].nexttable);
+
+ sfree(tab->table);
+ tab->table = NULL;
+
+ sfree(tab);
+ *ztab = NULL;
+
+ return (0);
+}
+
+struct zlib_decompress_ctx {
+ struct zlib_table *staticlentable, *staticdisttable;
+ struct zlib_table *currlentable, *currdisttable, *lenlentable;
+ enum {
+ START, OUTSIDEBLK,
+ TREES_HDR, TREES_LENLEN, TREES_LEN, TREES_LENREP,
+ INBLK, GOTLENSYM, GOTLEN, GOTDISTSYM,
+ UNCOMP_LEN, UNCOMP_NLEN, UNCOMP_DATA
+ } state;
+ int sym, hlit, hdist, hclen, lenptr, lenextrabits, lenaddon, len,
+ lenrep;
+ int uncomplen;
+ unsigned char lenlen[19];
+ unsigned char lengths[286 + 32];
+ unsigned long bits;
+ int nbits;
+ unsigned char window[WINSIZE];
+ int winpos;
+ unsigned char *outblk;
+ int outlen, outsize;
+};
+
+void *zlib_decompress_init(void)
+{
+ struct zlib_decompress_ctx *dctx = snew(struct zlib_decompress_ctx);
+ unsigned char lengths[288];
+
+ memset(lengths, 8, 144);
+ memset(lengths + 144, 9, 256 - 144);
+ memset(lengths + 256, 7, 280 - 256);
+ memset(lengths + 280, 8, 288 - 280);
+ dctx->staticlentable = zlib_mktable(lengths, 288);
+ memset(lengths, 5, 32);
+ dctx->staticdisttable = zlib_mktable(lengths, 32);
+ dctx->state = START; /* even before header */
+ dctx->currlentable = dctx->currdisttable = dctx->lenlentable = NULL;
+ dctx->bits = 0;
+ dctx->nbits = 0;
+ dctx->winpos = 0;
+
+ return dctx;
+}
+
+void zlib_decompress_cleanup(void *handle)
+{
+ struct zlib_decompress_ctx *dctx = (struct zlib_decompress_ctx *)handle;
+
+ if (dctx->currlentable && dctx->currlentable != dctx->staticlentable)
+ zlib_freetable(&dctx->currlentable);
+ if (dctx->currdisttable && dctx->currdisttable != dctx->staticdisttable)
+ zlib_freetable(&dctx->currdisttable);
+ if (dctx->lenlentable)
+ zlib_freetable(&dctx->lenlentable);
+ zlib_freetable(&dctx->staticlentable);
+ zlib_freetable(&dctx->staticdisttable);
+ sfree(dctx);
+}
+
+static int zlib_huflookup(unsigned long *bitsp, int *nbitsp,
+ struct zlib_table *tab)
+{
+ unsigned long bits = *bitsp;
+ int nbits = *nbitsp;
+ while (1) {
+ struct zlib_tableentry *ent;
+ ent = &tab->table[bits & tab->mask];
+ if (ent->nbits > nbits)
+ return -1; /* not enough data */
+ bits >>= ent->nbits;
+ nbits -= ent->nbits;
+ if (ent->code == -1)
+ tab = ent->nexttable;
+ else {
+ *bitsp = bits;
+ *nbitsp = nbits;
+ return ent->code;
+ }
+
+ if (!tab) {
+ /*
+ * There was a missing entry in the table, presumably
+ * due to an invalid Huffman table description, and the
+ * subsequent data has attempted to use the missing
+ * entry. Return a decoding failure.
+ */
+ return -2;
+ }
+ }
+}
+
+static void zlib_emit_char(struct zlib_decompress_ctx *dctx, int c)
+{
+ dctx->window[dctx->winpos] = c;
+ dctx->winpos = (dctx->winpos + 1) & (WINSIZE - 1);
+ if (dctx->outlen >= dctx->outsize) {
+ dctx->outsize = dctx->outlen + 512;
+ dctx->outblk = sresize(dctx->outblk, dctx->outsize, unsigned char);
+ }
+ dctx->outblk[dctx->outlen++] = c;
+}
+
+#define EATBITS(n) ( dctx->nbits -= (n), dctx->bits >>= (n) )
+
+int zlib_decompress_block(void *handle, unsigned char *block, int len,
+ unsigned char **outblock, int *outlen)
+{
+ struct zlib_decompress_ctx *dctx = (struct zlib_decompress_ctx *)handle;
+ const coderecord *rec;
+ int code, blktype, rep, dist, nlen, header;
+ static const unsigned char lenlenmap[] = {
+ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
+ };
+
+ dctx->outblk = snewn(256, unsigned char);
+ dctx->outsize = 256;
+ dctx->outlen = 0;
+
+ while (len > 0 || dctx->nbits > 0) {
+ while (dctx->nbits < 24 && len > 0) {
+ dctx->bits |= (*block++) << dctx->nbits;
+ dctx->nbits += 8;
+ len--;
+ }
+ switch (dctx->state) {
+ case START:
+ /* Expect 16-bit zlib header. */
+ if (dctx->nbits < 16)
+ goto finished; /* done all we can */
+
+ /*
+ * The header is stored as a big-endian 16-bit integer,
+ * in contrast to the general little-endian policy in
+ * the rest of the format :-(
+ */
+ header = (((dctx->bits & 0xFF00) >> 8) |
+ ((dctx->bits & 0x00FF) << 8));
+ EATBITS(16);
+
+ /*
+ * Check the header:
+ *
+ * - bits 8-11 should be 1000 (Deflate/RFC1951)
+ * - bits 12-15 should be at most 0111 (window size)
+ * - bit 5 should be zero (no dictionary present)
+ * - we don't care about bits 6-7 (compression rate)
+ * - bits 0-4 should be set up to make the whole thing
+ * a multiple of 31 (checksum).
+ */
+ if ((header & 0x0F00) != 0x0800 ||
+ (header & 0xF000) > 0x7000 ||
+ (header & 0x0020) != 0x0000 ||
+ (header % 31) != 0)
+ goto decode_error;
+
+ dctx->state = OUTSIDEBLK;
+ break;
+ case OUTSIDEBLK:
+ /* Expect 3-bit block header. */
+ if (dctx->nbits < 3)
+ goto finished; /* done all we can */
+ EATBITS(1);
+ blktype = dctx->bits & 3;
+ EATBITS(2);
+ if (blktype == 0) {
+ int to_eat = dctx->nbits & 7;
+ dctx->state = UNCOMP_LEN;
+ EATBITS(to_eat); /* align to byte boundary */
+ } else if (blktype == 1) {
+ dctx->currlentable = dctx->staticlentable;
+ dctx->currdisttable = dctx->staticdisttable;
+ dctx->state = INBLK;
+ } else if (blktype == 2) {
+ dctx->state = TREES_HDR;
+ }
+ break;
+ case TREES_HDR:
+ /*
+ * Dynamic block header. Five bits of HLIT, five of
+ * HDIST, four of HCLEN.
+ */
+ if (dctx->nbits < 5 + 5 + 4)
+ goto finished; /* done all we can */
+ dctx->hlit = 257 + (dctx->bits & 31);
+ EATBITS(5);
+ dctx->hdist = 1 + (dctx->bits & 31);
+ EATBITS(5);
+ dctx->hclen = 4 + (dctx->bits & 15);
+ EATBITS(4);
+ dctx->lenptr = 0;
+ dctx->state = TREES_LENLEN;
+ memset(dctx->lenlen, 0, sizeof(dctx->lenlen));
+ break;
+ case TREES_LENLEN:
+ if (dctx->nbits < 3)
+ goto finished;
+ while (dctx->lenptr < dctx->hclen && dctx->nbits >= 3) {
+ dctx->lenlen[lenlenmap[dctx->lenptr++]] =
+ (unsigned char) (dctx->bits & 7);
+ EATBITS(3);
+ }
+ if (dctx->lenptr == dctx->hclen) {
+ dctx->lenlentable = zlib_mktable(dctx->lenlen, 19);
+ dctx->state = TREES_LEN;
+ dctx->lenptr = 0;
+ }
+ break;
+ case TREES_LEN:
+ if (dctx->lenptr >= dctx->hlit + dctx->hdist) {
+ dctx->currlentable = zlib_mktable(dctx->lengths, dctx->hlit);
+ dctx->currdisttable = zlib_mktable(dctx->lengths + dctx->hlit,
+ dctx->hdist);
+ zlib_freetable(&dctx->lenlentable);
+ dctx->lenlentable = NULL;
+ dctx->state = INBLK;
+ break;
+ }
+ code =
+ zlib_huflookup(&dctx->bits, &dctx->nbits, dctx->lenlentable);
+ if (code == -1)
+ goto finished;
+ if (code == -2)
+ goto decode_error;
+ if (code < 16)
+ dctx->lengths[dctx->lenptr++] = code;
+ else {
+ dctx->lenextrabits = (code == 16 ? 2 : code == 17 ? 3 : 7);
+ dctx->lenaddon = (code == 18 ? 11 : 3);
+ dctx->lenrep = (code == 16 && dctx->lenptr > 0 ?
+ dctx->lengths[dctx->lenptr - 1] : 0);
+ dctx->state = TREES_LENREP;
+ }
+ break;
+ case TREES_LENREP:
+ if (dctx->nbits < dctx->lenextrabits)
+ goto finished;
+ rep =
+ dctx->lenaddon +
+ (dctx->bits & ((1 << dctx->lenextrabits) - 1));
+ EATBITS(dctx->lenextrabits);
+ while (rep > 0 && dctx->lenptr < dctx->hlit + dctx->hdist) {
+ dctx->lengths[dctx->lenptr] = dctx->lenrep;
+ dctx->lenptr++;
+ rep--;
+ }
+ dctx->state = TREES_LEN;
+ break;
+ case INBLK:
+ code =
+ zlib_huflookup(&dctx->bits, &dctx->nbits, dctx->currlentable);
+ if (code == -1)
+ goto finished;
+ if (code == -2)
+ goto decode_error;
+ if (code < 256)
+ zlib_emit_char(dctx, code);
+ else if (code == 256) {
+ dctx->state = OUTSIDEBLK;
+ if (dctx->currlentable != dctx->staticlentable) {
+ zlib_freetable(&dctx->currlentable);
+ dctx->currlentable = NULL;
+ }
+ if (dctx->currdisttable != dctx->staticdisttable) {
+ zlib_freetable(&dctx->currdisttable);
+ dctx->currdisttable = NULL;
+ }
+ } else if (code < 286) { /* static tree can give >285; ignore */
+ dctx->state = GOTLENSYM;
+ dctx->sym = code;
+ }
+ break;
+ case GOTLENSYM:
+ rec = &lencodes[dctx->sym - 257];
+ if (dctx->nbits < rec->extrabits)
+ goto finished;
+ dctx->len =
+ rec->min + (dctx->bits & ((1 << rec->extrabits) - 1));
+ EATBITS(rec->extrabits);
+ dctx->state = GOTLEN;
+ break;
+ case GOTLEN:
+ code =
+ zlib_huflookup(&dctx->bits, &dctx->nbits,
+ dctx->currdisttable);
+ if (code == -1)
+ goto finished;
+ if (code == -2)
+ goto decode_error;
+ dctx->state = GOTDISTSYM;
+ dctx->sym = code;
+ break;
+ case GOTDISTSYM:
+ rec = &distcodes[dctx->sym];
+ if (dctx->nbits < rec->extrabits)
+ goto finished;
+ dist = rec->min + (dctx->bits & ((1 << rec->extrabits) - 1));
+ EATBITS(rec->extrabits);
+ dctx->state = INBLK;
+ while (dctx->len--)
+ zlib_emit_char(dctx, dctx->window[(dctx->winpos - dist) &
+ (WINSIZE - 1)]);
+ break;
+ case UNCOMP_LEN:
+ /*
+ * Uncompressed block. We expect to see a 16-bit LEN.
+ */
+ if (dctx->nbits < 16)
+ goto finished;
+ dctx->uncomplen = dctx->bits & 0xFFFF;
+ EATBITS(16);
+ dctx->state = UNCOMP_NLEN;
+ break;
+ case UNCOMP_NLEN:
+ /*
+ * Uncompressed block. We expect to see a 16-bit NLEN,
+ * which should be the one's complement of the previous
+ * LEN.
+ */
+ if (dctx->nbits < 16)
+ goto finished;
+ nlen = dctx->bits & 0xFFFF;
+ EATBITS(16);
+ if (dctx->uncomplen != (nlen ^ 0xFFFF))
+ goto decode_error;
+ if (dctx->uncomplen == 0)
+ dctx->state = OUTSIDEBLK; /* block is empty */
+ else
+ dctx->state = UNCOMP_DATA;
+ break;
+ case UNCOMP_DATA:
+ if (dctx->nbits < 8)
+ goto finished;
+ zlib_emit_char(dctx, dctx->bits & 0xFF);
+ EATBITS(8);
+ if (--dctx->uncomplen == 0)
+ dctx->state = OUTSIDEBLK; /* end of uncompressed block */
+ break;
+ }
+ }
+
+ finished:
+ *outblock = dctx->outblk;
+ *outlen = dctx->outlen;
+ return 1;
+
+ decode_error:
+ sfree(dctx->outblk);
+ *outblock = dctx->outblk = NULL;
+ *outlen = 0;
+ return 0;
+}
+
+#ifdef ZLIB_STANDALONE
+
+#include <stdio.h>
+#include <string.h>
+
+int main(int argc, char **argv)
+{
+ unsigned char buf[16], *outbuf;
+ int ret, outlen;
+ void *handle;
+ int noheader = FALSE, opts = TRUE;
+ char *filename = NULL;
+ FILE *fp;
+
+ while (--argc) {
+ char *p = *++argv;
+
+ if (p[0] == '-' && opts) {
+ if (!strcmp(p, "-d"))
+ noheader = TRUE;
+ else if (!strcmp(p, "--"))
+ opts = FALSE; /* next thing is filename */
+ else {
+ fprintf(stderr, "unknown command line option '%s'\n", p);
+ return 1;
+ }
+ } else if (!filename) {
+ filename = p;
+ } else {
+ fprintf(stderr, "can only handle one filename\n");
+ return 1;
+ }
+ }
+
+ handle = zlib_decompress_init();
+
+ if (noheader) {
+ /*
+ * Provide missing zlib header if -d was specified.
+ */
+ zlib_decompress_block(handle, "\x78\x9C", 2, &outbuf, &outlen);
+ assert(outlen == 0);
+ }
+
+ if (filename)
+ fp = fopen(filename, "rb");
+ else
+ fp = stdin;
+
+ if (!fp) {
+ assert(filename);
+ fprintf(stderr, "unable to open '%s'\n", filename);
+ return 1;
+ }
+
+ while (1) {
+ ret = fread(buf, 1, sizeof(buf), fp);
+ if (ret <= 0)
+ break;
+ zlib_decompress_block(handle, buf, ret, &outbuf, &outlen);
+ if (outbuf) {
+ if (outlen)
+ fwrite(outbuf, 1, outlen, stdout);
+ sfree(outbuf);
+ } else {
+ fprintf(stderr, "decoding error\n");
+ return 1;
+ }
+ }
+
+ zlib_decompress_cleanup(handle);
+
+ if (filename)
+ fclose(fp);
+
+ return 0;
+}
+
+#else
+
+const struct ssh_compress ssh_zlib = {
+ "zlib",
+ "zlib@openssh.com", /* delayed version */
+ zlib_compress_init,
+ zlib_compress_cleanup,
+ zlib_compress_block,
+ zlib_decompress_init,
+ zlib_decompress_cleanup,
+ zlib_decompress_block,
+ zlib_disable_compression,
+ "zlib (RFC1950)"
+};
+
+#endif
diff --git a/tools/plink/telnet.c b/tools/plink/telnet.c
index c024538b9..098db292c 100644
--- a/tools/plink/telnet.c
+++ b/tools/plink/telnet.c
@@ -1,1132 +1,1135 @@
-/*
- * Telnet backend.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#include "putty.h"
-
-#ifndef FALSE
-#define FALSE 0
-#endif
-#ifndef TRUE
-#define TRUE 1
-#endif
-
-#define IAC 255 /* interpret as command: */
-#define DONT 254 /* you are not to use option */
-#define DO 253 /* please, you use option */
-#define WONT 252 /* I won't use option */
-#define WILL 251 /* I will use option */
-#define SB 250 /* interpret as subnegotiation */
-#define SE 240 /* end sub negotiation */
-
-#define GA 249 /* you may reverse the line */
-#define EL 248 /* erase the current line */
-#define EC 247 /* erase the current character */
-#define AYT 246 /* are you there */
-#define AO 245 /* abort output--but let prog finish */
-#define IP 244 /* interrupt process--permanently */
-#define BREAK 243 /* break */
-#define DM 242 /* data mark--for connect. cleaning */
-#define NOP 241 /* nop */
-#define EOR 239 /* end of record (transparent mode) */
-#define ABORT 238 /* Abort process */
-#define SUSP 237 /* Suspend process */
-#define xEOF 236 /* End of file: EOF is already used... */
-
-#define TELOPTS(X) \
- X(BINARY, 0) /* 8-bit data path */ \
- X(ECHO, 1) /* echo */ \
- X(RCP, 2) /* prepare to reconnect */ \
- X(SGA, 3) /* suppress go ahead */ \
- X(NAMS, 4) /* approximate message size */ \
- X(STATUS, 5) /* give status */ \
- X(TM, 6) /* timing mark */ \
- X(RCTE, 7) /* remote controlled transmission and echo */ \
- X(NAOL, 8) /* negotiate about output line width */ \
- X(NAOP, 9) /* negotiate about output page size */ \
- X(NAOCRD, 10) /* negotiate about CR disposition */ \
- X(NAOHTS, 11) /* negotiate about horizontal tabstops */ \
- X(NAOHTD, 12) /* negotiate about horizontal tab disposition */ \
- X(NAOFFD, 13) /* negotiate about formfeed disposition */ \
- X(NAOVTS, 14) /* negotiate about vertical tab stops */ \
- X(NAOVTD, 15) /* negotiate about vertical tab disposition */ \
- X(NAOLFD, 16) /* negotiate about output LF disposition */ \
- X(XASCII, 17) /* extended ascic character set */ \
- X(LOGOUT, 18) /* force logout */ \
- X(BM, 19) /* byte macro */ \
- X(DET, 20) /* data entry terminal */ \
- X(SUPDUP, 21) /* supdup protocol */ \
- X(SUPDUPOUTPUT, 22) /* supdup output */ \
- X(SNDLOC, 23) /* send location */ \
- X(TTYPE, 24) /* terminal type */ \
- X(EOR, 25) /* end or record */ \
- X(TUID, 26) /* TACACS user identification */ \
- X(OUTMRK, 27) /* output marking */ \
- X(TTYLOC, 28) /* terminal location number */ \
- X(3270REGIME, 29) /* 3270 regime */ \
- X(X3PAD, 30) /* X.3 PAD */ \
- X(NAWS, 31) /* window size */ \
- X(TSPEED, 32) /* terminal speed */ \
- X(LFLOW, 33) /* remote flow control */ \
- X(LINEMODE, 34) /* Linemode option */ \
- X(XDISPLOC, 35) /* X Display Location */ \
- X(OLD_ENVIRON, 36) /* Old - Environment variables */ \
- X(AUTHENTICATION, 37) /* Authenticate */ \
- X(ENCRYPT, 38) /* Encryption option */ \
- X(NEW_ENVIRON, 39) /* New - Environment variables */ \
- X(TN3270E, 40) /* TN3270 enhancements */ \
- X(XAUTH, 41) \
- X(CHARSET, 42) /* Character set */ \
- X(RSP, 43) /* Remote serial port */ \
- X(COM_PORT_OPTION, 44) /* Com port control */ \
- X(SLE, 45) /* Suppress local echo */ \
- X(STARTTLS, 46) /* Start TLS */ \
- X(KERMIT, 47) /* Automatic Kermit file transfer */ \
- X(SEND_URL, 48) \
- X(FORWARD_X, 49) \
- X(PRAGMA_LOGON, 138) \
- X(SSPI_LOGON, 139) \
- X(PRAGMA_HEARTBEAT, 140) \
- X(EXOPL, 255) /* extended-options-list */
-
-#define telnet_enum(x,y) TELOPT_##x = y,
-enum { TELOPTS(telnet_enum) dummy=0 };
-#undef telnet_enum
-
-#define TELQUAL_IS 0 /* option is... */
-#define TELQUAL_SEND 1 /* send option */
-#define TELQUAL_INFO 2 /* ENVIRON: informational version of IS */
-#define BSD_VAR 1
-#define BSD_VALUE 0
-#define RFC_VAR 0
-#define RFC_VALUE 1
-
-#define CR 13
-#define LF 10
-#define NUL 0
-
-#define iswritable(x) \
- ( (x) != IAC && \
- (telnet->opt_states[o_we_bin.index] == ACTIVE || (x) != CR))
-
-static char *telopt(int opt)
-{
-#define telnet_str(x,y) case TELOPT_##x: return #x;
- switch (opt) {
- TELOPTS(telnet_str)
- default:
- return "<unknown>";
- }
-#undef telnet_str
-}
-
-static void telnet_size(void *handle, int width, int height);
-
-struct Opt {
- int send; /* what we initially send */
- int nsend; /* -ve send if requested to stop it */
- int ack, nak; /* +ve and -ve acknowledgements */
- int option; /* the option code */
- int index; /* index into telnet->opt_states[] */
- enum {
- REQUESTED, ACTIVE, INACTIVE, REALLY_INACTIVE
- } initial_state;
-};
-
-enum {
- OPTINDEX_NAWS,
- OPTINDEX_TSPEED,
- OPTINDEX_TTYPE,
- OPTINDEX_OENV,
- OPTINDEX_NENV,
- OPTINDEX_ECHO,
- OPTINDEX_WE_SGA,
- OPTINDEX_THEY_SGA,
- OPTINDEX_WE_BIN,
- OPTINDEX_THEY_BIN,
- NUM_OPTS
-};
-
-static const struct Opt o_naws =
- { WILL, WONT, DO, DONT, TELOPT_NAWS, OPTINDEX_NAWS, REQUESTED };
-static const struct Opt o_tspeed =
- { WILL, WONT, DO, DONT, TELOPT_TSPEED, OPTINDEX_TSPEED, REQUESTED };
-static const struct Opt o_ttype =
- { WILL, WONT, DO, DONT, TELOPT_TTYPE, OPTINDEX_TTYPE, REQUESTED };
-static const struct Opt o_oenv =
- { WILL, WONT, DO, DONT, TELOPT_OLD_ENVIRON, OPTINDEX_OENV, INACTIVE };
-static const struct Opt o_nenv =
- { WILL, WONT, DO, DONT, TELOPT_NEW_ENVIRON, OPTINDEX_NENV, REQUESTED };
-static const struct Opt o_echo =
- { DO, DONT, WILL, WONT, TELOPT_ECHO, OPTINDEX_ECHO, REQUESTED };
-static const struct Opt o_we_sga =
- { WILL, WONT, DO, DONT, TELOPT_SGA, OPTINDEX_WE_SGA, REQUESTED };
-static const struct Opt o_they_sga =
- { DO, DONT, WILL, WONT, TELOPT_SGA, OPTINDEX_THEY_SGA, REQUESTED };
-static const struct Opt o_we_bin =
- { WILL, WONT, DO, DONT, TELOPT_BINARY, OPTINDEX_WE_BIN, INACTIVE };
-static const struct Opt o_they_bin =
- { DO, DONT, WILL, WONT, TELOPT_BINARY, OPTINDEX_THEY_BIN, INACTIVE };
-
-static const struct Opt *const opts[] = {
- &o_naws, &o_tspeed, &o_ttype, &o_oenv, &o_nenv, &o_echo,
- &o_we_sga, &o_they_sga, &o_we_bin, &o_they_bin, NULL
-};
-
-typedef struct telnet_tag {
- const struct plug_function_table *fn;
- /* the above field _must_ be first in the structure */
-
- Socket s;
-
- void *frontend;
- void *ldisc;
- int term_width, term_height;
-
- int opt_states[NUM_OPTS];
-
- int echoing, editing;
- int activated;
- int bufsize;
- int in_synch;
- int sb_opt, sb_len;
- unsigned char *sb_buf;
- int sb_size;
-
- enum {
- TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT,
- SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR
- } state;
-
- Conf *conf;
-
- Pinger pinger;
-} *Telnet;
-
-#define TELNET_MAX_BACKLOG 4096
-
-#define SB_DELTA 1024
-
-static void c_write(Telnet telnet, 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)
-{
- char *buf;
- /*
- * The strange-looking "<?""?>" below is there to avoid a
- * trigraph - a double question mark followed by > maps to a
- * closing brace character!
- */
- buf = dupprintf("%s:\t%s %s", sender,
- (cmd == WILL ? "WILL" : cmd == WONT ? "WONT" :
- cmd == DO ? "DO" : cmd == DONT ? "DONT" : "<?""?>"),
- telopt(option));
- logevent(telnet->frontend, buf);
- sfree(buf);
-}
-
-static void send_opt(Telnet telnet, int cmd, int option)
-{
- unsigned char b[3];
-
- b[0] = IAC;
- b[1] = cmd;
- b[2] = option;
- telnet->bufsize = sk_write(telnet->s, (char *)b, 3);
- log_option(telnet, "client", cmd, option);
-}
-
-static void deactivate_option(Telnet telnet, const struct Opt *o)
-{
- if (telnet->opt_states[o->index] == REQUESTED ||
- telnet->opt_states[o->index] == ACTIVE)
- send_opt(telnet, o->nsend, o->option);
- telnet->opt_states[o->index] = REALLY_INACTIVE;
-}
-
-/*
- * Generate side effects of enabling or disabling an option.
- */
-static void option_side_effects(Telnet telnet, const struct Opt *o, int enabled)
-{
- if (o->option == TELOPT_ECHO && o->send == DO)
- telnet->echoing = !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);
-
- /* Ensure we get the minimum options */
- if (!telnet->activated) {
- if (telnet->opt_states[o_echo.index] == INACTIVE) {
- telnet->opt_states[o_echo.index] = REQUESTED;
- send_opt(telnet, o_echo.send, o_echo.option);
- }
- if (telnet->opt_states[o_we_sga.index] == INACTIVE) {
- telnet->opt_states[o_we_sga.index] = REQUESTED;
- send_opt(telnet, o_we_sga.send, o_we_sga.option);
- }
- if (telnet->opt_states[o_they_sga.index] == INACTIVE) {
- telnet->opt_states[o_they_sga.index] = REQUESTED;
- send_opt(telnet, o_they_sga.send, o_they_sga.option);
- }
- telnet->activated = TRUE;
- }
-}
-
-static void activate_option(Telnet telnet, const struct Opt *o)
-{
- if (o->send == WILL && o->option == TELOPT_NAWS)
- telnet_size(telnet, telnet->term_width, telnet->term_height);
- if (o->send == WILL &&
- (o->option == TELOPT_NEW_ENVIRON ||
- o->option == TELOPT_OLD_ENVIRON)) {
- /*
- * We may only have one kind of ENVIRON going at a time.
- * This is a hack, but who cares.
- */
- deactivate_option(telnet, o->option ==
- TELOPT_NEW_ENVIRON ? &o_oenv : &o_nenv);
- }
- option_side_effects(telnet, o, 1);
-}
-
-static void refused_option(Telnet telnet, const struct Opt *o)
-{
- if (o->send == WILL && o->option == TELOPT_NEW_ENVIRON &&
- telnet->opt_states[o_oenv.index] == INACTIVE) {
- send_opt(telnet, WILL, TELOPT_OLD_ENVIRON);
- telnet->opt_states[o_oenv.index] = REQUESTED;
- }
- option_side_effects(telnet, o, 0);
-}
-
-static void proc_rec_opt(Telnet telnet, int cmd, int option)
-{
- const struct Opt *const *o;
-
- log_option(telnet, "server", cmd, option);
- for (o = opts; *o; o++) {
- if ((*o)->option == option && (*o)->ack == cmd) {
- switch (telnet->opt_states[(*o)->index]) {
- case REQUESTED:
- telnet->opt_states[(*o)->index] = ACTIVE;
- activate_option(telnet, *o);
- break;
- case ACTIVE:
- break;
- case INACTIVE:
- telnet->opt_states[(*o)->index] = ACTIVE;
- send_opt(telnet, (*o)->send, option);
- activate_option(telnet, *o);
- break;
- case REALLY_INACTIVE:
- send_opt(telnet, (*o)->nsend, option);
- break;
- }
- return;
- } else if ((*o)->option == option && (*o)->nak == cmd) {
- switch (telnet->opt_states[(*o)->index]) {
- case REQUESTED:
- telnet->opt_states[(*o)->index] = INACTIVE;
- refused_option(telnet, *o);
- break;
- case ACTIVE:
- telnet->opt_states[(*o)->index] = INACTIVE;
- send_opt(telnet, (*o)->nsend, option);
- option_side_effects(telnet, *o, 0);
- break;
- case INACTIVE:
- case REALLY_INACTIVE:
- break;
- }
- return;
- }
- }
- /*
- * If we reach here, the option was one we weren't prepared to
- * cope with. If the request was positive (WILL or DO), we send
- * a negative ack to indicate refusal. If the request was
- * negative (WONT / DONT), we must do nothing.
- */
- if (cmd == WILL || cmd == DO)
- send_opt(telnet, (cmd == WILL ? DONT : WONT), option);
-}
-
-static void process_subneg(Telnet telnet)
-{
- unsigned char *b, *p, *q;
- int var, value, n, bsize;
- char *e, *eval, *ekey, *user;
-
- switch (telnet->sb_opt) {
- case TELOPT_TSPEED:
- if (telnet->sb_len == 1 && telnet->sb_buf[0] == TELQUAL_SEND) {
- char *logbuf;
- char *termspeed = conf_get_str(telnet->conf, CONF_termspeed);
- b = snewn(20 + strlen(termspeed), unsigned char);
- b[0] = IAC;
- b[1] = SB;
- b[2] = TELOPT_TSPEED;
- b[3] = TELQUAL_IS;
- strcpy((char *)(b + 4), termspeed);
- n = 4 + strlen(termspeed);
- b[n] = IAC;
- b[n + 1] = SE;
- telnet->bufsize = sk_write(telnet->s, (char *)b, n + 2);
- logevent(telnet->frontend, "server:\tSB TSPEED SEND");
- logbuf = dupprintf("client:\tSB TSPEED IS %s", termspeed);
- logevent(telnet->frontend, logbuf);
- sfree(logbuf);
- sfree(b);
- } else
- logevent(telnet->frontend, "server:\tSB TSPEED <something weird>");
- break;
- case TELOPT_TTYPE:
- if (telnet->sb_len == 1 && telnet->sb_buf[0] == TELQUAL_SEND) {
- char *logbuf;
- char *termtype = conf_get_str(telnet->conf, CONF_termtype);
- b = snewn(20 + strlen(termtype), unsigned char);
- b[0] = IAC;
- b[1] = SB;
- b[2] = TELOPT_TTYPE;
- b[3] = TELQUAL_IS;
- for (n = 0; termtype[n]; n++)
- b[n + 4] = (termtype[n] >= 'a' && termtype[n] <= 'z' ?
- termtype[n] + 'A' - 'a' :
- termtype[n]);
- b[n + 4] = IAC;
- b[n + 5] = SE;
- telnet->bufsize = sk_write(telnet->s, (char *)b, n + 6);
- b[n + 4] = 0;
- logevent(telnet->frontend, "server:\tSB TTYPE SEND");
- logbuf = dupprintf("client:\tSB TTYPE IS %s", b + 4);
- logevent(telnet->frontend, logbuf);
- sfree(logbuf);
- sfree(b);
- } else
- logevent(telnet->frontend, "server:\tSB TTYPE <something weird>\r\n");
- break;
- case TELOPT_OLD_ENVIRON:
- case TELOPT_NEW_ENVIRON:
- p = telnet->sb_buf;
- q = p + telnet->sb_len;
- if (p < q && *p == TELQUAL_SEND) {
- char *logbuf;
- p++;
- logbuf = dupprintf("server:\tSB %s SEND", telopt(telnet->sb_opt));
- logevent(telnet->frontend, logbuf);
- sfree(logbuf);
- if (telnet->sb_opt == TELOPT_OLD_ENVIRON) {
- if (conf_get_int(telnet->conf, CONF_rfc_environ)) {
- value = RFC_VALUE;
- var = RFC_VAR;
- } else {
- value = BSD_VALUE;
- var = BSD_VAR;
- }
- /*
- * Try to guess the sense of VAR and VALUE.
- */
- while (p < q) {
- if (*p == RFC_VAR) {
- value = RFC_VALUE;
- var = RFC_VAR;
- } else if (*p == BSD_VAR) {
- value = BSD_VALUE;
- var = BSD_VAR;
- }
- p++;
- }
- } else {
- /*
- * With NEW_ENVIRON, the sense of VAR and VALUE
- * isn't in doubt.
- */
- value = RFC_VALUE;
- var = RFC_VAR;
- }
- bsize = 20;
- for (eval = conf_get_str_strs(telnet->conf, CONF_environmt,
- NULL, &ekey);
- eval != NULL;
- eval = conf_get_str_strs(telnet->conf, CONF_environmt,
- ekey, &ekey))
- bsize += strlen(ekey) + strlen(eval) + 2;
- user = get_remote_username(telnet->conf);
- if (user)
- bsize += 6 + strlen(user);
-
- b = snewn(bsize, unsigned char);
- b[0] = IAC;
- b[1] = SB;
- b[2] = telnet->sb_opt;
- b[3] = TELQUAL_IS;
- n = 4;
- for (eval = conf_get_str_strs(telnet->conf, CONF_environmt,
- NULL, &ekey);
- eval != NULL;
- eval = conf_get_str_strs(telnet->conf, CONF_environmt,
- ekey, &ekey)) {
- b[n++] = var;
- for (e = ekey; *e; e++)
- b[n++] = *e;
- b[n++] = value;
- for (e = eval; *e; e++)
- b[n++] = *e;
- }
- if (user) {
- b[n++] = var;
- b[n++] = 'U';
- b[n++] = 'S';
- b[n++] = 'E';
- b[n++] = 'R';
- b[n++] = value;
- for (e = user; *e; e++)
- b[n++] = *e;
- }
- b[n++] = IAC;
- b[n++] = SE;
- telnet->bufsize = sk_write(telnet->s, (char *)b, n);
- if (n == 6) {
- logbuf = dupprintf("client:\tSB %s IS <nothing>",
- telopt(telnet->sb_opt));
- logevent(telnet->frontend, logbuf);
- sfree(logbuf);
- } else {
- logbuf = dupprintf("client:\tSB %s IS:",
- telopt(telnet->sb_opt));
- logevent(telnet->frontend, logbuf);
- sfree(logbuf);
- for (eval = conf_get_str_strs(telnet->conf, CONF_environmt,
- NULL, &ekey);
- eval != NULL;
- eval = conf_get_str_strs(telnet->conf, CONF_environmt,
- ekey, &ekey)) {
- logbuf = dupprintf("\t%s=%s", ekey, eval);
- logevent(telnet->frontend, logbuf);
- sfree(logbuf);
- }
- if (user) {
- logbuf = dupprintf("\tUSER=%s", user);
- logevent(telnet->frontend, logbuf);
- sfree(logbuf);
- }
- }
- sfree(b);
- sfree(user);
- }
- break;
- }
-}
-
-static void do_telnet_read(Telnet telnet, char *buf, int len)
-{
- char *outbuf = NULL;
- int outbuflen = 0, outbufsize = 0;
-
-#define ADDTOBUF(c) do { \
- if (outbuflen >= outbufsize) { \
- outbufsize = outbuflen + 256; \
- outbuf = sresize(outbuf, outbufsize, char); \
- } \
- outbuf[outbuflen++] = (c); \
-} while (0)
-
- while (len--) {
- int c = (unsigned char) *buf++;
-
- switch (telnet->state) {
- case TOP_LEVEL:
- case SEENCR:
- if (c == NUL && telnet->state == SEENCR)
- telnet->state = TOP_LEVEL;
- else if (c == IAC)
- telnet->state = SEENIAC;
- else {
- if (!telnet->in_synch)
- ADDTOBUF(c);
-
-#if 1
- /* I can't get the F***ing winsock to insert the urgent IAC
- * into the right position! Even with SO_OOBINLINE it gives
- * it to recv too soon. And of course the DM byte (that
- * arrives in the same packet!) appears several K later!!
- *
- * Oh well, we do get the DM in the right place so I'll
- * just stop hiding on the next 0xf2 and hope for the best.
- */
- else if (c == DM)
- telnet->in_synch = 0;
-#endif
- if (c == CR && telnet->opt_states[o_they_bin.index] != ACTIVE)
- telnet->state = SEENCR;
- else
- telnet->state = TOP_LEVEL;
- }
- break;
- case SEENIAC:
- if (c == DO)
- telnet->state = SEENDO;
- else if (c == DONT)
- telnet->state = SEENDONT;
- else if (c == WILL)
- telnet->state = SEENWILL;
- else if (c == WONT)
- telnet->state = SEENWONT;
- else if (c == SB)
- telnet->state = SEENSB;
- else if (c == DM) {
- telnet->in_synch = 0;
- telnet->state = TOP_LEVEL;
- } else {
- /* ignore everything else; print it if it's IAC */
- if (c == IAC) {
- ADDTOBUF(c);
- }
- telnet->state = TOP_LEVEL;
- }
- break;
- case SEENWILL:
- proc_rec_opt(telnet, WILL, c);
- telnet->state = TOP_LEVEL;
- break;
- case SEENWONT:
- proc_rec_opt(telnet, WONT, c);
- telnet->state = TOP_LEVEL;
- break;
- case SEENDO:
- proc_rec_opt(telnet, DO, c);
- telnet->state = TOP_LEVEL;
- break;
- case SEENDONT:
- proc_rec_opt(telnet, DONT, c);
- telnet->state = TOP_LEVEL;
- break;
- case SEENSB:
- telnet->sb_opt = c;
- telnet->sb_len = 0;
- telnet->state = SUBNEGOT;
- break;
- case SUBNEGOT:
- if (c == IAC)
- telnet->state = SUBNEG_IAC;
- else {
- subneg_addchar:
- if (telnet->sb_len >= telnet->sb_size) {
- telnet->sb_size += SB_DELTA;
- telnet->sb_buf = sresize(telnet->sb_buf, telnet->sb_size,
- unsigned char);
- }
- telnet->sb_buf[telnet->sb_len++] = c;
- telnet->state = SUBNEGOT; /* in case we came here by goto */
- }
- break;
- case SUBNEG_IAC:
- if (c != SE)
- goto subneg_addchar; /* yes, it's a hack, I know, but... */
- else {
- process_subneg(telnet);
- telnet->state = TOP_LEVEL;
- }
- break;
- }
- }
-
- if (outbuflen)
- c_write(telnet, outbuf, outbuflen);
- sfree(outbuf);
-}
-
-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);
-}
-
-static int telnet_closing(Plug plug, const char *error_msg, int error_code,
- int calling_back)
-{
- Telnet telnet = (Telnet) plug;
-
- /*
- * We don't implement independent EOF in each direction for Telnet
- * connections; as soon as we get word that the remote side has
- * sent us EOF, we wind up the whole connection.
- */
-
- if (telnet->s) {
- sk_close(telnet->s);
- telnet->s = NULL;
- notify_remote_exit(telnet->frontend);
- }
- if (error_msg) {
- logevent(telnet->frontend, error_msg);
- connection_fatal(telnet->frontend, "%s", error_msg);
- }
- /* Otherwise, the remote side closed the connection normally. */
- return 0;
-}
-
-static int telnet_receive(Plug plug, int urgent, char *data, int len)
-{
- Telnet telnet = (Telnet) plug;
- if (urgent)
- telnet->in_synch = TRUE;
- do_telnet_read(telnet, data, len);
- return 1;
-}
-
-static void telnet_sent(Plug plug, int bufsize)
-{
- Telnet telnet = (Telnet) plug;
- telnet->bufsize = bufsize;
-}
-
-/*
- * Called to set up the Telnet connection.
- *
- * 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 *telnet_init(void *frontend_handle, void **backend_handle,
- Conf *conf, char *host, int port,
- char **realhost, int nodelay, int keepalive)
-{
- static const struct plug_function_table fn_table = {
- telnet_log,
- telnet_closing,
- telnet_receive,
- telnet_sent
- };
- SockAddr addr;
- const char *err;
- Telnet telnet;
- char *loghost;
- int addressfamily;
-
- telnet = snew(struct telnet_tag);
- telnet->fn = &fn_table;
- telnet->conf = conf_copy(conf);
- telnet->s = NULL;
- telnet->echoing = TRUE;
- telnet->editing = TRUE;
- telnet->activated = FALSE;
- telnet->sb_buf = NULL;
- telnet->sb_size = 0;
- telnet->frontend = frontend_handle;
- telnet->term_width = conf_get_int(telnet->conf, CONF_width);
- telnet->term_height = conf_get_int(telnet->conf, CONF_height);
- telnet->state = TOP_LEVEL;
- telnet->ldisc = NULL;
- telnet->pinger = NULL;
- *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);
- if ((err = sk_addr_error(addr)) != NULL) {
- sk_addr_free(addr);
- return err;
- }
-
- if (port < 0)
- port = 23; /* default telnet port */
-
- /*
- * Open socket.
- */
- telnet->s = new_connection(addr, *realhost, port, 0, 1,
- nodelay, keepalive, (Plug) telnet, telnet->conf);
- if ((err = sk_socket_error(telnet->s)) != NULL)
- return err;
-
- telnet->pinger = pinger_new(telnet->conf, &telnet_backend, telnet);
-
- /*
- * Initialise option states.
- */
- if (conf_get_int(telnet->conf, CONF_passive_telnet)) {
- const struct Opt *const *o;
-
- for (o = opts; *o; o++)
- telnet->opt_states[(*o)->index] = INACTIVE;
- } else {
- const struct Opt *const *o;
-
- for (o = opts; *o; o++) {
- telnet->opt_states[(*o)->index] = (*o)->initial_state;
- if (telnet->opt_states[(*o)->index] == REQUESTED)
- send_opt(telnet, (*o)->send, (*o)->option);
- }
- telnet->activated = TRUE;
- }
-
- /*
- * Set up SYNCH state.
- */
- telnet->in_synch = FALSE;
-
- /*
- * We can send special commands from the start.
- */
- update_specials_menu(telnet->frontend);
-
- /*
- * loghost overrides realhost, if specified.
- */
- loghost = conf_get_str(telnet->conf, CONF_loghost);
- if (*loghost) {
- char *colon;
-
- sfree(*realhost);
- *realhost = dupstr(loghost);
- colon = strrchr(*realhost, ':');
- if (colon) {
- /*
- * FIXME: if we ever update this aspect of ssh.c for
- * IPv6 literal management, this should change in line
- * with it.
- */
- *colon++ = '\0';
- }
- }
-
- return NULL;
-}
-
-static void telnet_free(void *handle)
-{
- Telnet telnet = (Telnet) handle;
-
- sfree(telnet->sb_buf);
- if (telnet->s)
- sk_close(telnet->s);
- if (telnet->pinger)
- pinger_free(telnet->pinger);
- conf_free(telnet->conf);
- sfree(telnet);
-}
-/*
- * Reconfigure the Telnet backend. There's no immediate action
- * necessary, in this backend: we just save the fresh config for
- * any subsequent negotiations.
- */
-static void telnet_reconfig(void *handle, Conf *conf)
-{
- Telnet telnet = (Telnet) handle;
- pinger_reconfig(telnet->pinger, telnet->conf, conf);
- conf_free(telnet->conf);
- telnet->conf = conf_copy(conf);
-}
-
-/*
- * Called to send data down the Telnet connection.
- */
-static int telnet_send(void *handle, char *buf, int len)
-{
- Telnet telnet = (Telnet) handle;
- unsigned char *p, *end;
- static const unsigned char iac[2] = { IAC, IAC };
- static const unsigned char cr[2] = { CR, NUL };
-#if 0
- static const unsigned char nl[2] = { CR, LF };
-#endif
-
- if (telnet->s == NULL)
- return 0;
-
- p = (unsigned char *)buf;
- end = (unsigned char *)(buf + len);
- while (p < end) {
- unsigned char *q = p;
-
- while (p < end && iswritable(*p))
- p++;
- telnet->bufsize = sk_write(telnet->s, (char *)q, p - q);
-
- while (p < end && !iswritable(*p)) {
- telnet->bufsize =
- sk_write(telnet->s, (char *)(*p == IAC ? iac : cr), 2);
- p++;
- }
- }
-
- return telnet->bufsize;
-}
-
-/*
- * Called to query the current socket sendability status.
- */
-static int telnet_sendbuffer(void *handle)
-{
- Telnet telnet = (Telnet) handle;
- return telnet->bufsize;
-}
-
-/*
- * Called to set the size of the window from Telnet's POV.
- */
-static void telnet_size(void *handle, int width, int height)
-{
- Telnet telnet = (Telnet) handle;
- unsigned char b[24];
- int n;
- char *logbuf;
-
- telnet->term_width = width;
- telnet->term_height = height;
-
- if (telnet->s == NULL || telnet->opt_states[o_naws.index] != ACTIVE)
- return;
- n = 0;
- b[n++] = IAC;
- b[n++] = SB;
- b[n++] = TELOPT_NAWS;
- b[n++] = telnet->term_width >> 8;
- if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */
- b[n++] = telnet->term_width & 0xFF;
- if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */
- b[n++] = telnet->term_height >> 8;
- if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */
- b[n++] = telnet->term_height & 0xFF;
- if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */
- b[n++] = IAC;
- b[n++] = SE;
- telnet->bufsize = sk_write(telnet->s, (char *)b, n);
- logbuf = dupprintf("client:\tSB NAWS %d,%d",
- telnet->term_width, telnet->term_height);
- logevent(telnet->frontend, logbuf);
- sfree(logbuf);
-}
-
-/*
- * Send Telnet special codes.
- */
-static void telnet_special(void *handle, Telnet_Special code)
-{
- Telnet telnet = (Telnet) handle;
- unsigned char b[2];
-
- if (telnet->s == NULL)
- return;
-
- b[0] = IAC;
- switch (code) {
- case TS_AYT:
- b[1] = AYT;
- telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
- break;
- case TS_BRK:
- b[1] = BREAK;
- telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
- break;
- case TS_EC:
- b[1] = EC;
- telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
- break;
- case TS_EL:
- b[1] = EL;
- telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
- break;
- case TS_GA:
- b[1] = GA;
- telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
- break;
- case TS_NOP:
- b[1] = NOP;
- telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
- break;
- case TS_ABORT:
- b[1] = ABORT;
- telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
- break;
- case TS_AO:
- b[1] = AO;
- telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
- break;
- case TS_IP:
- b[1] = IP;
- telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
- break;
- case TS_SUSP:
- b[1] = SUSP;
- telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
- break;
- case TS_EOR:
- b[1] = EOR;
- telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
- break;
- case TS_EOF:
- b[1] = xEOF;
- telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
- break;
- case TS_EOL:
- /* In BINARY mode, CR-LF becomes just CR -
- * and without the NUL suffix too. */
- if (telnet->opt_states[o_we_bin.index] == ACTIVE)
- telnet->bufsize = sk_write(telnet->s, "\r", 1);
- else
- telnet->bufsize = sk_write(telnet->s, "\r\n", 2);
- break;
- case TS_SYNCH:
- b[1] = DM;
- telnet->bufsize = sk_write(telnet->s, (char *)b, 1);
- telnet->bufsize = sk_write_oob(telnet->s, (char *)(b + 1), 1);
- break;
- case TS_RECHO:
- if (telnet->opt_states[o_echo.index] == INACTIVE ||
- telnet->opt_states[o_echo.index] == REALLY_INACTIVE) {
- telnet->opt_states[o_echo.index] = REQUESTED;
- send_opt(telnet, o_echo.send, o_echo.option);
- }
- break;
- case TS_LECHO:
- if (telnet->opt_states[o_echo.index] == ACTIVE) {
- telnet->opt_states[o_echo.index] = REQUESTED;
- send_opt(telnet, o_echo.nsend, o_echo.option);
- }
- break;
- case TS_PING:
- if (telnet->opt_states[o_they_sga.index] == ACTIVE) {
- b[1] = NOP;
- telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
- }
- break;
- default:
- break; /* never heard of it */
- }
-}
-
-static const struct telnet_special *telnet_get_specials(void *handle)
-{
- static const struct telnet_special specials[] = {
- {"Are You There", TS_AYT},
- {"Break", TS_BRK},
- {"Synch", TS_SYNCH},
- {"Erase Character", TS_EC},
- {"Erase Line", TS_EL},
- {"Go Ahead", TS_GA},
- {"No Operation", TS_NOP},
- {NULL, TS_SEP},
- {"Abort Process", TS_ABORT},
- {"Abort Output", TS_AO},
- {"Interrupt Process", TS_IP},
- {"Suspend Process", TS_SUSP},
- {NULL, TS_SEP},
- {"End Of Record", TS_EOR},
- {"End Of File", TS_EOF},
- {NULL, TS_EXITMENU}
- };
- return specials;
-}
-
-static int telnet_connected(void *handle)
-{
- Telnet telnet = (Telnet) handle;
- return telnet->s != NULL;
-}
-
-static int telnet_sendok(void *handle)
-{
- /* Telnet telnet = (Telnet) handle; */
- return 1;
-}
-
-static void telnet_unthrottle(void *handle, int backlog)
-{
- Telnet telnet = (Telnet) handle;
- sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG);
-}
-
-static int telnet_ldisc(void *handle, int option)
-{
- Telnet telnet = (Telnet) handle;
- if (option == LD_ECHO)
- return telnet->echoing;
- if (option == LD_EDIT)
- return telnet->editing;
- return FALSE;
-}
-
-static void telnet_provide_ldisc(void *handle, void *ldisc)
-{
- Telnet telnet = (Telnet) handle;
- telnet->ldisc = ldisc;
-}
-
-static void telnet_provide_logctx(void *handle, void *logctx)
-{
- /* This is a stub. */
-}
-
-static int telnet_exitcode(void *handle)
-{
- Telnet telnet = (Telnet) handle;
- if (telnet->s != NULL)
- return -1; /* still connected */
- else
- /* Telnet doesn't transmit exit codes back to the client */
- return 0;
-}
-
-/*
- * cfg_info for Telnet does nothing at all.
- */
-static int telnet_cfg_info(void *handle)
-{
- return 0;
-}
-
-Backend telnet_backend = {
- telnet_init,
- telnet_free,
- telnet_reconfig,
- telnet_send,
- telnet_sendbuffer,
- telnet_size,
- telnet_special,
- telnet_get_specials,
- telnet_connected,
- telnet_exitcode,
- telnet_sendok,
- telnet_ldisc,
- telnet_provide_ldisc,
- telnet_provide_logctx,
- telnet_unthrottle,
- telnet_cfg_info,
- "telnet",
- PROT_TELNET,
- 23
-};
+/*
+ * Telnet backend.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include "putty.h"
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#define IAC 255 /* interpret as command: */
+#define DONT 254 /* you are not to use option */
+#define DO 253 /* please, you use option */
+#define WONT 252 /* I won't use option */
+#define WILL 251 /* I will use option */
+#define SB 250 /* interpret as subnegotiation */
+#define SE 240 /* end sub negotiation */
+
+#define GA 249 /* you may reverse the line */
+#define EL 248 /* erase the current line */
+#define EC 247 /* erase the current character */
+#define AYT 246 /* are you there */
+#define AO 245 /* abort output--but let prog finish */
+#define IP 244 /* interrupt process--permanently */
+#define BREAK 243 /* break */
+#define DM 242 /* data mark--for connect. cleaning */
+#define NOP 241 /* nop */
+#define EOR 239 /* end of record (transparent mode) */
+#define ABORT 238 /* Abort process */
+#define SUSP 237 /* Suspend process */
+#define xEOF 236 /* End of file: EOF is already used... */
+
+#define TELOPTS(X) \
+ X(BINARY, 0) /* 8-bit data path */ \
+ X(ECHO, 1) /* echo */ \
+ X(RCP, 2) /* prepare to reconnect */ \
+ X(SGA, 3) /* suppress go ahead */ \
+ X(NAMS, 4) /* approximate message size */ \
+ X(STATUS, 5) /* give status */ \
+ X(TM, 6) /* timing mark */ \
+ X(RCTE, 7) /* remote controlled transmission and echo */ \
+ X(NAOL, 8) /* negotiate about output line width */ \
+ X(NAOP, 9) /* negotiate about output page size */ \
+ X(NAOCRD, 10) /* negotiate about CR disposition */ \
+ X(NAOHTS, 11) /* negotiate about horizontal tabstops */ \
+ X(NAOHTD, 12) /* negotiate about horizontal tab disposition */ \
+ X(NAOFFD, 13) /* negotiate about formfeed disposition */ \
+ X(NAOVTS, 14) /* negotiate about vertical tab stops */ \
+ X(NAOVTD, 15) /* negotiate about vertical tab disposition */ \
+ X(NAOLFD, 16) /* negotiate about output LF disposition */ \
+ X(XASCII, 17) /* extended ascic character set */ \
+ X(LOGOUT, 18) /* force logout */ \
+ X(BM, 19) /* byte macro */ \
+ X(DET, 20) /* data entry terminal */ \
+ X(SUPDUP, 21) /* supdup protocol */ \
+ X(SUPDUPOUTPUT, 22) /* supdup output */ \
+ X(SNDLOC, 23) /* send location */ \
+ X(TTYPE, 24) /* terminal type */ \
+ X(EOR, 25) /* end or record */ \
+ X(TUID, 26) /* TACACS user identification */ \
+ X(OUTMRK, 27) /* output marking */ \
+ X(TTYLOC, 28) /* terminal location number */ \
+ X(3270REGIME, 29) /* 3270 regime */ \
+ X(X3PAD, 30) /* X.3 PAD */ \
+ X(NAWS, 31) /* window size */ \
+ X(TSPEED, 32) /* terminal speed */ \
+ X(LFLOW, 33) /* remote flow control */ \
+ X(LINEMODE, 34) /* Linemode option */ \
+ X(XDISPLOC, 35) /* X Display Location */ \
+ X(OLD_ENVIRON, 36) /* Old - Environment variables */ \
+ X(AUTHENTICATION, 37) /* Authenticate */ \
+ X(ENCRYPT, 38) /* Encryption option */ \
+ X(NEW_ENVIRON, 39) /* New - Environment variables */ \
+ X(TN3270E, 40) /* TN3270 enhancements */ \
+ X(XAUTH, 41) \
+ X(CHARSET, 42) /* Character set */ \
+ X(RSP, 43) /* Remote serial port */ \
+ X(COM_PORT_OPTION, 44) /* Com port control */ \
+ X(SLE, 45) /* Suppress local echo */ \
+ X(STARTTLS, 46) /* Start TLS */ \
+ X(KERMIT, 47) /* Automatic Kermit file transfer */ \
+ X(SEND_URL, 48) \
+ X(FORWARD_X, 49) \
+ X(PRAGMA_LOGON, 138) \
+ X(SSPI_LOGON, 139) \
+ X(PRAGMA_HEARTBEAT, 140) \
+ X(EXOPL, 255) /* extended-options-list */
+
+#define telnet_enum(x,y) TELOPT_##x = y,
+enum { TELOPTS(telnet_enum) dummy=0 };
+#undef telnet_enum
+
+#define TELQUAL_IS 0 /* option is... */
+#define TELQUAL_SEND 1 /* send option */
+#define TELQUAL_INFO 2 /* ENVIRON: informational version of IS */
+#define BSD_VAR 1
+#define BSD_VALUE 0
+#define RFC_VAR 0
+#define RFC_VALUE 1
+
+#define CR 13
+#define LF 10
+#define NUL 0
+
+#define iswritable(x) \
+ ( (x) != IAC && \
+ (telnet->opt_states[o_we_bin.index] == ACTIVE || (x) != CR))
+
+static char *telopt(int opt)
+{
+#define telnet_str(x,y) case TELOPT_##x: return #x;
+ switch (opt) {
+ TELOPTS(telnet_str)
+ default:
+ return "<unknown>";
+ }
+#undef telnet_str
+}
+
+static void telnet_size(void *handle, int width, int height);
+
+struct Opt {
+ int send; /* what we initially send */
+ int nsend; /* -ve send if requested to stop it */
+ int ack, nak; /* +ve and -ve acknowledgements */
+ int option; /* the option code */
+ int index; /* index into telnet->opt_states[] */
+ enum {
+ REQUESTED, ACTIVE, INACTIVE, REALLY_INACTIVE
+ } initial_state;
+};
+
+enum {
+ OPTINDEX_NAWS,
+ OPTINDEX_TSPEED,
+ OPTINDEX_TTYPE,
+ OPTINDEX_OENV,
+ OPTINDEX_NENV,
+ OPTINDEX_ECHO,
+ OPTINDEX_WE_SGA,
+ OPTINDEX_THEY_SGA,
+ OPTINDEX_WE_BIN,
+ OPTINDEX_THEY_BIN,
+ NUM_OPTS
+};
+
+static const struct Opt o_naws =
+ { WILL, WONT, DO, DONT, TELOPT_NAWS, OPTINDEX_NAWS, REQUESTED };
+static const struct Opt o_tspeed =
+ { WILL, WONT, DO, DONT, TELOPT_TSPEED, OPTINDEX_TSPEED, REQUESTED };
+static const struct Opt o_ttype =
+ { WILL, WONT, DO, DONT, TELOPT_TTYPE, OPTINDEX_TTYPE, REQUESTED };
+static const struct Opt o_oenv =
+ { WILL, WONT, DO, DONT, TELOPT_OLD_ENVIRON, OPTINDEX_OENV, INACTIVE };
+static const struct Opt o_nenv =
+ { WILL, WONT, DO, DONT, TELOPT_NEW_ENVIRON, OPTINDEX_NENV, REQUESTED };
+static const struct Opt o_echo =
+ { DO, DONT, WILL, WONT, TELOPT_ECHO, OPTINDEX_ECHO, REQUESTED };
+static const struct Opt o_we_sga =
+ { WILL, WONT, DO, DONT, TELOPT_SGA, OPTINDEX_WE_SGA, REQUESTED };
+static const struct Opt o_they_sga =
+ { DO, DONT, WILL, WONT, TELOPT_SGA, OPTINDEX_THEY_SGA, REQUESTED };
+static const struct Opt o_we_bin =
+ { WILL, WONT, DO, DONT, TELOPT_BINARY, OPTINDEX_WE_BIN, INACTIVE };
+static const struct Opt o_they_bin =
+ { DO, DONT, WILL, WONT, TELOPT_BINARY, OPTINDEX_THEY_BIN, INACTIVE };
+
+static const struct Opt *const opts[] = {
+ &o_naws, &o_tspeed, &o_ttype, &o_oenv, &o_nenv, &o_echo,
+ &o_we_sga, &o_they_sga, &o_we_bin, &o_they_bin, NULL
+};
+
+typedef struct telnet_tag {
+ const struct plug_function_table *fn;
+ /* the above field _must_ be first in the structure */
+
+ Socket s;
+ int closed_on_socket_error;
+
+ void *frontend;
+ void *ldisc;
+ int term_width, term_height;
+
+ int opt_states[NUM_OPTS];
+
+ int echoing, editing;
+ int activated;
+ int bufsize;
+ int in_synch;
+ int sb_opt, sb_len;
+ unsigned char *sb_buf;
+ int sb_size;
+
+ enum {
+ TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT,
+ SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR
+ } state;
+
+ Conf *conf;
+
+ Pinger pinger;
+} *Telnet;
+
+#define TELNET_MAX_BACKLOG 4096
+
+#define SB_DELTA 1024
+
+static void c_write(Telnet telnet, 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)
+{
+ char *buf;
+ /*
+ * The strange-looking "<?""?>" below is there to avoid a
+ * trigraph - a double question mark followed by > maps to a
+ * closing brace character!
+ */
+ buf = dupprintf("%s:\t%s %s", sender,
+ (cmd == WILL ? "WILL" : cmd == WONT ? "WONT" :
+ cmd == DO ? "DO" : cmd == DONT ? "DONT" : "<?""?>"),
+ telopt(option));
+ logevent(telnet->frontend, buf);
+ sfree(buf);
+}
+
+static void send_opt(Telnet telnet, int cmd, int option)
+{
+ unsigned char b[3];
+
+ b[0] = IAC;
+ b[1] = cmd;
+ b[2] = option;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 3);
+ log_option(telnet, "client", cmd, option);
+}
+
+static void deactivate_option(Telnet telnet, const struct Opt *o)
+{
+ if (telnet->opt_states[o->index] == REQUESTED ||
+ telnet->opt_states[o->index] == ACTIVE)
+ send_opt(telnet, o->nsend, o->option);
+ telnet->opt_states[o->index] = REALLY_INACTIVE;
+}
+
+/*
+ * Generate side effects of enabling or disabling an option.
+ */
+static void option_side_effects(Telnet telnet, const struct Opt *o, int enabled)
+{
+ if (o->option == TELOPT_ECHO && o->send == DO)
+ telnet->echoing = !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);
+
+ /* Ensure we get the minimum options */
+ if (!telnet->activated) {
+ if (telnet->opt_states[o_echo.index] == INACTIVE) {
+ telnet->opt_states[o_echo.index] = REQUESTED;
+ send_opt(telnet, o_echo.send, o_echo.option);
+ }
+ if (telnet->opt_states[o_we_sga.index] == INACTIVE) {
+ telnet->opt_states[o_we_sga.index] = REQUESTED;
+ send_opt(telnet, o_we_sga.send, o_we_sga.option);
+ }
+ if (telnet->opt_states[o_they_sga.index] == INACTIVE) {
+ telnet->opt_states[o_they_sga.index] = REQUESTED;
+ send_opt(telnet, o_they_sga.send, o_they_sga.option);
+ }
+ telnet->activated = TRUE;
+ }
+}
+
+static void activate_option(Telnet telnet, const struct Opt *o)
+{
+ if (o->send == WILL && o->option == TELOPT_NAWS)
+ telnet_size(telnet, telnet->term_width, telnet->term_height);
+ if (o->send == WILL &&
+ (o->option == TELOPT_NEW_ENVIRON ||
+ o->option == TELOPT_OLD_ENVIRON)) {
+ /*
+ * We may only have one kind of ENVIRON going at a time.
+ * This is a hack, but who cares.
+ */
+ deactivate_option(telnet, o->option ==
+ TELOPT_NEW_ENVIRON ? &o_oenv : &o_nenv);
+ }
+ option_side_effects(telnet, o, 1);
+}
+
+static void refused_option(Telnet telnet, const struct Opt *o)
+{
+ if (o->send == WILL && o->option == TELOPT_NEW_ENVIRON &&
+ telnet->opt_states[o_oenv.index] == INACTIVE) {
+ send_opt(telnet, WILL, TELOPT_OLD_ENVIRON);
+ telnet->opt_states[o_oenv.index] = REQUESTED;
+ }
+ option_side_effects(telnet, o, 0);
+}
+
+static void proc_rec_opt(Telnet telnet, int cmd, int option)
+{
+ const struct Opt *const *o;
+
+ log_option(telnet, "server", cmd, option);
+ for (o = opts; *o; o++) {
+ if ((*o)->option == option && (*o)->ack == cmd) {
+ switch (telnet->opt_states[(*o)->index]) {
+ case REQUESTED:
+ telnet->opt_states[(*o)->index] = ACTIVE;
+ activate_option(telnet, *o);
+ break;
+ case ACTIVE:
+ break;
+ case INACTIVE:
+ telnet->opt_states[(*o)->index] = ACTIVE;
+ send_opt(telnet, (*o)->send, option);
+ activate_option(telnet, *o);
+ break;
+ case REALLY_INACTIVE:
+ send_opt(telnet, (*o)->nsend, option);
+ break;
+ }
+ return;
+ } else if ((*o)->option == option && (*o)->nak == cmd) {
+ switch (telnet->opt_states[(*o)->index]) {
+ case REQUESTED:
+ telnet->opt_states[(*o)->index] = INACTIVE;
+ refused_option(telnet, *o);
+ break;
+ case ACTIVE:
+ telnet->opt_states[(*o)->index] = INACTIVE;
+ send_opt(telnet, (*o)->nsend, option);
+ option_side_effects(telnet, *o, 0);
+ break;
+ case INACTIVE:
+ case REALLY_INACTIVE:
+ break;
+ }
+ return;
+ }
+ }
+ /*
+ * If we reach here, the option was one we weren't prepared to
+ * cope with. If the request was positive (WILL or DO), we send
+ * a negative ack to indicate refusal. If the request was
+ * negative (WONT / DONT), we must do nothing.
+ */
+ if (cmd == WILL || cmd == DO)
+ send_opt(telnet, (cmd == WILL ? DONT : WONT), option);
+}
+
+static void process_subneg(Telnet telnet)
+{
+ unsigned char *b, *p, *q;
+ int var, value, n, bsize;
+ char *e, *eval, *ekey, *user;
+
+ switch (telnet->sb_opt) {
+ case TELOPT_TSPEED:
+ if (telnet->sb_len == 1 && telnet->sb_buf[0] == TELQUAL_SEND) {
+ char *logbuf;
+ char *termspeed = conf_get_str(telnet->conf, CONF_termspeed);
+ b = snewn(20 + strlen(termspeed), unsigned char);
+ b[0] = IAC;
+ b[1] = SB;
+ b[2] = TELOPT_TSPEED;
+ b[3] = TELQUAL_IS;
+ strcpy((char *)(b + 4), termspeed);
+ n = 4 + strlen(termspeed);
+ b[n] = IAC;
+ b[n + 1] = SE;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, n + 2);
+ logevent(telnet->frontend, "server:\tSB TSPEED SEND");
+ logbuf = dupprintf("client:\tSB TSPEED IS %s", termspeed);
+ logevent(telnet->frontend, logbuf);
+ sfree(logbuf);
+ sfree(b);
+ } else
+ logevent(telnet->frontend, "server:\tSB TSPEED <something weird>");
+ break;
+ case TELOPT_TTYPE:
+ if (telnet->sb_len == 1 && telnet->sb_buf[0] == TELQUAL_SEND) {
+ char *logbuf;
+ char *termtype = conf_get_str(telnet->conf, CONF_termtype);
+ b = snewn(20 + strlen(termtype), unsigned char);
+ b[0] = IAC;
+ b[1] = SB;
+ b[2] = TELOPT_TTYPE;
+ b[3] = TELQUAL_IS;
+ for (n = 0; termtype[n]; n++)
+ b[n + 4] = (termtype[n] >= 'a' && termtype[n] <= 'z' ?
+ termtype[n] + 'A' - 'a' :
+ termtype[n]);
+ b[n + 4] = IAC;
+ b[n + 5] = SE;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, n + 6);
+ b[n + 4] = 0;
+ logevent(telnet->frontend, "server:\tSB TTYPE SEND");
+ logbuf = dupprintf("client:\tSB TTYPE IS %s", b + 4);
+ logevent(telnet->frontend, logbuf);
+ sfree(logbuf);
+ sfree(b);
+ } else
+ logevent(telnet->frontend, "server:\tSB TTYPE <something weird>\r\n");
+ break;
+ case TELOPT_OLD_ENVIRON:
+ case TELOPT_NEW_ENVIRON:
+ p = telnet->sb_buf;
+ q = p + telnet->sb_len;
+ if (p < q && *p == TELQUAL_SEND) {
+ char *logbuf;
+ p++;
+ logbuf = dupprintf("server:\tSB %s SEND", telopt(telnet->sb_opt));
+ logevent(telnet->frontend, logbuf);
+ sfree(logbuf);
+ if (telnet->sb_opt == TELOPT_OLD_ENVIRON) {
+ if (conf_get_int(telnet->conf, CONF_rfc_environ)) {
+ value = RFC_VALUE;
+ var = RFC_VAR;
+ } else {
+ value = BSD_VALUE;
+ var = BSD_VAR;
+ }
+ /*
+ * Try to guess the sense of VAR and VALUE.
+ */
+ while (p < q) {
+ if (*p == RFC_VAR) {
+ value = RFC_VALUE;
+ var = RFC_VAR;
+ } else if (*p == BSD_VAR) {
+ value = BSD_VALUE;
+ var = BSD_VAR;
+ }
+ p++;
+ }
+ } else {
+ /*
+ * With NEW_ENVIRON, the sense of VAR and VALUE
+ * isn't in doubt.
+ */
+ value = RFC_VALUE;
+ var = RFC_VAR;
+ }
+ bsize = 20;
+ for (eval = conf_get_str_strs(telnet->conf, CONF_environmt,
+ NULL, &ekey);
+ eval != NULL;
+ eval = conf_get_str_strs(telnet->conf, CONF_environmt,
+ ekey, &ekey))
+ bsize += strlen(ekey) + strlen(eval) + 2;
+ user = get_remote_username(telnet->conf);
+ if (user)
+ bsize += 6 + strlen(user);
+
+ b = snewn(bsize, unsigned char);
+ b[0] = IAC;
+ b[1] = SB;
+ b[2] = telnet->sb_opt;
+ b[3] = TELQUAL_IS;
+ n = 4;
+ for (eval = conf_get_str_strs(telnet->conf, CONF_environmt,
+ NULL, &ekey);
+ eval != NULL;
+ eval = conf_get_str_strs(telnet->conf, CONF_environmt,
+ ekey, &ekey)) {
+ b[n++] = var;
+ for (e = ekey; *e; e++)
+ b[n++] = *e;
+ b[n++] = value;
+ for (e = eval; *e; e++)
+ b[n++] = *e;
+ }
+ if (user) {
+ b[n++] = var;
+ b[n++] = 'U';
+ b[n++] = 'S';
+ b[n++] = 'E';
+ b[n++] = 'R';
+ b[n++] = value;
+ for (e = user; *e; e++)
+ b[n++] = *e;
+ }
+ b[n++] = IAC;
+ b[n++] = SE;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, n);
+ if (n == 6) {
+ logbuf = dupprintf("client:\tSB %s IS <nothing>",
+ telopt(telnet->sb_opt));
+ logevent(telnet->frontend, logbuf);
+ sfree(logbuf);
+ } else {
+ logbuf = dupprintf("client:\tSB %s IS:",
+ telopt(telnet->sb_opt));
+ logevent(telnet->frontend, logbuf);
+ sfree(logbuf);
+ for (eval = conf_get_str_strs(telnet->conf, CONF_environmt,
+ NULL, &ekey);
+ eval != NULL;
+ eval = conf_get_str_strs(telnet->conf, CONF_environmt,
+ ekey, &ekey)) {
+ logbuf = dupprintf("\t%s=%s", ekey, eval);
+ logevent(telnet->frontend, logbuf);
+ sfree(logbuf);
+ }
+ if (user) {
+ logbuf = dupprintf("\tUSER=%s", user);
+ logevent(telnet->frontend, logbuf);
+ sfree(logbuf);
+ }
+ }
+ sfree(b);
+ sfree(user);
+ }
+ break;
+ }
+}
+
+static void do_telnet_read(Telnet telnet, char *buf, int len)
+{
+ char *outbuf = NULL;
+ int outbuflen = 0, outbufsize = 0;
+
+#define ADDTOBUF(c) do { \
+ if (outbuflen >= outbufsize) { \
+ outbufsize = outbuflen + 256; \
+ outbuf = sresize(outbuf, outbufsize, char); \
+ } \
+ outbuf[outbuflen++] = (c); \
+} while (0)
+
+ while (len--) {
+ int c = (unsigned char) *buf++;
+
+ switch (telnet->state) {
+ case TOP_LEVEL:
+ case SEENCR:
+ if (c == NUL && telnet->state == SEENCR)
+ telnet->state = TOP_LEVEL;
+ else if (c == IAC)
+ telnet->state = SEENIAC;
+ else {
+ if (!telnet->in_synch)
+ ADDTOBUF(c);
+
+#if 1
+ /* I can't get the F***ing winsock to insert the urgent IAC
+ * into the right position! Even with SO_OOBINLINE it gives
+ * it to recv too soon. And of course the DM byte (that
+ * arrives in the same packet!) appears several K later!!
+ *
+ * Oh well, we do get the DM in the right place so I'll
+ * just stop hiding on the next 0xf2 and hope for the best.
+ */
+ else if (c == DM)
+ telnet->in_synch = 0;
+#endif
+ if (c == CR && telnet->opt_states[o_they_bin.index] != ACTIVE)
+ telnet->state = SEENCR;
+ else
+ telnet->state = TOP_LEVEL;
+ }
+ break;
+ case SEENIAC:
+ if (c == DO)
+ telnet->state = SEENDO;
+ else if (c == DONT)
+ telnet->state = SEENDONT;
+ else if (c == WILL)
+ telnet->state = SEENWILL;
+ else if (c == WONT)
+ telnet->state = SEENWONT;
+ else if (c == SB)
+ telnet->state = SEENSB;
+ else if (c == DM) {
+ telnet->in_synch = 0;
+ telnet->state = TOP_LEVEL;
+ } else {
+ /* ignore everything else; print it if it's IAC */
+ if (c == IAC) {
+ ADDTOBUF(c);
+ }
+ telnet->state = TOP_LEVEL;
+ }
+ break;
+ case SEENWILL:
+ proc_rec_opt(telnet, WILL, c);
+ telnet->state = TOP_LEVEL;
+ break;
+ case SEENWONT:
+ proc_rec_opt(telnet, WONT, c);
+ telnet->state = TOP_LEVEL;
+ break;
+ case SEENDO:
+ proc_rec_opt(telnet, DO, c);
+ telnet->state = TOP_LEVEL;
+ break;
+ case SEENDONT:
+ proc_rec_opt(telnet, DONT, c);
+ telnet->state = TOP_LEVEL;
+ break;
+ case SEENSB:
+ telnet->sb_opt = c;
+ telnet->sb_len = 0;
+ telnet->state = SUBNEGOT;
+ break;
+ case SUBNEGOT:
+ if (c == IAC)
+ telnet->state = SUBNEG_IAC;
+ else {
+ subneg_addchar:
+ if (telnet->sb_len >= telnet->sb_size) {
+ telnet->sb_size += SB_DELTA;
+ telnet->sb_buf = sresize(telnet->sb_buf, telnet->sb_size,
+ unsigned char);
+ }
+ telnet->sb_buf[telnet->sb_len++] = c;
+ telnet->state = SUBNEGOT; /* in case we came here by goto */
+ }
+ break;
+ case SUBNEG_IAC:
+ if (c != SE)
+ goto subneg_addchar; /* yes, it's a hack, I know, but... */
+ else {
+ process_subneg(telnet);
+ telnet->state = TOP_LEVEL;
+ }
+ break;
+ }
+ }
+
+ if (outbuflen)
+ c_write(telnet, outbuf, outbuflen);
+ sfree(outbuf);
+}
+
+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);
+}
+
+static int telnet_closing(Plug plug, const char *error_msg, int error_code,
+ int calling_back)
+{
+ Telnet telnet = (Telnet) plug;
+
+ /*
+ * We don't implement independent EOF in each direction for Telnet
+ * connections; as soon as we get word that the remote side has
+ * sent us EOF, we wind up the whole connection.
+ */
+
+ if (telnet->s) {
+ sk_close(telnet->s);
+ telnet->s = NULL;
+ if (error_msg)
+ telnet->closed_on_socket_error = TRUE;
+ notify_remote_exit(telnet->frontend);
+ }
+ if (error_msg) {
+ logevent(telnet->frontend, error_msg);
+ connection_fatal(telnet->frontend, "%s", error_msg);
+ }
+ /* Otherwise, the remote side closed the connection normally. */
+ return 0;
+}
+
+static int telnet_receive(Plug plug, int urgent, char *data, int len)
+{
+ Telnet telnet = (Telnet) plug;
+ if (urgent)
+ telnet->in_synch = TRUE;
+ do_telnet_read(telnet, data, len);
+ return 1;
+}
+
+static void telnet_sent(Plug plug, int bufsize)
+{
+ Telnet telnet = (Telnet) plug;
+ telnet->bufsize = bufsize;
+}
+
+/*
+ * Called to set up the Telnet connection.
+ *
+ * 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 *telnet_init(void *frontend_handle, void **backend_handle,
+ Conf *conf, char *host, int port,
+ char **realhost, int nodelay, int keepalive)
+{
+ static const struct plug_function_table fn_table = {
+ telnet_log,
+ telnet_closing,
+ telnet_receive,
+ telnet_sent
+ };
+ SockAddr addr;
+ const char *err;
+ Telnet telnet;
+ char *loghost;
+ int addressfamily;
+
+ telnet = snew(struct telnet_tag);
+ telnet->fn = &fn_table;
+ telnet->conf = conf_copy(conf);
+ telnet->s = NULL;
+ telnet->closed_on_socket_error = FALSE;
+ telnet->echoing = TRUE;
+ telnet->editing = TRUE;
+ telnet->activated = FALSE;
+ telnet->sb_buf = NULL;
+ telnet->sb_size = 0;
+ telnet->frontend = frontend_handle;
+ telnet->term_width = conf_get_int(telnet->conf, CONF_width);
+ telnet->term_height = conf_get_int(telnet->conf, CONF_height);
+ telnet->state = TOP_LEVEL;
+ telnet->ldisc = NULL;
+ telnet->pinger = NULL;
+ *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);
+ if ((err = sk_addr_error(addr)) != NULL) {
+ sk_addr_free(addr);
+ return err;
+ }
+
+ if (port < 0)
+ port = 23; /* default telnet port */
+
+ /*
+ * Open socket.
+ */
+ telnet->s = new_connection(addr, *realhost, port, 0, 1,
+ nodelay, keepalive, (Plug) telnet, telnet->conf);
+ if ((err = sk_socket_error(telnet->s)) != NULL)
+ return err;
+
+ telnet->pinger = pinger_new(telnet->conf, &telnet_backend, telnet);
+
+ /*
+ * Initialise option states.
+ */
+ if (conf_get_int(telnet->conf, CONF_passive_telnet)) {
+ const struct Opt *const *o;
+
+ for (o = opts; *o; o++)
+ telnet->opt_states[(*o)->index] = INACTIVE;
+ } else {
+ const struct Opt *const *o;
+
+ for (o = opts; *o; o++) {
+ telnet->opt_states[(*o)->index] = (*o)->initial_state;
+ if (telnet->opt_states[(*o)->index] == REQUESTED)
+ send_opt(telnet, (*o)->send, (*o)->option);
+ }
+ telnet->activated = TRUE;
+ }
+
+ /*
+ * Set up SYNCH state.
+ */
+ telnet->in_synch = FALSE;
+
+ /*
+ * We can send special commands from the start.
+ */
+ update_specials_menu(telnet->frontend);
+
+ /*
+ * loghost overrides realhost, if specified.
+ */
+ loghost = conf_get_str(telnet->conf, CONF_loghost);
+ if (*loghost) {
+ char *colon;
+
+ sfree(*realhost);
+ *realhost = dupstr(loghost);
+
+ colon = host_strrchr(*realhost, ':');
+ if (colon)
+ *colon++ = '\0';
+ }
+
+ return NULL;
+}
+
+static void telnet_free(void *handle)
+{
+ Telnet telnet = (Telnet) handle;
+
+ sfree(telnet->sb_buf);
+ if (telnet->s)
+ sk_close(telnet->s);
+ if (telnet->pinger)
+ pinger_free(telnet->pinger);
+ conf_free(telnet->conf);
+ sfree(telnet);
+}
+/*
+ * Reconfigure the Telnet backend. There's no immediate action
+ * necessary, in this backend: we just save the fresh config for
+ * any subsequent negotiations.
+ */
+static void telnet_reconfig(void *handle, Conf *conf)
+{
+ Telnet telnet = (Telnet) handle;
+ pinger_reconfig(telnet->pinger, telnet->conf, conf);
+ conf_free(telnet->conf);
+ telnet->conf = conf_copy(conf);
+}
+
+/*
+ * Called to send data down the Telnet connection.
+ */
+static int telnet_send(void *handle, char *buf, int len)
+{
+ Telnet telnet = (Telnet) handle;
+ unsigned char *p, *end;
+ static const unsigned char iac[2] = { IAC, IAC };
+ static const unsigned char cr[2] = { CR, NUL };
+#if 0
+ static const unsigned char nl[2] = { CR, LF };
+#endif
+
+ if (telnet->s == NULL)
+ return 0;
+
+ p = (unsigned char *)buf;
+ end = (unsigned char *)(buf + len);
+ while (p < end) {
+ unsigned char *q = p;
+
+ while (p < end && iswritable(*p))
+ p++;
+ telnet->bufsize = sk_write(telnet->s, (char *)q, p - q);
+
+ while (p < end && !iswritable(*p)) {
+ telnet->bufsize =
+ sk_write(telnet->s, (char *)(*p == IAC ? iac : cr), 2);
+ p++;
+ }
+ }
+
+ return telnet->bufsize;
+}
+
+/*
+ * Called to query the current socket sendability status.
+ */
+static int telnet_sendbuffer(void *handle)
+{
+ Telnet telnet = (Telnet) handle;
+ return telnet->bufsize;
+}
+
+/*
+ * Called to set the size of the window from Telnet's POV.
+ */
+static void telnet_size(void *handle, int width, int height)
+{
+ Telnet telnet = (Telnet) handle;
+ unsigned char b[24];
+ int n;
+ char *logbuf;
+
+ telnet->term_width = width;
+ telnet->term_height = height;
+
+ if (telnet->s == NULL || telnet->opt_states[o_naws.index] != ACTIVE)
+ return;
+ n = 0;
+ b[n++] = IAC;
+ b[n++] = SB;
+ b[n++] = TELOPT_NAWS;
+ b[n++] = telnet->term_width >> 8;
+ if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */
+ b[n++] = telnet->term_width & 0xFF;
+ if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */
+ b[n++] = telnet->term_height >> 8;
+ if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */
+ b[n++] = telnet->term_height & 0xFF;
+ if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */
+ b[n++] = IAC;
+ b[n++] = SE;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, n);
+ logbuf = dupprintf("client:\tSB NAWS %d,%d",
+ telnet->term_width, telnet->term_height);
+ logevent(telnet->frontend, logbuf);
+ sfree(logbuf);
+}
+
+/*
+ * Send Telnet special codes.
+ */
+static void telnet_special(void *handle, Telnet_Special code)
+{
+ Telnet telnet = (Telnet) handle;
+ unsigned char b[2];
+
+ if (telnet->s == NULL)
+ return;
+
+ b[0] = IAC;
+ switch (code) {
+ case TS_AYT:
+ b[1] = AYT;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_BRK:
+ b[1] = BREAK;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_EC:
+ b[1] = EC;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_EL:
+ b[1] = EL;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_GA:
+ b[1] = GA;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_NOP:
+ b[1] = NOP;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_ABORT:
+ b[1] = ABORT;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_AO:
+ b[1] = AO;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_IP:
+ b[1] = IP;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_SUSP:
+ b[1] = SUSP;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_EOR:
+ b[1] = EOR;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_EOF:
+ b[1] = xEOF;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ break;
+ case TS_EOL:
+ /* In BINARY mode, CR-LF becomes just CR -
+ * and without the NUL suffix too. */
+ if (telnet->opt_states[o_we_bin.index] == ACTIVE)
+ telnet->bufsize = sk_write(telnet->s, "\r", 1);
+ else
+ telnet->bufsize = sk_write(telnet->s, "\r\n", 2);
+ break;
+ case TS_SYNCH:
+ b[1] = DM;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 1);
+ telnet->bufsize = sk_write_oob(telnet->s, (char *)(b + 1), 1);
+ break;
+ case TS_RECHO:
+ if (telnet->opt_states[o_echo.index] == INACTIVE ||
+ telnet->opt_states[o_echo.index] == REALLY_INACTIVE) {
+ telnet->opt_states[o_echo.index] = REQUESTED;
+ send_opt(telnet, o_echo.send, o_echo.option);
+ }
+ break;
+ case TS_LECHO:
+ if (telnet->opt_states[o_echo.index] == ACTIVE) {
+ telnet->opt_states[o_echo.index] = REQUESTED;
+ send_opt(telnet, o_echo.nsend, o_echo.option);
+ }
+ break;
+ case TS_PING:
+ if (telnet->opt_states[o_they_sga.index] == ACTIVE) {
+ b[1] = NOP;
+ telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
+ }
+ break;
+ default:
+ break; /* never heard of it */
+ }
+}
+
+static const struct telnet_special *telnet_get_specials(void *handle)
+{
+ static const struct telnet_special specials[] = {
+ {"Are You There", TS_AYT},
+ {"Break", TS_BRK},
+ {"Synch", TS_SYNCH},
+ {"Erase Character", TS_EC},
+ {"Erase Line", TS_EL},
+ {"Go Ahead", TS_GA},
+ {"No Operation", TS_NOP},
+ {NULL, TS_SEP},
+ {"Abort Process", TS_ABORT},
+ {"Abort Output", TS_AO},
+ {"Interrupt Process", TS_IP},
+ {"Suspend Process", TS_SUSP},
+ {NULL, TS_SEP},
+ {"End Of Record", TS_EOR},
+ {"End Of File", TS_EOF},
+ {NULL, TS_EXITMENU}
+ };
+ return specials;
+}
+
+static int telnet_connected(void *handle)
+{
+ Telnet telnet = (Telnet) handle;
+ return telnet->s != NULL;
+}
+
+static int telnet_sendok(void *handle)
+{
+ /* Telnet telnet = (Telnet) handle; */
+ return 1;
+}
+
+static void telnet_unthrottle(void *handle, int backlog)
+{
+ Telnet telnet = (Telnet) handle;
+ sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG);
+}
+
+static int telnet_ldisc(void *handle, int option)
+{
+ Telnet telnet = (Telnet) handle;
+ if (option == LD_ECHO)
+ return telnet->echoing;
+ if (option == LD_EDIT)
+ return telnet->editing;
+ return FALSE;
+}
+
+static void telnet_provide_ldisc(void *handle, void *ldisc)
+{
+ Telnet telnet = (Telnet) handle;
+ telnet->ldisc = ldisc;
+}
+
+static void telnet_provide_logctx(void *handle, void *logctx)
+{
+ /* This is a stub. */
+}
+
+static int telnet_exitcode(void *handle)
+{
+ Telnet telnet = (Telnet) handle;
+ if (telnet->s != NULL)
+ return -1; /* still connected */
+ else if (telnet->closed_on_socket_error)
+ return INT_MAX; /* a socket error counts as an unclean exit */
+ else
+ /* Telnet doesn't transmit exit codes back to the client */
+ return 0;
+}
+
+/*
+ * cfg_info for Telnet does nothing at all.
+ */
+static int telnet_cfg_info(void *handle)
+{
+ return 0;
+}
+
+Backend telnet_backend = {
+ telnet_init,
+ telnet_free,
+ telnet_reconfig,
+ telnet_send,
+ telnet_sendbuffer,
+ telnet_size,
+ telnet_special,
+ telnet_get_specials,
+ telnet_connected,
+ telnet_exitcode,
+ telnet_sendok,
+ telnet_ldisc,
+ telnet_provide_ldisc,
+ telnet_provide_logctx,
+ telnet_unthrottle,
+ telnet_cfg_info,
+ "telnet",
+ PROT_TELNET,
+ 23
+};
diff --git a/tools/plink/terminal.h b/tools/plink/terminal.h
index 924cf56b9..135ef45a6 100644
--- a/tools/plink/terminal.h
+++ b/tools/plink/terminal.h
@@ -1,326 +1,329 @@
-/*
- * Internals of the Terminal structure, for those other modules
- * which need to look inside it. It would be nice if this could be
- * folded back into terminal.c in future, with an abstraction layer
- * to handle everything that other modules need to know about it;
- * but for the moment, this will do.
- */
-
-#ifndef PUTTY_TERMINAL_H
-#define PUTTY_TERMINAL_H
-
-#include "tree234.h"
-
-struct beeptime {
- struct beeptime *next;
- unsigned long ticks;
-};
-
-typedef struct {
- int y, x;
-} pos;
-
-#ifdef OPTIMISE_SCROLL
-struct scrollregion {
- struct scrollregion *next;
- int topline; /* Top line of scroll region. */
- int botline; /* Bottom line of scroll region. */
- int lines; /* Number of lines to scroll by - +ve is forwards. */
-};
-#endif /* OPTIMISE_SCROLL */
-
-typedef struct termchar termchar;
-typedef struct termline termline;
-
-struct termchar {
- /*
- * Any code in terminal.c which definitely needs to be changed
- * when extra fields are added here is labelled with a comment
- * saying FULL-TERMCHAR.
- */
- unsigned long chr;
- unsigned long attr;
-
- /*
- * The cc_next field is used to link multiple termchars
- * together into a list, so as to fit more than one character
- * into a character cell (Unicode combining characters).
- *
- * cc_next is a relative offset into the current array of
- * termchars. I.e. to advance to the next character in a list,
- * one does `tc += tc->next'.
- *
- * Zero means end of list.
- */
- int cc_next;
-};
-
-struct termline {
- unsigned short lattr;
- int cols; /* number of real columns on the line */
- int size; /* number of allocated termchars
- * (cc-lists may make this > cols) */
- int temporary; /* TRUE if decompressed from scrollback */
- int cc_free; /* offset to first cc in free list */
- struct termchar *chars;
-};
-
-struct bidi_cache_entry {
- int width;
- struct termchar *chars;
- int *forward, *backward; /* the permutations of line positions */
-};
-
-struct terminal_tag {
-
- int compatibility_level;
-
- tree234 *scrollback; /* lines scrolled off top of screen */
- tree234 *screen; /* lines on primary screen */
- tree234 *alt_screen; /* lines on alternate screen */
- int disptop; /* distance scrolled back (0 or -ve) */
- int tempsblines; /* number of lines of .scrollback that
- can be retrieved onto the terminal
- ("temporary scrollback") */
-
- termline **disptext; /* buffer of text on real screen */
- int dispcursx, dispcursy; /* location of cursor on real screen */
- int curstype; /* type of cursor on real screen */
-
-#define VBELL_TIMEOUT (TICKSPERSEC/10) /* visual bell lasts 1/10 sec */
-
- struct beeptime *beephead, *beeptail;
- int nbeeps;
- int beep_overloaded;
- long lastbeep;
-
-#define TTYPE termchar
-#define TSIZE (sizeof(TTYPE))
-
-#ifdef OPTIMISE_SCROLL
- struct scrollregion *scrollhead, *scrolltail;
-#endif /* OPTIMISE_SCROLL */
-
- int default_attr, curr_attr, save_attr;
- termchar basic_erase_char, erase_char;
-
- bufchain inbuf; /* terminal input buffer */
- pos curs; /* cursor */
- pos savecurs; /* saved cursor position */
- int marg_t, marg_b; /* scroll margins */
- int dec_om; /* DEC origin mode flag */
- int wrap, wrapnext; /* wrap flags */
- int insert; /* insert-mode flag */
- int cset; /* 0 or 1: which char set */
- int save_cset, save_csattr; /* saved with cursor position */
- int save_utf, save_wnext; /* saved with cursor position */
- int rvideo; /* global reverse video flag */
- unsigned long rvbell_startpoint; /* for ESC[?5hESC[?5l vbell */
- int cursor_on; /* cursor enabled flag */
- int reset_132; /* Flag ESC c resets to 80 cols */
- int use_bce; /* Use Background coloured erase */
- int cblinker; /* When blinking is the cursor on ? */
- int tblinker; /* When the blinking text is on */
- int blink_is_real; /* Actually blink blinking text */
- int term_echoing; /* Does terminal want local echo? */
- int term_editing; /* Does terminal want local edit? */
- int sco_acs, save_sco_acs; /* CSI 10,11,12m -> OEM charset */
- int vt52_bold; /* Force bold on non-bold colours */
- int utf; /* Are we in toggleable UTF-8 mode? */
- int utf_state; /* Is there a pending UTF-8 character */
- int utf_char; /* and what is it so far. */
- int utf_size; /* The size of the UTF character. */
- int printing, only_printing; /* Are we doing ANSI printing? */
- int print_state; /* state of print-end-sequence scan */
- bufchain printer_buf; /* buffered data for printer */
- printer_job *print_job;
-
- /* ESC 7 saved state for the alternate screen */
- pos alt_savecurs;
- int alt_save_attr;
- int alt_save_cset, alt_save_csattr;
- int alt_save_utf, alt_save_wnext;
- int alt_save_sco_acs;
-
- int rows, cols, savelines;
- int has_focus;
- int in_vbell;
- long vbell_end;
- int app_cursor_keys, app_keypad_keys, vt52_mode;
- int repeat_off, cr_lf_return;
- int seen_disp_event;
- int big_cursor;
-
- int xterm_mouse; /* send mouse messages to host */
- int mouse_is_down; /* used while tracking mouse buttons */
-
- int cset_attr[2];
-
-/*
- * Saved settings on the alternate screen.
- */
- int alt_x, alt_y, alt_om, alt_wrap, alt_wnext, alt_ins;
- int alt_cset, alt_sco_acs, alt_utf;
- int alt_t, alt_b;
- int alt_which;
- int alt_sblines; /* # of lines on alternate screen that should be used for scrollback. */
-
-#define ARGS_MAX 32 /* max # of esc sequence arguments */
-#define ARG_DEFAULT 0 /* if an arg isn't specified */
-#define def(a,d) ( (a) == ARG_DEFAULT ? (d) : (a) )
- int esc_args[ARGS_MAX];
- int esc_nargs;
- int esc_query;
-#define ANSI(x,y) ((x)+((y)<<8))
-#define ANSI_QUE(x) ANSI(x,TRUE)
-
-#define OSC_STR_MAX 2048
- int osc_strlen;
- char osc_string[OSC_STR_MAX + 1];
- int osc_w;
-
- char id_string[1024];
-
- unsigned char *tabs;
-
- enum {
- TOPLEVEL,
- SEEN_ESC,
- SEEN_CSI,
- SEEN_OSC,
- SEEN_OSC_W,
-
- DO_CTRLS,
-
- SEEN_OSC_P,
- OSC_STRING, OSC_MAYBE_ST,
- VT52_ESC,
- VT52_Y1,
- VT52_Y2,
- VT52_FG,
- VT52_BG
- } termstate;
-
- enum {
- NO_SELECTION, ABOUT_TO, DRAGGING, SELECTED
- } selstate;
- enum {
- LEXICOGRAPHIC, RECTANGULAR
- } seltype;
- enum {
- SM_CHAR, SM_WORD, SM_LINE
- } selmode;
- pos selstart, selend, selanchor;
-
- short wordness[256];
-
- /* Mask of attributes to pay attention to when painting. */
- int attr_mask;
-
- wchar_t *paste_buffer;
- int paste_len, paste_pos, paste_hold;
- long last_paste;
-
- void (*resize_fn)(void *, int, int);
- void *resize_ctx;
-
- void *ldisc;
-
- void *frontend;
-
- void *logctx;
-
- struct unicode_data *ucsdata;
-
- /*
- * We maintain a full copy of a Conf here, not merely a pointer
- * to it. That way, when we're passed a new one for
- * reconfiguration, we can check the differences and adjust the
- * _current_ setting of (e.g.) auto wrap mode rather than only
- * the default.
- */
- Conf *conf;
-
- /*
- * from_backend calls term_out, but it can also be called from
- * the ldisc if the ldisc is called _within_ term_out. So we
- * have to guard against re-entrancy - if from_backend is
- * called recursively like this, it will simply add data to the
- * end of the buffer term_out is in the process of working
- * through.
- */
- int in_term_out;
-
- /*
- * We schedule a window update shortly after receiving terminal
- * data. This tracks whether one is currently pending.
- */
- int window_update_pending;
- long next_update;
-
- /*
- * Track pending blinks and tblinks.
- */
- int tblink_pending, cblink_pending;
- long next_tblink, next_cblink;
-
- /*
- * These are buffers used by the bidi and Arabic shaping code.
- */
- termchar *ltemp;
- int ltemp_size;
- bidi_char *wcFrom, *wcTo;
- int wcFromTo_size;
- struct bidi_cache_entry *pre_bidi_cache, *post_bidi_cache;
- int bidi_cache_size;
-
- /*
- * We copy a bunch of stuff out of the Conf structure into local
- * fields in the Terminal structure, to avoid the repeated
- * tree234 lookups which would be involved in fetching them from
- * the former every time.
- */
- int ansi_colour;
- char *answerback;
- int answerbacklen;
- int arabicshaping;
- int beep;
- int bellovl;
- int bellovl_n;
- int bellovl_s;
- int bellovl_t;
- int bidi;
- int bksp_is_delete;
- int blink_cur;
- int blinktext;
- int cjk_ambig_wide;
- int conf_height;
- int conf_width;
- int crhaslf;
- int erase_to_scrollback;
- int funky_type;
- int lfhascr;
- int logflush;
- int logtype;
- int mouse_override;
- int nethack_keypad;
- int no_alt_screen;
- int no_applic_c;
- int no_applic_k;
- int no_dbackspace;
- int no_mouse_rep;
- int no_remote_charset;
- int no_remote_resize;
- int no_remote_wintitle;
- int rawcnp;
- int rect_select;
- int remote_qtitle_action;
- int rxvt_homeend;
- int scroll_on_disp;
- int scroll_on_key;
- int xterm_256_colour;
-};
-
-#define in_utf(term) ((term)->utf || (term)->ucsdata->line_codepage==CP_UTF8)
-
-#endif
+/*
+ * Internals of the Terminal structure, for those other modules
+ * which need to look inside it. It would be nice if this could be
+ * folded back into terminal.c in future, with an abstraction layer
+ * to handle everything that other modules need to know about it;
+ * but for the moment, this will do.
+ */
+
+#ifndef PUTTY_TERMINAL_H
+#define PUTTY_TERMINAL_H
+
+#include "tree234.h"
+
+struct beeptime {
+ struct beeptime *next;
+ unsigned long ticks;
+};
+
+typedef struct {
+ int y, x;
+} pos;
+
+#ifdef OPTIMISE_SCROLL
+struct scrollregion {
+ struct scrollregion *next;
+ int topline; /* Top line of scroll region. */
+ int botline; /* Bottom line of scroll region. */
+ int lines; /* Number of lines to scroll by - +ve is forwards. */
+};
+#endif /* OPTIMISE_SCROLL */
+
+typedef struct termchar termchar;
+typedef struct termline termline;
+
+struct termchar {
+ /*
+ * Any code in terminal.c which definitely needs to be changed
+ * when extra fields are added here is labelled with a comment
+ * saying FULL-TERMCHAR.
+ */
+ unsigned long chr;
+ unsigned long attr;
+
+ /*
+ * The cc_next field is used to link multiple termchars
+ * together into a list, so as to fit more than one character
+ * into a character cell (Unicode combining characters).
+ *
+ * cc_next is a relative offset into the current array of
+ * termchars. I.e. to advance to the next character in a list,
+ * one does `tc += tc->next'.
+ *
+ * Zero means end of list.
+ */
+ int cc_next;
+};
+
+struct termline {
+ unsigned short lattr;
+ int cols; /* number of real columns on the line */
+ int size; /* number of allocated termchars
+ * (cc-lists may make this > cols) */
+ int temporary; /* TRUE if decompressed from scrollback */
+ int cc_free; /* offset to first cc in free list */
+ struct termchar *chars;
+};
+
+struct bidi_cache_entry {
+ int width;
+ struct termchar *chars;
+ int *forward, *backward; /* the permutations of line positions */
+};
+
+struct terminal_tag {
+
+ int compatibility_level;
+
+ tree234 *scrollback; /* lines scrolled off top of screen */
+ tree234 *screen; /* lines on primary screen */
+ tree234 *alt_screen; /* lines on alternate screen */
+ int disptop; /* distance scrolled back (0 or -ve) */
+ int tempsblines; /* number of lines of .scrollback that
+ can be retrieved onto the terminal
+ ("temporary scrollback") */
+
+ termline **disptext; /* buffer of text on real screen */
+ int dispcursx, dispcursy; /* location of cursor on real screen */
+ int curstype; /* type of cursor on real screen */
+
+#define VBELL_TIMEOUT (TICKSPERSEC/10) /* visual bell lasts 1/10 sec */
+
+ struct beeptime *beephead, *beeptail;
+ int nbeeps;
+ int beep_overloaded;
+ long lastbeep;
+
+#define TTYPE termchar
+#define TSIZE (sizeof(TTYPE))
+
+#ifdef OPTIMISE_SCROLL
+ struct scrollregion *scrollhead, *scrolltail;
+#endif /* OPTIMISE_SCROLL */
+
+ int default_attr, curr_attr, save_attr;
+ termchar basic_erase_char, erase_char;
+
+ bufchain inbuf; /* terminal input buffer */
+ pos curs; /* cursor */
+ pos savecurs; /* saved cursor position */
+ int marg_t, marg_b; /* scroll margins */
+ int dec_om; /* DEC origin mode flag */
+ int wrap, wrapnext; /* wrap flags */
+ int insert; /* insert-mode flag */
+ int cset; /* 0 or 1: which char set */
+ int save_cset, save_csattr; /* saved with cursor position */
+ int save_utf, save_wnext; /* saved with cursor position */
+ int rvideo; /* global reverse video flag */
+ unsigned long rvbell_startpoint; /* for ESC[?5hESC[?5l vbell */
+ int cursor_on; /* cursor enabled flag */
+ int reset_132; /* Flag ESC c resets to 80 cols */
+ int use_bce; /* Use Background coloured erase */
+ int cblinker; /* When blinking is the cursor on ? */
+ int tblinker; /* When the blinking text is on */
+ int blink_is_real; /* Actually blink blinking text */
+ int term_echoing; /* Does terminal want local echo? */
+ int term_editing; /* Does terminal want local edit? */
+ int sco_acs, save_sco_acs; /* CSI 10,11,12m -> OEM charset */
+ int vt52_bold; /* Force bold on non-bold colours */
+ int utf; /* Are we in toggleable UTF-8 mode? */
+ int utf_state; /* Is there a pending UTF-8 character */
+ int utf_char; /* and what is it so far. */
+ int utf_size; /* The size of the UTF character. */
+ int printing, only_printing; /* Are we doing ANSI printing? */
+ int print_state; /* state of print-end-sequence scan */
+ bufchain printer_buf; /* buffered data for printer */
+ printer_job *print_job;
+
+ /* ESC 7 saved state for the alternate screen */
+ pos alt_savecurs;
+ int alt_save_attr;
+ int alt_save_cset, alt_save_csattr;
+ int alt_save_utf, alt_save_wnext;
+ int alt_save_sco_acs;
+
+ int rows, cols, savelines;
+ int has_focus;
+ int in_vbell;
+ long vbell_end;
+ int app_cursor_keys, app_keypad_keys, vt52_mode;
+ int repeat_off, cr_lf_return;
+ int seen_disp_event;
+ int big_cursor;
+
+ int xterm_mouse; /* send mouse messages to host */
+ int xterm_extended_mouse;
+ int urxvt_extended_mouse;
+ int mouse_is_down; /* used while tracking mouse buttons */
+
+ int bracketed_paste;
+
+ int cset_attr[2];
+
+/*
+ * Saved settings on the alternate screen.
+ */
+ int alt_x, alt_y, alt_om, alt_wrap, alt_wnext, alt_ins;
+ int alt_cset, alt_sco_acs, alt_utf;
+ int alt_t, alt_b;
+ int alt_which;
+ int alt_sblines; /* # of lines on alternate screen that should be used for scrollback. */
+
+#define ARGS_MAX 32 /* max # of esc sequence arguments */
+#define ARG_DEFAULT 0 /* if an arg isn't specified */
+#define def(a,d) ( (a) == ARG_DEFAULT ? (d) : (a) )
+ int esc_args[ARGS_MAX];
+ int esc_nargs;
+ int esc_query;
+#define ANSI(x,y) ((x)+((y)<<8))
+#define ANSI_QUE(x) ANSI(x,TRUE)
+
+#define OSC_STR_MAX 2048
+ int osc_strlen;
+ char osc_string[OSC_STR_MAX + 1];
+ int osc_w;
+
+ char id_string[1024];
+
+ unsigned char *tabs;
+
+ enum {
+ TOPLEVEL,
+ SEEN_ESC,
+ SEEN_CSI,
+ SEEN_OSC,
+ SEEN_OSC_W,
+
+ DO_CTRLS,
+
+ SEEN_OSC_P,
+ OSC_STRING, OSC_MAYBE_ST,
+ VT52_ESC,
+ VT52_Y1,
+ VT52_Y2,
+ VT52_FG,
+ VT52_BG
+ } termstate;
+
+ enum {
+ NO_SELECTION, ABOUT_TO, DRAGGING, SELECTED
+ } selstate;
+ enum {
+ LEXICOGRAPHIC, RECTANGULAR
+ } seltype;
+ enum {
+ SM_CHAR, SM_WORD, SM_LINE
+ } selmode;
+ pos selstart, selend, selanchor;
+
+ short wordness[256];
+
+ /* Mask of attributes to pay attention to when painting. */
+ int attr_mask;
+
+ wchar_t *paste_buffer;
+ int paste_len, paste_pos;
+
+ void (*resize_fn)(void *, int, int);
+ void *resize_ctx;
+
+ void *ldisc;
+
+ void *frontend;
+
+ void *logctx;
+
+ struct unicode_data *ucsdata;
+
+ /*
+ * We maintain a full copy of a Conf here, not merely a pointer
+ * to it. That way, when we're passed a new one for
+ * reconfiguration, we can check the differences and adjust the
+ * _current_ setting of (e.g.) auto wrap mode rather than only
+ * the default.
+ */
+ Conf *conf;
+
+ /*
+ * from_backend calls term_out, but it can also be called from
+ * the ldisc if the ldisc is called _within_ term_out. So we
+ * have to guard against re-entrancy - if from_backend is
+ * called recursively like this, it will simply add data to the
+ * end of the buffer term_out is in the process of working
+ * through.
+ */
+ int in_term_out;
+
+ /*
+ * We schedule a window update shortly after receiving terminal
+ * data. This tracks whether one is currently pending.
+ */
+ int window_update_pending;
+ long next_update;
+
+ /*
+ * Track pending blinks and tblinks.
+ */
+ int tblink_pending, cblink_pending;
+ long next_tblink, next_cblink;
+
+ /*
+ * These are buffers used by the bidi and Arabic shaping code.
+ */
+ termchar *ltemp;
+ int ltemp_size;
+ bidi_char *wcFrom, *wcTo;
+ int wcFromTo_size;
+ struct bidi_cache_entry *pre_bidi_cache, *post_bidi_cache;
+ int bidi_cache_size;
+
+ /*
+ * We copy a bunch of stuff out of the Conf structure into local
+ * fields in the Terminal structure, to avoid the repeated
+ * tree234 lookups which would be involved in fetching them from
+ * the former every time.
+ */
+ int ansi_colour;
+ char *answerback;
+ int answerbacklen;
+ int arabicshaping;
+ int beep;
+ int bellovl;
+ int bellovl_n;
+ int bellovl_s;
+ int bellovl_t;
+ int bidi;
+ int bksp_is_delete;
+ int blink_cur;
+ int blinktext;
+ int cjk_ambig_wide;
+ int conf_height;
+ int conf_width;
+ int crhaslf;
+ int erase_to_scrollback;
+ int funky_type;
+ int lfhascr;
+ int logflush;
+ int logtype;
+ int mouse_override;
+ int nethack_keypad;
+ int no_alt_screen;
+ int no_applic_c;
+ int no_applic_k;
+ int no_dbackspace;
+ int no_mouse_rep;
+ int no_remote_charset;
+ int no_remote_resize;
+ int no_remote_wintitle;
+ int rawcnp;
+ int rect_select;
+ int remote_qtitle_action;
+ int rxvt_homeend;
+ int scroll_on_disp;
+ int scroll_on_key;
+ int xterm_256_colour;
+};
+
+#define in_utf(term) ((term)->utf || (term)->ucsdata->line_codepage==CP_UTF8)
+
+#endif
diff --git a/tools/plink/timing.c b/tools/plink/timing.c
index 2b7b70cb9..ccd76cd66 100644
--- a/tools/plink/timing.c
+++ b/tools/plink/timing.c
@@ -1,243 +1,211 @@
-/*
- * timing.c
- *
- * This module tracks any timers set up by schedule_timer(). It
- * keeps all the currently active timers in a list; it informs the
- * front end of when the next timer is due to go off if that
- * changes; and, very importantly, it tracks the context pointers
- * passed to schedule_timer(), so that if a context is freed all
- * the timers associated with it can be immediately annulled.
- */
-
-#include <assert.h>
-#include <stdio.h>
-
-#include "putty.h"
-#include "tree234.h"
-
-struct timer {
- timer_fn_t fn;
- void *ctx;
- long now;
-};
-
-static tree234 *timers = NULL;
-static tree234 *timer_contexts = NULL;
-static long now = 0L;
-
-static int compare_timers(void *av, void *bv)
-{
- struct timer *a = (struct timer *)av;
- struct timer *b = (struct timer *)bv;
- long at = a->now - now;
- long bt = b->now - now;
-
- if (at < bt)
- return -1;
- else if (at > bt)
- return +1;
-
- /*
- * Failing that, compare on the other two fields, just so that
- * we don't get unwanted equality.
- */
-#ifdef __LCC__
- /* lcc won't let us compare function pointers. Legal, but annoying. */
- {
- int c = memcmp(&a->fn, &b->fn, sizeof(a->fn));
- if (c < 0)
- return -1;
- else if (c > 0)
- return +1;
- }
-#else
- if (a->fn < b->fn)
- return -1;
- else if (a->fn > b->fn)
- return +1;
-#endif
-
- if (a->ctx < b->ctx)
- return -1;
- else if (a->ctx > b->ctx)
- return +1;
-
- /*
- * Failing _that_, the two entries genuinely are equal, and we
- * never have a need to store them separately in the tree.
- */
- return 0;
-}
-
-static int compare_timer_contexts(void *av, void *bv)
-{
- char *a = (char *)av;
- char *b = (char *)bv;
- if (a < b)
- return -1;
- else if (a > b)
- return +1;
- return 0;
-}
-
-static void init_timers(void)
-{
- if (!timers) {
- timers = newtree234(compare_timers);
- timer_contexts = newtree234(compare_timer_contexts);
- now = GETTICKCOUNT();
- }
-}
-
-long schedule_timer(int ticks, timer_fn_t fn, void *ctx)
-{
- long when;
- struct timer *t, *first;
-
- init_timers();
-
- when = ticks + GETTICKCOUNT();
-
- /*
- * Just in case our various defences against timing skew fail
- * us: if we try to schedule a timer that's already in the
- * past, we instead schedule it for the immediate future.
- */
- if (when - now <= 0)
- when = now + 1;
-
- t = snew(struct timer);
- t->fn = fn;
- t->ctx = ctx;
- t->now = when;
-
- if (t != add234(timers, t)) {
- sfree(t); /* identical timer already exists */
- } else {
- add234(timer_contexts, t->ctx);/* don't care if this fails */
- }
-
- first = (struct timer *)index234(timers, 0);
- if (first == t) {
- /*
- * This timer is the very first on the list, so we must
- * notify the front end.
- */
- timer_change_notify(first->now);
- }
-
- return when;
-}
-
-/*
- * Call to run any timers whose time has reached the present.
- * Returns the time (in ticks) expected until the next timer after
- * that triggers.
- */
-int run_timers(long anow, long *next)
-{
- struct timer *first;
-
- init_timers();
-
-#ifdef TIMING_SYNC
- /*
- * In this ifdef I put some code which deals with the
- * possibility that `anow' disagrees with GETTICKCOUNT by a
- * significant margin. Our strategy for dealing with it differs
- * depending on platform, because on some platforms
- * GETTICKCOUNT is more likely to be right whereas on others
- * `anow' is a better gold standard.
- */
- {
- long tnow = GETTICKCOUNT();
-
- if (tnow + TICKSPERSEC/50 - anow < 0 ||
- anow + TICKSPERSEC/50 - tnow < 0
- ) {
-#if defined TIMING_SYNC_ANOW
- /*
- * If anow is accurate and the tick count is wrong,
- * this is likely to be because the tick count is
- * derived from the system clock which has changed (as
- * can occur on Unix). Therefore, we resolve this by
- * inventing an offset which is used to adjust all
- * future output from GETTICKCOUNT.
- *
- * A platform which defines TIMING_SYNC_ANOW is
- * expected to have also defined this offset variable
- * in (its platform-specific adjunct to) putty.h.
- * Therefore we can simply reference it here and assume
- * that it will exist.
- */
- tickcount_offset += anow - tnow;
-#elif defined TIMING_SYNC_TICKCOUNT
- /*
- * If the tick count is more likely to be accurate, we
- * simply use that as our time value, which may mean we
- * run no timers in this call (because we got called
- * early), or alternatively it may mean we run lots of
- * timers in a hurry because we were called late.
- */
- anow = tnow;
-#else
-/*
- * Any platform which defines TIMING_SYNC must also define one of the two
- * auxiliary symbols TIMING_SYNC_ANOW and TIMING_SYNC_TICKCOUNT, to
- * indicate which measurement to trust when the two disagree.
- */
-#error TIMING_SYNC definition incomplete
-#endif
- }
- }
-#endif
-
- now = anow;
-
- while (1) {
- first = (struct timer *)index234(timers, 0);
-
- if (!first)
- return FALSE; /* no timers remaining */
-
- if (find234(timer_contexts, first->ctx, NULL) == NULL) {
- /*
- * This timer belongs to a context that has been
- * expired. Delete it without running.
- */
- delpos234(timers, 0);
- sfree(first);
- } else if (first->now - now <= 0) {
- /*
- * This timer is active and has reached its running
- * time. Run it.
- */
- delpos234(timers, 0);
- first->fn(first->ctx, first->now);
- sfree(first);
- } else {
- /*
- * This is the first still-active timer that is in the
- * future. Return how long it has yet to go.
- */
- *next = first->now;
- return TRUE;
- }
- }
-}
-
-/*
- * Call to expire all timers associated with a given context.
- */
-void expire_timer_context(void *ctx)
-{
- init_timers();
-
- /*
- * We don't bother to check the return value; if the context
- * already wasn't in the tree (presumably because no timers
- * ever actually got scheduled for it) then that's fine and we
- * simply don't need to do anything.
- */
- del234(timer_contexts, ctx);
-}
+/*
+ * timing.c
+ *
+ * This module tracks any timers set up by schedule_timer(). It
+ * keeps all the currently active timers in a list; it informs the
+ * front end of when the next timer is due to go off if that
+ * changes; and, very importantly, it tracks the context pointers
+ * passed to schedule_timer(), so that if a context is freed all
+ * the timers associated with it can be immediately annulled.
+ *
+ *
+ * The problem is that computer clocks aren't perfectly accurate.
+ * The GETTICKCOUNT function returns a 32bit number that normally
+ * increases by about 1000 every second. On windows this uses the PC's
+ * interrupt timer and so is only accurate to around 20ppm. On unix it's
+ * a value that's calculated from the current UTC time and so is in theory
+ * accurate in the long term but may jitter and jump in the short term.
+ *
+ * What PuTTY needs from these timers is simply a way of delaying the
+ * calling of a function for a little while, if it's occasionally called a
+ * little early or late that's not a problem. So to protect against clock
+ * jumps schedule_timer records the time that it was called in the timer
+ * structure. With this information the run_timers function can see when
+ * the current GETTICKCOUNT value is after the time the event should be
+ * fired OR before the time it was set. In the latter case the clock must
+ * have jumped, the former is (probably) just the normal passage of time.
+ *
+ */
+
+#include <assert.h>
+#include <stdio.h>
+
+#include "putty.h"
+#include "tree234.h"
+
+struct timer {
+ timer_fn_t fn;
+ void *ctx;
+ unsigned long now;
+ unsigned long when_set;
+};
+
+static tree234 *timers = NULL;
+static tree234 *timer_contexts = NULL;
+static unsigned long now = 0L;
+
+static int compare_timers(void *av, void *bv)
+{
+ struct timer *a = (struct timer *)av;
+ struct timer *b = (struct timer *)bv;
+ long at = a->now - now;
+ long bt = b->now - now;
+
+ if (at < bt)
+ return -1;
+ else if (at > bt)
+ return +1;
+
+ /*
+ * Failing that, compare on the other two fields, just so that
+ * we don't get unwanted equality.
+ */
+#if defined(__LCC__) || defined(__clang__)
+ /* lcc won't let us compare function pointers. Legal, but annoying. */
+ {
+ int c = memcmp(&a->fn, &b->fn, sizeof(a->fn));
+ if (c)
+ return c;
+ }
+#else
+ if (a->fn < b->fn)
+ return -1;
+ else if (a->fn > b->fn)
+ return +1;
+#endif
+
+ if (a->ctx < b->ctx)
+ return -1;
+ else if (a->ctx > b->ctx)
+ return +1;
+
+ /*
+ * Failing _that_, the two entries genuinely are equal, and we
+ * never have a need to store them separately in the tree.
+ */
+ return 0;
+}
+
+static int compare_timer_contexts(void *av, void *bv)
+{
+ char *a = (char *)av;
+ char *b = (char *)bv;
+ if (a < b)
+ return -1;
+ else if (a > b)
+ return +1;
+ return 0;
+}
+
+static void init_timers(void)
+{
+ if (!timers) {
+ timers = newtree234(compare_timers);
+ timer_contexts = newtree234(compare_timer_contexts);
+ now = GETTICKCOUNT();
+ }
+}
+
+unsigned long schedule_timer(int ticks, timer_fn_t fn, void *ctx)
+{
+ unsigned long when;
+ struct timer *t, *first;
+
+ init_timers();
+
+ now = GETTICKCOUNT();
+ when = ticks + now;
+
+ /*
+ * Just in case our various defences against timing skew fail
+ * us: if we try to schedule a timer that's already in the
+ * past, we instead schedule it for the immediate future.
+ */
+ if (when - now <= 0)
+ when = now + 1;
+
+ t = snew(struct timer);
+ t->fn = fn;
+ t->ctx = ctx;
+ t->now = when;
+ t->when_set = now;
+
+ if (t != add234(timers, t)) {
+ sfree(t); /* identical timer already exists */
+ } else {
+ add234(timer_contexts, t->ctx);/* don't care if this fails */
+ }
+
+ first = (struct timer *)index234(timers, 0);
+ if (first == t) {
+ /*
+ * This timer is the very first on the list, so we must
+ * notify the front end.
+ */
+ timer_change_notify(first->now);
+ }
+
+ return when;
+}
+
+/*
+ * Call to run any timers whose time has reached the present.
+ * Returns the time (in ticks) expected until the next timer after
+ * that triggers.
+ */
+int run_timers(unsigned long anow, unsigned long *next)
+{
+ struct timer *first;
+
+ init_timers();
+
+ now = GETTICKCOUNT();
+
+ while (1) {
+ first = (struct timer *)index234(timers, 0);
+
+ if (!first)
+ return FALSE; /* no timers remaining */
+
+ if (find234(timer_contexts, first->ctx, NULL) == NULL) {
+ /*
+ * This timer belongs to a context that has been
+ * expired. Delete it without running.
+ */
+ delpos234(timers, 0);
+ sfree(first);
+ } else if (now - (first->when_set - 10) >
+ first->now - (first->when_set - 10)) {
+ /*
+ * This timer is active and has reached its running
+ * time. Run it.
+ */
+ delpos234(timers, 0);
+ first->fn(first->ctx, first->now);
+ sfree(first);
+ } else {
+ /*
+ * This is the first still-active timer that is in the
+ * future. Return how long it has yet to go.
+ */
+ *next = first->now;
+ return TRUE;
+ }
+ }
+}
+
+/*
+ * Call to expire all timers associated with a given context.
+ */
+void expire_timer_context(void *ctx)
+{
+ init_timers();
+
+ /*
+ * We don't bother to check the return value; if the context
+ * already wasn't in the tree (presumably because no timers
+ * ever actually got scheduled for it) then that's fine and we
+ * simply don't need to do anything.
+ */
+ del234(timer_contexts, ctx);
+}
diff --git a/tools/plink/tree234.c b/tools/plink/tree234.c
index 4e2da9dd6..f1c0c2edb 100644
--- a/tools/plink/tree234.c
+++ b/tools/plink/tree234.c
@@ -1,1479 +1,1486 @@
-/*
- * tree234.c: reasonably generic counted 2-3-4 tree routines.
- *
- * This file is copyright 1999-2001 Simon Tatham.
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use,
- * copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following
- * conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR
- * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
- * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-
-#include "puttymem.h"
-#include "tree234.h"
-
-#ifdef TEST
-#define LOG(x) (printf x)
-#else
-#define LOG(x)
-#endif
-
-typedef struct node234_Tag node234;
-
-struct tree234_Tag {
- node234 *root;
- cmpfn234 cmp;
-};
-
-struct node234_Tag {
- node234 *parent;
- node234 *kids[4];
- int counts[4];
- void *elems[3];
-};
-
-/*
- * Create a 2-3-4 tree.
- */
-tree234 *newtree234(cmpfn234 cmp)
-{
- tree234 *ret = snew(tree234);
- LOG(("created tree %p\n", ret));
- ret->root = NULL;
- ret->cmp = cmp;
- return ret;
-}
-
-/*
- * Free a 2-3-4 tree (not including freeing the elements).
- */
-static void freenode234(node234 * n)
-{
- if (!n)
- return;
- freenode234(n->kids[0]);
- freenode234(n->kids[1]);
- freenode234(n->kids[2]);
- freenode234(n->kids[3]);
- sfree(n);
-}
-
-void freetree234(tree234 * t)
-{
- freenode234(t->root);
- sfree(t);
-}
-
-/*
- * Internal function to count a node.
- */
-static int countnode234(node234 * n)
-{
- int count = 0;
- int i;
- if (!n)
- return 0;
- for (i = 0; i < 4; i++)
- count += n->counts[i];
- for (i = 0; i < 3; i++)
- if (n->elems[i])
- count++;
- return count;
-}
-
-/*
- * Count the elements in a tree.
- */
-int count234(tree234 * t)
-{
- if (t->root)
- return countnode234(t->root);
- else
- return 0;
-}
-
-/*
- * Add an element e to a 2-3-4 tree t. Returns e on success, or if
- * an existing element compares equal, returns that.
- */
-static void *add234_internal(tree234 * t, void *e, int index)
-{
- node234 *n, **np, *left, *right;
- void *orig_e = e;
- int c, lcount, rcount;
-
- LOG(("adding node %p to tree %p\n", e, t));
- if (t->root == NULL) {
- t->root = snew(node234);
- t->root->elems[1] = t->root->elems[2] = NULL;
- t->root->kids[0] = t->root->kids[1] = NULL;
- t->root->kids[2] = t->root->kids[3] = NULL;
- t->root->counts[0] = t->root->counts[1] = 0;
- t->root->counts[2] = t->root->counts[3] = 0;
- t->root->parent = NULL;
- t->root->elems[0] = e;
- LOG((" created root %p\n", t->root));
- return orig_e;
- }
-
- n = NULL; /* placate gcc; will always be set below since t->root != NULL */
- np = &t->root;
- while (*np) {
- int childnum;
- n = *np;
- LOG((" node %p: %p/%d [%p] %p/%d [%p] %p/%d [%p] %p/%d\n",
- n,
- n->kids[0], n->counts[0], n->elems[0],
- n->kids[1], n->counts[1], n->elems[1],
- n->kids[2], n->counts[2], n->elems[2],
- n->kids[3], n->counts[3]));
- if (index >= 0) {
- if (!n->kids[0]) {
- /*
- * Leaf node. We want to insert at kid position
- * equal to the index:
- *
- * 0 A 1 B 2 C 3
- */
- childnum = index;
- } else {
- /*
- * Internal node. We always descend through it (add
- * always starts at the bottom, never in the
- * middle).
- */
- do { /* this is a do ... while (0) to allow `break' */
- if (index <= n->counts[0]) {
- childnum = 0;
- break;
- }
- index -= n->counts[0] + 1;
- if (index <= n->counts[1]) {
- childnum = 1;
- break;
- }
- index -= n->counts[1] + 1;
- if (index <= n->counts[2]) {
- childnum = 2;
- break;
- }
- index -= n->counts[2] + 1;
- if (index <= n->counts[3]) {
- childnum = 3;
- break;
- }
- return NULL; /* error: index out of range */
- } while (0);
- }
- } else {
- if ((c = t->cmp(e, n->elems[0])) < 0)
- childnum = 0;
- else if (c == 0)
- return n->elems[0]; /* already exists */
- else if (n->elems[1] == NULL
- || (c = t->cmp(e, n->elems[1])) < 0) childnum = 1;
- else if (c == 0)
- return n->elems[1]; /* already exists */
- else if (n->elems[2] == NULL
- || (c = t->cmp(e, n->elems[2])) < 0) childnum = 2;
- else if (c == 0)
- return n->elems[2]; /* already exists */
- else
- childnum = 3;
- }
- np = &n->kids[childnum];
- LOG((" moving to child %d (%p)\n", childnum, *np));
- }
-
- /*
- * We need to insert the new element in n at position np.
- */
- left = NULL;
- lcount = 0;
- right = NULL;
- rcount = 0;
- while (n) {
- LOG((" at %p: %p/%d [%p] %p/%d [%p] %p/%d [%p] %p/%d\n",
- n,
- n->kids[0], n->counts[0], n->elems[0],
- n->kids[1], n->counts[1], n->elems[1],
- n->kids[2], n->counts[2], n->elems[2],
- n->kids[3], n->counts[3]));
- LOG((" need to insert %p/%d [%p] %p/%d at position %d\n",
- left, lcount, e, right, rcount, np - n->kids));
- if (n->elems[1] == NULL) {
- /*
- * Insert in a 2-node; simple.
- */
- if (np == &n->kids[0]) {
- LOG((" inserting on left of 2-node\n"));
- n->kids[2] = n->kids[1];
- n->counts[2] = n->counts[1];
- n->elems[1] = n->elems[0];
- n->kids[1] = right;
- n->counts[1] = rcount;
- n->elems[0] = e;
- n->kids[0] = left;
- n->counts[0] = lcount;
- } else { /* np == &n->kids[1] */
- LOG((" inserting on right of 2-node\n"));
- n->kids[2] = right;
- n->counts[2] = rcount;
- n->elems[1] = e;
- n->kids[1] = left;
- n->counts[1] = lcount;
- }
- if (n->kids[0])
- n->kids[0]->parent = n;
- if (n->kids[1])
- n->kids[1]->parent = n;
- if (n->kids[2])
- n->kids[2]->parent = n;
- LOG((" done\n"));
- break;
- } else if (n->elems[2] == NULL) {
- /*
- * Insert in a 3-node; simple.
- */
- if (np == &n->kids[0]) {
- LOG((" inserting on left of 3-node\n"));
- n->kids[3] = n->kids[2];
- n->counts[3] = n->counts[2];
- n->elems[2] = n->elems[1];
- n->kids[2] = n->kids[1];
- n->counts[2] = n->counts[1];
- n->elems[1] = n->elems[0];
- n->kids[1] = right;
- n->counts[1] = rcount;
- n->elems[0] = e;
- n->kids[0] = left;
- n->counts[0] = lcount;
- } else if (np == &n->kids[1]) {
- LOG((" inserting in middle of 3-node\n"));
- n->kids[3] = n->kids[2];
- n->counts[3] = n->counts[2];
- n->elems[2] = n->elems[1];
- n->kids[2] = right;
- n->counts[2] = rcount;
- n->elems[1] = e;
- n->kids[1] = left;
- n->counts[1] = lcount;
- } else { /* np == &n->kids[2] */
- LOG((" inserting on right of 3-node\n"));
- n->kids[3] = right;
- n->counts[3] = rcount;
- n->elems[2] = e;
- n->kids[2] = left;
- n->counts[2] = lcount;
- }
- if (n->kids[0])
- n->kids[0]->parent = n;
- if (n->kids[1])
- n->kids[1]->parent = n;
- if (n->kids[2])
- n->kids[2]->parent = n;
- if (n->kids[3])
- n->kids[3]->parent = n;
- LOG((" done\n"));
- break;
- } else {
- node234 *m = snew(node234);
- m->parent = n->parent;
- LOG((" splitting a 4-node; created new node %p\n", m));
- /*
- * Insert in a 4-node; split into a 2-node and a
- * 3-node, and move focus up a level.
- *
- * I don't think it matters which way round we put the
- * 2 and the 3. For simplicity, we'll put the 3 first
- * always.
- */
- if (np == &n->kids[0]) {
- m->kids[0] = left;
- m->counts[0] = lcount;
- m->elems[0] = e;
- m->kids[1] = right;
- m->counts[1] = rcount;
- m->elems[1] = n->elems[0];
- m->kids[2] = n->kids[1];
- m->counts[2] = n->counts[1];
- e = n->elems[1];
- n->kids[0] = n->kids[2];
- n->counts[0] = n->counts[2];
- n->elems[0] = n->elems[2];
- n->kids[1] = n->kids[3];
- n->counts[1] = n->counts[3];
- } else if (np == &n->kids[1]) {
- m->kids[0] = n->kids[0];
- m->counts[0] = n->counts[0];
- m->elems[0] = n->elems[0];
- m->kids[1] = left;
- m->counts[1] = lcount;
- m->elems[1] = e;
- m->kids[2] = right;
- m->counts[2] = rcount;
- e = n->elems[1];
- n->kids[0] = n->kids[2];
- n->counts[0] = n->counts[2];
- n->elems[0] = n->elems[2];
- n->kids[1] = n->kids[3];
- n->counts[1] = n->counts[3];
- } else if (np == &n->kids[2]) {
- m->kids[0] = n->kids[0];
- m->counts[0] = n->counts[0];
- m->elems[0] = n->elems[0];
- m->kids[1] = n->kids[1];
- m->counts[1] = n->counts[1];
- m->elems[1] = n->elems[1];
- m->kids[2] = left;
- m->counts[2] = lcount;
- /* e = e; */
- n->kids[0] = right;
- n->counts[0] = rcount;
- n->elems[0] = n->elems[2];
- n->kids[1] = n->kids[3];
- n->counts[1] = n->counts[3];
- } else { /* np == &n->kids[3] */
- m->kids[0] = n->kids[0];
- m->counts[0] = n->counts[0];
- m->elems[0] = n->elems[0];
- m->kids[1] = n->kids[1];
- m->counts[1] = n->counts[1];
- m->elems[1] = n->elems[1];
- m->kids[2] = n->kids[2];
- m->counts[2] = n->counts[2];
- n->kids[0] = left;
- n->counts[0] = lcount;
- n->elems[0] = e;
- n->kids[1] = right;
- n->counts[1] = rcount;
- e = n->elems[2];
- }
- m->kids[3] = n->kids[3] = n->kids[2] = NULL;
- m->counts[3] = n->counts[3] = n->counts[2] = 0;
- m->elems[2] = n->elems[2] = n->elems[1] = NULL;
- if (m->kids[0])
- m->kids[0]->parent = m;
- if (m->kids[1])
- m->kids[1]->parent = m;
- if (m->kids[2])
- m->kids[2]->parent = m;
- if (n->kids[0])
- n->kids[0]->parent = n;
- if (n->kids[1])
- n->kids[1]->parent = n;
- LOG((" left (%p): %p/%d [%p] %p/%d [%p] %p/%d\n", m,
- m->kids[0], m->counts[0], m->elems[0],
- m->kids[1], m->counts[1], m->elems[1],
- m->kids[2], m->counts[2]));
- LOG((" right (%p): %p/%d [%p] %p/%d\n", n,
- n->kids[0], n->counts[0], n->elems[0],
- n->kids[1], n->counts[1]));
- left = m;
- lcount = countnode234(left);
- right = n;
- rcount = countnode234(right);
- }
- if (n->parent)
- np = (n->parent->kids[0] == n ? &n->parent->kids[0] :
- n->parent->kids[1] == n ? &n->parent->kids[1] :
- n->parent->kids[2] == n ? &n->parent->kids[2] :
- &n->parent->kids[3]);
- n = n->parent;
- }
-
- /*
- * If we've come out of here by `break', n will still be
- * non-NULL and all we need to do is go back up the tree
- * updating counts. If we've come here because n is NULL, we
- * need to create a new root for the tree because the old one
- * has just split into two. */
- if (n) {
- while (n->parent) {
- int count = countnode234(n);
- int childnum;
- childnum = (n->parent->kids[0] == n ? 0 :
- n->parent->kids[1] == n ? 1 :
- n->parent->kids[2] == n ? 2 : 3);
- n->parent->counts[childnum] = count;
- n = n->parent;
- }
- } else {
- LOG((" root is overloaded, split into two\n"));
- t->root = snew(node234);
- t->root->kids[0] = left;
- t->root->counts[0] = lcount;
- t->root->elems[0] = e;
- t->root->kids[1] = right;
- t->root->counts[1] = rcount;
- t->root->elems[1] = NULL;
- t->root->kids[2] = NULL;
- t->root->counts[2] = 0;
- t->root->elems[2] = NULL;
- t->root->kids[3] = NULL;
- t->root->counts[3] = 0;
- t->root->parent = NULL;
- if (t->root->kids[0])
- t->root->kids[0]->parent = t->root;
- if (t->root->kids[1])
- t->root->kids[1]->parent = t->root;
- LOG((" new root is %p/%d [%p] %p/%d\n",
- t->root->kids[0], t->root->counts[0],
- t->root->elems[0], t->root->kids[1], t->root->counts[1]));
- }
-
- return orig_e;
-}
-
-void *add234(tree234 * t, void *e)
-{
- if (!t->cmp) /* tree is unsorted */
- return NULL;
-
- return add234_internal(t, e, -1);
-}
-void *addpos234(tree234 * t, void *e, int index)
-{
- if (index < 0 || /* index out of range */
- t->cmp) /* tree is sorted */
- return NULL; /* return failure */
-
- return add234_internal(t, e, index); /* this checks the upper bound */
-}
-
-/*
- * Look up the element at a given numeric index in a 2-3-4 tree.
- * Returns NULL if the index is out of range.
- */
-void *index234(tree234 * t, int index)
-{
- node234 *n;
-
- if (!t->root)
- return NULL; /* tree is empty */
-
- if (index < 0 || index >= countnode234(t->root))
- return NULL; /* out of range */
-
- n = t->root;
-
- while (n) {
- if (index < n->counts[0])
- n = n->kids[0];
- else if (index -= n->counts[0] + 1, index < 0)
- return n->elems[0];
- else if (index < n->counts[1])
- n = n->kids[1];
- else if (index -= n->counts[1] + 1, index < 0)
- return n->elems[1];
- else if (index < n->counts[2])
- n = n->kids[2];
- else if (index -= n->counts[2] + 1, index < 0)
- return n->elems[2];
- else
- n = n->kids[3];
- }
-
- /* We shouldn't ever get here. I wonder how we did. */
- return NULL;
-}
-
-/*
- * Find an element e in a sorted 2-3-4 tree t. Returns NULL if not
- * found. e is always passed as the first argument to cmp, so cmp
- * can be an asymmetric function if desired. cmp can also be passed
- * as NULL, in which case the compare function from the tree proper
- * will be used.
- */
-void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp,
- int relation, int *index)
-{
- node234 *n;
- void *ret;
- int c;
- int idx, ecount, kcount, cmpret;
-
- if (t->root == NULL)
- return NULL;
-
- if (cmp == NULL)
- cmp = t->cmp;
-
- n = t->root;
- /*
- * Attempt to find the element itself.
- */
- idx = 0;
- ecount = -1;
- /*
- * Prepare a fake `cmp' result if e is NULL.
- */
- cmpret = 0;
- if (e == NULL) {
- assert(relation == REL234_LT || relation == REL234_GT);
- if (relation == REL234_LT)
- cmpret = +1; /* e is a max: always greater */
- else if (relation == REL234_GT)
- cmpret = -1; /* e is a min: always smaller */
- }
- while (1) {
- for (kcount = 0; kcount < 4; kcount++) {
- if (kcount >= 3 || n->elems[kcount] == NULL ||
- (c = cmpret ? cmpret : cmp(e, n->elems[kcount])) < 0) {
- break;
- }
- if (n->kids[kcount])
- idx += n->counts[kcount];
- if (c == 0) {
- ecount = kcount;
- break;
- }
- idx++;
- }
- if (ecount >= 0)
- break;
- if (n->kids[kcount])
- n = n->kids[kcount];
- else
- break;
- }
-
- if (ecount >= 0) {
- /*
- * We have found the element we're looking for. It's
- * n->elems[ecount], at tree index idx. If our search
- * relation is EQ, LE or GE we can now go home.
- */
- if (relation != REL234_LT && relation != REL234_GT) {
- if (index)
- *index = idx;
- return n->elems[ecount];
- }
-
- /*
- * Otherwise, we'll do an indexed lookup for the previous
- * or next element. (It would be perfectly possible to
- * implement these search types in a non-counted tree by
- * going back up from where we are, but far more fiddly.)
- */
- if (relation == REL234_LT)
- idx--;
- else
- idx++;
- } else {
- /*
- * We've found our way to the bottom of the tree and we
- * know where we would insert this node if we wanted to:
- * we'd put it in in place of the (empty) subtree
- * n->kids[kcount], and it would have index idx
- *
- * But the actual element isn't there. So if our search
- * relation is EQ, we're doomed.
- */
- if (relation == REL234_EQ)
- return NULL;
-
- /*
- * Otherwise, we must do an index lookup for index idx-1
- * (if we're going left - LE or LT) or index idx (if we're
- * going right - GE or GT).
- */
- if (relation == REL234_LT || relation == REL234_LE) {
- idx--;
- }
- }
-
- /*
- * We know the index of the element we want; just call index234
- * to do the rest. This will return NULL if the index is out of
- * bounds, which is exactly what we want.
- */
- ret = index234(t, idx);
- if (ret && index)
- *index = idx;
- return ret;
-}
-void *find234(tree234 * t, void *e, cmpfn234 cmp)
-{
- return findrelpos234(t, e, cmp, REL234_EQ, NULL);
-}
-void *findrel234(tree234 * t, void *e, cmpfn234 cmp, int relation)
-{
- return findrelpos234(t, e, cmp, relation, NULL);
-}
-void *findpos234(tree234 * t, void *e, cmpfn234 cmp, int *index)
-{
- return findrelpos234(t, e, cmp, REL234_EQ, index);
-}
-
-/*
- * Delete an element e in a 2-3-4 tree. Does not free the element,
- * merely removes all links to it from the tree nodes.
- */
-static void *delpos234_internal(tree234 * t, int index)
-{
- node234 *n;
- void *retval;
- int ei = -1;
-
- retval = 0;
-
- n = t->root;
- LOG(("deleting item %d from tree %p\n", index, t));
- while (1) {
- while (n) {
- int ki;
- node234 *sub;
-
- LOG(
- (" node %p: %p/%d [%p] %p/%d [%p] %p/%d [%p] %p/%d index=%d\n",
- n, n->kids[0], n->counts[0], n->elems[0], n->kids[1],
- n->counts[1], n->elems[1], n->kids[2], n->counts[2],
- n->elems[2], n->kids[3], n->counts[3], index));
- if (index < n->counts[0]) {
- ki = 0;
- } else if (index -= n->counts[0] + 1, index < 0) {
- ei = 0;
- break;
- } else if (index < n->counts[1]) {
- ki = 1;
- } else if (index -= n->counts[1] + 1, index < 0) {
- ei = 1;
- break;
- } else if (index < n->counts[2]) {
- ki = 2;
- } else if (index -= n->counts[2] + 1, index < 0) {
- ei = 2;
- break;
- } else {
- ki = 3;
- }
- /*
- * Recurse down to subtree ki. If it has only one element,
- * we have to do some transformation to start with.
- */
- LOG((" moving to subtree %d\n", ki));
- sub = n->kids[ki];
- if (!sub->elems[1]) {
- LOG((" subtree has only one element!\n", ki));
- if (ki > 0 && n->kids[ki - 1]->elems[1]) {
- /*
- * Case 3a, left-handed variant. Child ki has
- * only one element, but child ki-1 has two or
- * more. So we need to move a subtree from ki-1
- * to ki.
- *
- * . C . . B .
- * / \ -> / \
- * [more] a A b B c d D e [more] a A b c C d D e
- */
- node234 *sib = n->kids[ki - 1];
- int lastelem = (sib->elems[2] ? 2 :
- sib->elems[1] ? 1 : 0);
- sub->kids[2] = sub->kids[1];
- sub->counts[2] = sub->counts[1];
- sub->elems[1] = sub->elems[0];
- sub->kids[1] = sub->kids[0];
- sub->counts[1] = sub->counts[0];
- sub->elems[0] = n->elems[ki - 1];
- sub->kids[0] = sib->kids[lastelem + 1];
- sub->counts[0] = sib->counts[lastelem + 1];
- if (sub->kids[0])
- sub->kids[0]->parent = sub;
- n->elems[ki - 1] = sib->elems[lastelem];
- sib->kids[lastelem + 1] = NULL;
- sib->counts[lastelem + 1] = 0;
- sib->elems[lastelem] = NULL;
- n->counts[ki] = countnode234(sub);
- LOG((" case 3a left\n"));
- LOG(
- (" index and left subtree count before adjustment: %d, %d\n",
- index, n->counts[ki - 1]));
- index += n->counts[ki - 1];
- n->counts[ki - 1] = countnode234(sib);
- index -= n->counts[ki - 1];
- LOG(
- (" index and left subtree count after adjustment: %d, %d\n",
- index, n->counts[ki - 1]));
- } else if (ki < 3 && n->kids[ki + 1]
- && n->kids[ki + 1]->elems[1]) {
- /*
- * Case 3a, right-handed variant. ki has only
- * one element but ki+1 has two or more. Move a
- * subtree from ki+1 to ki.
- *
- * . B . . C .
- * / \ -> / \
- * a A b c C d D e [more] a A b B c d D e [more]
- */
- node234 *sib = n->kids[ki + 1];
- int j;
- sub->elems[1] = n->elems[ki];
- sub->kids[2] = sib->kids[0];
- sub->counts[2] = sib->counts[0];
- if (sub->kids[2])
- sub->kids[2]->parent = sub;
- n->elems[ki] = sib->elems[0];
- sib->kids[0] = sib->kids[1];
- sib->counts[0] = sib->counts[1];
- for (j = 0; j < 2 && sib->elems[j + 1]; j++) {
- sib->kids[j + 1] = sib->kids[j + 2];
- sib->counts[j + 1] = sib->counts[j + 2];
- sib->elems[j] = sib->elems[j + 1];
- }
- sib->kids[j + 1] = NULL;
- sib->counts[j + 1] = 0;
- sib->elems[j] = NULL;
- n->counts[ki] = countnode234(sub);
- n->counts[ki + 1] = countnode234(sib);
- LOG((" case 3a right\n"));
- } else {
- /*
- * Case 3b. ki has only one element, and has no
- * neighbour with more than one. So pick a
- * neighbour and merge it with ki, taking an
- * element down from n to go in the middle.
- *
- * . B . .
- * / \ -> |
- * a A b c C d a A b B c C d
- *
- * (Since at all points we have avoided
- * descending to a node with only one element,
- * we can be sure that n is not reduced to
- * nothingness by this move, _unless_ it was
- * the very first node, ie the root of the
- * tree. In that case we remove the now-empty
- * root and replace it with its single large
- * child as shown.)
- */
- node234 *sib;
- int j;
-
- if (ki > 0) {
- ki--;
- index += n->counts[ki] + 1;
- }
- sib = n->kids[ki];
- sub = n->kids[ki + 1];
-
- sub->kids[3] = sub->kids[1];
- sub->counts[3] = sub->counts[1];
- sub->elems[2] = sub->elems[0];
- sub->kids[2] = sub->kids[0];
- sub->counts[2] = sub->counts[0];
- sub->elems[1] = n->elems[ki];
- sub->kids[1] = sib->kids[1];
- sub->counts[1] = sib->counts[1];
- if (sub->kids[1])
- sub->kids[1]->parent = sub;
- sub->elems[0] = sib->elems[0];
- sub->kids[0] = sib->kids[0];
- sub->counts[0] = sib->counts[0];
- if (sub->kids[0])
- sub->kids[0]->parent = sub;
-
- n->counts[ki + 1] = countnode234(sub);
-
- sfree(sib);
-
- /*
- * That's built the big node in sub. Now we
- * need to remove the reference to sib in n.
- */
- for (j = ki; j < 3 && n->kids[j + 1]; j++) {
- n->kids[j] = n->kids[j + 1];
- n->counts[j] = n->counts[j + 1];
- n->elems[j] = j < 2 ? n->elems[j + 1] : NULL;
- }
- n->kids[j] = NULL;
- n->counts[j] = 0;
- if (j < 3)
- n->elems[j] = NULL;
- LOG((" case 3b ki=%d\n", ki));
-
- if (!n->elems[0]) {
- /*
- * The root is empty and needs to be
- * removed.
- */
- LOG((" shifting root!\n"));
- t->root = sub;
- sub->parent = NULL;
- sfree(n);
- }
- }
- }
- n = sub;
- }
- if (!retval)
- retval = n->elems[ei];
-
- if (ei == -1)
- return NULL; /* although this shouldn't happen */
-
- /*
- * Treat special case: this is the one remaining item in
- * the tree. n is the tree root (no parent), has one
- * element (no elems[1]), and has no kids (no kids[0]).
- */
- if (!n->parent && !n->elems[1] && !n->kids[0]) {
- LOG((" removed last element in tree\n"));
- sfree(n);
- t->root = NULL;
- return retval;
- }
-
- /*
- * Now we have the element we want, as n->elems[ei], and we
- * have also arranged for that element not to be the only
- * one in its node. So...
- */
-
- if (!n->kids[0] && n->elems[1]) {
- /*
- * Case 1. n is a leaf node with more than one element,
- * so it's _really easy_. Just delete the thing and
- * we're done.
- */
- int i;
- LOG((" case 1\n"));
- for (i = ei; i < 2 && n->elems[i + 1]; i++)
- n->elems[i] = n->elems[i + 1];
- n->elems[i] = NULL;
- /*
- * Having done that to the leaf node, we now go back up
- * the tree fixing the counts.
- */
- while (n->parent) {
- int childnum;
- childnum = (n->parent->kids[0] == n ? 0 :
- n->parent->kids[1] == n ? 1 :
- n->parent->kids[2] == n ? 2 : 3);
- n->parent->counts[childnum]--;
- n = n->parent;
- }
- return retval; /* finished! */
- } else if (n->kids[ei]->elems[1]) {
- /*
- * Case 2a. n is an internal node, and the root of the
- * subtree to the left of e has more than one element.
- * So find the predecessor p to e (ie the largest node
- * in that subtree), place it where e currently is, and
- * then start the deletion process over again on the
- * subtree with p as target.
- */
- node234 *m = n->kids[ei];
- void *target;
- LOG((" case 2a\n"));
- while (m->kids[0]) {
- m = (m->kids[3] ? m->kids[3] :
- m->kids[2] ? m->kids[2] :
- m->kids[1] ? m->kids[1] : m->kids[0]);
- }
- target = (m->elems[2] ? m->elems[2] :
- m->elems[1] ? m->elems[1] : m->elems[0]);
- n->elems[ei] = target;
- index = n->counts[ei] - 1;
- n = n->kids[ei];
- } else if (n->kids[ei + 1]->elems[1]) {
- /*
- * Case 2b, symmetric to 2a but s/left/right/ and
- * s/predecessor/successor/. (And s/largest/smallest/).
- */
- node234 *m = n->kids[ei + 1];
- void *target;
- LOG((" case 2b\n"));
- while (m->kids[0]) {
- m = m->kids[0];
- }
- target = m->elems[0];
- n->elems[ei] = target;
- n = n->kids[ei + 1];
- index = 0;
- } else {
- /*
- * Case 2c. n is an internal node, and the subtrees to
- * the left and right of e both have only one element.
- * So combine the two subnodes into a single big node
- * with their own elements on the left and right and e
- * in the middle, then restart the deletion process on
- * that subtree, with e still as target.
- */
- node234 *a = n->kids[ei], *b = n->kids[ei + 1];
- int j;
-
- LOG((" case 2c\n"));
- a->elems[1] = n->elems[ei];
- a->kids[2] = b->kids[0];
- a->counts[2] = b->counts[0];
- if (a->kids[2])
- a->kids[2]->parent = a;
- a->elems[2] = b->elems[0];
- a->kids[3] = b->kids[1];
- a->counts[3] = b->counts[1];
- if (a->kids[3])
- a->kids[3]->parent = a;
- sfree(b);
- n->counts[ei] = countnode234(a);
- /*
- * That's built the big node in a, and destroyed b. Now
- * remove the reference to b (and e) in n.
- */
- for (j = ei; j < 2 && n->elems[j + 1]; j++) {
- n->elems[j] = n->elems[j + 1];
- n->kids[j + 1] = n->kids[j + 2];
- n->counts[j + 1] = n->counts[j + 2];
- }
- n->elems[j] = NULL;
- n->kids[j + 1] = NULL;
- n->counts[j + 1] = 0;
- /*
- * It's possible, in this case, that we've just removed
- * the only element in the root of the tree. If so,
- * shift the root.
- */
- if (n->elems[0] == NULL) {
- LOG((" shifting root!\n"));
- t->root = a;
- a->parent = NULL;
- sfree(n);
- }
- /*
- * Now go round the deletion process again, with n
- * pointing at the new big node and e still the same.
- */
- n = a;
- index = a->counts[0] + a->counts[1] + 1;
- }
- }
-}
-void *delpos234(tree234 * t, int index)
-{
- if (index < 0 || index >= countnode234(t->root))
- return NULL;
- return delpos234_internal(t, index);
-}
-void *del234(tree234 * t, void *e)
-{
- int index;
- if (!findrelpos234(t, e, NULL, REL234_EQ, &index))
- return NULL; /* it wasn't in there anyway */
- return delpos234_internal(t, index); /* it's there; delete it. */
-}
-
-#ifdef TEST
-
-/*
- * Test code for the 2-3-4 tree. This code maintains an alternative
- * representation of the data in the tree, in an array (using the
- * obvious and slow insert and delete functions). After each tree
- * operation, the verify() function is called, which ensures all
- * the tree properties are preserved:
- * - node->child->parent always equals node
- * - tree->root->parent always equals NULL
- * - number of kids == 0 or number of elements + 1;
- * - tree has the same depth everywhere
- * - every node has at least one element
- * - subtree element counts are accurate
- * - any NULL kid pointer is accompanied by a zero count
- * - in a sorted tree: ordering property between elements of a
- * node and elements of its children is preserved
- * and also ensures the list represented by the tree is the same
- * list it should be. (This last check also doubly verifies the
- * ordering properties, because the `same list it should be' is by
- * definition correctly ordered. It also ensures all nodes are
- * distinct, because the enum functions would get caught in a loop
- * if not.)
- */
-
-#include <stdarg.h>
-
-/*
- * Error reporting function.
- */
-void error(char *fmt, ...)
-{
- va_list ap;
- printf("ERROR: ");
- va_start(ap, fmt);
- vfprintf(stdout, fmt, ap);
- va_end(ap);
- printf("\n");
-}
-
-/* The array representation of the data. */
-void **array;
-int arraylen, arraysize;
-cmpfn234 cmp;
-
-/* The tree representation of the same data. */
-tree234 *tree;
-
-typedef struct {
- int treedepth;
- int elemcount;
-} chkctx;
-
-int chknode(chkctx * ctx, int level, node234 * node,
- void *lowbound, void *highbound)
-{
- int nkids, nelems;
- int i;
- int count;
-
- /* Count the non-NULL kids. */
- for (nkids = 0; nkids < 4 && node->kids[nkids]; nkids++);
- /* Ensure no kids beyond the first NULL are non-NULL. */
- for (i = nkids; i < 4; i++)
- if (node->kids[i]) {
- error("node %p: nkids=%d but kids[%d] non-NULL",
- node, nkids, i);
- } else if (node->counts[i]) {
- error("node %p: kids[%d] NULL but count[%d]=%d nonzero",
- node, i, i, node->counts[i]);
- }
-
- /* Count the non-NULL elements. */
- for (nelems = 0; nelems < 3 && node->elems[nelems]; nelems++);
- /* Ensure no elements beyond the first NULL are non-NULL. */
- for (i = nelems; i < 3; i++)
- if (node->elems[i]) {
- error("node %p: nelems=%d but elems[%d] non-NULL",
- node, nelems, i);
- }
-
- if (nkids == 0) {
- /*
- * If nkids==0, this is a leaf node; verify that the tree
- * depth is the same everywhere.
- */
- if (ctx->treedepth < 0)
- ctx->treedepth = level; /* we didn't know the depth yet */
- else if (ctx->treedepth != level)
- error("node %p: leaf at depth %d, previously seen depth %d",
- node, level, ctx->treedepth);
- } else {
- /*
- * If nkids != 0, then it should be nelems+1, unless nelems
- * is 0 in which case nkids should also be 0 (and so we
- * shouldn't be in this condition at all).
- */
- int shouldkids = (nelems ? nelems + 1 : 0);
- if (nkids != shouldkids) {
- error("node %p: %d elems should mean %d kids but has %d",
- node, nelems, shouldkids, nkids);
- }
- }
-
- /*
- * nelems should be at least 1.
- */
- if (nelems == 0) {
- error("node %p: no elems", node, nkids);
- }
-
- /*
- * Add nelems to the running element count of the whole tree.
- */
- ctx->elemcount += nelems;
-
- /*
- * Check ordering property: all elements should be strictly >
- * lowbound, strictly < highbound, and strictly < each other in
- * sequence. (lowbound and highbound are NULL at edges of tree
- * - both NULL at root node - and NULL is considered to be <
- * everything and > everything. IYSWIM.)
- */
- if (cmp) {
- for (i = -1; i < nelems; i++) {
- void *lower = (i == -1 ? lowbound : node->elems[i]);
- void *higher =
- (i + 1 == nelems ? highbound : node->elems[i + 1]);
- if (lower && higher && cmp(lower, higher) >= 0) {
- error("node %p: kid comparison [%d=%s,%d=%s] failed",
- node, i, lower, i + 1, higher);
- }
- }
- }
-
- /*
- * Check parent pointers: all non-NULL kids should have a
- * parent pointer coming back to this node.
- */
- for (i = 0; i < nkids; i++)
- if (node->kids[i]->parent != node) {
- error("node %p kid %d: parent ptr is %p not %p",
- node, i, node->kids[i]->parent, node);
- }
-
-
- /*
- * Now (finally!) recurse into subtrees.
- */
- count = nelems;
-
- for (i = 0; i < nkids; i++) {
- void *lower = (i == 0 ? lowbound : node->elems[i - 1]);
- void *higher = (i >= nelems ? highbound : node->elems[i]);
- int subcount =
- chknode(ctx, level + 1, node->kids[i], lower, higher);
- if (node->counts[i] != subcount) {
- error("node %p kid %d: count says %d, subtree really has %d",
- node, i, node->counts[i], subcount);
- }
- count += subcount;
- }
-
- return count;
-}
-
-void verify(void)
-{
- chkctx ctx;
- int i;
- void *p;
-
- ctx.treedepth = -1; /* depth unknown yet */
- ctx.elemcount = 0; /* no elements seen yet */
- /*
- * Verify validity of tree properties.
- */
- if (tree->root) {
- if (tree->root->parent != NULL)
- error("root->parent is %p should be null", tree->root->parent);
- chknode(&ctx, 0, tree->root, NULL, NULL);
- }
- printf("tree depth: %d\n", ctx.treedepth);
- /*
- * Enumerate the tree and ensure it matches up to the array.
- */
- for (i = 0; NULL != (p = index234(tree, i)); i++) {
- if (i >= arraylen)
- error("tree contains more than %d elements", arraylen);
- if (array[i] != p)
- error("enum at position %d: array says %s, tree says %s",
- i, array[i], p);
- }
- if (ctx.elemcount != i) {
- error("tree really contains %d elements, enum gave %d",
- ctx.elemcount, i);
- }
- if (i < arraylen) {
- error("enum gave only %d elements, array has %d", i, arraylen);
- }
- i = count234(tree);
- if (ctx.elemcount != i) {
- error("tree really contains %d elements, count234 gave %d",
- ctx.elemcount, i);
- }
-}
-
-void internal_addtest(void *elem, int index, void *realret)
-{
- int i, j;
- void *retval;
-
- if (arraysize < arraylen + 1) {
- arraysize = arraylen + 1 + 256;
- array = sresize(array, arraysize, void *);
- }
-
- i = index;
- /* now i points to the first element >= elem */
- retval = elem; /* expect elem returned (success) */
- for (j = arraylen; j > i; j--)
- array[j] = array[j - 1];
- array[i] = elem; /* add elem to array */
- arraylen++;
-
- if (realret != retval) {
- error("add: retval was %p expected %p", realret, retval);
- }
-
- verify();
-}
-
-void addtest(void *elem)
-{
- int i;
- void *realret;
-
- realret = add234(tree, elem);
-
- i = 0;
- while (i < arraylen && cmp(elem, array[i]) > 0)
- i++;
- if (i < arraylen && !cmp(elem, array[i])) {
- void *retval = array[i]; /* expect that returned not elem */
- if (realret != retval) {
- error("add: retval was %p expected %p", realret, retval);
- }
- } else
- internal_addtest(elem, i, realret);
-}
-
-void addpostest(void *elem, int i)
-{
- void *realret;
-
- realret = addpos234(tree, elem, i);
-
- internal_addtest(elem, i, realret);
-}
-
-void delpostest(int i)
-{
- int index = i;
- void *elem = array[i], *ret;
-
- /* i points to the right element */
- while (i < arraylen - 1) {
- array[i] = array[i + 1];
- i++;
- }
- arraylen--; /* delete elem from array */
-
- if (tree->cmp)
- ret = del234(tree, elem);
- else
- ret = delpos234(tree, index);
-
- if (ret != elem) {
- error("del returned %p, expected %p", ret, elem);
- }
-
- verify();
-}
-
-void deltest(void *elem)
-{
- int i;
-
- i = 0;
- while (i < arraylen && cmp(elem, array[i]) > 0)
- i++;
- if (i >= arraylen || cmp(elem, array[i]) != 0)
- return; /* don't do it! */
- delpostest(i);
-}
-
-/* A sample data set and test utility. Designed for pseudo-randomness,
- * and yet repeatability. */
-
-/*
- * This random number generator uses the `portable implementation'
- * given in ANSI C99 draft N869. It assumes `unsigned' is 32 bits;
- * change it if not.
- */
-int randomnumber(unsigned *seed)
-{
- *seed *= 1103515245;
- *seed += 12345;
- return ((*seed) / 65536) % 32768;
-}
-
-int mycmp(void *av, void *bv)
-{
- char const *a = (char const *) av;
- char const *b = (char const *) bv;
- return strcmp(a, b);
-}
-
-#define lenof(x) ( sizeof((x)) / sizeof(*(x)) )
-
-char *strings[] = {
- "a", "ab", "absque", "coram", "de",
- "palam", "clam", "cum", "ex", "e",
- "sine", "tenus", "pro", "prae",
- "banana", "carrot", "cabbage", "broccoli", "onion", "zebra",
- "penguin", "blancmange", "pangolin", "whale", "hedgehog",
- "giraffe", "peanut", "bungee", "foo", "bar", "baz", "quux",
- "murfl", "spoo", "breen", "flarn", "octothorpe",
- "snail", "tiger", "elephant", "octopus", "warthog", "armadillo",
- "aardvark", "wyvern", "dragon", "elf", "dwarf", "orc", "goblin",
- "pixie", "basilisk", "warg", "ape", "lizard", "newt", "shopkeeper",
- "wand", "ring", "amulet"
-};
-
-#define NSTR lenof(strings)
-
-int findtest(void)
-{
- const static int rels[] = {
- REL234_EQ, REL234_GE, REL234_LE, REL234_LT, REL234_GT
- };
- const static char *const relnames[] = {
- "EQ", "GE", "LE", "LT", "GT"
- };
- int i, j, rel, index;
- char *p, *ret, *realret, *realret2;
- int lo, hi, mid, c;
-
- for (i = 0; i < NSTR; i++) {
- p = strings[i];
- for (j = 0; j < sizeof(rels) / sizeof(*rels); j++) {
- rel = rels[j];
-
- lo = 0;
- hi = arraylen - 1;
- while (lo <= hi) {
- mid = (lo + hi) / 2;
- c = strcmp(p, array[mid]);
- if (c < 0)
- hi = mid - 1;
- else if (c > 0)
- lo = mid + 1;
- else
- break;
- }
-
- if (c == 0) {
- if (rel == REL234_LT)
- ret = (mid > 0 ? array[--mid] : NULL);
- else if (rel == REL234_GT)
- ret = (mid < arraylen - 1 ? array[++mid] : NULL);
- else
- ret = array[mid];
- } else {
- assert(lo == hi + 1);
- if (rel == REL234_LT || rel == REL234_LE) {
- mid = hi;
- ret = (hi >= 0 ? array[hi] : NULL);
- } else if (rel == REL234_GT || rel == REL234_GE) {
- mid = lo;
- ret = (lo < arraylen ? array[lo] : NULL);
- } else
- ret = NULL;
- }
-
- realret = findrelpos234(tree, p, NULL, rel, &index);
- if (realret != ret) {
- error("find(\"%s\",%s) gave %s should be %s",
- p, relnames[j], realret, ret);
- }
- if (realret && index != mid) {
- error("find(\"%s\",%s) gave %d should be %d",
- p, relnames[j], index, mid);
- }
- if (realret && rel == REL234_EQ) {
- realret2 = index234(tree, index);
- if (realret2 != realret) {
- error("find(\"%s\",%s) gave %s(%d) but %d -> %s",
- p, relnames[j], realret, index, index, realret2);
- }
- }
-#if 0
- printf("find(\"%s\",%s) gave %s(%d)\n", p, relnames[j],
- realret, index);
-#endif
- }
- }
-
- realret = findrelpos234(tree, NULL, NULL, REL234_GT, &index);
- if (arraylen && (realret != array[0] || index != 0)) {
- error("find(NULL,GT) gave %s(%d) should be %s(0)",
- realret, index, array[0]);
- } else if (!arraylen && (realret != NULL)) {
- error("find(NULL,GT) gave %s(%d) should be NULL", realret, index);
- }
-
- realret = findrelpos234(tree, NULL, NULL, REL234_LT, &index);
- if (arraylen
- && (realret != array[arraylen - 1] || index != arraylen - 1)) {
- error("find(NULL,LT) gave %s(%d) should be %s(0)", realret, index,
- array[arraylen - 1]);
- } else if (!arraylen && (realret != NULL)) {
- error("find(NULL,LT) gave %s(%d) should be NULL", realret, index);
- }
-}
-
-int main(void)
-{
- int in[NSTR];
- int i, j, k;
- unsigned seed = 0;
-
- for (i = 0; i < NSTR; i++)
- in[i] = 0;
- array = NULL;
- arraylen = arraysize = 0;
- tree = newtree234(mycmp);
- cmp = mycmp;
-
- verify();
- for (i = 0; i < 10000; i++) {
- j = randomnumber(&seed);
- j %= NSTR;
- printf("trial: %d\n", i);
- if (in[j]) {
- printf("deleting %s (%d)\n", strings[j], j);
- deltest(strings[j]);
- in[j] = 0;
- } else {
- printf("adding %s (%d)\n", strings[j], j);
- addtest(strings[j]);
- in[j] = 1;
- }
- findtest();
- }
-
- while (arraylen > 0) {
- j = randomnumber(&seed);
- j %= arraylen;
- deltest(array[j]);
- }
-
- freetree234(tree);
-
- /*
- * Now try an unsorted tree. We don't really need to test
- * delpos234 because we know del234 is based on it, so it's
- * already been tested in the above sorted-tree code; but for
- * completeness we'll use it to tear down our unsorted tree
- * once we've built it.
- */
- tree = newtree234(NULL);
- cmp = NULL;
- verify();
- for (i = 0; i < 1000; i++) {
- printf("trial: %d\n", i);
- j = randomnumber(&seed);
- j %= NSTR;
- k = randomnumber(&seed);
- k %= count234(tree) + 1;
- printf("adding string %s at index %d\n", strings[j], k);
- addpostest(strings[j], k);
- }
- while (count234(tree) > 0) {
- printf("cleanup: tree size %d\n", count234(tree));
- j = randomnumber(&seed);
- j %= count234(tree);
- printf("deleting string %s from index %d\n", array[j], j);
- delpostest(j);
- }
-
- return 0;
-}
-
-#endif
+/*
+ * tree234.c: reasonably generic counted 2-3-4 tree routines.
+ *
+ * This file is copyright 1999-2001 Simon Tatham.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "tree234.h"
+
+#ifdef TEST
+#define LOG(x) (printf x)
+#define snew(type) ((type *)malloc(sizeof(type)))
+#define snewn(n, type) ((type *)malloc((n) * sizeof(type)))
+#define sresize(ptr, n, type) \
+ ((type *)realloc(sizeof((type *)0 == (ptr)) ? (ptr) : (ptr), \
+ (n) * sizeof(type)))
+#define sfree(ptr) free(ptr)
+#else
+#include "puttymem.h"
+#define LOG(x)
+#endif
+
+typedef struct node234_Tag node234;
+
+struct tree234_Tag {
+ node234 *root;
+ cmpfn234 cmp;
+};
+
+struct node234_Tag {
+ node234 *parent;
+ node234 *kids[4];
+ int counts[4];
+ void *elems[3];
+};
+
+/*
+ * Create a 2-3-4 tree.
+ */
+tree234 *newtree234(cmpfn234 cmp)
+{
+ tree234 *ret = snew(tree234);
+ LOG(("created tree %p\n", ret));
+ ret->root = NULL;
+ ret->cmp = cmp;
+ return ret;
+}
+
+/*
+ * Free a 2-3-4 tree (not including freeing the elements).
+ */
+static void freenode234(node234 * n)
+{
+ if (!n)
+ return;
+ freenode234(n->kids[0]);
+ freenode234(n->kids[1]);
+ freenode234(n->kids[2]);
+ freenode234(n->kids[3]);
+ sfree(n);
+}
+
+void freetree234(tree234 * t)
+{
+ freenode234(t->root);
+ sfree(t);
+}
+
+/*
+ * Internal function to count a node.
+ */
+static int countnode234(node234 * n)
+{
+ int count = 0;
+ int i;
+ if (!n)
+ return 0;
+ for (i = 0; i < 4; i++)
+ count += n->counts[i];
+ for (i = 0; i < 3; i++)
+ if (n->elems[i])
+ count++;
+ return count;
+}
+
+/*
+ * Count the elements in a tree.
+ */
+int count234(tree234 * t)
+{
+ if (t->root)
+ return countnode234(t->root);
+ else
+ return 0;
+}
+
+/*
+ * Add an element e to a 2-3-4 tree t. Returns e on success, or if
+ * an existing element compares equal, returns that.
+ */
+static void *add234_internal(tree234 * t, void *e, int index)
+{
+ node234 *n, **np, *left, *right;
+ void *orig_e = e;
+ int c, lcount, rcount;
+
+ LOG(("adding node %p to tree %p\n", e, t));
+ if (t->root == NULL) {
+ t->root = snew(node234);
+ t->root->elems[1] = t->root->elems[2] = NULL;
+ t->root->kids[0] = t->root->kids[1] = NULL;
+ t->root->kids[2] = t->root->kids[3] = NULL;
+ t->root->counts[0] = t->root->counts[1] = 0;
+ t->root->counts[2] = t->root->counts[3] = 0;
+ t->root->parent = NULL;
+ t->root->elems[0] = e;
+ LOG((" created root %p\n", t->root));
+ return orig_e;
+ }
+
+ n = NULL; /* placate gcc; will always be set below since t->root != NULL */
+ np = &t->root;
+ while (*np) {
+ int childnum;
+ n = *np;
+ LOG((" node %p: %p/%d [%p] %p/%d [%p] %p/%d [%p] %p/%d\n",
+ n,
+ n->kids[0], n->counts[0], n->elems[0],
+ n->kids[1], n->counts[1], n->elems[1],
+ n->kids[2], n->counts[2], n->elems[2],
+ n->kids[3], n->counts[3]));
+ if (index >= 0) {
+ if (!n->kids[0]) {
+ /*
+ * Leaf node. We want to insert at kid position
+ * equal to the index:
+ *
+ * 0 A 1 B 2 C 3
+ */
+ childnum = index;
+ } else {
+ /*
+ * Internal node. We always descend through it (add
+ * always starts at the bottom, never in the
+ * middle).
+ */
+ do { /* this is a do ... while (0) to allow `break' */
+ if (index <= n->counts[0]) {
+ childnum = 0;
+ break;
+ }
+ index -= n->counts[0] + 1;
+ if (index <= n->counts[1]) {
+ childnum = 1;
+ break;
+ }
+ index -= n->counts[1] + 1;
+ if (index <= n->counts[2]) {
+ childnum = 2;
+ break;
+ }
+ index -= n->counts[2] + 1;
+ if (index <= n->counts[3]) {
+ childnum = 3;
+ break;
+ }
+ return NULL; /* error: index out of range */
+ } while (0);
+ }
+ } else {
+ if ((c = t->cmp(e, n->elems[0])) < 0)
+ childnum = 0;
+ else if (c == 0)
+ return n->elems[0]; /* already exists */
+ else if (n->elems[1] == NULL
+ || (c = t->cmp(e, n->elems[1])) < 0) childnum = 1;
+ else if (c == 0)
+ return n->elems[1]; /* already exists */
+ else if (n->elems[2] == NULL
+ || (c = t->cmp(e, n->elems[2])) < 0) childnum = 2;
+ else if (c == 0)
+ return n->elems[2]; /* already exists */
+ else
+ childnum = 3;
+ }
+ np = &n->kids[childnum];
+ LOG((" moving to child %d (%p)\n", childnum, *np));
+ }
+
+ /*
+ * We need to insert the new element in n at position np.
+ */
+ left = NULL;
+ lcount = 0;
+ right = NULL;
+ rcount = 0;
+ while (n) {
+ LOG((" at %p: %p/%d [%p] %p/%d [%p] %p/%d [%p] %p/%d\n",
+ n,
+ n->kids[0], n->counts[0], n->elems[0],
+ n->kids[1], n->counts[1], n->elems[1],
+ n->kids[2], n->counts[2], n->elems[2],
+ n->kids[3], n->counts[3]));
+ LOG((" need to insert %p/%d [%p] %p/%d at position %d\n",
+ left, lcount, e, right, rcount, (int)(np - n->kids)));
+ if (n->elems[1] == NULL) {
+ /*
+ * Insert in a 2-node; simple.
+ */
+ if (np == &n->kids[0]) {
+ LOG((" inserting on left of 2-node\n"));
+ n->kids[2] = n->kids[1];
+ n->counts[2] = n->counts[1];
+ n->elems[1] = n->elems[0];
+ n->kids[1] = right;
+ n->counts[1] = rcount;
+ n->elems[0] = e;
+ n->kids[0] = left;
+ n->counts[0] = lcount;
+ } else { /* np == &n->kids[1] */
+ LOG((" inserting on right of 2-node\n"));
+ n->kids[2] = right;
+ n->counts[2] = rcount;
+ n->elems[1] = e;
+ n->kids[1] = left;
+ n->counts[1] = lcount;
+ }
+ if (n->kids[0])
+ n->kids[0]->parent = n;
+ if (n->kids[1])
+ n->kids[1]->parent = n;
+ if (n->kids[2])
+ n->kids[2]->parent = n;
+ LOG((" done\n"));
+ break;
+ } else if (n->elems[2] == NULL) {
+ /*
+ * Insert in a 3-node; simple.
+ */
+ if (np == &n->kids[0]) {
+ LOG((" inserting on left of 3-node\n"));
+ n->kids[3] = n->kids[2];
+ n->counts[3] = n->counts[2];
+ n->elems[2] = n->elems[1];
+ n->kids[2] = n->kids[1];
+ n->counts[2] = n->counts[1];
+ n->elems[1] = n->elems[0];
+ n->kids[1] = right;
+ n->counts[1] = rcount;
+ n->elems[0] = e;
+ n->kids[0] = left;
+ n->counts[0] = lcount;
+ } else if (np == &n->kids[1]) {
+ LOG((" inserting in middle of 3-node\n"));
+ n->kids[3] = n->kids[2];
+ n->counts[3] = n->counts[2];
+ n->elems[2] = n->elems[1];
+ n->kids[2] = right;
+ n->counts[2] = rcount;
+ n->elems[1] = e;
+ n->kids[1] = left;
+ n->counts[1] = lcount;
+ } else { /* np == &n->kids[2] */
+ LOG((" inserting on right of 3-node\n"));
+ n->kids[3] = right;
+ n->counts[3] = rcount;
+ n->elems[2] = e;
+ n->kids[2] = left;
+ n->counts[2] = lcount;
+ }
+ if (n->kids[0])
+ n->kids[0]->parent = n;
+ if (n->kids[1])
+ n->kids[1]->parent = n;
+ if (n->kids[2])
+ n->kids[2]->parent = n;
+ if (n->kids[3])
+ n->kids[3]->parent = n;
+ LOG((" done\n"));
+ break;
+ } else {
+ node234 *m = snew(node234);
+ m->parent = n->parent;
+ LOG((" splitting a 4-node; created new node %p\n", m));
+ /*
+ * Insert in a 4-node; split into a 2-node and a
+ * 3-node, and move focus up a level.
+ *
+ * I don't think it matters which way round we put the
+ * 2 and the 3. For simplicity, we'll put the 3 first
+ * always.
+ */
+ if (np == &n->kids[0]) {
+ m->kids[0] = left;
+ m->counts[0] = lcount;
+ m->elems[0] = e;
+ m->kids[1] = right;
+ m->counts[1] = rcount;
+ m->elems[1] = n->elems[0];
+ m->kids[2] = n->kids[1];
+ m->counts[2] = n->counts[1];
+ e = n->elems[1];
+ n->kids[0] = n->kids[2];
+ n->counts[0] = n->counts[2];
+ n->elems[0] = n->elems[2];
+ n->kids[1] = n->kids[3];
+ n->counts[1] = n->counts[3];
+ } else if (np == &n->kids[1]) {
+ m->kids[0] = n->kids[0];
+ m->counts[0] = n->counts[0];
+ m->elems[0] = n->elems[0];
+ m->kids[1] = left;
+ m->counts[1] = lcount;
+ m->elems[1] = e;
+ m->kids[2] = right;
+ m->counts[2] = rcount;
+ e = n->elems[1];
+ n->kids[0] = n->kids[2];
+ n->counts[0] = n->counts[2];
+ n->elems[0] = n->elems[2];
+ n->kids[1] = n->kids[3];
+ n->counts[1] = n->counts[3];
+ } else if (np == &n->kids[2]) {
+ m->kids[0] = n->kids[0];
+ m->counts[0] = n->counts[0];
+ m->elems[0] = n->elems[0];
+ m->kids[1] = n->kids[1];
+ m->counts[1] = n->counts[1];
+ m->elems[1] = n->elems[1];
+ m->kids[2] = left;
+ m->counts[2] = lcount;
+ /* e = e; */
+ n->kids[0] = right;
+ n->counts[0] = rcount;
+ n->elems[0] = n->elems[2];
+ n->kids[1] = n->kids[3];
+ n->counts[1] = n->counts[3];
+ } else { /* np == &n->kids[3] */
+ m->kids[0] = n->kids[0];
+ m->counts[0] = n->counts[0];
+ m->elems[0] = n->elems[0];
+ m->kids[1] = n->kids[1];
+ m->counts[1] = n->counts[1];
+ m->elems[1] = n->elems[1];
+ m->kids[2] = n->kids[2];
+ m->counts[2] = n->counts[2];
+ n->kids[0] = left;
+ n->counts[0] = lcount;
+ n->elems[0] = e;
+ n->kids[1] = right;
+ n->counts[1] = rcount;
+ e = n->elems[2];
+ }
+ m->kids[3] = n->kids[3] = n->kids[2] = NULL;
+ m->counts[3] = n->counts[3] = n->counts[2] = 0;
+ m->elems[2] = n->elems[2] = n->elems[1] = NULL;
+ if (m->kids[0])
+ m->kids[0]->parent = m;
+ if (m->kids[1])
+ m->kids[1]->parent = m;
+ if (m->kids[2])
+ m->kids[2]->parent = m;
+ if (n->kids[0])
+ n->kids[0]->parent = n;
+ if (n->kids[1])
+ n->kids[1]->parent = n;
+ LOG((" left (%p): %p/%d [%p] %p/%d [%p] %p/%d\n", m,
+ m->kids[0], m->counts[0], m->elems[0],
+ m->kids[1], m->counts[1], m->elems[1],
+ m->kids[2], m->counts[2]));
+ LOG((" right (%p): %p/%d [%p] %p/%d\n", n,
+ n->kids[0], n->counts[0], n->elems[0],
+ n->kids[1], n->counts[1]));
+ left = m;
+ lcount = countnode234(left);
+ right = n;
+ rcount = countnode234(right);
+ }
+ if (n->parent)
+ np = (n->parent->kids[0] == n ? &n->parent->kids[0] :
+ n->parent->kids[1] == n ? &n->parent->kids[1] :
+ n->parent->kids[2] == n ? &n->parent->kids[2] :
+ &n->parent->kids[3]);
+ n = n->parent;
+ }
+
+ /*
+ * If we've come out of here by `break', n will still be
+ * non-NULL and all we need to do is go back up the tree
+ * updating counts. If we've come here because n is NULL, we
+ * need to create a new root for the tree because the old one
+ * has just split into two. */
+ if (n) {
+ while (n->parent) {
+ int count = countnode234(n);
+ int childnum;
+ childnum = (n->parent->kids[0] == n ? 0 :
+ n->parent->kids[1] == n ? 1 :
+ n->parent->kids[2] == n ? 2 : 3);
+ n->parent->counts[childnum] = count;
+ n = n->parent;
+ }
+ } else {
+ LOG((" root is overloaded, split into two\n"));
+ t->root = snew(node234);
+ t->root->kids[0] = left;
+ t->root->counts[0] = lcount;
+ t->root->elems[0] = e;
+ t->root->kids[1] = right;
+ t->root->counts[1] = rcount;
+ t->root->elems[1] = NULL;
+ t->root->kids[2] = NULL;
+ t->root->counts[2] = 0;
+ t->root->elems[2] = NULL;
+ t->root->kids[3] = NULL;
+ t->root->counts[3] = 0;
+ t->root->parent = NULL;
+ if (t->root->kids[0])
+ t->root->kids[0]->parent = t->root;
+ if (t->root->kids[1])
+ t->root->kids[1]->parent = t->root;
+ LOG((" new root is %p/%d [%p] %p/%d\n",
+ t->root->kids[0], t->root->counts[0],
+ t->root->elems[0], t->root->kids[1], t->root->counts[1]));
+ }
+
+ return orig_e;
+}
+
+void *add234(tree234 * t, void *e)
+{
+ if (!t->cmp) /* tree is unsorted */
+ return NULL;
+
+ return add234_internal(t, e, -1);
+}
+void *addpos234(tree234 * t, void *e, int index)
+{
+ if (index < 0 || /* index out of range */
+ t->cmp) /* tree is sorted */
+ return NULL; /* return failure */
+
+ return add234_internal(t, e, index); /* this checks the upper bound */
+}
+
+/*
+ * Look up the element at a given numeric index in a 2-3-4 tree.
+ * Returns NULL if the index is out of range.
+ */
+void *index234(tree234 * t, int index)
+{
+ node234 *n;
+
+ if (!t->root)
+ return NULL; /* tree is empty */
+
+ if (index < 0 || index >= countnode234(t->root))
+ return NULL; /* out of range */
+
+ n = t->root;
+
+ while (n) {
+ if (index < n->counts[0])
+ n = n->kids[0];
+ else if (index -= n->counts[0] + 1, index < 0)
+ return n->elems[0];
+ else if (index < n->counts[1])
+ n = n->kids[1];
+ else if (index -= n->counts[1] + 1, index < 0)
+ return n->elems[1];
+ else if (index < n->counts[2])
+ n = n->kids[2];
+ else if (index -= n->counts[2] + 1, index < 0)
+ return n->elems[2];
+ else
+ n = n->kids[3];
+ }
+
+ /* We shouldn't ever get here. I wonder how we did. */
+ return NULL;
+}
+
+/*
+ * Find an element e in a sorted 2-3-4 tree t. Returns NULL if not
+ * found. e is always passed as the first argument to cmp, so cmp
+ * can be an asymmetric function if desired. cmp can also be passed
+ * as NULL, in which case the compare function from the tree proper
+ * will be used.
+ */
+void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp,
+ int relation, int *index)
+{
+ node234 *n;
+ void *ret;
+ int c;
+ int idx, ecount, kcount, cmpret;
+
+ if (t->root == NULL)
+ return NULL;
+
+ if (cmp == NULL)
+ cmp = t->cmp;
+
+ n = t->root;
+ /*
+ * Attempt to find the element itself.
+ */
+ idx = 0;
+ ecount = -1;
+ /*
+ * Prepare a fake `cmp' result if e is NULL.
+ */
+ cmpret = 0;
+ if (e == NULL) {
+ assert(relation == REL234_LT || relation == REL234_GT);
+ if (relation == REL234_LT)
+ cmpret = +1; /* e is a max: always greater */
+ else if (relation == REL234_GT)
+ cmpret = -1; /* e is a min: always smaller */
+ }
+ while (1) {
+ for (kcount = 0; kcount < 4; kcount++) {
+ if (kcount >= 3 || n->elems[kcount] == NULL ||
+ (c = cmpret ? cmpret : cmp(e, n->elems[kcount])) < 0) {
+ break;
+ }
+ if (n->kids[kcount])
+ idx += n->counts[kcount];
+ if (c == 0) {
+ ecount = kcount;
+ break;
+ }
+ idx++;
+ }
+ if (ecount >= 0)
+ break;
+ if (n->kids[kcount])
+ n = n->kids[kcount];
+ else
+ break;
+ }
+
+ if (ecount >= 0) {
+ /*
+ * We have found the element we're looking for. It's
+ * n->elems[ecount], at tree index idx. If our search
+ * relation is EQ, LE or GE we can now go home.
+ */
+ if (relation != REL234_LT && relation != REL234_GT) {
+ if (index)
+ *index = idx;
+ return n->elems[ecount];
+ }
+
+ /*
+ * Otherwise, we'll do an indexed lookup for the previous
+ * or next element. (It would be perfectly possible to
+ * implement these search types in a non-counted tree by
+ * going back up from where we are, but far more fiddly.)
+ */
+ if (relation == REL234_LT)
+ idx--;
+ else
+ idx++;
+ } else {
+ /*
+ * We've found our way to the bottom of the tree and we
+ * know where we would insert this node if we wanted to:
+ * we'd put it in in place of the (empty) subtree
+ * n->kids[kcount], and it would have index idx
+ *
+ * But the actual element isn't there. So if our search
+ * relation is EQ, we're doomed.
+ */
+ if (relation == REL234_EQ)
+ return NULL;
+
+ /*
+ * Otherwise, we must do an index lookup for index idx-1
+ * (if we're going left - LE or LT) or index idx (if we're
+ * going right - GE or GT).
+ */
+ if (relation == REL234_LT || relation == REL234_LE) {
+ idx--;
+ }
+ }
+
+ /*
+ * We know the index of the element we want; just call index234
+ * to do the rest. This will return NULL if the index is out of
+ * bounds, which is exactly what we want.
+ */
+ ret = index234(t, idx);
+ if (ret && index)
+ *index = idx;
+ return ret;
+}
+void *find234(tree234 * t, void *e, cmpfn234 cmp)
+{
+ return findrelpos234(t, e, cmp, REL234_EQ, NULL);
+}
+void *findrel234(tree234 * t, void *e, cmpfn234 cmp, int relation)
+{
+ return findrelpos234(t, e, cmp, relation, NULL);
+}
+void *findpos234(tree234 * t, void *e, cmpfn234 cmp, int *index)
+{
+ return findrelpos234(t, e, cmp, REL234_EQ, index);
+}
+
+/*
+ * Delete an element e in a 2-3-4 tree. Does not free the element,
+ * merely removes all links to it from the tree nodes.
+ */
+static void *delpos234_internal(tree234 * t, int index)
+{
+ node234 *n;
+ void *retval;
+ int ei = -1;
+
+ retval = 0;
+
+ n = t->root;
+ LOG(("deleting item %d from tree %p\n", index, t));
+ while (1) {
+ while (n) {
+ int ki;
+ node234 *sub;
+
+ LOG(
+ (" node %p: %p/%d [%p] %p/%d [%p] %p/%d [%p] %p/%d index=%d\n",
+ n, n->kids[0], n->counts[0], n->elems[0], n->kids[1],
+ n->counts[1], n->elems[1], n->kids[2], n->counts[2],
+ n->elems[2], n->kids[3], n->counts[3], index));
+ if (index < n->counts[0]) {
+ ki = 0;
+ } else if (index -= n->counts[0] + 1, index < 0) {
+ ei = 0;
+ break;
+ } else if (index < n->counts[1]) {
+ ki = 1;
+ } else if (index -= n->counts[1] + 1, index < 0) {
+ ei = 1;
+ break;
+ } else if (index < n->counts[2]) {
+ ki = 2;
+ } else if (index -= n->counts[2] + 1, index < 0) {
+ ei = 2;
+ break;
+ } else {
+ ki = 3;
+ }
+ /*
+ * Recurse down to subtree ki. If it has only one element,
+ * we have to do some transformation to start with.
+ */
+ LOG((" moving to subtree %d\n", ki));
+ sub = n->kids[ki];
+ if (!sub->elems[1]) {
+ LOG((" subtree has only one element!\n", ki));
+ if (ki > 0 && n->kids[ki - 1]->elems[1]) {
+ /*
+ * Case 3a, left-handed variant. Child ki has
+ * only one element, but child ki-1 has two or
+ * more. So we need to move a subtree from ki-1
+ * to ki.
+ *
+ * . C . . B .
+ * / \ -> / \
+ * [more] a A b B c d D e [more] a A b c C d D e
+ */
+ node234 *sib = n->kids[ki - 1];
+ int lastelem = (sib->elems[2] ? 2 :
+ sib->elems[1] ? 1 : 0);
+ sub->kids[2] = sub->kids[1];
+ sub->counts[2] = sub->counts[1];
+ sub->elems[1] = sub->elems[0];
+ sub->kids[1] = sub->kids[0];
+ sub->counts[1] = sub->counts[0];
+ sub->elems[0] = n->elems[ki - 1];
+ sub->kids[0] = sib->kids[lastelem + 1];
+ sub->counts[0] = sib->counts[lastelem + 1];
+ if (sub->kids[0])
+ sub->kids[0]->parent = sub;
+ n->elems[ki - 1] = sib->elems[lastelem];
+ sib->kids[lastelem + 1] = NULL;
+ sib->counts[lastelem + 1] = 0;
+ sib->elems[lastelem] = NULL;
+ n->counts[ki] = countnode234(sub);
+ LOG((" case 3a left\n"));
+ LOG(
+ (" index and left subtree count before adjustment: %d, %d\n",
+ index, n->counts[ki - 1]));
+ index += n->counts[ki - 1];
+ n->counts[ki - 1] = countnode234(sib);
+ index -= n->counts[ki - 1];
+ LOG(
+ (" index and left subtree count after adjustment: %d, %d\n",
+ index, n->counts[ki - 1]));
+ } else if (ki < 3 && n->kids[ki + 1]
+ && n->kids[ki + 1]->elems[1]) {
+ /*
+ * Case 3a, right-handed variant. ki has only
+ * one element but ki+1 has two or more. Move a
+ * subtree from ki+1 to ki.
+ *
+ * . B . . C .
+ * / \ -> / \
+ * a A b c C d D e [more] a A b B c d D e [more]
+ */
+ node234 *sib = n->kids[ki + 1];
+ int j;
+ sub->elems[1] = n->elems[ki];
+ sub->kids[2] = sib->kids[0];
+ sub->counts[2] = sib->counts[0];
+ if (sub->kids[2])
+ sub->kids[2]->parent = sub;
+ n->elems[ki] = sib->elems[0];
+ sib->kids[0] = sib->kids[1];
+ sib->counts[0] = sib->counts[1];
+ for (j = 0; j < 2 && sib->elems[j + 1]; j++) {
+ sib->kids[j + 1] = sib->kids[j + 2];
+ sib->counts[j + 1] = sib->counts[j + 2];
+ sib->elems[j] = sib->elems[j + 1];
+ }
+ sib->kids[j + 1] = NULL;
+ sib->counts[j + 1] = 0;
+ sib->elems[j] = NULL;
+ n->counts[ki] = countnode234(sub);
+ n->counts[ki + 1] = countnode234(sib);
+ LOG((" case 3a right\n"));
+ } else {
+ /*
+ * Case 3b. ki has only one element, and has no
+ * neighbour with more than one. So pick a
+ * neighbour and merge it with ki, taking an
+ * element down from n to go in the middle.
+ *
+ * . B . .
+ * / \ -> |
+ * a A b c C d a A b B c C d
+ *
+ * (Since at all points we have avoided
+ * descending to a node with only one element,
+ * we can be sure that n is not reduced to
+ * nothingness by this move, _unless_ it was
+ * the very first node, ie the root of the
+ * tree. In that case we remove the now-empty
+ * root and replace it with its single large
+ * child as shown.)
+ */
+ node234 *sib;
+ int j;
+
+ if (ki > 0) {
+ ki--;
+ index += n->counts[ki] + 1;
+ }
+ sib = n->kids[ki];
+ sub = n->kids[ki + 1];
+
+ sub->kids[3] = sub->kids[1];
+ sub->counts[3] = sub->counts[1];
+ sub->elems[2] = sub->elems[0];
+ sub->kids[2] = sub->kids[0];
+ sub->counts[2] = sub->counts[0];
+ sub->elems[1] = n->elems[ki];
+ sub->kids[1] = sib->kids[1];
+ sub->counts[1] = sib->counts[1];
+ if (sub->kids[1])
+ sub->kids[1]->parent = sub;
+ sub->elems[0] = sib->elems[0];
+ sub->kids[0] = sib->kids[0];
+ sub->counts[0] = sib->counts[0];
+ if (sub->kids[0])
+ sub->kids[0]->parent = sub;
+
+ n->counts[ki + 1] = countnode234(sub);
+
+ sfree(sib);
+
+ /*
+ * That's built the big node in sub. Now we
+ * need to remove the reference to sib in n.
+ */
+ for (j = ki; j < 3 && n->kids[j + 1]; j++) {
+ n->kids[j] = n->kids[j + 1];
+ n->counts[j] = n->counts[j + 1];
+ n->elems[j] = j < 2 ? n->elems[j + 1] : NULL;
+ }
+ n->kids[j] = NULL;
+ n->counts[j] = 0;
+ if (j < 3)
+ n->elems[j] = NULL;
+ LOG((" case 3b ki=%d\n", ki));
+
+ if (!n->elems[0]) {
+ /*
+ * The root is empty and needs to be
+ * removed.
+ */
+ LOG((" shifting root!\n"));
+ t->root = sub;
+ sub->parent = NULL;
+ sfree(n);
+ }
+ }
+ }
+ n = sub;
+ }
+ if (!retval)
+ retval = n->elems[ei];
+
+ if (ei == -1)
+ return NULL; /* although this shouldn't happen */
+
+ /*
+ * Treat special case: this is the one remaining item in
+ * the tree. n is the tree root (no parent), has one
+ * element (no elems[1]), and has no kids (no kids[0]).
+ */
+ if (!n->parent && !n->elems[1] && !n->kids[0]) {
+ LOG((" removed last element in tree\n"));
+ sfree(n);
+ t->root = NULL;
+ return retval;
+ }
+
+ /*
+ * Now we have the element we want, as n->elems[ei], and we
+ * have also arranged for that element not to be the only
+ * one in its node. So...
+ */
+
+ if (!n->kids[0] && n->elems[1]) {
+ /*
+ * Case 1. n is a leaf node with more than one element,
+ * so it's _really easy_. Just delete the thing and
+ * we're done.
+ */
+ int i;
+ LOG((" case 1\n"));
+ for (i = ei; i < 2 && n->elems[i + 1]; i++)
+ n->elems[i] = n->elems[i + 1];
+ n->elems[i] = NULL;
+ /*
+ * Having done that to the leaf node, we now go back up
+ * the tree fixing the counts.
+ */
+ while (n->parent) {
+ int childnum;
+ childnum = (n->parent->kids[0] == n ? 0 :
+ n->parent->kids[1] == n ? 1 :
+ n->parent->kids[2] == n ? 2 : 3);
+ n->parent->counts[childnum]--;
+ n = n->parent;
+ }
+ return retval; /* finished! */
+ } else if (n->kids[ei]->elems[1]) {
+ /*
+ * Case 2a. n is an internal node, and the root of the
+ * subtree to the left of e has more than one element.
+ * So find the predecessor p to e (ie the largest node
+ * in that subtree), place it where e currently is, and
+ * then start the deletion process over again on the
+ * subtree with p as target.
+ */
+ node234 *m = n->kids[ei];
+ void *target;
+ LOG((" case 2a\n"));
+ while (m->kids[0]) {
+ m = (m->kids[3] ? m->kids[3] :
+ m->kids[2] ? m->kids[2] :
+ m->kids[1] ? m->kids[1] : m->kids[0]);
+ }
+ target = (m->elems[2] ? m->elems[2] :
+ m->elems[1] ? m->elems[1] : m->elems[0]);
+ n->elems[ei] = target;
+ index = n->counts[ei] - 1;
+ n = n->kids[ei];
+ } else if (n->kids[ei + 1]->elems[1]) {
+ /*
+ * Case 2b, symmetric to 2a but s/left/right/ and
+ * s/predecessor/successor/. (And s/largest/smallest/).
+ */
+ node234 *m = n->kids[ei + 1];
+ void *target;
+ LOG((" case 2b\n"));
+ while (m->kids[0]) {
+ m = m->kids[0];
+ }
+ target = m->elems[0];
+ n->elems[ei] = target;
+ n = n->kids[ei + 1];
+ index = 0;
+ } else {
+ /*
+ * Case 2c. n is an internal node, and the subtrees to
+ * the left and right of e both have only one element.
+ * So combine the two subnodes into a single big node
+ * with their own elements on the left and right and e
+ * in the middle, then restart the deletion process on
+ * that subtree, with e still as target.
+ */
+ node234 *a = n->kids[ei], *b = n->kids[ei + 1];
+ int j;
+
+ LOG((" case 2c\n"));
+ a->elems[1] = n->elems[ei];
+ a->kids[2] = b->kids[0];
+ a->counts[2] = b->counts[0];
+ if (a->kids[2])
+ a->kids[2]->parent = a;
+ a->elems[2] = b->elems[0];
+ a->kids[3] = b->kids[1];
+ a->counts[3] = b->counts[1];
+ if (a->kids[3])
+ a->kids[3]->parent = a;
+ sfree(b);
+ n->counts[ei] = countnode234(a);
+ /*
+ * That's built the big node in a, and destroyed b. Now
+ * remove the reference to b (and e) in n.
+ */
+ for (j = ei; j < 2 && n->elems[j + 1]; j++) {
+ n->elems[j] = n->elems[j + 1];
+ n->kids[j + 1] = n->kids[j + 2];
+ n->counts[j + 1] = n->counts[j + 2];
+ }
+ n->elems[j] = NULL;
+ n->kids[j + 1] = NULL;
+ n->counts[j + 1] = 0;
+ /*
+ * It's possible, in this case, that we've just removed
+ * the only element in the root of the tree. If so,
+ * shift the root.
+ */
+ if (n->elems[0] == NULL) {
+ LOG((" shifting root!\n"));
+ t->root = a;
+ a->parent = NULL;
+ sfree(n);
+ }
+ /*
+ * Now go round the deletion process again, with n
+ * pointing at the new big node and e still the same.
+ */
+ n = a;
+ index = a->counts[0] + a->counts[1] + 1;
+ }
+ }
+}
+void *delpos234(tree234 * t, int index)
+{
+ if (index < 0 || index >= countnode234(t->root))
+ return NULL;
+ return delpos234_internal(t, index);
+}
+void *del234(tree234 * t, void *e)
+{
+ int index;
+ if (!findrelpos234(t, e, NULL, REL234_EQ, &index))
+ return NULL; /* it wasn't in there anyway */
+ return delpos234_internal(t, index); /* it's there; delete it. */
+}
+
+#ifdef TEST
+
+/*
+ * Test code for the 2-3-4 tree. This code maintains an alternative
+ * representation of the data in the tree, in an array (using the
+ * obvious and slow insert and delete functions). After each tree
+ * operation, the verify() function is called, which ensures all
+ * the tree properties are preserved:
+ * - node->child->parent always equals node
+ * - tree->root->parent always equals NULL
+ * - number of kids == 0 or number of elements + 1;
+ * - tree has the same depth everywhere
+ * - every node has at least one element
+ * - subtree element counts are accurate
+ * - any NULL kid pointer is accompanied by a zero count
+ * - in a sorted tree: ordering property between elements of a
+ * node and elements of its children is preserved
+ * and also ensures the list represented by the tree is the same
+ * list it should be. (This last check also doubly verifies the
+ * ordering properties, because the `same list it should be' is by
+ * definition correctly ordered. It also ensures all nodes are
+ * distinct, because the enum functions would get caught in a loop
+ * if not.)
+ */
+
+#include <stdarg.h>
+
+/*
+ * Error reporting function.
+ */
+void error(char *fmt, ...)
+{
+ va_list ap;
+ printf("ERROR: ");
+ va_start(ap, fmt);
+ vfprintf(stdout, fmt, ap);
+ va_end(ap);
+ printf("\n");
+}
+
+/* The array representation of the data. */
+void **array;
+int arraylen, arraysize;
+cmpfn234 cmp;
+
+/* The tree representation of the same data. */
+tree234 *tree;
+
+typedef struct {
+ int treedepth;
+ int elemcount;
+} chkctx;
+
+int chknode(chkctx * ctx, int level, node234 * node,
+ void *lowbound, void *highbound)
+{
+ int nkids, nelems;
+ int i;
+ int count;
+
+ /* Count the non-NULL kids. */
+ for (nkids = 0; nkids < 4 && node->kids[nkids]; nkids++);
+ /* Ensure no kids beyond the first NULL are non-NULL. */
+ for (i = nkids; i < 4; i++)
+ if (node->kids[i]) {
+ error("node %p: nkids=%d but kids[%d] non-NULL",
+ node, nkids, i);
+ } else if (node->counts[i]) {
+ error("node %p: kids[%d] NULL but count[%d]=%d nonzero",
+ node, i, i, node->counts[i]);
+ }
+
+ /* Count the non-NULL elements. */
+ for (nelems = 0; nelems < 3 && node->elems[nelems]; nelems++);
+ /* Ensure no elements beyond the first NULL are non-NULL. */
+ for (i = nelems; i < 3; i++)
+ if (node->elems[i]) {
+ error("node %p: nelems=%d but elems[%d] non-NULL",
+ node, nelems, i);
+ }
+
+ if (nkids == 0) {
+ /*
+ * If nkids==0, this is a leaf node; verify that the tree
+ * depth is the same everywhere.
+ */
+ if (ctx->treedepth < 0)
+ ctx->treedepth = level; /* we didn't know the depth yet */
+ else if (ctx->treedepth != level)
+ error("node %p: leaf at depth %d, previously seen depth %d",
+ node, level, ctx->treedepth);
+ } else {
+ /*
+ * If nkids != 0, then it should be nelems+1, unless nelems
+ * is 0 in which case nkids should also be 0 (and so we
+ * shouldn't be in this condition at all).
+ */
+ int shouldkids = (nelems ? nelems + 1 : 0);
+ if (nkids != shouldkids) {
+ error("node %p: %d elems should mean %d kids but has %d",
+ node, nelems, shouldkids, nkids);
+ }
+ }
+
+ /*
+ * nelems should be at least 1.
+ */
+ if (nelems == 0) {
+ error("node %p: no elems", node, nkids);
+ }
+
+ /*
+ * Add nelems to the running element count of the whole tree.
+ */
+ ctx->elemcount += nelems;
+
+ /*
+ * Check ordering property: all elements should be strictly >
+ * lowbound, strictly < highbound, and strictly < each other in
+ * sequence. (lowbound and highbound are NULL at edges of tree
+ * - both NULL at root node - and NULL is considered to be <
+ * everything and > everything. IYSWIM.)
+ */
+ if (cmp) {
+ for (i = -1; i < nelems; i++) {
+ void *lower = (i == -1 ? lowbound : node->elems[i]);
+ void *higher =
+ (i + 1 == nelems ? highbound : node->elems[i + 1]);
+ if (lower && higher && cmp(lower, higher) >= 0) {
+ error("node %p: kid comparison [%d=%s,%d=%s] failed",
+ node, i, lower, i + 1, higher);
+ }
+ }
+ }
+
+ /*
+ * Check parent pointers: all non-NULL kids should have a
+ * parent pointer coming back to this node.
+ */
+ for (i = 0; i < nkids; i++)
+ if (node->kids[i]->parent != node) {
+ error("node %p kid %d: parent ptr is %p not %p",
+ node, i, node->kids[i]->parent, node);
+ }
+
+
+ /*
+ * Now (finally!) recurse into subtrees.
+ */
+ count = nelems;
+
+ for (i = 0; i < nkids; i++) {
+ void *lower = (i == 0 ? lowbound : node->elems[i - 1]);
+ void *higher = (i >= nelems ? highbound : node->elems[i]);
+ int subcount =
+ chknode(ctx, level + 1, node->kids[i], lower, higher);
+ if (node->counts[i] != subcount) {
+ error("node %p kid %d: count says %d, subtree really has %d",
+ node, i, node->counts[i], subcount);
+ }
+ count += subcount;
+ }
+
+ return count;
+}
+
+void verify(void)
+{
+ chkctx ctx;
+ int i;
+ void *p;
+
+ ctx.treedepth = -1; /* depth unknown yet */
+ ctx.elemcount = 0; /* no elements seen yet */
+ /*
+ * Verify validity of tree properties.
+ */
+ if (tree->root) {
+ if (tree->root->parent != NULL)
+ error("root->parent is %p should be null", tree->root->parent);
+ chknode(&ctx, 0, tree->root, NULL, NULL);
+ }
+ printf("tree depth: %d\n", ctx.treedepth);
+ /*
+ * Enumerate the tree and ensure it matches up to the array.
+ */
+ for (i = 0; NULL != (p = index234(tree, i)); i++) {
+ if (i >= arraylen)
+ error("tree contains more than %d elements", arraylen);
+ if (array[i] != p)
+ error("enum at position %d: array says %s, tree says %s",
+ i, array[i], p);
+ }
+ if (ctx.elemcount != i) {
+ error("tree really contains %d elements, enum gave %d",
+ ctx.elemcount, i);
+ }
+ if (i < arraylen) {
+ error("enum gave only %d elements, array has %d", i, arraylen);
+ }
+ i = count234(tree);
+ if (ctx.elemcount != i) {
+ error("tree really contains %d elements, count234 gave %d",
+ ctx.elemcount, i);
+ }
+}
+
+void internal_addtest(void *elem, int index, void *realret)
+{
+ int i, j;
+ void *retval;
+
+ if (arraysize < arraylen + 1) {
+ arraysize = arraylen + 1 + 256;
+ array = sresize(array, arraysize, void *);
+ }
+
+ i = index;
+ /* now i points to the first element >= elem */
+ retval = elem; /* expect elem returned (success) */
+ for (j = arraylen; j > i; j--)
+ array[j] = array[j - 1];
+ array[i] = elem; /* add elem to array */
+ arraylen++;
+
+ if (realret != retval) {
+ error("add: retval was %p expected %p", realret, retval);
+ }
+
+ verify();
+}
+
+void addtest(void *elem)
+{
+ int i;
+ void *realret;
+
+ realret = add234(tree, elem);
+
+ i = 0;
+ while (i < arraylen && cmp(elem, array[i]) > 0)
+ i++;
+ if (i < arraylen && !cmp(elem, array[i])) {
+ void *retval = array[i]; /* expect that returned not elem */
+ if (realret != retval) {
+ error("add: retval was %p expected %p", realret, retval);
+ }
+ } else
+ internal_addtest(elem, i, realret);
+}
+
+void addpostest(void *elem, int i)
+{
+ void *realret;
+
+ realret = addpos234(tree, elem, i);
+
+ internal_addtest(elem, i, realret);
+}
+
+void delpostest(int i)
+{
+ int index = i;
+ void *elem = array[i], *ret;
+
+ /* i points to the right element */
+ while (i < arraylen - 1) {
+ array[i] = array[i + 1];
+ i++;
+ }
+ arraylen--; /* delete elem from array */
+
+ if (tree->cmp)
+ ret = del234(tree, elem);
+ else
+ ret = delpos234(tree, index);
+
+ if (ret != elem) {
+ error("del returned %p, expected %p", ret, elem);
+ }
+
+ verify();
+}
+
+void deltest(void *elem)
+{
+ int i;
+
+ i = 0;
+ while (i < arraylen && cmp(elem, array[i]) > 0)
+ i++;
+ if (i >= arraylen || cmp(elem, array[i]) != 0)
+ return; /* don't do it! */
+ delpostest(i);
+}
+
+/* A sample data set and test utility. Designed for pseudo-randomness,
+ * and yet repeatability. */
+
+/*
+ * This random number generator uses the `portable implementation'
+ * given in ANSI C99 draft N869. It assumes `unsigned' is 32 bits;
+ * change it if not.
+ */
+int randomnumber(unsigned *seed)
+{
+ *seed *= 1103515245;
+ *seed += 12345;
+ return ((*seed) / 65536) % 32768;
+}
+
+int mycmp(void *av, void *bv)
+{
+ char const *a = (char const *) av;
+ char const *b = (char const *) bv;
+ return strcmp(a, b);
+}
+
+#define lenof(x) ( sizeof((x)) / sizeof(*(x)) )
+
+char *strings[] = {
+ "a", "ab", "absque", "coram", "de",
+ "palam", "clam", "cum", "ex", "e",
+ "sine", "tenus", "pro", "prae",
+ "banana", "carrot", "cabbage", "broccoli", "onion", "zebra",
+ "penguin", "blancmange", "pangolin", "whale", "hedgehog",
+ "giraffe", "peanut", "bungee", "foo", "bar", "baz", "quux",
+ "murfl", "spoo", "breen", "flarn", "octothorpe",
+ "snail", "tiger", "elephant", "octopus", "warthog", "armadillo",
+ "aardvark", "wyvern", "dragon", "elf", "dwarf", "orc", "goblin",
+ "pixie", "basilisk", "warg", "ape", "lizard", "newt", "shopkeeper",
+ "wand", "ring", "amulet"
+};
+
+#define NSTR lenof(strings)
+
+int findtest(void)
+{
+ const static int rels[] = {
+ REL234_EQ, REL234_GE, REL234_LE, REL234_LT, REL234_GT
+ };
+ const static char *const relnames[] = {
+ "EQ", "GE", "LE", "LT", "GT"
+ };
+ int i, j, rel, index;
+ char *p, *ret, *realret, *realret2;
+ int lo, hi, mid, c;
+
+ for (i = 0; i < NSTR; i++) {
+ p = strings[i];
+ for (j = 0; j < sizeof(rels) / sizeof(*rels); j++) {
+ rel = rels[j];
+
+ lo = 0;
+ hi = arraylen - 1;
+ while (lo <= hi) {
+ mid = (lo + hi) / 2;
+ c = strcmp(p, array[mid]);
+ if (c < 0)
+ hi = mid - 1;
+ else if (c > 0)
+ lo = mid + 1;
+ else
+ break;
+ }
+
+ if (c == 0) {
+ if (rel == REL234_LT)
+ ret = (mid > 0 ? array[--mid] : NULL);
+ else if (rel == REL234_GT)
+ ret = (mid < arraylen - 1 ? array[++mid] : NULL);
+ else
+ ret = array[mid];
+ } else {
+ assert(lo == hi + 1);
+ if (rel == REL234_LT || rel == REL234_LE) {
+ mid = hi;
+ ret = (hi >= 0 ? array[hi] : NULL);
+ } else if (rel == REL234_GT || rel == REL234_GE) {
+ mid = lo;
+ ret = (lo < arraylen ? array[lo] : NULL);
+ } else
+ ret = NULL;
+ }
+
+ realret = findrelpos234(tree, p, NULL, rel, &index);
+ if (realret != ret) {
+ error("find(\"%s\",%s) gave %s should be %s",
+ p, relnames[j], realret, ret);
+ }
+ if (realret && index != mid) {
+ error("find(\"%s\",%s) gave %d should be %d",
+ p, relnames[j], index, mid);
+ }
+ if (realret && rel == REL234_EQ) {
+ realret2 = index234(tree, index);
+ if (realret2 != realret) {
+ error("find(\"%s\",%s) gave %s(%d) but %d -> %s",
+ p, relnames[j], realret, index, index, realret2);
+ }
+ }
+#if 0
+ printf("find(\"%s\",%s) gave %s(%d)\n", p, relnames[j],
+ realret, index);
+#endif
+ }
+ }
+
+ realret = findrelpos234(tree, NULL, NULL, REL234_GT, &index);
+ if (arraylen && (realret != array[0] || index != 0)) {
+ error("find(NULL,GT) gave %s(%d) should be %s(0)",
+ realret, index, array[0]);
+ } else if (!arraylen && (realret != NULL)) {
+ error("find(NULL,GT) gave %s(%d) should be NULL", realret, index);
+ }
+
+ realret = findrelpos234(tree, NULL, NULL, REL234_LT, &index);
+ if (arraylen
+ && (realret != array[arraylen - 1] || index != arraylen - 1)) {
+ error("find(NULL,LT) gave %s(%d) should be %s(0)", realret, index,
+ array[arraylen - 1]);
+ } else if (!arraylen && (realret != NULL)) {
+ error("find(NULL,LT) gave %s(%d) should be NULL", realret, index);
+ }
+}
+
+int main(void)
+{
+ int in[NSTR];
+ int i, j, k;
+ unsigned seed = 0;
+
+ for (i = 0; i < NSTR; i++)
+ in[i] = 0;
+ array = NULL;
+ arraylen = arraysize = 0;
+ tree = newtree234(mycmp);
+ cmp = mycmp;
+
+ verify();
+ for (i = 0; i < 10000; i++) {
+ j = randomnumber(&seed);
+ j %= NSTR;
+ printf("trial: %d\n", i);
+ if (in[j]) {
+ printf("deleting %s (%d)\n", strings[j], j);
+ deltest(strings[j]);
+ in[j] = 0;
+ } else {
+ printf("adding %s (%d)\n", strings[j], j);
+ addtest(strings[j]);
+ in[j] = 1;
+ }
+ findtest();
+ }
+
+ while (arraylen > 0) {
+ j = randomnumber(&seed);
+ j %= arraylen;
+ deltest(array[j]);
+ }
+
+ freetree234(tree);
+
+ /*
+ * Now try an unsorted tree. We don't really need to test
+ * delpos234 because we know del234 is based on it, so it's
+ * already been tested in the above sorted-tree code; but for
+ * completeness we'll use it to tear down our unsorted tree
+ * once we've built it.
+ */
+ tree = newtree234(NULL);
+ cmp = NULL;
+ verify();
+ for (i = 0; i < 1000; i++) {
+ printf("trial: %d\n", i);
+ j = randomnumber(&seed);
+ j %= NSTR;
+ k = randomnumber(&seed);
+ k %= count234(tree) + 1;
+ printf("adding string %s at index %d\n", strings[j], k);
+ addpostest(strings[j], k);
+ }
+ while (count234(tree) > 0) {
+ printf("cleanup: tree size %d\n", count234(tree));
+ j = randomnumber(&seed);
+ j %= count234(tree);
+ printf("deleting string %s from index %d\n",
+ (const char *)array[j], j);
+ delpostest(j);
+ }
+
+ return 0;
+}
+
+#endif
diff --git a/tools/plink/wildcard.c b/tools/plink/wildcard.c
index 75a7573b2..c1cb0b49e 100644
--- a/tools/plink/wildcard.c
+++ b/tools/plink/wildcard.c
@@ -1,472 +1,473 @@
-/*
- * Wildcard matching engine for use with SFTP-based file transfer
- * programs (PSFTP, new-look PSCP): since SFTP has no notion of
- * getting the remote side to do globbing (and rightly so) we have
- * to do it locally, by retrieving all the filenames in a directory
- * and checking each against the wildcard pattern.
- */
-
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "putty.h"
-
-/*
- * Definition of wildcard syntax:
- *
- * - * matches any sequence of characters, including zero.
- * - ? matches exactly one character which can be anything.
- * - [abc] matches exactly one character which is a, b or c.
- * - [a-f] matches anything from a through f.
- * - [^a-f] matches anything _except_ a through f.
- * - [-_] matches - or _; [^-_] matches anything else. (The - is
- * non-special if it occurs immediately after the opening
- * bracket or ^.)
- * - [a^] matches an a or a ^. (The ^ is non-special if it does
- * _not_ occur immediately after the opening bracket.)
- * - \*, \?, \[, \], \\ match the single characters *, ?, [, ], \.
- * - All other characters are non-special and match themselves.
- */
-
-/*
- * Some notes on differences from POSIX globs (IEEE Std 1003.1, 2003 ed.):
- * - backslashes act as escapes even within [] bracket expressions
- * - does not support [!...] for non-matching list (POSIX are weird);
- * NB POSIX allows [^...] as well via "A bracket expression starting
- * with an unquoted circumflex character produces unspecified
- * results". If we wanted to allow [!...] we might want to define
- * [^!] as having its literal meaning (match '^' or '!').
- * - none of the scary [[:class:]] stuff, etc
- */
-
-/*
- * The wildcard matching technique we use is very simple and
- * potentially O(N^2) in running time, but I don't anticipate it
- * being that bad in reality (particularly since N will be the size
- * of a filename, which isn't all that much). Perhaps one day, once
- * PuTTY has grown a regexp matcher for some other reason, I might
- * come back and reimplement wildcards by translating them into
- * regexps or directly into NFAs; but for the moment, in the
- * absence of any other need for the NFA->DFA translation engine,
- * anything more than the simplest possible wildcard matcher is
- * vast code-size overkill.
- *
- * Essentially, these wildcards are much simpler than regexps in
- * that they consist of a sequence of rigid fragments (? and [...]
- * can never match more or less than one character) separated by
- * asterisks. It is therefore extremely simple to look at a rigid
- * fragment and determine whether or not it begins at a particular
- * point in the test string; so we can search along the string
- * until we find each fragment, then search for the next. As long
- * as we find each fragment in the _first_ place it occurs, there
- * will never be a danger of having to backpedal and try to find it
- * again somewhere else.
- */
-
-enum {
- WC_TRAILINGBACKSLASH = 1,
- WC_UNCLOSEDCLASS,
- WC_INVALIDRANGE
-};
-
-/*
- * Error reporting is done by returning various negative values
- * from the wildcard routines. Passing any such value to wc_error
- * will give a human-readable message.
- */
-const char *wc_error(int value)
-{
- value = abs(value);
- switch (value) {
- case WC_TRAILINGBACKSLASH:
- return "'\' occurred at end of string (expected another character)";
- case WC_UNCLOSEDCLASS:
- return "expected ']' to close character class";
- case WC_INVALIDRANGE:
- return "character range was not terminated (']' just after '-')";
- }
- return "INTERNAL ERROR: unrecognised wildcard error number";
-}
-
-/*
- * This is the routine that tests a target string to see if an
- * initial substring of it matches a fragment. If successful, it
- * returns 1, and advances both `fragment' and `target' past the
- * fragment and matching substring respectively. If unsuccessful it
- * returns zero. If the wildcard fragment suffers a syntax error,
- * it returns <0 and the precise value indexes into wc_error.
- */
-static int wc_match_fragment(const char **fragment, const char **target)
-{
- const char *f, *t;
-
- f = *fragment;
- t = *target;
- /*
- * The fragment terminates at either the end of the string, or
- * the first (unescaped) *.
- */
- while (*f && *f != '*' && *t) {
- /*
- * Extract one character from t, and one character's worth
- * of pattern from f, and step along both. Return 0 if they
- * fail to match.
- */
- if (*f == '\\') {
- /*
- * Backslash, which means f[1] is to be treated as a
- * literal character no matter what it is. It may not
- * be the end of the string.
- */
- if (!f[1])
- return -WC_TRAILINGBACKSLASH; /* error */
- if (f[1] != *t)
- return 0; /* failed to match */
- f += 2;
- } else if (*f == '?') {
- /*
- * Question mark matches anything.
- */
- f++;
- } else if (*f == '[') {
- int invert = 0;
- int matched = 0;
- /*
- * Open bracket introduces a character class.
- */
- f++;
- if (*f == '^') {
- invert = 1;
- f++;
- }
- while (*f != ']') {
- if (*f == '\\')
- f++; /* backslashes still work */
- if (!*f)
- return -WC_UNCLOSEDCLASS; /* error again */
- if (f[1] == '-') {
- int lower, upper, ourchr;
- lower = (unsigned char) *f++;
- f++; /* eat the minus */
- if (*f == ']')
- return -WC_INVALIDRANGE; /* different error! */
- if (*f == '\\')
- f++; /* backslashes _still_ work */
- if (!*f)
- return -WC_UNCLOSEDCLASS; /* error again */
- upper = (unsigned char) *f++;
- ourchr = (unsigned char) *t;
- if (lower > upper) {
- int t = lower; lower = upper; upper = t;
- }
- if (ourchr >= lower && ourchr <= upper)
- matched = 1;
- } else {
- matched |= (*t == *f++);
- }
- }
- if (invert == matched)
- return 0; /* failed to match character class */
- f++; /* eat the ] */
- } else {
- /*
- * Non-special character matches itself.
- */
- if (*f != *t)
- return 0;
- f++;
- }
- /*
- * Now we've done that, increment t past the character we
- * matched.
- */
- t++;
- }
- if (!*f || *f == '*') {
- /*
- * We have reached the end of f without finding a mismatch;
- * so we're done. Update the caller pointers and return 1.
- */
- *fragment = f;
- *target = t;
- return 1;
- }
- /*
- * Otherwise, we must have reached the end of t before we
- * reached the end of f; so we've failed. Return 0.
- */
- return 0;
-}
-
-/*
- * This is the real wildcard matching routine. It returns 1 for a
- * successful match, 0 for an unsuccessful match, and <0 for a
- * syntax error in the wildcard.
- */
-int wc_match(const char *wildcard, const char *target)
-{
- int ret;
-
- /*
- * Every time we see a '*' _followed_ by a fragment, we just
- * search along the string for a location at which the fragment
- * matches. The only special case is when we see a fragment
- * right at the start, in which case we just call the matching
- * routine once and give up if it fails.
- */
- if (*wildcard != '*') {
- ret = wc_match_fragment(&wildcard, &target);
- if (ret <= 0)
- return ret; /* pass back failure or error alike */
- }
-
- while (*wildcard) {
- assert(*wildcard == '*');
- while (*wildcard == '*')
- wildcard++;
-
- /*
- * It's possible we've just hit the end of the wildcard
- * after seeing a *, in which case there's no need to
- * bother searching any more because we've won.
- */
- if (!*wildcard)
- return 1;
-
- /*
- * Now `wildcard' points at the next fragment. So we
- * attempt to match it against `target', and if that fails
- * we increment `target' and try again, and so on. When we
- * find we're about to try matching against the empty
- * string, we give up and return 0.
- */
- ret = 0;
- while (*target) {
- const char *save_w = wildcard, *save_t = target;
-
- ret = wc_match_fragment(&wildcard, &target);
-
- if (ret < 0)
- return ret; /* syntax error */
-
- if (ret > 0 && !*wildcard && *target) {
- /*
- * Final special case - literally.
- *
- * This situation arises when we are matching a
- * _terminal_ fragment of the wildcard (that is,
- * there is nothing after it, e.g. "*a"), and it
- * has matched _too early_. For example, matching
- * "*a" against "parka" will match the "a" fragment
- * against the _first_ a, and then (if it weren't
- * for this special case) matching would fail
- * because we're at the end of the wildcard but not
- * at the end of the target string.
- *
- * In this case what we must do is measure the
- * length of the fragment in the target (which is
- * why we saved `target'), jump straight to that
- * distance from the end of the string using
- * strlen, and match the same fragment again there
- * (which is why we saved `wildcard'). Then we
- * return whatever that operation returns.
- */
- target = save_t + strlen(save_t) - (target - save_t);
- wildcard = save_w;
- return wc_match_fragment(&wildcard, &target);
- }
-
- if (ret > 0)
- break;
- target++;
- }
- if (ret > 0)
- continue;
- return 0;
- }
-
- /*
- * If we reach here, it must be because we successfully matched
- * a fragment and then found ourselves right at the end of the
- * wildcard. Hence, we return 1 if and only if we are also
- * right at the end of the target.
- */
- return (*target ? 0 : 1);
-}
-
-/*
- * Another utility routine that translates a non-wildcard string
- * into its raw equivalent by removing any escaping backslashes.
- * Expects a target string buffer of anything up to the length of
- * the original wildcard. You can also pass NULL as the output
- * buffer if you're only interested in the return value.
- *
- * Returns 1 on success, or 0 if a wildcard character was
- * encountered. In the latter case the output string MAY not be
- * zero-terminated and you should not use it for anything!
- */
-int wc_unescape(char *output, const char *wildcard)
-{
- while (*wildcard) {
- if (*wildcard == '\\') {
- wildcard++;
- /* We are lenient about trailing backslashes in non-wildcards. */
- if (*wildcard) {
- if (output)
- *output++ = *wildcard;
- wildcard++;
- }
- } else if (*wildcard == '*' || *wildcard == '?' ||
- *wildcard == '[' || *wildcard == ']') {
- return 0; /* it's a wildcard! */
- } else {
- if (output)
- *output++ = *wildcard;
- wildcard++;
- }
- }
- *output = '\0';
- return 1; /* it's clean */
-}
-
-#ifdef TESTMODE
-
-struct test {
- const char *wildcard;
- const char *target;
- int expected_result;
-};
-
-const struct test fragment_tests[] = {
- /*
- * We exhaustively unit-test the fragment matching routine
- * itself, which should save us the need to test all its
- * intricacies during the full wildcard tests.
- */
- {"abc", "abc", 1},
- {"abc", "abd", 0},
- {"abc", "abcd", 1},
- {"abcd", "abc", 0},
- {"ab[cd]", "abc", 1},
- {"ab[cd]", "abd", 1},
- {"ab[cd]", "abe", 0},
- {"ab[^cd]", "abc", 0},
- {"ab[^cd]", "abd", 0},
- {"ab[^cd]", "abe", 1},
- {"ab\\", "abc", -WC_TRAILINGBACKSLASH},
- {"ab\\*", "ab*", 1},
- {"ab\\?", "ab*", 0},
- {"ab?", "abc", 1},
- {"ab?", "ab", 0},
- {"ab[", "abc", -WC_UNCLOSEDCLASS},
- {"ab[c-", "abb", -WC_UNCLOSEDCLASS},
- {"ab[c-]", "abb", -WC_INVALIDRANGE},
- {"ab[c-e]", "abb", 0},
- {"ab[c-e]", "abc", 1},
- {"ab[c-e]", "abd", 1},
- {"ab[c-e]", "abe", 1},
- {"ab[c-e]", "abf", 0},
- {"ab[e-c]", "abb", 0},
- {"ab[e-c]", "abc", 1},
- {"ab[e-c]", "abd", 1},
- {"ab[e-c]", "abe", 1},
- {"ab[e-c]", "abf", 0},
- {"ab[^c-e]", "abb", 1},
- {"ab[^c-e]", "abc", 0},
- {"ab[^c-e]", "abd", 0},
- {"ab[^c-e]", "abe", 0},
- {"ab[^c-e]", "abf", 1},
- {"ab[^e-c]", "abb", 1},
- {"ab[^e-c]", "abc", 0},
- {"ab[^e-c]", "abd", 0},
- {"ab[^e-c]", "abe", 0},
- {"ab[^e-c]", "abf", 1},
- {"ab[a^]", "aba", 1},
- {"ab[a^]", "ab^", 1},
- {"ab[a^]", "abb", 0},
- {"ab[^a^]", "aba", 0},
- {"ab[^a^]", "ab^", 0},
- {"ab[^a^]", "abb", 1},
- {"ab[-c]", "ab-", 1},
- {"ab[-c]", "abc", 1},
- {"ab[-c]", "abd", 0},
- {"ab[^-c]", "ab-", 0},
- {"ab[^-c]", "abc", 0},
- {"ab[^-c]", "abd", 1},
- {"ab[\\[-\\]]", "abZ", 0},
- {"ab[\\[-\\]]", "ab[", 1},
- {"ab[\\[-\\]]", "ab\\", 1},
- {"ab[\\[-\\]]", "ab]", 1},
- {"ab[\\[-\\]]", "ab^", 0},
- {"ab[^\\[-\\]]", "abZ", 1},
- {"ab[^\\[-\\]]", "ab[", 0},
- {"ab[^\\[-\\]]", "ab\\", 0},
- {"ab[^\\[-\\]]", "ab]", 0},
- {"ab[^\\[-\\]]", "ab^", 1},
- {"ab[a-fA-F]", "aba", 1},
- {"ab[a-fA-F]", "abF", 1},
- {"ab[a-fA-F]", "abZ", 0},
-};
-
-const struct test full_tests[] = {
- {"a", "argh", 0},
- {"a", "ba", 0},
- {"a", "a", 1},
- {"a*", "aardvark", 1},
- {"a*", "badger", 0},
- {"*a", "park", 0},
- {"*a", "pArka", 1},
- {"*a", "parka", 1},
- {"*a*", "park", 1},
- {"*a*", "perk", 0},
- {"?b*r?", "abracadabra", 1},
- {"?b*r?", "abracadabr", 0},
- {"?b*r?", "abracadabzr", 0},
-};
-
-int main(void)
-{
- int i;
- int fails, passes;
-
- fails = passes = 0;
-
- for (i = 0; i < sizeof(fragment_tests)/sizeof(*fragment_tests); i++) {
- const char *f, *t;
- int eret, aret;
- f = fragment_tests[i].wildcard;
- t = fragment_tests[i].target;
- eret = fragment_tests[i].expected_result;
- aret = wc_match_fragment(&f, &t);
- if (aret != eret) {
- printf("failed test: /%s/ against /%s/ returned %d not %d\n",
- fragment_tests[i].wildcard, fragment_tests[i].target,
- aret, eret);
- fails++;
- } else
- passes++;
- }
-
- for (i = 0; i < sizeof(full_tests)/sizeof(*full_tests); i++) {
- const char *f, *t;
- int eret, aret;
- f = full_tests[i].wildcard;
- t = full_tests[i].target;
- eret = full_tests[i].expected_result;
- aret = wc_match(f, t);
- if (aret != eret) {
- printf("failed test: /%s/ against /%s/ returned %d not %d\n",
- full_tests[i].wildcard, full_tests[i].target,
- aret, eret);
- fails++;
- } else
- passes++;
- }
-
- printf("passed %d, failed %d\n", passes, fails);
-
- return 0;
-}
-
-#endif
+/*
+ * Wildcard matching engine for use with SFTP-based file transfer
+ * programs (PSFTP, new-look PSCP): since SFTP has no notion of
+ * getting the remote side to do globbing (and rightly so) we have
+ * to do it locally, by retrieving all the filenames in a directory
+ * and checking each against the wildcard pattern.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "putty.h"
+
+/*
+ * Definition of wildcard syntax:
+ *
+ * - * matches any sequence of characters, including zero.
+ * - ? matches exactly one character which can be anything.
+ * - [abc] matches exactly one character which is a, b or c.
+ * - [a-f] matches anything from a through f.
+ * - [^a-f] matches anything _except_ a through f.
+ * - [-_] matches - or _; [^-_] matches anything else. (The - is
+ * non-special if it occurs immediately after the opening
+ * bracket or ^.)
+ * - [a^] matches an a or a ^. (The ^ is non-special if it does
+ * _not_ occur immediately after the opening bracket.)
+ * - \*, \?, \[, \], \\ match the single characters *, ?, [, ], \.
+ * - All other characters are non-special and match themselves.
+ */
+
+/*
+ * Some notes on differences from POSIX globs (IEEE Std 1003.1, 2003 ed.):
+ * - backslashes act as escapes even within [] bracket expressions
+ * - does not support [!...] for non-matching list (POSIX are weird);
+ * NB POSIX allows [^...] as well via "A bracket expression starting
+ * with an unquoted circumflex character produces unspecified
+ * results". If we wanted to allow [!...] we might want to define
+ * [^!] as having its literal meaning (match '^' or '!').
+ * - none of the scary [[:class:]] stuff, etc
+ */
+
+/*
+ * The wildcard matching technique we use is very simple and
+ * potentially O(N^2) in running time, but I don't anticipate it
+ * being that bad in reality (particularly since N will be the size
+ * of a filename, which isn't all that much). Perhaps one day, once
+ * PuTTY has grown a regexp matcher for some other reason, I might
+ * come back and reimplement wildcards by translating them into
+ * regexps or directly into NFAs; but for the moment, in the
+ * absence of any other need for the NFA->DFA translation engine,
+ * anything more than the simplest possible wildcard matcher is
+ * vast code-size overkill.
+ *
+ * Essentially, these wildcards are much simpler than regexps in
+ * that they consist of a sequence of rigid fragments (? and [...]
+ * can never match more or less than one character) separated by
+ * asterisks. It is therefore extremely simple to look at a rigid
+ * fragment and determine whether or not it begins at a particular
+ * point in the test string; so we can search along the string
+ * until we find each fragment, then search for the next. As long
+ * as we find each fragment in the _first_ place it occurs, there
+ * will never be a danger of having to backpedal and try to find it
+ * again somewhere else.
+ */
+
+enum {
+ WC_TRAILINGBACKSLASH = 1,
+ WC_UNCLOSEDCLASS,
+ WC_INVALIDRANGE
+};
+
+/*
+ * Error reporting is done by returning various negative values
+ * from the wildcard routines. Passing any such value to wc_error
+ * will give a human-readable message.
+ */
+const char *wc_error(int value)
+{
+ value = abs(value);
+ switch (value) {
+ case WC_TRAILINGBACKSLASH:
+ return "'\' occurred at end of string (expected another character)";
+ case WC_UNCLOSEDCLASS:
+ return "expected ']' to close character class";
+ case WC_INVALIDRANGE:
+ return "character range was not terminated (']' just after '-')";
+ }
+ return "INTERNAL ERROR: unrecognised wildcard error number";
+}
+
+/*
+ * This is the routine that tests a target string to see if an
+ * initial substring of it matches a fragment. If successful, it
+ * returns 1, and advances both `fragment' and `target' past the
+ * fragment and matching substring respectively. If unsuccessful it
+ * returns zero. If the wildcard fragment suffers a syntax error,
+ * it returns <0 and the precise value indexes into wc_error.
+ */
+static int wc_match_fragment(const char **fragment, const char **target)
+{
+ const char *f, *t;
+
+ f = *fragment;
+ t = *target;
+ /*
+ * The fragment terminates at either the end of the string, or
+ * the first (unescaped) *.
+ */
+ while (*f && *f != '*' && *t) {
+ /*
+ * Extract one character from t, and one character's worth
+ * of pattern from f, and step along both. Return 0 if they
+ * fail to match.
+ */
+ if (*f == '\\') {
+ /*
+ * Backslash, which means f[1] is to be treated as a
+ * literal character no matter what it is. It may not
+ * be the end of the string.
+ */
+ if (!f[1])
+ return -WC_TRAILINGBACKSLASH; /* error */
+ if (f[1] != *t)
+ return 0; /* failed to match */
+ f += 2;
+ } else if (*f == '?') {
+ /*
+ * Question mark matches anything.
+ */
+ f++;
+ } else if (*f == '[') {
+ int invert = 0;
+ int matched = 0;
+ /*
+ * Open bracket introduces a character class.
+ */
+ f++;
+ if (*f == '^') {
+ invert = 1;
+ f++;
+ }
+ while (*f != ']') {
+ if (*f == '\\')
+ f++; /* backslashes still work */
+ if (!*f)
+ return -WC_UNCLOSEDCLASS; /* error again */
+ if (f[1] == '-') {
+ int lower, upper, ourchr;
+ lower = (unsigned char) *f++;
+ f++; /* eat the minus */
+ if (*f == ']')
+ return -WC_INVALIDRANGE; /* different error! */
+ if (*f == '\\')
+ f++; /* backslashes _still_ work */
+ if (!*f)
+ return -WC_UNCLOSEDCLASS; /* error again */
+ upper = (unsigned char) *f++;
+ ourchr = (unsigned char) *t;
+ if (lower > upper) {
+ int t = lower; lower = upper; upper = t;
+ }
+ if (ourchr >= lower && ourchr <= upper)
+ matched = 1;
+ } else {
+ matched |= (*t == *f++);
+ }
+ }
+ if (invert == matched)
+ return 0; /* failed to match character class */
+ f++; /* eat the ] */
+ } else {
+ /*
+ * Non-special character matches itself.
+ */
+ if (*f != *t)
+ return 0;
+ f++;
+ }
+ /*
+ * Now we've done that, increment t past the character we
+ * matched.
+ */
+ t++;
+ }
+ if (!*f || *f == '*') {
+ /*
+ * We have reached the end of f without finding a mismatch;
+ * so we're done. Update the caller pointers and return 1.
+ */
+ *fragment = f;
+ *target = t;
+ return 1;
+ }
+ /*
+ * Otherwise, we must have reached the end of t before we
+ * reached the end of f; so we've failed. Return 0.
+ */
+ return 0;
+}
+
+/*
+ * This is the real wildcard matching routine. It returns 1 for a
+ * successful match, 0 for an unsuccessful match, and <0 for a
+ * syntax error in the wildcard.
+ */
+int wc_match(const char *wildcard, const char *target)
+{
+ int ret;
+
+ /*
+ * Every time we see a '*' _followed_ by a fragment, we just
+ * search along the string for a location at which the fragment
+ * matches. The only special case is when we see a fragment
+ * right at the start, in which case we just call the matching
+ * routine once and give up if it fails.
+ */
+ if (*wildcard != '*') {
+ ret = wc_match_fragment(&wildcard, &target);
+ if (ret <= 0)
+ return ret; /* pass back failure or error alike */
+ }
+
+ while (*wildcard) {
+ assert(*wildcard == '*');
+ while (*wildcard == '*')
+ wildcard++;
+
+ /*
+ * It's possible we've just hit the end of the wildcard
+ * after seeing a *, in which case there's no need to
+ * bother searching any more because we've won.
+ */
+ if (!*wildcard)
+ return 1;
+
+ /*
+ * Now `wildcard' points at the next fragment. So we
+ * attempt to match it against `target', and if that fails
+ * we increment `target' and try again, and so on. When we
+ * find we're about to try matching against the empty
+ * string, we give up and return 0.
+ */
+ ret = 0;
+ while (*target) {
+ const char *save_w = wildcard, *save_t = target;
+
+ ret = wc_match_fragment(&wildcard, &target);
+
+ if (ret < 0)
+ return ret; /* syntax error */
+
+ if (ret > 0 && !*wildcard && *target) {
+ /*
+ * Final special case - literally.
+ *
+ * This situation arises when we are matching a
+ * _terminal_ fragment of the wildcard (that is,
+ * there is nothing after it, e.g. "*a"), and it
+ * has matched _too early_. For example, matching
+ * "*a" against "parka" will match the "a" fragment
+ * against the _first_ a, and then (if it weren't
+ * for this special case) matching would fail
+ * because we're at the end of the wildcard but not
+ * at the end of the target string.
+ *
+ * In this case what we must do is measure the
+ * length of the fragment in the target (which is
+ * why we saved `target'), jump straight to that
+ * distance from the end of the string using
+ * strlen, and match the same fragment again there
+ * (which is why we saved `wildcard'). Then we
+ * return whatever that operation returns.
+ */
+ target = save_t + strlen(save_t) - (target - save_t);
+ wildcard = save_w;
+ return wc_match_fragment(&wildcard, &target);
+ }
+
+ if (ret > 0)
+ break;
+ target++;
+ }
+ if (ret > 0)
+ continue;
+ return 0;
+ }
+
+ /*
+ * If we reach here, it must be because we successfully matched
+ * a fragment and then found ourselves right at the end of the
+ * wildcard. Hence, we return 1 if and only if we are also
+ * right at the end of the target.
+ */
+ return (*target ? 0 : 1);
+}
+
+/*
+ * Another utility routine that translates a non-wildcard string
+ * into its raw equivalent by removing any escaping backslashes.
+ * Expects a target string buffer of anything up to the length of
+ * the original wildcard. You can also pass NULL as the output
+ * buffer if you're only interested in the return value.
+ *
+ * Returns 1 on success, or 0 if a wildcard character was
+ * encountered. In the latter case the output string MAY not be
+ * zero-terminated and you should not use it for anything!
+ */
+int wc_unescape(char *output, const char *wildcard)
+{
+ while (*wildcard) {
+ if (*wildcard == '\\') {
+ wildcard++;
+ /* We are lenient about trailing backslashes in non-wildcards. */
+ if (*wildcard) {
+ if (output)
+ *output++ = *wildcard;
+ wildcard++;
+ }
+ } else if (*wildcard == '*' || *wildcard == '?' ||
+ *wildcard == '[' || *wildcard == ']') {
+ return 0; /* it's a wildcard! */
+ } else {
+ if (output)
+ *output++ = *wildcard;
+ wildcard++;
+ }
+ }
+ if (output)
+ *output = '\0';
+ return 1; /* it's clean */
+}
+
+#ifdef TESTMODE
+
+struct test {
+ const char *wildcard;
+ const char *target;
+ int expected_result;
+};
+
+const struct test fragment_tests[] = {
+ /*
+ * We exhaustively unit-test the fragment matching routine
+ * itself, which should save us the need to test all its
+ * intricacies during the full wildcard tests.
+ */
+ {"abc", "abc", 1},
+ {"abc", "abd", 0},
+ {"abc", "abcd", 1},
+ {"abcd", "abc", 0},
+ {"ab[cd]", "abc", 1},
+ {"ab[cd]", "abd", 1},
+ {"ab[cd]", "abe", 0},
+ {"ab[^cd]", "abc", 0},
+ {"ab[^cd]", "abd", 0},
+ {"ab[^cd]", "abe", 1},
+ {"ab\\", "abc", -WC_TRAILINGBACKSLASH},
+ {"ab\\*", "ab*", 1},
+ {"ab\\?", "ab*", 0},
+ {"ab?", "abc", 1},
+ {"ab?", "ab", 0},
+ {"ab[", "abc", -WC_UNCLOSEDCLASS},
+ {"ab[c-", "abb", -WC_UNCLOSEDCLASS},
+ {"ab[c-]", "abb", -WC_INVALIDRANGE},
+ {"ab[c-e]", "abb", 0},
+ {"ab[c-e]", "abc", 1},
+ {"ab[c-e]", "abd", 1},
+ {"ab[c-e]", "abe", 1},
+ {"ab[c-e]", "abf", 0},
+ {"ab[e-c]", "abb", 0},
+ {"ab[e-c]", "abc", 1},
+ {"ab[e-c]", "abd", 1},
+ {"ab[e-c]", "abe", 1},
+ {"ab[e-c]", "abf", 0},
+ {"ab[^c-e]", "abb", 1},
+ {"ab[^c-e]", "abc", 0},
+ {"ab[^c-e]", "abd", 0},
+ {"ab[^c-e]", "abe", 0},
+ {"ab[^c-e]", "abf", 1},
+ {"ab[^e-c]", "abb", 1},
+ {"ab[^e-c]", "abc", 0},
+ {"ab[^e-c]", "abd", 0},
+ {"ab[^e-c]", "abe", 0},
+ {"ab[^e-c]", "abf", 1},
+ {"ab[a^]", "aba", 1},
+ {"ab[a^]", "ab^", 1},
+ {"ab[a^]", "abb", 0},
+ {"ab[^a^]", "aba", 0},
+ {"ab[^a^]", "ab^", 0},
+ {"ab[^a^]", "abb", 1},
+ {"ab[-c]", "ab-", 1},
+ {"ab[-c]", "abc", 1},
+ {"ab[-c]", "abd", 0},
+ {"ab[^-c]", "ab-", 0},
+ {"ab[^-c]", "abc", 0},
+ {"ab[^-c]", "abd", 1},
+ {"ab[\\[-\\]]", "abZ", 0},
+ {"ab[\\[-\\]]", "ab[", 1},
+ {"ab[\\[-\\]]", "ab\\", 1},
+ {"ab[\\[-\\]]", "ab]", 1},
+ {"ab[\\[-\\]]", "ab^", 0},
+ {"ab[^\\[-\\]]", "abZ", 1},
+ {"ab[^\\[-\\]]", "ab[", 0},
+ {"ab[^\\[-\\]]", "ab\\", 0},
+ {"ab[^\\[-\\]]", "ab]", 0},
+ {"ab[^\\[-\\]]", "ab^", 1},
+ {"ab[a-fA-F]", "aba", 1},
+ {"ab[a-fA-F]", "abF", 1},
+ {"ab[a-fA-F]", "abZ", 0},
+};
+
+const struct test full_tests[] = {
+ {"a", "argh", 0},
+ {"a", "ba", 0},
+ {"a", "a", 1},
+ {"a*", "aardvark", 1},
+ {"a*", "badger", 0},
+ {"*a", "park", 0},
+ {"*a", "pArka", 1},
+ {"*a", "parka", 1},
+ {"*a*", "park", 1},
+ {"*a*", "perk", 0},
+ {"?b*r?", "abracadabra", 1},
+ {"?b*r?", "abracadabr", 0},
+ {"?b*r?", "abracadabzr", 0},
+};
+
+int main(void)
+{
+ int i;
+ int fails, passes;
+
+ fails = passes = 0;
+
+ for (i = 0; i < sizeof(fragment_tests)/sizeof(*fragment_tests); i++) {
+ const char *f, *t;
+ int eret, aret;
+ f = fragment_tests[i].wildcard;
+ t = fragment_tests[i].target;
+ eret = fragment_tests[i].expected_result;
+ aret = wc_match_fragment(&f, &t);
+ if (aret != eret) {
+ printf("failed test: /%s/ against /%s/ returned %d not %d\n",
+ fragment_tests[i].wildcard, fragment_tests[i].target,
+ aret, eret);
+ fails++;
+ } else
+ passes++;
+ }
+
+ for (i = 0; i < sizeof(full_tests)/sizeof(*full_tests); i++) {
+ const char *f, *t;
+ int eret, aret;
+ f = full_tests[i].wildcard;
+ t = full_tests[i].target;
+ eret = full_tests[i].expected_result;
+ aret = wc_match(f, t);
+ if (aret != eret) {
+ printf("failed test: /%s/ against /%s/ returned %d not %d\n",
+ full_tests[i].wildcard, full_tests[i].target,
+ aret, eret);
+ fails++;
+ } else
+ passes++;
+ }
+
+ printf("passed %d, failed %d\n", passes, fails);
+
+ return 0;
+}
+
+#endif
diff --git a/tools/plink/wincons.c b/tools/plink/wincons.c
index 41bbc130d..508be3f8d 100644
--- a/tools/plink/wincons.c
+++ b/tools/plink/wincons.c
@@ -1,428 +1,428 @@
-/*
- * wincons.c - various interactive-prompt routines shared between
- * the Windows console PuTTY tools
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-
-#include "putty.h"
-#include "storage.h"
-#include "ssh.h"
-
-int console_batch_mode = FALSE;
-
-static void *console_logctx = NULL;
-
-/*
- * Clean up and exit.
- */
-void cleanup_exit(int code)
-{
- /*
- * Clean up.
- */
- sk_cleanup();
-
- random_save_seed();
-#ifdef MSCRYPTOAPI
- crypto_wrapup();
-#endif
-
- exit(code);
-}
-
-void set_busy_status(void *frontend, int status)
-{
-}
-
-void notify_remote_exit(void *frontend)
-{
-}
-
-void timer_change_notify(long next)
-{
-}
-
-int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
- char *keystr, char *fingerprint,
- void (*callback)(void *ctx, int result), void *ctx)
-{
- int ret;
- HANDLE hin;
- DWORD savemode, i;
-
- static const char absentmsg_batch[] =
- "The server's host key is not cached in the registry. You\n"
- "have no guarantee that the server is the computer you\n"
- "think it is.\n"
- "The server's %s key fingerprint is:\n"
- "%s\n"
- "Connection abandoned.\n";
- static const char absentmsg[] =
- "The server's host key is not cached in the registry. You\n"
- "have no guarantee that the server is the computer you\n"
- "think it is.\n"
- "The server's %s key fingerprint is:\n"
- "%s\n"
- "If you trust this host, enter \"y\" to add the key to\n"
- "PuTTY's cache and carry on connecting.\n"
- "If you want to carry on connecting just once, without\n"
- "adding the key to the cache, enter \"n\".\n"
- "If you do not trust this host, press Return to abandon the\n"
- "connection.\n"
- "Store key in cache? (y/n) ";
-
- static const char wrongmsg_batch[] =
- "WARNING - POTENTIAL SECURITY BREACH!\n"
- "The server's host key does not match the one PuTTY has\n"
- "cached in the registry. This means that either the\n"
- "server administrator has changed the host key, or you\n"
- "have actually connected to another computer pretending\n"
- "to be the server.\n"
- "The new %s key fingerprint is:\n"
- "%s\n"
- "Connection abandoned.\n";
- static const char wrongmsg[] =
- "WARNING - POTENTIAL SECURITY BREACH!\n"
- "The server's host key does not match the one PuTTY has\n"
- "cached in the registry. This means that either the\n"
- "server administrator has changed the host key, or you\n"
- "have actually connected to another computer pretending\n"
- "to be the server.\n"
- "The new %s key fingerprint is:\n"
- "%s\n"
- "If you were expecting this change and trust the new key,\n"
- "enter \"y\" to update PuTTY's cache and continue connecting.\n"
- "If you want to carry on connecting but without updating\n"
- "the cache, enter \"n\".\n"
- "If you want to abandon the connection completely, press\n"
- "Return to cancel. Pressing Return is the ONLY guaranteed\n"
- "safe choice.\n"
- "Update cached key? (y/n, Return cancels connection) ";
-
- static const char abandoned[] = "Connection abandoned.\n";
-
- char line[32];
-
- /*
- * Verify the key against the registry.
- */
- ret = verify_host_key(host, port, keytype, keystr);
-
- if (ret == 0) /* success - key matched OK */
- return 1;
-
- if (ret == 2) { /* key was different */
- if (console_batch_mode) {
- fprintf(stderr, wrongmsg_batch, keytype, fingerprint);
- return 0;
- }
- fprintf(stderr, wrongmsg, keytype, fingerprint);
- fflush(stderr);
- }
- if (ret == 1) { /* key was absent */
- if (console_batch_mode) {
- fprintf(stderr, absentmsg_batch, keytype, fingerprint);
- return 0;
- }
- fprintf(stderr, absentmsg, keytype, fingerprint);
- fflush(stderr);
- }
-
- hin = GetStdHandle(STD_INPUT_HANDLE);
- GetConsoleMode(hin, &savemode);
- SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
- ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
- ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
- SetConsoleMode(hin, savemode);
-
- if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
- if (line[0] == 'y' || line[0] == 'Y')
- store_host_key(host, port, keytype, keystr);
- return 1;
- } else {
- fprintf(stderr, abandoned);
- return 0;
- }
-}
-
-void update_specials_menu(void *frontend)
-{
-}
-
-/*
- * Ask whether the selected algorithm is acceptable (since it was
- * below the configured 'warn' threshold).
- */
-int askalg(void *frontend, const char *algtype, const char *algname,
- void (*callback)(void *ctx, int result), void *ctx)
-{
- HANDLE hin;
- DWORD savemode, i;
-
- static const char msg[] =
- "The first %s supported by the server is\n"
- "%s, which is below the configured warning threshold.\n"
- "Continue with connection? (y/n) ";
- static const char msg_batch[] =
- "The first %s supported by the server is\n"
- "%s, which is below the configured warning threshold.\n"
- "Connection abandoned.\n";
- static const char abandoned[] = "Connection abandoned.\n";
-
- char line[32];
-
- if (console_batch_mode) {
- fprintf(stderr, msg_batch, algtype, algname);
- return 0;
- }
-
- fprintf(stderr, msg, algtype, algname);
- fflush(stderr);
-
- hin = GetStdHandle(STD_INPUT_HANDLE);
- GetConsoleMode(hin, &savemode);
- SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
- ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
- ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
- SetConsoleMode(hin, savemode);
-
- if (line[0] == 'y' || line[0] == 'Y') {
- return 1;
- } else {
- fprintf(stderr, abandoned);
- return 0;
- }
-}
-
-/*
- * Ask whether to wipe a session log file before writing to it.
- * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
- */
-int askappend(void *frontend, Filename *filename,
- void (*callback)(void *ctx, int result), void *ctx)
-{
- HANDLE hin;
- DWORD savemode, i;
-
- static const char msgtemplate[] =
- "The session log file \"%.*s\" already exists.\n"
- "You can overwrite it with a new session log,\n"
- "append your session log to the end of it,\n"
- "or disable session logging for this session.\n"
- "Enter \"y\" to wipe the file, \"n\" to append to it,\n"
- "or just press Return to disable logging.\n"
- "Wipe the log file? (y/n, Return cancels logging) ";
-
- static const char msgtemplate_batch[] =
- "The session log file \"%.*s\" already exists.\n"
- "Logging will not be enabled.\n";
-
- char line[32];
-
- if (console_batch_mode) {
- fprintf(stderr, msgtemplate_batch, FILENAME_MAX, filename->path);
- fflush(stderr);
- return 0;
- }
- fprintf(stderr, msgtemplate, FILENAME_MAX, filename->path);
- fflush(stderr);
-
- hin = GetStdHandle(STD_INPUT_HANDLE);
- GetConsoleMode(hin, &savemode);
- SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
- ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
- ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
- SetConsoleMode(hin, savemode);
-
- if (line[0] == 'y' || line[0] == 'Y')
- return 2;
- else if (line[0] == 'n' || line[0] == 'N')
- return 1;
- else
- return 0;
-}
-
-/*
- * Warn about the obsolescent key file format.
- *
- * Uniquely among these functions, this one does _not_ expect a
- * frontend handle. This means that if PuTTY is ported to a
- * platform which requires frontend handles, this function will be
- * an anomaly. Fortunately, the problem it addresses will not have
- * been present on that platform, so it can plausibly be
- * implemented as an empty function.
- */
-void old_keyfile_warning(void)
-{
- static const char message[] =
- "You are loading an SSH-2 private key which has an\n"
- "old version of the file format. This means your key\n"
- "file is not fully tamperproof. Future versions of\n"
- "PuTTY may stop supporting this private key format,\n"
- "so we recommend you convert your key to the new\n"
- "format.\n"
- "\n"
- "Once the key is loaded into PuTTYgen, you can perform\n"
- "this conversion simply by saving it again.\n";
-
- fputs(message, stderr);
-}
-
-/*
- * Display the fingerprints of the PGP Master Keys to the user.
- */
-void pgp_fingerprints(void)
-{
- fputs("These are the fingerprints of the PuTTY PGP Master Keys. They can\n"
- "be used to establish a trust path from this executable to another\n"
- "one. See the manual for more information.\n"
- "(Note: these fingerprints have nothing to do with SSH!)\n"
- "\n"
- "PuTTY Master Key (RSA), 1024-bit:\n"
- " " PGP_RSA_MASTER_KEY_FP "\n"
- "PuTTY Master Key (DSA), 1024-bit:\n"
- " " PGP_DSA_MASTER_KEY_FP "\n", stdout);
-}
-
-void console_provide_logctx(void *logctx)
-{
- console_logctx = logctx;
-}
-
-void logevent(void *frontend, const char *string)
-{
- log_eventlog(console_logctx, string);
-}
-
-static void console_data_untrusted(HANDLE hout, const char *data, int len)
-{
- DWORD dummy;
- /* FIXME: control-character filtering */
- WriteFile(hout, data, len, &dummy, NULL);
-}
-
-int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
-{
- HANDLE hin, hout;
- size_t curr_prompt;
-
- /*
- * Zero all the results, in case we abort half-way through.
- */
- {
- int i;
- for (i = 0; i < (int)p->n_prompts; i++)
- prompt_set_result(p->prompts[i], "");
- }
-
- /*
- * The prompts_t might contain a message to be displayed but no
- * actual prompt. More usually, though, it will contain
- * questions that the user needs to answer, in which case we
- * need to ensure that we're able to get the answers.
- */
- if (p->n_prompts) {
- if (console_batch_mode)
- return 0;
- hin = GetStdHandle(STD_INPUT_HANDLE);
- if (hin == INVALID_HANDLE_VALUE) {
- fprintf(stderr, "Cannot get standard input handle\n");
- cleanup_exit(1);
- }
- }
-
- /*
- * And if we have anything to print, we need standard output.
- */
- if ((p->name_reqd && p->name) || p->instruction || p->n_prompts) {
- hout = GetStdHandle(STD_OUTPUT_HANDLE);
- if (hout == INVALID_HANDLE_VALUE) {
- fprintf(stderr, "Cannot get standard output handle\n");
- cleanup_exit(1);
- }
- }
-
- /*
- * Preamble.
- */
- /* We only print the `name' caption if we have to... */
- if (p->name_reqd && p->name) {
- size_t l = strlen(p->name);
- console_data_untrusted(hout, p->name, l);
- if (p->name[l-1] != '\n')
- console_data_untrusted(hout, "\n", 1);
- }
- /* ...but we always print any `instruction'. */
- if (p->instruction) {
- size_t l = strlen(p->instruction);
- console_data_untrusted(hout, p->instruction, l);
- if (p->instruction[l-1] != '\n')
- console_data_untrusted(hout, "\n", 1);
- }
-
- for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) {
-
- DWORD savemode, newmode;
- int len;
- prompt_t *pr = p->prompts[curr_prompt];
-
- GetConsoleMode(hin, &savemode);
- newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
- if (!pr->echo)
- newmode &= ~ENABLE_ECHO_INPUT;
- else
- newmode |= ENABLE_ECHO_INPUT;
- SetConsoleMode(hin, newmode);
-
- console_data_untrusted(hout, pr->prompt, strlen(pr->prompt));
-
- len = 0;
- while (1) {
- DWORD ret = 0;
- BOOL r;
-
- prompt_ensure_result_size(pr, len * 5 / 4 + 512);
-
- r = ReadFile(hin, pr->result + len, pr->resultsize - len - 1,
- &ret, NULL);
-
- if (!r || ret == 0) {
- len = -1;
- break;
- }
- len += ret;
- if (pr->result[len - 1] == '\n') {
- len--;
- if (pr->result[len - 1] == '\r')
- len--;
- break;
- }
- }
-
- SetConsoleMode(hin, savemode);
-
- if (!pr->echo) {
- DWORD dummy;
- WriteFile(hout, "\r\n", 2, &dummy, NULL);
- }
-
- if (len < 0) {
- return 0; /* failure due to read error */
- }
-
- pr->result[len] = '\0';
- }
-
- return 1; /* success */
-}
-
-void frontend_keypress(void *handle)
-{
- /*
- * This is nothing but a stub, in console code.
- */
- return;
-}
+/*
+ * wincons.c - various interactive-prompt routines shared between
+ * the Windows console PuTTY tools
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include "putty.h"
+#include "storage.h"
+#include "ssh.h"
+
+int console_batch_mode = FALSE;
+
+static void *console_logctx = NULL;
+
+/*
+ * Clean up and exit.
+ */
+void cleanup_exit(int code)
+{
+ /*
+ * Clean up.
+ */
+ sk_cleanup();
+
+ random_save_seed();
+#ifdef MSCRYPTOAPI
+ crypto_wrapup();
+#endif
+
+ exit(code);
+}
+
+void set_busy_status(void *frontend, int status)
+{
+}
+
+void notify_remote_exit(void *frontend)
+{
+}
+
+void timer_change_notify(unsigned long next)
+{
+}
+
+int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
+ char *keystr, char *fingerprint,
+ void (*callback)(void *ctx, int result), void *ctx)
+{
+ int ret;
+ HANDLE hin;
+ DWORD savemode, i;
+
+ static const char absentmsg_batch[] =
+ "The server's host key is not cached in the registry. You\n"
+ "have no guarantee that the server is the computer you\n"
+ "think it is.\n"
+ "The server's %s key fingerprint is:\n"
+ "%s\n"
+ "Connection abandoned.\n";
+ static const char absentmsg[] =
+ "The server's host key is not cached in the registry. You\n"
+ "have no guarantee that the server is the computer you\n"
+ "think it is.\n"
+ "The server's %s key fingerprint is:\n"
+ "%s\n"
+ "If you trust this host, enter \"y\" to add the key to\n"
+ "PuTTY's cache and carry on connecting.\n"
+ "If you want to carry on connecting just once, without\n"
+ "adding the key to the cache, enter \"n\".\n"
+ "If you do not trust this host, press Return to abandon the\n"
+ "connection.\n"
+ "Store key in cache? (y/n) ";
+
+ static const char wrongmsg_batch[] =
+ "WARNING - POTENTIAL SECURITY BREACH!\n"
+ "The server's host key does not match the one PuTTY has\n"
+ "cached in the registry. This means that either the\n"
+ "server administrator has changed the host key, or you\n"
+ "have actually connected to another computer pretending\n"
+ "to be the server.\n"
+ "The new %s key fingerprint is:\n"
+ "%s\n"
+ "Connection abandoned.\n";
+ static const char wrongmsg[] =
+ "WARNING - POTENTIAL SECURITY BREACH!\n"
+ "The server's host key does not match the one PuTTY has\n"
+ "cached in the registry. This means that either the\n"
+ "server administrator has changed the host key, or you\n"
+ "have actually connected to another computer pretending\n"
+ "to be the server.\n"
+ "The new %s key fingerprint is:\n"
+ "%s\n"
+ "If you were expecting this change and trust the new key,\n"
+ "enter \"y\" to update PuTTY's cache and continue connecting.\n"
+ "If you want to carry on connecting but without updating\n"
+ "the cache, enter \"n\".\n"
+ "If you want to abandon the connection completely, press\n"
+ "Return to cancel. Pressing Return is the ONLY guaranteed\n"
+ "safe choice.\n"
+ "Update cached key? (y/n, Return cancels connection) ";
+
+ static const char abandoned[] = "Connection abandoned.\n";
+
+ char line[32];
+
+ /*
+ * Verify the key against the registry.
+ */
+ ret = verify_host_key(host, port, keytype, keystr);
+
+ if (ret == 0) /* success - key matched OK */
+ return 1;
+
+ if (ret == 2) { /* key was different */
+ if (console_batch_mode) {
+ fprintf(stderr, wrongmsg_batch, keytype, fingerprint);
+ return 0;
+ }
+ fprintf(stderr, wrongmsg, keytype, fingerprint);
+ fflush(stderr);
+ }
+ if (ret == 1) { /* key was absent */
+ if (console_batch_mode) {
+ fprintf(stderr, absentmsg_batch, keytype, fingerprint);
+ return 0;
+ }
+ fprintf(stderr, absentmsg, keytype, fingerprint);
+ fflush(stderr);
+ }
+
+ hin = GetStdHandle(STD_INPUT_HANDLE);
+ GetConsoleMode(hin, &savemode);
+ SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
+ ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
+ ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
+ SetConsoleMode(hin, savemode);
+
+ if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
+ if (line[0] == 'y' || line[0] == 'Y')
+ store_host_key(host, port, keytype, keystr);
+ return 1;
+ } else {
+ fprintf(stderr, abandoned);
+ return 0;
+ }
+}
+
+void update_specials_menu(void *frontend)
+{
+}
+
+/*
+ * Ask whether the selected algorithm is acceptable (since it was
+ * below the configured 'warn' threshold).
+ */
+int askalg(void *frontend, const char *algtype, const char *algname,
+ void (*callback)(void *ctx, int result), void *ctx)
+{
+ HANDLE hin;
+ DWORD savemode, i;
+
+ static const char msg[] =
+ "The first %s supported by the server is\n"
+ "%s, which is below the configured warning threshold.\n"
+ "Continue with connection? (y/n) ";
+ static const char msg_batch[] =
+ "The first %s supported by the server is\n"
+ "%s, which is below the configured warning threshold.\n"
+ "Connection abandoned.\n";
+ static const char abandoned[] = "Connection abandoned.\n";
+
+ char line[32];
+
+ if (console_batch_mode) {
+ fprintf(stderr, msg_batch, algtype, algname);
+ return 0;
+ }
+
+ fprintf(stderr, msg, algtype, algname);
+ fflush(stderr);
+
+ hin = GetStdHandle(STD_INPUT_HANDLE);
+ GetConsoleMode(hin, &savemode);
+ SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
+ ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
+ ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
+ SetConsoleMode(hin, savemode);
+
+ if (line[0] == 'y' || line[0] == 'Y') {
+ return 1;
+ } else {
+ fprintf(stderr, abandoned);
+ return 0;
+ }
+}
+
+/*
+ * Ask whether to wipe a session log file before writing to it.
+ * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
+ */
+int askappend(void *frontend, Filename *filename,
+ void (*callback)(void *ctx, int result), void *ctx)
+{
+ HANDLE hin;
+ DWORD savemode, i;
+
+ static const char msgtemplate[] =
+ "The session log file \"%.*s\" already exists.\n"
+ "You can overwrite it with a new session log,\n"
+ "append your session log to the end of it,\n"
+ "or disable session logging for this session.\n"
+ "Enter \"y\" to wipe the file, \"n\" to append to it,\n"
+ "or just press Return to disable logging.\n"
+ "Wipe the log file? (y/n, Return cancels logging) ";
+
+ static const char msgtemplate_batch[] =
+ "The session log file \"%.*s\" already exists.\n"
+ "Logging will not be enabled.\n";
+
+ char line[32];
+
+ if (console_batch_mode) {
+ fprintf(stderr, msgtemplate_batch, FILENAME_MAX, filename->path);
+ fflush(stderr);
+ return 0;
+ }
+ fprintf(stderr, msgtemplate, FILENAME_MAX, filename->path);
+ fflush(stderr);
+
+ hin = GetStdHandle(STD_INPUT_HANDLE);
+ GetConsoleMode(hin, &savemode);
+ SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
+ ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
+ ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
+ SetConsoleMode(hin, savemode);
+
+ if (line[0] == 'y' || line[0] == 'Y')
+ return 2;
+ else if (line[0] == 'n' || line[0] == 'N')
+ return 1;
+ else
+ return 0;
+}
+
+/*
+ * Warn about the obsolescent key file format.
+ *
+ * Uniquely among these functions, this one does _not_ expect a
+ * frontend handle. This means that if PuTTY is ported to a
+ * platform which requires frontend handles, this function will be
+ * an anomaly. Fortunately, the problem it addresses will not have
+ * been present on that platform, so it can plausibly be
+ * implemented as an empty function.
+ */
+void old_keyfile_warning(void)
+{
+ static const char message[] =
+ "You are loading an SSH-2 private key which has an\n"
+ "old version of the file format. This means your key\n"
+ "file is not fully tamperproof. Future versions of\n"
+ "PuTTY may stop supporting this private key format,\n"
+ "so we recommend you convert your key to the new\n"
+ "format.\n"
+ "\n"
+ "Once the key is loaded into PuTTYgen, you can perform\n"
+ "this conversion simply by saving it again.\n";
+
+ fputs(message, stderr);
+}
+
+/*
+ * Display the fingerprints of the PGP Master Keys to the user.
+ */
+void pgp_fingerprints(void)
+{
+ fputs("These are the fingerprints of the PuTTY PGP Master Keys. They can\n"
+ "be used to establish a trust path from this executable to another\n"
+ "one. See the manual for more information.\n"
+ "(Note: these fingerprints have nothing to do with SSH!)\n"
+ "\n"
+ "PuTTY Master Key (RSA), 1024-bit:\n"
+ " " PGP_RSA_MASTER_KEY_FP "\n"
+ "PuTTY Master Key (DSA), 1024-bit:\n"
+ " " PGP_DSA_MASTER_KEY_FP "\n", stdout);
+}
+
+void console_provide_logctx(void *logctx)
+{
+ console_logctx = logctx;
+}
+
+void logevent(void *frontend, const char *string)
+{
+ log_eventlog(console_logctx, string);
+}
+
+static void console_data_untrusted(HANDLE hout, const char *data, int len)
+{
+ DWORD dummy;
+ /* FIXME: control-character filtering */
+ WriteFile(hout, data, len, &dummy, NULL);
+}
+
+int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+{
+ HANDLE hin, hout;
+ size_t curr_prompt;
+
+ /*
+ * Zero all the results, in case we abort half-way through.
+ */
+ {
+ int i;
+ for (i = 0; i < (int)p->n_prompts; i++)
+ prompt_set_result(p->prompts[i], "");
+ }
+
+ /*
+ * The prompts_t might contain a message to be displayed but no
+ * actual prompt. More usually, though, it will contain
+ * questions that the user needs to answer, in which case we
+ * need to ensure that we're able to get the answers.
+ */
+ if (p->n_prompts) {
+ if (console_batch_mode)
+ return 0;
+ hin = GetStdHandle(STD_INPUT_HANDLE);
+ if (hin == INVALID_HANDLE_VALUE) {
+ fprintf(stderr, "Cannot get standard input handle\n");
+ cleanup_exit(1);
+ }
+ }
+
+ /*
+ * And if we have anything to print, we need standard output.
+ */
+ if ((p->name_reqd && p->name) || p->instruction || p->n_prompts) {
+ hout = GetStdHandle(STD_OUTPUT_HANDLE);
+ if (hout == INVALID_HANDLE_VALUE) {
+ fprintf(stderr, "Cannot get standard output handle\n");
+ cleanup_exit(1);
+ }
+ }
+
+ /*
+ * Preamble.
+ */
+ /* We only print the `name' caption if we have to... */
+ if (p->name_reqd && p->name) {
+ size_t l = strlen(p->name);
+ console_data_untrusted(hout, p->name, l);
+ if (p->name[l-1] != '\n')
+ console_data_untrusted(hout, "\n", 1);
+ }
+ /* ...but we always print any `instruction'. */
+ if (p->instruction) {
+ size_t l = strlen(p->instruction);
+ console_data_untrusted(hout, p->instruction, l);
+ if (p->instruction[l-1] != '\n')
+ console_data_untrusted(hout, "\n", 1);
+ }
+
+ for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) {
+
+ DWORD savemode, newmode;
+ int len;
+ prompt_t *pr = p->prompts[curr_prompt];
+
+ GetConsoleMode(hin, &savemode);
+ newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
+ if (!pr->echo)
+ newmode &= ~ENABLE_ECHO_INPUT;
+ else
+ newmode |= ENABLE_ECHO_INPUT;
+ SetConsoleMode(hin, newmode);
+
+ console_data_untrusted(hout, pr->prompt, strlen(pr->prompt));
+
+ len = 0;
+ while (1) {
+ DWORD ret = 0;
+ BOOL r;
+
+ prompt_ensure_result_size(pr, len * 5 / 4 + 512);
+
+ r = ReadFile(hin, pr->result + len, pr->resultsize - len - 1,
+ &ret, NULL);
+
+ if (!r || ret == 0) {
+ len = -1;
+ break;
+ }
+ len += ret;
+ if (pr->result[len - 1] == '\n') {
+ len--;
+ if (pr->result[len - 1] == '\r')
+ len--;
+ break;
+ }
+ }
+
+ SetConsoleMode(hin, savemode);
+
+ if (!pr->echo) {
+ DWORD dummy;
+ WriteFile(hout, "\r\n", 2, &dummy, NULL);
+ }
+
+ if (len < 0) {
+ return 0; /* failure due to read error */
+ }
+
+ pr->result[len] = '\0';
+ }
+
+ return 1; /* success */
+}
+
+void frontend_keypress(void *handle)
+{
+ /*
+ * This is nothing but a stub, in console code.
+ */
+ return;
+}
diff --git a/tools/plink/winhandl.c b/tools/plink/winhandl.c
index 06c2a6a07..b15d1f262 100644
--- a/tools/plink/winhandl.c
+++ b/tools/plink/winhandl.c
@@ -1,620 +1,683 @@
-/*
- * winhandl.c: Module to give Windows front ends the general
- * ability to deal with consoles, pipes, serial ports, or any other
- * type of data stream accessed through a Windows API HANDLE rather
- * than a WinSock SOCKET.
- *
- * We do this by spawning a subthread to continuously try to read
- * from the handle. Every time a read successfully returns some
- * data, the subthread sets an event object which is picked up by
- * the main thread, and the main thread then sets an event in
- * return to instruct the subthread to resume reading.
- *
- * Output works precisely the other way round, in a second
- * subthread. The output subthread should not be attempting to
- * write all the time, because it hasn't always got data _to_
- * write; so the output thread waits for an event object notifying
- * it to _attempt_ a write, and then it sets an event in return
- * when one completes.
- *
- * (It's terribly annoying having to spawn a subthread for each
- * direction of each handle. Technically it isn't necessary for
- * serial ports, since we could use overlapped I/O within the main
- * thread and wait directly on the event objects in the OVERLAPPED
- * structures. However, we can't use this trick for some types of
- * file handle at all - for some reason Windows restricts use of
- * OVERLAPPED to files which were opened with the overlapped flag -
- * and so we must use threads for those. This being the case, it's
- * simplest just to use threads for everything rather than trying
- * to keep track of multiple completely separate mechanisms.)
- */
-
-#include <assert.h>
-
-#include "putty.h"
-
-/* ----------------------------------------------------------------------
- * Generic definitions.
- */
-
-/*
- * Maximum amount of backlog we will allow to build up on an input
- * handle before we stop reading from it.
- */
-#define MAX_BACKLOG 32768
-
-struct handle_generic {
- /*
- * Initial fields common to both handle_input and handle_output
- * structures.
- *
- * The three HANDLEs are set up at initialisation time and are
- * thereafter read-only to both main thread and subthread.
- * `moribund' is only used by the main thread; `done' is
- * written by the main thread before signalling to the
- * subthread. `defunct' and `busy' are used only by the main
- * thread.
- */
- HANDLE h; /* the handle itself */
- HANDLE ev_to_main; /* event used to signal main thread */
- HANDLE ev_from_main; /* event used to signal back to us */
- int moribund; /* are we going to kill this soon? */
- int done; /* request subthread to terminate */
- int defunct; /* has the subthread already gone? */
- int busy; /* operation currently in progress? */
- void *privdata; /* for client to remember who they are */
-};
-
-/* ----------------------------------------------------------------------
- * Input threads.
- */
-
-/*
- * Data required by an input thread.
- */
-struct handle_input {
- /*
- * Copy of the handle_generic structure.
- */
- HANDLE h; /* the handle itself */
- HANDLE ev_to_main; /* event used to signal main thread */
- HANDLE ev_from_main; /* event used to signal back to us */
- int moribund; /* are we going to kill this soon? */
- int done; /* request subthread to terminate */
- int defunct; /* has the subthread already gone? */
- int busy; /* operation currently in progress? */
- void *privdata; /* for client to remember who they are */
-
- /*
- * Data set at initialisation and then read-only.
- */
- int flags;
-
- /*
- * Data set by the input thread before signalling ev_to_main,
- * and read by the main thread after receiving that signal.
- */
- char buffer[4096]; /* the data read from the handle */
- DWORD len; /* how much data that was */
- int readerr; /* lets us know about read errors */
-
- /*
- * Callback function called by this module when data arrives on
- * an input handle.
- */
- handle_inputfn_t gotdata;
-};
-
-/*
- * The actual thread procedure for an input thread.
- */
-static DWORD WINAPI handle_input_threadfunc(void *param)
-{
- struct handle_input *ctx = (struct handle_input *) param;
- OVERLAPPED ovl, *povl;
- HANDLE oev;
- int readret, readlen;
-
- if (ctx->flags & HANDLE_FLAG_OVERLAPPED) {
- povl = &ovl;
- oev = CreateEvent(NULL, TRUE, FALSE, NULL);
- } else {
- povl = NULL;
- }
-
- if (ctx->flags & HANDLE_FLAG_UNITBUFFER)
- readlen = 1;
- else
- readlen = sizeof(ctx->buffer);
-
- while (1) {
- if (povl) {
- memset(povl, 0, sizeof(OVERLAPPED));
- povl->hEvent = oev;
- }
- readret = ReadFile(ctx->h, ctx->buffer,readlen, &ctx->len, povl);
- if (!readret)
- ctx->readerr = GetLastError();
- else
- ctx->readerr = 0;
- if (povl && !readret && ctx->readerr == ERROR_IO_PENDING) {
- WaitForSingleObject(povl->hEvent, INFINITE);
- readret = GetOverlappedResult(ctx->h, povl, &ctx->len, FALSE);
- if (!readret)
- ctx->readerr = GetLastError();
- else
- ctx->readerr = 0;
- }
-
- if (!readret) {
- /*
- * Windows apparently sends ERROR_BROKEN_PIPE when a
- * pipe we're reading from is closed normally from the
- * writing end. This is ludicrous; if that situation
- * isn't a natural EOF, _nothing_ is. So if we get that
- * particular error, we pretend it's EOF.
- */
- if (ctx->readerr == ERROR_BROKEN_PIPE)
- ctx->readerr = 0;
- ctx->len = 0;
- }
-
- if (readret && ctx->len == 0 &&
- (ctx->flags & HANDLE_FLAG_IGNOREEOF))
- continue;
-
- SetEvent(ctx->ev_to_main);
-
- if (!ctx->len)
- break;
-
- WaitForSingleObject(ctx->ev_from_main, INFINITE);
- if (ctx->done)
- break; /* main thread told us to shut down */
- }
-
- if (povl)
- CloseHandle(oev);
-
- return 0;
-}
-
-/*
- * This is called after a succcessful read, or from the
- * `unthrottle' function. It decides whether or not to begin a new
- * read operation.
- */
-static void handle_throttle(struct handle_input *ctx, int backlog)
-{
- if (ctx->defunct)
- return;
-
- /*
- * If there's a read operation already in progress, do nothing:
- * when that completes, we'll come back here and be in a
- * position to make a better decision.
- */
- if (ctx->busy)
- return;
-
- /*
- * Otherwise, we must decide whether to start a new read based
- * on the size of the backlog.
- */
- if (backlog < MAX_BACKLOG) {
- SetEvent(ctx->ev_from_main);
- ctx->busy = TRUE;
- }
-}
-
-/* ----------------------------------------------------------------------
- * Output threads.
- */
-
-/*
- * Data required by an output thread.
- */
-struct handle_output {
- /*
- * Copy of the handle_generic structure.
- */
- HANDLE h; /* the handle itself */
- HANDLE ev_to_main; /* event used to signal main thread */
- HANDLE ev_from_main; /* event used to signal back to us */
- int moribund; /* are we going to kill this soon? */
- int done; /* request subthread to terminate */
- int defunct; /* has the subthread already gone? */
- int busy; /* operation currently in progress? */
- void *privdata; /* for client to remember who they are */
-
- /*
- * Data set at initialisation and then read-only.
- */
- int flags;
-
- /*
- * Data set by the main thread before signalling ev_from_main,
- * and read by the input thread after receiving that signal.
- */
- char *buffer; /* the data to write */
- DWORD len; /* how much data there is */
-
- /*
- * Data set by the input thread before signalling ev_to_main,
- * and read by the main thread after receiving that signal.
- */
- DWORD lenwritten; /* how much data we actually wrote */
- int writeerr; /* return value from WriteFile */
-
- /*
- * Data only ever read or written by the main thread.
- */
- bufchain queued_data; /* data still waiting to be written */
- enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
-
- /*
- * Callback function called when the backlog in the bufchain
- * drops.
- */
- handle_outputfn_t sentdata;
-};
-
-static DWORD WINAPI handle_output_threadfunc(void *param)
-{
- struct handle_output *ctx = (struct handle_output *) param;
- OVERLAPPED ovl, *povl;
- HANDLE oev;
- int writeret;
-
- if (ctx->flags & HANDLE_FLAG_OVERLAPPED) {
- povl = &ovl;
- oev = CreateEvent(NULL, TRUE, FALSE, NULL);
- } else {
- povl = NULL;
- }
-
- while (1) {
- WaitForSingleObject(ctx->ev_from_main, INFINITE);
- if (ctx->done) {
- SetEvent(ctx->ev_to_main);
- break;
- }
- if (povl) {
- memset(povl, 0, sizeof(OVERLAPPED));
- povl->hEvent = oev;
- }
-
- writeret = WriteFile(ctx->h, ctx->buffer, ctx->len,
- &ctx->lenwritten, povl);
- if (!writeret)
- ctx->writeerr = GetLastError();
- else
- ctx->writeerr = 0;
- if (povl && !writeret && GetLastError() == ERROR_IO_PENDING) {
- writeret = GetOverlappedResult(ctx->h, povl,
- &ctx->lenwritten, TRUE);
- if (!writeret)
- ctx->writeerr = GetLastError();
- else
- ctx->writeerr = 0;
- }
-
- SetEvent(ctx->ev_to_main);
- if (!writeret)
- break;
- }
-
- if (povl)
- CloseHandle(oev);
-
- return 0;
-}
-
-static void handle_try_output(struct handle_output *ctx)
-{
- void *senddata;
- int sendlen;
-
- if (!ctx->busy && bufchain_size(&ctx->queued_data)) {
- bufchain_prefix(&ctx->queued_data, &senddata, &sendlen);
- ctx->buffer = senddata;
- ctx->len = sendlen;
- SetEvent(ctx->ev_from_main);
- ctx->busy = TRUE;
- } else if (!ctx->busy && bufchain_size(&ctx->queued_data) == 0 &&
- ctx->outgoingeof == EOF_PENDING) {
- CloseHandle(ctx->h);
- ctx->h = INVALID_HANDLE_VALUE;
- ctx->outgoingeof = EOF_SENT;
- }
-}
-
-/* ----------------------------------------------------------------------
- * Unified code handling both input and output threads.
- */
-
-struct handle {
- int output;
- union {
- struct handle_generic g;
- struct handle_input i;
- struct handle_output o;
- } u;
-};
-
-static tree234 *handles_by_evtomain;
-
-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)
- return -1;
- else if ((unsigned)a->u.g.ev_to_main > (unsigned)b->u.g.ev_to_main)
- return +1;
- else
- return 0;
-}
-
-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)
- return -1;
- else if ((unsigned)*a > (unsigned)b->u.g.ev_to_main)
- return +1;
- else
- return 0;
-}
-
-struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata,
- void *privdata, int flags)
-{
- struct handle *h = snew(struct handle);
- DWORD in_threadid; /* required for Win9x */
-
- h->output = FALSE;
- h->u.i.h = handle;
- h->u.i.ev_to_main = CreateEvent(NULL, FALSE, FALSE, NULL);
- h->u.i.ev_from_main = CreateEvent(NULL, FALSE, FALSE, NULL);
- h->u.i.gotdata = gotdata;
- h->u.i.defunct = FALSE;
- h->u.i.moribund = FALSE;
- h->u.i.done = FALSE;
- h->u.i.privdata = privdata;
- h->u.i.flags = flags;
-
- if (!handles_by_evtomain)
- handles_by_evtomain = newtree234(handle_cmp_evtomain);
- add234(handles_by_evtomain, h);
-
- CreateThread(NULL, 0, handle_input_threadfunc,
- &h->u.i, 0, &in_threadid);
- h->u.i.busy = TRUE;
-
- return h;
-}
-
-struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata,
- void *privdata, int flags)
-{
- struct handle *h = snew(struct handle);
- DWORD out_threadid; /* required for Win9x */
-
- h->output = TRUE;
- h->u.o.h = handle;
- h->u.o.ev_to_main = CreateEvent(NULL, FALSE, FALSE, NULL);
- h->u.o.ev_from_main = CreateEvent(NULL, FALSE, FALSE, NULL);
- h->u.o.busy = FALSE;
- h->u.o.defunct = FALSE;
- h->u.o.moribund = FALSE;
- h->u.o.done = FALSE;
- h->u.o.privdata = privdata;
- bufchain_init(&h->u.o.queued_data);
- h->u.o.outgoingeof = EOF_NO;
- h->u.o.sentdata = sentdata;
- h->u.o.flags = flags;
-
- if (!handles_by_evtomain)
- handles_by_evtomain = newtree234(handle_cmp_evtomain);
- add234(handles_by_evtomain, h);
-
- CreateThread(NULL, 0, handle_output_threadfunc,
- &h->u.o, 0, &out_threadid);
-
- return h;
-}
-
-int handle_write(struct handle *h, const void *data, int len)
-{
- assert(h->output);
- assert(h->u.o.outgoingeof == EOF_NO);
- bufchain_add(&h->u.o.queued_data, data, len);
- handle_try_output(&h->u.o);
- return bufchain_size(&h->u.o.queued_data);
-}
-
-void handle_write_eof(struct handle *h)
-{
- /*
- * This function is called when we want to proactively send an
- * end-of-file notification on the handle. We can only do this by
- * actually closing the handle - so never call this on a
- * bidirectional handle if we're still interested in its incoming
- * direction!
- */
- assert(h->output);
- if (!h->u.o.outgoingeof == EOF_NO) {
- h->u.o.outgoingeof = EOF_PENDING;
- handle_try_output(&h->u.o);
- }
-}
-
-HANDLE *handle_get_events(int *nevents)
-{
- HANDLE *ret;
- struct handle *h;
- int i, n, size;
-
- /*
- * Go through our tree counting the handle objects currently
- * engaged in useful activity.
- */
- ret = NULL;
- n = size = 0;
- if (handles_by_evtomain) {
- for (i = 0; (h = index234(handles_by_evtomain, i)) != NULL; i++) {
- if (h->u.g.busy) {
- if (n >= size) {
- size += 32;
- ret = sresize(ret, size, HANDLE);
- }
- ret[n++] = h->u.g.ev_to_main;
- }
- }
- }
-
- *nevents = n;
- return ret;
-}
-
-static void handle_destroy(struct handle *h)
-{
- if (h->output)
- bufchain_clear(&h->u.o.queued_data);
- CloseHandle(h->u.g.ev_from_main);
- CloseHandle(h->u.g.ev_to_main);
- del234(handles_by_evtomain, h);
- sfree(h);
-}
-
-void handle_free(struct handle *h)
-{
- /*
- * If the handle is currently busy, we cannot immediately free
- * it. Instead we must wait until it's finished its current
- * operation, because otherwise the subthread will write to
- * invalid memory after we free its context from under it.
- */
- assert(h && !h->u.g.moribund);
- if (h->u.g.busy) {
- /*
- * Just set the moribund flag, which will be noticed next
- * time an operation completes.
- */
- h->u.g.moribund = TRUE;
- } else if (h->u.g.defunct) {
- /*
- * There isn't even a subthread; we can go straight to
- * handle_destroy.
- */
- handle_destroy(h);
- } else {
- /*
- * The subthread is alive but not busy, so we now signal it
- * to die. Set the moribund flag to indicate that it will
- * want destroying after that.
- */
- h->u.g.moribund = TRUE;
- h->u.g.done = TRUE;
- h->u.g.busy = TRUE;
- SetEvent(h->u.g.ev_from_main);
- }
-}
-
-void handle_got_event(HANDLE event)
-{
- struct handle *h;
-
- assert(handles_by_evtomain);
- h = find234(handles_by_evtomain, &event, handle_find_evtomain);
- if (!h) {
- /*
- * This isn't an error condition. If two or more event
- * objects were signalled during the same select operation,
- * and processing of the first caused the second handle to
- * be closed, then it will sometimes happen that we receive
- * an event notification here for a handle which is already
- * deceased. In that situation we simply do nothing.
- */
- return;
- }
-
- if (h->u.g.moribund) {
- /*
- * A moribund handle is already treated as dead from the
- * external user's point of view, so do nothing with the
- * actual event. Just signal the thread to die if
- * necessary, or destroy the handle if not.
- */
- if (h->u.g.done) {
- handle_destroy(h);
- } else {
- h->u.g.done = TRUE;
- h->u.g.busy = TRUE;
- SetEvent(h->u.g.ev_from_main);
- }
- return;
- }
-
- if (!h->output) {
- int backlog;
-
- h->u.i.busy = FALSE;
-
- /*
- * A signal on an input handle means data has arrived.
- */
- if (h->u.i.len == 0) {
- /*
- * EOF, or (nearly equivalently) read error.
- */
- h->u.i.gotdata(h, NULL, -h->u.i.readerr);
- h->u.i.defunct = TRUE;
- } else {
- backlog = h->u.i.gotdata(h, h->u.i.buffer, h->u.i.len);
- handle_throttle(&h->u.i, backlog);
- }
- } else {
- h->u.o.busy = FALSE;
-
- /*
- * A signal on an output handle means we have completed a
- * write. Call the callback to indicate that the output
- * buffer size has decreased, or to indicate an error.
- */
- if (h->u.o.writeerr) {
- /*
- * Write error. Send a negative value to the callback,
- * and mark the thread as defunct (because the output
- * thread is terminating by now).
- */
- h->u.o.sentdata(h, -h->u.o.writeerr);
- h->u.o.defunct = TRUE;
- } else {
- bufchain_consume(&h->u.o.queued_data, h->u.o.lenwritten);
- h->u.o.sentdata(h, bufchain_size(&h->u.o.queued_data));
- handle_try_output(&h->u.o);
- }
- }
-}
-
-void handle_unthrottle(struct handle *h, int backlog)
-{
- assert(!h->output);
- handle_throttle(&h->u.i, backlog);
-}
-
-int handle_backlog(struct handle *h)
-{
- assert(h->output);
- return bufchain_size(&h->u.o.queued_data);
-}
-
-void *handle_get_privdata(struct handle *h)
-{
- return h->u.g.privdata;
-}
+/*
+ * winhandl.c: Module to give Windows front ends the general
+ * ability to deal with consoles, pipes, serial ports, or any other
+ * type of data stream accessed through a Windows API HANDLE rather
+ * than a WinSock SOCKET.
+ *
+ * We do this by spawning a subthread to continuously try to read
+ * from the handle. Every time a read successfully returns some
+ * data, the subthread sets an event object which is picked up by
+ * the main thread, and the main thread then sets an event in
+ * return to instruct the subthread to resume reading.
+ *
+ * Output works precisely the other way round, in a second
+ * subthread. The output subthread should not be attempting to
+ * write all the time, because it hasn't always got data _to_
+ * write; so the output thread waits for an event object notifying
+ * it to _attempt_ a write, and then it sets an event in return
+ * when one completes.
+ *
+ * (It's terribly annoying having to spawn a subthread for each
+ * direction of each handle. Technically it isn't necessary for
+ * serial ports, since we could use overlapped I/O within the main
+ * thread and wait directly on the event objects in the OVERLAPPED
+ * structures. However, we can't use this trick for some types of
+ * file handle at all - for some reason Windows restricts use of
+ * OVERLAPPED to files which were opened with the overlapped flag -
+ * and so we must use threads for those. This being the case, it's
+ * simplest just to use threads for everything rather than trying
+ * to keep track of multiple completely separate mechanisms.)
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+
+/* ----------------------------------------------------------------------
+ * Generic definitions.
+ */
+
+/*
+ * Maximum amount of backlog we will allow to build up on an input
+ * handle before we stop reading from it.
+ */
+#define MAX_BACKLOG 32768
+
+struct handle_generic {
+ /*
+ * Initial fields common to both handle_input and handle_output
+ * structures.
+ *
+ * The three HANDLEs are set up at initialisation time and are
+ * thereafter read-only to both main thread and subthread.
+ * `moribund' is only used by the main thread; `done' is
+ * written by the main thread before signalling to the
+ * subthread. `defunct' and `busy' are used only by the main
+ * thread.
+ */
+ HANDLE h; /* the handle itself */
+ HANDLE ev_to_main; /* event used to signal main thread */
+ HANDLE ev_from_main; /* event used to signal back to us */
+ int moribund; /* are we going to kill this soon? */
+ int done; /* request subthread to terminate */
+ int defunct; /* has the subthread already gone? */
+ int busy; /* operation currently in progress? */
+ void *privdata; /* for client to remember who they are */
+};
+
+typedef enum { HT_INPUT, HT_OUTPUT, HT_FOREIGN } HandleType;
+
+/* ----------------------------------------------------------------------
+ * Input threads.
+ */
+
+/*
+ * Data required by an input thread.
+ */
+struct handle_input {
+ /*
+ * Copy of the handle_generic structure.
+ */
+ HANDLE h; /* the handle itself */
+ HANDLE ev_to_main; /* event used to signal main thread */
+ HANDLE ev_from_main; /* event used to signal back to us */
+ int moribund; /* are we going to kill this soon? */
+ int done; /* request subthread to terminate */
+ int defunct; /* has the subthread already gone? */
+ int busy; /* operation currently in progress? */
+ void *privdata; /* for client to remember who they are */
+
+ /*
+ * Data set at initialisation and then read-only.
+ */
+ int flags;
+
+ /*
+ * Data set by the input thread before signalling ev_to_main,
+ * and read by the main thread after receiving that signal.
+ */
+ char buffer[4096]; /* the data read from the handle */
+ DWORD len; /* how much data that was */
+ int readerr; /* lets us know about read errors */
+
+ /*
+ * Callback function called by this module when data arrives on
+ * an input handle.
+ */
+ handle_inputfn_t gotdata;
+};
+
+/*
+ * The actual thread procedure for an input thread.
+ */
+static DWORD WINAPI handle_input_threadfunc(void *param)
+{
+ struct handle_input *ctx = (struct handle_input *) param;
+ OVERLAPPED ovl, *povl;
+ HANDLE oev;
+ int readret, readlen;
+
+ if (ctx->flags & HANDLE_FLAG_OVERLAPPED) {
+ povl = &ovl;
+ oev = CreateEvent(NULL, TRUE, FALSE, NULL);
+ } else {
+ povl = NULL;
+ }
+
+ if (ctx->flags & HANDLE_FLAG_UNITBUFFER)
+ readlen = 1;
+ else
+ readlen = sizeof(ctx->buffer);
+
+ while (1) {
+ if (povl) {
+ memset(povl, 0, sizeof(OVERLAPPED));
+ povl->hEvent = oev;
+ }
+ readret = ReadFile(ctx->h, ctx->buffer,readlen, &ctx->len, povl);
+ if (!readret)
+ ctx->readerr = GetLastError();
+ else
+ ctx->readerr = 0;
+ if (povl && !readret && ctx->readerr == ERROR_IO_PENDING) {
+ WaitForSingleObject(povl->hEvent, INFINITE);
+ readret = GetOverlappedResult(ctx->h, povl, &ctx->len, FALSE);
+ if (!readret)
+ ctx->readerr = GetLastError();
+ else
+ ctx->readerr = 0;
+ }
+
+ if (!readret) {
+ /*
+ * Windows apparently sends ERROR_BROKEN_PIPE when a
+ * pipe we're reading from is closed normally from the
+ * writing end. This is ludicrous; if that situation
+ * isn't a natural EOF, _nothing_ is. So if we get that
+ * particular error, we pretend it's EOF.
+ */
+ if (ctx->readerr == ERROR_BROKEN_PIPE)
+ ctx->readerr = 0;
+ ctx->len = 0;
+ }
+
+ if (readret && ctx->len == 0 &&
+ (ctx->flags & HANDLE_FLAG_IGNOREEOF))
+ continue;
+
+ SetEvent(ctx->ev_to_main);
+
+ if (!ctx->len)
+ break;
+
+ WaitForSingleObject(ctx->ev_from_main, INFINITE);
+ if (ctx->done)
+ break; /* main thread told us to shut down */
+ }
+
+ if (povl)
+ CloseHandle(oev);
+
+ return 0;
+}
+
+/*
+ * This is called after a succcessful read, or from the
+ * `unthrottle' function. It decides whether or not to begin a new
+ * read operation.
+ */
+static void handle_throttle(struct handle_input *ctx, int backlog)
+{
+ if (ctx->defunct)
+ return;
+
+ /*
+ * If there's a read operation already in progress, do nothing:
+ * when that completes, we'll come back here and be in a
+ * position to make a better decision.
+ */
+ if (ctx->busy)
+ return;
+
+ /*
+ * Otherwise, we must decide whether to start a new read based
+ * on the size of the backlog.
+ */
+ if (backlog < MAX_BACKLOG) {
+ SetEvent(ctx->ev_from_main);
+ ctx->busy = TRUE;
+ }
+}
+
+/* ----------------------------------------------------------------------
+ * Output threads.
+ */
+
+/*
+ * Data required by an output thread.
+ */
+struct handle_output {
+ /*
+ * Copy of the handle_generic structure.
+ */
+ HANDLE h; /* the handle itself */
+ HANDLE ev_to_main; /* event used to signal main thread */
+ HANDLE ev_from_main; /* event used to signal back to us */
+ int moribund; /* are we going to kill this soon? */
+ int done; /* request subthread to terminate */
+ int defunct; /* has the subthread already gone? */
+ int busy; /* operation currently in progress? */
+ void *privdata; /* for client to remember who they are */
+
+ /*
+ * Data set at initialisation and then read-only.
+ */
+ int flags;
+
+ /*
+ * Data set by the main thread before signalling ev_from_main,
+ * and read by the input thread after receiving that signal.
+ */
+ char *buffer; /* the data to write */
+ DWORD len; /* how much data there is */
+
+ /*
+ * Data set by the input thread before signalling ev_to_main,
+ * and read by the main thread after receiving that signal.
+ */
+ DWORD lenwritten; /* how much data we actually wrote */
+ int writeerr; /* return value from WriteFile */
+
+ /*
+ * Data only ever read or written by the main thread.
+ */
+ bufchain queued_data; /* data still waiting to be written */
+ enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
+
+ /*
+ * Callback function called when the backlog in the bufchain
+ * drops.
+ */
+ handle_outputfn_t sentdata;
+};
+
+static DWORD WINAPI handle_output_threadfunc(void *param)
+{
+ struct handle_output *ctx = (struct handle_output *) param;
+ OVERLAPPED ovl, *povl;
+ HANDLE oev;
+ int writeret;
+
+ if (ctx->flags & HANDLE_FLAG_OVERLAPPED) {
+ povl = &ovl;
+ oev = CreateEvent(NULL, TRUE, FALSE, NULL);
+ } else {
+ povl = NULL;
+ }
+
+ while (1) {
+ WaitForSingleObject(ctx->ev_from_main, INFINITE);
+ if (ctx->done) {
+ SetEvent(ctx->ev_to_main);
+ break;
+ }
+ if (povl) {
+ memset(povl, 0, sizeof(OVERLAPPED));
+ povl->hEvent = oev;
+ }
+
+ writeret = WriteFile(ctx->h, ctx->buffer, ctx->len,
+ &ctx->lenwritten, povl);
+ if (!writeret)
+ ctx->writeerr = GetLastError();
+ else
+ ctx->writeerr = 0;
+ if (povl && !writeret && GetLastError() == ERROR_IO_PENDING) {
+ writeret = GetOverlappedResult(ctx->h, povl,
+ &ctx->lenwritten, TRUE);
+ if (!writeret)
+ ctx->writeerr = GetLastError();
+ else
+ ctx->writeerr = 0;
+ }
+
+ SetEvent(ctx->ev_to_main);
+ if (!writeret)
+ break;
+ }
+
+ if (povl)
+ CloseHandle(oev);
+
+ return 0;
+}
+
+static void handle_try_output(struct handle_output *ctx)
+{
+ void *senddata;
+ int sendlen;
+
+ if (!ctx->busy && bufchain_size(&ctx->queued_data)) {
+ bufchain_prefix(&ctx->queued_data, &senddata, &sendlen);
+ ctx->buffer = senddata;
+ ctx->len = sendlen;
+ SetEvent(ctx->ev_from_main);
+ ctx->busy = TRUE;
+ } else if (!ctx->busy && bufchain_size(&ctx->queued_data) == 0 &&
+ ctx->outgoingeof == EOF_PENDING) {
+ CloseHandle(ctx->h);
+ ctx->h = INVALID_HANDLE_VALUE;
+ ctx->outgoingeof = EOF_SENT;
+ }
+}
+
+/* ----------------------------------------------------------------------
+ * 'Foreign events'. These are handle structures which just contain a
+ * single event object passed to us by another module such as
+ * winnps.c, so that they can make use of our handle_get_events /
+ * handle_got_event mechanism for communicating with application main
+ * loops.
+ */
+struct handle_foreign {
+ /*
+ * Copy of the handle_generic structure.
+ */
+ HANDLE h; /* the handle itself */
+ HANDLE ev_to_main; /* event used to signal main thread */
+ HANDLE ev_from_main; /* event used to signal back to us */
+ int moribund; /* are we going to kill this soon? */
+ int done; /* request subthread to terminate */
+ int defunct; /* has the subthread already gone? */
+ int busy; /* operation currently in progress? */
+ void *privdata; /* for client to remember who they are */
+
+ /*
+ * Our own data, just consisting of knowledge of who to call back.
+ */
+ void (*callback)(void *);
+ void *ctx;
+};
+
+/* ----------------------------------------------------------------------
+ * Unified code handling both input and output threads.
+ */
+
+struct handle {
+ HandleType type;
+ union {
+ struct handle_generic g;
+ struct handle_input i;
+ struct handle_output o;
+ struct handle_foreign f;
+ } u;
+};
+
+static tree234 *handles_by_evtomain;
+
+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)
+ return -1;
+ else if ((unsigned)a->u.g.ev_to_main > (unsigned)b->u.g.ev_to_main)
+ return +1;
+ else
+ return 0;
+}
+
+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)
+ return -1;
+ else if ((unsigned)*a > (unsigned)b->u.g.ev_to_main)
+ return +1;
+ else
+ return 0;
+}
+
+struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata,
+ void *privdata, int flags)
+{
+ struct handle *h = snew(struct handle);
+ DWORD in_threadid; /* required for Win9x */
+
+ h->type = HT_INPUT;
+ h->u.i.h = handle;
+ h->u.i.ev_to_main = CreateEvent(NULL, FALSE, FALSE, NULL);
+ h->u.i.ev_from_main = CreateEvent(NULL, FALSE, FALSE, NULL);
+ h->u.i.gotdata = gotdata;
+ h->u.i.defunct = FALSE;
+ h->u.i.moribund = FALSE;
+ h->u.i.done = FALSE;
+ h->u.i.privdata = privdata;
+ h->u.i.flags = flags;
+
+ if (!handles_by_evtomain)
+ handles_by_evtomain = newtree234(handle_cmp_evtomain);
+ add234(handles_by_evtomain, h);
+
+ CreateThread(NULL, 0, handle_input_threadfunc,
+ &h->u.i, 0, &in_threadid);
+ h->u.i.busy = TRUE;
+
+ return h;
+}
+
+struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata,
+ void *privdata, int flags)
+{
+ struct handle *h = snew(struct handle);
+ DWORD out_threadid; /* required for Win9x */
+
+ h->type = HT_OUTPUT;
+ h->u.o.h = handle;
+ h->u.o.ev_to_main = CreateEvent(NULL, FALSE, FALSE, NULL);
+ h->u.o.ev_from_main = CreateEvent(NULL, FALSE, FALSE, NULL);
+ h->u.o.busy = FALSE;
+ h->u.o.defunct = FALSE;
+ h->u.o.moribund = FALSE;
+ h->u.o.done = FALSE;
+ h->u.o.privdata = privdata;
+ bufchain_init(&h->u.o.queued_data);
+ h->u.o.outgoingeof = EOF_NO;
+ h->u.o.sentdata = sentdata;
+ h->u.o.flags = flags;
+
+ if (!handles_by_evtomain)
+ handles_by_evtomain = newtree234(handle_cmp_evtomain);
+ add234(handles_by_evtomain, h);
+
+ CreateThread(NULL, 0, handle_output_threadfunc,
+ &h->u.o, 0, &out_threadid);
+
+ return h;
+}
+
+struct handle *handle_add_foreign_event(HANDLE event,
+ void (*callback)(void *), void *ctx)
+{
+ struct handle *h = snew(struct handle);
+
+ h->type = HT_FOREIGN;
+ h->u.f.h = INVALID_HANDLE_VALUE;
+ h->u.f.ev_to_main = event;
+ h->u.f.ev_from_main = INVALID_HANDLE_VALUE;
+ h->u.f.defunct = TRUE; /* we have no thread in the first place */
+ h->u.f.moribund = FALSE;
+ h->u.f.done = FALSE;
+ h->u.f.privdata = NULL;
+ h->u.f.callback = callback;
+ h->u.f.ctx = ctx;
+ h->u.f.busy = TRUE;
+
+ if (!handles_by_evtomain)
+ handles_by_evtomain = newtree234(handle_cmp_evtomain);
+ add234(handles_by_evtomain, h);
+
+ return h;
+}
+
+int handle_write(struct handle *h, const void *data, int len)
+{
+ assert(h->type == HT_OUTPUT);
+ assert(h->u.o.outgoingeof == EOF_NO);
+ bufchain_add(&h->u.o.queued_data, data, len);
+ handle_try_output(&h->u.o);
+ return bufchain_size(&h->u.o.queued_data);
+}
+
+void handle_write_eof(struct handle *h)
+{
+ /*
+ * This function is called when we want to proactively send an
+ * end-of-file notification on the handle. We can only do this by
+ * actually closing the handle - so never call this on a
+ * bidirectional handle if we're still interested in its incoming
+ * direction!
+ */
+ assert(h->type == HT_OUTPUT);
+ if (!h->u.o.outgoingeof == EOF_NO) {
+ h->u.o.outgoingeof = EOF_PENDING;
+ handle_try_output(&h->u.o);
+ }
+}
+
+HANDLE *handle_get_events(int *nevents)
+{
+ HANDLE *ret;
+ struct handle *h;
+ int i, n, size;
+
+ /*
+ * Go through our tree counting the handle objects currently
+ * engaged in useful activity.
+ */
+ ret = NULL;
+ n = size = 0;
+ if (handles_by_evtomain) {
+ for (i = 0; (h = index234(handles_by_evtomain, i)) != NULL; i++) {
+ if (h->u.g.busy) {
+ if (n >= size) {
+ size += 32;
+ ret = sresize(ret, size, HANDLE);
+ }
+ ret[n++] = h->u.g.ev_to_main;
+ }
+ }
+ }
+
+ *nevents = n;
+ return ret;
+}
+
+static void handle_destroy(struct handle *h)
+{
+ if (h->type == HT_OUTPUT)
+ bufchain_clear(&h->u.o.queued_data);
+ CloseHandle(h->u.g.ev_from_main);
+ CloseHandle(h->u.g.ev_to_main);
+ del234(handles_by_evtomain, h);
+ sfree(h);
+}
+
+void handle_free(struct handle *h)
+{
+ /*
+ * If the handle is currently busy, we cannot immediately free
+ * it. Instead we must wait until it's finished its current
+ * operation, because otherwise the subthread will write to
+ * invalid memory after we free its context from under it.
+ */
+ assert(h && !h->u.g.moribund);
+ if (h->u.g.busy) {
+ /*
+ * Just set the moribund flag, which will be noticed next
+ * time an operation completes.
+ */
+ h->u.g.moribund = TRUE;
+ } else if (h->u.g.defunct) {
+ /*
+ * There isn't even a subthread; we can go straight to
+ * handle_destroy.
+ */
+ handle_destroy(h);
+ } else {
+ /*
+ * The subthread is alive but not busy, so we now signal it
+ * to die. Set the moribund flag to indicate that it will
+ * want destroying after that.
+ */
+ h->u.g.moribund = TRUE;
+ h->u.g.done = TRUE;
+ h->u.g.busy = TRUE;
+ SetEvent(h->u.g.ev_from_main);
+ }
+}
+
+void handle_got_event(HANDLE event)
+{
+ struct handle *h;
+
+ assert(handles_by_evtomain);
+ h = find234(handles_by_evtomain, &event, handle_find_evtomain);
+ if (!h) {
+ /*
+ * This isn't an error condition. If two or more event
+ * objects were signalled during the same select operation,
+ * and processing of the first caused the second handle to
+ * be closed, then it will sometimes happen that we receive
+ * an event notification here for a handle which is already
+ * deceased. In that situation we simply do nothing.
+ */
+ return;
+ }
+
+ if (h->u.g.moribund) {
+ /*
+ * A moribund handle is already treated as dead from the
+ * external user's point of view, so do nothing with the
+ * actual event. Just signal the thread to die if
+ * necessary, or destroy the handle if not.
+ */
+ if (h->u.g.done) {
+ handle_destroy(h);
+ } else {
+ h->u.g.done = TRUE;
+ h->u.g.busy = TRUE;
+ SetEvent(h->u.g.ev_from_main);
+ }
+ return;
+ }
+
+ switch (h->type) {
+ int backlog;
+
+ case HT_INPUT:
+ h->u.i.busy = FALSE;
+
+ /*
+ * A signal on an input handle means data has arrived.
+ */
+ if (h->u.i.len == 0) {
+ /*
+ * EOF, or (nearly equivalently) read error.
+ */
+ h->u.i.gotdata(h, NULL, -h->u.i.readerr);
+ h->u.i.defunct = TRUE;
+ } else {
+ backlog = h->u.i.gotdata(h, h->u.i.buffer, h->u.i.len);
+ handle_throttle(&h->u.i, backlog);
+ }
+ break;
+
+ case HT_OUTPUT:
+ h->u.o.busy = FALSE;
+
+ /*
+ * A signal on an output handle means we have completed a
+ * write. Call the callback to indicate that the output
+ * buffer size has decreased, or to indicate an error.
+ */
+ if (h->u.o.writeerr) {
+ /*
+ * Write error. Send a negative value to the callback,
+ * and mark the thread as defunct (because the output
+ * thread is terminating by now).
+ */
+ h->u.o.sentdata(h, -h->u.o.writeerr);
+ h->u.o.defunct = TRUE;
+ } else {
+ bufchain_consume(&h->u.o.queued_data, h->u.o.lenwritten);
+ h->u.o.sentdata(h, bufchain_size(&h->u.o.queued_data));
+ handle_try_output(&h->u.o);
+ }
+ break;
+
+ case HT_FOREIGN:
+ /* Just call the callback. */
+ h->u.f.callback(h->u.f.ctx);
+ break;
+ }
+}
+
+void handle_unthrottle(struct handle *h, int backlog)
+{
+ assert(h->type == HT_INPUT);
+ handle_throttle(&h->u.i, backlog);
+}
+
+int handle_backlog(struct handle *h)
+{
+ assert(h->type == HT_OUTPUT);
+ return bufchain_size(&h->u.o.queued_data);
+}
+
+void *handle_get_privdata(struct handle *h)
+{
+ return h->u.g.privdata;
+}
diff --git a/tools/plink/winhelp.h b/tools/plink/winhelp.h
index d670c0cb8..14b588600 100644
--- a/tools/plink/winhelp.h
+++ b/tools/plink/winhelp.h
@@ -1,187 +1,189 @@
-/*
- * winhelp.h - define Windows Help context names.
- * Each definition has the form "winhelp-topic:halibut-topic", where:
- * - "winhelp-topic" matches up with the \cfg{winhelp-topic} directives
- * in the Halibut source, and is used for WinHelp;
- * - "halibut-topic" matches up with the Halibut keywords in the source,
- * and is used for HTML Help.
- */
-
-/* Maximum length for WINHELP_CTX_foo strings */
-#define WINHELP_CTX_MAXLEN 80
-
-/* These are used in the cross-platform configuration dialog code. */
-
-#define HELPCTX(x) P(WINHELP_CTX_ ## x)
-
-#define WINHELP_CTX_no_help NULL
-
-#define WINHELP_CTX_session_hostname "session.hostname:config-hostname"
-#define WINHELP_CTX_session_saved "session.saved:config-saving"
-#define WINHELP_CTX_session_coe "session.coe:config-closeonexit"
-#define WINHELP_CTX_logging_main "logging.main:config-logging"
-#define WINHELP_CTX_logging_filename "logging.filename:config-logfilename"
-#define WINHELP_CTX_logging_exists "logging.exists:config-logfileexists"
-#define WINHELP_CTX_logging_flush "logging.flush:config-logflush"
-#define WINHELP_CTX_logging_ssh_omit_password "logging.ssh.omitpassword:config-logssh"
-#define WINHELP_CTX_logging_ssh_omit_data "logging.ssh.omitdata:config-logssh"
-#define WINHELP_CTX_keyboard_backspace "keyboard.backspace:config-backspace"
-#define WINHELP_CTX_keyboard_homeend "keyboard.homeend:config-homeend"
-#define WINHELP_CTX_keyboard_funkeys "keyboard.funkeys:config-funkeys"
-#define WINHELP_CTX_keyboard_appkeypad "keyboard.appkeypad:config-appkeypad"
-#define WINHELP_CTX_keyboard_appcursor "keyboard.appcursor:config-appcursor"
-#define WINHELP_CTX_keyboard_nethack "keyboard.nethack:config-nethack"
-#define WINHELP_CTX_keyboard_compose "keyboard.compose:config-compose"
-#define WINHELP_CTX_keyboard_ctrlalt "keyboard.ctrlalt:config-ctrlalt"
-#define WINHELP_CTX_features_application "features.application:config-features-application"
-#define WINHELP_CTX_features_mouse "features.mouse:config-features-mouse"
-#define WINHELP_CTX_features_resize "features.resize:config-features-resize"
-#define WINHELP_CTX_features_altscreen "features.altscreen:config-features-altscreen"
-#define WINHELP_CTX_features_retitle "features.retitle:config-features-retitle"
-#define WINHELP_CTX_features_qtitle "features.qtitle:config-features-qtitle"
-#define WINHELP_CTX_features_dbackspace "features.dbackspace:config-features-dbackspace"
-#define WINHELP_CTX_features_charset "features.charset:config-features-charset"
-#define WINHELP_CTX_features_arabicshaping "features.arabicshaping:config-features-shaping"
-#define WINHELP_CTX_features_bidi "features.bidi:config-features-bidi"
-#define WINHELP_CTX_terminal_autowrap "terminal.autowrap:config-autowrap"
-#define WINHELP_CTX_terminal_decom "terminal.decom:config-decom"
-#define WINHELP_CTX_terminal_lfhascr "terminal.lfhascr:config-crlf"
-#define WINHELP_CTX_terminal_crhaslf "terminal.crhaslf:config-lfcr"
-#define WINHELP_CTX_terminal_bce "terminal.bce:config-erase"
-#define WINHELP_CTX_terminal_blink "terminal.blink:config-blink"
-#define WINHELP_CTX_terminal_answerback "terminal.answerback:config-answerback"
-#define WINHELP_CTX_terminal_localecho "terminal.localecho:config-localecho"
-#define WINHELP_CTX_terminal_localedit "terminal.localedit:config-localedit"
-#define WINHELP_CTX_terminal_printing "terminal.printing:config-printing"
-#define WINHELP_CTX_bell_style "bell.style:config-bellstyle"
-#define WINHELP_CTX_bell_taskbar "bell.taskbar:config-belltaskbar"
-#define WINHELP_CTX_bell_overload "bell.overload:config-bellovl"
-#define WINHELP_CTX_window_size "window.size:config-winsize"
-#define WINHELP_CTX_window_resize "window.resize:config-winsizelock"
-#define WINHELP_CTX_window_scrollback "window.scrollback:config-scrollback"
-#define WINHELP_CTX_window_erased "window.erased:config-erasetoscrollback"
-#define WINHELP_CTX_behaviour_closewarn "behaviour.closewarn:config-warnonclose"
-#define WINHELP_CTX_behaviour_altf4 "behaviour.altf4:config-altf4"
-#define WINHELP_CTX_behaviour_altspace "behaviour.altspace:config-altspace"
-#define WINHELP_CTX_behaviour_altonly "behaviour.altonly:config-altonly"
-#define WINHELP_CTX_behaviour_alwaysontop "behaviour.alwaysontop:config-alwaysontop"
-#define WINHELP_CTX_behaviour_altenter "behaviour.altenter:config-fullscreen"
-#define WINHELP_CTX_appearance_cursor "appearance.cursor:config-cursor"
-#define WINHELP_CTX_appearance_font "appearance.font:config-font"
-#define WINHELP_CTX_appearance_title "appearance.title:config-title"
-#define WINHELP_CTX_appearance_hidemouse "appearance.hidemouse:config-mouseptr"
-#define WINHELP_CTX_appearance_border "appearance.border:config-winborder"
-#define WINHELP_CTX_connection_termtype "connection.termtype:config-termtype"
-#define WINHELP_CTX_connection_termspeed "connection.termspeed:config-termspeed"
-#define WINHELP_CTX_connection_username "connection.username:config-username"
-#define WINHELP_CTX_connection_username_from_env "connection.usernamefromenv:config-username-from-env"
-#define WINHELP_CTX_connection_keepalive "connection.keepalive:config-keepalive"
-#define WINHELP_CTX_connection_nodelay "connection.nodelay:config-nodelay"
-#define WINHELP_CTX_connection_ipversion "connection.ipversion:config-address-family"
-#define WINHELP_CTX_connection_tcpkeepalive "connection.tcpkeepalive:config-tcp-keepalives"
-#define WINHELP_CTX_connection_loghost "connection.loghost:config-loghost"
-#define WINHELP_CTX_proxy_type "proxy.type:config-proxy-type"
-#define WINHELP_CTX_proxy_main "proxy.main:config-proxy"
-#define WINHELP_CTX_proxy_exclude "proxy.exclude:config-proxy-exclude"
-#define WINHELP_CTX_proxy_dns "proxy.dns:config-proxy-dns"
-#define WINHELP_CTX_proxy_auth "proxy.auth:config-proxy-auth"
-#define WINHELP_CTX_proxy_command "proxy.command:config-proxy-command"
-#define WINHELP_CTX_telnet_environ "telnet.environ:config-environ"
-#define WINHELP_CTX_telnet_oldenviron "telnet.oldenviron:config-oldenviron"
-#define WINHELP_CTX_telnet_passive "telnet.passive:config-ptelnet"
-#define WINHELP_CTX_telnet_specialkeys "telnet.specialkeys:config-telnetkey"
-#define WINHELP_CTX_telnet_newline "telnet.newline:config-telnetnl"
-#define WINHELP_CTX_rlogin_localuser "rlogin.localuser:config-rlogin-localuser"
-#define WINHELP_CTX_ssh_nopty "ssh.nopty:config-ssh-pty"
-#define WINHELP_CTX_ssh_ttymodes "ssh.ttymodes:config-ttymodes"
-#define WINHELP_CTX_ssh_noshell "ssh.noshell:config-ssh-noshell"
-#define WINHELP_CTX_ssh_ciphers "ssh.ciphers:config-ssh-encryption"
-#define WINHELP_CTX_ssh_protocol "ssh.protocol:config-ssh-prot"
-#define WINHELP_CTX_ssh_command "ssh.command:config-command"
-#define WINHELP_CTX_ssh_compress "ssh.compress:config-ssh-comp"
-#define WINHELP_CTX_ssh_kexlist "ssh.kex.order:config-ssh-kex-order"
-#define WINHELP_CTX_ssh_kex_repeat "ssh.kex.repeat:config-ssh-kex-rekey"
-#define WINHELP_CTX_ssh_auth_bypass "ssh.auth.bypass:config-ssh-noauth"
-#define WINHELP_CTX_ssh_auth_banner "ssh.auth.banner:config-ssh-banner"
-#define WINHELP_CTX_ssh_auth_privkey "ssh.auth.privkey:config-ssh-privkey"
-#define WINHELP_CTX_ssh_auth_agentfwd "ssh.auth.agentfwd:config-ssh-agentfwd"
-#define WINHELP_CTX_ssh_auth_changeuser "ssh.auth.changeuser:config-ssh-changeuser"
-#define WINHELP_CTX_ssh_auth_pageant "ssh.auth.pageant:config-ssh-tryagent"
-#define WINHELP_CTX_ssh_auth_tis "ssh.auth.tis:config-ssh-tis"
-#define WINHELP_CTX_ssh_auth_ki "ssh.auth.ki:config-ssh-ki"
-#define WINHELP_CTX_ssh_gssapi "ssh.auth.gssapi:config-ssh-auth-gssapi"
-#define WINHELP_CTX_ssh_gssapi_delegation "ssh.auth.gssapi.delegation:config-ssh-auth-gssapi-delegation"
-#define WINHELP_CTX_ssh_gssapi_libraries "ssh.auth.gssapi.libraries:config-ssh-auth-gssapi-libraries"
-#define WINHELP_CTX_selection_buttons "selection.buttons:config-mouse"
-#define WINHELP_CTX_selection_shiftdrag "selection.shiftdrag:config-mouseshift"
-#define WINHELP_CTX_selection_rect "selection.rect:config-rectselect"
-#define WINHELP_CTX_selection_charclasses "selection.charclasses:config-charclasses"
-#define WINHELP_CTX_selection_linedraw "selection.linedraw:config-linedrawpaste"
-#define WINHELP_CTX_selection_rtf "selection.rtf:config-rtfpaste"
-#define WINHELP_CTX_colours_ansi "colours.ansi:config-ansicolour"
-#define WINHELP_CTX_colours_xterm256 "colours.xterm256:config-xtermcolour"
-#define WINHELP_CTX_colours_bold "colours.bold:config-boldcolour"
-#define WINHELP_CTX_colours_system "colours.system:config-syscolour"
-#define WINHELP_CTX_colours_logpal "colours.logpal:config-logpalette"
-#define WINHELP_CTX_colours_config "colours.config:config-colourcfg"
-#define WINHELP_CTX_translation_codepage "translation.codepage:config-charset"
-#define WINHELP_CTX_translation_cjk_ambig_wide "translation.cjkambigwide:config-cjk-ambig-wide"
-#define WINHELP_CTX_translation_cyrillic "translation.cyrillic:config-cyr"
-#define WINHELP_CTX_translation_linedraw "translation.linedraw:config-linedraw"
-#define WINHELP_CTX_ssh_tunnels_x11 "ssh.tunnels.x11:config-ssh-x11"
-#define WINHELP_CTX_ssh_tunnels_x11auth "ssh.tunnels.x11auth:config-ssh-x11auth"
-#define WINHELP_CTX_ssh_tunnels_xauthority "ssh.tunnels.xauthority:config-ssh-xauthority"
-#define WINHELP_CTX_ssh_tunnels_portfwd "ssh.tunnels.portfwd:config-ssh-portfwd"
-#define WINHELP_CTX_ssh_tunnels_portfwd_localhost "ssh.tunnels.portfwd.localhost:config-ssh-portfwd-localhost"
-#define WINHELP_CTX_ssh_tunnels_portfwd_ipversion "ssh.tunnels.portfwd.ipversion:config-ssh-portfwd-address-family"
-#define WINHELP_CTX_ssh_bugs_ignore1 "ssh.bugs.ignore1:config-ssh-bug-ignore1"
-#define WINHELP_CTX_ssh_bugs_plainpw1 "ssh.bugs.plainpw1:config-ssh-bug-plainpw1"
-#define WINHELP_CTX_ssh_bugs_rsa1 "ssh.bugs.rsa1:config-ssh-bug-rsa1"
-#define WINHELP_CTX_ssh_bugs_ignore2 "ssh.bugs.ignore2:config-ssh-bug-ignore2"
-#define WINHELP_CTX_ssh_bugs_hmac2 "ssh.bugs.hmac2:config-ssh-bug-hmac2"
-#define WINHELP_CTX_ssh_bugs_derivekey2 "ssh.bugs.derivekey2:config-ssh-bug-derivekey2"
-#define WINHELP_CTX_ssh_bugs_rsapad2 "ssh.bugs.rsapad2:config-ssh-bug-sig"
-#define WINHELP_CTX_ssh_bugs_pksessid2 "ssh.bugs.pksessid2:config-ssh-bug-pksessid2"
-#define WINHELP_CTX_ssh_bugs_rekey2 "ssh.bugs.rekey2:config-ssh-bug-rekey"
-#define WINHELP_CTX_ssh_bugs_maxpkt2 "ssh.bugs.maxpkt2:config-ssh-bug-maxpkt2"
-#define WINHELP_CTX_serial_line "serial.line:config-serial-line"
-#define WINHELP_CTX_serial_speed "serial.speed:config-serial-speed"
-#define WINHELP_CTX_serial_databits "serial.databits:config-serial-databits"
-#define WINHELP_CTX_serial_stopbits "serial.stopbits:config-serial-stopbits"
-#define WINHELP_CTX_serial_parity "serial.parity:config-serial-parity"
-#define WINHELP_CTX_serial_flow "serial.flow:config-serial-flow"
-
-#define WINHELP_CTX_pageant_general "pageant.general:pageant"
-#define WINHELP_CTX_pageant_keylist "pageant.keylist:pageant-mainwin-keylist"
-#define WINHELP_CTX_pageant_addkey "pageant.addkey:pageant-mainwin-addkey"
-#define WINHELP_CTX_pageant_remkey "pageant.remkey:pageant-mainwin-remkey"
-#define WINHELP_CTX_pgpfingerprints "pgpfingerprints:pgpkeys"
-#define WINHELP_CTX_puttygen_general "puttygen.general:pubkey-puttygen"
-#define WINHELP_CTX_puttygen_keytype "puttygen.keytype:puttygen-keytype"
-#define WINHELP_CTX_puttygen_bits "puttygen.bits:puttygen-strength"
-#define WINHELP_CTX_puttygen_generate "puttygen.generate:puttygen-generate"
-#define WINHELP_CTX_puttygen_fingerprint "puttygen.fingerprint:puttygen-fingerprint"
-#define WINHELP_CTX_puttygen_comment "puttygen.comment:puttygen-comment"
-#define WINHELP_CTX_puttygen_passphrase "puttygen.passphrase:puttygen-passphrase"
-#define WINHELP_CTX_puttygen_savepriv "puttygen.savepriv:puttygen-savepriv"
-#define WINHELP_CTX_puttygen_savepub "puttygen.savepub:puttygen-savepub"
-#define WINHELP_CTX_puttygen_pastekey "puttygen.pastekey:puttygen-pastekey"
-#define WINHELP_CTX_puttygen_load "puttygen.load:puttygen-load"
-#define WINHELP_CTX_puttygen_conversions "puttygen.conversions:puttygen-conversions"
-
-/* These are used in Windows-specific bits of the frontend.
- * We (ab)use "help context identifiers" (dwContextId) to identify them. */
-
-#define HELPCTXID(x) WINHELP_CTXID_ ## x
-
-#define WINHELP_CTXID_no_help 0
-#define WINHELP_CTX_errors_hostkey_absent "errors.hostkey.absent:errors-hostkey-absent"
-#define WINHELP_CTXID_errors_hostkey_absent 1
-#define WINHELP_CTX_errors_hostkey_changed "errors.hostkey.changed:errors-hostkey-wrong"
-#define WINHELP_CTXID_errors_hostkey_changed 2
-#define WINHELP_CTX_errors_cantloadkey "errors.cantloadkey:errors-cant-load-key"
-#define WINHELP_CTXID_errors_cantloadkey 3
-#define WINHELP_CTX_option_cleanup "options.cleanup:using-cleanup"
-#define WINHELP_CTXID_option_cleanup 4
-#define WINHELP_CTX_pgp_fingerprints "pgpfingerprints:pgpkeys"
-#define WINHELP_CTXID_pgp_fingerprints 5
+/*
+ * winhelp.h - define Windows Help context names.
+ * Each definition has the form "winhelp-topic:halibut-topic", where:
+ * - "winhelp-topic" matches up with the \cfg{winhelp-topic} directives
+ * in the Halibut source, and is used for WinHelp;
+ * - "halibut-topic" matches up with the Halibut keywords in the source,
+ * and is used for HTML Help.
+ */
+
+/* Maximum length for WINHELP_CTX_foo strings */
+#define WINHELP_CTX_MAXLEN 80
+
+/* These are used in the cross-platform configuration dialog code. */
+
+#define HELPCTX(x) P(WINHELP_CTX_ ## x)
+
+#define WINHELP_CTX_no_help NULL
+
+#define WINHELP_CTX_session_hostname "session.hostname:config-hostname"
+#define WINHELP_CTX_session_saved "session.saved:config-saving"
+#define WINHELP_CTX_session_coe "session.coe:config-closeonexit"
+#define WINHELP_CTX_logging_main "logging.main:config-logging"
+#define WINHELP_CTX_logging_filename "logging.filename:config-logfilename"
+#define WINHELP_CTX_logging_exists "logging.exists:config-logfileexists"
+#define WINHELP_CTX_logging_flush "logging.flush:config-logflush"
+#define WINHELP_CTX_logging_ssh_omit_password "logging.ssh.omitpassword:config-logssh"
+#define WINHELP_CTX_logging_ssh_omit_data "logging.ssh.omitdata:config-logssh"
+#define WINHELP_CTX_keyboard_backspace "keyboard.backspace:config-backspace"
+#define WINHELP_CTX_keyboard_homeend "keyboard.homeend:config-homeend"
+#define WINHELP_CTX_keyboard_funkeys "keyboard.funkeys:config-funkeys"
+#define WINHELP_CTX_keyboard_appkeypad "keyboard.appkeypad:config-appkeypad"
+#define WINHELP_CTX_keyboard_appcursor "keyboard.appcursor:config-appcursor"
+#define WINHELP_CTX_keyboard_nethack "keyboard.nethack:config-nethack"
+#define WINHELP_CTX_keyboard_compose "keyboard.compose:config-compose"
+#define WINHELP_CTX_keyboard_ctrlalt "keyboard.ctrlalt:config-ctrlalt"
+#define WINHELP_CTX_features_application "features.application:config-features-application"
+#define WINHELP_CTX_features_mouse "features.mouse:config-features-mouse"
+#define WINHELP_CTX_features_resize "features.resize:config-features-resize"
+#define WINHELP_CTX_features_altscreen "features.altscreen:config-features-altscreen"
+#define WINHELP_CTX_features_retitle "features.retitle:config-features-retitle"
+#define WINHELP_CTX_features_qtitle "features.qtitle:config-features-qtitle"
+#define WINHELP_CTX_features_dbackspace "features.dbackspace:config-features-dbackspace"
+#define WINHELP_CTX_features_charset "features.charset:config-features-charset"
+#define WINHELP_CTX_features_arabicshaping "features.arabicshaping:config-features-shaping"
+#define WINHELP_CTX_features_bidi "features.bidi:config-features-bidi"
+#define WINHELP_CTX_terminal_autowrap "terminal.autowrap:config-autowrap"
+#define WINHELP_CTX_terminal_decom "terminal.decom:config-decom"
+#define WINHELP_CTX_terminal_lfhascr "terminal.lfhascr:config-crlf"
+#define WINHELP_CTX_terminal_crhaslf "terminal.crhaslf:config-lfcr"
+#define WINHELP_CTX_terminal_bce "terminal.bce:config-erase"
+#define WINHELP_CTX_terminal_blink "terminal.blink:config-blink"
+#define WINHELP_CTX_terminal_answerback "terminal.answerback:config-answerback"
+#define WINHELP_CTX_terminal_localecho "terminal.localecho:config-localecho"
+#define WINHELP_CTX_terminal_localedit "terminal.localedit:config-localedit"
+#define WINHELP_CTX_terminal_printing "terminal.printing:config-printing"
+#define WINHELP_CTX_bell_style "bell.style:config-bellstyle"
+#define WINHELP_CTX_bell_taskbar "bell.taskbar:config-belltaskbar"
+#define WINHELP_CTX_bell_overload "bell.overload:config-bellovl"
+#define WINHELP_CTX_window_size "window.size:config-winsize"
+#define WINHELP_CTX_window_resize "window.resize:config-winsizelock"
+#define WINHELP_CTX_window_scrollback "window.scrollback:config-scrollback"
+#define WINHELP_CTX_window_erased "window.erased:config-erasetoscrollback"
+#define WINHELP_CTX_behaviour_closewarn "behaviour.closewarn:config-warnonclose"
+#define WINHELP_CTX_behaviour_altf4 "behaviour.altf4:config-altf4"
+#define WINHELP_CTX_behaviour_altspace "behaviour.altspace:config-altspace"
+#define WINHELP_CTX_behaviour_altonly "behaviour.altonly:config-altonly"
+#define WINHELP_CTX_behaviour_alwaysontop "behaviour.alwaysontop:config-alwaysontop"
+#define WINHELP_CTX_behaviour_altenter "behaviour.altenter:config-fullscreen"
+#define WINHELP_CTX_appearance_cursor "appearance.cursor:config-cursor"
+#define WINHELP_CTX_appearance_font "appearance.font:config-font"
+#define WINHELP_CTX_appearance_title "appearance.title:config-title"
+#define WINHELP_CTX_appearance_hidemouse "appearance.hidemouse:config-mouseptr"
+#define WINHELP_CTX_appearance_border "appearance.border:config-winborder"
+#define WINHELP_CTX_connection_termtype "connection.termtype:config-termtype"
+#define WINHELP_CTX_connection_termspeed "connection.termspeed:config-termspeed"
+#define WINHELP_CTX_connection_username "connection.username:config-username"
+#define WINHELP_CTX_connection_username_from_env "connection.usernamefromenv:config-username-from-env"
+#define WINHELP_CTX_connection_keepalive "connection.keepalive:config-keepalive"
+#define WINHELP_CTX_connection_nodelay "connection.nodelay:config-nodelay"
+#define WINHELP_CTX_connection_ipversion "connection.ipversion:config-address-family"
+#define WINHELP_CTX_connection_tcpkeepalive "connection.tcpkeepalive:config-tcp-keepalives"
+#define WINHELP_CTX_connection_loghost "connection.loghost:config-loghost"
+#define WINHELP_CTX_proxy_type "proxy.type:config-proxy-type"
+#define WINHELP_CTX_proxy_main "proxy.main:config-proxy"
+#define WINHELP_CTX_proxy_exclude "proxy.exclude:config-proxy-exclude"
+#define WINHELP_CTX_proxy_dns "proxy.dns:config-proxy-dns"
+#define WINHELP_CTX_proxy_auth "proxy.auth:config-proxy-auth"
+#define WINHELP_CTX_proxy_command "proxy.command:config-proxy-command"
+#define WINHELP_CTX_telnet_environ "telnet.environ:config-environ"
+#define WINHELP_CTX_telnet_oldenviron "telnet.oldenviron:config-oldenviron"
+#define WINHELP_CTX_telnet_passive "telnet.passive:config-ptelnet"
+#define WINHELP_CTX_telnet_specialkeys "telnet.specialkeys:config-telnetkey"
+#define WINHELP_CTX_telnet_newline "telnet.newline:config-telnetnl"
+#define WINHELP_CTX_rlogin_localuser "rlogin.localuser:config-rlogin-localuser"
+#define WINHELP_CTX_ssh_nopty "ssh.nopty:config-ssh-pty"
+#define WINHELP_CTX_ssh_ttymodes "ssh.ttymodes:config-ttymodes"
+#define WINHELP_CTX_ssh_noshell "ssh.noshell:config-ssh-noshell"
+#define WINHELP_CTX_ssh_ciphers "ssh.ciphers:config-ssh-encryption"
+#define WINHELP_CTX_ssh_protocol "ssh.protocol:config-ssh-prot"
+#define WINHELP_CTX_ssh_command "ssh.command:config-command"
+#define WINHELP_CTX_ssh_compress "ssh.compress:config-ssh-comp"
+#define WINHELP_CTX_ssh_share "ssh.sharing:config-ssh-sharing"
+#define WINHELP_CTX_ssh_kexlist "ssh.kex.order:config-ssh-kex-order"
+#define WINHELP_CTX_ssh_kex_repeat "ssh.kex.repeat:config-ssh-kex-rekey"
+#define WINHELP_CTX_ssh_auth_bypass "ssh.auth.bypass:config-ssh-noauth"
+#define WINHELP_CTX_ssh_auth_banner "ssh.auth.banner:config-ssh-banner"
+#define WINHELP_CTX_ssh_auth_privkey "ssh.auth.privkey:config-ssh-privkey"
+#define WINHELP_CTX_ssh_auth_agentfwd "ssh.auth.agentfwd:config-ssh-agentfwd"
+#define WINHELP_CTX_ssh_auth_changeuser "ssh.auth.changeuser:config-ssh-changeuser"
+#define WINHELP_CTX_ssh_auth_pageant "ssh.auth.pageant:config-ssh-tryagent"
+#define WINHELP_CTX_ssh_auth_tis "ssh.auth.tis:config-ssh-tis"
+#define WINHELP_CTX_ssh_auth_ki "ssh.auth.ki:config-ssh-ki"
+#define WINHELP_CTX_ssh_gssapi "ssh.auth.gssapi:config-ssh-auth-gssapi"
+#define WINHELP_CTX_ssh_gssapi_delegation "ssh.auth.gssapi.delegation:config-ssh-auth-gssapi-delegation"
+#define WINHELP_CTX_ssh_gssapi_libraries "ssh.auth.gssapi.libraries:config-ssh-auth-gssapi-libraries"
+#define WINHELP_CTX_selection_buttons "selection.buttons:config-mouse"
+#define WINHELP_CTX_selection_shiftdrag "selection.shiftdrag:config-mouseshift"
+#define WINHELP_CTX_selection_rect "selection.rect:config-rectselect"
+#define WINHELP_CTX_selection_charclasses "selection.charclasses:config-charclasses"
+#define WINHELP_CTX_selection_linedraw "selection.linedraw:config-linedrawpaste"
+#define WINHELP_CTX_selection_rtf "selection.rtf:config-rtfpaste"
+#define WINHELP_CTX_colours_ansi "colours.ansi:config-ansicolour"
+#define WINHELP_CTX_colours_xterm256 "colours.xterm256:config-xtermcolour"
+#define WINHELP_CTX_colours_bold "colours.bold:config-boldcolour"
+#define WINHELP_CTX_colours_system "colours.system:config-syscolour"
+#define WINHELP_CTX_colours_logpal "colours.logpal:config-logpalette"
+#define WINHELP_CTX_colours_config "colours.config:config-colourcfg"
+#define WINHELP_CTX_translation_codepage "translation.codepage:config-charset"
+#define WINHELP_CTX_translation_cjk_ambig_wide "translation.cjkambigwide:config-cjk-ambig-wide"
+#define WINHELP_CTX_translation_cyrillic "translation.cyrillic:config-cyr"
+#define WINHELP_CTX_translation_linedraw "translation.linedraw:config-linedraw"
+#define WINHELP_CTX_ssh_tunnels_x11 "ssh.tunnels.x11:config-ssh-x11"
+#define WINHELP_CTX_ssh_tunnels_x11auth "ssh.tunnels.x11auth:config-ssh-x11auth"
+#define WINHELP_CTX_ssh_tunnels_xauthority "ssh.tunnels.xauthority:config-ssh-xauthority"
+#define WINHELP_CTX_ssh_tunnels_portfwd "ssh.tunnels.portfwd:config-ssh-portfwd"
+#define WINHELP_CTX_ssh_tunnels_portfwd_localhost "ssh.tunnels.portfwd.localhost:config-ssh-portfwd-localhost"
+#define WINHELP_CTX_ssh_tunnels_portfwd_ipversion "ssh.tunnels.portfwd.ipversion:config-ssh-portfwd-address-family"
+#define WINHELP_CTX_ssh_bugs_ignore1 "ssh.bugs.ignore1:config-ssh-bug-ignore1"
+#define WINHELP_CTX_ssh_bugs_plainpw1 "ssh.bugs.plainpw1:config-ssh-bug-plainpw1"
+#define WINHELP_CTX_ssh_bugs_rsa1 "ssh.bugs.rsa1:config-ssh-bug-rsa1"
+#define WINHELP_CTX_ssh_bugs_ignore2 "ssh.bugs.ignore2:config-ssh-bug-ignore2"
+#define WINHELP_CTX_ssh_bugs_hmac2 "ssh.bugs.hmac2:config-ssh-bug-hmac2"
+#define WINHELP_CTX_ssh_bugs_derivekey2 "ssh.bugs.derivekey2:config-ssh-bug-derivekey2"
+#define WINHELP_CTX_ssh_bugs_rsapad2 "ssh.bugs.rsapad2:config-ssh-bug-sig"
+#define WINHELP_CTX_ssh_bugs_pksessid2 "ssh.bugs.pksessid2:config-ssh-bug-pksessid2"
+#define WINHELP_CTX_ssh_bugs_rekey2 "ssh.bugs.rekey2:config-ssh-bug-rekey"
+#define WINHELP_CTX_ssh_bugs_maxpkt2 "ssh.bugs.maxpkt2:config-ssh-bug-maxpkt2"
+#define WINHELP_CTX_ssh_bugs_winadj "ssh.bugs.winadj:config-ssh-bug-winadj"
+#define WINHELP_CTX_serial_line "serial.line:config-serial-line"
+#define WINHELP_CTX_serial_speed "serial.speed:config-serial-speed"
+#define WINHELP_CTX_serial_databits "serial.databits:config-serial-databits"
+#define WINHELP_CTX_serial_stopbits "serial.stopbits:config-serial-stopbits"
+#define WINHELP_CTX_serial_parity "serial.parity:config-serial-parity"
+#define WINHELP_CTX_serial_flow "serial.flow:config-serial-flow"
+
+#define WINHELP_CTX_pageant_general "pageant.general:pageant"
+#define WINHELP_CTX_pageant_keylist "pageant.keylist:pageant-mainwin-keylist"
+#define WINHELP_CTX_pageant_addkey "pageant.addkey:pageant-mainwin-addkey"
+#define WINHELP_CTX_pageant_remkey "pageant.remkey:pageant-mainwin-remkey"
+#define WINHELP_CTX_pgpfingerprints "pgpfingerprints:pgpkeys"
+#define WINHELP_CTX_puttygen_general "puttygen.general:pubkey-puttygen"
+#define WINHELP_CTX_puttygen_keytype "puttygen.keytype:puttygen-keytype"
+#define WINHELP_CTX_puttygen_bits "puttygen.bits:puttygen-strength"
+#define WINHELP_CTX_puttygen_generate "puttygen.generate:puttygen-generate"
+#define WINHELP_CTX_puttygen_fingerprint "puttygen.fingerprint:puttygen-fingerprint"
+#define WINHELP_CTX_puttygen_comment "puttygen.comment:puttygen-comment"
+#define WINHELP_CTX_puttygen_passphrase "puttygen.passphrase:puttygen-passphrase"
+#define WINHELP_CTX_puttygen_savepriv "puttygen.savepriv:puttygen-savepriv"
+#define WINHELP_CTX_puttygen_savepub "puttygen.savepub:puttygen-savepub"
+#define WINHELP_CTX_puttygen_pastekey "puttygen.pastekey:puttygen-pastekey"
+#define WINHELP_CTX_puttygen_load "puttygen.load:puttygen-load"
+#define WINHELP_CTX_puttygen_conversions "puttygen.conversions:puttygen-conversions"
+
+/* These are used in Windows-specific bits of the frontend.
+ * We (ab)use "help context identifiers" (dwContextId) to identify them. */
+
+#define HELPCTXID(x) WINHELP_CTXID_ ## x
+
+#define WINHELP_CTXID_no_help 0
+#define WINHELP_CTX_errors_hostkey_absent "errors.hostkey.absent:errors-hostkey-absent"
+#define WINHELP_CTXID_errors_hostkey_absent 1
+#define WINHELP_CTX_errors_hostkey_changed "errors.hostkey.changed:errors-hostkey-wrong"
+#define WINHELP_CTXID_errors_hostkey_changed 2
+#define WINHELP_CTX_errors_cantloadkey "errors.cantloadkey:errors-cant-load-key"
+#define WINHELP_CTXID_errors_cantloadkey 3
+#define WINHELP_CTX_option_cleanup "options.cleanup:using-cleanup"
+#define WINHELP_CTXID_option_cleanup 4
+#define WINHELP_CTX_pgp_fingerprints "pgpfingerprints:pgpkeys"
+#define WINHELP_CTXID_pgp_fingerprints 5
diff --git a/tools/plink/winmisc.c b/tools/plink/winmisc.c
index e8a35ee1d..3b57adf8f 100644
--- a/tools/plink/winmisc.c
+++ b/tools/plink/winmisc.c
@@ -1,460 +1,530 @@
-/*
- * winmisc.c: miscellaneous Windows-specific things
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include "putty.h"
-#include <security.h>
-
-OSVERSIONINFO osVersion;
-
-char *platform_get_x_display(void) {
- /* We may as well check for DISPLAY in case it's useful. */
- return dupstr(getenv("DISPLAY"));
-}
-
-Filename *filename_from_str(const char *str)
-{
- Filename *ret = snew(Filename);
- ret->path = dupstr(str);
- return ret;
-}
-
-Filename *filename_copy(const Filename *fn)
-{
- return filename_from_str(fn->path);
-}
-
-const char *filename_to_str(const Filename *fn)
-{
- return fn->path;
-}
-
-int filename_equal(const Filename *f1, const Filename *f2)
-{
- return !strcmp(f1->path, f2->path);
-}
-
-int filename_is_null(const Filename *fn)
-{
- return !*fn->path;
-}
-
-void filename_free(Filename *fn)
-{
- sfree(fn->path);
- sfree(fn);
-}
-
-int filename_serialise(const Filename *f, void *vdata)
-{
- char *data = (char *)vdata;
- int len = strlen(f->path) + 1; /* include trailing NUL */
- if (data) {
- strcpy(data, f->path);
- }
- return len;
-}
-Filename *filename_deserialise(void *vdata, int maxsize, int *used)
-{
- char *data = (char *)vdata;
- char *end;
- end = memchr(data, '\0', maxsize);
- if (!end)
- return NULL;
- end++;
- *used = end - data;
- return filename_from_str(data);
-}
-
-char *get_username(void)
-{
- DWORD namelen;
- char *user;
- int got_username = FALSE;
- DECL_WINDOWS_FUNCTION(static, BOOLEAN, GetUserNameExA,
- (EXTENDED_NAME_FORMAT, LPSTR, PULONG));
-
- {
- static int tried_usernameex = FALSE;
- if (!tried_usernameex) {
- /* Not available on Win9x, so load dynamically */
- HMODULE secur32 = load_system32_dll("secur32.dll");
- GET_WINDOWS_FUNCTION(secur32, GetUserNameExA);
- tried_usernameex = TRUE;
- }
- }
-
- if (p_GetUserNameExA) {
- /*
- * If available, use the principal -- this avoids the problem
- * that the local username is case-insensitive but Kerberos
- * usernames are case-sensitive.
- */
-
- /* Get the length */
- namelen = 0;
- (void) p_GetUserNameExA(NameUserPrincipal, NULL, &namelen);
-
- user = snewn(namelen, char);
- got_username = p_GetUserNameExA(NameUserPrincipal, user, &namelen);
- if (got_username) {
- char *p = strchr(user, '@');
- if (p) *p = 0;
- } else {
- sfree(user);
- }
- }
-
- if (!got_username) {
- /* Fall back to local user name */
- namelen = 0;
- if (GetUserName(NULL, &namelen) == FALSE) {
- /*
- * Apparently this doesn't work at least on Windows XP SP2.
- * Thus assume a maximum of 256. It will fail again if it
- * doesn't fit.
- */
- namelen = 256;
- }
-
- user = snewn(namelen, char);
- got_username = GetUserName(user, &namelen);
- if (!got_username) {
- sfree(user);
- }
- }
-
- return got_username ? user : NULL;
-}
-
-BOOL init_winver(void)
-{
- ZeroMemory(&osVersion, sizeof(osVersion));
- osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
- return GetVersionEx ( (OSVERSIONINFO *) &osVersion);
-}
-
-HMODULE load_system32_dll(const char *libname)
-{
- /*
- * Wrapper function to load a DLL out of c:\windows\system32
- * without going through the full DLL search path. (Hence no
- * attack is possible by placing a substitute DLL earlier on that
- * path.)
- */
- static char *sysdir = NULL;
- char *fullpath;
- HMODULE ret;
-
- if (!sysdir) {
- int size = 0, len;
- do {
- size = 3*size/2 + 512;
- sysdir = sresize(sysdir, size, char);
- len = GetSystemDirectory(sysdir, size);
- } while (len >= size);
- }
-
- fullpath = dupcat(sysdir, "\\", libname, NULL);
- ret = LoadLibrary(fullpath);
- sfree(fullpath);
- return ret;
-}
-
-#ifdef DEBUG
-static FILE *debug_fp = NULL;
-static HANDLE debug_hdl = INVALID_HANDLE_VALUE;
-static int debug_got_console = 0;
-
-void dputs(char *buf)
-{
- DWORD dw;
-
- if (!debug_got_console) {
- if (AllocConsole()) {
- debug_got_console = 1;
- debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE);
- }
- }
- if (!debug_fp) {
- debug_fp = fopen("debug.log", "w");
- }
-
- if (debug_hdl != INVALID_HANDLE_VALUE) {
- WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL);
- }
- fputs(buf, debug_fp);
- fflush(debug_fp);
-}
-#endif
-
-#ifdef MINEFIELD
-/*
- * Minefield - a Windows equivalent for Electric Fence
- */
-
-#define PAGESIZE 4096
-
-/*
- * Design:
- *
- * We start by reserving as much virtual address space as Windows
- * will sensibly (or not sensibly) let us have. We flag it all as
- * invalid memory.
- *
- * Any allocation attempt is satisfied by committing one or more
- * pages, with an uncommitted page on either side. The returned
- * memory region is jammed up against the _end_ of the pages.
- *
- * Freeing anything causes instantaneous decommitment of the pages
- * involved, so stale pointers are caught as soon as possible.
- */
-
-static int minefield_initialised = 0;
-static void *minefield_region = NULL;
-static long minefield_size = 0;
-static long minefield_npages = 0;
-static long minefield_curpos = 0;
-static unsigned short *minefield_admin = NULL;
-static void *minefield_pages = NULL;
-
-static void minefield_admin_hide(int hide)
-{
- int access = hide ? PAGE_NOACCESS : PAGE_READWRITE;
- VirtualProtect(minefield_admin, minefield_npages * 2, access, NULL);
-}
-
-static void minefield_init(void)
-{
- int size;
- int admin_size;
- int i;
-
- for (size = 0x40000000; size > 0; size = ((size >> 3) * 7) & ~0xFFF) {
- minefield_region = VirtualAlloc(NULL, size,
- MEM_RESERVE, PAGE_NOACCESS);
- if (minefield_region)
- break;
- }
- minefield_size = size;
-
- /*
- * Firstly, allocate a section of that to be the admin block.
- * We'll need a two-byte field for each page.
- */
- minefield_admin = minefield_region;
- minefield_npages = minefield_size / PAGESIZE;
- admin_size = (minefield_npages * 2 + PAGESIZE - 1) & ~(PAGESIZE - 1);
- minefield_npages = (minefield_size - admin_size) / PAGESIZE;
- minefield_pages = (char *) minefield_region + admin_size;
-
- /*
- * Commit the admin region.
- */
- VirtualAlloc(minefield_admin, minefield_npages * 2,
- MEM_COMMIT, PAGE_READWRITE);
-
- /*
- * Mark all pages as unused (0xFFFF).
- */
- for (i = 0; i < minefield_npages; i++)
- minefield_admin[i] = 0xFFFF;
-
- /*
- * Hide the admin region.
- */
- minefield_admin_hide(1);
-
- minefield_initialised = 1;
-}
-
-static void minefield_bomb(void)
-{
- div(1, *(int *) minefield_pages);
-}
-
-static void *minefield_alloc(int size)
-{
- int npages;
- int pos, lim, region_end, region_start;
- int start;
- int i;
-
- npages = (size + PAGESIZE - 1) / PAGESIZE;
-
- minefield_admin_hide(0);
-
- /*
- * Search from current position until we find a contiguous
- * bunch of npages+2 unused pages.
- */
- pos = minefield_curpos;
- lim = minefield_npages;
- while (1) {
- /* Skip over used pages. */
- while (pos < lim && minefield_admin[pos] != 0xFFFF)
- pos++;
- /* Count unused pages. */
- start = pos;
- while (pos < lim && pos - start < npages + 2 &&
- minefield_admin[pos] == 0xFFFF)
- pos++;
- if (pos - start == npages + 2)
- break;
- /* If we've reached the limit, reset the limit or stop. */
- if (pos >= lim) {
- if (lim == minefield_npages) {
- /* go round and start again at zero */
- lim = minefield_curpos;
- pos = 0;
- } else {
- minefield_admin_hide(1);
- return NULL;
- }
- }
- }
-
- minefield_curpos = pos - 1;
-
- /*
- * We have npages+2 unused pages starting at start. We leave
- * the first and last of these alone and use the rest.
- */
- region_end = (start + npages + 1) * PAGESIZE;
- region_start = region_end - size;
- /* FIXME: could align here if we wanted */
-
- /*
- * Update the admin region.
- */
- for (i = start + 2; i < start + npages + 1; i++)
- minefield_admin[i] = 0xFFFE; /* used but no region starts here */
- minefield_admin[start + 1] = region_start % PAGESIZE;
-
- minefield_admin_hide(1);
-
- VirtualAlloc((char *) minefield_pages + region_start, size,
- MEM_COMMIT, PAGE_READWRITE);
- return (char *) minefield_pages + region_start;
-}
-
-static void minefield_free(void *ptr)
-{
- int region_start, i, j;
-
- minefield_admin_hide(0);
-
- region_start = (char *) ptr - (char *) minefield_pages;
- i = region_start / PAGESIZE;
- if (i < 0 || i >= minefield_npages ||
- minefield_admin[i] != region_start % PAGESIZE)
- minefield_bomb();
- for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++) {
- minefield_admin[j] = 0xFFFF;
- }
-
- VirtualFree(ptr, j * PAGESIZE - region_start, MEM_DECOMMIT);
-
- minefield_admin_hide(1);
-}
-
-static int minefield_get_size(void *ptr)
-{
- int region_start, i, j;
-
- minefield_admin_hide(0);
-
- region_start = (char *) ptr - (char *) minefield_pages;
- i = region_start / PAGESIZE;
- if (i < 0 || i >= minefield_npages ||
- minefield_admin[i] != region_start % PAGESIZE)
- minefield_bomb();
- for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++);
-
- minefield_admin_hide(1);
-
- return j * PAGESIZE - region_start;
-}
-
-void *minefield_c_malloc(size_t size)
-{
- if (!minefield_initialised)
- minefield_init();
- return minefield_alloc(size);
-}
-
-void minefield_c_free(void *p)
-{
- if (!minefield_initialised)
- minefield_init();
- minefield_free(p);
-}
-
-/*
- * realloc _always_ moves the chunk, for rapid detection of code
- * that assumes it won't.
- */
-void *minefield_c_realloc(void *p, size_t size)
-{
- size_t oldsize;
- void *q;
- if (!minefield_initialised)
- minefield_init();
- q = minefield_alloc(size);
- oldsize = minefield_get_size(p);
- memcpy(q, p, (oldsize < size ? oldsize : size));
- minefield_free(p);
- return q;
-}
-
-#endif /* MINEFIELD */
-
-FontSpec *fontspec_new(const char *name,
- int bold, int height, int charset)
-{
- FontSpec *f = snew(FontSpec);
- f->name = dupstr(name);
- f->isbold = bold;
- f->height = height;
- f->charset = charset;
- return f;
-}
-FontSpec *fontspec_copy(const FontSpec *f)
-{
- return fontspec_new(f->name, f->isbold, f->height, f->charset);
-}
-void fontspec_free(FontSpec *f)
-{
- sfree(f->name);
- sfree(f);
-}
-int fontspec_serialise(FontSpec *f, void *vdata)
-{
- char *data = (char *)vdata;
- int len = strlen(f->name) + 1; /* include trailing NUL */
- if (data) {
- strcpy(data, f->name);
- PUT_32BIT_MSB_FIRST(data + len, f->isbold);
- PUT_32BIT_MSB_FIRST(data + len + 4, f->height);
- PUT_32BIT_MSB_FIRST(data + len + 8, f->charset);
- }
- return len + 12; /* also include three 4-byte ints */
-}
-FontSpec *fontspec_deserialise(void *vdata, int maxsize, int *used)
-{
- char *data = (char *)vdata;
- char *end;
- if (maxsize < 13)
- return NULL;
- end = memchr(data, '\0', maxsize-12);
- if (!end)
- return NULL;
- end++;
- *used = end - data + 12;
- return fontspec_new(data,
- GET_32BIT_MSB_FIRST(end),
- GET_32BIT_MSB_FIRST(end + 4),
- GET_32BIT_MSB_FIRST(end + 8));
-}
+/*
+ * winmisc.c: miscellaneous Windows-specific things
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "putty.h"
+#include <security.h>
+
+OSVERSIONINFO osVersion;
+
+char *platform_get_x_display(void) {
+ /* We may as well check for DISPLAY in case it's useful. */
+ return dupstr(getenv("DISPLAY"));
+}
+
+Filename *filename_from_str(const char *str)
+{
+ Filename *ret = snew(Filename);
+ ret->path = dupstr(str);
+ return ret;
+}
+
+Filename *filename_copy(const Filename *fn)
+{
+ return filename_from_str(fn->path);
+}
+
+const char *filename_to_str(const Filename *fn)
+{
+ return fn->path;
+}
+
+int filename_equal(const Filename *f1, const Filename *f2)
+{
+ return !strcmp(f1->path, f2->path);
+}
+
+int filename_is_null(const Filename *fn)
+{
+ return !*fn->path;
+}
+
+void filename_free(Filename *fn)
+{
+ sfree(fn->path);
+ sfree(fn);
+}
+
+int filename_serialise(const Filename *f, void *vdata)
+{
+ char *data = (char *)vdata;
+ int len = strlen(f->path) + 1; /* include trailing NUL */
+ if (data) {
+ strcpy(data, f->path);
+ }
+ return len;
+}
+Filename *filename_deserialise(void *vdata, int maxsize, int *used)
+{
+ char *data = (char *)vdata;
+ char *end;
+ end = memchr(data, '\0', maxsize);
+ if (!end)
+ return NULL;
+ end++;
+ *used = end - data;
+ return filename_from_str(data);
+}
+
+#ifndef NO_SECUREZEROMEMORY
+/*
+ * Windows implementation of smemclr (see misc.c) using SecureZeroMemory.
+ */
+void smemclr(void *b, size_t n) {
+ if (b && n > 0)
+ SecureZeroMemory(b, n);
+}
+#endif
+
+char *get_username(void)
+{
+ DWORD namelen;
+ char *user;
+ int got_username = FALSE;
+ DECL_WINDOWS_FUNCTION(static, BOOLEAN, GetUserNameExA,
+ (EXTENDED_NAME_FORMAT, LPSTR, PULONG));
+
+ {
+ static int tried_usernameex = FALSE;
+ if (!tried_usernameex) {
+ /* Not available on Win9x, so load dynamically */
+ HMODULE secur32 = load_system32_dll("secur32.dll");
+ GET_WINDOWS_FUNCTION(secur32, GetUserNameExA);
+ tried_usernameex = TRUE;
+ }
+ }
+
+ if (p_GetUserNameExA) {
+ /*
+ * If available, use the principal -- this avoids the problem
+ * that the local username is case-insensitive but Kerberos
+ * usernames are case-sensitive.
+ */
+
+ /* Get the length */
+ namelen = 0;
+ (void) p_GetUserNameExA(NameUserPrincipal, NULL, &namelen);
+
+ user = snewn(namelen, char);
+ got_username = p_GetUserNameExA(NameUserPrincipal, user, &namelen);
+ if (got_username) {
+ char *p = strchr(user, '@');
+ if (p) *p = 0;
+ } else {
+ sfree(user);
+ }
+ }
+
+ if (!got_username) {
+ /* Fall back to local user name */
+ namelen = 0;
+ if (GetUserName(NULL, &namelen) == FALSE) {
+ /*
+ * Apparently this doesn't work at least on Windows XP SP2.
+ * Thus assume a maximum of 256. It will fail again if it
+ * doesn't fit.
+ */
+ namelen = 256;
+ }
+
+ user = snewn(namelen, char);
+ got_username = GetUserName(user, &namelen);
+ if (!got_username) {
+ sfree(user);
+ }
+ }
+
+ return got_username ? user : NULL;
+}
+
+BOOL init_winver(void)
+{
+ ZeroMemory(&osVersion, sizeof(osVersion));
+ osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
+ return GetVersionEx ( (OSVERSIONINFO *) &osVersion);
+}
+
+HMODULE load_system32_dll(const char *libname)
+{
+ /*
+ * Wrapper function to load a DLL out of c:\windows\system32
+ * without going through the full DLL search path. (Hence no
+ * attack is possible by placing a substitute DLL earlier on that
+ * path.)
+ */
+ static char *sysdir = NULL;
+ char *fullpath;
+ HMODULE ret;
+
+ if (!sysdir) {
+ int size = 0, len;
+ do {
+ size = 3*size/2 + 512;
+ sysdir = sresize(sysdir, size, char);
+ len = GetSystemDirectory(sysdir, size);
+ } while (len >= size);
+ }
+
+ fullpath = dupcat(sysdir, "\\", libname, NULL);
+ ret = LoadLibrary(fullpath);
+ sfree(fullpath);
+ return ret;
+}
+
+/*
+ * A tree234 containing mappings from system error codes to strings.
+ */
+
+struct errstring {
+ int error;
+ char *text;
+};
+
+static int errstring_find(void *av, void *bv)
+{
+ int *a = (int *)av;
+ struct errstring *b = (struct errstring *)bv;
+ if (*a < b->error)
+ return -1;
+ if (*a > b->error)
+ return +1;
+ return 0;
+}
+static int errstring_compare(void *av, void *bv)
+{
+ struct errstring *a = (struct errstring *)av;
+ return errstring_find(&a->error, bv);
+}
+
+static tree234 *errstrings = NULL;
+
+const char *win_strerror(int error)
+{
+ struct errstring *es;
+
+ if (!errstrings)
+ errstrings = newtree234(errstring_compare);
+
+ es = find234(errstrings, &error, errstring_find);
+
+ if (!es) {
+ char msgtext[65536]; /* maximum size for FormatMessage is 64K */
+
+ es = snew(struct errstring);
+ es->error = error;
+ if (!FormatMessage((FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS), NULL, error,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ msgtext, lenof(msgtext)-1, NULL)) {
+ sprintf(msgtext,
+ "(unable to format: FormatMessage returned %d)",
+ error, GetLastError());
+ } else {
+ int len = strlen(msgtext);
+ if (len > 0 && msgtext[len-1] == '\n')
+ msgtext[len-1] = '\0';
+ }
+ es->text = dupprintf("Error %d: %s", error, msgtext);
+ add234(errstrings, es);
+ }
+
+ return es->text;
+}
+
+#ifdef DEBUG
+static FILE *debug_fp = NULL;
+static HANDLE debug_hdl = INVALID_HANDLE_VALUE;
+static int debug_got_console = 0;
+
+void dputs(char *buf)
+{
+ DWORD dw;
+
+ if (!debug_got_console) {
+ if (AllocConsole()) {
+ debug_got_console = 1;
+ debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE);
+ }
+ }
+ if (!debug_fp) {
+ debug_fp = fopen("debug.log", "w");
+ }
+
+ if (debug_hdl != INVALID_HANDLE_VALUE) {
+ WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL);
+ }
+ fputs(buf, debug_fp);
+ fflush(debug_fp);
+}
+#endif
+
+#ifdef MINEFIELD
+/*
+ * Minefield - a Windows equivalent for Electric Fence
+ */
+
+#define PAGESIZE 4096
+
+/*
+ * Design:
+ *
+ * We start by reserving as much virtual address space as Windows
+ * will sensibly (or not sensibly) let us have. We flag it all as
+ * invalid memory.
+ *
+ * Any allocation attempt is satisfied by committing one or more
+ * pages, with an uncommitted page on either side. The returned
+ * memory region is jammed up against the _end_ of the pages.
+ *
+ * Freeing anything causes instantaneous decommitment of the pages
+ * involved, so stale pointers are caught as soon as possible.
+ */
+
+static int minefield_initialised = 0;
+static void *minefield_region = NULL;
+static long minefield_size = 0;
+static long minefield_npages = 0;
+static long minefield_curpos = 0;
+static unsigned short *minefield_admin = NULL;
+static void *minefield_pages = NULL;
+
+static void minefield_admin_hide(int hide)
+{
+ int access = hide ? PAGE_NOACCESS : PAGE_READWRITE;
+ VirtualProtect(minefield_admin, minefield_npages * 2, access, NULL);
+}
+
+static void minefield_init(void)
+{
+ int size;
+ int admin_size;
+ int i;
+
+ for (size = 0x40000000; size > 0; size = ((size >> 3) * 7) & ~0xFFF) {
+ minefield_region = VirtualAlloc(NULL, size,
+ MEM_RESERVE, PAGE_NOACCESS);
+ if (minefield_region)
+ break;
+ }
+ minefield_size = size;
+
+ /*
+ * Firstly, allocate a section of that to be the admin block.
+ * We'll need a two-byte field for each page.
+ */
+ minefield_admin = minefield_region;
+ minefield_npages = minefield_size / PAGESIZE;
+ admin_size = (minefield_npages * 2 + PAGESIZE - 1) & ~(PAGESIZE - 1);
+ minefield_npages = (minefield_size - admin_size) / PAGESIZE;
+ minefield_pages = (char *) minefield_region + admin_size;
+
+ /*
+ * Commit the admin region.
+ */
+ VirtualAlloc(minefield_admin, minefield_npages * 2,
+ MEM_COMMIT, PAGE_READWRITE);
+
+ /*
+ * Mark all pages as unused (0xFFFF).
+ */
+ for (i = 0; i < minefield_npages; i++)
+ minefield_admin[i] = 0xFFFF;
+
+ /*
+ * Hide the admin region.
+ */
+ minefield_admin_hide(1);
+
+ minefield_initialised = 1;
+}
+
+static void minefield_bomb(void)
+{
+ div(1, *(int *) minefield_pages);
+}
+
+static void *minefield_alloc(int size)
+{
+ int npages;
+ int pos, lim, region_end, region_start;
+ int start;
+ int i;
+
+ npages = (size + PAGESIZE - 1) / PAGESIZE;
+
+ minefield_admin_hide(0);
+
+ /*
+ * Search from current position until we find a contiguous
+ * bunch of npages+2 unused pages.
+ */
+ pos = minefield_curpos;
+ lim = minefield_npages;
+ while (1) {
+ /* Skip over used pages. */
+ while (pos < lim && minefield_admin[pos] != 0xFFFF)
+ pos++;
+ /* Count unused pages. */
+ start = pos;
+ while (pos < lim && pos - start < npages + 2 &&
+ minefield_admin[pos] == 0xFFFF)
+ pos++;
+ if (pos - start == npages + 2)
+ break;
+ /* If we've reached the limit, reset the limit or stop. */
+ if (pos >= lim) {
+ if (lim == minefield_npages) {
+ /* go round and start again at zero */
+ lim = minefield_curpos;
+ pos = 0;
+ } else {
+ minefield_admin_hide(1);
+ return NULL;
+ }
+ }
+ }
+
+ minefield_curpos = pos - 1;
+
+ /*
+ * We have npages+2 unused pages starting at start. We leave
+ * the first and last of these alone and use the rest.
+ */
+ region_end = (start + npages + 1) * PAGESIZE;
+ region_start = region_end - size;
+ /* FIXME: could align here if we wanted */
+
+ /*
+ * Update the admin region.
+ */
+ for (i = start + 2; i < start + npages + 1; i++)
+ minefield_admin[i] = 0xFFFE; /* used but no region starts here */
+ minefield_admin[start + 1] = region_start % PAGESIZE;
+
+ minefield_admin_hide(1);
+
+ VirtualAlloc((char *) minefield_pages + region_start, size,
+ MEM_COMMIT, PAGE_READWRITE);
+ return (char *) minefield_pages + region_start;
+}
+
+static void minefield_free(void *ptr)
+{
+ int region_start, i, j;
+
+ minefield_admin_hide(0);
+
+ region_start = (char *) ptr - (char *) minefield_pages;
+ i = region_start / PAGESIZE;
+ if (i < 0 || i >= minefield_npages ||
+ minefield_admin[i] != region_start % PAGESIZE)
+ minefield_bomb();
+ for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++) {
+ minefield_admin[j] = 0xFFFF;
+ }
+
+ VirtualFree(ptr, j * PAGESIZE - region_start, MEM_DECOMMIT);
+
+ minefield_admin_hide(1);
+}
+
+static int minefield_get_size(void *ptr)
+{
+ int region_start, i, j;
+
+ minefield_admin_hide(0);
+
+ region_start = (char *) ptr - (char *) minefield_pages;
+ i = region_start / PAGESIZE;
+ if (i < 0 || i >= minefield_npages ||
+ minefield_admin[i] != region_start % PAGESIZE)
+ minefield_bomb();
+ for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++);
+
+ minefield_admin_hide(1);
+
+ return j * PAGESIZE - region_start;
+}
+
+void *minefield_c_malloc(size_t size)
+{
+ if (!minefield_initialised)
+ minefield_init();
+ return minefield_alloc(size);
+}
+
+void minefield_c_free(void *p)
+{
+ if (!minefield_initialised)
+ minefield_init();
+ minefield_free(p);
+}
+
+/*
+ * realloc _always_ moves the chunk, for rapid detection of code
+ * that assumes it won't.
+ */
+void *minefield_c_realloc(void *p, size_t size)
+{
+ size_t oldsize;
+ void *q;
+ if (!minefield_initialised)
+ minefield_init();
+ q = minefield_alloc(size);
+ oldsize = minefield_get_size(p);
+ memcpy(q, p, (oldsize < size ? oldsize : size));
+ minefield_free(p);
+ return q;
+}
+
+#endif /* MINEFIELD */
+
+FontSpec *fontspec_new(const char *name,
+ int bold, int height, int charset)
+{
+ FontSpec *f = snew(FontSpec);
+ f->name = dupstr(name);
+ f->isbold = bold;
+ f->height = height;
+ f->charset = charset;
+ return f;
+}
+FontSpec *fontspec_copy(const FontSpec *f)
+{
+ return fontspec_new(f->name, f->isbold, f->height, f->charset);
+}
+void fontspec_free(FontSpec *f)
+{
+ sfree(f->name);
+ sfree(f);
+}
+int fontspec_serialise(FontSpec *f, void *vdata)
+{
+ char *data = (char *)vdata;
+ int len = strlen(f->name) + 1; /* include trailing NUL */
+ if (data) {
+ strcpy(data, f->name);
+ PUT_32BIT_MSB_FIRST(data + len, f->isbold);
+ PUT_32BIT_MSB_FIRST(data + len + 4, f->height);
+ PUT_32BIT_MSB_FIRST(data + len + 8, f->charset);
+ }
+ return len + 12; /* also include three 4-byte ints */
+}
+FontSpec *fontspec_deserialise(void *vdata, int maxsize, int *used)
+{
+ char *data = (char *)vdata;
+ char *end;
+ if (maxsize < 13)
+ return NULL;
+ end = memchr(data, '\0', maxsize-12);
+ if (!end)
+ return NULL;
+ end++;
+ *used = end - data + 12;
+ return fontspec_new(data,
+ GET_32BIT_MSB_FIRST(end),
+ GET_32BIT_MSB_FIRST(end + 4),
+ GET_32BIT_MSB_FIRST(end + 8));
+}
diff --git a/tools/plink/winnet.c b/tools/plink/winnet.c
index 11f9ba52e..039701cf0 100644
--- a/tools/plink/winnet.c
+++ b/tools/plink/winnet.c
@@ -1,1747 +1,1835 @@
-/*
- * Windows networking abstraction.
- *
- * For the IPv6 code in here I am indebted to Jeroen Massar and
- * unfix.org.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-
-#define DEFINE_PLUG_METHOD_MACROS
-#include "putty.h"
-#include "network.h"
-#include "tree234.h"
-
-#include <ws2tcpip.h>
-
-#ifndef NO_IPV6
-const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT;
-const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT;
-#endif
-
-#define ipv4_is_loopback(addr) \
- ((p_ntohl(addr.s_addr) & 0xFF000000L) == 0x7F000000L)
-
-/*
- * We used to typedef struct Socket_tag *Socket.
- *
- * Since we have made the networking abstraction slightly more
- * abstract, Socket no longer means a tcp socket (it could mean
- * an ssl socket). So now we must use Actual_Socket when we know
- * we are talking about a tcp socket.
- */
-typedef struct Socket_tag *Actual_Socket;
-
-/*
- * Mutable state that goes with a SockAddr: stores information
- * about where in the list of candidate IP(v*) addresses we've
- * currently got to.
- */
-typedef struct SockAddrStep_tag SockAddrStep;
-struct SockAddrStep_tag {
-#ifndef NO_IPV6
- struct addrinfo *ai; /* steps along addr->ais */
-#endif
- int curraddr;
-};
-
-struct Socket_tag {
- const struct socket_function_table *fn;
- /* the above variable absolutely *must* be the first in this structure */
- char *error;
- SOCKET s;
- Plug plug;
- void *private_ptr;
- bufchain output_data;
- int connected;
- int writable;
- int frozen; /* this causes readability notifications to be ignored */
- int frozen_readable; /* this means we missed at least one readability
- * notification while we were frozen */
- int localhost_only; /* for listening sockets */
- char oobdata[1];
- int sending_oob;
- int oobinline, nodelay, keepalive, privport;
- enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
- SockAddr addr;
- SockAddrStep step;
- int port;
- int pending_error; /* in case send() returns error */
- /*
- * We sometimes need pairs of Socket structures to be linked:
- * if we are listening on the same IPv6 and v4 port, for
- * example. So here we define `parent' and `child' pointers to
- * track this link.
- */
- Actual_Socket parent, child;
-};
-
-struct SockAddr_tag {
- int refcount;
- char *error;
- int resolved;
-#ifndef NO_IPV6
- struct addrinfo *ais; /* Addresses IPv6 style. */
-#endif
- unsigned long *addresses; /* Addresses IPv4 style. */
- int naddresses;
- char hostname[512]; /* Store an unresolved host name. */
-};
-
-/*
- * Which address family this address belongs to. AF_INET for IPv4;
- * AF_INET6 for IPv6; AF_UNSPEC indicates that name resolution has
- * not been done and a simple host name is held in this SockAddr
- * structure.
- */
-#ifndef NO_IPV6
-#define SOCKADDR_FAMILY(addr, step) \
- (!(addr)->resolved ? AF_UNSPEC : \
- (step).ai ? (step).ai->ai_family : AF_INET)
-#else
-#define SOCKADDR_FAMILY(addr, step) \
- (!(addr)->resolved ? AF_UNSPEC : AF_INET)
-#endif
-
-/*
- * Start a SockAddrStep structure to step through multiple
- * addresses.
- */
-#ifndef NO_IPV6
-#define START_STEP(addr, step) \
- ((step).ai = (addr)->ais, (step).curraddr = 0)
-#else
-#define START_STEP(addr, step) \
- ((step).curraddr = 0)
-#endif
-
-static tree234 *sktree;
-
-static int cmpfortree(void *av, void *bv)
-{
- Actual_Socket a = (Actual_Socket) av, b = (Actual_Socket) bv;
- unsigned long as = (unsigned long) a->s, bs = (unsigned long) b->s;
- if (as < bs)
- return -1;
- if (as > bs)
- return +1;
- if (a < b)
- return -1;
- if (a > b)
- return +1;
- return 0;
-}
-
-static int cmpforsearch(void *av, void *bv)
-{
- Actual_Socket b = (Actual_Socket) bv;
- unsigned long as = (unsigned long) av, bs = (unsigned long) b->s;
- if (as < bs)
- return -1;
- if (as > bs)
- return +1;
- return 0;
-}
-
-DECL_WINDOWS_FUNCTION(static, int, WSAStartup, (WORD, LPWSADATA));
-DECL_WINDOWS_FUNCTION(static, int, WSACleanup, (void));
-DECL_WINDOWS_FUNCTION(static, int, closesocket, (SOCKET));
-DECL_WINDOWS_FUNCTION(static, u_long, ntohl, (u_long));
-DECL_WINDOWS_FUNCTION(static, u_long, htonl, (u_long));
-DECL_WINDOWS_FUNCTION(static, u_short, htons, (u_short));
-DECL_WINDOWS_FUNCTION(static, u_short, ntohs, (u_short));
-DECL_WINDOWS_FUNCTION(static, int, gethostname, (char *, int));
-DECL_WINDOWS_FUNCTION(static, struct hostent FAR *, gethostbyname,
- (const char FAR *));
-DECL_WINDOWS_FUNCTION(static, struct servent FAR *, getservbyname,
- (const char FAR *, const char FAR *));
-DECL_WINDOWS_FUNCTION(static, unsigned long, inet_addr, (const char FAR *));
-DECL_WINDOWS_FUNCTION(static, char FAR *, inet_ntoa, (struct in_addr));
-DECL_WINDOWS_FUNCTION(static, int, connect,
- (SOCKET, const struct sockaddr FAR *, int));
-DECL_WINDOWS_FUNCTION(static, int, bind,
- (SOCKET, const struct sockaddr FAR *, int));
-DECL_WINDOWS_FUNCTION(static, int, setsockopt,
- (SOCKET, int, int, const char FAR *, int));
-DECL_WINDOWS_FUNCTION(static, SOCKET, socket, (int, int, int));
-DECL_WINDOWS_FUNCTION(static, int, listen, (SOCKET, int));
-DECL_WINDOWS_FUNCTION(static, int, send, (SOCKET, const char FAR *, int, int));
-DECL_WINDOWS_FUNCTION(static, int, shutdown, (SOCKET, int));
-DECL_WINDOWS_FUNCTION(static, int, ioctlsocket,
- (SOCKET, long, u_long FAR *));
-DECL_WINDOWS_FUNCTION(static, SOCKET, accept,
- (SOCKET, struct sockaddr FAR *, int FAR *));
-DECL_WINDOWS_FUNCTION(static, int, recv, (SOCKET, char FAR *, int, int));
-DECL_WINDOWS_FUNCTION(static, int, WSAIoctl,
- (SOCKET, DWORD, LPVOID, DWORD, LPVOID, DWORD,
- LPDWORD, LPWSAOVERLAPPED,
- LPWSAOVERLAPPED_COMPLETION_ROUTINE));
-#ifndef NO_IPV6
-DECL_WINDOWS_FUNCTION(static, int, getaddrinfo,
- (const char *nodename, const char *servname,
- const struct addrinfo *hints, struct addrinfo **res));
-DECL_WINDOWS_FUNCTION(static, void, freeaddrinfo, (struct addrinfo *res));
-DECL_WINDOWS_FUNCTION(static, int, getnameinfo,
- (const struct sockaddr FAR * sa, socklen_t salen,
- char FAR * host, size_t hostlen, char FAR * serv,
- size_t servlen, int flags));
-DECL_WINDOWS_FUNCTION(static, char *, gai_strerror, (int ecode));
-DECL_WINDOWS_FUNCTION(static, int, WSAAddressToStringA,
- (LPSOCKADDR, DWORD, LPWSAPROTOCOL_INFO,
- LPSTR, LPDWORD));
-#endif
-
-static HMODULE winsock_module = NULL;
-static WSADATA wsadata;
-#ifndef NO_IPV6
-static HMODULE winsock2_module = NULL;
-static HMODULE wship6_module = NULL;
-#endif
-
-int sk_startup(int hi, int lo)
-{
- WORD winsock_ver;
-
- winsock_ver = MAKEWORD(hi, lo);
-
- if (p_WSAStartup(winsock_ver, &wsadata)) {
- return FALSE;
- }
-
- if (LOBYTE(wsadata.wVersion) != LOBYTE(winsock_ver)) {
- return FALSE;
- }
-
-#ifdef NET_SETUP_DIAGNOSTICS
- {
- char buf[80];
- sprintf(buf, "Using WinSock %d.%d", hi, lo);
- logevent(NULL, buf);
- }
-#endif
- return TRUE;
-}
-
-void sk_init(void)
-{
-#ifndef NO_IPV6
- winsock2_module =
-#endif
- winsock_module = load_system32_dll("ws2_32.dll");
- if (!winsock_module) {
- winsock_module = load_system32_dll("wsock32.dll");
- }
- if (!winsock_module)
- fatalbox("Unable to load any WinSock library");
-
-#ifndef NO_IPV6
- /* Check if we have getaddrinfo in Winsock */
- if (GetProcAddress(winsock_module, "getaddrinfo") != NULL) {
-#ifdef NET_SETUP_DIAGNOSTICS
- logevent(NULL, "Native WinSock IPv6 support detected");
-#endif
- GET_WINDOWS_FUNCTION(winsock_module, getaddrinfo);
- GET_WINDOWS_FUNCTION(winsock_module, freeaddrinfo);
- GET_WINDOWS_FUNCTION(winsock_module, getnameinfo);
- GET_WINDOWS_FUNCTION(winsock_module, gai_strerror);
- } else {
- /* Fall back to wship6.dll for Windows 2000 */
- wship6_module = load_system32_dll("wship6.dll");
- if (wship6_module) {
-#ifdef NET_SETUP_DIAGNOSTICS
- logevent(NULL, "WSH IPv6 support detected");
-#endif
- GET_WINDOWS_FUNCTION(wship6_module, getaddrinfo);
- GET_WINDOWS_FUNCTION(wship6_module, freeaddrinfo);
- GET_WINDOWS_FUNCTION(wship6_module, getnameinfo);
- GET_WINDOWS_FUNCTION(wship6_module, gai_strerror);
- } else {
-#ifdef NET_SETUP_DIAGNOSTICS
- logevent(NULL, "No IPv6 support detected");
-#endif
- }
- }
- GET_WINDOWS_FUNCTION(winsock2_module, WSAAddressToStringA);
-#else
-#ifdef NET_SETUP_DIAGNOSTICS
- logevent(NULL, "PuTTY was built without IPv6 support");
-#endif
-#endif
-
- GET_WINDOWS_FUNCTION(winsock_module, WSAAsyncSelect);
- GET_WINDOWS_FUNCTION(winsock_module, WSAEventSelect);
- GET_WINDOWS_FUNCTION(winsock_module, select);
- GET_WINDOWS_FUNCTION(winsock_module, WSAGetLastError);
- GET_WINDOWS_FUNCTION(winsock_module, WSAEnumNetworkEvents);
- GET_WINDOWS_FUNCTION(winsock_module, WSAStartup);
- GET_WINDOWS_FUNCTION(winsock_module, WSACleanup);
- GET_WINDOWS_FUNCTION(winsock_module, closesocket);
- GET_WINDOWS_FUNCTION(winsock_module, ntohl);
- GET_WINDOWS_FUNCTION(winsock_module, htonl);
- GET_WINDOWS_FUNCTION(winsock_module, htons);
- GET_WINDOWS_FUNCTION(winsock_module, ntohs);
- GET_WINDOWS_FUNCTION(winsock_module, gethostname);
- GET_WINDOWS_FUNCTION(winsock_module, gethostbyname);
- GET_WINDOWS_FUNCTION(winsock_module, getservbyname);
- GET_WINDOWS_FUNCTION(winsock_module, inet_addr);
- GET_WINDOWS_FUNCTION(winsock_module, inet_ntoa);
- GET_WINDOWS_FUNCTION(winsock_module, connect);
- GET_WINDOWS_FUNCTION(winsock_module, bind);
- GET_WINDOWS_FUNCTION(winsock_module, setsockopt);
- GET_WINDOWS_FUNCTION(winsock_module, socket);
- GET_WINDOWS_FUNCTION(winsock_module, listen);
- GET_WINDOWS_FUNCTION(winsock_module, send);
- GET_WINDOWS_FUNCTION(winsock_module, shutdown);
- GET_WINDOWS_FUNCTION(winsock_module, ioctlsocket);
- GET_WINDOWS_FUNCTION(winsock_module, accept);
- GET_WINDOWS_FUNCTION(winsock_module, recv);
- GET_WINDOWS_FUNCTION(winsock_module, WSAIoctl);
-
- /* Try to get the best WinSock version we can get */
- if (!sk_startup(2,2) &&
- !sk_startup(2,0) &&
- !sk_startup(1,1)) {
- fatalbox("Unable to initialise WinSock");
- }
-
- sktree = newtree234(cmpfortree);
-}
-
-void sk_cleanup(void)
-{
- Actual_Socket s;
- int i;
-
- if (sktree) {
- for (i = 0; (s = index234(sktree, i)) != NULL; i++) {
- p_closesocket(s->s);
- }
- freetree234(sktree);
- sktree = NULL;
- }
-
- if (p_WSACleanup)
- p_WSACleanup();
- if (winsock_module)
- FreeLibrary(winsock_module);
-#ifndef NO_IPV6
- if (wship6_module)
- FreeLibrary(wship6_module);
-#endif
-}
-
-char *winsock_error_string(int error)
-{
- switch (error) {
- case WSAEACCES:
- return "Network error: Permission denied";
- case WSAEADDRINUSE:
- return "Network error: Address already in use";
- case WSAEADDRNOTAVAIL:
- return "Network error: Cannot assign requested address";
- case WSAEAFNOSUPPORT:
- return
- "Network error: Address family not supported by protocol family";
- case WSAEALREADY:
- return "Network error: Operation already in progress";
- case WSAECONNABORTED:
- return "Network error: Software caused connection abort";
- case WSAECONNREFUSED:
- return "Network error: Connection refused";
- case WSAECONNRESET:
- return "Network error: Connection reset by peer";
- case WSAEDESTADDRREQ:
- return "Network error: Destination address required";
- case WSAEFAULT:
- return "Network error: Bad address";
- case WSAEHOSTDOWN:
- return "Network error: Host is down";
- case WSAEHOSTUNREACH:
- return "Network error: No route to host";
- case WSAEINPROGRESS:
- return "Network error: Operation now in progress";
- case WSAEINTR:
- return "Network error: Interrupted function call";
- case WSAEINVAL:
- return "Network error: Invalid argument";
- case WSAEISCONN:
- return "Network error: Socket is already connected";
- case WSAEMFILE:
- return "Network error: Too many open files";
- case WSAEMSGSIZE:
- return "Network error: Message too long";
- case WSAENETDOWN:
- return "Network error: Network is down";
- case WSAENETRESET:
- return "Network error: Network dropped connection on reset";
- case WSAENETUNREACH:
- return "Network error: Network is unreachable";
- case WSAENOBUFS:
- return "Network error: No buffer space available";
- case WSAENOPROTOOPT:
- return "Network error: Bad protocol option";
- case WSAENOTCONN:
- return "Network error: Socket is not connected";
- case WSAENOTSOCK:
- return "Network error: Socket operation on non-socket";
- case WSAEOPNOTSUPP:
- return "Network error: Operation not supported";
- case WSAEPFNOSUPPORT:
- return "Network error: Protocol family not supported";
- case WSAEPROCLIM:
- return "Network error: Too many processes";
- case WSAEPROTONOSUPPORT:
- return "Network error: Protocol not supported";
- case WSAEPROTOTYPE:
- return "Network error: Protocol wrong type for socket";
- case WSAESHUTDOWN:
- return "Network error: Cannot send after socket shutdown";
- case WSAESOCKTNOSUPPORT:
- return "Network error: Socket type not supported";
- case WSAETIMEDOUT:
- return "Network error: Connection timed out";
- case WSAEWOULDBLOCK:
- return "Network error: Resource temporarily unavailable";
- case WSAEDISCON:
- return "Network error: Graceful shutdown in progress";
- default:
- return "Unknown network error";
- }
-}
-
-SockAddr sk_namelookup(const char *host, char **canonicalname,
- int address_family)
-{
- SockAddr ret = snew(struct SockAddr_tag);
- unsigned long a;
- char realhost[8192];
- int hint_family;
-
- /* Default to IPv4. */
- hint_family = (address_family == ADDRTYPE_IPV4 ? AF_INET :
-#ifndef NO_IPV6
- address_family == ADDRTYPE_IPV6 ? AF_INET6 :
-#endif
- AF_UNSPEC);
-
- /* Clear the structure and default to IPv4. */
- memset(ret, 0, sizeof(struct SockAddr_tag));
-#ifndef NO_IPV6
- ret->ais = NULL;
-#endif
- ret->addresses = NULL;
- ret->resolved = FALSE;
- ret->refcount = 1;
- *realhost = '\0';
-
- if ((a = p_inet_addr(host)) == (unsigned long) INADDR_NONE) {
- struct hostent *h = NULL;
- int err;
-#ifndef NO_IPV6
- /*
- * Use getaddrinfo when it's available
- */
- if (p_getaddrinfo) {
- struct addrinfo hints;
-#ifdef NET_SETUP_DIAGNOSTICS
- logevent(NULL, "Using getaddrinfo() for resolving");
-#endif
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = hint_family;
- hints.ai_flags = AI_CANONNAME;
- if ((err = p_getaddrinfo(host, NULL, &hints, &ret->ais)) == 0)
- ret->resolved = TRUE;
- } else
-#endif
- {
-#ifdef NET_SETUP_DIAGNOSTICS
- logevent(NULL, "Using gethostbyname() for resolving");
-#endif
- /*
- * Otherwise use the IPv4-only gethostbyname...
- * (NOTE: we don't use gethostbyname as a fallback!)
- */
- if ( (h = p_gethostbyname(host)) )
- ret->resolved = TRUE;
- else
- err = p_WSAGetLastError();
- }
-
- if (!ret->resolved) {
- ret->error = (err == WSAENETDOWN ? "Network is down" :
- err == WSAHOST_NOT_FOUND ? "Host does not exist" :
- err == WSATRY_AGAIN ? "Host not found" :
-#ifndef NO_IPV6
- p_getaddrinfo&&p_gai_strerror ? p_gai_strerror(err) :
-#endif
- "gethostbyname: unknown error");
- } else {
- ret->error = NULL;
-
-#ifndef NO_IPV6
- /* If we got an address info use that... */
- if (ret->ais) {
- /* Are we in IPv4 fallback mode? */
- /* We put the IPv4 address into the a variable so we can further-on use the IPv4 code... */
- if (ret->ais->ai_family == AF_INET)
- memcpy(&a,
- (char *) &((SOCKADDR_IN *) ret->ais->
- ai_addr)->sin_addr, sizeof(a));
-
- if (ret->ais->ai_canonname)
- strncpy(realhost, ret->ais->ai_canonname, lenof(realhost));
- else
- strncpy(realhost, host, lenof(realhost));
- }
- /* We used the IPv4-only gethostbyname()... */
- else
-#endif
- {
- int n;
- for (n = 0; h->h_addr_list[n]; n++);
- ret->addresses = snewn(n, unsigned long);
- ret->naddresses = n;
- for (n = 0; n < ret->naddresses; n++) {
- memcpy(&a, h->h_addr_list[n], sizeof(a));
- ret->addresses[n] = p_ntohl(a);
- }
- memcpy(&a, h->h_addr, sizeof(a));
- /* This way we are always sure the h->h_name is valid :) */
- strncpy(realhost, h->h_name, sizeof(realhost));
- }
- }
- } else {
- /*
- * This must be a numeric IPv4 address because it caused a
- * success return from inet_addr.
- */
- ret->addresses = snewn(1, unsigned long);
- ret->naddresses = 1;
- ret->addresses[0] = p_ntohl(a);
- ret->resolved = TRUE;
- strncpy(realhost, host, sizeof(realhost));
- }
- realhost[lenof(realhost)-1] = '\0';
- *canonicalname = snewn(1+strlen(realhost), char);
- strcpy(*canonicalname, realhost);
- return ret;
-}
-
-SockAddr sk_nonamelookup(const char *host)
-{
- SockAddr ret = snew(struct SockAddr_tag);
- ret->error = NULL;
- ret->resolved = FALSE;
-#ifndef NO_IPV6
- ret->ais = NULL;
-#endif
- ret->addresses = NULL;
- ret->naddresses = 0;
- ret->refcount = 1;
- strncpy(ret->hostname, host, lenof(ret->hostname));
- ret->hostname[lenof(ret->hostname)-1] = '\0';
- return ret;
-}
-
-int sk_nextaddr(SockAddr addr, SockAddrStep *step)
-{
-#ifndef NO_IPV6
- if (step->ai) {
- if (step->ai->ai_next) {
- step->ai = step->ai->ai_next;
- return TRUE;
- } else
- return FALSE;
- }
-#endif
- if (step->curraddr+1 < addr->naddresses) {
- step->curraddr++;
- return TRUE;
- } else {
- return FALSE;
- }
-}
-
-void sk_getaddr(SockAddr addr, char *buf, int buflen)
-{
- SockAddrStep step;
- START_STEP(addr, step);
-
-#ifndef NO_IPV6
- if (step.ai) {
- int err = 0;
- if (p_WSAAddressToStringA) {
- DWORD dwbuflen = buflen;
- err = p_WSAAddressToStringA(step.ai->ai_addr, step.ai->ai_addrlen,
- NULL, buf, &dwbuflen);
- } else
- err = -1;
- if (err) {
- strncpy(buf, addr->hostname, buflen);
- if (!buf[0])
- strncpy(buf, "<unknown>", buflen);
- buf[buflen-1] = '\0';
- }
- } else
-#endif
- if (SOCKADDR_FAMILY(addr, step) == AF_INET) {
- struct in_addr a;
- assert(addr->addresses && step.curraddr < addr->naddresses);
- a.s_addr = p_htonl(addr->addresses[step.curraddr]);
- strncpy(buf, p_inet_ntoa(a), buflen);
- buf[buflen-1] = '\0';
- } else {
- strncpy(buf, addr->hostname, buflen);
- buf[buflen-1] = '\0';
- }
-}
-
-int sk_hostname_is_local(char *name)
-{
- return !strcmp(name, "localhost") ||
- !strcmp(name, "::1") ||
- !strncmp(name, "127.", 4);
-}
-
-static INTERFACE_INFO local_interfaces[16];
-static int n_local_interfaces; /* 0=not yet, -1=failed, >0=number */
-
-static int ipv4_is_local_addr(struct in_addr addr)
-{
- if (ipv4_is_loopback(addr))
- return 1; /* loopback addresses are local */
- if (!n_local_interfaces) {
- SOCKET s = p_socket(AF_INET, SOCK_DGRAM, 0);
- DWORD retbytes;
-
- if (p_WSAIoctl &&
- p_WSAIoctl(s, SIO_GET_INTERFACE_LIST, NULL, 0,
- local_interfaces, sizeof(local_interfaces),
- &retbytes, NULL, NULL) == 0)
- n_local_interfaces = retbytes / sizeof(INTERFACE_INFO);
- else
- logevent(NULL, "Unable to get list of local IP addresses");
- }
- if (n_local_interfaces > 0) {
- int i;
- for (i = 0; i < n_local_interfaces; i++) {
- SOCKADDR_IN *address =
- (SOCKADDR_IN *)&local_interfaces[i].iiAddress;
- if (address->sin_addr.s_addr == addr.s_addr)
- return 1; /* this address is local */
- }
- }
- return 0; /* this address is not local */
-}
-
-int sk_address_is_local(SockAddr addr)
-{
- SockAddrStep step;
- int family;
- START_STEP(addr, step);
- family = SOCKADDR_FAMILY(addr, step);
-
-#ifndef NO_IPV6
- if (family == AF_INET6) {
- return IN6_IS_ADDR_LOOPBACK((const struct in6_addr *)step.ai->ai_addr);
- } else
-#endif
- if (family == AF_INET) {
-#ifndef NO_IPV6
- if (step.ai) {
- return ipv4_is_local_addr(((struct sockaddr_in *)step.ai->ai_addr)
- ->sin_addr);
- } else
-#endif
- {
- struct in_addr a;
- assert(addr->addresses && step.curraddr < addr->naddresses);
- a.s_addr = p_htonl(addr->addresses[step.curraddr]);
- return ipv4_is_local_addr(a);
- }
- } else {
- assert(family == AF_UNSPEC);
- return 0; /* we don't know; assume not */
- }
-}
-
-int sk_addrtype(SockAddr addr)
-{
- SockAddrStep step;
- int family;
- START_STEP(addr, step);
- family = SOCKADDR_FAMILY(addr, step);
-
- return (family == AF_INET ? ADDRTYPE_IPV4 :
-#ifndef NO_IPV6
- family == AF_INET6 ? ADDRTYPE_IPV6 :
-#endif
- ADDRTYPE_NAME);
-}
-
-void sk_addrcopy(SockAddr addr, char *buf)
-{
- SockAddrStep step;
- int family;
- START_STEP(addr, step);
- family = SOCKADDR_FAMILY(addr, step);
-
- assert(family != AF_UNSPEC);
-#ifndef NO_IPV6
- if (step.ai) {
- if (family == AF_INET)
- memcpy(buf, &((struct sockaddr_in *)step.ai->ai_addr)->sin_addr,
- sizeof(struct in_addr));
- else if (family == AF_INET6)
- memcpy(buf, &((struct sockaddr_in6 *)step.ai->ai_addr)->sin6_addr,
- sizeof(struct in6_addr));
- else
- assert(FALSE);
- } else
-#endif
- if (family == AF_INET) {
- struct in_addr a;
- assert(addr->addresses && step.curraddr < addr->naddresses);
- a.s_addr = p_htonl(addr->addresses[step.curraddr]);
- memcpy(buf, (char*) &a.s_addr, 4);
- }
-}
-
-void sk_addr_free(SockAddr addr)
-{
- if (--addr->refcount > 0)
- return;
-#ifndef NO_IPV6
- if (addr->ais && p_freeaddrinfo)
- p_freeaddrinfo(addr->ais);
-#endif
- if (addr->addresses)
- sfree(addr->addresses);
- sfree(addr);
-}
-
-SockAddr sk_addr_dup(SockAddr addr)
-{
- addr->refcount++;
- return addr;
-}
-
-static Plug sk_tcp_plug(Socket sock, Plug p)
-{
- Actual_Socket s = (Actual_Socket) sock;
- Plug ret = s->plug;
- if (p)
- s->plug = p;
- return ret;
-}
-
-static void sk_tcp_flush(Socket s)
-{
- /*
- * We send data to the socket as soon as we can anyway,
- * so we don't need to do anything here. :-)
- */
-}
-
-static void sk_tcp_close(Socket s);
-static int sk_tcp_write(Socket s, const char *data, int len);
-static int sk_tcp_write_oob(Socket s, const char *data, int len);
-static void sk_tcp_write_eof(Socket s);
-static void sk_tcp_set_private_ptr(Socket s, void *ptr);
-static void *sk_tcp_get_private_ptr(Socket s);
-static void sk_tcp_set_frozen(Socket s, int is_frozen);
-static const char *sk_tcp_socket_error(Socket s);
-
-extern char *do_select(SOCKET skt, int startup);
-
-Socket sk_register(void *sock, Plug plug)
-{
- static const struct socket_function_table fn_table = {
- sk_tcp_plug,
- sk_tcp_close,
- sk_tcp_write,
- sk_tcp_write_oob,
- sk_tcp_write_eof,
- sk_tcp_flush,
- sk_tcp_set_private_ptr,
- sk_tcp_get_private_ptr,
- sk_tcp_set_frozen,
- sk_tcp_socket_error
- };
-
- DWORD err;
- char *errstr;
- Actual_Socket ret;
-
- /*
- * Create Socket structure.
- */
- ret = snew(struct Socket_tag);
- ret->fn = &fn_table;
- ret->error = NULL;
- ret->plug = plug;
- bufchain_init(&ret->output_data);
- ret->writable = 1; /* to start with */
- ret->sending_oob = 0;
- ret->outgoingeof = EOF_NO;
- ret->frozen = 1;
- ret->frozen_readable = 0;
- ret->localhost_only = 0; /* unused, but best init anyway */
- ret->pending_error = 0;
- ret->parent = ret->child = NULL;
- ret->addr = NULL;
-
- ret->s = (SOCKET)sock;
-
- if (ret->s == INVALID_SOCKET) {
- err = p_WSAGetLastError();
- ret->error = winsock_error_string(err);
- return (Socket) ret;
- }
-
- ret->oobinline = 0;
-
- /* Set up a select mechanism. This could be an AsyncSelect on a
- * window, or an EventSelect on an event object. */
- errstr = do_select(ret->s, 1);
- if (errstr) {
- ret->error = errstr;
- return (Socket) ret;
- }
-
- add234(sktree, ret);
-
- return (Socket) ret;
-}
-
-static DWORD try_connect(Actual_Socket sock)
-{
- SOCKET s;
-#ifndef NO_IPV6
- SOCKADDR_IN6 a6;
-#endif
- SOCKADDR_IN a;
- DWORD err;
- char *errstr;
- short localport;
- int family;
-
- if (sock->s != INVALID_SOCKET) {
- do_select(sock->s, 0);
- p_closesocket(sock->s);
- }
-
- plug_log(sock->plug, 0, sock->addr, sock->port, NULL, 0);
-
- /*
- * Open socket.
- */
- family = SOCKADDR_FAMILY(sock->addr, sock->step);
-
- /*
- * Remove the socket from the tree before we overwrite its
- * internal socket id, because that forms part of the tree's
- * sorting criterion. We'll add it back before exiting this
- * function, whether we changed anything or not.
- */
- del234(sktree, sock);
-
- s = p_socket(family, SOCK_STREAM, 0);
- sock->s = s;
-
- if (s == INVALID_SOCKET) {
- err = p_WSAGetLastError();
- sock->error = winsock_error_string(err);
- goto ret;
- }
-
- if (sock->oobinline) {
- BOOL b = TRUE;
- p_setsockopt(s, SOL_SOCKET, SO_OOBINLINE, (void *) &b, sizeof(b));
- }
-
- if (sock->nodelay) {
- BOOL b = TRUE;
- p_setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void *) &b, sizeof(b));
- }
-
- if (sock->keepalive) {
- BOOL b = TRUE;
- p_setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *) &b, sizeof(b));
- }
-
- /*
- * Bind to local address.
- */
- if (sock->privport)
- localport = 1023; /* count from 1023 downwards */
- else
- localport = 0; /* just use port 0 (ie winsock picks) */
-
- /* Loop round trying to bind */
- while (1) {
- int sockcode;
-
-#ifndef NO_IPV6
- if (family == AF_INET6) {
- memset(&a6, 0, sizeof(a6));
- a6.sin6_family = AF_INET6;
- /*a6.sin6_addr = in6addr_any; */ /* == 0 done by memset() */
- a6.sin6_port = p_htons(localport);
- } else
-#endif
- {
- a.sin_family = AF_INET;
- a.sin_addr.s_addr = p_htonl(INADDR_ANY);
- a.sin_port = p_htons(localport);
- }
-#ifndef NO_IPV6
- sockcode = p_bind(s, (family == AF_INET6 ?
- (struct sockaddr *) &a6 :
- (struct sockaddr *) &a),
- (family == AF_INET6 ? sizeof(a6) : sizeof(a)));
-#else
- sockcode = p_bind(s, (struct sockaddr *) &a, sizeof(a));
-#endif
- if (sockcode != SOCKET_ERROR) {
- err = 0;
- break; /* done */
- } else {
- err = p_WSAGetLastError();
- if (err != WSAEADDRINUSE) /* failed, for a bad reason */
- break;
- }
-
- if (localport == 0)
- break; /* we're only looping once */
- localport--;
- if (localport == 0)
- break; /* we might have got to the end */
- }
-
- if (err) {
- sock->error = winsock_error_string(err);
- goto ret;
- }
-
- /*
- * Connect to remote address.
- */
-#ifndef NO_IPV6
- if (sock->step.ai) {
- if (family == AF_INET6) {
- a6.sin6_family = AF_INET6;
- a6.sin6_port = p_htons((short) sock->port);
- a6.sin6_addr =
- ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_addr;
- a6.sin6_flowinfo = ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_flowinfo;
- a6.sin6_scope_id = ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_scope_id;
- } else {
- a.sin_family = AF_INET;
- a.sin_addr =
- ((struct sockaddr_in *) sock->step.ai->ai_addr)->sin_addr;
- a.sin_port = p_htons((short) sock->port);
- }
- } else
-#endif
- {
- assert(sock->addr->addresses && sock->step.curraddr < sock->addr->naddresses);
- a.sin_family = AF_INET;
- a.sin_addr.s_addr = p_htonl(sock->addr->addresses[sock->step.curraddr]);
- a.sin_port = p_htons((short) sock->port);
- }
-
- /* Set up a select mechanism. This could be an AsyncSelect on a
- * window, or an EventSelect on an event object. */
- errstr = do_select(s, 1);
- if (errstr) {
- sock->error = errstr;
- err = 1;
- goto ret;
- }
-
- if ((
-#ifndef NO_IPV6
- p_connect(s,
- ((family == AF_INET6) ? (struct sockaddr *) &a6 :
- (struct sockaddr *) &a),
- (family == AF_INET6) ? sizeof(a6) : sizeof(a))
-#else
- p_connect(s, (struct sockaddr *) &a, sizeof(a))
-#endif
- ) == SOCKET_ERROR) {
- err = p_WSAGetLastError();
- /*
- * We expect a potential EWOULDBLOCK here, because the
- * chances are the front end has done a select for
- * FD_CONNECT, so that connect() will complete
- * asynchronously.
- */
- if ( err != WSAEWOULDBLOCK ) {
- sock->error = winsock_error_string(err);
- goto ret;
- }
- } else {
- /*
- * If we _don't_ get EWOULDBLOCK, the connect has completed
- * and we should set the socket as writable.
- */
- sock->writable = 1;
- }
-
- err = 0;
-
- ret:
-
- /*
- * No matter what happened, put the socket back in the tree.
- */
- add234(sktree, sock);
-
- if (err)
- plug_log(sock->plug, 1, sock->addr, sock->port, sock->error, err);
- return err;
-}
-
-Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
- int nodelay, int keepalive, Plug plug)
-{
- static const struct socket_function_table fn_table = {
- sk_tcp_plug,
- sk_tcp_close,
- sk_tcp_write,
- sk_tcp_write_oob,
- sk_tcp_write_eof,
- sk_tcp_flush,
- sk_tcp_set_private_ptr,
- sk_tcp_get_private_ptr,
- sk_tcp_set_frozen,
- sk_tcp_socket_error
- };
-
- Actual_Socket ret;
- DWORD err;
-
- /*
- * Create Socket structure.
- */
- ret = snew(struct Socket_tag);
- ret->fn = &fn_table;
- ret->error = NULL;
- ret->plug = plug;
- bufchain_init(&ret->output_data);
- ret->connected = 0; /* to start with */
- ret->writable = 0; /* to start with */
- ret->sending_oob = 0;
- ret->outgoingeof = EOF_NO;
- ret->frozen = 0;
- ret->frozen_readable = 0;
- ret->localhost_only = 0; /* unused, but best init anyway */
- ret->pending_error = 0;
- ret->parent = ret->child = NULL;
- ret->oobinline = oobinline;
- ret->nodelay = nodelay;
- ret->keepalive = keepalive;
- ret->privport = privport;
- ret->port = port;
- ret->addr = addr;
- START_STEP(ret->addr, ret->step);
- ret->s = INVALID_SOCKET;
-
- err = 0;
- do {
- err = try_connect(ret);
- } while (err && sk_nextaddr(ret->addr, &ret->step));
-
- return (Socket) ret;
-}
-
-Socket sk_newlistener(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,
- sk_tcp_close,
- sk_tcp_write,
- sk_tcp_write_oob,
- sk_tcp_write_eof,
- sk_tcp_flush,
- sk_tcp_set_private_ptr,
- sk_tcp_get_private_ptr,
- sk_tcp_set_frozen,
- sk_tcp_socket_error
- };
-
- SOCKET s;
-#ifndef NO_IPV6
- SOCKADDR_IN6 a6;
-#endif
- SOCKADDR_IN a;
-
- DWORD err;
- char *errstr;
- Actual_Socket ret;
- int retcode;
- int on = 1;
-
- int address_family;
-
- /*
- * Create Socket structure.
- */
- ret = snew(struct Socket_tag);
- ret->fn = &fn_table;
- ret->error = NULL;
- ret->plug = plug;
- bufchain_init(&ret->output_data);
- ret->writable = 0; /* to start with */
- ret->sending_oob = 0;
- ret->outgoingeof = EOF_NO;
- ret->frozen = 0;
- ret->frozen_readable = 0;
- ret->localhost_only = local_host_only;
- ret->pending_error = 0;
- ret->parent = ret->child = NULL;
- ret->addr = NULL;
-
- /*
- * Translate address_family from platform-independent constants
- * into local reality.
- */
- address_family = (orig_address_family == ADDRTYPE_IPV4 ? AF_INET :
-#ifndef NO_IPV6
- orig_address_family == ADDRTYPE_IPV6 ? AF_INET6 :
-#endif
- AF_UNSPEC);
-
- /*
- * Our default, if passed the `don't care' value
- * ADDRTYPE_UNSPEC, is to listen on IPv4. If IPv6 is supported,
- * we will also set up a second socket listening on IPv6, but
- * the v4 one is primary since that ought to work even on
- * non-v6-supporting systems.
- */
- if (address_family == AF_UNSPEC) address_family = AF_INET;
-
- /*
- * Open socket.
- */
- s = p_socket(address_family, SOCK_STREAM, 0);
- ret->s = s;
-
- if (s == INVALID_SOCKET) {
- err = p_WSAGetLastError();
- ret->error = winsock_error_string(err);
- return (Socket) ret;
- }
-
- ret->oobinline = 0;
-
- p_setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on));
-
-#ifndef NO_IPV6
- if (address_family == AF_INET6) {
- memset(&a6, 0, sizeof(a6));
- a6.sin6_family = AF_INET6;
- /* FIXME: srcaddr is ignored for IPv6, because I (SGT) don't
- * know how to do it. :-)
- * (jeroen:) saddr is specified as an address.. eg 2001:db8::1
- * Thus we need either a parser that understands [2001:db8::1]:80
- * style addresses and/or enhance this to understand hostnames too. */
- if (local_host_only)
- a6.sin6_addr = in6addr_loopback;
- else
- a6.sin6_addr = in6addr_any;
- a6.sin6_port = p_htons(port);
- } else
-#endif
- {
- int got_addr = 0;
- a.sin_family = AF_INET;
-
- /*
- * Bind to source address. First try an explicitly
- * specified one...
- */
- if (srcaddr) {
- a.sin_addr.s_addr = p_inet_addr(srcaddr);
- if (a.sin_addr.s_addr != INADDR_NONE) {
- /* Override localhost_only with specified listen addr. */
- ret->localhost_only = ipv4_is_loopback(a.sin_addr);
- got_addr = 1;
- }
- }
-
- /*
- * ... and failing that, go with one of the standard ones.
- */
- if (!got_addr) {
- if (local_host_only)
- a.sin_addr.s_addr = p_htonl(INADDR_LOOPBACK);
- else
- a.sin_addr.s_addr = p_htonl(INADDR_ANY);
- }
-
- a.sin_port = p_htons((short)port);
- }
-#ifndef NO_IPV6
- retcode = p_bind(s, (address_family == AF_INET6 ?
- (struct sockaddr *) &a6 :
- (struct sockaddr *) &a),
- (address_family ==
- AF_INET6 ? sizeof(a6) : sizeof(a)));
-#else
- retcode = p_bind(s, (struct sockaddr *) &a, sizeof(a));
-#endif
- if (retcode != SOCKET_ERROR) {
- err = 0;
- } else {
- err = p_WSAGetLastError();
- }
-
- if (err) {
- p_closesocket(s);
- ret->error = winsock_error_string(err);
- return (Socket) ret;
- }
-
-
- if (p_listen(s, SOMAXCONN) == SOCKET_ERROR) {
- p_closesocket(s);
- ret->error = winsock_error_string(err);
- return (Socket) ret;
- }
-
- /* Set up a select mechanism. This could be an AsyncSelect on a
- * window, or an EventSelect on an event object. */
- errstr = do_select(s, 1);
- if (errstr) {
- p_closesocket(s);
- ret->error = errstr;
- return (Socket) ret;
- }
-
- add234(sktree, ret);
-
-#ifndef NO_IPV6
- /*
- * If we were given ADDRTYPE_UNSPEC, we must also create an
- * IPv6 listening socket and link it to this one.
- */
- if (address_family == AF_INET && orig_address_family == ADDRTYPE_UNSPEC) {
- Actual_Socket other;
-
- other = (Actual_Socket) sk_newlistener(srcaddr, port, plug,
- local_host_only, ADDRTYPE_IPV6);
-
- if (other) {
- if (!other->error) {
- other->parent = ret;
- ret->child = other;
- } else {
- sfree(other);
- }
- }
- }
-#endif
-
- return (Socket) ret;
-}
-
-static void sk_tcp_close(Socket sock)
-{
- extern char *do_select(SOCKET skt, int startup);
- Actual_Socket s = (Actual_Socket) sock;
-
- if (s->child)
- sk_tcp_close((Socket)s->child);
-
- del234(sktree, s);
- do_select(s->s, 0);
- p_closesocket(s->s);
- if (s->addr)
- sk_addr_free(s->addr);
- sfree(s);
-}
-
-/*
- * The function which tries to send on a socket once it's deemed
- * writable.
- */
-void try_send(Actual_Socket s)
-{
- while (s->sending_oob || bufchain_size(&s->output_data) > 0) {
- int nsent;
- DWORD err;
- void *data;
- int len, urgentflag;
-
- if (s->sending_oob) {
- urgentflag = MSG_OOB;
- len = s->sending_oob;
- data = &s->oobdata;
- } else {
- urgentflag = 0;
- bufchain_prefix(&s->output_data, &data, &len);
- }
- nsent = p_send(s->s, data, len, urgentflag);
- noise_ultralight(nsent);
- if (nsent <= 0) {
- err = (nsent < 0 ? p_WSAGetLastError() : 0);
- if ((err < WSABASEERR && nsent < 0) || err == WSAEWOULDBLOCK) {
- /*
- * Perfectly normal: we've sent all we can for the moment.
- *
- * (Some WinSock send() implementations can return
- * <0 but leave no sensible error indication -
- * WSAGetLastError() is called but returns zero or
- * a small number - so we check that case and treat
- * it just like WSAEWOULDBLOCK.)
- */
- s->writable = FALSE;
- return;
- } else if (nsent == 0 ||
- err == WSAECONNABORTED || err == WSAECONNRESET) {
- /*
- * If send() returns CONNABORTED or CONNRESET, we
- * unfortunately can't just call plug_closing(),
- * because it's quite likely that we're currently
- * _in_ a call from the code we'd be calling back
- * to, so we'd have to make half the SSH code
- * reentrant. Instead we flag a pending error on
- * the socket, to be dealt with (by calling
- * plug_closing()) at some suitable future moment.
- */
- s->pending_error = err;
- return;
- } else {
- /* We're inside the Windows frontend here, so we know
- * that the frontend handle is unnecessary. */
- logevent(NULL, winsock_error_string(err));
- fatalbox("%s", winsock_error_string(err));
- }
- } else {
- if (s->sending_oob) {
- if (nsent < len) {
- memmove(s->oobdata, s->oobdata+nsent, len-nsent);
- s->sending_oob = len - nsent;
- } else {
- s->sending_oob = 0;
- }
- } else {
- bufchain_consume(&s->output_data, nsent);
- }
- }
- }
-
- /*
- * If we reach here, we've finished sending everything we might
- * have needed to send. Send EOF, if we need to.
- */
- if (s->outgoingeof == EOF_PENDING) {
- p_shutdown(s->s, SD_SEND);
- s->outgoingeof = EOF_SENT;
- }
-}
-
-static int sk_tcp_write(Socket sock, const char *buf, int len)
-{
- Actual_Socket s = (Actual_Socket) sock;
-
- assert(s->outgoingeof == EOF_NO);
-
- /*
- * Add the data to the buffer list on the socket.
- */
- bufchain_add(&s->output_data, buf, len);
-
- /*
- * Now try sending from the start of the buffer list.
- */
- if (s->writable)
- try_send(s);
-
- return bufchain_size(&s->output_data);
-}
-
-static int sk_tcp_write_oob(Socket sock, const char *buf, int len)
-{
- Actual_Socket s = (Actual_Socket) sock;
-
- assert(s->outgoingeof == EOF_NO);
-
- /*
- * Replace the buffer list on the socket with the data.
- */
- bufchain_clear(&s->output_data);
- assert(len <= sizeof(s->oobdata));
- memcpy(s->oobdata, buf, len);
- s->sending_oob = len;
-
- /*
- * Now try sending from the start of the buffer list.
- */
- if (s->writable)
- try_send(s);
-
- return s->sending_oob;
-}
-
-static void sk_tcp_write_eof(Socket sock)
-{
- Actual_Socket s = (Actual_Socket) sock;
-
- assert(s->outgoingeof == EOF_NO);
-
- /*
- * Mark the socket as pending outgoing EOF.
- */
- s->outgoingeof = EOF_PENDING;
-
- /*
- * Now try sending from the start of the buffer list.
- */
- if (s->writable)
- try_send(s);
-}
-
-int select_result(WPARAM wParam, LPARAM lParam)
-{
- int ret, open;
- DWORD err;
- char buf[20480]; /* nice big buffer for plenty of speed */
- Actual_Socket s;
- u_long atmark;
-
- /* wParam is the socket itself */
-
- if (wParam == 0)
- return 1; /* boggle */
-
- s = find234(sktree, (void *) wParam, cmpforsearch);
- if (!s)
- return 1; /* boggle */
-
- if ((err = WSAGETSELECTERROR(lParam)) != 0) {
- /*
- * An error has occurred on this socket. Pass it to the
- * plug.
- */
- if (s->addr) {
- plug_log(s->plug, 1, s->addr, s->port,
- winsock_error_string(err), err);
- while (s->addr && sk_nextaddr(s->addr, &s->step)) {
- err = try_connect(s);
- }
- }
- if (err != 0)
- return plug_closing(s->plug, winsock_error_string(err), err, 0);
- else
- return 1;
- }
-
- noise_ultralight(lParam);
-
- switch (WSAGETSELECTEVENT(lParam)) {
- case FD_CONNECT:
- s->connected = s->writable = 1;
- /*
- * Once a socket is connected, we can stop falling
- * back through the candidate addresses to connect
- * to.
- */
- if (s->addr) {
- sk_addr_free(s->addr);
- s->addr = NULL;
- }
- break;
- case FD_READ:
- /* In the case the socket is still frozen, we don't even bother */
- if (s->frozen) {
- s->frozen_readable = 1;
- break;
- }
-
- /*
- * We have received data on the socket. For an oobinline
- * socket, this might be data _before_ an urgent pointer,
- * in which case we send it to the back end with type==1
- * (data prior to urgent).
- */
- if (s->oobinline) {
- atmark = 1;
- p_ioctlsocket(s->s, SIOCATMARK, &atmark);
- /*
- * Avoid checking the return value from ioctlsocket(),
- * on the grounds that some WinSock wrappers don't
- * support it. If it does nothing, we get atmark==1,
- * which is equivalent to `no OOB pending', so the
- * effect will be to non-OOB-ify any OOB data.
- */
- } else
- atmark = 1;
-
- ret = p_recv(s->s, buf, sizeof(buf), 0);
- noise_ultralight(ret);
- if (ret < 0) {
- err = p_WSAGetLastError();
- if (err == WSAEWOULDBLOCK) {
- break;
- }
- }
- if (ret < 0) {
- return plug_closing(s->plug, winsock_error_string(err), err,
- 0);
- } else if (0 == ret) {
- return plug_closing(s->plug, NULL, 0, 0);
- } else {
- return plug_receive(s->plug, atmark ? 0 : 1, buf, ret);
- }
- break;
- case FD_OOB:
- /*
- * This will only happen on a non-oobinline socket. It
- * indicates that we can immediately perform an OOB read
- * and get back OOB data, which we will send to the back
- * end with type==2 (urgent data).
- */
- ret = p_recv(s->s, buf, sizeof(buf), MSG_OOB);
- noise_ultralight(ret);
- if (ret <= 0) {
- 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. */
- logevent(NULL, str);
- fatalbox("%s", str);
- } else {
- return plug_receive(s->plug, 2, buf, ret);
- }
- break;
- case FD_WRITE:
- {
- int bufsize_before, bufsize_after;
- s->writable = 1;
- bufsize_before = s->sending_oob + bufchain_size(&s->output_data);
- try_send(s);
- bufsize_after = s->sending_oob + bufchain_size(&s->output_data);
- if (bufsize_after < bufsize_before)
- plug_sent(s->plug, bufsize_after);
- }
- break;
- case FD_CLOSE:
- /* Signal a close on the socket. First read any outstanding data. */
- open = 1;
- do {
- ret = p_recv(s->s, buf, sizeof(buf), 0);
- if (ret < 0) {
- err = p_WSAGetLastError();
- if (err == WSAEWOULDBLOCK)
- break;
- return plug_closing(s->plug, winsock_error_string(err),
- err, 0);
- } else {
- if (ret)
- open &= plug_receive(s->plug, 0, buf, ret);
- else
- open &= plug_closing(s->plug, NULL, 0, 0);
- }
- } while (ret > 0);
- return open;
- case FD_ACCEPT:
- {
-#ifdef NO_IPV6
- struct sockaddr_in isa;
-#else
- struct sockaddr_storage isa;
-#endif
- int addrlen = sizeof(isa);
- SOCKET t; /* socket of connection */
-
- memset(&isa, 0, sizeof(isa));
- err = 0;
- t = p_accept(s->s,(struct sockaddr *)&isa,&addrlen);
- if (t == INVALID_SOCKET)
- {
- err = p_WSAGetLastError();
- if (err == WSATRY_AGAIN)
- break;
- }
-#ifndef NO_IPV6
- if (isa.ss_family == AF_INET &&
- s->localhost_only &&
- !ipv4_is_local_addr(((struct sockaddr_in *)&isa)->sin_addr))
-#else
- if (s->localhost_only && !ipv4_is_local_addr(isa.sin_addr))
-#endif
- {
- p_closesocket(t); /* dodgy WinSock let nonlocal through */
- } else if (plug_accepting(s->plug, (void*)t)) {
- p_closesocket(t); /* denied or error */
- }
- }
- }
-
- return 1;
-}
-
-/*
- * Deal with socket errors detected in try_send().
- */
-void net_pending_errors(void)
-{
- int i;
- Actual_Socket s;
-
- /*
- * This might be a fiddly business, because it's just possible
- * that handling a pending error on one socket might cause
- * others to be closed. (I can't think of any reason this might
- * happen in current SSH implementation, but to maintain
- * generality of this network layer I'll assume the worst.)
- *
- * So what we'll do is search the socket list for _one_ socket
- * with a pending error, and then handle it, and then search
- * the list again _from the beginning_. Repeat until we make a
- * pass with no socket errors present. That way we are
- * protected against the socket list changing under our feet.
- */
-
- do {
- for (i = 0; (s = index234(sktree, i)) != NULL; i++) {
- if (s->pending_error) {
- /*
- * An error has occurred on this socket. Pass it to the
- * plug.
- */
- plug_closing(s->plug,
- winsock_error_string(s->pending_error),
- s->pending_error, 0);
- break;
- }
- }
- } while (s);
-}
-
-/*
- * Each socket abstraction contains a `void *' private field in
- * which the client can keep state.
- */
-static void sk_tcp_set_private_ptr(Socket sock, void *ptr)
-{
- Actual_Socket s = (Actual_Socket) sock;
- s->private_ptr = ptr;
-}
-
-static void *sk_tcp_get_private_ptr(Socket sock)
-{
- Actual_Socket s = (Actual_Socket) sock;
- return s->private_ptr;
-}
-
-/*
- * Special error values are returned from sk_namelookup and sk_new
- * if there's a problem. These functions extract an error message,
- * or return NULL if there's no problem.
- */
-const char *sk_addr_error(SockAddr addr)
-{
- return addr->error;
-}
-static const char *sk_tcp_socket_error(Socket sock)
-{
- Actual_Socket s = (Actual_Socket) sock;
- return s->error;
-}
-
-static void sk_tcp_set_frozen(Socket sock, int is_frozen)
-{
- Actual_Socket s = (Actual_Socket) sock;
- if (s->frozen == is_frozen)
- return;
- s->frozen = is_frozen;
- if (!is_frozen) {
- do_select(s->s, 1);
- if (s->frozen_readable) {
- char c;
- p_recv(s->s, &c, 1, MSG_PEEK);
- }
- }
- s->frozen_readable = 0;
-}
-
-void socket_reselect_all(void)
-{
- Actual_Socket s;
- int i;
-
- for (i = 0; (s = index234(sktree, i)) != NULL; i++) {
- if (!s->frozen)
- do_select(s->s, 1);
- }
-}
-
-/*
- * For Plink: enumerate all sockets currently active.
- */
-SOCKET first_socket(int *state)
-{
- Actual_Socket s;
- *state = 0;
- s = index234(sktree, (*state)++);
- return s ? s->s : INVALID_SOCKET;
-}
-
-SOCKET next_socket(int *state)
-{
- Actual_Socket s = index234(sktree, (*state)++);
- return s ? s->s : INVALID_SOCKET;
-}
-
-extern int socket_writable(SOCKET skt)
-{
- Actual_Socket s = find234(sktree, (void *)skt, cmpforsearch);
-
- if (s)
- return bufchain_size(&s->output_data) > 0;
- else
- return 0;
-}
-
-int net_service_lookup(char *service)
-{
- struct servent *se;
- se = p_getservbyname(service, NULL);
- if (se != NULL)
- return p_ntohs(se->s_port);
- else
- return 0;
-}
-
-char *get_hostname(void)
-{
- int len = 128;
- char *hostname = NULL;
- do {
- len *= 2;
- hostname = sresize(hostname, len, char);
- if (p_gethostname(hostname, len) < 0) {
- sfree(hostname);
- hostname = NULL;
- break;
- }
- } while (strlen(hostname) >= (size_t)(len-1));
- return hostname;
-}
-
-SockAddr platform_get_x11_unix_address(const char *display, int displaynum,
- char **canonicalname)
-{
- SockAddr ret = snew(struct SockAddr_tag);
- memset(ret, 0, sizeof(struct SockAddr_tag));
- ret->error = "unix sockets not supported on this platform";
- ret->refcount = 1;
- return ret;
-}
+/*
+ * Windows networking abstraction.
+ *
+ * For the IPv6 code in here I am indebted to Jeroen Massar and
+ * unfix.org.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#define DEFINE_PLUG_METHOD_MACROS
+#include "putty.h"
+#include "network.h"
+#include "tree234.h"
+
+#include <ws2tcpip.h>
+
+#ifndef NO_IPV6
+const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT;
+const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT;
+#endif
+
+#define ipv4_is_loopback(addr) \
+ ((p_ntohl(addr.s_addr) & 0xFF000000L) == 0x7F000000L)
+
+/*
+ * We used to typedef struct Socket_tag *Socket.
+ *
+ * Since we have made the networking abstraction slightly more
+ * abstract, Socket no longer means a tcp socket (it could mean
+ * an ssl socket). So now we must use Actual_Socket when we know
+ * we are talking about a tcp socket.
+ */
+typedef struct Socket_tag *Actual_Socket;
+
+/*
+ * Mutable state that goes with a SockAddr: stores information
+ * about where in the list of candidate IP(v*) addresses we've
+ * currently got to.
+ */
+typedef struct SockAddrStep_tag SockAddrStep;
+struct SockAddrStep_tag {
+#ifndef NO_IPV6
+ struct addrinfo *ai; /* steps along addr->ais */
+#endif
+ int curraddr;
+};
+
+struct Socket_tag {
+ const struct socket_function_table *fn;
+ /* the above variable absolutely *must* be the first in this structure */
+ char *error;
+ SOCKET s;
+ Plug plug;
+ bufchain output_data;
+ int connected;
+ int writable;
+ int frozen; /* this causes readability notifications to be ignored */
+ int frozen_readable; /* this means we missed at least one readability
+ * notification while we were frozen */
+ int localhost_only; /* for listening sockets */
+ char oobdata[1];
+ int sending_oob;
+ int oobinline, nodelay, keepalive, privport;
+ enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
+ SockAddr addr;
+ SockAddrStep step;
+ int port;
+ int pending_error; /* in case send() returns error */
+ /*
+ * We sometimes need pairs of Socket structures to be linked:
+ * if we are listening on the same IPv6 and v4 port, for
+ * example. So here we define `parent' and `child' pointers to
+ * track this link.
+ */
+ Actual_Socket parent, child;
+};
+
+struct SockAddr_tag {
+ int refcount;
+ char *error;
+ int resolved;
+ int namedpipe; /* indicates that this SockAddr is phony, holding a Windows
+ * named pipe pathname instead of a network address */
+#ifndef NO_IPV6
+ struct addrinfo *ais; /* Addresses IPv6 style. */
+#endif
+ unsigned long *addresses; /* Addresses IPv4 style. */
+ int naddresses;
+ char hostname[512]; /* Store an unresolved host name. */
+};
+
+/*
+ * Which address family this address belongs to. AF_INET for IPv4;
+ * AF_INET6 for IPv6; AF_UNSPEC indicates that name resolution has
+ * not been done and a simple host name is held in this SockAddr
+ * structure.
+ */
+#ifndef NO_IPV6
+#define SOCKADDR_FAMILY(addr, step) \
+ (!(addr)->resolved ? AF_UNSPEC : \
+ (step).ai ? (step).ai->ai_family : AF_INET)
+#else
+#define SOCKADDR_FAMILY(addr, step) \
+ (!(addr)->resolved ? AF_UNSPEC : AF_INET)
+#endif
+
+/*
+ * Start a SockAddrStep structure to step through multiple
+ * addresses.
+ */
+#ifndef NO_IPV6
+#define START_STEP(addr, step) \
+ ((step).ai = (addr)->ais, (step).curraddr = 0)
+#else
+#define START_STEP(addr, step) \
+ ((step).curraddr = 0)
+#endif
+
+static tree234 *sktree;
+
+static int cmpfortree(void *av, void *bv)
+{
+ Actual_Socket a = (Actual_Socket) av, b = (Actual_Socket) bv;
+ unsigned long as = (unsigned long) a->s, bs = (unsigned long) b->s;
+ if (as < bs)
+ return -1;
+ if (as > bs)
+ return +1;
+ if (a < b)
+ return -1;
+ if (a > b)
+ return +1;
+ return 0;
+}
+
+static int cmpforsearch(void *av, void *bv)
+{
+ Actual_Socket b = (Actual_Socket) bv;
+ unsigned long as = (unsigned long) av, bs = (unsigned long) b->s;
+ if (as < bs)
+ return -1;
+ if (as > bs)
+ return +1;
+ return 0;
+}
+
+DECL_WINDOWS_FUNCTION(static, int, WSAStartup, (WORD, LPWSADATA));
+DECL_WINDOWS_FUNCTION(static, int, WSACleanup, (void));
+DECL_WINDOWS_FUNCTION(static, int, closesocket, (SOCKET));
+DECL_WINDOWS_FUNCTION(static, u_long, ntohl, (u_long));
+DECL_WINDOWS_FUNCTION(static, u_long, htonl, (u_long));
+DECL_WINDOWS_FUNCTION(static, u_short, htons, (u_short));
+DECL_WINDOWS_FUNCTION(static, u_short, ntohs, (u_short));
+DECL_WINDOWS_FUNCTION(static, int, gethostname, (char *, int));
+DECL_WINDOWS_FUNCTION(static, struct hostent FAR *, gethostbyname,
+ (const char FAR *));
+DECL_WINDOWS_FUNCTION(static, struct servent FAR *, getservbyname,
+ (const char FAR *, const char FAR *));
+DECL_WINDOWS_FUNCTION(static, unsigned long, inet_addr, (const char FAR *));
+DECL_WINDOWS_FUNCTION(static, char FAR *, inet_ntoa, (struct in_addr));
+DECL_WINDOWS_FUNCTION(static, int, connect,
+ (SOCKET, const struct sockaddr FAR *, int));
+DECL_WINDOWS_FUNCTION(static, int, bind,
+ (SOCKET, const struct sockaddr FAR *, int));
+DECL_WINDOWS_FUNCTION(static, int, setsockopt,
+ (SOCKET, int, int, const char FAR *, int));
+DECL_WINDOWS_FUNCTION(static, SOCKET, socket, (int, int, int));
+DECL_WINDOWS_FUNCTION(static, int, listen, (SOCKET, int));
+DECL_WINDOWS_FUNCTION(static, int, send, (SOCKET, const char FAR *, int, int));
+DECL_WINDOWS_FUNCTION(static, int, shutdown, (SOCKET, int));
+DECL_WINDOWS_FUNCTION(static, int, ioctlsocket,
+ (SOCKET, long, u_long FAR *));
+DECL_WINDOWS_FUNCTION(static, SOCKET, accept,
+ (SOCKET, struct sockaddr FAR *, int FAR *));
+DECL_WINDOWS_FUNCTION(static, int, recv, (SOCKET, char FAR *, int, int));
+DECL_WINDOWS_FUNCTION(static, int, WSAIoctl,
+ (SOCKET, DWORD, LPVOID, DWORD, LPVOID, DWORD,
+ LPDWORD, LPWSAOVERLAPPED,
+ LPWSAOVERLAPPED_COMPLETION_ROUTINE));
+#ifndef NO_IPV6
+DECL_WINDOWS_FUNCTION(static, int, getaddrinfo,
+ (const char *nodename, const char *servname,
+ const struct addrinfo *hints, struct addrinfo **res));
+DECL_WINDOWS_FUNCTION(static, void, freeaddrinfo, (struct addrinfo *res));
+DECL_WINDOWS_FUNCTION(static, int, getnameinfo,
+ (const struct sockaddr FAR * sa, socklen_t salen,
+ char FAR * host, size_t hostlen, char FAR * serv,
+ size_t servlen, int flags));
+DECL_WINDOWS_FUNCTION(static, char *, gai_strerror, (int ecode));
+DECL_WINDOWS_FUNCTION(static, int, WSAAddressToStringA,
+ (LPSOCKADDR, DWORD, LPWSAPROTOCOL_INFO,
+ LPSTR, LPDWORD));
+#endif
+
+static HMODULE winsock_module = NULL;
+static WSADATA wsadata;
+#ifndef NO_IPV6
+static HMODULE winsock2_module = NULL;
+static HMODULE wship6_module = NULL;
+#endif
+
+int sk_startup(int hi, int lo)
+{
+ WORD winsock_ver;
+
+ winsock_ver = MAKEWORD(hi, lo);
+
+ if (p_WSAStartup(winsock_ver, &wsadata)) {
+ return FALSE;
+ }
+
+ if (LOBYTE(wsadata.wVersion) != LOBYTE(winsock_ver)) {
+ return FALSE;
+ }
+
+#ifdef NET_SETUP_DIAGNOSTICS
+ {
+ char buf[80];
+ sprintf(buf, "Using WinSock %d.%d", hi, lo);
+ logevent(NULL, buf);
+ }
+#endif
+ return TRUE;
+}
+
+void sk_init(void)
+{
+#ifndef NO_IPV6
+ winsock2_module =
+#endif
+ winsock_module = load_system32_dll("ws2_32.dll");
+ if (!winsock_module) {
+ winsock_module = load_system32_dll("wsock32.dll");
+ }
+ if (!winsock_module)
+ fatalbox("Unable to load any WinSock library");
+
+#ifndef NO_IPV6
+ /* Check if we have getaddrinfo in Winsock */
+ if (GetProcAddress(winsock_module, "getaddrinfo") != NULL) {
+#ifdef NET_SETUP_DIAGNOSTICS
+ logevent(NULL, "Native WinSock IPv6 support detected");
+#endif
+ GET_WINDOWS_FUNCTION(winsock_module, getaddrinfo);
+ GET_WINDOWS_FUNCTION(winsock_module, freeaddrinfo);
+ GET_WINDOWS_FUNCTION(winsock_module, getnameinfo);
+ GET_WINDOWS_FUNCTION(winsock_module, gai_strerror);
+ } else {
+ /* Fall back to wship6.dll for Windows 2000 */
+ wship6_module = load_system32_dll("wship6.dll");
+ if (wship6_module) {
+#ifdef NET_SETUP_DIAGNOSTICS
+ logevent(NULL, "WSH IPv6 support detected");
+#endif
+ GET_WINDOWS_FUNCTION(wship6_module, getaddrinfo);
+ GET_WINDOWS_FUNCTION(wship6_module, freeaddrinfo);
+ GET_WINDOWS_FUNCTION(wship6_module, getnameinfo);
+ GET_WINDOWS_FUNCTION(wship6_module, gai_strerror);
+ } else {
+#ifdef NET_SETUP_DIAGNOSTICS
+ logevent(NULL, "No IPv6 support detected");
+#endif
+ }
+ }
+ GET_WINDOWS_FUNCTION(winsock2_module, WSAAddressToStringA);
+#else
+#ifdef NET_SETUP_DIAGNOSTICS
+ logevent(NULL, "PuTTY was built without IPv6 support");
+#endif
+#endif
+
+ GET_WINDOWS_FUNCTION(winsock_module, WSAAsyncSelect);
+ GET_WINDOWS_FUNCTION(winsock_module, WSAEventSelect);
+ GET_WINDOWS_FUNCTION(winsock_module, select);
+ GET_WINDOWS_FUNCTION(winsock_module, WSAGetLastError);
+ GET_WINDOWS_FUNCTION(winsock_module, WSAEnumNetworkEvents);
+ GET_WINDOWS_FUNCTION(winsock_module, WSAStartup);
+ GET_WINDOWS_FUNCTION(winsock_module, WSACleanup);
+ GET_WINDOWS_FUNCTION(winsock_module, closesocket);
+ GET_WINDOWS_FUNCTION(winsock_module, ntohl);
+ GET_WINDOWS_FUNCTION(winsock_module, htonl);
+ GET_WINDOWS_FUNCTION(winsock_module, htons);
+ GET_WINDOWS_FUNCTION(winsock_module, ntohs);
+ GET_WINDOWS_FUNCTION(winsock_module, gethostname);
+ GET_WINDOWS_FUNCTION(winsock_module, gethostbyname);
+ GET_WINDOWS_FUNCTION(winsock_module, getservbyname);
+ GET_WINDOWS_FUNCTION(winsock_module, inet_addr);
+ GET_WINDOWS_FUNCTION(winsock_module, inet_ntoa);
+ GET_WINDOWS_FUNCTION(winsock_module, connect);
+ GET_WINDOWS_FUNCTION(winsock_module, bind);
+ GET_WINDOWS_FUNCTION(winsock_module, setsockopt);
+ GET_WINDOWS_FUNCTION(winsock_module, socket);
+ GET_WINDOWS_FUNCTION(winsock_module, listen);
+ GET_WINDOWS_FUNCTION(winsock_module, send);
+ GET_WINDOWS_FUNCTION(winsock_module, shutdown);
+ GET_WINDOWS_FUNCTION(winsock_module, ioctlsocket);
+ GET_WINDOWS_FUNCTION(winsock_module, accept);
+ GET_WINDOWS_FUNCTION(winsock_module, recv);
+ GET_WINDOWS_FUNCTION(winsock_module, WSAIoctl);
+
+ /* Try to get the best WinSock version we can get */
+ if (!sk_startup(2,2) &&
+ !sk_startup(2,0) &&
+ !sk_startup(1,1)) {
+ fatalbox("Unable to initialise WinSock");
+ }
+
+ sktree = newtree234(cmpfortree);
+}
+
+void sk_cleanup(void)
+{
+ Actual_Socket s;
+ int i;
+
+ if (sktree) {
+ for (i = 0; (s = index234(sktree, i)) != NULL; i++) {
+ p_closesocket(s->s);
+ }
+ freetree234(sktree);
+ sktree = NULL;
+ }
+
+ if (p_WSACleanup)
+ p_WSACleanup();
+ if (winsock_module)
+ FreeLibrary(winsock_module);
+#ifndef NO_IPV6
+ if (wship6_module)
+ FreeLibrary(wship6_module);
+#endif
+}
+
+struct errstring {
+ int error;
+ char *text;
+};
+
+static int errstring_find(void *av, void *bv)
+{
+ int *a = (int *)av;
+ struct errstring *b = (struct errstring *)bv;
+ if (*a < b->error)
+ return -1;
+ if (*a > b->error)
+ return +1;
+ return 0;
+}
+static int errstring_compare(void *av, void *bv)
+{
+ struct errstring *a = (struct errstring *)av;
+ return errstring_find(&a->error, bv);
+}
+
+static tree234 *errstrings = NULL;
+
+char *winsock_error_string(int error)
+{
+ const char prefix[] = "Network error: ";
+ struct errstring *es;
+
+ /*
+ * Error codes we know about and have historically had reasonably
+ * sensible error messages for.
+ */
+ switch (error) {
+ case WSAEACCES:
+ return "Network error: Permission denied";
+ case WSAEADDRINUSE:
+ return "Network error: Address already in use";
+ case WSAEADDRNOTAVAIL:
+ return "Network error: Cannot assign requested address";
+ case WSAEAFNOSUPPORT:
+ return
+ "Network error: Address family not supported by protocol family";
+ case WSAEALREADY:
+ return "Network error: Operation already in progress";
+ case WSAECONNABORTED:
+ return "Network error: Software caused connection abort";
+ case WSAECONNREFUSED:
+ return "Network error: Connection refused";
+ case WSAECONNRESET:
+ return "Network error: Connection reset by peer";
+ case WSAEDESTADDRREQ:
+ return "Network error: Destination address required";
+ case WSAEFAULT:
+ return "Network error: Bad address";
+ case WSAEHOSTDOWN:
+ return "Network error: Host is down";
+ case WSAEHOSTUNREACH:
+ return "Network error: No route to host";
+ case WSAEINPROGRESS:
+ return "Network error: Operation now in progress";
+ case WSAEINTR:
+ return "Network error: Interrupted function call";
+ case WSAEINVAL:
+ return "Network error: Invalid argument";
+ case WSAEISCONN:
+ return "Network error: Socket is already connected";
+ case WSAEMFILE:
+ return "Network error: Too many open files";
+ case WSAEMSGSIZE:
+ return "Network error: Message too long";
+ case WSAENETDOWN:
+ return "Network error: Network is down";
+ case WSAENETRESET:
+ return "Network error: Network dropped connection on reset";
+ case WSAENETUNREACH:
+ return "Network error: Network is unreachable";
+ case WSAENOBUFS:
+ return "Network error: No buffer space available";
+ case WSAENOPROTOOPT:
+ return "Network error: Bad protocol option";
+ case WSAENOTCONN:
+ return "Network error: Socket is not connected";
+ case WSAENOTSOCK:
+ return "Network error: Socket operation on non-socket";
+ case WSAEOPNOTSUPP:
+ return "Network error: Operation not supported";
+ case WSAEPFNOSUPPORT:
+ return "Network error: Protocol family not supported";
+ case WSAEPROCLIM:
+ return "Network error: Too many processes";
+ case WSAEPROTONOSUPPORT:
+ return "Network error: Protocol not supported";
+ case WSAEPROTOTYPE:
+ return "Network error: Protocol wrong type for socket";
+ case WSAESHUTDOWN:
+ return "Network error: Cannot send after socket shutdown";
+ case WSAESOCKTNOSUPPORT:
+ return "Network error: Socket type not supported";
+ case WSAETIMEDOUT:
+ return "Network error: Connection timed out";
+ case WSAEWOULDBLOCK:
+ return "Network error: Resource temporarily unavailable";
+ case WSAEDISCON:
+ return "Network error: Graceful shutdown in progress";
+ }
+
+ /*
+ * Generic code to handle any other error.
+ *
+ * Slightly nasty hack here: we want to return a static string
+ * which the caller will never have to worry about freeing, but on
+ * the other hand if we call FormatMessage to get it then it will
+ * want to either allocate a buffer or write into one we own.
+ *
+ * So what we do is to maintain a tree234 of error strings we've
+ * already used. New ones are allocated from the heap, but then
+ * put in this tree and kept forever.
+ */
+
+ if (!errstrings)
+ errstrings = newtree234(errstring_compare);
+
+ es = find234(errstrings, &error, errstring_find);
+
+ if (!es) {
+ int bufsize, bufused;
+
+ es = snew(struct errstring);
+ es->error = error;
+ /* maximum size for FormatMessage is 64K */
+ bufsize = 65535 + sizeof(prefix);
+ es->text = snewn(bufsize, char);
+ strcpy(es->text, prefix);
+ bufused = strlen(es->text);
+ if (!FormatMessage((FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS), NULL, 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());
+ } else {
+ int len = strlen(es->text);
+ if (len > 0 && es->text[len-1] == '\n')
+ es->text[len-1] = '\0';
+ }
+ es->text = sresize(es->text, strlen(es->text) + 1, char);
+ add234(errstrings, es);
+ }
+
+ return es->text;
+}
+
+SockAddr sk_namelookup(const char *host, char **canonicalname,
+ int address_family)
+{
+ SockAddr ret = snew(struct SockAddr_tag);
+ unsigned long a;
+ char realhost[8192];
+ int hint_family;
+
+ /* Default to IPv4. */
+ hint_family = (address_family == ADDRTYPE_IPV4 ? AF_INET :
+#ifndef NO_IPV6
+ address_family == ADDRTYPE_IPV6 ? AF_INET6 :
+#endif
+ AF_UNSPEC);
+
+ /* Clear the structure and default to IPv4. */
+ memset(ret, 0, sizeof(struct SockAddr_tag));
+#ifndef NO_IPV6
+ ret->ais = NULL;
+#endif
+ ret->namedpipe = FALSE;
+ ret->addresses = NULL;
+ ret->resolved = FALSE;
+ ret->refcount = 1;
+ *realhost = '\0';
+
+ if ((a = p_inet_addr(host)) == (unsigned long) INADDR_NONE) {
+ struct hostent *h = NULL;
+ int err;
+#ifndef NO_IPV6
+ /*
+ * Use getaddrinfo when it's available
+ */
+ if (p_getaddrinfo) {
+ struct addrinfo hints;
+#ifdef NET_SETUP_DIAGNOSTICS
+ logevent(NULL, "Using getaddrinfo() for resolving");
+#endif
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = hint_family;
+ hints.ai_flags = AI_CANONNAME;
+ {
+ /* strip [] on IPv6 address literals */
+ char *trimmed_host = host_strduptrim(host);
+ err = p_getaddrinfo(trimmed_host, NULL, &hints, &ret->ais);
+ sfree(trimmed_host);
+ }
+ if (err == 0)
+ ret->resolved = TRUE;
+ } else
+#endif
+ {
+#ifdef NET_SETUP_DIAGNOSTICS
+ logevent(NULL, "Using gethostbyname() for resolving");
+#endif
+ /*
+ * Otherwise use the IPv4-only gethostbyname...
+ * (NOTE: we don't use gethostbyname as a fallback!)
+ */
+ if ( (h = p_gethostbyname(host)) )
+ ret->resolved = TRUE;
+ else
+ err = p_WSAGetLastError();
+ }
+
+ if (!ret->resolved) {
+ ret->error = (err == WSAENETDOWN ? "Network is down" :
+ err == WSAHOST_NOT_FOUND ? "Host does not exist" :
+ err == WSATRY_AGAIN ? "Host not found" :
+#ifndef NO_IPV6
+ p_getaddrinfo&&p_gai_strerror ? p_gai_strerror(err) :
+#endif
+ "gethostbyname: unknown error");
+ } else {
+ ret->error = NULL;
+
+#ifndef NO_IPV6
+ /* If we got an address info use that... */
+ if (ret->ais) {
+ /* Are we in IPv4 fallback mode? */
+ /* We put the IPv4 address into the a variable so we can further-on use the IPv4 code... */
+ if (ret->ais->ai_family == AF_INET)
+ memcpy(&a,
+ (char *) &((SOCKADDR_IN *) ret->ais->
+ ai_addr)->sin_addr, sizeof(a));
+
+ if (ret->ais->ai_canonname)
+ strncpy(realhost, ret->ais->ai_canonname, lenof(realhost));
+ else
+ strncpy(realhost, host, lenof(realhost));
+ }
+ /* We used the IPv4-only gethostbyname()... */
+ else
+#endif
+ {
+ int n;
+ for (n = 0; h->h_addr_list[n]; n++);
+ ret->addresses = snewn(n, unsigned long);
+ ret->naddresses = n;
+ for (n = 0; n < ret->naddresses; n++) {
+ memcpy(&a, h->h_addr_list[n], sizeof(a));
+ ret->addresses[n] = p_ntohl(a);
+ }
+ memcpy(&a, h->h_addr, sizeof(a));
+ /* This way we are always sure the h->h_name is valid :) */
+ strncpy(realhost, h->h_name, sizeof(realhost));
+ }
+ }
+ } else {
+ /*
+ * This must be a numeric IPv4 address because it caused a
+ * success return from inet_addr.
+ */
+ ret->addresses = snewn(1, unsigned long);
+ ret->naddresses = 1;
+ ret->addresses[0] = p_ntohl(a);
+ ret->resolved = TRUE;
+ strncpy(realhost, host, sizeof(realhost));
+ }
+ realhost[lenof(realhost)-1] = '\0';
+ *canonicalname = snewn(1+strlen(realhost), char);
+ strcpy(*canonicalname, realhost);
+ return ret;
+}
+
+SockAddr sk_nonamelookup(const char *host)
+{
+ SockAddr ret = snew(struct SockAddr_tag);
+ ret->error = NULL;
+ ret->resolved = FALSE;
+#ifndef NO_IPV6
+ ret->ais = NULL;
+#endif
+ ret->namedpipe = FALSE;
+ ret->addresses = NULL;
+ ret->naddresses = 0;
+ ret->refcount = 1;
+ strncpy(ret->hostname, host, lenof(ret->hostname));
+ ret->hostname[lenof(ret->hostname)-1] = '\0';
+ return ret;
+}
+
+SockAddr sk_namedpipe_addr(const char *pipename)
+{
+ SockAddr ret = snew(struct SockAddr_tag);
+ ret->error = NULL;
+ ret->resolved = FALSE;
+#ifndef NO_IPV6
+ ret->ais = NULL;
+#endif
+ ret->namedpipe = TRUE;
+ ret->addresses = NULL;
+ ret->naddresses = 0;
+ ret->refcount = 1;
+ strncpy(ret->hostname, pipename, lenof(ret->hostname));
+ ret->hostname[lenof(ret->hostname)-1] = '\0';
+ return ret;
+}
+
+int sk_nextaddr(SockAddr addr, SockAddrStep *step)
+{
+#ifndef NO_IPV6
+ if (step->ai) {
+ if (step->ai->ai_next) {
+ step->ai = step->ai->ai_next;
+ return TRUE;
+ } else
+ return FALSE;
+ }
+#endif
+ if (step->curraddr+1 < addr->naddresses) {
+ step->curraddr++;
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+void sk_getaddr(SockAddr addr, char *buf, int buflen)
+{
+ SockAddrStep step;
+ START_STEP(addr, step);
+
+#ifndef NO_IPV6
+ if (step.ai) {
+ int err = 0;
+ if (p_WSAAddressToStringA) {
+ DWORD dwbuflen = buflen;
+ err = p_WSAAddressToStringA(step.ai->ai_addr, step.ai->ai_addrlen,
+ NULL, buf, &dwbuflen);
+ } else
+ err = -1;
+ if (err) {
+ strncpy(buf, addr->hostname, buflen);
+ if (!buf[0])
+ strncpy(buf, "<unknown>", buflen);
+ buf[buflen-1] = '\0';
+ }
+ } else
+#endif
+ if (SOCKADDR_FAMILY(addr, step) == AF_INET) {
+ struct in_addr a;
+ assert(addr->addresses && step.curraddr < addr->naddresses);
+ a.s_addr = p_htonl(addr->addresses[step.curraddr]);
+ strncpy(buf, p_inet_ntoa(a), buflen);
+ buf[buflen-1] = '\0';
+ } else {
+ strncpy(buf, addr->hostname, buflen);
+ buf[buflen-1] = '\0';
+ }
+}
+
+int sk_addr_needs_port(SockAddr addr)
+{
+ return addr->namedpipe ? FALSE : TRUE;
+}
+
+int sk_hostname_is_local(const char *name)
+{
+ return !strcmp(name, "localhost") ||
+ !strcmp(name, "::1") ||
+ !strncmp(name, "127.", 4);
+}
+
+static INTERFACE_INFO local_interfaces[16];
+static int n_local_interfaces; /* 0=not yet, -1=failed, >0=number */
+
+static int ipv4_is_local_addr(struct in_addr addr)
+{
+ if (ipv4_is_loopback(addr))
+ return 1; /* loopback addresses are local */
+ if (!n_local_interfaces) {
+ SOCKET s = p_socket(AF_INET, SOCK_DGRAM, 0);
+ DWORD retbytes;
+
+ if (p_WSAIoctl &&
+ p_WSAIoctl(s, SIO_GET_INTERFACE_LIST, NULL, 0,
+ local_interfaces, sizeof(local_interfaces),
+ &retbytes, NULL, NULL) == 0)
+ n_local_interfaces = retbytes / sizeof(INTERFACE_INFO);
+ else
+ logevent(NULL, "Unable to get list of local IP addresses");
+ }
+ if (n_local_interfaces > 0) {
+ int i;
+ for (i = 0; i < n_local_interfaces; i++) {
+ SOCKADDR_IN *address =
+ (SOCKADDR_IN *)&local_interfaces[i].iiAddress;
+ if (address->sin_addr.s_addr == addr.s_addr)
+ return 1; /* this address is local */
+ }
+ }
+ return 0; /* this address is not local */
+}
+
+int sk_address_is_local(SockAddr addr)
+{
+ SockAddrStep step;
+ int family;
+ START_STEP(addr, step);
+ family = SOCKADDR_FAMILY(addr, step);
+
+#ifndef NO_IPV6
+ if (family == AF_INET6) {
+ return IN6_IS_ADDR_LOOPBACK(&((const struct sockaddr_in6 *)step.ai->ai_addr)->sin6_addr);
+ } else
+#endif
+ if (family == AF_INET) {
+#ifndef NO_IPV6
+ if (step.ai) {
+ return ipv4_is_local_addr(((struct sockaddr_in *)step.ai->ai_addr)
+ ->sin_addr);
+ } else
+#endif
+ {
+ struct in_addr a;
+ assert(addr->addresses && step.curraddr < addr->naddresses);
+ a.s_addr = p_htonl(addr->addresses[step.curraddr]);
+ return ipv4_is_local_addr(a);
+ }
+ } else {
+ assert(family == AF_UNSPEC);
+ return 0; /* we don't know; assume not */
+ }
+}
+
+int sk_address_is_special_local(SockAddr addr)
+{
+ return 0; /* no Unix-domain socket analogue here */
+}
+
+int sk_addrtype(SockAddr addr)
+{
+ SockAddrStep step;
+ int family;
+ START_STEP(addr, step);
+ family = SOCKADDR_FAMILY(addr, step);
+
+ return (family == AF_INET ? ADDRTYPE_IPV4 :
+#ifndef NO_IPV6
+ family == AF_INET6 ? ADDRTYPE_IPV6 :
+#endif
+ ADDRTYPE_NAME);
+}
+
+void sk_addrcopy(SockAddr addr, char *buf)
+{
+ SockAddrStep step;
+ int family;
+ START_STEP(addr, step);
+ family = SOCKADDR_FAMILY(addr, step);
+
+ assert(family != AF_UNSPEC);
+#ifndef NO_IPV6
+ if (step.ai) {
+ if (family == AF_INET)
+ memcpy(buf, &((struct sockaddr_in *)step.ai->ai_addr)->sin_addr,
+ sizeof(struct in_addr));
+ else if (family == AF_INET6)
+ memcpy(buf, &((struct sockaddr_in6 *)step.ai->ai_addr)->sin6_addr,
+ sizeof(struct in6_addr));
+ else
+ assert(FALSE);
+ } else
+#endif
+ if (family == AF_INET) {
+ struct in_addr a;
+ assert(addr->addresses && step.curraddr < addr->naddresses);
+ a.s_addr = p_htonl(addr->addresses[step.curraddr]);
+ memcpy(buf, (char*) &a.s_addr, 4);
+ }
+}
+
+void sk_addr_free(SockAddr addr)
+{
+ if (--addr->refcount > 0)
+ return;
+#ifndef NO_IPV6
+ if (addr->ais && p_freeaddrinfo)
+ p_freeaddrinfo(addr->ais);
+#endif
+ if (addr->addresses)
+ sfree(addr->addresses);
+ sfree(addr);
+}
+
+SockAddr sk_addr_dup(SockAddr addr)
+{
+ addr->refcount++;
+ return addr;
+}
+
+static Plug sk_tcp_plug(Socket sock, Plug p)
+{
+ Actual_Socket s = (Actual_Socket) sock;
+ Plug ret = s->plug;
+ if (p)
+ s->plug = p;
+ return ret;
+}
+
+static void sk_tcp_flush(Socket s)
+{
+ /*
+ * We send data to the socket as soon as we can anyway,
+ * so we don't need to do anything here. :-)
+ */
+}
+
+static void sk_tcp_close(Socket s);
+static int sk_tcp_write(Socket s, const char *data, int len);
+static int sk_tcp_write_oob(Socket s, const char *data, int len);
+static void sk_tcp_write_eof(Socket s);
+static void sk_tcp_set_frozen(Socket s, int is_frozen);
+static const char *sk_tcp_socket_error(Socket s);
+
+extern char *do_select(SOCKET skt, int startup);
+
+static Socket sk_tcp_accept(accept_ctx_t ctx, Plug plug)
+{
+ static const struct socket_function_table fn_table = {
+ sk_tcp_plug,
+ sk_tcp_close,
+ sk_tcp_write,
+ sk_tcp_write_oob,
+ sk_tcp_write_eof,
+ sk_tcp_flush,
+ sk_tcp_set_frozen,
+ sk_tcp_socket_error
+ };
+
+ DWORD err;
+ char *errstr;
+ Actual_Socket ret;
+
+ /*
+ * Create Socket structure.
+ */
+ ret = snew(struct Socket_tag);
+ ret->fn = &fn_table;
+ ret->error = NULL;
+ ret->plug = plug;
+ bufchain_init(&ret->output_data);
+ ret->writable = 1; /* to start with */
+ ret->sending_oob = 0;
+ ret->outgoingeof = EOF_NO;
+ ret->frozen = 1;
+ ret->frozen_readable = 0;
+ ret->localhost_only = 0; /* unused, but best init anyway */
+ ret->pending_error = 0;
+ ret->parent = ret->child = NULL;
+ ret->addr = NULL;
+
+ ret->s = (SOCKET)ctx.p;
+
+ if (ret->s == INVALID_SOCKET) {
+ err = p_WSAGetLastError();
+ ret->error = winsock_error_string(err);
+ return (Socket) ret;
+ }
+
+ ret->oobinline = 0;
+
+ /* Set up a select mechanism. This could be an AsyncSelect on a
+ * window, or an EventSelect on an event object. */
+ errstr = do_select(ret->s, 1);
+ if (errstr) {
+ ret->error = errstr;
+ return (Socket) ret;
+ }
+
+ add234(sktree, ret);
+
+ return (Socket) ret;
+}
+
+static DWORD try_connect(Actual_Socket sock)
+{
+ SOCKET s;
+#ifndef NO_IPV6
+ SOCKADDR_IN6 a6;
+#endif
+ SOCKADDR_IN a;
+ DWORD err;
+ char *errstr;
+ short localport;
+ int family;
+
+ if (sock->s != INVALID_SOCKET) {
+ do_select(sock->s, 0);
+ p_closesocket(sock->s);
+ }
+
+ plug_log(sock->plug, 0, sock->addr, sock->port, NULL, 0);
+
+ /*
+ * Open socket.
+ */
+ family = SOCKADDR_FAMILY(sock->addr, sock->step);
+
+ /*
+ * Remove the socket from the tree before we overwrite its
+ * internal socket id, because that forms part of the tree's
+ * sorting criterion. We'll add it back before exiting this
+ * function, whether we changed anything or not.
+ */
+ del234(sktree, sock);
+
+ s = p_socket(family, SOCK_STREAM, 0);
+ sock->s = s;
+
+ if (s == INVALID_SOCKET) {
+ err = p_WSAGetLastError();
+ sock->error = winsock_error_string(err);
+ goto ret;
+ }
+
+ if (sock->oobinline) {
+ BOOL b = TRUE;
+ p_setsockopt(s, SOL_SOCKET, SO_OOBINLINE, (void *) &b, sizeof(b));
+ }
+
+ if (sock->nodelay) {
+ BOOL b = TRUE;
+ p_setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void *) &b, sizeof(b));
+ }
+
+ if (sock->keepalive) {
+ BOOL b = TRUE;
+ p_setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *) &b, sizeof(b));
+ }
+
+ /*
+ * Bind to local address.
+ */
+ if (sock->privport)
+ localport = 1023; /* count from 1023 downwards */
+ else
+ localport = 0; /* just use port 0 (ie winsock picks) */
+
+ /* Loop round trying to bind */
+ while (1) {
+ int sockcode;
+
+#ifndef NO_IPV6
+ if (family == AF_INET6) {
+ memset(&a6, 0, sizeof(a6));
+ a6.sin6_family = AF_INET6;
+ /*a6.sin6_addr = in6addr_any; */ /* == 0 done by memset() */
+ a6.sin6_port = p_htons(localport);
+ } else
+#endif
+ {
+ a.sin_family = AF_INET;
+ a.sin_addr.s_addr = p_htonl(INADDR_ANY);
+ a.sin_port = p_htons(localport);
+ }
+#ifndef NO_IPV6
+ sockcode = p_bind(s, (family == AF_INET6 ?
+ (struct sockaddr *) &a6 :
+ (struct sockaddr *) &a),
+ (family == AF_INET6 ? sizeof(a6) : sizeof(a)));
+#else
+ sockcode = p_bind(s, (struct sockaddr *) &a, sizeof(a));
+#endif
+ if (sockcode != SOCKET_ERROR) {
+ err = 0;
+ break; /* done */
+ } else {
+ err = p_WSAGetLastError();
+ if (err != WSAEADDRINUSE) /* failed, for a bad reason */
+ break;
+ }
+
+ if (localport == 0)
+ break; /* we're only looping once */
+ localport--;
+ if (localport == 0)
+ break; /* we might have got to the end */
+ }
+
+ if (err) {
+ sock->error = winsock_error_string(err);
+ goto ret;
+ }
+
+ /*
+ * Connect to remote address.
+ */
+#ifndef NO_IPV6
+ if (sock->step.ai) {
+ if (family == AF_INET6) {
+ a6.sin6_family = AF_INET6;
+ a6.sin6_port = p_htons((short) sock->port);
+ a6.sin6_addr =
+ ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_addr;
+ a6.sin6_flowinfo = ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_flowinfo;
+ a6.sin6_scope_id = ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_scope_id;
+ } else {
+ a.sin_family = AF_INET;
+ a.sin_addr =
+ ((struct sockaddr_in *) sock->step.ai->ai_addr)->sin_addr;
+ a.sin_port = p_htons((short) sock->port);
+ }
+ } else
+#endif
+ {
+ assert(sock->addr->addresses && sock->step.curraddr < sock->addr->naddresses);
+ a.sin_family = AF_INET;
+ a.sin_addr.s_addr = p_htonl(sock->addr->addresses[sock->step.curraddr]);
+ a.sin_port = p_htons((short) sock->port);
+ }
+
+ /* Set up a select mechanism. This could be an AsyncSelect on a
+ * window, or an EventSelect on an event object. */
+ errstr = do_select(s, 1);
+ if (errstr) {
+ sock->error = errstr;
+ err = 1;
+ goto ret;
+ }
+
+ if ((
+#ifndef NO_IPV6
+ p_connect(s,
+ ((family == AF_INET6) ? (struct sockaddr *) &a6 :
+ (struct sockaddr *) &a),
+ (family == AF_INET6) ? sizeof(a6) : sizeof(a))
+#else
+ p_connect(s, (struct sockaddr *) &a, sizeof(a))
+#endif
+ ) == SOCKET_ERROR) {
+ err = p_WSAGetLastError();
+ /*
+ * We expect a potential EWOULDBLOCK here, because the
+ * chances are the front end has done a select for
+ * FD_CONNECT, so that connect() will complete
+ * asynchronously.
+ */
+ if ( err != WSAEWOULDBLOCK ) {
+ sock->error = winsock_error_string(err);
+ goto ret;
+ }
+ } else {
+ /*
+ * If we _don't_ get EWOULDBLOCK, the connect has completed
+ * and we should set the socket as writable.
+ */
+ sock->writable = 1;
+ }
+
+ err = 0;
+
+ ret:
+
+ /*
+ * No matter what happened, put the socket back in the tree.
+ */
+ add234(sktree, sock);
+
+ if (err)
+ plug_log(sock->plug, 1, sock->addr, sock->port, sock->error, err);
+ return err;
+}
+
+Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
+ int nodelay, int keepalive, Plug plug)
+{
+ static const struct socket_function_table fn_table = {
+ sk_tcp_plug,
+ sk_tcp_close,
+ sk_tcp_write,
+ sk_tcp_write_oob,
+ sk_tcp_write_eof,
+ sk_tcp_flush,
+ sk_tcp_set_frozen,
+ sk_tcp_socket_error
+ };
+
+ Actual_Socket ret;
+ DWORD err;
+
+ /*
+ * Create Socket structure.
+ */
+ ret = snew(struct Socket_tag);
+ ret->fn = &fn_table;
+ ret->error = NULL;
+ ret->plug = plug;
+ bufchain_init(&ret->output_data);
+ ret->connected = 0; /* to start with */
+ ret->writable = 0; /* to start with */
+ ret->sending_oob = 0;
+ ret->outgoingeof = EOF_NO;
+ ret->frozen = 0;
+ ret->frozen_readable = 0;
+ ret->localhost_only = 0; /* unused, but best init anyway */
+ ret->pending_error = 0;
+ ret->parent = ret->child = NULL;
+ ret->oobinline = oobinline;
+ ret->nodelay = nodelay;
+ ret->keepalive = keepalive;
+ ret->privport = privport;
+ ret->port = port;
+ ret->addr = addr;
+ START_STEP(ret->addr, ret->step);
+ ret->s = INVALID_SOCKET;
+
+ err = 0;
+ do {
+ err = try_connect(ret);
+ } while (err && sk_nextaddr(ret->addr, &ret->step));
+
+ return (Socket) ret;
+}
+
+Socket sk_newlistener(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,
+ sk_tcp_close,
+ sk_tcp_write,
+ sk_tcp_write_oob,
+ sk_tcp_write_eof,
+ sk_tcp_flush,
+ sk_tcp_set_frozen,
+ sk_tcp_socket_error
+ };
+
+ SOCKET s;
+#ifndef NO_IPV6
+ SOCKADDR_IN6 a6;
+#endif
+ SOCKADDR_IN a;
+
+ DWORD err;
+ char *errstr;
+ Actual_Socket ret;
+ int retcode;
+ int on = 1;
+
+ int address_family;
+
+ /*
+ * Create Socket structure.
+ */
+ ret = snew(struct Socket_tag);
+ ret->fn = &fn_table;
+ ret->error = NULL;
+ ret->plug = plug;
+ bufchain_init(&ret->output_data);
+ ret->writable = 0; /* to start with */
+ ret->sending_oob = 0;
+ ret->outgoingeof = EOF_NO;
+ ret->frozen = 0;
+ ret->frozen_readable = 0;
+ ret->localhost_only = local_host_only;
+ ret->pending_error = 0;
+ ret->parent = ret->child = NULL;
+ ret->addr = NULL;
+
+ /*
+ * Translate address_family from platform-independent constants
+ * into local reality.
+ */
+ address_family = (orig_address_family == ADDRTYPE_IPV4 ? AF_INET :
+#ifndef NO_IPV6
+ orig_address_family == ADDRTYPE_IPV6 ? AF_INET6 :
+#endif
+ AF_UNSPEC);
+
+ /*
+ * Our default, if passed the `don't care' value
+ * ADDRTYPE_UNSPEC, is to listen on IPv4. If IPv6 is supported,
+ * we will also set up a second socket listening on IPv6, but
+ * the v4 one is primary since that ought to work even on
+ * non-v6-supporting systems.
+ */
+ if (address_family == AF_UNSPEC) address_family = AF_INET;
+
+ /*
+ * Open socket.
+ */
+ s = p_socket(address_family, SOCK_STREAM, 0);
+ ret->s = s;
+
+ if (s == INVALID_SOCKET) {
+ err = p_WSAGetLastError();
+ ret->error = winsock_error_string(err);
+ return (Socket) ret;
+ }
+
+ ret->oobinline = 0;
+
+ p_setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on));
+
+#ifndef NO_IPV6
+ if (address_family == AF_INET6) {
+ memset(&a6, 0, sizeof(a6));
+ a6.sin6_family = AF_INET6;
+ if (local_host_only)
+ a6.sin6_addr = in6addr_loopback;
+ else
+ a6.sin6_addr = in6addr_any;
+ if (srcaddr != NULL && p_getaddrinfo) {
+ struct addrinfo hints;
+ struct addrinfo *ai;
+ int err;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET6;
+ hints.ai_flags = 0;
+ {
+ /* strip [] on IPv6 address literals */
+ char *trimmed_addr = host_strduptrim(srcaddr);
+ err = p_getaddrinfo(trimmed_addr, NULL, &hints, &ai);
+ sfree(trimmed_addr);
+ }
+ if (err == 0 && ai->ai_family == AF_INET6) {
+ a6.sin6_addr =
+ ((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr;
+ }
+ }
+ a6.sin6_port = p_htons(port);
+ } else
+#endif
+ {
+ int got_addr = 0;
+ a.sin_family = AF_INET;
+
+ /*
+ * Bind to source address. First try an explicitly
+ * specified one...
+ */
+ if (srcaddr) {
+ a.sin_addr.s_addr = p_inet_addr(srcaddr);
+ if (a.sin_addr.s_addr != INADDR_NONE) {
+ /* Override localhost_only with specified listen addr. */
+ ret->localhost_only = ipv4_is_loopback(a.sin_addr);
+ got_addr = 1;
+ }
+ }
+
+ /*
+ * ... and failing that, go with one of the standard ones.
+ */
+ if (!got_addr) {
+ if (local_host_only)
+ a.sin_addr.s_addr = p_htonl(INADDR_LOOPBACK);
+ else
+ a.sin_addr.s_addr = p_htonl(INADDR_ANY);
+ }
+
+ a.sin_port = p_htons((short)port);
+ }
+#ifndef NO_IPV6
+ retcode = p_bind(s, (address_family == AF_INET6 ?
+ (struct sockaddr *) &a6 :
+ (struct sockaddr *) &a),
+ (address_family ==
+ AF_INET6 ? sizeof(a6) : sizeof(a)));
+#else
+ retcode = p_bind(s, (struct sockaddr *) &a, sizeof(a));
+#endif
+ if (retcode != SOCKET_ERROR) {
+ err = 0;
+ } else {
+ err = p_WSAGetLastError();
+ }
+
+ if (err) {
+ p_closesocket(s);
+ ret->error = winsock_error_string(err);
+ return (Socket) ret;
+ }
+
+
+ if (p_listen(s, SOMAXCONN) == SOCKET_ERROR) {
+ p_closesocket(s);
+ ret->error = winsock_error_string(p_WSAGetLastError());
+ return (Socket) ret;
+ }
+
+ /* Set up a select mechanism. This could be an AsyncSelect on a
+ * window, or an EventSelect on an event object. */
+ errstr = do_select(s, 1);
+ if (errstr) {
+ p_closesocket(s);
+ ret->error = errstr;
+ return (Socket) ret;
+ }
+
+ add234(sktree, ret);
+
+#ifndef NO_IPV6
+ /*
+ * If we were given ADDRTYPE_UNSPEC, we must also create an
+ * IPv6 listening socket and link it to this one.
+ */
+ if (address_family == AF_INET && orig_address_family == ADDRTYPE_UNSPEC) {
+ Actual_Socket other;
+
+ other = (Actual_Socket) sk_newlistener(srcaddr, port, plug,
+ local_host_only, ADDRTYPE_IPV6);
+
+ if (other) {
+ if (!other->error) {
+ other->parent = ret;
+ ret->child = other;
+ } else {
+ sfree(other);
+ }
+ }
+ }
+#endif
+
+ return (Socket) ret;
+}
+
+static void sk_tcp_close(Socket sock)
+{
+ extern char *do_select(SOCKET skt, int startup);
+ Actual_Socket s = (Actual_Socket) sock;
+
+ if (s->child)
+ sk_tcp_close((Socket)s->child);
+
+ del234(sktree, s);
+ do_select(s->s, 0);
+ p_closesocket(s->s);
+ if (s->addr)
+ sk_addr_free(s->addr);
+ sfree(s);
+}
+
+/*
+ * Deal with socket errors detected in try_send().
+ */
+static void socket_error_callback(void *vs)
+{
+ Actual_Socket s = (Actual_Socket)vs;
+
+ /*
+ * Just in case other socket work has caused this socket to vanish
+ * or become somehow non-erroneous before this callback arrived...
+ */
+ if (!find234(sktree, s, NULL) || !s->pending_error)
+ return;
+
+ /*
+ * An error has occurred on this socket. Pass it to the plug.
+ */
+ plug_closing(s->plug, winsock_error_string(s->pending_error),
+ s->pending_error, 0);
+}
+
+/*
+ * The function which tries to send on a socket once it's deemed
+ * writable.
+ */
+void try_send(Actual_Socket s)
+{
+ while (s->sending_oob || bufchain_size(&s->output_data) > 0) {
+ int nsent;
+ DWORD err;
+ void *data;
+ int len, urgentflag;
+
+ if (s->sending_oob) {
+ urgentflag = MSG_OOB;
+ len = s->sending_oob;
+ data = &s->oobdata;
+ } else {
+ urgentflag = 0;
+ bufchain_prefix(&s->output_data, &data, &len);
+ }
+ nsent = p_send(s->s, data, len, urgentflag);
+ noise_ultralight(nsent);
+ if (nsent <= 0) {
+ err = (nsent < 0 ? p_WSAGetLastError() : 0);
+ if ((err < WSABASEERR && nsent < 0) || err == WSAEWOULDBLOCK) {
+ /*
+ * Perfectly normal: we've sent all we can for the moment.
+ *
+ * (Some WinSock send() implementations can return
+ * <0 but leave no sensible error indication -
+ * WSAGetLastError() is called but returns zero or
+ * a small number - so we check that case and treat
+ * it just like WSAEWOULDBLOCK.)
+ */
+ s->writable = FALSE;
+ return;
+ } else if (nsent == 0 ||
+ err == WSAECONNABORTED || err == WSAECONNRESET) {
+ /*
+ * If send() returns CONNABORTED or CONNRESET, we
+ * unfortunately can't just call plug_closing(),
+ * because it's quite likely that we're currently
+ * _in_ a call from the code we'd be calling back
+ * to, so we'd have to make half the SSH code
+ * reentrant. Instead we flag a pending error on
+ * the socket, to be dealt with (by calling
+ * plug_closing()) at some suitable future moment.
+ */
+ s->pending_error = err;
+ queue_toplevel_callback(socket_error_callback, s);
+ return;
+ } else {
+ /* We're inside the Windows frontend here, so we know
+ * that the frontend handle is unnecessary. */
+ logevent(NULL, winsock_error_string(err));
+ fatalbox("%s", winsock_error_string(err));
+ }
+ } else {
+ if (s->sending_oob) {
+ if (nsent < len) {
+ memmove(s->oobdata, s->oobdata+nsent, len-nsent);
+ s->sending_oob = len - nsent;
+ } else {
+ s->sending_oob = 0;
+ }
+ } else {
+ bufchain_consume(&s->output_data, nsent);
+ }
+ }
+ }
+
+ /*
+ * If we reach here, we've finished sending everything we might
+ * have needed to send. Send EOF, if we need to.
+ */
+ if (s->outgoingeof == EOF_PENDING) {
+ p_shutdown(s->s, SD_SEND);
+ s->outgoingeof = EOF_SENT;
+ }
+}
+
+static int sk_tcp_write(Socket sock, const char *buf, int len)
+{
+ Actual_Socket s = (Actual_Socket) sock;
+
+ assert(s->outgoingeof == EOF_NO);
+
+ /*
+ * Add the data to the buffer list on the socket.
+ */
+ bufchain_add(&s->output_data, buf, len);
+
+ /*
+ * Now try sending from the start of the buffer list.
+ */
+ if (s->writable)
+ try_send(s);
+
+ return bufchain_size(&s->output_data);
+}
+
+static int sk_tcp_write_oob(Socket sock, const char *buf, int len)
+{
+ Actual_Socket s = (Actual_Socket) sock;
+
+ assert(s->outgoingeof == EOF_NO);
+
+ /*
+ * Replace the buffer list on the socket with the data.
+ */
+ bufchain_clear(&s->output_data);
+ assert(len <= sizeof(s->oobdata));
+ memcpy(s->oobdata, buf, len);
+ s->sending_oob = len;
+
+ /*
+ * Now try sending from the start of the buffer list.
+ */
+ if (s->writable)
+ try_send(s);
+
+ return s->sending_oob;
+}
+
+static void sk_tcp_write_eof(Socket sock)
+{
+ Actual_Socket s = (Actual_Socket) sock;
+
+ assert(s->outgoingeof == EOF_NO);
+
+ /*
+ * Mark the socket as pending outgoing EOF.
+ */
+ s->outgoingeof = EOF_PENDING;
+
+ /*
+ * Now try sending from the start of the buffer list.
+ */
+ if (s->writable)
+ try_send(s);
+}
+
+int select_result(WPARAM wParam, LPARAM lParam)
+{
+ int ret, open;
+ DWORD err;
+ char buf[20480]; /* nice big buffer for plenty of speed */
+ Actual_Socket s;
+ u_long atmark;
+
+ /* wParam is the socket itself */
+
+ if (wParam == 0)
+ return 1; /* boggle */
+
+ s = find234(sktree, (void *) wParam, cmpforsearch);
+ if (!s)
+ return 1; /* boggle */
+
+ if ((err = WSAGETSELECTERROR(lParam)) != 0) {
+ /*
+ * An error has occurred on this socket. Pass it to the
+ * plug.
+ */
+ if (s->addr) {
+ plug_log(s->plug, 1, s->addr, s->port,
+ winsock_error_string(err), err);
+ while (s->addr && sk_nextaddr(s->addr, &s->step)) {
+ err = try_connect(s);
+ }
+ }
+ if (err != 0)
+ return plug_closing(s->plug, winsock_error_string(err), err, 0);
+ else
+ return 1;
+ }
+
+ noise_ultralight(lParam);
+
+ switch (WSAGETSELECTEVENT(lParam)) {
+ case FD_CONNECT:
+ s->connected = s->writable = 1;
+ /*
+ * Once a socket is connected, we can stop falling
+ * back through the candidate addresses to connect
+ * to.
+ */
+ if (s->addr) {
+ sk_addr_free(s->addr);
+ s->addr = NULL;
+ }
+ break;
+ case FD_READ:
+ /* In the case the socket is still frozen, we don't even bother */
+ if (s->frozen) {
+ s->frozen_readable = 1;
+ break;
+ }
+
+ /*
+ * We have received data on the socket. For an oobinline
+ * socket, this might be data _before_ an urgent pointer,
+ * in which case we send it to the back end with type==1
+ * (data prior to urgent).
+ */
+ if (s->oobinline) {
+ atmark = 1;
+ p_ioctlsocket(s->s, SIOCATMARK, &atmark);
+ /*
+ * Avoid checking the return value from ioctlsocket(),
+ * on the grounds that some WinSock wrappers don't
+ * support it. If it does nothing, we get atmark==1,
+ * which is equivalent to `no OOB pending', so the
+ * effect will be to non-OOB-ify any OOB data.
+ */
+ } else
+ atmark = 1;
+
+ ret = p_recv(s->s, buf, sizeof(buf), 0);
+ noise_ultralight(ret);
+ if (ret < 0) {
+ err = p_WSAGetLastError();
+ if (err == WSAEWOULDBLOCK) {
+ break;
+ }
+ }
+ if (ret < 0) {
+ return plug_closing(s->plug, winsock_error_string(err), err,
+ 0);
+ } else if (0 == ret) {
+ return plug_closing(s->plug, NULL, 0, 0);
+ } else {
+ return plug_receive(s->plug, atmark ? 0 : 1, buf, ret);
+ }
+ break;
+ case FD_OOB:
+ /*
+ * This will only happen on a non-oobinline socket. It
+ * indicates that we can immediately perform an OOB read
+ * and get back OOB data, which we will send to the back
+ * end with type==2 (urgent data).
+ */
+ ret = p_recv(s->s, buf, sizeof(buf), MSG_OOB);
+ noise_ultralight(ret);
+ if (ret <= 0) {
+ 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. */
+ logevent(NULL, str);
+ fatalbox("%s", str);
+ } else {
+ return plug_receive(s->plug, 2, buf, ret);
+ }
+ break;
+ case FD_WRITE:
+ {
+ int bufsize_before, bufsize_after;
+ s->writable = 1;
+ bufsize_before = s->sending_oob + bufchain_size(&s->output_data);
+ try_send(s);
+ bufsize_after = s->sending_oob + bufchain_size(&s->output_data);
+ if (bufsize_after < bufsize_before)
+ plug_sent(s->plug, bufsize_after);
+ }
+ break;
+ case FD_CLOSE:
+ /* Signal a close on the socket. First read any outstanding data. */
+ open = 1;
+ do {
+ ret = p_recv(s->s, buf, sizeof(buf), 0);
+ if (ret < 0) {
+ err = p_WSAGetLastError();
+ if (err == WSAEWOULDBLOCK)
+ break;
+ return plug_closing(s->plug, winsock_error_string(err),
+ err, 0);
+ } else {
+ if (ret)
+ open &= plug_receive(s->plug, 0, buf, ret);
+ else
+ open &= plug_closing(s->plug, NULL, 0, 0);
+ }
+ } while (ret > 0);
+ return open;
+ case FD_ACCEPT:
+ {
+#ifdef NO_IPV6
+ struct sockaddr_in isa;
+#else
+ struct sockaddr_storage isa;
+#endif
+ int addrlen = sizeof(isa);
+ SOCKET t; /* socket of connection */
+ accept_ctx_t actx;
+
+ memset(&isa, 0, sizeof(isa));
+ err = 0;
+ t = p_accept(s->s,(struct sockaddr *)&isa,&addrlen);
+ if (t == INVALID_SOCKET)
+ {
+ err = p_WSAGetLastError();
+ if (err == WSATRY_AGAIN)
+ break;
+ }
+
+ actx.p = (void *)t;
+
+#ifndef NO_IPV6
+ if (isa.ss_family == AF_INET &&
+ s->localhost_only &&
+ !ipv4_is_local_addr(((struct sockaddr_in *)&isa)->sin_addr))
+#else
+ if (s->localhost_only && !ipv4_is_local_addr(isa.sin_addr))
+#endif
+ {
+ p_closesocket(t); /* dodgy WinSock let nonlocal through */
+ } else if (plug_accepting(s->plug, sk_tcp_accept, actx)) {
+ p_closesocket(t); /* denied or error */
+ }
+ }
+ }
+
+ return 1;
+}
+
+/*
+ * Special error values are returned from sk_namelookup and sk_new
+ * if there's a problem. These functions extract an error message,
+ * or return NULL if there's no problem.
+ */
+const char *sk_addr_error(SockAddr addr)
+{
+ return addr->error;
+}
+static const char *sk_tcp_socket_error(Socket sock)
+{
+ Actual_Socket s = (Actual_Socket) sock;
+ return s->error;
+}
+
+static void sk_tcp_set_frozen(Socket sock, int is_frozen)
+{
+ Actual_Socket s = (Actual_Socket) sock;
+ if (s->frozen == is_frozen)
+ return;
+ s->frozen = is_frozen;
+ if (!is_frozen) {
+ do_select(s->s, 1);
+ if (s->frozen_readable) {
+ char c;
+ p_recv(s->s, &c, 1, MSG_PEEK);
+ }
+ }
+ s->frozen_readable = 0;
+}
+
+void socket_reselect_all(void)
+{
+ Actual_Socket s;
+ int i;
+
+ for (i = 0; (s = index234(sktree, i)) != NULL; i++) {
+ if (!s->frozen)
+ do_select(s->s, 1);
+ }
+}
+
+/*
+ * For Plink: enumerate all sockets currently active.
+ */
+SOCKET first_socket(int *state)
+{
+ Actual_Socket s;
+ *state = 0;
+ s = index234(sktree, (*state)++);
+ return s ? s->s : INVALID_SOCKET;
+}
+
+SOCKET next_socket(int *state)
+{
+ Actual_Socket s = index234(sktree, (*state)++);
+ return s ? s->s : INVALID_SOCKET;
+}
+
+extern int socket_writable(SOCKET skt)
+{
+ Actual_Socket s = find234(sktree, (void *)skt, cmpforsearch);
+
+ if (s)
+ return bufchain_size(&s->output_data) > 0;
+ else
+ return 0;
+}
+
+int net_service_lookup(char *service)
+{
+ struct servent *se;
+ se = p_getservbyname(service, NULL);
+ if (se != NULL)
+ return p_ntohs(se->s_port);
+ else
+ return 0;
+}
+
+char *get_hostname(void)
+{
+ int len = 128;
+ char *hostname = NULL;
+ do {
+ len *= 2;
+ hostname = sresize(hostname, len, char);
+ if (p_gethostname(hostname, len) < 0) {
+ sfree(hostname);
+ hostname = NULL;
+ break;
+ }
+ } while (strlen(hostname) >= (size_t)(len-1));
+ return hostname;
+}
+
+SockAddr platform_get_x11_unix_address(const char *display, int displaynum,
+ char **canonicalname)
+{
+ SockAddr ret = snew(struct SockAddr_tag);
+ memset(ret, 0, sizeof(struct SockAddr_tag));
+ ret->error = "unix sockets not supported on this platform";
+ ret->refcount = 1;
+ return ret;
+}
diff --git a/tools/plink/winnoise.c b/tools/plink/winnoise.c
index bdf869719..313645461 100644
--- a/tools/plink/winnoise.c
+++ b/tools/plink/winnoise.c
@@ -1,128 +1,155 @@
-/*
- * Noise generation for PuTTY's cryptographic random number
- * generator.
- */
-
-#include <stdio.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "storage.h"
-
-/*
- * This function is called once, at PuTTY startup, and will do some
- * seriously silly things like listing directories and getting disk
- * free space and a process snapshot.
- */
-
-void noise_get_heavy(void (*func) (void *, int))
-{
- HANDLE srch;
- WIN32_FIND_DATA finddata;
- DWORD pid;
- char winpath[MAX_PATH + 3];
-
- GetWindowsDirectory(winpath, sizeof(winpath));
- strcat(winpath, "\\*");
- srch = FindFirstFile(winpath, &finddata);
- if (srch != INVALID_HANDLE_VALUE) {
- do {
- func(&finddata, sizeof(finddata));
- } while (FindNextFile(srch, &finddata));
- FindClose(srch);
- }
-
- pid = GetCurrentProcessId();
- func(&pid, sizeof(pid));
-
- read_random_seed(func);
- /* Update the seed immediately, in case another instance uses it. */
- random_save_seed();
-}
-
-void random_save_seed(void)
-{
- int len;
- void *data;
-
- if (random_active) {
- random_get_savedata(&data, &len);
- write_random_seed(data, len);
- sfree(data);
- }
-}
-
-/*
- * This function is called every time the random pool needs
- * stirring, and will acquire the system time in all available
- * forms.
- */
-void noise_get_light(void (*func) (void *, int))
-{
- SYSTEMTIME systime;
- DWORD adjust[2];
- BOOL rubbish;
-
- GetSystemTime(&systime);
- func(&systime, sizeof(systime));
-
- GetSystemTimeAdjustment(&adjust[0], &adjust[1], &rubbish);
- func(&adjust, sizeof(adjust));
-}
-
-/*
- * This function is called on a timer, and it will monitor
- * frequently changing quantities such as the state of physical and
- * virtual memory, the state of the process's message queue, which
- * window is in the foreground, which owns the clipboard, etc.
- */
-void noise_regular(void)
-{
- HWND w;
- DWORD z;
- POINT pt;
- MEMORYSTATUS memstat;
- FILETIME times[4];
-
- w = GetForegroundWindow();
- random_add_noise(&w, sizeof(w));
- w = GetCapture();
- random_add_noise(&w, sizeof(w));
- w = GetClipboardOwner();
- random_add_noise(&w, sizeof(w));
- z = GetQueueStatus(QS_ALLEVENTS);
- random_add_noise(&z, sizeof(z));
-
- GetCursorPos(&pt);
- random_add_noise(&pt, sizeof(pt));
-
- GlobalMemoryStatus(&memstat);
- random_add_noise(&memstat, sizeof(memstat));
-
- GetThreadTimes(GetCurrentThread(), times, times + 1, times + 2,
- times + 3);
- random_add_noise(&times, sizeof(times));
- GetProcessTimes(GetCurrentProcess(), times, times + 1, times + 2,
- times + 3);
- random_add_noise(&times, sizeof(times));
-}
-
-/*
- * This function is called on every keypress or mouse move, and
- * will add the current Windows time and performance monitor
- * counter to the noise pool. It gets the scan code or mouse
- * position passed in.
- */
-void noise_ultralight(unsigned long data)
-{
- DWORD wintime;
- LARGE_INTEGER perftime;
-
- random_add_noise(&data, sizeof(DWORD));
-
- wintime = GetTickCount();
- random_add_noise(&wintime, sizeof(DWORD));
-
- if (QueryPerformanceCounter(&perftime))
- random_add_noise(&perftime, sizeof(perftime));
-}
+/*
+ * Noise generation for PuTTY's cryptographic random number
+ * generator.
+ */
+
+#include <stdio.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "storage.h"
+
+#include <wincrypt.h>
+
+DECL_WINDOWS_FUNCTION(static, BOOL, CryptAcquireContextA,
+ (HCRYPTPROV *, LPCTSTR, LPCTSTR, DWORD, DWORD));
+DECL_WINDOWS_FUNCTION(static, BOOL, CryptGenRandom,
+ (HCRYPTPROV, DWORD, BYTE *));
+DECL_WINDOWS_FUNCTION(static, BOOL, CryptReleaseContext,
+ (HCRYPTPROV, DWORD));
+static HMODULE wincrypt_module = NULL;
+
+/*
+ * This function is called once, at PuTTY startup.
+ */
+
+void noise_get_heavy(void (*func) (void *, int))
+{
+ HANDLE srch;
+ WIN32_FIND_DATA finddata;
+ DWORD pid;
+ HCRYPTPROV crypt_provider;
+ char winpath[MAX_PATH + 3];
+
+ GetWindowsDirectory(winpath, sizeof(winpath));
+ strcat(winpath, "\\*");
+ srch = FindFirstFile(winpath, &finddata);
+ if (srch != INVALID_HANDLE_VALUE) {
+ do {
+ func(&finddata, sizeof(finddata));
+ } while (FindNextFile(srch, &finddata));
+ FindClose(srch);
+ }
+
+ pid = GetCurrentProcessId();
+ func(&pid, sizeof(pid));
+
+ if (!wincrypt_module) {
+ wincrypt_module = load_system32_dll("advapi32.dll");
+ GET_WINDOWS_FUNCTION(wincrypt_module, CryptAcquireContextA);
+ GET_WINDOWS_FUNCTION(wincrypt_module, CryptGenRandom);
+ GET_WINDOWS_FUNCTION(wincrypt_module, CryptReleaseContext);
+ }
+
+ if (wincrypt_module && p_CryptAcquireContextA &&
+ p_CryptGenRandom && p_CryptReleaseContext &&
+ p_CryptAcquireContextA(&crypt_provider, NULL, NULL, PROV_RSA_FULL,
+ CRYPT_VERIFYCONTEXT)) {
+ BYTE buf[32];
+ if (p_CryptGenRandom(crypt_provider, 32, buf)) {
+ func(buf, sizeof(buf));
+ }
+ p_CryptReleaseContext(crypt_provider, 0);
+ }
+
+ read_random_seed(func);
+ /* Update the seed immediately, in case another instance uses it. */
+ random_save_seed();
+}
+
+void random_save_seed(void)
+{
+ int len;
+ void *data;
+
+ if (random_active) {
+ random_get_savedata(&data, &len);
+ write_random_seed(data, len);
+ sfree(data);
+ }
+}
+
+/*
+ * This function is called every time the random pool needs
+ * stirring, and will acquire the system time in all available
+ * forms.
+ */
+void noise_get_light(void (*func) (void *, int))
+{
+ SYSTEMTIME systime;
+ DWORD adjust[2];
+ BOOL rubbish;
+
+ GetSystemTime(&systime);
+ func(&systime, sizeof(systime));
+
+ GetSystemTimeAdjustment(&adjust[0], &adjust[1], &rubbish);
+ func(&adjust, sizeof(adjust));
+}
+
+/*
+ * This function is called on a timer, and it will monitor
+ * frequently changing quantities such as the state of physical and
+ * virtual memory, the state of the process's message queue, which
+ * window is in the foreground, which owns the clipboard, etc.
+ */
+void noise_regular(void)
+{
+ HWND w;
+ DWORD z;
+ POINT pt;
+ MEMORYSTATUS memstat;
+ FILETIME times[4];
+
+ w = GetForegroundWindow();
+ random_add_noise(&w, sizeof(w));
+ w = GetCapture();
+ random_add_noise(&w, sizeof(w));
+ w = GetClipboardOwner();
+ random_add_noise(&w, sizeof(w));
+ z = GetQueueStatus(QS_ALLEVENTS);
+ random_add_noise(&z, sizeof(z));
+
+ GetCursorPos(&pt);
+ random_add_noise(&pt, sizeof(pt));
+
+ GlobalMemoryStatus(&memstat);
+ random_add_noise(&memstat, sizeof(memstat));
+
+ GetThreadTimes(GetCurrentThread(), times, times + 1, times + 2,
+ times + 3);
+ random_add_noise(&times, sizeof(times));
+ GetProcessTimes(GetCurrentProcess(), times, times + 1, times + 2,
+ times + 3);
+ random_add_noise(&times, sizeof(times));
+}
+
+/*
+ * This function is called on every keypress or mouse move, and
+ * will add the current Windows time and performance monitor
+ * counter to the noise pool. It gets the scan code or mouse
+ * position passed in.
+ */
+void noise_ultralight(unsigned long data)
+{
+ DWORD wintime;
+ LARGE_INTEGER perftime;
+
+ random_add_noise(&data, sizeof(DWORD));
+
+ wintime = GetTickCount();
+ random_add_noise(&wintime, sizeof(DWORD));
+
+ if (QueryPerformanceCounter(&perftime))
+ random_add_noise(&perftime, sizeof(perftime));
+}
diff --git a/tools/plink/winpgntc.c b/tools/plink/winpgntc.c
index 0dabe7167..036ca7595 100644
--- a/tools/plink/winpgntc.c
+++ b/tools/plink/winpgntc.c
@@ -1,265 +1,187 @@
-/*
- * Pageant client code.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#include "putty.h"
-
-#ifndef NO_SECURITY
-#include <aclapi.h>
-#endif
-
-#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
-#define AGENT_MAX_MSGLEN 8192
-
-int agent_exists(void)
-{
- HWND hwnd;
- hwnd = FindWindow("Pageant", "Pageant");
- if (!hwnd)
- return FALSE;
- else
- return TRUE;
-}
-
-/*
- * Unfortunately, this asynchronous agent request mechanism doesn't
- * appear to work terribly well. I'm going to comment it out for
- * the moment, and see if I can come up with a better one :-/
- */
-#ifdef WINDOWS_ASYNC_AGENT
-
-struct agent_query_data {
- COPYDATASTRUCT cds;
- unsigned char *mapping;
- HANDLE handle;
- char *mapname;
- HWND hwnd;
- void (*callback)(void *, void *, int);
- void *callback_ctx;
-};
-
-DWORD WINAPI agent_query_thread(LPVOID param)
-{
- struct agent_query_data *data = (struct agent_query_data *)param;
- unsigned char *ret;
- int id, retlen;
-
- id = SendMessage(data->hwnd, WM_COPYDATA, (WPARAM) NULL,
- (LPARAM) &data->cds);
- ret = NULL;
- if (id > 0) {
- retlen = 4 + GET_32BIT(data->mapping);
- ret = snewn(retlen, unsigned char);
- if (ret) {
- memcpy(ret, data->mapping, retlen);
- }
- }
- if (!ret)
- retlen = 0;
- UnmapViewOfFile(data->mapping);
- CloseHandle(data->handle);
- sfree(data->mapname);
-
- agent_schedule_callback(data->callback, data->callback_ctx, ret, retlen);
-
- return 0;
-}
-
-#endif
-
-/*
- * Dynamically load advapi32.dll for SID manipulation. In its absence,
- * we degrade gracefully.
- */
-#ifndef NO_SECURITY
-int advapi_initialised = FALSE;
-static HMODULE advapi;
-DECL_WINDOWS_FUNCTION(static, BOOL, OpenProcessToken,
- (HANDLE, DWORD, PHANDLE));
-DECL_WINDOWS_FUNCTION(static, BOOL, GetTokenInformation,
- (HANDLE, TOKEN_INFORMATION_CLASS,
- LPVOID, DWORD, PDWORD));
-DECL_WINDOWS_FUNCTION(static, BOOL, InitializeSecurityDescriptor,
- (PSECURITY_DESCRIPTOR, DWORD));
-DECL_WINDOWS_FUNCTION(static, BOOL, SetSecurityDescriptorOwner,
- (PSECURITY_DESCRIPTOR, PSID, BOOL));
-DECL_WINDOWS_FUNCTION(, DWORD, GetSecurityInfo,
- (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
- PSID *, PSID *, PACL *, PACL *,
- PSECURITY_DESCRIPTOR *));
-int init_advapi(void)
-{
- advapi = load_system32_dll("advapi32.dll");
- return advapi &&
- GET_WINDOWS_FUNCTION(advapi, GetSecurityInfo) &&
- GET_WINDOWS_FUNCTION(advapi, OpenProcessToken) &&
- GET_WINDOWS_FUNCTION(advapi, GetTokenInformation) &&
- GET_WINDOWS_FUNCTION(advapi, InitializeSecurityDescriptor) &&
- GET_WINDOWS_FUNCTION(advapi, SetSecurityDescriptorOwner);
-}
-
-PSID get_user_sid(void)
-{
- HANDLE proc = NULL, tok = NULL;
- TOKEN_USER *user = NULL;
- DWORD toklen, sidlen;
- PSID sid = NULL, ret = NULL;
-
- if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE,
- GetCurrentProcessId())) == NULL)
- goto cleanup;
-
- if (!p_OpenProcessToken(proc, TOKEN_QUERY, &tok))
- goto cleanup;
-
- if (!p_GetTokenInformation(tok, TokenUser, NULL, 0, &toklen) &&
- GetLastError() != ERROR_INSUFFICIENT_BUFFER)
- goto cleanup;
-
- if ((user = (TOKEN_USER *)LocalAlloc(LPTR, toklen)) == NULL)
- goto cleanup;
-
- if (!p_GetTokenInformation(tok, TokenUser, user, toklen, &toklen))
- goto cleanup;
-
- sidlen = GetLengthSid(user->User.Sid);
-
- sid = (PSID)smalloc(sidlen);
-
- if (!CopySid(sidlen, sid, user->User.Sid))
- goto cleanup;
-
- /* Success. Move sid into the return value slot, and null it out
- * to stop the cleanup code freeing it. */
- ret = sid;
- sid = NULL;
-
- cleanup:
- if (proc != NULL)
- CloseHandle(proc);
- if (tok != NULL)
- CloseHandle(tok);
- if (user != NULL)
- LocalFree(user);
- if (sid != NULL)
- sfree(sid);
-
- return ret;
-}
-
-#endif
-
-int agent_query(void *in, int inlen, void **out, int *outlen,
- void (*callback)(void *, void *, int), void *callback_ctx)
-{
- HWND hwnd;
- char *mapname;
- HANDLE filemap;
- unsigned char *p, *ret;
- int id, retlen;
- COPYDATASTRUCT cds;
- SECURITY_ATTRIBUTES sa, *psa;
- PSECURITY_DESCRIPTOR psd = NULL;
- PSID usersid = NULL;
-
- *out = NULL;
- *outlen = 0;
-
- hwnd = FindWindow("Pageant", "Pageant");
- if (!hwnd)
- return 1; /* *out == NULL, so failure */
- mapname = dupprintf("PageantRequest%08x", (unsigned)GetCurrentThreadId());
-
-#ifndef NO_SECURITY
- if (advapi_initialised || init_advapi()) {
- /*
- * Make the file mapping we create for communication with
- * Pageant owned by the user SID rather than the default. This
- * should make communication between processes with slightly
- * different contexts more reliable: in particular, command
- * prompts launched as administrator should still be able to
- * run PSFTPs which refer back to the owning user's
- * unprivileged Pageant.
- */
- usersid = get_user_sid();
-
- psa = NULL;
- if (usersid) {
- psd = (PSECURITY_DESCRIPTOR)
- LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
- if (psd) {
- if (p_InitializeSecurityDescriptor
- (psd, SECURITY_DESCRIPTOR_REVISION) &&
- p_SetSecurityDescriptorOwner(psd, usersid, FALSE)) {
- sa.nLength = sizeof(sa);
- sa.bInheritHandle = TRUE;
- sa.lpSecurityDescriptor = psd;
- psa = &sa;
- } else {
- LocalFree(psd);
- psd = NULL;
- }
- }
- }
- }
-#endif /* NO_SECURITY */
-
- filemap = CreateFileMapping(INVALID_HANDLE_VALUE, psa, PAGE_READWRITE,
- 0, AGENT_MAX_MSGLEN, mapname);
- if (filemap == NULL || filemap == INVALID_HANDLE_VALUE)
- return 1; /* *out == NULL, so failure */
- p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
- memcpy(p, in, inlen);
- cds.dwData = AGENT_COPYDATA_ID;
- cds.cbData = 1 + strlen(mapname);
- cds.lpData = mapname;
-#ifdef WINDOWS_ASYNC_AGENT
- if (callback != NULL && !(flags & FLAG_SYNCAGENT)) {
- /*
- * We need an asynchronous Pageant request. Since I know of
- * no way to stop SendMessage from blocking the thread it's
- * called in, I see no option but to start a fresh thread.
- * When we're done we'll PostMessage the result back to our
- * main window, so that the callback is done in the primary
- * thread to avoid concurrency.
- */
- struct agent_query_data *data = snew(struct agent_query_data);
- DWORD threadid;
- data->mapping = p;
- data->handle = filemap;
- data->mapname = mapname;
- data->callback = callback;
- data->callback_ctx = callback_ctx;
- data->cds = cds; /* structure copy */
- data->hwnd = hwnd;
- if (CreateThread(NULL, 0, agent_query_thread, data, 0, &threadid))
- return 0;
- sfree(data);
- }
-#endif
-
- /*
- * The user either passed a null callback (indicating that the
- * query is required to be synchronous) or CreateThread failed.
- * Either way, we need a synchronous request.
- */
- id = SendMessage(hwnd, WM_COPYDATA, (WPARAM) NULL, (LPARAM) &cds);
- if (id > 0) {
- retlen = 4 + GET_32BIT(p);
- ret = snewn(retlen, unsigned char);
- if (ret) {
- memcpy(ret, p, retlen);
- *out = ret;
- *outlen = retlen;
- }
- }
- UnmapViewOfFile(p);
- CloseHandle(filemap);
- if (psd)
- LocalFree(psd);
- sfree(usersid);
- return 1;
-}
+/*
+ * Pageant client code.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "putty.h"
+
+#ifndef NO_SECURITY
+#include "winsecur.h"
+#endif
+
+#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
+#define AGENT_MAX_MSGLEN 8192
+
+int agent_exists(void)
+{
+ HWND hwnd;
+ hwnd = FindWindow("Pageant", "Pageant");
+ if (!hwnd)
+ return FALSE;
+ else
+ return TRUE;
+}
+
+/*
+ * Unfortunately, this asynchronous agent request mechanism doesn't
+ * appear to work terribly well. I'm going to comment it out for
+ * the moment, and see if I can come up with a better one :-/
+ */
+#ifdef WINDOWS_ASYNC_AGENT
+
+struct agent_query_data {
+ COPYDATASTRUCT cds;
+ unsigned char *mapping;
+ HANDLE handle;
+ char *mapname;
+ HWND hwnd;
+ void (*callback)(void *, void *, int);
+ void *callback_ctx;
+};
+
+DWORD WINAPI agent_query_thread(LPVOID param)
+{
+ struct agent_query_data *data = (struct agent_query_data *)param;
+ unsigned char *ret;
+ int id, retlen;
+
+ id = SendMessage(data->hwnd, WM_COPYDATA, (WPARAM) NULL,
+ (LPARAM) &data->cds);
+ ret = NULL;
+ if (id > 0) {
+ retlen = 4 + GET_32BIT(data->mapping);
+ ret = snewn(retlen, unsigned char);
+ if (ret) {
+ memcpy(ret, data->mapping, retlen);
+ }
+ }
+ if (!ret)
+ retlen = 0;
+ UnmapViewOfFile(data->mapping);
+ CloseHandle(data->handle);
+ sfree(data->mapname);
+
+ agent_schedule_callback(data->callback, data->callback_ctx, ret, retlen);
+
+ return 0;
+}
+
+#endif
+
+int agent_query(void *in, int inlen, void **out, int *outlen,
+ void (*callback)(void *, void *, int), void *callback_ctx)
+{
+ HWND hwnd;
+ char *mapname;
+ HANDLE filemap;
+ unsigned char *p, *ret;
+ int id, retlen;
+ COPYDATASTRUCT cds;
+ SECURITY_ATTRIBUTES sa, *psa;
+ PSECURITY_DESCRIPTOR psd = NULL;
+ PSID usersid = NULL;
+
+ *out = NULL;
+ *outlen = 0;
+
+ hwnd = FindWindow("Pageant", "Pageant");
+ if (!hwnd)
+ return 1; /* *out == NULL, so failure */
+ mapname = dupprintf("PageantRequest%08x", (unsigned)GetCurrentThreadId());
+
+ psa = NULL;
+#ifndef NO_SECURITY
+ if (got_advapi()) {
+ /*
+ * Make the file mapping we create for communication with
+ * Pageant owned by the user SID rather than the default. This
+ * should make communication between processes with slightly
+ * different contexts more reliable: in particular, command
+ * prompts launched as administrator should still be able to
+ * run PSFTPs which refer back to the owning user's
+ * unprivileged Pageant.
+ */
+ usersid = get_user_sid();
+
+ if (usersid) {
+ psd = (PSECURITY_DESCRIPTOR)
+ LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
+ if (psd) {
+ if (p_InitializeSecurityDescriptor
+ (psd, SECURITY_DESCRIPTOR_REVISION) &&
+ p_SetSecurityDescriptorOwner(psd, usersid, FALSE)) {
+ sa.nLength = sizeof(sa);
+ sa.bInheritHandle = TRUE;
+ sa.lpSecurityDescriptor = psd;
+ psa = &sa;
+ } else {
+ LocalFree(psd);
+ psd = NULL;
+ }
+ }
+ }
+ }
+#endif /* NO_SECURITY */
+
+ filemap = CreateFileMapping(INVALID_HANDLE_VALUE, psa, PAGE_READWRITE,
+ 0, AGENT_MAX_MSGLEN, mapname);
+ if (filemap == NULL || filemap == INVALID_HANDLE_VALUE) {
+ sfree(mapname);
+ return 1; /* *out == NULL, so failure */
+ }
+ p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
+ memcpy(p, in, inlen);
+ cds.dwData = AGENT_COPYDATA_ID;
+ cds.cbData = 1 + strlen(mapname);
+ cds.lpData = mapname;
+#ifdef WINDOWS_ASYNC_AGENT
+ if (callback != NULL && !(flags & FLAG_SYNCAGENT)) {
+ /*
+ * We need an asynchronous Pageant request. Since I know of
+ * no way to stop SendMessage from blocking the thread it's
+ * called in, I see no option but to start a fresh thread.
+ * When we're done we'll PostMessage the result back to our
+ * main window, so that the callback is done in the primary
+ * thread to avoid concurrency.
+ */
+ struct agent_query_data *data = snew(struct agent_query_data);
+ DWORD threadid;
+ data->mapping = p;
+ data->handle = filemap;
+ data->mapname = mapname;
+ data->callback = callback;
+ data->callback_ctx = callback_ctx;
+ data->cds = cds; /* structure copy */
+ data->hwnd = hwnd;
+ if (CreateThread(NULL, 0, agent_query_thread, data, 0, &threadid))
+ return 0;
+ sfree(mapname);
+ sfree(data);
+ }
+#endif
+
+ /*
+ * The user either passed a null callback (indicating that the
+ * query is required to be synchronous) or CreateThread failed.
+ * Either way, we need a synchronous request.
+ */
+ id = SendMessage(hwnd, WM_COPYDATA, (WPARAM) NULL, (LPARAM) &cds);
+ if (id > 0) {
+ retlen = 4 + GET_32BIT(p);
+ ret = snewn(retlen, unsigned char);
+ if (ret) {
+ memcpy(ret, p, retlen);
+ *out = ret;
+ *outlen = retlen;
+ }
+ }
+ UnmapViewOfFile(p);
+ CloseHandle(filemap);
+ sfree(mapname);
+ if (psd)
+ LocalFree(psd);
+ sfree(usersid);
+ return 1;
+}
diff --git a/tools/plink/winplink.c b/tools/plink/winplink.c
index 27c386c07..29ed4e5d2 100644..100755
--- a/tools/plink/winplink.c
+++ b/tools/plink/winplink.c
@@ -1,799 +1,831 @@
-/*
- * PLink - a Windows command-line (stdin/stdout) variant of PuTTY.
- */
-
-#include <stdio.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <stdarg.h>
-
-#define PUTTY_DO_GLOBALS /* actually _define_ globals */
-#include "putty.h"
-#include "storage.h"
-#include "tree234.h"
-
-#define WM_AGENT_CALLBACK (WM_APP + 4)
-
-struct agent_callback {
- void (*callback)(void *, void *, int);
- void *callback_ctx;
- void *data;
- int len;
-};
-
-void fatalbox(char *p, ...)
-{
- va_list ap;
- fprintf(stderr, "FATAL ERROR: ");
- va_start(ap, p);
- vfprintf(stderr, p, ap);
- va_end(ap);
- fputc('\n', stderr);
- if (logctx) {
- log_free(logctx);
- logctx = NULL;
- }
- cleanup_exit(1);
-}
-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);
- if (logctx) {
- log_free(logctx);
- logctx = NULL;
- }
- cleanup_exit(1);
-}
-void connection_fatal(void *frontend, char *p, ...)
-{
- va_list ap;
- fprintf(stderr, "FATAL ERROR: ");
- va_start(ap, p);
- vfprintf(stderr, p, ap);
- va_end(ap);
- fputc('\n', stderr);
- if (logctx) {
- log_free(logctx);
- logctx = NULL;
- }
- cleanup_exit(1);
-}
-void cmdline_error(char *p, ...)
-{
- va_list ap;
- fprintf(stderr, "plink: ");
- va_start(ap, p);
- vfprintf(stderr, p, ap);
- va_end(ap);
- fputc('\n', stderr);
- exit(1);
-}
-
-HANDLE inhandle, outhandle, errhandle;
-struct handle *stdin_handle, *stdout_handle, *stderr_handle;
-DWORD orig_console_mode;
-int connopen;
-
-WSAEVENT netevent;
-
-static Backend *back;
-static void *backhandle;
-static Conf *conf;
-
-int term_ldisc(Terminal *term, int mode)
-{
- return FALSE;
-}
-void ldisc_update(void *frontend, int echo, int edit)
-{
- /* Update stdin read mode to reflect changes in line discipline. */
- DWORD mode;
-
- mode = ENABLE_PROCESSED_INPUT;
- if (echo)
- mode = mode | ENABLE_ECHO_INPUT;
- else
- mode = mode & ~ENABLE_ECHO_INPUT;
- if (edit)
- mode = mode | ENABLE_LINE_INPUT;
- else
- mode = mode & ~ENABLE_LINE_INPUT;
- SetConsoleMode(inhandle, mode);
-}
-
-char *get_ttymode(void *frontend, const char *mode) { return NULL; }
-
-int from_backend(void *frontend_handle, int is_stderr,
- const char *data, int len)
-{
- if (is_stderr) {
- handle_write(stderr_handle, data, len);
- } else {
- handle_write(stdout_handle, data, len);
- }
-
- return handle_backlog(stdout_handle) + handle_backlog(stderr_handle);
-}
-
-int from_backend_untrusted(void *frontend_handle, const char *data, int len)
-{
- /*
- * No "untrusted" output should get here (the way the code is
- * currently, it's all diverted by FLAG_STDERR).
- */
- assert(!"Unexpected call to from_backend_untrusted()");
- return 0; /* not reached */
-}
-
-int from_backend_eof(void *frontend_handle)
-{
- handle_write_eof(stdout_handle);
- return FALSE; /* do not respond to incoming EOF with outgoing */
-}
-
-int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
-{
- int ret;
- ret = cmdline_get_passwd_input(p, in, inlen);
- if (ret == -1)
- ret = console_get_userpass_input(p, in, inlen);
- return ret;
-}
-
-static DWORD main_thread_id;
-
-void agent_schedule_callback(void (*callback)(void *, void *, int),
- void *callback_ctx, void *data, int len)
-{
- struct agent_callback *c = snew(struct agent_callback);
- c->callback = callback;
- c->callback_ctx = callback_ctx;
- c->data = data;
- c->len = len;
- PostThreadMessage(main_thread_id, WM_AGENT_CALLBACK, 0, (LPARAM)c);
-}
-
-/*
- * Short description of parameters.
- */
-static void usage(void)
-{
- printf("PuTTY Link: command-line connection utility\n");
- printf("%s\n", ver);
- printf("Usage: plink [options] [user@]host [command]\n");
- printf(" (\"host\" can also be a PuTTY saved session name)\n");
- printf("Options:\n");
- printf(" -V print version information and exit\n");
- printf(" -pgpfp print PGP key fingerprints and exit\n");
- printf(" -v show verbose messages\n");
- printf(" -load sessname Load settings from saved session\n");
- printf(" -ssh -telnet -rlogin -raw -serial\n");
- printf(" force use of a particular protocol\n");
- printf(" -P port connect to specified port\n");
- printf(" -l user connect with specified username\n");
- printf(" -batch disable all interactive prompts\n");
- printf("The following options only apply to SSH connections:\n");
- printf(" -pw passw login with specified password\n");
- printf(" -D [listen-IP:]listen-port\n");
- printf(" Dynamic SOCKS-based port forwarding\n");
- printf(" -L [listen-IP:]listen-port:host:port\n");
- printf(" Forward local port to remote address\n");
- printf(" -R [listen-IP:]listen-port:host:port\n");
- printf(" Forward remote port to local address\n");
- printf(" -X -x enable / disable X11 forwarding\n");
- printf(" -A -a enable / disable agent forwarding\n");
- printf(" -t -T enable / disable pty allocation\n");
- printf(" -1 -2 force use of particular protocol version\n");
- printf(" -4 -6 force use of IPv4 or IPv6\n");
- printf(" -C enable compression\n");
- printf(" -i key private key file for authentication\n");
- printf(" -noagent disable use of Pageant\n");
- printf(" -agent enable use of Pageant\n");
- printf(" -m file read remote command(s) from file\n");
- printf(" -s remote command is an SSH subsystem (SSH-2 only)\n");
- printf(" -N don't start a shell/command (SSH-2 only)\n");
- printf(" -nc host:port\n");
- printf(" open tunnel in place of session (SSH-2 only)\n");
- printf(" -sercfg configuration-string (e.g. 19200,8,n,1,X)\n");
- printf(" Specify the serial configuration (serial only)\n");
- exit(1);
-}
-
-static void version(void)
-{
- printf("plink: %s\n", ver);
- exit(1);
-}
-
-char *do_select(SOCKET skt, int startup)
-{
- int events;
- if (startup) {
- events = (FD_CONNECT | FD_READ | FD_WRITE |
- FD_OOB | FD_CLOSE | FD_ACCEPT);
- } else {
- events = 0;
- }
- if (p_WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {
- switch (p_WSAGetLastError()) {
- case WSAENETDOWN:
- return "Network is down";
- default:
- return "WSAEventSelect(): unknown error";
- }
- }
- return NULL;
-}
-
-int stdin_gotdata(struct handle *h, void *data, int len)
-{
- if (len < 0) {
- /*
- * Special case: report read error.
- */
- char buf[4096];
- FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, -len, 0,
- buf, lenof(buf), NULL);
- buf[lenof(buf)-1] = '\0';
- if (buf[strlen(buf)-1] == '\n')
- buf[strlen(buf)-1] = '\0';
- fprintf(stderr, "Unable to read from standard input: %s\n", buf);
- cleanup_exit(0);
- }
- noise_ultralight(len);
- if (connopen && back->connected(backhandle)) {
- if (len > 0) {
- return back->send(backhandle, data, len);
- } else {
- back->special(backhandle, TS_EOF);
- return 0;
- }
- } else
- return 0;
-}
-
-void stdouterr_sent(struct handle *h, int new_backlog)
-{
- if (new_backlog < 0) {
- /*
- * Special case: report write error.
- */
- char buf[4096];
- FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, -new_backlog, 0,
- buf, lenof(buf), NULL);
- buf[lenof(buf)-1] = '\0';
- if (buf[strlen(buf)-1] == '\n')
- buf[strlen(buf)-1] = '\0';
- fprintf(stderr, "Unable to write to standard %s: %s\n",
- (h == stdout_handle ? "output" : "error"), buf);
- cleanup_exit(0);
- }
- if (connopen && back->connected(backhandle)) {
- back->unthrottle(backhandle, (handle_backlog(stdout_handle) +
- handle_backlog(stderr_handle)));
- }
-}
-
-int main(int argc, char **argv)
-{
- int sending;
- int portnumber = -1;
- SOCKET *sklist;
- int skcount, sksize;
- int exitcode;
- int errors;
- int got_host = FALSE;
- int use_subsystem = 0;
- long now, next;
-
- sklist = NULL;
- skcount = sksize = 0;
- /*
- * Initialise port and protocol to sensible defaults. (These
- * will be overridden by more or less anything.)
- */
- default_protocol = PROT_SSH;
- default_port = 22;
-
- flags = FLAG_STDERR;
- /*
- * Process the command line.
- */
- conf = conf_new();
- do_defaults(NULL, conf);
- loaded_session = FALSE;
- default_protocol = conf_get_int(conf, CONF_protocol);
- default_port = conf_get_int(conf, CONF_port);
- errors = 0;
- {
- /*
- * Override the default protocol if PLINK_PROTOCOL is set.
- */
- char *p = getenv("PLINK_PROTOCOL");
- if (p) {
- const Backend *b = backend_from_name(p);
- if (b) {
- default_protocol = b->protocol;
- default_port = b->default_port;
- conf_set_int(conf, CONF_protocol, default_protocol);
- conf_set_int(conf, CONF_port, default_port);
- }
- }
- }
- while (--argc) {
- char *p = *++argv;
- if (*p == '-') {
- int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
- 1, conf);
- if (ret == -2) {
- fprintf(stderr,
- "plink: option \"%s\" requires an argument\n", p);
- errors = 1;
- } else if (ret == 2) {
- --argc, ++argv;
- } else if (ret == 1) {
- continue;
- } else if (!strcmp(p, "-batch")) {
- console_batch_mode = 1;
- } else if (!strcmp(p, "-s")) {
- /* Save status to write to conf later. */
- use_subsystem = 1;
- } else if (!strcmp(p, "-V")) {
- version();
- } else if (!strcmp(p, "-pgpfp")) {
- pgp_fingerprints();
- exit(1);
- } else {
- fprintf(stderr, "plink: unknown option \"%s\"\n", p);
- errors = 1;
- }
- } else if (*p) {
- if (!conf_launchable(conf) || !(got_host || loaded_session)) {
- char *q = p;
- /*
- * If the hostname starts with "telnet:", set the
- * protocol to Telnet and process the string as a
- * Telnet URL.
- */
- if (!strncmp(q, "telnet:", 7)) {
- char c;
-
- q += 7;
- if (q[0] == '/' && q[1] == '/')
- q += 2;
- conf_set_int(conf, CONF_protocol, PROT_TELNET);
- p = q;
- while (*p && *p != ':' && *p != '/')
- p++;
- c = *p;
- if (*p)
- *p++ = '\0';
- if (c == ':')
- conf_set_int(conf, CONF_port, atoi(p));
- else
- conf_set_int(conf, CONF_port, -1);
- conf_set_str(conf, CONF_host, q);
- got_host = TRUE;
- } else {
- char *r, *user, *host;
- /*
- * Before we process the [user@]host string, we
- * first check for the presence of a protocol
- * prefix (a protocol name followed by ",").
- */
- r = strchr(p, ',');
- if (r) {
- const Backend *b;
- *r = '\0';
- b = backend_from_name(p);
- if (b) {
- default_protocol = b->protocol;
- conf_set_int(conf, CONF_protocol,
- default_protocol);
- portnumber = b->default_port;
- }
- p = r + 1;
- }
-
- /*
- * A nonzero length string followed by an @ is treated
- * as a username. (We discount an _initial_ @.) The
- * rest of the string (or the whole string if no @)
- * is treated as a session name and/or hostname.
- */
- r = strrchr(p, '@');
- if (r == p)
- p++, r = NULL; /* discount initial @ */
- if (r) {
- *r++ = '\0';
- user = p, host = r;
- } else {
- user = NULL, host = p;
- }
-
- /*
- * Now attempt to load a saved session with the
- * same name as the hostname.
- */
- {
- Conf *conf2 = conf_new();
- do_defaults(host, conf2);
- if (loaded_session || !conf_launchable(conf2)) {
- /* No settings for this host; use defaults */
- /* (or session was already loaded with -load) */
- conf_set_str(conf, CONF_host, host);
- conf_set_int(conf, CONF_port, default_port);
- got_host = TRUE;
- } else {
- conf_copy_into(conf, conf2);
- loaded_session = TRUE;
- }
- conf_free(conf2);
- }
-
- if (user) {
- /* Patch in specified username. */
- conf_set_str(conf, CONF_username, user);
- }
-
- }
- } else {
- char *command;
- int cmdlen, cmdsize;
- cmdlen = cmdsize = 0;
- command = NULL;
-
- while (argc) {
- while (*p) {
- if (cmdlen >= cmdsize) {
- cmdsize = cmdlen + 512;
- command = sresize(command, cmdsize, char);
- }
- command[cmdlen++]=*p++;
- }
- if (cmdlen >= cmdsize) {
- cmdsize = cmdlen + 512;
- command = sresize(command, cmdsize, char);
- }
- command[cmdlen++]=' '; /* always add trailing space */
- if (--argc) p = *++argv;
- }
- if (cmdlen) command[--cmdlen]='\0';
- /* change trailing blank to NUL */
- conf_set_str(conf, CONF_remote_cmd, command);
- conf_set_str(conf, CONF_remote_cmd2, "");
- conf_set_int(conf, CONF_nopty, TRUE); /* command => no tty */
-
- break; /* done with cmdline */
- }
- }
- }
-
- if (errors)
- return 1;
-
- if (!conf_launchable(conf) || !(got_host || loaded_session)) {
- usage();
- }
-
- /*
- * Muck about with the hostname in various ways.
- */
- {
- char *hostbuf = dupstr(conf_get_str(conf, CONF_host));
- char *host = hostbuf;
- char *p, *q;
-
- /*
- * Trim leading whitespace.
- */
- host += strspn(host, " \t");
-
- /*
- * See if host is of the form user@host, and separate out
- * the username if so.
- */
- if (host[0] != '\0') {
- char *atsign = strrchr(host, '@');
- if (atsign) {
- *atsign = '\0';
- conf_set_str(conf, CONF_username, host);
- host = atsign + 1;
- }
- }
-
- /*
- * Trim off a colon suffix if it's there.
- */
- host[strcspn(host, ":")] = '\0';
-
- /*
- * Remove any remaining whitespace.
- */
- p = hostbuf;
- q = host;
- while (*q) {
- if (*q != ' ' && *q != '\t')
- *p++ = *q;
- q++;
- }
- *p = '\0';
-
- conf_set_str(conf, CONF_host, hostbuf);
- sfree(hostbuf);
- }
-
- /*
- * Perform command-line overrides on session configuration.
- */
- cmdline_run_saved(conf);
-
- /*
- * Apply subsystem status.
- */
- if (use_subsystem)
- conf_set_int(conf, CONF_ssh_subsys, TRUE);
-
- if (!*conf_get_str(conf, CONF_remote_cmd) &&
- !*conf_get_str(conf, CONF_remote_cmd2) &&
- !*conf_get_str(conf, CONF_ssh_nc_host))
- flags |= FLAG_INTERACTIVE;
-
- /*
- * Select protocol. This is farmed out into a table in a
- * separate file to enable an ssh-free variant.
- */
- back = backend_from_proto(conf_get_int(conf, CONF_protocol));
- if (back == NULL) {
- fprintf(stderr,
- "Internal fault: Unsupported protocol found\n");
- return 1;
- }
-
- /*
- * Select port.
- */
- if (portnumber != -1)
- conf_set_int(conf, CONF_port, portnumber);
-
- sk_init();
- if (p_WSAEventSelect == NULL) {
- fprintf(stderr, "Plink requires WinSock 2\n");
- return 1;
- }
-
- logctx = log_init(NULL, conf);
- console_provide_logctx(logctx);
-
- /*
- * Start up the connection.
- */
- netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
- {
- const char *error;
- char *realhost;
- /* nodelay is only useful if stdin is a character device (console) */
- int nodelay = conf_get_int(conf, CONF_tcp_nodelay) &&
- (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR);
-
- error = back->init(NULL, &backhandle, conf,
- conf_get_str(conf, CONF_host),
- conf_get_int(conf, CONF_port),
- &realhost, nodelay,
- conf_get_int(conf, CONF_tcp_keepalives));
- if (error) {
- fprintf(stderr, "Unable to open connection:\n%s", error);
- return 1;
- }
- back->provide_logctx(backhandle, logctx);
- sfree(realhost);
- }
- connopen = 1;
-
- inhandle = GetStdHandle(STD_INPUT_HANDLE);
- outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
- errhandle = GetStdHandle(STD_ERROR_HANDLE);
-
- /*
- * Turn off ECHO and LINE input modes. We don't care if this
- * call fails, because we know we aren't necessarily running in
- * a console.
- */
- GetConsoleMode(inhandle, &orig_console_mode);
- SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
-
- /*
- * Pass the output handles to the handle-handling subsystem.
- * (The input one we leave until we're through the
- * authentication process.)
- */
- stdout_handle = handle_output_new(outhandle, stdouterr_sent, NULL, 0);
- stderr_handle = handle_output_new(errhandle, stdouterr_sent, NULL, 0);
-
- main_thread_id = GetCurrentThreadId();
-
- sending = FALSE;
-
- now = GETTICKCOUNT();
-
- while (1) {
- int nhandles;
- HANDLE *handles;
- int n;
- DWORD ticks;
-
- if (!sending && back->sendok(backhandle)) {
- stdin_handle = handle_input_new(inhandle, stdin_gotdata, NULL,
- 0);
- sending = TRUE;
- }
-
- if (run_timers(now, &next)) {
- ticks = next - GETTICKCOUNT();
- if (ticks < 0) ticks = 0; /* just in case */
- } else {
- ticks = INFINITE;
- }
-
- handles = handle_get_events(&nhandles);
- handles = sresize(handles, nhandles+1, HANDLE);
- handles[nhandles] = netevent;
- n = MsgWaitForMultipleObjects(nhandles+1, handles, FALSE, ticks,
- QS_POSTMESSAGE);
- if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) {
- handle_got_event(handles[n - WAIT_OBJECT_0]);
- } else if (n == WAIT_OBJECT_0 + nhandles) {
- WSANETWORKEVENTS things;
- SOCKET socket;
- extern SOCKET first_socket(int *), next_socket(int *);
- extern int select_result(WPARAM, LPARAM);
- int i, socketstate;
-
- /*
- * We must not call select_result() for any socket
- * until we have finished enumerating within the tree.
- * This is because select_result() may close the socket
- * and modify the tree.
- */
- /* Count the active sockets. */
- i = 0;
- for (socket = first_socket(&socketstate);
- socket != INVALID_SOCKET;
- socket = next_socket(&socketstate)) i++;
-
- /* Expand the buffer if necessary. */
- if (i > sksize) {
- sksize = i + 16;
- sklist = sresize(sklist, sksize, SOCKET);
- }
-
- /* Retrieve the sockets into sklist. */
- skcount = 0;
- for (socket = first_socket(&socketstate);
- socket != INVALID_SOCKET;
- socket = next_socket(&socketstate)) {
- sklist[skcount++] = socket;
- }
-
- /* Now we're done enumerating; go through the list. */
- for (i = 0; i < skcount; i++) {
- WPARAM wp;
- socket = sklist[i];
- wp = (WPARAM) socket;
- if (!p_WSAEnumNetworkEvents(socket, NULL, &things)) {
- static const struct { int bit, mask; } eventtypes[] = {
- {FD_CONNECT_BIT, FD_CONNECT},
- {FD_READ_BIT, FD_READ},
- {FD_CLOSE_BIT, FD_CLOSE},
- {FD_OOB_BIT, FD_OOB},
- {FD_WRITE_BIT, FD_WRITE},
- {FD_ACCEPT_BIT, FD_ACCEPT},
- };
- int e;
-
- noise_ultralight(socket);
- noise_ultralight(things.lNetworkEvents);
-
- for (e = 0; e < lenof(eventtypes); e++)
- if (things.lNetworkEvents & eventtypes[e].mask) {
- LPARAM lp;
- int err = things.iErrorCode[eventtypes[e].bit];
- lp = WSAMAKESELECTREPLY(eventtypes[e].mask, err);
- connopen &= select_result(wp, lp);
- }
- }
- }
- } else if (n == WAIT_OBJECT_0 + nhandles + 1) {
- MSG msg;
- while (PeekMessage(&msg, INVALID_HANDLE_VALUE,
- WM_AGENT_CALLBACK, WM_AGENT_CALLBACK,
- PM_REMOVE)) {
- struct agent_callback *c = (struct agent_callback *)msg.lParam;
- c->callback(c->callback_ctx, c->data, c->len);
- sfree(c);
- }
- }
-
- if (n == WAIT_TIMEOUT) {
- now = next;
- } else {
- now = GETTICKCOUNT();
- }
-
- sfree(handles);
-
- if (sending)
- handle_unthrottle(stdin_handle, back->sendbuffer(backhandle));
-
- if ((!connopen || !back->connected(backhandle)) &&
- handle_backlog(stdout_handle) + handle_backlog(stderr_handle) == 0)
- break; /* we closed the connection */
- }
- exitcode = back->exitcode(backhandle);
- if (exitcode < 0) {
- fprintf(stderr, "Remote process exit code unavailable\n");
- exitcode = 1; /* this is an error condition */
- }
- cleanup_exit(exitcode);
- return 0; /* placate compiler warning */
-}
-
-#ifdef _MSC_VER
-#pragma warning(disable:4273)
-#endif
-
-#ifndef _DEBUG
-_Check_return_opt_ int __cdecl printf(_In_z_ _Printf_format_string_ const char * pFmt, ...)
-{
- static int ConsoleCreated=0;
- va_list arglist;
- if (!ConsoleCreated)
- {
- int hConHandle;
- long lStdHandle;
- CONSOLE_SCREEN_BUFFER_INFO coninfo;
-
- FILE *fp;
- const unsigned int MAX_CONSOLE_LINES = 500;
- ConsoleCreated=1;
- if (!AttachConsole(ATTACH_PARENT_PROCESS))
- AllocConsole();
-
- // set the screen buffer to be big enough to let us scroll text
- GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
- coninfo.dwSize.Y = MAX_CONSOLE_LINES;
- SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);
-
- // redirect unbuffered STDOUT to the console
- lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE);
- hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
- fp = fdopen( hConHandle, "w" );
- *stdout = *fp;
- setvbuf( stdout, NULL, _IONBF, 0 );
-
- // redirect unbuffered STDIN to the console
- lStdHandle = (long)GetStdHandle(STD_INPUT_HANDLE);
- hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
- fp = fdopen( hConHandle, "r" );
- *stdin = *fp;
- setvbuf( stdin, NULL, _IONBF, 0 );
-
- // redirect unbuffered STDERR to the console
- lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE);
- hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
- fp = fdopen( hConHandle, "w" );
- *stderr = *fp;
- setvbuf( stderr, NULL, _IONBF, 0 );
-
- }
-
- va_start(arglist, pFmt );
- return vfprintf(stderr, pFmt, arglist);
-}
+/*
+ * PLink - a Windows command-line (stdin/stdout) variant of PuTTY.
+ */
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <stdarg.h>
+
+#define PUTTY_DO_GLOBALS /* actually _define_ globals */
+#include "putty.h"
+#include "storage.h"
+#include "tree234.h"
+
+#define WM_AGENT_CALLBACK (WM_APP + 4)
+
+struct agent_callback {
+ void (*callback)(void *, void *, int);
+ void *callback_ctx;
+ void *data;
+ int len;
+};
+
+void fatalbox(char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "FATAL ERROR: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ if (logctx) {
+ log_free(logctx);
+ logctx = NULL;
+ }
+ cleanup_exit(1);
+}
+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);
+ if (logctx) {
+ log_free(logctx);
+ logctx = NULL;
+ }
+ cleanup_exit(1);
+}
+void nonfatal(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, char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "FATAL ERROR: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ if (logctx) {
+ log_free(logctx);
+ logctx = NULL;
+ }
+ cleanup_exit(1);
+}
+void cmdline_error(char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "plink: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ exit(1);
+}
+
+HANDLE inhandle, outhandle, errhandle;
+struct handle *stdin_handle, *stdout_handle, *stderr_handle;
+DWORD orig_console_mode;
+int connopen;
+
+WSAEVENT netevent;
+
+static Backend *back;
+static void *backhandle;
+static Conf *conf;
+
+int term_ldisc(Terminal *term, int mode)
+{
+ return FALSE;
+}
+void ldisc_update(void *frontend, int echo, int edit)
+{
+ /* Update stdin read mode to reflect changes in line discipline. */
+ DWORD mode;
+
+ mode = ENABLE_PROCESSED_INPUT;
+ if (echo)
+ mode = mode | ENABLE_ECHO_INPUT;
+ else
+ mode = mode & ~ENABLE_ECHO_INPUT;
+ if (edit)
+ mode = mode | ENABLE_LINE_INPUT;
+ else
+ mode = mode & ~ENABLE_LINE_INPUT;
+ SetConsoleMode(inhandle, mode);
+}
+
+char *get_ttymode(void *frontend, const char *mode) { return NULL; }
+
+int from_backend(void *frontend_handle, int is_stderr,
+ const char *data, int len)
+{
+ if (is_stderr) {
+ handle_write(stderr_handle, data, len);
+ } else {
+ handle_write(stdout_handle, data, len);
+ }
+
+ return handle_backlog(stdout_handle) + handle_backlog(stderr_handle);
+}
+
+int from_backend_untrusted(void *frontend_handle, const char *data, int len)
+{
+ /*
+ * No "untrusted" output should get here (the way the code is
+ * currently, it's all diverted by FLAG_STDERR).
+ */
+ assert(!"Unexpected call to from_backend_untrusted()");
+ return 0; /* not reached */
+}
+
+int from_backend_eof(void *frontend_handle)
+{
+ handle_write_eof(stdout_handle);
+ return FALSE; /* do not respond to incoming EOF with outgoing */
+}
+
+int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
+{
+ int ret;
+ ret = cmdline_get_passwd_input(p, in, inlen);
+ if (ret == -1)
+ ret = console_get_userpass_input(p, in, inlen);
+ return ret;
+}
+
+static DWORD main_thread_id;
+
+void agent_schedule_callback(void (*callback)(void *, void *, int),
+ void *callback_ctx, void *data, int len)
+{
+ struct agent_callback *c = snew(struct agent_callback);
+ c->callback = callback;
+ c->callback_ctx = callback_ctx;
+ c->data = data;
+ c->len = len;
+ PostThreadMessage(main_thread_id, WM_AGENT_CALLBACK, 0, (LPARAM)c);
+}
+
+/*
+ * Short description of parameters.
+ */
+static void usage(void)
+{
+ printf("PuTTY Link: command-line connection utility\n");
+ printf("%s\n", ver);
+ printf("Usage: plink [options] [user@]host [command]\n");
+ printf(" (\"host\" can also be a PuTTY saved session name)\n");
+ printf("Options:\n");
+ printf(" -V print version information and exit\n");
+ printf(" -pgpfp print PGP key fingerprints and exit\n");
+ printf(" -v show verbose messages\n");
+ printf(" -load sessname Load settings from saved session\n");
+ printf(" -ssh -telnet -rlogin -raw -serial\n");
+ printf(" force use of a particular protocol\n");
+ printf(" -P port connect to specified port\n");
+ printf(" -l user connect with specified username\n");
+ printf(" -batch disable all interactive prompts\n");
+ printf("The following options only apply to SSH connections:\n");
+ printf(" -pw passw login with specified password\n");
+ printf(" -D [listen-IP:]listen-port\n");
+ printf(" Dynamic SOCKS-based port forwarding\n");
+ printf(" -L [listen-IP:]listen-port:host:port\n");
+ printf(" Forward local port to remote address\n");
+ printf(" -R [listen-IP:]listen-port:host:port\n");
+ printf(" Forward remote port to local address\n");
+ printf(" -X -x enable / disable X11 forwarding\n");
+ printf(" -A -a enable / disable agent forwarding\n");
+ printf(" -t -T enable / disable pty allocation\n");
+ printf(" -1 -2 force use of particular protocol version\n");
+ printf(" -4 -6 force use of IPv4 or IPv6\n");
+ printf(" -C enable compression\n");
+ printf(" -i key private key file for authentication\n");
+ printf(" -noagent disable use of Pageant\n");
+ printf(" -agent enable use of Pageant\n");
+ printf(" -m file read remote command(s) from file\n");
+ printf(" -s remote command is an SSH subsystem (SSH-2 only)\n");
+ printf(" -N don't start a shell/command (SSH-2 only)\n");
+ printf(" -nc host:port\n");
+ printf(" open tunnel in place of session (SSH-2 only)\n");
+ printf(" -sercfg configuration-string (e.g. 19200,8,n,1,X)\n");
+ printf(" Specify the serial configuration (serial only)\n");
+ exit(1);
+}
+
+static void version(void)
+{
+ printf("plink: %s\n", ver);
+ exit(1);
+}
+
+char *do_select(SOCKET skt, int startup)
+{
+ int events;
+ if (startup) {
+ events = (FD_CONNECT | FD_READ | FD_WRITE |
+ FD_OOB | FD_CLOSE | FD_ACCEPT);
+ } else {
+ events = 0;
+ }
+ if (p_WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {
+ switch (p_WSAGetLastError()) {
+ case WSAENETDOWN:
+ return "Network is down";
+ default:
+ return "WSAEventSelect(): unknown error";
+ }
+ }
+ return NULL;
+}
+
+int stdin_gotdata(struct handle *h, void *data, int len)
+{
+ if (len < 0) {
+ /*
+ * Special case: report read error.
+ */
+ char buf[4096];
+ FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, -len, 0,
+ buf, lenof(buf), NULL);
+ buf[lenof(buf)-1] = '\0';
+ if (buf[strlen(buf)-1] == '\n')
+ buf[strlen(buf)-1] = '\0';
+ fprintf(stderr, "Unable to read from standard input: %s\n", buf);
+ cleanup_exit(0);
+ }
+ noise_ultralight(len);
+ if (connopen && back->connected(backhandle)) {
+ if (len > 0) {
+ return back->send(backhandle, data, len);
+ } else {
+ back->special(backhandle, TS_EOF);
+ return 0;
+ }
+ } else
+ return 0;
+}
+
+void stdouterr_sent(struct handle *h, int new_backlog)
+{
+ if (new_backlog < 0) {
+ /*
+ * Special case: report write error.
+ */
+ char buf[4096];
+ FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, -new_backlog, 0,
+ buf, lenof(buf), NULL);
+ buf[lenof(buf)-1] = '\0';
+ if (buf[strlen(buf)-1] == '\n')
+ buf[strlen(buf)-1] = '\0';
+ fprintf(stderr, "Unable to write to standard %s: %s\n",
+ (h == stdout_handle ? "output" : "error"), buf);
+ cleanup_exit(0);
+ }
+ if (connopen && back->connected(backhandle)) {
+ back->unthrottle(backhandle, (handle_backlog(stdout_handle) +
+ handle_backlog(stderr_handle)));
+ }
+}
+
+const int share_can_be_downstream = TRUE;
+const int share_can_be_upstream = TRUE;
+
+int main(int argc, char **argv)
+{
+ int sending;
+ int portnumber = -1;
+ SOCKET *sklist;
+ int skcount, sksize;
+ int exitcode;
+ int errors;
+ int got_host = FALSE;
+ int use_subsystem = 0;
+ unsigned long now, next, then;
+
+ sklist = NULL;
+ skcount = sksize = 0;
+ /*
+ * Initialise port and protocol to sensible defaults. (These
+ * will be overridden by more or less anything.)
+ */
+ default_protocol = PROT_SSH;
+ default_port = 22;
+
+ flags = FLAG_STDERR;
+ /*
+ * Process the command line.
+ */
+ conf = conf_new();
+ do_defaults(NULL, conf);
+ loaded_session = FALSE;
+ default_protocol = conf_get_int(conf, CONF_protocol);
+ default_port = conf_get_int(conf, CONF_port);
+ errors = 0;
+ {
+ /*
+ * Override the default protocol if PLINK_PROTOCOL is set.
+ */
+ char *p = getenv("PLINK_PROTOCOL");
+ if (p) {
+ const Backend *b = backend_from_name(p);
+ if (b) {
+ default_protocol = b->protocol;
+ default_port = b->default_port;
+ conf_set_int(conf, CONF_protocol, default_protocol);
+ conf_set_int(conf, CONF_port, default_port);
+ }
+ }
+ }
+ while (--argc) {
+ char *p = *++argv;
+ if (*p == '-') {
+ int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
+ 1, conf);
+ if (ret == -2) {
+ fprintf(stderr,
+ "plink: option \"%s\" requires an argument\n", p);
+ errors = 1;
+ } else if (ret == 2) {
+ --argc, ++argv;
+ } else if (ret == 1) {
+ continue;
+ } else if (!strcmp(p, "-batch")) {
+ console_batch_mode = 1;
+ } else if (!strcmp(p, "-s")) {
+ /* Save status to write to conf later. */
+ use_subsystem = 1;
+ } else if (!strcmp(p, "-V") || !strcmp(p, "--version")) {
+ version();
+ } else if (!strcmp(p, "--help")) {
+ usage();
+ } else if (!strcmp(p, "-pgpfp")) {
+ pgp_fingerprints();
+ exit(1);
+ } else {
+ fprintf(stderr, "plink: unknown option \"%s\"\n", p);
+ errors = 1;
+ }
+ } else if (*p) {
+ if (!conf_launchable(conf) || !(got_host || loaded_session)) {
+ char *q = p;
+ /*
+ * If the hostname starts with "telnet:", set the
+ * protocol to Telnet and process the string as a
+ * Telnet URL.
+ */
+ if (!strncmp(q, "telnet:", 7)) {
+ char c;
+
+ q += 7;
+ if (q[0] == '/' && q[1] == '/')
+ q += 2;
+ conf_set_int(conf, CONF_protocol, PROT_TELNET);
+ p = q;
+ p += host_strcspn(p, ":/");
+ c = *p;
+ if (*p)
+ *p++ = '\0';
+ if (c == ':')
+ conf_set_int(conf, CONF_port, atoi(p));
+ else
+ conf_set_int(conf, CONF_port, -1);
+ conf_set_str(conf, CONF_host, q);
+ got_host = TRUE;
+ } else {
+ char *r, *user, *host;
+ /*
+ * Before we process the [user@]host string, we
+ * first check for the presence of a protocol
+ * prefix (a protocol name followed by ",").
+ */
+ r = strchr(p, ',');
+ if (r) {
+ const Backend *b;
+ *r = '\0';
+ b = backend_from_name(p);
+ if (b) {
+ default_protocol = b->protocol;
+ conf_set_int(conf, CONF_protocol,
+ default_protocol);
+ portnumber = b->default_port;
+ }
+ p = r + 1;
+ }
+
+ /*
+ * A nonzero length string followed by an @ is treated
+ * as a username. (We discount an _initial_ @.) The
+ * rest of the string (or the whole string if no @)
+ * is treated as a session name and/or hostname.
+ */
+ r = strrchr(p, '@');
+ if (r == p)
+ p++, r = NULL; /* discount initial @ */
+ if (r) {
+ *r++ = '\0';
+ user = p, host = r;
+ } else {
+ user = NULL, host = p;
+ }
+
+ /*
+ * Now attempt to load a saved session with the
+ * same name as the hostname.
+ */
+ {
+ Conf *conf2 = conf_new();
+ do_defaults(host, conf2);
+ if (loaded_session || !conf_launchable(conf2)) {
+ /* No settings for this host; use defaults */
+ /* (or session was already loaded with -load) */
+ conf_set_str(conf, CONF_host, host);
+ conf_set_int(conf, CONF_port, default_port);
+ got_host = TRUE;
+ } else {
+ conf_copy_into(conf, conf2);
+ loaded_session = TRUE;
+ }
+ conf_free(conf2);
+ }
+
+ if (user) {
+ /* Patch in specified username. */
+ conf_set_str(conf, CONF_username, user);
+ }
+
+ }
+ } else {
+ char *command;
+ int cmdlen, cmdsize;
+ cmdlen = cmdsize = 0;
+ command = NULL;
+
+ while (argc) {
+ while (*p) {
+ if (cmdlen >= cmdsize) {
+ cmdsize = cmdlen + 512;
+ command = sresize(command, cmdsize, char);
+ }
+ command[cmdlen++]=*p++;
+ }
+ if (cmdlen >= cmdsize) {
+ cmdsize = cmdlen + 512;
+ command = sresize(command, cmdsize, char);
+ }
+ command[cmdlen++]=' '; /* always add trailing space */
+ if (--argc) p = *++argv;
+ }
+ if (cmdlen) command[--cmdlen]='\0';
+ /* change trailing blank to NUL */
+ conf_set_str(conf, CONF_remote_cmd, command);
+ conf_set_str(conf, CONF_remote_cmd2, "");
+ conf_set_int(conf, CONF_nopty, TRUE); /* command => no tty */
+
+ break; /* done with cmdline */
+ }
+ }
+ }
+
+ if (errors)
+ return 1;
+
+ if (!conf_launchable(conf) || !(got_host || loaded_session)) {
+ usage();
+ }
+
+ /*
+ * Muck about with the hostname in various ways.
+ */
+ {
+ char *hostbuf = dupstr(conf_get_str(conf, CONF_host));
+ char *host = hostbuf;
+ char *p, *q;
+
+ /*
+ * Trim leading whitespace.
+ */
+ host += strspn(host, " \t");
+
+ /*
+ * See if host is of the form user@host, and separate out
+ * the username if so.
+ */
+ if (host[0] != '\0') {
+ char *atsign = strrchr(host, '@');
+ if (atsign) {
+ *atsign = '\0';
+ conf_set_str(conf, CONF_username, host);
+ host = atsign + 1;
+ }
+ }
+
+ /*
+ * Trim a colon suffix off the hostname if it's there. In
+ * order to protect unbracketed IPv6 address literals
+ * against this treatment, we do not do this if there's
+ * _more_ than one colon.
+ */
+ {
+ char *c = host_strchr(host, ':');
+
+ if (c) {
+ char *d = host_strchr(c+1, ':');
+ if (!d)
+ *c = '\0';
+ }
+ }
+
+ /*
+ * Remove any remaining whitespace.
+ */
+ p = hostbuf;
+ q = host;
+ while (*q) {
+ if (*q != ' ' && *q != '\t')
+ *p++ = *q;
+ q++;
+ }
+ *p = '\0';
+
+ conf_set_str(conf, CONF_host, hostbuf);
+ sfree(hostbuf);
+ }
+
+ /*
+ * Perform command-line overrides on session configuration.
+ */
+ cmdline_run_saved(conf);
+
+ /*
+ * Apply subsystem status.
+ */
+ if (use_subsystem)
+ conf_set_int(conf, CONF_ssh_subsys, TRUE);
+
+ if (!*conf_get_str(conf, CONF_remote_cmd) &&
+ !*conf_get_str(conf, CONF_remote_cmd2) &&
+ !*conf_get_str(conf, CONF_ssh_nc_host))
+ flags |= FLAG_INTERACTIVE;
+
+ /*
+ * Select protocol. This is farmed out into a table in a
+ * separate file to enable an ssh-free variant.
+ */
+ back = backend_from_proto(conf_get_int(conf, CONF_protocol));
+ if (back == NULL) {
+ fprintf(stderr,
+ "Internal fault: Unsupported protocol found\n");
+ return 1;
+ }
+
+ /*
+ * Select port.
+ */
+ if (portnumber != -1)
+ conf_set_int(conf, CONF_port, portnumber);
+
+ sk_init();
+ if (p_WSAEventSelect == NULL) {
+ fprintf(stderr, "Plink requires WinSock 2\n");
+ return 1;
+ }
+
+ logctx = log_init(NULL, conf);
+ console_provide_logctx(logctx);
+
+ /*
+ * Start up the connection.
+ */
+ netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ {
+ const char *error;
+ char *realhost;
+ /* nodelay is only useful if stdin is a character device (console) */
+ int nodelay = conf_get_int(conf, CONF_tcp_nodelay) &&
+ (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR);
+
+ error = back->init(NULL, &backhandle, conf,
+ conf_get_str(conf, CONF_host),
+ conf_get_int(conf, CONF_port),
+ &realhost, nodelay,
+ conf_get_int(conf, CONF_tcp_keepalives));
+ if (error) {
+ fprintf(stderr, "Unable to open connection:\n%s", error);
+ return 1;
+ }
+ back->provide_logctx(backhandle, logctx);
+ sfree(realhost);
+ }
+ connopen = 1;
+
+ inhandle = GetStdHandle(STD_INPUT_HANDLE);
+ outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
+ errhandle = GetStdHandle(STD_ERROR_HANDLE);
+
+ /*
+ * Turn off ECHO and LINE input modes. We don't care if this
+ * call fails, because we know we aren't necessarily running in
+ * a console.
+ */
+ GetConsoleMode(inhandle, &orig_console_mode);
+ SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
+
+ /*
+ * Pass the output handles to the handle-handling subsystem.
+ * (The input one we leave until we're through the
+ * authentication process.)
+ */
+ stdout_handle = handle_output_new(outhandle, stdouterr_sent, NULL, 0);
+ stderr_handle = handle_output_new(errhandle, stdouterr_sent, NULL, 0);
+
+ main_thread_id = GetCurrentThreadId();
+
+ sending = FALSE;
+
+ now = GETTICKCOUNT();
+
+ while (1) {
+ int nhandles;
+ HANDLE *handles;
+ int n;
+ DWORD ticks;
+
+ if (!sending && back->sendok(backhandle)) {
+ stdin_handle = handle_input_new(inhandle, stdin_gotdata, NULL,
+ 0);
+ sending = TRUE;
+ }
+
+ if (toplevel_callback_pending()) {
+ ticks = 0;
+ } else if (run_timers(now, &next)) {
+ then = now;
+ now = GETTICKCOUNT();
+ if (now - then > next - then)
+ ticks = 0;
+ else
+ ticks = next - now;
+ } else {
+ ticks = INFINITE;
+ }
+
+ handles = handle_get_events(&nhandles);
+ handles = sresize(handles, nhandles+1, HANDLE);
+ handles[nhandles] = netevent;
+ n = MsgWaitForMultipleObjects(nhandles+1, handles, FALSE, ticks,
+ QS_POSTMESSAGE);
+ if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) {
+ handle_got_event(handles[n - WAIT_OBJECT_0]);
+ } else if (n == WAIT_OBJECT_0 + nhandles) {
+ WSANETWORKEVENTS things;
+ SOCKET socket;
+ extern SOCKET first_socket(int *), next_socket(int *);
+ extern int select_result(WPARAM, LPARAM);
+ int i, socketstate;
+
+ /*
+ * We must not call select_result() for any socket
+ * until we have finished enumerating within the tree.
+ * This is because select_result() may close the socket
+ * and modify the tree.
+ */
+ /* Count the active sockets. */
+ i = 0;
+ for (socket = first_socket(&socketstate);
+ socket != INVALID_SOCKET;
+ socket = next_socket(&socketstate)) i++;
+
+ /* Expand the buffer if necessary. */
+ if (i > sksize) {
+ sksize = i + 16;
+ sklist = sresize(sklist, sksize, SOCKET);
+ }
+
+ /* Retrieve the sockets into sklist. */
+ skcount = 0;
+ for (socket = first_socket(&socketstate);
+ socket != INVALID_SOCKET;
+ socket = next_socket(&socketstate)) {
+ sklist[skcount++] = socket;
+ }
+
+ /* Now we're done enumerating; go through the list. */
+ for (i = 0; i < skcount; i++) {
+ WPARAM wp;
+ socket = sklist[i];
+ wp = (WPARAM) socket;
+ if (!p_WSAEnumNetworkEvents(socket, NULL, &things)) {
+ static const struct { int bit, mask; } eventtypes[] = {
+ {FD_CONNECT_BIT, FD_CONNECT},
+ {FD_READ_BIT, FD_READ},
+ {FD_CLOSE_BIT, FD_CLOSE},
+ {FD_OOB_BIT, FD_OOB},
+ {FD_WRITE_BIT, FD_WRITE},
+ {FD_ACCEPT_BIT, FD_ACCEPT},
+ };
+ int e;
+
+ noise_ultralight(socket);
+ noise_ultralight(things.lNetworkEvents);
+
+ for (e = 0; e < lenof(eventtypes); e++)
+ if (things.lNetworkEvents & eventtypes[e].mask) {
+ LPARAM lp;
+ int err = things.iErrorCode[eventtypes[e].bit];
+ lp = WSAMAKESELECTREPLY(eventtypes[e].mask, err);
+ connopen &= select_result(wp, lp);
+ }
+ }
+ }
+ } else if (n == WAIT_OBJECT_0 + nhandles + 1) {
+ MSG msg;
+ while (PeekMessage(&msg, INVALID_HANDLE_VALUE,
+ WM_AGENT_CALLBACK, WM_AGENT_CALLBACK,
+ PM_REMOVE)) {
+ struct agent_callback *c = (struct agent_callback *)msg.lParam;
+ c->callback(c->callback_ctx, c->data, c->len);
+ sfree(c);
+ }
+ }
+
+ run_toplevel_callbacks();
+
+ if (n == WAIT_TIMEOUT) {
+ now = next;
+ } else {
+ now = GETTICKCOUNT();
+ }
+
+ sfree(handles);
+
+ if (sending)
+ handle_unthrottle(stdin_handle, back->sendbuffer(backhandle));
+
+ if ((!connopen || !back->connected(backhandle)) &&
+ handle_backlog(stdout_handle) + handle_backlog(stderr_handle) == 0)
+ break; /* we closed the connection */
+ }
+ exitcode = back->exitcode(backhandle);
+ if (exitcode < 0) {
+ fprintf(stderr, "Remote process exit code unavailable\n");
+ exitcode = 1; /* this is an error condition */
+ }
+ cleanup_exit(exitcode);
+ return 0; /* placate compiler warning */
+}
+
+#ifdef _MSC_VER
+#pragma warning(disable:4273)
+#endif
+
+#ifndef _DEBUG
+_Check_return_opt_ int __cdecl printf(_In_z_ _Printf_format_string_ const char * pFmt, ...)
+{
+ static int ConsoleCreated=0;
+ va_list arglist;
+ if (!ConsoleCreated)
+ {
+ int hConHandle;
+ long lStdHandle;
+ CONSOLE_SCREEN_BUFFER_INFO coninfo;
+
+ FILE *fp;
+ const unsigned int MAX_CONSOLE_LINES = 500;
+ ConsoleCreated=1;
+ if (!AttachConsole(ATTACH_PARENT_PROCESS))
+ AllocConsole();
+
+ // set the screen buffer to be big enough to let us scroll text
+ GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
+ coninfo.dwSize.Y = MAX_CONSOLE_LINES;
+ SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);
+
+ // redirect unbuffered STDOUT to the console
+ lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE);
+ hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
+ fp = fdopen( hConHandle, "w" );
+ *stdout = *fp;
+ setvbuf( stdout, NULL, _IONBF, 0 );
+
+ // redirect unbuffered STDIN to the console
+ lStdHandle = (long)GetStdHandle(STD_INPUT_HANDLE);
+ hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
+ fp = fdopen( hConHandle, "r" );
+ *stdin = *fp;
+ setvbuf( stdin, NULL, _IONBF, 0 );
+
+ // redirect unbuffered STDERR to the console
+ lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE);
+ hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
+ fp = fdopen( hConHandle, "w" );
+ *stderr = *fp;
+ setvbuf( stderr, NULL, _IONBF, 0 );
+
+ }
+
+ va_start(arglist, pFmt );
+ return vfprintf(stderr, pFmt, arglist);
+}
#endif
diff --git a/tools/plink/winproxy.c b/tools/plink/winproxy.c
index 7a8c7b69b..aa14b91ae 100644
--- a/tools/plink/winproxy.c
+++ b/tools/plink/winproxy.c
@@ -1,227 +1,94 @@
-/*
- * winproxy.c: Windows implementation of platform_new_connection(),
- * supporting an OpenSSH-like proxy command via the winhandl.c
- * mechanism.
- */
-
-#include <stdio.h>
-#include <assert.h>
-
-#define DEFINE_PLUG_METHOD_MACROS
-#include "tree234.h"
-#include "putty.h"
-#include "network.h"
-#include "proxy.h"
-
-typedef struct Socket_localproxy_tag *Local_Proxy_Socket;
-
-struct Socket_localproxy_tag {
- const struct socket_function_table *fn;
- /* the above variable absolutely *must* be the first in this structure */
-
- HANDLE to_cmd_H, from_cmd_H;
- struct handle *to_cmd_h, *from_cmd_h;
-
- char *error;
-
- Plug plug;
-
- void *privptr;
-};
-
-int localproxy_gotdata(struct handle *h, void *data, int len)
-{
- Local_Proxy_Socket ps = (Local_Proxy_Socket) handle_get_privdata(h);
-
- if (len < 0) {
- return plug_closing(ps->plug, "Read error from local proxy command",
- 0, 0);
- } else if (len == 0) {
- return plug_closing(ps->plug, NULL, 0, 0);
- } else {
- return plug_receive(ps->plug, 0, data, len);
- }
-}
-
-void localproxy_sentdata(struct handle *h, int new_backlog)
-{
- Local_Proxy_Socket ps = (Local_Proxy_Socket) handle_get_privdata(h);
-
- plug_sent(ps->plug, new_backlog);
-}
-
-static Plug sk_localproxy_plug (Socket s, Plug p)
-{
- Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
- Plug ret = ps->plug;
- if (p)
- ps->plug = p;
- return ret;
-}
-
-static void sk_localproxy_close (Socket s)
-{
- Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
-
- handle_free(ps->to_cmd_h);
- handle_free(ps->from_cmd_h);
- CloseHandle(ps->to_cmd_H);
- CloseHandle(ps->from_cmd_H);
-
- sfree(ps);
-}
-
-static int sk_localproxy_write (Socket s, const char *data, int len)
-{
- Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
-
- return handle_write(ps->to_cmd_h, data, len);
-}
-
-static int sk_localproxy_write_oob(Socket s, const char *data, int len)
-{
- /*
- * oob data is treated as inband; nasty, but nothing really
- * better we can do
- */
- return sk_localproxy_write(s, data, len);
-}
-
-static void sk_localproxy_write_eof(Socket s)
-{
- Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
-
- handle_write_eof(ps->to_cmd_h);
-}
-
-static void sk_localproxy_flush(Socket s)
-{
- /* Local_Proxy_Socket ps = (Local_Proxy_Socket) s; */
- /* do nothing */
-}
-
-static void sk_localproxy_set_private_ptr(Socket s, void *ptr)
-{
- Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
- ps->privptr = ptr;
-}
-
-static void *sk_localproxy_get_private_ptr(Socket s)
-{
- Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
- return ps->privptr;
-}
-
-static void sk_localproxy_set_frozen(Socket s, int is_frozen)
-{
- Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
-
- /*
- * FIXME
- */
-}
-
-static const char *sk_localproxy_socket_error(Socket s)
-{
- Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
- return ps->error;
-}
-
-Socket platform_new_connection(SockAddr addr, char *hostname,
- int port, int privport,
- int oobinline, int nodelay, int keepalive,
- Plug plug, Conf *conf)
-{
- char *cmd;
-
- static const struct socket_function_table socket_fn_table = {
- sk_localproxy_plug,
- sk_localproxy_close,
- sk_localproxy_write,
- sk_localproxy_write_oob,
- sk_localproxy_write_eof,
- sk_localproxy_flush,
- sk_localproxy_set_private_ptr,
- sk_localproxy_get_private_ptr,
- sk_localproxy_set_frozen,
- sk_localproxy_socket_error
- };
-
- Local_Proxy_Socket ret;
- HANDLE us_to_cmd, us_from_cmd, cmd_to_us, cmd_from_us;
- SECURITY_ATTRIBUTES sa;
- STARTUPINFO si;
- PROCESS_INFORMATION pi;
-
- if (conf_get_int(conf, CONF_proxy_type) != PROXY_CMD)
- return NULL;
-
- cmd = format_telnet_command(addr, port, conf);
-
- {
- 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);
- sfree(msg);
- }
-
- ret = snew(struct Socket_localproxy_tag);
- ret->fn = &socket_fn_table;
- ret->plug = plug;
- ret->error = NULL;
-
- /*
- * Create the pipes to the proxy command, and spawn the proxy
- * command process.
- */
- sa.nLength = sizeof(sa);
- sa.lpSecurityDescriptor = NULL; /* default */
- sa.bInheritHandle = TRUE;
- if (!CreatePipe(&us_from_cmd, &cmd_to_us, &sa, 0)) {
- ret->error = dupprintf("Unable to create pipes for proxy command");
- return (Socket)ret;
- }
-
- if (!CreatePipe(&cmd_from_us, &us_to_cmd, &sa, 0)) {
- CloseHandle(us_from_cmd);
- CloseHandle(cmd_to_us);
- ret->error = dupprintf("Unable to create pipes for proxy command");
- return (Socket)ret;
- }
-
- SetHandleInformation(us_to_cmd, HANDLE_FLAG_INHERIT, 0);
- SetHandleInformation(us_from_cmd, HANDLE_FLAG_INHERIT, 0);
-
- si.cb = sizeof(si);
- si.lpReserved = NULL;
- si.lpDesktop = NULL;
- si.lpTitle = NULL;
- si.dwFlags = STARTF_USESTDHANDLES;
- si.cbReserved2 = 0;
- si.lpReserved2 = NULL;
- si.hStdInput = cmd_from_us;
- si.hStdOutput = cmd_to_us;
- si.hStdError = NULL;
- CreateProcess(NULL, cmd, NULL, NULL, TRUE,
- CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS,
- NULL, NULL, &si, &pi);
-
- sfree(cmd);
-
- CloseHandle(cmd_from_us);
- CloseHandle(cmd_to_us);
-
- ret->to_cmd_H = us_to_cmd;
- ret->from_cmd_H = us_from_cmd;
-
- ret->from_cmd_h = handle_input_new(ret->from_cmd_H, localproxy_gotdata,
- ret, 0);
- ret->to_cmd_h = handle_output_new(ret->to_cmd_H, localproxy_sentdata,
- ret, 0);
-
- /* We are responsible for this and don't need it any more */
- sk_addr_free(addr);
-
- return (Socket) ret;
-}
+/*
+ * winproxy.c: Windows implementation of platform_new_connection(),
+ * supporting an OpenSSH-like proxy command via the winhandl.c
+ * mechanism.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+
+#define DEFINE_PLUG_METHOD_MACROS
+#include "tree234.h"
+#include "putty.h"
+#include "network.h"
+#include "proxy.h"
+
+Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, Plug plug,
+ int overlapped);
+
+Socket platform_new_connection(SockAddr addr, 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;
+ SECURITY_ATTRIBUTES sa;
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+
+ if (conf_get_int(conf, CONF_proxy_type) != PROXY_CMD)
+ return NULL;
+
+ cmd = format_telnet_command(addr, port, conf);
+
+ /* We are responsible for this and don't need it any more */
+ sk_addr_free(addr);
+
+ {
+ 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);
+ sfree(msg);
+ }
+
+ /*
+ * Create the pipes to the proxy command, and spawn the proxy
+ * command process.
+ */
+ sa.nLength = sizeof(sa);
+ sa.lpSecurityDescriptor = NULL; /* default */
+ sa.bInheritHandle = TRUE;
+ if (!CreatePipe(&us_from_cmd, &cmd_to_us, &sa, 0)) {
+ Socket ret =
+ new_error_socket("Unable to create pipes for proxy command", plug);
+ sfree(cmd);
+ return ret;
+ }
+
+ if (!CreatePipe(&cmd_from_us, &us_to_cmd, &sa, 0)) {
+ Socket ret =
+ new_error_socket("Unable to create pipes for proxy command", plug);
+ sfree(cmd);
+ CloseHandle(us_from_cmd);
+ CloseHandle(cmd_to_us);
+ return ret;
+ }
+
+ SetHandleInformation(us_to_cmd, HANDLE_FLAG_INHERIT, 0);
+ SetHandleInformation(us_from_cmd, HANDLE_FLAG_INHERIT, 0);
+
+ si.cb = sizeof(si);
+ si.lpReserved = NULL;
+ si.lpDesktop = NULL;
+ si.lpTitle = NULL;
+ si.dwFlags = STARTF_USESTDHANDLES;
+ si.cbReserved2 = 0;
+ si.lpReserved2 = NULL;
+ si.hStdInput = cmd_from_us;
+ si.hStdOutput = cmd_to_us;
+ si.hStdError = NULL;
+ CreateProcess(NULL, cmd, NULL, NULL, TRUE,
+ CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS,
+ NULL, NULL, &si, &pi);
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+
+ sfree(cmd);
+
+ CloseHandle(cmd_from_us);
+ CloseHandle(cmd_to_us);
+
+ return make_handle_socket(us_to_cmd, us_from_cmd, plug, FALSE);
+}
diff --git a/tools/plink/winstore.c b/tools/plink/winstore.c
index f152b8f69..ce5dae61d 100644
--- a/tools/plink/winstore.c
+++ b/tools/plink/winstore.c
@@ -1,846 +1,868 @@
-/*
- * winstore.c: Windows-specific implementation of the interface
- * defined in storage.h.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <limits.h>
-#include "putty.h"
-#include "storage.h"
-
-#include <shlobj.h>
-#ifndef CSIDL_APPDATA
-#define CSIDL_APPDATA 0x001a
-#endif
-#ifndef CSIDL_LOCAL_APPDATA
-#define CSIDL_LOCAL_APPDATA 0x001c
-#endif
-
-static const char *const reg_jumplist_key = PUTTY_REG_POS "\\Jumplist";
-static const char *const reg_jumplist_value = "Recent sessions";
-static const char *const puttystr = PUTTY_REG_POS "\\Sessions";
-
-static const char hex[16] = "0123456789ABCDEF";
-
-static int tried_shgetfolderpath = FALSE;
-static HMODULE shell32_module = NULL;
-DECL_WINDOWS_FUNCTION(static, HRESULT, SHGetFolderPathA,
- (HWND, int, HANDLE, DWORD, LPSTR));
-
-static void mungestr(const char *in, char *out)
-{
- int candot = 0;
-
- while (*in) {
- if (*in == ' ' || *in == '\\' || *in == '*' || *in == '?' ||
- *in == '%' || *in < ' ' || *in > '~' || (*in == '.'
- && !candot)) {
- *out++ = '%';
- *out++ = hex[((unsigned char) *in) >> 4];
- *out++ = hex[((unsigned char) *in) & 15];
- } else
- *out++ = *in;
- in++;
- candot = 1;
- }
- *out = '\0';
- return;
-}
-
-static void unmungestr(const char *in, char *out, int outlen)
-{
- while (*in) {
- if (*in == '%' && in[1] && in[2]) {
- int i, j;
-
- i = in[1] - '0';
- i -= (i > 9 ? 7 : 0);
- j = in[2] - '0';
- j -= (j > 9 ? 7 : 0);
-
- *out++ = (i << 4) + j;
- if (!--outlen)
- return;
- in += 3;
- } else {
- *out++ = *in++;
- if (!--outlen)
- return;
- }
- }
- *out = '\0';
- return;
-}
-
-void *open_settings_w(const char *sessionname, char **errmsg)
-{
- HKEY subkey1, sesskey;
- int ret;
- char *p;
-
- *errmsg = NULL;
-
- if (!sessionname || !*sessionname)
- sessionname = "Default Settings";
-
- p = snewn(3 * strlen(sessionname) + 1, char);
- mungestr(sessionname, p);
-
- ret = RegCreateKey(HKEY_CURRENT_USER, puttystr, &subkey1);
- if (ret != ERROR_SUCCESS) {
- sfree(p);
- *errmsg = dupprintf("Unable to create registry key\n"
- "HKEY_CURRENT_USER\\%s", puttystr);
- return NULL;
- }
- ret = RegCreateKey(subkey1, p, &sesskey);
- RegCloseKey(subkey1);
- if (ret != ERROR_SUCCESS) {
- *errmsg = dupprintf("Unable to create registry key\n"
- "HKEY_CURRENT_USER\\%s\\%s", puttystr, p);
- sfree(p);
- return NULL;
- }
- sfree(p);
- return (void *) sesskey;
-}
-
-void write_setting_s(void *handle, const char *key, const char *value)
-{
- if (handle)
- RegSetValueEx((HKEY) handle, key, 0, REG_SZ, value,
- 1 + strlen(value));
-}
-
-void write_setting_i(void *handle, const char *key, int value)
-{
- if (handle)
- RegSetValueEx((HKEY) handle, key, 0, REG_DWORD,
- (CONST BYTE *) &value, sizeof(value));
-}
-
-void close_settings_w(void *handle)
-{
- RegCloseKey((HKEY) handle);
-}
-
-void *open_settings_r(const char *sessionname)
-{
- HKEY subkey1, sesskey;
- char *p;
-
- if (!sessionname || !*sessionname)
- sessionname = "Default Settings";
-
- p = snewn(3 * strlen(sessionname) + 1, char);
- mungestr(sessionname, p);
-
- if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS) {
- sesskey = NULL;
- } else {
- if (RegOpenKey(subkey1, p, &sesskey) != ERROR_SUCCESS) {
- sesskey = NULL;
- }
- RegCloseKey(subkey1);
- }
-
- sfree(p);
-
- return (void *) sesskey;
-}
-
-char *read_setting_s(void *handle, const char *key)
-{
- DWORD type, size;
- char *ret;
-
- if (!handle)
- return NULL;
-
- /* Find out the type and size of the data. */
- if (RegQueryValueEx((HKEY) handle, key, 0,
- &type, NULL, &size) != ERROR_SUCCESS ||
- type != REG_SZ)
- return NULL;
-
- ret = snewn(size+1, char);
- if (RegQueryValueEx((HKEY) handle, key, 0,
- &type, ret, &size) != ERROR_SUCCESS ||
- type != REG_SZ) return NULL;
-
- return ret;
-}
-
-int read_setting_i(void *handle, const char *key, int defvalue)
-{
- DWORD type, val, size;
- size = sizeof(val);
-
- if (!handle ||
- RegQueryValueEx((HKEY) handle, key, 0, &type,
- (BYTE *) &val, &size) != ERROR_SUCCESS ||
- size != sizeof(val) || type != REG_DWORD)
- return defvalue;
- else
- return val;
-}
-
-FontSpec *read_setting_fontspec(void *handle, const char *name)
-{
- char *settingname;
- char *fontname;
- int isbold, height, charset;
-
- fontname = read_setting_s(handle, name);
- if (!fontname)
- return NULL;
-
- settingname = dupcat(name, "IsBold", NULL);
- isbold = read_setting_i(handle, settingname, -1);
- sfree(settingname);
- if (isbold == -1) return NULL;
-
- settingname = dupcat(name, "CharSet", NULL);
- charset = read_setting_i(handle, settingname, -1);
- sfree(settingname);
- if (charset == -1) return NULL;
-
- settingname = dupcat(name, "Height", NULL);
- height = read_setting_i(handle, settingname, INT_MIN);
- sfree(settingname);
- if (height == INT_MIN) return NULL;
-
- return fontspec_new(fontname, isbold, height, charset);
-}
-
-void write_setting_fontspec(void *handle, const char *name, FontSpec *font)
-{
- char *settingname;
-
- write_setting_s(handle, name, font->name);
- settingname = dupcat(name, "IsBold", NULL);
- write_setting_i(handle, settingname, font->isbold);
- sfree(settingname);
- settingname = dupcat(name, "CharSet", NULL);
- write_setting_i(handle, settingname, font->charset);
- sfree(settingname);
- settingname = dupcat(name, "Height", NULL);
- write_setting_i(handle, settingname, font->height);
- sfree(settingname);
-}
-
-Filename *read_setting_filename(void *handle, const char *name)
-{
- char *tmp = read_setting_s(handle, name);
- if (tmp) {
- Filename *ret = filename_from_str(tmp);
- sfree(tmp);
- return ret;
- } else
- return NULL;
-}
-
-void write_setting_filename(void *handle, const char *name, Filename *result)
-{
- write_setting_s(handle, name, result->path);
-}
-
-void close_settings_r(void *handle)
-{
- RegCloseKey((HKEY) handle);
-}
-
-void del_settings(const char *sessionname)
-{
- HKEY subkey1;
- char *p;
-
- if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS)
- return;
-
- p = snewn(3 * strlen(sessionname) + 1, char);
- mungestr(sessionname, p);
- RegDeleteKey(subkey1, p);
- sfree(p);
-
- RegCloseKey(subkey1);
-
- remove_session_from_jumplist(sessionname);
-}
-
-struct enumsettings {
- HKEY key;
- int i;
-};
-
-void *enum_settings_start(void)
-{
- struct enumsettings *ret;
- HKEY key;
-
- if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &key) != ERROR_SUCCESS)
- return NULL;
-
- ret = snew(struct enumsettings);
- if (ret) {
- ret->key = key;
- ret->i = 0;
- }
-
- return ret;
-}
-
-char *enum_settings_next(void *handle, char *buffer, int buflen)
-{
- struct enumsettings *e = (struct enumsettings *) handle;
- char *otherbuf;
- otherbuf = snewn(3 * buflen, char);
- if (RegEnumKey(e->key, e->i++, otherbuf, 3 * buflen) == ERROR_SUCCESS) {
- unmungestr(otherbuf, buffer, buflen);
- sfree(otherbuf);
- return buffer;
- } else {
- sfree(otherbuf);
- return NULL;
- }
-}
-
-void enum_settings_finish(void *handle)
-{
- struct enumsettings *e = (struct enumsettings *) handle;
- RegCloseKey(e->key);
- sfree(e);
-}
-
-static void hostkey_regname(char *buffer, const char *hostname,
- int port, const char *keytype)
-{
- int len;
- strcpy(buffer, keytype);
- strcat(buffer, "@");
- len = strlen(buffer);
- len += sprintf(buffer + len, "%d:", port);
- mungestr(hostname, buffer + strlen(buffer));
-}
-
-int verify_host_key(const char *hostname, int port,
- const char *keytype, const char *key)
-{
- char *otherstr, *regname;
- int len;
- HKEY rkey;
- DWORD readlen;
- DWORD type;
- int ret, compare;
-
- len = 1 + strlen(key);
-
- /*
- * Now read a saved key in from the registry and see what it
- * says.
- */
- otherstr = snewn(len, char);
- regname = snewn(3 * (strlen(hostname) + strlen(keytype)) + 15, char);
-
- hostkey_regname(regname, hostname, port, keytype);
-
- if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys",
- &rkey) != ERROR_SUCCESS)
- return 1; /* key does not exist in registry */
-
- readlen = len;
- ret = RegQueryValueEx(rkey, regname, NULL, &type, otherstr, &readlen);
-
- if (ret != ERROR_SUCCESS && ret != ERROR_MORE_DATA &&
- !strcmp(keytype, "rsa")) {
- /*
- * Key didn't exist. If the key type is RSA, we'll try
- * another trick, which is to look up the _old_ key format
- * under just the hostname and translate that.
- */
- char *justhost = regname + 1 + strcspn(regname, ":");
- char *oldstyle = snewn(len + 10, char); /* safety margin */
- readlen = len;
- ret = RegQueryValueEx(rkey, justhost, NULL, &type,
- oldstyle, &readlen);
-
- if (ret == ERROR_SUCCESS && type == REG_SZ) {
- /*
- * The old format is two old-style bignums separated by
- * a slash. An old-style bignum is made of groups of
- * four hex digits: digits are ordered in sensible
- * (most to least significant) order within each group,
- * but groups are ordered in silly (least to most)
- * order within the bignum. The new format is two
- * ordinary C-format hex numbers (0xABCDEFG...XYZ, with
- * A nonzero except in the special case 0x0, which
- * doesn't appear anyway in RSA keys) separated by a
- * comma. All hex digits are lowercase in both formats.
- */
- char *p = otherstr;
- char *q = oldstyle;
- int i, j;
-
- for (i = 0; i < 2; i++) {
- int ndigits, nwords;
- *p++ = '0';
- *p++ = 'x';
- ndigits = strcspn(q, "/"); /* find / or end of string */
- nwords = ndigits / 4;
- /* now trim ndigits to remove leading zeros */
- while (q[(ndigits - 1) ^ 3] == '0' && ndigits > 1)
- ndigits--;
- /* now move digits over to new string */
- for (j = 0; j < ndigits; j++)
- p[ndigits - 1 - j] = q[j ^ 3];
- p += ndigits;
- q += nwords * 4;
- if (*q) {
- q++; /* eat the slash */
- *p++ = ','; /* add a comma */
- }
- *p = '\0'; /* terminate the string */
- }
-
- /*
- * Now _if_ this key matches, we'll enter it in the new
- * format. If not, we'll assume something odd went
- * wrong, and hyper-cautiously do nothing.
- */
- if (!strcmp(otherstr, key))
- RegSetValueEx(rkey, regname, 0, REG_SZ, otherstr,
- strlen(otherstr) + 1);
- }
- }
-
- RegCloseKey(rkey);
-
- compare = strcmp(otherstr, key);
-
- sfree(otherstr);
- sfree(regname);
-
- if (ret == ERROR_MORE_DATA ||
- (ret == ERROR_SUCCESS && type == REG_SZ && compare))
- return 2; /* key is different in registry */
- else if (ret != ERROR_SUCCESS || type != REG_SZ)
- return 1; /* key does not exist in registry */
- else
- return 0; /* key matched OK in registry */
-}
-
-void store_host_key(const char *hostname, int port,
- const char *keytype, const char *key)
-{
- char *regname;
- HKEY rkey;
-
- regname = snewn(3 * (strlen(hostname) + strlen(keytype)) + 15, char);
-
- hostkey_regname(regname, hostname, port, keytype);
-
- if (RegCreateKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys",
- &rkey) == ERROR_SUCCESS) {
- RegSetValueEx(rkey, regname, 0, REG_SZ, key, strlen(key) + 1);
- RegCloseKey(rkey);
- } /* else key does not exist in registry */
-
- sfree(regname);
-}
-
-/*
- * Open (or delete) the random seed file.
- */
-enum { DEL, OPEN_R, OPEN_W };
-static int try_random_seed(char const *path, int action, HANDLE *ret)
-{
- if (action == DEL) {
- remove(path);
- *ret = INVALID_HANDLE_VALUE;
- return FALSE; /* so we'll do the next ones too */
- }
-
- *ret = CreateFile(path,
- action == OPEN_W ? GENERIC_WRITE : GENERIC_READ,
- action == OPEN_W ? 0 : (FILE_SHARE_READ |
- FILE_SHARE_WRITE),
- NULL,
- action == OPEN_W ? CREATE_ALWAYS : OPEN_EXISTING,
- action == OPEN_W ? FILE_ATTRIBUTE_NORMAL : 0,
- NULL);
-
- return (*ret != INVALID_HANDLE_VALUE);
-}
-
-static HANDLE access_random_seed(int action)
-{
- HKEY rkey;
- DWORD type, size;
- HANDLE rethandle;
- char seedpath[2 * MAX_PATH + 10] = "\0";
-
- /*
- * Iterate over a selection of possible random seed paths until
- * we find one that works.
- *
- * We do this iteration separately for reading and writing,
- * meaning that we will automatically migrate random seed files
- * if a better location becomes available (by reading from the
- * best location in which we actually find one, and then
- * writing to the best location in which we can _create_ one).
- */
-
- /*
- * First, try the location specified by the user in the
- * Registry, if any.
- */
- size = sizeof(seedpath);
- if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &rkey) ==
- ERROR_SUCCESS) {
- int ret = RegQueryValueEx(rkey, "RandSeedFile",
- 0, &type, seedpath, &size);
- if (ret != ERROR_SUCCESS || type != REG_SZ)
- seedpath[0] = '\0';
- RegCloseKey(rkey);
-
- if (*seedpath && try_random_seed(seedpath, action, &rethandle))
- return rethandle;
- }
-
- /*
- * Next, try the user's local Application Data directory,
- * followed by their non-local one. This is found using the
- * SHGetFolderPath function, which won't be present on all
- * versions of Windows.
- */
- if (!tried_shgetfolderpath) {
- /* This is likely only to bear fruit on systems with IE5+
- * installed, or WinMe/2K+. There is some faffing with
- * SHFOLDER.DLL we could do to try to find an equivalent
- * on older versions of Windows if we cared enough.
- * However, the invocation below requires IE5+ anyway,
- * so stuff that. */
- shell32_module = load_system32_dll("shell32.dll");
- GET_WINDOWS_FUNCTION(shell32_module, SHGetFolderPathA);
- tried_shgetfolderpath = TRUE;
- }
- if (p_SHGetFolderPathA) {
- if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA,
- NULL, SHGFP_TYPE_CURRENT, seedpath))) {
- strcat(seedpath, "\\PUTTY.RND");
- if (try_random_seed(seedpath, action, &rethandle))
- return rethandle;
- }
-
- if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_APPDATA,
- NULL, SHGFP_TYPE_CURRENT, seedpath))) {
- strcat(seedpath, "\\PUTTY.RND");
- if (try_random_seed(seedpath, action, &rethandle))
- return rethandle;
- }
- }
-
- /*
- * Failing that, try %HOMEDRIVE%%HOMEPATH% as a guess at the
- * user's home directory.
- */
- {
- int len, ret;
-
- len =
- GetEnvironmentVariable("HOMEDRIVE", seedpath,
- sizeof(seedpath));
- ret =
- GetEnvironmentVariable("HOMEPATH", seedpath + len,
- sizeof(seedpath) - len);
- if (ret != 0) {
- strcat(seedpath, "\\PUTTY.RND");
- if (try_random_seed(seedpath, action, &rethandle))
- return rethandle;
- }
- }
-
- /*
- * And finally, fall back to C:\WINDOWS.
- */
- GetWindowsDirectory(seedpath, sizeof(seedpath));
- strcat(seedpath, "\\PUTTY.RND");
- if (try_random_seed(seedpath, action, &rethandle))
- return rethandle;
-
- /*
- * If even that failed, give up.
- */
- return INVALID_HANDLE_VALUE;
-}
-
-void read_random_seed(noise_consumer_t consumer)
-{
- HANDLE seedf = access_random_seed(OPEN_R);
-
- if (seedf != INVALID_HANDLE_VALUE) {
- while (1) {
- char buf[1024];
- DWORD len;
-
- if (ReadFile(seedf, buf, sizeof(buf), &len, NULL) && len)
- consumer(buf, len);
- else
- break;
- }
- CloseHandle(seedf);
- }
-}
-
-void write_random_seed(void *data, int len)
-{
- HANDLE seedf = access_random_seed(OPEN_W);
-
- if (seedf != INVALID_HANDLE_VALUE) {
- DWORD lenwritten;
-
- WriteFile(seedf, data, len, &lenwritten, NULL);
- CloseHandle(seedf);
- }
-}
-
-/*
- * Internal function supporting the jump list registry code. All the
- * functions to add, remove and read the list have substantially
- * similar content, so this is a generalisation of all of them which
- * transforms the list in the registry by prepending 'add' (if
- * non-null), removing 'rem' from what's left (if non-null), and
- * returning the resulting concatenated list of strings in 'out' (if
- * non-null).
- */
-static int transform_jumplist_registry
- (const char *add, const char *rem, char **out)
-{
- int ret;
- HKEY pjumplist_key, psettings_tmp;
- DWORD type;
- int value_length;
- char *old_value, *new_value;
- char *piterator_old, *piterator_new, *piterator_tmp;
-
- ret = RegCreateKeyEx(HKEY_CURRENT_USER, reg_jumplist_key, 0, NULL,
- REG_OPTION_NON_VOLATILE, (KEY_READ | KEY_WRITE), NULL,
- &pjumplist_key, NULL);
- if (ret != ERROR_SUCCESS) {
- return JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE;
- }
-
- /* Get current list of saved sessions in the registry. */
- value_length = 200;
- old_value = snewn(value_length, char);
- ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type,
- old_value, &value_length);
- /* When the passed buffer is too small, ERROR_MORE_DATA is
- * returned and the required size is returned in the length
- * argument. */
- if (ret == ERROR_MORE_DATA) {
- sfree(old_value);
- old_value = snewn(value_length, char);
- ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type,
- old_value, &value_length);
- }
-
- if (ret == ERROR_FILE_NOT_FOUND) {
- /* Value doesn't exist yet. Start from an empty value. */
- *old_value = '\0';
- *(old_value + 1) = '\0';
- } else if (ret != ERROR_SUCCESS) {
- /* Some non-recoverable error occurred. */
- sfree(old_value);
- RegCloseKey(pjumplist_key);
- return JUMPLISTREG_ERROR_VALUEREAD_FAILURE;
- } else if (type != REG_MULTI_SZ) {
- /* The value present in the registry has the wrong type: we
- * try to delete it and start from an empty value. */
- ret = RegDeleteValue(pjumplist_key, reg_jumplist_value);
- if (ret != ERROR_SUCCESS) {
- sfree(old_value);
- RegCloseKey(pjumplist_key);
- return JUMPLISTREG_ERROR_VALUEREAD_FAILURE;
- }
-
- *old_value = '\0';
- *(old_value + 1) = '\0';
- }
-
- /* Check validity of registry data: REG_MULTI_SZ value must end
- * with \0\0. */
- piterator_tmp = old_value;
- while (((piterator_tmp - old_value) < (value_length - 1)) &&
- !(*piterator_tmp == '\0' && *(piterator_tmp+1) == '\0')) {
- ++piterator_tmp;
- }
-
- if ((piterator_tmp - old_value) >= (value_length-1)) {
- /* Invalid value. Start from an empty value. */
- *old_value = '\0';
- *(old_value + 1) = '\0';
- }
-
- /*
- * Modify the list, if we're modifying.
- */
- if (add || rem) {
- /* Walk through the existing list and construct the new list of
- * saved sessions. */
- new_value = snewn(value_length + (add ? strlen(add) + 1 : 0), char);
- piterator_new = new_value;
- piterator_old = old_value;
-
- /* First add the new item to the beginning of the list. */
- if (add) {
- strcpy(piterator_new, add);
- piterator_new += strlen(piterator_new) + 1;
- }
- /* Now add the existing list, taking care to leave out the removed
- * item, if it was already in the existing list. */
- while (*piterator_old != '\0') {
- if (!rem || strcmp(piterator_old, rem) != 0) {
- /* Check if this is a valid session, otherwise don't add. */
- psettings_tmp = open_settings_r(piterator_old);
- if (psettings_tmp != NULL) {
- close_settings_r(psettings_tmp);
- strcpy(piterator_new, piterator_old);
- piterator_new += strlen(piterator_new) + 1;
- }
- }
- piterator_old += strlen(piterator_old) + 1;
- }
- *piterator_new = '\0';
- ++piterator_new;
-
- /* Save the new list to the registry. */
- ret = RegSetValueEx(pjumplist_key, reg_jumplist_value, 0, REG_MULTI_SZ,
- new_value, piterator_new - new_value);
-
- sfree(old_value);
- old_value = new_value;
- } else
- ret = ERROR_SUCCESS;
-
- /*
- * Either return or free the result.
- */
- if (out)
- *out = old_value;
- else
- sfree(old_value);
-
- /* Clean up and return. */
- RegCloseKey(pjumplist_key);
-
- if (ret != ERROR_SUCCESS) {
- return JUMPLISTREG_ERROR_VALUEWRITE_FAILURE;
- } else {
- return JUMPLISTREG_OK;
- }
-}
-
-/* Adds a new entry to the jumplist entries in the registry. */
-int add_to_jumplist_registry(const char *item)
-{
- return transform_jumplist_registry(item, item, NULL);
-}
-
-/* Removes an item from the jumplist entries in the registry. */
-int remove_from_jumplist_registry(const char *item)
-{
- return transform_jumplist_registry(NULL, item, NULL);
-}
-
-/* Returns the jumplist entries from the registry. Caller must free
- * the returned pointer. */
-char *get_jumplist_registry_entries (void)
-{
- char *list_value;
-
- if (transform_jumplist_registry(NULL,NULL,&list_value) != ERROR_SUCCESS) {
- list_value = snewn(2, char);
- *list_value = '\0';
- *(list_value + 1) = '\0';
- }
- return list_value;
-}
-
-/*
- * Recursively delete a registry key and everything under it.
- */
-static void registry_recursive_remove(HKEY key)
-{
- DWORD i;
- char name[MAX_PATH + 1];
- HKEY subkey;
-
- i = 0;
- while (RegEnumKey(key, i, name, sizeof(name)) == ERROR_SUCCESS) {
- if (RegOpenKey(key, name, &subkey) == ERROR_SUCCESS) {
- registry_recursive_remove(subkey);
- RegCloseKey(subkey);
- }
- RegDeleteKey(key, name);
- }
-}
-
-void cleanup_all(void)
-{
- HKEY key;
- int ret;
- char name[MAX_PATH + 1];
-
- /* ------------------------------------------------------------
- * Wipe out the random seed file, in all of its possible
- * locations.
- */
- access_random_seed(DEL);
-
- /* ------------------------------------------------------------
- * Ask Windows to delete any jump list information associated
- * with this installation of PuTTY.
- */
- clear_jumplist();
-
- /* ------------------------------------------------------------
- * Destroy all registry information associated with PuTTY.
- */
-
- /*
- * Open the main PuTTY registry key and remove everything in it.
- */
- if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &key) ==
- ERROR_SUCCESS) {
- registry_recursive_remove(key);
- RegCloseKey(key);
- }
- /*
- * Now open the parent key and remove the PuTTY main key. Once
- * we've done that, see if the parent key has any other
- * children.
- */
- if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_PARENT,
- &key) == ERROR_SUCCESS) {
- RegDeleteKey(key, PUTTY_REG_PARENT_CHILD);
- ret = RegEnumKey(key, 0, name, sizeof(name));
- RegCloseKey(key);
- /*
- * If the parent key had no other children, we must delete
- * it in its turn. That means opening the _grandparent_
- * key.
- */
- if (ret != ERROR_SUCCESS) {
- if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_GPARENT,
- &key) == ERROR_SUCCESS) {
- RegDeleteKey(key, PUTTY_REG_GPARENT_CHILD);
- RegCloseKey(key);
- }
- }
- }
- /*
- * Now we're done.
- */
-}
+/*
+ * winstore.c: Windows-specific implementation of the interface
+ * defined in storage.h.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include "putty.h"
+#include "storage.h"
+
+#include <shlobj.h>
+#ifndef CSIDL_APPDATA
+#define CSIDL_APPDATA 0x001a
+#endif
+#ifndef CSIDL_LOCAL_APPDATA
+#define CSIDL_LOCAL_APPDATA 0x001c
+#endif
+
+static const char *const reg_jumplist_key = PUTTY_REG_POS "\\Jumplist";
+static const char *const reg_jumplist_value = "Recent sessions";
+static const char *const puttystr = PUTTY_REG_POS "\\Sessions";
+
+static const char hex[16] = "0123456789ABCDEF";
+
+static int tried_shgetfolderpath = FALSE;
+static HMODULE shell32_module = NULL;
+DECL_WINDOWS_FUNCTION(static, HRESULT, SHGetFolderPathA,
+ (HWND, int, HANDLE, DWORD, LPSTR));
+
+static void mungestr(const char *in, char *out)
+{
+ int candot = 0;
+
+ while (*in) {
+ if (*in == ' ' || *in == '\\' || *in == '*' || *in == '?' ||
+ *in == '%' || *in < ' ' || *in > '~' || (*in == '.'
+ && !candot)) {
+ *out++ = '%';
+ *out++ = hex[((unsigned char) *in) >> 4];
+ *out++ = hex[((unsigned char) *in) & 15];
+ } else
+ *out++ = *in;
+ in++;
+ candot = 1;
+ }
+ *out = '\0';
+ return;
+}
+
+static void unmungestr(const char *in, char *out, int outlen)
+{
+ while (*in) {
+ if (*in == '%' && in[1] && in[2]) {
+ int i, j;
+
+ i = in[1] - '0';
+ i -= (i > 9 ? 7 : 0);
+ j = in[2] - '0';
+ j -= (j > 9 ? 7 : 0);
+
+ *out++ = (i << 4) + j;
+ if (!--outlen)
+ return;
+ in += 3;
+ } else {
+ *out++ = *in++;
+ if (!--outlen)
+ return;
+ }
+ }
+ *out = '\0';
+ return;
+}
+
+void *open_settings_w(const char *sessionname, char **errmsg)
+{
+ HKEY subkey1, sesskey;
+ int ret;
+ char *p;
+
+ *errmsg = NULL;
+
+ if (!sessionname || !*sessionname)
+ sessionname = "Default Settings";
+
+ p = snewn(3 * strlen(sessionname) + 1, char);
+ mungestr(sessionname, p);
+
+ ret = RegCreateKey(HKEY_CURRENT_USER, puttystr, &subkey1);
+ if (ret != ERROR_SUCCESS) {
+ sfree(p);
+ *errmsg = dupprintf("Unable to create registry key\n"
+ "HKEY_CURRENT_USER\\%s", puttystr);
+ return NULL;
+ }
+ ret = RegCreateKey(subkey1, p, &sesskey);
+ RegCloseKey(subkey1);
+ if (ret != ERROR_SUCCESS) {
+ *errmsg = dupprintf("Unable to create registry key\n"
+ "HKEY_CURRENT_USER\\%s\\%s", puttystr, p);
+ sfree(p);
+ return NULL;
+ }
+ sfree(p);
+ return (void *) sesskey;
+}
+
+void write_setting_s(void *handle, const char *key, const char *value)
+{
+ if (handle)
+ RegSetValueEx((HKEY) handle, key, 0, REG_SZ, value,
+ 1 + strlen(value));
+}
+
+void write_setting_i(void *handle, const char *key, int value)
+{
+ if (handle)
+ RegSetValueEx((HKEY) handle, key, 0, REG_DWORD,
+ (CONST BYTE *) &value, sizeof(value));
+}
+
+void close_settings_w(void *handle)
+{
+ RegCloseKey((HKEY) handle);
+}
+
+void *open_settings_r(const char *sessionname)
+{
+ HKEY subkey1, sesskey;
+ char *p;
+
+ if (!sessionname || !*sessionname)
+ sessionname = "Default Settings";
+
+ p = snewn(3 * strlen(sessionname) + 1, char);
+ mungestr(sessionname, p);
+
+ if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS) {
+ sesskey = NULL;
+ } else {
+ if (RegOpenKey(subkey1, p, &sesskey) != ERROR_SUCCESS) {
+ sesskey = NULL;
+ }
+ RegCloseKey(subkey1);
+ }
+
+ sfree(p);
+
+ return (void *) sesskey;
+}
+
+char *read_setting_s(void *handle, const char *key)
+{
+ DWORD type, size;
+ char *ret;
+
+ if (!handle)
+ return NULL;
+
+ /* Find out the type and size of the data. */
+ if (RegQueryValueEx((HKEY) handle, key, 0,
+ &type, NULL, &size) != ERROR_SUCCESS ||
+ type != REG_SZ)
+ return NULL;
+
+ ret = snewn(size+1, char);
+ if (RegQueryValueEx((HKEY) handle, key, 0,
+ &type, ret, &size) != ERROR_SUCCESS ||
+ type != REG_SZ) {
+ sfree(ret);
+ return NULL;
+ }
+
+ return ret;
+}
+
+int read_setting_i(void *handle, const char *key, int defvalue)
+{
+ DWORD type, val, size;
+ size = sizeof(val);
+
+ if (!handle ||
+ RegQueryValueEx((HKEY) handle, key, 0, &type,
+ (BYTE *) &val, &size) != ERROR_SUCCESS ||
+ size != sizeof(val) || type != REG_DWORD)
+ return defvalue;
+ else
+ return val;
+}
+
+FontSpec *read_setting_fontspec(void *handle, const char *name)
+{
+ char *settingname;
+ char *fontname;
+ FontSpec *ret;
+ int isbold, height, charset;
+
+ fontname = read_setting_s(handle, name);
+ if (!fontname)
+ return NULL;
+
+ settingname = dupcat(name, "IsBold", NULL);
+ isbold = read_setting_i(handle, settingname, -1);
+ sfree(settingname);
+ if (isbold == -1) {
+ sfree(fontname);
+ return NULL;
+ }
+
+ settingname = dupcat(name, "CharSet", NULL);
+ charset = read_setting_i(handle, settingname, -1);
+ sfree(settingname);
+ if (charset == -1) {
+ sfree(fontname);
+ return NULL;
+ }
+
+ settingname = dupcat(name, "Height", NULL);
+ height = read_setting_i(handle, settingname, INT_MIN);
+ sfree(settingname);
+ if (height == INT_MIN) {
+ sfree(fontname);
+ return NULL;
+ }
+
+ ret = fontspec_new(fontname, isbold, height, charset);
+ sfree(fontname);
+ return ret;
+}
+
+void write_setting_fontspec(void *handle, const char *name, FontSpec *font)
+{
+ char *settingname;
+
+ write_setting_s(handle, name, font->name);
+ settingname = dupcat(name, "IsBold", NULL);
+ write_setting_i(handle, settingname, font->isbold);
+ sfree(settingname);
+ settingname = dupcat(name, "CharSet", NULL);
+ write_setting_i(handle, settingname, font->charset);
+ sfree(settingname);
+ settingname = dupcat(name, "Height", NULL);
+ write_setting_i(handle, settingname, font->height);
+ sfree(settingname);
+}
+
+Filename *read_setting_filename(void *handle, const char *name)
+{
+ char *tmp = read_setting_s(handle, name);
+ if (tmp) {
+ Filename *ret = filename_from_str(tmp);
+ sfree(tmp);
+ return ret;
+ } else
+ return NULL;
+}
+
+void write_setting_filename(void *handle, const char *name, Filename *result)
+{
+ write_setting_s(handle, name, result->path);
+}
+
+void close_settings_r(void *handle)
+{
+ RegCloseKey((HKEY) handle);
+}
+
+void del_settings(const char *sessionname)
+{
+ HKEY subkey1;
+ char *p;
+
+ if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS)
+ return;
+
+ p = snewn(3 * strlen(sessionname) + 1, char);
+ mungestr(sessionname, p);
+ RegDeleteKey(subkey1, p);
+ sfree(p);
+
+ RegCloseKey(subkey1);
+
+ remove_session_from_jumplist(sessionname);
+}
+
+struct enumsettings {
+ HKEY key;
+ int i;
+};
+
+void *enum_settings_start(void)
+{
+ struct enumsettings *ret;
+ HKEY key;
+
+ if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &key) != ERROR_SUCCESS)
+ return NULL;
+
+ ret = snew(struct enumsettings);
+ if (ret) {
+ ret->key = key;
+ ret->i = 0;
+ }
+
+ return ret;
+}
+
+char *enum_settings_next(void *handle, char *buffer, int buflen)
+{
+ struct enumsettings *e = (struct enumsettings *) handle;
+ char *otherbuf;
+ otherbuf = snewn(3 * buflen, char);
+ if (RegEnumKey(e->key, e->i++, otherbuf, 3 * buflen) == ERROR_SUCCESS) {
+ unmungestr(otherbuf, buffer, buflen);
+ sfree(otherbuf);
+ return buffer;
+ } else {
+ sfree(otherbuf);
+ return NULL;
+ }
+}
+
+void enum_settings_finish(void *handle)
+{
+ struct enumsettings *e = (struct enumsettings *) handle;
+ RegCloseKey(e->key);
+ sfree(e);
+}
+
+static void hostkey_regname(char *buffer, const char *hostname,
+ int port, const char *keytype)
+{
+ int len;
+ strcpy(buffer, keytype);
+ strcat(buffer, "@");
+ len = strlen(buffer);
+ len += sprintf(buffer + len, "%d:", port);
+ mungestr(hostname, buffer + strlen(buffer));
+}
+
+int verify_host_key(const char *hostname, int port,
+ const char *keytype, const char *key)
+{
+ char *otherstr, *regname;
+ int len;
+ HKEY rkey;
+ DWORD readlen;
+ DWORD type;
+ int ret, compare;
+
+ len = 1 + strlen(key);
+
+ /*
+ * Now read a saved key in from the registry and see what it
+ * says.
+ */
+ regname = snewn(3 * (strlen(hostname) + strlen(keytype)) + 15, char);
+
+ hostkey_regname(regname, hostname, port, keytype);
+
+ if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys",
+ &rkey) != ERROR_SUCCESS) {
+ sfree(regname);
+ return 1; /* key does not exist in registry */
+ }
+
+ readlen = len;
+ otherstr = snewn(len, char);
+ ret = RegQueryValueEx(rkey, regname, NULL, &type, otherstr, &readlen);
+
+ if (ret != ERROR_SUCCESS && ret != ERROR_MORE_DATA &&
+ !strcmp(keytype, "rsa")) {
+ /*
+ * Key didn't exist. If the key type is RSA, we'll try
+ * another trick, which is to look up the _old_ key format
+ * under just the hostname and translate that.
+ */
+ char *justhost = regname + 1 + strcspn(regname, ":");
+ char *oldstyle = snewn(len + 10, char); /* safety margin */
+ readlen = len;
+ ret = RegQueryValueEx(rkey, justhost, NULL, &type,
+ oldstyle, &readlen);
+
+ if (ret == ERROR_SUCCESS && type == REG_SZ) {
+ /*
+ * The old format is two old-style bignums separated by
+ * a slash. An old-style bignum is made of groups of
+ * four hex digits: digits are ordered in sensible
+ * (most to least significant) order within each group,
+ * but groups are ordered in silly (least to most)
+ * order within the bignum. The new format is two
+ * ordinary C-format hex numbers (0xABCDEFG...XYZ, with
+ * A nonzero except in the special case 0x0, which
+ * doesn't appear anyway in RSA keys) separated by a
+ * comma. All hex digits are lowercase in both formats.
+ */
+ char *p = otherstr;
+ char *q = oldstyle;
+ int i, j;
+
+ for (i = 0; i < 2; i++) {
+ int ndigits, nwords;
+ *p++ = '0';
+ *p++ = 'x';
+ ndigits = strcspn(q, "/"); /* find / or end of string */
+ nwords = ndigits / 4;
+ /* now trim ndigits to remove leading zeros */
+ while (q[(ndigits - 1) ^ 3] == '0' && ndigits > 1)
+ ndigits--;
+ /* now move digits over to new string */
+ for (j = 0; j < ndigits; j++)
+ p[ndigits - 1 - j] = q[j ^ 3];
+ p += ndigits;
+ q += nwords * 4;
+ if (*q) {
+ q++; /* eat the slash */
+ *p++ = ','; /* add a comma */
+ }
+ *p = '\0'; /* terminate the string */
+ }
+
+ /*
+ * Now _if_ this key matches, we'll enter it in the new
+ * format. If not, we'll assume something odd went
+ * wrong, and hyper-cautiously do nothing.
+ */
+ if (!strcmp(otherstr, key))
+ RegSetValueEx(rkey, regname, 0, REG_SZ, otherstr,
+ strlen(otherstr) + 1);
+ }
+
+ sfree(oldstyle);
+ }
+
+ RegCloseKey(rkey);
+
+ compare = strcmp(otherstr, key);
+
+ sfree(otherstr);
+ sfree(regname);
+
+ if (ret == ERROR_MORE_DATA ||
+ (ret == ERROR_SUCCESS && type == REG_SZ && compare))
+ return 2; /* key is different in registry */
+ else if (ret != ERROR_SUCCESS || type != REG_SZ)
+ return 1; /* key does not exist in registry */
+ else
+ return 0; /* key matched OK in registry */
+}
+
+void store_host_key(const char *hostname, int port,
+ const char *keytype, const char *key)
+{
+ char *regname;
+ HKEY rkey;
+
+ regname = snewn(3 * (strlen(hostname) + strlen(keytype)) + 15, char);
+
+ hostkey_regname(regname, hostname, port, keytype);
+
+ if (RegCreateKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys",
+ &rkey) == ERROR_SUCCESS) {
+ RegSetValueEx(rkey, regname, 0, REG_SZ, key, strlen(key) + 1);
+ RegCloseKey(rkey);
+ } /* else key does not exist in registry */
+
+ sfree(regname);
+}
+
+/*
+ * Open (or delete) the random seed file.
+ */
+enum { DEL, OPEN_R, OPEN_W };
+static int try_random_seed(char const *path, int action, HANDLE *ret)
+{
+ if (action == DEL) {
+ if (!DeleteFile(path) && GetLastError() != ERROR_FILE_NOT_FOUND) {
+ nonfatal("Unable to delete '%s': %s", path,
+ win_strerror(GetLastError()));
+ }
+ *ret = INVALID_HANDLE_VALUE;
+ return FALSE; /* so we'll do the next ones too */
+ }
+
+ *ret = CreateFile(path,
+ action == OPEN_W ? GENERIC_WRITE : GENERIC_READ,
+ action == OPEN_W ? 0 : (FILE_SHARE_READ |
+ FILE_SHARE_WRITE),
+ NULL,
+ action == OPEN_W ? CREATE_ALWAYS : OPEN_EXISTING,
+ action == OPEN_W ? FILE_ATTRIBUTE_NORMAL : 0,
+ NULL);
+
+ return (*ret != INVALID_HANDLE_VALUE);
+}
+
+static HANDLE access_random_seed(int action)
+{
+ HKEY rkey;
+ DWORD type, size;
+ HANDLE rethandle;
+ char seedpath[2 * MAX_PATH + 10] = "\0";
+
+ /*
+ * Iterate over a selection of possible random seed paths until
+ * we find one that works.
+ *
+ * We do this iteration separately for reading and writing,
+ * meaning that we will automatically migrate random seed files
+ * if a better location becomes available (by reading from the
+ * best location in which we actually find one, and then
+ * writing to the best location in which we can _create_ one).
+ */
+
+ /*
+ * First, try the location specified by the user in the
+ * Registry, if any.
+ */
+ size = sizeof(seedpath);
+ if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &rkey) ==
+ ERROR_SUCCESS) {
+ int ret = RegQueryValueEx(rkey, "RandSeedFile",
+ 0, &type, seedpath, &size);
+ if (ret != ERROR_SUCCESS || type != REG_SZ)
+ seedpath[0] = '\0';
+ RegCloseKey(rkey);
+
+ if (*seedpath && try_random_seed(seedpath, action, &rethandle))
+ return rethandle;
+ }
+
+ /*
+ * Next, try the user's local Application Data directory,
+ * followed by their non-local one. This is found using the
+ * SHGetFolderPath function, which won't be present on all
+ * versions of Windows.
+ */
+ if (!tried_shgetfolderpath) {
+ /* This is likely only to bear fruit on systems with IE5+
+ * installed, or WinMe/2K+. There is some faffing with
+ * SHFOLDER.DLL we could do to try to find an equivalent
+ * on older versions of Windows if we cared enough.
+ * However, the invocation below requires IE5+ anyway,
+ * so stuff that. */
+ shell32_module = load_system32_dll("shell32.dll");
+ GET_WINDOWS_FUNCTION(shell32_module, SHGetFolderPathA);
+ tried_shgetfolderpath = TRUE;
+ }
+ if (p_SHGetFolderPathA) {
+ if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA,
+ NULL, SHGFP_TYPE_CURRENT, seedpath))) {
+ strcat(seedpath, "\\PUTTY.RND");
+ if (try_random_seed(seedpath, action, &rethandle))
+ return rethandle;
+ }
+
+ if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_APPDATA,
+ NULL, SHGFP_TYPE_CURRENT, seedpath))) {
+ strcat(seedpath, "\\PUTTY.RND");
+ if (try_random_seed(seedpath, action, &rethandle))
+ return rethandle;
+ }
+ }
+
+ /*
+ * Failing that, try %HOMEDRIVE%%HOMEPATH% as a guess at the
+ * user's home directory.
+ */
+ {
+ int len, ret;
+
+ len =
+ GetEnvironmentVariable("HOMEDRIVE", seedpath,
+ sizeof(seedpath));
+ ret =
+ GetEnvironmentVariable("HOMEPATH", seedpath + len,
+ sizeof(seedpath) - len);
+ if (ret != 0) {
+ strcat(seedpath, "\\PUTTY.RND");
+ if (try_random_seed(seedpath, action, &rethandle))
+ return rethandle;
+ }
+ }
+
+ /*
+ * And finally, fall back to C:\WINDOWS.
+ */
+ GetWindowsDirectory(seedpath, sizeof(seedpath));
+ strcat(seedpath, "\\PUTTY.RND");
+ if (try_random_seed(seedpath, action, &rethandle))
+ return rethandle;
+
+ /*
+ * If even that failed, give up.
+ */
+ return INVALID_HANDLE_VALUE;
+}
+
+void read_random_seed(noise_consumer_t consumer)
+{
+ HANDLE seedf = access_random_seed(OPEN_R);
+
+ if (seedf != INVALID_HANDLE_VALUE) {
+ while (1) {
+ char buf[1024];
+ DWORD len;
+
+ if (ReadFile(seedf, buf, sizeof(buf), &len, NULL) && len)
+ consumer(buf, len);
+ else
+ break;
+ }
+ CloseHandle(seedf);
+ }
+}
+
+void write_random_seed(void *data, int len)
+{
+ HANDLE seedf = access_random_seed(OPEN_W);
+
+ if (seedf != INVALID_HANDLE_VALUE) {
+ DWORD lenwritten;
+
+ WriteFile(seedf, data, len, &lenwritten, NULL);
+ CloseHandle(seedf);
+ }
+}
+
+/*
+ * Internal function supporting the jump list registry code. All the
+ * functions to add, remove and read the list have substantially
+ * similar content, so this is a generalisation of all of them which
+ * transforms the list in the registry by prepending 'add' (if
+ * non-null), removing 'rem' from what's left (if non-null), and
+ * returning the resulting concatenated list of strings in 'out' (if
+ * non-null).
+ */
+static int transform_jumplist_registry
+ (const char *add, const char *rem, char **out)
+{
+ int ret;
+ HKEY pjumplist_key, psettings_tmp;
+ DWORD type;
+ int value_length;
+ char *old_value, *new_value;
+ char *piterator_old, *piterator_new, *piterator_tmp;
+
+ ret = RegCreateKeyEx(HKEY_CURRENT_USER, reg_jumplist_key, 0, NULL,
+ REG_OPTION_NON_VOLATILE, (KEY_READ | KEY_WRITE), NULL,
+ &pjumplist_key, NULL);
+ if (ret != ERROR_SUCCESS) {
+ return JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE;
+ }
+
+ /* Get current list of saved sessions in the registry. */
+ value_length = 200;
+ old_value = snewn(value_length, char);
+ ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type,
+ old_value, &value_length);
+ /* When the passed buffer is too small, ERROR_MORE_DATA is
+ * returned and the required size is returned in the length
+ * argument. */
+ if (ret == ERROR_MORE_DATA) {
+ sfree(old_value);
+ old_value = snewn(value_length, char);
+ ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type,
+ old_value, &value_length);
+ }
+
+ if (ret == ERROR_FILE_NOT_FOUND) {
+ /* Value doesn't exist yet. Start from an empty value. */
+ *old_value = '\0';
+ *(old_value + 1) = '\0';
+ } else if (ret != ERROR_SUCCESS) {
+ /* Some non-recoverable error occurred. */
+ sfree(old_value);
+ RegCloseKey(pjumplist_key);
+ return JUMPLISTREG_ERROR_VALUEREAD_FAILURE;
+ } else if (type != REG_MULTI_SZ) {
+ /* The value present in the registry has the wrong type: we
+ * try to delete it and start from an empty value. */
+ ret = RegDeleteValue(pjumplist_key, reg_jumplist_value);
+ if (ret != ERROR_SUCCESS) {
+ sfree(old_value);
+ RegCloseKey(pjumplist_key);
+ return JUMPLISTREG_ERROR_VALUEREAD_FAILURE;
+ }
+
+ *old_value = '\0';
+ *(old_value + 1) = '\0';
+ }
+
+ /* Check validity of registry data: REG_MULTI_SZ value must end
+ * with \0\0. */
+ piterator_tmp = old_value;
+ while (((piterator_tmp - old_value) < (value_length - 1)) &&
+ !(*piterator_tmp == '\0' && *(piterator_tmp+1) == '\0')) {
+ ++piterator_tmp;
+ }
+
+ if ((piterator_tmp - old_value) >= (value_length-1)) {
+ /* Invalid value. Start from an empty value. */
+ *old_value = '\0';
+ *(old_value + 1) = '\0';
+ }
+
+ /*
+ * Modify the list, if we're modifying.
+ */
+ if (add || rem) {
+ /* Walk through the existing list and construct the new list of
+ * saved sessions. */
+ new_value = snewn(value_length + (add ? strlen(add) + 1 : 0), char);
+ piterator_new = new_value;
+ piterator_old = old_value;
+
+ /* First add the new item to the beginning of the list. */
+ if (add) {
+ strcpy(piterator_new, add);
+ piterator_new += strlen(piterator_new) + 1;
+ }
+ /* Now add the existing list, taking care to leave out the removed
+ * item, if it was already in the existing list. */
+ while (*piterator_old != '\0') {
+ if (!rem || strcmp(piterator_old, rem) != 0) {
+ /* Check if this is a valid session, otherwise don't add. */
+ psettings_tmp = open_settings_r(piterator_old);
+ if (psettings_tmp != NULL) {
+ close_settings_r(psettings_tmp);
+ strcpy(piterator_new, piterator_old);
+ piterator_new += strlen(piterator_new) + 1;
+ }
+ }
+ piterator_old += strlen(piterator_old) + 1;
+ }
+ *piterator_new = '\0';
+ ++piterator_new;
+
+ /* Save the new list to the registry. */
+ ret = RegSetValueEx(pjumplist_key, reg_jumplist_value, 0, REG_MULTI_SZ,
+ new_value, piterator_new - new_value);
+
+ sfree(old_value);
+ old_value = new_value;
+ } else
+ ret = ERROR_SUCCESS;
+
+ /*
+ * Either return or free the result.
+ */
+ if (out && ret == ERROR_SUCCESS)
+ *out = old_value;
+ else
+ sfree(old_value);
+
+ /* Clean up and return. */
+ RegCloseKey(pjumplist_key);
+
+ if (ret != ERROR_SUCCESS) {
+ return JUMPLISTREG_ERROR_VALUEWRITE_FAILURE;
+ } else {
+ return JUMPLISTREG_OK;
+ }
+}
+
+/* Adds a new entry to the jumplist entries in the registry. */
+int add_to_jumplist_registry(const char *item)
+{
+ return transform_jumplist_registry(item, item, NULL);
+}
+
+/* Removes an item from the jumplist entries in the registry. */
+int remove_from_jumplist_registry(const char *item)
+{
+ return transform_jumplist_registry(NULL, item, NULL);
+}
+
+/* Returns the jumplist entries from the registry. Caller must free
+ * the returned pointer. */
+char *get_jumplist_registry_entries (void)
+{
+ char *list_value;
+
+ if (transform_jumplist_registry(NULL,NULL,&list_value) != JUMPLISTREG_OK) {
+ list_value = snewn(2, char);
+ *list_value = '\0';
+ *(list_value + 1) = '\0';
+ }
+ return list_value;
+}
+
+/*
+ * Recursively delete a registry key and everything under it.
+ */
+static void registry_recursive_remove(HKEY key)
+{
+ DWORD i;
+ char name[MAX_PATH + 1];
+ HKEY subkey;
+
+ i = 0;
+ while (RegEnumKey(key, i, name, sizeof(name)) == ERROR_SUCCESS) {
+ if (RegOpenKey(key, name, &subkey) == ERROR_SUCCESS) {
+ registry_recursive_remove(subkey);
+ RegCloseKey(subkey);
+ }
+ RegDeleteKey(key, name);
+ }
+}
+
+void cleanup_all(void)
+{
+ HKEY key;
+ int ret;
+ char name[MAX_PATH + 1];
+
+ /* ------------------------------------------------------------
+ * Wipe out the random seed file, in all of its possible
+ * locations.
+ */
+ access_random_seed(DEL);
+
+ /* ------------------------------------------------------------
+ * Ask Windows to delete any jump list information associated
+ * with this installation of PuTTY.
+ */
+ clear_jumplist();
+
+ /* ------------------------------------------------------------
+ * Destroy all registry information associated with PuTTY.
+ */
+
+ /*
+ * Open the main PuTTY registry key and remove everything in it.
+ */
+ if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &key) ==
+ ERROR_SUCCESS) {
+ registry_recursive_remove(key);
+ RegCloseKey(key);
+ }
+ /*
+ * Now open the parent key and remove the PuTTY main key. Once
+ * we've done that, see if the parent key has any other
+ * children.
+ */
+ if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_PARENT,
+ &key) == ERROR_SUCCESS) {
+ RegDeleteKey(key, PUTTY_REG_PARENT_CHILD);
+ ret = RegEnumKey(key, 0, name, sizeof(name));
+ RegCloseKey(key);
+ /*
+ * If the parent key had no other children, we must delete
+ * it in its turn. That means opening the _grandparent_
+ * key.
+ */
+ if (ret != ERROR_SUCCESS) {
+ if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_GPARENT,
+ &key) == ERROR_SUCCESS) {
+ RegDeleteKey(key, PUTTY_REG_GPARENT_CHILD);
+ RegCloseKey(key);
+ }
+ }
+ }
+ /*
+ * Now we're done.
+ */
+}
diff --git a/tools/plink/winstuff.h b/tools/plink/winstuff.h
index d2d65a47f..8f5e0dff0 100644
--- a/tools/plink/winstuff.h
+++ b/tools/plink/winstuff.h
@@ -1,559 +1,555 @@
-/*
- * winstuff.h: Windows-specific inter-module stuff.
- */
-
-#ifndef PUTTY_WINSTUFF_H
-#define PUTTY_WINSTUFF_H
-
-#ifndef AUTO_WINSOCK
-#include <winsock2.h>
-#endif
-#include <windows.h>
-#include <stdio.h> /* for FILENAME_MAX */
-
-#include "tree234.h"
-
-#include "winhelp.h"
-
-struct Filename {
- char *path;
-};
-#define f_open(filename, mode, isprivate) ( fopen((filename)->path, (mode)) )
-
-struct FontSpec {
- char *name;
- int isbold;
- int height;
- int charset;
-};
-struct FontSpec *fontspec_new(const char *name,
- int bold, int height, int charset);
-
-#ifndef CLEARTYPE_QUALITY
-#define CLEARTYPE_QUALITY 5
-#endif
-#define FONT_QUALITY(fq) ( \
- (fq) == FQ_DEFAULT ? DEFAULT_QUALITY : \
- (fq) == FQ_ANTIALIASED ? ANTIALIASED_QUALITY : \
- (fq) == FQ_NONANTIALIASED ? NONANTIALIASED_QUALITY : \
- CLEARTYPE_QUALITY)
-
-#define PLATFORM_IS_UTF16 /* enable UTF-16 processing when exchanging
- * wchar_t strings with environment */
-
-/*
- * Where we can, we use GetWindowLongPtr and friends because they're
- * more useful on 64-bit platforms, but they're a relatively recent
- * innovation, missing from VC++ 6 and older MinGW. Degrade nicely.
- * (NB that on some systems, some of these things are available but
- * not others...)
- */
-
-#ifndef GCLP_HCURSOR
-/* GetClassLongPtr and friends */
-#undef GetClassLongPtr
-#define GetClassLongPtr GetClassLong
-#undef SetClassLongPtr
-#define SetClassLongPtr SetClassLong
-#define GCLP_HCURSOR GCL_HCURSOR
-/* GetWindowLongPtr and friends */
-#undef GetWindowLongPtr
-#define GetWindowLongPtr GetWindowLong
-#undef SetWindowLongPtr
-#define SetWindowLongPtr SetWindowLong
-#undef GWLP_USERDATA
-#define GWLP_USERDATA GWL_USERDATA
-#undef DWLP_MSGRESULT
-#define DWLP_MSGRESULT DWL_MSGRESULT
-/* Since we've clobbered the above functions, we should clobber the
- * associated type regardless of whether it's defined. */
-#undef LONG_PTR
-#define LONG_PTR LONG
-#endif
-
-#define BOXFLAGS DLGWINDOWEXTRA
-#define BOXRESULT (DLGWINDOWEXTRA + sizeof(LONG_PTR))
-#define DF_END 0x0001
-
-/*
- * Dynamically linked functions. These come in two flavours:
- *
- * - GET_WINDOWS_FUNCTION does not expose "name" to the preprocessor,
- * so will always dynamically link against exactly what is specified
- * in "name". If you're not sure, use this one.
- *
- * - GET_WINDOWS_FUNCTION_PP allows "name" to be redirected via
- * preprocessor definitions like "#define foo bar"; this is principally
- * intended for the ANSI/Unicode DoSomething/DoSomethingA/DoSomethingW.
- * If your function has an argument of type "LPTSTR" or similar, this
- * is the variant to use.
- * (However, it can't always be used, as it trips over more complicated
- * macro trickery such as the WspiapiGetAddrInfo wrapper for getaddrinfo.)
- *
- * (DECL_WINDOWS_FUNCTION works with both these variants.)
- */
-#define DECL_WINDOWS_FUNCTION(linkage, rettype, name, params) \
- typedef rettype (WINAPI *t_##name) params; \
- linkage t_##name p_##name
-#define STR1(x) #x
-#define STR(x) STR1(x)
-#define GET_WINDOWS_FUNCTION_PP(module, name) \
- (p_##name = module ? (t_##name) GetProcAddress(module, STR(name)) : NULL)
-#define GET_WINDOWS_FUNCTION(module, name) \
- (p_##name = module ? (t_##name) GetProcAddress(module, #name) : NULL)
-
-/*
- * Global variables. Most modules declare these `extern', but
- * window.c will do `#define PUTTY_DO_GLOBALS' before including this
- * module, and so will get them properly defined.
-*/
-#ifndef GLOBAL
-#ifdef PUTTY_DO_GLOBALS
-#define GLOBAL
-#else
-#define GLOBAL extern
-#endif
-#endif
-
-#ifndef DONE_TYPEDEFS
-#define DONE_TYPEDEFS
-typedef struct conf_tag Conf;
-typedef struct backend_tag Backend;
-typedef struct terminal_tag Terminal;
-#endif
-
-#define PUTTY_REG_POS "Software\\SimonTatham\\PuTTY"
-#define PUTTY_REG_PARENT "Software\\SimonTatham"
-#define PUTTY_REG_PARENT_CHILD "PuTTY"
-#define PUTTY_REG_GPARENT "Software"
-#define PUTTY_REG_GPARENT_CHILD "SimonTatham"
-
-/* Result values for the jumplist registry functions. */
-#define JUMPLISTREG_OK 0
-#define JUMPLISTREG_ERROR_INVALID_PARAMETER 1
-#define JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE 2
-#define JUMPLISTREG_ERROR_VALUEREAD_FAILURE 3
-#define JUMPLISTREG_ERROR_VALUEWRITE_FAILURE 4
-#define JUMPLISTREG_ERROR_INVALID_VALUE 5
-
-#define PUTTY_HELP_FILE "putty.hlp"
-#define PUTTY_CHM_FILE "putty.chm"
-#define PUTTY_HELP_CONTENTS "putty.cnt"
-
-#define GETTICKCOUNT GetTickCount
-#define CURSORBLINK GetCaretBlinkTime()
-#define TICKSPERSEC 1000 /* GetTickCount returns milliseconds */
-
-#define DEFAULT_CODEPAGE CP_ACP
-
-typedef HDC Context;
-
-typedef unsigned int uint32; /* int is 32-bits on Win32 and Win64. */
-#define PUTTY_UINT32_DEFINED
-
-#ifndef NO_GSSAPI
-/*
- * GSS-API stuff
- */
-#define GSS_CC CALLBACK
-/*
-typedef struct Ssh_gss_buf {
- size_t length;
- char *value;
-} Ssh_gss_buf;
-
-#define SSH_GSS_EMPTY_BUF (Ssh_gss_buf) {0,NULL}
-typedef void *Ssh_gss_name;
-*/
-#endif
-
-/*
- * Window handles for the windows that can be running during a
- * PuTTY session.
- */
-GLOBAL HWND hwnd; /* the main terminal window */
-GLOBAL HWND logbox;
-
-/*
- * The all-important instance handle.
- */
-GLOBAL HINSTANCE hinst;
-
-/*
- * Help file stuff in winhelp.c.
- */
-void init_help(void);
-void shutdown_help(void);
-int has_help(void);
-void launch_help(HWND hwnd, const char *topic);
-void quit_help(HWND hwnd);
-
-/*
- * The terminal and logging context are notionally local to the
- * Windows front end, but they must be shared between window.c and
- * windlg.c. Likewise the saved-sessions list.
- */
-GLOBAL Terminal *term;
-GLOBAL void *logctx;
-
-#define WM_NETEVENT (WM_APP + 5)
-
-/*
- * On Windows, we send MA_2CLK as the only event marking the second
- * press of a mouse button. Compare unix.h.
- */
-#define MULTICLICK_ONLY_EVENT 1
-
-/*
- * On Windows, data written to the clipboard must be NUL-terminated.
- */
-#define SELECTION_NUL_TERMINATED 1
-
-/*
- * On Windows, copying to the clipboard terminates lines with CRLF.
- */
-#define SEL_NL { 13, 10 }
-
-/*
- * sk_getxdmdata() does not exist under Windows (not that I
- * couldn't write it if I wanted to, but I haven't bothered), so
- * it's a macro which always returns NULL. With any luck this will
- * cause the compiler to notice it can optimise away the
- * implementation of XDM-AUTHORIZATION-1 in x11fwd.c :-)
- */
-#define sk_getxdmdata(socket, lenp) (NULL)
-
-/*
- * File-selector filter strings used in the config box. On Windows,
- * these strings are of exactly the type needed to go in
- * `lpstrFilter' in an OPENFILENAME structure.
- */
-#define FILTER_KEY_FILES ("PuTTY Private Key Files (*.ppk)\0*.ppk\0" \
- "All Files (*.*)\0*\0\0\0")
-#define FILTER_WAVE_FILES ("Wave Files (*.wav)\0*.WAV\0" \
- "All Files (*.*)\0*\0\0\0")
-#define FILTER_DYNLIB_FILES ("Dynamic Library Files (*.dll)\0*.dll\0" \
- "All Files (*.*)\0*\0\0\0")
-
-/*
- * On some versions of Windows, it has been known for WM_TIMER to
- * occasionally get its callback time simply wrong, and call us
- * back several minutes early. Defining these symbols enables
- * compensation code in timing.c.
- */
-#define TIMING_SYNC
-#define TIMING_SYNC_TICKCOUNT
-
-/*
- * winnet.c dynamically loads WinSock 2 or WinSock 1 depending on
- * what it can get, which means any WinSock routines used outside
- * that module must be exported from it as function pointers. So
- * here they are.
- */
-DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAAsyncSelect,
- (SOCKET, HWND, u_int, long));
-DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAEventSelect,
- (SOCKET, WSAEVENT, long));
-DECL_WINDOWS_FUNCTION(GLOBAL, int, select,
- (int, fd_set FAR *, fd_set FAR *,
- fd_set FAR *, const struct timeval FAR *));
-DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAGetLastError, (void));
-DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAEnumNetworkEvents,
- (SOCKET, WSAEVENT, LPWSANETWORKEVENTS));
-
-extern int socket_writable(SOCKET skt);
-
-extern void socket_reselect_all(void);
-
-/*
- * Exports from winctrls.c.
- */
-
-struct ctlpos {
- HWND hwnd;
- WPARAM font;
- int dlu4inpix;
- int ypos, width;
- int xoff;
- int boxystart, boxid;
- char *boxtext;
-};
-
-/*
- * Exports from winutils.c.
- */
-typedef struct filereq_tag filereq; /* cwd for file requester */
-BOOL request_file(filereq *state, OPENFILENAME *of, int preserve, int save);
-filereq *filereq_new(void);
-void filereq_free(filereq *state);
-int message_box(LPCTSTR text, LPCTSTR caption, DWORD style, DWORD helpctxid);
-char *GetDlgItemText_alloc(HWND hwnd, int id);
-void split_into_argv(char *, int *, char ***, char ***);
-
-/*
- * Private structure for prefslist state. Only in the header file
- * so that we can delegate allocation to callers.
- */
-struct prefslist {
- int listid, upbid, dnbid;
- int srcitem;
- int dummyitem;
- int dragging;
-};
-
-/*
- * This structure is passed to event handler functions as the `dlg'
- * parameter, and hence is passed back to winctrls access functions.
- */
-struct dlgparam {
- HWND hwnd; /* the hwnd of the dialog box */
- struct winctrls *controltrees[8]; /* can have several of these */
- int nctrltrees;
- char *wintitle; /* title of actual window */
- char *errtitle; /* title of error sub-messageboxes */
- void *data; /* data to pass in refresh events */
- union control *focused, *lastfocused; /* which ctrl has focus now/before */
- char shortcuts[128]; /* track which shortcuts in use */
- int coloursel_wanted; /* has an event handler asked for
- * a colour selector? */
- struct { unsigned char r, g, b, ok; } coloursel_result; /* 0-255 */
- tree234 *privdata; /* stores per-control private data */
- int ended, endresult; /* has the dialog been ended? */
- int fixed_pitch_fonts; /* are we constrained to fixed fonts? */
-};
-
-/*
- * Exports from winctrls.c.
- */
-void ctlposinit(struct ctlpos *cp, HWND hwnd,
- int leftborder, int rightborder, int topborder);
-HWND doctl(struct ctlpos *cp, RECT r,
- char *wclass, int wstyle, int exstyle, char *wtext, int wid);
-void bartitle(struct ctlpos *cp, char *name, int id);
-void beginbox(struct ctlpos *cp, char *name, int idbox);
-void endbox(struct ctlpos *cp);
-void editboxfw(struct ctlpos *cp, int password, char *text,
- int staticid, int editid);
-void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...);
-void bareradioline(struct ctlpos *cp, int nacross, ...);
-void radiobig(struct ctlpos *cp, char *text, int id, ...);
-void checkbox(struct ctlpos *cp, char *text, int id);
-void statictext(struct ctlpos *cp, char *text, int lines, int id);
-void staticbtn(struct ctlpos *cp, char *stext, int sid,
- char *btext, int bid);
-void static2btn(struct ctlpos *cp, char *stext, int sid,
- char *btext1, int bid1, char *btext2, int bid2);
-void staticedit(struct ctlpos *cp, char *stext,
- int sid, int eid, int percentedit);
-void staticddl(struct ctlpos *cp, char *stext,
- int sid, int lid, int percentlist);
-void combobox(struct ctlpos *cp, char *text, int staticid, int listid);
-void staticpassedit(struct ctlpos *cp, char *stext,
- int sid, int eid, int percentedit);
-void bigeditctrl(struct ctlpos *cp, char *stext,
- int sid, int eid, int lines);
-void ersatztab(struct ctlpos *cp, char *stext, int sid, int lid, int s2id);
-void editbutton(struct ctlpos *cp, char *stext, int sid,
- int eid, char *btext, int bid);
-void sesssaver(struct ctlpos *cp, char *text,
- int staticid, int editid, int listid, ...);
-void envsetter(struct ctlpos *cp, char *stext, int sid,
- char *e1stext, int e1sid, int e1id,
- char *e2stext, int e2sid, int e2id,
- int listid, char *b1text, int b1id, char *b2text, int b2id);
-void charclass(struct ctlpos *cp, char *stext, int sid, int listid,
- char *btext, int bid, int eid, char *s2text, int s2id);
-void colouredit(struct ctlpos *cp, char *stext, int sid, int listid,
- char *btext, int bid, ...);
-void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines,
- char *stext, int sid, int listid, int upbid, int dnbid);
-int handle_prefslist(struct prefslist *hdl,
- int *array, int maxmemb,
- int is_dlmsg, HWND hwnd,
- WPARAM wParam, LPARAM lParam);
-void progressbar(struct ctlpos *cp, int id);
-void fwdsetter(struct ctlpos *cp, int listid, char *stext, int sid,
- char *e1stext, int e1sid, int e1id,
- char *e2stext, int e2sid, int e2id,
- char *btext, int bid,
- char *r1text, int r1id, char *r2text, int r2id);
-
-void dlg_auto_set_fixed_pitch_flag(void *dlg);
-int dlg_get_fixed_pitch_flag(void *dlg);
-void dlg_set_fixed_pitch_flag(void *dlg, int flag);
-
-#define MAX_SHORTCUTS_PER_CTRL 16
-
-/*
- * This structure is what's stored for each `union control' in the
- * portable-dialog interface.
- */
-struct winctrl {
- union control *ctrl;
- /*
- * The control may have several components at the Windows
- * level, with different dialog IDs. To avoid needing N
- * separate platformsidectrl structures (which could be stored
- * separately in a tree234 so that lookup by ID worked), we
- * impose the constraint that those IDs must be in a contiguous
- * block.
- */
- int base_id;
- int num_ids;
- /*
- * Remember what keyboard shortcuts were used by this control,
- * so that when we remove it again we can take them out of the
- * list in the dlgparam.
- */
- char shortcuts[MAX_SHORTCUTS_PER_CTRL];
- /*
- * Some controls need a piece of allocated memory in which to
- * store temporary data about the control.
- */
- void *data;
-};
-/*
- * And this structure holds a set of the above, in two separate
- * tree234s so that it can find an item by `union control' or by
- * dialog ID.
- */
-struct winctrls {
- tree234 *byctrl, *byid;
-};
-struct controlset;
-struct controlbox;
-
-void winctrl_init(struct winctrls *);
-void winctrl_cleanup(struct winctrls *);
-void winctrl_add(struct winctrls *, struct winctrl *);
-void winctrl_remove(struct winctrls *, struct winctrl *);
-struct winctrl *winctrl_findbyctrl(struct winctrls *, union control *);
-struct winctrl *winctrl_findbyid(struct winctrls *, int);
-struct winctrl *winctrl_findbyindex(struct winctrls *, int);
-void winctrl_layout(struct dlgparam *dp, struct winctrls *wc,
- struct ctlpos *cp, struct controlset *s, int *id);
-int winctrl_handle_command(struct dlgparam *dp, UINT msg,
- WPARAM wParam, LPARAM lParam);
-void winctrl_rem_shortcuts(struct dlgparam *dp, struct winctrl *c);
-int winctrl_context_help(struct dlgparam *dp, HWND hwnd, int id);
-
-void dp_init(struct dlgparam *dp);
-void dp_add_tree(struct dlgparam *dp, struct winctrls *tree);
-void dp_cleanup(struct dlgparam *dp);
-
-/*
- * Exports from wincfg.c.
- */
-void win_setup_config_box(struct controlbox *b, HWND *hwndp, int has_help,
- int midsession, int protocol);
-
-/*
- * Exports from windlg.c.
- */
-void defuse_showwindow(void);
-int do_config(void);
-int do_reconfig(HWND, int);
-void showeventlog(HWND);
-void showabout(HWND);
-void force_normal(HWND hwnd);
-void modal_about_box(HWND hwnd);
-void show_help(HWND hwnd);
-
-/*
- * Exports from winmisc.c.
- */
-extern OSVERSIONINFO osVersion;
-BOOL init_winver(void);
-HMODULE load_system32_dll(const char *libname);
-
-/*
- * Exports from sizetip.c.
- */
-void UpdateSizeTip(HWND src, int cx, int cy);
-void EnableSizeTip(int bEnable);
-
-/*
- * Exports from unicode.c.
- */
-struct unicode_data;
-void init_ucs(Conf *, struct unicode_data *);
-
-/*
- * Exports from winhandl.c.
- */
-#define HANDLE_FLAG_OVERLAPPED 1
-#define HANDLE_FLAG_IGNOREEOF 2
-#define HANDLE_FLAG_UNITBUFFER 4
-struct handle;
-typedef int (*handle_inputfn_t)(struct handle *h, void *data, int len);
-typedef void (*handle_outputfn_t)(struct handle *h, int new_backlog);
-struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata,
- void *privdata, int flags);
-struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata,
- void *privdata, int flags);
-int handle_write(struct handle *h, const void *data, int len);
-void handle_write_eof(struct handle *h);
-HANDLE *handle_get_events(int *nevents);
-void handle_free(struct handle *h);
-void handle_got_event(HANDLE event);
-void handle_unthrottle(struct handle *h, int backlog);
-int handle_backlog(struct handle *h);
-void *handle_get_privdata(struct handle *h);
-
-/*
- * winpgntc.c needs to schedule callbacks for asynchronous agent
- * requests. This has to be done differently in GUI and console, so
- * there's an exported function used for the purpose.
- *
- * Also, we supply FLAG_SYNCAGENT to force agent requests to be
- * synchronous in pscp and psftp.
- */
-void agent_schedule_callback(void (*callback)(void *, void *, int),
- void *callback_ctx, void *data, int len);
-#define FLAG_SYNCAGENT 0x1000
-
-/*
- * winpgntc.c also exports these two functions which are used by the
- * server side of Pageant as well, to get the user SID for comparing
- * with clients'.
- */
-int init_advapi(void); /* initialises everything needed by get_user_sid */
-PSID get_user_sid(void);
-
-/*
- * Exports from winser.c.
- */
-extern Backend serial_backend;
-
-/*
- * Exports from winjump.c.
- */
-#define JUMPLIST_SUPPORTED /* suppress #defines in putty.h */
-void add_session_to_jumplist(const char * const sessionname);
-void remove_session_from_jumplist(const char * const sessionname);
-void clear_jumplist(void);
-
-/*
- * Extra functions in winstore.c over and above the interface in
- * storage.h.
- *
- * These functions manipulate the Registry section which mirrors the
- * current Windows 7 jump list. (Because the real jump list storage is
- * write-only, we need to keep another copy of whatever we put in it,
- * so that we can put in a slightly modified version the next time.)
- */
-
-/* Adds a saved session to the registry jump list mirror. 'item' is a
- * string naming a saved session. */
-int add_to_jumplist_registry(const char *item);
-
-/* Removes an item from the registry jump list mirror. */
-int remove_from_jumplist_registry(const char *item);
-
-/* Returns the current jump list entries from the registry. Caller
- * must free the returned pointer, which points to a contiguous
- * sequence of NUL-terminated strings in memory, terminated with an
- * empty one. */
-char *get_jumplist_registry_entries(void);
-
-#endif
+/*
+ * winstuff.h: Windows-specific inter-module stuff.
+ */
+
+#ifndef PUTTY_WINSTUFF_H
+#define PUTTY_WINSTUFF_H
+
+#ifndef AUTO_WINSOCK
+#include <winsock2.h>
+#endif
+#include <windows.h>
+#include <stdio.h> /* for FILENAME_MAX */
+
+#include "tree234.h"
+
+#include "winhelp.h"
+
+struct Filename {
+ char *path;
+};
+#define f_open(filename, mode, isprivate) ( fopen((filename)->path, (mode)) )
+
+struct FontSpec {
+ char *name;
+ int isbold;
+ int height;
+ int charset;
+};
+struct FontSpec *fontspec_new(const char *name,
+ int bold, int height, int charset);
+
+#ifndef CLEARTYPE_QUALITY
+#define CLEARTYPE_QUALITY 5
+#endif
+#define FONT_QUALITY(fq) ( \
+ (fq) == FQ_DEFAULT ? DEFAULT_QUALITY : \
+ (fq) == FQ_ANTIALIASED ? ANTIALIASED_QUALITY : \
+ (fq) == FQ_NONANTIALIASED ? NONANTIALIASED_QUALITY : \
+ CLEARTYPE_QUALITY)
+
+#define PLATFORM_IS_UTF16 /* enable UTF-16 processing when exchanging
+ * wchar_t strings with environment */
+
+/*
+ * Where we can, we use GetWindowLongPtr and friends because they're
+ * more useful on 64-bit platforms, but they're a relatively recent
+ * innovation, missing from VC++ 6 and older MinGW. Degrade nicely.
+ * (NB that on some systems, some of these things are available but
+ * not others...)
+ */
+
+#ifndef GCLP_HCURSOR
+/* GetClassLongPtr and friends */
+#undef GetClassLongPtr
+#define GetClassLongPtr GetClassLong
+#undef SetClassLongPtr
+#define SetClassLongPtr SetClassLong
+#define GCLP_HCURSOR GCL_HCURSOR
+/* GetWindowLongPtr and friends */
+#undef GetWindowLongPtr
+#define GetWindowLongPtr GetWindowLong
+#undef SetWindowLongPtr
+#define SetWindowLongPtr SetWindowLong
+#undef GWLP_USERDATA
+#define GWLP_USERDATA GWL_USERDATA
+#undef DWLP_MSGRESULT
+#define DWLP_MSGRESULT DWL_MSGRESULT
+/* Since we've clobbered the above functions, we should clobber the
+ * associated type regardless of whether it's defined. */
+#undef LONG_PTR
+#define LONG_PTR LONG
+#endif
+
+#define BOXFLAGS DLGWINDOWEXTRA
+#define BOXRESULT (DLGWINDOWEXTRA + sizeof(LONG_PTR))
+#define DF_END 0x0001
+
+#ifndef NO_SECUREZEROMEMORY
+#define PLATFORM_HAS_SMEMCLR /* inhibit cross-platform one in misc.c */
+#endif
+
+/*
+ * Dynamically linked functions. These come in two flavours:
+ *
+ * - GET_WINDOWS_FUNCTION does not expose "name" to the preprocessor,
+ * so will always dynamically link against exactly what is specified
+ * in "name". If you're not sure, use this one.
+ *
+ * - GET_WINDOWS_FUNCTION_PP allows "name" to be redirected via
+ * preprocessor definitions like "#define foo bar"; this is principally
+ * intended for the ANSI/Unicode DoSomething/DoSomethingA/DoSomethingW.
+ * If your function has an argument of type "LPTSTR" or similar, this
+ * is the variant to use.
+ * (However, it can't always be used, as it trips over more complicated
+ * macro trickery such as the WspiapiGetAddrInfo wrapper for getaddrinfo.)
+ *
+ * (DECL_WINDOWS_FUNCTION works with both these variants.)
+ */
+#define DECL_WINDOWS_FUNCTION(linkage, rettype, name, params) \
+ typedef rettype (WINAPI *t_##name) params; \
+ linkage t_##name p_##name
+#define STR1(x) #x
+#define STR(x) STR1(x)
+#define GET_WINDOWS_FUNCTION_PP(module, name) \
+ (p_##name = module ? (t_##name) GetProcAddress(module, STR(name)) : NULL)
+#define GET_WINDOWS_FUNCTION(module, name) \
+ (p_##name = module ? (t_##name) GetProcAddress(module, #name) : NULL)
+
+/*
+ * Global variables. Most modules declare these `extern', but
+ * window.c will do `#define PUTTY_DO_GLOBALS' before including this
+ * module, and so will get them properly defined.
+*/
+#ifndef GLOBAL
+#ifdef PUTTY_DO_GLOBALS
+#define GLOBAL
+#else
+#define GLOBAL extern
+#endif
+#endif
+
+#ifndef DONE_TYPEDEFS
+#define DONE_TYPEDEFS
+typedef struct conf_tag Conf;
+typedef struct backend_tag Backend;
+typedef struct terminal_tag Terminal;
+#endif
+
+#define PUTTY_REG_POS "Software\\SimonTatham\\PuTTY"
+#define PUTTY_REG_PARENT "Software\\SimonTatham"
+#define PUTTY_REG_PARENT_CHILD "PuTTY"
+#define PUTTY_REG_GPARENT "Software"
+#define PUTTY_REG_GPARENT_CHILD "SimonTatham"
+
+/* Result values for the jumplist registry functions. */
+#define JUMPLISTREG_OK 0
+#define JUMPLISTREG_ERROR_INVALID_PARAMETER 1
+#define JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE 2
+#define JUMPLISTREG_ERROR_VALUEREAD_FAILURE 3
+#define JUMPLISTREG_ERROR_VALUEWRITE_FAILURE 4
+#define JUMPLISTREG_ERROR_INVALID_VALUE 5
+
+#define PUTTY_HELP_FILE "putty.hlp"
+#define PUTTY_CHM_FILE "putty.chm"
+#define PUTTY_HELP_CONTENTS "putty.cnt"
+
+#define GETTICKCOUNT GetTickCount
+#define CURSORBLINK GetCaretBlinkTime()
+#define TICKSPERSEC 1000 /* GetTickCount returns milliseconds */
+
+#define DEFAULT_CODEPAGE CP_ACP
+#define USES_VTLINE_HACK
+
+typedef HDC Context;
+
+typedef unsigned int uint32; /* int is 32-bits on Win32 and Win64. */
+#define PUTTY_UINT32_DEFINED
+
+#ifndef NO_GSSAPI
+/*
+ * GSS-API stuff
+ */
+#define GSS_CC CALLBACK
+/*
+typedef struct Ssh_gss_buf {
+ size_t length;
+ char *value;
+} Ssh_gss_buf;
+
+#define SSH_GSS_EMPTY_BUF (Ssh_gss_buf) {0,NULL}
+typedef void *Ssh_gss_name;
+*/
+#endif
+
+/*
+ * Window handles for the windows that can be running during a
+ * PuTTY session.
+ */
+GLOBAL HWND hwnd; /* the main terminal window */
+GLOBAL HWND logbox;
+
+/*
+ * The all-important instance handle.
+ */
+GLOBAL HINSTANCE hinst;
+
+/*
+ * Help file stuff in winhelp.c.
+ */
+void init_help(void);
+void shutdown_help(void);
+int has_help(void);
+void launch_help(HWND hwnd, const char *topic);
+void quit_help(HWND hwnd);
+
+/*
+ * The terminal and logging context are notionally local to the
+ * Windows front end, but they must be shared between window.c and
+ * windlg.c. Likewise the saved-sessions list.
+ */
+GLOBAL Terminal *term;
+GLOBAL void *logctx;
+
+#define WM_NETEVENT (WM_APP + 5)
+
+/*
+ * On Windows, we send MA_2CLK as the only event marking the second
+ * press of a mouse button. Compare unix.h.
+ */
+#define MULTICLICK_ONLY_EVENT 1
+
+/*
+ * On Windows, data written to the clipboard must be NUL-terminated.
+ */
+#define SELECTION_NUL_TERMINATED 1
+
+/*
+ * On Windows, copying to the clipboard terminates lines with CRLF.
+ */
+#define SEL_NL { 13, 10 }
+
+/*
+ * sk_getxdmdata() does not exist under Windows (not that I
+ * couldn't write it if I wanted to, but I haven't bothered), so
+ * it's a macro which always returns NULL. With any luck this will
+ * cause the compiler to notice it can optimise away the
+ * implementation of XDM-AUTHORIZATION-1 in x11fwd.c :-)
+ */
+#define sk_getxdmdata(socket, lenp) (NULL)
+
+/*
+ * File-selector filter strings used in the config box. On Windows,
+ * these strings are of exactly the type needed to go in
+ * `lpstrFilter' in an OPENFILENAME structure.
+ */
+#define FILTER_KEY_FILES ("PuTTY Private Key Files (*.ppk)\0*.ppk\0" \
+ "All Files (*.*)\0*\0\0\0")
+#define FILTER_WAVE_FILES ("Wave Files (*.wav)\0*.WAV\0" \
+ "All Files (*.*)\0*\0\0\0")
+#define FILTER_DYNLIB_FILES ("Dynamic Library Files (*.dll)\0*.dll\0" \
+ "All Files (*.*)\0*\0\0\0")
+
+/*
+ * Exports from winnet.c.
+ */
+extern int select_result(WPARAM, LPARAM);
+
+/*
+ * winnet.c dynamically loads WinSock 2 or WinSock 1 depending on
+ * what it can get, which means any WinSock routines used outside
+ * that module must be exported from it as function pointers. So
+ * here they are.
+ */
+DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAAsyncSelect,
+ (SOCKET, HWND, u_int, long));
+DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAEventSelect,
+ (SOCKET, WSAEVENT, long));
+DECL_WINDOWS_FUNCTION(GLOBAL, int, select,
+ (int, fd_set FAR *, fd_set FAR *,
+ fd_set FAR *, const struct timeval FAR *));
+DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAGetLastError, (void));
+DECL_WINDOWS_FUNCTION(GLOBAL, int, WSAEnumNetworkEvents,
+ (SOCKET, WSAEVENT, LPWSANETWORKEVENTS));
+
+extern int socket_writable(SOCKET skt);
+
+extern void socket_reselect_all(void);
+
+/*
+ * Exports from winctrls.c.
+ */
+
+struct ctlpos {
+ HWND hwnd;
+ WPARAM font;
+ int dlu4inpix;
+ int ypos, width;
+ int xoff;
+ int boxystart, boxid;
+ char *boxtext;
+};
+
+/*
+ * Exports from winutils.c.
+ */
+typedef struct filereq_tag filereq; /* cwd for file requester */
+BOOL request_file(filereq *state, OPENFILENAME *of, int preserve, int save);
+filereq *filereq_new(void);
+void filereq_free(filereq *state);
+int message_box(LPCTSTR text, LPCTSTR caption, DWORD style, DWORD helpctxid);
+char *GetDlgItemText_alloc(HWND hwnd, int id);
+void split_into_argv(char *, int *, char ***, char ***);
+
+/*
+ * Private structure for prefslist state. Only in the header file
+ * so that we can delegate allocation to callers.
+ */
+struct prefslist {
+ int listid, upbid, dnbid;
+ int srcitem;
+ int dummyitem;
+ int dragging;
+};
+
+/*
+ * This structure is passed to event handler functions as the `dlg'
+ * parameter, and hence is passed back to winctrls access functions.
+ */
+struct dlgparam {
+ HWND hwnd; /* the hwnd of the dialog box */
+ struct winctrls *controltrees[8]; /* can have several of these */
+ int nctrltrees;
+ char *wintitle; /* title of actual window */
+ char *errtitle; /* title of error sub-messageboxes */
+ void *data; /* data to pass in refresh events */
+ union control *focused, *lastfocused; /* which ctrl has focus now/before */
+ char shortcuts[128]; /* track which shortcuts in use */
+ int coloursel_wanted; /* has an event handler asked for
+ * a colour selector? */
+ struct { unsigned char r, g, b, ok; } coloursel_result; /* 0-255 */
+ tree234 *privdata; /* stores per-control private data */
+ int ended, endresult; /* has the dialog been ended? */
+ int fixed_pitch_fonts; /* are we constrained to fixed fonts? */
+};
+
+/*
+ * Exports from winctrls.c.
+ */
+void ctlposinit(struct ctlpos *cp, HWND hwnd,
+ int leftborder, int rightborder, int topborder);
+HWND doctl(struct ctlpos *cp, RECT r,
+ char *wclass, int wstyle, int exstyle, char *wtext, int wid);
+void bartitle(struct ctlpos *cp, char *name, int id);
+void beginbox(struct ctlpos *cp, char *name, int idbox);
+void endbox(struct ctlpos *cp);
+void editboxfw(struct ctlpos *cp, int password, char *text,
+ int staticid, int editid);
+void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...);
+void bareradioline(struct ctlpos *cp, int nacross, ...);
+void radiobig(struct ctlpos *cp, char *text, int id, ...);
+void checkbox(struct ctlpos *cp, char *text, int id);
+void statictext(struct ctlpos *cp, char *text, int lines, int id);
+void staticbtn(struct ctlpos *cp, char *stext, int sid,
+ char *btext, int bid);
+void static2btn(struct ctlpos *cp, char *stext, int sid,
+ char *btext1, int bid1, char *btext2, int bid2);
+void staticedit(struct ctlpos *cp, char *stext,
+ int sid, int eid, int percentedit);
+void staticddl(struct ctlpos *cp, char *stext,
+ int sid, int lid, int percentlist);
+void combobox(struct ctlpos *cp, char *text, int staticid, int listid);
+void staticpassedit(struct ctlpos *cp, char *stext,
+ int sid, int eid, int percentedit);
+void bigeditctrl(struct ctlpos *cp, char *stext,
+ int sid, int eid, int lines);
+void ersatztab(struct ctlpos *cp, char *stext, int sid, int lid, int s2id);
+void editbutton(struct ctlpos *cp, char *stext, int sid,
+ int eid, char *btext, int bid);
+void sesssaver(struct ctlpos *cp, char *text,
+ int staticid, int editid, int listid, ...);
+void envsetter(struct ctlpos *cp, char *stext, int sid,
+ char *e1stext, int e1sid, int e1id,
+ char *e2stext, int e2sid, int e2id,
+ int listid, char *b1text, int b1id, char *b2text, int b2id);
+void charclass(struct ctlpos *cp, char *stext, int sid, int listid,
+ char *btext, int bid, int eid, char *s2text, int s2id);
+void colouredit(struct ctlpos *cp, char *stext, int sid, int listid,
+ char *btext, int bid, ...);
+void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines,
+ char *stext, int sid, int listid, int upbid, int dnbid);
+int handle_prefslist(struct prefslist *hdl,
+ int *array, int maxmemb,
+ int is_dlmsg, HWND hwnd,
+ WPARAM wParam, LPARAM lParam);
+void progressbar(struct ctlpos *cp, int id);
+void fwdsetter(struct ctlpos *cp, int listid, char *stext, int sid,
+ char *e1stext, int e1sid, int e1id,
+ char *e2stext, int e2sid, int e2id,
+ char *btext, int bid,
+ char *r1text, int r1id, char *r2text, int r2id);
+
+void dlg_auto_set_fixed_pitch_flag(void *dlg);
+int dlg_get_fixed_pitch_flag(void *dlg);
+void dlg_set_fixed_pitch_flag(void *dlg, int flag);
+
+#define MAX_SHORTCUTS_PER_CTRL 16
+
+/*
+ * This structure is what's stored for each `union control' in the
+ * portable-dialog interface.
+ */
+struct winctrl {
+ union control *ctrl;
+ /*
+ * The control may have several components at the Windows
+ * level, with different dialog IDs. To avoid needing N
+ * separate platformsidectrl structures (which could be stored
+ * separately in a tree234 so that lookup by ID worked), we
+ * impose the constraint that those IDs must be in a contiguous
+ * block.
+ */
+ int base_id;
+ int num_ids;
+ /*
+ * Remember what keyboard shortcuts were used by this control,
+ * so that when we remove it again we can take them out of the
+ * list in the dlgparam.
+ */
+ char shortcuts[MAX_SHORTCUTS_PER_CTRL];
+ /*
+ * Some controls need a piece of allocated memory in which to
+ * store temporary data about the control.
+ */
+ void *data;
+};
+/*
+ * And this structure holds a set of the above, in two separate
+ * tree234s so that it can find an item by `union control' or by
+ * dialog ID.
+ */
+struct winctrls {
+ tree234 *byctrl, *byid;
+};
+struct controlset;
+struct controlbox;
+
+void winctrl_init(struct winctrls *);
+void winctrl_cleanup(struct winctrls *);
+void winctrl_add(struct winctrls *, struct winctrl *);
+void winctrl_remove(struct winctrls *, struct winctrl *);
+struct winctrl *winctrl_findbyctrl(struct winctrls *, union control *);
+struct winctrl *winctrl_findbyid(struct winctrls *, int);
+struct winctrl *winctrl_findbyindex(struct winctrls *, int);
+void winctrl_layout(struct dlgparam *dp, struct winctrls *wc,
+ struct ctlpos *cp, struct controlset *s, int *id);
+int winctrl_handle_command(struct dlgparam *dp, UINT msg,
+ WPARAM wParam, LPARAM lParam);
+void winctrl_rem_shortcuts(struct dlgparam *dp, struct winctrl *c);
+int winctrl_context_help(struct dlgparam *dp, HWND hwnd, int id);
+
+void dp_init(struct dlgparam *dp);
+void dp_add_tree(struct dlgparam *dp, struct winctrls *tree);
+void dp_cleanup(struct dlgparam *dp);
+
+/*
+ * Exports from wincfg.c.
+ */
+void win_setup_config_box(struct controlbox *b, HWND *hwndp, int has_help,
+ int midsession, int protocol);
+
+/*
+ * Exports from windlg.c.
+ */
+void defuse_showwindow(void);
+int do_config(void);
+int do_reconfig(HWND, int);
+void showeventlog(HWND);
+void showabout(HWND);
+void force_normal(HWND hwnd);
+void modal_about_box(HWND hwnd);
+void show_help(HWND hwnd);
+
+/*
+ * Exports from winmisc.c.
+ */
+extern OSVERSIONINFO osVersion;
+BOOL init_winver(void);
+HMODULE load_system32_dll(const char *libname);
+const char *win_strerror(int error);
+
+/*
+ * Exports from sizetip.c.
+ */
+void UpdateSizeTip(HWND src, int cx, int cy);
+void EnableSizeTip(int bEnable);
+
+/*
+ * Exports from unicode.c.
+ */
+struct unicode_data;
+void init_ucs(Conf *, struct unicode_data *);
+
+/*
+ * Exports from winhandl.c.
+ */
+#define HANDLE_FLAG_OVERLAPPED 1
+#define HANDLE_FLAG_IGNOREEOF 2
+#define HANDLE_FLAG_UNITBUFFER 4
+struct handle;
+typedef int (*handle_inputfn_t)(struct handle *h, void *data, int len);
+typedef void (*handle_outputfn_t)(struct handle *h, int new_backlog);
+struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata,
+ void *privdata, int flags);
+struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata,
+ void *privdata, int flags);
+int handle_write(struct handle *h, const void *data, int len);
+void handle_write_eof(struct handle *h);
+HANDLE *handle_get_events(int *nevents);
+void handle_free(struct handle *h);
+void handle_got_event(HANDLE event);
+void handle_unthrottle(struct handle *h, int backlog);
+int handle_backlog(struct handle *h);
+void *handle_get_privdata(struct handle *h);
+struct handle *handle_add_foreign_event(HANDLE event,
+ void (*callback)(void *), void *ctx);
+
+/*
+ * winpgntc.c needs to schedule callbacks for asynchronous agent
+ * requests. This has to be done differently in GUI and console, so
+ * there's an exported function used for the purpose.
+ *
+ * Also, we supply FLAG_SYNCAGENT to force agent requests to be
+ * synchronous in pscp and psftp.
+ */
+void agent_schedule_callback(void (*callback)(void *, void *, int),
+ void *callback_ctx, void *data, int len);
+#define FLAG_SYNCAGENT 0x1000
+
+/*
+ * Exports from winser.c.
+ */
+extern Backend serial_backend;
+
+/*
+ * Exports from winjump.c.
+ */
+#define JUMPLIST_SUPPORTED /* suppress #defines in putty.h */
+void add_session_to_jumplist(const char * const sessionname);
+void remove_session_from_jumplist(const char * const sessionname);
+void clear_jumplist(void);
+
+/*
+ * Extra functions in winstore.c over and above the interface in
+ * storage.h.
+ *
+ * These functions manipulate the Registry section which mirrors the
+ * current Windows 7 jump list. (Because the real jump list storage is
+ * write-only, we need to keep another copy of whatever we put in it,
+ * so that we can put in a slightly modified version the next time.)
+ */
+
+/* Adds a saved session to the registry jump list mirror. 'item' is a
+ * string naming a saved session. */
+int add_to_jumplist_registry(const char *item);
+
+/* Removes an item from the registry jump list mirror. */
+int remove_from_jumplist_registry(const char *item);
+
+/* Returns the current jump list entries from the registry. Caller
+ * must free the returned pointer, which points to a contiguous
+ * sequence of NUL-terminated strings in memory, terminated with an
+ * empty one. */
+char *get_jumplist_registry_entries(void);
+
+#endif
diff --git a/tools/plink/x11fwd.c b/tools/plink/x11fwd.c
index d98908a77..7ecb4cc27 100644
--- a/tools/plink/x11fwd.c
+++ b/tools/plink/x11fwd.c
@@ -1,796 +1,1088 @@
-/*
- * Platform-independent bits of X11 forwarding.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <time.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "tree234.h"
-
-#define GET_16BIT(endian, cp) \
- (endian=='B' ? GET_16BIT_MSB_FIRST(cp) : GET_16BIT_LSB_FIRST(cp))
-
-#define PUT_16BIT(endian, cp, val) \
- (endian=='B' ? PUT_16BIT_MSB_FIRST(cp, val) : PUT_16BIT_LSB_FIRST(cp, val))
-
-const char *const x11_authnames[] = {
- "", "MIT-MAGIC-COOKIE-1", "XDM-AUTHORIZATION-1"
-};
-
-struct XDMSeen {
- unsigned int time;
- unsigned char clientid[6];
-};
-
-struct X11Private {
- const struct plug_function_table *fn;
- /* the above variable absolutely *must* be the first in this structure */
- unsigned char firstpkt[12]; /* first X data packet */
- struct X11Display *disp;
- char *auth_protocol;
- unsigned char *auth_data;
- int data_read, auth_plen, auth_psize, auth_dlen, auth_dsize;
- int verified;
- int throttled, throttle_override;
- unsigned long peer_ip;
- int peer_port;
- void *c; /* data used by ssh.c */
- Socket s;
-};
-
-static int xdmseen_cmp(void *a, void *b)
-{
- struct XDMSeen *sa = a, *sb = b;
- return sa->time > sb->time ? 1 :
- sa->time < sb->time ? -1 :
- memcmp(sa->clientid, sb->clientid, sizeof(sa->clientid));
-}
-
-/* Do-nothing "plug" implementation, used by x11_setup_display() when it
- * creates a trial connection (and then immediately closes it).
- * XXX: bit out of place here, could in principle live in a platform-
- * independent network.c or something */
-static void dummy_plug_log(Plug p, int type, SockAddr addr, int port,
- const char *error_msg, int error_code) { }
-static int dummy_plug_closing
- (Plug p, const char *error_msg, int error_code, int calling_back)
-{ return 1; }
-static int dummy_plug_receive(Plug p, int urgent, char *data, int len)
-{ return 1; }
-static void dummy_plug_sent(Plug p, int bufsize) { }
-static int dummy_plug_accepting(Plug p, OSSocket sock) { return 1; }
-static const struct plug_function_table dummy_plug = {
- dummy_plug_log, dummy_plug_closing, dummy_plug_receive,
- dummy_plug_sent, dummy_plug_accepting
-};
-
-struct X11Display *x11_setup_display(char *display, int authtype, Conf *conf)
-{
- struct X11Display *disp = snew(struct X11Display);
- char *localcopy;
- int i;
-
- if (!display || !*display) {
- localcopy = platform_get_x_display();
- if (!localcopy || !*localcopy) {
- sfree(localcopy);
- localcopy = dupstr(":0"); /* plausible default for any platform */
- }
- } else
- localcopy = dupstr(display);
-
- /*
- * Parse the display name.
- *
- * We expect this to have one of the following forms:
- *
- * - the standard X format which looks like
- * [ [ protocol '/' ] host ] ':' displaynumber [ '.' screennumber ]
- * (X11 also permits a double colon to indicate DECnet, but
- * that's not our problem, thankfully!)
- *
- * - only seen in the wild on MacOS (so far): a pathname to a
- * Unix-domain socket, which will typically and confusingly
- * end in ":0", and which I'm currently distinguishing from
- * the standard scheme by noting that it starts with '/'.
- */
- if (localcopy[0] == '/') {
- disp->unixsocketpath = localcopy;
- disp->unixdomain = TRUE;
- disp->hostname = NULL;
- disp->displaynum = -1;
- disp->screennum = 0;
- disp->addr = NULL;
- } else {
- char *colon, *dot, *slash;
- char *protocol, *hostname;
-
- colon = strrchr(localcopy, ':');
- if (!colon) {
- sfree(disp);
- sfree(localcopy);
- return NULL; /* FIXME: report a specific error? */
- }
-
- *colon++ = '\0';
- dot = strchr(colon, '.');
- if (dot)
- *dot++ = '\0';
-
- disp->displaynum = atoi(colon);
- if (dot)
- disp->screennum = atoi(dot);
- else
- disp->screennum = 0;
-
- protocol = NULL;
- hostname = localcopy;
- if (colon > localcopy) {
- slash = strchr(localcopy, '/');
- if (slash) {
- *slash++ = '\0';
- protocol = localcopy;
- hostname = slash;
- }
- }
-
- disp->hostname = *hostname ? dupstr(hostname) : NULL;
-
- if (protocol)
- disp->unixdomain = (!strcmp(protocol, "local") ||
- !strcmp(protocol, "unix"));
- else if (!*hostname || !strcmp(hostname, "unix"))
- disp->unixdomain = platform_uses_x11_unix_by_default;
- else
- disp->unixdomain = FALSE;
-
- if (!disp->hostname && !disp->unixdomain)
- disp->hostname = dupstr("localhost");
-
- disp->unixsocketpath = NULL;
- disp->addr = NULL;
-
- sfree(localcopy);
- }
-
- /*
- * Look up the display hostname, if we need to.
- */
- if (!disp->unixdomain) {
- const char *err;
-
- disp->port = 6000 + disp->displaynum;
- disp->addr = name_lookup(disp->hostname, disp->port,
- &disp->realhost, conf, ADDRTYPE_UNSPEC);
-
- if ((err = sk_addr_error(disp->addr)) != NULL) {
- sk_addr_free(disp->addr);
- sfree(disp->hostname);
- sfree(disp->unixsocketpath);
- return NULL; /* FIXME: report an error */
- }
- }
-
- /*
- * Try upgrading an IP-style localhost display to a Unix-socket
- * display (as the standard X connection libraries do).
- */
- if (!disp->unixdomain && sk_address_is_local(disp->addr)) {
- SockAddr ux = platform_get_x11_unix_address(NULL, disp->displaynum);
- const char *err = sk_addr_error(ux);
- if (!err) {
- /* Create trial connection to see if there is a useful Unix-domain
- * socket */
- const struct plug_function_table *dummy = &dummy_plug;
- Socket s = sk_new(sk_addr_dup(ux), 0, 0, 0, 0, 0, (Plug)&dummy);
- err = sk_socket_error(s);
- sk_close(s);
- }
- if (err) {
- sk_addr_free(ux);
- } else {
- sk_addr_free(disp->addr);
- disp->unixdomain = TRUE;
- disp->addr = ux;
- /* Fill in the rest in a moment */
- }
- }
-
- if (disp->unixdomain) {
- if (!disp->addr)
- disp->addr = platform_get_x11_unix_address(disp->unixsocketpath,
- disp->displaynum);
- if (disp->unixsocketpath)
- disp->realhost = dupstr(disp->unixsocketpath);
- else
- disp->realhost = dupprintf("unix:%d", disp->displaynum);
- disp->port = 0;
- }
-
- /*
- * Invent the remote authorisation details.
- */
- if (authtype == X11_MIT) {
- disp->remoteauthproto = X11_MIT;
-
- /* MIT-MAGIC-COOKIE-1. Cookie size is 128 bits (16 bytes). */
- disp->remoteauthdata = snewn(16, unsigned char);
- for (i = 0; i < 16; i++)
- disp->remoteauthdata[i] = random_byte();
- disp->remoteauthdatalen = 16;
-
- disp->xdmseen = NULL;
- } else {
- assert(authtype == X11_XDM);
- disp->remoteauthproto = X11_XDM;
-
- /* XDM-AUTHORIZATION-1. Cookie size is 16 bytes; byte 8 is zero. */
- disp->remoteauthdata = snewn(16, unsigned char);
- for (i = 0; i < 16; i++)
- disp->remoteauthdata[i] = (i == 8 ? 0 : random_byte());
- disp->remoteauthdatalen = 16;
-
- disp->xdmseen = newtree234(xdmseen_cmp);
- }
- disp->remoteauthprotoname = dupstr(x11_authnames[disp->remoteauthproto]);
- disp->remoteauthdatastring = snewn(disp->remoteauthdatalen * 2 + 1, char);
- for (i = 0; i < disp->remoteauthdatalen; i++)
- sprintf(disp->remoteauthdatastring + i*2, "%02x",
- disp->remoteauthdata[i]);
-
- /*
- * Fetch the local authorisation details.
- */
- disp->localauthproto = X11_NO_AUTH;
- disp->localauthdata = NULL;
- disp->localauthdatalen = 0;
- platform_get_x11_auth(disp, conf);
-
- return disp;
-}
-
-void x11_free_display(struct X11Display *disp)
-{
- if (disp->xdmseen != NULL) {
- struct XDMSeen *seen;
- while ((seen = delpos234(disp->xdmseen, 0)) != NULL)
- sfree(seen);
- freetree234(disp->xdmseen);
- }
- sfree(disp->hostname);
- sfree(disp->unixsocketpath);
- if (disp->localauthdata)
- memset(disp->localauthdata, 0, disp->localauthdatalen);
- sfree(disp->localauthdata);
- if (disp->remoteauthdata)
- memset(disp->remoteauthdata, 0, disp->remoteauthdatalen);
- sfree(disp->remoteauthdata);
- sfree(disp->remoteauthprotoname);
- sfree(disp->remoteauthdatastring);
- sk_addr_free(disp->addr);
- sfree(disp);
-}
-
-#define XDM_MAXSKEW 20*60 /* 20 minute clock skew should be OK */
-
-static char *x11_verify(unsigned long peer_ip, int peer_port,
- struct X11Display *disp, char *proto,
- unsigned char *data, int dlen)
-{
- if (strcmp(proto, x11_authnames[disp->remoteauthproto]) != 0)
- return "wrong authorisation protocol attempted";
- if (disp->remoteauthproto == X11_MIT) {
- if (dlen != disp->remoteauthdatalen)
- return "MIT-MAGIC-COOKIE-1 data was wrong length";
- if (memcmp(disp->remoteauthdata, data, dlen) != 0)
- return "MIT-MAGIC-COOKIE-1 data did not match";
- }
- if (disp->remoteauthproto == X11_XDM) {
- unsigned long t;
- time_t tim;
- int i;
- struct XDMSeen *seen, *ret;
-
- if (dlen != 24)
- return "XDM-AUTHORIZATION-1 data was wrong length";
- if (peer_port == -1)
- return "cannot do XDM-AUTHORIZATION-1 without remote address data";
- des_decrypt_xdmauth(disp->remoteauthdata+9, data, 24);
- if (memcmp(disp->remoteauthdata, data, 8) != 0)
- return "XDM-AUTHORIZATION-1 data failed check"; /* cookie wrong */
- if (GET_32BIT_MSB_FIRST(data+8) != peer_ip)
- return "XDM-AUTHORIZATION-1 data failed check"; /* IP wrong */
- if ((int)GET_16BIT_MSB_FIRST(data+12) != peer_port)
- return "XDM-AUTHORIZATION-1 data failed check"; /* port wrong */
- t = GET_32BIT_MSB_FIRST(data+14);
- for (i = 18; i < 24; i++)
- if (data[i] != 0) /* zero padding wrong */
- return "XDM-AUTHORIZATION-1 data failed check";
- tim = time(NULL);
- if (abs(t - tim) > XDM_MAXSKEW)
- return "XDM-AUTHORIZATION-1 time stamp was too far out";
- seen = snew(struct XDMSeen);
- seen->time = t;
- memcpy(seen->clientid, data+8, 6);
- assert(disp->xdmseen != NULL);
- ret = add234(disp->xdmseen, seen);
- if (ret != seen) {
- sfree(seen);
- return "XDM-AUTHORIZATION-1 data replayed";
- }
- /* While we're here, purge entries too old to be replayed. */
- for (;;) {
- seen = index234(disp->xdmseen, 0);
- assert(seen != NULL);
- if (t - seen->time <= XDM_MAXSKEW)
- break;
- sfree(delpos234(disp->xdmseen, 0));
- }
- }
- /* implement other protocols here if ever required */
- return NULL;
-}
-
-void x11_get_auth_from_authfile(struct X11Display *disp,
- const char *authfilename)
-{
- FILE *authfp;
- char *buf, *ptr, *str[4];
- int len[4];
- int family, protocol;
- int ideal_match = FALSE;
- char *ourhostname = get_hostname();
-
- /*
- * Normally we should look for precisely the details specified in
- * `disp'. However, there's an oddity when the display is local:
- * displays like "localhost:0" usually have their details stored
- * in a Unix-domain-socket record (even if there isn't actually a
- * real Unix-domain socket available, as with OpenSSH's proxy X11
- * server).
- *
- * This is apparently a fudge to get round the meaninglessness of
- * "localhost" in a shared-home-directory context -- xauth entries
- * for Unix-domain sockets already disambiguate this by storing
- * the *local* hostname in the conveniently-blank hostname field,
- * but IP "localhost" records couldn't do this. So, typically, an
- * IP "localhost" entry in the auth database isn't present and if
- * it were it would be ignored.
- *
- * However, we don't entirely trust that (say) Windows X servers
- * won't rely on a straight "localhost" entry, bad idea though
- * that is; so if we can't find a Unix-domain-socket entry we'll
- * fall back to an IP-based entry if we can find one.
- */
- int localhost = !disp->unixdomain && sk_address_is_local(disp->addr);
-
- authfp = fopen(authfilename, "rb");
- if (!authfp)
- return;
-
- /* Records in .Xauthority contain four strings of up to 64K each */
- buf = snewn(65537 * 4, char);
-
- while (!ideal_match) {
- int c, i, j, match = FALSE;
-
-#define GET do { c = fgetc(authfp); if (c == EOF) goto done; c = (unsigned char)c; } while (0)
- /* Expect a big-endian 2-byte number giving address family */
- GET; family = c;
- GET; family = (family << 8) | c;
- /* Then expect four strings, each composed of a big-endian 2-byte
- * length field followed by that many bytes of data */
- ptr = buf;
- for (i = 0; i < 4; i++) {
- GET; len[i] = c;
- GET; len[i] = (len[i] << 8) | c;
- str[i] = ptr;
- for (j = 0; j < len[i]; j++) {
- GET; *ptr++ = c;
- }
- *ptr++ = '\0';
- }
-#undef GET
-
- /*
- * Now we have a full X authority record in memory. See
- * whether it matches the display we're trying to
- * authenticate to.
- *
- * The details we've just read should be interpreted as
- * follows:
- *
- * - 'family' is the network address family used to
- * connect to the display. 0 means IPv4; 6 means IPv6;
- * 256 means Unix-domain sockets.
- *
- * - str[0] is the network address itself. For IPv4 and
- * IPv6, this is a string of binary data of the
- * appropriate length (respectively 4 and 16 bytes)
- * representing the address in big-endian format, e.g.
- * 7F 00 00 01 means IPv4 localhost. For Unix-domain
- * sockets, this is the host name of the machine on
- * which the Unix-domain display resides (so that an
- * .Xauthority file on a shared file system can contain
- * authority entries for Unix-domain displays on
- * several machines without them clashing).
- *
- * - str[1] is the display number. I've no idea why
- * .Xauthority stores this as a string when it has a
- * perfectly good integer format, but there we go.
- *
- * - str[2] is the authorisation method, encoded as its
- * canonical string name (i.e. "MIT-MAGIC-COOKIE-1",
- * "XDM-AUTHORIZATION-1" or something we don't
- * recognise).
- *
- * - str[3] is the actual authorisation data, stored in
- * binary form.
- */
-
- if (disp->displaynum < 0 || disp->displaynum != atoi(str[1]))
- continue; /* not the one */
-
- for (protocol = 1; protocol < lenof(x11_authnames); protocol++)
- if (!strcmp(str[2], x11_authnames[protocol]))
- break;
- if (protocol == lenof(x11_authnames))
- continue; /* don't recognise this protocol, look for another */
-
- switch (family) {
- case 0: /* IPv4 */
- if (!disp->unixdomain &&
- sk_addrtype(disp->addr) == ADDRTYPE_IPV4) {
- char buf[4];
- sk_addrcopy(disp->addr, buf);
- if (len[0] == 4 && !memcmp(str[0], buf, 4)) {
- match = TRUE;
- /* If this is a "localhost" entry, note it down
- * but carry on looking for a Unix-domain entry. */
- ideal_match = !localhost;
- }
- }
- break;
- case 6: /* IPv6 */
- if (!disp->unixdomain &&
- sk_addrtype(disp->addr) == ADDRTYPE_IPV6) {
- char buf[16];
- sk_addrcopy(disp->addr, buf);
- if (len[0] == 16 && !memcmp(str[0], buf, 16)) {
- match = TRUE;
- ideal_match = !localhost;
- }
- }
- break;
- case 256: /* Unix-domain / localhost */
- if ((disp->unixdomain || localhost)
- && ourhostname && !strcmp(ourhostname, str[0]))
- /* A matching Unix-domain socket is always the best
- * match. */
- match = ideal_match = TRUE;
- break;
- }
-
- if (match) {
- /* Current best guess -- may be overridden if !ideal_match */
- disp->localauthproto = protocol;
- sfree(disp->localauthdata); /* free previous guess, if any */
- disp->localauthdata = snewn(len[3], unsigned char);
- memcpy(disp->localauthdata, str[3], len[3]);
- disp->localauthdatalen = len[3];
- }
- }
-
- done:
- fclose(authfp);
- memset(buf, 0, 65537 * 4);
- sfree(buf);
- sfree(ourhostname);
-}
-
-static void x11_log(Plug p, int type, SockAddr addr, int port,
- const char *error_msg, int error_code)
-{
- /* We have no interface to the logging module here, so we drop these. */
-}
-
-static int x11_closing(Plug plug, const char *error_msg, int error_code,
- int calling_back)
-{
- struct X11Private *pr = (struct X11Private *) plug;
-
- /*
- * We have no way to communicate down the forwarded connection,
- * so if an error occurred on the socket, we just ignore it
- * and treat it like a proper close.
- *
- * FIXME: except we could initiate a full close here instead of
- * just an outgoing EOF? ssh.c currently has no API for that, but
- * it could.
- */
- sshfwd_write_eof(pr->c);
- return 1;
-}
-
-static int x11_receive(Plug plug, int urgent, char *data, int len)
-{
- struct X11Private *pr = (struct X11Private *) plug;
-
- if (sshfwd_write(pr->c, data, len) > 0) {
- pr->throttled = 1;
- sk_set_frozen(pr->s, 1);
- }
-
- return 1;
-}
-
-static void x11_sent(Plug plug, int bufsize)
-{
- struct X11Private *pr = (struct X11Private *) plug;
-
- sshfwd_unthrottle(pr->c, bufsize);
-}
-
-/*
- * When setting up X forwarding, we should send the screen number
- * from the specified local display. This function extracts it from
- * the display string.
- */
-int x11_get_screen_number(char *display)
-{
- int n;
-
- n = strcspn(display, ":");
- if (!display[n])
- return 0;
- n = strcspn(display, ".");
- if (!display[n])
- return 0;
- return atoi(display + n + 1);
-}
-
-/*
- * Called to set up the raw connection.
- *
- * Returns an error message, or NULL on success.
- * also, fills the SocketsStructure
- */
-extern const char *x11_init(Socket *s, struct X11Display *disp, void *c,
- const char *peeraddr, int peerport, Conf *conf)
-{
- static const struct plug_function_table fn_table = {
- x11_log,
- x11_closing,
- x11_receive,
- x11_sent,
- NULL
- };
-
- const char *err;
- struct X11Private *pr;
-
- /*
- * Open socket.
- */
- pr = snew(struct X11Private);
- pr->fn = &fn_table;
- pr->auth_protocol = NULL;
- pr->disp = disp;
- pr->verified = 0;
- pr->data_read = 0;
- pr->throttled = pr->throttle_override = 0;
- pr->c = c;
-
- pr->s = *s = new_connection(sk_addr_dup(disp->addr),
- disp->realhost, disp->port,
- 0, 1, 0, 0, (Plug) pr, conf);
- if ((err = sk_socket_error(*s)) != NULL) {
- sfree(pr);
- return err;
- }
-
- /*
- * See if we can make sense of the peer address we were given.
- */
- {
- int i[4];
- if (peeraddr &&
- 4 == sscanf(peeraddr, "%d.%d.%d.%d", i+0, i+1, i+2, i+3)) {
- pr->peer_ip = (i[0] << 24) | (i[1] << 16) | (i[2] << 8) | i[3];
- pr->peer_port = peerport;
- } else {
- pr->peer_ip = 0;
- pr->peer_port = -1;
- }
- }
-
- sk_set_private_ptr(*s, pr);
- return NULL;
-}
-
-void x11_close(Socket s)
-{
- struct X11Private *pr;
- if (!s)
- return;
- pr = (struct X11Private *) sk_get_private_ptr(s);
- if (pr->auth_protocol) {
- sfree(pr->auth_protocol);
- sfree(pr->auth_data);
- }
-
- sfree(pr);
-
- sk_close(s);
-}
-
-void x11_unthrottle(Socket s)
-{
- struct X11Private *pr;
- if (!s)
- return;
- pr = (struct X11Private *) sk_get_private_ptr(s);
-
- pr->throttled = 0;
- sk_set_frozen(s, pr->throttled || pr->throttle_override);
-}
-
-void x11_override_throttle(Socket s, int enable)
-{
- struct X11Private *pr;
- if (!s)
- return;
- pr = (struct X11Private *) sk_get_private_ptr(s);
-
- pr->throttle_override = enable;
- sk_set_frozen(s, pr->throttled || pr->throttle_override);
-}
-
-/*
- * Called to send data down the raw connection.
- */
-int x11_send(Socket s, char *data, int len)
-{
- struct X11Private *pr;
- if (!s)
- return 0;
- pr = (struct X11Private *) sk_get_private_ptr(s);
-
- /*
- * Read the first packet.
- */
- while (len > 0 && pr->data_read < 12)
- pr->firstpkt[pr->data_read++] = (unsigned char) (len--, *data++);
- if (pr->data_read < 12)
- return 0;
-
- /*
- * If we have not allocated the auth_protocol and auth_data
- * strings, do so now.
- */
- if (!pr->auth_protocol) {
- pr->auth_plen = GET_16BIT(pr->firstpkt[0], pr->firstpkt + 6);
- pr->auth_dlen = GET_16BIT(pr->firstpkt[0], pr->firstpkt + 8);
- pr->auth_psize = (pr->auth_plen + 3) & ~3;
- pr->auth_dsize = (pr->auth_dlen + 3) & ~3;
- /* Leave room for a terminating zero, to make our lives easier. */
- pr->auth_protocol = snewn(pr->auth_psize + 1, char);
- pr->auth_data = snewn(pr->auth_dsize, unsigned char);
- }
-
- /*
- * Read the auth_protocol and auth_data strings.
- */
- while (len > 0 && pr->data_read < 12 + pr->auth_psize)
- pr->auth_protocol[pr->data_read++ - 12] = (len--, *data++);
- while (len > 0 && pr->data_read < 12 + pr->auth_psize + pr->auth_dsize)
- pr->auth_data[pr->data_read++ - 12 -
- pr->auth_psize] = (unsigned char) (len--, *data++);
- if (pr->data_read < 12 + pr->auth_psize + pr->auth_dsize)
- return 0;
-
- /*
- * If we haven't verified the authorisation, do so now.
- */
- if (!pr->verified) {
- char *err;
-
- pr->auth_protocol[pr->auth_plen] = '\0'; /* ASCIZ */
- err = x11_verify(pr->peer_ip, pr->peer_port,
- pr->disp, pr->auth_protocol,
- pr->auth_data, pr->auth_dlen);
-
- /*
- * If authorisation failed, construct and send an error
- * packet, then terminate the connection.
- */
- if (err) {
- char *message;
- int msglen, msgsize;
- unsigned char *reply;
-
- message = dupprintf("%s X11 proxy: %s", appname, err);
- msglen = strlen(message);
- reply = snewn(8 + msglen+1 + 4, unsigned char); /* include zero */
- msgsize = (msglen + 3) & ~3;
- reply[0] = 0; /* failure */
- reply[1] = msglen; /* length of reason string */
- memcpy(reply + 2, pr->firstpkt + 2, 4); /* major/minor proto vsn */
- PUT_16BIT(pr->firstpkt[0], reply + 6, msgsize >> 2);/* data len */
- memset(reply + 8, 0, msgsize);
- memcpy(reply + 8, message, msglen);
- sshfwd_write(pr->c, (char *)reply, 8 + msgsize);
- sshfwd_write_eof(pr->c);
- sfree(reply);
- sfree(message);
- return 0;
- }
-
- /*
- * Now we know we're going to accept the connection. Strip
- * the fake auth data, and optionally put real auth data in
- * instead.
- */
- {
- char realauthdata[64];
- int realauthlen = 0;
- int authstrlen = strlen(x11_authnames[pr->disp->localauthproto]);
- int buflen = 0; /* initialise to placate optimiser */
- static const char zeroes[4] = { 0,0,0,0 };
- void *buf;
-
- if (pr->disp->localauthproto == X11_MIT) {
- assert(pr->disp->localauthdatalen <= lenof(realauthdata));
- realauthlen = pr->disp->localauthdatalen;
- memcpy(realauthdata, pr->disp->localauthdata, realauthlen);
- } else if (pr->disp->localauthproto == X11_XDM &&
- pr->disp->localauthdatalen == 16 &&
- ((buf = sk_getxdmdata(s, &buflen))!=0)) {
- time_t t;
- realauthlen = (buflen+12+7) & ~7;
- assert(realauthlen <= lenof(realauthdata));
- memset(realauthdata, 0, realauthlen);
- memcpy(realauthdata, pr->disp->localauthdata, 8);
- memcpy(realauthdata+8, buf, buflen);
- t = time(NULL);
- PUT_32BIT_MSB_FIRST(realauthdata+8+buflen, t);
- des_encrypt_xdmauth(pr->disp->localauthdata+9,
- (unsigned char *)realauthdata,
- realauthlen);
- sfree(buf);
- }
- /* implement other auth methods here if required */
-
- PUT_16BIT(pr->firstpkt[0], pr->firstpkt + 6, authstrlen);
- PUT_16BIT(pr->firstpkt[0], pr->firstpkt + 8, realauthlen);
-
- sk_write(s, (char *)pr->firstpkt, 12);
-
- if (authstrlen) {
- sk_write(s, x11_authnames[pr->disp->localauthproto],
- authstrlen);
- sk_write(s, zeroes, 3 & (-authstrlen));
- }
- if (realauthlen) {
- sk_write(s, realauthdata, realauthlen);
- sk_write(s, zeroes, 3 & (-realauthlen));
- }
- }
- pr->verified = 1;
- }
-
- /*
- * After initialisation, just copy data simply.
- */
-
- return sk_write(s, data, len);
-}
-
-void x11_send_eof(Socket s)
-{
- sk_write_eof(s);
-}
+/*
+ * Platform-independent bits of X11 forwarding.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <time.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "tree234.h"
+
+#define GET_16BIT(endian, cp) \
+ (endian=='B' ? GET_16BIT_MSB_FIRST(cp) : GET_16BIT_LSB_FIRST(cp))
+
+#define PUT_16BIT(endian, cp, val) \
+ (endian=='B' ? PUT_16BIT_MSB_FIRST(cp, val) : PUT_16BIT_LSB_FIRST(cp, val))
+
+const char *const x11_authnames[] = {
+ "", "MIT-MAGIC-COOKIE-1", "XDM-AUTHORIZATION-1"
+};
+
+struct XDMSeen {
+ unsigned int time;
+ unsigned char clientid[6];
+};
+
+struct X11Connection {
+ const struct plug_function_table *fn;
+ /* the above variable absolutely *must* be the first in this structure */
+ unsigned char firstpkt[12]; /* first X data packet */
+ tree234 *authtree;
+ struct X11Display *disp;
+ char *auth_protocol;
+ unsigned char *auth_data;
+ int data_read, auth_plen, auth_psize, auth_dlen, auth_dsize;
+ int verified;
+ int throttled, throttle_override;
+ int no_data_sent_to_x_client;
+ char *peer_addr;
+ int peer_port;
+ struct ssh_channel *c; /* channel structure held by ssh.c */
+ Socket s;
+};
+
+static int xdmseen_cmp(void *a, void *b)
+{
+ struct XDMSeen *sa = a, *sb = b;
+ return sa->time > sb->time ? 1 :
+ sa->time < sb->time ? -1 :
+ memcmp(sa->clientid, sb->clientid, sizeof(sa->clientid));
+}
+
+/* Do-nothing "plug" implementation, used by x11_setup_display() when it
+ * creates a trial connection (and then immediately closes it).
+ * XXX: bit out of place here, could in principle live in a platform-
+ * independent network.c or something */
+static void dummy_plug_log(Plug p, int type, SockAddr addr, int port,
+ const char *error_msg, int error_code) { }
+static int dummy_plug_closing
+ (Plug p, const char *error_msg, int error_code, int calling_back)
+{ return 1; }
+static int dummy_plug_receive(Plug p, int urgent, char *data, int len)
+{ return 1; }
+static void dummy_plug_sent(Plug p, int bufsize) { }
+static int dummy_plug_accepting(Plug p, accept_fn_t constructor, accept_ctx_t ctx) { return 1; }
+static const struct plug_function_table dummy_plug = {
+ dummy_plug_log, dummy_plug_closing, dummy_plug_receive,
+ dummy_plug_sent, dummy_plug_accepting
+};
+
+struct X11FakeAuth *x11_invent_fake_auth(tree234 *authtree, int authtype)
+{
+ struct X11FakeAuth *auth = snew(struct X11FakeAuth);
+ int i;
+
+ /*
+ * This function has the job of inventing a set of X11 fake auth
+ * data, and adding it to 'authtree'. We must preserve the
+ * property that for any given actual authorisation attempt, _at
+ * most one_ thing in the tree can possibly match it.
+ *
+ * For MIT-MAGIC-COOKIE-1, that's not too difficult: the match
+ * criterion is simply that the entire cookie is correct, so we
+ * just have to make sure we don't make up two cookies the same.
+ * (Vanishingly unlikely, but we check anyway to be sure, and go
+ * round again inventing a new cookie if add234 tells us the one
+ * we thought of is already in use.)
+ *
+ * For XDM-AUTHORIZATION-1, it's a little more fiddly. The setup
+ * with XA1 is that half the cookie is used as a DES key with
+ * which to CBC-encrypt an assortment of stuff. Happily, the stuff
+ * encrypted _begins_ with the other half of the cookie, and the
+ * IV is always zero, which means that any valid XA1 authorisation
+ * attempt for a given cookie must begin with the same cipher
+ * block, consisting of the DES ECB encryption of the first half
+ * of the cookie using the second half as a key. So we compute
+ * that cipher block here and now, and use it as the sorting key
+ * for distinguishing XA1 entries in the tree.
+ */
+
+ if (authtype == X11_MIT) {
+ auth->proto = X11_MIT;
+
+ /* MIT-MAGIC-COOKIE-1. Cookie size is 128 bits (16 bytes). */
+ auth->datalen = 16;
+ auth->data = snewn(auth->datalen, unsigned char);
+ auth->xa1_firstblock = NULL;
+
+ while (1) {
+ for (i = 0; i < auth->datalen; i++)
+ auth->data[i] = random_byte();
+ if (add234(authtree, auth) == auth)
+ break;
+ }
+
+ auth->xdmseen = NULL;
+ } else {
+ assert(authtype == X11_XDM);
+ auth->proto = X11_XDM;
+
+ /* XDM-AUTHORIZATION-1. Cookie size is 16 bytes; byte 8 is zero. */
+ auth->datalen = 16;
+ auth->data = snewn(auth->datalen, unsigned char);
+ auth->xa1_firstblock = snewn(8, unsigned char);
+ memset(auth->xa1_firstblock, 0, 8);
+
+ while (1) {
+ for (i = 0; i < auth->datalen; i++)
+ auth->data[i] = (i == 8 ? 0 : random_byte());
+ memcpy(auth->xa1_firstblock, auth->data, 8);
+ des_encrypt_xdmauth(auth->data + 9, auth->xa1_firstblock, 8);
+ if (add234(authtree, auth) == auth)
+ break;
+ }
+
+ auth->xdmseen = newtree234(xdmseen_cmp);
+ }
+ auth->protoname = dupstr(x11_authnames[auth->proto]);
+ auth->datastring = snewn(auth->datalen * 2 + 1, char);
+ for (i = 0; i < auth->datalen; i++)
+ sprintf(auth->datastring + i*2, "%02x",
+ auth->data[i]);
+
+ auth->disp = NULL;
+ auth->share_cs = auth->share_chan = NULL;
+
+ return auth;
+}
+
+void x11_free_fake_auth(struct X11FakeAuth *auth)
+{
+ if (auth->data)
+ smemclr(auth->data, auth->datalen);
+ sfree(auth->data);
+ sfree(auth->protoname);
+ sfree(auth->datastring);
+ sfree(auth->xa1_firstblock);
+ if (auth->xdmseen != NULL) {
+ struct XDMSeen *seen;
+ while ((seen = delpos234(auth->xdmseen, 0)) != NULL)
+ sfree(seen);
+ freetree234(auth->xdmseen);
+ }
+ sfree(auth);
+}
+
+int x11_authcmp(void *av, void *bv)
+{
+ struct X11FakeAuth *a = (struct X11FakeAuth *)av;
+ struct X11FakeAuth *b = (struct X11FakeAuth *)bv;
+
+ if (a->proto < b->proto)
+ return -1;
+ else if (a->proto > b->proto)
+ return +1;
+
+ if (a->proto == X11_MIT) {
+ if (a->datalen < b->datalen)
+ return -1;
+ else if (a->datalen > b->datalen)
+ return +1;
+
+ return memcmp(a->data, b->data, a->datalen);
+ } else {
+ assert(a->proto == X11_XDM);
+
+ return memcmp(a->xa1_firstblock, b->xa1_firstblock, 8);
+ }
+}
+
+struct X11Display *x11_setup_display(char *display, Conf *conf)
+{
+ struct X11Display *disp = snew(struct X11Display);
+ char *localcopy;
+
+ if (!display || !*display) {
+ localcopy = platform_get_x_display();
+ if (!localcopy || !*localcopy) {
+ sfree(localcopy);
+ localcopy = dupstr(":0"); /* plausible default for any platform */
+ }
+ } else
+ localcopy = dupstr(display);
+
+ /*
+ * Parse the display name.
+ *
+ * We expect this to have one of the following forms:
+ *
+ * - the standard X format which looks like
+ * [ [ protocol '/' ] host ] ':' displaynumber [ '.' screennumber ]
+ * (X11 also permits a double colon to indicate DECnet, but
+ * that's not our problem, thankfully!)
+ *
+ * - only seen in the wild on MacOS (so far): a pathname to a
+ * Unix-domain socket, which will typically and confusingly
+ * end in ":0", and which I'm currently distinguishing from
+ * the standard scheme by noting that it starts with '/'.
+ */
+ if (localcopy[0] == '/') {
+ disp->unixsocketpath = localcopy;
+ disp->unixdomain = TRUE;
+ disp->hostname = NULL;
+ disp->displaynum = -1;
+ disp->screennum = 0;
+ disp->addr = NULL;
+ } else {
+ char *colon, *dot, *slash;
+ char *protocol, *hostname;
+
+ colon = host_strrchr(localcopy, ':');
+ if (!colon) {
+ sfree(disp);
+ sfree(localcopy);
+ return NULL; /* FIXME: report a specific error? */
+ }
+
+ *colon++ = '\0';
+ dot = strchr(colon, '.');
+ if (dot)
+ *dot++ = '\0';
+
+ disp->displaynum = atoi(colon);
+ if (dot)
+ disp->screennum = atoi(dot);
+ else
+ disp->screennum = 0;
+
+ protocol = NULL;
+ hostname = localcopy;
+ if (colon > localcopy) {
+ slash = strchr(localcopy, '/');
+ if (slash) {
+ *slash++ = '\0';
+ protocol = localcopy;
+ hostname = slash;
+ }
+ }
+
+ disp->hostname = *hostname ? dupstr(hostname) : NULL;
+
+ if (protocol)
+ disp->unixdomain = (!strcmp(protocol, "local") ||
+ !strcmp(protocol, "unix"));
+ else if (!*hostname || !strcmp(hostname, "unix"))
+ disp->unixdomain = platform_uses_x11_unix_by_default;
+ else
+ disp->unixdomain = FALSE;
+
+ if (!disp->hostname && !disp->unixdomain)
+ disp->hostname = dupstr("localhost");
+
+ disp->unixsocketpath = NULL;
+ disp->addr = NULL;
+
+ sfree(localcopy);
+ }
+
+ /*
+ * Look up the display hostname, if we need to.
+ */
+ if (!disp->unixdomain) {
+ const char *err;
+
+ disp->port = 6000 + disp->displaynum;
+ disp->addr = name_lookup(disp->hostname, disp->port,
+ &disp->realhost, conf, ADDRTYPE_UNSPEC);
+
+ if ((err = sk_addr_error(disp->addr)) != NULL) {
+ sk_addr_free(disp->addr);
+ sfree(disp->hostname);
+ sfree(disp->unixsocketpath);
+ sfree(disp);
+ return NULL; /* FIXME: report an error */
+ }
+ }
+
+ /*
+ * Try upgrading an IP-style localhost display to a Unix-socket
+ * display (as the standard X connection libraries do).
+ */
+ if (!disp->unixdomain && sk_address_is_local(disp->addr)) {
+ SockAddr ux = platform_get_x11_unix_address(NULL, disp->displaynum);
+ const char *err = sk_addr_error(ux);
+ if (!err) {
+ /* Create trial connection to see if there is a useful Unix-domain
+ * socket */
+ const struct plug_function_table *dummy = &dummy_plug;
+ Socket s = sk_new(sk_addr_dup(ux), 0, 0, 0, 0, 0, (Plug)&dummy);
+ err = sk_socket_error(s);
+ sk_close(s);
+ }
+ if (err) {
+ sk_addr_free(ux);
+ } else {
+ sk_addr_free(disp->addr);
+ disp->unixdomain = TRUE;
+ disp->addr = ux;
+ /* Fill in the rest in a moment */
+ }
+ }
+
+ if (disp->unixdomain) {
+ if (!disp->addr)
+ disp->addr = platform_get_x11_unix_address(disp->unixsocketpath,
+ disp->displaynum);
+ if (disp->unixsocketpath)
+ disp->realhost = dupstr(disp->unixsocketpath);
+ else
+ disp->realhost = dupprintf("unix:%d", disp->displaynum);
+ disp->port = 0;
+ }
+
+ /*
+ * Fetch the local authorisation details.
+ */
+ disp->localauthproto = X11_NO_AUTH;
+ disp->localauthdata = NULL;
+ disp->localauthdatalen = 0;
+ platform_get_x11_auth(disp, conf);
+
+ return disp;
+}
+
+void x11_free_display(struct X11Display *disp)
+{
+ sfree(disp->hostname);
+ sfree(disp->unixsocketpath);
+ if (disp->localauthdata)
+ smemclr(disp->localauthdata, disp->localauthdatalen);
+ sfree(disp->localauthdata);
+ sk_addr_free(disp->addr);
+ sfree(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)
+{
+ struct X11FakeAuth match_dummy; /* for passing to find234 */
+ struct X11FakeAuth *auth;
+
+ /*
+ * First, do a lookup in our tree to find the only authorisation
+ * record that _might_ match.
+ */
+ if (!strcmp(proto, x11_authnames[X11_MIT])) {
+ /*
+ * Just look up the whole cookie that was presented to us,
+ * which x11_authcmp will compare against the cookies we
+ * currently believe in.
+ */
+ match_dummy.proto = X11_MIT;
+ match_dummy.datalen = dlen;
+ match_dummy.data = data;
+ } else if (!strcmp(proto, x11_authnames[X11_XDM])) {
+ /*
+ * Look up the first cipher block, against the stored first
+ * cipher blocks for the XDM-AUTHORIZATION-1 cookies we
+ * currently know. (See comment in x11_invent_fake_auth.)
+ */
+ match_dummy.proto = X11_XDM;
+ match_dummy.xa1_firstblock = data;
+ } else {
+ return "Unsupported authorisation protocol";
+ }
+
+ if ((auth = find234(authtree, &match_dummy, 0)) == NULL)
+ return "Authorisation not recognised";
+
+ /*
+ * If we're using MIT-MAGIC-COOKIE-1, that was all we needed. If
+ * we're doing XDM-AUTHORIZATION-1, though, we have to check the
+ * rest of the auth data.
+ */
+ if (auth->proto == X11_XDM) {
+ unsigned long t;
+ time_t tim;
+ int i;
+ struct XDMSeen *seen, *ret;
+
+ if (dlen != 24)
+ return "XDM-AUTHORIZATION-1 data was wrong length";
+ if (peer_port == -1)
+ return "cannot do XDM-AUTHORIZATION-1 without remote address data";
+ des_decrypt_xdmauth(auth->data+9, data, 24);
+ if (memcmp(auth->data, data, 8) != 0)
+ return "XDM-AUTHORIZATION-1 data failed check"; /* cookie wrong */
+ if (GET_32BIT_MSB_FIRST(data+8) != peer_ip)
+ return "XDM-AUTHORIZATION-1 data failed check"; /* IP wrong */
+ if ((int)GET_16BIT_MSB_FIRST(data+12) != peer_port)
+ return "XDM-AUTHORIZATION-1 data failed check"; /* port wrong */
+ t = GET_32BIT_MSB_FIRST(data+14);
+ for (i = 18; i < 24; i++)
+ if (data[i] != 0) /* zero padding wrong */
+ return "XDM-AUTHORIZATION-1 data failed check";
+ tim = time(NULL);
+ if (abs(t - tim) > XDM_MAXSKEW)
+ return "XDM-AUTHORIZATION-1 time stamp was too far out";
+ seen = snew(struct XDMSeen);
+ seen->time = t;
+ memcpy(seen->clientid, data+8, 6);
+ assert(auth->xdmseen != NULL);
+ ret = add234(auth->xdmseen, seen);
+ if (ret != seen) {
+ sfree(seen);
+ return "XDM-AUTHORIZATION-1 data replayed";
+ }
+ /* While we're here, purge entries too old to be replayed. */
+ for (;;) {
+ seen = index234(auth->xdmseen, 0);
+ assert(seen != NULL);
+ if (t - seen->time <= XDM_MAXSKEW)
+ break;
+ sfree(delpos234(auth->xdmseen, 0));
+ }
+ }
+ /* implement other protocols here if ever required */
+
+ *auth_ret = auth;
+ return NULL;
+}
+
+void x11_get_auth_from_authfile(struct X11Display *disp,
+ const char *authfilename)
+{
+ FILE *authfp;
+ char *buf, *ptr, *str[4];
+ int len[4];
+ int family, protocol;
+ int ideal_match = FALSE;
+ char *ourhostname;
+
+ /*
+ * Normally we should look for precisely the details specified in
+ * `disp'. However, there's an oddity when the display is local:
+ * displays like "localhost:0" usually have their details stored
+ * in a Unix-domain-socket record (even if there isn't actually a
+ * real Unix-domain socket available, as with OpenSSH's proxy X11
+ * server).
+ *
+ * This is apparently a fudge to get round the meaninglessness of
+ * "localhost" in a shared-home-directory context -- xauth entries
+ * for Unix-domain sockets already disambiguate this by storing
+ * the *local* hostname in the conveniently-blank hostname field,
+ * but IP "localhost" records couldn't do this. So, typically, an
+ * IP "localhost" entry in the auth database isn't present and if
+ * it were it would be ignored.
+ *
+ * However, we don't entirely trust that (say) Windows X servers
+ * won't rely on a straight "localhost" entry, bad idea though
+ * that is; so if we can't find a Unix-domain-socket entry we'll
+ * fall back to an IP-based entry if we can find one.
+ */
+ int localhost = !disp->unixdomain && sk_address_is_local(disp->addr);
+
+ authfp = fopen(authfilename, "rb");
+ if (!authfp)
+ return;
+
+ ourhostname = get_hostname();
+
+ /* Records in .Xauthority contain four strings of up to 64K each */
+ buf = snewn(65537 * 4, char);
+
+ while (!ideal_match) {
+ int c, i, j, match = FALSE;
+
+#define GET do { c = fgetc(authfp); if (c == EOF) goto done; c = (unsigned char)c; } while (0)
+ /* Expect a big-endian 2-byte number giving address family */
+ GET; family = c;
+ GET; family = (family << 8) | c;
+ /* Then expect four strings, each composed of a big-endian 2-byte
+ * length field followed by that many bytes of data */
+ ptr = buf;
+ for (i = 0; i < 4; i++) {
+ GET; len[i] = c;
+ GET; len[i] = (len[i] << 8) | c;
+ str[i] = ptr;
+ for (j = 0; j < len[i]; j++) {
+ GET; *ptr++ = c;
+ }
+ *ptr++ = '\0';
+ }
+#undef GET
+
+ /*
+ * Now we have a full X authority record in memory. See
+ * whether it matches the display we're trying to
+ * authenticate to.
+ *
+ * The details we've just read should be interpreted as
+ * follows:
+ *
+ * - 'family' is the network address family used to
+ * connect to the display. 0 means IPv4; 6 means IPv6;
+ * 256 means Unix-domain sockets.
+ *
+ * - str[0] is the network address itself. For IPv4 and
+ * IPv6, this is a string of binary data of the
+ * appropriate length (respectively 4 and 16 bytes)
+ * representing the address in big-endian format, e.g.
+ * 7F 00 00 01 means IPv4 localhost. For Unix-domain
+ * sockets, this is the host name of the machine on
+ * which the Unix-domain display resides (so that an
+ * .Xauthority file on a shared file system can contain
+ * authority entries for Unix-domain displays on
+ * several machines without them clashing).
+ *
+ * - str[1] is the display number. I've no idea why
+ * .Xauthority stores this as a string when it has a
+ * perfectly good integer format, but there we go.
+ *
+ * - str[2] is the authorisation method, encoded as its
+ * canonical string name (i.e. "MIT-MAGIC-COOKIE-1",
+ * "XDM-AUTHORIZATION-1" or something we don't
+ * recognise).
+ *
+ * - str[3] is the actual authorisation data, stored in
+ * binary form.
+ */
+
+ if (disp->displaynum < 0 || disp->displaynum != atoi(str[1]))
+ continue; /* not the one */
+
+ for (protocol = 1; protocol < lenof(x11_authnames); protocol++)
+ if (!strcmp(str[2], x11_authnames[protocol]))
+ break;
+ if (protocol == lenof(x11_authnames))
+ continue; /* don't recognise this protocol, look for another */
+
+ switch (family) {
+ case 0: /* IPv4 */
+ if (!disp->unixdomain &&
+ sk_addrtype(disp->addr) == ADDRTYPE_IPV4) {
+ char buf[4];
+ sk_addrcopy(disp->addr, buf);
+ if (len[0] == 4 && !memcmp(str[0], buf, 4)) {
+ match = TRUE;
+ /* If this is a "localhost" entry, note it down
+ * but carry on looking for a Unix-domain entry. */
+ ideal_match = !localhost;
+ }
+ }
+ break;
+ case 6: /* IPv6 */
+ if (!disp->unixdomain &&
+ sk_addrtype(disp->addr) == ADDRTYPE_IPV6) {
+ char buf[16];
+ sk_addrcopy(disp->addr, buf);
+ if (len[0] == 16 && !memcmp(str[0], buf, 16)) {
+ match = TRUE;
+ ideal_match = !localhost;
+ }
+ }
+ break;
+ case 256: /* Unix-domain / localhost */
+ if ((disp->unixdomain || localhost)
+ && ourhostname && !strcmp(ourhostname, str[0]))
+ /* A matching Unix-domain socket is always the best
+ * match. */
+ match = ideal_match = TRUE;
+ break;
+ }
+
+ if (match) {
+ /* Current best guess -- may be overridden if !ideal_match */
+ disp->localauthproto = protocol;
+ sfree(disp->localauthdata); /* free previous guess, if any */
+ disp->localauthdata = snewn(len[3], unsigned char);
+ memcpy(disp->localauthdata, str[3], len[3]);
+ disp->localauthdatalen = len[3];
+ }
+ }
+
+ done:
+ fclose(authfp);
+ smemclr(buf, 65537 * 4);
+ sfree(buf);
+ sfree(ourhostname);
+}
+
+static void x11_log(Plug p, int type, SockAddr addr, int port,
+ const char *error_msg, int error_code)
+{
+ /* We have no interface to the logging module here, so we drop these. */
+}
+
+static void x11_send_init_error(struct X11Connection *conn,
+ const char *err_message);
+
+static int x11_closing(Plug plug, const char *error_msg, int error_code,
+ int calling_back)
+{
+ struct X11Connection *xconn = (struct X11Connection *) plug;
+
+ if (error_msg) {
+ /*
+ * Socket error. If we're still at the connection setup stage,
+ * construct an X11 error packet passing on the problem.
+ */
+ if (xconn->no_data_sent_to_x_client) {
+ char *err_message = dupprintf("unable to connect to forwarded "
+ "X server: %s", error_msg);
+ x11_send_init_error(xconn, err_message);
+ sfree(err_message);
+ }
+
+ /*
+ * Whether we did that or not, now we slam the connection
+ * shut.
+ */
+ sshfwd_unclean_close(xconn->c, error_msg);
+ } else {
+ /*
+ * Ordinary EOF received on socket. Send an EOF on the SSH
+ * channel.
+ */
+ if (xconn->c)
+ sshfwd_write_eof(xconn->c);
+ }
+
+ return 1;
+}
+
+static int x11_receive(Plug plug, int urgent, char *data, int len)
+{
+ struct X11Connection *xconn = (struct X11Connection *) plug;
+
+ if (sshfwd_write(xconn->c, data, len) > 0) {
+ xconn->throttled = 1;
+ xconn->no_data_sent_to_x_client = FALSE;
+ sk_set_frozen(xconn->s, 1);
+ }
+
+ return 1;
+}
+
+static void x11_sent(Plug plug, int bufsize)
+{
+ struct X11Connection *xconn = (struct X11Connection *) plug;
+
+ sshfwd_unthrottle(xconn->c, bufsize);
+}
+
+/*
+ * When setting up X forwarding, we should send the screen number
+ * from the specified local display. This function extracts it from
+ * the display string.
+ */
+int x11_get_screen_number(char *display)
+{
+ int n;
+
+ n = host_strcspn(display, ":");
+ if (!display[n])
+ return 0;
+ n = strcspn(display, ".");
+ if (!display[n])
+ return 0;
+ return atoi(display + n + 1);
+}
+
+/*
+ * Called to set up the X11Connection structure, though this does not
+ * yet connect to an actual server.
+ */
+struct X11Connection *x11_init(tree234 *authtree, void *c,
+ const char *peeraddr, int peerport)
+{
+ static const struct plug_function_table fn_table = {
+ x11_log,
+ x11_closing,
+ x11_receive,
+ x11_sent,
+ NULL
+ };
+
+ struct X11Connection *xconn;
+
+ /*
+ * Open socket.
+ */
+ xconn = snew(struct X11Connection);
+ xconn->fn = &fn_table;
+ xconn->auth_protocol = NULL;
+ xconn->authtree = authtree;
+ xconn->verified = 0;
+ xconn->data_read = 0;
+ xconn->throttled = xconn->throttle_override = 0;
+ xconn->no_data_sent_to_x_client = TRUE;
+ xconn->c = c;
+
+ /*
+ * We don't actually open a local socket to the X server just yet,
+ * because we don't know which one it is. Instead, we'll wait
+ * until we see the incoming authentication data, which may tell
+ * us what display to connect to, or whether we have to divert
+ * this X forwarding channel to a connection-sharing downstream
+ * rather than handling it ourself.
+ */
+ xconn->disp = NULL;
+ xconn->s = NULL;
+
+ /*
+ * Stash the peer address we were given in its original text form.
+ */
+ xconn->peer_addr = peeraddr ? dupstr(peeraddr) : NULL;
+ xconn->peer_port = peerport;
+
+ return xconn;
+}
+
+void x11_close(struct X11Connection *xconn)
+{
+ if (!xconn)
+ return;
+
+ if (xconn->auth_protocol) {
+ sfree(xconn->auth_protocol);
+ sfree(xconn->auth_data);
+ }
+
+ if (xconn->s)
+ sk_close(xconn->s);
+
+ sfree(xconn->peer_addr);
+ sfree(xconn);
+}
+
+void x11_unthrottle(struct X11Connection *xconn)
+{
+ if (!xconn)
+ return;
+
+ xconn->throttled = 0;
+ if (xconn->s)
+ sk_set_frozen(xconn->s, xconn->throttled || xconn->throttle_override);
+}
+
+void x11_override_throttle(struct X11Connection *xconn, int enable)
+{
+ if (!xconn)
+ return;
+
+ xconn->throttle_override = enable;
+ if (xconn->s)
+ sk_set_frozen(xconn->s, xconn->throttled || xconn->throttle_override);
+}
+
+static void x11_send_init_error(struct X11Connection *xconn,
+ const char *err_message)
+{
+ char *full_message;
+ int msglen, msgsize;
+ unsigned char *reply;
+
+ full_message = dupprintf("%s X11 proxy: %s\n", appname, err_message);
+
+ msglen = strlen(full_message);
+ reply = snewn(8 + msglen+1 + 4, unsigned char); /* include zero */
+ msgsize = (msglen + 3) & ~3;
+ reply[0] = 0; /* failure */
+ reply[1] = msglen; /* length of reason string */
+ memcpy(reply + 2, xconn->firstpkt + 2, 4); /* major/minor proto vsn */
+ PUT_16BIT(xconn->firstpkt[0], reply + 6, msgsize >> 2);/* data len */
+ memset(reply + 8, 0, msgsize);
+ memcpy(reply + 8, full_message, msglen);
+ sshfwd_write(xconn->c, (char *)reply, 8 + msgsize);
+ sshfwd_write_eof(xconn->c);
+ xconn->no_data_sent_to_x_client = FALSE;
+ sfree(reply);
+ sfree(full_message);
+}
+
+static int x11_parse_ip(const char *addr_string, unsigned long *ip)
+{
+
+ /*
+ * See if we can make sense of this string as an IPv4 address, for
+ * XDM-AUTHORIZATION-1 purposes.
+ */
+ int i[4];
+ if (addr_string &&
+ 4 == sscanf(addr_string, "%d.%d.%d.%d", i+0, i+1, i+2, i+3)) {
+ *ip = (i[0] << 24) | (i[1] << 16) | (i[2] << 8) | i[3];
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+/*
+ * Called to send data down the raw connection.
+ */
+int x11_send(struct X11Connection *xconn, char *data, int len)
+{
+ if (!xconn)
+ return 0;
+
+ /*
+ * Read the first packet.
+ */
+ while (len > 0 && xconn->data_read < 12)
+ xconn->firstpkt[xconn->data_read++] = (unsigned char) (len--, *data++);
+ if (xconn->data_read < 12)
+ return 0;
+
+ /*
+ * If we have not allocated the auth_protocol and auth_data
+ * strings, do so now.
+ */
+ if (!xconn->auth_protocol) {
+ xconn->auth_plen = GET_16BIT(xconn->firstpkt[0], xconn->firstpkt + 6);
+ xconn->auth_dlen = GET_16BIT(xconn->firstpkt[0], xconn->firstpkt + 8);
+ xconn->auth_psize = (xconn->auth_plen + 3) & ~3;
+ xconn->auth_dsize = (xconn->auth_dlen + 3) & ~3;
+ /* Leave room for a terminating zero, to make our lives easier. */
+ xconn->auth_protocol = snewn(xconn->auth_psize + 1, char);
+ xconn->auth_data = snewn(xconn->auth_dsize, unsigned char);
+ }
+
+ /*
+ * Read the auth_protocol and auth_data strings.
+ */
+ while (len > 0 &&
+ xconn->data_read < 12 + xconn->auth_psize)
+ xconn->auth_protocol[xconn->data_read++ - 12] = (len--, *data++);
+ while (len > 0 &&
+ xconn->data_read < 12 + xconn->auth_psize + xconn->auth_dsize)
+ xconn->auth_data[xconn->data_read++ - 12 -
+ xconn->auth_psize] = (unsigned char) (len--, *data++);
+ if (xconn->data_read < 12 + xconn->auth_psize + xconn->auth_dsize)
+ return 0;
+
+ /*
+ * If we haven't verified the authorisation, do so now.
+ */
+ if (!xconn->verified) {
+ const char *err;
+ struct X11FakeAuth *auth_matched = NULL;
+ unsigned long peer_ip;
+ int peer_port;
+ int protomajor, protominor;
+ void *greeting;
+ int greeting_len;
+ unsigned char *socketdata;
+ int socketdatalen;
+ char new_peer_addr[32];
+ int new_peer_port;
+
+ protomajor = GET_16BIT(xconn->firstpkt[0], xconn->firstpkt + 2);
+ protominor = GET_16BIT(xconn->firstpkt[0], xconn->firstpkt + 4);
+
+ assert(!xconn->s);
+
+ xconn->auth_protocol[xconn->auth_plen] = '\0'; /* ASCIZ */
+
+ peer_ip = 0; /* placate optimiser */
+ if (x11_parse_ip(xconn->peer_addr, &peer_ip))
+ peer_port = xconn->peer_port;
+ else
+ peer_port = -1; /* signal no peer address data available */
+
+ err = x11_verify(peer_ip, peer_port,
+ xconn->authtree, xconn->auth_protocol,
+ xconn->auth_data, xconn->auth_dlen, &auth_matched);
+ if (err) {
+ x11_send_init_error(xconn, err);
+ return 0;
+ }
+ assert(auth_matched);
+
+ /*
+ * If this auth points to a connection-sharing downstream
+ * rather than an X display we know how to connect to
+ * directly, pass it off to the sharing module now.
+ */
+ if (auth_matched->share_cs) {
+ sshfwd_x11_sharing_handover(xconn->c, auth_matched->share_cs,
+ auth_matched->share_chan,
+ xconn->peer_addr, xconn->peer_port,
+ xconn->firstpkt[0],
+ protomajor, protominor, data, len);
+ return 0;
+ }
+
+ /*
+ * Now we know we're going to accept the connection, and what
+ * X display to connect to. Actually connect to it.
+ */
+ sshfwd_x11_is_local(xconn->c);
+ xconn->disp = auth_matched->disp;
+ xconn->s = new_connection(sk_addr_dup(xconn->disp->addr),
+ xconn->disp->realhost, xconn->disp->port,
+ 0, 1, 0, 0, (Plug) xconn,
+ sshfwd_get_conf(xconn->c));
+ if ((err = sk_socket_error(xconn->s)) != NULL) {
+ char *err_message = dupprintf("unable to connect to"
+ " forwarded X server: %s", err);
+ x11_send_init_error(xconn, err_message);
+ sfree(err_message);
+ return 0;
+ }
+
+ /*
+ * Write a new connection header containing our replacement
+ * auth data.
+ */
+
+ socketdata = sk_getxdmdata(xconn->s, &socketdatalen);
+ if (socketdata && socketdatalen==6) {
+ sprintf(new_peer_addr, "%d.%d.%d.%d", socketdata[0],
+ socketdata[1], socketdata[2], socketdata[3]);
+ new_peer_port = GET_16BIT_MSB_FIRST(socketdata + 4);
+ } else {
+ strcpy(new_peer_addr, "0.0.0.0");
+ new_peer_port = 0;
+ }
+
+ greeting = x11_make_greeting(xconn->firstpkt[0],
+ protomajor, protominor,
+ xconn->disp->localauthproto,
+ xconn->disp->localauthdata,
+ xconn->disp->localauthdatalen,
+ new_peer_addr, new_peer_port,
+ &greeting_len);
+
+ sk_write(xconn->s, greeting, greeting_len);
+
+ smemclr(greeting, greeting_len);
+ sfree(greeting);
+
+ /*
+ * Now we're done.
+ */
+ xconn->verified = 1;
+ }
+
+ /*
+ * After initialisation, just copy data simply.
+ */
+
+ return sk_write(xconn->s, data, len);
+}
+
+void x11_send_eof(struct X11Connection *xconn)
+{
+ if (xconn->s) {
+ sk_write_eof(xconn->s);
+ } else {
+ /*
+ * If EOF is received from the X client before we've got to
+ * the point of actually connecting to an X server, then we
+ * should send an EOF back to the client so that the
+ * forwarded channel will be terminated.
+ */
+ if (xconn->c)
+ sshfwd_write_eof(xconn->c);
+ }
+}
+
+/*
+ * Utility functions used by connection sharing to convert textual
+ * representations of an X11 auth protocol name + hex cookie into our
+ * usual integer protocol id and binary auth data.
+ */
+int x11_identify_auth_proto(const char *protoname)
+{
+ int protocol;
+
+ for (protocol = 1; protocol < lenof(x11_authnames); protocol++)
+ if (!strcmp(protoname, x11_authnames[protocol]))
+ return protocol;
+ return -1;
+}
+
+void *x11_dehexify(const char *hex, int *outlen)
+{
+ int len, i;
+ unsigned char *ret;
+
+ len = strlen(hex) / 2;
+ ret = snewn(len, unsigned char);
+
+ for (i = 0; i < len; i++) {
+ char bytestr[3];
+ unsigned val = 0;
+ bytestr[0] = hex[2*i];
+ bytestr[1] = hex[2*i+1];
+ bytestr[2] = '\0';
+ sscanf(bytestr, "%x", &val);
+ ret[i] = val;
+ }
+
+ *outlen = len;
+ return ret;
+}
+
+/*
+ * Construct an X11 greeting packet, including making up the right
+ * authorisation data.
+ */
+void *x11_make_greeting(int endian, int protomajor, int protominor,
+ int auth_proto, const void *auth_data, int auth_len,
+ const char *peer_addr, int peer_port,
+ int *outlen)
+{
+ unsigned char *greeting;
+ unsigned char realauthdata[64];
+ const char *authname;
+ const unsigned char *authdata;
+ int authnamelen, authnamelen_pad;
+ int authdatalen, authdatalen_pad;
+ int greeting_len;
+
+ authname = x11_authnames[auth_proto];
+ authnamelen = strlen(authname);
+ authnamelen_pad = (authnamelen + 3) & ~3;
+
+ if (auth_proto == X11_MIT) {
+ authdata = auth_data;
+ authdatalen = auth_len;
+ } else if (auth_proto == X11_XDM && auth_len == 16) {
+ time_t t;
+ unsigned long peer_ip = 0;
+
+ x11_parse_ip(peer_addr, &peer_ip);
+
+ authdata = realauthdata;
+ authdatalen = 24;
+ memset(realauthdata, 0, authdatalen);
+ memcpy(realauthdata, auth_data, 8);
+ PUT_32BIT_MSB_FIRST(realauthdata+8, peer_ip);
+ PUT_16BIT_MSB_FIRST(realauthdata+12, peer_port);
+ t = time(NULL);
+ PUT_32BIT_MSB_FIRST(realauthdata+14, t);
+
+ des_encrypt_xdmauth((const unsigned char *)auth_data + 9,
+ realauthdata, authdatalen);
+ } else {
+ authdata = realauthdata;
+ authdatalen = 0;
+ }
+
+ authdatalen_pad = (authdatalen + 3) & ~3;
+ greeting_len = 12 + authnamelen_pad + authdatalen_pad;
+
+ greeting = snewn(greeting_len, unsigned char);
+ memset(greeting, 0, greeting_len);
+ greeting[0] = endian;
+ PUT_16BIT(endian, greeting+2, protomajor);
+ PUT_16BIT(endian, greeting+4, protominor);
+ PUT_16BIT(endian, greeting+6, authnamelen);
+ PUT_16BIT(endian, greeting+8, authdatalen);
+ memcpy(greeting+12, authname, authnamelen);
+ memcpy(greeting+12+authnamelen_pad, authdata, authdatalen);
+
+ smemclr(realauthdata, sizeof(realauthdata));
+
+ *outlen = greeting_len;
+ return greeting;
+}