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 'ssh/sesschan.c')
-rw-r--r--ssh/sesschan.c798
1 files changed, 798 insertions, 0 deletions
diff --git a/ssh/sesschan.c b/ssh/sesschan.c
new file mode 100644
index 00000000..9776eaf2
--- /dev/null
+++ b/ssh/sesschan.c
@@ -0,0 +1,798 @@
+/*
+ * Implement the "session" channel type for the SSH server.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "channel.h"
+#include "server.h"
+#include "sftp.h"
+
+struct agentfwd {
+ ConnectionLayer *cl;
+ Socket *socket;
+ Plug plug;
+};
+
+typedef struct sesschan {
+ SshChannel *c;
+
+ LogContext *parent_logctx, *child_logctx;
+ Conf *conf;
+ const SftpServerVtable *sftpserver_vt;
+
+ LogPolicy logpolicy;
+ Seat seat;
+
+ bool want_pty;
+ struct ssh_ttymodes ttymodes;
+ int wc, hc, wp, hp;
+ strbuf *termtype;
+
+ bool ignoring_input;
+ bool seen_eof, seen_exit;
+
+ Plug xfwd_plug;
+ int n_x11_sockets;
+ Socket *x11_sockets[MAX_X11_SOCKETS];
+
+ agentfwd *agent;
+
+ Backend *backend;
+
+ bufchain subsys_input;
+ SftpServer *sftpsrv;
+ ScpServer *scpsrv;
+ const SshServerConfig *ssc;
+
+ Channel chan;
+} sesschan;
+
+static void sesschan_free(Channel *chan);
+static size_t sesschan_send(
+ Channel *chan, bool is_stderr, const void *, size_t);
+static void sesschan_send_eof(Channel *chan);
+static char *sesschan_log_close_msg(Channel *chan);
+static bool sesschan_want_close(Channel *, bool, bool);
+static void sesschan_set_input_wanted(Channel *chan, bool wanted);
+static bool sesschan_run_shell(Channel *chan);
+static bool sesschan_run_command(Channel *chan, ptrlen command);
+static bool sesschan_run_subsystem(Channel *chan, ptrlen subsys);
+static bool sesschan_enable_x11_forwarding(
+ Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata,
+ unsigned screen_number);
+static bool sesschan_enable_agent_forwarding(Channel *chan);
+static bool sesschan_allocate_pty(
+ Channel *chan, ptrlen termtype, unsigned width, unsigned height,
+ unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes);
+static bool sesschan_set_env(Channel *chan, ptrlen var, ptrlen value);
+static bool sesschan_send_break(Channel *chan, unsigned length);
+static bool sesschan_send_signal(Channel *chan, ptrlen signame);
+static bool sesschan_change_window_size(
+ Channel *chan, unsigned width, unsigned height,
+ unsigned pixwidth, unsigned pixheight);
+
+static const ChannelVtable sesschan_channelvt = {
+ .free = sesschan_free,
+ .open_confirmation = chan_remotely_opened_confirmation,
+ .open_failed = chan_remotely_opened_failure,
+ .send = sesschan_send,
+ .send_eof = sesschan_send_eof,
+ .set_input_wanted = sesschan_set_input_wanted,
+ .log_close_msg = sesschan_log_close_msg,
+ .want_close = sesschan_want_close,
+ .rcvd_exit_status = chan_no_exit_status,
+ .rcvd_exit_signal = chan_no_exit_signal,
+ .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric,
+ .run_shell = sesschan_run_shell,
+ .run_command = sesschan_run_command,
+ .run_subsystem = sesschan_run_subsystem,
+ .enable_x11_forwarding = sesschan_enable_x11_forwarding,
+ .enable_agent_forwarding = sesschan_enable_agent_forwarding,
+ .allocate_pty = sesschan_allocate_pty,
+ .set_env = sesschan_set_env,
+ .send_break = sesschan_send_break,
+ .send_signal = sesschan_send_signal,
+ .change_window_size = sesschan_change_window_size,
+ .request_response = chan_no_request_response,
+};
+
+static size_t sftp_chan_send(
+ Channel *chan, bool is_stderr, const void *, size_t);
+static void sftp_chan_send_eof(Channel *chan);
+static char *sftp_log_close_msg(Channel *chan);
+
+static const ChannelVtable sftp_channelvt = {
+ .free = sesschan_free,
+ .open_confirmation = chan_remotely_opened_confirmation,
+ .open_failed = chan_remotely_opened_failure,
+ .send = sftp_chan_send,
+ .send_eof = sftp_chan_send_eof,
+ .set_input_wanted = sesschan_set_input_wanted,
+ .log_close_msg = sftp_log_close_msg,
+ .want_close = chan_default_want_close,
+ .rcvd_exit_status = chan_no_exit_status,
+ .rcvd_exit_signal = chan_no_exit_signal,
+ .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric,
+ .run_shell = chan_no_run_shell,
+ .run_command = chan_no_run_command,
+ .run_subsystem = chan_no_run_subsystem,
+ .enable_x11_forwarding = chan_no_enable_x11_forwarding,
+ .enable_agent_forwarding = chan_no_enable_agent_forwarding,
+ .allocate_pty = chan_no_allocate_pty,
+ .set_env = chan_no_set_env,
+ .send_break = chan_no_send_break,
+ .send_signal = chan_no_send_signal,
+ .change_window_size = chan_no_change_window_size,
+ .request_response = chan_no_request_response,
+};
+
+static size_t scp_chan_send(
+ Channel *chan, bool is_stderr, const void *, size_t);
+static void scp_chan_send_eof(Channel *chan);
+static void scp_set_input_wanted(Channel *chan, bool wanted);
+static char *scp_log_close_msg(Channel *chan);
+
+static const ChannelVtable scp_channelvt = {
+ .free = sesschan_free,
+ .open_confirmation = chan_remotely_opened_confirmation,
+ .open_failed = chan_remotely_opened_failure,
+ .send = scp_chan_send,
+ .send_eof = scp_chan_send_eof,
+ .set_input_wanted = scp_set_input_wanted,
+ .log_close_msg = scp_log_close_msg,
+ .want_close = chan_default_want_close,
+ .rcvd_exit_status = chan_no_exit_status,
+ .rcvd_exit_signal = chan_no_exit_signal,
+ .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric,
+ .run_shell = chan_no_run_shell,
+ .run_command = chan_no_run_command,
+ .run_subsystem = chan_no_run_subsystem,
+ .enable_x11_forwarding = chan_no_enable_x11_forwarding,
+ .enable_agent_forwarding = chan_no_enable_agent_forwarding,
+ .allocate_pty = chan_no_allocate_pty,
+ .set_env = chan_no_set_env,
+ .send_break = chan_no_send_break,
+ .send_signal = chan_no_send_signal,
+ .change_window_size = chan_no_change_window_size,
+ .request_response = chan_no_request_response,
+};
+
+static void sesschan_eventlog(LogPolicy *lp, const char *event) {}
+static void sesschan_logging_error(LogPolicy *lp, const char *event) {}
+static int sesschan_askappend(
+ LogPolicy *lp, Filename *filename,
+ void (*callback)(void *ctx, int result), void *ctx) { return 2; }
+
+static const LogPolicyVtable sesschan_logpolicy_vt = {
+ .eventlog = sesschan_eventlog,
+ .askappend = sesschan_askappend,
+ .logging_error = sesschan_logging_error,
+ .verbose = null_lp_verbose_no,
+};
+
+static size_t sesschan_seat_output(
+ Seat *, SeatOutputType type, const void *, size_t);
+static bool sesschan_seat_eof(Seat *);
+static void sesschan_notify_remote_exit(Seat *seat);
+static void sesschan_connection_fatal(Seat *seat, const char *message);
+static bool sesschan_get_window_pixel_size(Seat *seat, int *w, int *h);
+
+static const SeatVtable sesschan_seat_vt = {
+ .output = sesschan_seat_output,
+ .eof = sesschan_seat_eof,
+ .sent = nullseat_sent,
+ .banner = nullseat_banner,
+ .get_userpass_input = nullseat_get_userpass_input,
+ .notify_session_started = nullseat_notify_session_started,
+ .notify_remote_exit = sesschan_notify_remote_exit,
+ .notify_remote_disconnect = nullseat_notify_remote_disconnect,
+ .connection_fatal = sesschan_connection_fatal,
+ .update_specials_menu = nullseat_update_specials_menu,
+ .get_ttymode = nullseat_get_ttymode,
+ .set_busy_status = nullseat_set_busy_status,
+ .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,
+ .get_windowid = nullseat_get_windowid,
+ .get_window_pixel_size = sesschan_get_window_pixel_size,
+ .stripctrl_new = nullseat_stripctrl_new,
+ .set_trust_status = nullseat_set_trust_status,
+ .can_set_trust_status = nullseat_can_set_trust_status_no,
+ .has_mixed_input_stream = nullseat_has_mixed_input_stream_no,
+ .verbose = nullseat_verbose_no,
+ .interactive = nullseat_interactive_no,
+ .get_cursor_position = nullseat_get_cursor_position,
+};
+
+Channel *sesschan_new(SshChannel *c, LogContext *logctx,
+ const SftpServerVtable *sftpserver_vt,
+ const SshServerConfig *ssc)
+{
+ sesschan *sess = snew(sesschan);
+ memset(sess, 0, sizeof(sesschan));
+
+ sess->c = c;
+ sess->chan.vt = &sesschan_channelvt;
+ sess->chan.initial_fixed_window_size = 0;
+ sess->parent_logctx = logctx;
+ sess->ssc = ssc;
+
+ /* Start with a completely default Conf */
+ sess->conf = conf_new();
+ load_open_settings(NULL, sess->conf);
+
+ /* Set close-on-exit = true to suppress pty.c's "[pterm: process
+ * terminated with status x]" message */
+ conf_set_int(sess->conf, CONF_close_on_exit, FORCE_ON);
+
+ sess->seat.vt = &sesschan_seat_vt;
+ sess->logpolicy.vt = &sesschan_logpolicy_vt;
+ sess->child_logctx = log_init(&sess->logpolicy, sess->conf);
+
+ sess->sftpserver_vt = sftpserver_vt;
+
+ bufchain_init(&sess->subsys_input);
+
+ return &sess->chan;
+}
+
+static void sesschan_free(Channel *chan)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+ int i;
+
+ delete_callbacks_for_context(sess);
+ conf_free(sess->conf);
+ if (sess->backend)
+ backend_free(sess->backend);
+ bufchain_clear(&sess->subsys_input);
+ if (sess->sftpsrv)
+ sftpsrv_free(sess->sftpsrv);
+ for (i = 0; i < sess->n_x11_sockets; i++)
+ sk_close(sess->x11_sockets[i]);
+ if (sess->agent)
+ agentfwd_free(sess->agent);
+
+ sfree(sess);
+}
+
+static size_t sesschan_send(Channel *chan, bool is_stderr,
+ const void *data, size_t length)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+
+ if (!sess->backend || sess->ignoring_input)
+ return 0;
+
+ backend_send(sess->backend, data, length);
+ return backend_sendbuffer(sess->backend);
+}
+
+static void sesschan_send_eof(Channel *chan)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+ if (sess->backend)
+ backend_special(sess->backend, SS_EOF, 0);
+}
+
+static char *sesschan_log_close_msg(Channel *chan)
+{
+ return dupstr("Session channel closed");
+}
+
+static void sesschan_set_input_wanted(Channel *chan, bool wanted)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+ /* Request the back end to resume sending input, if it had become
+ * throttled by the channel window shortening */
+ if (wanted && sess->backend)
+ backend_unthrottle(sess->backend, 0);
+}
+
+static void sesschan_start_backend(sesschan *sess, const char *cmd)
+{
+ /*
+ * List of environment variables that we should not pass through
+ * from the login session Uppity was run in (which, it being a
+ * test server, there will usually be one of). These variables
+ * will be set as part of X or agent forwarding, and shouldn't be
+ * confusingly set in the absence of that.
+ *
+ * (DISPLAY must also be cleared, but pty.c will do that anyway
+ * when our get_x_display method returns NULL.)
+ */
+ static const char *const env_to_unset[] = {
+ "XAUTHORITY", "SSH_AUTH_SOCK", "SSH_AGENT_PID",
+ NULL /* terminator */
+ };
+
+ sess->backend = pty_backend_create(
+ &sess->seat, sess->child_logctx, sess->conf, NULL, cmd,
+ sess->ttymodes, !sess->want_pty, sess->ssc->session_starting_dir,
+ env_to_unset);
+ backend_size(sess->backend, sess->wc, sess->hc);
+}
+
+bool sesschan_run_shell(Channel *chan)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+
+ if (sess->backend)
+ return false;
+
+ sesschan_start_backend(sess, NULL);
+ return true;
+}
+
+bool sesschan_run_command(Channel *chan, ptrlen command)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+
+ if (sess->backend)
+ return false;
+
+ /* FIXME: make this possible to configure off */
+ if ((sess->scpsrv = scp_recognise_exec(sess->c, sess->sftpserver_vt,
+ command)) != NULL) {
+ sess->chan.vt = &scp_channelvt;
+ logevent(sess->parent_logctx, "Starting built-in SCP server");
+ return true;
+ }
+
+ char *command_str = mkstr(command);
+ sesschan_start_backend(sess, command_str);
+ sfree(command_str);
+
+ return true;
+}
+
+bool sesschan_run_subsystem(Channel *chan, ptrlen subsys)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+
+ if (ptrlen_eq_string(subsys, "sftp") && sess->sftpserver_vt) {
+ sess->sftpsrv = sftpsrv_new(sess->sftpserver_vt);
+ sess->chan.vt = &sftp_channelvt;
+ logevent(sess->parent_logctx, "Starting built-in SFTP subsystem");
+ return true;
+ }
+
+ return false;
+}
+
+static void fwd_log(Plug *plug, PlugLogType type, SockAddr *addr, int port,
+ const char *error_msg, int error_code)
+{ /* don't expect any weirdnesses from a listening socket */ }
+static void fwd_closing(Plug *plug, PlugCloseType type, const char *error_msg)
+{ /* not here, either */ }
+
+static int xfwd_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
+{
+ sesschan *sess = container_of(p, sesschan, xfwd_plug);
+ Plug *plug;
+ Channel *chan;
+ Socket *s;
+ SocketPeerInfo *pi;
+ const char *err;
+
+ chan = portfwd_raw_new(sess->c->cl, &plug, false);
+ s = constructor(ctx, plug);
+ if ((err = sk_socket_error(s)) != NULL) {
+ portfwd_raw_free(chan);
+ return 1;
+ }
+ pi = sk_peer_info(s);
+ portfwd_raw_setup(chan, s, ssh_serverside_x11_open(sess->c->cl, chan, pi));
+ sk_free_peer_info(pi);
+
+ return 0;
+}
+
+static const PlugVtable xfwd_plugvt = {
+ .log = fwd_log,
+ .closing = fwd_closing,
+ .accepting = xfwd_accepting,
+};
+
+bool sesschan_enable_x11_forwarding(
+ Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata_hex,
+ unsigned screen_number)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+ strbuf *authdata_bin;
+ size_t i;
+
+ if (oneshot)
+ return false; /* not supported */
+
+ /*
+ * Decode the authorisation data from ASCII hex into binary.
+ */
+ if (authdata_hex.len % 2)
+ return false; /* expected an even number of digits */
+ authdata_bin = strbuf_new_nm();
+ for (i = 0; i < authdata_hex.len; i += 2) {
+ const unsigned char *hex = authdata_hex.ptr;
+ char hexbuf[3];
+
+ if (!isxdigit(hex[i]) || !isxdigit(hex[i+1])) {
+ strbuf_free(authdata_bin);
+ return false; /* not hex */
+ }
+
+ hexbuf[0] = hex[i];
+ hexbuf[1] = hex[i+1];
+ hexbuf[2] = '\0';
+ put_byte(authdata_bin, strtoul(hexbuf, NULL, 16));
+ }
+
+ sess->xfwd_plug.vt = &xfwd_plugvt;
+
+ char *screensuffix = dupprintf(".%u", screen_number);
+
+ sess->n_x11_sockets = platform_make_x11_server(
+ &sess->xfwd_plug, appname, 10, screensuffix,
+ authproto, ptrlen_from_strbuf(authdata_bin),
+ sess->x11_sockets, sess->conf);
+
+ sfree(screensuffix);
+ strbuf_free(authdata_bin);
+ return sess->n_x11_sockets != 0;
+}
+
+static int agentfwd_accepting(
+ Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
+{
+ agentfwd *agent = container_of(p, agentfwd, plug);
+ Plug *plug;
+ Channel *chan;
+ Socket *s;
+ const char *err;
+
+ chan = portfwd_raw_new(agent->cl, &plug, false);
+ s = constructor(ctx, plug);
+ if ((err = sk_socket_error(s)) != NULL) {
+ portfwd_raw_free(chan);
+ return 1;
+ }
+ portfwd_raw_setup(chan, s, ssh_serverside_agent_open(agent->cl, chan));
+
+ return 0;
+}
+
+static const PlugVtable agentfwd_plugvt = {
+ .log = fwd_log,
+ .closing = fwd_closing,
+ .accepting = agentfwd_accepting,
+};
+
+agentfwd *agentfwd_new(ConnectionLayer *cl, char **socketname_out)
+{
+ agentfwd *agent = snew(agentfwd);
+ agent->cl = cl;
+ agent->plug.vt = &agentfwd_plugvt;
+
+ char *dir_prefix = dupprintf("/tmp/%s-agentfwd", appname);
+ char *error = NULL, *socketname = NULL;
+ agent->socket = platform_make_agent_socket(
+ &agent->plug, dir_prefix, &error, &socketname);
+ sfree(dir_prefix);
+ sfree(error);
+
+ if (!agent->socket) {
+ sfree(agent);
+ sfree(socketname);
+ return NULL;
+ }
+
+ *socketname_out = socketname;
+ return agent;
+}
+
+void agentfwd_free(agentfwd *agent)
+{
+ if (agent->socket)
+ sk_close(agent->socket);
+ sfree(agent);
+}
+
+bool sesschan_enable_agent_forwarding(Channel *chan)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+ char *socketname;
+
+ assert(!sess->agent);
+
+ sess->agent = agentfwd_new(sess->c->cl, &socketname);
+
+ if (!sess->agent)
+ return false;
+
+ conf_set_str_str(sess->conf, CONF_environmt, "SSH_AUTH_SOCK", socketname);
+ sfree(socketname);
+ return true;
+}
+
+bool sesschan_allocate_pty(
+ Channel *chan, ptrlen termtype, unsigned width, unsigned height,
+ unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+ char *s;
+
+ if (sess->want_pty)
+ return false;
+
+ s = mkstr(termtype);
+ conf_set_str(sess->conf, CONF_termtype, s);
+ sfree(s);
+
+ sess->want_pty = true;
+ sess->ttymodes = modes;
+ sess->wc = width;
+ sess->hc = height;
+ sess->wp = pixwidth;
+ sess->hp = pixheight;
+
+ return true;
+}
+
+bool sesschan_set_env(Channel *chan, ptrlen var, ptrlen value)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+
+ char *svar = mkstr(var), *svalue = mkstr(value);
+ conf_set_str_str(sess->conf, CONF_environmt, svar, svalue);
+ sfree(svar);
+ sfree(svalue);
+
+ return true;
+}
+
+bool sesschan_send_break(Channel *chan, unsigned length)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+
+ if (sess->backend) {
+ /* We ignore the break length. We could pass it through as the
+ * 'arg' parameter, and have pty.c collect it and pass it on
+ * to tcsendbreak, but since tcsendbreak in turn assigns
+ * implementation-defined semantics to _its_ duration
+ * parameter, this all just sounds too difficult. */
+ backend_special(sess->backend, SS_BRK, 0);
+ return true;
+ }
+ return false;
+}
+
+bool sesschan_send_signal(Channel *chan, ptrlen signame)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+
+ /* Start with a code that definitely isn't a signal (or indeed a
+ * special command at all), to indicate 'nothing matched'. */
+ SessionSpecialCode code = SS_EXITMENU;
+
+ #define SIGNAL_SUB(name) \
+ if (ptrlen_eq_string(signame, #name)) code = SS_SIG ## name;
+ #define SIGNAL_MAIN(name, text) SIGNAL_SUB(name)
+ #include "signal-list.h"
+ #undef SIGNAL_MAIN
+ #undef SIGNAL_SUB
+
+ if (code == SS_EXITMENU)
+ return false;
+
+ backend_special(sess->backend, code, 0);
+ return true;
+}
+
+bool sesschan_change_window_size(
+ Channel *chan, unsigned width, unsigned height,
+ unsigned pixwidth, unsigned pixheight)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+
+ if (!sess->want_pty)
+ return false;
+
+ sess->wc = width;
+ sess->hc = height;
+ sess->wp = pixwidth;
+ sess->hp = pixheight;
+
+ if (sess->backend)
+ backend_size(sess->backend, sess->wc, sess->hc);
+
+ return true;
+}
+
+static size_t sesschan_seat_output(
+ Seat *seat, SeatOutputType type, const void *data, size_t len)
+{
+ sesschan *sess = container_of(seat, sesschan, seat);
+ return sshfwd_write_ext(sess->c, type == SEAT_OUTPUT_STDERR, data, len);
+}
+
+static void sesschan_check_close_callback(void *vctx)
+{
+ sesschan *sess = (sesschan *)vctx;
+
+ /*
+ * Once we've seen incoming EOF from the backend (aka EIO from the
+ * pty master) and also passed on the process's exit status, we
+ * should proactively initiate closure of the session channel.
+ */
+ if (sess->seen_eof && sess->seen_exit)
+ sshfwd_initiate_close(sess->c, NULL);
+}
+
+static bool sesschan_want_close(Channel *chan, bool seen_eof, bool rcvd_eof)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+
+ /*
+ * Similarly to above, we don't want to initiate channel closure
+ * until we've sent the process's exit status, _even_ if EOF of
+ * the actual data stream has happened in both directions.
+ */
+ return (sess->seen_eof && sess->seen_exit);
+}
+
+static bool sesschan_seat_eof(Seat *seat)
+{
+ sesschan *sess = container_of(seat, sesschan, seat);
+
+ sshfwd_write_eof(sess->c);
+ sess->seen_eof = true;
+
+ queue_toplevel_callback(sesschan_check_close_callback, sess);
+ return true;
+}
+
+static void sesschan_notify_remote_exit(Seat *seat)
+{
+ sesschan *sess = container_of(seat, sesschan, seat);
+
+ if (!sess->backend)
+ return;
+
+ bool got_signal = false;
+ if (!sess->ssc->exit_signal_numeric) {
+ char *sigmsg;
+ ptrlen signame = pty_backend_exit_signame(sess->backend, &sigmsg);
+
+ if (signame.len) {
+ if (!sigmsg)
+ sigmsg = dupstr("");
+
+ sshfwd_send_exit_signal(
+ sess->c, signame, false, ptrlen_from_asciz(sigmsg));
+
+ got_signal = true;
+ }
+
+ sfree(sigmsg);
+ } else {
+ int signum = pty_backend_exit_signum(sess->backend);
+
+ if (signum >= 0) {
+ sshfwd_send_exit_signal_numeric(sess->c, signum, false,
+ PTRLEN_LITERAL(""));
+ got_signal = true;
+ }
+ }
+
+ if (!got_signal)
+ sshfwd_send_exit_status(sess->c, backend_exitcode(sess->backend));
+
+ sess->seen_exit = true;
+ queue_toplevel_callback(sesschan_check_close_callback, sess);
+}
+
+static void sesschan_connection_fatal(Seat *seat, const char *message)
+{
+ sesschan *sess = container_of(seat, sesschan, seat);
+
+ /* Closest translation I can think of */
+ sshfwd_send_exit_signal(
+ sess->c, PTRLEN_LITERAL("HUP"), false, ptrlen_from_asciz(message));
+
+ sess->ignoring_input = true;
+}
+
+static bool sesschan_get_window_pixel_size(Seat *seat, int *width, int *height)
+{
+ sesschan *sess = container_of(seat, sesschan, seat);
+
+ *width = sess->wp;
+ *height = sess->hp;
+
+ return true;
+}
+
+/* ----------------------------------------------------------------------
+ * Built-in SFTP subsystem.
+ */
+
+static size_t sftp_chan_send(Channel *chan, bool is_stderr,
+ const void *data, size_t length)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+
+ bufchain_add(&sess->subsys_input, data, length);
+
+ while (bufchain_size(&sess->subsys_input) >= 4) {
+ char lenbuf[4];
+ unsigned pktlen;
+ struct sftp_packet *pkt, *reply;
+
+ bufchain_fetch(&sess->subsys_input, lenbuf, 4);
+ pktlen = GET_32BIT_MSB_FIRST(lenbuf);
+
+ if (bufchain_size(&sess->subsys_input) - 4 < pktlen)
+ break; /* wait for more data */
+
+ bufchain_consume(&sess->subsys_input, 4);
+ pkt = sftp_recv_prepare(pktlen);
+ bufchain_fetch_consume(&sess->subsys_input, pkt->data, pktlen);
+ sftp_recv_finish(pkt);
+ reply = sftp_handle_request(sess->sftpsrv, pkt);
+ sftp_pkt_free(pkt);
+
+ sftp_send_prepare(reply);
+ sshfwd_write(sess->c, reply->data, reply->length);
+ sftp_pkt_free(reply);
+ }
+
+ return 0;
+}
+
+static void sftp_chan_send_eof(Channel *chan)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+ sshfwd_write_eof(sess->c);
+}
+
+static char *sftp_log_close_msg(Channel *chan)
+{
+ return dupstr("Session channel (SFTP) closed");
+}
+
+/* ----------------------------------------------------------------------
+ * Built-in SCP subsystem.
+ */
+
+static size_t scp_chan_send(Channel *chan, bool is_stderr,
+ const void *data, size_t length)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+ return scp_send(sess->scpsrv, data, length);
+}
+
+static void scp_chan_send_eof(Channel *chan)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+ scp_eof(sess->scpsrv);
+}
+
+static char *scp_log_close_msg(Channel *chan)
+{
+ return dupstr("Session channel (SCP) closed");
+}
+
+static void scp_set_input_wanted(Channel *chan, bool wanted)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+ scp_throttle(sess->scpsrv, !wanted);
+}