Support the SSH-2 mechanism for sending signals to a running session. Neither
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index e8d2431..c127fc5 100644 (file)
--- 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);
 
@@ -5612,43 +5613,48 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt)
      * point; there's no need to send SERVICE_REQUEST.
      */
 
+    ssh->channels = newtree234(ssh_channelcmp);
+
     /*
-     * So now create a channel with a session in it.
+     * Create the main session channel.
      */
-    ssh->channels = newtree234(ssh_channelcmp);
-    ssh->mainchan = snew(struct ssh_channel);
-    ssh->mainchan->ssh = ssh;
-    ssh->mainchan->localid = alloc_channel_id(ssh);
-    ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_OPEN);
-    ssh2_pkt_addstring(ssh, "session");
-    ssh2_pkt_adduint32(ssh, ssh->mainchan->localid);
-    ssh->mainchan->v.v2.locwindow = OUR_V2_WINSIZE;
-    ssh2_pkt_adduint32(ssh, ssh->mainchan->v.v2.locwindow);/* our window size */
-    ssh2_pkt_adduint32(ssh, 0x4000UL);      /* our max pkt size */
-    ssh2_pkt_send(ssh);
-    crWaitUntilV(ispkt);
-    if (ssh->pktin.type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
-       bombout(("Server refused to open a session"));
-       crStopV;
-       /* FIXME: error data comes back in FAILURE packet */
-    }
-    if (ssh_pkt_getuint32(ssh) != ssh->mainchan->localid) {
-       bombout(("Server's channel confirmation cited wrong channel"));
-       crStopV;
-    }
-    ssh->mainchan->remoteid = ssh_pkt_getuint32(ssh);
-    ssh->mainchan->type = CHAN_MAINSESSION;
-    ssh->mainchan->closes = 0;
-    ssh->mainchan->v.v2.remwindow = ssh_pkt_getuint32(ssh);
-    ssh->mainchan->v.v2.remmaxpkt = ssh_pkt_getuint32(ssh);
-    bufchain_init(&ssh->mainchan->v.v2.outbuffer);
-    add234(ssh->channels, ssh->mainchan);
-    logevent("Opened channel for session");
+    if (!ssh->cfg.ssh_no_shell) {
+       ssh->mainchan = snew(struct ssh_channel);
+       ssh->mainchan->ssh = ssh;
+       ssh->mainchan->localid = alloc_channel_id(ssh);
+       ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_OPEN);
+       ssh2_pkt_addstring(ssh, "session");
+       ssh2_pkt_adduint32(ssh, ssh->mainchan->localid);
+       ssh->mainchan->v.v2.locwindow = OUR_V2_WINSIZE;
+       ssh2_pkt_adduint32(ssh, ssh->mainchan->v.v2.locwindow);/* our window size */
+       ssh2_pkt_adduint32(ssh, 0x4000UL);      /* our max pkt size */
+       ssh2_pkt_send(ssh);
+       crWaitUntilV(ispkt);
+       if (ssh->pktin.type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
+           bombout(("Server refused to open a session"));
+           crStopV;
+           /* FIXME: error data comes back in FAILURE packet */
+       }
+       if (ssh_pkt_getuint32(ssh) != ssh->mainchan->localid) {
+           bombout(("Server's channel confirmation cited wrong channel"));
+           crStopV;
+       }
+       ssh->mainchan->remoteid = ssh_pkt_getuint32(ssh);
+       ssh->mainchan->type = CHAN_MAINSESSION;
+       ssh->mainchan->closes = 0;
+       ssh->mainchan->v.v2.remwindow = ssh_pkt_getuint32(ssh);
+       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;
 
     /*
      * Potentially enable X11 forwarding.
      */
-    if (ssh->cfg.x11_forward) {
+    if (ssh->mainchan && ssh->cfg.x11_forward) {
        char proto[20], data[64];
        logevent("Requesting X11 forwarding");
        ssh->x11auth = x11_invent_auth(proto, sizeof(proto),
@@ -5861,7 +5867,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt)
     /*
      * Potentially enable agent forwarding.
      */
-    if (ssh->cfg.agentfwd && agent_exists()) {
+    if (ssh->mainchan && ssh->cfg.agentfwd && agent_exists()) {
        logevent("Requesting OpenSSH-style agent forwarding");
        ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_REQUEST);
        ssh2_pkt_adduint32(ssh, ssh->mainchan->remoteid);
@@ -5897,7 +5903,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt)
     /*
      * Now allocate a pty for the session.
      */
-    if (!ssh->cfg.nopty) {
+    if (ssh->mainchan && !ssh->cfg.nopty) {
        /* Unpick the terminal-speed string. */
        /* XXX perhaps we should allow no speeds to be sent. */
         ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */
@@ -5950,11 +5956,87 @@ 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.
      */
-    while (1) {
+    if (ssh->mainchan) while (1) {
        int subsys;
        char *cmd;
 
@@ -6028,7 +6110,8 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt)
      */
     if (ssh->ldisc)
        ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */
-    ssh->send_ok = 1;
+    if (ssh->mainchan)
+       ssh->send_ok = 1;
     while (1) {
        crReturnV;
        s->try_send = FALSE;
@@ -6145,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);
@@ -6171,8 +6256,10 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt)
 
                /*
                 * See if that was the last channel left open.
+                * (This is only our termination condition if we're
+                * not running in -N mode.)
                 */
-               if (count234(ssh->channels) == 0) {
+               if (!ssh->cfg.ssh_no_shell && count234(ssh->channels) == 0) {
                    logevent("All channels closed. Disconnecting");
 #if 0
                     /*
@@ -6452,7 +6539,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt)
                bombout(("Strange packet received: type %d", ssh->pktin.type));
                crStopV;
            }
-       } else {
+       } else if (ssh->mainchan) {
            /*
             * We have spare data. Add it to the channel buffer.
             */
@@ -6772,7 +6859,7 @@ static void ssh_size(void *handle, int width, int height)
                            PKT_INT, ssh->term_height,
                            PKT_INT, ssh->term_width,
                            PKT_INT, 0, PKT_INT, 0, PKT_END);
-           } else {
+           } else if (ssh->mainchan) {
                ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_REQUEST);
                ssh2_pkt_adduint32(ssh, ssh->mainchan->remoteid);
                ssh2_pkt_addstring(ssh, "window-change");
@@ -6794,23 +6881,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
 }
 
 /*
@@ -6834,7 +6960,7 @@ static void ssh_special(void *handle, Telnet_Special code)
        }
        if (ssh->version == 1) {
            send_packet(ssh, SSH1_CMSG_EOF, PKT_END);
-       } else {
+       } else if (ssh->mainchan) {
            ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_EOF);
            ssh2_pkt_adduint32(ssh, ssh->mainchan->remoteid);
            ssh2_pkt_send(ssh);
@@ -6856,7 +6982,7 @@ static void ssh_special(void *handle, Telnet_Special code)
            || ssh->state == SSH_STATE_PREPACKET) return;
        if (ssh->version == 1) {
            logevent("Unable to send BREAK signal in SSH1");
-       } else {
+       } else if (ssh->mainchan) {
            ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_REQUEST);
            ssh2_pkt_adduint32(ssh, ssh->mainchan->remoteid);
            ssh2_pkt_addstring(ssh, "break");
@@ -6865,7 +6991,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 */
+       }
     }
 }