Skip to content
Snippets Groups Projects
mailsrvr.c 167 KiB
Newer Older
/* 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 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
/* ANSI C Library headers */
deuce's avatar
deuce committed
#include <limits.h>			/* UINT_MAX */
rswindell's avatar
rswindell committed
#include <stdio.h>
#include <stdlib.h>			/* ltoa in GNU C lib */
#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 */
#undef SBBS	/* this shouldn't be defined unless building sbbs.dll/libsbbs.so */
rswindell's avatar
rswindell committed
#include "mailsrvr.h"
#include "crc32.h"
#include "netwrap.h"	/* getNameServerList() */
deuce's avatar
deuce committed
#include "multisock.h"
rswindell's avatar
rswindell committed
/* Constants */
static const char*	server_name="Synchronet Mail Server";
#define FORWARD			"forward:"
#define NO_FORWARD		"local:"
int dns_getmx(char* name, char* mx, char* mx2
			  ,DWORD intf, DWORD ip_addr, BOOL use_tcp, int timeout);
static char* pop_err	=	"-ERR";
static char* auth_ok	=	"235 User Authenticated";
static char* sys_error	=	"421 System error";
static char* sys_unavail=	"421 System unavailable, try again later";
static char* insuf_stor =	"452 Insufficient system storage";
static char* badarg_rsp =	"501 Bad argument";
static char* badseq_rsp	=	"503 Bad sequence of commands";
static char* badauth_rsp=	"535 Authentication failure";
static char* badrsp_err	=	"%s replied with:\r\n\"%s\"\r\n"
							"instead of the expected reply:\r\n\"%s ...\"";
#define TIMEOUT_THREAD_WAIT		60		/* Seconds */
#define DNSBL_THROTTLE_VALUE	1000	/* Milliseconds */

#define STATUS_WFC	"Listening"

static mail_startup_t* startup=NULL;
static scfg_t	scfg;
deuce's avatar
deuce committed
static struct xpms_set	*mail_set=NULL;
static BOOL terminated=FALSE;
static protected_uint32_t active_clients;
static protected_uint32_t thread_count;
static volatile int		active_sendmail=0;
static volatile BOOL	sendmail_running=FALSE;
static volatile BOOL	terminate_server=FALSE;
static volatile BOOL	terminate_sendmail=FALSE;
static sem_t	sendmail_wakeup_sem;
static char		revision[16];
static str_list_t recycle_semfiles;
static str_list_t shutdown_semfiles;
static js_server_props_t js_server_props;
	volatile ulong	sockets;
	volatile ulong	errors;
	volatile ulong	crit_errors;
	volatile ulong	connections_ignored;
	volatile ulong	connections_refused;
	volatile ulong	connections_served;
	volatile ulong	pop3_served;
	volatile ulong	smtp_served;
	volatile ulong	sessions_refused;
	volatile ulong	msgs_ignored;
	volatile ulong	msgs_refused;
	volatile ulong	msgs_received;
rswindell's avatar
rswindell committed
	char		name[INI_MAX_VALUE_LEN];
rswindell's avatar
rswindell committed
	char		eval[INI_MAX_VALUE_LEN];
	BOOL		ignore_on_error;	/* Ignore mail message if cmdline fails */
	ulong		handled;			/* counter (for stats display) */

