Skip to content
Snippets Groups Projects
mailsrvr.c 67.7 KiB
Newer Older
/* mailsrvr.c */

/* Synchronet Mail (SMTP/POP3) server and sendmail threads */

/* $Id$ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright 2000 Rob Swindell - http://www.synchro.net/copyright.html		*
 *																			*
 * 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.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

rswindell's avatar
rswindell committed
/* Platform-specific headers */
#ifdef _WIN32
#include <io.h>			/* open/close */
#include <share.h>		/* share open flags */
rswindell's avatar
rswindell committed
#include <windows.h>
#include <process.h>	/* _beginthread */
rswindell's avatar
rswindell committed
#endif

/* ANSI C Library headers */
#include <stdio.h>
#include <stdlib.h>		/* ltoa in GNU C lib */
rswindell's avatar
rswindell committed
#include <stdarg.h>		/* va_list */
#include <string.h>		/* strrchr */
#include <ctype.h>		/* isdigit */
#include <fcntl.h>		/* Open flags */
#include <errno.h>		/* errno */
rswindell's avatar
rswindell committed

/* Synchronet-specific headers */
#include "scfgdefs.h"
rswindell's avatar
rswindell committed
#include "mailsrvr.h"
#include "userdat.h"
#include "smblib.h"
#include "crc32.h"
rswindell's avatar
rswindell committed
#include "sbbsinet.h"
rswindell's avatar
rswindell committed
/* Constants */
#define MAIL_VERSION "1.10"

rswindell's avatar
rswindell committed
#ifdef _WIN32
#define IMPORT	__declspec(dllimport)
#else
#define IMPORT
#endif

IMPORT BOOL		load_cfg(scfg_t* cfg, char* text[]);
IMPORT ushort	crc16(char *str);
IMPORT char *	zonestr(short zone);
IMPORT int		putsmsg(scfg_t* cfg, int usernumber, char *strin);
IMPORT mail_t*	loadmail(smb_t* smb, ulong* msgs, uint usernumber
									,int which, long mode);
rswindell's avatar
rswindell committed
IMPORT void		freemail(mail_t* mail);
IMPORT BOOL		trashcan(scfg_t* cfg, char *insearch, char *name);
int dns_getmx(char* name, char* mx, char* mx2, DWORD intf, DWORD ip_addr, BOOL use_tcp);

#define SMTP_OK		"250 OK"
#define SMTP_BADSEQ	"503 Bad sequence of commands"

#define TIMEOUT_THREAD_WAIT		60		/* Seconds */

#define STATUS_WFC	"Listening"

static const char *wday[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
static const char *mon[]={"Jan","Feb","Mar","Apr","May","Jun"
            ,"Jul","Aug","Sep","Oct","Nov","Dec"};

static mail_startup_t* startup=NULL;
static scfg_t	scfg;
static SOCKET	server_socket=INVALID_SOCKET;
static SOCKET	pop3_socket=INVALID_SOCKET;
static int		active_clients=0;
static int		active_sendmail=0;
static BOOL		sendmail_running=FALSE;
static DWORD	sockets=0;

typedef struct {
	SOCKET			socket;
	SOCKADDR_IN		client_addr;
} smtp_t,pop3_t;

static int lprintf(char *fmt, ...)
{
	va_list argptr;
	char sbuf[1024];

    if(startup==NULL || startup->lputs==NULL)
        return(0);

	va_start(argptr,fmt);
    vsprintf(sbuf,fmt,argptr);
    va_end(argptr);
    return(startup->lputs(sbuf));
}

#ifdef _WINSOCKAPI_

static WSADATA WSAData;

static BOOL winsock_startup(void)
{
	int		status;             /* Status Code */

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
		lprintf("%s %s",WSAData.szDescription, WSAData.szSystemStatus);
		return (TRUE);
	}

    lprintf("!WinSock startup ERROR %d", status);
	return (FALSE);
}

#else /* No WINSOCK */

rswindell's avatar
rswindell committed
#define winsock_startup()	(TRUE)

#endif

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
		startup->clients(active_clients+active_sendmail);
}

static void client_on(SOCKET sock, client_t* client)
{
	if(startup!=NULL && startup->client_on!=NULL)
		startup->client_on(TRUE,sock,client);
}

