engine/plugins/emailnot/imapnoti.c

466 lines
9.8 KiB
C

/*
Copyright (C) 2005 David Walton.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
As a special exception, you may incorpotate patents and libraries regarding only hashing and security, on the conditions that it is also open source.
This means md4/5, rsa, ssl and similar.
*/
#include "../plugin.h"
//code to sit on an imap server and check for new emails every now and then.
char *STR_Parse(char *str, char *out, int outlen, char *punctuation)
{
char *s = str;
char *f;
skipwhite:
//skip over the whitespace
while (*s <= ' ' && *s)
s++;
if (*s == '/')
{
if (s[1] == '/') //c++ style comment
{
while(*s != '\n' && *s)
s++;
goto skipwhite;
}
if (s[1] == '*')
{
s+=2;
while(*s)
{
if (s[0] == '*' && s[1] == '/')
{
s+=2;
break;
}
s++;
}
goto skipwhite;
}
}
if (*s == '\"')
{
s++;
while(*s && outlen>1)
{
if (*s == '\"')
{
s++;
break;
}
*out++ = *s++;
outlen--;
}
*out++ = '\0';
return s;
}
if (strchr(punctuation, *s))
{ //starts with punctuation, so return only the first char
if (outlen < 2)
return NULL; //aaaah!
*out++ = *s;
*out++ = '\0';
s++;
return s;
}
//skip over non-white
for (f = s; outlen>1 && *(unsigned char*)f > ' '; f++, outlen--)
{
if (strchr(punctuation, *f))
{ //found punctuation, so return up to here
break;
}
*out++ = *f;
}
*out++ = '\0';
return f;
}
//exported.
void IMAP_CreateConnection(char *servername, char *username, char *password);
int imap_checkfrequency=60*1000;
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];
unsigned int 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;
qhandle_t 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;
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 = malloc(sizeof(imap_con_t));
con->socket = Net_TCPConnect(addy, IMAP_PORT);
if (!con->socket)
{
Con_Printf ("IMAP_CreateConnection: connect failed\n");
free(con);
return;
}
strlcpy(con->server, addy, sizeof(con->server));
strlcpy(con->username, username, sizeof(con->username));
strlcpy(con->password, password, sizeof(con->password));
con->next = imapsv;
imapsv = con;
Con_Printf ("Connected to %s (%s)\n", addy, username);
}
static void IMAP_EmitCommand(imap_con_t *imap, char *text)
{
int newlen;
//makes a few things easier though
newlen = imap->sendlen + 2 + strlen(text) + 2;
if (newlen >= imap->sendbuffersize || !imap->sendbuffer) //pre-length check.
{
char *newbuf;
imap->sendbuffersize = newlen*2;
newbuf = malloc(imap->sendbuffersize); //the null terminator comes from the >=
if (!newbuf)
{
Con_Printf("Memory is low\n");
imap->drop = true; //failed.
return;
}
if (imap->sendbuffer)
{
memcpy(newbuf, imap->sendbuffer, imap->sendlen);
free(imap->sendbuffer);
}
imap->sendbuffer = newbuf;
}
snprintf(imap->sendbuffer+imap->sendlen, newlen+1, "* %s\r\n", text);
imap->sendlen = newlen;
}
static char *IMAP_AddressStructure(char *msg, char *out, int outsize)
{
char name[256];
char mailbox[64];
char hostname[128];
char route[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 = STR_Parse(msg, name, sizeof(name), ""); //name
msg = STR_Parse(msg, route, sizeof(route), ""); //smtp route (ignored normally)
msg = STR_Parse(msg, mailbox, sizeof(mailbox), ""); //mailbox
msg = STR_Parse(msg, hostname, sizeof(hostname), ""); //hostname
while(indents && *msg == ')')
msg++;
if (out)
{
if (!strcmp(name, "NIL"))
snprintf(out, outsize, "%s@%s", mailbox, hostname);
else
snprintf(out, outsize, "%s <%s@%s>", name, mailbox, hostname);
}
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 = malloc(imap->readbuffersize);
if (!ending)
{
Con_Printf("Memory is low\n");
return false;
}
if (imap->readbuffer)
{
memcpy(ending, imap->readbuffer, len);
free(imap->readbuffer);
}
imap->readbuffer = ending;
}
len = Net_Recv(imap->socket, imap->readbuffer+imap->readlen, imap->readbuffersize-imap->readlen-1);
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_Milliseconds();
}
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_Milliseconds();
}
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];
char date[256];
num += 18;
num = STR_Parse(num, date, sizeof(date), "");
num = STR_Parse(num, subject, sizeof(subject), "");
// Con_Printf("Date/Time: %s\n", date);
num = IMAP_AddressStructure(num, from, sizeof(from));
if ((rand() & 3) == 3)
{
if (rand()&1)
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(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 < Sys_Milliseconds())
{ //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_Milliseconds();
}
}
if (imap->sendlen)
{
len = Net_Send(imap->socket, imap->sendbuffer, imap->sendlen);
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;
Net_Close(imap->socket);
free(imap);
if (!prev)
break;
}
prev = imap;
}
}