Support the SSH-2 mechanism for sending signals to a running session. Neither
authorjacob <jacob@cda61777-01e9-0310-a592-d414129be87e>
Sun, 17 Oct 2004 21:22:22 +0000 (21:22 +0000)
committerjacob <jacob@cda61777-01e9-0310-a592-d414129be87e>
Sun, 17 Oct 2004 21:22:22 +0000 (21:22 +0000)
of the SSH servers I conveniently have access to (Debian stable OpenSSH --
3.4p1 -- and lshd) seem to take a blind bit of notice, but the channel
requests look fine to me in the packet log.

I've included all the signals explicitly defined by
draft-ietf-secsh-connect-19, but I've put the more obscure ones in a submenu
of the specials menu; there's therefore been some minor upheaval to support
such submenus.

git-svn-id: svn://svn.tartarus.org/sgt/putty@4652 cda61777-01e9-0310-a592-d414129be87e

doc/using.but
putty.h
ssh.c
telnet.c
unix/pterm.c
window.c

index cda9b3b..03619f6 100644 (file)
@@ -1,4 +1,4 @@
-\versionid $Id: using.but,v 1.34 2004/10/13 13:43:11 simon Exp $
+\versionid $Id: using.but,v 1.35 2004/10/17 21:22:22 jacob Exp $
 
 \C{using} Using PuTTY
 
@@ -175,17 +175,25 @@ PuTTY can also be configured to send this when Ctrl-Z is typed; see
 
 In an SSH connection, the following special commands are available:
 
+\b \I{IGNORE message, SSH special command}\I{No-op, in SSH}IGNORE message
+
+\lcont{
+Should have no effect.
+}
+
 \b \I{Break, SSH special command}Break
 
 \lcont{
-Optional extension; may not be supported by server. PuTTY requests the
-server's default break length.
+Only available in SSH-2, and only during a session. Optional
+extension; may not be supported by server. PuTTY requests the server's
+default break length.
 }
 
-\b \I{IGNORE message, SSH special command}\I{No-op, in SSH}IGNORE message
+\b \I{Signal, SSH special command}Signals (SIGINT, SIGTERM etc)
 
 \lcont{
-Should have no effect.
+Only available in SSH-2, and only during a session. Sends various
+POSIX signals. Not honoured by all servers.
 }
 
 \S2{using-newsession} Starting new sessions
diff --git a/putty.h b/putty.h
index e5cce12..81cb5c5 100644 (file)
--- a/putty.h
+++ b/putty.h
@@ -127,13 +127,23 @@ struct unicode_data {
 #define LGTYP_PACKETS 3                       /* logmode: SSH data packets */
 
 typedef enum {
+    /* Actual special commands. Originally Telnet, but some codes have
+     * been re-used for similar specials in other protocols. */
     TS_AYT, TS_BRK, TS_SYNCH, TS_EC, TS_EL, TS_GA, TS_NOP, TS_ABORT,
     TS_AO, TS_IP, TS_SUSP, TS_EOR, TS_EOF, TS_LECHO, TS_RECHO, TS_PING,
-    TS_EOL
+    TS_EOL,
+    /* POSIX-style signals. (not Telnet) */
+    TS_SIGABRT, TS_SIGALRM, TS_SIGFPE,  TS_SIGHUP,  TS_SIGILL,
+    TS_SIGINT,  TS_SIGKILL, TS_SIGPIPE, TS_SIGQUIT, TS_SIGSEGV,
+    TS_SIGTERM, TS_SIGUSR1, TS_SIGUSR2,
+    /* Pseudo-specials used for constructing the specials menu. */
+    TS_SEP,        /* Separator */
+    TS_SUBMENU,            /* Start a new submenu with specified name */
+    TS_EXITMENU            /* Exit current submenu or end of specials */
 } Telnet_Special;
 
 struct telnet_special {
-    const char *name;                 /* NULL==end, ""==separator */
+    const char *name;
     int code;
 };
 
diff --git a/ssh.c b/ssh.c
index 4fed0b8..c127fc5 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -6885,12 +6885,25 @@ static const struct telnet_special *ssh_get_specials(void *handle)
        {"IGNORE message", TS_NOP},
     };
     static const struct telnet_special ssh2_session_specials[] = {
-       {"", 0},
-       {"Break", TS_BRK}
-       /* XXX we should also support signals */
+       {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, 0}
+       {NULL, TS_EXITMENU}
     };
     static struct telnet_special ssh_specials[lenof(ignore_special) +
                                              lenof(ssh2_session_specials) +
@@ -6978,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 */
+       }
     }
 }
 
index 540c6d2..67db0fd 100644 (file)
--- a/telnet.c
+++ b/telnet.c
@@ -963,6 +963,8 @@ static void telnet_special(void *handle, Telnet_Special code)
            telnet->bufsize = sk_write(telnet->s, (char *)b, 2);
        }
        break;
+      default:
+       break;  /* never heard of it */
     }
 }
 
@@ -976,15 +978,15 @@ static const struct telnet_special *telnet_get_specials(void *handle)
        {"Erase Line", TS_EL},
        {"Go Ahead", TS_GA},
        {"No Operation", TS_NOP},
-       {"", 0},
+       {NULL, TS_SEP},
        {"Abort Process", TS_ABORT},
        {"Abort Output", TS_AO},
        {"Interrupt Process", TS_IP},
        {"Suspend Process", TS_SUSP},
-       {"", 0},
+       {NULL, TS_SEP},
        {"End Of Record", TS_EOR},
        {"End Of File", TS_EOF},
