0f0a2507 |
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; |
bc06669b |
32 | enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; |
0f0a2507 |
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 | |
bc06669b |
99 | if (ps->to_cmd >= 0) { |
100 | del234(localproxy_by_tofd, ps); |
101 | uxsel_del(ps->to_cmd); |
102 | close(ps->to_cmd); |
103 | } |
0f0a2507 |
104 | |
bc06669b |
105 | del234(localproxy_by_fromfd, ps); |
1c63e246 |
106 | uxsel_del(ps->from_cmd); |
0f0a2507 |
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 | |
bc06669b |
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 | |
0f0a2507 |
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 | |
bc06669b |
155 | assert(ps->outgoingeof == EOF_NO); |
156 | |
0f0a2507 |
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 | |
bc06669b |
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 | |
0f0a2507 |
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 { |
6d983afb |
235 | return plug_receive(s->plug, 0, buf, ret); |
0f0a2507 |
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, |
79bf227b |
249 | int oobinline, int nodelay, int keepalive, |
4a693cfc |
250 | Plug plug, Conf *conf) |
0f0a2507 |
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, |
bc06669b |
259 | sk_localproxy_write_eof, |
0f0a2507 |
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 | |
4a693cfc |
270 | if (conf_get_int(conf, CONF_proxy_type) != PROXY_CMD) |
0f0a2507 |
271 | return NULL; |
272 | |
4a693cfc |
273 | cmd = format_telnet_command(addr, port, conf); |
0f0a2507 |
274 | |
275 | ret = snew(struct Socket_localproxy_tag); |
276 | ret->fn = &socket_fn_table; |
277 | ret->plug = plug; |
278 | ret->error = NULL; |
bc06669b |
279 | ret->outgoingeof = EOF_NO; |
0f0a2507 |
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 | return (Socket)ret; |
292 | } |
90f2ed42 |
293 | cloexec(to_cmd_pipe[1]); |
294 | cloexec(from_cmd_pipe[0]); |
0f0a2507 |
295 | |
296 | pid = fork(); |
297 | |
298 | if (pid < 0) { |
299 | ret->error = dupprintf("fork: %s", strerror(errno)); |
038ec85e |
300 | sfree(cmd); |
0f0a2507 |
301 | return (Socket)ret; |
302 | } else if (pid == 0) { |
0f0a2507 |
303 | close(0); |
304 | close(1); |
305 | dup2(to_cmd_pipe[0], 0); |
306 | dup2(from_cmd_pipe[1], 1); |
90f2ed42 |
307 | close(to_cmd_pipe[0]); |
308 | close(from_cmd_pipe[1]); |
0f0a2507 |
309 | fcntl(0, F_SETFD, 0); |
310 | fcntl(1, F_SETFD, 0); |
30264db9 |
311 | execl("/bin/sh", "sh", "-c", cmd, (void *)NULL); |
0f0a2507 |
312 | _exit(255); |
313 | } |
314 | |
53e66fad |
315 | sfree(cmd); |
316 | |
0f0a2507 |
317 | close(to_cmd_pipe[0]); |
318 | close(from_cmd_pipe[1]); |
319 | |
320 | ret->to_cmd = to_cmd_pipe[1]; |
321 | ret->from_cmd = from_cmd_pipe[0]; |
322 | |
323 | if (!localproxy_by_fromfd) |
324 | localproxy_by_fromfd = newtree234(localproxy_fromfd_cmp); |
325 | if (!localproxy_by_tofd) |
326 | localproxy_by_tofd = newtree234(localproxy_tofd_cmp); |
327 | |
328 | add234(localproxy_by_fromfd, ret); |
329 | add234(localproxy_by_tofd, ret); |
330 | |
331 | uxsel_set(ret->from_cmd, 1, localproxy_select_result); |
332 | |
f85e6f6e |
333 | /* We are responsible for this and don't need it any more */ |
334 | sk_addr_free(addr); |
335 | |
0f0a2507 |
336 | return (Socket) ret; |
337 | } |