]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/uxproxy.c
Fix a memory leak in uxproxy.c.
[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;              /* fds */
25
26     char *error;
27
28     Plug plug;
29
30     bufchain pending_output_data;
31     bufchain pending_input_data;
32     enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
33 };
34
35 static int localproxy_select_result(int fd, int event);
36
37 /*
38  * Trees to look up the pipe fds in.
39  */
40 static tree234 *localproxy_by_fromfd, *localproxy_by_tofd;
41 static int localproxy_fromfd_cmp(void *av, void *bv)
42 {
43     Local_Proxy_Socket a = (Local_Proxy_Socket)av;
44     Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
45     if (a->from_cmd < b->from_cmd)
46         return -1;
47     if (a->from_cmd > b->from_cmd)
48         return +1;
49     return 0;
50 }
51 static int localproxy_fromfd_find(void *av, void *bv)
52 {
53     int a = *(int *)av;
54     Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
55     if (a < b->from_cmd)
56         return -1;
57     if (a > b->from_cmd)
58         return +1;
59     return 0;
60 }
61 static int localproxy_tofd_cmp(void *av, void *bv)
62 {
63     Local_Proxy_Socket a = (Local_Proxy_Socket)av;
64     Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
65     if (a->to_cmd < b->to_cmd)
66         return -1;
67     if (a->to_cmd > b->to_cmd)
68         return +1;
69     return 0;
70 }
71 static int localproxy_tofd_find(void *av, void *bv)
72 {
73     int a = *(int *)av;
74     Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
75     if (a < b->to_cmd)
76         return -1;
77     if (a > b->to_cmd)
78         return +1;
79     return 0;
80 }
81
82 /* basic proxy socket functions */
83
84 static Plug sk_localproxy_plug (Socket s, Plug p)
85 {
86     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
87     Plug ret = ps->plug;
88     if (p)
89         ps->plug = p;
90     return ret;
91 }
92
93 static void sk_localproxy_close (Socket s)
94 {
95     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
96
97     if (ps->to_cmd >= 0) {
98         del234(localproxy_by_tofd, ps);
99         uxsel_del(ps->to_cmd);
100         close(ps->to_cmd);
101     }
102
103     del234(localproxy_by_fromfd, ps);
104     uxsel_del(ps->from_cmd);
105     close(ps->from_cmd);
106
107     bufchain_clear(&ps->pending_input_data);
108     bufchain_clear(&ps->pending_output_data);
109     sfree(ps);
110 }
111
112 static int localproxy_try_send(Local_Proxy_Socket ps)
113 {
114     int sent = 0;
115
116     while (bufchain_size(&ps->pending_output_data) > 0) {
117         void *data;
118         int len, ret;
119
120         bufchain_prefix(&ps->pending_output_data, &data, &len);
121         ret = write(ps->to_cmd, data, len);
122         if (ret < 0 && errno != EWOULDBLOCK) {
123             /* We're inside the Unix frontend here, so we know
124              * that the frontend handle is unnecessary. */
125             logevent(NULL, strerror(errno));
126             fatalbox("%s", strerror(errno));
127         } else if (ret <= 0) {
128             break;
129         } else {
130             bufchain_consume(&ps->pending_output_data, ret);
131             sent += ret;
132         }
133     }
134
135     if (ps->outgoingeof == EOF_PENDING) {
136         del234(localproxy_by_tofd, ps);
137         close(ps->to_cmd);
138         uxsel_del(ps->to_cmd);
139         ps->to_cmd = -1;
140         ps->outgoingeof = EOF_SENT;
141     }
142
143     if (bufchain_size(&ps->pending_output_data) == 0)
144         uxsel_del(ps->to_cmd);
145     else
146         uxsel_set(ps->to_cmd, 2, localproxy_select_result);
147
148     return sent;
149 }
150
151 static int sk_localproxy_write (Socket s, const char *data, int len)
152 {
153     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
154
155     assert(ps->outgoingeof == EOF_NO);
156
157     bufchain_add(&ps->pending_output_data, data, len);
158
159     localproxy_try_send(ps);
160
161     return bufchain_size(&ps->pending_output_data);
162 }
163
164 static int sk_localproxy_write_oob (Socket s, const char *data, int len)
165 {
166     /*
167      * oob data is treated as inband; nasty, but nothing really
168      * better we can do
169      */
170     return sk_localproxy_write(s, data, len);
171 }
172
173 static void sk_localproxy_write_eof (Socket s)
174 {
175     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
176
177     assert(ps->outgoingeof == EOF_NO);
178     ps->outgoingeof = EOF_PENDING;
179
180     localproxy_try_send(ps);
181 }
182
183 static void sk_localproxy_flush (Socket s)
184 {
185     /* Local_Proxy_Socket ps = (Local_Proxy_Socket) s; */
186     /* do nothing */
187 }
188
189 static void sk_localproxy_set_frozen (Socket s, int is_frozen)
190 {
191     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
192
193     if (is_frozen)
194         uxsel_del(ps->from_cmd);
195     else
196         uxsel_set(ps->from_cmd, 1, localproxy_select_result);
197 }
198
199 static const char * sk_localproxy_socket_error (Socket s)
200 {
201     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
202     return ps->error;
203 }
204
205 static int localproxy_select_result(int fd, int event)
206 {
207     Local_Proxy_Socket s;
208     char buf[20480];
209     int ret;
210
211     if (!(s = find234(localproxy_by_fromfd, &fd, localproxy_fromfd_find)) &&
212         !(s = find234(localproxy_by_tofd, &fd, localproxy_tofd_find)) )
213         return 1;                      /* boggle */
214
215     if (event == 1) {
216         assert(fd == s->from_cmd);
217         ret = read(fd, buf, sizeof(buf));
218         if (ret < 0) {
219             return plug_closing(s->plug, strerror(errno), errno, 0);
220         } else if (ret == 0) {
221             return plug_closing(s->plug, NULL, 0, 0);
222         } else {
223             return plug_receive(s->plug, 0, buf, ret);
224         }
225     } else if (event == 2) {
226         assert(fd == s->to_cmd);
227         if (localproxy_try_send(s))
228             plug_sent(s->plug, bufchain_size(&s->pending_output_data));
229         return 1;
230     }
231
232     return 1;
233 }
234
235 Socket platform_new_connection(SockAddr addr, const char *hostname,
236                                int port, int privport,
237                                int oobinline, int nodelay, int keepalive,
238                                Plug plug, Conf *conf)
239 {
240     char *cmd;
241
242     static const struct socket_function_table socket_fn_table = {
243         sk_localproxy_plug,
244         sk_localproxy_close,
245         sk_localproxy_write,
246         sk_localproxy_write_oob,
247         sk_localproxy_write_eof,
248         sk_localproxy_flush,
249         sk_localproxy_set_frozen,
250         sk_localproxy_socket_error,
251         NULL, /* peer_info */
252     };
253
254     Local_Proxy_Socket ret;
255     int to_cmd_pipe[2], from_cmd_pipe[2], pid, proxytype;
256
257     proxytype = conf_get_int(conf, CONF_proxy_type);
258     if (proxytype != PROXY_CMD && proxytype != PROXY_FUZZ)
259         return NULL;
260
261     ret = snew(struct Socket_localproxy_tag);
262     ret->fn = &socket_fn_table;
263     ret->plug = plug;
264     ret->error = NULL;
265     ret->outgoingeof = EOF_NO;
266
267     bufchain_init(&ret->pending_input_data);
268     bufchain_init(&ret->pending_output_data);
269
270     if (proxytype == PROXY_CMD) {
271         cmd = format_telnet_command(addr, port, conf);
272
273         /*
274          * Create the pipes to the proxy command, and spawn the proxy
275          * command process.
276          */
277         if (pipe(to_cmd_pipe) < 0 ||
278             pipe(from_cmd_pipe) < 0) {
279             ret->error = dupprintf("pipe: %s", strerror(errno));
280             sfree(cmd);
281             return (Socket)ret;
282         }
283         cloexec(to_cmd_pipe[1]);
284         cloexec(from_cmd_pipe[0]);
285
286         pid = fork();
287
288         if (pid < 0) {
289             ret->error = dupprintf("fork: %s", strerror(errno));
290             sfree(cmd);
291             return (Socket)ret;
292         } else if (pid == 0) {
293             close(0);
294             close(1);
295             dup2(to_cmd_pipe[0], 0);
296             dup2(from_cmd_pipe[1], 1);
297             close(to_cmd_pipe[0]);
298             close(from_cmd_pipe[1]);
299             noncloexec(0);
300             noncloexec(1);
301             execl("/bin/sh", "sh", "-c", cmd, (void *)NULL);
302             _exit(255);
303         }
304
305         sfree(cmd);
306
307         close(to_cmd_pipe[0]);
308         close(from_cmd_pipe[1]);
309
310         ret->to_cmd = to_cmd_pipe[1];
311         ret->from_cmd = from_cmd_pipe[0];
312     } else {
313         cmd = format_telnet_command(addr, port, conf);
314         ret->to_cmd = open("/dev/null", O_WRONLY);
315         if (ret->to_cmd == -1) {
316             ret->error = dupprintf("/dev/null: %s", strerror(errno));
317             sfree(cmd);
318             return (Socket)ret;
319         }
320         ret->from_cmd = open(cmd, O_RDONLY);
321         if (ret->from_cmd == -1) {
322             ret->error = dupprintf("%s: %s", cmd, strerror(errno));
323             sfree(cmd);
324             return (Socket)ret;
325         }
326         sfree(cmd);
327     }
328
329     if (!localproxy_by_fromfd)
330         localproxy_by_fromfd = newtree234(localproxy_fromfd_cmp);
331     if (!localproxy_by_tofd)
332         localproxy_by_tofd = newtree234(localproxy_tofd_cmp);
333
334     add234(localproxy_by_fromfd, ret);
335     add234(localproxy_by_tofd, ret);
336
337     uxsel_set(ret->from_cmd, 1, localproxy_select_result);
338
339     /* We are responsible for this and don't need it any more */
340     sk_addr_free(addr);
341
342     return (Socket) ret;
343 }