Justin Bradford's proxy support patch. Currently supports only HTTP
[u/mdw/putty] / proxy.c
CommitLineData
8eebd221 1/*
2 * Network proxy abstraction in PuTTY
3 *
4 * A proxy layer, if necessary, wedges itself between the network
5 * code and the higher level backend.
6 */
7
8#include <windows.h>
9
10#define DEFINE_PLUG_METHOD_MACROS
11#include "putty.h"
12#include "network.h"
13#include "proxy.h"
14
15/*
16 * Call this when proxy negotiation is complete, so that this
17 * socket can begin working normally.
18 */
19void proxy_activate (Proxy_Socket p)
20{
21 void *data;
22 int len;
23
24 p->lock_close =
25 p->lock_write =
26 p->lock_write_oob =
27 p->lock_receive =
28 p->lock_flush =
29 p->lock_closing =
30 p->lock_sent =
31 p->lock_accepting =
32 p->lock_freeze = 1;
33
34 p->state = PROXY_STATE_ACTIVE;
35
36 /* let's try to keep extra receive events from coming through */
37 sk_set_frozen(p->sub_socket, 1);
38
39 while (bufchain_size(&p->pending_oob_output_data) > 0) {
40 bufchain_prefix(&p->pending_oob_output_data, &data, &len);
41 sk_write_oob(p->sub_socket, data, len);
42 bufchain_consume(&p->pending_oob_output_data, len);
43 }
44 bufchain_clear(&p->pending_oob_output_data);
45
46 while (bufchain_size(&p->pending_output_data) > 0) {
47 bufchain_prefix(&p->pending_output_data, &data, &len);
48 sk_write(p->sub_socket, data, len);
49 bufchain_consume(&p->pending_output_data, len);
50 }
51 bufchain_clear(&p->pending_output_data);
52
53 p->lock_write_oob = 0;
54 p->lock_write = 0;
55
56 if (p->pending_flush) sk_flush(p->sub_socket);
57 p->lock_flush = 0;
58
59 while (bufchain_size(&p->pending_input_data) > 0) {
60 bufchain_prefix(&p->pending_input_data, &data, &len);
61 plug_receive(p->plug, 0, data, len);
62 bufchain_consume(&p->pending_input_data, len);
63 }
64 bufchain_clear(&p->pending_input_data);
65 p->lock_receive = 0;
66
67 /* now set the underlying socket to whatever freeze state they wanted */
68 sk_set_frozen(p->sub_socket, p->freeze);
69 p->lock_freeze = 0;
70
71 p->lock_sent = 0;
72 p->lock_accepting = 0;
73 p->lock_closing = 0;
74 p->lock_close = 0;
75}
76
77/* basic proxy socket functions */
78
79static Plug sk_proxy_plug (Socket s, Plug p)
80{
81 Proxy_Socket ps = (Proxy_Socket) s;
82 Plug ret = ps->plug;
83 if (p)
84 ps->plug = p;
85 return ret;
86}
87
88static void sk_proxy_close (Socket s)
89{
90 Proxy_Socket ps = (Proxy_Socket) s;
91
92 while (ps->lock_close) ;
93 sk_close(ps->sub_socket);
94 sfree(ps);
95}
96
97static int sk_proxy_write (Socket s, char *data, int len)
98{
99 Proxy_Socket ps = (Proxy_Socket) s;
100
101 while (ps->lock_write) ;
102 if (ps->state != PROXY_STATE_ACTIVE) {
103 bufchain_add(&ps->pending_output_data, data, len);
104 return bufchain_size(&ps->pending_output_data);
105 }
106 return sk_write(ps->sub_socket, data, len);
107}
108
109static int sk_proxy_write_oob (Socket s, char *data, int len)
110{
111 Proxy_Socket ps = (Proxy_Socket) s;
112
113 while (ps->lock_write_oob) ;
114 if (ps->state != PROXY_STATE_ACTIVE) {
115 bufchain_clear(&ps->pending_output_data);
116 bufchain_clear(&ps->pending_oob_output_data);
117 bufchain_add(&ps->pending_oob_output_data, data, len);
118 return len;
119 }
120 return sk_write_oob(ps->sub_socket, data, len);
121}
122
123static void sk_proxy_flush (Socket s)
124{
125 Proxy_Socket ps = (Proxy_Socket) s;
126
127 while (ps->lock_flush) ;
128 if (ps->state != PROXY_STATE_ACTIVE) {
129 ps->pending_flush = 1;
130 return;
131 }
132 sk_flush(ps->sub_socket);
133}
134
135static void sk_proxy_set_private_ptr (Socket s, void *ptr)
136{
137 Proxy_Socket ps = (Proxy_Socket) s;
138 sk_set_private_ptr(ps->sub_socket, ptr);
139}
140
141static void * sk_proxy_get_private_ptr (Socket s)
142{
143 Proxy_Socket ps = (Proxy_Socket) s;
144 return sk_get_private_ptr(ps->sub_socket);
145}
146
147static void sk_proxy_set_frozen (Socket s, int is_frozen)
148{
149 Proxy_Socket ps = (Proxy_Socket) s;
150
151 while (ps->lock_freeze) ;
152 if (ps->state != PROXY_STATE_ACTIVE) {
153 ps->freeze = is_frozen;
154 return;
155 }
156 sk_set_frozen(ps->sub_socket, is_frozen);
157}
158
159static char * sk_proxy_socket_error (Socket s)
160{
161 Proxy_Socket ps = (Proxy_Socket) s;
162 if (ps->error != NULL || ps->sub_socket == NULL) {
163 return ps->error;
164 }
165 return sk_socket_error(ps->sub_socket);
166}
167
168/* basic proxy plug functions */
169
170static int plug_proxy_closing (Plug p, char *error_msg,
171 int error_code, int calling_back)
172{
173 Proxy_Plug pp = (Proxy_Plug) p;
174 Proxy_Socket ps = pp->proxy_socket;
175
176 while (ps->lock_closing) ;
177 if (ps->state != PROXY_STATE_ACTIVE) {
178 ps->closing_error_msg = error_msg;
179 ps->closing_error_code = error_code;
180 ps->closing_calling_back = calling_back;
181 return ps->negotiate(ps, PROXY_CHANGE_CLOSING);
182 }
183 return plug_closing(ps->plug, error_msg,
184 error_code, calling_back);
185}
186
187static int plug_proxy_receive (Plug p, int urgent, char *data, int len)
188{
189 Proxy_Plug pp = (Proxy_Plug) p;
190 Proxy_Socket ps = pp->proxy_socket;
191
192 while (ps->lock_receive) ;
193 if (ps->state != PROXY_STATE_ACTIVE) {
194 /* we will lose the urgentness of this data, but since most,
195 * if not all, of this data will be consumed by the negotiation
196 * process, hopefully it won't affect the protocol above us
197 */
198 bufchain_add(&ps->pending_input_data, data, len);
199 ps->receive_urgent = urgent;
200 ps->receive_data = data;
201 ps->receive_len = len;
202 return ps->negotiate(ps, PROXY_CHANGE_RECEIVE);
203 }
204 return plug_receive(ps->plug, urgent, data, len);
205}
206
207static void plug_proxy_sent (Plug p, int bufsize)
208{
209 Proxy_Plug pp = (Proxy_Plug) p;
210 Proxy_Socket ps = pp->proxy_socket;
211
212 while (ps->lock_sent) ;
213 if (ps->state != PROXY_STATE_ACTIVE) {
214 ps->sent_bufsize = bufsize;
215 ps->negotiate(ps, PROXY_CHANGE_SENT);
216 return;
217 }
218 plug_sent(ps->plug, bufsize);
219}
220
221static int plug_proxy_accepting (Plug p, void *sock)
222{
223 Proxy_Plug pp = (Proxy_Plug) p;
224 Proxy_Socket ps = pp->proxy_socket;
225
226 while (ps->lock_accepting) ;
227 if (ps->state != PROXY_STATE_ACTIVE) {
228 ps->accepting_sock = sock;
229 return ps->negotiate(ps, PROXY_CHANGE_ACCEPTING);
230 }
231 return plug_accepting(ps->plug, sock);
232}
233
234static int proxy_for_destination (SockAddr addr, char * hostname, int port)
235{
236 int s = 0, e = 0;
237 char hostip[64];
238 int hostip_len, hostname_len;
239 char * exclude_list;
240
241 /* we want a string representation of the IP address for comparisons */
242 sk_getaddr(addr, hostip, 64);
243
244 hostip_len = strlen(hostip);
245 hostname_len = strlen(hostname);
246
247 exclude_list = cfg.proxy_exclude_list;
248
249 /* now parse the exclude list, and see if either our IP
250 * or hostname matches anything in it.
251 */
252
253 while (exclude_list[s]) {
254 while (exclude_list[s] &&
255 (isspace(exclude_list[s]) ||
256 exclude_list[s] == ',')) s++;
257
258 if (!exclude_list[s]) break;
259
260 e = s;
261
262 while (exclude_list[e] &&
263 (isalnum(exclude_list[e]) ||
264 exclude_list[e] == '-' ||
265 exclude_list[e] == '.' ||
266 exclude_list[e] == '*')) e++;
267
268 if (exclude_list[s] == '*') {
269 /* wildcard at beginning of entry */
270
271 if (strnicmp(hostip + hostip_len - (e - s - 1),
272 exclude_list + s + 1, e - s - 1) == 0 ||
273 strnicmp(hostname + hostname_len - (e - s - 1),
274 exclude_list + s + 1, e - s - 1) == 0)
275 return 0; /* IP/hostname range excluded. do not use proxy. */
276
277 } else if (exclude_list[e-1] == '*') {
278 /* wildcard at end of entry */
279
280 if (strnicmp(hostip, exclude_list + s, e - s - 1) == 0 ||
281 strnicmp(hostname, exclude_list + s, e - s - 1) == 0)
282 return 0; /* IP/hostname range excluded. do not use proxy. */
283
284 } else {
285 /* no wildcard at either end, so let's try an absolute
286 * match (ie. a specific IP)
287 */
288
289 if (stricmp(hostip, exclude_list + s) == 0)
290 return 0; /* IP/hostname excluded. do not use proxy. */
291 if (stricmp(hostname, exclude_list + s) == 0)
292 return 0; /* IP/hostname excluded. do not use proxy. */
293 }
294
295 s = e;
296 }
297
298 /* no matches in the exclude list, so use the proxy */
299 return 1;
300}
301
302Socket new_connection(SockAddr addr, char *hostname,
303 int port, int privport,
304 int oobinline, int nodelay, Plug plug)
305{
306 static struct socket_function_table socket_fn_table = {
307 sk_proxy_plug,
308 sk_proxy_close,
309 sk_proxy_write,
310 sk_proxy_write_oob,
311 sk_proxy_flush,
312 sk_proxy_set_private_ptr,
313 sk_proxy_get_private_ptr,
314 sk_proxy_set_frozen,
315 sk_proxy_socket_error
316 };
317
318 static struct plug_function_table plug_fn_table = {
319 plug_proxy_closing,
320 plug_proxy_receive,
321 plug_proxy_sent,
322 plug_proxy_accepting
323 };
324
325 if (cfg.proxy_type != PROXY_NONE &&
326 proxy_for_destination(addr, hostname, port))
327 {
328 Proxy_Socket ret;
329 Proxy_Plug pplug;
330 SockAddr proxy_addr;
331 char * proxy_canonical_name;
332
333 ret = smalloc(sizeof(struct Socket_proxy_tag));
334 ret->fn = &socket_fn_table;
335 ret->plug = plug;
336 ret->remote_addr = addr;
337 ret->remote_port = port;
338
339 bufchain_init(&ret->pending_input_data);
340 bufchain_init(&ret->pending_output_data);
341 bufchain_init(&ret->pending_oob_output_data);
342
343 ret->lock_close =
344 ret->lock_write =
345 ret->lock_write_oob =
346 ret->lock_receive =
347 ret->lock_flush =
348 ret->lock_closing =
349 ret->lock_sent =
350 ret->lock_accepting = 0;
351
352 ret->sub_socket = NULL;
353 ret->state = PROXY_STATE_NEW;
354
355 if (cfg.proxy_type == PROXY_HTTP) {
356 ret->negotiate = proxy_http_negotiate;
357 } else if (cfg.proxy_type == PROXY_SOCKS) {
358 ret->negotiate = proxy_socks_negotiate;
359 } else if (cfg.proxy_type == PROXY_TELNET) {
360 ret->negotiate = proxy_telnet_negotiate;
361 } else {
362 ret->error = "Network error: Unknown proxy method";
363 return (Socket) ret;
364 }
365
366 /* create the proxy plug to map calls from the actual
367 * socket into our proxy socket layer */
368 pplug = smalloc(sizeof(struct Plug_proxy_tag));
369 pplug->fn = &plug_fn_table;
370 pplug->proxy_socket = ret;
371
372 /* look-up proxy */
373 proxy_addr = sk_namelookup(cfg.proxy_host,
374 &proxy_canonical_name);
375 sfree(proxy_canonical_name);
376
377 /* create the actual socket we will be using,
378 * connected to our proxy server and port.
379 */
380 ret->sub_socket = sk_new(proxy_addr, cfg.proxy_port,
381 privport, oobinline,
382 nodelay, (Plug) pplug);
383 if (sk_socket_error(ret->sub_socket) != NULL)
384 return (Socket) ret;
385
386 sk_addr_free(proxy_addr);
387
388 /* start the proxy negotiation process... */
389 sk_set_frozen(ret->sub_socket, 0);
390 ret->negotiate(ret, PROXY_CHANGE_NEW);
391
392 return (Socket) ret;
393 }
394
395 /* no proxy, so just return the direct socket */
396 return sk_new(addr, port, privport, oobinline, nodelay, plug);
397}
398
399Socket new_listener(int port, Plug plug, int local_host_only)
400{
401 /* TODO: SOCKS (and potentially others) support inbound
402 * TODO: connections via the proxy. support them.
403 */
404
405 return sk_newlistener(port, plug, local_host_only);
406}
407
408/* ----------------------------------------------------------------------
409 * HTTP CONNECT proxy type.
410 */
411
412static int get_line_end (char * data, int len)
413{
414 int off = 0;
415
416 while (off < len)
417 {
418 if (data[off] == '\n') {
419 /* we have a newline */
420 off++;
421
422 /* is that the only thing on this line? */
423 if (off <= 2) return off;
424
425 /* if not, then there is the possibility that this header
426 * continues onto the next line, if it starts with a space
427 * or a tab.
428 */
429
430 if (off + 1 < len &&
431 data[off+1] != ' ' &&
432 data[off+1] != '\t') return off;
433
434 /* the line does continue, so we have to keep going
435 * until we see an the header's "real" end of line.
436 */
437 off++;
438 }
439
440 off++;
441 }
442
443 return -1;
444}
445
446int proxy_http_negotiate (Proxy_Socket p, int change)
447{
448 if (p->state == PROXY_STATE_NEW) {
449 /* we are just beginning the proxy negotiate process,
450 * so we'll send off the initial bits of the request.
451 * for this proxy method, it's just a simple HTTP
452 * request
453 */
454 char buf[1024], dest[21];
455
456 sk_getaddr(p->remote_addr, dest, 20);
457
458 sprintf(buf, "CONNECT %s:%i HTTP/1.1\r\nHost: %s:%i\r\n\r\n",
459 dest, p->remote_port, dest, p->remote_port);
460 sk_write(p->sub_socket, buf, strlen(buf));
461
462 p->state = 1;
463
464 return 0;
465 }
466
467 if (change == PROXY_CHANGE_CLOSING) {
468 /* if our proxy negotiation process involves closing and opening
469 * new sockets, then we would want to intercept this closing
470 * callback when we were expecting it. if we aren't anticipating
471 * a socket close, then some error must have occurred. we'll
472 * just pass those errors up to the backend.
473 */
474 return plug_closing(p->plug, p->closing_error_msg,
475 p->closing_error_code,
476 p->closing_calling_back);
477 }
478
479 if (change == PROXY_CHANGE_SENT) {
480 /* some (or all) of what we wrote to the proxy was sent.
481 * we don't do anything new, however, until we receive the
482 * proxy's response. we might want to set a timer so we can
483 * timeout the proxy negotiation after a while...
484 */
485 return 0;
486 }
487
488 if (change == PROXY_CHANGE_ACCEPTING) {
489 /* we should _never_ see this, as we are using our socket to
490 * connect to a proxy, not accepting inbound connections.
491 * what should we do? close the socket with an appropriate
492 * error message?
493 */
494 return plug_accepting(p->plug, p->accepting_sock);
495 }
496
497 if (change == PROXY_CHANGE_RECEIVE) {
498 /* we have received data from the underlying socket, which
499 * we'll need to parse, process, and respond to appropriately.
500 */
501
502 void *data;
503 int len;
504 int eol;
505
506 if (p->state == 1) {
507
508 int min_ver, maj_ver, status;
509
510 /* get the status line */
511 bufchain_prefix(&p->pending_input_data, &data, &len);
512 eol = get_line_end(data, len);
513 if (eol < 0) return 1;
514
515 sscanf((char *)data, "HTTP/%i.%i %i", &maj_ver, &min_ver, &status);
516
517 /* remove the status line from the input buffer. */
518 bufchain_consume(&p->pending_input_data, eol);
519
520 /* TODO: we need to support Proxy-Auth headers */
521
522 if (status < 200 || status > 299) {
523 /* error */
524 /* TODO: return a more specific error message,
525 * TODO: based on the status code.
526 */
527 plug_closing(p->plug, "Network error: Error while communicating with proxy",
528 PROXY_ERROR_GENERAL, 0);
529 return 1;
530 }
531
532 p->state = 2;
533 }
534
535 if (p->state == 2) {
536
537 /* get headers. we're done when we get a
538 * header of length 2, (ie. just "\r\n")
539 */
540
541 bufchain_prefix(&p->pending_input_data, &data, &len);
542 eol = get_line_end(data, len);
543 while (eol > 2)
544 {
545 /* TODO: Proxy-Auth stuff. in some cases, we will
546 * TODO: need to extract information from headers.
547 */
548 bufchain_consume(&p->pending_input_data, eol);
549 bufchain_prefix(&p->pending_input_data, &data, &len);
550 eol = get_line_end(data, len);
551 }
552
553 if (eol == 2) {
554 /* we're done */
555 bufchain_consume(&p->pending_input_data, 2);
556 proxy_activate(p);
557 /* proxy activate will have dealt with
558 * whatever is left of the buffer */
559 return 1;
560 }
561
562 return 1;
563 }
564 }
565
566 plug_closing(p->plug, "Network error: Unexpected proxy error",
567 PROXY_ERROR_UNEXPECTED, 0);
568 return 0;
569}
570
571/* ----------------------------------------------------------------------
572 * SOCKS proxy type (as yet unimplemented).
573 */
574
575int proxy_socks_negotiate (Proxy_Socket p, int change)
576{
577 p->error = "Network error: SOCKS proxy implementation is incomplete";
578 return 0;
579}
580
581/* ----------------------------------------------------------------------
582 * `Telnet' proxy type (as yet unimplemented).
583 *
584 * (This is for ad-hoc proxies where you connect to the proxy's
585 * telnet port and send a command such as `connect host port'. The
586 * command is configurable, since this proxy type is typically not
587 * standardised or at all well-defined.)
588 */
589
590int proxy_telnet_negotiate (Proxy_Socket p, int change)
591{
592 p->error = "Network error: Telnet proxy implementation is incomplete";
593 return 0;
594}