]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/uxproxy.c
f92c459be284ee341a35fda4c3e61b14690b75ec
[PuTTY.git] / unix / uxproxy.c
1 /*
2  * uxproxy.c: Unix implementation of platform_new_connection(),
3  * supporting an OpenSSH-like proxy command.
4  */
5
6 #include <stdio.h>
7 #include <assert.h>
8 #include <errno.h>
9 #include <unistd.h>
10 #include <fcntl.h>
11
12 #define DEFINE_PLUG_METHOD_MACROS
13 #include "tree234.h"
14 #include "putty.h"
15 #include "network.h"
16 #include "proxy.h"
17
18 typedef struct Socket_localproxy_tag * Local_Proxy_Socket;
19
20 struct Socket_localproxy_tag {
21     const struct socket_function_table *fn;
22     /* the above variable absolutely *must* be the first in this structure */
23
24     int to_cmd, from_cmd, cmd_err;     /* fds */
25
26     char *error;
27
28     Plug plug;
29
30     bufchain pending_output_data;
31     bufchain pending_input_data;
32     bufchain pending_error_data;
33     enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
34 };
35
36 static int localproxy_select_result(int fd, int event);
37
38 /*
39  * Trees to look up the pipe fds in.
40  */
41 static tree234 *localproxy_by_fromfd;
42 static tree234 *localproxy_by_tofd;
43 static tree234 *localproxy_by_errfd;
44 static int localproxy_fromfd_cmp(void *av, void *bv)
45 {
46     Local_Proxy_Socket a = (Local_Proxy_Socket)av;
47     Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
48     if (a->from_cmd < b->from_cmd)
49         return -1;
50     if (a->from_cmd > b->from_cmd)
51         return +1;
52     return 0;
53 }
54 static int localproxy_fromfd_find(void *av, void *bv)
55 {
56     int a = *(int *)av;
57     Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
58     if (a < b->from_cmd)
59         return -1;
60     if (a > b->from_cmd)
61         return +1;
62     return 0;
63 }
64 static int localproxy_tofd_cmp(void *av, void *bv)
65 {
66     Local_Proxy_Socket a = (Local_Proxy_Socket)av;
67     Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
68     if (a->to_cmd < b->to_cmd)
69         return -1;
70     if (a->to_cmd > b->to_cmd)
71         return +1;
72     return 0;
73 }
74 static int localproxy_tofd_find(void *av, void *bv)
75 {
76     int a = *(int *)av;
77     Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
78     if (a < b->to_cmd)
79         return -1;
80     if (a > b->to_cmd)
81         return +1;
82     return 0;
83 }
84 static int localproxy_errfd_cmp(void *av, void *bv)
85 {
86     Local_Proxy_Socket a = (Local_Proxy_Socket)av;
87     Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
88     if (a->cmd_err < b->cmd_err)
89         return -1;
90     if (a->cmd_err > b->cmd_err)
91         return +1;
92     return 0;
93 }
94 static int localproxy_errfd_find(void *av, void *bv)
95 {
96     int a = *(int *)av;
97     Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
98     if (a < b->cmd_err)
99         return -1;
100     if (a > b->cmd_err)
101         return +1;
102     return 0;
103 }
104
105 /* basic proxy socket functions */
106
107 static Plug sk_localproxy_plug (Socket s, Plug p)
108 {
109     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
110     Plug ret = ps->plug;
111     if (p)
112         ps->plug = p;
113     return ret;
114 }
115
116 static void sk_localproxy_close (Socket s)
117 {
118     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
119
120     if (ps->to_cmd >= 0) {
121         del234(localproxy_by_tofd, ps);
122         uxsel_del(ps->to_cmd);
123         close(ps->to_cmd);
124     }
125
126     del234(localproxy_by_fromfd, ps);
127     uxsel_del(ps->from_cmd);
128     close(ps->from_cmd);
129
130     del234(localproxy_by_errfd, ps);
131     uxsel_del(ps->cmd_err);
132     close(ps->cmd_err);
133
134     bufchain_clear(&ps->pending_input_data);
135     bufchain_clear(&ps->pending_output_data);
136     bufchain_clear(&ps->pending_error_data);
137
138     sfree(ps);
139 }
140
141 static int localproxy_try_send(Local_Proxy_Socket ps)
142 {
143     int sent = 0;
144
145     while (bufchain_size(&ps->pending_output_data) > 0) {
146         void *data;
147         int len, ret;
148
149         bufchain_prefix(&ps->pending_output_data, &data, &len);
150         ret = write(ps->to_cmd, data, len);
151         if (ret < 0 && errno != EWOULDBLOCK) {
152             /* We're inside the Unix frontend here, so we know
153              * that the frontend handle is unnecessary. */
154             logevent(NULL, strerror(errno));
155             fatalbox("%s", strerror(errno));
156         } else if (ret <= 0) {
157             break;
158         } else {
159             bufchain_consume(&ps->pending_output_data, ret);
160             sent += ret;
161         }
162     }
163
164     if (ps->outgoingeof == EOF_PENDING) {
165         del234(localproxy_by_tofd, ps);
166         close(ps->to_cmd);
167         uxsel_del(ps->to_cmd);
168         ps->to_cmd = -1;
169         ps->outgoingeof = EOF_SENT;
170     }
171
172     if (bufchain_size(&ps->pending_output_data) == 0)
173         uxsel_del(ps->to_cmd);
174     else
175         uxsel_set(ps->to_cmd, 2, localproxy_select_result);
176
177     return sent;
178 }
179
180 static int sk_localproxy_write (Socket s, const char *data, int len)
181 {
182     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
183
184     assert(ps->outgoingeof == EOF_NO);
185
186     bufchain_add(&ps->pending_output_data, data, len);
187
188     localproxy_try_send(ps);
189
190     return bufchain_size(&ps->pending_output_data);
191 }
192
193 static int sk_localproxy_write_oob (Socket s, const char *data, int len)
194 {
195     /*
196      * oob data is treated as inband; nasty, but nothing really
197      * better we can do
198      */
199     return sk_localproxy_write(s, data, len);
200 }
201
202 static void sk_localproxy_write_eof (Socket s)
203 {
204     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
205
206     assert(ps->outgoingeof == EOF_NO);
207     ps->outgoingeof = EOF_PENDING;
208
209     localproxy_try_send(ps);
210 }
211
212 static void sk_localproxy_flush (Socket s)
213 {
214     /* Local_Proxy_Socket ps = (Local_Proxy_Socket) s; */
215     /* do nothing */
216 }
217
218 static void sk_localproxy_set_frozen (Socket s, int is_frozen)
219 {
220     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
221
222     if (is_frozen)
223         uxsel_del(ps->from_cmd);
224     else
225         uxsel_set(ps->from_cmd, 1, localproxy_select_result);
226 }
227
228 static const char * sk_localproxy_socket_error (Socket s)
229 {
230     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
231     return ps->error;
232 }
233
234 static int localproxy_select_result(int fd, int event)
235 {
236     Local_Proxy_Socket s;
237     char buf[20480];
238     int ret;
239
240     if (!(s = find234(localproxy_by_fromfd, &fd, localproxy_fromfd_find)) &&
241         !(s = find234(localproxy_by_fromfd, &fd, localproxy_errfd_find)) &&
242         !(s = find234(localproxy_by_tofd, &fd, localproxy_tofd_find)) )
243         return 1;                      /* boggle */
244
245     if (event == 1) {
246         if (fd == s->cmd_err) {
247             ret = read(fd, buf, sizeof(buf));
248             if (ret > 0)
249                 log_proxy_stderr(s->plug, &s->pending_error_data, buf, ret);
250         } else {
251             assert(fd == s->from_cmd);
252             ret = read(fd, buf, sizeof(buf));
253             if (ret < 0) {
254                 return plug_closing(s->plug, strerror(errno), errno, 0);
255             } else if (ret == 0) {
256                 return plug_closing(s->plug, NULL, 0, 0);
257             } else {
258                 return plug_receive(s->plug, 0, buf, ret);
259             }
260         }
261     } else if (event == 2) {
262         assert(fd == s->to_cmd);
263         if (localproxy_try_send(s))
264             plug_sent(s->plug, bufchain_size(&s->pending_output_data));
265         return 1;
266     }
267
268     return 1;
269 }
270
271 Socket platform_new_connection(SockAddr addr, const char *hostname,
272                                int port, int privport,
273                                int oobinline, int nodelay, int keepalive,
274                                Plug plug, Conf *conf)
275 {
276     char *cmd;
277
278     static const struct socket_function_table socket_fn_table = {
279         sk_localproxy_plug,
280         sk_localproxy_close,
281         sk_localproxy_write,
282         sk_localproxy_write_oob,
283         sk_localproxy_write_eof,
284         sk_localproxy_flush,
285         sk_localproxy_set_frozen,
286         sk_localproxy_socket_error,
287         NULL, /* peer_info */
288     };
289
290     Local_Proxy_Socket ret;
291     int to_cmd_pipe[2], from_cmd_pipe[2], cmd_err_pipe[2], pid, proxytype;
292
293     proxytype = conf_get_int(conf, CONF_proxy_type);
294     if (proxytype != PROXY_CMD && proxytype != PROXY_FUZZ)
295         return NULL;
296
297     ret = snew(struct Socket_localproxy_tag);
298     ret->fn = &socket_fn_table;
299     ret->plug = plug;
300     ret->error = NULL;
301     ret->outgoingeof = EOF_NO;
302
303     bufchain_init(&ret->pending_input_data);
304     bufchain_init(&ret->pending_output_data);
305     bufchain_init(&ret->pending_error_data);
306
307     if (proxytype == PROXY_CMD) {
308         cmd = format_telnet_command(addr, port, conf);
309
310         if (flags & FLAG_STDERR) {
311             /* If we have a sensible stderr, the proxy command can
312              * send its own standard error there, so we won't
313              * interfere. */
314             cmd_err_pipe[0] = cmd_err_pipe[1] = -1;
315         } else {
316             /* If we don't have a sensible stderr, we should catch the
317              * proxy command's standard error to put in our event
318              * log. */
319             cmd_err_pipe[0] = cmd_err_pipe[1] = 0;
320         }
321
322         {
323             char *logmsg = dupprintf("Starting local proxy command: %s", cmd);
324             plug_log(plug, 2, NULL, 0, logmsg, 0);
325             sfree(logmsg);
326         }
327
328         /*
329          * Create the pipes to the proxy command, and spawn the proxy
330          * command process.
331          */
332         if (pipe(to_cmd_pipe) < 0 ||
333             pipe(from_cmd_pipe) < 0 ||
334             (cmd_err_pipe[0] == 0 && pipe(cmd_err_pipe) < 0)) {
335             ret->error = dupprintf("pipe: %s", strerror(errno));
336             sfree(cmd);
337             return (Socket)ret;
338         }
339         cloexec(to_cmd_pipe[1]);
340         cloexec(from_cmd_pipe[0]);
341         if (cmd_err_pipe[0] >= 0)
342             cloexec(cmd_err_pipe[0]);
343
344         pid = fork();
345
346         if (pid < 0) {
347             ret->error = dupprintf("fork: %s", strerror(errno));
348             sfree(cmd);
349             return (Socket)ret;
350         } else if (pid == 0) {
351             close(0);
352             close(1);
353             dup2(to_cmd_pipe[0], 0);
354             dup2(from_cmd_pipe[1], 1);
355             close(to_cmd_pipe[0]);
356             close(from_cmd_pipe[1]);
357             if (cmd_err_pipe[0] >= 0) {
358                 dup2(cmd_err_pipe[1], 2);
359                 close(cmd_err_pipe[1]);
360             }
361             noncloexec(0);
362             noncloexec(1);
363             execl("/bin/sh", "sh", "-c", cmd, (void *)NULL);
364             _exit(255);
365         }
366
367         sfree(cmd);
368
369         close(to_cmd_pipe[0]);
370         close(from_cmd_pipe[1]);
371         if (cmd_err_pipe[0] >= 0)
372             close(cmd_err_pipe[1]);
373
374         ret->to_cmd = to_cmd_pipe[1];
375         ret->from_cmd = from_cmd_pipe[0];
376         ret->cmd_err = cmd_err_pipe[0];
377     } else {
378         cmd = format_telnet_command(addr, port, conf);
379         ret->to_cmd = open("/dev/null", O_WRONLY);
380         if (ret->to_cmd == -1) {
381             ret->error = dupprintf("/dev/null: %s", strerror(errno));
382             sfree(cmd);
383             return (Socket)ret;
384         }
385         ret->from_cmd = open(cmd, O_RDONLY);
386         if (ret->from_cmd == -1) {
387             ret->error = dupprintf("%s: %s", cmd, strerror(errno));
388             sfree(cmd);
389             return (Socket)ret;
390         }
391         sfree(cmd);
392         ret->cmd_err = -1;
393     }
394
395     if (!localproxy_by_fromfd)
396         localproxy_by_fromfd = newtree234(localproxy_fromfd_cmp);
397     if (!localproxy_by_tofd)
398         localproxy_by_tofd = newtree234(localproxy_tofd_cmp);
399     if (!localproxy_by_errfd)
400         localproxy_by_errfd = newtree234(localproxy_errfd_cmp);
401
402     add234(localproxy_by_fromfd, ret);
403     add234(localproxy_by_tofd, ret);
404     if (ret->cmd_err >= 0)
405         add234(localproxy_by_errfd, ret);
406
407     uxsel_set(ret->from_cmd, 1, localproxy_select_result);
408     if (ret->cmd_err >= 0)
409         uxsel_set(ret->cmd_err, 1, localproxy_select_result);
410
411     /* We are responsible for this and don't need it any more */
412     sk_addr_free(addr);
413
414     return (Socket) ret;
415 }