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