зеркало из https://github.com/github/putty.git
760 строки
23 KiB
C
760 строки
23 KiB
C
/*
|
|
* 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 "sshchan.h"
|
|
#include "sshserver.h"
|
|
#include "sftp.h"
|
|
|
|
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];
|
|
|
|
Plug agentfwd_plug;
|
|
Socket *agentfwd_socket;
|
|
|
|
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 *, bool is_stderr, 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,
|
|
.get_userpass_input = nullseat_get_userpass_input,
|
|
.notify_remote_exit = sesschan_notify_remote_exit,
|
|
.connection_fatal = sesschan_connection_fatal,
|
|
.update_specials_menu = nullseat_update_specials_menu,
|
|
.get_ttymode = nullseat_get_ttymode,
|
|
.set_busy_status = nullseat_set_busy_status,
|
|
.verify_ssh_host_key = nullseat_verify_ssh_host_key,
|
|
.confirm_weak_crypto_primitive = nullseat_confirm_weak_crypto_primitive,
|
|
.confirm_weak_cached_hostkey = nullseat_confirm_weak_cached_hostkey,
|
|
.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,
|
|
.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 uxpty.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->agentfwd_socket)
|
|
sk_close(sess->agentfwd_socket);
|
|
|
|
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;
|
|
|
|
return backend_send(sess->backend, data, length);
|
|
}
|
|
|
|
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)
|
|
{
|
|
/* I don't think we need to do anything here */
|
|
}
|
|
|
|
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 uxpty.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, const char *error_msg, int error_code,
|
|
bool calling_back)
|
|
{ /* 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;
|
|
char screensuffix[32];
|
|
|
|
if (oneshot)
|
|
return false; /* not supported */
|
|
|
|
snprintf(screensuffix, sizeof(screensuffix), ".%u", screen_number);
|
|
|
|
/*
|
|
* 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;
|
|
|
|
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);
|
|
|
|
strbuf_free(authdata_bin);
|
|
return sess->n_x11_sockets != 0;
|
|
}
|
|
|
|
static int agentfwd_accepting(
|
|
Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
|
|
{
|
|
sesschan *sess = container_of(p, sesschan, agentfwd_plug);
|
|
Plug *plug;
|
|
Channel *chan;
|
|
Socket *s;
|
|
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;
|
|
}
|
|
portfwd_raw_setup(chan, s, ssh_serverside_agent_open(sess->c->cl, chan));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const PlugVtable agentfwd_plugvt = {
|
|
.log = fwd_log,
|
|
.closing = fwd_closing,
|
|
.accepting = agentfwd_accepting,
|
|
};
|
|
|
|
bool sesschan_enable_agent_forwarding(Channel *chan)
|
|
{
|
|
sesschan *sess = container_of(chan, sesschan, chan);
|
|
char *error, *socketname, *dir_prefix;
|
|
|
|
dir_prefix = dupprintf("/tmp/%s-agentfwd", appname);
|
|
|
|
sess->agentfwd_plug.vt = &agentfwd_plugvt;
|
|
sess->agentfwd_socket = platform_make_agent_socket(
|
|
&sess->agentfwd_plug, dir_prefix, &error, &socketname);
|
|
|
|
sfree(dir_prefix);
|
|
|
|
if (sess->agentfwd_socket) {
|
|
conf_set_str_str(sess->conf, CONF_environmt,
|
|
"SSH_AUTH_SOCK", socketname);
|
|
}
|
|
|
|
sfree(error);
|
|
sfree(socketname);
|
|
|
|
return sess->agentfwd_socket != NULL;
|
|
}
|
|
|
|
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 uxpty.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 "sshsignals.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, bool is_stderr, const void *data, size_t len)
|
|
{
|
|
sesschan *sess = container_of(seat, sesschan, seat);
|
|
return sshfwd_write_ext(sess->c, is_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));
|
|
|
|
sfree(sigmsg);
|
|
|
|
got_signal = true;
|
|
}
|
|
} 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);
|
|
}
|