mirror of
https://github.com/3proxy/3proxy.git
synced 2026-10-15 16:15:49 +08:00
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
506 lines
15 KiB
C
506 lines
15 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, conf.timeouts[CHAIN_TO]) != (int)strlen((char *)buf))
|
|
return 31;
|
|
param->statssrv64+=len;
|
|
param->nwrites++;
|
|
if((res = sockgetlinebuf(param, SERVER,buf,13,'\n',conf.timeouts[CHAIN_TO])) < 13)
|
|
return 32;
|
|
if(buf[9] != '2') return 33;
|
|
while((res = sockgetlinebuf(param, SERVER,buf,1023,'\n', conf.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, conf.timeouts[CHAIN_TO]) < len){
|
|
return 41;
|
|
}
|
|
param->statssrv64+=len;
|
|
param->nwrites++;
|
|
if((len = sockgetlinebuf(param, SERVER, buf, (redir->type == R_SOCKS4B)? 3:8, EOF, conf.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, conf.timeouts[CHAIN_TO]) < 2){
|
|
return 51;
|
|
}
|
|
param->statssrv64+=user?4:3;
|
|
param->nwrites++;
|
|
if(sockgetlinebuf(param, SERVER, buf, 2, EOF, conf.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, conf.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, conf.timeouts[CHAIN_TO]) != len){
|
|
return 51;
|
|
}
|
|
param->statssrv64+=len;
|
|
param->nwrites++;
|
|
if(sockgetlinebuf(param, SERVER, buf, 4, EOF, conf.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, conf.timeouts[CHAIN_TO]) == 6)
|
|
break;
|
|
return 59;
|
|
case 3:
|
|
if (sockgetlinebuf(param, SERVER, buf, 1, EOF, conf.timeouts[CHAIN_TO]) != 1) return 59;
|
|
len = (unsigned char)buf[4];
|
|
if (sockgetlinebuf(param, SERVER, buf, len + 2, EOF, conf.timeouts[CHAIN_TO]) != len + 2) return 59;
|
|
break;
|
|
case 4:
|
|
if (sockgetlinebuf(param, SERVER, buf, 18, EOF, conf.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 = ¶m->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 chain * cur, PROXYSOCKADDRTYPE * sa){
|
|
PROXYSOCKADDRTYPE fresh;
|
|
|
|
*sa = cur->addr;
|
|
if(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(!getip46(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(¶m->req) || !*SAPORT(¶m->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(¶m->sinsl) && (*SAFAMILY(¶m->sincr) == AF_INET || *SAFAMILY(¶m->sincr) == AF_INET6))param->sinsl = param->sincr;
|
|
#ifndef NOIPV6
|
|
else if(cur->cidr && *SAFAMILY(¶m->sinsl) == AF_INET6){
|
|
uint16_t c;
|
|
int i;
|
|
|
|
for(i = 0; i < 8; i++){
|
|
if(i*16 >= cur->cidr) ((uint16_t *)SAADDR(¶m->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(¶m->sinsl))[i]);
|
|
((uint16_t *)SAADDR(¶m->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(¶m->sinsr);
|
|
chainaddr(cur, ¶m->sinsr);
|
|
*SAPORT(¶m->sinsr) = port;
|
|
}
|
|
else if(SAISNULL(&cur->addr) && *SAPORT(&cur->addr)) *SAPORT(¶m->sinsr) = *SAPORT(&cur->addr);
|
|
else {
|
|
chainaddr(cur, ¶m->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(¶m->sincr) == AF_INET6 ? "TCP6" : "TCP4");
|
|
len += myinet_ntop(*SAFAMILY(¶m->sincr), SAADDR(¶m->sincr), buf+len, sizeof(buf) - len);
|
|
buf[len++] = ' ';
|
|
len += myinet_ntop(*SAFAMILY(¶m->sincl), SAADDR(¶m->sincl), buf+len, sizeof(buf) - len);
|
|
len += sprintf(buf + len, " %hu %hu\r\n",
|
|
ntohs(*SAPORT(¶m->sincr)),
|
|
ntohs(*SAPORT(¶m->sincl))
|
|
);
|
|
if(socksend(param, param->remsock, (unsigned char *)buf, len, conf.timeouts[CHAIN_TO])!=len) return 39;
|
|
if(cur->type == R_HA) return 0;
|
|
}
|
|
}
|
|
else {
|
|
PROXYSOCKADDRTYPE next;
|
|
|
|
chainaddr(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 *)¶m->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;
|
|
|
|
}
|