static void client_off(SOCKET sock)
{
	if(startup!=NULL && startup->client_on!=NULL)
		startup->client_on(FALSE,sock,NULL);
}

static void thread_up(void)
{
	if(startup!=NULL && startup->thread_up!=NULL)
		startup->thread_up(TRUE);
}

static void thread_down(void)
{
	if(startup!=NULL && startup->thread_up!=NULL)
		startup->thread_up(FALSE);
}

SOCKET open_socket(int type)

	sock=socket(AF_INET, type, IPPROTO_IP);
	if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL) 
		startup->socket_open(TRUE);
	if(sock!=INVALID_SOCKET) {
		sockets++;
#if 0 /*def _DEBUG */
		lprintf("%04d Socket opened (%d sockets in use)",sock,sockets);
#endif
	}
	return(sock);
}

int close_socket(SOCKET sock)
{
	int		result;
	ulong	l=0;

	ioctlsocket(sock, FIONBIO, &l);	/* Insure that we close successfully */

	result=closesocket(sock);
	if(/* result==0 && */ startup!=NULL && startup->socket_open!=NULL) 
		startup->socket_open(FALSE);
	sockets--;
	if(result!=0)
		lprintf("!ERROR %d closing socket %d",ERROR_VALUE,sock);
	else {
#if 0 /*def _DEBUG */
		lprintf("%04d Socket closed (%d sockets in use)",sock,sockets);
#endif
	}

	return(result);
}

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
	    startup->status(str);
}

