3proxy/src/smtpp.c
Vladimir Dubrovin a20f646169
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
Pre-allocate client/server/tmp/etc buffers with client structure
2026-10-12 17:19:55 +03:00

365 lines
12 KiB
C

/*
3APA3A simplest proxy server
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
please read License Agreement
*/
#include "proxy.h"
#ifdef WITH_SMTPP
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
#ifdef WITHMAIN
#define NOSTARTTLS 1
#else
#define NOSTARTTLS param->srv->nostarttls
#endif
char ehlo[] = "250-Proxy\r\n"
"250-AUTH PLAIN LOGIN\r\n"
"250-8BITMIME\r\n"
"250 DSN\r\n";
char ehlotls[] = "250-Proxy\r\n"
"250-AUTH PLAIN LOGIN\r\n"
"250-8BITMIME\r\n"
"250-STARTTLS\r\n"
"250 DSN\r\n";
int readreply (struct clientparam* param) {
unsigned char * buf;
int res, i, bufsize = 640;
buf = param->tmpbuf; /* a filter may replace it with one of its own */
do {
i = sockgetlinebuf(param, SERVER, buf, bufsize-1, '\n', conf.timeouts[STRING_L]);
if(i < 1) break;
#ifndef WITHMAIN
res = handlehdrfilterssrv(param, &buf, &bufsize, 0, &i);
if(res != PASS) {
if(buf != param->tmpbuf) free(buf);
return -1;
}
#endif
socksend(param, param->clisock, buf, i, conf.timeouts[STRING_S]);
} while (i > 3 && buf[3] == '-');
if(i < 3) {
if(buf != param->tmpbuf) free(buf);
return 0;
}
buf[i] = 0;
res = atoi((char *)buf);
if(buf != param->tmpbuf) free(buf);
return res;
}
int readcommand (struct clientparam* param) {
unsigned char * buf;
int i, bufsize = 320;
#ifndef WITHMAIN
int res;
#endif
int ret = 1;
buf = param->tmpbuf; /* a filter may replace it with one of its own */
i = sockgetlinebuf(param, CLIENT, buf, bufsize-1, '\n', conf.timeouts[STRING_L]);
if(i < 4) return 0;
#ifndef WITHMAIN
if(!strncasecmp((char *)buf, "MAIL", 4) || !strncasecmp((char *)buf, "RCPT", 4) || !strncasecmp((char *)buf, "STARTTLS", 8) || !strncasecmp((char *)buf, "TURN", 4)){
res = handlehdrfilterscli(param, &buf, &bufsize, 0, &i);
if(res != PASS) {
if(buf != param->tmpbuf) free(buf);
if(res == HANDLED) return 2;
return -1;
}
}
#endif
socksend(param, param->remsock, buf, i, conf.timeouts[STRING_S]);
if(!strncasecmp((char *)buf, "STARTTLS", 8) || !strncasecmp((char *)buf, "TURN", 4)){
ret = 22;
}
if(buf != param->tmpbuf) free(buf);
return ret;
}
int readdata (struct clientparam* param) {
unsigned char * buf;
int i, bufsize = 4096;
#ifndef WITHMAIN
int res;
#endif
buf = param->tmpbuf; /* a filter may replace it with one of its own */
while ((i = sockgetlinebuf(param, CLIENT, buf, bufsize-1, '\n', conf.timeouts[STRING_L])) > 0 && !(i==3 && buf[0] == '.')){
#ifndef WITHMAIN
res = handledatfltcli(param, &buf, &bufsize, 0, &i);
if(res != PASS) {
if(buf != param->tmpbuf) free(buf);
if(res == HANDLED) return 1;
return -1;
}
#endif
socksend(param, param->remsock, buf, i, conf.timeouts[STRING_S]);
}
if(i < 1) {
if(buf != param->tmpbuf) free(buf);
return 0;
}
socksend(param, param->remsock, buf, i, conf.timeouts[STRING_S]);
if(buf != param->tmpbuf) free(buf);
return 1;
}
void * smtppchild(struct clientparam* param) {
int i=0, res;
unsigned char buf[320];
unsigned char username[256];
char * command = NULL;
int login = 0;
if(socksend(param, param->clisock, (unsigned char *)"220 Proxy\r\n", 11, conf.timeouts[STRING_S])!=11) {RETURN (611);}
i = sockgetlinebuf(param, CLIENT, buf, sizeof(buf) - 10, '\n', conf.timeouts[STRING_S]);
while(i > 4 && (strncasecmp((char *)buf, "AUTH PLAIN", 10) || !(login = 2)) && (strncasecmp((char *)buf, "AUTH LOGIN", 10) || !(login = 1))){
if(!strncasecmp((char *)buf, "QUIT", 4)){
socksend(param, param->clisock, (unsigned char *)"221 Proxy\r\n", 11,conf.timeouts[STRING_S]);
RETURN(0);
}
else if(!strncasecmp((char *)buf, "HELO ", 5)){
socksend(param, param->clisock, (unsigned char *)"250 Proxy\r\n", 11,conf.timeouts[STRING_S]);
}
else if(!strncasecmp((char *)buf, "EHLO ", 5)){
if(!NOSTARTTLS) socksend(param, param->clisock, (unsigned char *)ehlotls, sizeof(ehlotls) - 1,conf.timeouts[STRING_S]);
else socksend(param, param->clisock, (unsigned char *)ehlo, sizeof(ehlo) - 1,conf.timeouts[STRING_S]);
}
#ifndef WITHMAIN
else if(!strncasecmp((char *)buf, "STARTTLS", 8) && !param->srv->nostarttls){
if(socksend(param, param->clisock, (unsigned char *)"220 2.0.0 Ready to start TLS\r\n", 30, conf.timeouts[STRING_S])!=30) {RETURN(673);}
param->clientstarttls = S_SMTPP;
if(!param->srv->targetport) param->srv->targetport = htons(587);
return (void *)tlsprchild;
}
#endif
else if(!*param->hostname) socksend(param, param->clisock, (unsigned char *)"571 need AUTH first\r\n", 22, conf.timeouts[STRING_S]);
else {
login = -1;
buf[i] = 0;
command = strdup((char *)buf);
break;
}
i = sockgetlinebuf(param, CLIENT, buf, sizeof(buf) - 10, '\n', conf.timeouts[STRING_S]);
}
if(!login) {RETURN(662);}
if(login == 1){
socksend(param, param->clisock, (unsigned char *)"334 VXNlcm5hbWU6\r\n", 18,conf.timeouts[STRING_S]);
i = sockgetlinebuf(param, CLIENT, buf, sizeof(buf) - 10, '\n', conf.timeouts[STRING_S]);
if(i < 3) {RETURN(663);}
buf[i-2] = 0;
i = de64(buf,username,255);
if(i < 1) {RETURN(664);}
username[i] = 0;
/* The name has to carry the host to connect to, and the answer says
whether it did: without one there is nowhere to go, and what follows
reads the name as if there were. */
if(parseconnusername((char *)username, param, 0, 587)) {RETURN(669);}
socksend(param, param->clisock, (unsigned char *)"334 UGFzc3dvcmQ6\r\n", 18,conf.timeouts[STRING_S]);
i = sockgetlinebuf(param, CLIENT, buf, sizeof(buf) - 10, '\n', conf.timeouts[STRING_S]);
if(i < 2) {RETURN(665);}
buf[i-2] = 0;
i = de64(buf,username,255);
if(i < 0) {RETURN(666);}
username[i] = 0;
if(PARAMSTR(param, extpassword, username)) {RETURN(666);}
}
else if(login == 2){
if(i > 13) {
buf[i-2] = 0;
i = de64(buf+11,username,255);
}
else {
socksend(param, param->clisock, (unsigned char *)"334\r\n", 5,conf.timeouts[STRING_S]);
i = sockgetlinebuf(param, CLIENT, buf, sizeof(buf) - 10, '\n', conf.timeouts[STRING_S]);
if(i < 3) {RETURN(667);}
buf[i-2] = 0;
i = de64(buf,username,255);
}
if(i < 3 || *username) {RETURN(668);}
username[i] = 0;
if(parseconnusername((char *)username+1, param, 0, 587)) {RETURN(670);}
res = (int)strlen((char *)username+1) + 2;
if(res < i){
if(PARAMSTR(param, extpassword, username + res)) {RETURN(670);}
}
}
#ifndef WITHMAIN
{
/* the host is filtered as a request, in the scratch buffer, where a
filter may also hand back a buffer of its own */
int action, reqbufsize, reqsize;
unsigned char *req = param->tmpbuf;
reqsize = (int)strlen((char *)param->hostname) + 1;
reqbufsize = CLIENTTMPBUFSIZE;
memcpy(req, param->hostname, reqsize);
action = handlereqfilters(param, &req, &reqbufsize, 0, &reqsize);
if(action == PASS && PARAMSTR(param, hostname, req)) action = REJECT;
if(req != param->tmpbuf) free(req);
if(action == HANDLED){
RETURN(0);
}
if(action != PASS) RETURN(617);
}
#endif
param->operation = CONNECT;
res = (*param->srv->authfunc)(param);
if(res) {RETURN(res);}
i = getmultiline(param, SERVER, buf, sizeof(buf) - 1, NULL, NULL);
if( i < 3 ) {RETURN(671);}
buf[i] = 0;
if(strncasecmp((char *)buf, "220", 3)||!strncasecmp((char *)buf+4, "PROXY", 5)){RETURN(672);}
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, conf.timeouts[STRING_S])!= i) {RETURN(673);}
param->statscli64+=i;
param->nwrites++;
login = 0;
do {
i = sockgetlinebuf(param, SERVER, buf, sizeof(buf) - 1, '\n', conf.timeouts[STRING_L]);
if(i < 1) break;
buf[i] = 0;
if(strstr((char *)buf, "LOGIN")) login |= 1;
if(strstr((char *)buf, "PLAIN")) login |= 2;
} while (i > 3 && buf[3] == '-');
if(i<3) {RETURN(672);}
if(!command || (*param->extusername && *param->extpassword)){
if(!*param->extusername || !*param->extpassword || !login){
socksend(param, param->clisock, (unsigned char *)"235 auth required\r\n", 19,conf.timeouts[STRING_S]);
}
if ((login & 1)) {
socksend(param, param->remsock, (unsigned char *)"AUTH LOGIN\r\n", 12, conf.timeouts[STRING_S]);
param->statscli64+=12;
param->nwrites++;
i = sockgetlinebuf(param, SERVER, buf, sizeof(buf) - 1, '\n', conf.timeouts[STRING_L]);
if(i<4 || strncasecmp((char *)buf, "334", 3)) {RETURN(680);}
if(!en64(param->extusername, buf, (int)strlen((char *)param->extusername), (int)sizeof(buf))) {RETURN(683);}
socksend(param, param->remsock, buf, (int)strlen((char *)buf), conf.timeouts[STRING_S]);
socksend(param, param->remsock, (unsigned char *)"\r\n", 2, conf.timeouts[STRING_S]);
param->statscli64+=(i+2);
param->nwrites+=2;
i = sockgetlinebuf(param, SERVER, buf, sizeof(buf) - 1, '\n', conf.timeouts[STRING_L]);
if(i<4 || strncasecmp((char *)buf, "334", 3)) {RETURN(681);}
if(!en64(param->extpassword, buf, (int)strlen((char *)param->extpassword), (int)sizeof(buf))) {RETURN(683);}
socksend(param, param->remsock, buf, (int)strlen((char *)buf), conf.timeouts[STRING_S]);
socksend(param, param->remsock, (unsigned char *)"\r\n", 2, conf.timeouts[STRING_S]);
param->statscli64+=(i+2);
param->nwrites+=2;
}
else if((login & 2)){
socksend(param, param->remsock, (unsigned char *)"AUTH PLAIN\r\n", 12, conf.timeouts[STRING_S]);
param->statscli64+=(12);
param->nwrites++;
i = sockgetlinebuf(param, SERVER, buf, sizeof(buf) - 1, '\n', conf.timeouts[STRING_L]);
if(i<4 || strncasecmp((char *)buf, "334", 3)) {RETURN(682);}
*username = 0;
i = (int)strlen((char *)param->extusername) + 1;
res = (int)strlen((char *)param->extpassword);
if(i + 1 + res >= (int)sizeof(username)) {RETURN(683);}
memcpy(username+1, param->extusername, i);
i++;
memcpy(username + i, param->extpassword, res);
i+=res;
if(!en64(username, buf, i, (int)sizeof(buf))) {RETURN(683);}
i = (int)strlen((char *)buf);
socksend(param, param->remsock, buf, i, conf.timeouts[STRING_S]);
socksend(param, param->remsock, (unsigned char *)"\r\n", 2, conf.timeouts[STRING_S]);
param->statscli64+=(i+2);
param->nwrites+=2;
}
if(command) {
i = sockgetlinebuf(param, SERVER, buf, sizeof(buf) - 1, '\n', conf.timeouts[STRING_L]);
}
res = 1;
}
if(command) {
res = 1;
#ifndef WITHMAIN
if(!strncasecmp(command, "MAIL", 4) || !strncasecmp(command, "RCPT", 4) || !strncasecmp(command, "STARTTLS", 8) || !strncasecmp(command, "TURN", 4)){
int clen = (int)strlen(command), csize = clen + 1;
res = handlehdrfilterscli(param, (unsigned char **)&command, &csize, 0, &clen);
if(res != PASS) {
if(res == HANDLED) res = 2;
else RETURN(677);
}
else if(!strncasecmp(command, "STARTTLS", 8) || !strncasecmp(command, "TURN", 4))
res = 22;
}
#endif
i = (int)strlen(command);
if(res != 2) socksend(param, param->remsock, (unsigned char *)command, i, conf.timeouts[STRING_S]);
}
#ifndef WITHMAIN
if(param->nhdrfilterscli || param->nhdrfilterssrv || param->ndatfilterscli || param->ndatfilterssrv){
do {
if(res == 22) RETURN (mapsocket(param, 180));
if(res != 2 && (res = readreply(param)) <= 0) break;
if(res == 221) RETURN(0);
if(res == 354) res = readdata(param);
else res = readcommand(param);
} while(res > 0);
if(res == 22)
if(res >= 0) RETURN(0);
RETURN(676);
}
else
#endif
RETURN (mapsocket(param, 180));
CLEANRET:
if(*param->hostname&&*param->extusername) {
sprintf((char *)buf, "%.128s@%.128s%c%hu", param->extusername, param->hostname, *SAPORT(&param->sinsr)==587?0:':',ntohs(*SAPORT(&param->sinsr)));
dolog(param, buf);
}
else dolog(param, NULL);
if(param->clisock != INVALID_SOCKET) {
if ((param->res > 0 && param->res < 100) || (param->res > 661 && param->res <700)) socksend(param, param->clisock, (unsigned char *)"571 \r\n", 6,conf.timeouts[STRING_S]);
}
if(command) free(command);
return (NULL);
}
#ifdef WITHMAIN
struct proxydef childdef = {
smtppchild,
587,
0,
S_SMTPP,
" -hdefault_host[:port] - use this host and port as default if no host specified\n -x - disable STARTTLS\n"
};
#include "proxymain.c"
#endif
#else
/* Built without this proxy of its own. The command and the redirect
naming it are the STARTTLS proxy speaking that protocol, which
negotiates the same way and passes the session on: what "tlspr -Xsmtp"
does, under the name a configuration already uses. */
void * smtppchild(struct clientparam * param){
param->starttls = S_SMTPP;
return (void *)tlsprchild;
}
#endif