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