mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 09:27:59 +00:00
f317f8e67e
The format _strings_ were previously centralised into the platform- independent console.c, as const char arrays. Now the actual formatting operation is centralised as well, by means of console.c providing a function that takes all the necessary parameters and returns a formatted piece of text for the console. Mostly this is so that I can add extra parameters to the message with some confidence: changing a format string in one file and two fprintf statements in other files to match seems like the kind of situation you wish you hadn't got into in the first place :-)
465 lines
14 KiB
C
465 lines
14 KiB
C
/*
|
|
* wincons.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);
|
|
}
|
|
|
|
int console_verify_ssh_host_key(
|
|
Seat *seat, const char *host, int port, const char *keytype,
|
|
char *keystr, const char *keydisp, char **fingerprints,
|
|
void (*callback)(void *ctx, int result), void *ctx)
|
|
{
|
|
int ret;
|
|
HANDLE hin;
|
|
DWORD savemode, i;
|
|
char *common;
|
|
const char *intro, *prompt;
|
|
|
|
char line[32];
|
|
|
|
/*
|
|
* Verify the key against the registry.
|
|
*/
|
|
ret = verify_host_key(host, port, keytype, keystr);
|
|
|
|
if (ret == 0) /* success - key matched OK */
|
|
return 1;
|
|
|
|
FingerprintType fptype_default =
|
|
ssh2_pick_default_fingerprint(fingerprints);
|
|
|
|
if (ret == 2) { /* key was different */
|
|
common = hk_wrongmsg_common(keytype, fingerprints[fptype_default]);
|
|
intro = hk_wrongmsg_interactive_intro;
|
|
prompt = hk_wrongmsg_interactive_prompt;
|
|
} else { /* key was absent */
|
|
common = hk_absentmsg_common(keytype, fingerprints[fptype_default]);
|
|
intro = hk_absentmsg_interactive_intro;
|
|
prompt = hk_absentmsg_interactive_prompt;
|
|
}
|
|
|
|
fputs(common, stderr);
|
|
sfree(common);
|
|
|
|
if (console_batch_mode) {
|
|
fputs(console_abandoned_msg, stderr);
|
|
return 0;
|
|
}
|
|
|
|
fputs(intro, stderr);
|
|
fflush(stderr);
|
|
|
|
while (true) {
|
|
fputs(prompt, stderr);
|
|
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') {
|
|
fprintf(stderr, "Full public key:\n%s\n", keydisp);
|
|
if (fingerprints[SSH_FPTYPE_SHA256])
|
|
fprintf(stderr, "SHA256 key fingerprint:\n%s\n",
|
|
fingerprints[SSH_FPTYPE_SHA256]);
|
|
if (fingerprints[SSH_FPTYPE_MD5])
|
|
fprintf(stderr, "MD5 key fingerprint:\n%s\n",
|
|
fingerprints[SSH_FPTYPE_MD5]);
|
|
} 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 1;
|
|
} else {
|
|
fputs(console_abandoned_msg, stderr);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int console_confirm_weak_crypto_primitive(
|
|
Seat *seat, const char *algtype, const char *algname,
|
|
void (*callback)(void *ctx, int 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 0;
|
|
}
|
|
|
|
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 1;
|
|
} else {
|
|
fputs(console_abandoned_msg, stderr);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int console_confirm_weak_cached_hostkey(
|
|
Seat *seat, const char *algname, const char *betteralgs,
|
|
void (*callback)(void *ctx, int 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 0;
|
|
}
|
|
|
|
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 1;
|
|
} else {
|
|
fputs(console_abandoned_msg, stderr);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
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 || !is_interactive() || !console_antispoof_prompt) {
|
|
/*
|
|
* 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.
|
|
*
|
|
* If standard input isn't connected to a terminal, likewise,
|
|
* because even if the server did send a spoof authentication
|
|
* prompt, the user couldn't respond to it via the terminal
|
|
* anyway.
|
|
*
|
|
* We also return true without enabling any defences if the
|
|
* user has purposely disabled the antispoof prompt.
|
|
*/
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
int 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 0;
|
|
hin = GetStdHandle(STD_INPUT_HANDLE);
|
|
if (hin == INVALID_HANDLE_VALUE) {
|
|
fprintf(stderr, "Cannot get standard input handle\n");
|
|
cleanup_exit(1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* And if we have anything to print, we need standard output.
|
|
*/
|
|
if ((p->name_reqd && p->name) || p->instruction || p->n_prompts) {
|
|
hout = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
if (hout == INVALID_HANDLE_VALUE) {
|
|
fprintf(stderr, "Cannot get standard output handle\n");
|
|
cleanup_exit(1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Preamble.
|
|
*/
|
|
/* We only print the `name' caption if we have to... */
|
|
if (p->name_reqd && p->name) {
|
|
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;
|
|
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) || ret == 0) {
|
|
failed = true;
|
|
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 0; /* failure due to read error */
|
|
}
|
|
}
|
|
|
|
return 1; /* success */
|
|
}
|