typedef struct {
	SOCKET			socket;
deuce's avatar
deuce committed
	union xp_sockaddr	client_addr;
	socklen_t		client_addr_len;
static int lprintf(int level, const char *fmt, ...)
{
	va_list argptr;
	char sbuf[1024];

	va_start(argptr,fmt);
    vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
	sbuf[sizeof(sbuf)-1]=0;
    va_end(argptr);
		char errmsg[sizeof(sbuf)+16];
		SAFEPRINTF(errmsg, "mail %s", sbuf);
		errorlog(&scfg,startup==NULL ? NULL:startup->host_name,errmsg), stats.errors++;
		if(startup!=NULL && startup->errormsg!=NULL)
			startup->errormsg(startup->cbdata,level,errmsg);

	if(level <= LOG_CRIT)
		stats.crit_errors++;

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

#if defined(_WIN32)
	if(IsBadCodePtr((FARPROC)startup->lputs))
		return(0);
#endif

    return(startup->lputs(startup->cbdata,level,sbuf));
}

#ifdef _WINSOCKAPI_

static WSADATA WSAData;
#define SOCKLIB_DESC WSAData.szDescription
static BOOL WSAInitialized=FALSE;

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

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
		lprintf(LOG_DEBUG,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
    lprintf(LOG_CRIT,"!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(startup->cbdata,protected_uint32_value(active_clients)+active_sendmail);
static void client_on(SOCKET sock, client_t* client, BOOL update)
	if(startup!=NULL && startup->client_on!=NULL)
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
}

static void client_off(SOCKET sock)
{
	if(startup!=NULL && startup->client_on!=NULL)
		startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
static void thread_up(BOOL setuid)
{
	if(startup!=NULL && startup->thread_up!=NULL)
		startup->thread_up(startup->cbdata,TRUE,setuid);
static int32_t thread_down(void)
	int32_t count = protected_uint32_adjust(&thread_count,-1);
	if(startup!=NULL && startup->thread_up!=NULL)
		startup->thread_up(startup->cbdata,FALSE,FALSE);
deuce's avatar
deuce committed
void mail_open_socket(SOCKET sock, void* cb_protocol)
deuce's avatar
deuce committed
	char	*protocol=(char *)cb_protocol;
deuce's avatar
deuce committed
	if(startup!=NULL && startup->socket_open!=NULL)
		startup->socket_open(startup->cbdata,TRUE);
deuce's avatar
deuce committed
	SAFEPRINTF(section,"mail|%s",protocol);
	if(set_socket_options(&scfg, sock, section, error, sizeof(error)))
		lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
deuce's avatar
deuce committed
	stats.sockets++;
}

void mail_close_socket_cb(SOCKET sock, void* cb_protocol)
{
	if(startup!=NULL && startup->socket_open!=NULL)
		startup->socket_open(startup->cbdata,FALSE);
	stats.sockets--;
	shutdown(sock,SHUT_RDWR);	/* required on Unix */
	result=closesocket(sock);
	if(startup!=NULL && startup->socket_open!=NULL)
		startup->socket_open(startup->cbdata,FALSE);
	if(result!=0) {
		if(ERROR_VALUE!=ENOTSOCK)
			lprintf(LOG_WARNING,"%04d !ERROR %d closing socket",sock, ERROR_VALUE);
#if 0 /*def _DEBUG */
		lprintf(LOG_DEBUG,"%04d Socket closed (%d sockets in use)",sock,stats.sockets);
#endif

	return(result);
}

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
	    startup->status(startup->cbdata,str);
int sockprintf(SOCKET sock, char *fmt, ...)
	int		result;
	va_list argptr;
	char	sbuf[1024];
	fd_set	socket_set;
	struct timeval tv;

    va_start(argptr,fmt);
    len=vsnprintf(sbuf,maxlen=sizeof(sbuf)-2,fmt,argptr);
    va_end(argptr);

	if(len<0 || len > maxlen) /* format error or output truncated */
	if(startup->options&MAIL_OPT_DEBUG_TX)
		lprintf(LOG_DEBUG,"%04d TX: %.*s", sock, len, sbuf);
	memcpy(sbuf+len,"\r\n",2);
	if(sock==INVALID_SOCKET) {
		lprintf(LOG_WARNING,"!INVALID SOCKET in call to sockprintf");
	/* Check socket for writability (using select) */
	tv.tv_usec=0;

	FD_ZERO(&socket_set);
	FD_SET(sock,&socket_set);

	if((result=select(sock+1,NULL,&socket_set,NULL,&tv))<1) {
			lprintf(LOG_NOTICE,"%04d !TIMEOUT selecting socket for send"
			lprintf(LOG_NOTICE,"%04d !ERROR %d selecting socket for send"
	while((result=sendsocket(sock,sbuf,len))!=len) {
		if(result==SOCKET_ERROR) {
rswindell's avatar
rswindell committed
			if(ERROR_VALUE==EWOULDBLOCK) {
rswindell's avatar
rswindell committed
			if(ERROR_VALUE==ECONNRESET) 
				lprintf(LOG_NOTICE,"%04d Connection reset by peer on send",sock);
			else if(ERROR_VALUE==ECONNABORTED) 
				lprintf(LOG_NOTICE,"%04d Connection aborted by peer on send",sock);
				lprintf(LOG_NOTICE,"%04d !ERROR %d sending on socket",sock,ERROR_VALUE);
		lprintf(LOG_WARNING,"%04d !ERROR: short send on socket: %d instead of %d",sock,result,len);
static void sockerror(SOCKET socket, int rd, const char* action)
		lprintf(LOG_NOTICE,"%04d Socket closed by peer on %s"
			,socket, action);
	else if(rd==SOCKET_ERROR) {
rswindell's avatar
rswindell committed
		if(ERROR_VALUE==ECONNRESET) 
			lprintf(LOG_NOTICE,"%04d Connection reset by peer on %s"
				,socket, action);
		else if(ERROR_VALUE==ECONNABORTED) 
			lprintf(LOG_NOTICE,"%04d Connection aborted by peer on %s"
				,socket, action);
			lprintf(LOG_NOTICE,"%04d !SOCKET ERROR %d on %s"
				,socket, ERROR_VALUE, action);
		lprintf(LOG_WARNING,"%04d !SOCKET ERROR: unexpected return value %d from %s"
			,socket, rd, action);
static int sockreadline(SOCKET socket, char* buf, int len)
{
	char	ch;
	int		i,rd=0;
	start=time(NULL);

	if(socket==INVALID_SOCKET) {
		lprintf(LOG_WARNING,"!INVALID SOCKET in call to sockreadline");
deuce's avatar
deuce committed
		if(terminated || terminate_server) {
			lprintf(LOG_WARNING,"%04d !ABORTING sockreadline",socket);
		tv.tv_sec=startup->max_inactivity;
		tv.tv_usec=0;

		FD_ZERO(&socket_set);
		FD_SET(socket,&socket_set);

		i=select(socket+1,&socket_set,NULL,NULL,&tv);

				if(startup->max_inactivity && (time(NULL)-start)>startup->max_inactivity) {
					lprintf(LOG_WARNING,"%04d !TIMEOUT in sockreadline (%u seconds):  INACTIVE SOCKET",socket,startup->max_inactivity);
			sockerror(socket,i,"select");
			sockerror(socket,i,"receive");
		if(ch=='\n' /* && rd>=1 */ ) { /* Mar-9-2003: terminate on sole LF */
	
	return(rd);
}

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

	while(1) {
		rd = sockreadline(socket, buf, len);
		if(rd<1) {
			if(rd==0)
				lprintf(LOG_WARNING,"%04d !RECEIVED BLANK RESPONSE, Expected '%s'", socket, rsp);
			return(FALSE);
		if(buf[3]=='-')	{ /* Multi-line response */
			if(startup->options&MAIL_OPT_DEBUG_RX_RSP) 
				lprintf(LOG_DEBUG,"%04d RX: %s",socket,buf);
		if(rsp!=NULL && strnicmp(buf,rsp,strlen(rsp))) {
			lprintf(LOG_WARNING,"%04d !INVALID RESPONSE: '%s' Expected: '%s'", socket, buf, rsp);
			return(FALSE);
		}
		break;
	}
	if(startup->options&MAIL_OPT_DEBUG_RX_RSP) 
		lprintf(LOG_DEBUG,"%04d RX: %s",socket,buf);
rswindell's avatar
rswindell committed
/* non-standard, but documented (mostly) in draft-newman-msgheader-originfo-05 */
void originator_info(SOCKET socket, smbmsg_t* msg)
{
	char* user		= msg->from_ext;
	char* login		= smb_get_hfield(msg,SENDERUSERID,NULL);
	char* server	= smb_get_hfield(msg,SENDERSERVER,NULL);
	char* client	= smb_get_hfield(msg,SENDERHOSTNAME,NULL);
	char* addr		= smb_get_hfield(msg,SENDERIPADDR,NULL);
	char* prot		= smb_get_hfield(msg,SENDERPROTOCOL,NULL);
	char* port		= smb_get_hfield(msg,SENDERPORT,NULL);
	char* time		= smb_get_hfield(msg,SENDERTIME,NULL);

	if(user || login || server || client || addr || prot || port || time)
		sockprintf(socket
			,"X-Originator-Info: account=%s; login-id=%s; server=%s; client=%s; addr=%s; prot=%s; port=%s; time=%s"
			,user
			,login
			,server
			,client
			,addr
			,prot
			,port
			,time
			);
}

/* RFC822: The maximum total length of a text line including the
   <CRLF> is 1000 characters (but not counting the leading
   dot duplicated for transparency). 

   POP3 (RFC1939) actually calls for a 512 byte line length limit!
*/
#define MAX_LINE_LEN	998		
static ulong sockmimetext(SOCKET socket, smbmsg_t* msg, char* msgtxt, ulong maxlines
						  ,str_list_t file_list, char* mime_boundary)
	char		toaddr[256]="";
	char		fromaddr[256]="";
rswindell's avatar
rswindell committed
	char		msgid[256];
	char		date[64];
	/* HEADERS (in recommended order per RFC822 4.1) */

	if(msg->reverse_path!=NULL)
		if(!sockprintf(socket,"Return-Path: %s", msg->reverse_path))
			return(0);

	for(i=0;i<msg->total_hfields;i++)
		if(msg->hfield[i].type == SMTPRECEIVED && msg->hfield_dat[i]!=NULL) 
			if(!sockprintf(socket,"Received: %s", msg->hfield_dat[i]))
				return(0);

	if(!sockprintf(socket,"Date: %s",msgdate(msg->hdr.when_written,date)))
		return(0);

	if((p=smb_get_hfield(msg,RFC822FROM,NULL))!=NULL)
		s=sockprintf(socket,"From: %s",p);	/* use original RFC822 header field */
	else {
rswindell's avatar
rswindell committed
		char fromname[256];
rswindell's avatar
rswindell committed
		SAFEPRINTF(fromname, "\"%s\"", msg->from);
		if(msg->from_net.type==NET_QWK && msg->from_net.addr!=NULL)
			SAFEPRINTF2(fromaddr,"%s!%s"
				,(char*)msg->from_net.addr
				,usermailaddr(&scfg,fromhost,msg->from));
rswindell's avatar
rswindell committed
		else if(msg->from_net.type==NET_FIDO && msg->from_net.addr!=NULL) {
			faddr_t* faddr = (faddr_t *)msg->from_net.addr;
			char faddrstr[128];
rswindell's avatar
rswindell committed
			SAFEPRINTF2(fromname,"\"%s\" (%s)", msg->from, smb_faddrtoa(faddr, NULL));
rswindell's avatar
rswindell committed
			if(faddr->point)
				SAFEPRINTF4(faddrstr,"p%hu.f%hu.n%hu.z%hu"FIDO_TLD
					,faddr->point, faddr->node, faddr->net, faddr->zone);
			else
				SAFEPRINTF3(faddrstr,"f%hu.n%hu.z%hu"FIDO_TLD
					,faddr->node, faddr->net, faddr->zone);
			SAFEPRINTF2(fromaddr,"%s@%s", usermailaddr(NULL,fromhost,msg->from), faddrstr);
		} else if(msg->from_net.type!=NET_NONE && msg->from_net.addr!=NULL)
			SAFECOPY(fromaddr,(char*)msg->from_net.addr);
		else 
			usermailaddr(&scfg,fromaddr,msg->from);
		if(fromaddr[0]=='<')
rswindell's avatar
rswindell committed
			s=sockprintf(socket,"From: %s %s",fromname,fromaddr);
rswindell's avatar
rswindell committed
			s=sockprintf(socket,"From: %s <%s>",fromname,fromaddr);
	if(msg->from_org!=NULL || msg->from_net.type==NET_NONE)
		if(!sockprintf(socket,"Organization: %s"
			,msg->from_org==NULL ? scfg.sys_name : msg->from_org))
			return(0);
	if(!sockprintf(socket,"Subject: %s",msg->subj))
		return(0);

	if((p=smb_get_hfield(msg,RFC822TO,NULL))!=NULL)
		s=sockprintf(socket,"To: %s",p);	/* use original RFC822 header field */
	else {
		if(strchr(msg->to,'@')!=NULL || msg->to_net.addr==NULL)
			s=sockprintf(socket,"To: %s",msg->to);	/* Avoid double-@ */
		else if(msg->to_net.type==NET_INTERNET || msg->to_net.type==NET_QWK) {
			if(strchr((char*)msg->to_net.addr,'<')!=NULL)
				s=sockprintf(socket,"To: %s",(char*)msg->to_net.addr);
			else
				s=sockprintf(socket,"To: \"%s\" <%s>",msg->to,(char*)msg->to_net.addr);
rswindell's avatar
rswindell committed
		} else if(msg->to_net.type==NET_FIDO) {
rswindell's avatar
rswindell committed
			s=sockprintf(socket,"To: \"%s\" (%s)",msg->to, smb_faddrtoa((fidoaddr_t*)msg->to_net.addr, NULL));
		} else {
			usermailaddr(&scfg,toaddr,msg->to);
			s=sockprintf(socket,"To: \"%s\" <%s>",msg->to,toaddr);
		}
	if((p=smb_get_hfield(msg,SMB_CARBONCOPY,NULL))!=NULL)
		if(!sockprintf(socket,"CC: %s",p))
			return(0);
	if((p=smb_get_hfield(msg,RFC822REPLYTO,NULL))==NULL) {
		if(msg->replyto_net.type==NET_INTERNET)
rswindell's avatar
rswindell committed
			p=msg->replyto_net.addr;
	if(p!=NULL) {
		if(np!=NULL)
			s=sockprintf(socket,"Reply-To: \"%s\" <%s>",np,p);
		else 
			s=sockprintf(socket,"Reply-To: %s",p);
	}
rswindell's avatar
rswindell committed
	if(!sockprintf(socket,"Message-ID: %s",get_msgid(&scfg,INVALID_SUB,msg,msgid,sizeof(msgid))))
		if(!sockprintf(socket,"In-Reply-To: %s",msg->reply_id))
			return(0);
rswindell's avatar
rswindell committed
	originator_info(socket, msg);
	/* Include all possible FidoNet header fields here */
	for(i=0;i<msg->total_hfields;i++) {
		switch(msg->hfield[i].type) {
			case FIDOCTRL:
			case FIDOAREA:	
			case FIDOSEENBY:
			case FIDOPATH:
			case FIDOMSGID:
			case FIDOREPLYID:
			case FIDOPID:
			case FIDOFLAGS:
			case FIDOTID:
				if(!sockprintf(socket, "%s: %s", smb_hfieldtype(msg->hfield[i].type), (char*)msg->hfield_dat[i]))
					return(0);
				break;
		}
	}
	for(i=0;i<msg->total_hfields;i++) { 
		if(msg->hfield[i].type==RFC822HEADER) { 
			if(strnicmp((char*)msg->hfield_dat[i],"Content-Type:",13)==0)
				content_type=msg->hfield_dat[i];
			if(!sockprintf(socket,"%s",(char*)msg->hfield_dat[i]))
				return(0);
	/* Default MIME Content-Type for non-Internet messages */
	if(msg->from_net.type!=NET_INTERNET && content_type==NULL && startup->default_charset[0]) {
		/* No content-type specified, so assume IBM code-page 437 (full ex-ASCII) */
		sockprintf(socket,"Content-Type: text/plain; charset=%s", startup->default_charset);
		sockprintf(socket,"Content-Transfer-Encoding: 8bit");
	}

	if(strListCount(file_list)) {	/* File attachments */
        mimeheaders(socket,mime_boundary);
        mimeblurb(socket,mime_boundary);
        mimetextpartheader(socket, mime_boundary, startup->default_charset);
	if(!sockprintf(socket,""))	/* Header Terminator */
		return(0);
	/* MESSAGE BODY */
	np=msgtxt;
	while(*np && lines<maxlines) {
		while(len<MAX_LINE_LEN && *(np+len)!=0 && *(np+len)!='\n')
		while(tlen && *(np+(tlen-1))<=' ') /* Takes care of '\r' or spaces */
		if(!sockprintf(socket, "%s%.*s", *np=='.' ? ".":"", tlen, np))
rswindell's avatar
rswindell committed
		lines++;
		/* release time-slices every x lines */
		if(startup->lines_per_yield
			&& !(lines%startup->lines_per_yield))	
	if(file_list!=NULL) {
		for(i=0;file_list[i];i++) { 
			sockprintf(socket,"");
			lprintf(LOG_INFO,"%04u MIME Encoding and sending %s",socket,file_list[i]);
			if(!mimeattach(socket,mime_boundary,file_list[i]))
				lprintf(LOG_ERR,"%04u !ERROR opening/encoding/sending %s",socket,file_list[i]);
			else {
				endmime(socket,mime_boundary);
				if(msg->hdr.auxattr&MSG_KILLFILE)
					if(remove(file_list[i])!=0)
						lprintf(LOG_WARNING,"%04u !ERROR %d removing %s",socket,errno,file_list[i]);
			}
    sockprintf(socket,".");	/* End of text */
static ulong sockmsgtxt(SOCKET socket, smbmsg_t* msg, char* msgtxt, ulong maxlines)
{
	char		filepath[MAX_PATH+1];
	ulong		retval;
	char*		boundary=NULL;
	unsigned	i;
	str_list_t	file_list=NULL;
	str_list_t	split;

	if(msg->hdr.auxattr&MSG_FILEATTACH) {

		boundary = mimegetboundary();
		file_list = strListInit();

		/* Parse header fields */
		for(i=0;i<msg->total_hfields;i++)
	        if(msg->hfield[i].type==FILEATTACH) 
				strListPush(&file_list,(char*)msg->hfield_dat[i]);

		/* Parse subject (if necessary) */
		if(!strListCount(file_list)) {	/* filename(s) stored in subject */
			split=strListSplitCopy(NULL,msg->subj," ");
			if(split!=NULL) {
				for(i=0;split[i];i++) {
					if(msg->idx.to!=0)
						SAFEPRINTF3(filepath,"%sfile/%04u.in/%s"
							,scfg.data_dir,msg->idx.to,getfname(truncsp(split[i])));
					else
						SAFEPRINTF3(filepath,"%sfile/%04u.out/%s"
							,scfg.data_dir,msg->idx.from,getfname(truncsp(split[i])));
					strListPush(&file_list,filepath);
				}
				strListFree(&split);
			}
		}
    }

	retval = sockmimetext(socket,msg,msgtxt,maxlines,file_list,boundary);

	strListFree(&file_list);

	if(boundary!=NULL)
		free(boundary);

	return(retval);
}

static u_long resolve_ip(char *inaddr)
	if(*addr=='[' && *(p=lastchar(addr))==']') { /* Support [ip_address] notation */
		if(*p!='.' && !isdigit((uchar)*p))
		return(inet_addr(addr));
	if((host=gethostbyname(inaddr))==NULL)
	return(*((ulong*)host->h_addr_list[0]));
}

/****************************************************************************/
/* Consecutive failed login (possible password hack) attempt tracking		*/
/****************************************************************************/
/* Counter is global so it is tracked between multiple connections.			*/
/* Failed consecutive login attempts > 10 will generate a hacklog entry	and	*/
/* immediately disconnect (after the usual failed-login delay).				*/
/* A failed login from a different host resets the counter.					*/
/* A successful login from the same host resets the counter.				*/
/****************************************************************************/

deuce's avatar
deuce committed
static void badlogin(SOCKET sock, const char* prot, const char* resp, char* user, char* passwd, char* host, union xp_sockaddr* addr)
deuce's avatar
deuce committed
	char	ip[INET6_ADDRSTRLEN];
		SAFEPRINTF(reason,"%s LOGIN", prot);
		count=loginFailure(startup->login_attempt_list, addr, prot, user, passwd);
rswindell's avatar
rswindell committed
		if(startup->login_attempt.hack_threshold && count>=startup->login_attempt.hack_threshold)
			hacklog(&scfg, reason, user, passwd, host, addr);
deuce's avatar
deuce committed
		inet_addrtop(addr, ip, sizeof(ip));
rswindell's avatar
rswindell committed
		if(startup->login_attempt.filter_threshold && count>=startup->login_attempt.filter_threshold)
			filter_ip(&scfg, (char*)prot, "- TOO MANY CONSECUTIVE FAILED LOGIN ATTEMPTS"
deuce's avatar
deuce committed
				,host, ip, user, /* fname: */NULL);
rswindell's avatar
rswindell committed
	mswait(startup->login_attempt.delay);

static void pop3_thread(void* arg)
{
	char*		p;
	char		str[128];
	char		buf[512];
	char		host_name[128];
deuce's avatar
deuce committed
	char		host_ip[INET6_ADDRSTRLEN];
	char		challenge[256];
	uchar		digest[MD5_DIGEST_SIZE];
	char*		msgtxt;
	int			i;
	int			rd;
	BOOL		activity=TRUE;
	BOOL		apop=FALSE;
	ulong		login_attempts;
	SOCKET		socket;
	smb_t		smb;
	smbmsg_t	msg;
	user_t		user;
	client_t	client;
	mail_t*		mail;
	pop3_t		pop3=*(pop3_t*)arg;
	login_attempt_t attempted;

	free(arg);

	socket=pop3.socket;

	if(startup->options&MAIL_OPT_DEBUG_POP3)
		lprintf(LOG_DEBUG,"%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);
deuce's avatar
deuce committed
	inet_addrtop(&pop3.client_addr, host_ip, sizeof(host_ip));

	if(startup->options&MAIL_OPT_DEBUG_POP3)
		lprintf(LOG_INFO,"%04d POP3 connection accepted from: %s port %u"
deuce's avatar
deuce committed
			,socket, host_ip, inet_addrport(&pop3.client_addr));
deuce's avatar
deuce committed
	if(getnameinfo(&pop3.client_addr.addr, pop3.client_addr_len, host_name, sizeof(host_name), NULL, 0, (startup->options&MAIL_OPT_NO_HOST_LOOKUP)?NI_NUMERICHOST:0)!=0)
		SAFECOPY(host_name, "<no name>");
	if(!(startup->options&MAIL_OPT_NO_HOST_LOOKUP) && (startup->options&MAIL_OPT_DEBUG_POP3))
		lprintf(LOG_INFO,"%04d POP3 Hostname: %s", socket, host_name);

	ulong banned = loginBanned(&scfg, startup->login_attempt_list, socket, host_name, startup->login_attempt, &attempted);
	if(banned || trashcan(&scfg,host_ip,"ip")) {
		if(banned) {
			char ban_duration[128];
			lprintf(LOG_NOTICE, "%04d !TEMPORARY BAN of %s (%u login attempts, last: %s) - remaining: %s"
				,socket, host_ip, attempted.count-attempted.dupes, attempted.user, seconds_to_str(banned, ban_duration));
		}
		else
			lprintf(LOG_NOTICE,"%04d !POP3 CLIENT IP ADDRESS BLOCKED: %s",socket, host_ip);
		sockprintf(socket,"-ERR Access denied.");
	if(trashcan(&scfg,host_name,"host")) {
		lprintf(LOG_NOTICE,"%04d !POP3 CLIENT HOSTNAME BLOCKED: %s"
		sockprintf(socket,"-ERR Access denied.");
	protected_uint32_adjust(&active_clients, 1);

	/* Initialize client display */
	client.size=sizeof(client);
	SAFECOPY(client.addr,host_ip);
	SAFECOPY(client.host,host_name);
deuce's avatar
deuce committed
	client.port=inet_addrport(&pop3.client_addr);
	client.protocol="POP3";
	client_on(socket,&client,FALSE /* update */);
	SAFEPRINTF(str,"POP3: %s", host_ip);
rswindell's avatar
rswindell committed
	if(startup->login_attempt.throttle
		&& (login_attempts=loginAttempts(startup->login_attempt_list, &pop3.client_addr)) > 1) {
		lprintf(LOG_DEBUG,"%04d POP3 Throttling suspicious connection from: %s (%u login attempts)"
deuce's avatar
deuce committed
			,socket, host_ip, login_attempts);
rswindell's avatar
rswindell committed
		mswait(login_attempts*startup->login_attempt.throttle);
rswindell's avatar
rswindell committed
	mail=NULL;

	do {
		memset(&smb,0,sizeof(smb));
		memset(&msg,0,sizeof(msg));
		srand((unsigned int)(time(NULL) ^ (time_t)GetCurrentThreadId()));	/* seed random number generator */
		rand();	/* throw-away first result */
		safe_snprintf(challenge,sizeof(challenge),"<%x%x%lx%lx@%.128s>"
			,rand(),socket,(ulong)time(NULL),clock(),startup->host_name);

		sockprintf(socket,"+OK Synchronet POP3 Server %s-%s Ready %s"
			,revision,PLATFORM_DESC,challenge);
		/* Requires USER command first */
		for(i=3;i;i--) {
			if(!sockgetrsp(socket,NULL,buf,sizeof(buf)))
				break;
			if(!strnicmp(buf,"USER ",5))
				break;
			if(!strnicmp(buf,"APOP ",5)) {
				apop=TRUE;
			}
			sockprintf(socket,"-ERR USER or APOP command expected");
		if(!i || buf[0]==0)	/* no USER or APOP command received */
		if(apop) {
			if((response=strrchr(p,' '))!=NULL)
				*(response++)=0;
			else
				response=p;
		}
		SAFECOPY(username,p);
		if((p = strstr(username, NO_SPAM)) != NULL) {
			*p = 0;
			lm_mode = LM_NOSPAM;
		} else
			lm_mode = 0;
		if(!apop) {
			sockprintf(socket,"+OK");
			if(!sockgetrsp(socket,"PASS ",buf,sizeof(buf))) {
				sockprintf(socket,"-ERR PASS command expected");
				break;
			}
			p=buf+5;
			SAFECOPY(password,p);
		user.number=matchuser(&scfg,username,FALSE /*sysop_alias*/);
		if(!user.number) {
				lprintf(LOG_NOTICE,"%04d !POP3 UNKNOWN USER: '%s' (password: %s)"
				lprintf(LOG_NOTICE,"%04d !POP3 UNKNOWN USER: '%s'"
			badlogin(socket, client.protocol, pop_err, username, password, host_name, &pop3.client_addr);
			break;
		}
		if((i=getuserdat(&scfg, &user))!=0) {
			lprintf(LOG_ERR,"%04d !POP3 ERROR %d getting data on user (%s)"
				,socket, i, username);
			badlogin(socket, client.protocol, pop_err, NULL, NULL, NULL, NULL);
			break;
		}
		if(user.misc&(DELETED|INACTIVE)) {
			lprintf(LOG_NOTICE,"%04d !POP3 DELETED or INACTIVE user #%u (%s)"
				,socket, user.number, username);
			badlogin(socket, client.protocol, pop_err, NULL, NULL, NULL, NULL);
		if(apop) {
			strlwr(user.pass);	/* this is case-sensitive, so convert to lowercase */
			strcat(challenge,user.pass);
			MD5_calc(digest,challenge,strlen(challenge));
			MD5_hex((BYTE*)str,digest);
			if(strcmp(str,response)) {
				lprintf(LOG_NOTICE,"%04d !POP3 %s FAILED APOP authentication"
					,socket,username);
#if 0
				lprintf(LOG_DEBUG,"%04d !POP3 digest data: %s",socket,challenge);
				lprintf(LOG_DEBUG,"%04d !POP3 calc digest: %s",socket,str);
				lprintf(LOG_DEBUG,"%04d !POP3 resp digest: %s",socket,response);
				badlogin(socket, client.protocol, pop_err, username, response, host_name, &pop3.client_addr);
				break;
			}
		} else if(stricmp(password,user.pass)) {