X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/0ed48730a1c7c81cf93a49ce61541461d7df5a58..f80d4744684828c7b99f1dc25d4b21cf0b59c786:/ssh.c diff --git a/ssh.c b/ssh.c index 8356a075..268fc049 100644 --- a/ssh.c +++ b/ssh.c @@ -4788,6 +4788,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt) int siglen, retlen, len; char *q, *agentreq, *ret; int try_send; + int num_env, env_left, env_ok; }; crState(do_ssh2_authconn_state); @@ -5645,6 +5646,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt) ssh->mainchan->v.v2.remmaxpkt = ssh_pkt_getuint32(ssh); bufchain_init(&ssh->mainchan->v.v2.outbuffer); add234(ssh->channels, ssh->mainchan); + update_specials_menu(ssh->frontend); logevent("Opened channel for session"); } else ssh->mainchan = NULL; @@ -5954,6 +5956,82 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt) } /* + * Send environment variables. + * + * Simplest thing here is to send all the requests at once, and + * then wait for a whole bunch of successes or failures. + */ + if (ssh->mainchan && *ssh->cfg.environmt) { + char *e = ssh->cfg.environmt; + char *var, *varend, *val; + + s->num_env = 0; + + while (*e) { + var = e; + while (*e && *e != '\t') e++; + varend = e; + if (*e == '\t') e++; + val = e; + while (*e) e++; + e++; + + ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_REQUEST); + ssh2_pkt_adduint32(ssh, ssh->mainchan->remoteid); + ssh2_pkt_addstring(ssh, "env"); + ssh2_pkt_addbool(ssh, 1); /* want reply */ + ssh2_pkt_addstring_start(ssh); + ssh2_pkt_addstring_data(ssh, var, varend-var); + ssh2_pkt_addstring(ssh, val); + ssh2_pkt_send(ssh); + + s->num_env++; + } + + logeventf(ssh, "Sent %d environment variables", s->num_env); + + s->env_ok = 0; + s->env_left = s->num_env; + + while (s->env_left > 0) { + do { + crWaitUntilV(ispkt); + if (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) { + unsigned i = ssh_pkt_getuint32(ssh); + struct ssh_channel *c; + c = find234(ssh->channels, &i, ssh_channelfind); + if (!c) + continue; /* nonexistent channel */ + c->v.v2.remwindow += ssh_pkt_getuint32(ssh); + } + } while (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST); + + if (ssh->pktin.type != SSH2_MSG_CHANNEL_SUCCESS) { + if (ssh->pktin.type != SSH2_MSG_CHANNEL_FAILURE) { + bombout(("Unexpected response to environment request:" + " packet type %d", ssh->pktin.type)); + crStopV; + } + } else { + s->env_ok++; + } + + s->env_left--; + } + + if (s->env_ok == s->num_env) { + logevent("All environment variables successfully set"); + } else if (s->env_ok == 0) { + logevent("All environment variables refused"); + c_write_str(ssh, "Server refused to set environment variables\r\n"); + } else { + logeventf(ssh, "%d environment variables refused", + s->num_env - s->env_ok); + c_write_str(ssh, "Server refused to set all environment variables\r\n"); + } + } + + /* * Start a shell or a remote command. We may have to attempt * this twice if the config data has provided a second choice * of command. @@ -6150,7 +6228,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt) /* Do pre-close processing on the channel. */ switch (c->type) { case CHAN_MAINSESSION: - break; /* nothing to see here, move along */ + ssh->mainchan = NULL; + update_specials_menu(ssh->frontend); + break; case CHAN_X11: if (c->u.x11.s != NULL) x11_close(c->u.x11.s); @@ -6274,6 +6354,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt) unsigned localid; char *type; int typelen, want_reply; + int reply = SSH2_MSG_CHANNEL_FAILURE; /* default */ struct ssh_channel *c; localid = ssh_pkt_getuint32(ssh); @@ -6305,18 +6386,93 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt) * the request type string to see if it's something * we recognise. */ - if (typelen == 11 && !memcmp(type, "exit-status", 11) && - c == ssh->mainchan) { - /* We recognise "exit-status" on the primary channel. */ - char buf[100]; - ssh->exitcode = ssh_pkt_getuint32(ssh); - sprintf(buf, "Server sent command exit status %d", - ssh->exitcode); - logevent(buf); - if (want_reply) { - ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_SUCCESS); - ssh2_pkt_adduint32(ssh, c->remoteid); - ssh2_pkt_send(ssh); + if (c == ssh->mainchan) { + /* + * We recognise "exit-status" and "exit-signal" on + * the primary channel. + */ + if (typelen == 11 && + !memcmp(type, "exit-status", 11)) { + + ssh->exitcode = ssh_pkt_getuint32(ssh); + logeventf(ssh, "Server sent command exit status %d", + ssh->exitcode); + reply = SSH2_MSG_CHANNEL_SUCCESS; + + } else if (typelen == 11 && + !memcmp(type, "exit-signal", 11)) { + + int is_plausible = TRUE, is_int = FALSE; + char *fmt_sig = "", *fmt_msg = ""; + char *msg; + int msglen = 0, core = FALSE; + /* ICK: older versions of OpenSSH (e.g. 3.4p1) + * provide an `int' for the signal, despite its + * having been a `string' in the drafts since at + * least 2001. (Fixed in session.c 1.147.) Try to + * infer which we can safely parse it as. */ + { + unsigned char *p = ssh->pktin.body + + ssh->pktin.savedpos; + long len = ssh->pktin.length - ssh->pktin.savedpos; + unsigned long num = GET_32BIT(p); /* what is it? */ + /* If it's 0, it hardly matters; assume string */ + if (num == 0) { + is_int = FALSE; + } else { + int maybe_int = FALSE, maybe_str = FALSE; +#define CHECK_HYPOTHESIS(offset, result) \ + do { \ + long q = offset; \ + if (q+4 <= len) { \ + q = q + 4 + GET_32BIT(p+q); \ + if (q+4 <= len && (q = q + 4 + GET_32BIT(p+q)) && q == len) \ + result = TRUE; \ + } \ + } while(0) + CHECK_HYPOTHESIS(4+1, maybe_int); + CHECK_HYPOTHESIS(4+num+1, maybe_str); +#undef CHECK_HYPOTHESIS + if (maybe_int && !maybe_str) + is_int = TRUE; + else if (!maybe_int && maybe_str) + is_int = FALSE; + else + /* Crikey. Either or neither. Panic. */ + is_plausible = FALSE; + } + } + if (is_plausible) { + if (is_int) { + /* Old non-standard OpenSSH. */ + int signum = ssh_pkt_getuint32(ssh); + fmt_sig = dupprintf(" %d", signum); + } else { + /* As per the drafts. */ + char *sig; + int siglen; + ssh_pkt_getstring(ssh, &sig, &siglen); + /* Signal name isn't supposed to be blank, but + * let's cope gracefully if it is. */ + if (siglen) { + fmt_sig = dupprintf(" \"%.*s\"", + siglen, sig); + } + } + core = ssh2_pkt_getbool(ssh); + ssh_pkt_getstring(ssh, &msg, &msglen); + if (msglen) { + fmt_msg = dupprintf(" (\"%.*s\")", msglen, msg); + } + /* ignore lang tag */ + } /* else don't attempt to parse */ + logeventf(ssh, "Server exited on signal%s%s%s", + fmt_sig, core ? " (core dumped)" : "", + fmt_msg); + if (*fmt_sig) sfree(fmt_sig); + if (*fmt_msg) sfree(fmt_msg); + reply = SSH2_MSG_CHANNEL_SUCCESS; + } } else { /* @@ -6325,11 +6481,12 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt) * or respond with CHANNEL_FAILURE, depending * on want_reply. */ - if (want_reply) { - ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_FAILURE); - ssh2_pkt_adduint32(ssh, c->remoteid); - ssh2_pkt_send(ssh); - } + reply = SSH2_MSG_CHANNEL_FAILURE; + } + if (want_reply) { + ssh2_pkt_init(ssh, reply); + ssh2_pkt_adduint32(ssh, c->remoteid); + ssh2_pkt_send(ssh); } } else if (ssh->pktin.type == SSH2_MSG_GLOBAL_REQUEST) { char *type; @@ -6801,23 +6958,62 @@ static void ssh_size(void *handle, int width, int height) */ static const struct telnet_special *ssh_get_specials(void *handle) { + static const struct telnet_special ignore_special[] = { + {"IGNORE message", TS_NOP}, + }; + static const struct telnet_special ssh2_session_specials[] = { + {NULL, TS_SEP}, + {"Break", TS_BRK}, + /* These are the signal names defined by draft-ietf-secsh-connect-19. + * They include all the ISO C signals, but are a subset of the POSIX + * required signals. */ + {"SIGINT (Interrupt)", TS_SIGINT}, + {"SIGTERM (Terminate)", TS_SIGTERM}, + {"SIGKILL (Kill)", TS_SIGKILL}, + {"SIGQUIT (Quit)", TS_SIGQUIT}, + {"SIGHUP (Hangup)", TS_SIGHUP}, + {"More signals", TS_SUBMENU}, + {"SIGABRT", TS_SIGABRT}, {"SIGALRM", TS_SIGALRM}, + {"SIGFPE", TS_SIGFPE}, {"SIGILL", TS_SIGILL}, + {"SIGPIPE", TS_SIGPIPE}, {"SIGSEGV", TS_SIGSEGV}, + {"SIGUSR1", TS_SIGUSR1}, {"SIGUSR2", TS_SIGUSR2}, + {NULL, TS_EXITMENU} + }; + static const struct telnet_special specials_end[] = { + {NULL, TS_EXITMENU} + }; + static struct telnet_special ssh_specials[lenof(ignore_special) + + lenof(ssh2_session_specials) + + lenof(specials_end)]; Ssh ssh = (Ssh) handle; + int i = 0; +#define ADD_SPECIALS(name) \ + do { \ + assert((i + lenof(name)) <= lenof(ssh_specials)); \ + memcpy(&ssh_specials[i], name, sizeof name); \ + i += lenof(name); \ + } while(0) if (ssh->version == 1) { - static const struct telnet_special ssh1_specials[] = { - {"IGNORE message", TS_NOP}, - {NULL, 0} - }; - return ssh1_specials; + /* Don't bother offering IGNORE if we've decided the remote + * won't cope with it, since we wouldn't bother sending it if + * asked anyway. */ + if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) + ADD_SPECIALS(ignore_special); } else if (ssh->version == 2) { - static const struct telnet_special ssh2_specials[] = { - {"Break", TS_BRK}, - {"IGNORE message", TS_NOP}, - {NULL, 0} - }; - return ssh2_specials; - } else + /* XXX add rekey, when implemented */ + ADD_SPECIALS(ignore_special); + if (ssh->mainchan) + ADD_SPECIALS(ssh2_session_specials); + } /* else we're not ready yet */ + + if (i) { + ADD_SPECIALS(specials_end); + return ssh_specials; + } else { return NULL; + } +#undef ADD_SPECIALS } /* @@ -6872,7 +7068,37 @@ static void ssh_special(void *handle, Telnet_Special code) ssh2_pkt_send(ssh); } } else { - /* do nothing */ + /* Is is a POSIX signal? */ + char *signame = NULL; + if (code == TS_SIGABRT) signame = "ABRT"; + if (code == TS_SIGALRM) signame = "ALRM"; + if (code == TS_SIGFPE) signame = "FPE"; + if (code == TS_SIGHUP) signame = "HUP"; + if (code == TS_SIGILL) signame = "ILL"; + if (code == TS_SIGINT) signame = "INT"; + if (code == TS_SIGKILL) signame = "KILL"; + if (code == TS_SIGPIPE) signame = "PIPE"; + if (code == TS_SIGQUIT) signame = "QUIT"; + if (code == TS_SIGSEGV) signame = "SEGV"; + if (code == TS_SIGTERM) signame = "TERM"; + if (code == TS_SIGUSR1) signame = "USR1"; + if (code == TS_SIGUSR2) signame = "USR2"; + /* The SSH-2 protocol does in principle support arbitrary named + * signals, including signame@domain, but we don't support those. */ + if (signame) { + /* It's a signal. */ + if (ssh->version == 2 && ssh->mainchan) { + ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_REQUEST); + ssh2_pkt_adduint32(ssh, ssh->mainchan->remoteid); + ssh2_pkt_addstring(ssh, "signal"); + ssh2_pkt_addbool(ssh, 0); + ssh2_pkt_addstring(ssh, signame); + ssh2_pkt_send(ssh); + logeventf(ssh, "Sent signal SIG%s", signame); + } + } else { + /* Never heard of it. Do nothing */ + } } }