diff --git a/cmdgen.c b/cmdgen.c index 20c0d117..36da55ee 100644 --- a/cmdgen.c +++ b/cmdgen.c @@ -25,9 +25,10 @@ * order to avoid depleting the test system's /dev/random * unnecessarily. * - * - Calls to console_get_line() are replaced with the diagnostic - * function below, so that I can run tests in an automated - * manner and provide their interactive passphrase inputs. + * - Calls to console_get_userpass_input() are replaced with the + * diagnostic function below, so that I can run tests in an + * automated manner and provide their interactive passphrase + * inputs. * * - main() is renamed to cmdgen_main(); at the bottom of the file * I define another main() which calls the former repeatedly to @@ -40,19 +41,23 @@ char *get_random_data(int len) memset(buf, 'x', len); return buf; } -#define console_get_line console_get_line_diagnostic +#define console_get_userpass_input console_get_userpass_input_diagnostic int nprompts, promptsgot; const char *prompts[3]; -int console_get_line(const char *prompt, char *str, int maxlen, int is_pw) +int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen) { - if (promptsgot < nprompts) { - assert(strlen(prompts[promptsgot]) < maxlen); - strcpy(str, prompts[promptsgot++]); - return TRUE; - } else { - promptsgot++; /* track number of requests anyway */ - return FALSE; + size_t i; + int ret = 1; + for (i = 0; i < p->n_prompts; i++) { + if (promptsgot < nprompts) { + assert(strlen(prompts[promptsgot]) < p->prompts[i]->result_len); + strcpy(p->prompts[i]->result, prompts[promptsgot++]); + } else { + promptsgot++; /* track number of requests anyway */ + ret = 0; + } } + return ret; } #define main cmdgen_main #endif @@ -670,11 +675,20 @@ int main(int argc, char **argv) * If so, ask for a passphrase. */ if (encrypted && load_encrypted) { - passphrase = snewn(512, char); - if (!console_get_line("Enter passphrase to load key: ", - passphrase, 512, TRUE)) { + prompts_t *p = new_prompts(NULL); + int ret; + p->to_server = FALSE; + p->name = dupstr("SSH key passphrase"); + add_prompt(p, dupstr("Enter passphrase to load key: "), FALSE, 512); + ret = console_get_userpass_input(p, NULL, 0); + assert(ret >= 0); + if (!ret) { + free_prompts(p); perror("puttygen: unable to read passphrase"); return 1; + } else { + passphrase = dupstr(p->prompts[0]->result); + free_prompts(p); } } else { passphrase = NULL; @@ -785,31 +799,35 @@ int main(int argc, char **argv) * we have just generated a key. */ if (change_passphrase || keytype != NOKEYGEN) { - char *passphrase2; + prompts_t *p = new_prompts(NULL); + int ret; - if (passphrase) { - memset(passphrase, 0, strlen(passphrase)); - sfree(passphrase); - } - - passphrase = snewn(512, char); - passphrase2 = snewn(512, char); - if (!console_get_line("Enter passphrase to save key: ", - passphrase, 512, TRUE) || - !console_get_line("Re-enter passphrase to verify: ", - passphrase2, 512, TRUE)) { + p->to_server = FALSE; + p->name = dupstr("New SSH key passphrase"); + add_prompt(p, dupstr("Enter passphrase to save key: "), FALSE, 512); + add_prompt(p, dupstr("Re-enter passphrase to verify: "), FALSE, 512); + ret = console_get_userpass_input(p, NULL, 0); + assert(ret >= 0); + if (!ret) { + free_prompts(p); perror("puttygen: unable to read new passphrase"); return 1; - } - if (strcmp(passphrase, passphrase2)) { - fprintf(stderr, "puttygen: passphrases do not match\n"); - return 1; - } - memset(passphrase2, 0, strlen(passphrase2)); - sfree(passphrase2); - if (!*passphrase) { - sfree(passphrase); - passphrase = NULL; + } else { + if (strcmp(p->prompts[0]->result, p->prompts[1]->result)) { + free_prompts(p); + fprintf(stderr, "puttygen: passphrases do not match\n"); + return 1; + } + if (passphrase) { + memset(passphrase, 0, strlen(passphrase)); + sfree(passphrase); + } + passphrase = dupstr(p->prompts[0]->result); + free_prompts(p); + if (!*passphrase) { + sfree(passphrase); + passphrase = NULL; + } } } diff --git a/cmdline.c b/cmdline.c index fdd95849..112463c3 100644 --- a/cmdline.c +++ b/cmdline.c @@ -63,23 +63,40 @@ void cmdline_cleanup(void) if (need_save) { cmdline_save_param(p, value, pri); return ret; } \ } while (0) -char *cmdline_password = NULL; +static char *cmdline_password = NULL; + +/* + * 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 cmdline_get_line(const char *prompt, char *str, - int maxlen, int is_pw) -{ static int tried_once = 0; - assert(is_pw && cmdline_password); - - if (tried_once) { - return 0; - } else { - strncpy(str, cmdline_password, maxlen); - str[maxlen - 1] = '\0'; - tried_once = 1; - return 1; + /* + * 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; + + strncpy(p->prompts[0]->result, cmdline_password, + p->prompts[0]->result_len); + p->prompts[0]->result[p->prompts[0]->result_len-1] = '\0'; + memset(cmdline_password, 0, strlen(cmdline_password)); + tried_once = 1; + return 1; + } /* @@ -272,8 +289,6 @@ int cmdline_process_param(char *p, char *value, int need_save, Config *cfg) RETURN(2); UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); cmdline_password = value; - ssh_get_line = cmdline_get_line; - ssh_getline_pw_only = TRUE; } if (!strcmp(p, "-A")) { diff --git a/mac/macterm.c b/mac/macterm.c index 443c0c8b..68d82760 100644 --- a/mac/macterm.c +++ b/mac/macterm.c @@ -1898,6 +1898,12 @@ int from_backend(void *frontend, int is_stderr, const char *data, int len) return term_data(s->term, is_stderr, data, len); } +int get_userpass_input(prompts_t *p, unsigned char *in, int inlen) +{ + Session *s = p->frontend; + return term_get_userpass_input(s->term, p, in, inlen); +} + /* * Emacs magic: * Local Variables: diff --git a/macosx/osxwin.m b/macosx/osxwin.m index e534c909..ba3a08ec 100644 --- a/macosx/osxwin.m +++ b/macosx/osxwin.m @@ -893,6 +893,13 @@ int from_backend(void *frontend, int is_stderr, const char *data, int len) return [win fromBackend:data len:len isStderr:is_stderr]; } +int get_userpass_input(prompts_t *p, unsigned char *in, int inlen) +{ + SessionWindow *win = (SessionWindow *)p->frontend; + Terminal *term = [win term]; + return term_get_userpass_input(term, p, in, inlen); +} + void frontend_keypress(void *handle) { /* FIXME */ diff --git a/misc.c b/misc.c index 9c8d2039..37ac0a18 100644 --- a/misc.c +++ b/misc.c @@ -87,6 +87,46 @@ char ctrlparse(char *s, char **next) 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, size_t len) +{ + prompt_t *pr = snew(prompt_t); + unsigned char *result = snewn(len, unsigned char); + pr->prompt = promptstr; + pr->echo = echo; + pr->result = result; + pr->result_len = len; + p->n_prompts++; + p->prompts = sresize(p->prompts, p->n_prompts, prompt_t *); + p->prompts[p->n_prompts-1] = pr; +} +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->result_len); /* 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. */ diff --git a/pscp.c b/pscp.c index 19e5df4a..575025f2 100644 --- a/pscp.c +++ b/pscp.c @@ -220,6 +220,15 @@ int from_backend(void *frontend, int is_stderr, const char *data, int datalen) return 0; } +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 */ +} static int ssh_scp_recv(unsigned char *buf, int len) { outptr = buf; @@ -2204,7 +2213,6 @@ int psftp_main(int argc, char *argv[]) #endif ; cmdline_tooltype = TOOLTYPE_FILETRANSFER; - ssh_get_line = &console_get_line; sk_init(); /* Load Default Settings before doing anything else. */ diff --git a/psftp.c b/psftp.c index 53b6616d..2705d45d 100644 --- a/psftp.c +++ b/psftp.c @@ -2523,6 +2523,15 @@ int from_backend(void *frontend, int is_stderr, const char *data, int datalen) return 0; } +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 sftp_recvdata(char *buf, int len) { outptr = (unsigned char *) buf; @@ -2714,14 +2723,21 @@ static int psftp_connect(char *userhost, char *user, int portnumber) cfg.username[sizeof(cfg.username) - 1] = '\0'; } if (!cfg.username[0]) { - if (!console_get_line("login as: ", - cfg.username, sizeof(cfg.username), FALSE)) { + /* FIXME: leave this to ssh.c? */ + int ret; + prompts_t *p = new_prompts(NULL); + p->to_server = TRUE; + p->name = dupstr("SSH login name"); + add_prompt(p, dupstr("login as: "), TRUE, lenof(cfg.username)); + ret = get_userpass_input(p, NULL, 0); + assert(ret >= 0); + if (!ret) { + free_prompts(p); fprintf(stderr, "psftp: no username, aborting\n"); cleanup_exit(1); } else { - int len = strlen(cfg.username); - if (cfg.username[len - 1] == '\n') - cfg.username[len - 1] = '\0'; + memcpy(cfg.username, p->prompts[0]->result, lenof(cfg.username)); + free_prompts(p); } } @@ -2819,7 +2835,6 @@ int psftp_main(int argc, char *argv[]) #endif ; cmdline_tooltype = TOOLTYPE_FILETRANSFER; - ssh_get_line = &console_get_line; sk_init(); userhost = user = NULL; diff --git a/putty.h b/putty.h index b6284d53..1a2d7ecd 100644 --- a/putty.h +++ b/putty.h @@ -611,6 +611,52 @@ GLOBAL int loaded_session; 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; + char *result; /* allocated/freed by caller */ + size_t result_len; +} 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; + 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, size_t len); +/* Burn the evidence. (Assumes _all_ strings want free()ing.) */ +void free_prompts(prompts_t *p); + /* * Exports from the front end. */ @@ -652,10 +698,17 @@ void ldisc_update(void *frontend, int echo, int edit); * 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); 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); @@ -745,12 +798,15 @@ void term_copyall(Terminal *); void term_reconfig(Terminal *, Config *); 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); /* * Exports from logging.c. @@ -800,14 +856,8 @@ extern Backend rlogin_backend; extern Backend telnet_backend; /* - * Exports from ssh.c. (NB the getline variables have to be GLOBAL - * so that PuTTYtel will still compile - otherwise it would depend - * on ssh.c.) + * Exports from ssh.c. */ - -GLOBAL int (*ssh_get_line) (const char *prompt, char *str, int maxlen, - int is_pw); -GLOBAL int ssh_getline_pw_only; extern Backend ssh_backend; /* @@ -952,11 +1002,11 @@ int askappend(void *frontend, Filename filename, void (*callback)(void *ctx, int result), void *ctx); /* - * Exports from console.c (that aren't equivalents to things in - * windlg.c). + * 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_line(const char *prompt, char *str, int maxlen, int is_pw); +int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen); void console_provide_logctx(void *logctx); int is_interactive(void); @@ -980,7 +1030,7 @@ void printer_finish_job(printer_job *); int cmdline_process_param(char *, char *, int, Config *); void cmdline_run_saved(Config *); void cmdline_cleanup(void); -extern char *cmdline_password; +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; diff --git a/ssh.c b/ssh.c index 5493191e..45ecf260 100644 --- a/ssh.c +++ b/ssh.c @@ -459,6 +459,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, #define OUR_V2_WINSIZE 16384 #define OUR_V2_MAXPKT 0x4000UL +/* Maximum length of passwords/passphrases (arbitrary) */ +#define SSH_MAX_PASSWORD_LEN 100 + const static struct ssh_signkey *hostkey_algs[] = { &ssh_rsa, &ssh_dss }; const static struct ssh_mac *macs[] = { @@ -752,13 +755,6 @@ struct ssh_tag { int fallback_cmd; bufchain banner; /* accumulates banners during do_ssh2_authconn */ - /* - * Used for username and password input. - */ - char *userpass_input_buffer; - int userpass_input_buflen; - int userpass_input_bufpos; - int userpass_input_echo; int pkt_ctx; @@ -1046,27 +1042,28 @@ static int alloc_channel_id(Ssh ssh) 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] & 0x60)) + fputc(buf[i], stderr); +} + static void c_write(Ssh ssh, const char *buf, int len) { - if ((flags & FLAG_STDERR)) { - int i; - for (i = 0; i < len; i++) - if (buf[i] != '\r') - fputc(buf[i], stderr); - return; - } - from_backend(ssh->frontend, 1, buf, 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) { - int i; - for (i = 0; i < len; i++) { - if (buf[i] == '\n') - c_write(ssh, "\r\n", 2); - else if ((buf[i] & 0x60) || (buf[i] == '\r')) - c_write(ssh, buf + i, 1); - } + 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) @@ -2751,78 +2748,6 @@ static void ssh_throttle_all(Ssh ssh, int enable, int bufsize) } } -/* - * Username and password input, abstracted off into routines - * reusable in several places - even between SSH-1 and SSH-2. - */ - -/* Set up a username or password input loop on a given buffer. */ -static void setup_userpass_input(Ssh ssh, char *buffer, int buflen, int echo) -{ - ssh->userpass_input_buffer = buffer; - ssh->userpass_input_buflen = buflen; - ssh->userpass_input_bufpos = 0; - ssh->userpass_input_echo = echo; -} - -/* - * Process some terminal data in the course of username/password - * input. Returns >0 for success (line of input returned in - * buffer), <0 for failure (user hit ^C/^D, bomb out and exit), 0 - * for inconclusive (keep waiting for more input please). - */ -static int process_userpass_input(Ssh ssh, unsigned char *in, int inlen) -{ - char c; - - while (inlen--) { - switch (c = *in++) { - case 10: - case 13: - ssh->userpass_input_buffer[ssh->userpass_input_bufpos] = 0; - ssh->userpass_input_buffer[ssh->userpass_input_buflen-1] = 0; - return +1; - break; - case 8: - case 127: - if (ssh->userpass_input_bufpos > 0) { - if (ssh->userpass_input_echo) - c_write_str(ssh, "\b \b"); - ssh->userpass_input_bufpos--; - } - break; - case 21: - case 27: - while (ssh->userpass_input_bufpos > 0) { - if (ssh->userpass_input_echo) - c_write_str(ssh, "\b \b"); - ssh->userpass_input_bufpos--; - } - break; - case 3: - case 4: - return -1; - break; - default: - /* - * This simplistic check for printability is disabled - * when we're doing password input, because some people - * have control characters in their passwords.o - */ - if ((!ssh->userpass_input_echo || - (c >= ' ' && c <= '~') || - ((unsigned char) c >= 160)) - && ssh->userpass_input_bufpos < ssh->userpass_input_buflen-1) { - ssh->userpass_input_buffer[ssh->userpass_input_bufpos++] = c; - if (ssh->userpass_input_echo) - c_write(ssh, &c, 1); - } - break; - } - } - return 0; -} - static void ssh_agent_callback(void *sshv, void *reply, int replylen) { Ssh ssh = (Ssh) sshv; @@ -2935,9 +2860,9 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, char username[100]; void *publickey_blob; int publickey_bloblen; - char password[100]; - char prompt[200]; - int pos; + char *publickey_comment; + int publickey_encrypted; + prompts_t *cur_prompt; char c; int pwpkt_type; unsigned char request[5], *response, *p; @@ -3199,33 +3124,33 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, logevent("Successfully started encryption"); - fflush(stdout); + fflush(stdout); /* FIXME eh? */ { if (!*ssh->cfg.username) { - if (ssh_get_line && !ssh_getline_pw_only) { - if (!ssh_get_line("login as: ", - s->username, sizeof(s->username), FALSE)) { - /* - * get_line failed to get a username. - * Terminate. - */ - ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE); - crStop(1); - } - } else { - int ret; /* need not be kept over crReturn */ - c_write_str(ssh, "login as: "); + 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, + lenof(s->username)); + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { ssh->send_ok = 1; - - setup_userpass_input(ssh, s->username, sizeof(s->username), 1); - do { - crWaitUntil(!pktin); - ret = process_userpass_input(ssh, in, inlen); - } while (ret == 0); - if (ret < 0) - cleanup_exit(0); - c_write_str(ssh, "\r\n"); + 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); + } + memcpy(s->username, s->cur_prompt->prompts[0]->result, + lenof(s->username)); + free_prompts(s->cur_prompt); } else { strncpy(s->username, ssh->cfg.username, sizeof(s->username)); s->username[sizeof(s->username)-1] = '\0'; @@ -3233,14 +3158,14 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, send_packet(ssh, SSH1_CMSG_USER, PKT_STR, s->username, PKT_END); { - char userlog[22 + sizeof(s->username)]; - sprintf(userlog, "Sent username \"%s\"", s->username); + char *userlog = dupprintf("Sent username \"%s\"", s->username); logevent(userlog); if (flags & FLAG_INTERACTIVE && (!((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)))) { - strcat(userlog, "\r\n"); c_write_str(ssh, userlog); + c_write_str(ssh, "\r\n"); } + sfree(userlog); } } @@ -3253,12 +3178,44 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, s->tried_publickey = s->tried_agent = 0; } s->tis_auth_refused = s->ccard_auth_refused = 0; - /* Load the public half of ssh->cfg.keyfile so we notice if it's in Pageant */ + /* + * Load the public half of any configured keyfile for later use. + */ if (!filename_is_null(ssh->cfg.keyfile)) { - if (!rsakey_pubblob(&ssh->cfg.keyfile, - &s->publickey_blob, &s->publickey_bloblen, - NULL, NULL)) + int keytype; + logeventf(ssh, "Reading private key file \"%.150s\"", + filename_to_str(&ssh->cfg.keyfile)); + keytype = key_type(&ssh->cfg.keyfile); + if (keytype == SSH_KEYTYPE_SSH1) { + const char *error; + if (rsakey_pubblob(&ssh->cfg.keyfile, + &s->publickey_blob, &s->publickey_bloblen, + &s->publickey_comment, &error)) { + s->publickey_encrypted = rsakey_encrypted(&ssh->cfg.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(&ssh->cfg.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(&ssh->cfg.keyfile), + key_type_to_str(keytype)); + c_write_str(ssh, msgbuf); + sfree(msgbuf); s->publickey_blob = NULL; + } } else s->publickey_blob = NULL; @@ -3432,8 +3389,148 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, if (s->authed) break; } - if (!filename_is_null(ssh->cfg.keyfile) && !s->tried_publickey) - s->pwpkt_type = SSH1_CMSG_AUTH_RSA; + 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"); + logeventf(ssh, "Trying public key \"%s\"", + filename_to_str(&ssh->cfg.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, SSH_MAX_PASSWORD_LEN); + 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. + */ + ret = loadrsakey(&ssh->cfg.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(&ssh->cfg.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 */ + s->tried_publickey = 0; + got_passphrase = FALSE; + /* and try again */ + } + } + + 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 password */ + } + 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 password */ + } 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 (ssh->cfg.try_tis_auth && (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) && @@ -3451,23 +3548,31 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, } 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); } - c_write_str(ssh, "Using TIS authentication.\r\n"); logevent("Received TIS challenge"); - if (challengelen > sizeof(s->prompt) - 1) - challengelen = sizeof(s->prompt) - 1;/* prevent overrun */ - memcpy(s->prompt, challenge, challengelen); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH TIS authentication"); /* Prompt heuristic comes from OpenSSH */ - strncpy(s->prompt + challengelen, - memchr(s->prompt, '\n', challengelen) ? - "": "\r\nResponse: ", - (sizeof s->prompt) - challengelen); - s->prompt[(sizeof s->prompt) - 1] = '\0'; + 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, SSH_MAX_PASSWORD_LEN); + sfree(instr_suf); } } if (ssh->cfg.try_tis_auth && @@ -3485,53 +3590,40 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, } 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); } - c_write_str(ssh, "Using CryptoCard authentication.\r\n"); logevent("Received CryptoCard challenge"); - if (challengelen > sizeof(s->prompt) - 1) - challengelen = sizeof(s->prompt) - 1;/* prevent overrun */ - memcpy(s->prompt, challenge, challengelen); - strncpy(s->prompt + challengelen, - memchr(s->prompt, '\n', challengelen) ? - "" : "\r\nResponse: ", - sizeof(s->prompt) - challengelen); - s->prompt[sizeof(s->prompt) - 1] = '\0'; + 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, SSH_MAX_PASSWORD_LEN); + sfree(instr_suf); } } if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { - sprintf(s->prompt, "%.90s@%.90s's password: ", - s->username, ssh->savedhost); - } - if (s->pwpkt_type == SSH1_CMSG_AUTH_RSA) { - char *comment = NULL; - int type; - if (flags & FLAG_VERBOSE) - c_write_str(ssh, "Trying public key authentication.\r\n"); - logeventf(ssh, "Trying public key \"%s\"", - filename_to_str(&ssh->cfg.keyfile)); - type = key_type(&ssh->cfg.keyfile); - if (type != SSH_KEYTYPE_SSH1) { - char *msg = dupprintf("Key is of wrong type (%s)", - key_type_to_str(type)); - logevent(msg); - c_write_str(ssh, msg); - c_write_str(ssh, "\r\n"); - sfree(msg); - s->tried_publickey = 1; - continue; - } - if (!rsakey_encrypted(&ssh->cfg.keyfile, &comment)) { - if (flags & FLAG_VERBOSE) - c_write_str(ssh, "No passphrase required.\r\n"); - goto tryauth; - } - sprintf(s->prompt, "Passphrase for key \"%.100s\": ", comment); - sfree(comment); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("SSH password"); + add_prompt(s->cur_prompt, dupprintf("%.90s@%.90s's password: ", + s->username, ssh->savedhost), + FALSE, SSH_MAX_PASSWORD_LEN); } /* @@ -3539,245 +3631,155 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, * or CryptoCard exchange if we're doing TIS or CryptoCard * authentication. */ - if (ssh_get_line) { - if (!ssh_get_line(s->prompt, s->password, - sizeof(s->password), TRUE)) { + { + 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) { /* - * get_line failed to get a password (for example + * Failed to get a password (for example * because one was supplied on the command line * which has already failed to work). Terminate. */ - ssh_disconnect(ssh, NULL, "Unable to authenticate", 0, FALSE); - crStop(1); + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, NULL, "Unable to authenticate", 0, TRUE); + crStop(0); } - } else { - /* Prompt may have come from server. We've munged it a bit, so - * we know it to be zero-terminated at least once. */ - int ret; /* need not be saved over crReturn */ - c_write_untrusted(ssh, s->prompt, strlen(s->prompt)); - s->pos = 0; - - setup_userpass_input(ssh, s->password, sizeof(s->password), 0); - do { - crWaitUntil(!pktin); - ret = process_userpass_input(ssh, in, inlen); - } while (ret == 0); - if (ret < 0) - cleanup_exit(0); - c_write_str(ssh, "\r\n"); } - tryauth: - if (s->pwpkt_type == SSH1_CMSG_AUTH_RSA) { + if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { /* - * Try public key authentication with the specified - * key file. + * 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 (the old 1.2.18 through 1.2.22) + * can't deal with SSH1_MSG_IGNORE. 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. + * + * One server (a Cisco one) can deal with neither + * SSH1_MSG_IGNORE _nor_ a padded password string. + * For this server we are left with no defences + * against password length sniffing. */ - s->tried_publickey = 1; - - { - const char *error = NULL; - int ret = loadrsakey(&ssh->cfg.keyfile, &s->key, s->password, - &error); - if (ret == 0) { - c_write_str(ssh, "Couldn't load private key from "); - c_write_str(ssh, filename_to_str(&ssh->cfg.keyfile)); - c_write_str(ssh, " ("); - c_write_str(ssh, error); - c_write_str(ssh, ").\r\n"); - continue; /* go and try password */ - } - if (ret == -1) { - c_write_str(ssh, "Wrong passphrase.\r\n"); - s->tried_publickey = 0; - continue; /* try again */ - } - } - - /* - * 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 password */ - } - 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 password */ - } else if (pktin->type != SSH1_SMSG_SUCCESS) { - bombout(("Bizarre response to RSA authentication response")); - crStop(0); - } - - break; /* we're through! */ - } else { - if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { + if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) { /* - * 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 (the old 1.2.18 through 1.2.22) - * can't deal with SSH1_MSG_IGNORE. 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. - * - * One server (a Cisco one) can deal with neither - * SSH1_MSG_IGNORE _nor_ a padded password string. - * For this server we are left with no defences - * against password length sniffing. + * The server can deal with SSH1_MSG_IGNORE, so + * we can use the primary defence. */ - if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) { - /* - * The server can deal with SSH1_MSG_IGNORE, so - * we can use the primary defence. - */ - int bottom, top, pwlen, i; - char *randomstr; + int bottom, top, pwlen, i; + char *randomstr; - pwlen = strlen(s->password); - 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->password, - 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->password); - if (len < sizeof(string)) { - ss = string; - strcpy(string, s->password); - len++; /* cover the zero byte */ - while (len < sizeof(string)) { - string[len++] = (char) random_byte(); - } - } else { - ss = s->password; - } - logevent("Sending length-padded password"); - send_packet(ssh, s->pwpkt_type, PKTT_PASSWORD, - PKT_INT, len, PKT_DATA, ss, len, - PKTT_OTHER, PKT_END); + pwlen = strlen(s->cur_prompt->prompts[0]->result); + if (pwlen < 16) { + bottom = 0; /* zero length passwords are OK! :-) */ + top = 15; } else { - /* - * The server has _both_ - * BUG_CHOKES_ON_SSH1_IGNORE and - * BUG_NEEDS_SSH1_PLAIN_PASSWORD. There is - * therefore nothing we can do. - */ - int len; - len = strlen(s->password); - logevent("Sending unpadded password"); - send_packet(ssh, s->pwpkt_type, - PKTT_PASSWORD, PKT_INT, len, - PKT_DATA, s->password, len, - PKTT_OTHER, PKT_END); + bottom = pwlen & ~7; + top = bottom + 7; } - } else { + + 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_STR, s->password, PKTT_OTHER, PKT_END); + PKT_INT, len, PKT_DATA, ss, len, + PKTT_OTHER, PKT_END); + } else { + /* + * The server has _both_ + * BUG_CHOKES_ON_SSH1_IGNORE and + * BUG_NEEDS_SSH1_PLAIN_PASSWORD. There is + * therefore nothing we can do. + */ + 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"); - memset(s->password, 0, strlen(s->password)); + free_prompts(s->cur_prompt); crWaitUntil(pktin); if (pktin->type == SSH1_SMSG_FAILURE) { if (flags & FLAG_VERBOSE) @@ -3789,6 +3791,12 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, } } + /* Clear up */ + if (s->publickey_blob) { + sfree(s->publickey_blob); + sfree(s->publickey_comment); + } + logevent("Authentication successful"); crFinish(1); @@ -6482,15 +6490,17 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int done_service_req; int gotit, need_pw, can_pubkey, can_passwd, can_keyb_inter; int tried_pubkey_config, tried_agent; - int kbd_inter_running, kbd_inter_refused; + int kbd_inter_refused; int we_are_in; - int num_prompts, curr_prompt, echo; + prompts_t *cur_prompt; + int num_prompts; char username[100]; int got_username; - char pwprompt[512]; - char password[100]; void *publickey_blob; int publickey_bloblen; + int publickey_encrypted; + char *publickey_algorithm; + char *publickey_comment; unsigned char request[5], *response, *p; int responselen; int keyi, nkeys; @@ -6536,6 +6546,58 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, } } + /* + * 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. + */ + if (!filename_is_null(ssh->cfg.keyfile)) { + int keytype; + logeventf(ssh, "Reading private key file \"%.150s\"", + filename_to_str(&ssh->cfg.keyfile)); + keytype = key_type(&ssh->cfg.keyfile); + if (keytype == SSH_KEYTYPE_SSH2) { + const char *error; + s->publickey_blob = + ssh2_userkey_loadpub(&ssh->cfg.keyfile, + &s->publickey_algorithm, + &s->publickey_bloblen, + &s->publickey_comment, &error); + if (s->publickey_blob) { + s->publickey_encrypted = + ssh2_userkey_encrypted(&ssh->cfg.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(&ssh->cfg.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(&ssh->cfg.keyfile), + key_type_to_str(keytype)); + c_write_str(ssh, msgbuf); + sfree(msgbuf); + s->publickey_blob = NULL; + } + } + + } + /* * We repeat this whole loop, including the username prompt, * until we manage a successful authentication. If the user @@ -6576,30 +6638,31 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, * it again. */ } else if (!*ssh->cfg.username) { - if (ssh_get_line && !ssh_getline_pw_only) { - if (!ssh_get_line("login as: ", - s->username, sizeof(s->username), FALSE)) { - /* - * get_line failed to get a username. - * Terminate. - */ - ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE); - crStopV; - } - } else { - int ret; /* need not be saved across crReturn */ - c_write_str(ssh, "login as: "); + 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, + lenof(s->username)); + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { ssh->send_ok = 1; - setup_userpass_input(ssh, s->username, sizeof(s->username), 1); - do { - crWaitUntilV(!pktin); - ret = process_userpass_input(ssh, in, inlen); - } while (ret == 0); - if (ret < 0) - cleanup_exit(0); - c_write_str(ssh, "\r\n"); + crWaitUntilV(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; } - s->username[strcspn(s->username, "\n\r")] = '\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; + } + memcpy(s->username, s->cur_prompt->prompts[0]->result, + lenof(s->username)); + free_prompts(s->cur_prompt); } else { char *stuff; strncpy(s->username, ssh->cfg.username, sizeof(s->username)); @@ -6630,32 +6693,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, s->tried_pubkey_config = FALSE; s->tried_agent = FALSE; - s->kbd_inter_running = FALSE; s->kbd_inter_refused = FALSE; - /* Load the pub half of ssh->cfg.keyfile so we notice if it's in Pageant */ - if (!filename_is_null(ssh->cfg.keyfile)) { - int keytype; - logeventf(ssh, "Reading private key file \"%.150s\"", - filename_to_str(&ssh->cfg.keyfile)); - keytype = key_type(&ssh->cfg.keyfile); - if (keytype == SSH_KEYTYPE_SSH2) { - s->publickey_blob = - ssh2_userkey_loadpub(&ssh->cfg.keyfile, NULL, - &s->publickey_bloblen, NULL, 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(&ssh->cfg.keyfile), - key_type_to_str(keytype)); - c_write_str(ssh, msgbuf); - sfree(msgbuf); - s->publickey_blob = NULL; - } - } else - s->publickey_blob = NULL; while (1) { /* @@ -6693,18 +6731,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, break; } - if (s->kbd_inter_running && - pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { - /* - * This is either a further set-of-prompts packet - * in keyboard-interactive authentication, or it's - * the same one and we came back here with `gotit' - * set. In the former case, we must reset the - * curr_prompt variable. - */ - if (!s->gotit) - s->curr_prompt = 0; - } else if (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) { + if (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) { /* FIXME: perhaps we should support this? */ bombout(("PASSWD_CHANGEREQ not yet supported")); crStopV; @@ -6725,7 +6752,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, char *methods; int methlen; ssh_pkt_getstring(pktin, &methods, &methlen); - s->kbd_inter_running = FALSE; if (!ssh2_pkt_getbool(pktin)) { /* * We have received an unequivocal Access @@ -6782,20 +6808,10 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, in_commasep_string("keyboard-interactive", methods, methlen); } - s->method = 0; ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; - s->need_pw = FALSE; - /* - * Most password/passphrase prompts will be - * non-echoing, so we set this to 0 by default. - * Exception is that some keyboard-interactive prompts - * can be echoing, in which case we'll set this to 1. - */ - s->echo = 0; + if (s->can_pubkey && agent_exists() && !s->tried_agent) { - if (!s->method && s->can_pubkey && - agent_exists() && !s->tried_agent) { /* * Attempt public-key authentication using Pageant. */ @@ -6960,234 +6976,127 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, continue; } sfree(s->response); - } - if (!s->method && s->can_pubkey && s->publickey_blob - && !s->tried_pubkey_config) { - unsigned char *pub_blob; - char *algorithm, *comment; - int pub_blob_len; + } else if (s->can_pubkey && s->publickey_blob && + !s->tried_pubkey_config) { - s->tried_pubkey_config = TRUE; + struct ssh2_userkey *key; /* not live over crReturn */ + char *passphrase; /* not live over crReturn */ ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; ssh->pkt_ctx |= 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. */ - pub_blob = - (unsigned char *)ssh2_userkey_loadpub(&ssh->cfg.keyfile, - &algorithm, - &pub_blob_len, - NULL, NULL); - if (pub_blob) { - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, s->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, algorithm); - ssh2_pkt_addstring_start(s->pktout); - ssh2_pkt_addstring_data(s->pktout, (char *)pub_blob, - pub_blob_len); - ssh2_pkt_send(ssh, s->pktout); - logevent("Offered public key"); - - crWaitUntilV(pktin); - if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) { - s->gotit = TRUE; - s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD; - continue; /* key refused; give up on it */ - } - - logevent("Offer of public key accepted"); - /* - * Actually attempt a serious authentication using - * the key. - */ - if (ssh2_userkey_encrypted(&ssh->cfg.keyfile, &comment)) { - sprintf(s->pwprompt, - "Passphrase for key \"%.100s\": ", - comment); - s->need_pw = TRUE; - } else { - s->need_pw = FALSE; - } - if (flags & FLAG_VERBOSE) { - c_write_str(ssh, "Authenticating with public key \""); - c_write_str(ssh, comment); - c_write_str(ssh, "\"\r\n"); - } - s->method = AUTH_PUBLICKEY_FILE; - } - } - - if (!s->method && s->can_keyb_inter && !s->kbd_inter_refused && - !s->kbd_inter_running) { - s->method = AUTH_KEYBOARD_INTERACTIVE; - s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; - - ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; - ssh->pkt_ctx |= SSH2_PKTCTX_KBDINTER; - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); ssh2_pkt_addstring(s->pktout, s->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, ""); + 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_INFO_REQUEST) { - if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) - s->gotit = TRUE; - logevent("Keyboard-interactive authentication refused"); - s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET; - s->kbd_inter_refused = TRUE; /* don't try it again */ - continue; - } - - c_write_str(ssh, "Using keyboard-interactive authentication.\r\n"); - s->kbd_inter_running = TRUE; - s->curr_prompt = 0; - } - - if (s->kbd_inter_running) { - s->method = AUTH_KEYBOARD_INTERACTIVE; - s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; - - ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; - ssh->pkt_ctx |= SSH2_PKTCTX_KBDINTER; - - if (s->curr_prompt == 0) { - /* - * We've got a fresh USERAUTH_INFO_REQUEST. - * Display header data, and start going through - * the prompts. - */ - char *name, *inst, *lang; - int name_len, inst_len, lang_len; - - ssh_pkt_getstring(pktin, &name, &name_len); - ssh_pkt_getstring(pktin, &inst, &inst_len); - ssh_pkt_getstring(pktin, &lang, &lang_len); - if (name_len > 0) { - c_write_untrusted(ssh, name, name_len); - c_write_str(ssh, "\r\n"); - } - if (inst_len > 0) { - c_write_untrusted(ssh, inst, inst_len); - c_write_str(ssh, "\r\n"); - } - s->num_prompts = ssh_pkt_getuint32(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 */ } + s->method = AUTH_PUBLICKEY_FILE; + logevent("Offer of public key accepted"); /* - * If there are prompts remaining in the packet, - * display one and get a response. + * Actually attempt a serious authentication using + * the key. */ - if (s->curr_prompt < s->num_prompts) { - char *prompt; - int prompt_len; - - ssh_pkt_getstring(pktin, &prompt, &prompt_len); - if (prompt_len > 0) { - static const char trunc[] = ": "; - static const int prlen = sizeof(s->pwprompt) - - lenof(trunc); - if (prompt_len > prlen) { - memcpy(s->pwprompt, prompt, prlen); - strcpy(s->pwprompt + prlen, trunc); - } else { - memcpy(s->pwprompt, prompt, prompt_len); - s->pwprompt[prompt_len] = '\0'; - } - } else { - strcpy(s->pwprompt, - ": "); - } - s->echo = ssh2_pkt_getbool(pktin); - s->need_pw = TRUE; - } else - s->need_pw = FALSE; - } - - if (!s->method && s->can_passwd) { - s->method = AUTH_PASSWORD; - ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; - ssh->pkt_ctx |= SSH2_PKTCTX_PASSWORD; - sprintf(s->pwprompt, "%.90s@%.90s's password: ", s->username, - ssh->savedhost); - s->need_pw = TRUE; - } - - if (s->need_pw) { - if (ssh_get_line) { - if (!ssh_get_line(s->pwprompt, s->password, - sizeof(s->password), TRUE)) { + 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_line failed to get a password (for - * example because one was supplied on the - * command line which has already failed to - * work). Terminate. + * Get a passphrase from the user. */ - ssh_disconnect(ssh, NULL, "Unable to authenticate", - SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, - FALSE); - crStopV; - } - } else { - int ret; /* need not be saved across crReturn */ - c_write_untrusted(ssh, s->pwprompt, strlen(s->pwprompt)); - ssh->send_ok = 1; - - setup_userpass_input(ssh, s->password, - sizeof(s->password), s->echo); - do { - crWaitUntilV(!pktin); - ret = process_userpass_input(ssh, in, inlen); - } while (ret == 0); - if (ret < 0) - cleanup_exit(0); - c_write_str(ssh, "\r\n"); - } - } - - if (s->method == AUTH_PUBLICKEY_FILE) { - /* - * We have our passphrase. Now try the actual authentication. - */ - struct ssh2_userkey *key; - const char *error = NULL; - - key = ssh2_load_userkey(&ssh->cfg.keyfile, s->password, - &error); - if (key == SSH2_WRONG_PASSPHRASE || key == NULL) { - if (key == SSH2_WRONG_PASSPHRASE) { - c_write_str(ssh, "Wrong passphrase\r\n"); - s->tried_pubkey_config = FALSE; + 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, SSH_MAX_PASSWORD_LEN); + 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 { - c_write_str(ssh, "Unable to load private key ("); - c_write_str(ssh, error); - c_write_str(ssh, ")\r\n"); - s->tried_pubkey_config = TRUE; + passphrase = NULL; /* no passphrase needed */ } - /* Send a spurious AUTH_NONE to return to the top. */ - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, s->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; - } else { + + /* + * Try decrypting the key. + */ + key = ssh2_load_userkey(&ssh->cfg.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; + int p; /* * We have loaded the private key and the server @@ -7196,13 +7105,18 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, */ s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); ssh2_pkt_addstring(s->pktout, s->username); - ssh2_pkt_addstring(s->pktout, "ssh-connection"); /* service requested */ - ssh2_pkt_addstring(s->pktout, "publickey"); /* method */ + 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); + 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); + ssh2_pkt_addstring_data(s->pktout, (char *)pkblob, + pkblob_len); /* * The data to be signed is: @@ -7214,21 +7128,21 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, */ sigdata_len = s->pktout->length - 5 + 4 + ssh->v2_session_id_len; - if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID) - sigdata_len -= 4; + 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; - } + 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); + 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, @@ -7241,8 +7155,169 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, s->type = AUTH_TYPE_PUBLICKEY; key->alg->freekey(key->data); } - } else if (s->method == AUTH_PASSWORD) { + + } else if (s->can_keyb_inter && !s->kbd_inter_refused) { + /* + * Keyboard-interactive authentication. + */ + char *name, *inst, *lang; + int name_len, inst_len, lang_len; + int i; + + s->method = AUTH_KEYBOARD_INTERACTIVE; + s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; + + ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; + ssh->pkt_ctx |= SSH2_PKTCTX_KBDINTER; + + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + ssh2_pkt_addstring(s->pktout, s->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); + + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_USERAUTH_INFO_REQUEST) { + /* Server is not willing to do keyboard-interactive + * at all. Give up on it entirely. */ + s->gotit = TRUE; + if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) + logevent("Keyboard-interactive authentication refused"); + s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET; + s->kbd_inter_refused = TRUE; /* don't try it again */ + continue; + } + + /* + * 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; + 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; + } + s->cur_prompt->instruction = + dupprintf("Using keyboard-interactive authentication.%s%.*s", + inst_len ? "\n" : "", inst_len, inst); + s->cur_prompt->instr_reqd = TRUE; + + /* + * Get the prompts 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, SSH_MAX_PASSWORD_LEN); + } + + /* + * Get the user's responses. + */ + if (s->num_prompts) { + 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 responses to the server. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE); + s->pktout->forcepad = 256; + 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(ssh, s->pktout); + s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; /*FIXME?*/ + + } else if (s->can_passwd) { + + /* + * Plain old password authentication. + */ + int ret; /* not live over crReturn */ + + s->method = AUTH_PASSWORD; + ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; + ssh->pkt_ctx |= 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("%.90s@%.90s's password: ", + s->username, + ssh->savedhost), + FALSE, SSH_MAX_PASSWORD_LEN); + + 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 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. @@ -7254,54 +7329,39 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); s->pktout->forcepad = 256; ssh2_pkt_addstring(s->pktout, s->username); - ssh2_pkt_addstring(s->pktout, "ssh-connection"); /* service requested */ + 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); - memset(s->password, 0, sizeof(s->password)); + ssh2_pkt_addstring(s->pktout, + s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); end_log_omission(ssh, s->pktout); ssh2_pkt_send(ssh, s->pktout); logevent("Sent password"); - s->type = AUTH_TYPE_PASSWORD; - } else if (s->method == AUTH_KEYBOARD_INTERACTIVE) { - if (s->curr_prompt == 0) { - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE); - s->pktout->forcepad = 256; - ssh2_pkt_adduint32(s->pktout, s->num_prompts); - } - if (s->need_pw) { /* only add pw if we just got one! */ - dont_log_password(ssh, s->pktout, PKTLOG_BLANK); - ssh2_pkt_addstring(s->pktout, s->password); - memset(s->password, 0, sizeof(s->password)); - end_log_omission(ssh, s->pktout); - s->curr_prompt++; - } - if (s->curr_prompt >= s->num_prompts) { - ssh2_pkt_send(ssh, s->pktout); - } else { - /* - * If there are prompts remaining, we set - * `gotit' so that we won't attempt to get - * another packet. Then we go back round the - * loop and will end up retrieving another - * prompt out of the existing packet. Funky or - * what? - */ - s->gotit = TRUE; - } - s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; + s->type = AUTH_TYPE_PASSWORD; /*FIXME?*/ + } else { + ssh_disconnect(ssh, NULL, "No supported authentication methods available", SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, FALSE); 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); + } + /* * Now the connection protocol has started, one way or another. */ diff --git a/terminal.c b/terminal.c index ad9acc70..d712fc90 100644 --- a/terminal.c +++ b/terminal.c @@ -6257,6 +6257,24 @@ int term_data(Terminal *term, int is_stderr, const char *data, int len) return 0; } +/* + * Write untrusted data to the terminal. + * The only control character that should be honoured is \n (which + * will behave as a CRLF). + */ +int term_data_untrusted(Terminal *term, const char *data, int len) +{ + int i; + /* FIXME: more sophisticated checking? */ + for (i = 0; i < len; i++) { + if (data[i] == '\n') + term_data(term, 1, "\r\n", 2); + else if (data[i] & 0x60) + term_data(term, 1, data + i, 1); + } + return 0; /* assumes that term_data() always returns 0 */ +} + void term_provide_logctx(Terminal *term, void *logctx) { term->logctx = logctx; @@ -6281,3 +6299,128 @@ char *term_get_ttymode(Terminal *term, const char *mode) /* FIXME: perhaps we should set ONLCR based on cfg.lfhascr as well? */ return dupstr(val); } + +struct term_userpass_state { + size_t curr_prompt; + int done_prompt; /* printed out prompt yet? */ + size_t pos; /* cursor position */ +}; + +/* + * Process some terminal data in the course of username/password + * input. + */ +int term_get_userpass_input(Terminal *term, prompts_t *p, + unsigned char *in, int inlen) +{ + struct term_userpass_state *s = (struct term_userpass_state *)p->data; + if (!s) { + /* + * First call. Set some stuff up. + */ + p->data = s = snew(struct term_userpass_state); + s->curr_prompt = 0; + s->done_prompt = 0; + /* We only print the `name' caption if we have to... */ + if (p->name_reqd && p->name) { + size_t l = strlen(p->name); + term_data_untrusted(term, p->name, l); + if (p->name[l-1] != '\n') + term_data_untrusted(term, "\n", 1); + } + /* ...but we always print any `instruction'. */ + if (p->instruction) { + size_t l = strlen(p->instruction); + term_data_untrusted(term, p->instruction, l); + if (p->instruction[l-1] != '\n') + term_data_untrusted(term, "\n", 1); + } + /* + * Zero all the results, in case we abort half-way through. + */ + { + int i; + for (i = 0; i < p->n_prompts; i++) + memset(p->prompts[i]->result, 0, p->prompts[i]->result_len); + } + } + + while (s->curr_prompt < p->n_prompts) { + + prompt_t *pr = p->prompts[s->curr_prompt]; + int finished_prompt = 0; + + if (!s->done_prompt) { + term_data_untrusted(term, pr->prompt, strlen(pr->prompt)); + s->done_prompt = 1; + s->pos = 0; + } + + /* Breaking out here ensures that the prompt is printed even + * if we're now waiting for user data. */ + if (!in || !inlen) break; + + /* FIXME: should we be using local-line-editing code instead? */ + while (!finished_prompt && inlen) { + char c = *in++; + inlen--; + switch (c) { + case 10: + case 13: + term_data(term, 0, "\r\n", 2); + pr->result[s->pos] = '\0'; + pr->result[pr->result_len - 1] = '\0'; + /* go to next prompt, if any */ + s->curr_prompt++; + s->done_prompt = 0; + finished_prompt = 1; /* break out */ + break; + case 8: + case 127: + if (s->pos > 0) { + if (pr->echo) + term_data(term, 0, "\b \b", 3); + s->pos--; + } + break; + case 21: + case 27: + while (s->pos > 0) { + if (pr->echo) + term_data(term, 0, "\b \b", 3); + s->pos--; + } + break; + case 3: + case 4: + /* Immediate abort. */ + term_data(term, 0, "\r\n", 2); + sfree(s); + return 0; /* user abort */ + default: + /* + * This simplistic check for printability is disabled + * when we're doing password input, because some people + * have control characters in their passwords. + */ + if ((!pr->echo || + (c >= ' ' && c <= '~') || + ((unsigned char) c >= 160)) + && s->pos < pr->result_len - 1) { + pr->result[s->pos++] = c; + if (pr->echo) + term_data(term, 0, &c, 1); + } + break; + } + } + + } + + if (s->curr_prompt < p->n_prompts) { + return -1; /* more data required */ + } else { + sfree(s); + return +1; /* all done */ + } +} diff --git a/unix/gtkwin.c b/unix/gtkwin.c index 6818fff8..6193a04d 100644 --- a/unix/gtkwin.c +++ b/unix/gtkwin.c @@ -189,6 +189,22 @@ int from_backend(void *frontend, int is_stderr, const char *data, int len) return term_data(inst->term, is_stderr, data, len); } +int from_backend_untrusted(void *frontend, const char *data, int len) +{ + struct gui_data *inst = (struct gui_data *)frontend; + return term_data_untrusted(inst->term, data, len); +} + +int get_userpass_input(prompts_t *p, unsigned char *in, int inlen) +{ + struct gui_data *inst = (struct gui_data *)p->frontend; + int ret; + ret = cmdline_get_passwd_input(p, in, inlen); + if (ret == -1) + ret = term_get_userpass_input(inst->term, p, in, inlen); + return ret; +} + void logevent(void *frontend, const char *string) { struct gui_data *inst = (struct gui_data *)frontend; diff --git a/unix/uxcons.c b/unix/uxcons.c index 7652e23d..d4848a6a 100644 --- a/unix/uxcons.c +++ b/unix/uxcons.c @@ -280,41 +280,81 @@ void logevent(void *frontend, const char *string) log_eventlog(console_logctx, string); } -int console_get_line(const char *prompt, char *str, - int maxlen, int is_pw) +static void console_data_untrusted(const char *data, int len) { - struct termios oldmode, newmode; int i; + for (i = 0; i < len; i++) + if ((data[i] & 0x60) || (data[i] == '\n')) + fputc(data[i], stdout); + fflush(stdout); +} - if (console_batch_mode) { - if (maxlen > 0) - str[0] = '\0'; +int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen) +{ + size_t curr_prompt; + + /* + * Zero all the results, in case we abort half-way through. + */ + { + int i; + for (i = 0; i < p->n_prompts; i++) + memset(p->prompts[i]->result, 0, p->prompts[i]->result_len); + } + + if (console_batch_mode) return 0; - } else { + + /* + * 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(p->name, l); + if (p->name[l-1] != '\n') + console_data_untrusted("\n", 1); + } + /* ...but we always print any `instruction'. */ + if (p->instruction) { + size_t l = strlen(p->instruction); + console_data_untrusted(p->instruction, l); + if (p->instruction[l-1] != '\n') + console_data_untrusted("\n", 1); + } + + for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) { + + struct termios oldmode, newmode; + int i; + prompt_t *pr = p->prompts[curr_prompt]; + tcgetattr(0, &oldmode); newmode = oldmode; newmode.c_lflag |= ISIG | ICANON; - if (is_pw) + if (!pr->echo) newmode.c_lflag &= ~ECHO; else newmode.c_lflag |= ECHO; tcsetattr(0, TCSANOW, &newmode); - fputs(prompt, stdout); - fflush(stdout); - i = read(0, str, maxlen - 1); + console_data_untrusted(pr->prompt, strlen(pr->prompt)); + + i = read(0, pr->result, pr->result_len - 1); tcsetattr(0, TCSANOW, &oldmode); - if (i > 0 && str[i-1] == '\n') + if (i > 0 && pr->result[i-1] == '\n') i--; - str[i] = '\0'; + pr->result[i] = '\0'; - if (is_pw) + if (!pr->echo) fputs("\n", stdout); - return 1; } + + return 1; /* success */ + } void frontend_keypress(void *handle) diff --git a/unix/uxplink.c b/unix/uxplink.c index 13dea441..90b151f7 100644 --- a/unix/uxplink.c +++ b/unix/uxplink.c @@ -394,6 +394,25 @@ int from_backend(void *frontend_handle, int is_stderr, return osize + esize; } +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 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; +} + /* * Handle data from a local tty in PARMRK format. */ @@ -534,8 +553,6 @@ int main(int argc, char **argv) void *ldisc, *logctx; long now; - ssh_get_line = console_get_line; - fdlist = NULL; fdcount = fdsize = 0; /* diff --git a/unix/uxsftp.c b/unix/uxsftp.c index 3691cacc..0779ff18 100644 --- a/unix/uxsftp.c +++ b/unix/uxsftp.c @@ -70,6 +70,15 @@ Filename platform_default_filename(const char *name) char *get_ttymode(void *frontend, const char *mode) { return NULL; } +int get_userpass_input(prompts_t *p, unsigned char *in, int inlen) +{ + int ret; + ret = cmdline_get_passwd_input(p, in, inlen); + if (ret == -1) + ret = console_get_userpass_input(p, in, inlen); + return ret; +} + /* * Stubs for the GUI feedback mechanism in Windows PSCP. */ diff --git a/windows/wincons.c b/windows/wincons.c index c38fa011..e0f0215f 100644 --- a/windows/wincons.c +++ b/windows/wincons.c @@ -298,48 +298,90 @@ void logevent(void *frontend, const char *string) log_eventlog(console_logctx, string); } -int console_get_line(const char *prompt, char *str, - int maxlen, int is_pw) +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; - DWORD savemode, newmode, i; + size_t curr_prompt; - if (console_batch_mode) { - if (maxlen > 0) - str[0] = '\0'; + /* + * Zero all the results, in case we abort half-way through. + */ + { + int i; + for (i = 0; i < p->n_prompts; i++) + memset(p->prompts[i]->result, 0, p->prompts[i]->result_len); + } + + if (console_batch_mode) return 0; - } else { - hin = GetStdHandle(STD_INPUT_HANDLE); - hout = GetStdHandle(STD_OUTPUT_HANDLE); - if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) { - fprintf(stderr, "Cannot get standard input/output handles\n"); - cleanup_exit(1); - } + + hin = GetStdHandle(STD_INPUT_HANDLE); + hout = GetStdHandle(STD_OUTPUT_HANDLE); + if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) { + fprintf(stderr, "Cannot get standard input/output handles\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, i = 0; + prompt_t *pr = p->prompts[curr_prompt]; + BOOL r; GetConsoleMode(hin, &savemode); newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT; - if (is_pw) + if (!pr->echo) newmode &= ~ENABLE_ECHO_INPUT; else newmode |= ENABLE_ECHO_INPUT; SetConsoleMode(hin, newmode); - WriteFile(hout, prompt, strlen(prompt), &i, NULL); - ReadFile(hin, str, maxlen - 1, &i, NULL); + console_data_untrusted(hout, pr->prompt, strlen(pr->prompt)); + + r = ReadFile(hin, pr->result, pr->result_len - 1, &i, NULL); SetConsoleMode(hin, savemode); - if ((int) i > maxlen) - i = maxlen - 1; + if ((int) i > pr->result_len) + i = pr->result_len - 1; else i = i - 2; - str[i] = '\0'; + pr->result[i] = '\0'; - if (is_pw) - WriteFile(hout, "\r\n", 2, &i, NULL); + if (!pr->echo) { + DWORD dummy; + WriteFile(hout, "\r\n", 2, &dummy, NULL); + } - return 1; } + + return 1; /* success */ + } void frontend_keypress(void *handle) diff --git a/windows/window.c b/windows/window.c index 0bd800f6..43a5e24c 100644 --- a/windows/window.c +++ b/windows/window.c @@ -5069,6 +5069,20 @@ int from_backend(void *frontend, int is_stderr, const char *data, int len) return term_data(term, is_stderr, data, len); } +int from_backend_untrusted(void *frontend, const char *data, int len) +{ + return term_data_untrusted(term, data, len); +} + +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 = term_get_userpass_input(term, p, in, inlen); + return ret; +} + void agent_schedule_callback(void (*callback)(void *, void *, int), void *callback_ctx, void *data, int len) { diff --git a/windows/winplink.c b/windows/winplink.c index a67a2375..f6596ee3 100644 --- a/windows/winplink.c +++ b/windows/winplink.c @@ -189,6 +189,25 @@ int from_backend(void *frontend_handle, int is_stderr, return osize + esize; } +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 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), @@ -285,8 +304,6 @@ int main(int argc, char **argv) int use_subsystem = 0; long now, next; - ssh_get_line = console_get_line; - sklist = NULL; skcount = sksize = 0; /* diff --git a/windows/winsftp.c b/windows/winsftp.c index 6eecb313..47d3c437 100644 --- a/windows/winsftp.c +++ b/windows/winsftp.c @@ -127,6 +127,15 @@ void gui_enable(char *arg) char *get_ttymode(void *frontend, const char *mode) { return NULL; } +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; +} + /* ---------------------------------------------------------------------- * File access abstraction. */