mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-27 14:12:25 +00:00
550 lines
13 KiB
C
550 lines
13 KiB
C
|
#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 <sys/types.h>
|
||
|
#include <sys/socket.h>
|
||
|
#include <netinet/in.h>
|
||
|
#include <netdb.h>
|
||
|
#include <sys/param.h>
|
||
|
#include <sys/ioctl.h>
|
||
|
#include <sys/uio.h>
|
||
|
#include <arpa/inet.h>
|
||
|
#include <errno.h>
|
||
|
|
||
|
#include <unistd.h>
|
||
|
|
||
|
#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
|