/* 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; }
/* creates the listening socket, bind, applies options; waits for incoming connection, checks its source address and port. Depending on fork option, it may fork a subprocess. pf specifies the syntax expected for range option. In the case of generic socket it is 0 (expecting raw binary data), and the real pf can be obtained from us->af_family; for other socket types pf == us->af_family Returns 0 if a connection was accepted; with fork option, this is always in a subprocess! Other return values indicate a problem; this can happen in the master process or in a subprocess. This function does not retry. If you need retries, handle this in a loop in the calling function (and always provide the options...) After fork, we set the forever/retry of the child process to 0 applies and consumes the following option: PH_INIT, PH_PASTSOCKET, PH_PREBIND, PH_BIND, PH_PASTBIND, PH_EARLY, PH_PREOPEN, PH_FD, PH_CONNECTED, PH_LATE, PH_LATE2 OPT_FORK, OPT_SO_TYPE, OPT_SO_PROTOTYPE, OPT_BACKLOG, OPT_RANGE, tcpwrap, OPT_SOURCEPORT, OPT_LOWPORT, cloexec */ int _xioopen_listen(struct single *xfd, int xioflags, struct sockaddr *us, socklen_t uslen, struct opt *opts, int pf, int socktype, int proto, int level) { struct sockaddr sa; socklen_t salen; int backlog = 5; /* why? 1 seems to cause problems under some load */ char *rangename; bool dofork = false; int maxchildren = 0; char infobuff[256]; char lisname[256]; union sockaddr_union _peername; union sockaddr_union _sockname; union sockaddr_union *pa = &_peername; /* peer address */ union sockaddr_union *la = &_sockname; /* local address */ socklen_t pas = sizeof(_peername); /* peer address size */ socklen_t las = sizeof(_sockname); /* local address size */ int result; retropt_bool(opts, OPT_FORK, &dofork); if (dofork) { if (!(xioflags & XIO_MAYFORK)) { Error("option fork not allowed here"); return STAT_NORETRY; } xfd->flags |= XIO_DOESFORK; } retropt_int(opts, OPT_MAX_CHILDREN, &maxchildren); if (! dofork && maxchildren) { Error("option max-children not allowed without option fork"); return STAT_NORETRY; } if (applyopts_single(xfd, opts, PH_INIT) < 0) return -1; if (dofork) { xiosetchilddied(); /* set SIGCHLD handler */ } if ((xfd->fd = xiosocket(opts, us->sa_family, socktype, proto, level)) < 0) { return STAT_RETRYLATER; } applyopts_cloexec(xfd->fd, opts); applyopts(xfd->fd, opts, PH_PREBIND); applyopts(xfd->fd, opts, PH_BIND); if (Bind(xfd->fd, (struct sockaddr *)us, uslen) < 0) { Msg4(level, "bind(%d, {%s}, "F_socklen"): %s", xfd->fd, sockaddr_info(us, uslen, infobuff, sizeof(infobuff)), uslen, strerror(errno)); Close(xfd->fd); return STAT_RETRYLATER; } #if WITH_UNIX if (us->sa_family == AF_UNIX) { applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_FD); } #endif /* under some circumstances (e.g., TCP listen on port 0) bind() fills empty fields that we want to know. */ salen = sizeof(sa); if (Getsockname(xfd->fd, us, &uslen) < 0) { Warn4("getsockname(%d, %p, {%d}): %s", xfd->fd, &us, uslen, strerror(errno)); } applyopts(xfd->fd, opts, PH_PASTBIND); #if WITH_UNIX if (us->sa_family == AF_UNIX) { /*applyopts_early(((struct sockaddr_un *)us)->sun_path, opts);*/ applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_EARLY); applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_PREOPEN); } #endif /* WITH_UNIX */ #if WITH_IP4 /*|| WITH_IP6*/ if (retropt_string(opts, OPT_RANGE, &rangename) >= 0) { if (xioparserange(rangename, pf, &xfd->para.socket.range) < 0) { free(rangename); return STAT_NORETRY; } free(rangename); xfd->para.socket.dorange = true; } #endif #if (WITH_TCP || WITH_UDP) && WITH_LIBWRAP xio_retropt_tcpwrap(xfd, opts); #endif /* && (WITH_TCP || WITH_UDP) && WITH_LIBWRAP */ #if WITH_TCP || WITH_UDP if (retropt_ushort(opts, OPT_SOURCEPORT, &xfd->para.socket.ip.sourceport) >= 0) { xfd->para.socket.ip.dosourceport = true; } retropt_bool(opts, OPT_LOWPORT, &xfd->para.socket.ip.lowport); #endif /* WITH_TCP || WITH_UDP */ applyopts(xfd->fd, opts, PH_PRELISTEN); retropt_int(opts, OPT_BACKLOG, &backlog); if (Listen(xfd->fd, backlog) < 0) { Error3("listen(%d, %d): %s", xfd->fd, backlog, strerror(errno)); return STAT_RETRYLATER; } if (xioopts.logopt == 'm') { Info("starting accept loop, switching to syslog"); diag_set('y', xioopts.syslogfac); xioopts.logopt = 'y'; } else { Info("starting accept loop"); } while (true) { /* but we only loop if fork option is set */ char peername[256]; char sockname[256]; int ps; /* peer socket */ pa = &_peername; la = &_sockname; salen = sizeof(struct sockaddr); do { /*? int level = E_ERROR;*/ Notice1("listening on %s", sockaddr_info(us, uslen, lisname, sizeof(lisname))); ps = Accept(xfd->fd, (struct sockaddr *)&sa, &salen); if (ps >= 0) { /*0 Info4("accept(%d, %p, {"F_Zu"}) -> %d", xfd->fd, &sa, salen, ps);*/ break; /* success, break out of loop */ } if (errno == EINTR) { continue; } if (errno == ECONNABORTED) { Notice4("accept(%d, %p, {"F_socklen"}): %s", xfd->fd, &sa, salen, strerror(errno)); continue; } Msg4(level, "accept(%d, %p, {"F_socklen"}): %s", xfd->fd, &sa, salen, strerror(errno)); Close(xfd->fd); return STAT_RETRYLATER; } while (true); applyopts_cloexec(ps, opts); if (Getpeername(ps, &pa->soa, &pas) < 0) { Warn4("getpeername(%d, %p, {"F_socklen"}): %s", ps, pa, pas, strerror(errno)); pa = NULL; } if (Getsockname(ps, &la->soa, &las) < 0) { Warn4("getsockname(%d, %p, {"F_socklen"}): %s", ps, la, las, strerror(errno)); la = NULL; } Notice2("accepting connection from %s on %s", pa? sockaddr_info(&pa->soa, pas, peername, sizeof(peername)):"NULL", la? sockaddr_info(&la->soa, las, sockname, sizeof(sockname)):"NULL"); if (pa != NULL && la != NULL && xiocheckpeer(xfd, pa, la) < 0) { if (Shutdown(ps, 2) < 0) { Info2("shutdown(%d, 2): %s", ps, strerror(errno)); } Close(ps); continue; } if (pa != NULL) Info1("permitting connection from %s", sockaddr_info((struct sockaddr *)pa, pas, infobuff, sizeof(infobuff))); if (dofork) { pid_t pid; /* mostly int; only used with fork */ sigset_t mask_sigchld; /* we must prevent that the current packet triggers another fork; therefore we wait for a signal from the recent child: USR1 indicates that is has consumed the last packet; CHLD means it has terminated */ /* block SIGCHLD and SIGUSR1 until parent is ready to react */ sigemptyset(&mask_sigchld); sigaddset(&mask_sigchld, SIGCHLD); Sigprocmask(SIG_BLOCK, &mask_sigchld, NULL); if ((pid = xio_fork(false, level==E_ERROR?level:E_WARN)) < 0) { Close(xfd->fd); Sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL); return STAT_RETRYLATER; } if (pid == 0) { /* child */ pid_t cpid = Getpid(); Sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL); Info1("just born: child process "F_pid, cpid); xiosetenvulong("PID", cpid, 1); if (Close(xfd->fd) < 0) { Info2("close(%d): %s", xfd->fd, strerror(errno)); } xfd->fd = ps; #if WITH_RETRY /* !? */ xfd->forever = false; xfd->retry = 0; level = E_ERROR; #endif /* WITH_RETRY */ break; } /* server: continue loop with listen */ /* shutdown() closes the socket even for the child process, but close() does what we want */ if (Close(ps) < 0) { Info2("close(%d): %s", ps, strerror(errno)); } /* now we are ready to handle signals */ Sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL); while (maxchildren) { if (num_child < maxchildren) break; Notice("maxchildren are active, waiting"); /* UINT_MAX would even be nicer, but Openindiana works only with 31 bits */ while (!Sleep(INT_MAX)) ; /* any signal lets us continue */ } Info("still listening"); } else { if (Close(xfd->fd) < 0) { Info2("close(%d): %s", xfd->fd, strerror(errno)); } xfd->fd = ps; break; } } applyopts(xfd->fd, opts, PH_FD); applyopts(xfd->fd, opts, PH_PASTSOCKET); applyopts(xfd->fd, opts, PH_CONNECTED); if ((result = _xio_openlate(xfd, opts)) < 0) return result; /* set the env vars describing the local and remote sockets */ if (la != NULL) xiosetsockaddrenv("SOCK", la, las, proto); if (pa != NULL) xiosetsockaddrenv("PEER", pa, pas, proto); return 0; }
/* perform socks4 client dialog on existing FD. Called within fork/retry loop, after connect() */ int _xioopen_socks4_connect(struct single *xfd, struct socks4 *sockhead, size_t headlen, int level) { ssize_t bytes; int result; unsigned char buff[SIZEOF_STRUCT_SOCKS4]; struct socks4 *replyhead = (struct socks4 *)buff; char *destdomname = NULL; /* send socks header (target addr+port, +auth) */ #if WITH_MSGLEVEL <= E_INFO if (ntohl(sockhead->dest) <= 0x000000ff) { destdomname = strchr(sockhead->userid, '\0')+1; } Info11("sending socks4%s request VN=%d DC=%d DSTPORT=%d DSTIP=%d.%d.%d.%d USERID=%s%s%s", destdomname?"a":"", sockhead->version, sockhead->action, ntohs(sockhead->port), ((unsigned char *)&sockhead->dest)[0], ((unsigned char *)&sockhead->dest)[1], ((unsigned char *)&sockhead->dest)[2], ((unsigned char *)&sockhead->dest)[3], sockhead->userid, destdomname?" DESTNAME=":"", destdomname?destdomname:""); #endif /* WITH_MSGLEVEL <= E_INFO */ #if WITH_MSGLEVEL <= E_DEBUG { char *msgbuff; if ((msgbuff = Malloc(3*headlen)) != NULL) { xiohexdump((const unsigned char *)sockhead, headlen, msgbuff); Debug1("sending socks4(a) request data %s", msgbuff); } } #endif /* WITH_MSGLEVEL <= E_DEBUG */ if (writefull(xfd->fd, sockhead, headlen) < 0) { Msg4(level, "write(%d, %p, "F_Zu"): %s", xfd->fd, sockhead, headlen, strerror(errno)); if (Close(xfd->fd) < 0) { Info2("close(%d): %s", xfd->fd, strerror(errno)); } return STAT_RETRYLATER; /* retry complete open cycle */ } bytes = 0; Info("waiting for socks reply"); while (bytes >= 0) { /* loop over answer chunks until complete or error */ /* receive socks answer */ do { result = Read(xfd->fd, buff+bytes, SIZEOF_STRUCT_SOCKS4-bytes); } while (result < 0 && errno == EINTR); if (result < 0) { Msg4(level, "read(%d, %p, "F_Zu"): %s", xfd->fd, buff+bytes, SIZEOF_STRUCT_SOCKS4-bytes, strerror(errno)); if (Close(xfd->fd) < 0) { Info2("close(%d): %s", xfd->fd, strerror(errno)); } } if (result == 0) { Msg(level, "read(): EOF during read of socks reply, peer might not be a socks4 server"); if (Close(xfd->fd) < 0) { Info2("close(%d): %s", xfd->fd, strerror(errno)); } return STAT_RETRYLATER; } #if WITH_MSGLEVEL <= E_DEBUG { char msgbuff[3*SIZEOF_STRUCT_SOCKS4]; * xiohexdump((const unsigned char *)replyhead+bytes, result, msgbuff) = '\0'; Debug2("received socks4 reply data (offset "F_Zd"): %s", bytes, msgbuff); } #endif /* WITH_MSGLEVEL <= E_DEBUG */ bytes += result; if (bytes == SIZEOF_STRUCT_SOCKS4) { Debug1("received all "F_Zd" bytes", bytes); break; } Debug2("received %d bytes, waiting for "F_Zu" more bytes", result, SIZEOF_STRUCT_SOCKS4-bytes); } if (result <= 0) { /* we had a problem while reading socks answer */ return STAT_RETRYLATER; /* retry complete open cycle */ } Info7("received socks reply VN=%u CD=%u DSTPORT=%u DSTIP=%u.%u.%u.%u", replyhead->version, replyhead->action, ntohs(replyhead->port), ((uint8_t *)&replyhead->dest)[0], ((uint8_t *)&replyhead->dest)[1], ((uint8_t *)&replyhead->dest)[2], ((uint8_t *)&replyhead->dest)[3]); if (replyhead->version != 0) { Warn1("socks: reply code version is not 0 (%d)", replyhead->version); } switch (replyhead->action) { case SOCKS_CD_GRANTED: /* Notice("socks: connect request succeeded"); */ #if 0 if (Getsockname(xfd->fd, (struct sockaddr *)&us, &uslen) < 0) { Warn4("getsockname(%d, %p, {%d}): %s", xfd->fd, &us, uslen, strerror(errno)); } Notice1("successfully connected from %s via socks4", sockaddr_info((struct sockaddr *)&us, infobuff, sizeof(infobuff))); #else Notice("successfully connected via socks4"); #endif break; case SOCKS_CD_FAILED: Msg(level, "socks: connect request rejected or failed"); return STAT_RETRYLATER; case SOCKS_CD_NOIDENT: Msg(level, "socks: ident refused by client"); return STAT_RETRYLATER; case SOCKS_CD_IDENTFAILED: Msg(level, "socks: ident failed"); return STAT_RETRYLATER; default: Msg1(level, "socks: undefined status %u", replyhead->action); } return STAT_OK; }
int _xioopen_proxy_connect(struct single *xfd, struct proxyvars *proxyvars, int level) { size_t offset; char request[CONNLEN]; char buff[BUFLEN+1]; #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 */ sprintf(request, "CONNECT %s:%u HTTP/1.0\r\n", proxyvars->targetaddr, proxyvars->targetport); /* 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 */ do { sresult = Write(xfd->wfd, request, strlen(request)); } while (sresult < 0 && errno == EINTR); if (sresult < 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'; do { sresult = Write(xfd->wfd, header, strlen(header)); } while (sresult < 0 && errno == EINTR); if (sresult < 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\""); do { sresult = Write(xfd->wfd, "\r\n", 2); } while (sresult < 0 && errno == EINTR); /*! */ /* 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; }