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
8 #include "xiosysincludes.h"
13 #include "xio-socket.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
, int xioflags
, xiofile_t
*fd
, const struct addrdesc
*addrdesc
);
25 const struct optdesc opt_proxyport
= { "proxyport", NULL
, OPT_PROXYPORT
, GROUP_HTTP
, PH_LATE
, TYPE_STRING
, OFUNC_SPEC
};
26 const struct optdesc opt_ignorecr
= { "ignorecr", NULL
, OPT_IGNORECR
, GROUP_HTTP
, PH_LATE
, TYPE_BOOL
, OFUNC_SPEC
};
27 const struct optdesc opt_http_version
= { "http-version", NULL
, OPT_HTTP_VERSION
, GROUP_HTTP
, PH_LATE
, TYPE_STRING
, OFUNC_SPEC
};
28 const struct optdesc opt_proxy_resolve
= { "proxy-resolve", "resolve", OPT_PROXY_RESOLVE
, GROUP_HTTP
, PH_LATE
, TYPE_BOOL
, OFUNC_SPEC
};
29 const struct optdesc opt_proxy_authorization
= { "proxy-authorization", "proxyauth", OPT_PROXY_AUTHORIZATION
, GROUP_HTTP
, PH_LATE
, TYPE_STRING
, OFUNC_SPEC
};
30 const struct optdesc opt_proxy_authorization_file
= { "proxy-authorization-file", "proxyauthfile", OPT_PROXY_AUTHORIZATION_FILE
, GROUP_HTTP
, PH_LATE
, TYPE_STRING
, OFUNC_SPEC
};
32 const struct addrdesc xioaddr_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 */
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 */
52 /* get buflen bytes from proxy server;
54 returns <0 when error occurs
57 xioproxy_recvbytes(struct single
*sfd
, char *buff
, size_t buflen
, int level
) {
60 /* we need at least buflen bytes... */
61 result
= Read(sfd
->fd
, buff
, buflen
);
62 } while (result
< 0 && errno
== EINTR
); /*! EAGAIN? */
64 Msg4(level
, "read(%d, %p, "F_Zu
"): %s",
65 sfd
->fd
, buff
, buflen
, strerror(errno
));
69 Msg(level
, "proxy_connect: connection closed by proxy");
78 static int xioopen_proxy_connect(
84 const struct addrdesc
*addrdesc
)
86 /* we expect the form: host:host:port */
87 struct single
*sfd
= &xxfd
->stream
;
88 struct opt
*opts0
= NULL
;
89 struct proxyvars struct_proxyvars
= { 0 }, *proxyvars
= &struct_proxyvars
;
90 /* variables to be filled with address option values */
94 union sockaddr_union us_sa
, *us
= &us_sa
;
95 socklen_t uslen
= sizeof(us_sa
);
96 struct addrinfo
*themlist
, *themp
;
97 struct addrinfo
**ai_sorted
;
99 const char *proxyname
; char *proxyport
= NULL
;
100 const char *targetname
, *targetport
;
101 int ipproto
= IPPROTO_TCP
;
102 bool needbind
= false;
103 bool lowport
= false;
104 int socktype
= SOCK_STREAM
;
109 xio_syntax(argv
[0], 3, argc
-1, addrdesc
->syntax
);
113 targetname
= argv
[2];
114 targetport
= argv
[3];
116 if (sfd
->howtoend
== END_UNSPEC
)
117 sfd
->howtoend
= END_SHUTDOWN
;
118 if (applyopts_single(sfd
, opts
, PH_INIT
) < 0)
120 applyopts(sfd
, 1, opts
, PH_INIT
);
122 retropt_int(opts
, OPT_SO_TYPE
, &socktype
);
124 retropt_bool(opts
, OPT_FORK
, &dofork
);
126 if (retropt_string(opts
, OPT_PROXYPORT
, &proxyport
) < 0) {
127 if ((proxyport
= strdup(PROXYPORT
)) == NULL
) {
128 errno
= ENOMEM
; return -1;
132 result
= _xioopen_proxy_prepare(proxyvars
, opts
, targetname
, targetport
,
133 sfd
->para
.socket
.ip
.ai_flags
);
134 if (result
!= STAT_OK
)
137 Notice4("opening connection to %s:%u via proxy %s:%s",
138 proxyvars
->targetaddr
, proxyvars
->targetport
, proxyname
, proxyport
);
141 do { /* loop over retries (failed connect and proxy-request attempts) */
146 _xioopen_ipapp_prepare(opts
, &opts0
, proxyname
, proxyport
,
148 sfd
->para
.socket
.ip
.ai_flags
,
149 &themlist
, us
, &uslen
,
150 &needbind
, &lowport
, socktype
);
151 if (result
!= STAT_OK
)
154 /* Count addrinfo entries */
157 while (themp
!= NULL
) {
159 themp
= themp
->ai_next
;
161 ai_sorted
= Calloc((i
+1), sizeof(struct addrinfo
*));
162 if (ai_sorted
== NULL
)
163 return STAT_RETRYLATER
;
164 /* Generate a list of addresses sorted by preferred ip version */
165 _xio_sort_ip_addresses(themlist
, ai_sorted
);
167 /* Loop over themlist */
169 themp
= ai_sorted
[i
++];
170 while (themp
!= NULL
) {
171 Notice4("opening connection to %s:%u via proxy %s:%s",
172 proxyvars
->targetaddr
, proxyvars
->targetport
, proxyname
, proxyport
);
174 if (sfd
->forever
|| sfd
->retry
|| ai_sorted
[i
] != NULL
) {
177 #endif /* WITH_RETRY */
181 _xioopen_connect(sfd
,
182 needbind
?us
:NULL
, uslen
,
183 themp
->ai_addr
, themp
->ai_addrlen
,
184 opts
, pf
?pf
:themp
->ai_family
, socktype
, IPPROTO_TCP
, lowport
, level
);
185 if (result
== STAT_OK
)
187 themp
= ai_sorted
[i
++];
189 result
= STAT_RETRYLATER
;
194 case STAT_RETRYLATER
:
196 if (sfd
->forever
|| sfd
->retry
) {
198 if (result
== STAT_RETRYLATER
)
199 Nanosleep(&sfd
->intervall
, NULL
);
202 #endif /* WITH_RETRY */
205 xiofreeaddrinfo(themlist
);
209 xiofreeaddrinfo(themlist
);
210 applyopts(sfd
, -1, opts
, PH_ALL
);
212 if ((result
= _xio_openlate(sfd
, opts
)) < 0)
215 result
= _xioopen_proxy_connect(sfd
, proxyvars
, level
);
219 case STAT_RETRYLATER
:
221 if (sfd
->forever
|| sfd
->retry
--) {
222 if (result
== STAT_RETRYLATER
) Nanosleep(&sfd
->intervall
, NULL
);
225 #endif /* WITH_RETRY */
231 xiosetchilddied(); /* set SIGCHLD handler */
238 if (sfd
->forever
|| sfd
->retry
) {
241 while ((pid
= xio_fork(false, level
, sfd
->shutup
)) < 0) {
242 if (sfd
->forever
|| --sfd
->retry
) {
243 Nanosleep(&sfd
->intervall
, NULL
); continue;
245 return STAT_RETRYLATER
;
248 if (pid
== 0) { /* child process */
249 sfd
->forever
= false; sfd
->retry
= 0;
255 Nanosleep(&sfd
->intervall
, NULL
);
256 dropopts(opts
, PH_ALL
);
257 opts
= copyopts(opts0
, GROUP_ALL
);
260 #endif /* WITH_RETRY */
265 } while (true); /* end of complete open loop - drop out on success */
267 Notice4("successfully connected to %s:%u via proxy %s:%s",
268 proxyvars
->targetaddr
, proxyvars
->targetport
,
269 proxyname
, proxyport
);
275 int _xioopen_proxy_prepare(
276 struct proxyvars
*proxyvars
,
278 const char *targetname
,
279 const char *targetport
,
280 const int ai_flags
[2]) {
281 union sockaddr_union host
;
282 socklen_t socklen
= sizeof(host
);
285 retropt_bool(opts
, OPT_IGNORECR
, &proxyvars
->ignorecr
);
286 retropt_bool(opts
, OPT_PROXY_RESOLVE
, &proxyvars
->doresolve
);
287 retropt_string(opts
, OPT_HTTP_VERSION
, &proxyvars
->version
);
288 retropt_string(opts
, OPT_PROXY_AUTHORIZATION
, &proxyvars
->authstring
);
289 retropt_string(opts
, OPT_PROXY_AUTHORIZATION_FILE
, &proxyvars
->authfile
);
291 if (proxyvars
->authfile
) {
296 /* if we have a file containing authentication credentials and they were also
297 provided on the command line, something is misspecified */
298 if (proxyvars
->authstring
) {
299 Error("Only one of options proxy-authorization and proxy-authorization-file allowed");
302 authfd
= Open(proxyvars
->authfile
, O_RDONLY
, 0);
304 Error2("open(\"%s\", O_RDONLY): %s", proxyvars
->authfile
, strerror(errno
));
307 /* go to the end of our proxy auth file to
308 figure out how long our proxy auth is */
309 if ((length
= Lseek(authfd
, 0, SEEK_END
)) < 0) {
310 Error2("lseek(<%s>, 0, SEEK_END): %s",
311 proxyvars
->authfile
, strerror(errno
));
312 return STAT_RETRYLATER
;
314 proxyvars
->authstring
= Malloc(length
+1);
315 /* go back to the beginning */
316 Lseek(authfd
, 0, SEEK_SET
);
317 /* read our proxy info from the file */
318 if ((bytes
= Read(authfd
, proxyvars
->authstring
, (size_t) length
)) < 0) {
319 Error3("read(<%s>, , "F_Zu
"): %s", proxyvars
->authfile
, length
, strerror(errno
));
320 free(proxyvars
->authstring
);
324 if (bytes
< length
) {
325 Error3("read(<%s>, , "F_Zu
"): got only "F_Zu
" bytes",
326 proxyvars
->authfile
, length
, bytes
);
330 proxyvars
->authstring
[bytes
] = '\0'; /* string termination */
334 if (proxyvars
->doresolve
) {
335 /* currently we only resolve to IPv4 addresses. This is in accordance to
336 RFC 2396; however once it becomes clear how IPv6 addresses should be
337 represented in CONNECT commands this code might need to be extended */
338 rc
= xioresolve(targetname
, targetport
, PF_INET
/*!?*/,
339 SOCK_STREAM
, IPPROTO_TCP
,
340 &host
, &socklen
, ai_flags
);
342 proxyvars
->targetaddr
= strdup(targetname
);
344 #define LEN 16 /* www.xxx.yyy.zzz\0 */
345 if ((proxyvars
->targetaddr
= Malloc(LEN
)) == NULL
) {
346 return STAT_RETRYLATER
;
348 snprintf(proxyvars
->targetaddr
, LEN
, "%u.%u.%u.%u",
349 ((unsigned char *)&host
.ip4
.sin_addr
.s_addr
)[0],
350 ((unsigned char *)&host
.ip4
.sin_addr
.s_addr
)[1],
351 ((unsigned char *)&host
.ip4
.sin_addr
.s_addr
)[2],
352 ((unsigned char *)&host
.ip4
.sin_addr
.s_addr
)[3]);
356 proxyvars
->targetaddr
= strdup(targetname
);
359 proxyvars
->targetport
= htons(parseport(targetport
, IPPROTO_TCP
));
364 int _xioopen_proxy_connect(struct single
*sfd
,
365 struct proxyvars
*proxyvars
,
368 char request
[CONNLEN
]; /* HTTP connection request line */
370 char buff
[BUFLEN
+1]; /* for receiving HTTP reply headers */
372 #error not enough buffer space
374 char textbuff
[2*BUFLEN
+1]; /* just for sanitizing print data */
379 /* generate proxy request header - points to final target */
380 if (proxyvars
->version
== NULL
) {
381 proxyvars
->version
= "1.0";
383 rv
= snprintf(request
, CONNLEN
, "CONNECT %s:%u HTTP/%s\r\n",
384 proxyvars
->targetaddr
, proxyvars
->targetport
, proxyvars
->version
);
385 if (rv
>= CONNLEN
|| rv
< 0) {
386 Error("_xioopen_proxy_connect(): PROXY CONNECT buffer too small");
390 /* send proxy CONNECT request (target addr+port) */
391 * xiosanitize(request
, strlen(request
), textbuff
) = '\0';
392 Info1("sending \"%s\"", textbuff
);
393 /* write errors are assumed to always be hard errors, no retry */
394 if (writefull(sfd
->fd
, request
, strlen(request
)) < 0) {
395 Msg4(level
, "write(%d, %p, "F_Zu
"): %s",
396 sfd
->fd
, request
, strlen(request
), strerror(errno
));
397 if (Close(sfd
->fd
) < 0) {
398 Info2("close(%d): %s", sfd
->fd
, strerror(errno
));
400 return STAT_RETRYLATER
;
403 if (proxyvars
->authstring
) {
404 /* send proxy authentication header */
405 # define XIOAUTHHEAD "Proxy-authorization: Basic "
406 # define XIOAUTHLEN 27
407 static const char *authhead
= XIOAUTHHEAD
;
413 Malloc(XIOAUTHLEN
+((strlen(proxyvars
->authstring
)+2)/3)*4+3))
417 strcpy(header
, authhead
);
418 next
= xiob64encodeline(proxyvars
->authstring
,
419 strlen(proxyvars
->authstring
),
420 strchr(header
, '\0'));
422 Info1("sending \"%s\\r\\n\"", header
);
423 *next
++ = '\r'; *next
++ = '\n'; *next
++ = '\0';
424 if (writefull(sfd
->fd
, header
, strlen(header
)) < 0) {
425 Msg4(level
, "write(%d, %p, "F_Zu
"): %s",
426 sfd
->fd
, header
, strlen(header
), strerror(errno
));
427 if (Close(sfd
->fd
) < 0) {
428 Info2("close(%d): %s", sfd
->fd
, strerror(errno
));
430 return STAT_RETRYLATER
;
436 Info("sending \"\\r\\n\"");
437 if (writefull(sfd
->fd
, "\r\n", 2) < 0) {
438 Msg2(level
, "write(%d, \"\\r\\n\", 2): %s",
439 sfd
->fd
, strerror(errno
));
440 if (Close(sfd
->fd
) < 0) {
441 Info2("close(%d): %s", sfd
->fd
, strerror(errno
));
443 return STAT_RETRYLATER
;
446 /* request is kept for later error messages */
447 *strstr(request
, " HTTP") = '\0';
449 /* receive proxy answer; looks like "HTTP/1.0 200 .*\r\nHeaders..\r\n\r\n" */
450 /* socat version 1 depends on a valid fd for data transfer; address
451 therefore cannot buffer data. So, to prevent reading beyond the end of
452 the answer headers, only single bytes are read. puh. */
453 state
= XIOSTATE_HTTP1
;
454 offset
= 0; /* up to where the buffer is filled (relative) */
455 /*eol;*/ /* points to the first lineterm of the current line */
457 sresult
= xioproxy_recvbytes(sfd
, buff
+offset
, 1, level
);
459 state
= XIOSTATE_ERROR
;
460 break; /* leave read cycles */
466 /* 0 or more bytes of first line received, no '\r' yet */
467 if (*(buff
+offset
) == '\r') {
469 state
= XIOSTATE_HTTP2
;
472 if (proxyvars
->ignorecr
&& *(buff
+offset
) == '\n') {
474 state
= XIOSTATE_HTTP3
;
480 /* first line received including '\r' */
481 if (*(buff
+offset
) != '\n') {
482 state
= XIOSTATE_HTTP1
;
485 state
= XIOSTATE_HTTP3
;
489 /* received status (first line) and "\r\n" */
490 if (*(buff
+offset
) == '\r') {
491 state
= XIOSTATE_HTTP7
;
494 if (proxyvars
->ignorecr
&& *(buff
+offset
) == '\n') {
495 state
= XIOSTATE_HTTP8
;
498 state
= XIOSTATE_HTTP4
;
503 if (*(buff
+offset
) == '\r') {
505 state
= XIOSTATE_HTTP5
;
508 if (proxyvars
->ignorecr
&& *(buff
+offset
) == '\n') {
510 state
= XIOSTATE_HTTP6
;
516 /* within header, '\r' received */
517 if (*(buff
+offset
) != '\n') {
518 state
= XIOSTATE_HTTP4
;
521 state
= XIOSTATE_HTTP6
;
525 /* received status (first line) and 1 or more headers, "\r\n" */
526 if (*(buff
+offset
) == '\r') {
527 state
= XIOSTATE_HTTP7
;
530 if (proxyvars
->ignorecr
&& *(buff
+offset
) == '\n') {
531 state
= XIOSTATE_HTTP8
;
534 state
= XIOSTATE_HTTP4
;
538 /* received status (first line), 0 or more headers, "\r\n\r" */
539 if (*(buff
+offset
) == '\n') {
540 state
= XIOSTATE_HTTP8
;
543 if (*(buff
+offset
) == '\r') {
544 if (proxyvars
->ignorecr
) {
545 break; /* ignore it, keep waiting for '\n' */
547 state
= XIOSTATE_HTTP5
;
551 state
= XIOSTATE_HTTP4
;
557 /* end of status line reached */
558 if (state
== XIOSTATE_HTTP3
) {
560 /* set a terminating null - on or after CRLF? */
561 *(buff
+offset
) = '\0';
563 * xiosanitize(buff
, Min(offset
, (sizeof(textbuff
)-1)>>1), textbuff
)
565 Info1("proxy_connect: received answer \"%s\"", textbuff
);
567 * xiosanitize(buff
, Min(strlen(buff
), (sizeof(textbuff
)-1)>>1),
569 if (strncmp(buff
, "HTTP/1.0 ", 9) &&
570 strncmp(buff
, "HTTP/1.1 ", 9)) {
572 Msg1(level
, "proxy: invalid answer \"%s\"", textbuff
);
573 return STAT_RETRYLATER
;
577 /* skip multiple spaces */
578 while (*ptr
== ' ') ++ptr
;
581 if (strncmp(ptr
, "200", 3)) {
584 "HTTP/1.0 200 Connection established"
585 "HTTP/1.0 400 Invalid request "CONNECT 10.244.9.3:8080 HTTP/1.0" (unknown method)"
586 "HTTP/1.0 403 Forbidden - by rule"
587 "HTTP/1.0 407 Proxy Authentication Required"
588 Proxy-Authenticate: Basic realm="Squid proxy-caching web server"
589 > 50 72 6f 78 79 2d 61 75 74 68 6f 72 69 7a 61 74 Proxy-authorizat
590 > 69 6f 6e 3a 20 42 61 73 69 63 20 61 57 4e 6f 63 ion: Basic aWNoc
591 > 32 56 73 59 6e 4e 30 4f 6e 4e 30 63 6d 56 75 5a 2VsYnN0OnN0cmVuZ
592 > 32 64 6c 61 47 56 70 62 51 3d 3d 0d 0a 2dlaGVpbQ==..
593 b64encode("username:password")
594 "HTTP/1.0 500 Can't connect to host"
597 "HTTP/1.0 400 Bad Request"
598 "HTTP/1.0 403 Forbidden"
599 "HTTP/1.0 503 Service Unavailable"
600 interesting header: "X-Squid-Error: ERR_CONNECT_FAIL 111" */
602 "HTTP/1.0 400 Bad Request"
603 "HTTP/1.1 405 Method Not Allowed"
606 "HTTP/1.1 200 Connection established"
607 "HTTP/1.1 404 Host not found or not responding, errno: 79"
608 "HTTP/1.1 404 Host not found or not responding, errno: 32"
609 "HTTP/1.1 404 Host not found or not responding, errno: 13"
612 "HTTP/1.1 404 Object Not Found"
615 while (*ptr
== ' ') ++ptr
;
617 Msg2(level
, "%s: %s", request
, ptr
);
618 return STAT_RETRYLATER
;
622 /* "HTTP/1.0 200 Connection established" */
623 /*Info1("proxy: \"%s\"", textbuff+13);*/
626 } else if (state
== XIOSTATE_HTTP6
) {
627 /* end of a header line reached */
630 /* set a terminating null */
631 *(buff
+offset
) = '\0';
634 xiosanitize(buff
, Min(offset
, (sizeof(textbuff
)-1)>>1),
637 Info1("proxy_connect: received header \"%s\"", textbuff
);
641 } while (state
!= XIOSTATE_HTTP8
&& offset
< BUFLEN
);
643 if (state
== XIOSTATE_ERROR
) {
644 return STAT_RETRYLATER
;
647 if (offset
>= BUFLEN
) {
648 Msg1(level
, "proxy answer exceeds %d bytes, aborting", BUFLEN
);
655 #endif /* WITH_PROXY */