7db5b6d5dc
git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@1700 fc73d0e0-1445-4013-8a0c-d673dee63da5
465 lines
9.8 KiB
C
465 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;
|
|
}
|
|
}
|