Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mRemoteNG/PuTTYNG.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'utils/tempseat.c')
-rw-r--r--utils/tempseat.c430
1 files changed, 430 insertions, 0 deletions
diff --git a/utils/tempseat.c b/utils/tempseat.c
new file mode 100644
index 00000000..ad37b0e3
--- /dev/null
+++ b/utils/tempseat.c
@@ -0,0 +1,430 @@
+/*
+ * 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, const char *algtype, const char *algname,
+ 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, const char *algname, const char *betteralgs,
+ 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);
+}