3proxy/src/tlspr.c
2026-10-13 15:12:05 +03:00

438 lines
14 KiB
C

/*
3APA3A simplest proxy server
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
please read License Agreement
*/
#include "proxy.h"
#ifndef PORTMAP
#define PORTMAP
#endif
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
unsigned size16(unsigned char *buf){
unsigned res;
res = (((unsigned)buf[0]) << 8) + +buf[1];
return res;
}
#define BSIZE (4096)
#define SNILEN (256)
#define PROTOLEN (32)
int srvstarttls(struct clientparam *param, PROXYSERVICE proto){
unsigned char buf[1024];
int i, found = 0;
switch(proto){
case S_IMAPP:
i = sockgetlinebuf(param, SERVER, buf, sizeof(buf) - 1, '\n', param->srv->timeouts[STRING_L]);
if(i < 4) return 1;
buf[i] = 0;
if(strncasecmp((char *)buf, "* OK", 4)) return 1;
found = hascap(buf, "STARTTLS");
if(!found){
if(socksend(param, param->remsock, (unsigned char *)"zz CAPABILITY\r\n", 15, param->srv->timeouts[STRING_S])!=15) return 2;
for(;;){
i = sockgetlinebuf(param, SERVER, buf, sizeof(buf) - 1, '\n', param->srv->timeouts[STRING_L]);
if(i < 3) return 2;
buf[i] = 0;
if(!strncasecmp((char *)buf, "* CAPABILITY", 12) && hascap(buf, "STARTTLS")) found = 1;
if(!strncasecmp((char *)buf, "zz ", 3)) break;
}
if(!found) return 3;
}
if(socksend(param, param->remsock, (unsigned char *)"zz STARTTLS\r\n", 13, param->srv->timeouts[STRING_S])!=13) return 2;
i = sockgetlinebuf(param, SERVER, buf, sizeof(buf) - 1, '\n', param->srv->timeouts[STRING_L]);
if(i < 5) return 2;
buf[i] = 0;
if(strncasecmp((char *)buf, "zz OK", 5)) return 3;
break;
case S_POP3P:
i = sockgetlinebuf(param, SERVER, buf, sizeof(buf) - 1, '\n', param->srv->timeouts[STRING_L]);
if(i < 3) return 1;
buf[i] = 0;
if(strncasecmp((char *)buf, "+OK", 3)) return 1;
if(socksend(param, param->remsock, (unsigned char *)"CAPA\r\n", 6, param->srv->timeouts[STRING_S])!=6) return 2;
for(;;){
i = sockgetlinebuf(param, SERVER, buf, sizeof(buf) - 1, '\n', param->srv->timeouts[STRING_L]);
if(i < 1) return 2;
buf[i] = 0;
if(buf[0] == '.') break;
if(!strncasecmp((char *)buf, "-ERR", 4)) return 2;
if(hascap(buf, "STLS")) found = 1;
}
if(!found) return 3;
if(socksend(param, param->remsock, (unsigned char *)"STLS\r\n", 6, param->srv->timeouts[STRING_S])!=6) return 2;
i = sockgetlinebuf(param, SERVER, buf, sizeof(buf) - 1, '\n', param->srv->timeouts[STRING_L]);
if(i < 3) return 2;
buf[i] = 0;
if(strncasecmp((char *)buf, "+OK", 3)) return 3;
break;
case S_SMTPP:
i = getmultiline(param, SERVER, buf, sizeof(buf) - 1, NULL, NULL);
if(i < 3) return 1;
buf[i] = 0;
if(strncasecmp((char *)buf, "220", 3)) return 1;
i = sprintf((char *)buf, "EHLO [");
i += myinet_ntop(*SAFAMILY(&param->sinsl), SAADDR(&param->sinsl), (char *)buf+strlen((char *)buf), 64);
i += sprintf((char *)buf+strlen((char *)buf), "]\r\n");
if(socksend(param, param->remsock, buf, i, param->srv->timeouts[STRING_S])!= i) return 2;
i = getmultiline(param, SERVER, buf, sizeof(buf) - 1, "STARTTLS", &found);
if(i < 3) return 2;
buf[i] = 0;
if(strncasecmp((char *)buf, "250", 3)) return 2;
if(!found) return 3;
if(socksend(param, param->remsock, (unsigned char *)"STARTTLS\r\n", 10, param->srv->timeouts[STRING_S])!= 10) return 2;
i = sockgetlinebuf(param, SERVER, buf, sizeof(buf) - 1, '\n', param->srv->timeouts[STRING_L]);
if(i < 3) return 2;
buf[i] = 0;
if(strncasecmp((char *)buf, "220", 3)) return 3;
break;
default:
return 1;
}
return 0;
}
uint16_t starttlsport(PROXYSERVICE proto){
switch(proto){
case S_IMAPP: return 143;
case S_POP3P: return 110;
case S_SMTPP: return 587;
default: return 443;
}
}
int clistarttls(struct clientparam *param, PROXYSERVICE proto){
unsigned char buf[1024];
unsigned char tag[64];
unsigned char *se, *cmd;
int i;
switch(proto){
case S_IMAPP:
i = sprintf((char *)buf, "* OK [CAPABILITY IMAP4rev1 STARTTLS LOGINDISABLED] IMAP4rev1 Proxy Ready\r\n");
if(socksend(param, param->clisock, buf, i, param->srv->timeouts[STRING_S])!=i) return 1;
for(;;){
i = sockgetlinebuf(param, CLIENT, buf, sizeof(buf) - 10, '\n', param->srv->timeouts[STRING_S]);
if(i < 4) return 1;
buf[i] = 0;
if ((se=(unsigned char *)strchr((char *)buf, '\r'))) *se = 0;
if (!(se=(unsigned char *)strchr((char *)buf, ' ')) || (se - buf) >= (int)(sizeof(tag) - 1)) return 1;
memcpy(tag, buf, se - buf);
tag[se - buf] = 0;
cmd = se + 1;
if(!strncasecmp((char *)cmd, "LOGOUT", 6)){
socksend(param, param->clisock, (unsigned char *)"* BYE\r\n", 7, param->srv->timeouts[STRING_S]);
sprintf((char *)buf, "%.60s OK LOGOUT completed\r\n", (char *)tag);
socksend(param, param->clisock, buf, (int)strlen((char *)buf), param->srv->timeouts[STRING_S]);
return -1;
}
if(!strncasecmp((char *)cmd, "CAPABILITY", 10)){
i = sprintf((char *)buf, "* CAPABILITY IMAP4rev1 STARTTLS LOGINDISABLED\r\n");
socksend(param, param->clisock, buf, i, param->srv->timeouts[STRING_S]);
sprintf((char *)buf, "%.60s OK CAPABILITY completed\r\n", (char *)tag);
socksend(param, param->clisock, buf, (int)strlen((char *)buf), param->srv->timeouts[STRING_S]);
continue;
}
if(!strncasecmp((char *)cmd, "STARTTLS", 8)){
sprintf((char *)buf, "%.60s OK Begin TLS negotiation\r\n", (char *)tag);
if(socksend(param, param->clisock, buf, (int)strlen((char *)buf), param->srv->timeouts[STRING_S]) <= 0) return 1;
return 0;
}
sprintf((char *)buf, "%.60s BAD need STARTTLS first\r\n", (char *)tag);
socksend(param, param->clisock, buf, (int)strlen((char *)buf), param->srv->timeouts[STRING_S]);
}
case S_POP3P:
if(socksend(param, param->clisock, (unsigned char *)"+OK Proxy\r\n", 11, param->srv->timeouts[STRING_S])!=11) return 1;
for(;;){
i = sockgetlinebuf(param, CLIENT, buf, sizeof(buf) - 10, '\n', param->srv->timeouts[STRING_S]);
if(i < 4) return 1;
if(!strncasecmp((char *)buf, "STLS", 4)){
if(socksend(param, param->clisock, (unsigned char *)"+OK Begin TLS negotiation\r\n", 27, param->srv->timeouts[STRING_S])!=27) return 1;
return 0;
}
if(!strncasecmp((char *)buf, "CAPA", 4)){
socksend(param, param->clisock, (unsigned char *)"+OK Capability list follows\r\nSTLS\r\n.\r\n", 38, param->srv->timeouts[STRING_S]);
continue;
}
if(!strncasecmp((char *)buf, "QUIT", 4)){
socksend(param, param->clisock, (unsigned char *)"+OK\r\n", 5, param->srv->timeouts[STRING_S]);
return -1;
}
socksend(param, param->clisock, (unsigned char *)"-ERR need STLS first\r\n", 22, param->srv->timeouts[STRING_S]);
}
case S_SMTPP:
if(socksend(param, param->clisock, (unsigned char *)"220 Proxy\r\n", 11, param->srv->timeouts[STRING_S])!=11) return 1;
for(;;){
i = sockgetlinebuf(param, CLIENT, buf, sizeof(buf) - 10, '\n', param->srv->timeouts[STRING_S]);
if(i < 4) return 1;
if(!strncasecmp((char *)buf, "STARTTLS", 8)){
if(socksend(param, param->clisock, (unsigned char *)"220 2.0.0 Ready to start TLS\r\n", 30, param->srv->timeouts[STRING_S])!=30) return 1;
return 0;
}
if(!strncasecmp((char *)buf, "EHLO ", 5)){
socksend(param, param->clisock, (unsigned char *)"250-Proxy\r\n250 STARTTLS\r\n", 25, param->srv->timeouts[STRING_S]);
continue;
}
if(!strncasecmp((char *)buf, "HELO ", 5)){
socksend(param, param->clisock, (unsigned char *)"250 Proxy\r\n", 11, param->srv->timeouts[STRING_S]);
continue;
}
if(!strncasecmp((char *)buf, "QUIT", 4)){
socksend(param, param->clisock, (unsigned char *)"221 Proxy\r\n", 11, param->srv->timeouts[STRING_S]);
return -1;
}
socksend(param, param->clisock, (unsigned char *)"530 5.7.0 Must issue a STARTTLS command first\r\n", 47, param->srv->timeouts[STRING_S]);
}
default:
break;
}
return 1;
}
int parsehello(int type, unsigned char *hello, unsigned len, char *sni, int * snipos, int *lv, char * proto){
unsigned offset;
unsigned hlen, slen, cslen, elen, snllen, snlen, alpnlen;
int snifound=0;
if(len < 64) return -1;
if(hello[5] != type) return -2;
if(hello[6] != 0) return -3;
hlen = size16(hello+7);
if((hlen+9) != len) return -4;
offset = 9;
if(hello[offset] != 3) return -5;
*lv = hello[offset+1];
offset += 34;
slen = hello[offset];
if((offset + slen + 3) > len) return -6;
offset += (slen+1);
if(type == 1){
cslen = size16(hello+offset);
if((offset + cslen + 3) > len) return -7;
offset += (cslen+2);
cslen = hello[offset];
if((offset + cslen + 3) > len) return -8;
offset += (cslen+1);
}
else if(type == 2){
offset += 3;
}
elen = size16(hello+offset);
offset += 2;
if(elen+offset != len) return -9;
while(elen > 3){
unsigned xlen;
xlen = size16(hello+offset+2);
if(xlen+4 > elen) return -10;
if(type == 1 && xlen >= 5 && hello[offset] == 0 && hello[offset+1] == 0){
snllen=size16(hello+offset+4);
if(snllen>3){
if(snllen+2 != xlen) return -12;
if(hello[offset+6] != 0) return -13;
snlen=size16(hello+offset+7);
if(snlen + 3 > snllen) return -14;
if(snlen+1 > SNILEN) return -15;
memcpy(sni, hello + offset + 9, snlen);
*snipos = offset + 9;
sni[snlen] = 0;
snifound = snlen;
}
}
else if(hello[offset] == 0 && hello[offset+1] == 43){
if(xlen>2){
*lv = hello[offset+6];
}
else if(xlen==2){
*lv = hello[offset+5];
}
}
else if(xlen >= 3 && hello[offset] == 0 && hello[offset+1] == 16){
alpnlen=hello[offset+6];
if(alpnlen+7>elen) return -16;
if(alpnlen+1>PROTOLEN) return -17;
memcpy(proto, hello+offset+7, alpnlen);
proto[alpnlen] = 0;
}
offset += (xlen+4);
elen -= (xlen+4);
}
return snifound;
}
/* Has one whole TLS record from one side at the start of its buffer, once
whatever is still buffered from the other side is passed on. Returns its
length, or less than 0; the server's codes are one further on than the
client's, as the log has always had them. */
static int tlstobuf(struct clientparam *param, DIRECTION which){
unsigned *oinbuf = which? &param->cliinbuf : &param->srvinbuf;
unsigned *ooffset = which? &param->clioffset : &param->srvoffset;
unsigned char *obuf = which? param->clibuf : param->srvbuf;
unsigned char *buf;
int e = which? 1 : 0;
unsigned long len;
if(*oinbuf != *ooffset){
len = *oinbuf - *ooffset;
if((unsigned long)socksend(param, which? param->remsock : param->clisock, obuf + *ooffset, (int)len, param->srv->timeouts[STRING_S]) != len)
return which? -1 : -2;
*oinbuf = *ooffset = 0;
}
if(sockfillbuf(param, which, 5, param->srv->timeouts[STRING_S]) < 5) return -2 - e;
buf = which? param->srvbuf : param->clibuf;
if(buf[1] != 3) return -3 - e;
len = 5 + size16(buf + 3);
if(len > (which? param->srvbufsize : param->clibufsize)) return -4 - e;
if(sockfillbuf(param, which, len, param->srv->timeouts[STRING_S]) < len) return -5 - e;
return (int)len;
}
void * tlsprchild(struct clientparam* param) {
int res;
char sni[SNILEN];
char req[SNILEN+PROTOLEN+16];
int lv=-1;
char proto[PROTOLEN]="-";
int snipos = 0;
PROXYSERVICE stlsproto = param->clientstarttls? param->clientstarttls :
(param->starttls? param->starttls : param->srv->srvstarttls);
if(!param->clientstarttls && stlsproto){
res = clistarttls(param, stlsproto);
if(res > 0) RETURN(364);
if(res < 0) RETURN(0);
}
res = tlstobuf(param, CLIENT);
if(res <= 0 || param->clibuf[0] != 22){
if(param->srv->requirecert)RETURN(300-res);
}
else {
lv = param->clibuf[2];
res = parsehello(1, param->clibuf, (unsigned)res, sni, &snipos, &lv, proto);
if(res > 0){
if(*param->hostname){
if(PARAMSTR(param, hostname, sni)) RETURN (100);
}
else if (parsehostname(sni, param, param->srv->targetport? ntohs(param->srv->targetport):starttlsport(stlsproto))) RETURN (100);
if(param->srv->s_option && snipos && res > 1){
int len;
len = socksend(param, param->remsock, param->clibuf+param->clioffset,snipos + (res/2), param->srv->timeouts[STRING_S]);
if(len != snipos + (res/2)){
RETURN(310);
}
param->clioffset += snipos + (res/2);
}
}
else if (res < 0 && param->srv->requirecert) RETURN(310-res);
}
param->operation = CONNECT;
param->redirectfunc = NULL;
res = (*param->srv->authfunc)(param);
if(res) {RETURN(res);}
if (param->npredatfilters){
int action;
action = handlepredatflt(param);
if(action == HANDLED){
RETURN(0);
}
if(action != PASS) RETURN(19);
}
if(param->redirectfunc && param->redirectfunc != tlsprchild){
return (void *)param->redirectfunc;
}
if(stlsproto){
res = srvstarttls(param, stlsproto);
if(res){
socksend(param, param->clisock, (unsigned char *)"\x15\x03\x01\x00\x02\x02\x28", 7, param->srv->timeouts[STRING_S]);
RETURN(360+res);
}
}
if(param->srv->requirecert > 1){
res = tlstobuf(param, SERVER);
if(res <= 0 || param->srvbuf[0] != 22) RETURN(340-res);
lv = param->srvbuf[2];
res = parsehello(2, param->srvbuf, (unsigned)res, sni, &snipos, &lv, proto);
if (res < 0) RETURN(350-res);
}
if(param->srv->requirecert > 2){
int srvcert=0, clicert=0, reqcert=0, len, done;
if(lv > 3) RETURN(370);
for(done=0;!done;) {
len = param->srvinbuf;
if(socksend(param, param->clisock, param->srvbuf,len, param->srv->timeouts[STRING_S]) != len) RETURN(371);
param->srvinbuf = 0;
res = tlstobuf(param, SERVER);
if(res <= 0) RETURN(380-res);
if(param->srvbuf[0]!= 22) break;
switch(param->srvbuf[5]){
case 11:
/* process server certificates here */
if(param->srvbuf[6]||param->srvbuf[7]||param->srvbuf[8]>64) srvcert = 1;
break;
case 13:
reqcert = 1;
break;
case 14:
done = 1;
break;
default:
break;
}
}
if(!srvcert) RETURN(373);
if(param->srv->requirecert > 3){
if(!reqcert) RETURN(374);
for(done=0;!done;) {
res = tlstobuf(param, CLIENT);
if(res <= 0) RETURN(390-res);
len = res;
if(param->clibuf[0]!= 22) break;
switch(param->clibuf[5]){
case 11:
/* process client certificates here */
if(param->clibuf[6]||param->clibuf[7]||param->clibuf[8]>64)clicert = 1;
break;
case 14:
done = 1;
break;
default:
break;
}
if(done) break;
if(socksend(param, param->remsock, param->clibuf,len, param->srv->timeouts[STRING_S]) != len) RETURN(375);
param->cliinbuf = 0;
}
if(!clicert) RETURN(375);
}
}
RETURN (mapsocket(param, param->srv->timeouts[CONNECTION_L]));
CLEANRET:
sprintf(req, "%sv%d.%d %s %s", lv<0?"NONE":lv?"TLS":"SSL", lv<0?0:lv?1:3, lv<0?0:lv?lv-1:0, *param->hostname?(char *)param->hostname:"-", proto);
dolog(param, (unsigned char *)req);
return (NULL);
}
#ifdef WITHMAIN
struct proxydef childdef = {
tlsprchild,
1443,
0,
S_TLSPR,
""
};
#include "proxymain.c"
#endif