3proxy/src/imapp.c
Vladimir Dubrovin 6c4663a045 Build the mail proxies and FTP only when they are asked for
pop3p, imapp and smtpp are built with MAILPROXY=true, and ftppr and the
ftp:// scheme of the HTTP proxy with FTP=true; with CMake the switches are
3PROXY_USE_MAILPROXY and 3PROXY_USE_FTP. Neither is in a default build, and
the standalone binaries follow what was built.

A configuration naming one of them is still read either way. The three mail
protocols amount to a STARTTLS negotiation now that mail is carried over
TLS, so without a proxy of their own those names are tlspr speaking the
protocol: the file holds a stand-in which sets the protocol for the
connection and returns tlspr for the caller to run, which serves the
service name and a parent chain alike and leaves conf.c and the redirect
table untouched. FTP has no such fallback, so the service is known, answers
nothing and logs the refusal.

The Linux workflow builds both, so all of it is still compiled and run.
2026-09-16 20:11:50 +03:00

267 lines
10 KiB
C

/*
3APA3A simplest proxy server
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
please read License Agreement
*/
#include "proxy.h"
#ifdef WITH_IMAPP
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
#define CL_LOGINCMD 0
#define CL_PLAIN 1
#define CL_LOGIN 2
#define CAP_PLAIN 1
#define CAP_LOGIN 2
#ifdef WITHMAIN
#define NOSTARTTLS 1
#else
#define NOSTARTTLS param->srv->nostarttls
#endif
void * imappchild(struct clientparam* param) {
int i=0, res, method=CL_LOGINCMD, caps=0;
unsigned char buf[2048];
unsigned char srvbuf[1024];
unsigned char ibuf[2048];
unsigned char tag[64];
unsigned char ub[320];
unsigned char pb[320];
unsigned char *se, *cmd, *user, *pass, *p1, *p2;
*tag = 0;
i = sprintf((char *)buf, "* OK [CAPABILITY IMAP4rev1%s AUTH=PLAIN AUTH=LOGIN] IMAP4rev1 Proxy Ready\r\n", NOSTARTTLS?"":" STARTTLS");
if(socksend(param, param->clisock, buf, i, conf.timeouts[STRING_S])!=i) {RETURN (691);}
for(;;){
i = sockgetlinebuf(param, CLIENT, buf, sizeof(buf) - 10, '\n', conf.timeouts[STRING_S]);
if(i < 4) {RETURN(692);}
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(692);}
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, conf.timeouts[STRING_S]);
sprintf((char *)buf, "%.60s OK LOGOUT completed\r\n", (char *)tag);
socksend(param, param->clisock, buf, (int)strlen((char *)buf), conf.timeouts[STRING_S]);
RETURN(0);
}
if(!strncasecmp((char *)cmd, "CAPABILITY", 10)){
i = sprintf((char *)buf, "* CAPABILITY IMAP4rev1%s AUTH=PLAIN AUTH=LOGIN\r\n", NOSTARTTLS?"":" STARTTLS");
socksend(param, param->clisock, buf, i, conf.timeouts[STRING_S]);
sprintf((char *)buf, "%.60s OK CAPABILITY completed\r\n", (char *)tag);
socksend(param, param->clisock, buf, (int)strlen((char *)buf), conf.timeouts[STRING_S]);
continue;
}
#ifndef WITHMAIN
if(!strncasecmp((char *)cmd, "STARTTLS", 8) && !param->srv->nostarttls){
sprintf((char *)buf, "%.60s OK Begin TLS negotiation\r\n", (char *)tag);
if(socksend(param, param->clisock, buf, (int)strlen((char *)buf), conf.timeouts[STRING_S]) <= 0) {RETURN(698);}
param->clientstarttls = S_IMAPP;
if(!param->srv->targetport) param->srv->targetport = htons(143);
return (void *)tlsprchild;
}
#endif
if(!strncasecmp((char *)cmd, "LOGIN ", 6)){
user = cmd + 6;
if(*user == '"') {
user++;
if (!(se=(unsigned char *)strchr((char *)user, '"'))) {RETURN(693);}
*se = 0;
pass = se + 1;
if(*pass == ' ') pass++;
}
else {
if (!(se=(unsigned char *)strchr((char *)user, ' '))) {RETURN(693);}
*se = 0;
pass = se + 1;
}
if(strlen((char *)user) >= sizeof(ub) || strlen((char *)pass) >= sizeof(pb)) {RETURN(693);}
strcpy((char *)ub, (char *)user);
strcpy((char *)pb, (char *)pass);
break;
}
if(!strncasecmp((char *)cmd, "AUTHENTICATE PLAIN", 18)){
method = CL_PLAIN;
se = cmd + 18;
if(*se == ' ') se++; else se = NULL;
if(!se || !*se){
if(socksend(param, param->clisock, (unsigned char *)"+ \r\n", 4, conf.timeouts[STRING_S])!=4) {RETURN(698);}
i = sockgetlinebuf(param, CLIENT, buf, sizeof(buf) - 10, '\n', conf.timeouts[STRING_S]);
if(i < 1) {RETURN(698);}
buf[i] = 0;
if ((se=(unsigned char *)strchr((char *)buf, '\r'))) *se = 0;
se = buf;
}
i = de64(se, ibuf, (int)sizeof(ibuf) - 1);
if(i < 3) {RETURN(693);}
ibuf[i] = 0;
p1 = (unsigned char *)memchr(ibuf, 0, i);
if(!p1 || p1 == ibuf + i - 1) {RETURN(693);}
p2 = (unsigned char *)memchr(p1 + 1, 0, i - (int)(p1 + 1 - ibuf));
if(p2){
user = p1 + 1;
pass = p2 + 1;
}
else {
user = ibuf;
pass = p1 + 1;
}
if(strlen((char *)user) >= sizeof(ub) || strlen((char *)pass) >= sizeof(pb)) {RETURN(693);}
strcpy((char *)ub, (char *)user);
strcpy((char *)pb, (char *)pass);
break;
}
if(!strncasecmp((char *)cmd, "AUTHENTICATE LOGIN", 18)){
method = CL_LOGIN;
se = cmd + 18;
if(*se == ' ') se++; else se = NULL;
if(se && *se){
i = de64(se, ub, (int)sizeof(ub) - 1);
if(i < 0) {RETURN(693);}
ub[i] = 0;
}
else {
if(socksend(param, param->clisock, (unsigned char *)"+ VXNlcm5hbWU6\r\n", 16, conf.timeouts[STRING_S])!=16) {RETURN(698);}
i = sockgetlinebuf(param, CLIENT, buf, sizeof(buf) - 10, '\n', conf.timeouts[STRING_S]);
if(i < 1) {RETURN(698);}
buf[i] = 0;
if ((se=(unsigned char *)strchr((char *)buf, '\r'))) *se = 0;
i = de64(buf, ub, (int)sizeof(ub) - 1);
if(i < 0) {RETURN(693);}
ub[i] = 0;
}
if(socksend(param, param->clisock, (unsigned char *)"+ UGFzc3dvcmQ6\r\n", 16, conf.timeouts[STRING_S])!=16) {RETURN(698);}
i = sockgetlinebuf(param, CLIENT, buf, sizeof(buf) - 10, '\n', conf.timeouts[STRING_S]);
if(i < 1) {RETURN(698);}
buf[i] = 0;
if ((se=(unsigned char *)strchr((char *)buf, '\r'))) *se = 0;
i = de64(buf, pb, (int)sizeof(pb) - 1);
if(i < 0) {RETURN(693);}
pb[i] = 0;
break;
}
sprintf((char *)buf, "%.60s BAD need LOGIN first\r\n", (char *)tag);
socksend(param, param->clisock, buf, (int)strlen((char *)buf), conf.timeouts[STRING_S]);
}
if(parseconnusername((char *)ub, param, 0, 143)){RETURN(694);}
if(*pb && !param->password) param->password = (unsigned char *)strdup((char *)pb);
param->operation = CONNECT;
res = (*param->srv->authfunc)(param);
if(res) {RETURN(res);}
i = sockgetlinebuf(param, SERVER, srvbuf, sizeof(srvbuf) - 1, '\n', conf.timeouts[STRING_L]);
if( i < 4 ) {RETURN(695);}
srvbuf[i] = 0;
if(strncasecmp((char *)srvbuf, "* OK", 4)){RETURN(696);}
if((se = (unsigned char *)strstr((char *)srvbuf, "[CAPABILITY "))){
if(hascap(se, "AUTH=PLAIN")) caps |= CAP_PLAIN;
if(hascap(se, "AUTH=LOGIN")) caps |= CAP_LOGIN;
}
else {
if(socksend(param, param->remsock, (unsigned char *)"zz CAPABILITY\r\n", 15, conf.timeouts[STRING_S])!=15) {RETURN(697);}
for(;;){
i = sockgetlinebuf(param, SERVER, srvbuf, sizeof(srvbuf) - 1, '\n', conf.timeouts[STRING_L]);
if(i < 3) {RETURN(697);}
srvbuf[i] = 0;
if(!strncasecmp((char *)srvbuf, "* CAPABILITY", 12)){
if(hascap(srvbuf, "AUTH=PLAIN")) caps |= CAP_PLAIN;
if(hascap(srvbuf, "AUTH=LOGIN")) caps |= CAP_LOGIN;
continue;
}
if(!strncasecmp((char *)srvbuf, "zz ", 3)) break;
}
}
if(method == CL_PLAIN && !(caps & CAP_PLAIN)) method = (caps & CAP_LOGIN)? CL_LOGIN : CL_LOGINCMD;
else if(method == CL_LOGIN && !(caps & CAP_LOGIN)) method = (caps & CAP_PLAIN)? CL_PLAIN : CL_LOGINCMD;
else if(method == CL_LOGINCMD && caps) method = (caps & CAP_PLAIN)? CL_PLAIN : CL_LOGIN;
if(method == CL_PLAIN){
i = (int)strlen((char *)param->extusername);
res = (int)strlen((char *)pb);
if(i + res + 2 > (int)sizeof(ibuf)) {RETURN(693);}
ibuf[0] = 0;
memcpy(ibuf + 1, param->extusername, i);
ibuf[i + 1] = 0;
memcpy(ibuf + i + 2, pb, res);
if(!en64(ibuf, srvbuf, i + res + 2, (int)sizeof(srvbuf))) {RETURN(693);}
if( socksend(param, param->remsock, tag, (int)strlen((char *)tag), conf.timeouts[STRING_S]) <= 0 ||
socksend(param, param->remsock, (unsigned char *)" AUTHENTICATE PLAIN ", 20, conf.timeouts[STRING_S])!= 20 ||
socksend(param, param->remsock, srvbuf, (int)strlen((char *)srvbuf), conf.timeouts[STRING_S]) <= 0 ||
socksend(param, param->remsock, (unsigned char *)"\r\n", 2, conf.timeouts[STRING_S])!=2)
{RETURN(699);}
}
else if(method == CL_LOGIN){
if( socksend(param, param->remsock, tag, (int)strlen((char *)tag), conf.timeouts[STRING_S]) <= 0 ||
socksend(param, param->remsock, (unsigned char *)" AUTHENTICATE LOGIN\r\n", 21, conf.timeouts[STRING_S])!= 21)
{RETURN(699);}
i = sockgetlinebuf(param, SERVER, srvbuf, sizeof(srvbuf) - 1, '\n', conf.timeouts[STRING_L]);
if(i < 1 || *srvbuf != '+') {RETURN(699);}
if(!en64(param->extusername, ibuf, (int)strlen((char *)param->extusername), (int)sizeof(ibuf))) {RETURN(693);}
if( socksend(param, param->remsock, ibuf, (int)strlen((char *)ibuf), conf.timeouts[STRING_S]) <= 0 ||
socksend(param, param->remsock, (unsigned char *)"\r\n", 2, conf.timeouts[STRING_S])!=2)
{RETURN(699);}
i = sockgetlinebuf(param, SERVER, srvbuf, sizeof(srvbuf) - 1, '\n', conf.timeouts[STRING_L]);
if(i < 1 || *srvbuf != '+') {RETURN(699);}
if(!en64(pb, ibuf, (int)strlen((char *)pb), (int)sizeof(ibuf))) {RETURN(693);}
if( socksend(param, param->remsock, ibuf, (int)strlen((char *)ibuf), conf.timeouts[STRING_S]) <= 0 ||
socksend(param, param->remsock, (unsigned char *)"\r\n", 2, conf.timeouts[STRING_S])!=2)
{RETURN(699);}
}
else {
if( socksend(param, param->remsock, tag, (int)strlen((char *)tag), conf.timeouts[STRING_S]) <= 0 ||
socksend(param, param->remsock, (unsigned char *)" LOGIN ", 7, conf.timeouts[STRING_S])!= 7 ||
socksend(param, param->remsock, param->extusername, (int)strlen((char *)param->extusername), conf.timeouts[STRING_S]) <= 0 ||
socksend(param, param->remsock, (unsigned char *)" ", 1, conf.timeouts[STRING_S])!= 1 ||
socksend(param, param->remsock, pb, (int)strlen((char *)pb), conf.timeouts[STRING_S]) <= 0 ||
socksend(param, param->remsock, (unsigned char *)"\r\n", 2, conf.timeouts[STRING_S])!=2)
{RETURN(699);}
}
param->statscli64 += (uint64_t)(strlen((char *)tag) + strlen((char *)param->extusername) + strlen((char *)pb) + 11);
param->nwrites++;
RETURN (mapsocket(param, 180));
CLEANRET:
if(param->hostname&&param->extusername) {
sprintf((char *)buf, "%.128s@%.128s%c%hu", param->extusername, param->hostname, (*SAPORT(&param->sinsr)==143)?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 > 691 && param->res <700)) {
sprintf((char *)buf, "%.60s NO proxy error\r\n", *tag? (char *)tag : "*");
socksend(param, param->clisock, buf, (int)strlen((char *)buf),conf.timeouts[STRING_S]);
}
}
return (NULL);
}
#ifdef WITHMAIN
struct proxydef childdef = {
imappchild,
143,
0,
S_IMAPP,
" -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 -Ximap"
does, under the name a configuration already uses. */
void * imappchild(struct clientparam * param){
param->starttls = S_IMAPP;
return (void *)tlsprchild;
}
#endif