#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