From a3fe3e22d85e8aa795df85c21814fc84cac42e99 Mon Sep 17 00:00:00 2001 From: marha Date: Mon, 14 Apr 2014 23:43:21 +0200 Subject: plink: updated to revision 10170 of putty --- tools/plink/cmdline.c | 1140 +-- tools/plink/conf.c | 9 +- tools/plink/ldisc.c | 708 +- tools/plink/logging.c | 920 +- tools/plink/misc.c | 1564 ++-- tools/plink/misc.h | 279 +- tools/plink/network.h | 490 +- tools/plink/pinger.c | 144 +- tools/plink/portfwd.c | 1203 +-- tools/plink/proxy.c | 3016 +++---- tools/plink/proxy.h | 249 +- tools/plink/putty.h | 2863 +++---- tools/plink/puttymem.h | 94 +- tools/plink/raw.c | 686 +- tools/plink/rlogin.c | 859 +- tools/plink/settings.c | 2153 ++--- tools/plink/ssh.c | 21302 +++++++++++++++++++++++++---------------------- tools/plink/ssh.h | 1416 ++-- tools/plink/sshaes.c | 2468 +++--- tools/plink/ssharcf.c | 246 +- tools/plink/sshbn.c | 3919 ++++----- tools/plink/sshdes.c | 2119 ++--- tools/plink/sshdss.c | 1343 +-- tools/plink/sshmd5.c | 684 +- tools/plink/sshpubk.c | 2444 +++--- tools/plink/sshrand.c | 579 +- tools/plink/sshrsa.c | 2191 ++--- tools/plink/sshsh256.c | 648 +- tools/plink/sshsha.c | 846 +- tools/plink/sshzlib.c | 2779 ++++--- tools/plink/telnet.c | 2267 ++--- tools/plink/terminal.h | 655 +- tools/plink/timing.c | 454 +- tools/plink/tree234.c | 2965 +++---- tools/plink/wildcard.c | 945 +-- tools/plink/wincons.c | 856 +- tools/plink/winhandl.c | 1303 +-- tools/plink/winhelp.h | 376 +- tools/plink/winmisc.c | 990 ++- tools/plink/winnet.c | 3582 ++++---- tools/plink/winnoise.c | 283 +- tools/plink/winpgntc.c | 452 +- tools/plink/winplink.c | 1520 ++-- tools/plink/winproxy.c | 321 +- tools/plink/winstore.c | 1714 ++-- tools/plink/winstuff.h | 1114 ++- tools/plink/x11fwd.c | 1884 +++-- 47 files changed, 41713 insertions(+), 39329 deletions(-) (limited to 'tools/plink') 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 -#include -#include -#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 +#include +#include +#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 -#include - -#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; /* 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 +#include + +#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; /* 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 -#include -#include - -#include -#include - -#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": "&&":& - */ -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 +#include +#include + +#include +#include + +#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": "&&":& + */ +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 -#include -#include -#include -#include -#include -#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 - * 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 , 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 +#include +#include +#include +#include +#include +#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 + * 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 , 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 0ecb34445..d6a80bf0e 100644 --- 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 /* for FILE * */ -#include /* for va_list */ -#include /* 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), \ - (cp)[1] = (unsigned char)((value) >> 8), \ - (cp)[2] = (unsigned char)((value) >> 16), \ - (cp)[3] = (unsigned char)((value) >> 24) ) - -#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), \ - (cp)[1] = (unsigned char)((value) >> 8) ) - -#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), \ - (cp)[1] = (unsigned char)((value) >> 16), \ - (cp)[2] = (unsigned char)((value) >> 8), \ - (cp)[3] = (unsigned char)(value) ) - -#define PUT_32BIT(cp, value) PUT_32BIT_MSB_FIRST(cp, value) - -#define GET_16BIT_MSB_FIRST(cp) \ - (((unsigned long)(unsigned char)(cp)[0] << 8) | \ - ((unsigned long)(unsigned char)(cp)[1])) - -#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 /* for FILE * */ +#include /* for va_list */ +#include /* 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), \ + (cp)[1] = (unsigned char)((value) >> 8), \ + (cp)[2] = (unsigned char)((value) >> 16), \ + (cp)[3] = (unsigned char)((value) >> 24) ) + +#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), \ + (cp)[1] = (unsigned char)((value) >> 8) ) + +#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), \ + (cp)[1] = (unsigned char)((value) >> 16), \ + (cp)[2] = (unsigned char)((value) >> 8), \ + (cp)[3] = (unsigned char)(value) ) + +#define PUT_32BIT(cp, value) PUT_32BIT_MSB_FIRST(cp, value) + +#define GET_16BIT_MSB_FIRST(cp) \ + (((unsigned long)(unsigned char)(cp)[0] << 8) | \ + ((unsigned long)(unsigned char)(cp)[1])) + +#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 -#include - -#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 +#include + +#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 -#include -#include - -#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 +#include +#include + +#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 /* 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 /* 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 /* for size_t */ -#include /* 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 /* for size_t */ +#include /* 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 -#include - -#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 +#include +#include + +#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 -#include -#include - -#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 +#include +#include +#include + +#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 -#include -#include -#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", value "D") to the - * conceptually incoherent legacy storage format (key - * "D", 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<= 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 +#include +#include +#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", value "D") to the + * conceptually incoherent legacy storage format (key + * "D", 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<= 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 8f1aa15de..4eb84f899 100644 --- a/tools/plink/ssh.c +++ b/tools/plink/ssh.c @@ -1,10163 +1,11139 @@ -/* - * SSH backend. - */ - -#include -#include -#include -#include -#include -#include - -#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); -} - -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 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--". */ - 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[] = { - "", - "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[] = - ": "; - - 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, , */ - 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 +#include +#include +#include +#include +#include + +#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); +} + +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 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 "-". */ + 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[] = { + "", + "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[] = + ": "; + + 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, , */ + 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 -#include - -#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 +#include + +#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 -#include - -#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 +#include + +#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 -#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 +#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 51cecdf2b..a5e0552ff 100644 --- a/tools/plink/sshbn.c +++ b/tools/plink/sshbn.c @@ -1,1918 +1,2001 @@ -/* - * Bignum routines for RSA and DH and stuff. - */ - -#include -#include -#include -#include - -#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. - * */ -#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; - 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; - 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; - 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; - 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 > a[i + k]) - c++; - a[i + k] -= (BignumInt) t; - } - - /* 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 -#include -#include - -/* - * 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 +#include +#include +#include +#include + +#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. + * */ +#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; + 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; + 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; + 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; + 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 > a[i + k]) + c++; + a[i + k] -= (BignumInt) t; + } + + /* 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 +#include +#include + +/* + * 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 -#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 +#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 +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 -#include -#include - -#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 +#include +#include + +#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 -#include -#include - -#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 +#include +#include + +#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 - -/* 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 + +/* 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 -#include -#include -#include - -#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 +#include +#include +#include + +#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 -#include -#include - -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 +#include +#include + +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 -#include - -#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 -#include - -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 +#include +#include + +#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 +#include + +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 -#include - -#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 ""; - } -#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 "); - 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 \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 ", - 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 +#include +#include + +#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 ""; + } +#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 "); + 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 \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 ", + 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 -#include - -#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 +#include + +#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 -#include -#include - -#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 - -/* - * 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 +#include +#include + +#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 + +/* + * 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 -#include -#include - -#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 +#include +#include + +#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 -#include -#include - -#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 +#include +#include + +#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 - -#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 + +#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 -#include -#include "putty.h" -#include - -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 +#include +#include "putty.h" +#include + +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 -#include -#include - -#define DEFINE_PLUG_METHOD_MACROS -#include "putty.h" -#include "network.h" -#include "tree234.h" - -#include - -#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, "", 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 +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "putty.h" +#include "network.h" +#include "tree234.h" + +#include + +#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, "", 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 - -#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(×, sizeof(times)); - GetProcessTimes(GetCurrentProcess(), times, times + 1, times + 2, - times + 3); - random_add_noise(×, 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 + +#include "putty.h" +#include "ssh.h" +#include "storage.h" + +#include + +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(×, sizeof(times)); + GetProcessTimes(GetCurrentProcess(), times, times + 1, times + 2, + times + 3); + random_add_noise(×, 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 -#include - -#include "putty.h" - -#ifndef NO_SECURITY -#include -#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 +#include + +#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 bd65f7825..451eff3b8 100644 --- a/tools/plink/winplink.c +++ b/tools/plink/winplink.c @@ -1,744 +1,776 @@ -/* - * PLink - a Windows command-line (stdin/stdout) variant of PuTTY. - */ - -#include -#include -#include -#include - -#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 */ -} +/* + * PLink - a Windows command-line (stdin/stdout) variant of PuTTY. + */ + +#include +#include +#include +#include + +#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 */ +} 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 -#include - -#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 +#include + +#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 -#include -#include -#include "putty.h" -#include "storage.h" - -#include -#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 +#include +#include +#include "putty.h" +#include "storage.h" + +#include +#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 -#endif -#include -#include /* 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 +#endif +#include +#include /* 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 -#include -#include -#include - -#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 +#include +#include +#include + +#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; +} -- cgit v1.2.3