3proxy/src/socks.c
Vladimir Dubrovin b4bbcb9e9a Socket functions: _poll becomes _prepoll, which never waits
A layer over the sockets, TLS or a plugin's, no longer waits itself.
Its _prepoll only tells what it knows without asking the system: the
fds it knows to be ready, data TLS has already decrypted, or, returning
0, that the sockets have to be polled, for events it may rewrite: a
TLS read waiting for its socket to be writable. The wait is
sockpoll()'s, which calls _prepoll first and then polls, so that a wait
can be made other than with poll() in one place: a client of an event
loop is to wait in the loop. Whatever a rewritten wait gets is given to
the caller as what it asked for, its chance to try again, and the
events it asked for are given back. With no fds sockpoll() sleeps,
which WSAPoll() would refuse.

TLS remembers what a read or a write it could not do waits for. A read
needing to write, or a write to read, was an error which ended the
connection, as when TLS 1.3 answers a key update while the socket
cannot take more; it now waits for what TLS needs, as one needing what
it was asked for always did.

Incompatible for plugins, which have to be built again with the same
version of 3proxy, the structures they share not kept compatible:
- struct sockfuncs has _prepoll(state, fds, nfds) in place of
  _poll(state, fds, nfds, timeout). A plugin replacing _poll replaces
  _prepoll: it must not wait, returns the number of fds it set revents
  of (events then left as they are), 0 for the fds to be polled (with
  events as it leaves them, revents left to the poll) or -1, and calls
  the _prepoll it replaced when it has nothing to say itself.
- Waiting for sockets is sockpoll(sf, state, fds, nfds, timeout), not
  so._poll() of the socket functions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 19:54:03 +03:00

440 lines
14 KiB
C

