Check which getprotobynumber_r() variant to use
[socat.git] / xio-proxy.c
blob67a9d8cc30b1814a0cf56b50b17804a233ec2b4b
1 /* source: xio-proxy.c */
2 /* Copyright Gerhard Rieger and contributors (see file CHANGES) */
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-ip.h"
15 #include "xio-ipapp.h"
16 #include "xio-ascii.h" /* for base64 encoding of authentication */
18 #include "xio-proxy.h"
21 #define PROXYPORT "8080"
23 static int xioopen_proxy_connect(int argc, const char *argv[], struct opt *opts,
24 int xioflags, xiofile_t *fd,
25 unsigned groups, int dummy1, int dummy2,
26 int dummy3);
28 const struct optdesc opt_proxyport = { "proxyport", NULL, OPT_PROXYPORT, GROUP_HTTP, PH_LATE, TYPE_STRING, OFUNC_SPEC };
29 const struct optdesc opt_ignorecr = { "ignorecr", NULL, OPT_IGNORECR, GROUP_HTTP, PH_LATE, TYPE_BOOL, OFUNC_SPEC };
30 const struct optdesc opt_proxy_resolve = { "proxy-resolve", "resolve", OPT_PROXY_RESOLVE, GROUP_HTTP, PH_LATE, TYPE_BOOL, OFUNC_SPEC };
31 const struct optdesc opt_proxy_authorization = { "proxy-authorization", "proxyauth", OPT_PROXY_AUTHORIZATION, GROUP_HTTP, PH_LATE, TYPE_STRING, OFUNC_SPEC };
32 const struct optdesc opt_proxy_authorization_file = { "proxy-authorization-file", "proxyauthfile", OPT_PROXY_AUTHORIZATION_FILE, GROUP_HTTP, PH_LATE, TYPE_STRING, OFUNC_SPEC };
34 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>") };
37 /*0#define CONNLEN 40*/ /* "CONNECT 123.156.189.123:65432 HTTP/1.0\r\n\0" */
38 #define CONNLEN 281 /* "CONNECT <255bytes>:65432 HTTP/1.0\r\n\0" */
40 /* states during receiving answer */
41 enum {
42 XIOSTATE_HTTP1, /* 0 or more bytes of first line received, no \r */
43 XIOSTATE_HTTP2, /* first line received including \r */
44 XIOSTATE_HTTP3, /* received status and \r\n */
45 XIOSTATE_HTTP4, /* within header */
46 XIOSTATE_HTTP5, /* within header, \r */
47 XIOSTATE_HTTP6, /* received status and 1 or more headers, \r\n */
48 XIOSTATE_HTTP7, /* received status line, ev. headers, \r\n\r */
49 XIOSTATE_HTTP8, /* complete answer received */
50 XIOSTATE_ERROR /* error during HTTP headers */
51 } ;
54 /* get buflen bytes from proxy server;
55 handles EINTR;
56 returns <0 when error occurs
58 static ssize_t
59 xioproxy_recvbytes(struct single *xfd, char *buff, size_t buflen, int level) {
60 ssize_t result;
61 do {
62 /* we need at least buflen bytes... */
63 result = Read(xfd->fd, buff, buflen);
64 } while (result < 0 && errno == EINTR); /*! EAGAIN? */
65 if (result < 0) {
66 Msg4(level, "read(%d, %p, "F_Zu"): %s",
67 xfd->fd, buff, buflen, strerror(errno));
68 return result;
70 if (result == 0) {
71 Msg(level, "proxy_connect: connection closed by proxy");
73 return result;
77 #define BUFLEN 2048
80 static int xioopen_proxy_connect(int argc, const char *argv[], struct opt *opts,
81 int xioflags, xiofile_t *xxfd,
82 unsigned groups, int dummy1, int dummy2,
83 int dummy3) {
84 /* we expect the form: host:host:port */
85 struct single *xfd = &xxfd->stream;
86 struct opt *opts0 = NULL;
87 struct proxyvars struct_proxyvars = { 0 }, *proxyvars = &struct_proxyvars;
88 /* variables to be filled with address option values */
89 bool dofork = false;
90 /* */
91 int pf = PF_UNSPEC;
92 union sockaddr_union us_sa, *us = &us_sa;
93 union sockaddr_union them_sa, *them = &them_sa;
94 socklen_t uslen = sizeof(us_sa);
95 socklen_t themlen = sizeof(them_sa);
96 const char *proxyname; char *proxyport = NULL;
97 const char *targetname, *targetport;
98 int ipproto = IPPROTO_TCP;
99 bool needbind = false;
100 bool lowport = false;
101 int socktype = SOCK_STREAM;
102 int level;
103 int result;
105 if (argc != 4) {
106 Error1("%s: 3 parameters required", argv[0]);
107 return STAT_NORETRY;
109 proxyname = argv[1];
110 targetname = argv[2];
111 targetport = argv[3];
113 xfd->howtoend = END_SHUTDOWN;
114 if (applyopts_single(xfd, opts, PH_INIT) < 0) return -1;
115 applyopts(-1, opts, PH_INIT);
117 retropt_int(opts, OPT_SO_TYPE, &socktype);
119 retropt_bool(opts, OPT_FORK, &dofork);
121 if (retropt_string(opts, OPT_PROXYPORT, &proxyport) < 0) {
122 if ((proxyport = strdup(PROXYPORT)) == NULL) {
123 errno = ENOMEM; return -1;
127 result = _xioopen_proxy_prepare(proxyvars, opts, targetname, targetport);
128 if (result != STAT_OK) return result;
130 result =
131 _xioopen_ipapp_prepare(opts, &opts0, proxyname, proxyport,
132 &pf, ipproto,
133 xfd->para.socket.ip.res_opts[1],
134 xfd->para.socket.ip.res_opts[0],
135 them, &themlen, us, &uslen,
136 &needbind, &lowport, socktype);
137 if (result != STAT_OK) return result;
139 Notice4("opening connection to %s:%u via proxy %s:%s",
140 proxyvars->targetaddr, proxyvars->targetport, proxyname, proxyport);
142 do { /* loop over failed connect and proxy connect attempts */
144 #if WITH_RETRY
145 if (xfd->forever || xfd->retry) {
146 level = E_INFO;
147 } else
148 #endif /* WITH_RETRY */
149 level = E_ERROR;
151 result =
152 _xioopen_connect(xfd,
153 needbind?us:NULL, sizeof(*us),
154 (struct sockaddr *)them, themlen,
155 opts, pf, socktype, IPPROTO_TCP, lowport, level);
156 switch (result) {
157 case STAT_OK: break;
158 #if WITH_RETRY
159 case STAT_RETRYLATER:
160 case STAT_RETRYNOW:
161 if (xfd->forever || xfd->retry--) {
162 if (result == STAT_RETRYLATER) Nanosleep(&xfd->intervall, NULL);
163 continue;
165 #endif /* WITH_RETRY */
166 default:
167 return result;
170 applyopts(xfd->fd, opts, PH_ALL);
172 if ((result = _xio_openlate(xfd, opts)) < 0)
173 return result;
175 result = _xioopen_proxy_connect(xfd, proxyvars, level);
176 switch (result) {
177 case STAT_OK: break;
178 #if WITH_RETRY
179 case STAT_RETRYLATER:
180 case STAT_RETRYNOW:
181 if (xfd->forever || xfd->retry--) {
182 if (result == STAT_RETRYLATER) Nanosleep(&xfd->intervall, NULL);
183 continue;
185 #endif /* WITH_RETRY */
186 default:
187 return result;
190 if (dofork) {
191 xiosetchilddied(); /* set SIGCHLD handler */
194 #if WITH_RETRY
195 if (dofork) {
196 pid_t pid;
197 int level = E_ERROR;
198 if (xfd->forever || xfd->retry) {
199 level = E_WARN;
201 while ((pid = xio_fork(false, level)) < 0) {
202 if (xfd->forever || --xfd->retry) {
203 Nanosleep(&xfd->intervall, NULL); continue;
205 return STAT_RETRYLATER;
208 if (pid == 0) { /* child process */
209 xfd->forever = false; xfd->retry = 0;
210 break;
213 /* parent process */
214 Close(xfd->fd);
215 Nanosleep(&xfd->intervall, NULL);
216 dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL);
217 continue;
218 } else
219 #endif /* WITH_RETRY */
221 break;
224 } while (true); /* end of complete open loop - drop out on success */
226 Notice4("successfully connected to %s:%u via proxy %s:%s",
227 proxyvars->targetaddr, proxyvars->targetport,
228 proxyname, proxyport);
230 return 0;
234 int _xioopen_proxy_prepare(struct proxyvars *proxyvars, struct opt *opts,
235 const char *targetname, const char *targetport) {
236 union sockaddr_union host;
237 socklen_t socklen = sizeof(host);
238 int rc;
240 retropt_bool(opts, OPT_IGNORECR, &proxyvars->ignorecr);
241 retropt_bool(opts, OPT_PROXY_RESOLVE, &proxyvars->doresolve);
242 retropt_string(opts, OPT_PROXY_AUTHORIZATION, &proxyvars->authstring);
243 retropt_string(opts, OPT_PROXY_AUTHORIZATION_FILE, &proxyvars->authfile);
245 if (proxyvars->authfile) {
246 int authfd;
247 off_t length;
248 ssize_t bytes;
250 /* if we have a file containing authentication credentials and they were also
251 provided on the command line, something is misspecified */
252 if (proxyvars->authstring) {
253 Error("Only one of options proxy-authorization and proxy-authorization-file allowed");
254 return STAT_NORETRY;
256 authfd = Open(proxyvars->authfile, O_RDONLY, 0);
257 if (authfd < 0) {
258 Error2("open(\"%s\", O_RDONLY): %s", proxyvars->authfile, strerror(errno));
259 return STAT_NORETRY;
261 /* go to the end of our proxy auth file to
262 figure out how long our proxy auth is */
263 if ((length = Lseek(authfd, 0, SEEK_END)) < 0) {
264 Error2("lseek(<%s>, 0, SEEK_END): %s",
265 proxyvars->authfile, strerror(errno));
266 return STAT_RETRYLATER;
268 proxyvars->authstring = Malloc(length+1);
269 /* go back to the beginning */
270 Lseek(authfd, 0, SEEK_SET);
271 /* read our proxy info from the file */
272 if ((bytes = Read(authfd, proxyvars->authstring, (size_t) length)) < 0) {
273 Error3("read(<%s>, , "F_Zu"): %s", proxyvars->authfile, length, strerror(errno));
274 free(proxyvars->authstring);
275 Close(authfd);
276 return STAT_NORETRY;
278 if (bytes < length) {
279 Error3("read(<%s>, , "F_Zu"): got only "F_Zu" bytes",
280 proxyvars->authfile, length, bytes);
281 Close(authfd);
282 return STAT_NORETRY;
284 proxyvars->authstring[bytes] = '\0'; /* string termination */
285 Close(authfd);
288 if (proxyvars->doresolve) {
289 /* currently we only resolve to IPv4 addresses. This is in accordance to
290 RFC 2396; however once it becomes clear how IPv6 addresses should be
291 represented in CONNECT commands this code might be extended */
292 rc = xiogetaddrinfo(targetname, targetport, PF_UNSPEC,
293 SOCK_STREAM, IPPROTO_TCP,
294 &host, &socklen, 0, 0);
295 if (rc != STAT_OK) {
296 proxyvars->targetaddr = strdup(targetname);
297 } else {
298 #define LEN 16 /* www.xxx.yyy.zzz\0 */
299 if ((proxyvars->targetaddr = Malloc(LEN)) == NULL) {
300 return STAT_RETRYLATER;
302 snprintf(proxyvars->targetaddr, LEN, "%u.%u.%u.%u",
303 ((unsigned char *)&host.ip4.sin_addr.s_addr)[0],
304 ((unsigned char *)&host.ip4.sin_addr.s_addr)[1],
305 ((unsigned char *)&host.ip4.sin_addr.s_addr)[2],
306 ((unsigned char *)&host.ip4.sin_addr.s_addr)[3]);
307 #undef LEN
309 } else {
310 proxyvars->targetaddr = strdup(targetname);
313 proxyvars->targetport = htons(parseport(targetport, IPPROTO_TCP));
315 return STAT_OK;
318 int _xioopen_proxy_connect(struct single *xfd,
319 struct proxyvars *proxyvars,
320 int level) {
321 size_t offset;
322 char request[CONNLEN]; /* HTTP connection request line */
323 int rv;
324 char buff[BUFLEN+1]; /* for receiving HTTP reply headers */
325 #if CONNLEN > BUFLEN
326 #error not enough buffer space
327 #endif
328 char textbuff[2*BUFLEN+1]; /* just for sanitizing print data */
329 char *eol = buff;
330 int state;
331 ssize_t sresult;
333 /* generate proxy request header - points to final target */
334 rv = snprintf(request, CONNLEN, "CONNECT %s:%u HTTP/1.0\r\n",
335 proxyvars->targetaddr, proxyvars->targetport);
336 if (rv >= CONNLEN || rv < 0) {
337 Error("_xioopen_proxy_connect(): PROXY CONNECT buffer too small");
338 return -1;
341 /* send proxy CONNECT request (target addr+port) */
342 * xiosanitize(request, strlen(request), textbuff) = '\0';
343 Info1("sending \"%s\"", textbuff);
344 /* write errors are assumed to always be hard errors, no retry */
345 if (writefull(xfd->fd, request, strlen(request)) < 0) {
346 Msg4(level, "write(%d, %p, "F_Zu"): %s",
347 xfd->fd, request, strlen(request), strerror(errno));
348 if (Close(xfd->fd) < 0) {
349 Info2("close(%d): %s", xfd->fd, strerror(errno));
351 return STAT_RETRYLATER;
354 if (proxyvars->authstring) {
355 /* send proxy authentication header */
356 # define XIOAUTHHEAD "Proxy-authorization: Basic "
357 # define XIOAUTHLEN 27
358 static const char *authhead = XIOAUTHHEAD;
359 # define HEADLEN 256
360 char *header, *next;
362 /* ...\r\n\0 */
363 if ((header =
364 Malloc(XIOAUTHLEN+((strlen(proxyvars->authstring)+2)/3)*4+3))
365 == NULL) {
366 return -1;
368 strcpy(header, authhead);
369 next = xiob64encodeline(proxyvars->authstring,
370 strlen(proxyvars->authstring),
371 strchr(header, '\0'));
372 *next = '\0';
373 Info1("sending \"%s\\r\\n\"", header);
374 *next++ = '\r'; *next++ = '\n'; *next++ = '\0';
375 if (writefull(xfd->fd, header, strlen(header)) < 0) {
376 Msg4(level, "write(%d, %p, "F_Zu"): %s",
377 xfd->fd, header, strlen(header), strerror(errno));
378 if (Close(xfd->fd) < 0) {
379 Info2("close(%d): %s", xfd->fd, strerror(errno));
381 return STAT_RETRYLATER;
384 free(header);
387 Info("sending \"\\r\\n\"");
388 if (writefull(xfd->fd, "\r\n", 2) < 0) {
389 Msg2(level, "write(%d, \"\\r\\n\", 2): %s",
390 xfd->fd, strerror(errno));
391 if (Close(xfd->fd) < 0) {
392 Info2("close(%d): %s", xfd->fd, strerror(errno));
394 return STAT_RETRYLATER;
397 /* request is kept for later error messages */
398 *strstr(request, " HTTP") = '\0';
400 /* receive proxy answer; looks like "HTTP/1.0 200 .*\r\nHeaders..\r\n\r\n" */
401 /* socat version 1 depends on a valid fd for data transfer; address
402 therefore cannot buffer data. So, to prevent reading beyond the end of
403 the answer headers, only single bytes are read. puh. */
404 state = XIOSTATE_HTTP1;
405 offset = 0; /* up to where the buffer is filled (relative) */
406 /*eol;*/ /* points to the first lineterm of the current line */
407 do {
408 sresult = xioproxy_recvbytes(xfd, buff+offset, 1, level);
409 if (sresult <= 0) {
410 state = XIOSTATE_ERROR;
411 break; /* leave read cycles */
414 switch (state) {
416 case XIOSTATE_HTTP1:
417 /* 0 or more bytes of first line received, no '\r' yet */
418 if (*(buff+offset) == '\r') {
419 eol = buff+offset;
420 state = XIOSTATE_HTTP2;
421 break;
423 if (proxyvars->ignorecr && *(buff+offset) == '\n') {
424 eol = buff+offset;
425 state = XIOSTATE_HTTP3;
426 break;
428 break;
430 case XIOSTATE_HTTP2:
431 /* first line received including '\r' */
432 if (*(buff+offset) != '\n') {
433 state = XIOSTATE_HTTP1;
434 break;
436 state = XIOSTATE_HTTP3;
437 break;
439 case XIOSTATE_HTTP3:
440 /* received status (first line) and "\r\n" */
441 if (*(buff+offset) == '\r') {
442 state = XIOSTATE_HTTP7;
443 break;
445 if (proxyvars->ignorecr && *(buff+offset) == '\n') {
446 state = XIOSTATE_HTTP8;
447 break;
449 state = XIOSTATE_HTTP4;
450 break;
452 case XIOSTATE_HTTP4:
453 /* within header */
454 if (*(buff+offset) == '\r') {
455 eol = buff+offset;
456 state = XIOSTATE_HTTP5;
457 break;
459 if (proxyvars->ignorecr && *(buff+offset) == '\n') {
460 eol = buff+offset;
461 state = XIOSTATE_HTTP6;
462 break;
464 break;
466 case XIOSTATE_HTTP5:
467 /* within header, '\r' received */
468 if (*(buff+offset) != '\n') {
469 state = XIOSTATE_HTTP4;
470 break;
472 state = XIOSTATE_HTTP6;
473 break;
475 case XIOSTATE_HTTP6:
476 /* received status (first line) and 1 or more headers, "\r\n" */
477 if (*(buff+offset) == '\r') {
478 state = XIOSTATE_HTTP7;
479 break;
481 if (proxyvars->ignorecr && *(buff+offset) == '\n') {
482 state = XIOSTATE_HTTP8;
483 break;
485 state = XIOSTATE_HTTP4;
486 break;
488 case XIOSTATE_HTTP7:
489 /* received status (first line), 0 or more headers, "\r\n\r" */
490 if (*(buff+offset) == '\n') {
491 state = XIOSTATE_HTTP8;
492 break;
494 if (*(buff+offset) == '\r') {
495 if (proxyvars->ignorecr) {
496 break; /* ignore it, keep waiting for '\n' */
497 } else {
498 state = XIOSTATE_HTTP5;
500 break;
502 state = XIOSTATE_HTTP4;
503 break;
506 ++offset;
508 /* end of status line reached */
509 if (state == XIOSTATE_HTTP3) {
510 char *ptr;
511 /* set a terminating null - on or after CRLF? */
512 *(buff+offset) = '\0';
514 * xiosanitize(buff, Min(offset, (sizeof(textbuff)-1)>>1), textbuff)
515 = '\0';
516 Info1("proxy_connect: received answer \"%s\"", textbuff);
517 *eol = '\0';
518 * xiosanitize(buff, Min(strlen(buff), (sizeof(textbuff)-1)>>1),
519 textbuff) = '\0';
520 if (strncmp(buff, "HTTP/1.0 ", 9) &&
521 strncmp(buff, "HTTP/1.1 ", 9)) {
522 /* invalid answer */
523 Msg1(level, "proxy: invalid answer \"%s\"", textbuff);
524 return STAT_RETRYLATER;
526 ptr = buff+9;
528 /* skip multiple spaces */
529 while (*ptr == ' ') ++ptr;
531 /* HTTP answer */
532 if (strncmp(ptr, "200", 3)) {
533 /* not ok */
534 /* CERN:
535 "HTTP/1.0 200 Connection established"
536 "HTTP/1.0 400 Invalid request "CONNECT 10.244.9.3:8080 HTTP/1.0" (unknown method)"
537 "HTTP/1.0 403 Forbidden - by rule"
538 "HTTP/1.0 407 Proxy Authentication Required"
539 Proxy-Authenticate: Basic realm="Squid proxy-caching web server"
540 > 50 72 6f 78 79 2d 61 75 74 68 6f 72 69 7a 61 74 Proxy-authorizat
541 > 69 6f 6e 3a 20 42 61 73 69 63 20 61 57 4e 6f 63 ion: Basic aWNoc
542 > 32 56 73 59 6e 4e 30 4f 6e 4e 30 63 6d 56 75 5a 2VsYnN0OnN0cmVuZ
543 > 32 64 6c 61 47 56 70 62 51 3d 3d 0d 0a 2dlaGVpbQ==..
544 b64encode("username:password")
545 "HTTP/1.0 500 Can't connect to host"
547 /* Squid:
548 "HTTP/1.0 400 Bad Request"
549 "HTTP/1.0 403 Forbidden"
550 "HTTP/1.0 503 Service Unavailable"
551 interesting header: "X-Squid-Error: ERR_CONNECT_FAIL 111" */
552 /* Apache:
553 "HTTP/1.0 400 Bad Request"
554 "HTTP/1.1 405 Method Not Allowed"
556 /* WTE:
557 "HTTP/1.1 200 Connection established"
558 "HTTP/1.1 404 Host not found or not responding, errno: 79"
559 "HTTP/1.1 404 Host not found or not responding, errno: 32"
560 "HTTP/1.1 404 Host not found or not responding, errno: 13"
562 /* IIS:
563 "HTTP/1.1 404 Object Not Found"
565 ptr += 3;
566 while (*ptr == ' ') ++ptr;
568 Msg2(level, "%s: %s", request, ptr);
569 return STAT_RETRYLATER;
572 /* ok!! */
573 /* "HTTP/1.0 200 Connection established" */
574 /*Info1("proxy: \"%s\"", textbuff+13);*/
575 offset = 0;
577 } else if (state == XIOSTATE_HTTP6) {
578 /* end of a header line reached */
579 char *endp;
581 /* set a terminating null */
582 *(buff+offset) = '\0';
584 endp =
585 xiosanitize(buff, Min(offset, (sizeof(textbuff)-1)>>1),
586 textbuff);
587 *endp = '\0';
588 Info1("proxy_connect: received header \"%s\"", textbuff);
589 offset = 0;
592 } while (state != XIOSTATE_HTTP8 && offset < BUFLEN);
594 if (state == XIOSTATE_ERROR) {
595 return STAT_RETRYLATER;
598 if (offset >= BUFLEN) {
599 Msg1(level, "proxy answer exceeds %d bytes, aborting", BUFLEN);
600 return STAT_NORETRY;
603 return STAT_OK;
606 #endif /* WITH_PROXY */