]> asedeno.scripts.mit.edu Git - PuTTY_svn.git/blob - unix/uxproxy.c
f808a67796c66cf3f6c37e99fdd241b788eac582
[PuTTY_svn.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     void *privptr;
35 };
36
37 static int localproxy_select_result(int fd, int event);
38
39 /*
40  * Trees to look up the pipe fds in.
41  */
42 static tree234 *localproxy_by_fromfd, *localproxy_by_tofd;
43 static int localproxy_fromfd_cmp(void *av, void *bv)
44 {
45     Local_Proxy_Socket a = (Local_Proxy_Socket)av;
46     Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
47     if (a->from_cmd < b->from_cmd)
48         return -1;
49     if (a->from_cmd > b->from_cmd)
50         return +1;
51     return 0;
52 }
53 static int localproxy_fromfd_find(void *av, void *bv)
54 {
55     int a = *(int *)av;
56     Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
57     if (a < b->from_cmd)
58         return -1;
59     if (a > b->from_cmd)
60         return +1;
61     return 0;
62 }
63 static int localproxy_tofd_cmp(void *av, void *bv)
64 {
65     Local_Proxy_Socket a = (Local_Proxy_Socket)av;
66     Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
67     if (a->to_cmd < b->to_cmd)
68         return -1;
69     if (a->to_cmd > b->to_cmd)
70         return +1;
71     return 0;
72 }
73 static int localproxy_tofd_find(void *av, void *bv)
74 {
75     int a = *(int *)av;
76     Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
77     if (a < b->to_cmd)
78         return -1;
79     if (a > b->to_cmd)
80         return +1;
81     return 0;
82 }
83
84 /* basic proxy socket functions */
85
86 static Plug sk_localproxy_plug (Socket s, Plug p)
87 {
88     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
89     Plug ret = ps->plug;
90     if (p)
91         ps->plug = p;
92     return ret;
93 }
94
95 static void sk_localproxy_close (Socket s)
96 {
97     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
98
99     if (ps->to_cmd >= 0) {
100         del234(localproxy_by_tofd, ps);
101         uxsel_del(ps->to_cmd);
102         close(ps->to_cmd);
103     }
104
105     del234(localproxy_by_fromfd, ps);
106     uxsel_del(ps->from_cmd);
107     close(ps->from_cmd);
108
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_private_ptr (Socket s, void *ptr)
190 {
191     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
192     ps->privptr = ptr;
193 }
194
195 static void * sk_localproxy_get_private_ptr (Socket s)
196 {
197     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
198     return ps->privptr;
199 }
200
201 static void sk_localproxy_set_frozen (Socket s, int is_frozen)
202 {
203     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
204
205     if (is_frozen)
206         uxsel_del(ps->from_cmd);
207     else
208         uxsel_set(ps->from_cmd, 1, localproxy_select_result);
209 }
210
211 static const char * sk_localproxy_socket_error (Socket s)
212 {
213     Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
214     return ps->error;
215 }
216
217 static int localproxy_select_result(int fd, int event)
218 {
219     Local_Proxy_Socket s;
220     char buf[20480];
221     int ret;
222
223     if (!(s = find234(localproxy_by_fromfd, &fd, localproxy_fromfd_find)) &&
224         !(s = find234(localproxy_by_tofd, &fd, localproxy_tofd_find)) )
225         return 1;                      /* boggle */
226
227     if (event == 1) {
228         assert(fd == s->from_cmd);
229         ret = read(fd, buf, sizeof(buf));
230         if (ret < 0) {
231             return plug_closing(s->plug, strerror(errno), errno, 0);
232         } else if (ret == 0) {
233             return plug_closing(s->plug, NULL, 0, 0);
234         } else {
235             return plug_receive(s->plug, 0, buf, ret);
236         }
237     } else if (event == 2) {
238         assert(fd == s->to_cmd);
239         if (localproxy_try_send(s))
240             plug_sent(s->plug, bufchain_size(&s->pending_output_data));
241         return 1;
242     }
243
244     return 1;
245 }
246
247 Socket platform_new_connection(SockAddr addr, char *hostname,
248                                int port, int privport,
249                                int oobinline, int nodelay, int keepalive,
250                                Plug plug, Conf *conf)
251 {
252     char *cmd;
253
254     static const struct socket_function_table socket_fn_table = {
255         sk_localproxy_plug,
256         sk_localproxy_close,
257         sk_localproxy_write,
258         sk_localproxy_write_oob,
259         sk_localproxy_write_eof,
260         sk_localproxy_flush,
261         sk_localproxy_set_private_ptr,
262         sk_localproxy_get_private_ptr,
263         sk_localproxy_set_frozen,
264         sk_localproxy_socket_error
265     };
266
267     Local_Proxy_Socket ret;
268     int to_cmd_pipe[2], from_cmd_pipe[2], pid;
269
270     if (conf_get_int(conf, CONF_proxy_type) != PROXY_CMD)
271         return NULL;
272
273     cmd = format_telnet_command(addr, port, conf);
274
275     ret = snew(struct Socket_localproxy_tag);
276     ret->fn = &socket_fn_table;
277     ret->plug = plug;
278     ret->error = NULL;
279     ret->outgoingeof = EOF_NO;
280
281     bufchain_init(&ret->pending_input_data);
282     bufchain_init(&ret->pending_output_data);
283
284     /*
285      * Create the pipes to the proxy command, and spawn the proxy
286      * command process.
287      */
288     if (pipe(to_cmd_pipe) < 0 ||
289         pipe(from_cmd_pipe) < 0) {
290         ret->error = dupprintf("pipe: %s", strerror(errno));
291         sfree(cmd);
292         return (Socket)ret;
293     }
294     cloexec(to_cmd_pipe[1]);
295     cloexec(from_cmd_pipe[0]);
296
297     pid = fork();
298
299     if (pid < 0) {
300         ret->error = dupprintf("fork: %s", strerror(errno));
301         sfree(cmd);
302         return (Socket)ret;
303     } else if (pid == 0) {
304         close(0);
305         close(1);
306         dup2(to_cmd_pipe[0], 0);
307         dup2(from_cmd_pipe[1], 1);
308         close(to_cmd_pipe[0]);
309         close(from_cmd_pipe[1]);
310         noncloexec(0);
311         noncloexec(1);
312         execl("/bin/sh", "sh", "-c", cmd, (void *)NULL);
313         _exit(255);
314     }
315
316     sfree(cmd);
317
318     close(to_cmd_pipe[0]);
319     close(from_cmd_pipe[1]);
320
321     ret->to_cmd = to_cmd_pipe[1];
322     ret->from_cmd = from_cmd_pipe[0];
323
324     if (!localproxy_by_fromfd)
325         localproxy_by_fromfd = newtree234(localproxy_fromfd_cmp);
326     if (!localproxy_by_tofd)
327         localproxy_by_tofd = newtree234(localproxy_tofd_cmp);
328
329     add234(localproxy_by_fromfd, ret);
330     add234(localproxy_by_tofd, ret);
331
332     uxsel_set(ret->from_cmd, 1, localproxy_select_result);
333
334     /* We are responsible for this and don't need it any more */
335     sk_addr_free(addr);
336
337     return (Socket) ret;
338 }