Add a configuration option for TCP keepalives (SO_KEEPALIVE), default off.
[u/mdw/putty] / unix / uxproxy.c
CommitLineData
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
18typedef struct Socket_localproxy_tag * Local_Proxy_Socket;
19
20struct 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
36static int localproxy_select_result(int fd, int event);
37
38/*
39 * Trees to look up the pipe fds in.
40 */
41static tree234 *localproxy_by_fromfd, *localproxy_by_tofd;
42static 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}
52static 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}
62static 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}
72static 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
85static 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
94static 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
107static 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
138static 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
149static 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
158static void sk_localproxy_flush (Socket s)
159{
160 /* Local_Proxy_Socket ps = (Local_Proxy_Socket) s; */
161 /* do nothing */
162}
163
164static 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
170static void * sk_localproxy_get_private_ptr (Socket s)
171{
172 Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
173 return ps->privptr;
174}
175
176static 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
186static const char * sk_localproxy_socket_error (Socket s)
187{
188 Local_Proxy_Socket ps = (Local_Proxy_Socket) s;
189 return ps->error;
190}
191
192static 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
222Socket platform_new_connection(SockAddr addr, char *hostname,
223 int port, int privport,
79bf227b 224 int oobinline, int nodelay, int keepalive,
225 Plug plug, const Config *cfg)
0f0a2507 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}