fteqw/engine/http/ftpserver.c
Spoike 5d2ff1286d first version with dtls support. disabled for now.
schannel (ie: windows native) works as a client, not a server.
gnutls provides both client+server support. servers need to load a pre-generated cert from disk.
tweaked gamepads to actually work in the web target.
tweak gamepads a bit. added gp_* bind aliases. xinput+sdl+web should all use the same key mappings.
finally added the itemtimer glsl.
tweaked software renderer to not be quite so buggy, but you probably won't realise that if you try it.
disabled the ill-fated QWOVERQ3 feature.
don't do the oldorigin thing in quakeworld mods. hopefully this'll fix cspree's weird stuck-in-floor issue.
dpp7 is buggy serverside. disabled for now. I'm part way through rewriting its deltas.

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5103 fc73d0e0-1445-4013-8a0c-d673dee63da5
2017-05-18 10:24:09 +00:00

1510 lines
36 KiB
C

#include "quakedef.h"
#ifdef WEBSVONLY
#undef vsnprintf
#undef _vsnprintf
#ifdef _WIN32
#define vsnprintf _vsnprintf
#endif
#endif
#ifdef WEBSERVER
#include "iweb.h"
//hows this as a bug.
//TCP data can travel at different speeds.
//If the later bits of a data channel arrive after the message saying that a transfer was compleate,
//the later bits of the file may not arrive before the client closes the conenction.
//this is a major bug and can prevent the server from giving files at a high pl/ping
#include "netinc.h"
static iwboolean ftpserverinitied = false;
static SOCKET ftpserversocket = INVALID_SOCKET;
static int ftpserverport = 0;
qboolean ftpserverfailed;
typedef struct FTPclient_s{
char peername[256];
char name[64];
char pwd[64];
int auth; //has it got auth?
char path[256];
char renamefrom[256];
char commandbuffer[256];
char messagebuffer[256];
int cmdbuflen;
int msgbuflen;
int controlaf;
SOCKET controlsock;
SOCKET datasock; //FTP only allows one transfer per connection.
int dataislisten;
int datadir; //0 no data, 1 reading, 2 writing
vfsfile_t *file;
qboolean brieflist; //incoming list command was an nlist
qofs_t restartpos;
unsigned long blocking;
#ifdef MULTITHREAD
void *transferthread;
#endif
struct FTPclient_s *next;
} FTPclient_t;
FTPclient_t *FTPclient;
SOCKET FTP_BeginListening(int aftype, int port)
{
struct sockaddr_qstorage address;
unsigned long _true = true;
unsigned long _false = false;
int i;
SOCKET sock;
int af;
int prot;
if (!port)
port = IWebGetSafeListeningPort();
switch(aftype)
{
case 0:
#ifdef IPPROTO_IPV6
case 2:
af = AF_INET6;
prot = IPPROTO_TCP;
break;
#endif
case 1:
af = AF_INET;
prot = IPPROTO_TCP;
break;
// case 11:
// af = AF_IPX;
// prot = NSPROTO_SPX;
// break;
default:
return INVALID_SOCKET;
}
if ((sock = socket (af, SOCK_STREAM, prot)) == -1)
{
IWebPrintf ("FTP_BeginListening: socket: %s\n", strerror(neterrno()));
return INVALID_SOCKET;
}
if (ioctlsocket (sock, FIONBIO, &_true) == -1)
{
IWebPrintf ("FTP_BeginListening: ioctl FIONBIO: %s", strerror(neterrno()));
return INVALID_SOCKET;
}
#ifdef IPPROTO_IPV6
if (aftype == 0 || aftype == 2)
{
//0=ipv4+ipv6
//2=ipv6 only
if (aftype == 0)
{
if (0 > setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&_false, sizeof(_false)))
{
//abort and do ipv4 only if hybrid sockets don't work.
closesocket(sock);
return FTP_BeginListening(1, port);
}
}
else
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&_true, sizeof(_true));
memset(&address, 0, sizeof(address));
((struct sockaddr_in6*)&address)->sin6_family = AF_INET6;
if (port == PORT_ANY)
((struct sockaddr_in6*)&address)->sin6_port = 0;
else
((struct sockaddr_in6*)&address)->sin6_port = htons((short)port);
}
else
#endif
{
//1=ipv4 only
((struct sockaddr_in*)&address)->sin_family = AF_INET;
//ZOID -- check for interface binding option
if ((i = COM_CheckParm("-ip")) != 0 && i < com_argc) {
((struct sockaddr_in*)&address)->sin_addr.s_addr = inet_addr(com_argv[i+1]);
Con_TPrintf("Binding to IP Interface Address of %s\n",
inet_ntoa(((struct sockaddr_in*)&address)->sin_addr));
} else
((struct sockaddr_in*)&address)->sin_addr.s_addr = INADDR_ANY;
if (port == PORT_ANY)
((struct sockaddr_in*)&address)->sin_port = 0;
else
((struct sockaddr_in*)&address)->sin_port = htons((short)port);
}
if( bind (sock, (void *)&address, sizeof(address)) == -1)
{
IWebPrintf("FTP_BeginListening: failed to bind socket\n");
closesocket(ftpserversocket);
return INVALID_SOCKET;
}
listen(sock, 3);
return sock;
}
void FTP_ServerShutdown(void)
{
closesocket(ftpserversocket);
ftpserversocket = INVALID_SOCKET;
ftpserverinitied = false;
IWebPrintf("FTP server is deactivated\n");
}
static iwboolean FTP_AllowUpLoad(const char *fname, FTPclient_t *cl)
{
if (cl->auth & IWEBACC_FULL)
return true;
if (!(cl->auth & IWEBACC_WRITE))
return false;
return IWebAllowUpLoad(fname, cl->name);
}
static iwboolean FTP_AllowDownLoad(const char *fname, FTPclient_t *cl)
{
if (cl->auth & IWEBACC_FULL)
return true;
if (!(cl->auth & IWEBACC_READ))
return false;
if (FTP_AllowUpLoad(fname, cl))
return true;
return SV_AllowDownload(fname);
}
static iwboolean FTP_AllowList(const char *fname, FTPclient_t *cl)
{
return FTP_AllowDownLoad(fname, cl) || FTP_AllowUpLoad(fname, cl);
}
//we ought to filter this to remove duplicates.
static int QDECL SendFileNameTo(const char *rawname, qofs_t size, time_t mtime, void *param, searchpathfuncs_t *spath)
{
FTPclient_t *cl = param;
SOCKET socket = cl->datasock;
// int i;
char buffer[256+1];
char *slash;
char nondirname[MAX_QPATH];
int isdir = rawname[strlen(rawname)-1] == '/';
char *fname;
#ifndef WEBSVONLY //copy protection of the like that QWSV normally has.
if (!isdir)
if (!FTP_AllowList(rawname, cl))
return true;
#endif
Q_strncpyz(nondirname, rawname, sizeof(nondirname));
if (isdir)
nondirname[strlen(nondirname)-1] = '\0';
fname = nondirname;
while((slash = strchr(fname, '/')))
fname = slash+1;
if (cl->brieflist)
Q_snprintfz(buffer, sizeof(buffer), "%s\r\n", fname);
else
{
char timestamp[32];
if (1)
strftime(timestamp, sizeof(timestamp), "%b %d %Y", gmtime(&mtime));
else
strftime(timestamp, sizeof(timestamp), "%b %d %H:%M", gmtime(&mtime));
Q_snprintfz(buffer, sizeof(buffer), "%c%c%c-------\t1\troot\troot\t%8"PRIuQOFS" %s %s\r\n",
isdir?'d':'-',
FTP_AllowDownLoad(rawname, cl)?'r':'-',
FTP_AllowUpLoad(rawname, cl)?'w':'-',
size, timestamp, fname);
}
// strcpy(buffer, fname);
// for (i = strlen(buffer); i < 40; i+=8)
// strcat(buffer, "\t");
send(socket, buffer, strlen(buffer), 0);
return true;
}
SOCKET FTP_SV_makelistensocket(unsigned long nblocking)
{
char name[256];
SOCKET sock;
struct hostent *hent;
struct sockaddr_in address;
// int fromlen;
address.sin_family = AF_INET;
if (gethostname(name, sizeof(name)) == -1)
return INVALID_SOCKET;
hent = gethostbyname(name);
if (!hent)
return INVALID_SOCKET;
address.sin_addr.s_addr = *(int *)(hent->h_addr_list[0]);
address.sin_port = 0;
if ((sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
{
Sys_Error ("FTP_TCP_OpenSocket: socket: %s", strerror(neterrno()));
}
if (ioctlsocket (sock, FIONBIO, &nblocking) == -1)
{
Sys_Error ("FTP_TCP_OpenSocket: ioctl FIONBIO: %s", strerror(neterrno()));
}
if( bind (sock, (void *)&address, sizeof(address)) == -1)
{
closesocket(sock);
return INVALID_SOCKET;
}
listen(sock, 2);
return sock;
}
int FTP_SVGetSocketPort (SOCKET socket)
{
struct sockaddr_qstorage addr;
int adrlen = sizeof(addr);
if (getsockname(socket, (struct sockaddr*)&addr, &adrlen) == -1)
return false;
if (((struct sockaddr_in*)&addr)->sin_family == AF_INET6)
return ntohs(((struct sockaddr_in6*)&addr)->sin6_port);
else if (((struct sockaddr_in*)&addr)->sin_family == AF_INET)
return ntohs(((struct sockaddr_in*)&addr)->sin_port);
else
return 0; //no idea
}
//only to be used for ipv4 sockets.
iwboolean FTP_SVSocketToV4String (SOCKET socket, char *s)
{
struct sockaddr_qstorage addr;
qbyte *baddr;
int adrlen = sizeof(addr);
char name[256];
unsigned short port;
if (getsockname(socket, (struct sockaddr*)&addr, &adrlen) == -1)
return false;
if (((struct sockaddr_in*)&addr)->sin_family == AF_INET6)
{
port = ((struct sockaddr_in6*)&addr)->sin6_port;
baddr = ((struct sockaddr_in6*)&addr)->sin6_addr.s6_addr;
if (memcmp(baddr, "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12))
return false; //must be ipv4-mapped for this ipv4 function
baddr += 12;
}
else if (((struct sockaddr_in*)&addr)->sin_family == AF_INET)
{
port = ((struct sockaddr_in*)&addr)->sin_port;
baddr = (qbyte*)&((struct sockaddr_in*)&addr)->sin_addr;
}
else
return false;
if (!*(int*)baddr)
{
//FIXME doesn't work on anything but windows.
if (gethostname(name, sizeof(name)) != -1)
{
struct hostent *hent = gethostbyname(name);
if (hent)
baddr = hent->h_addr_list[0];
}
}
if (!baddr)
return false;
sprintf(s, "%i,%i,%i,%i,%i,%i", baddr[0], baddr[1], baddr[2], baddr[3], ((qbyte *)&port)[0], ((qbyte *)&port)[1]);
return true;
}
iwboolean FTP_V4StringToAdr (const char *s, struct sockaddr_in *addr)
{
((qbyte*)&addr->sin_addr)[0] = strtol(s, (char**)&s, 0);
if (*s++ != ',') return false;
((qbyte*)&addr->sin_addr)[1] = strtol(s, (char**)&s, 0);
if (*s++ != ',') return false;
((qbyte*)&addr->sin_addr)[2] = strtol(s, (char**)&s, 0);
if (*s++ != ',') return false;
((qbyte*)&addr->sin_addr)[3] = strtol(s, (char**)&s, 0);
if (*s++ != ',') return false;
((qbyte*)&addr->sin_port)[0] = strtol(s, (char**)&s, 0);
if (*s++ != ',') return false;
((qbyte*)&addr->sin_port)[1] = strtol(s, (char**)&s, 0);
return true;
}
iwboolean FTP_HostToSockaddr(int prot, char *host, int port, struct sockaddr_qstorage *addr, size_t *addrsize)
{
iwboolean r = false;
struct addrinfo *res, hint;
char service[16];
*addrsize = 0;
memset(&hint, 0, sizeof(hint));
hint.ai_flags = AI_NUMERICHOST;
switch(prot)
{
case 1:
hint.ai_family = AF_INET;
break;
case 2:
hint.ai_family = AF_INET6;
break;
}
Q_snprintfz(service, sizeof(service), "%i", port);
if (getaddrinfo(host, service, &hint, &res))
return false;
if (res && res->ai_addr && res->ai_addrlen <= sizeof(*addr))
{
*addrsize = res->ai_addrlen;
memcpy(addr, res->ai_addr, res->ai_addrlen);
r = true;
}
freeaddrinfo(res);
return r;
#if 0
host = va("[%s]", host);
return NET_StringToSockaddr(host, port, addr, NULL, NULL);
#endif
}
/*
* Responsable for sending all control server -> client messages.
* Queues the message if it cannot send now.
* Kicks if too big a queue.
*/
void QueueMessage(FTPclient_t *cl, char *msg)
{
IWebDPrintf("FTP> %s", msg);
if (send (cl->controlsock, msg, strlen(msg), 0) == -1)
{ //wasn't sent
if (strlen(msg) + strlen(cl->messagebuffer) >= sizeof(cl->messagebuffer)-1)
{
closesocket(cl->controlsock); //but don't mark it as closed, so we get errors later (for this is how we shall tell).
return;
}
strcat(cl->messagebuffer, msg);
}
}
void VARGS QueueMessageva(FTPclient_t *cl, char *fmt, ...)
{
va_list argptr;
char msg[1024];
va_start (argptr, fmt);
vsnprintf (msg,sizeof(msg)-1, fmt,argptr);
msg[sizeof(msg)-1] = 0;
va_end (argptr);
IWebDPrintf("FTP> %s", msg);
if (send (cl->controlsock, msg, strlen(msg), 0) == -1)
{ //wasn't sent
if (strlen(msg) + strlen(cl->messagebuffer) >= sizeof(cl->messagebuffer)-1)
{
closesocket(cl->controlsock);
cl->controlsock = INVALID_SOCKET;
}
strcat(cl->messagebuffer, msg);
}
}
//if eg: "RETR Some File Name.txt \r\n", and the RETR was already parsed, we'll read out the Some File Name until the \r\n
//returns a directly-usable game path.
qboolean FTP_ReadToAbsFilename(FTPclient_t *cl, const char *msg, char *out, size_t outsize)
{
size_t len = 0;
const char *end;
if (*msg == ' ')
msg++;
else
{
*out = 0;
return false;
}
end = msg;
while(*end)
{
if (*end == '\r' || *end == '\n')
break;
end++;
}
//trim stupid trailing space that windows insists on fucking shit up with
if (end > msg && end[-1] == ' ')
end--;
//figure out the root
if (*msg == '/')
msg++;
else
{ //FIXME: nuke the +1s
len = strlen(cl->path);
if (len >= outsize)
{ //too long...
*out = 0;
return false;
}
memcpy(out, cl->path, len);
}
while (msg < end)
{
if (*msg == '/')
msg++; //double slashes will be silently ignored. also simplifies ../ etc.
else if (!strncmp(msg, "..", 2) && (msg+2 == end || msg[2] == '/'))
{ // "/foo/../bar" should end up as just "bar"
msg+=2;
if (!len)
{ //can't go above root...
*out = 0;
return false;
}
while (len > 0)
{
if (out[--len] == '/')
break;
}
}
else if (!strncmp(msg, ".", 1) && (msg+1 == end || msg[1] == '/'))
{ //filenames relative to the working directory are stupid, but whatever
msg+=1;
while (len > 0)
{
if (out[--len] == '/')
break;
}
}
else
{
const char *s;
for (s = msg; s < end; s++)
{
if (*s == '/')
break;
}
if (s == msg)
{ //error...
*out = 0;
return false;
}
if (len + s-msg + 2 > outsize)
break;
if (len)
out[len++] = '/';
memcpy(out+len, msg, s-msg);
len += s-msg;
msg = s;
}
}
out[len] = 0;
return true;
}
#ifdef MULTITHREAD
int FTP_TransferThread(void *vcl)
{
char resource[8192];
FTPclient_t *cl = vcl;
u_long _false = false;
ioctlsocket (cl->datasock, FIONBIO, &_false);
if ((cl->datadir&~64) == 1)
{
while(1)
{
int ammount = VFS_READ(cl->file, resource, sizeof(resource));
int chunk, sent;
if (ammount <= 0)
break;
for (sent = 0; sent < ammount; sent += chunk)
{
chunk = send(cl->datasock, resource, ammount-sent, 0);
if (chunk <= 0)
break;
}
if (ammount != sent)
break;
}
}
else if ((cl->datadir&~64) == 2)
{
while(1)
{
int ammount = recv(cl->datasock, resource, sizeof(resource), 0);
if (ammount <= 0)
break;
if (ammount != VFS_WRITE(cl->file, resource, ammount))
break;
}
}
cl->datadir &= ~64;
return 0;
}
#endif
iwboolean FTP_ServerThinkForConnection(FTPclient_t *cl)
{
int ret;
struct sockaddr_qstorage from;
int fromlen;
char *msg, *line;
char mode[64];
char resource[8192];
int _true = true;
#ifdef MULTITHREAD
if (cl->datadir & 64)
{
if (*cl->messagebuffer)
{ //fixme: gah!
if (send (cl->controlsock, cl->messagebuffer, strlen(cl->messagebuffer), 0) != -1)
*cl->messagebuffer = '\0'; //YAY! It went!
}
return false;
}
if (cl->transferthread)
{
Sys_WaitOnThread(cl->transferthread);
cl->transferthread = NULL;
}
#endif
if (cl->datadir == 1)
{
int pos, sent;
int ammount, wanted = sizeof(resource);
pos = VFS_TELL(cl->file);
ammount = VFS_READ(cl->file, resource, wanted);
sent = send(cl->datasock, resource, ammount, 0);
if (sent == -1)
{
VFS_SEEK(cl->file, pos);
if (neterrno() != NET_EWOULDBLOCK)
{
closesocket(cl->datasock);
cl->datasock = INVALID_SOCKET;
VFS_CLOSE(cl->file);
cl->file = NULL;
QueueMessage (cl, "226 Transfer complete .\r\n");
cl->datadir = 0;
}
}
else
{
if (sent != ammount)
VFS_SEEK(cl->file, pos + sent);
if (ammount != wanted && sent == ammount) //file is over
{
send(cl->datasock, resource, 0, 0);
send(cl->datasock, resource, 0, 0);
send(cl->datasock, resource, 0, 0);
closesocket(cl->datasock);
cl->datasock = INVALID_SOCKET;
VFS_CLOSE(cl->file);
cl->file = NULL;
QueueMessage (cl, "226 Transfer complete .\r\n");
cl->datadir = 0;
}
}
pos = cl->datadir?1:!cl->blocking;
if (ioctlsocket (cl->controlsock, FIONBIO, (u_long *)&pos) == -1)
{
IWebPrintf ("FTP_ServerRun: blocking error: %s\n", strerror(neterrno()));
return 0;
}
}
else if (cl->datadir == 2)
{
int len;
while((len = recv(cl->datasock, resource, sizeof(resource), 0)) >0 )
{
VFS_WRITE(cl->file, resource, len);
}
if (len == -1)
{
if (neterrno() != NET_EWOULDBLOCK)
{
closesocket(cl->datasock);
cl->datasock = INVALID_SOCKET;
if (cl->file)
VFS_CLOSE(cl->file);
cl->file = NULL;
QueueMessage (cl, "226 Transfer complete .\r\n");
cl->datadir = 0;
}
}
if (len == 0)
{
QueueMessage (cl, "226 Transfer complete .\r\n");
VFS_CLOSE(cl->file);
cl->file = NULL;
cl->datadir = 0;
}
}
ret = recv(cl->controlsock, cl->commandbuffer+cl->cmdbuflen, sizeof(cl->commandbuffer)-1 - cl->cmdbuflen, 0);
if (ret == -1)
{
int e = neterrno();
if (e == NET_EWOULDBLOCK)
return false; //remove
if (e == NET_ECONNABORTED || e == NET_ECONNRESET)
return true;
IWebPrintf ("NET_GetPacket: %s\n", strerror(e));
return true;
}
if (*cl->messagebuffer)
{
if (send (cl->controlsock, cl->messagebuffer, strlen(cl->messagebuffer), 0) != -1)
*cl->messagebuffer = '\0'; //YAY! It went!
}
if (ret == 0)
return false;
cl->cmdbuflen += ret;
cl->commandbuffer[cl->cmdbuflen] = 0;
line = cl->commandbuffer;
while (1)
{
msg = line;
while (*line)
{
if (*line == '\r')
*line = ' ';
if (*line == '\n')
break;
line++;
}
if (!*line) //broken client
{
memmove(cl->commandbuffer, line, strlen(line)+1);
cl->cmdbuflen = strlen(line);
break;
}
*line = '\0';
line++;
IWebDPrintf("FTP: %s\n", msg);
msg = COM_ParseOut(msg, mode, sizeof(mode));
if (!stricmp(mode, "SYST"))
{
QueueMessage (cl, "215 UNIX Type: L8.\r\n"); //some browsers can be wierd about things.
}
else if (!stricmp(mode, "FEAT"))
{
QueueMessage (cl, "211-Extensions supported:\r\n");
QueueMessage (cl, " SIZE\r\n");
QueueMessage (cl, " REST\r\n");
QueueMessage (cl, " EPSV\r\n");
QueueMessage (cl, " EPRT\r\n");
// QueueMessage (cl, " MDTM\r\n");
QueueMessage (cl, "211 End\r\n");
}
else if (!stricmp(mode, "user"))
{
msg = COM_ParseOut(msg, cl->name, sizeof(cl->name));
cl->auth = 0; //any access rights go away now, so they can't spoof read access with dodgy filenames.
QueueMessage (cl, "331 User name received, will be checked with password.\r\n");
}
else if (!stricmp(mode, "pass"))
{
msg = COM_ParseOut(msg, cl->pwd, sizeof(cl->pwd));
cl->auth = IWebAuthorize(cl->name, cl->pwd);
if (cl->auth)
QueueMessage (cl, "230 User logged in.\r\n");
else
QueueMessage (cl, "530 Username or Password was incorrect or otherwise invalid.\r\n");
}
else if (!stricmp(mode, "TYPE"))
{
if (!cl->auth)
{
QueueMessage (cl, "530 Not logged in.\r\n");
continue;
}
msg = COM_ParseOut(msg, resource, sizeof(resource));
if (!stricmp(resource, "A")) //ascii
{
QueueMessage (cl, "200 ascii selected.\r\n");
}
else if (!stricmp(resource, "I")) //binary
{
QueueMessage (cl, "200 binary selected.\r\n");
}
else
{
QueueMessage (cl, "200 ascii selected.\r\n");
}
}
else if (!stricmp(mode, "PWD"))
{
if (!cl->auth)
{
QueueMessage (cl, "530 Not logged in.\r\n");
continue;
}
if (*cl->path)
QueueMessageva (cl, "257 \"/%s/\"\r\n", cl->path);
else
QueueMessageva (cl, "257 \"/\"\r\n");
}
else if (!stricmp(mode, "CWD"))
{
if (!cl->auth)
{
QueueMessage (cl, "530 Not logged in.\r\n");
continue;
}
if (!FTP_ReadToAbsFilename(cl, msg, resource, sizeof(resource)))
{
QueueMessage (cl, "550 invalid path.\r\n");
continue;
}
Q_strncpyz(cl->path, resource, sizeof(cl->path));
QueueMessage (cl, "200 directory changed.\r\n");
}
else if (!stricmp(mode, "MKD"))
{
//create directory
FTP_ReadToAbsFilename(cl, msg, resource, sizeof(resource));
if (!cl->auth)
{
QueueMessage (cl, "530 Not logged in.\r\n");
continue;
}
if (!*resource || !FTP_AllowUpLoad(resource, cl))
{
IWebPrintf("%s: Denied mkdir request for \"ftp://%s@%s/%s\"\n", cl->peername, cl->name, "", resource);
QueueMessage (cl, "550 Access denied.\r\n");
continue;
}
IWebPrintf("%s: Mkdir request for \"ftp://%s@%s/%s\"\n", cl->peername, cl->name, "", resource);
Q_strncatz(resource, "/", sizeof(resource));
FS_CreatePath(resource, FS_GAMEONLY);
QueueMessage (cl, "250 Success.\r\n");
}
else if (!stricmp(mode, "EPSV"))
{
int aftype = 0;
//one argument, "1"=ipv4, "2"=ipv6, "11"=ipx (by rfc 1700). if not present, use same as control connection
//reply: "229 Entering Extended Passive Mode (|||$PORTNUM|)\r\n"
while(*msg == ' ')
msg++;
if (!strncmp(msg, "ALL", 3))
continue; //rfc2428 'EPSV ALL' is a signal to client NATs that PORT/EPRT/PASV will not be used, and that they can just treat it as a regular TCP connection same as anything else. we 'must' also refuse any of those commands too, but we shouldn't receive them anyway.
aftype = atoi(msg);
if (!aftype)
aftype = cl->controlaf;
if (!cl->auth)
{
QueueMessage (cl, "530 Not logged in.\r\n");
continue;
}
if (cl->datasock != INVALID_SOCKET)
{
closesocket(cl->datasock);
cl->datasock = INVALID_SOCKET;
}
cl->datasock = FTP_BeginListening(aftype, 0);
if (cl->datasock == INVALID_SOCKET)
QueueMessage (cl, "425 server was unable to make a listen socket\r\n");
else
{
QueueMessageva (cl, "229 Entering Extended Passive Mode (|||%i|).\r\n", FTP_SVGetSocketPort(cl->datasock));
}
cl->dataislisten = true;
}
else if (!stricmp(mode, "PASV"))
{
if (!cl->auth)
{
QueueMessage (cl, "530 Not logged in.\r\n");
continue;
}
if (cl->datasock != INVALID_SOCKET)
{
closesocket(cl->datasock);
cl->datasock = INVALID_SOCKET;
}
cl->datasock = FTP_BeginListening(1, 0);
if (cl->datasock == INVALID_SOCKET)
QueueMessage (cl, "425 server was unable to make a listen socket\r\n");
else
{
if (FTP_SVSocketToV4String(cl->datasock, resource))
QueueMessageva (cl, "227 Entering Passive Mode (%s).\r\n", resource);
else
QueueMessageva (cl, "550 Unable to parse address.\r\n", resource);
}
cl->dataislisten = true;
}
else if (!stricmp(mode, "EPRT"))
{
//eg: one of:
//EPRT |1|132.235.1.2|6275|
//EPRT |2|1080::8:800:200C:417A|5282|
//reply: 522 Network protocol not supported, use (1,2)
char d;
int prot;
int port;
char *eon, *host;
struct sockaddr_qstorage peer;
size_t peersize;
while(*msg == ' ')
msg++;
d = *msg++;
prot = strtol(msg, &msg, 0);
host = ++msg;
eon = strchr(msg, d);
if (eon)
{
*eon++ = 0;
msg = eon;
}
cl->dataislisten = false;
if (cl->datasock != INVALID_SOCKET)
closesocket(cl->datasock);
cl->datasock = INVALID_SOCKET;
port = strtol(msg, &msg, 0);
if (*msg != d || !eon || !FTP_HostToSockaddr(prot, host, port, &peer, &peersize))
QueueMessage (cl, "522 Network protocol not supported, use (1,2).\r\n");
else
{
memset(&from, 0, sizeof(from));
((struct sockaddr*)&from)->sa_family = ((struct sockaddr*)&peer)->sa_family;
if ((cl->datasock = socket (((struct sockaddr*)&from)->sa_family, SOCK_STREAM, IPPROTO_TCP)) != -1)
{
if (ioctlsocket (cl->datasock, FIONBIO, (u_long *)&_true) != -1)
if( bind (cl->datasock, (void *)&from, peersize) != -1)
{
connect(cl->datasock, (struct sockaddr *)&peer, peersize);
QueueMessage (cl, "200 Opened data channel.\r\n");
continue;
}
closesocket(cl->datasock);
cl->datasock=INVALID_SOCKET;
}
QueueMessage (cl, "550 Command not fully implemented.\r\n");
}
}
else if (!stricmp(mode, "PORT"))
{
if (!cl->auth)
{
QueueMessage (cl, "530 Not logged in.\r\n");
continue;
}
if (cl->datasock != INVALID_SOCKET)
{
closesocket(cl->datasock);
cl->datasock = INVALID_SOCKET;
}
msg = COM_ParseOut(msg, resource, sizeof(resource));
cl->dataislisten = false;
memset(&from, 0, sizeof(from));
((struct sockaddr_in*)&from)->sin_family = AF_INET;
if ((cl->datasock = socket (((struct sockaddr*)&from)->sa_family, SOCK_STREAM, IPPROTO_TCP)) == -1)
{
Sys_Error ("FTP_ServerThinkForConnection: socket: %s", strerror(neterrno()));
}
if (ioctlsocket (cl->datasock, FIONBIO, (u_long *)&_true) == -1)
{
Sys_Error ("FTP_ServerThinkForConnection: ioctl FIONBIO: %s", strerror(neterrno()));
}
if( bind (cl->datasock, (void *)&from, sizeof(from)) == -1)
{
closesocket(cl->datasock);
cl->datasock=INVALID_SOCKET;
QueueMessage (cl, "425 server bind error.\r\n");
continue;
}
fromlen = sizeof(from);
if (FTP_V4StringToAdr(resource, (struct sockaddr_in *)&from))
{
connect(cl->datasock, (struct sockaddr *)&from, fromlen);
QueueMessage (cl, "200 Opened data channel.\r\n");
}
else
{
closesocket(cl->datasock);
cl->datasock=INVALID_SOCKET;
QueueMessage (cl, "425 server resolve error.\r\n");
}
}
else if (!stricmp(mode, "LIST") || !stricmp(mode, "NLST"))
{
char buffer[256];
cl->brieflist = !stricmp(mode, "NLST");
if (!cl->auth)
{
QueueMessage (cl, "530 Not logged in.\r\n");
continue;
}
if (cl->dataislisten) //accept a connect.
{
int err;
int _true = true;
int temp;
struct sockaddr_qstorage adr;
int adrlen = sizeof(adr);
temp = accept(cl->datasock, (struct sockaddr *)&adr, &adrlen);
closesocket(cl->datasock);
cl->datasock = temp;
cl->dataislisten = false;
if (cl->datasock == INVALID_SOCKET)
{
err = neterrno();
QueueMessageva (cl, "425 Can't accept pasv data connection - %i.\r\n", err);
continue;
}
else
ioctlsocket(cl->datasock, FIONBIO, (u_long *)&_true);
}
if (cl->datasock == INVALID_SOCKET)
{
QueueMessage (cl, "503 Bad sequence of commands.\r\n");
continue;
}
if (*cl->path == '/')
strcpy(buffer, cl->path+1);
else
strcpy(buffer, cl->path);
if (*buffer) //last character should be a /
if (buffer[strlen(buffer)-1] != '/')
strcat(buffer, "/");
strcat(buffer, "*");
QueueMessage (cl, "125 Opening FAKE ASCII mode data connection for file.\r\n");
COM_EnumerateFiles(buffer, SendFileNameTo, cl);
QueueMessage (cl, "226 Transfer complete.\r\n");
closesocket(cl->datasock);
cl->datasock = INVALID_SOCKET;
}
/*else if (!stricmp(mode, "MDTM"))
{
char ospath[MAX_OSPATH];
struct tm *t;
FTP_ReadToAbsFilename(cl, msg, resource, sizeof(resource));
if (FS_NativePath(resource, FS_GAME, ospath, sizeof(ospath)))
{
t = gmtime(Sys_FileTime(path));
QueueMessageva (cl, "213 %04i%02i%02i%02i%02i%02i\r\n",
1900+t->tm_year, 1+t->tm->mon, 1+t->tm->mday,
t->tm->hour, t->tm->min, t->tm_sec );
}
else
QueueMessageva (cl, "550 unavailable.\r\n", size);
}*/
else if (!stricmp(mode, "SIZE")) //why IE can't use the list command to find file length, I've no idea.
{
//STRU, MODE, and TYPE may change the reported size...
vfsfile_t *f;
qofs_t size = qofs_ErrorValue();
FTP_ReadToAbsFilename(cl, msg, resource, sizeof(resource));
if (*resource && FTP_AllowList(resource, cl))
{
f = FS_OpenVFS(resource, "rb", FS_GAME);
if (f)
{
size = VFS_GETLEN(f);
VFS_CLOSE(f);
}
}
if (qofs_Error(size))
QueueMessageva (cl, "550 Couldn't read file.\r\n", size);
else
QueueMessageva (cl, "213 %"PRIuQOFS"\r\n", size);
}
else if (!stricmp(mode, "REST"))
{
COM_ParseOut(msg, resource, sizeof(resource));
cl->restartpos = strtoull(resource, NULL, 0);
}
else if (!stricmp(mode, "RETR"))
{
qboolean waspassive = cl->dataislisten;
if (!cl->auth)
{
QueueMessage (cl, "530 Not logged in.\r\n");
continue;
}
if (cl->dataislisten) //accept a connect.
{
int _true = true;
int temp;
struct sockaddr_qstorage adr;
int adrlen = sizeof(adr);
temp = accept(cl->datasock, (struct sockaddr *)&adr, &adrlen);
closesocket(cl->datasock);
cl->datasock = temp;
cl->dataislisten = false;
if (cl->datasock == INVALID_SOCKET)
{
QueueMessageva (cl, "425 Can't accept pasv data connection - %i.\r\n", neterrno());
continue;
}
else
ioctlsocket(cl->datasock, FIONBIO, (u_long *)&_true);
}
if (cl->datasock == INVALID_SOCKET)
{
QueueMessage (cl, "503 Bad sequence of commands.\r\n");
continue;
}
FTP_ReadToAbsFilename(cl, msg, resource, sizeof(resource));
IWebPrintf("%s: Download request for \"ftp://%s@%s/%s\"\n", cl->peername, cl->name, "", resource);
if (FTP_AllowDownLoad(resource, cl))
cl->file = FS_OpenVFS(resource, "rb", FS_GAME);
else
cl->file = IWebGenerateFile(resource, NULL, 0);
if (!cl->file)
{
QueueMessage (cl, "550 File not found.\r\n");
}
else
{ //send data
if (waspassive)
QueueMessage (cl, "150 Opening BINARY mode data connection for file.\r\n");
else
QueueMessage (cl, "125 Opening BINARY mode data connection for file.\r\n");
cl->datadir = 1;
if (cl->restartpos)
VFS_SEEK(cl->file, cl->restartpos);
}
cl->restartpos = 0;
}
else if (!stricmp(mode, "STOR") || !stricmp(mode, "APPE"))
{
if (!cl->auth)
{
QueueMessage (cl, "530 Not logged in.\r\n");
continue;
}
FTP_ReadToAbsFilename(cl, msg, resource, sizeof(resource));
if (!*resource || !FTP_AllowUpLoad(resource, cl))
{
IWebPrintf("%s: Denied upload request for \"ftp://%s@%s/%s\"\n", cl->peername, cl->name, "", resource);
QueueMessage (cl, "550 Permission denied.\r\n");
}
else
{
if (cl->dataislisten) //accept a connect.
{
int _true = true;
int temp;
struct sockaddr_qstorage adr;
int adrlen = sizeof(adr);
temp = accept(cl->datasock, (struct sockaddr *)&adr, &adrlen);
closesocket(cl->datasock);
cl->datasock = temp;
cl->dataislisten = false;
if (cl->datasock == INVALID_SOCKET)
{
QueueMessageva (cl, "425 Can't accept pasv data connection - %i.\r\n", neterrno());
continue;
}
else
ioctlsocket(cl->datasock, FIONBIO, (u_long *)&_true);
}
if (cl->datasock == INVALID_SOCKET)
{
QueueMessage (cl, "502 Bad sequence of commands.\r\n");
continue;
}
IWebPrintf("%s: Upload request for \"ftp://%s@%s/%s\"\n", cl->peername, cl->name, "", resource);
if (cl->restartpos || !stricmp(mode, "APPE")) //write without truncating.
cl->file = FS_OpenVFS(resource, "w+b", FS_GAMEONLY);
else
{
cl->file = FS_OpenVFS(resource, "rb", FS_GAMEONLY);
if (cl->file)
{
VFS_CLOSE(cl->file);
QueueMessage (cl, "550 File already exists.\r\n");
continue;
}
cl->file = FS_OpenVFS(resource, "wb", FS_GAME);
}
if (!cl->file)
{
QueueMessage (cl, "550 Couldn't open output.\r\n");
}
else
{ //send data
QueueMessage (cl, "125 Opening BINARY mode data connection for input.\r\n");
cl->datadir = 2;
if (cl->restartpos)
VFS_SEEK(cl->file, cl->restartpos);
else if (!stricmp(mode, "APPE"))
VFS_SEEK(cl->file, VFS_GETLEN(cl->file));
}
cl->restartpos = 0;
}
}
else if (!stricmp(mode, "RNFR"))
{
FTP_ReadToAbsFilename(cl, msg, cl->renamefrom, sizeof(cl->renamefrom));
QueueMessage (cl, "350 Success.\r\n");
}
else if (!stricmp(mode, "RNTO"))
{
FTP_ReadToAbsFilename(cl, msg, resource, sizeof(resource));
if (!cl->auth)
{
QueueMessage (cl, "530 Not logged in.\r\n");
continue;
}
if (!*cl->renamefrom)
if (!FTP_AllowUpLoad(cl->renamefrom, cl) || !FTP_AllowUpLoad(resource, cl))
{
QueueMessage (cl, "550 Access denied.\r\n");
continue;
}
IWebPrintf("%s: Rename request from \"ftp://%s@/%s\" to \"/%s\"\n", cl->peername, cl->name, cl->renamefrom, resource);
if (FS_Rename(cl->renamefrom, resource, FS_GAMEONLY))
QueueMessage (cl, "250 Success.\r\n");
else
QueueMessage (cl, "550 Requested action not taken.\r\n");
FS_FlushFSHashRemoved(cl->renamefrom);
FS_FlushFSHashWritten(resource);
*cl->renamefrom = 0;
}
else if (!stricmp(mode, "DELE") || !stricmp(mode, "RMD"))
{
FTP_ReadToAbsFilename(cl, msg, resource, sizeof(resource));
if (!cl->auth)
{
QueueMessage (cl, "530 Not logged in.\r\n");
continue;
}
if (!*resource || !FTP_AllowUpLoad(resource, cl))
{
IWebPrintf("%s: Denied delete request for \"ftp://%s@/%s\"\n", cl->peername, cl->name, resource);
QueueMessage (cl, "550 Access denied.\r\n");
continue;
}
IWebPrintf("%s: Delete request for \"ftp://%s@/%s\"\n", cl->peername, cl->name, resource);
if (!stricmp(mode, "RMD"))
{
char path[MAX_OSPATH];
if (FS_NativePath(resource, FS_GAMEONLY, path, sizeof(path)) && Sys_rmdir(path))
QueueMessage (cl, "250 Success.\r\n");
else
QueueMessage (cl, "550 Requested action not taken.\r\n");
}
else
{
if (FS_Remove(resource, FS_GAMEONLY))
QueueMessage (cl, "250 Success.\r\n");
else
QueueMessage (cl, "550 Requested action not taken.\r\n");
}
FS_FlushFSHashRemoved(resource);
}
else if (!stricmp(mode, "STRU"))
{
if (!cl->auth)
{
QueueMessage (cl, "530 Not logged in.\r\n");
continue;
}
msg = COM_ParseOut(msg, resource, sizeof(resource));
if (!strcmp(resource, "F")) //file
{
QueueMessage (cl, "200 recordless structure selected.\r\n");
}
// else if (!strcmp(resource, "R")) //record
// else if (!strcmp(resource, "P")) //page
else
{
QueueMessage (cl, "504 not implemented (it's a simple server).\r\n");
}
}
else if (!stricmp(mode, "NOOP"))
{
QueueMessage (cl, "200 Do something then!\r\n");
}
else if (!stricmp(mode, "QUIT"))
{
QueueMessage (cl, "200 About to quit.\r\n");
return true;
}
else
{
QueueMessage (cl, "502 Command not implemented.\r\n");
}
}
#ifdef MULTITHREAD
if (cl->datadir && !cl->transferthread)
{
cl->datadir|=64;
cl->transferthread = Sys_CreateThread("FTP RECV", FTP_TransferThread, cl, 0, 65536);
}
#endif
return false;
}
#if defined(WEBSVONLY) && defined(_WIN32)
unsigned int WINAPI BlockingClient(FTPclient_t *cl)
{
unsigned long _false = false;
if (ioctlsocket (cl->controlsock, FIONBIO, &_false) == -1)
{
IWebPrintf ("FTP_ServerRun: blocking error: %s\n", strerror(neterrno()));
return 0;
}
cl->blocking = true;
while (!FTP_ServerThinkForConnection(cl))
{
Sleep(10);
}
if (cl->file)
VFS_CLOSE(cl->file);
closesocket(cl->controlsock);
if (cl->datasock)
closesocket(cl->datasock);
IWebFree(cl);
return 0;
}
#endif
iwboolean FTP_ServerRun(iwboolean ftpserverwanted, int port)
{
FTPclient_t *cl, *prevcl;
struct sockaddr_qstorage from;
int fromlen;
SOCKET clientsock;
unsigned long _true = true;
if (!port)
port = 21;
if (ftpserverport != port)
{
ftpserverport = port;
ftpserverwanted = false; //forces it to restart if the port is changed.
}
if (!ftpserverinitied)
{
if (ftpserverwanted)
{
ftpserversocket = FTP_BeginListening(0, port);
if (ftpserversocket == INVALID_SOCKET)
{
ftpserverfailed = true;
IWebPrintf("Unable to establish listening FTP socket\n");
}
else
IWebPrintf("FTP server is running\n");
ftpserverinitied = true;
}
return false;
}
else if (!ftpserverwanted)
{
FTP_ServerShutdown();
return false;
}
prevcl = NULL;
for (cl = FTPclient; cl; cl = cl->next)
{
if (FTP_ServerThinkForConnection(cl))
{
if (cl->file)
VFS_CLOSE(cl->file);
closesocket(cl->controlsock);
if (cl->datasock)
closesocket(cl->datasock);
if (prevcl)
{
prevcl->next = cl->next;
IWebFree(cl);
cl = prevcl;
if (!cl) //kills loop
break;
}
else
{
FTPclient = cl->next;
IWebFree(cl);
cl = FTPclient;
if (!cl) //kills loop
break;
}
}
prevcl = cl;
}
fromlen = sizeof(from);
if (ftpserversocket == INVALID_SOCKET)
clientsock = INVALID_SOCKET;
else
clientsock = accept(ftpserversocket, (struct sockaddr *)&from, &fromlen);
if (clientsock == INVALID_SOCKET)
{
int e = neterrno();
if (e == NET_EWOULDBLOCK)
return false;
if (e == NET_ECONNABORTED || e == NET_ECONNRESET)
{
Con_TPrintf ("Connection lost or aborted\n");
return false;
}
IWebPrintf ("NET_GetPacket: %s\n", strerror(e));
return false;
}
if (ioctlsocket (clientsock, FIONBIO, &_true) == -1)
{
IWebPrintf ("FTP_ServerRun: blocking error: %s\n", strerror(neterrno()));
return false;
}
cl = IWebMalloc(sizeof(FTPclient_t));
if (!cl) //iwebmalloc is allowed to fail.
{
char *msg = "421 Not enough memory is allocated.\r\n"; //don't be totally anti social
send(clientsock, msg, strlen(msg), 0);
closesocket(clientsock); //try to forget this ever happend
return true;
}
NET_SockadrToString(cl->peername, sizeof(cl->peername), &from);
IWebPrintf("%s: New FTP connection\n", cl->peername);
//RFC1700
if (((struct sockaddr *)&from)->sa_family == AF_INET)
cl->controlaf = 1;
else if (((struct sockaddr *)&from)->sa_family == AF_INET6)
cl->controlaf = 2;
#ifdef USEIPX
else if (((struct sockaddr *)&from)->sa_family == AF_IPX)
cl->controlaf = 11;
#endif
else
cl->controlaf = 0;
cl->controlsock = clientsock;
cl->datasock = INVALID_SOCKET;
cl->next = FTPclient;
cl->blocking = false;
strcpy(cl->path, "");
QueueMessage(cl, "220-" FULLENGINENAME " FTP Server.\r\n220 Welcomes all new users.\r\n");
#if defined(WEBSVONLY) && defined(_WIN32)
if (!CreateThread(NULL, 128, BlockingClient, cl, 0, NULL))
#endif
FTPclient = cl;
return true;
}
#endif