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
8 #include "xiosysincludes.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
,
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 */
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
*xfd
, char *buff
, size_t buflen
, int level
) {
60 /* we need at least buflen bytes... */
61 result
= Read(xfd
->fd
, buff
, buflen
);
62 } while (result
< 0 && errno
== EINTR
); /*! EAGAIN? */
64 Msg4(level
, "read(%d, %p, "F_Zu
"): %s",
65 xfd
->fd
, buff
, buflen
, strerror(errno
));
69 Msg(level
, "proxy_connect: connection closed by proxy");
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
,
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 */
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;
99 int socktype
= SOCK_STREAM
;
104 Error1("%s: 3 parameters required", argv
[0]);
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
;
129 _xioopen_ipapp_prepare(opts
, &opts0
, proxyname
, proxyport
,
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 */
143 if (xfd
->forever
|| xfd
->retry
) {
146 #endif /* WITH_RETRY */
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
);
157 case STAT_RETRYLATER
:
159 if (xfd
->forever
|| xfd
->retry
--) {
160 if (result
== STAT_RETRYLATER
) Nanosleep(&xfd
->intervall
, NULL
);
163 #endif /* WITH_RETRY */
168 applyopts(xfd
->fd
, opts
, PH_ALL
);
170 if ((result
= _xio_openlate(xfd
, opts
)) < 0)
173 result
= _xioopen_proxy_connect(xfd
, proxyvars
, level
);
177 case STAT_RETRYLATER
:
179 if (xfd
->forever
|| xfd
->retry
--) {
180 if (result
== STAT_RETRYLATER
) Nanosleep(&xfd
->intervall
, NULL
);
183 #endif /* WITH_RETRY */
189 xiosetchilddied(); /* set SIGCHLD handler */
196 if (xfd
->forever
|| xfd
->retry
) {
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;
213 Nanosleep(&xfd
->intervall
, NULL
);
214 dropopts(opts
, PH_ALL
); opts
= copyopts(opts0
, GROUP_ALL
);
217 #endif /* WITH_RETRY */
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
);
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
);
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
);
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]);
266 proxyvars
->targetaddr
= strdup(targetname
);
269 proxyvars
->targetport
= htons(parseport(targetport
, IPPROTO_TCP
));
274 int _xioopen_proxy_connect(struct single
*xfd
,
275 struct proxyvars
*proxyvars
,
278 char request
[CONNLEN
]; /* HTTP connection request line */
280 char buff
[BUFLEN
+1]; /* for receiving HTTP reply headers */
282 #error not enough buffer space
284 char textbuff
[2*BUFLEN
+1]; /* just for sanitizing print data */
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");
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
;
320 Malloc(XIOAUTHLEN
+((strlen(proxyvars
->authstring
)+2)/3)*4+3))
324 strcpy(header
, authhead
);
325 next
= xiob64encodeline(proxyvars
->authstring
,
326 strlen(proxyvars
->authstring
),
327 strchr(header
, '\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
;
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 */
364 sresult
= xioproxy_recvbytes(xfd
, buff
+offset
, 1, level
);
366 state
= XIOSTATE_ERROR
;
367 break; /* leave read cycles */
373 /* 0 or more bytes of first line received, no '\r' yet */
374 if (*(buff
+offset
) == '\r') {
376 state
= XIOSTATE_HTTP2
;
379 if (proxyvars
->ignorecr
&& *(buff
+offset
) == '\n') {
381 state
= XIOSTATE_HTTP3
;
387 /* first line received including '\r' */
388 if (*(buff
+offset
) != '\n') {
389 state
= XIOSTATE_HTTP1
;
392 state
= XIOSTATE_HTTP3
;
396 /* received status (first line) and "\r\n" */
397 if (*(buff
+offset
) == '\r') {
398 state
= XIOSTATE_HTTP7
;
401 if (proxyvars
->ignorecr
&& *(buff
+offset
) == '\n') {
402 state
= XIOSTATE_HTTP8
;
405 state
= XIOSTATE_HTTP4
;
410 if (*(buff
+offset
) == '\r') {
412 state
= XIOSTATE_HTTP5
;
415 if (proxyvars
->ignorecr
&& *(buff
+offset
) == '\n') {
417 state
= XIOSTATE_HTTP6
;
423 /* within header, '\r' received */
424 if (*(buff
+offset
) != '\n') {
425 state
= XIOSTATE_HTTP4
;
428 state
= XIOSTATE_HTTP6
;
432 /* received status (first line) and 1 or more headers, "\r\n" */
433 if (*(buff
+offset
) == '\r') {
434 state
= XIOSTATE_HTTP7
;
437 if (proxyvars
->ignorecr
&& *(buff
+offset
) == '\n') {
438 state
= XIOSTATE_HTTP8
;
441 state
= XIOSTATE_HTTP4
;
445 /* received status (first line), 0 or more headers, "\r\n\r" */
446 if (*(buff
+offset
) == '\n') {
447 state
= XIOSTATE_HTTP8
;
450 if (*(buff
+offset
) == '\r') {
451 if (proxyvars
->ignorecr
) {
452 break; /* ignore it, keep waiting for '\n' */
454 state
= XIOSTATE_HTTP5
;
458 state
= XIOSTATE_HTTP4
;
464 /* end of status line reached */
465 if (state
== XIOSTATE_HTTP3
) {
467 /* set a terminating null - on or after CRLF? */
468 *(buff
+offset
) = '\0';
470 * xiosanitize(buff
, Min(offset
, (sizeof(textbuff
)-1)>>1), textbuff
)
472 Info1("proxy_connect: received answer \"%s\"", textbuff
);
474 * xiosanitize(buff
, Min(strlen(buff
), (sizeof(textbuff
)-1)>>1),
476 if (strncmp(buff
, "HTTP/1.0 ", 9) &&
477 strncmp(buff
, "HTTP/1.1 ", 9)) {
479 Msg1(level
, "proxy: invalid answer \"%s\"", textbuff
);
480 return STAT_RETRYLATER
;
484 /* skip multiple spaces */
485 while (*ptr
== ' ') ++ptr
;
488 if (strncmp(ptr
, "200", 3)) {
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"
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" */
509 "HTTP/1.0 400 Bad Request"
510 "HTTP/1.1 405 Method Not Allowed"
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"
519 "HTTP/1.1 404 Object Not Found"
522 while (*ptr
== ' ') ++ptr
;
524 Msg2(level
, "%s: %s", request
, ptr
);
525 return STAT_RETRYLATER
;
529 /* "HTTP/1.0 200 Connection established" */
530 /*Info1("proxy: \"%s\"", textbuff+13);*/
533 } else if (state
== XIOSTATE_HTTP6
) {
534 /* end of a header line reached */
537 /* set a terminating null */
538 *(buff
+offset
) = '\0';
541 xiosanitize(buff
, Min(offset
, (sizeof(textbuff
)-1)>>1),
544 Info1("proxy_connect: received header \"%s\"", textbuff
);
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
);
562 #endif /* WITH_PROXY */