add listen-timeout to function as an accept timeout
[socat/sam.git] / xio-proxy.c
blob622e2c5e59a4693f8adc8496f9c32367659d97b9
1 /* source: xio-proxy.c */
2 /* Copyright Gerhard Rieger 2002-2008 */
3 /* Published under the GNU General Public License V.2, see file COPYING */
5 /* this file contains the source for opening addresses of HTTP proxy CONNECT
6 type */
8 #include "xiosysincludes.h"
10 #if WITH_PROXY
12 #include "xioopen.h"
13 #include "xio-socket.h"
14 #include "xio-ipapp.h"
15 #include "xio-ascii.h" /* for base64 encoding of authentication */
17 #include "xio-proxy.h"
20 #define PROXYPORT "8080"
22 static int xioopen_proxy_connect(int argc, const char *argv[], struct opt *opts,
23 int xioflags, xiofile_t *fd,
24 unsigned groups, int dummy1, int dummy2,
25 int dummy3);
27 const struct optdesc opt_proxyport = { "proxyport", NULL, OPT_PROXYPORT, GROUP_HTTP, PH_LATE, TYPE_STRING, OFUNC_SPEC };
28 const struct optdesc opt_ignorecr = { "ignorecr", NULL, OPT_IGNORECR, GROUP_HTTP, PH_LATE, TYPE_BOOL, OFUNC_SPEC };
29 const struct optdesc opt_proxy_resolve = { "proxy-resolve", "resolve", OPT_PROXY_RESOLVE, GROUP_HTTP, PH_LATE, TYPE_BOOL, OFUNC_SPEC };
30 const struct optdesc opt_proxy_authorization = { "proxy-authorization", "proxyauth", OPT_PROXY_AUTHORIZATION, GROUP_HTTP, PH_LATE, TYPE_STRING, OFUNC_SPEC };
32 const struct addrdesc addr_proxy_connect = { "proxy", 3, xioopen_proxy_connect, GROUP_FD|GROUP_SOCKET|GROUP_SOCK_IP4|GROUP_SOCK_IP6|GROUP_IP_TCP|GROUP_HTTP|GROUP_CHILD|GROUP_RETRY, 0, 0, 0 HELP(":<proxy-server>:<host>:<port>") };
35 /*0#define CONNLEN 40*/ /* "CONNECT 123.156.189.123:65432 HTTP/1.0\r\n\0" */
36 #define CONNLEN 281 /* "CONNECT <255bytes>:65432 HTTP/1.0\r\n\0" */
38 /* states during receiving answer */
39 enum {
40 XIOSTATE_HTTP1, /* 0 or more bytes of first line received, no \r */
41 XIOSTATE_HTTP2, /* first line received including \r */
42 XIOSTATE_HTTP3, /* received status and \r\n */
43 XIOSTATE_HTTP4, /* within header */
44 XIOSTATE_HTTP5, /* within header, \r */
45 XIOSTATE_HTTP6, /* received status and 1 or more headers, \r\n */
46 XIOSTATE_HTTP7, /* received status line, ev. headers, \r\n\r */
47 XIOSTATE_HTTP8, /* complete answer received */
48 XIOSTATE_ERROR /* error during HTTP headers */
49 } ;
52 /* get buflen bytes from proxy server;
53 handles EINTR;
54 returns <0 when error occurs
56 static ssize_t
57 xioproxy_recvbytes(struct single *xfd, char *buff, size_t buflen, int level) {
58 ssize_t result;
59 do {
60 /* we need at least buflen bytes... */
61 result = Read(xfd->fd, buff, buflen);
62 } while (result < 0 && errno == EINTR); /*! EAGAIN? */
63 if (result < 0) {
64 Msg4(level, "read(%d, %p, "F_Zu"): %s",
65 xfd->fd, buff, buflen, strerror(errno));
66 return result;
68 if (result == 0) {
69 Msg(level, "proxy_connect: connection closed by proxy");
71 return result;
75 #define BUFLEN 2048
78 static int xioopen_proxy_connect(int argc, const char *argv[], struct opt *opts,
79 int xioflags, xiofile_t *xxfd,
80 unsigned groups, int dummy1, int dummy2,
81 int dummy3) {
82 /* we expect the form: host:host:port */
83 struct single *xfd = &xxfd->stream;
84 struct opt *opts0 = NULL;
85 struct proxyvars struct_proxyvars = { 0 }, *proxyvars = &struct_proxyvars;
86 /* variables to be filled with address option values */
87 bool dofork = false;
88 /* */
89 int pf = PF_UNSPEC;
90 union sockaddr_union us_sa, *us = &us_sa;
91 union sockaddr_union them_sa, *them = &them_sa;
92 socklen_t uslen = sizeof(us_sa);
93 socklen_t themlen = sizeof(them_sa);
94 const char *proxyname; char *proxyport = NULL;
95 const char *targetname, *targetport;
96 int ipproto = IPPROTO_TCP;
97 bool needbind = false;
98 bool lowport = false;
99 int socktype = SOCK_STREAM;
100 int level;
101 int result;
103 if (argc != 4) {
104 Error1("%s: 3 parameters required", argv[0]);
105 return STAT_NORETRY;
107 proxyname = argv[1];
108 targetname = argv[2];
109 targetport = argv[3];
111 xfd->howtoend = END_SHUTDOWN;
112 if (applyopts_single(xfd, opts, PH_INIT) < 0) return -1;
113 applyopts(-1, opts, PH_INIT);
115 retropt_int(opts, OPT_SO_TYPE, &socktype);
117 retropt_bool(opts, OPT_FORK, &dofork);
119 if (retropt_string(opts, OPT_PROXYPORT, &proxyport) < 0) {
120 if ((proxyport = strdup(PROXYPORT)) == NULL) {
121 errno = ENOMEM; return -1;
125 result = _xioopen_proxy_prepare(proxyvars, opts, targetname, targetport);
126 if (result != STAT_OK) return result;
128 result =
129 _xioopen_ipapp_prepare(opts, &opts0, proxyname, proxyport,
130 &pf, ipproto,
131 xfd->para.socket.ip.res_opts[1],
132 xfd->para.socket.ip.res_opts[0],
133 them, &themlen, us, &uslen,
134 &needbind, &lowport, socktype);
135 if (result != STAT_OK) return result;
137 Notice4("opening connection to %s:%u via proxy %s:%s",
138 proxyvars->targetaddr, proxyvars->targetport, proxyname, proxyport);
140 do { /* loop over failed connect and proxy connect attempts */
142 #if WITH_RETRY
143 if (xfd->forever || xfd->retry) {
144 level = E_INFO;
145 } else
146 #endif /* WITH_RETRY */
147 level = E_ERROR;
149 result =
150 _xioopen_connect(xfd,
151 needbind?(struct sockaddr *)us:NULL, sizeof(*us),
152 (struct sockaddr *)them, themlen,
153 opts, pf, socktype, IPPROTO_TCP, lowport, level);
154 switch (result) {
155 case STAT_OK: break;
156 #if WITH_RETRY
157 case STAT_RETRYLATER:
158 case STAT_RETRYNOW:
159 if (xfd->forever || xfd->retry--) {
160 if (result == STAT_RETRYLATER) Nanosleep(&xfd->intervall, NULL);
161 continue;
163 #endif /* WITH_RETRY */
164 default:
165 return result;
168 applyopts(xfd->fd, opts, PH_ALL);
170 if ((result = _xio_openlate(xfd, opts)) < 0)
171 return result;
173 result = _xioopen_proxy_connect(xfd, proxyvars, level);
174 switch (result) {
175 case STAT_OK: break;
176 #if WITH_RETRY
177 case STAT_RETRYLATER:
178 case STAT_RETRYNOW:
179 if (xfd->forever || xfd->retry--) {
180 if (result == STAT_RETRYLATER) Nanosleep(&xfd->intervall, NULL);
181 continue;
183 #endif /* WITH_RETRY */
184 default:
185 return result;
188 if (dofork) {
189 xiosetchilddied(); /* set SIGCHLD handler */
192 #if WITH_RETRY
193 if (dofork) {
194 pid_t pid;
195 int level = E_ERROR;
196 if (xfd->forever || xfd->retry) {
197 level = E_WARN;
199 while ((pid = xio_fork(false, level)) < 0) {
200 if (xfd->forever || --xfd->retry) {
201 Nanosleep(&xfd->intervall, NULL); continue;
203 return STAT_RETRYLATER;
206 if (pid == 0) { /* child process */
207 xfd->forever = false; xfd->retry = 0;
208 break;
211 /* parent process */
212 Close(xfd->fd);
213 Nanosleep(&xfd->intervall, NULL);
214 dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL);
215 continue;
216 } else
217 #endif /* WITH_RETRY */
219 break;
222 } while (true); /* end of complete open loop - drop out on success */
224 Notice4("successfully connected to %s:%u via proxy %s:%s",
225 proxyvars->targetaddr, proxyvars->targetport,
226 proxyname, proxyport);
228 return 0;
232 int _xioopen_proxy_prepare(struct proxyvars *proxyvars, struct opt *opts,
233 const char *targetname, const char *targetport) {
234 struct hostent *host;
236 retropt_bool(opts, OPT_IGNORECR, &proxyvars->ignorecr);
237 retropt_bool(opts, OPT_PROXY_RESOLVE, &proxyvars->doresolve);
238 retropt_string(opts, OPT_PROXY_AUTHORIZATION, &proxyvars->authstring);
240 if (proxyvars->doresolve) {
241 /* currently we only resolve to IPv4 addresses. This is in accordance to
242 RFC 2396; however once it becomes clear how IPv6 addresses should be
243 represented in CONNECT commands this code might be extended */
244 host = Gethostbyname(targetname);
245 if (host == NULL) {
246 int level = E_WARN;
247 /* note: cast is req on AIX: */
248 Msg2(level, "gethostbyname(\"%s\"): %s", targetname,
249 h_errno == NETDB_INTERNAL ? strerror(errno) :
250 (char *)hstrerror(h_errno)/*0 h_messages[h_errno-1]*/);
252 proxyvars->targetaddr = strdup(targetname);
253 } else {
254 #define LEN 16 /* www.xxx.yyy.zzz\0 */
255 if ((proxyvars->targetaddr = Malloc(LEN)) == NULL) {
256 return STAT_RETRYLATER;
258 snprintf(proxyvars->targetaddr, LEN, "%u.%u.%u.%u",
259 (unsigned char)host->h_addr_list[0][0],
260 (unsigned char)host->h_addr_list[0][1],
261 (unsigned char)host->h_addr_list[0][2],
262 (unsigned char)host->h_addr_list[0][3]);
263 #undef LEN
265 } else {
266 proxyvars->targetaddr = strdup(targetname);
269 proxyvars->targetport = htons(parseport(targetport, IPPROTO_TCP));
271 return STAT_OK;
274 int _xioopen_proxy_connect(struct single *xfd,
275 struct proxyvars *proxyvars,
276 int level) {
277 size_t offset;
278 char request[CONNLEN];
279 char buff[BUFLEN+1];
280 #if CONNLEN > BUFLEN
281 #error not enough buffer space
282 #endif
283 char textbuff[2*BUFLEN+1]; /* just for sanitizing print data */
284 char *eol = buff;
285 int state;
286 ssize_t sresult;
288 /* generate proxy request header - points to final target */
289 sprintf(request, "CONNECT %s:%u HTTP/1.0\r\n",
290 proxyvars->targetaddr, proxyvars->targetport);
292 /* send proxy CONNECT request (target addr+port) */
293 * xiosanitize(request, strlen(request), textbuff) = '\0';
294 Info1("sending \"%s\"", textbuff);
295 /* write errors are assumed to always be hard errors, no retry */
296 do {
297 sresult = Write(xfd->fd, request, strlen(request));
298 } while (sresult < 0 && errno == EINTR);
299 if (sresult < 0) {
300 Msg4(level, "write(%d, %p, "F_Zu"): %s",
301 xfd->fd, request, strlen(request), strerror(errno));
302 if (Close(xfd->fd) < 0) {
303 Info2("close(%d): %s", xfd->fd, strerror(errno));
305 return STAT_RETRYLATER;
308 if (proxyvars->authstring) {
309 /* send proxy authentication header */
310 # define XIOAUTHHEAD "Proxy-authorization: Basic "
311 # define XIOAUTHLEN 27
312 static const char *authhead = XIOAUTHHEAD;
313 # define HEADLEN 256
314 char *header, *next;
316 /* ...\r\n\0 */
317 if ((header =
318 Malloc(XIOAUTHLEN+((strlen(proxyvars->authstring)+2)/3)*4+3))
319 == NULL) {
320 return -1;
322 strcpy(header, authhead);
323 next = xiob64encodeline(proxyvars->authstring,
324 strlen(proxyvars->authstring),
325 strchr(header, '\0'));
326 *next = '\0';
327 Info1("sending \"%s\\r\\n\"", header);
328 *next++ = '\r'; *next++ = '\n'; *next++ = '\0';
329 do {
330 sresult = Write(xfd->fd, header, strlen(header));
331 } while (sresult < 0 && errno == EINTR);
332 if (sresult < 0) {
333 Msg4(level, "write(%d, %p, "F_Zu"): %s",
334 xfd->fd, header, strlen(header), strerror(errno));
335 if (Close(xfd->fd) < 0) {
336 Info2("close(%d): %s", xfd->fd, strerror(errno));
338 return STAT_RETRYLATER;
341 free(header);
344 Info("sending \"\\r\\n\"");
345 do {
346 sresult = Write(xfd->fd, "\r\n", 2);
347 } while (sresult < 0 && errno == EINTR);
348 /*! */
350 /* request is kept for later error messages */
351 *strstr(request, " HTTP") = '\0';
353 /* receive proxy answer; looks like "HTTP/1.0 200 .*\r\nHeaders..\r\n\r\n" */
354 /* socat version 1 depends on a valid fd for data transfer; address
355 therefore cannot buffer data. So, to prevent reading beyond the end of
356 the answer headers, only single bytes are read. puh. */
357 state = XIOSTATE_HTTP1;
358 offset = 0; /* up to where the buffer is filled (relative) */
359 /*eol;*/ /* points to the first lineterm of the current line */
360 do {
361 sresult = xioproxy_recvbytes(xfd, buff+offset, 1, level);
362 if (sresult <= 0) {
363 state = XIOSTATE_ERROR;
364 break; /* leave read cycles */
367 switch (state) {
369 case XIOSTATE_HTTP1:
370 /* 0 or more bytes of first line received, no '\r' yet */
371 if (*(buff+offset) == '\r') {
372 eol = buff+offset;
373 state = XIOSTATE_HTTP2;
374 break;
376 if (proxyvars->ignorecr && *(buff+offset) == '\n') {
377 eol = buff+offset;
378 state = XIOSTATE_HTTP3;
379 break;
381 break;
383 case XIOSTATE_HTTP2:
384 /* first line received including '\r' */
385 if (*(buff+offset) != '\n') {
386 state = XIOSTATE_HTTP1;
387 break;
389 state = XIOSTATE_HTTP3;
390 break;
392 case XIOSTATE_HTTP3:
393 /* received status (first line) and "\r\n" */
394 if (*(buff+offset) == '\r') {
395 state = XIOSTATE_HTTP7;
396 break;
398 if (proxyvars->ignorecr && *(buff+offset) == '\n') {
399 state = XIOSTATE_HTTP8;
400 break;
402 state = XIOSTATE_HTTP4;
403 break;
405 case XIOSTATE_HTTP4:
406 /* within header */
407 if (*(buff+offset) == '\r') {
408 eol = buff+offset;
409 state = XIOSTATE_HTTP5;
410 break;
412 if (proxyvars->ignorecr && *(buff+offset) == '\n') {
413 eol = buff+offset;
414 state = XIOSTATE_HTTP6;
415 break;
417 break;
419 case XIOSTATE_HTTP5:
420 /* within header, '\r' received */
421 if (*(buff+offset) != '\n') {
422 state = XIOSTATE_HTTP4;
423 break;
425 state = XIOSTATE_HTTP6;
426 break;
428 case XIOSTATE_HTTP6:
429 /* received status (first line) and 1 or more headers, "\r\n" */
430 if (*(buff+offset) == '\r') {
431 state = XIOSTATE_HTTP7;
432 break;
434 if (proxyvars->ignorecr && *(buff+offset) == '\n') {
435 state = XIOSTATE_HTTP8;
436 break;
438 state = XIOSTATE_HTTP4;
439 break;
441 case XIOSTATE_HTTP7:
442 /* received status (first line), 0 or more headers, "\r\n\r" */
443 if (*(buff+offset) == '\n') {
444 state = XIOSTATE_HTTP8;
445 break;
447 if (*(buff+offset) == '\r') {
448 if (proxyvars->ignorecr) {
449 break; /* ignore it, keep waiting for '\n' */
450 } else {
451 state = XIOSTATE_HTTP5;
453 break;
455 state = XIOSTATE_HTTP4;
456 break;
459 ++offset;
461 /* end of status line reached */
462 if (state == XIOSTATE_HTTP3) {
463 char *ptr;
464 /* set a terminating null - on or after CRLF? */
465 *(buff+offset) = '\0';
467 * xiosanitize(buff, Min(offset, (sizeof(textbuff)-1)>>1), textbuff)
468 = '\0';
469 Info1("proxy_connect: received answer \"%s\"", textbuff);
470 *eol = '\0';
471 * xiosanitize(buff, Min(strlen(buff), (sizeof(textbuff)-1)>>1),
472 textbuff) = '\0';
473 if (strncmp(buff, "HTTP/1.0 ", 9) &&
474 strncmp(buff, "HTTP/1.1 ", 9)) {
475 /* invalid answer */
476 Msg1(level, "proxy: invalid answer \"%s\"", textbuff);
477 return STAT_RETRYLATER;
479 ptr = buff+9;
481 /* skip multiple spaces */
482 while (*ptr == ' ') ++ptr;
484 /* HTTP answer */
485 if (strncmp(ptr, "200", 3)) {
486 /* not ok */
487 /* CERN:
488 "HTTP/1.0 200 Connection established"
489 "HTTP/1.0 400 Invalid request "CONNECT 10.244.9.3:8080 HTTP/1.0" (unknown method)"
490 "HTTP/1.0 403 Forbidden - by rule"
491 "HTTP/1.0 407 Proxy Authentication Required"
492 Proxy-Authenticate: Basic realm="Squid proxy-caching web server"
493 > 50 72 6f 78 79 2d 61 75 74 68 6f 72 69 7a 61 74 Proxy-authorizat
494 > 69 6f 6e 3a 20 42 61 73 69 63 20 61 57 4e 6f 63 ion: Basic aWNoc
495 > 32 56 73 59 6e 4e 30 4f 6e 4e 30 63 6d 56 75 5a 2VsYnN0OnN0cmVuZ
496 > 32 64 6c 61 47 56 70 62 51 3d 3d 0d 0a 2dlaGVpbQ==..
497 b64encode("username:password")
498 "HTTP/1.0 500 Can't connect to host"
500 /* Squid:
501 "HTTP/1.0 400 Bad Request"
502 "HTTP/1.0 403 Forbidden"
503 "HTTP/1.0 503 Service Unavailable"
504 interesting header: "X-Squid-Error: ERR_CONNECT_FAIL 111" */
505 /* Apache:
506 "HTTP/1.0 400 Bad Request"
507 "HTTP/1.1 405 Method Not Allowed"
509 /* WTE:
510 "HTTP/1.1 200 Connection established"
511 "HTTP/1.1 404 Host not found or not responding, errno: 79"
512 "HTTP/1.1 404 Host not found or not responding, errno: 32"
513 "HTTP/1.1 404 Host not found or not responding, errno: 13"
515 /* IIS:
516 "HTTP/1.1 404 Object Not Found"
518 ptr += 3;
519 while (*ptr == ' ') ++ptr;
521 Msg2(level, "%s: %s", request, ptr);
522 return STAT_RETRYLATER;
525 /* ok!! */
526 /* "HTTP/1.0 200 Connection established" */
527 /*Info1("proxy: \"%s\"", textbuff+13);*/
528 offset = 0;
530 } else if (state == XIOSTATE_HTTP6) {
531 /* end of a header line reached */
532 char *endp;
534 /* set a terminating null */
535 *(buff+offset) = '\0';
537 endp =
538 xiosanitize(buff, Min(offset, (sizeof(textbuff)-1)>>1),
539 textbuff);
540 *endp = '\0';
541 Info1("proxy_connect: received header \"%s\"", textbuff);
542 offset = 0;
545 } while (state != XIOSTATE_HTTP8 && offset < BUFLEN);
547 if (state == XIOSTATE_ERROR) {
548 return STAT_RETRYLATER;
551 if (offset >= BUFLEN) {
552 Msg1(level, "proxy answer exceeds "F_Zu" bytes, aborting", BUFLEN);
553 return STAT_NORETRY;
556 return STAT_OK;
559 #endif /* WITH_PROXY */