-       {NULL, 0}
+       {NULL, TS_EXITMENU}
     };
     return specials;
 }
index da1bc8d..ca2dab8 100644 (file)
@@ -3160,22 +3160,51 @@ void update_specials_menu(void *frontend)
     else
        specials = NULL;
 
+    /* I believe this disposes of submenus too. */
     gtk_container_foreach(GTK_CONTAINER(inst->specialsmenu),
                          (GtkCallback)gtk_widget_destroy, NULL);
     if (specials) {
        int i;
-       GtkWidget *menuitem;
-       for (i = 0; specials[i].name; i++) {
-           if (*specials[i].name) {
+       GtkWidget *menu = inst->specialsmenu;
+       /* A lame "stack" for submenus that will do for now. */
+       GtkWidget *saved_menu = NULL;
+       int nesting = 1;
+       for (i = 0; nesting > 0; i++) {
+           GtkWidget *menuitem = NULL;
+           switch (specials[i].code) {
+             case TS_SUBMENU:
+               assert (nesting < 2);
+               saved_menu = menu; /* XXX lame stacking */
+               menu = gtk_menu_new();
+               menuitem = gtk_menu_item_new_with_label(specials[i].name);
+               gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
+               gtk_container_add(GTK_CONTAINER(saved_menu), menuitem);
+               gtk_widget_show(menuitem);
+               menuitem = NULL;
+               nesting++;
+               break;
+             case TS_EXITMENU:
+               nesting--;
+               if (nesting) {
+                   menu = saved_menu; /* XXX lame stacking */
+                   saved_menu = NULL;
+               }
+               break;
+             case TS_SEP:
+               menuitem = gtk_menu_item_new();
+               break;
+             default:
                menuitem = gtk_menu_item_new_with_label(specials[i].name);
                gtk_object_set_data(GTK_OBJECT(menuitem), "user-data",
                                    GINT_TO_POINTER(specials[i].code));
                gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
                                   GTK_SIGNAL_FUNC(special_menuitem), inst);
-           } else
-               menuitem = gtk_menu_item_new();
-           gtk_container_add(GTK_CONTAINER(inst->specialsmenu), menuitem);
-           gtk_widget_show(menuitem);
+               break;
+           }
+           if (menuitem) {
+               gtk_container_add(GTK_CONTAINER(menu), menuitem);
+               gtk_widget_show(menuitem);
+           }
        }
        gtk_widget_show(inst->specialsitem1);
        gtk_widget_show(inst->specialsitem2);
index b2ff8af..9f1dbe3 100644 (file)
--- a/window.c
+++ b/window.c
@@ -111,6 +111,7 @@ static struct unicode_data ucsdata;
 static int session_closed;
 
 static const struct telnet_special *specials;
+static int n_specials;
 
 static struct {
     HMENU menu;
@@ -915,20 +916,48 @@ void update_specials_menu(void *frontend)
        specials = NULL;
 
     if (specials) {
-       p = CreateMenu();
-       for (i = 0; specials[i].name; i++) {
+       /* We can't use Windows to provide a stack for submenus, so
+        * here's a lame "stack" that will do for now. */
+       HMENU saved_menu = NULL;
+       int nesting = 1;
+       p = CreatePopupMenu();
+       for (i = 0; nesting > 0; i++) {
            assert(IDM_SPECIAL_MIN + 0x10 * i < IDM_SPECIAL_MAX);
-           if (*specials[i].name)
+           switch (specials[i].code) {
+             case TS_SEP:
+               AppendMenu(p, MF_SEPARATOR, 0, 0);
+               break;
+             case TS_SUBMENU:
+               assert(nesting < 2);
+               nesting++;
+               saved_menu = p; /* XXX lame stacking */
+               p = CreatePopupMenu();
+               AppendMenu(saved_menu, MF_POPUP | MF_ENABLED,
+                          (UINT) p, specials[i].name);
+               break;
+             case TS_EXITMENU:
+               nesting--;
+               if (nesting) {
+                   p = saved_menu; /* XXX lame stacking */
+                   saved_menu = NULL;
+               }
+               break;
+             default:
                AppendMenu(p, MF_ENABLED, IDM_SPECIAL_MIN + 0x10 * i,
                           specials[i].name);
-           else
-               AppendMenu(p, MF_SEPARATOR, 0, 0);
+               break;
+           }
        }
-    } else
+       /* Squirrel the highest special. */
+       n_specials = i - 1;
+    } else {
        p = NULL;
+       n_specials = 0;
+    }
 
     for (j = 0; j < lenof(popup_menus); j++) {
        if (menu_already_exists) {
+           /* XXX does this free up all submenus? */
            DeleteMenu(popup_menus[j].menu,
                       popup_menus[j].specials_submenu_pos,
                       MF_BYPOSITION);
@@ -2088,20 +2117,16 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
            }
            if (wParam >= IDM_SPECIAL_MIN && wParam <= IDM_SPECIAL_MAX) {
                int i = (wParam - IDM_SPECIAL_MIN) / 0x10;
-               int j;
                /*
                 * Ensure we haven't been sent a bogus SYSCOMMAND
                 * which would cause us to reference invalid memory
                 * and crash. Perhaps I'm just too paranoid here.
                 */
-               for (j = 0; j < i; j++)
-                   if (!specials || !specials[j].name)
-                       break;
-               if (j == i) {
-                   if (back)
-                       back->special(backhandle, specials[i].code);
-                   net_pending_errors();
-               }
+               if (i >= n_specials)
+                   break;
+               if (back)
+                   back->special(backhandle, specials[i].code);
+               net_pending_errors();
            }
        }
        break;