mirror of
https://github.com/3proxy/3proxy.git
synced 2026-10-30 14:45:49 +08:00
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.
363 lines
12 KiB
C
363 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_FTP
|
|
|
|
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
|
|
#define BUFSIZE 2048
|
|
|
|
void * ftpprchild(struct clientparam* param) {
|
|
int i=0, res;
|
|
unsigned char *buf;
|
|
unsigned char *se;
|
|
int status = 0;
|
|
int inbuf;
|
|
int pasv = 0;
|
|
SOCKET sc = INVALID_SOCKET, ss = INVALID_SOCKET, clidatasock = INVALID_SOCKET;
|
|
SASIZETYPE sasize;
|
|
char * req = NULL;
|
|
struct linger lg;
|
|
struct pollfd fds;
|
|
|
|
if(!(buf = malloc(BUFSIZE))) RETURN(876);
|
|
param->ctrlsock = param->clisock;
|
|
param->operation = CONNECT;
|
|
lg.l_onoff = 1;
|
|
lg.l_linger = conf.timeouts[STRING_L];;
|
|
if(socksend(param, param->ctrlsock, (unsigned char *)"220 Ready\r\n", 11, conf.timeouts[STRING_S])!=11) {RETURN (801);}
|
|
for(;;){
|
|
i = sockgetlinebuf(param, CLIENT, buf, BUFSIZE - 10, '\n', conf.timeouts[CONNECTION_S]);
|
|
if(!i) {
|
|
RETURN(0);
|
|
}
|
|
if(i<4) {RETURN(802);}
|
|
buf[i] = 0;
|
|
if ((se=(unsigned char *)strchr((char *)buf, '\r'))) *se = 0;
|
|
if (req) free (req);
|
|
req = NULL;
|
|
|
|
if (!strncasecmp((char *)buf, "OPEN ", 5)){
|
|
if(parsehostname((char *)buf+5, param, 21)){RETURN(803);}
|
|
if(param->remsock != INVALID_SOCKET) {
|
|
param->srv->so._shutdown(param->sostate, param->remsock, SHUT_RDWR);
|
|
param->srv->so._closesocket(param->sostate, param->remsock);
|
|
param->remsock = INVALID_SOCKET;
|
|
}
|
|
if((res = (*param->srv->authfunc)(param))) {RETURN(res);}
|
|
param->ctrlsocksrv = param->remsock;
|
|
if(socksend(param, param->ctrlsock, (unsigned char *)"220 Ready\r\n", 11, conf.timeouts[STRING_S])!=11) {RETURN (801);}
|
|
status = 1;
|
|
}
|
|
else if (!strncasecmp((char *)buf, "USER ", 5)){
|
|
if(parseconnusername((char *)buf +5, param, 0, 21)){RETURN(804);}
|
|
if(!status){
|
|
if((res = (*param->srv->authfunc)(param))) {RETURN(res);}
|
|
param->ctrlsocksrv = param->remsock;
|
|
}
|
|
if(socksend(param, param->ctrlsock, (unsigned char *)"331 ok\r\n", 8, conf.timeouts[STRING_S])!=8) {RETURN (807);}
|
|
status = 2;
|
|
|
|
}
|
|
else if (!strncasecmp((char *)buf, "PASS ", 5)){
|
|
/* The user name carries the server to log in to, and it arrives
|
|
with USER. Without it there is nothing to log in to, and what
|
|
follows would read the name and the host as if there were. */
|
|
if(!param->hostname || !param->extusername){
|
|
socksend(param, param->ctrlsock, (unsigned char *)"503 Login with USER first\r\n", 27, conf.timeouts[STRING_S]);
|
|
RETURN(805);
|
|
}
|
|
param->extpassword = (unsigned char *)strdup((char *)buf+5);
|
|
inbuf = BUFSIZE;
|
|
res = ftplogin(param, (char *)buf, &inbuf);
|
|
param->res = res;
|
|
if(inbuf && inbuf != BUFSIZE && socksend(param, param->ctrlsock, buf, inbuf, conf.timeouts[STRING_S])!=inbuf) {RETURN (807);}
|
|
if(!res) status = 3;
|
|
sprintf((char *)buf, "%.128s@%.128s%c%hu", param->extusername, param->hostname, (ntohs(*SAPORT(¶m->sinsr))==21)?0:':', ntohs(*SAPORT(¶m->sinsr)));
|
|
req = strdup((char *)buf);
|
|
#ifndef WITHMAIN
|
|
{
|
|
int action, reqbufsize, reqsize;
|
|
reqbufsize = BUFSIZE;
|
|
reqsize = (int)strlen((char *)buf) + 1;
|
|
|
|
action = handlereqfilters(param, &buf, &reqbufsize, 0, &reqsize);
|
|
if(action == HANDLED){
|
|
RETURN(0);
|
|
}
|
|
if(action != PASS) RETURN(877);
|
|
}
|
|
#endif
|
|
}
|
|
else if (status >= 3 && (
|
|
(!strncasecmp((char *)buf, "PASV", 4) && (pasv = 1)) ||
|
|
(!strncasecmp((char *)buf, "EPSV", 4) && (pasv = 2)) ||
|
|
(!strncasecmp((char *)buf, "PORT ", 5) && !(pasv = 0))
|
|
)){
|
|
#ifndef WITHMAIN
|
|
{
|
|
int action, reqbufsize, reqsize;
|
|
reqbufsize = BUFSIZE;
|
|
reqsize = (int)strlen((char *)buf) + 1;
|
|
|
|
action = handlehdrfilterscli(param, &buf, &reqbufsize, 0, &reqsize);
|
|
if(action == HANDLED){
|
|
RETURN(0);
|
|
}
|
|
if(action != PASS) RETURN(878);
|
|
}
|
|
#endif
|
|
if(sc != INVALID_SOCKET) {
|
|
param->srv->so._shutdown(param->sostate, sc, SHUT_RDWR);
|
|
param->srv->so._closesocket(param->sostate, sc);
|
|
sc = INVALID_SOCKET;
|
|
}
|
|
if(ss != INVALID_SOCKET) {
|
|
param->srv->so._shutdown(param->sostate, ss, SHUT_RDWR);
|
|
param->srv->so._closesocket(param->sostate, ss);
|
|
ss = INVALID_SOCKET;
|
|
}
|
|
if(clidatasock != INVALID_SOCKET) {
|
|
param->srv->so._shutdown(param->sostate, clidatasock, SHUT_RDWR);
|
|
param->srv->so._closesocket(param->sostate, clidatasock);
|
|
clidatasock = INVALID_SOCKET;
|
|
}
|
|
if ((clidatasock=socket(SASOCK(¶m->sincl), SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {RETURN(821);}
|
|
*SAPORT(¶m->sincl) = 0;
|
|
if(bindwithrange(param, clidatasock, ¶m->sincl, param->intport)){RETURN(822);}
|
|
if (pasv) {
|
|
if(param->srv->so._listen(param->sostate, clidatasock, 1)) {RETURN(823);}
|
|
sasize = sizeof(param->sincl);
|
|
if(param->srv->so._getsockname(param->sostate, clidatasock, (struct sockaddr *)¶m->sincl, &sasize)){RETURN(824);}
|
|
if(pasv == 1){
|
|
if(*SAFAMILY(¶m->sincl) == AF_INET)
|
|
sprintf((char *)buf, "227 OK (%u,%u,%u,%u,%u,%u)\r\n",
|
|
(unsigned)(((unsigned char *)(SAADDR(¶m->sincl)))[0]),
|
|
(unsigned)(((unsigned char *)(SAADDR(¶m->sincl)))[1]),
|
|
(unsigned)(((unsigned char *)(SAADDR(¶m->sincl)))[2]),
|
|
(unsigned)(((unsigned char *)(SAADDR(¶m->sincl)))[3]),
|
|
(unsigned)(((unsigned char *)(SAPORT(¶m->sincl)))[0]),
|
|
(unsigned)(((unsigned char *)(SAPORT(¶m->sincl)))[1])
|
|
);
|
|
else sprintf((char *)buf, "227 OK (127,0,0,1,%u,%u)\r\n",
|
|
(unsigned)(((unsigned char *)(SAPORT(¶m->sincl)))[0]),
|
|
(unsigned)(((unsigned char *)(SAPORT(¶m->sincl)))[1])
|
|
);
|
|
}
|
|
else {
|
|
sprintf((char *)buf, "229 OK (|||%u|)\r\n",
|
|
(unsigned)ntohs(*SAPORT(¶m->sincl))
|
|
);
|
|
}
|
|
}
|
|
else {
|
|
unsigned long b1, b2, b3, b4;
|
|
unsigned short b5, b6;
|
|
|
|
if(sscanf((char *)buf+5, "%lu,%lu,%lu,%lu,%hu,%hu", &b1, &b2, &b3, &b4, &b5, &b6)!=6) {RETURN(828);}
|
|
*SAPORT(¶m->sincr) = htons((uint16_t)((b5<<8)^b6));
|
|
if(connectwithpoll(param, clidatasock, (struct sockaddr *)¶m->sincr, SASIZE(¶m->sincr),conf.timeouts[CONNECT_TO])) {
|
|
param->srv->so._closesocket(param->sostate, clidatasock);
|
|
clidatasock = INVALID_SOCKET;
|
|
RETURN(826);
|
|
}
|
|
sprintf((char *)buf, "200 OK\r\n");
|
|
}
|
|
#ifndef WITHMAIN
|
|
{
|
|
int action, reqbufsize, reqsize;
|
|
reqbufsize = BUFSIZE;
|
|
reqsize = (int)strlen((char *)buf) + 1;
|
|
|
|
action = handlehdrfilterssrv(param, &buf, &reqbufsize, 0, &reqsize);
|
|
if(action == HANDLED){
|
|
RETURN(0);
|
|
}
|
|
if(action != PASS) RETURN(879);
|
|
}
|
|
#endif
|
|
if(socksend(param, param->ctrlsock, buf, (int)strlen((char *)buf), conf.timeouts[STRING_S])!=(int)strlen((char *)buf)) {RETURN (825);}
|
|
status = 4;
|
|
}
|
|
else if (status == 4 && (
|
|
!(strncasecmp((char *)buf, "RETR ", 5) && (param->operation = FTP_GET)) ||
|
|
!(strncasecmp((char *)buf, "LIST", 4) && (param->operation = FTP_LIST))||
|
|
!(strncasecmp((char *)buf, "NLST ", 5) && (param->operation = FTP_LIST)) ||
|
|
!(strncasecmp((char *)buf, "MLSD", 4) && (param->operation = FTP_LIST)) ||
|
|
!(strncasecmp((char *)buf, "APPE ", 5) && (param->operation = FTP_PUT)) ||
|
|
!(strncasecmp((char *)buf, "STOR ", 5) && (param->operation = FTP_PUT))
|
|
)){
|
|
int arg = (buf[4] && buf[5])? 1:0;
|
|
int ressent = 0;
|
|
|
|
|
|
#ifndef WITHMAIN
|
|
{
|
|
int action, reqbufsize, reqsize;
|
|
reqbufsize = BUFSIZE;
|
|
reqsize = (int)strlen((char *)buf) + 1;
|
|
|
|
action = handlehdrfilterscli(param, &buf, &reqbufsize, 0, &reqsize);
|
|
if(action == HANDLED){
|
|
RETURN(0);
|
|
}
|
|
if(action != PASS) RETURN(880);
|
|
}
|
|
#endif
|
|
if(clidatasock == INVALID_SOCKET) { RETURN (829);}
|
|
if(pasv){
|
|
|
|
memset(&fds, 0, sizeof(fds));
|
|
fds.fd = clidatasock;
|
|
fds.events = POLLIN;
|
|
|
|
res = param->srv->so._poll (param->sostate, &fds, 1, conf.timeouts[STRING_L]*1000);
|
|
if(res != 1) {
|
|
RETURN(857);
|
|
}
|
|
sasize = sizeof(param->sincr);
|
|
ss = param->srv->so._accept(param->sostate, clidatasock, (struct sockaddr *)¶m->sincr, &sasize);
|
|
if (ss == INVALID_SOCKET) { RETURN (858);}
|
|
#ifdef _WIN32
|
|
{ unsigned long ul = 1; ioctlsocket(ss, FIONBIO, &ul); }
|
|
#else
|
|
fcntl(ss, F_SETFL, O_NONBLOCK | fcntl(ss, F_GETFL));
|
|
#endif
|
|
param->srv->so._shutdown(param->sostate, clidatasock, SHUT_RDWR);
|
|
param->srv->so._closesocket(param->sostate, clidatasock);
|
|
clidatasock = ss;
|
|
ss = INVALID_SOCKET;
|
|
}
|
|
if(clidatasock == INVALID_SOCKET){RETURN(828);}
|
|
req = strdup((char *)buf);
|
|
buf[4] = 0;
|
|
status = 3;
|
|
ss = ftpcommand(param, buf, arg? buf+5 : NULL);
|
|
if (ss == INVALID_SOCKET) {
|
|
param->srv->so._shutdown(param->sostate, clidatasock, SHUT_RDWR);
|
|
param->srv->so._closesocket(param->sostate, clidatasock);
|
|
clidatasock = INVALID_SOCKET;
|
|
|
|
if(socksend(param, param->ctrlsock, (unsigned char *)"550 err\r\n", 9, conf.timeouts[STRING_S])!=9) {RETURN (831);}
|
|
continue;
|
|
}
|
|
|
|
if(socksend(param, param->ctrlsock, (unsigned char *)"125 data\r\n", 10, conf.timeouts[STRING_S]) != 10) {
|
|
param->remsock = INVALID_SOCKET;
|
|
RETURN (832);
|
|
}
|
|
if(param->srvoffset < param->srvinbuf)while((i = sockgetlinebuf(param, SERVER, buf, BUFSIZE, '\n', 0)) > 3){
|
|
if(socksend(param, param->ctrlsock, buf, i, conf.timeouts[STRING_S])!=i) {RETURN(833);}
|
|
if(isnumber(*buf) && buf[3] != '-') {
|
|
ressent = 1;
|
|
break;
|
|
}
|
|
}
|
|
sc = param->remsock;
|
|
param->remsock = ss;
|
|
param->srv->so._setsockopt(param->sostate, param->remsock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg));
|
|
param->srv->so._setsockopt(param->sostate, clidatasock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg));
|
|
param->clisock = clidatasock;
|
|
res = mapsocket(param, conf.timeouts[CONNECTION_S]);
|
|
if(param->remsock != INVALID_SOCKET) {
|
|
param->srv->so._shutdown (param->sostate, param->remsock, SHUT_RDWR);
|
|
param->srv->so._closesocket(param->sostate, param->remsock);
|
|
}
|
|
if(param->clisock != INVALID_SOCKET) {
|
|
param->srv->so._shutdown (param->sostate, param->clisock, SHUT_RDWR);
|
|
param->srv->so._closesocket(param->sostate, param->clisock);
|
|
}
|
|
param->clisock = param->ctrlsock;
|
|
param->remsock = sc;
|
|
sc = INVALID_SOCKET;
|
|
ss = INVALID_SOCKET;
|
|
clidatasock = INVALID_SOCKET;
|
|
if(!ressent){
|
|
while((i = sockgetlinebuf(param, SERVER, buf, BUFSIZE, '\n', conf.timeouts[STRING_L])) > 3){
|
|
if(socksend(param, param->ctrlsock, buf, i, conf.timeouts[STRING_S])!=i) {RETURN(833);}
|
|
if(isnumber(*buf) && buf[3] != '-') break;
|
|
}
|
|
if(i < 3) {RETURN(834);}
|
|
}
|
|
}
|
|
else {
|
|
if(status < 3) {
|
|
if(socksend(param, param->remsock, (unsigned char *)"530 login\r\n", 11, conf.timeouts[STRING_S])!=1) {RETURN (810);}
|
|
continue;
|
|
}
|
|
if(!strncasecmp((char *)buf, "QUIT", 4)) status = 5;
|
|
if(!strncasecmp((char *)buf, "CWD ", 4)) req = strdup((char *)buf);
|
|
i = (int)strlen((char *)buf);
|
|
buf[i++] = '\r';
|
|
buf[i++] = '\n';
|
|
if(socksend(param, param->remsock, buf, i, conf.timeouts[STRING_S])!=i) {RETURN (811);}
|
|
param->statscli64+=(i);
|
|
param->nwrites++;
|
|
while((i = sockgetlinebuf(param, SERVER, buf, BUFSIZE, '\n', conf.timeouts[STRING_L])) > 0){
|
|
if(socksend(param, param->ctrlsock, buf, i, conf.timeouts[STRING_S])!=i) {RETURN (812);}
|
|
if(i > 4 && isnumber(*buf) && buf[3] != '-') break;
|
|
}
|
|
if(status == 5) {RETURN (0);}
|
|
if(i < 3) {RETURN (813);}
|
|
}
|
|
sasize = sizeof(param->sincr);
|
|
if(param->srv->so._getpeername(param->sostate, param->ctrlsock, (struct sockaddr *)¶m->sincr, &sasize)){RETURN(819);}
|
|
if(req && (param->statscli64 || param->statssrv64)){
|
|
dolog(param, (unsigned char *)req);
|
|
}
|
|
}
|
|
|
|
CLEANRET:
|
|
|
|
if(sc != INVALID_SOCKET) {
|
|
param->srv->so._shutdown(param->sostate, sc, SHUT_RDWR);
|
|
param->srv->so._closesocket(param->sostate, sc);
|
|
}
|
|
if(ss != INVALID_SOCKET) {
|
|
param->srv->so._shutdown(param->sostate, ss, SHUT_RDWR);
|
|
param->srv->so._closesocket(param->sostate, ss);
|
|
}
|
|
if(clidatasock != INVALID_SOCKET) {
|
|
param->srv->so._shutdown(param->sostate, clidatasock, SHUT_RDWR);
|
|
param->srv->so._closesocket(param->sostate, clidatasock);
|
|
}
|
|
sasize = sizeof(param->sincr);
|
|
param->srv->so._getpeername(param->sostate, param->ctrlsock, (struct sockaddr *)¶m->sincr, &sasize);
|
|
if(param->res != 0 || param->statscli64 || param->statssrv64 ){
|
|
dolog(param, (unsigned char *)((req && (param->res > 802))? req:NULL));
|
|
}
|
|
if(req) free(req);
|
|
if(buf) free(buf);
|
|
return (NULL);
|
|
}
|
|
|
|
#ifdef WITHMAIN
|
|
struct proxydef childdef = {
|
|
ftpprchild,
|
|
21,
|
|
0,
|
|
S_FTPPR,
|
|
" -hdefault_host[:port] - use this host and port as default if no host specified\n"
|
|
};
|
|
#include "proxymain.c"
|
|
#endif
|
|
|
|
#else
|
|
|
|
/* Built without FTP support. The service and the redirect naming it are
|
|
still known, so a configuration carrying them is read rather than
|
|
refused, and a client reaching one is turned away. */
|
|
void * ftpprchild(struct clientparam * param){
|
|
param->res = 878;
|
|
dolog(param, (unsigned char *)"ftp support is not built in");
|
|
return NULL;
|
|
}
|
|
|
|
#endif
|