9ae7e2621d
Lots of changes. CSQC should be functional, but is still tied to debug builds. It WILL have some bugs still, hopefully I'll be able to clean them up better if people test it a bit. Precompiled headers are working properly now. Compile times are now much quicker in msvc. This takes most of the files this commit. Restructured how client commands work. They're buffered outside the network message, some multithreaded code is in. It needs a bit of testing before it's active. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@885 fc73d0e0-1445-4013-8a0c-d673dee63da5
644 lines
14 KiB
C
644 lines
14 KiB
C
#include "quakedef.h"
|
|
|
|
#ifdef EMAILSERVER
|
|
|
|
#include "winquake.h"
|
|
|
|
//FIXME: the DELE command's effects arn't properly checked.
|
|
//FIXME: no UIDL command
|
|
|
|
//FIXME: remove sequential naming.
|
|
|
|
char *MD5_GetPop3APOPString(char *timestamp, char *secrit);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "hash.h"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
#define EWOULDBLOCK WSAEWOULDBLOCK
|
|
#define EMSGSIZE WSAEMSGSIZE
|
|
#define ECONNRESET WSAECONNRESET
|
|
#define ECONNABORTED WSAECONNABORTED
|
|
#define ECONNREFUSED WSAECONNREFUSED
|
|
#define EADDRNOTAVAIL WSAEADDRNOTAVAIL
|
|
|
|
|
|
#define qerrno WSAGetLastError()
|
|
#else
|
|
#define qerrno errno
|
|
|
|
#define MSG_PARTIAL 0
|
|
#include <stdio.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <netdb.h>
|
|
#include <sys/param.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/uio.h>
|
|
#include <arpa/inet.h>
|
|
#include <errno.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#define closesocket close
|
|
#define ioctlsocket ioctl
|
|
#endif
|
|
|
|
|
|
#define POP3_PORT 110
|
|
#define POP3_TIMEOUT 30
|
|
|
|
static qboolean pop3active;
|
|
static int pop3serversocket;
|
|
|
|
typedef struct {
|
|
char filename[MAX_QPATH];
|
|
int size;
|
|
qboolean deleted;
|
|
bucket_t bucket;
|
|
} pop3message_t;
|
|
|
|
typedef struct svpop3client_s {
|
|
struct svpop3client_s *next;
|
|
|
|
int socket;
|
|
|
|
float timeout;
|
|
|
|
char *messagelump;
|
|
int messagelumppos;
|
|
int messagelumplen;
|
|
qboolean messagelumphitbody;
|
|
int messagelumplines; //lines to send past header
|
|
|
|
int nummessages;
|
|
int totalsize;
|
|
|
|
char greeting[64];
|
|
char username[64];
|
|
qboolean loggedin;
|
|
|
|
char inmessagebuffer[1024];
|
|
int inmessagelen;
|
|
char outmessagebuffer[1024];
|
|
int outmessagelen;
|
|
qboolean dropwhensent;
|
|
|
|
#define NUMBUCKETS 64
|
|
hashtable_t emails;
|
|
bucket_t *bucketpointer[NUMBUCKETS];
|
|
} svpop3client_t;
|
|
static svpop3client_t *svpop3client;
|
|
|
|
|
|
|
|
|
|
|
|
static void POP3_ServerInit(void)
|
|
{
|
|
struct sockaddr_in address;
|
|
unsigned long _true = true;
|
|
int port = POP3_PORT;
|
|
|
|
if ((pop3serversocket = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
|
|
{
|
|
Sys_Error ("FTP_TCP_OpenSocket: socket:", strerror(qerrno));
|
|
}
|
|
|
|
if (ioctlsocket (pop3serversocket, FIONBIO, &_true) == -1)
|
|
{
|
|
Sys_Error ("FTP_TCP_OpenSocket: ioctl FIONBIO:", strerror(qerrno));
|
|
}
|
|
|
|
address.sin_family = AF_INET;
|
|
address.sin_addr.s_addr = INADDR_ANY;
|
|
|
|
if (port == PORT_ANY)
|
|
address.sin_port = 0;
|
|
else
|
|
address.sin_port = htons((short)port);
|
|
|
|
if( bind (pop3serversocket, (void *)&address, sizeof(address)) == -1)
|
|
{
|
|
closesocket(pop3serversocket);
|
|
return;
|
|
}
|
|
|
|
listen(pop3serversocket, 3);
|
|
|
|
pop3active = true;
|
|
|
|
|
|
IWebPrintf("POP3 server is running\n");
|
|
return;
|
|
}
|
|
|
|
static void POP3_ServerShutdown(void)
|
|
{
|
|
closesocket(pop3serversocket);
|
|
pop3active = false;
|
|
}
|
|
|
|
static void SV_POP3_QueueMessage(svpop3client_t *cl, char *msg)
|
|
{
|
|
int len = strlen(msg);
|
|
if (len + cl->outmessagelen > sizeof(cl->outmessagebuffer)-1)
|
|
len = sizeof(cl->outmessagebuffer)-1 - cl->outmessagelen;
|
|
Q_strncpyz(cl->outmessagebuffer+cl->outmessagelen, msg, len+1);
|
|
cl->outmessagelen += len;
|
|
}
|
|
|
|
static void POP3_NewConnection(int socket)
|
|
{
|
|
unsigned long _true = true;
|
|
svpop3client_t *newcl;
|
|
newcl = IWebMalloc(sizeof(svpop3client_t));
|
|
if (!newcl) //bother
|
|
{
|
|
closesocket(socket);
|
|
return;
|
|
}
|
|
if (ioctlsocket (socket, FIONBIO, &_true) == -1)
|
|
{
|
|
closesocket(socket);
|
|
return;
|
|
}
|
|
memset(newcl, 0, sizeof(svpop3client_t));
|
|
|
|
newcl->socket = socket;
|
|
newcl->next = svpop3client;
|
|
svpop3client = newcl;
|
|
|
|
newcl->timeout = realtime + POP3_TIMEOUT;
|
|
|
|
*newcl->outmessagebuffer = '\0';
|
|
|
|
sprintf(newcl->greeting, "<%i.%i.%i.%i.%i.%i.%i>", rand(), rand(), rand(), rand(), rand(), rand(), rand());
|
|
// _true = strlen(newcl->greeting);
|
|
|
|
printf("newclient\n");
|
|
|
|
SV_POP3_QueueMessage(newcl, va("+OK %s\r\n", newcl->greeting));
|
|
}
|
|
|
|
static int SV_POP3_AddMessage(char *filename, int flags, void *incl)
|
|
{
|
|
FILE *f;
|
|
svpop3client_t *cl = incl;
|
|
pop3message_t *msg;
|
|
|
|
f = fopen(filename, "rb");
|
|
if (!f)
|
|
return true; //shouldn't happen
|
|
|
|
msg = IWebMalloc(sizeof(pop3message_t));
|
|
if (!msg)
|
|
{
|
|
fclose(f);
|
|
return false;
|
|
}
|
|
|
|
Q_strncpyz(msg->filename, filename, sizeof(msg->filename));
|
|
msg->deleted = false;
|
|
|
|
fseek(f, 0, SEEK_END);
|
|
msg->size = ftell(f);
|
|
fclose(f);
|
|
|
|
cl->totalsize+=msg->size;
|
|
cl->nummessages++;
|
|
|
|
Hash_Add(&cl->emails, va("%i", ++cl->nummessages), msg, &msg->bucket);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void SV_POP3_CountMessages(svpop3client_t *cl)
|
|
{
|
|
Hash_InitTable(&cl->emails, NUMBUCKETS, cl->bucketpointer);
|
|
cl->totalsize=0;
|
|
Sys_EnumerateFiles(".", va("emails/%s/*.eml", cl->username), SV_POP3_AddMessage, cl);
|
|
}
|
|
|
|
static pop3message_t *SV_POP3_GetMessage(svpop3client_t *cl, int num)
|
|
{
|
|
pop3message_t *msg;
|
|
|
|
msg = IWebMalloc(sizeof(pop3message_t));
|
|
if (!msg)
|
|
return NULL;
|
|
|
|
if (msg->deleted)
|
|
return NULL;
|
|
|
|
msg = Hash_Get(&cl->emails, va("%i", num));
|
|
|
|
return msg;
|
|
}
|
|
|
|
static void SV_POP3_CleanUp(svpop3client_t *cl, qboolean dodelete) //closes the messages list created by SV_POP3_CountMessages
|
|
{
|
|
pop3message_t *msg;
|
|
int mn;
|
|
for (mn = 1; mn <= cl->nummessages; mn++)
|
|
{
|
|
msg = SV_POP3_GetMessage(cl, mn);
|
|
if (!msg)
|
|
continue;
|
|
|
|
if (dodelete && msg->deleted)
|
|
{
|
|
remove(msg->filename);
|
|
}
|
|
|
|
Hash_Remove(&cl->emails, va("%i", mn));
|
|
BZ_Free(msg);
|
|
}
|
|
}
|
|
|
|
static qboolean SV_POP3_ReadMessage(svpop3client_t *cl, int index)
|
|
{
|
|
FILE *f;
|
|
pop3message_t *msg;
|
|
|
|
msg = SV_POP3_GetMessage(cl, index);
|
|
if (!msg)
|
|
return false;
|
|
if (msg->deleted)
|
|
return false;
|
|
|
|
f = fopen(msg->filename, "rb");
|
|
if (!f)
|
|
return false;
|
|
fseek(f, 0, SEEK_END);
|
|
cl->messagelumplen = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
cl->messagelump = IWebMalloc(cl->messagelumplen+3);
|
|
fread(cl->messagelump, 1, cl->messagelumplen, f);
|
|
cl->messagelump[cl->messagelumplen++] = '\r';
|
|
cl->messagelump[cl->messagelumplen++] = '\n';
|
|
fclose(f);
|
|
|
|
cl->messagelumplen = strlen(cl->messagelump);
|
|
cl->messagelumppos = 0;
|
|
cl->messagelumphitbody = false;
|
|
cl->messagelumplines = 0x7fffffff;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void SV_POP3_BuildListing(svpop3client_t *cl, qboolean isuidl)
|
|
{
|
|
pop3message_t *msg;
|
|
int mn;
|
|
char *listing;
|
|
|
|
listing = cl->messagelump = IWebMalloc(cl->nummessages*64+3);
|
|
|
|
for (mn = 1; mn <= cl->nummessages; mn++)
|
|
{
|
|
msg = SV_POP3_GetMessage(cl, mn);
|
|
if (!msg || msg->deleted)
|
|
continue;
|
|
|
|
if (isuidl)
|
|
sprintf(listing, "%i %s,S=%i\r\n", mn, msg->filename, msg->size);
|
|
else
|
|
sprintf(listing, "%i %i\r\n", mn, msg->size);
|
|
listing += strlen(listing);
|
|
}
|
|
|
|
cl->messagelumplen = listing - cl->messagelump;
|
|
}
|
|
|
|
static qboolean SV_POP3_RunClient(svpop3client_t *cl) //true means client should be dropped
|
|
{
|
|
int read;
|
|
char *nl;
|
|
char *token;
|
|
int blankline;
|
|
|
|
if (cl->messagelump)
|
|
{
|
|
blankline=false;
|
|
while (cl->outmessagelen < sizeof(cl->outmessagebuffer)-100)
|
|
{
|
|
if (cl->messagelumppos >= cl->messagelumplen)
|
|
break;
|
|
|
|
if (cl->messagelump[cl->messagelumppos] == '.') //double up all '.'s at start of lines
|
|
cl->outmessagebuffer[cl->outmessagelen++] = '.';
|
|
|
|
blankline = true;
|
|
for(;;)
|
|
{
|
|
if (cl->messagelumppos >= cl->messagelumplen)
|
|
break;
|
|
if (cl->messagelump[cl->messagelumppos] > ' ')
|
|
blankline = false;
|
|
cl->outmessagebuffer[cl->outmessagelen++] = cl->messagelump[cl->messagelumppos];
|
|
if (cl->messagelump[cl->messagelumppos++] == '\n')
|
|
break;
|
|
}
|
|
if (blankline)
|
|
cl->messagelumphitbody = true;
|
|
if (cl->messagelumphitbody)
|
|
{
|
|
if (cl->messagelumplines--<=0)
|
|
cl->messagelumppos = cl->messagelumplen; //easy way to terminate.
|
|
}
|
|
}
|
|
if (cl->messagelumppos >= cl->messagelumplen)
|
|
{ //we've sent the entire buffer now.
|
|
cl->outmessagebuffer[cl->outmessagelen++] = '.';
|
|
cl->outmessagebuffer[cl->outmessagelen++] = '\r';
|
|
cl->outmessagebuffer[cl->outmessagelen++] = '\n';
|
|
|
|
BZ_Free(cl->messagelump);
|
|
cl->messagelump = NULL;
|
|
}
|
|
cl->outmessagebuffer[cl->outmessagelen] = '\0';
|
|
printf("%s\n", cl->outmessagebuffer);
|
|
}
|
|
if (cl->outmessagelen)
|
|
{
|
|
read = send(cl->socket, cl->outmessagebuffer, cl->outmessagelen, MSG_PARTIAL);
|
|
if (read < 0)
|
|
read = 0;
|
|
memmove(cl->outmessagebuffer, cl->outmessagebuffer + read, cl->outmessagelen - read);
|
|
cl->outmessagelen -= read;
|
|
cl->outmessagebuffer[cl->outmessagelen] = '\0';
|
|
|
|
if (cl->dropwhensent && !cl->outmessagelen)
|
|
return true;
|
|
}
|
|
|
|
read = recv(cl->socket, cl->inmessagebuffer+cl->inmessagelen, sizeof(cl->inmessagebuffer)-1-cl->inmessagelen, MSG_PARTIAL);
|
|
|
|
if (read == -1)
|
|
{
|
|
if (qerrno != EWOULDBLOCK) //blocking is the only way to keep the connection on fail.
|
|
return true;
|
|
|
|
if (!*cl->inmessagebuffer)
|
|
return false;
|
|
//nonblocking allows us to get multiple commands from one packet.
|
|
}
|
|
else if (read == 0) //don't quite know why this happens.
|
|
return true; //believed to be an indication that the other end has disconnected.
|
|
else
|
|
{
|
|
cl->timeout = realtime + POP3_TIMEOUT;
|
|
|
|
cl->inmessagelen += read;
|
|
if (cl->inmessagelen >= sizeof(cl->inmessagebuffer)-1) //happens if we fill the buffer with no hope of empting it.
|
|
return true;
|
|
cl->inmessagebuffer[cl->inmessagelen] = '\0';
|
|
}
|
|
nl = strchr(cl->inmessagebuffer, '\n');
|
|
if (nl)
|
|
{
|
|
*nl = '\0';
|
|
//Con_Printf("%s\n", cl->inmessagebuffer);
|
|
read = nl - cl->inmessagebuffer + 1;
|
|
|
|
token = COM_ParseToken(cl->inmessagebuffer);
|
|
|
|
//auth mechanism 1
|
|
if (!strcmp(com_token, "USER"))
|
|
{
|
|
token = COM_ParseToken(token);
|
|
if (*com_token)
|
|
{
|
|
Q_strncpyz(cl->username, com_token, sizeof(cl->username));
|
|
SV_POP3_QueueMessage(cl, "+OK User name accepted, password please\r\n");
|
|
|
|
SV_POP3_CleanUp(cl, true);
|
|
cl->loggedin = false;
|
|
}
|
|
else
|
|
SV_POP3_QueueMessage(cl, "-ERR no username was specified\r\n");
|
|
}
|
|
else if (!strcmp(com_token, "PASS"))
|
|
{
|
|
#ifndef CLIENTONLY
|
|
int id;
|
|
extern cvar_t rank_filename;
|
|
|
|
token = COM_ParseToken(token);
|
|
id = Rank_GetPlayerID(cl->username, atoi(com_token), false);
|
|
if (!id && *rank_filename.string)
|
|
{
|
|
SV_POP3_QueueMessage(cl, "-ERR User or Password not valid\r\n");
|
|
|
|
SV_POP3_CleanUp(cl, true);
|
|
cl->loggedin = false;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
SV_POP3_QueueMessage(cl, "+OK Logged in\r\n");
|
|
cl->loggedin = true;
|
|
SV_POP3_CountMessages(cl);
|
|
}
|
|
}
|
|
|
|
//auth2
|
|
else if (!strcmp(com_token, "APOP"))
|
|
{
|
|
int id;
|
|
int pass;
|
|
extern cvar_t rank_filename;
|
|
|
|
token = COM_ParseToken(token);
|
|
if (*com_token)
|
|
{
|
|
Q_strncpyz(cl->username, com_token, sizeof(cl->username));
|
|
|
|
#ifndef CLIENTONLY
|
|
token = COM_ParseToken(token);
|
|
pass = Rank_GetPass(cl->username);
|
|
id = Rank_GetPlayerID(cl->username, pass, false);
|
|
if ((!id && *rank_filename.string) || strcmp(MD5_GetPop3APOPString(cl->greeting, va("%i", pass)), com_token))
|
|
{
|
|
SV_POP3_QueueMessage(cl, "-ERR User or Password not valid\r\n");
|
|
|
|
SV_POP3_CleanUp(cl, true);
|
|
cl->loggedin = false;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
SV_POP3_QueueMessage(cl, "+OK Logged in\r\n");
|
|
cl->loggedin = true;
|
|
SV_POP3_CountMessages(cl);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//now they need to have been logged in properly.
|
|
else if (!cl->loggedin)
|
|
SV_POP3_QueueMessage(cl, "-ERR You didn't log in properly\r\n");
|
|
else if (!strcmp(com_token, "STAT"))
|
|
{
|
|
char text[64];
|
|
sprintf(text, "+OK %i %i\r\n", cl->nummessages, cl->totalsize);
|
|
SV_POP3_QueueMessage(cl, text);
|
|
}
|
|
else if (!strcmp(com_token, "LIST"))
|
|
{
|
|
SV_POP3_QueueMessage(cl, "+OK EMail listing follows:\r\n");
|
|
SV_POP3_BuildListing(cl, false);
|
|
}
|
|
else if (!strcmp(com_token, "DELE"))
|
|
{
|
|
pop3message_t *msg;
|
|
int mnum;
|
|
token = COM_ParseToken(token);
|
|
mnum = atoi(com_token);
|
|
|
|
msg = SV_POP3_GetMessage(cl, mnum);
|
|
if (!msg)
|
|
SV_POP3_QueueMessage(cl, "-ERR message index out of range\r\n");
|
|
else if (msg->deleted)
|
|
SV_POP3_QueueMessage(cl, "-ERR message already deleted\r\n");
|
|
else
|
|
{
|
|
msg->deleted = true;
|
|
SV_POP3_QueueMessage(cl, "+OK Message marked for deleted\r\n");
|
|
}
|
|
}
|
|
else if (!strcmp(com_token, "TOP"))
|
|
{
|
|
token = COM_ParseToken(token);
|
|
if (SV_POP3_ReadMessage(cl, atoi(com_token)))
|
|
{
|
|
token = COM_ParseToken(token);
|
|
SV_POP3_QueueMessage(cl, "+OK message contents follow:\n");
|
|
cl->messagelumplines = atoi(com_token);
|
|
}
|
|
else
|
|
SV_POP3_QueueMessage(cl, "-ERR Message index wasn't valid\n");
|
|
}
|
|
else if (!strcmp(com_token, "RETR"))
|
|
{
|
|
token = COM_ParseToken(token);
|
|
if (SV_POP3_ReadMessage(cl, atoi(com_token)))
|
|
SV_POP3_QueueMessage(cl, "+OK message contents follow:\n");
|
|
else
|
|
SV_POP3_QueueMessage(cl, "-ERR Message index wasn't valid\n");
|
|
}
|
|
else if (!strcmp(com_token, "UIDL"))
|
|
{
|
|
SV_POP3_QueueMessage(cl, "+OK I hope someone likes you\r\n");
|
|
SV_POP3_BuildListing(cl, true);
|
|
}
|
|
else if (!strcmp(com_token, "QUIT"))
|
|
{
|
|
SV_POP3_CleanUp(cl, true);
|
|
|
|
SV_POP3_QueueMessage(cl, "+OK I hope someone likes you\r\n");
|
|
cl->dropwhensent = true;
|
|
}
|
|
else
|
|
SV_POP3_QueueMessage(cl, "-ERR Unrecognised command\r\n");
|
|
//printf("%s\n", cl->outmessagebuffer);
|
|
|
|
memmove(cl->inmessagebuffer, cl->inmessagebuffer + read, cl->inmessagelen - read);
|
|
cl->inmessagelen -= read;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void SV_POP3_RunClients(void)
|
|
{
|
|
svpop3client_t *cl, *prev;
|
|
|
|
cl = svpop3client;
|
|
prev = NULL;
|
|
while(cl)
|
|
{
|
|
if (cl->timeout < realtime || SV_POP3_RunClient(cl))
|
|
{
|
|
printf("drop client\n");
|
|
closesocket(cl->socket);
|
|
if (prev)
|
|
prev->next = cl->next;
|
|
else
|
|
svpop3client = cl->next;
|
|
|
|
if (cl->messagelump)
|
|
IWebFree(cl->messagelump);
|
|
SV_POP3_CleanUp(cl, false);
|
|
|
|
IWebFree(cl);
|
|
if (prev)
|
|
cl = prev->next;
|
|
else
|
|
cl = svpop3client;
|
|
continue;
|
|
}
|
|
prev = cl;
|
|
cl = cl->next;
|
|
}
|
|
}
|
|
|
|
qboolean SV_POP3(qboolean activewanted)
|
|
{
|
|
struct sockaddr from;
|
|
int fromlen;
|
|
int clientsock;
|
|
|
|
if (!pop3active)
|
|
{
|
|
if (activewanted)
|
|
POP3_ServerInit();
|
|
else
|
|
return false;
|
|
}
|
|
else if (!activewanted)
|
|
{
|
|
POP3_ServerShutdown();
|
|
return false;
|
|
}
|
|
|
|
fromlen = sizeof(from);
|
|
clientsock = accept(pop3serversocket, (struct sockaddr *)&from, &fromlen);
|
|
|
|
if (clientsock == -1)
|
|
{
|
|
if (qerrno == ECONNABORTED || qerrno == ECONNRESET)
|
|
{
|
|
Con_TPrintf (TL_CONNECTIONLOSTORABORTED);
|
|
return false;
|
|
}
|
|
else if (qerrno != EWOULDBLOCK)
|
|
{
|
|
Con_TPrintf (TL_NETGETPACKETERROR, strerror(qerrno));
|
|
return false;
|
|
}
|
|
}
|
|
else //we got a new client. yay.
|
|
POP3_NewConnection(clientsock);
|
|
|
|
SV_POP3_RunClients();
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif
|