3proxy/src/ftppr.c
Vladimir Dubrovin be619898c6 Fix missing device binding for few cases
(cherry picked from commit b4f5ffbf8c)
2026-10-10 17:42:48 +03:00

349 lines
12 KiB
C

/*
3APA3A simplest proxy server
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
please read License Agreement
*/
#include "proxy.h"
#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(&param->sinsr))==21)?0:':', ntohs(*SAPORT(&param->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(&param->sincl), SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {RETURN(821);}
if(intbindtodevice(param, clidatasock)){RETURN(822);}
*SAPORT(&param->sincl) = 0;
if(param->srv->so._bind(param->sostate, clidatasock, (struct sockaddr *)&param->sincl, SASIZE(&param->sincl))){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 *)&param->sincl, &sasize)){RETURN(824);}
if(pasv == 1){
if(*SAFAMILY(&param->sincl) == AF_INET)
sprintf((char *)buf, "227 OK (%u,%u,%u,%u,%u,%u)\r\n",
(unsigned)(((unsigned char *)(SAADDR(&param->sincl)))[0]),
(unsigned)(((unsigned char *)(SAADDR(&param->sincl)))[1]),
(unsigned)(((unsigned char *)(SAADDR(&param->sincl)))[2]),
(unsigned)(((unsigned char *)(SAADDR(&param->sincl)))[3]),
(unsigned)(((unsigned char *)(SAPORT(&param->sincl)))[0]),
(unsigned)(((unsigned char *)(SAPORT(&param->sincl)))[1])
);
else sprintf((char *)buf, "227 OK (127,0,0,1,%u,%u)\r\n",
(unsigned)(((unsigned char *)(SAPORT(&param->sincl)))[0]),
(unsigned)(((unsigned char *)(SAPORT(&param->sincl)))[1])
);
}
else {
sprintf((char *)buf, "229 OK (|||%u|)\r\n",
(unsigned)ntohs(*SAPORT(&param->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(&param->sincr) = htons((uint16_t)((b5<<8)^b6));
if(connectwithpoll(param, clidatasock, (struct sockaddr *)&param->sincr, SASIZE(&param->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 *)&param->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 *)&param->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 *)&param->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