mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 17:38:00 +00:00
9fcbb86f71
This centralises the messages for weak crypto algorithms (general, and host keys in particular, the latter including a list of all the other available host key types) into ssh/common.c, in much the same way as we previously did for ordinary host key warnings. The reason is the same too: I'm about to want to vary the text in one of those dialog boxes, so it's convenient to start by putting it somewhere that I can modify just once.
431 lines
14 KiB
C
431 lines
14 KiB
C
/*
|
|
* Implementation of the Seat trait that buffers output and other
|
|
* events until it can give them back to a real Seat.
|
|
*
|
|
* This is used by the SSH proxying code, which temporarily takes over
|
|
* the real user-facing Seat so that it can issue host key warnings,
|
|
* password prompts etc for the proxy SSH connection. While it's got
|
|
* the real Seat, it gives the primary connection's backend one of
|
|
* these temporary Seats in the interim, so that if the backend wants
|
|
* to send some kind of initial output, or start by reconfiguring the
|
|
* trust status, or what have you, then it can do that without having
|
|
* to keep careful track of the fact that its Seat is out on loan.
|
|
*/
|
|
|
|
#include "putty.h"
|
|
|
|
struct output_chunk {
|
|
struct output_chunk *next;
|
|
SeatOutputType type;
|
|
size_t size;
|
|
};
|
|
|
|
typedef struct TempSeat TempSeat;
|
|
struct TempSeat {
|
|
Seat *realseat;
|
|
|
|
/*
|
|
* Single bufchain to hold all the buffered output, regardless of
|
|
* its type.
|
|
*/
|
|
bufchain output;
|
|
|
|
/*
|
|
* List of pieces of that bufchain that are intended for one or
|
|
* another output destination
|
|
*/
|
|
struct output_chunk *outchunk_head, *outchunk_tail;
|
|
|
|
bool seen_session_started;
|
|
bool seen_remote_exit;
|
|
bool seen_remote_disconnect;
|
|
bool seen_update_specials_menu;
|
|
bool seen_echoedit_update, echoing, editing;
|
|
bool seen_trust_status, trusted;
|
|
|
|
Seat seat;
|
|
};
|
|
|
|
/* ----------------------------------------------------------------------
|
|
* Methods we can usefully buffer, and pass their results on to the
|
|
* real Seat in tempseat_flush().
|
|
*/
|
|
|
|
static size_t tempseat_output(Seat *seat, SeatOutputType type,
|
|
const void *data, size_t len)
|
|
{
|
|
TempSeat *ts = container_of(seat, TempSeat, seat);
|
|
|
|
bufchain_add(&ts->output, data, len);
|
|
|
|
if (!(ts->outchunk_tail && ts->outchunk_tail->type == type)) {
|
|
struct output_chunk *new_chunk = snew(struct output_chunk);
|
|
|
|
new_chunk->type = type;
|
|
new_chunk->size = 0;
|
|
|
|
new_chunk->next = NULL;
|
|
if (ts->outchunk_tail)
|
|
ts->outchunk_tail->next = new_chunk;
|
|
else
|
|
ts->outchunk_head = new_chunk;
|
|
ts->outchunk_tail = new_chunk;
|
|
}
|
|
ts->outchunk_tail->size += len;
|
|
|
|
return bufchain_size(&ts->output);
|
|
}
|
|
|
|
static void tempseat_notify_session_started(Seat *seat)
|
|
{
|
|
TempSeat *ts = container_of(seat, TempSeat, seat);
|
|
ts->seen_session_started = true;
|
|
}
|
|
|
|
static void tempseat_notify_remote_exit(Seat *seat)
|
|
{
|
|
TempSeat *ts = container_of(seat, TempSeat, seat);
|
|
ts->seen_remote_exit = true;
|
|
}
|
|
|
|
static void tempseat_notify_remote_disconnect(Seat *seat)
|
|
{
|
|
TempSeat *ts = container_of(seat, TempSeat, seat);
|
|
ts->seen_remote_disconnect = true;
|
|
}
|
|
|
|
static void tempseat_update_specials_menu(Seat *seat)
|
|
{
|
|
TempSeat *ts = container_of(seat, TempSeat, seat);
|
|
ts->seen_update_specials_menu = true;
|
|
}
|
|
|
|
static void tempseat_echoedit_update(Seat *seat, bool echoing, bool editing)
|
|
{
|
|
TempSeat *ts = container_of(seat, TempSeat, seat);
|
|
ts->seen_echoedit_update = true;
|
|
ts->echoing = echoing;
|
|
ts->editing = editing;
|
|
}
|
|
|
|
static void tempseat_set_trust_status(Seat *seat, bool trusted)
|
|
{
|
|
TempSeat *ts = container_of(seat, TempSeat, seat);
|
|
ts->seen_trust_status = true;
|
|
ts->trusted = trusted;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------
|
|
* Methods we can safely pass straight on to the real Seat, usually
|
|
* (but not in every case) because they're read-only queries.
|
|
*/
|
|
|
|
static char *tempseat_get_ttymode(Seat *seat, const char *mode)
|
|
{
|
|
TempSeat *ts = container_of(seat, TempSeat, seat);
|
|
return seat_get_ttymode(ts->realseat, mode);
|
|
}
|
|
|
|
static void tempseat_set_busy_status(Seat *seat, BusyStatus status)
|
|
{
|
|
TempSeat *ts = container_of(seat, TempSeat, seat);
|
|
/*
|
|
* set_busy_status is generally called when something is about to
|
|
* do some single-threaded, event-loop blocking computation. This
|
|
* _shouldn't_ happen in a backend while it's waiting for a
|
|
* network connection to be made, but if for some reason it were
|
|
* to, there's no reason we can't just pass this straight to the
|
|
* real seat, because we expect that it will mark itself busy,
|
|
* compute, and mark itself unbusy, all between yields to the
|
|
* event loop that might give whatever else is using the real Seat
|
|
* an opportunity to do anything.
|
|
*/
|
|
seat_set_busy_status(ts->realseat, status);
|
|
}
|
|
|
|
static bool tempseat_is_utf8(Seat *seat)
|
|
{
|
|
TempSeat *ts = container_of(seat, TempSeat, seat);
|
|
return seat_is_utf8(ts->realseat);
|
|
}
|
|
|
|
static const char *tempseat_get_x_display(Seat *seat)
|
|
{
|
|
TempSeat *ts = container_of(seat, TempSeat, seat);
|
|
return seat_get_x_display(ts->realseat);
|
|
}
|
|
|
|
static bool tempseat_get_windowid(Seat *seat, long *id_out)
|
|
{
|
|
TempSeat *ts = container_of(seat, TempSeat, seat);
|
|
return seat_get_windowid(ts->realseat, id_out);
|
|
}
|
|
|
|
static bool tempseat_get_window_pixel_size(Seat *seat, int *width, int *height)
|
|
{
|
|
TempSeat *ts = container_of(seat, TempSeat, seat);
|
|
return seat_get_window_pixel_size(ts->realseat, width, height);
|
|
}
|
|
|
|
static StripCtrlChars *tempseat_stripctrl_new(
|
|
Seat *seat, BinarySink *bs_out, SeatInteractionContext sic)
|
|
{
|
|
TempSeat *ts = container_of(seat, TempSeat, seat);
|
|
return seat_stripctrl_new(ts->realseat, bs_out, sic);
|
|
}
|
|
|
|
static bool tempseat_verbose(Seat *seat)
|
|
{
|
|
TempSeat *ts = container_of(seat, TempSeat, seat);
|
|
return seat_verbose(ts->realseat);
|
|
}
|
|
|
|
static bool tempseat_interactive(Seat *seat)
|
|
{
|
|
TempSeat *ts = container_of(seat, TempSeat, seat);
|
|
return seat_interactive(ts->realseat);
|
|
}
|
|
|
|
static bool tempseat_get_cursor_position(Seat *seat, int *x, int *y)
|
|
{
|
|
TempSeat *ts = container_of(seat, TempSeat, seat);
|
|
return seat_get_cursor_position(ts->realseat, x, y);
|
|
}
|
|
|
|
static bool tempseat_can_set_trust_status(Seat *seat)
|
|
{
|
|
TempSeat *ts = container_of(seat, TempSeat, seat);
|
|
return seat_can_set_trust_status(ts->realseat);
|
|
}
|
|
|
|
static bool tempseat_has_mixed_input_stream(Seat *seat)
|
|
{
|
|
TempSeat *ts = container_of(seat, TempSeat, 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.
|
|
*
|
|
* A backend in possession of a TempSeat ought to be sitting and
|
|
* patiently waiting for a network connection attempt to either
|
|
* succeed or fail. And it should be aware of the possibility that the
|
|
* proxy setup code to which it has lent the real Seat might need to
|
|
* present interactive prompts - that's the whole point of lending out
|
|
* the Seat in the first place - so it absolutely shouldn't get any
|
|
* ideas about issuing some kind of prompt of its own while it waits
|
|
* for the network connection.
|
|
*/
|
|
|
|
static SeatPromptResult tempseat_get_userpass_input(Seat *seat, prompts_t *p)
|
|
{
|
|
/*
|
|
* Interactive prompts of this nature are a thing that a backend
|
|
* MUST NOT do while not in possession of the real Seat, because
|
|
* the whole point of temporarily lending the real Seat to
|
|
* something else is that so it can have a clear field to do
|
|
* interactive stuff of its own while making a network connection.
|
|
*/
|
|
unreachable("get_userpass_input should never be called on TempSeat");
|
|
}
|
|
|
|
static size_t tempseat_banner(Seat *seat, const void *data, size_t len)
|
|
{
|
|
unreachable("banner should never be called on TempSeat");
|
|
}
|
|
|
|
static SeatPromptResult tempseat_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)
|
|
{
|
|
unreachable("confirm_ssh_host_key should never be called on TempSeat");
|
|
}
|
|
|
|
static SeatPromptResult tempseat_confirm_weak_crypto_primitive(
|
|
Seat *seat, SeatDialogText *text,
|
|
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
|
{
|
|
unreachable("confirm_weak_crypto_primitive "
|
|
"should never be called on TempSeat");
|
|
}
|
|
|
|
static SeatPromptResult tempseat_confirm_weak_cached_hostkey(
|
|
Seat *seat, SeatDialogText *text,
|
|
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
|
|
{
|
|
unreachable("confirm_weak_cached_hostkey "
|
|
"should never be called on TempSeat");
|
|
}
|
|
|
|
static void tempseat_connection_fatal(Seat *seat, const char *message)
|
|
{
|
|
/*
|
|
* Fatal errors are another thing a backend should not have any
|
|
* reason to encounter while waiting to hear back about its
|
|
* network connection setup.
|
|
*
|
|
* Also, if a backend _did_ call this, it would be hellish to
|
|
* unpick all the error handling. Just passing on the fatal error
|
|
* to the real Seat wouldn't be good enough: what about freeing
|
|
* all the various things that are confusingly holding pointers to
|
|
* each other? Better to leave this as an assertion-failure level
|
|
* issue, so that if it does ever happen by accident, we'll know
|
|
* it's a bug.
|
|
*/
|
|
unreachable("connection_fatal should never be called on TempSeat");
|
|
}
|
|
|
|
static bool tempseat_eof(Seat *seat)
|
|
{
|
|
/*
|
|
* EOF is _very nearly_ something that we could buffer, and pass
|
|
* on to the real Seat at flush time. The only difficulty is that
|
|
* sometimes the front end wants to respond to an incoming EOF by
|
|
* instructing the back end to send an outgoing one, which it does
|
|
* by returning a bool from its eof method.
|
|
*
|
|
* So we'd have to arrange that tempseat_flush caught that return
|
|
* value and passed it on to the calling backend. And then every
|
|
* backend would have to deal with tempseat_flush maybe returning
|
|
* it an 'actually, please start closing down now' indication,
|
|
* which could only happen _in theory_, if it had for some reason
|
|
* called seat_eof on the TempSeat.
|
|
*
|
|
* But in fact, we don't expect back ends to call seat_eof on the
|
|
* TempSeat in the first place, so all of that effort would be a
|
|
* total waste. Hence, we'll put EOF in the category of things we
|
|
* expect backends never to do while the real Seat is out on loan.
|
|
*/
|
|
unreachable("eof should never be called on TempSeat");
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------
|
|
* Done with the TempSeat methods. Here's the vtable definition and
|
|
* the main setup/teardown code.
|
|
*/
|
|
|
|
static const struct SeatVtable tempseat_vt = {
|
|
.output = tempseat_output,
|
|
.eof = tempseat_eof,
|
|
.sent = nullseat_sent,
|
|
.banner = tempseat_banner,
|
|
.get_userpass_input = tempseat_get_userpass_input,
|
|
.notify_session_started = tempseat_notify_session_started,
|
|
.notify_remote_exit = tempseat_notify_remote_exit,
|
|
.notify_remote_disconnect = tempseat_notify_remote_disconnect,
|
|
.connection_fatal = tempseat_connection_fatal,
|
|
.update_specials_menu = tempseat_update_specials_menu,
|
|
.get_ttymode = tempseat_get_ttymode,
|
|
.set_busy_status = tempseat_set_busy_status,
|
|
.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,
|
|
.get_windowid = tempseat_get_windowid,
|
|
.get_window_pixel_size = tempseat_get_window_pixel_size,
|
|
.stripctrl_new = tempseat_stripctrl_new,
|
|
.set_trust_status = tempseat_set_trust_status,
|
|
.can_set_trust_status = tempseat_can_set_trust_status,
|
|
.has_mixed_input_stream = tempseat_has_mixed_input_stream,
|
|
.verbose = tempseat_verbose,
|
|
.interactive = tempseat_interactive,
|
|
.get_cursor_position = tempseat_get_cursor_position,
|
|
};
|
|
|
|
Seat *tempseat_new(Seat *realseat)
|
|
{
|
|
TempSeat *ts = snew(TempSeat);
|
|
memset(ts, 0, sizeof(*ts));
|
|
ts->seat.vt = &tempseat_vt;
|
|
|
|
ts->realseat = realseat;
|
|
bufchain_init(&ts->output);
|
|
ts->outchunk_head = ts->outchunk_tail = NULL;
|
|
|
|
return &ts->seat;
|
|
}
|
|
|
|
bool is_tempseat(Seat *seat)
|
|
{
|
|
return seat->vt == &tempseat_vt;
|
|
}
|
|
|
|
Seat *tempseat_get_real(Seat *seat)
|
|
{
|
|
assert(seat->vt == &tempseat_vt);
|
|
TempSeat *ts = container_of(seat, TempSeat, seat);
|
|
return ts->realseat;
|
|
}
|
|
|
|
void tempseat_free(Seat *seat)
|
|
{
|
|
assert(seat->vt == &tempseat_vt);
|
|
TempSeat *ts = container_of(seat, TempSeat, seat);
|
|
bufchain_clear(&ts->output);
|
|
while (ts->outchunk_head) {
|
|
struct output_chunk *chunk = ts->outchunk_head;
|
|
ts->outchunk_head = chunk->next;
|
|
sfree(chunk);
|
|
}
|
|
sfree(ts);
|
|
}
|
|
|
|
void tempseat_flush(Seat *seat)
|
|
{
|
|
assert(seat->vt == &tempseat_vt);
|
|
TempSeat *ts = container_of(seat, TempSeat, seat);
|
|
|
|
/* Empty the output bufchains into the real seat, taking care to
|
|
* preserve both separation and interleaving */
|
|
while (bufchain_size(&ts->output)) {
|
|
ptrlen pl = bufchain_prefix(&ts->output);
|
|
|
|
assert(ts->outchunk_head);
|
|
struct output_chunk *chunk = ts->outchunk_head;
|
|
|
|
if (pl.len > chunk->size)
|
|
pl.len = chunk->size;
|
|
|
|
seat_output(ts->realseat, chunk->type, pl.ptr, pl.len);
|
|
bufchain_consume(&ts->output, pl.len);
|
|
chunk->size -= pl.len;
|
|
if (chunk->size == 0) {
|
|
ts->outchunk_head = chunk->next;
|
|
sfree(chunk);
|
|
}
|
|
}
|
|
|
|
/* That should have exactly emptied the output chunk list too */
|
|
assert(!ts->outchunk_head);
|
|
|
|
/* Pass on any other kinds of event we've buffered */
|
|
if (ts->seen_session_started)
|
|
seat_notify_session_started(ts->realseat);
|
|
if (ts->seen_remote_exit)
|
|
seat_notify_remote_exit(ts->realseat);
|
|
if (ts->seen_remote_disconnect)
|
|
seat_notify_remote_disconnect(ts->realseat);
|
|
if (ts->seen_update_specials_menu)
|
|
seat_update_specials_menu(ts->realseat);
|
|
if (ts->seen_echoedit_update)
|
|
seat_echoedit_update(ts->realseat, ts->echoing, ts->editing);
|
|
if (ts->seen_trust_status)
|
|
seat_set_trust_status(ts->realseat, ts->trusted);
|
|
}
|