| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548 | /* * 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 = {    // WINSCP    /*.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){    pinitassert(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;        #ifndef WINSCP        struct X11Display *x11disp;        struct X11FakeAuth *x11auth;        #endif        bool retry_cmd_now = false;        #ifndef WINSCP        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;            }        }        #endif        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 if (*cmd) {        sshfwd_start_command(mc->sc, true, cmd);    } else {        sshfwd_start_shell(mc->sc, true); // WINSCP    }    mc->req_cmd_fallback = true;}static void mainchan_request_response(Channel *chan, bool success){    pinitassert(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) || conf_get_bool(mc->conf, CONF_force_remote_cmd2)) { // WINSCP            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){    pinitassert(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){    pinitassert(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){    pinitassert(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){    pinitassert(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){    pinitassert(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){    pinitassert(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){    pinitassert(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 */    /* WINSCP (causes linker warning and we do not use these)    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);}
 |