# 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
"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,
--- /dev/null
+/*
+ * 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;
+}
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,
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;
* 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;
}
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;
}
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
/*
* Proxy types.
*/
- PROXY_NONE, PROXY_HTTP, PROXY_SOCKS, PROXY_TELNET
+ PROXY_NONE, PROXY_HTTP, PROXY_SOCKS, PROXY_TELNET, PROXY_CMD
};
enum {
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;
+ }
+ }
+ }
+
}
--- /dev/null
+/*
+ * 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;
+}