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/mainchan.c')
-rw-r--r--ssh/mainchan.c539
1 files changed, 539 insertions, 0 deletions
diff --git a/ssh/mainchan.c b/ssh/mainchan.c
new file mode 100644
index 00000000..52492ff6
--- /dev/null
+++ b/ssh/mainchan.c
@@ -0,0 +1,539 @@
+/*
+ * SSH main session channel handling.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "ppl.h"
+#include "channel.h"
+
+static void mainchan_free(Channel *chan);
+static void mainchan_open_confirmation(Channel *chan);
+static void mainchan_open_failure(Channel *chan, const char *errtext);
+static size_t mainchan_send(
+ Channel *chan, bool is_stderr, const void *, size_t);
+static void mainchan_send_eof(Channel *chan);
+static void mainchan_set_input_wanted(Channel *chan, bool wanted);
+static char *mainchan_log_close_msg(Channel *chan);
+static bool mainchan_rcvd_exit_status(Channel *chan, int status);
+static bool mainchan_rcvd_exit_signal(
+ Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg);
+static bool mainchan_rcvd_exit_signal_numeric(
+ Channel *chan, int signum, bool core_dumped, ptrlen msg);
+static void mainchan_request_response(Channel *chan, bool success);
+
+static const ChannelVtable mainchan_channelvt = {
+ .free = mainchan_free,
+ .open_confirmation = mainchan_open_confirmation,
+ .open_failed = mainchan_open_failure,
+ .send = mainchan_send,
+ .send_eof = mainchan_send_eof,
+ .set_input_wanted = mainchan_set_input_wanted,
+ .log_close_msg = mainchan_log_close_msg,
+ .want_close = chan_default_want_close,
+ .rcvd_exit_status = mainchan_rcvd_exit_status,
+ .rcvd_exit_signal = mainchan_rcvd_exit_signal,
+ .rcvd_exit_signal_numeric = mainchan_rcvd_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 = mainchan_request_response,
+};
+
+typedef enum MainChanType {
+ MAINCHAN_SESSION, MAINCHAN_DIRECT_TCPIP
+} MainChanType;
+
+struct mainchan {
+ SshChannel *sc;
+ Conf *conf;
+ PacketProtocolLayer *ppl;
+ ConnectionLayer *cl;
+
+ MainChanType type;
+ bool is_simple;
+
+ bool req_x11, req_agent, req_pty, req_cmd_primary, req_cmd_fallback;
+ int n_req_env, n_env_replies, n_env_fails;
+ bool eof_pending, eof_sent, got_pty, ready;
+
+ int term_width, term_height;
+
+ Channel chan;
+};
+
+mainchan *mainchan_new(
+ PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf,
+ int term_width, int term_height, bool is_simple, SshChannel **sc_out)
+{
+ mainchan *mc;
+
+ if (conf_get_bool(conf, CONF_ssh_no_shell))
+ return NULL; /* no main channel at all */
+
+ mc = snew(mainchan);
+ memset(mc, 0, sizeof(mainchan));
+ mc->ppl = ppl;
+ mc->cl = cl;
+ mc->conf = conf_copy(conf);
+ mc->term_width = term_width;
+ mc->term_height = term_height;
+ mc->is_simple = is_simple;
+
+ mc->sc = NULL;
+ mc->chan.vt = &mainchan_channelvt;
+ mc->chan.initial_fixed_window_size = 0;
+
+ if (*conf_get_str(mc->conf, CONF_ssh_nc_host)) {
+ const char *host = conf_get_str(mc->conf, CONF_ssh_nc_host);
+ int port = conf_get_int(mc->conf, CONF_ssh_nc_port);
+
+ mc->sc = ssh_lportfwd_open(cl, host, port, "main channel",
+ NULL, &mc->chan);
+ mc->type = MAINCHAN_DIRECT_TCPIP;
+ } else {
+ mc->sc = ssh_session_open(cl, &mc->chan);
+ mc->type = MAINCHAN_SESSION;
+ }
+
+ if (sc_out) *sc_out = mc->sc;
+ return mc;
+}
+
+static void mainchan_free(Channel *chan)
+{
+ assert(chan->vt == &mainchan_channelvt);
+ mainchan *mc = container_of(chan, mainchan, chan);
+ conf_free(mc->conf);
+ sfree(mc);
+}
+
+static void mainchan_try_fallback_command(mainchan *mc);
+static void mainchan_ready(mainchan *mc);
+
+static void mainchan_open_confirmation(Channel *chan)
+{
+ mainchan *mc = container_of(chan, mainchan, chan);
+ PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+
+ seat_update_specials_menu(mc->ppl->seat);
+ ppl_logevent("Opened main channel");
+ seat_notify_session_started(mc->ppl->seat);
+
+ if (mc->is_simple)
+ sshfwd_hint_channel_is_simple(mc->sc);
+
+ if (mc->type == MAINCHAN_SESSION) {
+ /*
+ * Send the CHANNEL_REQUESTS for the main session channel.
+ */
+ char *key, *val, *cmd;
+ struct X11Display *x11disp;
+ struct X11FakeAuth *x11auth;
+ bool retry_cmd_now = false;
+
+ if (conf_get_bool(mc->conf, CONF_x11_forward)) {
+ char *x11_setup_err;
+ if ((x11disp = x11_setup_display(
+ conf_get_str(mc->conf, CONF_x11_display),
+ mc->conf, &x11_setup_err)) == NULL) {
+ ppl_logevent("X11 forwarding not enabled: unable to"
+ " initialise X display: %s", x11_setup_err);
+ sfree(x11_setup_err);
+ } else {
+ x11auth = ssh_add_x11_display(
+ mc->cl, conf_get_int(mc->conf, CONF_x11_auth), x11disp);
+
+ sshfwd_request_x11_forwarding(
+ mc->sc, true, x11auth->protoname, x11auth->datastring,
+ x11disp->screennum, false);
+ mc->req_x11 = true;
+ }
+ }
+
+ if (ssh_agent_forwarding_permitted(mc->cl)) {
+ sshfwd_request_agent_forwarding(mc->sc, true);
+ mc->req_agent = true;
+ }
+
+ if (!conf_get_bool(mc->conf, CONF_nopty)) {
+ sshfwd_request_pty(
+ mc->sc, true, mc->conf, mc->term_width, mc->term_height);
+ mc->req_pty = true;
+ }
+
+ for (val = conf_get_str_strs(mc->conf, CONF_environmt, NULL, &key);
+ val != NULL;
+ val = conf_get_str_strs(mc->conf, CONF_environmt, key, &key)) {
+ sshfwd_send_env_var(mc->sc, true, key, val);
+ mc->n_req_env++;
+ }
+ if (mc->n_req_env)
+ ppl_logevent("Sent %d environment variables", mc->n_req_env);
+
+ cmd = conf_get_str(mc->conf, CONF_remote_cmd);
+ if (conf_get_bool(mc->conf, CONF_ssh_subsys)) {
+ retry_cmd_now = !sshfwd_start_subsystem(mc->sc, true, cmd);
+ } else if (*cmd) {
+ sshfwd_start_command(mc->sc, true, cmd);
+ } else {
+ sshfwd_start_shell(mc->sc, true);
+ }
+
+ if (retry_cmd_now)
+ mainchan_try_fallback_command(mc);
+ else
+ mc->req_cmd_primary = true;
+
+ } else {
+ ssh_set_ldisc_option(mc->cl, LD_ECHO, true);
+ ssh_set_ldisc_option(mc->cl, LD_EDIT, true);
+ mainchan_ready(mc);
+ }
+}
+
+static void mainchan_try_fallback_command(mainchan *mc)
+{
+ const char *cmd = conf_get_str(mc->conf, CONF_remote_cmd2);
+ if (conf_get_bool(mc->conf, CONF_ssh_subsys2)) {
+ sshfwd_start_subsystem(mc->sc, true, cmd);
+ } else {
+ sshfwd_start_command(mc->sc, true, cmd);
+ }
+ mc->req_cmd_fallback = true;
+}
+
+static void mainchan_request_response(Channel *chan, bool success)
+{
+ assert(chan->vt == &mainchan_channelvt);
+ mainchan *mc = container_of(chan, mainchan, chan);
+ PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+
+ if (mc->req_x11) {
+ mc->req_x11 = false;
+
+ if (success) {
+ ppl_logevent("X11 forwarding enabled");
+ ssh_enable_x_fwd(mc->cl);
+ } else {
+ ppl_logevent("X11 forwarding refused");
+ }
+ return;
+ }
+
+ if (mc->req_agent) {
+ mc->req_agent = false;
+
+ if (success) {
+ ppl_logevent("Agent forwarding enabled");
+ } else {
+ ppl_logevent("Agent forwarding refused");
+ }
+ return;
+ }
+
+ if (mc->req_pty) {
+ mc->req_pty = false;
+
+ if (success) {
+ ppl_logevent("Allocated pty");
+ mc->got_pty = true;
+ } else {
+ ppl_logevent("Server refused to allocate pty");
+ ppl_printf("Server refused to allocate pty\r\n");
+ ssh_set_ldisc_option(mc->cl, LD_ECHO, true);
+ ssh_set_ldisc_option(mc->cl, LD_EDIT, true);
+ }
+ return;
+ }
+
+ if (mc->n_env_replies < mc->n_req_env) {
+ int j = mc->n_env_replies++;
+ if (!success) {
+ ppl_logevent("Server refused to set environment variable %s",
+ conf_get_str_nthstrkey(mc->conf,
+ CONF_environmt, j));
+ mc->n_env_fails++;
+ }
+
+ if (mc->n_env_replies == mc->n_req_env) {
+ if (mc->n_env_fails == 0) {
+ ppl_logevent("All environment variables successfully set");
+ } else if (mc->n_env_fails == mc->n_req_env) {
+ ppl_logevent("All environment variables refused");
+ ppl_printf("Server refused to set environment "
+ "variables\r\n");
+ } else {
+ ppl_printf("Server refused to set all environment "
+ "variables\r\n");
+ }
+ }
+ return;
+ }
+
+ if (mc->req_cmd_primary) {
+ mc->req_cmd_primary = false;
+
+ if (success) {
+ ppl_logevent("Started a shell/command");
+ mainchan_ready(mc);
+ } else if (*conf_get_str(mc->conf, CONF_remote_cmd2)) {
+ ppl_logevent("Primary command failed; attempting fallback");
+ mainchan_try_fallback_command(mc);
+ } else {
+ /*
+ * If there's no remote_cmd2 configured, then we have no
+ * fallback command, so we've run out of options.
+ */
+ ssh_sw_abort_deferred(mc->ppl->ssh,
+ "Server refused to start a shell/command");
+ }
+ return;
+ }
+
+ if (mc->req_cmd_fallback) {
+ mc->req_cmd_fallback = false;
+
+ if (success) {
+ ppl_logevent("Started a shell/command");
+ ssh_got_fallback_cmd(mc->ppl->ssh);
+ mainchan_ready(mc);
+ } else {
+ ssh_sw_abort_deferred(mc->ppl->ssh,
+ "Server refused to start a shell/command");
+ }
+ return;
+ }
+}
+
+static void mainchan_ready(mainchan *mc)
+{
+ mc->ready = true;
+
+ ssh_set_wants_user_input(mc->cl, true);
+ ssh_got_user_input(mc->cl); /* in case any is already queued */
+
+ /* If an EOF arrived before we were ready, handle it now. */
+ if (mc->eof_pending) {
+ mc->eof_pending = false;
+ mainchan_special_cmd(mc, SS_EOF, 0);
+ }
+
+ ssh_ldisc_update(mc->ppl->ssh);
+ queue_idempotent_callback(&mc->ppl->ic_process_queue);
+}
+
+static void mainchan_open_failure(Channel *chan, const char *errtext)
+{
+ assert(chan->vt == &mainchan_channelvt);
+ mainchan *mc = container_of(chan, mainchan, chan);
+
+ ssh_sw_abort_deferred(mc->ppl->ssh,
+ "Server refused to open main channel: %s", errtext);
+}
+
+static size_t mainchan_send(Channel *chan, bool is_stderr,
+ const void *data, size_t length)
+{
+ assert(chan->vt == &mainchan_channelvt);
+ mainchan *mc = container_of(chan, mainchan, chan);
+ return seat_output(mc->ppl->seat, is_stderr, data, length);
+}
+
+static void mainchan_send_eof(Channel *chan)
+{
+ assert(chan->vt == &mainchan_channelvt);
+ mainchan *mc = container_of(chan, mainchan, chan);
+ PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+
+ if (!mc->eof_sent && (seat_eof(mc->ppl->seat) || mc->got_pty)) {
+ /*
+ * Either seat_eof told us that the front end wants us to
+ * close the outgoing side of the connection as soon as we see
+ * EOF from the far end, or else we've unilaterally decided to
+ * do that because we've allocated a remote pty and hence EOF
+ * isn't a particularly meaningful concept.
+ */
+ sshfwd_write_eof(mc->sc);
+ ppl_logevent("Sent EOF message");
+ mc->eof_sent = true;
+ ssh_set_wants_user_input(mc->cl, false); /* stop reading from stdin */
+ }
+}
+
+static void mainchan_set_input_wanted(Channel *chan, bool wanted)
+{
+ assert(chan->vt == &mainchan_channelvt);
+ mainchan *mc = container_of(chan, mainchan, chan);
+
+ /*
+ * This is the main channel of the SSH session, i.e. the one tied
+ * to the standard input (or GUI) of the primary SSH client user
+ * interface. So ssh->send_ok is how we control whether we're
+ * reading from that input.
+ */
+ ssh_set_wants_user_input(mc->cl, wanted);
+}
+
+static char *mainchan_log_close_msg(Channel *chan)
+{
+ return dupstr("Main session channel closed");
+}
+
+static bool mainchan_rcvd_exit_status(Channel *chan, int status)
+{
+ assert(chan->vt == &mainchan_channelvt);
+ mainchan *mc = container_of(chan, mainchan, chan);
+ PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+
+ ssh_got_exitcode(mc->ppl->ssh, status);
+ ppl_logevent("Session sent command exit status %d", status);
+ return true;
+}
+
+static void mainchan_log_exit_signal_common(
+ mainchan *mc, const char *sigdesc,
+ bool core_dumped, ptrlen msg)
+{
+ PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+
+ const char *core_msg = core_dumped ? " (core dumped)" : "";
+ const char *msg_pre = (msg.len ? " (" : "");
+ const char *msg_post = (msg.len ? ")" : "");
+ ppl_logevent("Session exited on %s%s%s%.*s%s",
+ sigdesc, core_msg, msg_pre, PTRLEN_PRINTF(msg), msg_post);
+}
+
+static bool mainchan_rcvd_exit_signal(
+ Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg)
+{
+ assert(chan->vt == &mainchan_channelvt);
+ mainchan *mc = container_of(chan, mainchan, chan);
+ int exitcode;
+ char *signame_str;
+
+ /*
+ * Translate the signal description back into a locally meaningful
+ * number, or 128 if the string didn't match any we recognise.
+ */
+ exitcode = 128;
+
+ #define SIGNAL_SUB(s) \
+ if (ptrlen_eq_string(signame, #s)) \
+ exitcode = 128 + SIG ## s;
+ #define SIGNAL_MAIN(s, text) SIGNAL_SUB(s)
+ #define SIGNALS_LOCAL_ONLY
+ #include "signal-list.h"
+ #undef SIGNAL_SUB
+ #undef SIGNAL_MAIN
+ #undef SIGNALS_LOCAL_ONLY
+
+ ssh_got_exitcode(mc->ppl->ssh, exitcode);
+ if (exitcode == 128)
+ signame_str = dupprintf("unrecognised signal \"%.*s\"",
+ PTRLEN_PRINTF(signame));
+ else
+ signame_str = dupprintf("signal SIG%.*s", PTRLEN_PRINTF(signame));
+ mainchan_log_exit_signal_common(mc, signame_str, core_dumped, msg);
+ sfree(signame_str);
+ return true;
+}
+
+static bool mainchan_rcvd_exit_signal_numeric(
+ Channel *chan, int signum, bool core_dumped, ptrlen msg)
+{
+ assert(chan->vt == &mainchan_channelvt);
+ mainchan *mc = container_of(chan, mainchan, chan);
+ char *signum_str;
+
+ ssh_got_exitcode(mc->ppl->ssh, 128 + signum);
+ signum_str = dupprintf("signal %d", signum);
+ mainchan_log_exit_signal_common(mc, signum_str, core_dumped, msg);
+ sfree(signum_str);
+ return true;
+}
+
+void mainchan_get_specials(
+ mainchan *mc, add_special_fn_t add_special, void *ctx)
+{
+ /* FIXME: this _does_ depend on whether these services are supported */
+
+ add_special(ctx, "Break", SS_BRK, 0);
+
+ #define SIGNAL_MAIN(name, desc) \
+ add_special(ctx, "SIG" #name " (" desc ")", SS_SIG ## name, 0);
+ #define SIGNAL_SUB(name)
+ #include "signal-list.h"
+ #undef SIGNAL_MAIN
+ #undef SIGNAL_SUB
+
+ add_special(ctx, "More signals", SS_SUBMENU, 0);
+
+ #define SIGNAL_MAIN(name, desc)
+ #define SIGNAL_SUB(name) \
+ add_special(ctx, "SIG" #name, SS_SIG ## name, 0);
+ #include "signal-list.h"
+ #undef SIGNAL_MAIN
+ #undef SIGNAL_SUB
+
+ add_special(ctx, NULL, SS_EXITMENU, 0);
+}
+
+static const char *ssh_signal_lookup(SessionSpecialCode code)
+{
+ #define SIGNAL_SUB(name) \
+ if (code == SS_SIG ## name) return #name;
+ #define SIGNAL_MAIN(name, desc) SIGNAL_SUB(name)
+ #include "signal-list.h"
+ #undef SIGNAL_MAIN
+ #undef SIGNAL_SUB
+
+ /* If none of those clauses matched, fail lookup. */
+ return NULL;
+}
+
+void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg)
+{
+ PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+ const char *signame;
+
+ if (code == SS_EOF) {
+ if (!mc->ready) {
+ /*
+ * Buffer the EOF to send as soon as the main channel is
+ * fully set up.
+ */
+ mc->eof_pending = true;
+ } else if (!mc->eof_sent) {
+ sshfwd_write_eof(mc->sc);
+ mc->eof_sent = true;
+ }
+ } else if (code == SS_BRK) {
+ sshfwd_send_serial_break(
+ mc->sc, false, 0 /* default break length */);
+ } else if ((signame = ssh_signal_lookup(code)) != NULL) {
+ /* It's a signal. */
+ sshfwd_send_signal(mc->sc, false, signame);
+ ppl_logevent("Sent signal SIG%s", signame);
+ }
+}
+
+void mainchan_terminal_size(mainchan *mc, int width, int height)
+{
+ mc->term_width = width;
+ mc->term_height = height;
+
+ if (mc->req_pty || mc->got_pty)
+ sshfwd_send_terminal_size_change(mc->sc, width, height);
+}