socat/xio-proxy.c

636 lines
19 KiB
C

/* source: xio-proxy.c */
/* Copyright Gerhard Rieger */
/* Published under the GNU General Public License V.2, see file COPYING */
/* this file contains the source for opening addresses of HTTP proxy CONNECT
type */
#include "xiosysincludes.h"
#if WITH_PROXY
#include "xioopen.h"
#include "xio-socket.h"
#include "xio-ipapp.h"
#include "xio-ascii.h" /* for base64 encoding of authentication */
#include "xio-proxy.h"
#define PROXYPORT "8080"
static int xioopen_proxy_connect2(int argc, const char *argv[], struct opt *opts,
int xioflags, xiofile_t *xxfd,
unsigned groups, int dummy1, int dummy2,
int dummy3);
static int xioopen_proxy_connect3(int argc, const char *argv[], struct opt *opts,
int xioflags, xiofile_t *xxfd,
unsigned groups, int dummy1, int dummy2,
int dummy3);
const struct optdesc opt_proxyport = { "proxyport", NULL, OPT_PROXYPORT, GROUP_HTTP, PH_LATE, TYPE_STRING, OFUNC_SPEC };
const struct optdesc opt_ignorecr = { "ignorecr", NULL, OPT_IGNORECR, GROUP_HTTP, PH_LATE, TYPE_BOOL, OFUNC_SPEC };
const struct optdesc opt_proxy_resolve = { "proxy-resolve", "resolve", OPT_PROXY_RESOLVE, GROUP_HTTP, PH_LATE, TYPE_BOOL, OFUNC_SPEC };
const struct optdesc opt_proxy_authorization = { "proxy-authorization", "proxyauth", OPT_PROXY_AUTHORIZATION, GROUP_HTTP, PH_LATE, TYPE_STRING, OFUNC_SPEC };
static const struct xioaddr_inter_desc xioaddr_proxy_connect2 = { XIOADDR_INTER, "proxy", 2, XIOBIT_ALL, GROUP_HTTP|GROUP_CHILD|GROUP_RETRY, XIOSHUT_DOWN, XIOCLOSE_CLOSE, xioopen_proxy_connect2, 0, 0, 0, XIOBIT_RDWR HELP(":<host>:<port>") };
static const struct xioaddr_endpoint_desc xioaddr_proxy_connect3 = { XIOADDR_ENDPOINT, "proxy", 3, XIOBIT_ALL, GROUP_FD|GROUP_SOCKET|GROUP_SOCK_IP4|GROUP_SOCK_IP6|GROUP_IP_TCP|GROUP_HTTP|GROUP_CHILD|GROUP_RETRY, XIOSHUT_DOWN, XIOCLOSE_CLOSE, xioopen_proxy_connect3, 0, 0, 0 HELP(":<proxy-server>:<host>:<port>") };
const union xioaddr_desc *xioaddrs_proxy_connect[] = {
(union xioaddr_desc *)&xioaddr_proxy_connect2,
(union xioaddr_desc *)&xioaddr_proxy_connect3,
NULL
};
/*0#define CONNLEN 40*/ /* "CONNECT 123.156.189.123:65432 HTTP/1.0\r\n\0" */
#define CONNLEN 281 /* "CONNECT <255bytes>:65432 HTTP/1.0\r\n\0" */
/* states during receiving answer */
enum {
XIOSTATE_HTTP1, /* 0 or more bytes of first line received, no \r */
XIOSTATE_HTTP2, /* first line received including \r */
XIOSTATE_HTTP3, /* received status and \r\n */
XIOSTATE_HTTP4, /* within header */
XIOSTATE_HTTP5, /* within header, \r */
XIOSTATE_HTTP6, /* received status and 1 or more headers, \r\n */
XIOSTATE_HTTP7, /* received status line, ev. headers, \r\n\r */
XIOSTATE_HTTP8, /* complete answer received */
XIOSTATE_ERROR /* error during HTTP headers */
} ;
/* get buflen bytes from proxy server;
handles EINTR;
returns <0 when error occurs
*/
static ssize_t
xioproxy_recvbytes(struct single *xfd, char *buff, size_t buflen, int level) {
ssize_t result;
do {
/* we need at least 16 bytes... */
result = Read(xfd->rfd, buff, buflen);
} while (result < 0 && errno == EINTR); /*! EAGAIN? */
if (result < 0) {
Msg4(level, "read(%d, %p, "F_Zu"): %s",
xfd->rfd, buff, buflen, strerror(errno));
return result;
}
if (result == 0) {
Msg(level, "proxy_connect: connection closed by proxy");
}
return result;
}
#define BUFLEN 2048
static int xioopen_proxy_connect2(int argc, const char *argv[], struct opt *opts,
int xioflags, xiofile_t *xxfd,
unsigned groups, int dummy1, int dummy2,
int dummy3) {
struct single *xfd = &xxfd->stream;
struct proxyvars struct_proxyvars = { 0 }, *proxyvars = &struct_proxyvars;
const char *targetname, *targetport;
/* variables to be filled with address option values */
bool dofork = false;
int result;
if (xfd->rfd < 0) {
Error("xioopen_proxy_connect(): proxyname missing");
return STAT_NORETRY;
}
targetname = argv[1];
targetport = argv[2];
if (applyopts_single(xfd, opts, PH_INIT) < 0) return -1;
applyopts(-1, opts, PH_INIT);
retropt_bool(opts, OPT_FORK, &dofork);
if (dofork && !(xioflags & XIO_MAYFORK)) {
Error("fork option not allowed by application");
}
result = _xioopen_proxy_prepare(proxyvars, opts, targetname, targetport);
if (result != STAT_OK) return result;
Notice2("opening connection to %s:%u using proxy CONNECT",
proxyvars->targetaddr, proxyvars->targetport);
xfd->dtype = XIODATA_STREAM;
applyopts(xfd->rfd, opts, PH_ALL);
/*!*/
if ((result = _xio_openlate(xfd, opts)) < 0)
return result;
result = _xioopen_proxy_connect(xfd, proxyvars, E_ERROR);
switch (result) {
case STAT_OK: break;
default:
return result;
}
Notice2("successfully connected to %s:%u via proxy",
proxyvars->targetaddr, proxyvars->targetport);
return STAT_OK;
}
static int xioopen_proxy_connect3(int argc, const char *argv[], struct opt *opts,
int xioflags, xiofile_t *xxfd,
unsigned groups, int dummy1, int dummy2,
int dummy3) {
/* we expect the form: host:host:port */
struct single *xfd = &xxfd->stream;
int rw = (xioflags & XIO_ACCMODE);
struct proxyvars struct_proxyvars = { 0 }, *proxyvars = &struct_proxyvars;
const char *proxyname;
char *proxyport = NULL;
const char *targetname, *targetport;
/* variables to be filled with address option values */
bool dofork = false;
int socktype = SOCK_STREAM;
struct opt *opts0 = NULL;
/* */
int pf = PF_UNSPEC;
union sockaddr_union us_sa, *us = &us_sa;
union sockaddr_union them_sa, *them = &them_sa;
socklen_t uslen = sizeof(us_sa);
socklen_t themlen = sizeof(them_sa);
int ipproto = IPPROTO_TCP;
bool needbind = false;
bool lowport = false;
int level;
int result;
if (xfd->rfd >= 0) {
Error("xioopen_proxy_connect(): proxyname not allowed here");
return STAT_NORETRY;
}
proxyname = argv[1];
targetname = argv[2];
targetport = argv[3];
xfd->howtoshut = XIOSHUT_DOWN;
xfd->howtoclose = XIOCLOSE_CLOSE;
if (applyopts_single(xfd, opts, PH_INIT) < 0) return -1;
applyopts(-1, opts, PH_INIT);
retropt_int(opts, OPT_SO_TYPE, &socktype);
retropt_bool(opts, OPT_FORK, &dofork);
if (dofork && !(xioflags & XIO_MAYFORK)) {
Error("fork option not allowed by application");
}
if (retropt_string(opts, OPT_PROXYPORT, &proxyport) < 0) {
if ((proxyport = strdup(PROXYPORT)) == NULL) {
errno = ENOMEM; return -1;
}
}
result = _xioopen_proxy_prepare(proxyvars, opts, targetname, targetport);
if (result != STAT_OK) return result;
result =
_xioopen_ipapp_prepare(opts, &opts0, proxyname, proxyport,
&pf, ipproto,
xfd->para.socket.ip.res_opts[1],
xfd->para.socket.ip.res_opts[0],
them, &themlen, us, &uslen,
&needbind, &lowport, socktype);
if (result != STAT_OK) return result;
Notice4("opening connection to %s:%u via proxy %s:%s",
proxyvars->targetaddr, proxyvars->targetport, proxyname, proxyport);
do { /* loop over failed connect and proxy connect attempts */
#if WITH_RETRY
if (xfd->forever || xfd->retry) {
level = E_INFO;
} else
#endif /* WITH_RETRY */
level = E_ERROR;
result =
_xioopen_connect(xfd,
needbind?(struct sockaddr *)us:NULL, sizeof(*us),
(struct sockaddr *)them, themlen,
opts, pf, socktype, IPPROTO_TCP, lowport, level);
switch (result) {
case STAT_OK: break;
#if WITH_RETRY
case STAT_RETRYLATER:
case STAT_RETRYNOW:
if (xfd->forever || xfd->retry--) {
if (result == STAT_RETRYLATER) Nanosleep(&xfd->intervall, NULL);
continue;
}
#endif /* WITH_RETRY */
default:
return result;
}
if (XIOWITHWR(rw)) xfd->wfd = xfd->rfd;
if (!XIOWITHRD(rw)) xfd->rfd = -1;
applyopts(xfd->rfd, opts, PH_ALL);
/*!*/
if ((result = _xio_openlate(xfd, opts)) < 0)
return result;
result = _xioopen_proxy_connect(xfd, proxyvars, level);
switch (result) {
case STAT_OK: break;
#if WITH_RETRY
case STAT_RETRYLATER:
case STAT_RETRYNOW:
if (xfd->forever || xfd->retry--) {
if (result == STAT_RETRYLATER) Nanosleep(&xfd->intervall, NULL);
continue;
}
#endif /* WITH_RETRY */
default:
return result;
}
if (dofork) {
xiosetchilddied(); /* set SIGCHLD handler */
}
#if WITH_RETRY
if (dofork) {
pid_t pid;
int level = E_ERROR;
if (xfd->forever || xfd->retry) {
level = E_WARN;
}
while ((pid = xio_fork(false, level)) < 0) {
if (xfd->forever || --xfd->retry) {
Nanosleep(&xfd->intervall, NULL); continue;
}
return STAT_RETRYLATER;
}
if (pid == 0) { /* child process */
xfd->forever = false; xfd->retry = 0;
break;
}
/* parent process */
Notice1("forked off child process "F_pid, pid);
Close(xfd->rfd);
Close(xfd->wfd);
Nanosleep(&xfd->intervall, NULL);
dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL);
continue;
} else
#endif /* WITH_RETRY */
{
break;
}
} while (true); /* end of complete open loop - drop out on success */
Notice2("successfully connected to %s:%u via proxy",
proxyvars->targetaddr, proxyvars->targetport);
return 0;
}
int _xioopen_proxy_prepare(struct proxyvars *proxyvars, struct opt *opts,
const char *targetname, const char *targetport) {
struct hostent *host;
retropt_bool(opts, OPT_IGNORECR, &proxyvars->ignorecr);
retropt_bool(opts, OPT_PROXY_RESOLVE, &proxyvars->doresolve);
retropt_string(opts, OPT_PROXY_AUTHORIZATION, &proxyvars->authstring);
if (proxyvars->doresolve) {
/* currently we only resolve to IPv4 addresses. This is in accordance to
RFC 2396; however once it becomes clear how IPv6 addresses should be
represented in CONNECT commands this code might be extended */
host = Gethostbyname(targetname);
if (host == NULL) {
int level = E_WARN;
/* note: cast is req on AIX: */
Msg2(level, "gethostbyname(\"%s\"): %s", targetname,
h_errno == NETDB_INTERNAL ? strerror(errno) :
(char *)hstrerror(h_errno)/*0 h_messages[h_errno-1]*/);
proxyvars->targetaddr = strdup(targetname);
} else {
#define LEN 16 /* www.xxx.yyy.zzz\0 */
if ((proxyvars->targetaddr = Malloc(LEN)) == NULL) {
return STAT_RETRYLATER;
}
snprintf(proxyvars->targetaddr, LEN, "%u.%u.%u.%u",
(unsigned char)host->h_addr_list[0][0],
(unsigned char)host->h_addr_list[0][1],
(unsigned char)host->h_addr_list[0][2],
(unsigned char)host->h_addr_list[0][3]);
#undef LEN
}
} else {
proxyvars->targetaddr = strdup(targetname);
}
proxyvars->targetport = htons(parseport(targetport, IPPROTO_TCP));
return STAT_OK;
}
int _xioopen_proxy_connect(struct single *xfd,
struct proxyvars *proxyvars,
int level) {
size_t offset;
char request[CONNLEN]; /* HTTP connection request line */
int rv;
char buff[BUFLEN+1]; /* for receiving HTTP reply headers */
#if CONNLEN > BUFLEN
#error not enough buffer space
#endif
char textbuff[2*BUFLEN+1]; /* just for sanitizing print data */
char *eol = buff;
int state;
ssize_t sresult;
/* generate proxy request header - points to final target */
rv = snprintf(request, CONNLEN, "CONNECT %s:%u HTTP/1.0\r\n",
proxyvars->targetaddr, proxyvars->targetport);
if (rv >= CONNLEN || rv < 0) {
Error("_xioopen_proxy_connect(): PROXY CONNECT buffer too small");
return -1;
}
/* send proxy CONNECT request (target addr+port) */
* xiosanitize(request, strlen(request), textbuff) = '\0';
Info1("sending \"%s\"", textbuff);
/* write errors are assumed to always be hard errors, no retry */
if (writefull(xfd->wfd, request, strlen(request)) < 0) {
Msg4(level, "write(%d, %p, "F_Zu"): %s",
xfd->wfd, request, strlen(request), strerror(errno));
if (Close(xfd->wfd) < 0) {
Info2("close(%d): %s", xfd->wfd, strerror(errno));
}
return STAT_RETRYLATER;
}
if (proxyvars->authstring) {
/* send proxy authentication header */
# define XIOAUTHHEAD "Proxy-authorization: Basic "
# define XIOAUTHLEN 27
static const char *authhead = XIOAUTHHEAD;
# define HEADLEN 256
char *header, *next;
/* ...\r\n\0 */
if ((header =
Malloc(XIOAUTHLEN+((strlen(proxyvars->authstring)+2)/3)*4+3))
== NULL) {
return -1;
}
strcpy(header, authhead);
next = xiob64encodeline(proxyvars->authstring,
strlen(proxyvars->authstring),
strchr(header, '\0'));
*next = '\0';
Info1("sending \"%s\\r\\n\"", header);
*next++ = '\r'; *next++ = '\n'; *next++ = '\0';
if (writefull(xfd->wfd, header, strlen(header)) < 0) {
Msg4(level, "write(%d, %p, "F_Zu"): %s",
xfd->wfd, header, strlen(header), strerror(errno));
if (Close(xfd->wfd/*!*/) < 0) {
Info2("close(%d): %s", xfd->wfd, strerror(errno));
}
return STAT_RETRYLATER;
}
free(header);
}
Info("sending \"\\r\\n\"");
if (writefull(xfd->wfd, "\r\n", 2) < 0) {
Msg2(level, "write(%d, \"\\r\\n\", 2): %s",
xfd->wfd, strerror(errno));
if (Close(xfd->wfd) < 0) {
Info2("close(%d): %s", xfd->wfd, strerror(errno));
}
return STAT_RETRYLATER;
}
/* request is kept for later error messages */
*strstr(request, " HTTP") = '\0';
/* receive proxy answer; looks like "HTTP/1.0 200 .*\r\nHeaders..\r\n\r\n" */
/* socat version 1 depends on a valid fd for data transfer; address
therefore cannot buffer data. So, to prevent reading beyond the end of
the answer headers, only single bytes are read. puh. */
state = XIOSTATE_HTTP1;
offset = 0; /* up to where the buffer is filled (relative) */
/*eol;*/ /* points to the first lineterm of the current line */
do {
sresult = xioproxy_recvbytes(xfd, buff+offset, 1, level);
if (sresult <= 0) {
state = XIOSTATE_ERROR;
break; /* leave read cycles */
}
switch (state) {
case XIOSTATE_HTTP1:
/* 0 or more bytes of first line received, no '\r' yet */
if (*(buff+offset) == '\r') {
eol = buff+offset;
state = XIOSTATE_HTTP2;
break;
}
if (proxyvars->ignorecr && *(buff+offset) == '\n') {
eol = buff+offset;
state = XIOSTATE_HTTP3;
break;
}
break;
case XIOSTATE_HTTP2:
/* first line received including '\r' */
if (*(buff+offset) != '\n') {
state = XIOSTATE_HTTP1;
break;
}
state = XIOSTATE_HTTP3;
break;
case XIOSTATE_HTTP3:
/* received status (first line) and "\r\n" */
if (*(buff+offset) == '\r') {
state = XIOSTATE_HTTP7;
break;
}
if (proxyvars->ignorecr && *(buff+offset) == '\n') {
state = XIOSTATE_HTTP8;
break;
}
state = XIOSTATE_HTTP4;
break;
case XIOSTATE_HTTP4:
/* within header */
if (*(buff+offset) == '\r') {
eol = buff+offset;
state = XIOSTATE_HTTP5;
break;
}
if (proxyvars->ignorecr && *(buff+offset) == '\n') {
eol = buff+offset;
state = XIOSTATE_HTTP6;
break;
}
break;
case XIOSTATE_HTTP5:
/* within header, '\r' received */
if (*(buff+offset) != '\n') {
state = XIOSTATE_HTTP4;
break;
}
state = XIOSTATE_HTTP6;
break;
case XIOSTATE_HTTP6:
/* received status (first line) and 1 or more headers, "\r\n" */
if (*(buff+offset) == '\r') {
state = XIOSTATE_HTTP7;
break;
}
if (proxyvars->ignorecr && *(buff+offset) == '\n') {
state = XIOSTATE_HTTP8;
break;
}
state = XIOSTATE_HTTP4;
break;
case XIOSTATE_HTTP7:
/* received status (first line), 0 or more headers, "\r\n\r" */
if (*(buff+offset) == '\n') {
state = XIOSTATE_HTTP8;
break;
}
if (*(buff+offset) == '\r') {
if (proxyvars->ignorecr) {
break; /* ignore it, keep waiting for '\n' */
} else {
state = XIOSTATE_HTTP5;
}
break;
}
state = XIOSTATE_HTTP4;
break;
}
++offset;
/* end of status line reached */
if (state == XIOSTATE_HTTP3) {
char *ptr;
/* set a terminating null - on or after CRLF? */
*(buff+offset) = '\0';
* xiosanitize(buff, Min(offset, (sizeof(textbuff)-1)>>1), textbuff)
= '\0';
Info1("proxy_connect: received answer \"%s\"", textbuff);
*eol = '\0';
* xiosanitize(buff, Min(strlen(buff), (sizeof(textbuff)-1)>>1),
textbuff) = '\0';
if (strncmp(buff, "HTTP/1.0 ", 9) &&
strncmp(buff, "HTTP/1.1 ", 9)) {
/* invalid answer */
Msg1(level, "proxy: invalid answer \"%s\"", textbuff);
return STAT_RETRYLATER;
}
ptr = buff+9;
/* skip multiple spaces */
while (*ptr == ' ') ++ptr;
/* HTTP answer */
if (strncmp(ptr, "200", 3)) {
/* not ok */
/* CERN:
"HTTP/1.0 200 Connection established"
"HTTP/1.0 400 Invalid request "CONNECT 10.244.9.3:8080 HTTP/1.0" (unknown method)"
"HTTP/1.0 403 Forbidden - by rule"
"HTTP/1.0 407 Proxy Authentication Required"
Proxy-Authenticate: Basic realm="Squid proxy-caching web server"
> 50 72 6f 78 79 2d 61 75 74 68 6f 72 69 7a 61 74 Proxy-authorizat
> 69 6f 6e 3a 20 42 61 73 69 63 20 61 57 4e 6f 63 ion: Basic aWNoc
> 32 56 73 59 6e 4e 30 4f 6e 4e 30 63 6d 56 75 5a 2VsYnN0OnN0cmVuZ
> 32 64 6c 61 47 56 70 62 51 3d 3d 0d 0a 2dlaGVpbQ==..
b64encode("username:password")
"HTTP/1.0 500 Can't connect to host"
*/
/* Squid:
"HTTP/1.0 400 Bad Request"
"HTTP/1.0 403 Forbidden"
"HTTP/1.0 503 Service Unavailable"
interesting header: "X-Squid-Error: ERR_CONNECT_FAIL 111" */
/* Apache:
"HTTP/1.0 400 Bad Request"
"HTTP/1.1 405 Method Not Allowed"
*/
/* WTE:
"HTTP/1.1 200 Connection established"
"HTTP/1.1 404 Host not found or not responding, errno: 79"
"HTTP/1.1 404 Host not found or not responding, errno: 32"
"HTTP/1.1 404 Host not found or not responding, errno: 13"
*/
/* IIS:
"HTTP/1.1 404 Object Not Found"
*/
ptr += 3;
while (*ptr == ' ') ++ptr;
Msg2(level, "%s: %s", request, ptr);
return STAT_RETRYLATER;
}
/* ok!! */
/* "HTTP/1.0 200 Connection established" */
/*Info1("proxy: \"%s\"", textbuff+13);*/
offset = 0;
} else if (state == XIOSTATE_HTTP6) {
/* end of a header line reached */
char *endp;
/* set a terminating null */
*(buff+offset) = '\0';
endp =
xiosanitize(buff, Min(offset, (sizeof(textbuff)-1)>>1),
textbuff);
*endp = '\0';
Info1("proxy_connect: received header \"%s\"", textbuff);
offset = 0;
}
} while (state != XIOSTATE_HTTP8 && offset < BUFLEN);
if (state == XIOSTATE_ERROR) {
return STAT_RETRYLATER;
}
if (offset >= BUFLEN) {
Msg1(level, "proxy answer exceeds %d bytes, aborting", BUFLEN);
return STAT_NORETRY;
}
return STAT_OK;
}
#endif /* WITH_PROXY */