diff --git a/console.c b/console.c index 62c64aff..0ecfb265 100644 --- a/console.c +++ b/console.c @@ -9,18 +9,6 @@ #include "misc.h" #include "console.h" -const char weakcrypto_msg_common_fmt[] = - "The first %s supported by the server is\n" - "%s, which is below the configured warning threshold.\n"; - -const char weakhk_msg_common_fmt[] = - "The first host key type we have stored for this server\n" - "is %s, which is below the configured warning threshold.\n" - "The server also provides the following types of host key\n" - "above the threshold, which we do not have stored:\n" - "%s\n"; - -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) @@ -30,6 +18,8 @@ const SeatDialogPromptDescriptions *console_prompt_descriptions(Seat *seat) .hk_connect_once_action = "enter \"n\"", .hk_cancel_action = "press Return", .hk_cancel_action_Participle = "Pressing Return", + .weak_accept_action = "enter \"y\"", + .weak_cancel_action = "enter \"n\"", }; return &descs; } diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c index 165c6c0a..4a797d38 100644 --- a/proxy/sshproxy.c +++ b/proxy/sshproxy.c @@ -430,8 +430,32 @@ static SeatPromptResult sshproxy_confirm_ssh_host_key( return SPR_SW_ABORT("Noninteractive SSH proxy cannot confirm host key"); } +static void sshproxy_format_seatdialogtext(strbuf *sb, SeatDialogText *text) +{ + for (SeatDialogTextItem *item = text->items, + *end = item+text->nitems; item < end; item++) { + switch (item->type) { + case SDT_SCARY_HEADING: + case SDT_PARA: + case SDT_DISPLAY: + put_stringz(sb, item->text); + put_byte(sb, '\n'); + break; + case SDT_BATCH_ABORT: + put_stringz(sb, item->text); + put_byte(sb, '\n'); + goto endloop; + default: + break; + } + } + + endloop: + while (strbuf_chomp(sb, '\n')); +} + static SeatPromptResult sshproxy_confirm_weak_crypto_primitive( - Seat *seat, const char *algtype, const char *algname, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { SshProxy *sp = container_of(seat, SshProxy, seat); @@ -442,22 +466,24 @@ static SeatPromptResult sshproxy_confirm_weak_crypto_primitive( * request on to it. */ return seat_confirm_weak_crypto_primitive( - wrap(sp->clientseat), algtype, algname, callback, ctx); + wrap(sp->clientseat), text, callback, ctx); } /* * Otherwise, behave as if we're in batch mode: take the safest * option. */ - sshproxy_error(sp, "First %s supported by server is %s, below warning " - "threshold. Abandoning proxy SSH connection.", - algtype, algname); + strbuf *sb = strbuf_new(); + sshproxy_format_seatdialogtext(sb, text); + sshproxy_error(sp, sb->s); + strbuf_free(sb); + return SPR_SW_ABORT("Noninteractive SSH proxy cannot confirm " "weak crypto primitive"); } static SeatPromptResult sshproxy_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { SshProxy *sp = container_of(seat, SshProxy, seat); @@ -468,16 +494,18 @@ static SeatPromptResult sshproxy_confirm_weak_cached_hostkey( * request on to it. */ return seat_confirm_weak_cached_hostkey( - wrap(sp->clientseat), algname, betteralgs, callback, ctx); + wrap(sp->clientseat), text, callback, ctx); } /* * Otherwise, behave as if we're in batch mode: take the safest * option. */ - sshproxy_error(sp, "First host key type stored for server is %s, below " - "warning threshold. Abandoning proxy SSH connection.", - algname); + strbuf *sb = strbuf_new(); + sshproxy_format_seatdialogtext(sb, text); + sshproxy_error(sp, sb->s); + strbuf_free(sb); + return SPR_SW_ABORT("Noninteractive SSH proxy cannot confirm " "weak cached host key"); } diff --git a/putty.h b/putty.h index 6c5ed992..e4444c0e 100644 --- a/putty.h +++ b/putty.h @@ -1293,7 +1293,7 @@ struct SeatVtable { * confirm_ssh_host_key above. */ SeatPromptResult (*confirm_weak_crypto_primitive)( - Seat *seat, const char *algtype, const char *algname, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); /* @@ -1304,11 +1304,10 @@ struct SeatVtable { * This form is used in the case where we're using a host key * below the warning threshold because that's the best one we have * cached, but at least one host key algorithm *above* the - * threshold is available that we don't have cached. 'betteralgs' - * lists the better algorithm(s). + * threshold is available that we don't have cached. */ SeatPromptResult (*confirm_weak_cached_hostkey)( - Seat *seat, const char *algname, const char *betteralgs, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); /* @@ -1444,15 +1443,15 @@ static inline SeatPromptResult seat_confirm_ssh_host_key( { return iseat.seat->vt->confirm_ssh_host_key( 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, + InteractionReadySeat iseat, SeatDialogText *text, void (*cb)(void *ctx, SeatPromptResult result), void *ctx) { return iseat.seat->vt->confirm_weak_crypto_primitive( - iseat.seat, atyp, aname, cb, ctx); } + iseat.seat, text, cb, ctx); } static inline SeatPromptResult seat_confirm_weak_cached_hostkey( - InteractionReadySeat iseat, const char *aname, const char *better, + InteractionReadySeat iseat, SeatDialogText *text, void (*cb)(void *ctx, SeatPromptResult result), void *ctx) { return iseat.seat->vt->confirm_weak_cached_hostkey( - iseat.seat, aname, better, cb, ctx); } + iseat.seat, text, cb, ctx); } static inline const SeatDialogPromptDescriptions *seat_prompt_descriptions( Seat *seat) { return seat->vt->prompt_descriptions(seat); } @@ -1505,6 +1504,7 @@ struct SeatDialogPromptDescriptions { const char *hk_accept_action; const char *hk_connect_once_action; const char *hk_cancel_action, *hk_cancel_action_Participle; + const char *weak_accept_action, *weak_cancel_action; }; /* In the utils subdir: print a message to the Seat which can't be @@ -1537,10 +1537,10 @@ SeatPromptResult nullseat_confirm_ssh_host_key( 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, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); SeatPromptResult nullseat_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); const SeatDialogPromptDescriptions *nullseat_prompt_descriptions(Seat *seat); bool nullseat_is_never_utf8(Seat *seat); @@ -1573,10 +1573,10 @@ SeatPromptResult console_confirm_ssh_host_key( 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, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); SeatPromptResult console_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); StripCtrlChars *console_stripctrl_new( Seat *seat, BinarySink *bs_out, SeatInteractionContext sic); diff --git a/ssh.h b/ssh.h index 2df0e3ed..b871ce8b 100644 --- a/ssh.h +++ b/ssh.h @@ -1908,6 +1908,12 @@ SeatPromptResult verify_ssh_host_key( ssh_key *key, const char *keytype, char *keystr, const char *keydisp, char **fingerprints, int ca_count, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); +SeatPromptResult confirm_weak_crypto_primitive( + InteractionReadySeat iseat, const char *algtype, const char *algname, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); +SeatPromptResult confirm_weak_cached_hostkey( + InteractionReadySeat iseat, const char *algname, const char **betteralgs, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx); typedef struct ssh_transient_hostkey_cache ssh_transient_hostkey_cache; ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void); diff --git a/ssh/common.c b/ssh/common.c index ad2d4632..2a1297c0 100644 --- a/ssh/common.c +++ b/ssh/common.c @@ -1085,6 +1085,79 @@ SeatPromptResult verify_ssh_host_key( return toret; } +SeatPromptResult confirm_weak_crypto_primitive( + InteractionReadySeat iseat, const char *algtype, const char *algname, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) +{ + SeatDialogText *text = seat_dialog_text_new(); + const SeatDialogPromptDescriptions *pds = + seat_prompt_descriptions(iseat.seat); + + seat_dialog_text_append(text, SDT_TITLE, "%s Security Alert", appname); + + seat_dialog_text_append( + text, SDT_PARA, + "The first %s supported by the server is %s, " + "which is below the configured warning threshold.", + algtype, algname); + + /* In batch mode, we print the above information and then this + * abort message, and stop. */ + seat_dialog_text_append(text, SDT_BATCH_ABORT, "Connection abandoned."); + + seat_dialog_text_append( + text, SDT_PARA, "To accept the risk and continue, %s. " + "To abandon the connection, %s.", + pds->weak_accept_action, pds->weak_cancel_action); + + seat_dialog_text_append(text, SDT_PROMPT, "Continue with connection?"); + + SeatPromptResult toret = seat_confirm_weak_crypto_primitive( + iseat, text, callback, ctx); + seat_dialog_text_free(text); + return toret; +} + +SeatPromptResult confirm_weak_cached_hostkey( + InteractionReadySeat iseat, const char *algname, const char **betteralgs, + void (*callback)(void *ctx, SeatPromptResult result), void *ctx) +{ + SeatDialogText *text = seat_dialog_text_new(); + const SeatDialogPromptDescriptions *pds = + seat_prompt_descriptions(iseat.seat); + + seat_dialog_text_append(text, SDT_TITLE, "%s Security Alert", appname); + + seat_dialog_text_append( + text, SDT_PARA, + "The first host key type we have stored for this server " + "is %s, which is below the configured warning threshold.", algname); + + seat_dialog_text_append( + text, SDT_PARA, + "The server also provides the following types of host key " + "above the threshold, which we do not have stored:"); + + for (const char **p = betteralgs; *p; p++) + seat_dialog_text_append(text, SDT_DISPLAY, "%s", *p); + + /* In batch mode, we print the above information and then this + * abort message, and stop. */ + seat_dialog_text_append(text, SDT_BATCH_ABORT, "Connection abandoned."); + + seat_dialog_text_append( + text, SDT_PARA, "To accept the risk and continue, %s. " + "To abandon the connection, %s.", + pds->weak_accept_action, pds->weak_cancel_action); + + seat_dialog_text_append(text, SDT_PROMPT, "Continue with connection?"); + + SeatPromptResult toret = seat_confirm_weak_cached_hostkey( + iseat, text, callback, ctx); + seat_dialog_text_free(text); + return toret; +} + /* ---------------------------------------------------------------------- * Common functions shared between SSH-1 layers. */ diff --git a/ssh/login1.c b/ssh/login1.c index aa731848..88d1dd04 100644 --- a/ssh/login1.c +++ b/ssh/login1.c @@ -323,7 +323,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) /* Warn about chosen cipher if necessary. */ if (warn) { - s->spr = seat_confirm_weak_crypto_primitive( + s->spr = confirm_weak_crypto_primitive( ppl_get_iseat(&s->ppl), "cipher", cipher_string, ssh1_login_dialog_callback, s); crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE); diff --git a/ssh/server.c b/ssh/server.c index 188426b3..a0aa277d 100644 --- a/ssh/server.c +++ b/ssh/server.c @@ -98,11 +98,11 @@ void mainchan_terminal_size(mainchan *mc, int width, int height) {} /* Seat functions to ensure we don't get choosy about crypto - as the * server, it's not up to us to give user warnings */ static SeatPromptResult server_confirm_weak_crypto_primitive( - Seat *seat, const char *algtype, const char *algname, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { return SPR_OK; } static SeatPromptResult server_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { return SPR_OK; } diff --git a/ssh/transport2.c b/ssh/transport2.c index 69346b4f..053ca570 100644 --- a/ssh/transport2.c +++ b/ssh/transport2.c @@ -1514,7 +1514,8 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) if (s->warn_hk) { int j, k; - char *betteralgs; + const char **betteralgs = NULL; + size_t nbetter = 0, bettersize = 0; /* * Change warning box wording depending on why we chose a @@ -1523,7 +1524,6 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) * could usefully cross-certify. Otherwise, use the same * standard wording as any other weak crypto primitive. */ - betteralgs = NULL; for (j = 0; j < s->n_uncert_hostkeys; j++) { const struct ssh_signkey_with_user_pref_id *hktype = &ssh2_hostkey_algs[s->uncert_hostkeys[j]]; @@ -1538,19 +1538,16 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) } } if (better) { - if (betteralgs) { - char *old_ba = betteralgs; - betteralgs = dupcat(betteralgs, ",", hktype->alg->ssh_id); - sfree(old_ba); - } else { - betteralgs = dupstr(hktype->alg->ssh_id); - } + sgrowarray(betteralgs, bettersize, nbetter); + betteralgs[nbetter++] = hktype->alg->ssh_id; } } if (betteralgs) { /* Use the special warning prompt that lets us provide * a list of better algorithms */ - s->spr = seat_confirm_weak_cached_hostkey( + sgrowarray(betteralgs, bettersize, nbetter); + betteralgs[nbetter] = NULL; + s->spr = confirm_weak_cached_hostkey( ppl_get_iseat(&s->ppl), s->hostkey_alg->ssh_id, betteralgs, ssh2_transport_dialog_callback, s); sfree(betteralgs); @@ -2389,7 +2386,7 @@ static int ca_blob_compare(void *av, void *bv) } /* - * Wrapper on seat_confirm_weak_crypto_primitive(), which uses the + * Wrapper on confirm_weak_crypto_primitive(), which uses the * tree234 s->weak_algorithms_consented_to to ensure we ask at most * once about any given crypto primitive. */ @@ -2401,7 +2398,7 @@ static SeatPromptResult ssh2_transport_confirm_weak_crypto_primitive( return SPR_OK; add234(s->weak_algorithms_consented_to, (void *)alg); - return seat_confirm_weak_crypto_primitive( + return confirm_weak_crypto_primitive( ppl_get_iseat(&s->ppl), type, name, ssh2_transport_dialog_callback, s); } diff --git a/stubs/null-seat.c b/stubs/null-seat.c index 37cb0f4c..2db45127 100644 --- a/stubs/null-seat.c +++ b/stubs/null-seat.c @@ -26,11 +26,11 @@ SeatPromptResult nullseat_confirm_ssh_host_key( 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( - Seat *seat, const char *algtype, const char *algname, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { return SPR_SW_ABORT("this seat can't handle interactive prompts"); } SeatPromptResult nullseat_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { return SPR_SW_ABORT("this seat can't handle interactive prompts"); } bool nullseat_is_never_utf8(Seat *seat) { return false; } @@ -60,6 +60,8 @@ const SeatDialogPromptDescriptions *nullseat_prompt_descriptions(Seat *seat) .hk_connect_once_action = "", .hk_cancel_action = "", .hk_cancel_action_Participle = "", + .weak_accept_action = "", + .weak_cancel_action = "", }; return &descs; } diff --git a/unix/console.c b/unix/console.c index 286ecf29..ba35ccea 100644 --- a/unix/console.c +++ b/unix/console.c @@ -102,20 +102,18 @@ static int block_and_read(int fd, void *buf, size_t len) return ret; } -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) +/* + * Helper function to print the message from a SeatDialogText. Returns + * the final prompt to print on the input line, or NULL if a + * batch-mode abort is needed. In the latter case it will have printed + * the abort text already. + */ +static const char *console_print_seatdialogtext(SeatDialogText *text) { - char line[32]; - struct termios cf; const char *prompt = NULL; - stdio_sink errsink[1]; stdio_sink_init(errsink, stderr); - premsg(&cf); - for (SeatDialogTextItem *item = text->items, *end = item+text->nitems; item < end; item++) { switch (item->type) { @@ -135,8 +133,7 @@ SeatPromptResult console_confirm_ssh_host_key( 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"); + return NULL; } break; case SDT_PROMPT: @@ -146,7 +143,26 @@ SeatPromptResult console_confirm_ssh_host_key( break; } } + assert(prompt); /* something in the SeatDialogText should have set this */ + return prompt; +} + +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) +{ + char line[32]; + struct termios cf; + + premsg(&cf); + + const char *prompt = console_print_seatdialogtext(text); + if (!prompt) { + postmsg(&cf); + return SPR_SW_ABORT("Cannot confirm a host key in batch mode"); + } while (true) { fprintf(stderr, @@ -202,23 +218,22 @@ SeatPromptResult console_confirm_ssh_host_key( } SeatPromptResult console_confirm_weak_crypto_primitive( - Seat *seat, const char *algtype, const char *algname, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { char line[32]; struct termios cf; premsg(&cf); - fprintf(stderr, weakcrypto_msg_common_fmt, algtype, algname); - if (console_batch_mode) { - fputs(console_abandoned_msg, stderr); + const char *prompt = console_print_seatdialogtext(text); + if (!prompt) { postmsg(&cf); return SPR_SW_ABORT("Cannot confirm a weak crypto primitive " "in batch mode"); } - fputs(console_continue_prompt, stderr); + fprintf(stderr, "%s (y/n) ", prompt); fflush(stderr); { @@ -244,23 +259,22 @@ SeatPromptResult console_confirm_weak_crypto_primitive( } SeatPromptResult console_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { char line[32]; struct termios cf; premsg(&cf); - fprintf(stderr, weakhk_msg_common_fmt, algname, betteralgs); - if (console_batch_mode) { - fputs(console_abandoned_msg, stderr); + const char *prompt = console_print_seatdialogtext(text); + if (!prompt) { postmsg(&cf); return SPR_SW_ABORT("Cannot confirm a weak cached host key " "in batch mode"); } - fputs(console_continue_prompt, stderr); + fprintf(stderr, "%s (y/n) ", prompt); fflush(stderr); { diff --git a/unix/dialog.c b/unix/dialog.c index 5663ca92..63a94545 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -3609,10 +3609,54 @@ const SeatDialogPromptDescriptions *gtk_seat_prompt_descriptions(Seat *seat) .hk_connect_once_action = "press \"Connect Once\"", .hk_cancel_action = "press \"Cancel\"", .hk_cancel_action_Participle = "Pressing \"Cancel\"", + .weak_accept_action = "press \"Yes\"", + .weak_cancel_action = "press \"No\"", }; return &descs; } +/* + * Format a SeatDialogText into a strbuf, also adjusting the box width + * to cope with displayed text. Returns the dialog box title. + */ +static const char *gtk_format_seatdialogtext( + SeatDialogText *text, strbuf *dlg_text, int *width) +{ + const char *dlg_title = NULL; + + 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; + } + } + + /* + * Trim trailing newlines. + */ + while (strbuf_chomp(dlg_text, '\n')); + + return dlg_title; +} + SeatPromptResult gtk_seat_confirm_ssh_host_key( Seat *seat, const char *host, int port, const char *keytype, char *keystr, SeatDialogText *text, HelpCtx helpctx, @@ -3627,35 +3671,9 @@ SeatPromptResult gtk_seat_confirm_ssh_host_key( button_array_hostkey, lenof(button_array_hostkey), }; - 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')); + strbuf *dlg_text = strbuf_new(); + const char *dlg_title = gtk_format_seatdialogtext(text, dlg_text, &width); GtkWidget *mainwin, *msgbox; @@ -3752,19 +3770,16 @@ static void simple_prompt_result_spr_callback(void *vctx, int result) * below the configured 'warn' threshold). */ SeatPromptResult gtk_seat_confirm_weak_crypto_primitive( - Seat *seat, const char *algtype, const char *algname, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { - static const char msg[] = - "The first %s supported by the server is " - "%s, which is below the configured warning threshold.\n" - "Continue with connection?"; - - char *text; struct simple_prompt_result_spr_ctx *result_ctx; GtkWidget *mainwin, *msgbox; - text = dupprintf(msg, algtype, algname); + int width = string_width("Reasonably long line of text " + "as a width template"); + strbuf *dlg_text = strbuf_new(); + const char *dlg_title = gtk_format_seatdialogtext(text, dlg_text, &width); result_ctx = snew(struct simple_prompt_result_spr_ctx); result_ctx->callback = callback; @@ -3774,33 +3789,26 @@ SeatPromptResult gtk_seat_confirm_weak_crypto_primitive( mainwin = GTK_WIDGET(gtk_seat_get_window(seat)); msgbox = create_message_box( - mainwin, "PuTTY Security Alert", text, - string_width("Reasonably long line of text as a width template"), - false, &buttons_yn, simple_prompt_result_spr_callback, result_ctx); + mainwin, dlg_title, dlg_text->s, width, false, + &buttons_yn, simple_prompt_result_spr_callback, result_ctx); register_dialog(seat, result_ctx->dialog_slot, msgbox); - sfree(text); + strbuf_free(dlg_text); return SPR_INCOMPLETE; } SeatPromptResult gtk_seat_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { - static const char msg[] = - "The first host key type we have stored for this server\n" - "is %s, which is below the configured warning threshold.\n" - "The server also provides the following types of host key\n" - "above the threshold, which we do not have stored:\n" - "%s\n" - "Continue with connection?"; - - char *text; struct simple_prompt_result_spr_ctx *result_ctx; GtkWidget *mainwin, *msgbox; - text = dupprintf(msg, algname, betteralgs); + int width = string_width("is ecdsa-nistp521, which is below the configured" + " warning threshold."); + strbuf *dlg_text = strbuf_new(); + const char *dlg_title = gtk_format_seatdialogtext(text, dlg_text, &width); result_ctx = snew(struct simple_prompt_result_spr_ctx); result_ctx->callback = callback; @@ -3810,13 +3818,11 @@ SeatPromptResult gtk_seat_confirm_weak_cached_hostkey( mainwin = GTK_WIDGET(gtk_seat_get_window(seat)); msgbox = create_message_box( - mainwin, "PuTTY Security Alert", text, - string_width("is ecdsa-nistp521, which is below the configured" - " warning threshold."), - false, &buttons_yn, simple_prompt_result_spr_callback, result_ctx); + mainwin, dlg_title, dlg_text->s, width, false, + &buttons_yn, simple_prompt_result_spr_callback, result_ctx); register_dialog(seat, result_ctx->dialog_slot, msgbox); - sfree(text); + strbuf_free(dlg_text); return SPR_INCOMPLETE; } diff --git a/unix/platform.h b/unix/platform.h index 3b1db9ba..a6bc7ad1 100644 --- a/unix/platform.h +++ b/unix/platform.h @@ -225,10 +225,10 @@ SeatPromptResult gtk_seat_confirm_ssh_host_key( 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, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); SeatPromptResult gtk_seat_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); const SeatDialogPromptDescriptions *gtk_seat_prompt_descriptions(Seat *seat); #ifdef MAY_REFER_TO_GTK_IN_HEADERS diff --git a/utils/tempseat.c b/utils/tempseat.c index ad37b0e3..39e641f9 100644 --- a/utils/tempseat.c +++ b/utils/tempseat.c @@ -255,7 +255,7 @@ static SeatPromptResult tempseat_confirm_ssh_host_key( } static SeatPromptResult tempseat_confirm_weak_crypto_primitive( - Seat *seat, const char *algtype, const char *algname, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { unreachable("confirm_weak_crypto_primitive " @@ -263,7 +263,7 @@ static SeatPromptResult tempseat_confirm_weak_crypto_primitive( } static SeatPromptResult tempseat_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { unreachable("confirm_weak_cached_hostkey " diff --git a/windows/console.c b/windows/console.c index 2fd572b4..df10ae51 100644 --- a/windows/console.c +++ b/windows/console.c @@ -32,20 +32,18 @@ void console_print_error_msg(const char *prefix, const char *msg) 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) +/* + * Helper function to print the message from a SeatDialogText. Returns + * the final prompt to print on the input line, or NULL if a + * batch-mode abort is needed. In the latter case it will have printed + * the abort text already. + */ +static const char *console_print_seatdialogtext(SeatDialogText *text) { - 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) { @@ -65,7 +63,7 @@ SeatPromptResult console_confirm_ssh_host_key( if (console_batch_mode) { fprintf(stderr, "%s\n", item->text); fflush(stderr); - return SPR_SW_ABORT("Cannot confirm a host key in batch mode"); + return NULL; } break; case SDT_PROMPT: @@ -76,6 +74,22 @@ SeatPromptResult console_confirm_ssh_host_key( } } assert(prompt); /* something in the SeatDialogText should have set this */ + return prompt; +} + +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 = console_print_seatdialogtext(text); + if (!prompt) + return SPR_SW_ABORT("Cannot confirm a host key in batch mode"); + + char line[32]; while (true) { fprintf(stderr, @@ -128,23 +142,20 @@ SeatPromptResult console_confirm_ssh_host_key( } SeatPromptResult console_confirm_weak_crypto_primitive( - Seat *seat, const char *algtype, const char *algname, + Seat *seat, SeatDialogText *text, 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); + const char *prompt = console_print_seatdialogtext(text); + if (!prompt) return SPR_SW_ABORT("Cannot confirm a weak crypto primitive " "in batch mode"); - } - fputs(console_continue_prompt, stderr); + char line[32]; + + fprintf(stderr, "%s (y/n) ", prompt); fflush(stderr); hin = GetStdHandle(STD_INPUT_HANDLE); @@ -163,23 +174,20 @@ SeatPromptResult console_confirm_weak_crypto_primitive( } SeatPromptResult console_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, + Seat *seat, SeatDialogText *text, 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); + const char *prompt = console_print_seatdialogtext(text); + if (!prompt) return SPR_SW_ABORT("Cannot confirm a weak cached host key " "in batch mode"); - } - fputs(console_continue_prompt, stderr); + char line[32]; + + fprintf(stderr, "%s (y/n) ", prompt); fflush(stderr); hin = GetStdHandle(STD_INPUT_HANDLE); diff --git a/windows/dialog.c b/windows/dialog.c index 5e49cca9..03d63806 100644 --- a/windows/dialog.c +++ b/windows/dialog.c @@ -963,6 +963,39 @@ static INT_PTR HostKeyMoreInfoProc(HWND hwnd, UINT msg, WPARAM wParam, return 0; } +static const char *process_seatdialogtext( + strbuf *dlg_text, const char **scary_heading, SeatDialogText *text) +{ + const char *dlg_title = ""; + + for (SeatDialogTextItem *item = text->items, + *end = item + 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: + assert(scary_heading != NULL && "only expect a scary heading if " + "the dialog has somewhere to put it"); + *scary_heading = item->text; + break; + case SDT_TITLE: + dlg_title = item->text; + break; + default: + break; + } + } + + /* Trim any trailing newlines */ + while (strbuf_chomp(dlg_text, '\r') || strbuf_chomp(dlg_text, '\n')); + + return dlg_title; +} + static INT_PTR HostKeyDialogProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, void *vctx) { @@ -971,32 +1004,15 @@ static INT_PTR HostKeyDialogProc(HWND hwnd, UINT msg, switch (msg) { case WM_INITDIALOG: { strbuf *dlg_text = strbuf_new(); - const char *dlg_title = ""; - ctx->has_title = false; - LPCTSTR iconid = IDI_QUESTION; + const char *scary_heading = NULL; + const char *dlg_title = process_seatdialogtext( + dlg_text, &scary_heading, ctx->text); - 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->has_title = true; - break; - case SDT_TITLE: - dlg_title = item->text; - break; - default: - break; - } + LPCTSTR iconid = IDI_QUESTION; + if (scary_heading) { + SetDlgItemText(hwnd, IDC_HK_TITLE, scary_heading); + iconid = IDI_WARNING; } - while (strbuf_chomp(dlg_text, '\r') || strbuf_chomp(dlg_text, '\n')); SetDlgItemText(hwnd, IDC_HK_TEXT, dlg_text->s); MakeDlgItemBorderless(hwnd, IDC_HK_TEXT); @@ -1135,6 +1151,8 @@ const SeatDialogPromptDescriptions *win_seat_prompt_descriptions(Seat *seat) .hk_connect_once_action = "press \"Connect Once\"", .hk_cancel_action = "press \"Cancel\"", .hk_cancel_action_Participle = "Pressing \"Cancel\"", + .weak_accept_action = "press \"Yes\"", + .weak_cancel_action = "press \"No\"", }; return &descs; } @@ -1169,25 +1187,17 @@ SeatPromptResult win_seat_confirm_ssh_host_key( * below the configured 'warn' threshold). */ SeatPromptResult win_seat_confirm_weak_crypto_primitive( - Seat *seat, const char *algtype, const char *algname, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { - static const char mbtitle[] = "%s Security Alert"; - static const char msg[] = - "The first %s supported by the server\n" - "is %s, which is below the configured\n" - "warning threshold.\n" - "Do you want to continue with this connection?\n"; - char *message, *title; - int mbret; + strbuf *dlg_text = strbuf_new(); + const char *dlg_title = process_seatdialogtext(dlg_text, NULL, text); - message = dupprintf(msg, algtype, algname); - title = dupprintf(mbtitle, appname); - mbret = MessageBox(NULL, message, title, - MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2); + int mbret = MessageBox(NULL, dlg_text->s, dlg_title, + MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2); socket_reselect_all(); - sfree(message); - sfree(title); + strbuf_free(dlg_text); + if (mbret == IDYES) return SPR_OK; else @@ -1195,27 +1205,17 @@ SeatPromptResult win_seat_confirm_weak_crypto_primitive( } SeatPromptResult win_seat_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx) { - static const char mbtitle[] = "%s Security Alert"; - static const char msg[] = - "The first host key type we have stored for this server\n" - "is %s, which is below the configured warning threshold.\n" - "The server also provides the following types of host key\n" - "above the threshold, which we do not have stored:\n" - "%s\n" - "Do you want to continue with this connection?\n"; - char *message, *title; - int mbret; + strbuf *dlg_text = strbuf_new(); + const char *dlg_title = process_seatdialogtext(dlg_text, NULL, text); - message = dupprintf(msg, algname, betteralgs); - title = dupprintf(mbtitle, appname); - mbret = MessageBox(NULL, message, title, - MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2); + int mbret = MessageBox(NULL, dlg_text->s, dlg_title, + MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2); socket_reselect_all(); - sfree(message); - sfree(title); + strbuf_free(dlg_text); + if (mbret == IDYES) return SPR_OK; else diff --git a/windows/platform.h b/windows/platform.h index 959a207c..cd9ef989 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -237,10 +237,10 @@ SeatPromptResult win_seat_confirm_ssh_host_key( 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, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); SeatPromptResult win_seat_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, + Seat *seat, SeatDialogText *text, void (*callback)(void *ctx, SeatPromptResult result), void *ctx); const SeatDialogPromptDescriptions *win_seat_prompt_descriptions(Seat *seat);