version 1.7.3.0
[socat.git] / xio-proxy.c
blob63582ccb5cd92b8c87a015b9747af052d121786a
1 /* source: xio-proxy.c */
2 /* Copyright Gerhard Rieger */
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]; /* HTTP connection request line */
279 int rv;
280 char buff[BUFLEN+1]; /* for receiving HTTP reply headers */
281 #if CONNLEN > BUFLEN
282 #error not enough buffer space
283 #endif
284 char textbuff[2*BUFLEN+1]; /* just for sanitizing print data */
285 char *eol = buff;
286 int state;
287 ssize_t sresult;
289 /* generate proxy request header - points to final target */
290 rv = snprintf(request, CONNLEN, "CONNECT %s:%u HTTP/1.0\r\n",
291 proxyvars->targetaddr, proxyvars->targetport);
292 if (rv >= CONNLEN || rv < 0) {
293 Error("_xioopen_proxy_connect(): PROXY CONNECT buffer too small");
294 return -1;
297 /* send proxy CONNECT request (target addr+port) */
298 * xiosanitize(request, strlen(request), textbuff) = '\0';
299 Info1("sending \"%s\"", textbuff);
300 /* write errors are assumed to always be hard errors, no retry */
301 if (writefull(xfd->fd, request, strlen(request)) < 0) {
302 Msg4(level, "write(%d, %p, "F_Zu"): %s",
303 xfd->fd, request, strlen(request), strerror(errno));
304 if (Close(xfd->fd) < 0) {
305 Info2("close(%d): %s", xfd->fd, strerror(errno));
307 return STAT_RETRYLATER;
310 if (proxyvars->authstring) {
311 /* send proxy authentication header */
312 # define XIOAUTHHEAD "Proxy-authorization: Basic "
313 # define XIOAUTHLEN 27
314 static const char *authhead = XIOAUTHHEAD;
315 # define HEADLEN 256
316 char *header, *next;
318 /* ...\r\n\0 */
319 if ((header =
320 Malloc(XIOAUTHLEN+((strlen(proxyvars->authstring)+2)/3)*4+3))
321 == NULL) {
322 return -1;
324 strcpy(header, authhead);
325 next = xiob64encodeline(proxyvars->authstring,
326 strlen(proxyvars->authstring),
327 strchr(header, '\0'));
328 *next = '\0';
329 Info1("sending \"%s\\r\\n\"", header);
330 *next++ = '\r'; *next++ = '\n'; *next++ = '\0';
331 if (writefull(xfd->fd, header, strlen(header)) < 0) {
332 Msg4(level, "write(%d, %p, "F_Zu"): %s",
333 xfd->fd, header, strlen(header), strerror(errno));
334 if (Close(xfd->fd) < 0) {
335 Info2("close(%d): %s", xfd->fd, strerror(errno));
337 return STAT_RETRYLATER;
340 free(header);
343 Info("sending \"\\r\\n\"");
344 if (writefull(xfd->fd, "\r\n", 2) < 0) {
345 Msg2(level, "write(%d, \"\\r\\n\", 2): %s",
346 xfd->fd, strerror(errno));
347 if (Close(xfd->fd) < 0) {
348 Info2("close(%d): %s", xfd->fd, strerror(errno));
350 return STAT_RETRYLATER;
353 /* request is kept for later error messages */
354 *strstr(request, " HTTP") = '\0';
356 /* receive proxy answer; looks like "HTTP/1.0 200 .*\r\nHeaders..\r\n\r\n" */
357 /* socat version 1 depends on a valid fd for data transfer; address
358 therefore cannot buffer data. So, to prevent reading beyond the end of
359 the answer headers, only single bytes are read. puh. */
360 state = XIOSTATE_HTTP1;
361 offset = 0; /* up to where the buffer is filled (relative) */
362 /*eol;*/ /* points to the first lineterm of the current line */
363 do {
364 sresult = xioproxy_recvbytes(xfd, buff+offset, 1, level);
365 if (sresult <= 0) {
366 state = XIOSTATE_ERROR;
367 break; /* leave read cycles */
370 switch (state) {
372 case XIOSTATE_HTTP1:
373 /* 0 or more bytes of first line received, no '\r' yet */
374 if (*(buff+offset) == '\r') {
375 eol = buff+offset;
376 state = XIOSTATE_HTTP2;
377 break;
379 if (proxyvars->ignorecr && *(buff+offset) == '\n') {
380 eol = buff+offset;
381 state = XIOSTATE_HTTP3;
382 break;
384 break;
386 case XIOSTATE_HTTP2:
387 /* first line received including '\r' */
388 if (*(buff+offset) != '\n') {
389 state = XIOSTATE_HTTP1;
390 break;
392 state = XIOSTATE_HTTP3;
393 break;
395 case XIOSTATE_HTTP3:
396 /* received status (first line) and "\r\n" */
397 if (*(buff+offset) == '\r') {
398 state = XIOSTATE_HTTP7;
399 break;
401 if (proxyvars->ignorecr && *(buff+offset) == '\n') {
402 state = XIOSTATE_HTTP8;
403 break;
405 state = XIOSTATE_HTTP4;
406 break;
408 case XIOSTATE_HTTP4:
409 /* within header */
410 if (*(buff+offset) == '\r') {
411 eol = buff+offset;
412 state = XIOSTATE_HTTP5;
413 break;
415 if (proxyvars->ignorecr && *(buff+offset) == '\n') {
416 eol = buff+offset;
417 state = XIOSTATE_HTTP6;
418 break;
420 break;
422 case XIOSTATE_HTTP5:
423 /* within header, '\r' received */
424 if (*(buff+offset) != '\n') {
425 state = XIOSTATE_HTTP4;
426 break;
428 state = XIOSTATE_HTTP6;
429 break;
431 case XIOSTATE_HTTP6:
432 /* received status (first line) and 1 or more headers, "\r\n" */
433 if (*(buff+offset) == '\r') {
434 state = XIOSTATE_HTTP7;
435 break;
437 if (proxyvars->ignorecr && *(buff+offset) == '\n') {
438 state = XIOSTATE_HTTP8;
439 break;
441 state = XIOSTATE_HTTP4;
442 break;
444 case XIOSTATE_HTTP7:
445 /* received status (first line), 0 or more headers, "\r\n\r" */
446 if (*(buff+offset) == '\n') {
447 state = XIOSTATE_HTTP8;
448 break;
450 if (*(buff+offset) == '\r') {
451 if (proxyvars->ignorecr) {
452 break; /* ignore it, keep waiting for '\n' */
453 } else {
454 state = XIOSTATE_HTTP5;
456 break;
458 state = XIOSTATE_HTTP4;
459 break;
462 ++offset;
464 /* end of status line reached */
465 if (state == XIOSTATE_HTTP3) {
466 char *ptr;
467 /* set a terminating null - on or after CRLF? */
468 *(buff+offset) = '\0';
470 * xiosanitize(buff, Min(offset, (sizeof(textbuff)-1)>>1), textbuff)
471 = '\0';
472 Info1("proxy_connect: received answer \"%s\"", textbuff);
473 *eol = '\0';
474 * xiosanitize(buff, Min(strlen(buff), (sizeof(textbuff)-1)>>1),
475 textbuff) = '\0';
476 if (strncmp(buff, "HTTP/1.0 ", 9) &&
477 strncmp(buff, "HTTP/1.1 ", 9)) {
478 /* invalid answer */
479 Msg1(level, "proxy: invalid answer \"%s\"", textbuff);
480 return STAT_RETRYLATER;
482 ptr = buff+9;
484 /* skip multiple spaces */
485 while (*ptr == ' ') ++ptr;
487 /* HTTP answer */
488 if (strncmp(ptr, "200", 3)) {
489 /* not ok */
490 /* CERN:
491 "HTTP/1.0 200 Connection established"
492 "HTTP/1.0 400 Invalid request "CONNECT 10.244.9.3:8080 HTTP/1.0" (unknown method)"
493 "HTTP/1.0 403 Forbidden - by rule"
494 "HTTP/1.0 407 Proxy Authentication Required"
495 Proxy-Authenticate: Basic realm="Squid proxy-caching web server"
496 > 50 72 6f 78 79 2d 61 75 74 68 6f 72 69 7a 61 74 Proxy-authorizat
497 > 69 6f 6e 3a 20 42 61 73 69 63 20 61 57 4e 6f 63 ion: Basic aWNoc
498 > 32 56 73 59 6e 4e 30 4f 6e 4e 30 63 6d 56 75 5a 2VsYnN0OnN0cmVuZ
499 > 32 64 6c 61 47 56 70 62 51 3d 3d 0d 0a 2dlaGVpbQ==..
500 b64encode("username:password")
501 "HTTP/1.0 500 Can't connect to host"
503 /* Squid:
504 "HTTP/1.0 400 Bad Request"
505 "HTTP/1.0 403 Forbidden"
506 "HTTP/1.0 503 Service Unavailable"
507 interesting header: "X-Squid-Error: ERR_CONNECT_FAIL 111" */
508 /* Apache:
509 "HTTP/1.0 400 Bad Request"
510 "HTTP/1.1 405 Method Not Allowed"
512 /* WTE:
513 "HTTP/1.1 200 Connection established"
514 "HTTP/1.1 404 Host not found or not responding, errno: 79"
515 "HTTP/1.1 404 Host not found or not responding, errno: 32"
516 "HTTP/1.1 404 Host not found or not responding, errno: 13"
518 /* IIS:
519 "HTTP/1.1 404 Object Not Found"
521 ptr += 3;
522 while (*ptr == ' ') ++ptr;
524 Msg2(level, "%s: %s", request, ptr);
525 return STAT_RETRYLATER;
528 /* ok!! */
529 /* "HTTP/1.0 200 Connection established" */
530 /*Info1("proxy: \"%s\"", textbuff+13);*/
531 offset = 0;
533 } else if (state == XIOSTATE_HTTP6) {
534 /* end of a header line reached */
535 char *endp;
537 /* set a terminating null */
538 *(buff+offset) = '\0';
540 endp =
541 xiosanitize(buff, Min(offset, (sizeof(textbuff)-1)>>1),
542 textbuff);
543 *endp = '\0';
544 Info1("proxy_connect: received header \"%s\"", textbuff);
545 offset = 0;
548 } while (state != XIOSTATE_HTTP8 && offset < BUFLEN);
550 if (state == XIOSTATE_ERROR) {
551 return STAT_RETRYLATER;
554 if (offset >= BUFLEN) {
555 Msg1(level, "proxy answer exceeds %d bytes, aborting", BUFLEN);
556 return STAT_NORETRY;
559 return STAT_OK;
562 #endif /* WITH_PROXY */