]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/uxproxy.c
uxproxy: fix write error handling on outgoing pipe.
[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             plug_closing(ps->plug, strerror(errno), errno, 0);
153             return 0;
154         } else if (ret <= 0) {
155             break;
156         } else {
157             bufchain_consume(&ps->pending_output_data, ret);
158             sent += ret;
159         }
160     }
161
162     if (ps->outgoingeof == EOF_PENDING) {
163         del234(localproxy_by_tofd, ps);
164         close(ps->to_cmd);
165         uxsel_del(ps->to_cmd);
166         ps->to_cmd = -1;
167         ps->outgoingeof = EOF_SENT;
168     }
169
170     if (bufchain_size(&ps->pending_output_data) == 0)
171         uxsel_del(ps->to_cmd);
172     else
173         uxsel_set(ps->to_cmd, 2, localproxy_select_result);
174
175     return sent;
176 }
177
178 static int sk_localproxy_write (Socket s, const char *data, int len)
179 {
180     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
181
182     assert(ps->outgoingeof == EOF_NO);
183
184     bufchain_add(&ps->pending_output_data, data, len);
185
186     localproxy_try_send(ps);
187
188     return bufchain_size(&ps->pending_output_data);
189 }
190
191 static int sk_localproxy_write_oob (Socket s, const char *data, int len)
192 {
193     /*
194      * oob data is treated as inband; nasty, but nothing really
195      * better we can do
196      */
197     return sk_localproxy_write(s, data, len);
198 }
199
200 static void sk_localproxy_write_eof (Socket s)
201 {
202     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
203
204     assert(ps->outgoingeof == EOF_NO);
205     ps->outgoingeof = EOF_PENDING;
206
207     localproxy_try_send(ps);
208 }
209
210 static void sk_localproxy_flush (Socket s)
211 {
212     /* Local_Proxy_Socket ps = (Local_Proxy_Socket) s; */
213     /* do nothing */
214 }
215
216 static void sk_localproxy_set_frozen (Socket s, int is_frozen)
217 {
218     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
219
220     if (is_frozen)
221         uxsel_del(ps->from_cmd);
222     else
223         uxsel_set(ps->from_cmd, 1, localproxy_select_result);
224 }
225
226 static const char * sk_localproxy_socket_error (Socket s)
227 {
228     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
229     return ps->error;
230 }
231
232 static int localproxy_select_result(int fd, int event)
233 {
234     Local_Proxy_Socket s;
235     char buf[20480];
236     int ret;
237
238     if (!(s = find234(localproxy_by_fromfd, &fd, localproxy_fromfd_find)) &&
239         !(s = find234(localproxy_by_fromfd, &fd, localproxy_errfd_find)) &&
240         !(s = find234(localproxy_by_tofd, &fd, localproxy_tofd_find)) )
241         return 1;                      /* boggle */
242
243     if (event == 1) {
244         if (fd == s->cmd_err) {
245             ret = read(fd, buf, sizeof(buf));
246             if (ret > 0)
247                 log_proxy_stderr(s->plug, &s->pending_error_data, buf, ret);
248         } else {
249             assert(fd == s->from_cmd);
250             ret = read(fd, buf, sizeof(buf));
251             if (ret < 0) {
252                 return plug_closing(s->plug, strerror(errno), errno, 0);
253             } else if (ret == 0) {
254                 return plug_closing(s->plug, NULL, 0, 0);
255             } else {
256                 return plug_receive(s->plug, 0, buf, ret);
257             }
258         }
259     } else if (event == 2) {
260         assert(fd == s->to_cmd);
261         if (localproxy_try_send(s))
262             plug_sent(s->plug, bufchain_size(&s->pending_output_data));
263         return 1;
264     }
265
266     return 1;
267 }
268
269 Socket platform_new_connection(SockAddr addr, const char *hostname,
270                                int port, int privport,
271                                int oobinline, int nodelay, int keepalive,
272                                Plug plug, Conf *conf)
273 {
274     char *cmd;
275
276     static const struct socket_function_table socket_fn_table = {
277         sk_localproxy_plug,
278         sk_localproxy_close,
279         sk_localproxy_write,
280         sk_localproxy_write_oob,
281         sk_localproxy_write_eof,
282         sk_localproxy_flush,
283         sk_localproxy_set_frozen,
284         sk_localproxy_socket_error,
285         NULL, /* peer_info */
286     };
287
288     Local_Proxy_Socket ret;
289     int to_cmd_pipe[2], from_cmd_pipe[2], cmd_err_pipe[2], pid, proxytype;
290
291     proxytype = conf_get_int(conf, CONF_proxy_type);
292     if (proxytype != PROXY_CMD && proxytype != PROXY_FUZZ)
293         return NULL;
294
295     ret = snew(struct Socket_localproxy_tag);
296     ret->fn = &socket_fn_table;
297     ret->plug = plug;
298     ret->error = NULL;
299     ret->outgoingeof = EOF_NO;
300
301     bufchain_init(&ret->pending_input_data);
302     bufchain_init(&ret->pending_output_data);
303     bufchain_init(&ret->pending_error_data);
304
305     if (proxytype == PROXY_CMD) {
306         cmd = format_telnet_command(addr, port, conf);
307
308         if (flags & FLAG_STDERR) {
309             /* If we have a sensible stderr, the proxy command can
310              * send its own standard error there, so we won't
311              * interfere. */
312             cmd_err_pipe[0] = cmd_err_pipe[1] = -1;
313         } else {
314             /* If we don't have a sensible stderr, we should catch the
315              * proxy command's standard error to put in our event
316              * log. */
317             cmd_err_pipe[0] = cmd_err_pipe[1] = 0;
318         }
319
320         {
321             char *logmsg = dupprintf("Starting local proxy command: %s", cmd);
322             plug_log(plug, 2, NULL, 0, logmsg, 0);
323             sfree(logmsg);
324         }
325
326         /*
327          * Create the pipes to the proxy command, and spawn the proxy
328          * command process.
329          */
330         if (pipe(to_cmd_pipe) < 0 ||
331             pipe(from_cmd_pipe) < 0 ||
332             (cmd_err_pipe[0] == 0 && pipe(cmd_err_pipe) < 0)) {
333             ret->error = dupprintf("pipe: %s", strerror(errno));
334             sfree(cmd);
335             return (Socket)ret;
336         }
337         cloexec(to_cmd_pipe[1]);
338         cloexec(from_cmd_pipe[0]);
339         if (cmd_err_pipe[0] >= 0)
340             cloexec(cmd_err_pipe[0]);
341
342         pid = fork();
343
344         if (pid < 0) {
345             ret->error = dupprintf("fork: %s", strerror(errno));
346             sfree(cmd);
347             return (Socket)ret;
348         } else if (pid == 0) {
349             close(0);
350             close(1);
351             dup2(to_cmd_pipe[0], 0);
352             dup2(from_cmd_pipe[1], 1);
353             close(to_cmd_pipe[0]);
354             close(from_cmd_pipe[1]);
355             if (cmd_err_pipe[0] >= 0) {
356                 dup2(cmd_err_pipe[1], 2);
357                 close(cmd_err_pipe[1]);
358             }
359             noncloexec(0);
360             noncloexec(1);
361             execl("/bin/sh", "sh", "-c", cmd, (void *)NULL);
362             _exit(255);
363         }
364
365         sfree(cmd);
366
367         close(to_cmd_pipe[0]);
368         close(from_cmd_pipe[1]);
369         if (cmd_err_pipe[0] >= 0)
370             close(cmd_err_pipe[1]);
371
372         ret->to_cmd = to_cmd_pipe[1];
373         ret->from_cmd = from_cmd_pipe[0];
374         ret->cmd_err = cmd_err_pipe[0];
375     } else {
376         cmd = format_telnet_command(addr, port, conf);
377         ret->to_cmd = open("/dev/null", O_WRONLY);
378         if (ret->to_cmd == -1) {
379             ret->error = dupprintf("/dev/null: %s", strerror(errno));
380             sfree(cmd);
381             return (Socket)ret;
382         }
383         ret->from_cmd = open(cmd, O_RDONLY);
384         if (ret->from_cmd == -1) {
385             ret->error = dupprintf("%s: %s", cmd, strerror(errno));
386             sfree(cmd);
387             return (Socket)ret;
388         }
389         sfree(cmd);
390         ret->cmd_err = -1;
391     }
392
393     if (!localproxy_by_fromfd)
394         localproxy_by_fromfd = newtree234(localproxy_fromfd_cmp);
395     if (!localproxy_by_tofd)
396         localproxy_by_tofd = newtree234(localproxy_tofd_cmp);
397     if (!localproxy_by_errfd)
398         localproxy_by_errfd = newtree234(localproxy_errfd_cmp);
399
400     add234(localproxy_by_fromfd, ret);
401     add234(localproxy_by_tofd, ret);
402     if (ret->cmd_err >= 0)
403         add234(localproxy_by_errfd, ret);
404
405     uxsel_set(ret->from_cmd, 1, localproxy_select_result);
406     if (ret->cmd_err >= 0)
407         uxsel_set(ret->cmd_err, 1, localproxy_select_result);
408
409     /* We are responsible for this and don't need it any more */
410     sk_addr_free(addr);
411
412     return (Socket) ret;
413 }