static int sockprintf(SOCKET sock, char *fmt, ...)
{
	int		len;
	int		result;
	va_list argptr;
	char	sbuf[1024];

    va_start(argptr,fmt);
    len=vsprintf(sbuf,fmt,argptr);
	if(startup->options&MAIL_OPT_DEBUG_TX)
		lprintf("TX: %s", sbuf);
	strcat(sbuf,"\r\n");
	len+=2;
    va_end(argptr);
	while((result=send(sock,sbuf,len,0))!=len) {
		if(result==SOCKET_ERROR) {
rswindell's avatar
rswindell committed
			if(ERROR_VALUE==EWOULDBLOCK) {
rswindell's avatar
rswindell committed
			if(ERROR_VALUE==ECONNRESET) 
				lprintf("%04d Connection reset by peer on send",sock);
			else
				lprintf("!ERROR %d sending on socket %d",ERROR_VALUE,sock);
			return(0);
		}
		lprintf("!ERROR: short send on socket %d: %d instead of %d",sock,result,len);
	}
	return(len);
}

static time_t checktime(void)
{
	struct tm tm;

    memset(&tm,0,sizeof(tm));
    tm.tm_year=94;
    tm.tm_mday=1;
    return(mktime(&tm)^0x2D24BD00L);
}

static void recverror(SOCKET socket, int rd)
{
	if(rd==0) 
		lprintf("Socket %d closed",socket);
	else if(rd==SOCKET_ERROR) {
rswindell's avatar
rswindell committed
		if(ERROR_VALUE==ECONNRESET) 
			lprintf("%04d Connection reset by peer on receive",socket);
		else
			lprintf("Error %d on socket %d", ERROR_VALUE, socket);
	} else
		lprintf("recv on socket %d returned unexpected value: %d",socket,rd);
}

static int sockreadline(SOCKET socket, char* buf, int len)
{
	char	ch;
	int		i,rd=0;
	time_t	start;

	start=time(NULL);
	
	while(rd<len-1) {
		i= recv(socket, &ch, 1, 0);
		if(i<1) {
rswindell's avatar
rswindell committed
			if(ERROR_VALUE==EWOULDBLOCK) {
				if((time(NULL)-start)>startup->max_inactivity) {
					lprintf("!Socket %d inactive",socket);
					return(0);
				}
				Sleep(1);
				continue;
			}
			recverror(socket,i);
			return(i);
		}
		if(ch=='\n' && rd>=1) {
			break;
		}	
		buf[rd++]=ch;
	}
	buf[rd-1]=0;
	
	return(rd);
}

/************************************************/
/* Truncates white-space chars off end of 'str' */
/************************************************/
static void truncsp(char *str)
{
	uint c;

c=strlen(str);
while(c && (uchar)str[c-1]<=' ') c--;
str[c]=0;
}


static char* alias(char* name, char* alias)
{
	int		file;
	char*	p=name;
	char*	np;
	char*	tp;
	char	fname[MAX_PATH];
	FILE*	fp;

	sprintf(fname,"%sALIAS.CFG",scfg.ctrl_dir);
	if((file=sopen(fname,O_RDONLY|O_BINARY,SH_DENYNO))==-1)
		return(name);

	if((fp=fdopen(file,"rb"))==NULL) {
		close(file);
		return(name);
	}

	while(!feof(fp)) {
		if(!fgets(alias,80,fp))
			break;
		np=alias;
		while(*np && *np<=' ') np++;
		if(*np==';')
			continue;
		tp=np;
		while(*tp && *tp>' ') tp++;
		if(*tp) *tp=0;
		if(!stricmp(np,name)) {
			np=tp+1;
			while(*np && *np<=' ') np++;
			p=np;
			truncsp(p);
			lprintf("Alias: %s",p);
			break;
		}
	}
	fclose(fp);
	return(p);
}

static BOOL sockgetrsp(SOCKET socket, char* rsp, char *buf, int len)
{
	int rd;

	while(1) {
		rd = sockreadline(socket, buf, len);
		if(rd<1) 
			return(FALSE);
		if(buf[3]=='-')	{ /* Multi-line response */
			if(startup->options&MAIL_OPT_DEBUG_RX_RSP) 
				lprintf("RX: %s",buf);
			continue;
		}
		if(strnicmp(buf,rsp,strlen(rsp))) {
			lprintf("!INVALID RESPONSE: '%s' Expected: '%s'", buf, rsp);
			return(FALSE);
		}
		break;
	}
	if(startup->options&MAIL_OPT_DEBUG_RX_RSP) 
		lprintf("RX: %s",buf);
	return(TRUE);
}

static void usermailaddr(char* addr, char* name)
{
	int i;

	if(strchr(name,'.'))
		sprintf(addr,"\"%s\"@",name);
	else {
		sprintf(addr,"%s@",name);
		/* convert "first last@" to "first.last@" */
		for(i=0;addr[i];i++)
			if(addr[i]==' ' || addr[i]&0x80)
				addr[i]='.';
		strlwr(addr);
	}
	strcat(addr,scfg.sys_inetaddr);
}

static char *msgdate(when_t when, char* buf)
{
	struct tm	tm;
	struct tm*	tm_p;
	
	tm_p=localtime((const time_t*)&when.time);
	if(tm_p!=NULL)
		tm=*tm_p;
	else
		memset(&tm,0,sizeof(tm));
	sprintf(buf,"%s, %d %s %d %02d:%02d:%02d %s"
		,wday[tm.tm_wday]
		,tm.tm_mday
		,mon[tm.tm_mon]
		,1900+tm.tm_year
		,tm.tm_hour
		,tm.tm_min
		,tm.tm_sec
		,zonestr(when.zone)
		);
	return(buf);
}

#define MAX_LINE_LEN	1000

static void sockmsgtxt(SOCKET socket, smbmsg_t* msg, char* msgtxt, char* fromaddr
						,ulong maxlines)
{
	char		toaddr[256];
	char		date[64];
	char*		p;
	char*		tp;
	int			i;
	ulong		line;

	/* HEADERS */
	sockprintf(socket,"Date: %s",msgdate(msg->hdr.when_written,date));
	if(fromaddr[0]=='<')
		sockprintf(socket,"From: \"%s\" %s",msg->from,fromaddr);
	else
		sockprintf(socket,"From: \"%s\" <%s>",msg->from,fromaddr);
	sockprintf(socket,"Subject: %s",msg->subj);
	if(msg->to_net.type==NET_INTERNET || msg->to_net.type==NET_QWK) {
		if(*((char*)msg->to_net.addr)=='<')
			sockprintf(socket,"To: \"%s\" %s",msg->to,(char*)msg->to_net.addr);
		else
			sockprintf(socket,"To: \"%s\" <%s>",msg->to,(char*)msg->to_net.addr);
	} else {
		usermailaddr(toaddr,msg->to);
		sockprintf(socket,"To: \"%s\" <%s>",msg->to,toaddr);
	}
	if(msg->replyto_net.type==NET_INTERNET)
		sockprintf(socket,"Reply-To: %s",msg->replyto_net.addr);
	else
		sockprintf(socket,"Reply-To: %s",fromaddr);
	sockprintf(socket,"Message-ID: <%08lX.%lu@%s>"
		,msg->hdr.when_written.time,msg->idx.number,scfg.sys_inetaddr);
	for(i=0;i<msg->total_hfields;i++) 
		if(msg->hfield[i].type==RFC822HEADER)
			sockprintf(socket,"%s",(char*)msg->hfield_dat[i]);
	sockprintf(socket,"");	/* Header Terminator */
	/* MESSAGE BODY */
	line=0;
	p=msgtxt;
	while(*p && line<maxlines) {
		tp=strchr(p,'\n');
		if(tp) {
			if(tp-p>MAX_LINE_LEN)
				tp=p+MAX_LINE_LEN;
			*tp=0;
		}
		truncsp(p);	/* Takes care of '\r' or spaces */
		if(*p=='.')
			i=sockprintf(socket,".%.*s",MAX_LINE_LEN,p);
		else
			i=sockprintf(socket,"%.*s",MAX_LINE_LEN,p);
		if(!i)
			break;
		if(tp==NULL)
			break;
		p=tp+1;
		line++;
		Sleep(1);
	}
	sockprintf(socket,".");	/* End of text */
}

static u_long resolve_ip(char *addr)
{
	HOSTENT*	host;

	if(isdigit(addr[0]) && strchr(addr,'.'))
		return(inet_addr(addr));
	if ((host=gethostbyname(addr))==NULL) {
		lprintf("!ERROR resolving host name: %s",addr);
		return(0);
	}
	return(*((ulong*)host->h_addr_list[0]));
}


static void pop3_thread(void* arg)
{
	char*		p;
	char		str[128];
	char		buf[512];
	char		host_name[128];
	char		host_ip[64];
	char		username[LEN_ALIAS+1];
	char		password[LEN_PASS+1];
	char		fromaddr[256];
	char*		msgtxt;
	int			i;
	int			rd;
	ulong		l;
	ulong		lines;
	ulong		msgs,bytes,msgnum,msgbytes;
	SOCKET		socket;
	HOSTENT*	host;
	smb_t		smb;
	smbmsg_t	msg;
	user_t		user;
	client_t	client;
	mail_t*		mail;
	pop3_t		pop3=*(pop3_t*)arg;

	thread_up();

	free(arg);

	socket=pop3.socket;

	if(startup->options&MAIL_OPT_DEBUG_POP3)
		lprintf("%04d POP3 session thread started", socket);
#ifdef _WIN32
	if(startup->pop3_sound[0] && !(startup->options&MAIL_OPT_MUTE)) 
		PlaySound(startup->pop3_sound, NULL, SND_ASYNC|SND_FILENAME);

	strcpy(host_ip,inet_ntoa(pop3.client_addr.sin_addr));

	if(startup->options&MAIL_OPT_DEBUG_POP3)
		lprintf("%04d POP3 connection accepted from: %s"
			,socket, host_ip);

	if(startup->options&MAIL_OPT_NO_HOST_LOOKUP)
		host=NULL;
	else
		host=gethostbyaddr((char *)&pop3.client_addr.sin_addr
			,sizeof(pop3.client_addr.sin_addr),AF_INET);

	if(host!=NULL && host->h_name!=NULL)
		sprintf(host_name,"%.*s",(int)sizeof(host_name)-1,host->h_name);
	else
		strcpy(host_name,"<no name>");

	if(startup->options&MAIL_OPT_DEBUG_POP3)
		lprintf("%04d POP3 client name: %s", socket, host_name);
	if(trashcan(&scfg,host_ip,"IP")) {
		lprintf("%04d !POP3 client blocked in IP.CAN: %s"
		sockprintf(socket,"-ERR Access denied.");
		close_socket(socket);
		thread_down();
		return;
	}

	if(trashcan(&scfg,host_name,"HOST")) {
		lprintf("%04d !POP3 client blocked in HOST.CAN: %s"
		sockprintf(socket,"-ERR Access denied.");
		close_socket(socket);
		thread_down();
		return;
	}

	active_clients++;
	update_clients();

	/* Initialize client display */
	client.size=sizeof(client);
	client.time=time(NULL);
	sprintf(client.addr,"%.*s",(int)sizeof(client.addr)-1,host_ip);
	sprintf(client.host,"%.*s",(int)sizeof(client.host)-1,host_name);
	client.port=ntohs(pop3.client_addr.sin_port);
	client.protocol="POP3";
	client.user="<unknown>";
	client_on(socket,&client);

	sprintf(str,"POP3: %s", host_ip);
	status(str);

rswindell's avatar
rswindell committed
	mail=NULL;

	do {
		memset(&smb,0,sizeof(smb));
		memset(&msg,0,sizeof(msg));

		sprintf(smb.file,"%sMAIL",scfg.data_dir);
		if((i=smb_open(&smb))!=0) {
			lprintf("Error %d (%s) opening %s",i,smb.last_error,smb.file);
			sockprintf(socket,"-ERR %d opening %s",i,smb.file);
			break;
		}

		sockprintf(socket,"+OK Synchronet POP3 Server v%s Ready",MAIL_VERSION);

		if(!sockgetrsp(socket,"USER ",buf,sizeof(buf))) {
			sockprintf(socket,"-ERR USER command expected");
			break;
		}
		p=buf+5;
		while(*p && *p<=' ') p++;
		sprintf(username,"%.*s",(int)sizeof(username)-1,p);
		sockprintf(socket,"+OK");
		if(!sockgetrsp(socket,"PASS ",buf,sizeof(buf))) {
			sockprintf(socket,"-ERR PASS command expected");
			break;
		}
		p=buf+5;
		while(*p && *p<=' ') p++;
		sprintf(password,"%.*s",(int)sizeof(password)-1,p);
		user.number=matchuser(&scfg,username);
		if(!user.number) {
			for(i=0;username[i];i++)
				if(username[i]=='.')
					username[i]=' ';
			user.number=matchuser(&scfg,username);
		}
		if(!user.number) {
			lprintf("%04d !POP3 UNKNOWN USER: %s", socket, username);
			sockprintf(socket,"-ERR");
			break;
		}
		if((i=getuserdat(&scfg, &user))!=0) {
			lprintf("%04d !POP3 ERROR %d getting data on user #%u (%s)"
				,socket, i, user.number, username);
			sockprintf(socket, "-ERR");
			break;
		}
		if(user.misc&(DELETED|INACTIVE)) {
			lprintf("%04d !POP3 DELETED or INACTIVE user #%u (%s)"
				,socket, user.number, username);
			sockprintf(socket, "-ERR");
			break;
		}
		if(stricmp(password,user.pass)) {
			lprintf("%04d !POP3 WRONG PASSWORD for user %s: '%s' expected '%s'"
				,socket, username, password, user.pass);
			sockprintf(socket, "-ERR");
			break;
		}

		/* Update client display */
		client.user=user.alias;
		client_on(socket,&client);

		if(startup->options&MAIL_OPT_DEBUG_POP3)		
			lprintf("%04d POP3 %s logged in", socket, user.alias);
		sprintf(str,"POP3: %s",user.alias);
		status(str);

		sockprintf(socket,"+OK User verified");

		mail=loadmail(&smb,&msgs,user.number,MAIL_YOUR,0);

		for(l=bytes=0;l<msgs;l++) {
			msg.hdr.number=mail[l].number;
			if((i=smb_getmsgidx(&smb,&msg))!=0) {
				lprintf("%04d !POP3 ERROR %d getting message index"
					,socket ,i);
				break;
			}
			if((i=smb_lockmsghdr(&smb,&msg))!=0) {
				lprintf("%04d !POP3 ERROR %d locking message header #%lu"
					,socket ,i ,msg.hdr.number);
				break; 
			}
			i=smb_getmsghdr(&smb,&msg);
			smb_unlockmsghdr(&smb,&msg);
			if(i!=0) {
				lprintf("%04d !POP3 ERROR %d getting message header #%lu"
					,socket, i, msg.hdr.number);
				break;
			}
			for(i=0;i<msg.hdr.total_dfields;i++)
				if(msg.dfield[i].type==TEXT_BODY || msg.dfield[i].type==TEXT_TAIL)
					bytes+=msg.dfield[i].length;
			smb_freemsgmem(&msg);
		}			

		while(1) {	/* TRANSACTION STATE */
			rd = sockreadline(socket, buf, sizeof(buf));
			if(rd<1) 
				break;
			if(startup->options&MAIL_OPT_DEBUG_POP3)
				lprintf("%04d POP3 RX: %s", socket, buf);
			if(!stricmp(buf, "NOOP")) {
				sockprintf(socket,"+OK");
				continue;
			}
			if(!stricmp(buf, "QUIT")) {
				sockprintf(socket,"+OK");
				break;
			}
			if(!stricmp(buf, "STAT")) {
				sockprintf(socket,"+OK %lu %lu",msgs,bytes);
				continue;
			}
			if(!stricmp(buf, "RSET")) {
				for(l=0;l<msgs;l++) {
					msg.hdr.number=mail[l].number;
					if((i=smb_getmsgidx(&smb,&msg))!=0) {
						lprintf("%04d !POP3 ERROR %d getting message index"
							,socket, i);
						break;
					}
					if((i=smb_lockmsghdr(&smb,&msg))!=0) {
						lprintf("%04d !POP3 ERROR %d locking message header #%lu"
							,socket, i,msg.hdr.number);
						break; 
					}
					if((i=smb_getmsghdr(&smb,&msg))!=0) {
						lprintf("%04d !POP3 ERROR %d getting message header #%lu"
							,socket, i, msg.hdr.number);
						break;
					}
					msg.hdr.attr=mail[l].attr;
					msg.idx.attr=msg.hdr.attr;
					if((i=smb_putmsg(&smb,&msg))!=0)
						lprintf("%04d !POP3 ERROR %d updating message index"
							,socket, i);
					smb_unlockmsghdr(&smb,&msg);
					smb_freemsgmem(&msg);
				}
				if(l<msgs)
					sockprintf(socket,"-ERR %d messages reset (ERROR: %d)",l,i);
				else
					sockprintf(socket,"+OK %lu messages (%lu bytes)",msgs,bytes);
				continue;
			}
			if(!strnicmp(buf, "LIST",4) || !strnicmp(buf,"UIDL",4)) {
				p=buf+4;
				while(*p && *p<=' ') p++;
				if(isdigit(*p)) {
					msgnum=atol(p);
					if(msgnum<1 || msgnum>msgs) {
						lprintf("%04d !POP3 INVALID message #%ld"
							,socket, msgnum);
						sockprintf(socket,"-ERR no such message");
						continue;
					}
					msg.hdr.number=mail[msgnum-1].number;
					if((i=smb_getmsgidx(&smb,&msg))!=0) {
						lprintf("%04d !POP3 ERROR %d getting message index"
							,socket, i);
						sockprintf(socket,"-ERR %d getting message index",i);
						break;
					}
					if(msg.idx.attr&MSG_DELETE) {
						lprintf("%04d !POP3 Attempt to list deleted message"
							,socket);
						sockprintf(socket,"-ERR message deleted");
						continue;
					}
					if((i=smb_lockmsghdr(&smb,&msg))!=0) {
						lprintf("%04d !POP3 ERROR %d locking message header #%lu"
							,socket, i, msg.hdr.number);
						sockprintf(socket,"-ERR %d locking message header",i);
						continue; 
					}
					i=smb_getmsghdr(&smb,&msg);
					smb_unlockmsghdr(&smb,&msg);
					if(i!=0) {
						smb_freemsgmem(&msg);
						lprintf("%04d !POP3 ERROR %d getting message header #%lu"
							,socket, i,msg.hdr.number);
						sockprintf(socket,"-ERR %d getting message header",i);
						continue;
					}
					if(!strnicmp(buf, "LIST",4)) {
						msgbytes=0;
						for(i=0;i<msg.hdr.total_dfields;i++)
							if(msg.dfield[i].type==TEXT_BODY || msg.dfield[i].type==TEXT_TAIL)
								msgbytes+=msg.dfield[i].length;
						sockprintf(socket,"+OK %lu %lu",msgnum,msgbytes);
					} else /* UIDL */
						sockprintf(socket,"+OK %lu %lu",msgnum,msg.hdr.number);

					smb_freemsgmem(&msg);
					continue;
				}
				/* List ALL messages */
				sockprintf(socket,"+OK %lu messages (%lu bytes)",msgs,bytes);
				for(l=0;l<msgs;l++) {
					msg.hdr.number=mail[l].number;
					if((i=smb_getmsgidx(&smb,&msg))!=0) {
						lprintf("%04d !POP3 ERROR %d getting message index"
							,socket, i);
						break;
					}
					if(msg.idx.attr&MSG_DELETE) 
						continue;
					if((i=smb_lockmsghdr(&smb,&msg))!=0) {
						lprintf("%04d !POP3 ERROR %d locking message header #%lu"
							,socket, i,msg.hdr.number);
						break; 
					}
					i=smb_getmsghdr(&smb,&msg);
					smb_unlockmsghdr(&smb,&msg);
					if(i!=0) {
						smb_freemsgmem(&msg);
						lprintf("%04d !POP3 ERROR %d getting message header #%lu"
							,socket, i,msg.hdr.number);
						break;
					}
					if(!strnicmp(buf, "LIST",4)) {
						msgbytes=0;
						for(i=0;i<msg.hdr.total_dfields;i++)
							if(msg.dfield[i].type==TEXT_BODY || msg.dfield[i].type==TEXT_TAIL)
								msgbytes+=msg.dfield[i].length;
						sockprintf(socket,"%lu %lu",l+1,msgbytes);
					} else /* UIDL */
						sockprintf(socket,"%lu %lu",l+1,msg.hdr.number);

					smb_freemsgmem(&msg);
				}			
				sockprintf(socket,".");
				continue;
			}
			if(!strnicmp(buf, "RETR ",5) || !strnicmp(buf,"TOP ",4)) {
				sprintf(str,"POP3: %s", user.alias);
				status(str);

				lines=-1;
				p=buf+4;
				while(*p && *p<=' ') p++;
				msgnum=atol(p);

				if(!strnicmp(buf,"TOP ",4)) {
					while(*p && isdigit(*p)) p++;
					while(*p && *p<=' ') p++;
					lines=atol(p);
				}
				if(msgnum<1 || msgnum>msgs) {
					lprintf("%04d !POP3 %s attempted to retrieve an INVALID message #%ld"
						,socket, user.alias, msgnum);
					sockprintf(socket,"-ERR no such message");
					continue;
				}
				msg.hdr.number=mail[msgnum-1].number;

				lprintf("%04d POP3 %s retrieving message #%ld"
					,socket, user.alias, msg.hdr.number);

				if((i=smb_getmsgidx(&smb,&msg))!=0) {
					lprintf("%04d !POP3 ERROR %d getting message index"
						,socket, i);
					sockprintf(socket,"-ERR %d getting message index",i);
					continue;
				}
				if(msg.idx.attr&MSG_DELETE) {
					lprintf("%04d !POP3 Attempt to retrieve deleted message"
						,socket);
					sockprintf(socket,"-ERR message deleted");
					continue;
				}
				if((i=smb_lockmsghdr(&smb,&msg))!=0) {
					lprintf("%04d !POP3 ERROR %d locking message header #%lu"
						,socket, i, msg.hdr.number);
					sockprintf(socket,"-ERR %d locking message header",i);
					continue; 
				}
				if((i=smb_getmsghdr(&smb,&msg))!=0) {
					smb_unlockmsghdr(&smb,&msg);
					lprintf("%04d !POP3 ERROR %d getting message header #%lu"
						,socket, i, msg.hdr.number);
					sockprintf(socket,"-ERR %d getting message header",i);
					continue;
				}
				smb_unlockmsghdr(&smb,&msg);

				if((msgtxt=smb_getmsgtxt(&smb,&msg,GETMSGTXT_TAILS))==NULL) {
					smb_freemsgmem(&msg);
					lprintf("%04d !POP3 ERROR retrieving message text for message #%lu"
						,socket, msg.hdr.number);
					sockprintf(socket,"-ERR retrieving message text");
					continue;
				}

				sockprintf(socket,"+OK message follows");
				if(msg.from_net.type==NET_INTERNET)
					strcpy(fromaddr,msg.from_net.addr);
				else if(msg.from_net.type==NET_QWK)
					sprintf(fromaddr,"\"%s@%s\"@%s"
						,msg.from,(char*)msg.from_net.addr,scfg.sys_inetaddr);
				else 
					usermailaddr(fromaddr,msg.from);
				sockmsgtxt(socket,&msg,msgtxt,fromaddr,lines);
				/* if(startup->options&MAIL_OPT_DEBUG_POP3) */
				lprintf("%04d POP3 Mail transfer complete", socket);

				msg.hdr.attr|=MSG_READ;
				msg.idx.attr=msg.hdr.attr;
				msg.hdr.netattr|=MSG_SENT;

				if((i=smb_lockmsghdr(&smb,&msg))!=0) 
					lprintf("%04d !POP3 ERROR %d locking message header #%lu"
						,socket, i, msg.hdr.number);
				if((i=smb_putmsg(&smb,&msg))!=0)
					lprintf("%04d !POP3 ERROR %d marking message #%lu as read"
						,socket, i, msg.hdr.number);
				smb_unlockmsghdr(&smb,&msg);
				smb_freemsgmem(&msg);
				smb_freemsgtxt(msgtxt);
				continue;
			}
			if(!strnicmp(buf, "DELE ",5)) {
				p=buf+5;
				while(*p && *p<=' ') p++;
				msgnum=atol(p);

				if(msgnum<1 || msgnum>msgs) {
					lprintf("%04d !POP3 %s attempted to delete an INVALID message #%ld"
						,socket, user.alias, msgnum);
					sockprintf(socket,"-ERR no such message");
					continue;
				}
				msg.hdr.number=mail[msgnum-1].number;

				lprintf("%04d POP3 %s deleting message #%ld"
					,socket, user.alias, msg.hdr.number);

				if((i=smb_getmsgidx(&smb,&msg))!=0) {
					lprintf("%04d !POP3 ERROR %d getting message index"
						,socket, i);
					sockprintf(socket,"-ERR %d getting message index",i);
					continue;
				}
				if((i=smb_lockmsghdr(&smb,&msg))!=0) {
					lprintf("%04d !POP3 ERROR %d locking message header #%lu"
						,socket, i,msg.hdr.number);
					sockprintf(socket,"-ERR %d locking message header",i);
					continue; 
				}
				if((i=smb_getmsghdr(&smb,&msg))!=0) {
					smb_unlockmsghdr(&smb,&msg);
					lprintf("%04d !POP3 ERROR %d getting message header #%lu"
						,socket, i,msg.hdr.number);
					sockprintf(socket,"-ERR %d getting message header",i);
					continue;
				}
				msg.hdr.attr|=MSG_DELETE;
				msg.idx.attr=msg.hdr.attr;
				if((i=smb_putmsg(&smb,&msg))!=0) {
					smb_unlockmsghdr(&smb,&msg);
					smb_freemsgmem(&msg);
					lprintf("%04d !POP3 ERROR %d marking message as read", socket, i);
					sockprintf(socket,"-ERR %d marking message for deletion",i);
					continue;
				}
				smb_unlockmsghdr(&smb,&msg);
				smb_freemsgmem(&msg);
				sockprintf(socket,"+OK");
				if(startup->options&MAIL_OPT_DEBUG_POP3)
					lprintf("%04d POP3 Message deleted", socket);
			lprintf("%04d !POP3 UNSUPPORTED COMMAND: %s", socket, buf);
			sockprintf(socket,"-ERR UNSUPPORTED COMMAND: %s",buf);
		}
		if(user.number)
			putuserrec(&scfg,user.number,U_LASTON,8,ultoa(time(NULL),str,16));

	} while(0);

	status(STATUS_WFC);

	/* Free up resources here */
	close_socket(socket);

	if(mail!=NULL)
		freemail(mail);

	smb_freemsgmem(&msg);
	smb_close(&smb);

	if(startup->options&MAIL_OPT_DEBUG_POP3)
		lprintf("%04d POP3 session thread terminated", socket);

	active_clients--;
	update_clients();
	client_off(socket);

	thread_down();
}

BOOL rblchk(DWORD mail_addr_n, char* rbl_addr)