Support, on Unix only (so far), for OpenSSH-style generic proxying
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Tue, 6 May 2003 19:52:31 +0000 (19:52 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Tue, 6 May 2003 19:52:31 +0000 (19:52 +0000)
(running a local command in a pair of pipes and proxying through
that, for example `ssh proxyhost nc -q0 %host %port').

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

Recipe
config.c
pproxy.c [new file with mode: 0644]
proxy.c
proxy.h
putty.h
unix/uxcfg.c
unix/uxproxy.c [new file with mode: 0644]

diff --git a/Recipe b/Recipe
index 5d6f740..255c4cb 100644 (file)
--- a/Recipe
+++ b/Recipe
@@ -118,11 +118,11 @@ SFTP     = sftp int64 logging
 # Miscellaneous objects appearing in all the network utilities (not
 # Pageant or PuTTYgen).
 WINMISC  = misc version winstore settings tree234 winnet proxy cmdline
-         + windefs winmisc
+         + windefs winmisc pproxy
 UXMISC   = misc version uxstore settings tree234 uxsel uxnet proxy cmdline
-         + uxmisc
+         + uxmisc uxproxy
 MACMISC  = misc version macstore settings tree234 macnet mtcpnet otnet proxy
-         + macmisc macabout
+         + macmisc macabout pproxy
 
 # Character set library, for use in pterm.
 CHARSET  = sbcsdat slookup sbcs utf8 toucs fromucs xenc mimeenc macenc localenc
index fd5cb95..e462ec2 100644 (file)
--- a/config.c
+++ b/config.c
@@ -1296,14 +1296,14 @@ void setup_config_box(struct controlbox *b, struct sesslist *sesslist,
                      "Options controlling proxy usage");
 
        s = ctrl_getset(b, "Connection/Proxy", "basics", "Proxy basics");
-       ctrl_radiobuttons(s, "Proxy type:", NO_SHORTCUT, 4,
+       ctrl_radiobuttons(s, "Proxy type:", 't', 4,
                          HELPCTX(proxy_type),
                          dlg_stdradiobutton_handler,
                          I(offsetof(Config, proxy_type)),
-                         "None", 'n', I(PROXY_NONE),
-                         "HTTP", 't', I(PROXY_HTTP),
-                         "SOCKS", 's', I(PROXY_SOCKS),
-                         "Telnet", 'l', I(PROXY_TELNET),
+                         "None", I(PROXY_NONE),
+                         "HTTP", I(PROXY_HTTP),
+                         "SOCKS", I(PROXY_SOCKS),
+                         "Telnet", I(PROXY_TELNET),
                          NULL);
        ctrl_columns(s, 2, 80, 20);
        c = ctrl_editbox(s, "Proxy hostname", 'y', 100,
diff --git a/pproxy.c b/pproxy.c
new file mode 100644 (file)
index 0000000..6a870ed
--- /dev/null
+++ b/pproxy.c
@@ -0,0 +1,17 @@
+/*
+ * pproxy.c: dummy implementation of platform_new_connection(), to
+ * be supplanted on any platform which has its own local proxy
+ * method.
+ */
+
+#include "putty.h"
+#include "network.h"
+#include "proxy.h"
+
+Socket new_connection(SockAddr addr, char *hostname,
+                     int port, int privport,
+                     int oobinline, int nodelay, Plug plug,
+                     const Config *cfg)
+{
+    return NULL;
+}
diff --git a/proxy.c b/proxy.c
index a15b3ba..59f2abb 100644 (file)
--- a/proxy.c
+++ b/proxy.c
@@ -347,6 +347,11 @@ SockAddr name_lookup(char *host, int port, char **canonicalname,
     return sk_namelookup(host, canonicalname);
 }
 
+Socket platform_new_connection(SockAddr addr, char *hostname,
+                              int port, int privport,
+                              int oobinline, int nodelay, Plug plug,
+                              const Config *cfg);
+
 Socket new_connection(SockAddr addr, char *hostname,
                      int port, int privport,
                      int oobinline, int nodelay, Plug plug,
@@ -378,6 +383,11 @@ Socket new_connection(SockAddr addr, char *hostname,
        Proxy_Plug pplug;
        SockAddr proxy_addr;
        char *proxy_canonical_name;
+       Socket sret;
+
+       if ( (sret = platform_new_connection(addr, hostname, port, privport,
+                                            oobinline, nodelay, plug, cfg)) )
+           return sret;
 
        ret = snew(struct Socket_proxy_tag);
        ret->fn = &socket_fn_table;
@@ -1161,103 +1171,117 @@ int proxy_socks5_negotiate (Proxy_Socket p, int change)
  * standardised or at all well-defined.)
  */
 
-int proxy_telnet_negotiate (Proxy_Socket p, int change)
+char *format_telnet_command(SockAddr addr, int port, const Config *cfg)
 {
-    if (p->state == PROXY_CHANGE_NEW) {
-
-       int so = 0, eo = 0;
-
-       /* we need to escape \\, \%, \r, \n, \t, \x??, \0???, 
-        * %%, %host, %port, %user, and %pass
-        */
-
-       while (p->cfg.proxy_telnet_command[eo] != 0) {
-
-           /* scan forward until we hit end-of-line, 
-            * or an escape character (\ or %) */
-           while (p->cfg.proxy_telnet_command[eo] != 0 &&
-                  p->cfg.proxy_telnet_command[eo] != '%' &&
-                  p->cfg.proxy_telnet_command[eo] != '\\') eo++;
-
-           /* if we hit eol, break out of our escaping loop */
-           if (p->cfg.proxy_telnet_command[eo] == 0) break;
-
-           /* if there was any unescaped text before the escape
-            * character, send that now */
-           if (eo != so) {
-               sk_write(p->sub_socket, 
-                        p->cfg.proxy_telnet_command + so, eo - so);
-           }
-
-           so = eo++;
-
-           /* if the escape character was the last character of
-            * the line, we'll just stop and send it. */
-           if (p->cfg.proxy_telnet_command[eo] == 0) break;
+    char *ret = NULL;
+    int retlen = 0, retsize = 0;
+    int so = 0, eo = 0;
+#define ENSURE(n) do { \
+    if (retsize < retlen + n) { \
+       retsize = retlen + n + 512; \
+       ret = sresize(ret, retsize, char); \
+    } \
+} while (0)
+
+    /* we need to escape \\, \%, \r, \n, \t, \x??, \0???, 
+     * %%, %host, %port, %user, and %pass
+     */
 
-           if (p->cfg.proxy_telnet_command[so] == '\\') {
+    while (cfg->proxy_telnet_command[eo] != 0) {
 
-               /* we recognize \\, \%, \r, \n, \t, \x??. 
-                * anything else, we just send unescaped (including the \). 
-                */
+       /* scan forward until we hit end-of-line,
+        * or an escape character (\ or %) */
+       while (cfg->proxy_telnet_command[eo] != 0 &&
+              cfg->proxy_telnet_command[eo] != '%' &&
+              cfg->proxy_telnet_command[eo] != '\\') eo++;
 
-               switch (p->cfg.proxy_telnet_command[eo]) {
+       /* if we hit eol, break out of our escaping loop */
+       if (cfg->proxy_telnet_command[eo] == 0) break;
 
-                 case '\\':
-                   sk_write(p->sub_socket, "\\", 1);
-                   eo++;
-                   break;
+       /* if there was any unescaped text before the escape
+        * character, send that now */
+       if (eo != so) {
+           ENSURE(eo - so);
+           memcpy(ret + retlen, cfg->proxy_telnet_command + so, eo - so);
+           retlen += eo - so;
+       }
 
-                 case '%':
-                   sk_write(p->sub_socket, "%%", 1);
-                   eo++;
-                   break;
+       so = eo++;
 
-                 case 'r':
-                   sk_write(p->sub_socket, "\r", 1);
-                   eo++;
-                   break;
+       /* if the escape character was the last character of
+        * the line, we'll just stop and send it. */
+       if (cfg->proxy_telnet_command[eo] == 0) break;
 
-                 case 'n':
-                   sk_write(p->sub_socket, "\n", 1);
-                   eo++;
-                   break;
+       if (cfg->proxy_telnet_command[so] == '\\') {
 
-                 case 't':
-                   sk_write(p->sub_socket, "\t", 1);
-                   eo++;
-                   break;
+           /* we recognize \\, \%, \r, \n, \t, \x??.
+            * anything else, we just send unescaped (including the \).
+            */
 
-                 case 'x':
-                 case 'X':
-                   {
+           switch (cfg->proxy_telnet_command[eo]) {
+
+             case '\\':
+               ENSURE(1);
+               ret[retlen++] = '\\';
+               eo++;
+               break;
+
+             case '%':
+               ENSURE(1);
+               ret[retlen++] = '%';
+               eo++;
+               break;
+
+             case 'r':
+               ENSURE(1);
+               ret[retlen++] = '\r';
+               eo++;
+               break;
+
+             case 'n':
+               ENSURE(1);
+               ret[retlen++] = '\n';
+               eo++;
+               break;
+
+             case 't':
+               ENSURE(1);
+               ret[retlen++] = '\t';
+               eo++;
+               break;
+
+             case 'x':
+             case 'X':
+               {
                    /* escaped hexadecimal value (ie. \xff) */
                    unsigned char v = 0;
                    int i = 0;
 
                    for (;;) {
                        eo++;
-                       if (p->cfg.proxy_telnet_command[eo] >= '0' &&
-                           p->cfg.proxy_telnet_command[eo] <= '9')
-                           v += p->cfg.proxy_telnet_command[eo] - '0';
-                       else if (p->cfg.proxy_telnet_command[eo] >= 'a' &&
-                                p->cfg.proxy_telnet_command[eo] <= 'f')
-                           v += p->cfg.proxy_telnet_command[eo] - 'a' + 10;
-                       else if (p->cfg.proxy_telnet_command[eo] >= 'A' &&
-                                p->cfg.proxy_telnet_command[eo] <= 'F')
-                           v += p->cfg.proxy_telnet_command[eo] - 'A' + 10;
+                       if (cfg->proxy_telnet_command[eo] >= '0' &&
+                           cfg->proxy_telnet_command[eo] <= '9')
+                           v += cfg->proxy_telnet_command[eo] - '0';
+                       else if (cfg->proxy_telnet_command[eo] >= 'a' &&
+                                cfg->proxy_telnet_command[eo] <= 'f')
+                           v += cfg->proxy_telnet_command[eo] - 'a' + 10;
+                       else if (cfg->proxy_telnet_command[eo] >= 'A' &&
+                                cfg->proxy_telnet_command[eo] <= 'F')
+                           v += cfg->proxy_telnet_command[eo] - 'A' + 10;
                        else {
                            /* non hex character, so we abort and just
                             * send the whole thing unescaped (including \x)
                             */
-                           sk_write(p->sub_socket, "\\", 1);
+                           ENSURE(1);
+                           ret[retlen++] = '\\';
                            eo = so + 1;
                            break;
                        }
 
                        /* we only extract two hex characters */
                        if (i == 1) {
-                           sk_write(p->sub_socket, (char *)&v, 1);
+                           ENSURE(1);
+                           ret[retlen++] = v;
                            eo++;
                            break;
                        }
@@ -1265,69 +1289,102 @@ int proxy_telnet_negotiate (Proxy_Socket p, int change)
                        i++;
                        v <<= 4;
                    }
-                   }
-                   break;
-
-                 default:
-                   sk_write(p->sub_socket, 
-                            p->cfg.proxy_telnet_command + so, 2);
-                   eo++;
-                   break;
                }
-           } else {
-
-               /* % escape. we recognize %%, %host, %port, %user, %pass. 
-                * anything else, we just send unescaped (including the %). 
-                */
+               break;
 
-               if (p->cfg.proxy_telnet_command[eo] == '%') {
-                   sk_write(p->sub_socket, "%", 1);
-                   eo++;
-               }
-               else if (strnicmp(p->cfg.proxy_telnet_command + eo,
-                                 "host", 4) == 0) {
-                   char dest[512];
-                   sk_getaddr(p->remote_addr, dest, lenof(dest));
-                   sk_write(p->sub_socket, dest, strlen(dest));
-                   eo += 4;
-               }
-               else if (strnicmp(p->cfg.proxy_telnet_command + eo,
-                                 "port", 4) == 0) {
-                   char port[8];
-                   sprintf(port, "%i", p->remote_port);
-                   sk_write(p->sub_socket, port, strlen(port));
-                   eo += 4;
-               }
-               else if (strnicmp(p->cfg.proxy_telnet_command + eo,
-                                 "user", 4) == 0) {
-                   sk_write(p->sub_socket, p->cfg.proxy_username, 
-                       strlen(p->cfg.proxy_username));
-                   eo += 4;
-               }
-               else if (strnicmp(p->cfg.proxy_telnet_command + eo,
-                                 "pass", 4) == 0) {
-                   sk_write(p->sub_socket, p->cfg.proxy_password, 
-                       strlen(p->cfg.proxy_password));
-                   eo += 4;
-               }
-               else {
-                   /* we don't escape this, so send the % now, and
-                    * don't advance eo, so that we'll consider the
-                    * text immediately following the % as unescaped.
-                    */
-                   sk_write(p->sub_socket, "%", 1);
-               }
+             default:
+               ENSURE(2);
+               memcpy(ret+retlen, cfg->proxy_telnet_command + so, 2);
+               retlen += 2;
+               eo++;
+               break;
            }
+       } else {
 
-           /* resume scanning for additional escapes after this one. */
-           so = eo;
-       }
+           /* % escape. we recognize %%, %host, %port, %user, %pass.
+            * anything else, we just send unescaped (including the %).
+            */
 
-       /* if there is any unescaped text at the end of the line, send it */
-       if (eo != so) {
-           sk_write(p->sub_socket, p->cfg.proxy_telnet_command + so, eo - so);
+           if (cfg->proxy_telnet_command[eo] == '%') {
+               ENSURE(1);
+               ret[retlen++] = '%';
+               eo++;
+           }
+           else if (strnicmp(cfg->proxy_telnet_command + eo,
+                             "host", 4) == 0) {
+               char dest[512];
+               int destlen;
+               sk_getaddr(addr, dest, lenof(dest));
+               destlen = strlen(dest);
+               ENSURE(destlen);
+               memcpy(ret+retlen, dest, destlen);
+               retlen += destlen;
+               eo += 4;
+           }
+           else if (strnicmp(cfg->proxy_telnet_command + eo,
+                             "port", 4) == 0) {
+               char portstr[8], portlen;
+               portlen = sprintf(portstr, "%i", port);
+               ENSURE(portlen);
+               memcpy(ret + retlen, portstr, portlen);
+               retlen += portlen;
+               eo += 4;
+           }
+           else if (strnicmp(cfg->proxy_telnet_command + eo,
+                             "user", 4) == 0) {
+               int userlen = strlen(cfg->proxy_username);
+               ENSURE(userlen);
+               memcpy(ret+retlen, cfg->proxy_username, userlen);
+               retlen += userlen;
+               eo += 4;
+           }
+           else if (strnicmp(cfg->proxy_telnet_command + eo,
+                             "pass", 4) == 0) {
+               int passlen = strlen(cfg->proxy_password);
+               ENSURE(passlen);
+               memcpy(ret+retlen, cfg->proxy_password, passlen);
+               retlen += passlen;
+               eo += 4;
+           }
+           else {
+               /* we don't escape this, so send the % now, and
+                * don't advance eo, so that we'll consider the
+                * text immediately following the % as unescaped.
+                */
+               ENSURE(1);
+               ret[retlen++] = '%';
+           }
        }
 
+       /* resume scanning for additional escapes after this one. */
+       so = eo;
+    }
+
+    /* if there is any unescaped text at the end of the line, send it */
+    if (eo != so) {
+       ENSURE(eo - so);
+       memcpy(ret + retlen, cfg->proxy_telnet_command + so, eo - so);
+       retlen += eo - so;
+    }
+
+    ENSURE(1);
+    ret[retlen] = '\0';
+    return ret;
+
+#undef ENSURE
+}
+
+int proxy_telnet_negotiate (Proxy_Socket p, int change)
+{
+    if (p->state == PROXY_CHANGE_NEW) {
+       char *formatted_cmd;
+
+       formatted_cmd = format_telnet_command(p->remote_addr, p->remote_port,
+                                             &p->cfg);
+
+       sk_write(p->sub_socket, formatted_cmd, strlen(formatted_cmd));
+       sfree(formatted_cmd);
+
        p->state = 1;
        return 0;
     }
diff --git a/proxy.h b/proxy.h
index 2bd5220..d4dec6a 100644 (file)
--- a/proxy.h
+++ b/proxy.h
@@ -100,4 +100,10 @@ extern int proxy_telnet_negotiate (Proxy_Socket, int);
 extern int proxy_socks4_negotiate (Proxy_Socket, int);
 extern int proxy_socks5_negotiate (Proxy_Socket, int);
 
+/*
+ * This may be reused by local-command proxies on individual
+ * platforms.
+ */
+char *format_telnet_command(SockAddr addr, int port, const Config *cfg);
+
 #endif
diff --git a/putty.h b/putty.h
index 93a10c7..1a02dbc 100644 (file)
--- a/putty.h
+++ b/putty.h
@@ -224,7 +224,7 @@ enum {
     /*
      * Proxy types.
      */
-    PROXY_NONE, PROXY_HTTP, PROXY_SOCKS, PROXY_TELNET
+    PROXY_NONE, PROXY_HTTP, PROXY_SOCKS, PROXY_TELNET, PROXY_CMD
 };
 
 enum {
index 9b13348..1bf58b7 100644 (file)
@@ -125,4 +125,48 @@ void unix_setup_config_box(struct controlbox *b, int midsession, void *win)
     ctrl_editbox(s, "Horizontal offset for shadow bold:", 'z', 20,
                 HELPCTX(no_help), dlg_stdeditbox_handler,
                  I(offsetof(Config,shadowboldoffset)), I(-1));
+
+    /*
+     * Unix supports a local-command proxy. This also means we must
+     * adjust the text on the `Telnet command' control.
+     */
+    s = ctrl_getset(b, "Connection/Proxy", "basics", "Proxy basics");
+    {
+       int i;
+       for (i = 0; i < s->ncontrols; i++) {
+           c = s->ctrls[i];
+           if (c->generic.type == CTRL_RADIO &&
+               c->generic.context.i == offsetof(Config, proxy_type)) {
+               assert(c->generic.handler == dlg_stdradiobutton_handler);
+               c->radio.nbuttons++;
+               c->radio.ncolumns++;
+               c->radio.buttons =
+                   sresize(c->radio.buttons, c->radio.nbuttons, char *);
+               c->radio.buttons[c->radio.nbuttons-1] =
+                   dupstr("Local");
+               c->radio.buttondata =
+                   sresize(c->radio.buttondata, c->radio.nbuttons, intorptr);
+               c->radio.buttondata[c->radio.nbuttons-1] = I(PROXY_CMD);
+               break;
+           }
+       }
+    }
+    s = ctrl_getset(b, "Connection/Proxy", "misc",
+                   "Miscellaneous proxy settings");
+    {
+       int i;
+       for (i = 0; i < s->ncontrols; i++) {
+           c = s->ctrls[i];
+           if (c->generic.type == CTRL_EDITBOX &&
+               c->generic.context.i ==
+               offsetof(Config, proxy_telnet_command)) {
+               assert(c->generic.handler == dlg_stdeditbox_handler);
+               sfree(c->generic.label);
+               c->generic.label = dupstr("Telnet command, or local"
+                                         " proxy command");
+               break;
+           }
+       }
+    }
+
 }
diff --git a/unix/uxproxy.c b/unix/uxproxy.c
new file mode 100644 (file)
index 0000000..cd256fd
--- /dev/null
@@ -0,0 +1,303 @@
+/*
+ * uxproxy.c: Unix implementation of platform_new_connection(),
+ * supporting an OpenSSH-like proxy command.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#define DEFINE_PLUG_METHOD_MACROS
+#include "tree234.h"
+#include "putty.h"
+#include "network.h"
+#include "proxy.h"
+
+typedef struct Socket_localproxy_tag * Local_Proxy_Socket;
+
+struct Socket_localproxy_tag {
+    const struct socket_function_table *fn;
+    /* the above variable absolutely *must* be the first in this structure */
+
+    int to_cmd, from_cmd;             /* fds */
+
+    char *error;
+
+    Plug plug;
+
+    bufchain pending_output_data;
+    bufchain pending_input_data;
+
+    void *privptr;
+};
+
+static int localproxy_select_result(int fd, int event);
+
+/*
+ * Trees to look up the pipe fds in.
+ */
+static tree234 *localproxy_by_fromfd, *localproxy_by_tofd;
+static int localproxy_fromfd_cmp(void *av, void *bv)
+{
+    Local_Proxy_Socket a = (Local_Proxy_Socket)av;
+    Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
+    if (a->from_cmd < b->from_cmd)
+       return -1;
+    if (a->from_cmd > b->from_cmd)
+       return +1;
+    return 0;
+}
+static int localproxy_fromfd_find(void *av, void *bv)
+{
+    int a = *(int *)av;
+    Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
+    if (a < b->from_cmd)
+       return -1;
+    if (a > b->from_cmd)
+       return +1;
+    return 0;
+}
+static int localproxy_tofd_cmp(void *av, void *bv)
+{
+    Local_Proxy_Socket a = (Local_Proxy_Socket)av;
+    Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
+    if (a->to_cmd < b->to_cmd)
+       return -1;
+    if (a->to_cmd > b->to_cmd)
+       return +1;
+    return 0;
+}
+static int localproxy_tofd_find(void *av, void *bv)
+{
+    int a = *(int *)av;
+    Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
+    if (a < b->to_cmd)
+       return -1;
+    if (a > b->to_cmd)
+       return +1;
+    return 0;
+}
+
+/* basic proxy socket functions */
+
+static Plug sk_localproxy_plug (Socket s, Plug p)
+{
+    Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
+    Plug ret = ps->plug;
+    if (p)
+       ps->plug = p;
+    return ret;
+}
+
+static void sk_localproxy_close (Socket s)
+{
+    Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
+
+    del234(localproxy_by_fromfd, ps);
+    del234(localproxy_by_tofd, ps);
+
+    close(ps->to_cmd);
+    close(ps->from_cmd);
+
+    sfree(ps);
+}
+
+static int localproxy_try_send(Local_Proxy_Socket ps)
+{
+    int sent = 0;
+
+    while (bufchain_size(&ps->pending_output_data) > 0) {
+       void *data;
+       int len, ret;
+
+       bufchain_prefix(&ps->pending_output_data, &data, &len);
+       ret = write(ps->to_cmd, data, len);
+       if (ret < 0 && errno != EWOULDBLOCK) {
+           /* We're inside the Unix frontend here, so we know
+            * that the frontend handle is unnecessary. */
+           logevent(NULL, strerror(errno));
+           fatalbox("%s", strerror(errno));
+       } else if (ret <= 0) {
+           break;
+       } else {
+           bufchain_consume(&ps->pending_output_data, ret);
+           sent += ret;
+       }
+    }
+
+    if (bufchain_size(&ps->pending_output_data) == 0)
+       uxsel_del(ps->to_cmd);
+    else
+       uxsel_set(ps->to_cmd, 2, localproxy_select_result);
+
+    return sent;
+}
+
+static int sk_localproxy_write (Socket s, const char *data, int len)
+{
+    Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
+
+    bufchain_add(&ps->pending_output_data, data, len);
+
+    localproxy_try_send(ps);
+
+    return bufchain_size(&ps->pending_output_data);
+}
+
+static int sk_localproxy_write_oob (Socket s, const char *data, int len)
+{
+    /*
+     * oob data is treated as inband; nasty, but nothing really
+     * better we can do
+     */
+    return sk_localproxy_write(s, data, len);
+}
+
+static void sk_localproxy_flush (Socket s)
+{
+    /* Local_Proxy_Socket ps = (Local_Proxy_Socket) s; */
+    /* do nothing */
+}
+
+static void sk_localproxy_set_private_ptr (Socket s, void *ptr)
+{
+    Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
+    ps->privptr = ptr;
+}
+
+static void * sk_localproxy_get_private_ptr (Socket s)
+{
+    Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
+    return ps->privptr;
+}
+
+static void sk_localproxy_set_frozen (Socket s, int is_frozen)
+{
+    Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
+
+    if (is_frozen)
+       uxsel_del(ps->from_cmd);
+    else
+       uxsel_set(ps->from_cmd, 1, localproxy_select_result);
+}
+
+static const char * sk_localproxy_socket_error (Socket s)
+{
+    Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
+    return ps->error;
+}
+
+static int localproxy_select_result(int fd, int event)
+{
+    Local_Proxy_Socket s;
+    char buf[20480];
+    int ret;
+
+    if (!(s = find234(localproxy_by_fromfd, &fd, localproxy_fromfd_find)) &&
+       !(s = find234(localproxy_by_tofd, &fd, localproxy_tofd_find)) )
+       return 1;                      /* boggle */
+
+    if (event == 1) {
+       assert(fd == s->from_cmd);
+       ret = read(fd, buf, sizeof(buf));
+       if (ret < 0) {
+           return plug_closing(s->plug, strerror(errno), errno, 0);
+       } else if (ret == 0) {
+           return plug_closing(s->plug, NULL, 0, 0);
+       } else {
+           return plug_receive(s->plug, 1, buf, ret);
+       }
+    } else if (event == 2) {
+       assert(fd == s->to_cmd);
+       if (localproxy_try_send(s))
+           plug_sent(s->plug, bufchain_size(&s->pending_output_data));
+       return 1;
+    }
+
+    return 1;
+}
+
+Socket platform_new_connection(SockAddr addr, char *hostname,
+                              int port, int privport,
+                              int oobinline, int nodelay, Plug plug,
+                              const Config *cfg)
+{
+    char *cmd;
+
+    static const struct socket_function_table socket_fn_table = {
+       sk_localproxy_plug,
+       sk_localproxy_close,
+       sk_localproxy_write,
+       sk_localproxy_write_oob,
+       sk_localproxy_flush,
+       sk_localproxy_set_private_ptr,
+       sk_localproxy_get_private_ptr,
+       sk_localproxy_set_frozen,
+       sk_localproxy_socket_error
+    };
+
+    Local_Proxy_Socket ret;
+    int to_cmd_pipe[2], from_cmd_pipe[2], pid;
+
+    if (cfg->proxy_type != PROXY_CMD)
+       return NULL;
+
+    cmd = format_telnet_command(addr, port, cfg);
+
+    ret = snew(struct Socket_localproxy_tag);
+    ret->fn = &socket_fn_table;
+    ret->plug = plug;
+    ret->error = NULL;
+
+    bufchain_init(&ret->pending_input_data);
+    bufchain_init(&ret->pending_output_data);
+
+    /*
+     * Create the pipes to the proxy command, and spawn the proxy
+     * command process.
+     */
+    if (pipe(to_cmd_pipe) < 0 ||
+       pipe(from_cmd_pipe) < 0) {
+       ret->error = dupprintf("pipe: %s", strerror(errno));
+       return (Socket)ret;
+    }
+
+    pid = fork();
+
+    if (pid < 0) {
+       ret->error = dupprintf("fork: %s", strerror(errno));
+       return (Socket)ret;
+    } else if (pid == 0) {
+       int i;
+       close(0);
+       close(1);
+       dup2(to_cmd_pipe[0], 0);
+       dup2(from_cmd_pipe[1], 1);
+       for (i = 3; i < 127; i++)
+           close(i);
+       fcntl(0, F_SETFD, 0);
+       fcntl(1, F_SETFD, 0);
+       execl("/bin/sh", "sh", "-c", cmd, NULL);
+       _exit(255);
+    }
+
+    close(to_cmd_pipe[0]);
+    close(from_cmd_pipe[1]);
+
+    ret->to_cmd = to_cmd_pipe[1];
+    ret->from_cmd = from_cmd_pipe[0];
+
+    if (!localproxy_by_fromfd)
+       localproxy_by_fromfd = newtree234(localproxy_fromfd_cmp);
+    if (!localproxy_by_tofd)
+       localproxy_by_tofd = newtree234(localproxy_tofd_cmp);
+
+    add234(localproxy_by_fromfd, ret);
+    add234(localproxy_by_tofd, ret);
+
+    uxsel_set(ret->from_cmd, 1, localproxy_select_result);
+
+    return (Socket) ret;
+}