/*
3APA3A simplest proxy server
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
please read License Agreement
*/
#include "proxy.h"
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
unsigned char * commands[] = {(unsigned char *)"UNKNOWN", (unsigned char *)"CONNECT", (unsigned char *)"BIND", (unsigned char *)"UDPMAP"};
#define BUFSIZE 1024
#if SOCKSTRACE > 0
char tracebuf[256];
#endif
static void printcommand(unsigned char * buf, int command, struct clientparam *param){
sprintf((char *)buf, "%s ", commands[command]);
if(*param->hostname){
sprintf((char *)buf + strlen((char *)buf), "%.256s", param->hostname);
}
else
myinet_ntop(*SAFAMILY(&param->req), SAADDR(&param->req), (char *)buf + strlen((char *)buf), 64);
sprintf((char *)buf+strlen((char *)buf), ":%hu", ntohs(*SAPORT(&param->req)));
}
void * sockschild(struct clientparam* param) {
int res;
unsigned i=0;
SOCKET s;
unsigned size;
SASIZETYPE sasize;
uint16_t port = 0;
/* a filter may hand back a buffer of its own in place of this one */
unsigned char * buf = param->tmpbuf;
/* the line logged, made apart from buf: logging makes its own line in
the scratch buffer */
unsigned char logline[340];
unsigned char c;
unsigned char command=0;
struct pollfd fds[3];
int ver=0;
int havepass = 0;
PROXYSOCKADDRTYPE sin;
param->service = S_SOCKS;
memset(buf, 0, BUFSIZE);
if ((ver = sockgetcharcli(param, param->srv->timeouts[SINGLEBYTE_L], 0)) != 5 && ver != 4) {
RETURN(401);
} /* version */
param->service = ver;
if(ver == 5){
if ((i = sockgetcharcli(param, param->srv->timeouts[SINGLEBYTE_S], 0)) == EOF) {RETURN(441);} /* nmethods */
for (; i; i--) {
if ((res = sockgetcharcli(param, param->srv->timeouts[SINGLEBYTE_S], 0)) == EOF) {RETURN(441);}
if (res == 2 && param->srv->needuser) {
havepass = res;
}
}
buf[0] = 5;
buf[1] = (param->srv->needuser > 1 && !havepass)? 255 : havepass;
if(socksend(param, param->clisock, buf, 2, param->srv->timeouts[STRING_S])!=2){RETURN(401);}
if (param->srv->needuser > 1 && !havepass) RETURN(4);
if (havepass) {
if (((res = sockgetcharcli(param, param->srv->timeouts[SINGLEBYTE_L], 0))) != 1) {
RETURN(412);
}
if ((i = sockgetcharcli(param, param->srv->timeouts[SINGLEBYTE_S], 0)) == EOF) {RETURN(451);}
if (i && (res = sockgetlinebuf(param, CLIENT, buf, i, 0, param->srv->timeouts[STRING_S])) != (int)i) {RETURN(441);}
buf[i] = 0;
if(!*param->username) {
if(PARAMSTR(param, username, buf)){RETURN(21);}
}
if ((i = sockgetcharcli(param, param->srv->timeouts[SINGLEBYTE_S], 0)) == EOF) {RETURN(445);}
if (i && (res = sockgetlinebuf(param, CLIENT, buf, i, 0, param->srv->timeouts[STRING_S])) != (int)i) {RETURN(441);}
buf[i] = 0;
if(!*param->password) {
if(PARAMSTR(param, password, buf)){RETURN(21);}
}
buf[0] = 1;
buf[1] = 0;
if(socksend(param, param->clisock, buf, 2, param->srv->timeouts[STRING_S])!=2){RETURN(481);}
}
if ((c = sockgetcharcli(param, param->srv->timeouts[SINGLEBYTE_L], 0)) != 5) {
RETURN(421);
}
}
if( (command = sockgetcharcli(param, param->srv->timeouts[SINGLEBYTE_S], 0)) < 1 || command > 3){command = 0; RETURN(407);} /* command */
if(ver == 5){
if (sockgetcharcli(param, param->srv->timeouts[SINGLEBYTE_S], 0) == EOF) {RETURN(447);} /* reserved */
c = sockgetcharcli(param, param->srv->timeouts[SINGLEBYTE_S], 0); /* atype */
}
else {
if ((res = sockgetcharcli(param, param->srv->timeouts[SINGLEBYTE_S], 0)) == EOF) {RETURN(441);}
buf[0] = (unsigned char) res;
if ((res = sockgetcharcli(param, param->srv->timeouts[SINGLEBYTE_S], 0)) == EOF) {RETURN(441);}
buf[1] = (unsigned char) res;
memcpy(&port, buf, 2);
c = 1;
}
size = 4;
switch(c) {
#ifndef NOIPV6
case 4:
size = 16;
#endif
case 1:
for (i = 0; i<size; i++){
if ((res = sockgetcharcli(param, param->srv->timeouts[SINGLEBYTE_S], 0)) == EOF) {RETURN(441);}
buf[i] = (unsigned char)res;
}
res = socks5_setaddr(param->srv->family, c, buf, &param->req);
if(res == 1) {RETURN(997);}
if(res == 2 && command == 1) {RETURN(431);}
param->sinsr = param->req;
myinet_ntop(*SAFAMILY(&param->sinsr), SAADDR(&param->sinsr), (char *)buf, 64);
break;
case 3:
if ((size = sockgetcharcli(param, param->srv->timeouts[SINGLEBYTE_S], 0)) == EOF) {RETURN(451);} /* nmethods */
for (i=0; i<size; i++){ /* size < 256 */
if ((res = sockgetcharcli(param, param->srv->timeouts[SINGLEBYTE_S], 0)) == EOF) {RETURN(451);}
buf[i] = (unsigned char)res;
}
buf[i] = 0;
if(command == 2 && param->srv->family != 6 && (!strcmp((char *)buf, "0.0.0.0") || !strcmp((char *)buf, "0"))) param->req = param->srv->extsa;
else if(!getip46m(param->srv->resolvmode, param->srv->family, buf, (struct sockaddr *) &param->req)) RETURN(100);
param->sinsr = param->req;
break;
default:
RETURN(997);
}
if(PARAMSTR(param, hostname, buf)){RETURN(21);}
if (ver == 5) {
if ((res = sockgetcharcli(param, param->srv->timeouts[SINGLEBYTE_S], 0)) == EOF) {RETURN(441);}
buf[0] = (unsigned char) res;
if ((res = sockgetcharcli(param, param->srv->timeouts[SINGLEBYTE_S], 0)) == EOF) {RETURN(441);}
buf[1] = (unsigned char) res;
memcpy(&port, buf, 2);
}
else {
if(sockgetlinebuf(param, CLIENT, buf, BUFSIZE - 1, 0, param->srv->timeouts[STRING_S]) < 0) {RETURN(441);}
buf[127] = 0;
if(param->srv->needuser && *buf && !*param->username) {
if(PARAMSTR(param, username, buf)){RETURN(21);}
}
if(!memcmp(SAADDR(&param->req), "\0\0\0", 3)){
param->service = S_SOCKS45;
if(sockgetlinebuf(param, CLIENT, buf, BUFSIZE - 1, 0, param->srv->timeouts[STRING_S]) < 0) {RETURN(441);}
buf[127] = 0;
if(PARAMSTR(param, hostname, buf)){RETURN(21);}
if(!getip46m(param->srv->resolvmode, param->srv->family, buf, (struct sockaddr *) &param->req)) RETURN(100);
param->sinsr = param->req;
}
}
*SAPORT(&param->sinsr) = *SAPORT(&param->req) = port;
if(command == 1 && !*SAPORT(&param->sinsr)) {RETURN(461);}
switch(command) {
case 1:
param->operation = CONNECT;
break;
case 2:
case 3:
#ifndef NOIPV6
param->sinsl = *SAFAMILY(&param->req)==AF_INET6? param->srv->extsa6 : param->srv->extsa;
#else
param->sinsl = param->srv->extsa;
#endif
param->operation = command == 2?BIND:UDPASSOC;
if(command == 2){
if ((param->remsock=nssocket(param->srv, &param->srv->so, param->sostate, NS_OUT, SASOCK(&param->req), SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {RETURN (11);}
setopts(param->remsock, &param->srv->lissockopts, param->srv->maxseg);
if(extbindtodevice(param, param->remsock)) {RETURN (12);}
}
break;
default:
RETURN(997);
}
if(command == 3) param->preauth = 1;
res = (*param->srv->authfunc)(param);
param->preauth = 0;
if(command == 3 && res == 2) res = 0;
if(res) {
RETURN(res);
}
#ifndef WITHMAIN
if(param->nreqfilters && buf){
int reqbufsize = BUFSIZE, reqsize, action;
printcommand(buf, command, param);
reqsize = strlen((char *)buf);
action = handlereqfilters(param, &buf, &reqbufsize, 0, &reqsize);
if(action == HANDLED){
RETURN(0);
}
if(action != PASS) RETURN(517);
}
#endif
if(command == 3) {
if((res = udpbind(param))) {RETURN(res);}
}
else if(command == 2) {
if(bindwithrange(param, param->remsock, &param->sinsl, param->extport)) {
/* a range has already been searched, retrying on any port would
ignore what was asked for */
if(param->extport) RETURN (12);
*SAPORT(&param->sinsl) = 0;
if(param->srv->so._bind(param->sostate, param->remsock,(struct sockaddr *)&param->sinsl,SASIZE(&param->sinsl)))RETURN (12);
#if SOCKSTRACE > 0
fprintf(stderr, "%hu bound to communicate with server\n", *SAPORT(&param->sinsl));
fflush(stderr);
#endif
}
sasize = sizeof(param->sinsl);
param->srv->so._getsockname(param->sostate, param->remsock, (struct sockaddr *)&param->sinsl, &sasize);
}
if(command == 3) {
param->ctrlsock = param->clisock;
param->clisock = nssocket(param->srv, &param->srv->so, param->sostate, NS_IN, SASOCK(&param->sincr), SOCK_DGRAM, IPPROTO_UDP);
if(param->clisock == INVALID_SOCKET) {RETURN(11);}
#ifdef _WIN32
{ unsigned long ul = 1; ioctlsocket(param->clisock, FIONBIO, &ul); }
#else
fcntl(param->clisock, F_SETFL, O_NONBLOCK | fcntl(param->clisock, F_GETFL));
#endif
if(intbindtodevice(param, param->clisock)) {RETURN (12);}
sin = param->sincl;
*SAPORT(&sin) = 0;
/* the port the client is told to send its datagrams to */
if(bindwithrange(param, param->clisock, &sin, param->intport)) {RETURN (12);}
sasize = sizeof(sin);
param->srv->so._getsockname(param->sostate, param->clisock, (struct sockaddr *)&sin, &sasize);
#if SOCKSTRACE > 0
fprintf(stderr, "%hu binded to communicate with client\n",
ntohs(*SAPORT(&sin))
);
fflush(stderr);
#endif
}
param->res = 0;
CLEANRET:
if(param->clisock != INVALID_SOCKET && buf){
int repcode;
sasize = sizeof(sin);
/* CONNECT and BIND report the address of the socket towards the
destination, UDP ASSOCIATE the one the client sends its datagrams
to, as does a request which never got a socket of its own */
if(command != 3 && param->remsock != INVALID_SOCKET){
param->srv->so._getsockname(param->sostate, param->remsock, (struct sockaddr *)&sin, &sasize);
if(!SAISNULL(&param->srv->extNat)){
uint16_t port;
port = *SAPORT(&sin);
sin = param->srv->extNat;
*SAPORT(&sin) = port;
}
}
else {
param->srv->so._getsockname(param->sostate, param->clisock, (struct sockaddr *)&sin, &sasize);
if(command == 3 && !SAISNULL(&param->srv->intNat)){
uint16_t port;
port = *SAPORT(&sin);
sin = param->srv->intNat;
*SAPORT(&sin) = port;
}
}
#if SOCKSTRACE > 0
myinet_ntop(*SAFAMILY(&sin), SAADDR(&sin), tracebuf, SASIZE(&sin));
fprintf(stderr, "Sending confirmation to client with code %d for %s with %s:%hu\n",
param->res,
commands[command],
tracebuf,
ntohs(*SAPORT(&sin))
);
fflush(stderr);
#endif
if(!param->res) repcode = 0;
else if(param->res <= 10) repcode = 2;
else if (param->res < 20) repcode = 5;
else if (param->res < 30) repcode = 1;
else if (param->res < 100) repcode = 4;
else if (param->res == 100) repcode = 4;
else if (param->res == 997) repcode = 8;
else repcode = param->res%10;
if(ver == 5){
buf[0] = 5;
buf[1] = repcode;
buf[2] = 0;
buf[3] = (*SAFAMILY(&sin) == AF_INET)?1:4;
memcpy(buf+4, SAADDR(&sin), SAADDRLEN(&sin));
memcpy(buf+4+SAADDRLEN(&sin), SAPORT(&sin), 2);
socksend(param, (command == 3)?param->ctrlsock:param->clisock, buf, 6+SAADDRLEN(&sin), param->srv->timeouts[STRING_S]);
}
else{
buf[0] = 0;
buf[1] = 90 + !!(repcode);
if(*SAFAMILY(&sin) == AF_INET){
memcpy(buf+2, SAPORT(&sin), 2);
memcpy(buf+4, SAADDR(&sin), 4);
} else {
memset(buf+2, 0, 6);
param->res = 997;
}
socksend(param, param->clisock, buf, 8, param->srv->timeouts[STRING_S]);
}
if (param->npredatfilters){
int action;
action = handlepredatflt(param);
if(action == HANDLED){
param->res = 0;
}
if(action != PASS){
param->res = 19;
}
}
if (param->res == 0) {
switch(command) {
case 1:
if(param->redirectfunc){
if(buf != param->tmpbuf) free(buf);
return (void *)param->redirectfunc;
}
param->res = mapsocket(param, param->srv->timeouts[CONNECTION_L]);
break;
case 2:
param->srv->so._listen (param->sostate, param->remsock, 1);
fds[0].fd = param->remsock;
fds[1].fd = param->clisock;
fds[0].events = fds[1].events = POLLIN;
res = sockpoll(&param->srv->so, param->sostate, fds, 2, param->srv->timeouts[CONNECTION_L] * 1000);
if (res < 1 || fds[1].revents) {
res = 460;
break;
}
sasize = sizeof(param->sinsr);
s = param->srv->so._accept(param->sostate, param->remsock, (struct sockaddr *)&param->sinsr, &sasize);
param->srv->so._closesocket(param->sostate, param->remsock);
param->remsock = s;
if(s == INVALID_SOCKET) {
param->res = 462;
break;
}
if(!SAISNULL(&param->req) &&
memcmp(SAADDR(&param->req),SAADDR(&param->sinsr),SAADDRLEN(&param->req))) {
param->res = 470;
break;
}
{
#ifdef _WIN32
unsigned long ul=1;
ioctlsocket(param->remsock, FIONBIO, &ul);
#else
{
int flags = fcntl(param->remsock, F_GETFL);
if(flags != -1) fcntl(param->remsock, F_SETFL, O_NONBLOCK | flags);
}
#endif
}
#if SOCKSTRACE > 0
fprintf(stderr, "Sending incoming connection to client with code %d for %s with %hu\n",
param->res,
commands[command],
*SAPORT(&param->sinsr)
);
fflush(stderr);
#endif
if(ver == 5){
buf[3] = (*SAFAMILY(&param->sinsr) == AF_INET)?1:4;
memcpy(buf+4, SAADDR(&param->sinsr), SAADDRLEN(&param->sinsr));
memcpy(buf+4+SAADDRLEN(&param->sinsr), SAPORT(&param->sinsr), 2);
socksend(param, param->clisock, buf, 6+SAADDRLEN(&param->sinsr), param->srv->timeouts[STRING_S]);
}
else {
memcpy (buf+2, SAPORT(&param->sinsr), 2);
memcpy (buf+4, SAADDR(&param->sinsr), 4);
socksend(param, param->clisock, buf, 8, param->srv->timeouts[STRING_S]);
}
param->res = mapsocket(param, param->srv->timeouts[CONNECTION_S]);
break;
case 3:
param->sinsr = param->req;
param->res = udpsockmap(param, param->srv->timeouts[CONNECTION_L]);
break;
default:
param->res = 417;
break;
}
}
}
if(command > 3) command = 0;
printcommand(logline, command, param);
dolog(param, logline);
if(buf != param->tmpbuf) free(buf);
return (NULL);
}
#ifdef WITHMAIN
struct proxydef childdef = {
sockschild,
1080,
0,
S_SOCKS,
"-Ne(EXTERNAL_IP) External NAT address (between 3proxy and destination server) to report to client for CONNECT / BIND\n"
"-Ni(INTERNAL_IP) Internal NAT address (between client and 3proxy) to report to client for UDPASSOC\n"
"NAT is required to map IP-to-IP without port translation\n"
};
#include "proxymain.c"
#endif