#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