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 /* We're inside the Unix frontend here, so we know
153 * that the frontend handle is unnecessary. */
154 logevent(NULL, strerror(errno));
155 fatalbox("%s", strerror(errno));
156 } else if (ret <= 0) {
159 bufchain_consume(&ps->pending_output_data, ret);
164 if (ps->outgoingeof == EOF_PENDING) {
165 del234(localproxy_by_tofd, ps);
167 uxsel_del(ps->to_cmd);
169 ps->outgoingeof = EOF_SENT;
172 if (bufchain_size(&ps->pending_output_data) == 0)
173 uxsel_del(ps->to_cmd);
175 uxsel_set(ps->to_cmd, 2, localproxy_select_result);
180 static int sk_localproxy_write (Socket s, const char *data, int len)
182 Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
184 assert(ps->outgoingeof == EOF_NO);
186 bufchain_add(&ps->pending_output_data, data, len);
188 localproxy_try_send(ps);
190 return bufchain_size(&ps->pending_output_data);
193 static int sk_localproxy_write_oob (Socket s, const char *data, int len)
196 * oob data is treated as inband; nasty, but nothing really
199 return sk_localproxy_write(s, data, len);
202 static void sk_localproxy_write_eof (Socket s)
204 Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
206 assert(ps->outgoingeof == EOF_NO);
207 ps->outgoingeof = EOF_PENDING;
209 localproxy_try_send(ps);
212 static void sk_localproxy_flush (Socket s)
214 /* Local_Proxy_Socket ps = (Local_Proxy_Socket) s; */
218 static void sk_localproxy_set_frozen (Socket s, int is_frozen)
220 Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
223 uxsel_del(ps->from_cmd);
225 uxsel_set(ps->from_cmd, 1, localproxy_select_result);
228 static const char * sk_localproxy_socket_error (Socket s)
230 Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
234 static int localproxy_select_result(int fd, int event)
236 Local_Proxy_Socket s;
240 if (!(s = find234(localproxy_by_fromfd, &fd, localproxy_fromfd_find)) &&
241 !(s = find234(localproxy_by_fromfd, &fd, localproxy_errfd_find)) &&
242 !(s = find234(localproxy_by_tofd, &fd, localproxy_tofd_find)) )
243 return 1; /* boggle */
246 if (fd == s->cmd_err) {
247 ret = read(fd, buf, sizeof(buf));
249 log_proxy_stderr(s->plug, &s->pending_error_data, buf, ret);
251 assert(fd == s->from_cmd);
252 ret = read(fd, buf, sizeof(buf));
254 return plug_closing(s->plug, strerror(errno), errno, 0);
255 } else if (ret == 0) {
256 return plug_closing(s->plug, NULL, 0, 0);
258 return plug_receive(s->plug, 0, buf, ret);
261 } else if (event == 2) {
262 assert(fd == s->to_cmd);
263 if (localproxy_try_send(s))
264 plug_sent(s->plug, bufchain_size(&s->pending_output_data));
271 Socket platform_new_connection(SockAddr addr, const char *hostname,
272 int port, int privport,
273 int oobinline, int nodelay, int keepalive,
274 Plug plug, Conf *conf)
278 static const struct socket_function_table socket_fn_table = {
282 sk_localproxy_write_oob,
283 sk_localproxy_write_eof,
285 sk_localproxy_set_frozen,
286 sk_localproxy_socket_error,
287 NULL, /* peer_info */
290 Local_Proxy_Socket ret;
291 int to_cmd_pipe[2], from_cmd_pipe[2], cmd_err_pipe[2], pid, proxytype;
293 proxytype = conf_get_int(conf, CONF_proxy_type);
294 if (proxytype != PROXY_CMD && proxytype != PROXY_FUZZ)
297 ret = snew(struct Socket_localproxy_tag);
298 ret->fn = &socket_fn_table;
301 ret->outgoingeof = EOF_NO;
303 bufchain_init(&ret->pending_input_data);
304 bufchain_init(&ret->pending_output_data);
305 bufchain_init(&ret->pending_error_data);
307 if (proxytype == PROXY_CMD) {
308 cmd = format_telnet_command(addr, port, conf);
310 if (flags & FLAG_STDERR) {
311 /* If we have a sensible stderr, the proxy command can
312 * send its own standard error there, so we won't
314 cmd_err_pipe[0] = cmd_err_pipe[1] = -1;
316 /* If we don't have a sensible stderr, we should catch the
317 * proxy command's standard error to put in our event
319 cmd_err_pipe[0] = cmd_err_pipe[1] = 0;
323 char *logmsg = dupprintf("Starting local proxy command: %s", cmd);
324 plug_log(plug, 2, NULL, 0, logmsg, 0);
329 * Create the pipes to the proxy command, and spawn the proxy
332 if (pipe(to_cmd_pipe) < 0 ||
333 pipe(from_cmd_pipe) < 0 ||
334 (cmd_err_pipe[0] == 0 && pipe(cmd_err_pipe) < 0)) {
335 ret->error = dupprintf("pipe: %s", strerror(errno));
339 cloexec(to_cmd_pipe[1]);
340 cloexec(from_cmd_pipe[0]);
341 if (cmd_err_pipe[0] >= 0)
342 cloexec(cmd_err_pipe[0]);
347 ret->error = dupprintf("fork: %s", strerror(errno));
350 } else if (pid == 0) {
353 dup2(to_cmd_pipe[0], 0);
354 dup2(from_cmd_pipe[1], 1);
355 close(to_cmd_pipe[0]);
356 close(from_cmd_pipe[1]);
357 if (cmd_err_pipe[0] >= 0) {
358 dup2(cmd_err_pipe[1], 2);
359 close(cmd_err_pipe[1]);
363 execl("/bin/sh", "sh", "-c", cmd, (void *)NULL);
369 close(to_cmd_pipe[0]);
370 close(from_cmd_pipe[1]);
371 if (cmd_err_pipe[0] >= 0)
372 close(cmd_err_pipe[1]);
374 ret->to_cmd = to_cmd_pipe[1];
375 ret->from_cmd = from_cmd_pipe[0];
376 ret->cmd_err = cmd_err_pipe[0];
378 cmd = format_telnet_command(addr, port, conf);
379 ret->to_cmd = open("/dev/null", O_WRONLY);
380 if (ret->to_cmd == -1) {
381 ret->error = dupprintf("/dev/null: %s", strerror(errno));
385 ret->from_cmd = open(cmd, O_RDONLY);
386 if (ret->from_cmd == -1) {
387 ret->error = dupprintf("%s: %s", cmd, strerror(errno));
395 if (!localproxy_by_fromfd)
396 localproxy_by_fromfd = newtree234(localproxy_fromfd_cmp);
397 if (!localproxy_by_tofd)
398 localproxy_by_tofd = newtree234(localproxy_tofd_cmp);
399 if (!localproxy_by_errfd)
400 localproxy_by_errfd = newtree234(localproxy_errfd_cmp);
402 add234(localproxy_by_fromfd, ret);
403 add234(localproxy_by_tofd, ret);
404 if (ret->cmd_err >= 0)
405 add234(localproxy_by_errfd, ret);
407 uxsel_set(ret->from_cmd, 1, localproxy_select_result);
408 if (ret->cmd_err >= 0)
409 uxsel_set(ret->cmd_err, 1, localproxy_select_result);
411 /* We are responsible for this and don't need it any more */