3proxy/src/redirect.c
Vladimir Dubrovin 3d20e1104e
Some checks are pending
C/C++ CI Linux / ${{ matrix.target }} (ubuntu-24.04-arm) (push) Waiting to run
C/C++ CI Linux / ${{ matrix.target }} (ubuntu-latest) (push) Waiting to run
C/C++ CI MacOS / ${{ matrix.target }} (macos-15) (push) Waiting to run
C/C++ CI Windows / ${{ matrix.target }} (windows-2022) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (macos-15) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (ubuntu-24.04-arm) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (ubuntu-latest) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (windows-2022) (push) Waiting to run
C/C++ CI cmake / ubuntu-latest (wolfSSL) (push) Waiting to run
resolver: per service, defaultresolve, fakeresolve or sysresolve
Each service resolves names the way the last of defaultresolve (the name
servers in force, configured or the system's with the hosts file),
fakeresolve (127.0.0.2) and sysresolve (the C library) read before it
says; the last one in the configuration also applies outside services.
defaultresolve is the new command, and the default.

The name servers and the hosts file no longer depend on the mode: nserver
lines, or the system's, are always in force, so dnspr keeps forwarding;
with fakeresolve it does not, as before. fakeresolver moves to common.c,
which the standalone services link without resolve.c.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 18:45:48 +03:00

506 lines
16 KiB
C

/*
3APA3A simplest proxy server
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
please read License Agreement
*/
#include "proxy.h"
static FILTER_ACTION (*ext_ssl_parent)(struct clientparam * param) = NULL;
static FILTER_ACTION ssl_parent(struct clientparam * param){
if(ext_ssl_parent) return ext_ssl_parent(param);
ext_ssl_parent = pluginlink.findbyname("ssl_parent");
if(ext_ssl_parent) return ext_ssl_parent(param);
return REJECT;
}
int clientnegotiate(struct chain * redir, struct clientparam * param, struct sockaddr * addr, unsigned char * hostname){
unsigned char *buf;
unsigned char *username;
int res;
int len=0;
unsigned char * user, *pass;
user = redir->extuser;
pass = redir->extpass;
buf = param->srvbuf;
username = buf + 2048;
if(user) {
if (*user == '*') {
if(!*param->username) return 4;
user = param->username;
pass = param->password;
}
}
if(redir->secure){
res = ssl_parent(param);
if(res != PASS) return res;
}
switch(redir->type){
case R_TCP:
case R_HTTP:
case R_IMAP:
return 0;
case R_CONNECT:
case R_CONNECTP:
{
len = sprintf((char *)buf, "CONNECT ");
if(redir->type == R_CONNECTP && hostname) {
char * needreplace;
needreplace = strchr((char *)hostname, ':');
if(needreplace) buf[len++] = '[';
len += sprintf((char *)buf + len, "%.256s", (char *)hostname);
if(needreplace) buf[len++] = ']';
}
else {
if(*SAFAMILY(addr) == AF_INET6) buf[len++] = '[';
len += myinet_ntop(*SAFAMILY(addr), SAADDR(addr), (char *)buf+len, 256);
if(*SAFAMILY(addr) == AF_INET6) buf[len++] = ']';
}
len += sprintf((char *)buf + len,
":%hu HTTP/1.0\r\nConnection: keep-alive\r\n", ntohs(*SAPORT(addr)));
if(user){
len += sprintf((char *)buf + len, "Proxy-Authorization: Basic ");
sprintf((char *)username, "%.128s:%.128s", user, pass?pass:(unsigned char *)"");
if(!en64(username, buf+len, (int)strlen((char *)username), 2048 - len)) return 21;
len = (int)strlen((char *)buf);
len += sprintf((char *)buf + len, "\r\n");
}
len += sprintf((char *)buf + len, "\r\n");
if(socksend(param, param->remsock, buf, len, param->srv->timeouts[CHAIN_TO]) != (int)strlen((char *)buf))
return 31;
param->statssrv64+=len;
param->nwrites++;
if((res = sockgetlinebuf(param, SERVER,buf,13,'\n',param->srv->timeouts[CHAIN_TO])) < 13)
return 32;
if(buf[9] != '2') return 33;
while((res = sockgetlinebuf(param, SERVER,buf,1023,'\n', param->srv->timeouts[CHAIN_TO])) > 2);
if(res <= 0) return 34;
return 0;
}
case R_SOCKS4:
case R_SOCKS4P:
case R_SOCKS4B:
{
if(*SAFAMILY(addr) != AF_INET) return 44;
buf[0] = 4;
buf[1] = 1;
memcpy(buf+2, SAPORT(addr), 2);
if(redir->type == R_SOCKS4P && hostname) {
buf[4] = buf[5] = buf[6] = 0;
buf[7] = 3;
}
else memcpy(buf+4, SAADDR(addr), 4);
if(!user)user = (unsigned char *)"anonymous";
len = (int)strlen((char *)user) + 1;
memcpy(buf+8, user, len);
len += 8;
if(redir->type == R_SOCKS4P && hostname) {
int hostnamelen;
hostnamelen = (int)strlen((char *)hostname) + 1;
if(hostnamelen > 255) hostnamelen = 255;
memcpy(buf+len, hostname, hostnamelen);
len += hostnamelen;
}
if(socksend(param, param->remsock, buf, len, param->srv->timeouts[CHAIN_TO]) < len){
return 41;
}
param->statssrv64+=len;
param->nwrites++;
if((len = sockgetlinebuf(param, SERVER, buf, (redir->type == R_SOCKS4B)? 3:8, EOF, param->srv->timeouts[CHAIN_TO])) != ((redir->type == R_SOCKS4B)? 3:8)){
return 42;
}
if(buf[1] != 90) {
return 43;
}
}
return 0;
case R_SOCKS5:
case R_SOCKS5P:
case R_SOCKS5B:
{
int inbuf = 0;
int atyp;
int skip_port = 0;
buf[0] = 5;
buf[1] = user? 2 : 1;
buf[2] = 0;
buf[3] = 2;
if(socksend(param, param->remsock, buf, user?4:3, param->srv->timeouts[CHAIN_TO]) < 2){
return 51;
}
param->statssrv64+=user?4:3;
param->nwrites++;
if(sockgetlinebuf(param, SERVER, buf, 2, EOF, param->srv->timeouts[CHAIN_TO]) != 2){
return 52;
}
if(buf[0] != 5) {
return 53;
}
if(buf[1] != 0 && !(buf[1] == 2 && user)){
return 54;
}
if(buf[1] == 2){
buf[inbuf++] = 1;
buf[inbuf] = (unsigned char)strlen((char *)user);
memcpy(buf+inbuf+1, user, buf[inbuf]);
inbuf += buf[inbuf] + 1;
buf[inbuf] = pass?(unsigned char)strlen((char *)pass):0;
if(pass)memcpy(buf+inbuf+1, pass, buf[inbuf]);
inbuf += buf[inbuf] + 1;
if(socksend(param, param->remsock, buf, inbuf, param->srv->timeouts[CHAIN_TO]) != inbuf){
return 51;
}
param->statssrv64+=inbuf;
param->nwrites++;
if(sockgetlinebuf(param, SERVER, buf, 2, EOF, 60) != 2){
return 55;
}
if(buf[0] != 1 || buf[1] != 0) {
return 56;
}
}
buf[0] = 5;
buf[1] = (param->operation == UDPASSOC) ? 3 : 1;
buf[2] = 0;
if (param->operation == UDPASSOC) {
buf[3] = 1;
memset(buf + 4, 0, 6);
len = 10;
skip_port = 1;
} else if(redir->type == R_SOCKS5P && hostname) {
buf[3] = 3;
len = (int)strlen((char *)hostname);
if(len > 255) len = 255;
buf[4] = len;
memcpy(buf + 5, hostname, len);
len += 5;
}
else {
len = 3;
buf[len++] = (*SAFAMILY(addr) == AF_INET)? 1 : 4;
memcpy(buf+len, SAADDR(addr), SAADDRLEN(addr));
len += SAADDRLEN(addr);
}
if (!skip_port) {
memcpy(buf+len, SAPORT(addr), 2);
len += 2;
}
if(socksend(param, param->remsock, buf, len, param->srv->timeouts[CHAIN_TO]) != len){
return 51;
}
param->statssrv64+=len;
param->nwrites++;
if(sockgetlinebuf(param, SERVER, buf, 4, EOF, param->srv->timeouts[CHAIN_TO]) != 4){
return 57;
}
if(buf[0] != 5) {
return 53;
}
if(buf[1] != 0) {
return 60 + (buf[1] % 10);
}
atyp = buf[3];
switch (buf[3]) {
case 1:
if (redir->type == R_SOCKS5B || sockgetlinebuf(param, SERVER, buf, 6, EOF, param->srv->timeouts[CHAIN_TO]) == 6)
break;
return 59;
case 3:
if (sockgetlinebuf(param, SERVER, buf, 1, EOF, param->srv->timeouts[CHAIN_TO]) != 1) return 59;
len = (unsigned char)buf[4];
if (sockgetlinebuf(param, SERVER, buf, len + 2, EOF, param->srv->timeouts[CHAIN_TO]) != len + 2) return 59;
break;
case 4:
if (sockgetlinebuf(param, SERVER, buf, 18, EOF, param->srv->timeouts[CHAIN_TO]) == 18)
break;
return 59;
default:
return 58;
}
if (param->operation == UDPASSOC && (redir->type == R_SOCKS5 || redir->type == R_SOCKS5P) && param->udp_nhops < 3) {
PROXYSOCKADDRTYPE *relay = &param->udp_relay[param->udp_nhops];
memset(relay, 0, sizeof(*relay));
if (atyp == 1) {
*SAFAMILY(relay) = AF_INET;
memcpy(SAADDR(relay), buf, 4);
memcpy(SAPORT(relay), buf + 4, 2);
if (param->udp_nhops == 0) {
param->sinsr = *relay;
}
param->udp_nhops++;
} else if (atyp == 4) {
*SAFAMILY(relay) = AF_INET6;
memcpy(SAADDR(relay), buf, 16);
memcpy(SAPORT(relay), buf + 16, 2);
if (param->udp_nhops == 0) param->sinsr = *relay;
param->udp_nhops++;
}
}
return 0;
}
default:
return 30;
}
}
/* The local port ranges do not depend on the destination, so they can be taken
* as soon as a rule matches. UDP ASSOCIATE is authorized before the destination
* is known and returns before the chain is walked, which would otherwise leave
* the socket the client sends its datagrams to outside the configured range.
*/
void applyportranges(struct clientparam * param, struct ace * acentry){
struct chain *cur;
for(cur = acentry->chains; cur; cur = cur->next){
if(cur->type == R_EXTPORT) param->extport = cur->range;
else if(cur->type == R_INTPORT) param->intport = cur->range;
}
}
static void chainaddr(struct clientparam * param, struct chain * cur, PROXYSOCKADDRTYPE * sa){
PROXYSOCKADDRTYPE fresh;
*sa = cur->addr;
if(param->srv->resolvmode != RESOLVE_DEFAULT || resolvfunc != myresolver) return;
if(!cur->exthost || SAISNULL(&cur->addr)) return;
#ifdef WITH_UN
if(*SAFAMILY(&cur->addr) == AF_UNIX) return;
#endif
if(afdetect(cur->exthost) != -1) return;
memset(&fresh, 0, sizeof(fresh));
if(!getip46m(RESOLVE_DEFAULT, 46, cur->exthost, (struct sockaddr *)&fresh)) return;
*SAPORT(&fresh) = *SAPORT(&cur->addr);
*sa = fresh;
}
static int parentfailed(struct clientparam * param, struct chain * ch){
int i;
for(i = 0; i < param->nfailedparents; i++)
if(param->failedparents[i] == ch) return 1;
return 0;
}
/* Remember a parent which could not be used for this connection, so that a
* retry picks another member of its group. The list is per connection: a
* parent which is down for one client is not taken away from the others.
*/
static void parentfail(struct clientparam * param, struct chain * ch){
if(!ch || param->nfailedparents >= MAXFAILEDPARENTS) return;
if(parentfailed(param, ch)) return;
param->failedparents[param->nfailedparents++] = ch;
}
/* Pick the parent to use for one group.
*
* A group is the members whose weights add up to WEIGHTSCALE, together with
* the zero weight members among them. *after is left pointing at the group
* after this one, or at NULL. A group which lands within WEIGHTFUZZ of the
* whole share counts as a whole one, so that 333 three times over is a group
* rather than a group and a remainder of a thousandth.
*
* A member which already failed for this connection is not offered again and
* its weight is given to the others, so a retry goes somewhere else. Zero
* weight members are the fallback: they are only offered once every weighted
* member of the group has failed, and they never take part in the share.
* Where the weights add up to plainly less than the whole share the remainder
* keeps its meaning of "no parent at all" and is still counted, so such a
* group can still leave the connection direct.
*
* Returns NULL when the group adds no parent. *exhausted tells the two cases
* apart: it is set when the group had parents and all of them are gone, which
* is a failure rather than a reason to connect directly.
*/
static struct chain * pickchain(struct clientparam * param, struct chain * group,
struct chain ** after, int * exhausted){
struct chain *cur;
uint64_t total = 0, avail = 0, slack;
uint64_t r;
*exhausted = 0;
for(cur = group; cur; cur = cur->next){
if(total + WEIGHTFUZZ >= WEIGHTSCALE && cur->weight) break;
total += cur->weight;
if(cur->weight && !parentfailed(param, cur)) avail += cur->weight;
}
*after = cur;
if(avail){
slack = (total + WEIGHTFUZZ < WEIGHTSCALE)? WEIGHTSCALE - total : 0;
r = ((uint64_t)myrand() << 32 | myrand()) % (avail + slack);
for(cur = group; cur != *after; cur = cur->next){
if(!cur->weight || parentfailed(param, cur)) continue;
if(r < cur->weight) return cur;
r -= cur->weight;
}
return NULL;
}
for(cur = group; cur != *after; cur = cur->next){
if(!cur->weight && !parentfailed(param, cur)) return cur;
}
if(total) *exhausted = 1;
return NULL;
}
int handleredirect(struct clientparam * param, struct ace * acentry){
int connected = 0;
int res;
int ha = 0;
struct chain * cur;
struct chain * after;
struct chain * redir = NULL;
int saved = 0;
if((SAISNULL(&param->req) || !*SAPORT(&param->req)) && param->operation != UDPASSOC) {
return 100;
}
for(cur = acentry->chains; cur; cur = after){
struct chain * sel;
int exhausted;
sel = pickchain(param, cur, &after, &exhausted);
/* every parent of the group is gone: connecting direct instead
would be a way around the rule, so the request fails */
if(exhausted) return 13;
if(!sel) continue;
cur = sel;
if(cur->type != R_EXTIP && cur->type != R_HA &&
cur->type != R_EXTPORT && cur->type != R_INTPORT) param->redirected++;
if(!connected){
if(cur->type == R_EXTPORT || cur->type == R_INTPORT){
if(cur->type == R_EXTPORT) param->extport = cur->range;
else param->intport = cur->range;
if(after)continue;
return 0;
}
if(cur->type == R_EXTIP){
param->sinsl = cur->addr;
if(SAISNULL(&param->sinsl) && (*SAFAMILY(&param->sincr) == AF_INET || *SAFAMILY(&param->sincr) == AF_INET6))param->sinsl = param->sincr;
#ifndef NOIPV6
else if(cur->cidr && *SAFAMILY(&param->sinsl) == AF_INET6){
uint16_t c;
int i;
for(i = 0; i < 8; i++){
if(i*16 >= cur->cidr) ((uint16_t *)SAADDR(&param->sinsl))[i] |= (uint16_t)myrand();
else if ((i+1)*16 > cur->cidr){
c = (uint16_t)myrand();
c >>= (cur->cidr - (i*16));
c |= ntohs(((uint16_t *)SAADDR(&param->sinsl))[i]);
((uint16_t *)SAADDR(&param->sinsl))[i] = htons(c);
}
}
}
#endif
if(after)continue;
return 0;
}
else if(SAISNULL(&cur->addr) && !*SAPORT(&cur->addr)){
int i;
if(cur->extuser){
if(*cur->extuser == '*' && !*param->username) return 4;
if(PARAMSTR(param, extusername, (*cur->extuser == '*')? param->username : cur->extuser)) return 21;
if(cur->extpass && PARAMSTR(param, extpassword, (*cur->extuser == '*' && *param->password)?param->password : cur->extpass)) return 21;
}
for(i=0; redirs[i].name; i++){
if(cur->type == redirs[i].redir) {
param->redirectfunc = redirs[i].func;
break;
}
}
if(cur->type == R_HA){
ha = 1;
}
if(after)continue;
if(!ha) return 0;
if(param->operation == UDPASSOC) return 0;
}
else if(!*SAPORT(&cur->addr) && !SAISNULL(&cur->addr)) {
uint16_t port = *SAPORT(&param->sinsr);
chainaddr(param, cur, &param->sinsr);
*SAPORT(&param->sinsr) = port;
}
else if(SAISNULL(&cur->addr) && *SAPORT(&cur->addr)) *SAPORT(&param->sinsr) = *SAPORT(&cur->addr);
else {
chainaddr(param, cur, &param->sinsr);
}
if(param->operation == UDPASSOC){
SOCKET s;
if(cur->type != R_SOCKS5 && cur->type != R_SOCKS5P) return 70;
s = param->remsock;
param->remsock = INVALID_SOCKET;
param->ctrlsocksrv = s;
saved = 1;
}
if((res = alwaysauth(param))){
parentfail(param, cur);
return (res >= 10)? res : 60+res;
}
if(ha) {
char buf[128];
int len;
len = sprintf(buf, "PROXY %s ",
*SAFAMILY(&param->sincr) == AF_INET6 ? "TCP6" : "TCP4");
len += myinet_ntop(*SAFAMILY(&param->sincr), SAADDR(&param->sincr), buf+len, sizeof(buf) - len);
buf[len++] = ' ';
len += myinet_ntop(*SAFAMILY(&param->sincl), SAADDR(&param->sincl), buf+len, sizeof(buf) - len);
len += sprintf(buf + len, " %hu %hu\r\n",
ntohs(*SAPORT(&param->sincr)),
ntohs(*SAPORT(&param->sincl))
);
if(socksend(param, param->remsock, (unsigned char *)buf, len, param->srv->timeouts[CHAIN_TO])!=len) return 39;
if(cur->type == R_HA) return 0;
}
}
else {
PROXYSOCKADDRTYPE next;
chainaddr(param, cur, &next);
res = (redir)?clientnegotiate(redir, param, (struct sockaddr *)&next, cur->exthost):0;
if(res) {
parentfail(param, cur);
return res;
}
}
redir = cur;
param->redirtype = redir->type;
if(redir->type == R_TCP || redir->type ==R_HTTP) {
if(cur->extuser){
if(*cur -> extuser == '*' && !*param->username) return 4;
if(PARAMSTR(param, extusername, (*cur->extuser == '*')? param->username : cur->extuser)) return 21;
if(cur->extpass && PARAMSTR(param, extpassword, (*cur->extuser == '*' && *param->password)?param->password : cur->extpass)) return 21;
}
if(redir->secure) return ssl_parent(param);
return 0;
}
connected = 1;
}
if(!connected || !redir) return 0;
res = clientnegotiate(redir, param, (struct sockaddr *)&param->req, *param->hostname? param->hostname : NULL);
if(res) parentfail(param, redir);
if(saved){
SOCKET s;
s = param->ctrlsocksrv;
param->ctrlsocksrv = param->remsock;
param->remsock = s;
param->operation = UDPASSOC;
}
return res;
}