diff --git a/network.h b/network.h index 067775e4..1839072a 100644 --- a/network.h +++ b/network.h @@ -121,11 +121,28 @@ struct PlugVtable { * host proxy). If you don't have one, all proxy types are required to * be able to manage without (and will just degrade their logging * control). + * + * If calling this from a backend with a Seat, you can also give it a + * pointer to your 'Seat *'. In that situation, it might replace the + * 'Seat *' with a temporary seat of its own, and give the real Seat + * to the proxy system so that it can ask for passwords (and, in the + * case of SSH proxying, other prompts like host key checks). If that + * happens, then the resulting 'temp seat' is the backend's property, + * and it will have to remember to free it when cleaning up, or after + * flushing it back into the real seat when the network connection + * attempt completes. + * + * You can free your TempSeat and resume using the real Seat when one + * of two things happens: either your Plug's closing() method is + * called (indicating failure to connect), or its log() method is + * called with PLUGLOG_CONNECT_SUCCESS. In the latter case, you'll + * probably want to flush the TempSeat's contents into the real Seat, + * of course. */ Socket *new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, LogPolicy *lp); + Plug *plug, Conf *conf, LogPolicy *lp, Seat **seat); Socket *new_listener(const char *srcaddr, int port, Plug *plug, bool local_host_only, Conf *conf, int addressfamily); SockAddr *name_lookup(const char *host, int port, char **canonicalname, diff --git a/noproxy.c b/noproxy.c index 6b5832ab..82347d51 100644 --- a/noproxy.c +++ b/noproxy.c @@ -20,7 +20,7 @@ SockAddr *name_lookup(const char *host, int port, char **canonicalname, Socket *new_connection(SockAddr *addr, const char *hostname, int port, bool privport, bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, LogPolicy *lp) + Plug *plug, Conf *conf, LogPolicy *lp, Seat **seat) { return sk_new(addr, port, privport, oobinline, nodelay, keepalive, plug); } diff --git a/otherbackends/raw.c b/otherbackends/raw.c index 638e2a50..87a136ba 100644 --- a/otherbackends/raw.c +++ b/otherbackends/raw.c @@ -39,8 +39,15 @@ static void raw_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, Raw *raw = container_of(plug, Raw, plug); backend_socket_log(raw->seat, raw->logctx, type, addr, port, error_msg, error_code, raw->conf, raw->socket_connected); - if (type == PLUGLOG_CONNECT_SUCCESS) + if (type == PLUGLOG_CONNECT_SUCCESS) { raw->socket_connected = true; + if (is_tempseat(raw->seat)) { + Seat *ts = raw->seat; + tempseat_flush(ts); + raw->seat = tempseat_get_real(ts); + tempseat_free(ts); + } + } } static void raw_check_close(Raw *raw) @@ -132,9 +139,6 @@ static char *raw_init(const BackendVtable *vt, Seat *seat, int addressfamily; char *loghost; - /* No local authentication phase in this protocol */ - seat_set_trust_status(seat, false); - raw = snew(Raw); raw->plug.vt = &Raw_plugvt; raw->backend.vt = vt; @@ -168,10 +172,13 @@ static char *raw_init(const BackendVtable *vt, Seat *seat, */ raw->s = new_connection(addr, *realhost, port, false, true, nodelay, keepalive, &raw->plug, conf, - log_get_policy(logctx)); + log_get_policy(logctx), &raw->seat); if ((err = sk_socket_error(raw->s)) != NULL) return dupstr(err); + /* No local authentication phase in this protocol */ + seat_set_trust_status(raw->seat, false); + loghost = conf_get_str(conf, CONF_loghost); if (*loghost) { char *colon; @@ -191,6 +198,8 @@ static void raw_free(Backend *be) { Raw *raw = container_of(be, Raw, backend); + if (is_tempseat(raw->seat)) + tempseat_free(raw->seat); if (raw->s) sk_close(raw->s); conf_free(raw->conf); diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c index 3748e536..ecdb2c36 100644 --- a/otherbackends/rlogin.c +++ b/otherbackends/rlogin.c @@ -45,8 +45,15 @@ static void rlogin_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, backend_socket_log(rlogin->seat, rlogin->logctx, type, addr, port, error_msg, error_code, rlogin->conf, rlogin->socket_connected); - if (type == PLUGLOG_CONNECT_SUCCESS) + if (type == PLUGLOG_CONNECT_SUCCESS) { rlogin->socket_connected = true; + if (is_tempseat(rlogin->seat)) { + Seat *ts = rlogin->seat; + tempseat_flush(ts); + rlogin->seat = tempseat_get_real(ts); + tempseat_free(ts); + } + } } static void rlogin_closing(Plug *plug, const char *error_msg, int error_code, @@ -205,7 +212,7 @@ static char *rlogin_init(const BackendVtable *vt, Seat *seat, */ rlogin->s = new_connection(addr, *realhost, port, true, false, nodelay, keepalive, &rlogin->plug, conf, - log_get_policy(logctx)); + log_get_policy(logctx), &rlogin->seat); if ((err = sk_socket_error(rlogin->s)) != NULL) return dupstr(err); @@ -256,6 +263,8 @@ static void rlogin_free(Backend *be) { Rlogin *rlogin = container_of(be, Rlogin, backend); + if (is_tempseat(rlogin->seat)) + tempseat_free(rlogin->seat); if (rlogin->prompt) free_prompts(rlogin->prompt); if (rlogin->s) diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c index cfa1b2ad..ae7e67f7 100644 --- a/otherbackends/supdup.c +++ b/otherbackends/supdup.c @@ -563,8 +563,15 @@ static void supdup_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, backend_socket_log(supdup->seat, supdup->logctx, type, addr, port, error_msg, error_code, supdup->conf, supdup->socket_connected); - if (type == PLUGLOG_CONNECT_SUCCESS) + if (type == PLUGLOG_CONNECT_SUCCESS) { supdup->socket_connected = true; + if (is_tempseat(supdup->seat)) { + Seat *ts = supdup->seat; + tempseat_flush(ts); + supdup->seat = tempseat_get_real(ts); + tempseat_free(ts); + } + } } static void supdup_closing(Plug *plug, const char *error_msg, int error_code, @@ -713,7 +720,7 @@ static char *supdup_init(const BackendVtable *x, Seat *seat, */ supdup->s = new_connection(addr, *realhost, port, false, true, nodelay, keepalive, &supdup->plug, supdup->conf, - log_get_policy(logctx)); + log_get_policy(logctx), &supdup->seat); if ((err = sk_socket_error(supdup->s)) != NULL) return dupstr(err); @@ -783,6 +790,8 @@ static void supdup_free(Backend *be) { Supdup *supdup = container_of(be, Supdup, backend); + if (is_tempseat(supdup->seat)) + tempseat_free(supdup->seat); if (supdup->s) sk_close(supdup->s); if (supdup->pinger) diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c index b006305d..540d384c 100644 --- a/otherbackends/telnet.c +++ b/otherbackends/telnet.c @@ -626,8 +626,15 @@ static void telnet_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, backend_socket_log(telnet->seat, telnet->logctx, type, addr, port, error_msg, error_code, telnet->conf, telnet->socket_connected); - if (type == PLUGLOG_CONNECT_SUCCESS) + if (type == PLUGLOG_CONNECT_SUCCESS) { telnet->socket_connected = true; + if (is_tempseat(telnet->seat)) { + Seat *ts = telnet->seat; + tempseat_flush(ts); + telnet->seat = tempseat_get_real(ts); + tempseat_free(ts); + } + } } static void telnet_closing(Plug *plug, const char *error_msg, int error_code, @@ -698,9 +705,6 @@ static char *telnet_init(const BackendVtable *vt, Seat *seat, char *loghost; int addressfamily; - /* No local authentication phase in this protocol */ - seat_set_trust_status(seat, false); - telnet = snew(Telnet); telnet->plug.vt = &Telnet_plugvt; telnet->backend.vt = vt; @@ -740,10 +744,13 @@ static char *telnet_init(const BackendVtable *vt, Seat *seat, */ telnet->s = new_connection(addr, *realhost, port, false, true, nodelay, keepalive, &telnet->plug, telnet->conf, - log_get_policy(logctx)); + log_get_policy(logctx), &telnet->seat); if ((err = sk_socket_error(telnet->s)) != NULL) return dupstr(err); + /* No local authentication phase in this protocol */ + seat_set_trust_status(telnet->seat, false); + telnet->pinger = pinger_new(telnet->conf, &telnet->backend); /* @@ -797,6 +804,8 @@ static void telnet_free(Backend *be) { Telnet *telnet = container_of(be, Telnet, backend); + if (is_tempseat(telnet->seat)) + tempseat_free(telnet->seat); strbuf_free(telnet->sb_buf); if (telnet->s) sk_close(telnet->s); diff --git a/putty.h b/putty.h index 8538bd52..8b419a32 100644 --- a/putty.h +++ b/putty.h @@ -1295,6 +1295,31 @@ bool console_can_set_trust_status(Seat *seat); int filexfer_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input); bool cmdline_seat_verbose(Seat *seat); +/* + * TempSeat: a seat implementation that can be given to a backend + * temporarily while network proxy setup is using the real seat. + * Buffers output and trust-status changes until the real seat is + * available again. + */ + +/* Called by the proxy code to make a TempSeat. */ +Seat *tempseat_new(Seat *real); + +/* Query functions to tell if a Seat _is_ temporary, and if so, to + * return the underlying real Seat. */ +bool is_tempseat(Seat *seat); +Seat *tempseat_get_real(Seat *seat); + +/* Called by the backend once the proxy connection has finished + * setting up (or failed), to pass on any buffered stuff to the real + * seat. */ +void tempseat_flush(Seat *ts); + +/* Frees a TempSeat, without flushing anything it has buffered. (Call + * this after tempseat_flush, or alternatively, when you were going to + * abandon the whole connection anyway.) */ +void tempseat_free(Seat *ts); + typedef struct rgb { uint8_t r, g, b; } rgb; diff --git a/ssh/portfwd.c b/ssh/portfwd.c index a67268a4..268935eb 100644 --- a/ssh/portfwd.c +++ b/ssh/portfwd.c @@ -1161,7 +1161,7 @@ char *portfwdmgr_connect(PortFwdManager *mgr, Channel **chan_ret, pf->s = new_connection(addr, dummy_realhost, port, false, true, false, false, &pf->plug, mgr->conf, - NULL); + NULL, NULL); sfree(dummy_realhost); if ((err = sk_socket_error(pf->s)) != NULL) { char *err_ret = dupstr(err); diff --git a/ssh/ssh.c b/ssh/ssh.c index 8b7dd92c..98b7a68a 100644 --- a/ssh/ssh.c +++ b/ssh/ssh.c @@ -598,6 +598,15 @@ static void ssh_socket_log(Plug *plug, PlugLogType type, SockAddr *addr, backend_socket_log(ssh->seat, ssh->logctx, type, addr, port, error_msg, error_code, ssh->conf, ssh->session_started); + + if (type == PLUGLOG_CONNECT_SUCCESS) { + if (is_tempseat(ssh->seat)) { + Seat *ts = ssh->seat; + tempseat_flush(ts); + ssh->seat = tempseat_get_real(ts); + tempseat_free(ts); + } + } } static void ssh_closing(Plug *plug, const char *error_msg, int error_code, @@ -790,7 +799,7 @@ static char *connect_to_host( ssh->s = new_connection(addr, *realhost, port, false, true, nodelay, keepalive, &ssh->plug, ssh->conf, - log_get_policy(ssh->logctx)); + log_get_policy(ssh->logctx), &ssh->seat); if ((err = sk_socket_error(ssh->s)) != NULL) { ssh->s = NULL; seat_notify_remote_exit(ssh->seat); @@ -955,6 +964,9 @@ static void ssh_free(Backend *be) ssh_shutdown(ssh); + if (is_tempseat(ssh->seat)) + tempseat_free(ssh->seat); + conf_free(ssh->conf); if (ssh->connshare) sharestate_free(ssh->connshare); diff --git a/ssh/x11fwd.c b/ssh/x11fwd.c index eb4d4584..60a9f1aa 100644 --- a/ssh/x11fwd.c +++ b/ssh/x11fwd.c @@ -564,7 +564,7 @@ static size_t x11_send( xconn->s = new_connection(sk_addr_dup(xconn->disp->addr), xconn->disp->realhost, xconn->disp->port, false, true, false, false, &xconn->plug, - sshfwd_get_conf(xconn->c), NULL); + sshfwd_get_conf(xconn->c), NULL, NULL); if ((err = sk_socket_error(xconn->s)) != NULL) { char *err_message = dupprintf("unable to connect to" " forwarded X server: %s", err); diff --git a/unix/pageant.c b/unix/pageant.c index b68b829d..7402ac60 100644 --- a/unix/pageant.c +++ b/unix/pageant.c @@ -1190,7 +1190,7 @@ void run_agent(FILE *logfp, const char *symlink_path) s = new_connection(sk_addr_dup(disp->addr), disp->realhost, disp->port, false, true, false, false, &conn->plug, conf, - NULL); + NULL, NULL); if ((err = sk_socket_error(s)) != NULL) { fprintf(stderr, "pageant: unable to connect to X server: %s", err); exit(1); diff --git a/unix/sharing.c b/unix/sharing.c index 268b8df1..58038ab9 100644 --- a/unix/sharing.c +++ b/unix/sharing.c @@ -297,7 +297,7 @@ int platform_ssh_share(const char *pi_name, Conf *conf, if (can_downstream) { retsock = new_connection(unix_sock_addr(sockname), "", 0, false, true, false, false, - downplug, conf, NULL); + downplug, conf, NULL, NULL); if (sk_socket_error(retsock) == NULL) { sfree(*logtext); *logtext = sockname; diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 260f9920..4b10442b 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -42,6 +42,7 @@ add_sources_from_current_dir(utils strbuf.c string_length_for_printf.c stripctrl.c + tempseat.c tree234.c validate_manual_hostkey.c version.c diff --git a/utils/tempseat.c b/utils/tempseat.c new file mode 100644 index 00000000..22cf642c --- /dev/null +++ b/utils/tempseat.c @@ -0,0 +1,353 @@ +/* + * 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" + +typedef struct TempSeat TempSeat; +struct TempSeat { + Seat *realseat; + bufchain outputs[2]; /* stdout, stderr */ + 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, bool is_stderr, const void *data, + size_t len) +{ + TempSeat *ts = container_of(seat, TempSeat, seat); + bufchain_add(&ts->outputs[is_stderr], data, len); + return bufchain_size(&ts->outputs[0]) + bufchain_size(&ts->outputs[1]); +} + +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); +} + +/* ---------------------------------------------------------------------- + * 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 int tempseat_get_userpass_input(Seat *seat, prompts_t *p, + bufchain *input) +{ + /* + * 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 int tempseat_verify_ssh_host_key( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, const char *keydisp, char **key_fingerprints, + void (*callback)(void *ctx, int result), void *ctx) +{ + unreachable("verify_ssh_host_key should never be called on TempSeat"); +} + +static int tempseat_confirm_weak_crypto_primitive( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx) +{ + unreachable("confirm_weak_crypto_primitive " + "should never be called on TempSeat"); +} + +static int tempseat_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int 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, + .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, + .verify_ssh_host_key = tempseat_verify_ssh_host_key, + .confirm_weak_crypto_primitive = tempseat_confirm_weak_crypto_primitive, + .confirm_weak_cached_hostkey = tempseat_confirm_weak_cached_hostkey, + .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, + .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; + for (unsigned i = 0; i < 2; i++) + bufchain_init(&ts->outputs[i]); + + 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); + for (unsigned i = 0; i < 2; i++) + bufchain_clear(&ts->outputs[i]); + sfree(ts); +} + +void tempseat_flush(Seat *seat) +{ + assert(seat->vt == &tempseat_vt); + TempSeat *ts = container_of(seat, TempSeat, seat); + + /* Empty the stdout/stderr bufchains into the real seat */ + for (unsigned i = 0; i < 2; i++) { + while (bufchain_size(&ts->outputs[i])) { + ptrlen pl = bufchain_prefix(&ts->outputs[i]); + seat_output(ts->realseat, i, pl.ptr, pl.len); + bufchain_consume(&ts->outputs[i], pl.len); + } + } + + /* 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); +}