diff --git a/console.c b/console.c index 465fdfa7..62c64aff 100644 --- a/console.c +++ b/console.c @@ -9,56 +9,6 @@ #include "misc.h" #include "console.h" -char *hk_absentmsg_common(const char *host, int port, - const char *keytype, const char *fingerprint) -{ - return dupprintf( - "The host key is not cached for this server:\n" - " %s (port %d)\n" - "You have no guarantee that the server is the computer\n" - "you think it is.\n" - "The server's %s key fingerprint is:\n" - " %s\n", host, port, keytype, fingerprint); -} - -const char hk_absentmsg_interactive_intro[] = - "If you trust this host, enter \"y\" to add the key to\n" - "PuTTY's cache and carry on connecting.\n" - "If you want to carry on connecting just once, without\n" - "adding the key to the cache, enter \"n\".\n" - "If you do not trust this host, press Return to abandon the\n" - "connection.\n"; -const char hk_absentmsg_interactive_prompt[] = - "Store key in cache? (y/n, Return cancels connection, " - "i for more info) "; - -char *hk_wrongmsg_common(const char *host, int port, - const char *keytype, const char *fingerprint) -{ - return dupprintf( - "WARNING - POTENTIAL SECURITY BREACH!\n" - "The host key does not match the one PuTTY has cached\n" - "for this server:\n" - " %s (port %d)\n" - "This means that either the server administrator has\n" - "changed the host key, or you have actually connected\n" - "to another computer pretending to be the server.\n" - "The new %s key fingerprint is:\n" - " %s\n", host, port, keytype, fingerprint); -} - -const char hk_wrongmsg_interactive_intro[] = - "If you were expecting this change and trust the new key,\n" - "enter \"y\" to update PuTTY's cache and continue connecting.\n" - "If you want to carry on connecting but without updating\n" - "the cache, enter \"n\".\n" - "If you want to abandon the connection completely, press\n" - "Return to cancel. Pressing Return is the ONLY guaranteed\n" - "safe choice.\n"; -const char hk_wrongmsg_interactive_prompt[] = - "Update cached key? (y/n, Return cancels connection, " - "i for more info) "; - const char weakcrypto_msg_common_fmt[] = "The first %s supported by the server is\n" "%s, which is below the configured warning threshold.\n"; @@ -73,6 +23,17 @@ const char weakhk_msg_common_fmt[] = const char console_continue_prompt[] = "Continue with connection? (y/n) "; const char console_abandoned_msg[] = "Connection abandoned.\n"; +const SeatDialogPromptDescriptions *console_prompt_descriptions(Seat *seat) +{ + static const SeatDialogPromptDescriptions descs = { + .hk_accept_action = "enter \"y\"", + .hk_connect_once_action = "enter \"n\"", + .hk_cancel_action = "press Return", + .hk_cancel_action_Participle = "Pressing Return", + }; + return &descs; +} + bool console_batch_mode = false; /* diff --git a/defs.h b/defs.h index 48cbaf23..286e0c96 100644 --- a/defs.h +++ b/defs.h @@ -116,6 +116,9 @@ typedef struct LogPolicyVtable LogPolicyVtable; typedef struct Seat Seat; typedef struct SeatVtable SeatVtable; +typedef struct SeatDialogText SeatDialogText; +typedef struct SeatDialogTextItem SeatDialogTextItem; +typedef struct SeatDialogPromptDescriptions SeatDialogPromptDescriptions; typedef struct SeatPromptResult SeatPromptResult; typedef struct cmdline_get_passwd_input_state cmdline_get_passwd_input_state; diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c index 3e5a696f..2aed2b6b 100644 --- a/proxy/sshproxy.c +++ b/proxy/sshproxy.c @@ -407,7 +407,7 @@ static void sshproxy_connection_fatal(Seat *seat, const char *message) static SeatPromptResult sshproxy_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { SshProxy *sp = container_of(seat, SshProxy, seat); @@ -418,8 +418,8 @@ static SeatPromptResult sshproxy_confirm_ssh_host_key( * request on to it. */ return seat_confirm_ssh_host_key( - wrap(sp->clientseat), host, port, keytype, keystr, keydisp, - key_fingerprints, mismatch, callback, ctx); + wrap(sp->clientseat), host, port, keytype, keystr, text, + helpctx, callback, ctx); } /* @@ -482,6 +482,20 @@ static SeatPromptResult sshproxy_confirm_weak_cached_hostkey( "weak cached host key"); } +static const SeatDialogPromptDescriptions *sshproxy_prompt_descriptions( + Seat *seat) +{ + SshProxy *sp = container_of(seat, SshProxy, seat); + + /* If we have a client seat, return their prompt descriptions, so + * that prompts passed on to them will make sense. */ + if (sp->clientseat) + return seat_prompt_descriptions(sp->clientseat); + + /* Otherwise, it doesn't matter what we return, so do the easiest thing. */ + return nullseat_prompt_descriptions(NULL); +} + static StripCtrlChars *sshproxy_stripctrl_new( Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) { @@ -533,6 +547,7 @@ static const SeatVtable SshProxy_seat_vt = { .confirm_ssh_host_key = sshproxy_confirm_ssh_host_key, .confirm_weak_crypto_primitive = sshproxy_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = sshproxy_confirm_weak_cached_hostkey, + .prompt_descriptions = sshproxy_prompt_descriptions, .is_utf8 = nullseat_is_never_utf8, .echoedit_update = nullseat_echoedit_update, .get_x_display = nullseat_get_x_display, diff --git a/pscp.c b/pscp.c index da9e9aaa..4df8cb95 100644 --- a/pscp.c +++ b/pscp.c @@ -77,6 +77,7 @@ static const SeatVtable pscp_seat_vt = { .confirm_ssh_host_key = console_confirm_ssh_host_key, .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, + .prompt_descriptions = console_prompt_descriptions, .is_utf8 = nullseat_is_never_utf8, .echoedit_update = nullseat_echoedit_update, .get_x_display = nullseat_get_x_display, diff --git a/psftp.c b/psftp.c index bddc18a2..a1600cd9 100644 --- a/psftp.c +++ b/psftp.c @@ -58,6 +58,7 @@ static const SeatVtable psftp_seat_vt = { .confirm_ssh_host_key = console_confirm_ssh_host_key, .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, + .prompt_descriptions = console_prompt_descriptions, .is_utf8 = nullseat_is_never_utf8, .echoedit_update = nullseat_echoedit_update, .get_x_display = nullseat_get_x_display, diff --git a/putty.h b/putty.h index d7f33369..6f177474 100644 --- a/putty.h +++ b/putty.h @@ -1085,6 +1085,24 @@ typedef enum SeatOutputType { SEAT_OUTPUT_STDOUT, SEAT_OUTPUT_STDERR } SeatOutputType; +typedef enum SeatDialogTextType { + SDT_PARA, SDT_DISPLAY, SDT_SCARY_HEADING, + SDT_TITLE, SDT_PROMPT, SDT_BATCH_ABORT, + SDT_MORE_INFO_KEY, SDT_MORE_INFO_VALUE_SHORT, SDT_MORE_INFO_VALUE_BLOB +} SeatDialogTextType; +struct SeatDialogTextItem { + SeatDialogTextType type; + char *text; +}; +struct SeatDialogText { + size_t nitems, itemsize; + SeatDialogTextItem *items; +}; +SeatDialogText *seat_dialog_text_new(void); +void seat_dialog_text_free(SeatDialogText *sdt); +PRINTF_LIKE(3, 4) void seat_dialog_text_append( + SeatDialogText *sdt, SeatDialogTextType type, const char *fmt, ...); + /* * Data type 'Seat', which is an API intended to contain essentially * everything that a back end might need to talk to its client for: @@ -1259,9 +1277,8 @@ struct SeatVtable { */ SeatPromptResult (*confirm_ssh_host_key)( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, - bool mismatch, void (*callback)(void *ctx, SeatPromptResult result), - void *ctx); + char *keystr, SeatDialogText *text, HelpCtx helpctx, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); /* * Check with the seat whether it's OK to use a cryptographic @@ -1288,6 +1305,13 @@ struct SeatVtable { Seat *seat, const char *algname, const char *betteralgs, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); + /* + * Some snippets of text describing the UI actions in host key + * prompts / dialog boxes, to be used in ssh/common.c when it + * assembles the full text of those prompts. + */ + const SeatDialogPromptDescriptions *(*prompt_descriptions)(Seat *seat); + /* * Indicates whether the seat is expecting to interact with the * user in the UTF-8 character set. (Affects e.g. visual erase @@ -1409,10 +1433,10 @@ static inline void seat_set_busy_status(Seat *seat, BusyStatus status) { seat->vt->set_busy_status(seat, status); } static inline SeatPromptResult seat_confirm_ssh_host_key( InteractionReadySeat iseat, const char *h, int p, const char *ktyp, - char *kstr, const char *kdsp, char **fps, bool mis, + char *kstr, SeatDialogText *text, HelpCtx helpctx, void (*cb)(void *ctx, SeatPromptResult result), void *ctx) { return iseat.seat->vt->confirm_ssh_host_key( - iseat.seat, h, p, ktyp, kstr, kdsp, fps, mis, cb, ctx); } + iseat.seat, h, p, ktyp, kstr, text, helpctx, cb, ctx); } static inline SeatPromptResult seat_confirm_weak_crypto_primitive( InteractionReadySeat iseat, const char *atyp, const char *aname, void (*cb)(void *ctx, SeatPromptResult result), void *ctx) @@ -1423,6 +1447,9 @@ static inline SeatPromptResult seat_confirm_weak_cached_hostkey( void (*cb)(void *ctx, SeatPromptResult result), void *ctx) { return iseat.seat->vt->confirm_weak_cached_hostkey( iseat.seat, aname, better, cb, ctx); } +static inline const SeatDialogPromptDescriptions *seat_prompt_descriptions( + Seat *seat) +{ return seat->vt->prompt_descriptions(seat); } static inline bool seat_is_utf8(Seat *seat) { return seat->vt->is_utf8(seat); } static inline void seat_echoedit_update(Seat *seat, bool ec, bool ed) @@ -1468,6 +1495,12 @@ static inline size_t seat_stderr_pl(Seat *seat, ptrlen data) static inline size_t seat_banner_pl(InteractionReadySeat iseat, ptrlen data) { return iseat.seat->vt->banner(iseat.seat, data.ptr, data.len); } +struct SeatDialogPromptDescriptions { + const char *hk_accept_action; + const char *hk_connect_once_action; + const char *hk_cancel_action, *hk_cancel_action_Participle; +}; + /* In the utils subdir: print a message to the Seat which can't be * spoofed by server-supplied auth-time output such as SSH banners */ void seat_antispoof_msg(InteractionReadySeat iseat, const char *msg); @@ -1495,7 +1528,7 @@ char *nullseat_get_ttymode(Seat *seat, const char *mode); void nullseat_set_busy_status(Seat *seat, BusyStatus status); SeatPromptResult nullseat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); SeatPromptResult nullseat_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, @@ -1503,6 +1536,7 @@ SeatPromptResult nullseat_confirm_weak_crypto_primitive( SeatPromptResult nullseat_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); +const SeatDialogPromptDescriptions *nullseat_prompt_descriptions(Seat *seat); bool nullseat_is_never_utf8(Seat *seat); bool nullseat_is_always_utf8(Seat *seat); void nullseat_echoedit_update(Seat *seat, bool echoing, bool editing); @@ -1530,7 +1564,7 @@ bool nullseat_get_cursor_position(Seat *seat, int *x, int *y); void console_connection_fatal(Seat *seat, const char *message); SeatPromptResult console_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); SeatPromptResult console_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, @@ -1543,6 +1577,7 @@ StripCtrlChars *console_stripctrl_new( void console_set_trust_status(Seat *seat, bool trusted); bool console_can_set_trust_status(Seat *seat); bool console_has_mixed_input_stream(Seat *seat); +const SeatDialogPromptDescriptions *console_prompt_descriptions(Seat *seat); /* * Other centralised seat functions. diff --git a/ssh/common.c b/ssh/common.c index 424c8f71..d5e1d55f 100644 --- a/ssh/common.c +++ b/ssh/common.c @@ -924,10 +924,104 @@ SeatPromptResult verify_ssh_host_key( * The key is either missing from the cache, or does not match. * Either way, fall back to an interactive prompt from the Seat. */ - bool mismatch = (storage_status != 1); - return seat_confirm_ssh_host_key( - iseat, host, port, keytype, keystr, keydisp, fingerprints, mismatch, - callback, ctx); + SeatDialogText *text = seat_dialog_text_new(); + const SeatDialogPromptDescriptions *pds = + seat_prompt_descriptions(iseat.seat); + + FingerprintType fptype_default = + ssh2_pick_default_fingerprint(fingerprints); + + seat_dialog_text_append( + text, SDT_TITLE, "%s Security Alert", appname); + + if (storage_status == 1) { + seat_dialog_text_append( + text, SDT_PARA, "The host key is not cached for this server:"); + seat_dialog_text_append( + text, SDT_DISPLAY, "%s (port %d)", host, port); + seat_dialog_text_append( + text, SDT_PARA, "You have no guarantee that the server is the " + "computer you think it is."); + seat_dialog_text_append( + text, SDT_PARA, "The server's %s key fingerprint is:", keytype); + seat_dialog_text_append( + text, SDT_DISPLAY, "%s", fingerprints[fptype_default]); + } else { + seat_dialog_text_append( + text, SDT_SCARY_HEADING, "WARNING - POTENTIAL SECURITY BREACH!"); + seat_dialog_text_append( + text, SDT_PARA, "The host key does not match the one %s has " + "cached for this server:", appname); + seat_dialog_text_append( + text, SDT_DISPLAY, "%s (port %d)", host, port); + seat_dialog_text_append( + text, SDT_PARA, "This means that either the server administrator " + "has changed the host key, or you have actually connected to " + "another computer pretending to be the server."); + seat_dialog_text_append( + text, SDT_PARA, "The new %s key fingerprint is:", keytype); + seat_dialog_text_append( + text, SDT_DISPLAY, "%s", fingerprints[fptype_default]); + } + + /* The above text is printed even in batch mode. Here's where we stop if + * we can't present interactive prompts. */ + seat_dialog_text_append( + text, SDT_BATCH_ABORT, "Connection abandoned."); + + HelpCtx helpctx; + + if (storage_status == 1) { + seat_dialog_text_append( + text, SDT_PARA, "If you trust this host, %s to add the key to " + "%s's cache and carry on connecting.", + pds->hk_accept_action, appname); + seat_dialog_text_append( + text, SDT_PARA, "If you want to carry on connecting just once, " + "without adding the key to the cache, %s.", + pds->hk_connect_once_action); + seat_dialog_text_append( + text, SDT_PARA, "If you do not trust this host, %s to abandon the " + "connection.", pds->hk_cancel_action); + seat_dialog_text_append( + text, SDT_PROMPT, "Store key in cache?"); + helpctx = HELPCTX(errors_hostkey_absent); + } else { + seat_dialog_text_append( + text, SDT_PARA, "If you were expecting this change and trust the " + "new key, %s to update %s's cache and carry on connecting.", + pds->hk_accept_action, appname); + seat_dialog_text_append( + text, SDT_PARA, "If you want to carry on connecting but without " + "updating the cache, %s.", pds->hk_connect_once_action); + seat_dialog_text_append( + text, SDT_PARA, "If you want to abandon the connection " + "completely, %s to cancel. %s is the ONLY guaranteed safe choice.", + pds->hk_cancel_action, pds->hk_cancel_action_Participle); + seat_dialog_text_append( + text, SDT_PROMPT, "Update cached key?"); + helpctx = HELPCTX(errors_hostkey_changed); + } + + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, + "Full text of host's public key"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_BLOB, "%s", keydisp); + + if (fingerprints[SSH_FPTYPE_SHA256]) { + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, "SHA256 fingerprint"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", + fingerprints[SSH_FPTYPE_SHA256]); + } + if (fingerprints[SSH_FPTYPE_MD5]) { + seat_dialog_text_append(text, SDT_MORE_INFO_KEY, "MD5 fingerprint"); + seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", + fingerprints[SSH_FPTYPE_MD5]); + } + + SeatPromptResult toret = seat_confirm_ssh_host_key( + iseat, host, port, keytype, keystr, text, helpctx, callback, ctx); + seat_dialog_text_free(text); + return toret; } /* ---------------------------------------------------------------------- diff --git a/ssh/server.c b/ssh/server.c index f69bdd83..188426b3 100644 --- a/ssh/server.c +++ b/ssh/server.c @@ -122,6 +122,7 @@ static const SeatVtable server_seat_vt = { .confirm_ssh_host_key = nullseat_confirm_ssh_host_key, .confirm_weak_crypto_primitive = server_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = server_confirm_weak_cached_hostkey, + .prompt_descriptions = nullseat_prompt_descriptions, .is_utf8 = nullseat_is_never_utf8, .echoedit_update = nullseat_echoedit_update, .get_x_display = nullseat_get_x_display, diff --git a/ssh/sesschan.c b/ssh/sesschan.c index 978ffb9a..b8f9311e 100644 --- a/ssh/sesschan.c +++ b/ssh/sesschan.c @@ -199,6 +199,7 @@ static const SeatVtable sesschan_seat_vt = { .confirm_ssh_host_key = nullseat_confirm_ssh_host_key, .confirm_weak_crypto_primitive = nullseat_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = nullseat_confirm_weak_cached_hostkey, + .prompt_descriptions = nullseat_prompt_descriptions, .is_utf8 = nullseat_is_never_utf8, .echoedit_update = nullseat_echoedit_update, .get_x_display = nullseat_get_x_display, diff --git a/unix/console.c b/unix/console.c index cc6c9853..75e51a7f 100644 --- a/unix/console.c +++ b/unix/console.c @@ -104,43 +104,53 @@ static int block_and_read(int fd, void *buf, size_t len) SeatPromptResult console_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { char line[32]; struct termios cf; - char *common; - const char *intro, *prompt; + const char *prompt; - FingerprintType fptype_default = - ssh2_pick_default_fingerprint(fingerprints); - - if (mismatch) { /* key was different */ - common = hk_wrongmsg_common(host, port, keytype, - fingerprints[fptype_default]); - intro = hk_wrongmsg_interactive_intro; - prompt = hk_wrongmsg_interactive_prompt; - } else { /* key was absent */ - common = hk_absentmsg_common(host, port, keytype, - fingerprints[fptype_default]); - intro = hk_absentmsg_interactive_intro; - prompt = hk_absentmsg_interactive_prompt; - } + stdio_sink errsink[1]; + stdio_sink_init(errsink, stderr); premsg(&cf); - fputs(common, stderr); - sfree(common); - if (console_batch_mode) { - fputs(console_abandoned_msg, stderr); - postmsg(&cf); - return SPR_SW_ABORT("Cannot confirm a host key in batch mode"); + 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); + postmsg(&cf); + return SPR_SW_ABORT("Cannot confirm a host key in batch mode"); + } + break; + case SDT_PROMPT: + prompt = item->text; + break; + default: + break; + } } - fputs(intro, stderr); - fflush(stderr); while (true) { - fputs(prompt, stderr); + fprintf(stderr, + "%s (y/n, Return cancels connection, i for more info) ", + prompt); fflush(stderr); struct termios oldmode, newmode; @@ -154,13 +164,22 @@ SeatPromptResult console_confirm_ssh_host_key( tcsetattr(0, TCSANOW, &oldmode); 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]); + 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; } diff --git a/unix/dialog.c b/unix/dialog.c index 161f393d..602991f1 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -3592,41 +3592,22 @@ static void more_info_button_clicked(GtkButton *button, gpointer vctx) &buttons_ok, more_info_closed, ctx); } +const SeatDialogPromptDescriptions *gtk_seat_prompt_descriptions(Seat *seat) +{ + static const SeatDialogPromptDescriptions descs = { + .hk_accept_action = "press \"Accept\"", + .hk_connect_once_action = "press \"Connect Once\"", + .hk_cancel_action = "press \"Cancel\"", + .hk_cancel_action_Participle = "Pressing \"Cancel\"", + }; + return &descs; +} + SeatPromptResult gtk_seat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { - static const char absenttxt[] = - "The host key is not cached for this server:\n\n" - "%s (port %d)\n\n" - "You have no guarantee that the server is the computer " - "you think it is.\n" - "The server's %s key fingerprint is:\n\n" - "%s\n\n" - "If you trust this host, press \"Accept\" to add the key to " - "PuTTY's cache and carry on connecting.\n" - "If you want to carry on connecting just once, without " - "adding the key to the cache, press \"Connect Once\".\n" - "If you do not trust this host, press \"Cancel\" to abandon the " - "connection."; - static const char wrongtxt[] = - "WARNING - POTENTIAL SECURITY BREACH!\n" - "The host key does not match the one PuTTY has cached " - "for this server:\n\n" - "%s (port %d)\n\n" - "This means that either the server administrator has " - "changed the host key, or you have actually connected " - "to another computer pretending to be the server.\n" - "The new %s key fingerprint is:\n\n" - "%s\n\n" - "If you were expecting this change and trust the new key, " - "press \"Accept\" to update PuTTY's cache and continue connecting.\n" - "If you want to carry on connecting but without updating " - "the cache, press \"Connect Once\".\n" - "If you want to abandon the connection completely, press " - "\"Cancel\" to cancel. Pressing \"Cancel\" is the ONLY guaranteed " - "safe choice."; static const struct message_box_button button_array_hostkey[] = { {"Accept", 'a', 0, 2}, {"Connect Once", 'o', 0, 1}, @@ -3636,17 +3617,40 @@ SeatPromptResult gtk_seat_confirm_ssh_host_key( button_array_hostkey, lenof(button_array_hostkey), }; - char *text; - struct confirm_ssh_host_key_dialog_ctx *result_ctx; + const char *dlg_title = NULL; + strbuf *dlg_text = strbuf_new(); + int width = string_width("default dialog width determination string"); + + for (SeatDialogTextItem *item = text->items, + *end = item + text->nitems; item < end; item++) { + switch (item->type) { + case SDT_PARA: + put_fmt(dlg_text, "%s\n\n", item->text); + break; + case SDT_DISPLAY: { + put_fmt(dlg_text, "%s\n\n", item->text); + int thiswidth = string_width(item->text); + if (width < thiswidth) + width = thiswidth; + break; + } + case SDT_SCARY_HEADING: + /* Can't change font size or weight in this context */ + put_fmt(dlg_text, "%s\n\n", item->text); + break; + case SDT_TITLE: + dlg_title = item->text; + break; + default: + break; + } + } + while (strbuf_chomp(dlg_text, '\n')); + GtkWidget *mainwin, *msgbox; - FingerprintType fptype_default = - ssh2_pick_default_fingerprint(fingerprints); - - text = dupprintf((mismatch ? wrongtxt : absenttxt), host, port, - keytype, fingerprints[fptype_default]); - - result_ctx = snew(struct confirm_ssh_host_key_dialog_ctx); + struct confirm_ssh_host_key_dialog_ctx *result_ctx = + snew(struct confirm_ssh_host_key_dialog_ctx); result_ctx->callback = callback; result_ctx->callback_ctx = ctx; result_ctx->host = dupstr(host); @@ -3658,41 +3662,48 @@ SeatPromptResult gtk_seat_confirm_ssh_host_key( mainwin = GTK_WIDGET(gtk_seat_get_window(seat)); GtkWidget *more_info_button = NULL; msgbox = create_message_box_general( - mainwin, "PuTTY Security Alert", text, - string_width(fingerprints[fptype_default]), true, + mainwin, dlg_title, dlg_text->s, width, true, &buttons_hostkey, confirm_ssh_host_key_result_callback, result_ctx, add_more_info_button, &more_info_button); result_ctx->main_dialog = msgbox; result_ctx->more_info_dialog = NULL; - strbuf *sb = strbuf_new(); - if (fingerprints[SSH_FPTYPE_SHA256]) - put_fmt(sb, "SHA256 fingerprint: %s\n", - fingerprints[SSH_FPTYPE_SHA256]); - if (fingerprints[SSH_FPTYPE_MD5]) - put_fmt(sb, "MD5 fingerprint: %s\n", - fingerprints[SSH_FPTYPE_MD5]); - put_fmt(sb, "Full text of host's public key:"); - /* We have to manually wrap the public key, or else the GtkLabel - * will resize itself to accommodate the longest word, which will - * lead to a hilariously wide message box. */ - for (const char *p = keydisp, *q = p + strlen(p); p < q ;) { - size_t linelen = q-p; - if (linelen > 72) - linelen = 72; - put_byte(sb, '\n'); - put_data(sb, p, linelen); - p += linelen; + strbuf *moreinfo = strbuf_new(); + for (SeatDialogTextItem *item = text->items, + *end = item + text->nitems; item < end; item++) { + switch (item->type) { + case SDT_MORE_INFO_KEY: + put_fmt(moreinfo, "%s", item->text); + break; + case SDT_MORE_INFO_VALUE_SHORT: + put_fmt(moreinfo, ": %s\n", item->text); + break; + case SDT_MORE_INFO_VALUE_BLOB: + /* We have to manually wrap the public key, or else the GtkLabel + * will resize itself to accommodate the longest word, which will + * lead to a hilariously wide message box. */ + for (const char *p = item->text, *q = p + strlen(p); p < q ;) { + size_t linelen = q-p; + if (linelen > 72) + linelen = 72; + put_byte(moreinfo, '\n'); + put_data(moreinfo, p, linelen); + p += linelen; + } + break; + default: + break; + } } - result_ctx->more_info = strbuf_to_str(sb); + result_ctx->more_info = strbuf_to_str(moreinfo); g_signal_connect(G_OBJECT(more_info_button), "clicked", G_CALLBACK(more_info_button_clicked), result_ctx); register_dialog(seat, DIALOG_SLOT_NETWORK_PROMPT, msgbox); - sfree(text); + strbuf_free(dlg_text); return SPR_INCOMPLETE; /* dialog still in progress */ } diff --git a/unix/platform.h b/unix/platform.h index 31da1294..a3cbfd13 100644 --- a/unix/platform.h +++ b/unix/platform.h @@ -222,7 +222,7 @@ int gtkdlg_askappend(Seat *seat, Filename *filename, void (*callback)(void *ctx, int result), void *ctx); SeatPromptResult gtk_seat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); SeatPromptResult gtk_seat_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, @@ -230,6 +230,7 @@ SeatPromptResult gtk_seat_confirm_weak_crypto_primitive( SeatPromptResult gtk_seat_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); +const SeatDialogPromptDescriptions *gtk_seat_prompt_descriptions(Seat *seat); #ifdef MAY_REFER_TO_GTK_IN_HEADERS struct message_box_button { const char *title; diff --git a/unix/plink.c b/unix/plink.c index 9e109f01..4ab371a0 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -408,6 +408,7 @@ static const SeatVtable plink_seat_vt = { .confirm_ssh_host_key = console_confirm_ssh_host_key, .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, + .prompt_descriptions = console_prompt_descriptions, .is_utf8 = nullseat_is_never_utf8, .echoedit_update = plink_echoedit_update, .get_x_display = nullseat_get_x_display, diff --git a/unix/window.c b/unix/window.c index 9d33904a..f8232e9a 100644 --- a/unix/window.c +++ b/unix/window.c @@ -429,6 +429,7 @@ static const SeatVtable gtk_seat_vt = { .confirm_ssh_host_key = gtk_seat_confirm_ssh_host_key, .confirm_weak_crypto_primitive = gtk_seat_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = gtk_seat_confirm_weak_cached_hostkey, + .prompt_descriptions = gtk_seat_prompt_descriptions, .is_utf8 = gtk_seat_is_utf8, .echoedit_update = nullseat_echoedit_update, .get_x_display = gtk_seat_get_x_display, diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 34644cbd..e717af49 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -53,6 +53,7 @@ add_sources_from_current_dir(utils ptrlen.c read_file_into.c seat_connection_fatal.c + seat_dialog_text.c sessprep.c sk_free_peer_info.c smemclr.c diff --git a/utils/nullseat.c b/utils/nullseat.c index ba09839e..37cb0f4c 100644 --- a/utils/nullseat.c +++ b/utils/nullseat.c @@ -22,7 +22,7 @@ char *nullseat_get_ttymode(Seat *seat, const char *mode) { return NULL; } void nullseat_set_busy_status(Seat *seat, BusyStatus status) {} SeatPromptResult nullseat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { return SPR_SW_ABORT("this seat can't handle interactive prompts"); } SeatPromptResult nullseat_confirm_weak_crypto_primitive( @@ -52,3 +52,14 @@ bool nullseat_verbose_yes(Seat *seat) { return true; } bool nullseat_interactive_no(Seat *seat) { return false; } bool nullseat_interactive_yes(Seat *seat) { return true; } bool nullseat_get_cursor_position(Seat *seat, int *x, int *y) { return false; } + +const SeatDialogPromptDescriptions *nullseat_prompt_descriptions(Seat *seat) +{ + static const SeatDialogPromptDescriptions descs = { + .hk_accept_action = "", + .hk_connect_once_action = "", + .hk_cancel_action = "", + .hk_cancel_action_Participle = "", + }; + return &descs; +} diff --git a/utils/seat_dialog_text.c b/utils/seat_dialog_text.c new file mode 100644 index 00000000..03890b93 --- /dev/null +++ b/utils/seat_dialog_text.c @@ -0,0 +1,41 @@ +/* + * Helper routines for dealing with SeatDialogText structures. + */ + +#include + +#include "putty.h" + +SeatDialogText *seat_dialog_text_new(void) +{ + SeatDialogText *sdt = snew(SeatDialogText); + sdt->nitems = sdt->itemsize = 0; + sdt->items = NULL; + return sdt; +} + +void seat_dialog_text_free(SeatDialogText *sdt) +{ + for (size_t i = 0; i < sdt->nitems; i++) + sfree(sdt->items[i].text); + sfree(sdt->items); + sfree(sdt); +} + +static void seat_dialog_text_append_v( + SeatDialogText *sdt, SeatDialogTextType type, const char *fmt, va_list ap) +{ + sgrowarray(sdt->items, sdt->itemsize, sdt->nitems); + SeatDialogTextItem *item = &sdt->items[sdt->nitems++]; + item->type = type; + item->text = dupvprintf(fmt, ap); +} + +void seat_dialog_text_append(SeatDialogText *sdt, SeatDialogTextType type, + const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + seat_dialog_text_append_v(sdt, type, fmt, ap); + va_end(ap); +} diff --git a/utils/tempseat.c b/utils/tempseat.c index 785c00c2..ad37b0e3 100644 --- a/utils/tempseat.c +++ b/utils/tempseat.c @@ -204,6 +204,17 @@ static bool tempseat_has_mixed_input_stream(Seat *seat) return seat_has_mixed_input_stream(ts->realseat); } +static const SeatDialogPromptDescriptions *tempseat_prompt_descriptions( + Seat *seat) +{ + /* It might be OK to put this in the 'unreachable' category, but I + * think it's equally good to put it here, which allows for + * someone _preparing_ a prompt right now that they intend to + * present once the TempSeat has given way to the real one. */ + TempSeat *ts = container_of(seat, TempSeat, seat); + return seat_prompt_descriptions(ts->realseat); +} + /* ---------------------------------------------------------------------- * Methods that should never be called on a TempSeat, so we can put an * unreachable() in them. @@ -237,7 +248,7 @@ static size_t tempseat_banner(Seat *seat, const void *data, size_t len) static SeatPromptResult tempseat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { unreachable("confirm_ssh_host_key should never be called on TempSeat"); @@ -322,6 +333,7 @@ static const struct SeatVtable tempseat_vt = { .confirm_ssh_host_key = tempseat_confirm_ssh_host_key, .confirm_weak_crypto_primitive = tempseat_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = tempseat_confirm_weak_cached_hostkey, + .prompt_descriptions = tempseat_prompt_descriptions, .is_utf8 = tempseat_is_utf8, .echoedit_update = tempseat_echoedit_update, .get_x_display = tempseat_get_x_display, diff --git a/windows/console.c b/windows/console.c index 81ab7d06..3cc41364 100644 --- a/windows/console.c +++ b/windows/console.c @@ -34,44 +34,52 @@ void console_print_error_msg(const char *prefix, const char *msg) SeatPromptResult console_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { HANDLE hin; DWORD savemode, i; - char *common; - const char *intro, *prompt; + const char *prompt; + + stdio_sink errsink[1]; + stdio_sink_init(errsink, stderr); char line[32]; - FingerprintType fptype_default = - ssh2_pick_default_fingerprint(fingerprints); - - if (mismatch) { /* key was different */ - common = hk_wrongmsg_common(host, port, keytype, - fingerprints[fptype_default]); - intro = hk_wrongmsg_interactive_intro; - prompt = hk_wrongmsg_interactive_prompt; - } else { /* key was absent */ - common = hk_absentmsg_common(host, port, keytype, - fingerprints[fptype_default]); - intro = hk_absentmsg_interactive_intro; - prompt = hk_absentmsg_interactive_prompt; + 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; + } } - fputs(common, stderr); - sfree(common); - - if (console_batch_mode) { - fputs(console_abandoned_msg, stderr); - return SPR_SW_ABORT("Cannot confirm a host key in batch mode"); - } - - fputs(intro, stderr); - fflush(stderr); - while (true) { - fputs(prompt, stderr); + 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 */ @@ -84,13 +92,22 @@ SeatPromptResult console_confirm_ssh_host_key( 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]); + 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; } diff --git a/windows/dialog.c b/windows/dialog.c index f2c309b8..85f16348 100644 --- a/windows/dialog.c +++ b/windows/dialog.c @@ -842,14 +842,8 @@ void showabout(HWND hwnd) } struct hostkey_dialog_ctx { - const char *const *keywords; - const char *const *values; - const char *host; - int port; - FingerprintType fptype_default; - char **fingerprints; - const char *keydisp; - LPCTSTR iconid; + SeatDialogText *text; + bool has_title; const char *helpctx; }; @@ -860,16 +854,99 @@ static INT_PTR HostKeyMoreInfoProc(HWND hwnd, UINT msg, WPARAM wParam, switch (msg) { case WM_INITDIALOG: { + int index = 100, y = 12; - if (ctx->fingerprints[SSH_FPTYPE_SHA256]) - SetDlgItemText(hwnd, IDC_HKI_SHA256, - ctx->fingerprints[SSH_FPTYPE_SHA256]); - if (ctx->fingerprints[SSH_FPTYPE_MD5]) - SetDlgItemText(hwnd, IDC_HKI_MD5, - ctx->fingerprints[SSH_FPTYPE_MD5]); + WPARAM font = SendMessage(hwnd, WM_GETFONT, 0, 0); - SetDlgItemText(hwnd, IDA_TEXT, ctx->keydisp); + const char *key = NULL; + for (SeatDialogTextItem *item = ctx->text->items, + *end = item + ctx->text->nitems; item < end; item++) { + switch (item->type) { + case SDT_MORE_INFO_KEY: + key = item->text; + break; + case SDT_MORE_INFO_VALUE_SHORT: + case SDT_MORE_INFO_VALUE_BLOB: { + RECT rk, rv; + DWORD editstyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP | + ES_AUTOHSCROLL | ES_READONLY; + if (item->type == SDT_MORE_INFO_VALUE_BLOB) { + rk.left = 12; + rk.right = 376; + rk.top = y; + rk.bottom = 8; + y += 10; + editstyle |= ES_MULTILINE; + rv.left = 12; + rv.right = 376; + rv.top = y; + rv.bottom = 64; + y += 68; + } else { + rk.left = 12; + rk.right = 80; + rk.top = y+2; + rk.bottom = 8; + + rv.left = 100; + rv.right = 288; + rv.top = y; + rv.bottom = 12; + + y += 16; + } + + MapDialogRect(hwnd, &rk); + HWND ctl = CreateWindowEx( + 0, "STATIC", key, WS_CHILD | WS_VISIBLE, + rk.left, rk.top, rk.right, rk.bottom, + hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL); + SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0)); + + MapDialogRect(hwnd, &rv); + ctl = CreateWindowEx( + WS_EX_CLIENTEDGE, "EDIT", item->text, editstyle, + rv.left, rv.top, rv.right, rv.bottom, + hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL); + SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0)); + break; + } + default: + break; + } + } + + /* + * Now resize the overall window, and move the Close button at + * the bottom. + */ + RECT r; + r.left = 176; + r.top = y + 10; + r.right = r.bottom = 0; + MapDialogRect(hwnd, &r); + HWND ctl = GetDlgItem(hwnd, IDOK); + SetWindowPos(ctl, NULL, r.left, r.top, 0, 0, + SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER); + + r.left = r.top = r.right = 0; + r.bottom = 300; + MapDialogRect(hwnd, &r); + int oldheight = r.bottom; + + r.left = r.top = r.right = 0; + r.bottom = y + 30; + MapDialogRect(hwnd, &r); + int newheight = r.bottom; + + GetWindowRect(hwnd, &r); + + SetWindowPos(hwnd, NULL, 0, 0, r.right - r.left, + r.bottom - r.top + newheight - oldheight, + SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER); + + ShowWindow(hwnd, SW_SHOWNORMAL); return 1; } case WM_COMMAND: @@ -893,44 +970,106 @@ static INT_PTR HostKeyDialogProc(HWND hwnd, UINT msg, switch (msg) { case WM_INITDIALOG: { - strbuf *sb = strbuf_new(); - for (int id = 100;; id++) { - char buf[256]; + strbuf *dlg_text = strbuf_new(); + const char *dlg_title = ""; + ctx->has_title = false; + LPCTSTR iconid = IDI_QUESTION; + ctx->helpctx = WINHELP_CTX_errors_hostkey_absent; - if (!GetDlgItemText(hwnd, id, buf, (int)lenof(buf))) + for (SeatDialogTextItem *item = ctx->text->items, + *end = item + ctx->text->nitems; item < end; item++) { + switch (item->type) { + case SDT_PARA: + put_fmt(dlg_text, "%s\r\n\r\n", item->text); + break; + case SDT_DISPLAY: + put_fmt(dlg_text, "%s\r\n\r\n", item->text); + break; + case SDT_SCARY_HEADING: + SetDlgItemText(hwnd, IDC_HK_TITLE, item->text); + iconid = IDI_WARNING; + ctx->helpctx = WINHELP_CTX_errors_hostkey_changed; + ctx->has_title = true; + break; + case SDT_TITLE: + dlg_title = item->text; + break; + default: break; - - strbuf_clear(sb); - for (const char *p = buf; *p ;) { - if (*p == '{') { - for (size_t i = 0; ctx->keywords[i]; i++) { - if (strstartswith(p, ctx->keywords[i])) { - p += strlen(ctx->keywords[i]); - put_dataz(sb, ctx->values[i]); - goto matched; - } - } - } else { - put_byte(sb, *p++); - } - matched:; } - - SetDlgItemText(hwnd, id, sb->s); } - strbuf_free(sb); + while (strbuf_chomp(dlg_text, '\r') || strbuf_chomp(dlg_text, '\n')); - char *hostport = dupprintf("%s (port %d)", ctx->host, ctx->port); - SetDlgItemText(hwnd, IDC_HK_HOST, hostport); - sfree(hostport); - MakeDlgItemBorderless(hwnd, IDC_HK_HOST); + SetDlgItemText(hwnd, IDC_HK_TEXT, dlg_text->s); + MakeDlgItemBorderless(hwnd, IDC_HK_TEXT); + strbuf_free(dlg_text); - SetDlgItemText(hwnd, IDC_HK_FINGERPRINT, - ctx->fingerprints[ctx->fptype_default]); - MakeDlgItemBorderless(hwnd, IDC_HK_FINGERPRINT); + SetWindowText(hwnd, dlg_title); + + if (!ctx->has_title) { + HWND item = GetDlgItem(hwnd, IDC_HK_TITLE); + if (item) + DestroyWindow(item); + } + + /* + * Find out how tall the text in the edit control really ended + * up (after line wrapping), and adjust the height of the + * whole box to match it. + */ + int height = SendDlgItemMessage(hwnd, IDC_HK_TEXT, + EM_GETLINECOUNT, 0, 0); + height *= 8; /* height of a text line, by definition of dialog units */ + + int edittop = ctx->has_title ? 40 : 20; + + RECT r; + r.left = 40; + r.top = edittop; + r.right = 290; + r.bottom = height; + MapDialogRect(hwnd, &r); + SetWindowPos(GetDlgItem(hwnd, IDC_HK_TEXT), NULL, + r.left, r.top, r.right, r.bottom, + SWP_NOREDRAW | SWP_NOZORDER); + + static const struct { + int id, x; + } buttons[] = { + { IDCANCEL, 288 }, + { IDC_HK_ACCEPT, 168 }, + { IDC_HK_ONCE, 216 }, + { IDC_HK_MOREINFO, 60 }, + { IDHELP, 12 }, + }; + for (size_t i = 0; i < lenof(buttons); i++) { + HWND ctl = GetDlgItem(hwnd, buttons[i].id); + r.left = buttons[i].x; + r.top = edittop + height + 20; + r.right = r.bottom = 0; + MapDialogRect(hwnd, &r); + SetWindowPos(ctl, NULL, r.left, r.top, 0, 0, + SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER); + } + + r.left = r.top = r.right = 0; + r.bottom = 240; + MapDialogRect(hwnd, &r); + int oldheight = r.bottom; + + r.left = r.top = r.right = 0; + r.bottom = edittop + height + 40; + MapDialogRect(hwnd, &r); + int newheight = r.bottom; + + GetWindowRect(hwnd, &r); + + SetWindowPos(hwnd, NULL, 0, 0, r.right - r.left, + r.bottom - r.top + newheight - oldheight, + SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER); HANDLE icon = LoadImage( - NULL, ctx->iconid, IMAGE_ICON, + NULL, iconid, IMAGE_ICON, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), LR_SHARED); SendDlgItemMessage(hwnd, IDC_HK_ICON, STM_SETICON, (WPARAM)icon, 0); @@ -941,13 +1080,16 @@ static INT_PTR HostKeyDialogProc(HWND hwnd, UINT msg, DestroyWindow(item); } + ShowWindow(hwnd, SW_SHOWNORMAL); + return 1; } case WM_CTLCOLORSTATIC: { HDC hdc = (HDC)wParam; HWND control = (HWND)lParam; - if (GetWindowLongPtr(control, GWLP_ID) == IDC_HK_TITLE) { + if (GetWindowLongPtr(control, GWLP_ID) == IDC_HK_TITLE && + ctx->has_title) { SetBkMode(hdc, TRANSPARENT); HFONT prev_font = (HFONT)SelectObject( hdc, (HFONT)GetStockObject(SYSTEM_FONT)); @@ -988,34 +1130,30 @@ static INT_PTR HostKeyDialogProc(HWND hwnd, UINT msg, return 0; } +const SeatDialogPromptDescriptions *win_seat_prompt_descriptions(Seat *seat) +{ + static const SeatDialogPromptDescriptions descs = { + .hk_accept_action = "press \"Accept\"", + .hk_connect_once_action = "press \"Connect Once\"", + .hk_cancel_action = "press \"Cancel\"", + .hk_cancel_action_Participle = "Pressing \"Cancel\"", + }; + return &descs; +} + SeatPromptResult win_seat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **fingerprints, bool mismatch, - void (*callback)(void *ctx, SeatPromptResult result), void *vctx) + char *keystr, SeatDialogText *text, HelpCtx helpctx, + void (*callback)(void *ctx, SeatPromptResult result), void *cbctx) { WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); - static const char *const keywords[] = - { "{KEYTYPE}", "{APPNAME}", NULL }; - - const char *values[2]; - values[0] = keytype; - values[1] = appname; - struct hostkey_dialog_ctx ctx[1]; - ctx->keywords = keywords; - ctx->values = values; - ctx->fingerprints = fingerprints; - ctx->fptype_default = ssh2_pick_default_fingerprint(fingerprints); - ctx->keydisp = keydisp; - ctx->iconid = (mismatch ? IDI_WARNING : IDI_QUESTION); - ctx->helpctx = (mismatch ? WINHELP_CTX_errors_hostkey_changed : - WINHELP_CTX_errors_hostkey_absent); - ctx->host = host; - ctx->port = port; - int dlgid = (mismatch ? IDD_HK_WRONG : IDD_HK_ABSENT); + ctx->text = text; + ctx->helpctx = helpctx; + int mbret = ShinyDialogBox( - hinst, MAKEINTRESOURCE(dlgid), "PuTTYHostKeyDialog", + hinst, MAKEINTRESOURCE(IDD_HOSTKEY), "PuTTYHostKeyDialog", wgs->term_hwnd, HostKeyDialogProc, ctx); assert(mbret==IDC_HK_ACCEPT || mbret==IDC_HK_ONCE || mbret==IDCANCEL); if (mbret == IDC_HK_ACCEPT) { diff --git a/windows/platform.h b/windows/platform.h index c33ca1c4..d0510faf 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -226,7 +226,7 @@ int has_embedded_chm(void); /* 1 = yes, 0 = no, -1 = N/A */ */ SeatPromptResult win_seat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, - char *keystr, const char *keydisp, char **key_fingerprints, bool mismatch, + char *keystr, SeatDialogText *text, HelpCtx helpctx, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); SeatPromptResult win_seat_confirm_weak_crypto_primitive( Seat *seat, const char *algtype, const char *algname, @@ -234,6 +234,7 @@ SeatPromptResult win_seat_confirm_weak_crypto_primitive( SeatPromptResult win_seat_confirm_weak_cached_hostkey( Seat *seat, const char *algname, const char *betteralgs, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); +const SeatDialogPromptDescriptions *win_seat_prompt_descriptions(Seat *seat); /* * Windows-specific clipboard helper function shared with dialog.c, diff --git a/windows/plink.c b/windows/plink.c index 94bf4f0c..4ae6cf4d 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -102,6 +102,7 @@ static const SeatVtable plink_seat_vt = { .confirm_ssh_host_key = console_confirm_ssh_host_key, .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, + .prompt_descriptions = console_prompt_descriptions, .is_utf8 = nullseat_is_never_utf8, .echoedit_update = plink_echoedit_update, .get_x_display = nullseat_get_x_display, diff --git a/windows/putty-common.rc2 b/windows/putty-common.rc2 index b6ccb3d8..c6d403a5 100644 --- a/windows/putty-common.rc2 +++ b/windows/putty-common.rc2 @@ -57,78 +57,32 @@ BEGIN END /* No accelerators used */ -IDD_HK_ABSENT DIALOG DISCARDABLE 50, 50, 340, 160 +IDD_HOSTKEY DIALOG DISCARDABLE 50, 50, 340, 240 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "PuTTY Security Alert" FONT 8, "MS Shell Dlg" CLASS "PuTTYHostKeyDialog" BEGIN - LTEXT "The host key is not cached for this server:", 100, 40, 20, 300, 8 - LTEXT "You have no guarantee that the server is the computer you think it is.", 101, 40, 40, 300, 8 - LTEXT "The server's {KEYTYPE} key fingerprint is:", 102, 40, 52, 300, 8 - LTEXT "If you trust this host, press ""Accept"" to add the key to {APPNAME}'s", 103, 40, 72, 300, 8 - LTEXT "cache and carry on connecting.", 104, 40, 80, 300, 8 - LTEXT "If you want to carry on connecting just once, without adding the key", 105, 40, 92, 300, 8 - LTEXT "to the cache, press ""Connect Once"".", 106, 40, 100, 300, 8 - LTEXT "If you do not trust this host, press ""Cancel"" to abandon the connection.", 107, 40, 112, 300, 8 - ICON "", IDC_HK_ICON, 10, 18, 0, 0 - PUSHBUTTON "&Cancel", IDCANCEL, 288, 140, 40, 14 - PUSHBUTTON "&Accept", IDC_HK_ACCEPT, 168, 140, 40, 14 - PUSHBUTTON "Connect &Once", IDC_HK_ONCE, 216, 140, 64, 14 - PUSHBUTTON "More &info...", IDC_HK_MOREINFO, 60, 140, 64, 14 - PUSHBUTTON "&Help", IDHELP, 12, 140, 40, 14 + PUSHBUTTON "Cancel", IDCANCEL, 288, 220, 40, 14 + PUSHBUTTON "&Accept", IDC_HK_ACCEPT, 168, 220, 40, 14 + PUSHBUTTON "Connect &Once", IDC_HK_ONCE, 216, 220, 64, 14 + PUSHBUTTON "More &info...", IDC_HK_MOREINFO, 60, 220, 64, 14 + PUSHBUTTON "&Help", IDHELP, 12, 220, 40, 14 - EDITTEXT IDC_HK_HOST, 40, 28, 300, 12, ES_READONLY | ES_LEFT, 0 - EDITTEXT IDC_HK_FINGERPRINT, 40, 60, 300, 12, ES_READONLY | ES_LEFT, 0 -END + LTEXT "", IDC_HK_TITLE, 40, 20, 300, 12 -/* No accelerators used */ -IDD_HK_WRONG DIALOG DISCARDABLE 50, 50, 340, 200 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTY Security Alert" -FONT 8, "MS Shell Dlg" -BEGIN - LTEXT "WARNING - POTENTIAL SECURITY BREACH!", IDC_HK_TITLE, 40, 20, 300, 12 - - LTEXT "The host key does not match the one {APPNAME} has cached for this server:", 100, 40, 36, 300, 8 - LTEXT "This means that either the server administrator has changed the", 101, 40, 56, 300, 8 - LTEXT "host key, or you have actually connected to another computer", 102, 40, 64, 300, 8 - LTEXT "pretending to be the server.", 103, 40, 72, 300, 8 - LTEXT "The new {KEYTYPE} key fingerprint is:", 104, 40, 84, 300, 8 - LTEXT "If you were expecting this change and trust the new key, press", 105, 40, 104, 300, 8 - LTEXT """Accept"" to update {APPNAME}'s cache and continue connecting.", 106, 40, 112, 300, 8 - LTEXT "If you want to carry on connecting but without updating the cache,", 107, 40, 124, 300, 8 - LTEXT "press ""Connect Once"".", 108, 40, 132, 300, 8 - LTEXT "If you want to abandon the connection completely, press ""Cancel"".", 109, 40, 144, 300, 8 - LTEXT "Pressing ""Cancel"" is the ONLY guaranteed safe choice.", 110, 40, 152, 300, 8 - - ICON "", IDC_HK_ICON, 10, 16, 0, 0 - - PUSHBUTTON "Cancel", IDCANCEL, 288, 180, 40, 14 - PUSHBUTTON "Accept", IDC_HK_ACCEPT, 168, 180, 40, 14 - PUSHBUTTON "Connect Once", IDC_HK_ONCE, 216, 180, 64, 14 - PUSHBUTTON "More info...", IDC_HK_MOREINFO, 60, 180, 64, 14 - PUSHBUTTON "Help", IDHELP, 12, 180, 40, 14 - - EDITTEXT IDC_HK_HOST, 40, 44, 300, 12, ES_READONLY | ES_LEFT, 0 - EDITTEXT IDC_HK_FINGERPRINT, 40, 92, 300, 12, ES_READONLY | ES_LEFT, 0 + EDITTEXT IDC_HK_TEXT, 40, 20, 290, 200, ES_READONLY | ES_MULTILINE | ES_LEFT, WS_EX_STATICEDGE END /* Accelerators used: clw */ -IDD_HK_MOREINFO DIALOG DISCARDABLE 140, 40, 400, 156 +IDD_HK_MOREINFO DIALOG DISCARDABLE 140, 40, 400, 300 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "PuTTY: information about the server's host key" FONT 8, "MS Shell Dlg" CLASS "PuTTYHostKeyMoreInfo" BEGIN - LTEXT "SHA256 fingerprint:", 100, 12, 12, 80, 8 - EDITTEXT IDC_HKI_SHA256, 100, 10, 288, 12, ES_READONLY - LTEXT "MD5 fingerprint:", 101, 12, 28, 80, 8 - EDITTEXT IDC_HKI_MD5, 100, 26, 288, 12, ES_READONLY - LTEXT "Full public key:", 102, 12, 44, 376, 8 - EDITTEXT IDC_HKI_PUBKEY, 12, 54, 376, 64, ES_READONLY | ES_MULTILINE | ES_LEFT | ES_AUTOVSCROLL, WS_EX_STATICEDGE DEFPUSHBUTTON "&Close", IDOK, 176, 130, 48, 14 END diff --git a/windows/putty-rc.h b/windows/putty-rc.h index c1658e5d..35d9bcda 100644 --- a/windows/putty-rc.h +++ b/windows/putty-rc.h @@ -13,8 +13,7 @@ #define IDD_ABOUTBOX 111 #define IDD_RECONF 112 #define IDD_LICENCEBOX 113 -#define IDD_HK_ABSENT 114 -#define IDD_HK_WRONG 115 +#define IDD_HOSTKEY 114 #define IDD_HK_MOREINFO 116 #define IDD_CA_CONFIG 117 @@ -35,6 +34,7 @@ #define IDC_HK_ICON 98 #define IDC_HK_TITLE 99 +#define IDC_HK_TEXT 100 #define IDC_HK_ACCEPT 1001 #define IDC_HK_ONCE 1000 #define IDC_HK_HOST 1002 diff --git a/windows/window.c b/windows/window.c index 40eb5c14..12936808 100644 --- a/windows/window.c +++ b/windows/window.c @@ -346,6 +346,7 @@ static const SeatVtable win_seat_vt = { .confirm_ssh_host_key = win_seat_confirm_ssh_host_key, .confirm_weak_crypto_primitive = win_seat_confirm_weak_crypto_primitive, .confirm_weak_cached_hostkey = win_seat_confirm_weak_cached_hostkey, + .prompt_descriptions = win_seat_prompt_descriptions, .is_utf8 = win_seat_is_utf8, .echoedit_update = nullseat_echoedit_update, .get_x_display = nullseat_get_x_display,