From b3fb01bfedcdc38a8c354eb73dbe34105eee1fcf Mon Sep 17 00:00:00 2001 From: Spoike Date: Mon, 23 Aug 2004 00:10:47 +0000 Subject: [PATCH] Email notification and email servers. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@14 fc73d0e0-1445-4013-8a0c-d673dee63da5 --- engine/email/imapnoti.c | 433 ++++++++++++++++++++++++++ engine/email/pop3noti.c | 549 ++++++++++++++++++++++++++++++++ engine/email/sv_pop3.c | 670 ++++++++++++++++++++++++++++++++++++++++ engine/email/sv_smtp.c | 449 +++++++++++++++++++++++++++ 4 files changed, 2101 insertions(+) create mode 100644 engine/email/imapnoti.c create mode 100644 engine/email/pop3noti.c create mode 100644 engine/email/sv_pop3.c create mode 100644 engine/email/sv_smtp.c diff --git a/engine/email/imapnoti.c b/engine/email/imapnoti.c new file mode 100644 index 000000000..9c8c08225 --- /dev/null +++ b/engine/email/imapnoti.c @@ -0,0 +1,433 @@ +#include "bothdefs.h" + +#ifdef EMAILCLIENT + +//code to sit on an imap server and check for new emails every now and then. + +#include "quakedef.h" +#include "winquake.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 + #include + #include + #include + #include + #include + #include + #include + #include + + #include + + #define closesocket close + #define ioctlsocket ioctl +#endif + + + +//exported. +void IMAP_CreateConnection(char *servername, char *username, char *password); +cvar_t imap_checkfrequency = {"imap_checkfrequency", "60"}; //once a min +void IMAP_Think (void); +//end export list. + + + + +#define IMAP_PORT 143 + + +typedef struct imap_con_s { + char server[128]; + char username[128]; + char password[128]; + + float lastnoop; + + //these are used so we can fail a send. + //or recieve only part of an input. + //FIXME: make dynamically sizable, as it could drop if the send is too small (That's okay. + // but if the read is bigger than one command we suddenly fail entirly. + int sendlen; + int sendbuffersize; + char *sendbuffer; + int readlen; + int readbuffersize; + char *readbuffer; + + qboolean drop; + + int socket; + + enum { + IMAP_WAITINGFORINITIALRESPONCE, + IMAP_AUTHING, + IMAP_AUTHED, + IMAP_INBOX + } state; + + struct imap_con_s *next; +} imap_con_t; + +static imap_con_t *imapsv; + +void IMAP_CreateConnection(char *addy, char *username, char *password) +{ + unsigned long _true = true; + struct sockaddr_qstorage from; + imap_con_t *con; + + for (con = imapsv; con; con = con->next) + { + if (!strcmp(con->server, addy)) + { + Con_Printf("Already connected to that imap server\n"); + return; + } + } + + con = IWebMalloc(sizeof(imap_con_t)); + + + + if ((con->socket = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) + { + Sys_Error ("IMAP_CreateConnection: socket: %s\n", strerror(qerrno)); + } + + + {//quake routines using dns and stuff (Really, I wanna keep quake and imap fairly seperate) + netadr_t qaddy; + NET_StringToAdr (addy, &qaddy); + if (!qaddy.port) + qaddy.port = htons(IMAP_PORT); + NetadrToSockadr(&qaddy, &from); + } + + //not yet blocking. + if (connect(con->socket, (struct sockaddr *)&from, sizeof(from)) == -1) + { + IWebWarnPrintf ("IMAP_CreateConnection: connect: %i %s\n", qerrno, strerror(qerrno)); + closesocket(con->socket); + IWebFree(con); + return; + } + + if (ioctlsocket (con->socket, FIONBIO, &_true) == -1) //now make it non blocking. + { + Sys_Error ("IMAP_CreateConnection: ioctl FIONBIO: %s\n", strerror(qerrno)); + } + + Q_strncpyz(con->server, addy, sizeof(con->server)); + Q_strncpyz(con->username, username, sizeof(con->username)); + Q_strncpyz(con->password, password, sizeof(con->password)); + + con->next = imapsv; + imapsv = con; +} + +static void IMAP_EmitCommand(imap_con_t *imap, char *text) +{ + int newlen; + char rt[64]; + sprintf(rt, "* "); //now this is lame. Quite possibly unreliable... + //makes a few things easier though + + newlen = imap->sendlen + strlen(text) + strlen(rt) + 2; + + if (newlen >= imap->sendbuffersize || !imap->sendbuffer) //pre-length check. + { + char *newbuf; + imap->sendbuffersize = newlen*2; + newbuf = IWebMalloc(imap->sendbuffersize); + if (!newbuf) + { + Con_Printf("Memory is low\n"); + imap->drop = true; //failed. + return; + } + if (imap->sendbuffer) + { + memcpy(newbuf, imap->sendbuffer, imap->sendlen); + IWebFree(imap->sendbuffer); + } + imap->sendbuffer = newbuf; + } + + imap->sendlen = newlen; + + strncat(imap->sendbuffer, rt, imap->sendbuffersize-1); + strncat(imap->sendbuffer, text, imap->sendbuffersize-1); + strncat(imap->sendbuffer, "\r\n", imap->sendbuffersize-1); +} + +static char *IMAP_AddressStructure(char *msg, char *out, int outsize) +{ + char name[256]; + char mailbox[64]; + char hostname[128]; + int indents=0; + while(*msg == ' ') + msg++; + while(*msg == '(') //do it like this, we can get 2... I'm not sure if that's always true.. + { + msg++; + indents++; + } + + msg = COM_Parse(msg); //name + Q_strncpyz(name, com_token, sizeof(name)); + msg = COM_Parse(msg); //smtp route (ignored normally) + msg = COM_Parse(msg); //mailbox + Q_strncpyz(mailbox, com_token, sizeof(mailbox)); + msg = COM_Parse(msg); //hostname + Q_strncpyz(hostname, com_token, sizeof(hostname)); + + while(indents && *msg == ')') + msg++; + + if (out) + { + if (!strcmp(name, "NIL")) + { + Q_strncpyz(out, mailbox, outsize-1); + strncat(out, "@", outsize-1); + strncat(out, hostname, outsize-1); + } + else + { + Q_strncpyz(out, name, outsize-1); + + strncat(out, " <", outsize-1); + strncat(out, mailbox, outsize-1); + strncat(out, "@", outsize-1); + strncat(out, hostname, outsize-1); + strncat(out, ">", outsize-1); + } + } + + return msg; +} + +static qboolean IMAP_ThinkCon(imap_con_t *imap) //false means drop the connection. +{ + char *ending; + int len; + + //get the buffer, stick it in our read holder + if (imap->readlen+32 >= imap->readbuffersize || !imap->readbuffer) + { + len = imap->readbuffersize; + if (!imap->readbuffer) + imap->readbuffersize = 256; + else + imap->readbuffersize*=2; + + ending = IWebMalloc(imap->readbuffersize); + if (!ending) + { + Con_Printf("Memory is low\n"); + return false; + } + if (imap->readbuffer) + { + memcpy(ending, imap->readbuffer, len); + IWebFree(imap->readbuffer); + } + imap->readbuffer = ending; + } + + len = recv(imap->socket, imap->readbuffer+imap->readlen, imap->readbuffersize-imap->readlen-1, 0); + if (len>0) + { + imap->readlen+=len; + imap->readbuffer[imap->readlen] = '\0'; + } + + if (imap->readlen>0) + { + ending = strstr(imap->readbuffer, "\r\n"); + + if (ending) //pollable text. + { + *ending = '\0'; +// Con_Printf("%s\n", imap->readbuffer); + + ending+=2; + if (imap->state == IMAP_WAITINGFORINITIALRESPONCE) + { + //can be one of two things. + if (!strncmp(imap->readbuffer, "* OK", 4)) + { + IMAP_EmitCommand(imap, va("LOGIN %s %s", imap->username, imap->password)); + imap->state = IMAP_AUTHING; + } + else if (!strncmp(imap->readbuffer, "* PREAUTH", 9)) + { + Con_Printf("Logged on to %s\n", imap->server); + IMAP_EmitCommand(imap, "SELECT INBOX"); + imap->state = IMAP_AUTHED; + imap->lastnoop = Sys_DoubleTime(); + } + else + { + Con_Printf("Unexpected response from IMAP server\n"); + return false; + } + } + else if (imap->state == IMAP_AUTHING) + { + if (!strncmp(imap->readbuffer, "* OK", 4)) + { + Con_Printf("Logged on to %s\n", imap->server); + IMAP_EmitCommand(imap, "SELECT INBOX"); + imap->state = IMAP_AUTHED; + imap->lastnoop = Sys_DoubleTime(); + } + else + { + Con_Printf("Unexpected response from IMAP server\n"); + return false; + } + } + else if (imap->state == IMAP_AUTHED) + { + char *num; + num = imap->readbuffer; + if (!strncmp(imap->readbuffer, "* SEARCH ", 8)) //we only ever search for recent messages. So we fetch them and get sender and subject. + { + char *s; + s = imap->readbuffer+8; + num = NULL; + while(*s) + { + s++; + num = s; + while (*s >= '0' && *s <= '9') + s++; + + IMAP_EmitCommand(imap, va("FETCH %i ENVELOPE", atoi(num))); //envelope so that it's all one line. + } + } + else if (imap->readbuffer[0] == '*' && imap->readbuffer[1] == ' ') + { + num = imap->readbuffer+2; + while(*num >= '0' && *num <= '9') + { + num++; + } + if (!strcmp(num, " RECENT")) + { + if (atoi(imap->readbuffer+2) > 0) + { + IMAP_EmitCommand(imap, "SEARCH RECENT"); + } + } + else if (!strncmp(num, " FETCH (ENVELOPE (", 18)) + { + char from[256]; + char subject[256]; + + num += 18; + + num = COM_Parse(num); +// Con_Printf("Date/Time: %s\n", com_token); + + num = COM_Parse(num); + Q_strncpyz(subject, com_token, sizeof(subject)); + + num = IMAP_AddressStructure(num, from, sizeof(from)); + + + if ((rand() & 3) == 3) + { + if (rand()) + Con_Printf("\n^2New spam has arrived\n"); + else + Con_Printf("\n^2You have new spam\n"); + } + else if (rand()&1) + Con_Printf("\n^2New mail has arrived\n"); + else + Con_Printf("\n^2You have new mail\n"); + + Con_Printf("Subject: %s\n", subject); + Con_Printf("From: %s\n", from); + + SCR_CenterPrint(0, va("NEW MAIL HAS ARRIVED\n\nTo: %s@%s\nFrom: %s\nSubject: %s", imap->username, imap->server, from, subject)); + + //throw the rest away. + } + } + } + else + { + Con_Printf("Bad client state\n"); + return false; + } + imap->readlen -= ending - imap->readbuffer; + memmove(imap->readbuffer, ending, strlen(ending)+1); + } + } + if (imap->drop) + return false; + + if (imap->state == IMAP_AUTHED) + { + if (imap->lastnoop + imap_checkfrequency.value < Sys_DoubleTime()) + { //we need to keep the connection reasonably active + + IMAP_EmitCommand(imap, "SELECT INBOX"); //this causes the recent flags to be reset. This is the only way I found. + imap->lastnoop = Sys_DoubleTime(); + } + } + + if (imap->sendlen) + { + len = send(imap->socket, imap->sendbuffer, imap->sendlen, 0); + if (len>0) + { + imap->sendlen-=len; + memmove(imap->sendbuffer, imap->sendbuffer+len, imap->sendlen+1); + } + } + return true; +} + +void IMAP_Think (void) +{ + imap_con_t *prev = NULL; + imap_con_t *imap; + + for (imap = imapsv; imap; imap = imap->next) + { + if (imap->drop || !IMAP_ThinkCon(imap)) + { + if (!prev) + imapsv = imap->next; + else + prev->next = imap->next; + closesocket(imap->socket); + BZ_Free(imap); + if (!prev) + break; + } + + prev = imap; + } +} + +#endif diff --git a/engine/email/pop3noti.c b/engine/email/pop3noti.c new file mode 100644 index 000000000..6d4789b08 --- /dev/null +++ b/engine/email/pop3noti.c @@ -0,0 +1,549 @@ +#include "bothdefs.h" + +#ifdef EMAILCLIENT + +//the idea is to send a UIDL request, and compare against the previous list. +//this list will be stored on disk on quit. + +//be aware that we cannot stay connected. POP3 mailboxs are not refreshable without disconnecting. +//so we have a special state. + + +#include "quakedef.h" +#include "winquake.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 + #include + #include + #include + #include + #include + #include + #include + #include + + #include + + #define closesocket close + #define ioctlsocket ioctl +#endif + + +char *MD5_GetPop3APOPString(char *timestamp, char *secrit); + + +#define HASH_FILESYSTEM +#ifdef HASH_FILESYSTEM +//#include "hash.h" + +#define Hash_BytesForBuckets(b) (sizeof(bucket_t)*b) + +#define STRCMP(s1,s2) (((*s1)!=(*s2)) || strcmp(s1+1,s2+1)) //saves about 2-6 out of 120 - expansion of idea from fastqcc +typedef struct bucket_s { + void *data; + char *keystring; + struct bucket_s *next; +} bucket_t; +typedef struct hashtable_s { + int numbuckets; + bucket_t **bucket; +} hashtable_t; + +void Hash_InitTable(hashtable_t *table, int numbucks, void *mem); //mem must be 0 filled. (memset(mem, 0, size)) +int Hash_Key(char *name, int modulus); +void *Hash_Get(hashtable_t *table, char *name); +void *Hash_GetKey(hashtable_t *table, int key); +void *Hash_GetNext(hashtable_t *table, char *name, void *old); +void *Hash_Add(hashtable_t *table, char *name, void *data); +void *Hash_Add2(hashtable_t *table, char *name, void *data, bucket_t *buck); +void *Hash_AddKey(hashtable_t *table, int key, void *data); +void Hash_Remove(hashtable_t *table, char *name); +#endif + + + + + + +//exported. +void POP3_CreateConnection(char *servername, char *username, char *password); +cvar_t pop3_checkfrequency = {"pop3_checkfrequency", "60"}; //once a min +void POP3_Think (void); +void POP3_WriteCache (void); +//end export list. + +#define HASHELEMENTS 512 + +static hashtable_t pop3msghash; + +qboolean POP3_IsMessageUnique(char *hash) +{ + char *buf; + if (!pop3msghash.numbuckets) + Hash_InitTable(&pop3msghash, HASHELEMENTS, BZ_Malloc(Hash_BytesForBuckets(HASHELEMENTS))); + if (Hash_Get(&pop3msghash, hash)) + return false; + + buf = Z_Malloc(sizeof(bucket_t) + strlen(hash)+1); + strcpy(buf+sizeof(bucket_t), hash); + hash = buf+sizeof(bucket_t); + Hash_Add2(&pop3msghash, hash, hash, (bucket_t *)buf); + + return true; +} + +#define POP3_PORT 110 + + +typedef struct pop3_con_s { + char server[128]; + char username[128]; + char password[128]; + + float lastnoop; + + //these are used so we can fail a send. + //or recieve only part of an input. + //FIXME: make dynamically sizable, as it could drop if the send is too small (That's okay. + // but if the read is bigger than one command we suddenly fail entirly.) + int sendlen; + int sendbuffersize; + char *sendbuffer; + int readlen; + int readbuffersize; + char *readbuffer; + + qboolean drop; + + int socket; + + //we have a certain number of stages. + enum { + POP3_NOTCONNECTED, + POP3_WAITINGFORINITIALRESPONCE, //waiting for an initial response. + POP3_AUTHING, //wating for a response from USER + POP3_AUTHING2, //Set PASS, waiting to see if we passed. + POP3_LISTING, //Sent UIDL, waiting to see + POP3_RETRIEVING, //sent TOP, waiting for message headers to print info. + POP3_HEADER, + POP3_BODY, + POP3_QUITTING + } state; + + int retrlist[256]; //unrecognised uidls are added to this list. + int numtoretrieve; + + char msgsubject[256]; + char msgfrom[256]; + + struct pop3_con_s *next; +} pop3_con_t; + +static pop3_con_t *pop3sv; + +void POP3_CreateConnection(char *addy, char *username, char *password) +{ + unsigned long _true = true; + struct sockaddr_in from; + struct sockaddr_qstorage to; + pop3_con_t *con; + + for (con = pop3sv; con; con = con->next) + { + if (!strcmp(con->server, addy) && !strcmp(con->username, username)) + { + if (con->state == POP3_NOTCONNECTED && !con->socket) + break; + Con_Printf("Already connected to that pop3 server\n"); + return; + } + } + + {//quake routines using dns and stuff (Really, I wanna keep quake and pop3 fairly seperate) + netadr_t qaddy; + if (!NET_StringToAdr (addy, &qaddy)) + return; //failed to resolve dns. + if (!qaddy.port) + qaddy.port = htons(POP3_PORT); + NetadrToSockadr(&qaddy, &to); + } + + if (!con) + con = IWebMalloc(sizeof(pop3_con_t)); + else + con->state = POP3_WAITINGFORINITIALRESPONCE; + + + + if ((con->socket = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) + { + Sys_Error ("POP3_CreateConnection: socket: %s\n", strerror(qerrno)); + } + + memset(&from, 0, sizeof(from)); + from.sin_family = AF_INET; + if (bind(con->socket, (struct sockaddr*)&from, sizeof(from)) == -1) + { + IWebWarnPrintf ("POP3_CreateConnection: failed to bind: %s\n", strerror(qerrno)); + } + + //not yet blocking. + if (connect(con->socket, (struct sockaddr*)&to, sizeof(to)) == -1) + { + IWebWarnPrintf ("POP3_CreateConnection: connect: %s\n", strerror(qerrno)); + closesocket(con->socket); + IWebFree(con); + return; + } + + if (ioctlsocket (con->socket, FIONBIO, &_true) == -1) //now make it non blocking. + { + Sys_Error ("POP3_CreateConnection: ioctl FIONBIO: %s\n", strerror(qerrno)); + } + + Q_strncpyz(con->server, addy, sizeof(con->server)); + Q_strncpyz(con->username, username, sizeof(con->username)); + Q_strncpyz(con->password, password, sizeof(con->password)); + + if (!con->state) + { + con->state = POP3_WAITINGFORINITIALRESPONCE; + + con->next = pop3sv; + pop3sv = con; + + Con_Printf("Connected to %s\n", con->server); + } +} + +static void POP3_EmitCommand(pop3_con_t *pop3, char *text) +{ + int newlen; + + newlen = pop3->sendlen + strlen(text) + 2; + + if (newlen >= pop3->sendbuffersize || !pop3->sendbuffer) //pre-length check. + { + char *newbuf; + pop3->sendbuffersize = newlen*2; + newbuf = IWebMalloc(pop3->sendbuffersize); + if (!newbuf) + { + Con_Printf("Memory is low\n"); + pop3->drop = true; //failed. + return; + } + if (pop3->sendbuffer) + { + memcpy(newbuf, pop3->sendbuffer, pop3->sendlen); + IWebFree(pop3->sendbuffer); + } + pop3->sendbuffer = newbuf; + } + + pop3->sendlen = newlen; + + strncat(pop3->sendbuffer, text, pop3->sendbuffersize-1); + strncat(pop3->sendbuffer, "\r\n", pop3->sendbuffersize-1); + +// Con_Printf("^3%s\n", text); +} + +static qboolean POP3_ThinkCon(pop3_con_t *pop3) //false means drop the connection. +{ + char *ending; + int len; + + if (pop3->state == POP3_NOTCONNECTED && !pop3->socket) + { + if (pop3->lastnoop + pop3_checkfrequency.value < Sys_DoubleTime()) + { //we need to recreate the connection now. + pop3->lastnoop = Sys_DoubleTime(); + POP3_CreateConnection(pop3->server, pop3->username, pop3->password); + } + + return true; + } + + //get the buffer, stick it in our read holder + if (pop3->readlen+32 >= pop3->readbuffersize || !pop3->readbuffer) + { + len = pop3->readbuffersize; + if (!pop3->readbuffer) + pop3->readbuffersize = 256; + else + pop3->readbuffersize*=2; + + ending = IWebMalloc(pop3->readbuffersize); + if (!ending) + { + Con_Printf("Memory is low\n"); + return false; + } + if (pop3->readbuffer) + { + memcpy(ending, pop3->readbuffer, len); + IWebFree(pop3->readbuffer); + } + pop3->readbuffer = ending; + } + + len = recv(pop3->socket, pop3->readbuffer+pop3->readlen, pop3->readbuffersize-pop3->readlen-1, 0); + if (len>0) + { + pop3->readlen+=len; + pop3->readbuffer[pop3->readlen] = '\0'; + } + + if (pop3->readlen>0) + { + ending = strstr(pop3->readbuffer, "\r\n"); + + if (ending) //pollable text. + { + *ending = '\0'; +// Con_Printf("^2%s\n", pop3->readbuffer); + + ending+=2; + if (pop3->state == POP3_WAITINGFORINITIALRESPONCE) + { + if (!strncmp(pop3->readbuffer, "+OK", 3)) + { + char *angle1; + char *angle2 = NULL; + angle1 = strchr(pop3->readbuffer, '<'); + if (angle1) + { + angle2 = strchr(angle1+1, '>'); + } + if (angle2) + { //just in case + angle2[1] = '\0'; + + POP3_EmitCommand(pop3, va("APOP %s %s", pop3->username, MD5_GetPop3APOPString(angle1, pop3->password))); + pop3->state = POP3_AUTHING2; + } + else + { + POP3_EmitCommand(pop3, va("USER %s", pop3->username)); + pop3->state = POP3_AUTHING; + } + } + else + { + Con_Printf("Unexpected response from POP3 server\n"); + return false; //some sort of error. + } + } + else if (pop3->state == POP3_AUTHING) + { + if (!strncmp(pop3->readbuffer, "+OK", 3)) + { + POP3_EmitCommand(pop3, va("PASS %s", pop3->password)); + pop3->state = POP3_AUTHING2; + } + else + { + Con_Printf("Unexpected response from POP3 server.\nCheck username/password\n"); + return false; //some sort of error. + } + } + else if (pop3->state == POP3_AUTHING2) + { + if (!strncmp(pop3->readbuffer, "+OK", 3)) + { + POP3_EmitCommand(pop3, "UIDL"); + pop3->state = POP3_LISTING; + pop3->lastnoop = Sys_DoubleTime(); + } + else + { + Con_Printf("Unexpected response from POP3 server.\nCheck username/password\n"); + return false; + } + } + else if (pop3->state == POP3_LISTING) + { + if (!strncmp(pop3->readbuffer, "-ERR", 4)) + { + Con_Printf("Unexpected response from POP3 server.\nUIDL not supported?\n"); + return false; + } + else if (!strncmp(pop3->readbuffer, "+OK", 3)) + { + } + else if (!strncmp(pop3->readbuffer, ".", 1)) //we only ever search for recent messages. So we fetch them and get sender and subject. + { + if (!pop3->numtoretrieve) + { + pop3->state = POP3_QUITTING; + POP3_EmitCommand(pop3, "QUIT"); + } + else + { + pop3->state = POP3_RETRIEVING; + POP3_EmitCommand(pop3, va("RETR %i", pop3->retrlist[--pop3->numtoretrieve])); + } + } + else + { + char *s; + s = pop3->readbuffer; + if (*s) + { + s++; + while (*s >= '0' && *s <= '9') + s++; + while (*s == ' ') + s++; + } + + if (POP3_IsMessageUnique(s)) + if (pop3->numtoretrieve < sizeof(pop3->retrlist)/sizeof(pop3->retrlist[0])) + pop3->retrlist[pop3->numtoretrieve++] = atoi(pop3->readbuffer); + } + } + else if (pop3->state == POP3_RETRIEVING) + { + if (!strncmp(pop3->readbuffer, "+OK", 3)) + { + pop3->msgsubject[0] = '\0'; + pop3->msgfrom[0] = '\0'; + + pop3->state = POP3_HEADER; + } + else + { //erm... go for the next? + if (!pop3->numtoretrieve) + { + pop3->state = POP3_QUITTING; + POP3_EmitCommand(pop3, "QUIT"); + } + else + POP3_EmitCommand(pop3, va("RETR %i", pop3->retrlist[--pop3->numtoretrieve])); + } + } + else if (pop3->state == POP3_HEADER) + { + if (!strnicmp(pop3->readbuffer, "From: ", 6)) + Q_strncpyz(pop3->msgfrom, pop3->readbuffer + 6, sizeof(pop3->msgfrom)); + else if (!strnicmp(pop3->readbuffer, "Subject: ", 9)) + Q_strncpyz(pop3->msgsubject, pop3->readbuffer + 9, sizeof(pop3->msgsubject)); + else if (!strncmp(pop3->readbuffer, ".", 1)) + { + Con_Printf("New message:\nFrom: %s\nSubject: %s\n", pop3->msgfrom, pop3->msgsubject); + + if (!pop3->numtoretrieve) + { + pop3->state = POP3_QUITTING; + POP3_EmitCommand(pop3, "QUIT"); + } + else + { + pop3->state = POP3_RETRIEVING; + POP3_EmitCommand(pop3, va("RETR %i", pop3->retrlist[--pop3->numtoretrieve])); + } + } + else if (!*pop3->readbuffer) + pop3->state = POP3_BODY; + } + else if (pop3->state == POP3_BODY) + { + if (!strncmp(pop3->readbuffer, "..", 2)) + { + //line of text, skipping first '.' + Con_Printf("%s\n", pop3->readbuffer+1); + } + else if (!strncmp(pop3->readbuffer, ".", 1)) + { + Con_Printf("New message:\nFrom: %s\nSubject: %s\n", pop3->msgfrom, pop3->msgsubject); + + if (!pop3->numtoretrieve) + { + pop3->state = POP3_QUITTING; + POP3_EmitCommand(pop3, "QUIT"); + } + else + { + pop3->state = POP3_RETRIEVING; + POP3_EmitCommand(pop3, va("RETR %i", pop3->retrlist[--pop3->numtoretrieve])); + } + } + else + { + //normal line of text + Con_Printf("%s\n", pop3->readbuffer); + } + } + else if (pop3->state == POP3_QUITTING) + { + pop3->state = POP3_NOTCONNECTED; + closesocket(pop3->socket); + pop3->lastnoop = Sys_DoubleTime(); + pop3->socket = 0; + pop3->readlen = 0; + pop3->sendlen = 0; + return true; + } + else + { + Con_Printf("Bad client state\n"); + return false; + } + pop3->readlen -= ending - pop3->readbuffer; + memmove(pop3->readbuffer, ending, strlen(ending)+1); + } + } + if (pop3->drop) + return false; + + if (pop3->sendlen) + { + len = send(pop3->socket, pop3->sendbuffer, pop3->sendlen, 0); + if (len>0) + { + pop3->sendlen-=len; + memmove(pop3->sendbuffer, pop3->sendbuffer+len, pop3->sendlen+1); + } + } + return true; +} + +void POP3_Think (void) +{ + pop3_con_t *prev = NULL; + pop3_con_t *pop3; + + for (pop3 = pop3sv; pop3; pop3 = pop3->next) + { + if (pop3->drop || !POP3_ThinkCon(pop3)) + { + if (!prev) + pop3sv = pop3->next; + else + prev->next = pop3->next; + if (pop3->socket) + closesocket(pop3->socket); + BZ_Free(pop3); + if (!prev) + break; + } + + prev = pop3; + } +} + +#endif diff --git a/engine/email/sv_pop3.c b/engine/email/sv_pop3.c new file mode 100644 index 000000000..40a4c43f6 --- /dev/null +++ b/engine/email/sv_pop3.c @@ -0,0 +1,670 @@ +#include "bothdefs.h" + +#ifdef EMAILSERVER + +#include "quakedef.h" +#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); + + + + + + +#define HASH_FILESYSTEM +#ifdef HASH_FILESYSTEM +//#include "hash.h" + +#define Hash_BytesForBuckets(b) (sizeof(bucket_t)*b) + +#define STRCMP(s1,s2) (((*s1)!=(*s2)) || strcmp(s1+1,s2+1)) //saves about 2-6 out of 120 - expansion of idea from fastqcc +typedef struct bucket_s { + void *data; + char *keystring; + struct bucket_s *next; +} bucket_t; +typedef struct hashtable_s { + int numbuckets; + bucket_t **bucket; +} hashtable_t; + +void Hash_InitTable(hashtable_t *table, int numbucks, void *mem); //mem must be 0 filled. (memset(mem, 0, size)) +int Hash_Key(char *name, int modulus); +void *Hash_Get(hashtable_t *table, char *name); +void *Hash_GetKey(hashtable_t *table, int key); +void *Hash_GetNext(hashtable_t *table, char *name, void *old); +void *Hash_Add(hashtable_t *table, char *name, void *data); +void *Hash_Add2(hashtable_t *table, char *name, void *data, bucket_t *buck); +void *Hash_AddKey(hashtable_t *table, int key, void *data); +void Hash_Remove(hashtable_t *table, char *name); +#endif + + + + + + + + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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_Add2(&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 diff --git a/engine/email/sv_smtp.c b/engine/email/sv_smtp.c new file mode 100644 index 000000000..cb0778f02 --- /dev/null +++ b/engine/email/sv_smtp.c @@ -0,0 +1,449 @@ +#include "bothdefs.h" + +#ifdef EMAILSERVER + +#include "../http/iweb.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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define closesocket close +#define ioctlsocket ioctl +#endif + + +#define SMTP_PORT 25 +#define SMTP_TIMEOUT 30 + +static qboolean smtpactive; +static int smtpserversocket; + +typedef struct svsmtpclient_s { + struct svsmtpclient_s *next; + + int socket; + + float timeout; + + char fromaddr[64]; + char toaddr[64]; + + char inmessagebuffer[1024]; + int inmessagelen; + char outmessagebuffer[1024]; + int outmessagelen; + + qboolean gettingdata; + + FILE *file; + char sendingtotemp[256]; +} svsmtpclient_t; +static svsmtpclient_t *svsmtpclient; + + + + + +static void SMTP_ServerInit(void) +{ + struct sockaddr_in address; + unsigned long _true = true; + int port = SMTP_PORT; + + if ((smtpserversocket = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) + { + Sys_Error ("FTP_TCP_OpenSocket: socket:", strerror(qerrno)); + } + + if (ioctlsocket (smtpserversocket, 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 (smtpserversocket, (void *)&address, sizeof(address)) == -1) + { + closesocket(smtpserversocket); + return; + } + + listen(smtpserversocket, 3); + + smtpactive = true; + + + IWebPrintf("SMTP server is running\n"); + return; +} + +static void SMTP_ServerShutdown(void) +{ + closesocket(smtpserversocket); + smtpactive = false; +} + +static void SV_SMTP_QueueMessage(svsmtpclient_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 SMTP_NewConnection(int socket) +{ + svsmtpclient_t *newcl; + newcl = IWebMalloc(sizeof(svsmtpclient_t)); + if (!newcl) //bother + { + closesocket(socket); + return; + } + memset(newcl, 0, sizeof(svsmtpclient_t)); + + newcl->socket = socket; + newcl->next = svsmtpclient; + svsmtpclient = newcl; + + newcl->timeout = realtime + SMTP_TIMEOUT; + + *newcl->outmessagebuffer = '\0'; + + SV_SMTP_QueueMessage(newcl, "220 81.107.21.148 Probably best to say HELO now.\r\n"); +} + +static qboolean SV_SMTP_RunClient(svsmtpclient_t *cl) //true means client should be dropped +{ + int read; + char *nl, *token; + + if (cl->outmessagelen) + { + read = send(cl->socket, cl->outmessagebuffer, cl->outmessagelen, 0); + if (read < 0) + read = 0; + memmove(cl->outmessagebuffer, cl->outmessagebuffer + read, cl->outmessagelen - read+1); + cl->outmessagelen -= read; + } + + read = recv(cl->socket, cl->inmessagebuffer+cl->inmessagelen, sizeof(cl->inmessagebuffer)-1-cl->inmessagelen, 0); + + if (read == -1) + { + + if (qerrno != EWOULDBLOCK) //blocking is the only way to keep the connection on fail. + return true; + if (!cl->inmessagelen) + return false; + } + else if (read == 0) //don't quite know why this happens. + { + if (!cl->inmessagelen) + return false; + } + else + { + cl->timeout = realtime + SMTP_TIMEOUT; + + cl->inmessagelen += read; + if (cl->inmessagelen >= sizeof(cl->inmessagebuffer)-1 && !cl->gettingdata) //happens if we fill the buffer with no hope of empting it. + return true; + cl->inmessagebuffer[cl->inmessagelen] = '\0'; + } + + while (cl->gettingdata) + { + nl = strstr(cl->inmessagebuffer, "\r\n."); + if (!nl) + nl = cl->inmessagebuffer + cl->inmessagelen - 10; + else + nl+=2; + if (nl < cl->inmessagebuffer) + nl = cl->inmessagebuffer; //waiting for a crnl, so we can'texast this buffer in case we chop the \r\n + + if (!strcmp(cl->inmessagebuffer, ".\r\n")) + { + FILE *f; + int tries; + int len; + char name[256]; + char buffer[1024]; + char *to; + char *at; + + cl->gettingdata = false; + + + to = strchr(cl->toaddr, '<'); + at = strchr(cl->toaddr, '@'); + if (!to || !at || strstr(to, "..")) + SV_SMTP_QueueMessage(cl, "452 Couldn't open file.\r\n"); + else + { + to++; + *at = '\0'; + f=NULL; + for (tries = 0; tries < 10; tries++) //give it a few goes. + { + sprintf(name, "emails/%s/e%i.eml", to, rand()); + COM_CreatePath(name); + f = fopen(name, "wb"); + if (f) + break; + } + + if (f) + { + + { + netadr_t adr; + struct sockaddr_qstorage name; + int namelen = sizeof(name); + + getpeername(cl->socket, (struct sockaddr*)&name, &namelen); + SockadrToNetadr(&name, &adr); + sprintf(buffer, "Received: from %s\r\n", NET_AdrToString(adr)); + fwrite(buffer, strlen(buffer), 1, f); + } + + fseek(cl->file, 0, SEEK_END); + len = ftell(cl->file); + fseek(cl->file, 0, SEEK_SET); + while(len) + { + tries = sizeof(buffer); + if (tries > len) + tries = len; + len -= tries; + + fread(buffer, tries, 1, cl->file); + if (fwrite(buffer, tries, 1, f) != 1) + { + fclose(f); + f = NULL; + unlink(name); + len = 0; + SV_SMTP_QueueMessage(cl, "452 Insufficient system storage.\r\n"); + } + } + if (f) + { + fclose(f); + SV_SMTP_QueueMessage(cl, "250 Finally. You do go on a bit.\r\n"); + } + } + else + SV_SMTP_QueueMessage(cl, "452 Couldn't open file.\r\n"); + } + + fclose(cl->file); + } + else + { +// for (read = 0; read < nl - cl->inmessagebuffer; read++) +// putch(cl->inmessagebuffer[read]); + fwrite(cl->inmessagebuffer, nl - cl->inmessagebuffer, 1, cl->file); + } + + cl->inmessagelen -= nl - cl->inmessagebuffer; + memmove(cl->inmessagebuffer, nl, strlen(nl)+1); + return false; + } + + nl = strstr(cl->inmessagebuffer, "\r\n"); + if (nl) + { + *nl = '\0'; + nl+=2; + + { + + printf("%s\n", cl->inmessagebuffer); + + token = COM_Parse(cl->inmessagebuffer); + + if (!strcmp(com_token, "QUIT")) + { + SV_SMTP_QueueMessage(cl, "221 Well, it was good while it lasted\r\n"); + return true; + } + else if (!strcmp(com_token, "HELO")) + { + SV_SMTP_QueueMessage(cl, va("250 %s Electronic you say? That's good then. :o)\r\n", "81.107.21.148")); + } + else if (!strcmp(com_token, "MAIL")) + { + token = COM_Parse(token); + if (strcmp(com_token, "FROM:")) + { + if (strncmp(com_token, "FROM:", 5)) + SV_SMTP_QueueMessage(cl, "501 Syntax error. Expected MAIL FROM: fromaddr\r\n"); + else + { + SV_SMTP_QueueMessage(cl, "250 Get on with it\r\n"); + Q_strncpyz(cl->fromaddr, com_token+5, sizeof(cl->fromaddr)); + } + } + else + { + SV_SMTP_QueueMessage(cl, "250 Get on with it\r\n"); + Q_strncpyz(cl->fromaddr, token, sizeof(cl->fromaddr)); + } + } + else if (!strcmp(com_token, "RCPT")) + { + token = COM_Parse(token); + if (strcmp(com_token, "TO:")) + { + if (!strncmp(com_token, "TO:<", 4)) + { + token = com_token+3; + } + else + { + token = NULL; + SV_SMTP_QueueMessage(cl, "501 Syntax error. Expected RCPT TO: toaddr\r\n"); + } + } + if (token) + { + SV_SMTP_QueueMessage(cl, "250 Yada yada yada\r\n"); + Q_strncpyz(cl->toaddr, token, sizeof(cl->toaddr)); + } + } + else if (!strcmp(com_token, "DATA")) + { + cl->file = tmpfile(); + if (!cl->file) + { + SV_SMTP_QueueMessage(cl, "550 Access Denied to You.\r\n"); + } + else + { + SV_SMTP_QueueMessage(cl, "354 I'm waiting\r\n"); + cl->gettingdata = true; + } + } + else + SV_SMTP_QueueMessage(cl, "500 Stop speaking pig-latin\r\n"); + } + cl->inmessagelen -= nl - cl->inmessagebuffer; + memmove(cl->inmessagebuffer, nl, strlen(nl)+1); + } + + return false; +} + +static void SV_SMTP_RunClients(void) +{ + svsmtpclient_t *cl, *prev; + + cl = svsmtpclient; + prev = NULL; + while(cl) + { + if (cl->timeout < realtime || SV_SMTP_RunClient(cl)) + { + closesocket(cl->socket); + if (prev) + prev->next = cl->next; + else + svsmtpclient = cl->next; + + if (cl->file) + fclose(cl->file); + IWebFree(cl); + if (prev) + cl = prev->next; + else + cl = svsmtpclient; + continue; + } + prev = cl; + cl = cl->next; + } +} + +qboolean SV_SMTP(qboolean activewanted) +{ + struct sockaddr_qstorage from; + int fromlen; + int clientsock; + + if (!smtpactive) + { + if (activewanted) + SMTP_ServerInit(); + else + return false; + } + else if (!activewanted) + { + SMTP_ServerShutdown(); + return false; + } + + fromlen = sizeof(from); + clientsock = accept(smtpserversocket, (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. + { + { + netadr_t adr; + SockadrToNetadr(&from, &adr); + Con_DPrintf("SMTP connect initiated from %s\n", NET_AdrToString(adr)); + } + SMTP_NewConnection(clientsock); + } + + SV_SMTP_RunClients(); + + return true; +} + +#endif