diff options
Diffstat (limited to 'windows/console.c')
-rw-r--r-- | windows/console.c | 496 |
1 files changed, 496 insertions, 0 deletions
diff --git a/windows/console.c b/windows/console.c new file mode 100644 index 00000000..2fd572b4 --- /dev/null +++ b/windows/console.c @@ -0,0 +1,496 @@ +/* + * console.c - various interactive-prompt routines shared between + * the Windows console PuTTY tools + */ + +#include <stdio.h> +#include <stdlib.h> + +#include "putty.h" +#include "storage.h" +#include "ssh.h" +#include "console.h" + +void cleanup_exit(int code) +{ + /* + * Clean up. + */ + sk_cleanup(); + + random_save_seed(); + + exit(code); +} + +void console_print_error_msg(const char *prefix, const char *msg) +{ + fputs(prefix, stderr); + fputs(": ", stderr); + fputs(msg, stderr); + fputc('\n', stderr); + fflush(stderr); +} + +SeatPromptResult console_confirm_ssh_host_key( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, SeatDialogText *text, HelpCtx helpctx, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) +{ + HANDLE hin; + DWORD savemode, i; + const char *prompt = NULL; + + stdio_sink errsink[1]; + stdio_sink_init(errsink, stderr); + + char line[32]; + + for (SeatDialogTextItem *item = text->items, + *end = item+text->nitems; item < end; item++) { + switch (item->type) { + case SDT_PARA: + wordwrap(BinarySink_UPCAST(errsink), + ptrlen_from_asciz(item->text), 60); + fputc('\n', stderr); + break; + case SDT_DISPLAY: + fprintf(stderr, " %s\n", item->text); + break; + case SDT_SCARY_HEADING: + /* Can't change font size or weight in this context */ + fprintf(stderr, "%s\n", item->text); + break; + case SDT_BATCH_ABORT: + if (console_batch_mode) { + fprintf(stderr, "%s\n", item->text); + fflush(stderr); + return SPR_SW_ABORT("Cannot confirm a host key in batch mode"); + } + break; + case SDT_PROMPT: + prompt = item->text; + break; + default: + break; + } + } + assert(prompt); /* something in the SeatDialogText should have set this */ + + while (true) { + fprintf(stderr, + "%s (y/n, Return cancels connection, i for more info) ", + prompt); + fflush(stderr); + + line[0] = '\0'; /* fail safe if ReadFile returns no data */ + + 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] == 'i' || line[0] == 'I') { + for (SeatDialogTextItem *item = text->items, + *end = item+text->nitems; item < end; item++) { + switch (item->type) { + case SDT_MORE_INFO_KEY: + fprintf(stderr, "%s", item->text); + break; + case SDT_MORE_INFO_VALUE_SHORT: + fprintf(stderr, ": %s\n", item->text); + break; + case SDT_MORE_INFO_VALUE_BLOB: + fprintf(stderr, ":\n%s\n", item->text); + break; + default: + break; + } + } + } else { + break; + } + } + + /* In case of misplaced reflexes from another program, also recognise 'q' + * as 'abandon connection rather than trust this key' */ + if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n' && + line[0] != 'q' && line[0] != 'Q') { + if (line[0] == 'y' || line[0] == 'Y') + store_host_key(host, port, keytype, keystr); + return SPR_OK; + } else { + fputs(console_abandoned_msg, stderr); + return SPR_USER_ABORT; + } +} + +SeatPromptResult console_confirm_weak_crypto_primitive( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) +{ + HANDLE hin; + DWORD savemode, i; + + char line[32]; + + fprintf(stderr, weakcrypto_msg_common_fmt, algtype, algname); + + if (console_batch_mode) { + fputs(console_abandoned_msg, stderr); + return SPR_SW_ABORT("Cannot confirm a weak crypto primitive " + "in batch mode"); + } + + fputs(console_continue_prompt, stderr); + 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 SPR_OK; + } else { + fputs(console_abandoned_msg, stderr); + return SPR_USER_ABORT; + } +} + +SeatPromptResult console_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) +{ + HANDLE hin; + DWORD savemode, i; + + char line[32]; + + fprintf(stderr, weakhk_msg_common_fmt, algname, betteralgs); + + if (console_batch_mode) { + fputs(console_abandoned_msg, stderr); + return SPR_SW_ABORT("Cannot confirm a weak cached host key " + "in batch mode"); + } + + fputs(console_continue_prompt, stderr); + 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 SPR_OK; + } else { + fputs(console_abandoned_msg, stderr); + return SPR_USER_ABORT; + } +} + +bool is_interactive(void) +{ + return is_console_handle(GetStdHandle(STD_INPUT_HANDLE)); +} + +bool console_antispoof_prompt = true; + +void console_set_trust_status(Seat *seat, bool trusted) +{ + /* Do nothing in response to a change of trust status, because + * there's nothing we can do in a console environment. However, + * the query function below will make a fiddly decision about + * whether to tell the backend to enable fallback handling. */ +} + +bool console_can_set_trust_status(Seat *seat) +{ + if (console_batch_mode) { + /* + * In batch mode, we don't need to worry about the server + * mimicking our interactive authentication, because the user + * already knows not to expect any. + */ + return true; + } + + return false; +} + +bool console_has_mixed_input_stream(Seat *seat) +{ + if (!is_interactive() || !console_antispoof_prompt) { + /* + * If standard input isn't connected to a terminal, then even + * if the server did send a spoof authentication prompt, the + * user couldn't respond to it via the terminal anyway. + * + * We also pretend this is true if the user has purposely + * disabled the antispoof prompt. + */ + return false; + } + + return true; +} + +/* + * 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 console_askappend(LogPolicy *lp, 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 as of " PGP_MASTER_KEY_YEAR + " (" PGP_MASTER_KEY_DETAILS "):\n" + " " PGP_MASTER_KEY_FP "\n\n" + "Previous Master Key (" PGP_PREV_MASTER_KEY_YEAR + ", " PGP_PREV_MASTER_KEY_DETAILS "):\n" + " " PGP_PREV_MASTER_KEY_FP "\n", stdout); +} + +void console_logging_error(LogPolicy *lp, const char *string) +{ + /* Ordinary Event Log entries are displayed in the same way as + * logging errors, but only in verbose mode */ + fprintf(stderr, "%s\n", string); + fflush(stderr); +} + +void console_eventlog(LogPolicy *lp, const char *string) +{ + /* Ordinary Event Log entries are displayed in the same way as + * logging errors, but only in verbose mode */ + if (lp_verbose(lp)) + console_logging_error(lp, string); +} + +StripCtrlChars *console_stripctrl_new( + Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) +{ + return stripctrl_new(bs_out, false, 0); +} + +static void console_write(HANDLE hout, ptrlen data) +{ + DWORD dummy; + WriteFile(hout, data.ptr, data.len, &dummy, NULL); +} + +SeatPromptResult console_get_userpass_input(prompts_t *p) +{ + HANDLE hin = INVALID_HANDLE_VALUE, hout = INVALID_HANDLE_VALUE; + 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 SPR_SW_ABORT("Cannot answer interactive prompts " + "in batch mode"); + 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) { + ptrlen plname = ptrlen_from_asciz(p->name); + console_write(hout, plname); + if (!ptrlen_endswith(plname, PTRLEN_LITERAL("\n"), NULL)) + console_write(hout, PTRLEN_LITERAL("\n")); + } + /* ...but we always print any `instruction'. */ + if (p->instruction) { + ptrlen plinst = ptrlen_from_asciz(p->instruction); + console_write(hout, plinst); + if (!ptrlen_endswith(plinst, PTRLEN_LITERAL("\n"), NULL)) + console_write(hout, PTRLEN_LITERAL("\n")); + } + + for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) { + + DWORD savemode, newmode; + 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_write(hout, ptrlen_from_asciz(pr->prompt)); + + bool failed = false; + SeatPromptResult spr; + while (1) { + /* + * Amount of data to try to read from the console in one + * go. This isn't completely arbitrary: a user reported + * that trying to read more than 31366 bytes at a time + * would fail with ERROR_NOT_ENOUGH_MEMORY on Windows 7, + * and Ruby's Win32 support module has evidence of a + * similar workaround: + * + * https://github.com/ruby/ruby/blob/0aa5195262d4193d3accf3e6b9bad236238b816b/win32/win32.c#L6842 + * + * To keep things simple, I stick with a nice round power + * of 2 rather than trying to go to the very limit of that + * bug. (We're typically reading user passphrases and the + * like here, so even this much is overkill really.) + */ + DWORD toread = 16384; + + size_t prev_result_len = pr->result->len; + void *ptr = strbuf_append(pr->result, toread); + + DWORD ret = 0; + if (!ReadFile(hin, ptr, toread, &ret, NULL)) { + /* An OS error when reading from the console is treated as an + * unexpected error and reported to the user. */ + failed = true; + spr = make_spr_sw_abort_winerror( + "Error reading from console", GetLastError()); + break; + } else if (ret == 0) { + /* Regard EOF on the terminal as a deliberate user-abort */ + failed = true; + spr = SPR_USER_ABORT; + break; + } + + strbuf_shrink_to(pr->result, prev_result_len + ret); + if (strbuf_chomp(pr->result, '\n')) { + strbuf_chomp(pr->result, '\r'); + break; + } + } + + SetConsoleMode(hin, savemode); + + if (!pr->echo) + console_write(hout, PTRLEN_LITERAL("\r\n")); + + if (failed) + return spr; + } + + return SPR_OK; +} |