2 * uxproxy.c: Unix implementation of platform_new_connection(),
3 * supporting an OpenSSH-like proxy command.
12 #define DEFINE_PLUG_METHOD_MACROS
18 typedef struct Socket_localproxy_tag * Local_Proxy_Socket;
20 struct Socket_localproxy_tag {
21 const struct socket_function_table *fn;
22 /* the above variable absolutely *must* be the first in this structure */
24 int to_cmd, from_cmd, cmd_err; /* fds */
30 bufchain pending_output_data;
31 bufchain pending_input_data;
32 bufchain pending_error_data;
33 enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
36 static int localproxy_select_result(int fd, int event);
39 * Trees to look up the pipe fds in.
41 static tree234 *localproxy_by_fromfd;
42 static tree234 *localproxy_by_tofd;
43 static tree234 *localproxy_by_errfd;
44 static int localproxy_fromfd_cmp(void *av, void *bv)
46 Local_Proxy_Socket a = (Local_Proxy_Socket)av;
47 Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
48 if (a->from_cmd < b->from_cmd)
50 if (a->from_cmd > b->from_cmd)
54 static int localproxy_fromfd_find(void *av, void *bv)
57 Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
64 static int localproxy_tofd_cmp(void *av, void *bv)
66 Local_Proxy_Socket a = (Local_Proxy_Socket)av;
67 Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
68 if (a->to_cmd < b->to_cmd)
70 if (a->to_cmd > b->to_cmd)
74 static int localproxy_tofd_find(void *av, void *bv)
77 Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
84 static int localproxy_errfd_cmp(void *av, void *bv)
86 Local_Proxy_Socket a = (Local_Proxy_Socket)av;
87 Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
88 if (a->cmd_err < b->cmd_err)
90 if (a->cmd_err > b->cmd_err)
94 static int localproxy_errfd_find(void *av, void *bv)
97 Local_Proxy_Socket b = (Local_Proxy_Socket)bv;
105 /* basic proxy socket functions */
107 static Plug sk_localproxy_plug (Socket s, Plug p)
109 Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
116 static void sk_localproxy_close (Socket s)
118 Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
120 if (ps->to_cmd >= 0) {
121 del234(localproxy_by_tofd, ps);
122 uxsel_del(ps->to_cmd);
126 del234(localproxy_by_fromfd, ps);
127 uxsel_del(ps->from_cmd);
130 del234(localproxy_by_errfd, ps);
131 uxsel_del(ps->cmd_err);
134 bufchain_clear(&ps->pending_input_data);
135 bufchain_clear(&ps->pending_output_data);
136 bufchain_clear(&ps->pending_error_data);
141 static int localproxy_try_send(Local_Proxy_Socket ps)
145 while (bufchain_size(&ps->pending_output_data) > 0) {
149 bufchain_prefix(&ps->pending_output_data, &data, &len);
150 ret = write(ps->to_cmd, data, len);
151 if (ret < 0 && errno != EWOULDBLOCK) {
152 plug_closing(ps->plug, strerror(errno), errno, 0);
154 } else if (ret <= 0) {
157 bufchain_consume(&ps->pending_output_data, ret);
162 if (ps->outgoingeof == EOF_PENDING) {
163 del234(localproxy_by_tofd, ps);
165 uxsel_del(ps->to_cmd);
167 ps->outgoingeof = EOF_SENT;
170 if (bufchain_size(&ps->pending_output_data) == 0)
171 uxsel_del(ps->to_cmd);
173 uxsel_set(ps->to_cmd, 2, localproxy_select_result);
178 static int sk_localproxy_write (Socket s, const char *data, int len)
180 Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
182 assert(ps->outgoingeof == EOF_NO);
184 bufchain_add(&ps->pending_output_data, data, len);
186 localproxy_try_send(ps);
188 return bufchain_size(&ps->pending_output_data);
191 static int sk_localproxy_write_oob (Socket s, const char *data, int len)
194 * oob data is treated as inband; nasty, but nothing really
197 return sk_localproxy_write(s, data, len);
200 static void sk_localproxy_write_eof (Socket s)
202 Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
204 assert(ps->outgoingeof == EOF_NO);
205 ps->outgoingeof = EOF_PENDING;
207 localproxy_try_send(ps);
210 static void sk_localproxy_flush (Socket s)
212 /* Local_Proxy_Socket ps = (Local_Proxy_Socket) s; */
216 static void sk_localproxy_set_frozen (Socket s, int is_frozen)
218 Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
221 uxsel_del(ps->from_cmd);
223 uxsel_set(ps->from_cmd, 1, localproxy_select_result);
226 static const char * sk_localproxy_socket_error (Socket s)
228 Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
232 static int localproxy_select_result(int fd, int event)
234 Local_Proxy_Socket s;
238 if (!(s = find234(localproxy_by_fromfd, &fd, localproxy_fromfd_find)) &&
239 !(s = find234(localproxy_by_fromfd, &fd, localproxy_errfd_find)) &&
240 !(s = find234(localproxy_by_tofd, &fd, localproxy_tofd_find)) )
241 return 1; /* boggle */
244 if (fd == s->cmd_err) {
245 ret = read(fd, buf, sizeof(buf));
247 log_proxy_stderr(s->plug, &s->pending_error_data, buf, ret);
249 assert(fd == s->from_cmd);
250 ret = read(fd, buf, sizeof(buf));
252 return plug_closing(s->plug, strerror(errno), errno, 0);
253 } else if (ret == 0) {
254 return plug_closing(s->plug, NULL, 0, 0);
256 return plug_receive(s->plug, 0, buf, ret);
259 } else if (event == 2) {
260 assert(fd == s->to_cmd);
261 if (localproxy_try_send(s))
262 plug_sent(s->plug, bufchain_size(&s->pending_output_data));
269 Socket platform_new_connection(SockAddr addr, const char *hostname,
270 int port, int privport,
271 int oobinline, int nodelay, int keepalive,
272 Plug plug, Conf *conf)
276 static const struct socket_function_table socket_fn_table = {
280 sk_localproxy_write_oob,
281 sk_localproxy_write_eof,
283 sk_localproxy_set_frozen,
284 sk_localproxy_socket_error,
285 NULL, /* peer_info */
288 Local_Proxy_Socket ret;
289 int to_cmd_pipe[2], from_cmd_pipe[2], cmd_err_pipe[2], pid, proxytype;
291 proxytype = conf_get_int(conf, CONF_proxy_type);
292 if (proxytype != PROXY_CMD && proxytype != PROXY_FUZZ)
295 ret = snew(struct Socket_localproxy_tag);
296 ret->fn = &socket_fn_table;
299 ret->outgoingeof = EOF_NO;
301 bufchain_init(&ret->pending_input_data);
302 bufchain_init(&ret->pending_output_data);
303 bufchain_init(&ret->pending_error_data);
305 if (proxytype == PROXY_CMD) {
306 cmd = format_telnet_command(addr, port, conf);
308 if (flags & FLAG_STDERR) {
309 /* If we have a sensible stderr, the proxy command can
310 * send its own standard error there, so we won't
312 cmd_err_pipe[0] = cmd_err_pipe[1] = -1;
314 /* If we don't have a sensible stderr, we should catch the
315 * proxy command's standard error to put in our event
317 cmd_err_pipe[0] = cmd_err_pipe[1] = 0;
321 char *logmsg = dupprintf("Starting local proxy command: %s", cmd);
322 plug_log(plug, 2, NULL, 0, logmsg, 0);
327 * Create the pipes to the proxy command, and spawn the proxy
330 if (pipe(to_cmd_pipe) < 0 ||
331 pipe(from_cmd_pipe) < 0 ||
332 (cmd_err_pipe[0] == 0 && pipe(cmd_err_pipe) < 0)) {
333 ret->error = dupprintf("pipe: %s", strerror(errno));
337 cloexec(to_cmd_pipe[1]);
338 cloexec(from_cmd_pipe[0]);
339 if (cmd_err_pipe[0] >= 0)
340 cloexec(cmd_err_pipe[0]);
345 ret->error = dupprintf("fork: %s", strerror(errno));
348 } else if (pid == 0) {
351 dup2(to_cmd_pipe[0], 0);
352 dup2(from_cmd_pipe[1], 1);
353 close(to_cmd_pipe[0]);
354 close(from_cmd_pipe[1]);
355 if (cmd_err_pipe[0] >= 0) {
356 dup2(cmd_err_pipe[1], 2);
357 close(cmd_err_pipe[1]);
361 execl("/bin/sh", "sh", "-c", cmd, (void *)NULL);
367 close(to_cmd_pipe[0]);
368 close(from_cmd_pipe[1]);
369 if (cmd_err_pipe[0] >= 0)
370 close(cmd_err_pipe[1]);
372 ret->to_cmd = to_cmd_pipe[1];
373 ret->from_cmd = from_cmd_pipe[0];
374 ret->cmd_err = cmd_err_pipe[0];
376 cmd = format_telnet_command(addr, port, conf);
377 ret->to_cmd = open("/dev/null", O_WRONLY);
378 if (ret->to_cmd == -1) {
379 ret->error = dupprintf("/dev/null: %s", strerror(errno));
383 ret->from_cmd = open(cmd, O_RDONLY);
384 if (ret->from_cmd == -1) {
385 ret->error = dupprintf("%s: %s", cmd, strerror(errno));
393 if (!localproxy_by_fromfd)
394 localproxy_by_fromfd = newtree234(localproxy_fromfd_cmp);
395 if (!localproxy_by_tofd)
396 localproxy_by_tofd = newtree234(localproxy_tofd_cmp);
397 if (!localproxy_by_errfd)
398 localproxy_by_errfd = newtree234(localproxy_errfd_cmp);
400 add234(localproxy_by_fromfd, ret);
401 add234(localproxy_by_tofd, ret);
402 if (ret->cmd_err >= 0)
403 add234(localproxy_by_errfd, ret);
405 uxsel_set(ret->from_cmd, 1, localproxy_select_result);
406 if (ret->cmd_err >= 0)
407 uxsel_set(ret->cmd_err, 1, localproxy_select_result);
409 /* We are responsible for this and don't need it any more */