Skip to content
Snippets Groups Projects
mailsrvr.c 210 KiB
Newer Older
/* Synchronet Mail (SMTP/POP3) server and sendmail threads */

/****************************************************************************
 * @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										*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * 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 <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"
#include "ssl.h"
#include "cryptlib.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);
#define pop_error		"-ERR System Error: %s, try again later"
#define pop_auth_error	"-ERR Authentication failure"
#define ok_rsp			"250 OK"
#define auth_ok			"235 User Authenticated"
#define smtp_error		"421 System Error: %s, try again later"
#define insuf_stor		"452 Insufficient system storage"
#define badarg_rsp 		"501 Bad argument"
#define badseq_rsp		"503 Bad sequence of commands"
#define badauth_rsp		"535 Authentication failure"
#define badrsp_err		"%s replied with:\r\n\"%s\"\r\ninstead 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;
static link_list_t current_logins;
static link_list_t current_connections;
static bool savemsg_mutex_created = false;
static pthread_mutex_t savemsg_mutex;

static const char* servprot_smtp = "SMTP";
static const char* servprot_submission = "SMTP";
static const char* servprot_submissions = "SMTPS";
static const char* servprot_pop3 = "POP3";
static const char* servprot_pop3s = "POP3S";
	volatile ulong	sockets;
	volatile ulong	errors;
	volatile ulong	crit_errors;
	volatile ulong	connections_exceeded;
	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;
deuce's avatar
deuce committed
#define GCES(status, server, sock, sess, action) do {                             \
deuce's avatar
deuce committed
	char *GCES_estr;                                                               \
	int GCES_level;                                                                 \
deuce's avatar
deuce committed
	get_crypt_error_string(status, sess, &GCES_estr, action, &GCES_level);  \
deuce's avatar
deuce committed
	if (GCES_estr) {                                                                  \
		lprintf(GCES_level, "%04d %s %s", sock, server, GCES_estr);                     \
deuce's avatar
deuce committed
		free_crypt_attrstr(GCES_estr);                                                  \
deuce's avatar
deuce committed
#define GCESH(status, server, sock, host, sess, action) do {                      \
deuce's avatar
deuce committed
	char *GCES_estr;                                                               \
	int GCES_level;                                                                 \
deuce's avatar
deuce committed
	get_crypt_error_string(status, sess, &GCES_estr, action, &GCES_level);  \
deuce's avatar
deuce committed
	if (GCES_estr) {                                                                  \
		lprintf(GCES_level, "%04d %s [%s] %s", sock, server, host, GCES_estr);         \
		free_crypt_attrstr(GCES_estr);                                                  \
	}                                                                                    \
} while(0)

#define GCESHL(status, server, sock, host, log_level, sess, action) do {                      \
	char *GCES_estr;                                                               \
	int GCES_level;                                                                 \
	get_crypt_error_string(status, sess, &GCES_estr, action, &GCES_level);  \
	if (GCES_estr) {                                                                  \
		lprintf(log_level, "%04d %s [%s] %s", sock, server, host, GCES_estr);         \
deuce's avatar
deuce committed
		free_crypt_attrstr(GCES_estr);                                                  \
#if defined(__GNUC__)   // Catch printf-format errors with lprintf
static int lprintf(int level, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
#endif
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, level, 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, condense_whitespace(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)
static char* server_host_name(void)
{
	return startup->host_name[0] ? startup->host_name : scfg.sys_inetaddr;
}

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(!update)
		listAddNodeData(&current_connections, client->addr, strlen(client->addr)+1, sock, LAST_NODE);
	if(startup!=NULL && startup->client_on!=NULL)
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
}

static void client_off(SOCKET sock)
{
	listRemoveTaggedNode(&current_connections, sock, /* free_data */TRUE);
	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 %s !ERROR %s", sock, protocol, 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--;
int mail_close_socket(SOCKET *sock, int *sess)
	if (*sess != -1) {
		cryptDestroySession(*sess);
		*sess = -1;
	}
	if(*sock==INVALID_SOCKET)
	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);
	return(result);
}

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
	    startup->status(startup->cbdata,str);
int sockprintf(SOCKET sock, const char* prot, CRYPT_SESSION sess, 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 %s TX: %.*s", sock, prot, len, sbuf);
	if(sock==INVALID_SOCKET) {
		lprintf(LOG_WARNING,"%s !INVALID SOCKET in call to sockprintf", prot);
	/* 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 %s !TIMEOUT selecting socket for send"
				,sock, prot);
			lprintf(LOG_NOTICE,"%04d %s !ERROR %d selecting socket for send"
				,sock, prot, ERROR_VALUE);
	if (sess != -1) {
		int tls_sent;
		int sent = 0;
		while (sent < len) {
			result = cryptPushData(sess, sbuf+sent, len-sent, &tls_sent);
			if (result == CRYPT_OK)
				sent += tls_sent;
				GCES(result, prot, sock, sess, "pushing data");
				return 0;
		if ((result = cryptFlushData(sess)) != CRYPT_OK) {
			GCES(result, prot, sock, sess, "flushing data");
			return 0;
	}
	else {
		// It looks like this could stutter on partial sends -- Deuce
		while((result=sendsocket(sock,sbuf,len))!=len) {
			if(result==SOCKET_ERROR) {
				if(ERROR_VALUE==EWOULDBLOCK) {
					YIELD();
					continue;
				}
				if(ERROR_VALUE==ECONNRESET) 
					lprintf(LOG_NOTICE,"%04d %s Connection reset by peer on send",sock,prot);
				else if(ERROR_VALUE==ECONNABORTED) 
					lprintf(LOG_NOTICE,"%04d %s Connection aborted by peer on send",sock, prot);
					lprintf(LOG_NOTICE,"%04d %s !ERROR %d sending on socket",sock,prot,ERROR_VALUE);
			lprintf(LOG_WARNING,"%04d %s !ERROR: short send on socket: %d instead of %d",sock,prot,result,len);
static void sockerror(SOCKET socket, const char* prot, int rd, const char* action)
		lprintf(LOG_NOTICE,"%04d %s Socket closed by peer on %s"
			,socket, prot, action);
	else if(rd==SOCKET_ERROR) {
rswindell's avatar
rswindell committed
		if(ERROR_VALUE==ECONNRESET) 
			lprintf(LOG_NOTICE,"%04d %s Connection reset by peer on %s"
				,socket, prot, action);
		else if(ERROR_VALUE==ECONNABORTED) 
			lprintf(LOG_NOTICE,"%04d %s Connection aborted by peer on %s"
				,socket, prot, action);
			lprintf(LOG_NOTICE,"%04d %s !SOCKET ERROR %d on %s"
				,socket, prot, ERROR_VALUE, action);
		lprintf(LOG_WARNING,"%04d %s !SOCKET ERROR: unexpected return value %d from %s"
			,socket, prot, rd, action);
static int sock_recvbyte(SOCKET sock, const char* prot, CRYPT_SESSION sess, char *buf, time_t start)
{
	int len=0;
	fd_set	socket_set;
	struct	timeval	tv;
	int ret;
	int i;

	if (sess > -1) {
		while (1) {
			ret = cryptPopData(sess, buf, 1, &len);
			GCES(ret, prot, sock, sess, "popping data");
			switch(ret) {
				case CRYPT_OK:
					break;
				case CRYPT_ERROR_TIMEOUT:
					return -1;
				case CRYPT_ERROR_COMPLETE:
					return 0;
				default:
					if (ret < -1)
						return ret;
					return -2;
			}
			if (len)
				return len;
			if(startup->max_inactivity && (time(NULL)-start)>startup->max_inactivity) {
				lprintf(LOG_WARNING,"%04d %s !TIMEOUT in sock_recvbyte (%u seconds):  INACTIVE SOCKET"
					,sock, prot, startup->max_inactivity);
				return(-1);
			}
		}
	}
	else {
		tv.tv_sec=startup->max_inactivity;
		tv.tv_usec=0;

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

		i=select(sock+1,&socket_set,NULL,NULL,&tv);
		if(i<1) {
			if(i==0) {
				if(startup->max_inactivity && (time(NULL)-start)>startup->max_inactivity) {
					lprintf(LOG_WARNING,"%04d %s !TIMEOUT in sock_recvbyte (%u seconds):  INACTIVE SOCKET"
						,sock, prot, startup->max_inactivity);
					return(-1);
				}
				return 0;
			}
			return 0;
		}
		i=recv(sock, buf, 1, 0);
		if(i<1)
static int sockreadline(SOCKET socket, const char* prot, CRYPT_SESSION sess, char* buf, int len)
{
	char	ch;
	int		i,rd=0;
	time_t	start;

	start=time(NULL);

	if(socket==INVALID_SOCKET) {
		lprintf(LOG_WARNING,"%s !INVALID SOCKET in call to sockreadline", prot);
deuce's avatar
deuce committed
		if(terminated || terminate_server) {
			lprintf(LOG_WARNING,"%04d %s !ABORTING sockreadline",socket, prot);
		i = sock_recvbyte(socket, prot, sess, &ch, start);
		if(ch=='\n' /* && rd>=1 */ ) { /* Mar-9-2003: terminate on sole LF */
static BOOL sockgetrsp(SOCKET socket, const char* prot, CRYPT_SESSION sess, char* rsp, char *buf, int len)
		rd = sockreadline(socket, prot, sess, buf, len);
				lprintf(LOG_WARNING,"%04d %s !RECEIVED BLANK RESPONSE, Expected '%s'", socket, prot, rsp);
			return(FALSE);
		if(buf[3]=='-')	{ /* Multi-line response */
			if(startup->options&MAIL_OPT_DEBUG_RX_RSP) 
				lprintf(LOG_DEBUG,"%04d %s RX: %s",socket, prot, buf);
		if(rsp!=NULL && strnicmp(buf,rsp,strlen(rsp))) {
			lprintf(LOG_WARNING,"%04d %s !INVALID RESPONSE: '%s' Expected: '%s'", socket, prot, buf, rsp);
			return(FALSE);
		}
		break;
	}
	if(startup->options&MAIL_OPT_DEBUG_RX_RSP) 
		lprintf(LOG_DEBUG,"%04d %s RX: %s",socket, prot, buf);
static int sockgetrsp_opt(SOCKET socket, const char* prot, CRYPT_SESSION sess, char* rsp, char *opt, char *buf, int len)
{
	int rd;
	int ret = 0;
	size_t moptlen;
	char *mopt;

	moptlen = strlen(rsp)+strlen(opt) + 1;
	mopt = malloc(moptlen+1);
	if (mopt == NULL)
		return -1;
	sprintf(mopt, "%s-%s", rsp, opt);
	while(1) {
		rd = sockreadline(socket, prot, sess, buf, len);
		if(rd<1) {
			if(rd==0)
				lprintf(LOG_WARNING,"%04d %s !RECEIVED BLANK RESPONSE, Expected '%s'", socket, prot, rsp);
			free(mopt);
			return(-1);
		}
		if(buf[3]=='-')	{ /* Multi-line response */
			if (strncmp(buf, mopt, moptlen) == 0)
				ret = 1;
			if(startup->options&MAIL_OPT_DEBUG_RX_RSP) 
				lprintf(LOG_DEBUG,"%04d %s RX: %s",socket, prot, buf);
			continue;
		}
		if(rsp!=NULL && strnicmp(buf,rsp,strlen(rsp))) {
			lprintf(LOG_WARNING,"%04d %s !INVALID RESPONSE: '%s' Expected: '%s'", socket, prot, buf, rsp);
			free(mopt);
			return(-1);
		}
		break;
	}
	if (strncmp(buf, mopt, moptlen) == 0)
	free(mopt);
	if(startup->options&MAIL_OPT_DEBUG_RX_RSP)
		lprintf(LOG_DEBUG,"%04d %s RX: %s",socket, prot, buf);
rswindell's avatar
rswindell committed
/* non-standard, but documented (mostly) in draft-newman-msgheader-originfo-05 */
void originator_info(SOCKET socket, const char* protocol, CRYPT_SESSION sess, smbmsg_t* msg)
rswindell's avatar
rswindell committed
{
	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)
rswindell's avatar
rswindell committed
			,"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
			);
}

char* angle_bracket(char* buf, size_t max, const char* addr)
{
	if(*addr == '<')
		safe_snprintf(buf, max, "%s", addr);
	else
		safe_snprintf(buf, max, "<%s>", addr);
	return buf;
}

int compare_addrs(const char* addr1, const char* addr2)
{
	char tmp1[256];
	char tmp2[256];

	return strcmp(angle_bracket(tmp1, sizeof(tmp1), addr1), angle_bracket(tmp2, sizeof(tmp2), addr2));
}

/* 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, const char* prot, CRYPT_SESSION sess, 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];
rswindell's avatar
rswindell committed
	char*		p;
	/* HEADERS (in recommended order per RFC822 4.1) */
		if(!sockprintf(socket,prot,sess,"Return-Path: %s", msg->reverse_path))
	for(i=0;i<msg->total_hfields;i++)
		if(msg->hfield[i].type == SMTPRECEIVED && msg->hfield_dat[i]!=NULL) 
			if(!sockprintf(socket,prot,sess,"Received: %s", (char*)msg->hfield_dat[i]))
	if(!sockprintf(socket,prot,sess,"Date: %s",msgdate(msg->hdr.when_written,date)))

	if((p=smb_get_hfield(msg,RFC822FROM,NULL))!=NULL)
		s=sockprintf(socket,prot,sess,"From: %s",p);	/* use original RFC822 header field */
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);
		s = sockprintf(socket,prot,sess,"From: %s %s", fromname, angle_bracket(tmp, sizeof(tmp), fromaddr));
	if((p = smb_get_hfield(msg, RFC822ORG, NULL)) != NULL) {
		if(!sockprintf(socket,prot,sess,"Organization: %s", p))
	} else {
		if(msg->from_org!=NULL || msg->from_net.type==NET_NONE)
			if(!sockprintf(socket,prot,sess,"Organization: %s"
				,msg->from_org==NULL ? scfg.sys_name : msg->from_org))
				return(0);
	}
	p = smb_get_hfield(msg, RFC822SUBJECT, NULL);
	if(!sockprintf(socket,prot,sess,"Subject: %s", p == NULL ? msg->subj : p))
	if((p = smb_get_hfield(msg, RFC822TO, NULL)) != NULL)
		s=sockprintf(socket,prot,sess,"To: %s", p);	/* use original RFC822 header field (MIME-Encoded) */
	else if((p = msg->to_list) != NULL)
		s=sockprintf(socket,prot,sess,"To: %s", p);	/* use original RFC822 header field */
	else {
		if(strchr(msg->to,'@')!=NULL || msg->to_net.addr==NULL)
			s=sockprintf(socket,prot,sess,"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,prot,sess,"To: %s",(char*)msg->to_net.addr);
				s=sockprintf(socket,prot,sess,"To: \"%s\" <%s>",msg->to,(char*)msg->to_net.addr);
rswindell's avatar
rswindell committed
		} else if(msg->to_net.type==NET_FIDO) {
			s=sockprintf(socket,prot,sess,"To: \"%s\" (%s)",msg->to, smb_faddrtoa((fidoaddr_t*)msg->to_net.addr, NULL));
		} else {
			usermailaddr(&scfg,toaddr,msg->to);
			s=sockprintf(socket,prot,sess,"To: \"%s\" <%s>",msg->to,toaddr);
	if((p = smb_get_hfield(msg, RFC822CC, NULL)) != NULL || msg->cc_list != NULL)
		if(!sockprintf(socket,prot,sess,"Cc: %s", p == NULL ? msg->cc_list : p))
	p = smb_get_hfield(msg, RFC822REPLYTO, NULL);
	if(p == NULL && (p = msg->replyto_list) == NULL) {
		if(msg->replyto_net.type==NET_INTERNET)
rswindell's avatar
rswindell committed
			p=msg->replyto_net.addr;
	if(p!=NULL && strchr((char*)p, '@') != NULL) {
			s=sockprintf(socket,prot,sess,"Reply-To: \"%s\" <%s>",np,p);
			s=sockprintf(socket,prot,sess,"Reply-To: %s",p);
	if(!sockprintf(socket,prot,sess,"Message-ID: %s",get_msgid(&scfg,INVALID_SUB,msg,msgid,sizeof(msgid))))
		if(!sockprintf(socket,prot,sess,"In-Reply-To: %s",msg->reply_id))
	if(msg->hdr.priority != SMB_PRIORITY_UNSPECIFIED)
		if(!sockprintf(socket,prot,sess,"X-Priority: %u", (uint)msg->hdr.priority))
			return(0);

	originator_info(socket, prot, sess, 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, prot, sess, "%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,prot, sess,"%s",(char*)msg->hfield_dat[i]))
	/* Default MIME Content-Type for non-Internet messages */
	if(msg->from_net.type!=NET_INTERNET && content_type==NULL) {
		const char* charset =  smb_msg_is_utf8(msg) ? "UTF-8" : startup->default_charset;
		if(*charset)
			sockprintf(socket,prot,sess,"Content-Type: text/plain; charset=%s", charset);
		sockprintf(socket,prot,sess,"Content-Transfer-Encoding: 8bit");
	if(strListCount(file_list)) {	/* File attachments */
        mimeheaders(socket,prot,sess,mime_boundary);
        sockprintf(socket,prot,sess,"");
        mimeblurb(socket,prot,sess,mime_boundary);
        sockprintf(socket,prot,sess,"");
        mimetextpartheader(socket,prot,sess,mime_boundary, startup->default_charset);
	if(!sockprintf(socket,prot,sess,""))	/* Header Terminator */
	/* 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,prot,sess, "%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))	
			lprintf(LOG_DEBUG,"%04d %s sent %lu lines (%ld bytes) of body text"
				,socket, prot, lines, (long)(np-msgtxt));
	lprintf(LOG_DEBUG,"%04d %s sent %lu lines (%ld bytes) of body text"
		,socket, prot, lines, (long)(np-msgtxt));
	if(file_list!=NULL) {
		for(i=0;file_list[i];i++) { 
			sockprintf(socket,prot,sess,"");
			lprintf(LOG_INFO,"%04u %s MIME Encoding and sending %s", socket, prot, file_list[i]);
			if(!mimeattach(socket,prot,sess,mime_boundary,file_list[i]))
				lprintf(LOG_ERR,"%04u %s !ERROR opening/encoding/sending %s", socket, prot, file_list[i]);
				endmime(socket,prot,sess,mime_boundary);
				if(msg->hdr.auxattr&MSG_KILLFILE)
					if(remove(file_list[i])!=0)
rswindell's avatar
rswindell committed
						lprintf(LOG_WARNING,"%04u %s !ERROR %d (%s) removing %s", socket, prot, errno, strerror(errno), file_list[i]);
    sockprintf(socket,prot,sess,".");	/* End of text */
static ulong sockmsgtxt(SOCKET socket, const char* prot, CRYPT_SESSION sess, 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 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,prot,sess,msg,msgtxt,maxlines,file_list,boundary);

	strListFree(&file_list);

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

	return(retval);
}

static u_long resolve_ip(const char *inaddr)
	if(*addr=='[' && *(p=lastchar(addr))==']') { /* Support [ip_address] notation */
		if(*p!='.' && !IS_DIGIT(*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.				*/
/****************************************************************************/

static void badlogin(SOCKET sock, CRYPT_SESSION sess, 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];
	
	if(user == NULL)
		user = "<unspecified>";
		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));
		if(startup->login_attempt.filter_threshold && count>=startup->login_attempt.filter_threshold) {
			SAFEPRINTF(reason, "- TOO MANY CONSECUTIVE FAILED LOGIN ATTEMPTS (%lu)", count);
			filter_ip(&scfg, (char*)prot, reason ,host, ip, user, /* fname: */NULL);
		}
rswindell's avatar
rswindell committed
	mswait(startup->login_attempt.delay);
	sockprintf(sock,prot,sess, "%s", resp);

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		server_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;
	CRYPT_SESSION	session = -1;
	BOOL nodelay=TRUE;
	ulong nb = 0;
	char *estr;
	int level;
	int stat;
	client.protocol = pop3.tls_port ? "POP3S" : "POP3";

	if(startup->options&MAIL_OPT_DEBUG_POP3)
		lprintf(LOG_DEBUG,"%04d %s session thread started", socket, client.protocol);
#ifdef _WIN32
	if(startup->pop3_sound[0] && !(startup->options&MAIL_OPT_MUTE)) 
		PlaySound(startup->pop3_sound, NULL, SND_ASYNC|SND_FILENAME);
	socklen_t addr_len = sizeof(server_addr);
	if((i=getsockname(socket, &server_addr.addr, &addr_len))!=0) {
		lprintf(LOG_CRIT,"%04d %s !ERROR %d (%d) getting address/port"
			,socket, client.protocol, i, ERROR_VALUE);
		mail_close_socket(&socket, &session);
		thread_down();
		return;
	}

deuce's avatar
deuce committed
	inet_addrtop(&pop3.client_addr, host_ip, sizeof(host_ip));
	inet_addrtop(&server_addr, server_ip, sizeof(server_ip));

	if(startup->options&MAIL_OPT_DEBUG_POP3)
		lprintf(LOG_INFO,"%04d %s [%s] connection accepted on %s port %u from port %u"
			,socket, client.protocol, host_ip, server_ip, inet_addrport(&server_addr), inet_addrport(&pop3.client_addr));
	SAFECOPY(host_name, STR_NO_HOSTNAME);
	if(!(startup->options&MAIL_OPT_NO_HOST_LOOKUP)) {
		getnameinfo(&pop3.client_addr.addr, pop3.client_addr_len, host_name, sizeof(host_name), NULL, 0, NI_NAMEREQD);
		if(startup->options&MAIL_OPT_DEBUG_POP3)
			lprintf(LOG_INFO,"%04d %s [%s] Hostname: %s", socket, client.protocol, host_ip, host_name);
		if (get_ssl_cert(&scfg, &estr, &level) == -1) {
			if (estr) {
				lprintf(level, "%04d %s [%s] !Failure getting certificate: %s", socket, client.protocol, host_ip, estr);
				free_crypt_attrstr(estr);
			mail_close_socket(&socket, &session);
		if ((stat=cryptCreateSession(&session, CRYPT_UNUSED, CRYPT_SESSION_SSL_SERVER)) != CRYPT_OK) {
			GCESH(stat, client.protocol, socket, host_ip, CRYPT_UNUSED, "creating session");
			mail_close_socket(&socket, &session);
		if ((stat=cryptSetAttribute(session, CRYPT_SESSINFO_SSL_OPTIONS, CRYPT_SSLOPTION_DISABLE_CERTVERIFY)) != CRYPT_OK) {
			GCESH(stat, client.protocol, socket, host_ip, session, "disabling certificate verification");
			mail_close_socket(&socket, &session);
		if ((stat=cryptSetAttribute(session, CRYPT_SESSINFO_PRIVATEKEY, scfg.tls_certificate)) != CRYPT_OK) {
			GCESH(stat, client.protocol, socket, host_ip, session, "setting private key");
			mail_close_socket(&socket, &session);
			thread_down();
			return;
		}
		nodelay = TRUE;
		setsockopt(socket,IPPROTO_TCP,TCP_NODELAY,(char*)&nodelay,sizeof(nodelay));
		nb=0;
		ioctlsocket(socket,FIONBIO,&nb);
		if ((stat = cryptSetAttribute(session, CRYPT_SESSINFO_NETWORKSOCKET, socket)) != CRYPT_OK) {
			GCESH(stat, client.protocol, socket, host_ip, session, "setting session socket");
			mail_close_socket(&socket, &session);
		if ((stat = cryptSetAttribute(session, CRYPT_SESSINFO_ACTIVE, 1)) != CRYPT_OK) {
			GCESH(stat, client.protocol, socket, host_ip, session, "setting session active");
			mail_close_socket(&socket, &session);
		if (startup->max_inactivity) {
			if (cryptSetAttribute(session, CRYPT_OPTION_NET_READTIMEOUT, startup->max_inactivity) != CRYPT_OK) {
				GCESH(stat, client.protocol, socket, host_ip, session, "setting read timeout");
				mail_close_socket(&socket, &session);
	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 %s [%s] !TEMPORARY BAN (%lu login attempts, last: %s) - remaining: %s"
				,socket, client.protocol, host_ip, attempted.count-attempted.dupes, attempted.user, seconds_to_str(banned, ban_duration));
			lprintf(LOG_NOTICE,"%04d %s [%s] !CLIENT IP ADDRESS BLOCKED",socket, client.protocol, host_ip);
		sockprintf(socket,client.protocol,session,"-ERR Access denied.");
		mail_close_socket(&socket, &session);
	if(trashcan(&scfg,host_name,"host")) {
		lprintf(LOG_NOTICE,"%04d %s [%s] !CLIENT HOSTNAME BLOCKED: %s"
			,socket, client.protocol, host_ip, host_name);
		sockprintf(socket,client.protocol,session,"-ERR Access denied.");
		mail_close_socket(&socket, &session);
	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_on(socket,&client,FALSE /* update */);
	SAFEPRINTF2(str,"%s: %s", client.protocol, 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 %s [%s] Throttling suspicious connection (%lu login attempts)"
			,socket, client.protocol, 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),(ulong)clock(), server_host_name());
		sockprintf(socket,client.protocol,session,"+OK Synchronet %s Server %s-%s Ready %s"
			,client.protocol, revision,PLATFORM_DESC,challenge);
		/* Requires USER or APOP command first */
		for(i=5;i;i--) {
			if(!sockgetrsp(socket,client.protocol,session,NULL,buf,sizeof(buf)))
				break;
			if(!strnicmp(buf,"USER ",5))
				break;
			else if(!strnicmp(buf,"APOP ",5)) {
			else if (!stricmp(buf, "CAPA")) {
				// Capabilities
				sockprintf(socket,client.protocol,session, "+OK Capability list follows");
				sockprintf(socket,client.protocol,session, "TOP\r\nUSER\r\nPIPELINING\r\nUIDL\r\nIMPLEMENTATION Synchronet POP3 Server %s-%s\r\n%s.", revision, PLATFORM_DESC, (session != -1 || get_ssl_cert(&scfg, NULL, NULL) == -1) ? "" : "STLS\r\n");
				i++;
			}
			else if (!stricmp(buf, "STLS")) {
				if (get_ssl_cert(&scfg, &estr, &level) == -1) {
					if (estr) {
						lprintf(level, "%04d %s [%s] !TLS Failure getting certificate: %s", socket, client.protocol, host_ip, estr);
					sockprintf(socket,client.protocol,session,"-ERR STLS command not supported");
				sockprintf(socket,client.protocol,session,"+OK Begin TLS negotiation");
				if ((stat=cryptCreateSession(&session, CRYPT_UNUSED, CRYPT_SESSION_SSL_SERVER)) != CRYPT_OK) {
					GCESH(stat, client.protocol, socket, host_ip, CRYPT_UNUSED, "creating session");
					buf[0] = 0;
					break;
				}
				if ((stat=cryptSetAttribute(session, CRYPT_SESSINFO_SSL_OPTIONS, CRYPT_SSLOPTION_DISABLE_CERTVERIFY)) != CRYPT_OK) {
					GCESH(stat, client.protocol, socket, host_ip, session, "disabling certificate verification");
					buf[0] = 0;
					break;
				}
				if ((stat=cryptSetAttribute(session, CRYPT_SESSINFO_PRIVATEKEY, scfg.tls_certificate)) != CRYPT_OK) {
					GCESH(stat, client.protocol, socket, host_ip, session, "setting private key");
					buf[0] = 0;
					break;
				}
				nodelay = TRUE;
				setsockopt(socket,IPPROTO_TCP,TCP_NODELAY,(char*)&nodelay,sizeof(nodelay));
				nb=0;
				ioctlsocket(socket,FIONBIO,&nb);
				if ((stat = cryptSetAttribute(session, CRYPT_SESSINFO_NETWORKSOCKET, socket)) != CRYPT_OK) {
					GCESH(stat, client.protocol, socket, host_ip, session, "setting network socket");
					buf[0] = 0;
					break;
				}
				if ((stat=cryptSetAttribute(session, CRYPT_SESSINFO_ACTIVE, 1)) != CRYPT_OK) {
					GCESH(stat, client.protocol, socket, host_ip, session, "setting session active");
					buf[0] = 0;
					break;
				}
				if (startup->max_inactivity) {
					if ((stat=cryptSetAttribute(session, CRYPT_OPTION_NET_READTIMEOUT, startup->max_inactivity)) != CRYPT_OK) {
						GCESH(stat, client.protocol, socket, host_ip, session, "setting read timeout");
				sockprintf(socket,client.protocol,session,"-ERR USER, APOP, CAPA, or STLS 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;
			sockprintf(socket,client.protocol,session,"+OK");
			if(!sockgetrsp(socket,client.protocol,session,"PASS ",buf,sizeof(buf))) {
				sockprintf(socket,client.protocol,session,"-ERR PASS command expected");
			SAFECOPY(password,p);
		user.number=matchuser(&scfg,username,FALSE /*sysop_alias*/);
		if(!user.number) {
				lprintf(LOG_NOTICE,"%04d %s [%s] !UNKNOWN USER: '%s' (password: %s)"
					,socket, client.protocol, host_ip, username, password);
				lprintf(LOG_NOTICE,"%04d %s [%s] !UNKNOWN USER: '%s'"
					,socket, client.protocol, host_ip, username);
			badlogin(socket, session, client.protocol, pop_auth_error, username, password, host_name, &pop3.client_addr);
			break;
		}
		if((i=getuserdat(&scfg, &user))!=0) {
			lprintf(LOG_ERR,"%04d %s [%s] !ERROR %d getting data on user (%s)"
				,socket, client.protocol, host_ip, i, username);
			break;
		}
		if(user.misc&(DELETED|INACTIVE)) {
			lprintf(LOG_NOTICE,"%04d %s [%s] !DELETED or INACTIVE user #%u (%s)"
				,socket, client.protocol, host_ip, user.number, username);
			badlogin(socket, session, client.protocol, pop_auth_error, username, password, 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 %s [%s] !FAILED APOP authentication: %s"
					,socket, client.protocol, host_ip, username);
				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, session, client.protocol, pop_auth_error, username, response, host_name, &pop3.client_addr);
				break;
			}
		} else if(stricmp(password,user.pass)) {
				lprintf(LOG_NOTICE,"%04d %s [%s] !FAILED Password attempt for user %s: '%s' expected '%s'"
					,socket, client.protocol, host_ip, username, password, user.pass);
				lprintf(LOG_NOTICE,"%04d %s [%s] !FAILED Password attempt for user %s"
					,socket, client.protocol, host_ip, username);
			badlogin(socket, session, client.protocol, pop_auth_error, username, password, host_name, &pop3.client_addr);
			loginSuccess(startup->login_attempt_list, &pop3.client_addr);
			listAddNodeData(&current_logins, client.addr, strlen(client.addr) + 1, socket, LAST_NODE);
		}
		putuserrec(&scfg,user.number,U_COMP,LEN_COMP,host_name);
deuce's avatar
deuce committed
		putuserrec(&scfg,user.number,U_IPADDR,LEN_IPADDR,host_ip);

		/* Update client display */
		client.user=user.alias;
		client_on(socket,&client,TRUE /* update */);
		if(startup->options&MAIL_OPT_DEBUG_POP3)
			lprintf(LOG_INFO,"%04d %s [%s] %s logged-in %s", socket, client.protocol, host_ip, user.alias, apop ? "via APOP":"");
		SAFEPRINTF2(str,"%s: %s", client.protocol, user.alias);
		SAFEPRINTF(smb.file,"%smail",scfg.data_dir);
			lprintf(LOG_WARNING,"%04d %s <%s> !MAIL BASE LOCKED: %s",socket, client.protocol, user.alias, smb.last_error);
			sockprintf(socket,client.protocol,session,"-ERR database locked, try again later");
			break;
		}
		smb.retry_time=scfg.smb_retry_time;
		smb.subnum=INVALID_SUB;
		if((i=smb_open(&smb))!=SMB_SUCCESS) {
			lprintf(LOG_ERR,"%04d %s <%s> !ERROR %d (%s) opening %s", socket, client.protocol, user.alias, i, smb.last_error,smb.file);
			sockprintf(socket,client.protocol,session,"-ERR %d opening %s",i,smb.file);
		mail=loadmail(&smb,&msgs,user.number,MAIL_YOUR,lm_mode);

		for(l=bytes=0;l<msgs;l++) {
			msg.hdr.number=mail[l].number;
			if((i=smb_getmsgidx(&smb,&msg))!=SMB_SUCCESS) {
				lprintf(LOG_ERR,"%04d %s <%s> !ERROR %d (%s) getting message index"
					,socket, client.protocol, user.alias, i, smb.last_error);
			if((i=smb_lockmsghdr(&smb,&msg))!=SMB_SUCCESS) {
				lprintf(LOG_WARNING,"%04d %s <%s> !ERROR %d (%s) locking message header #%u"
					,socket, client.protocol, user.alias, i, smb.last_error, msg.hdr.number);
				break; 
			}
			i=smb_getmsghdr(&smb,&msg);
			smb_unlockmsghdr(&smb,&msg);
			if(i!=0) {
				lprintf(LOG_ERR,"%04d %s <%s> !ERROR %d (%s) line %u, msg #%u"
					,socket, client.protocol, user.alias, i, smb.last_error, __LINE__, msg.idx.number);
			smb_freemsgmem(&msg);
			sockprintf(socket,client.protocol,session,"-ERR message #%d: %d (%s)"
				,mail[l].number,i,smb.last_error);
			break;
		}

		sockprintf(socket,client.protocol,session,"+OK %u messages (%lu bytes)",msgs,bytes);
		while(1) {	/* TRANSACTION STATE */
			rd = sockreadline(socket, client.protocol, session, buf, sizeof(buf));
			if(startup->options&MAIL_OPT_DEBUG_POP3)
				lprintf(LOG_DEBUG,"%04d %s RX: %s", socket, client.protocol, buf);
			if(!stricmp(buf, "NOOP")) {
				sockprintf(socket,client.protocol,session,"+OK");
			if(!stricmp(buf, "CAPA")) {
				// Capabilities
				sockprintf(socket,client.protocol,session, "+OK Capability list follows");
				sockprintf(socket,client.protocol,session, "TOP\r\nUSER\r\nPIPELINING\r\nUIDL\r\nIMPLEMENTATION Synchronet POP3 Server %s-%s\r\n.", revision, PLATFORM_DESC);
				continue;
			}
			if(!stricmp(buf, "QUIT")) {
				sockprintf(socket,client.protocol,session,"+OK");
				break;
			}
			if(!stricmp(buf, "STAT")) {
				sockprintf(socket,client.protocol,session,"+OK %u %lu",msgs,bytes);
				continue;
			}
			if(!stricmp(buf, "RSET")) {
				if((i=smb_locksmbhdr(&smb))!=SMB_SUCCESS) {
					lprintf(LOG_ERR,"%04d %s <%s> !ERROR %d (%s) locking message base"
						,socket, client.protocol, user.alias, i, smb.last_error);
					sockprintf(socket,client.protocol,session,"-ERR %d locking message base",i);
				for(l=0;l<msgs;l++) {
					msg.hdr.number=mail[l].number;
					if((i=smb_getmsgidx(&smb,&msg))!=SMB_SUCCESS) {
						lprintf(LOG_ERR,"%04d %s <%s> !ERROR %d (%s) getting message index"
							,socket, client.protocol, user.alias, i, smb.last_error);
					if((i=smb_lockmsghdr(&smb,&msg))!=SMB_SUCCESS) {
						lprintf(LOG_WARNING,"%04d %s <%s> !ERROR %d (%s) locking message header #%u"
							,socket, client.protocol, user.alias, i, smb.last_error, msg.hdr.number);
					if((i=smb_getmsghdr(&smb,&msg))!=SMB_SUCCESS) {
						lprintf(LOG_ERR,"%04d %s <%s> !ERROR %d (%s) line %u, msg #%u"
							,socket, client.protocol, user.alias, i, smb.last_error, __LINE__, msg.idx.number);
						break;
					}
					msg.hdr.attr=mail[l].attr;
					if((i=smb_putmsg(&smb,&msg))!=SMB_SUCCESS)
						lprintf(LOG_ERR,"%04d %s <%s> !ERROR %d (%s) updating message index"
							,socket, client.protocol, user.alias, i, smb.last_error);
					smb_unlockmsghdr(&smb,&msg);
					smb_freemsgmem(&msg);
				}
					sockprintf(socket,client.protocol,session,"-ERR %d messages reset (ERROR: %d)",l,i);
					sockprintf(socket,client.protocol,session,"+OK %u messages (%lu bytes)",msgs,bytes);
				continue;
			}
			if(!strnicmp(buf, "LIST",4) || !strnicmp(buf,"UIDL",4)) {
				p=buf+4;
					msgnum=strtoul(p, NULL, 10);
					if(msgnum<1 || msgnum>msgs) {
						lprintf(LOG_NOTICE,"%04d %s <%s> !INVALID message #%" PRIu32
							,socket, client.protocol, user.alias, msgnum);
						sockprintf(socket,client.protocol,session,"-ERR no such message");
						continue;
					}
					msg.hdr.number=mail[msgnum-1].number;
					if((i=smb_getmsgidx(&smb,&msg))!=SMB_SUCCESS) {
						lprintf(LOG_ERR,"%04d %s <%s> !ERROR %d (%s) getting message index"
							,socket, client.protocol, user.alias, i, smb.last_error);
						sockprintf(socket,client.protocol,session,"-ERR %d getting message index",i);
						break;
					}
					if(msg.idx.attr&MSG_DELETE) {
						lprintf(LOG_NOTICE,"%04d %s <%s> !ATTEMPT to list DELETED message"
							,socket, client.protocol, user.alias);
						sockprintf(socket,client.protocol,session,"-ERR message deleted");
					if((i=smb_lockmsghdr(&smb,&msg))!=SMB_SUCCESS) {
						lprintf(LOG_WARNING,"%04d %s <%s> !ERROR %d (%s) locking message header #%u"
							,socket, client.protocol, user.alias, i, smb.last_error, msg.hdr.number);
						sockprintf(socket,client.protocol,session,"-ERR %d locking message header",i);
						continue; 
					}
					i=smb_getmsghdr(&smb,&msg);
					smb_unlockmsghdr(&smb,&msg);
					if(i!=0) {
						smb_freemsgmem(&msg);
						lprintf(LOG_ERR,"%04d %s <%s> !ERROR %d (%s) line %u, msg #%u"
							,socket, client.protocol, user.alias, i, smb.last_error, __LINE__, msg.idx.number);
						sockprintf(socket,client.protocol,session,"-ERR %d getting message header",i);
						continue;
					}
					if(!strnicmp(buf, "LIST",4)) {
						sockprintf(socket,client.protocol,session,"+OK %" PRIu32 " %lu",msgnum,smb_getmsgtxtlen(&msg));
					} else /* UIDL */
						sockprintf(socket,client.protocol,session,"+OK %" PRIu32 " %u",msgnum,msg.hdr.number);

					smb_freemsgmem(&msg);
					continue;
				}
				/* List ALL messages */
				sockprintf(socket,client.protocol,session,"+OK %u messages (%lu bytes)",msgs,bytes);
				for(l=0;l<msgs;l++) {
					msg.hdr.number=mail[l].number;
					if((i=smb_getmsgidx(&smb,&msg))!=SMB_SUCCESS) {
						lprintf(LOG_ERR,"%04d %s <%s> !ERROR %d (%s) getting message index"
							,socket, client.protocol, user.alias, i, smb.last_error);
						break;
					}
					if(msg.idx.attr&MSG_DELETE) 
						continue;
					if((i=smb_lockmsghdr(&smb,&msg))!=SMB_SUCCESS) {
						lprintf(LOG_WARNING,"%04d %s <%s> !ERROR %d (%s) locking message header #%u"
							,socket, client.protocol, user.alias, i, smb.last_error, msg.hdr.number);
						break; 
					}
					i=smb_getmsghdr(&smb,&msg);
					smb_unlockmsghdr(&smb,&msg);
					if(i!=0) {
						smb_freemsgmem(&msg);
						lprintf(LOG_ERR,"%04d %s <%s> !ERROR %d (%s) line %u, msg #%u"
							,socket, client.protocol, user.alias, i, smb.last_error, __LINE__, msg.idx.number);
						break;
					}
					if(!strnicmp(buf, "LIST",4)) {
						sockprintf(socket,client.protocol,session,"%u %lu",l+1,smb_getmsgtxtlen(&msg));
					} else /* UIDL */
						sockprintf(socket,client.protocol,session,"%u %u",l+1,msg.hdr.number);
				sockprintf(socket,client.protocol,session,".");
			if(!strnicmp(buf, "RETR ",5) || !strnicmp(buf,"TOP ",4)) {
				SAFEPRINTF2(str,"%s: %s", client.protocol, user.alias);
				status(str);

				lines=-1;
				p=buf+4;
				msgnum=strtoul(p, NULL, 10);

				if(!strnicmp(buf,"TOP ",4)) {
					lines=atol(p);
				}
				if(msgnum<1 || msgnum>msgs) {
					lprintf(LOG_NOTICE,"%04d %s <%s> !ATTEMPTED to retrieve an INVALID message #%" PRIu32
						,socket, client.protocol, user.alias, msgnum);
					sockprintf(socket,client.protocol,session,"-ERR no such message");
					continue;
				}
				msg.hdr.number=mail[msgnum-1].number;

				lprintf(LOG_INFO,"%04d %s <%s> retrieving message #%u with command: %s"
					,socket, client.protocol, user.alias, msg.hdr.number, buf);
				if((i=smb_getmsgidx(&smb,&msg))!=SMB_SUCCESS) {
					lprintf(LOG_ERR,"%04d %s <%s> !ERROR %d (%s) getting message index"
						,socket, client.protocol, user.alias, i, smb.last_error);
					sockprintf(socket,client.protocol,session,"-ERR %d getting message index",i);
					continue;
				}
				if(msg.idx.attr&MSG_DELETE) {
					lprintf(LOG_NOTICE,"%04d %s <%s> !ATTEMPT to retrieve DELETED message"
						,socket, client.protocol, user.alias);
					sockprintf(socket,client.protocol,session,"-ERR message deleted");
				if((i=smb_lockmsghdr(&smb,&msg))!=SMB_SUCCESS) {
					lprintf(LOG_WARNING,"%04d %s <%s> !ERROR %d (%s) locking message header #%u"
						,socket, client.protocol, user.alias, i, smb.last_error, msg.hdr.number);
					sockprintf(socket,client.protocol,session,"-ERR %d locking message header",i);
				i=smb_getmsghdr(&smb,&msg);
				smb_unlockmsghdr(&smb,&msg);
				if(i!=0) {
					lprintf(LOG_ERR,"%04d %s <%s> !ERROR %d (%s) line %u, msg #%u"
						,socket, client.protocol, user.alias, i, smb.last_error, __LINE__, msg.idx.number);
					sockprintf(socket,client.protocol,session,"-ERR %d getting message header",i);
				if((msgtxt=smb_getmsgtxt(&smb,&msg,GETMSGTXT_ALL))==NULL) {
					smb_freemsgmem(&msg);
					lprintf(LOG_ERR,"%04d %s <%s> !ERROR (%s) retrieving message #%u text"
						,socket, client.protocol, user.alias, smb.last_error, msg.hdr.number);
					sockprintf(socket,client.protocol,session,"-ERR retrieving message text");
rswindell's avatar
rswindell committed
				if(lines > 0					/* Works around BlackBerry mail server */
					&& lines >= strlen(msgtxt))	/* which requests the number of bytes (instead of lines) using TOP */
					lines=-1;					

				sockprintf(socket,client.protocol,session,"+OK message follows");
				lprintf(LOG_DEBUG,"%04d %s <%s> sending message text (%lu bytes)"
					,socket, client.protocol, user.alias, (ulong)strlen(msgtxt));
				lines_sent=sockmsgtxt(socket, client.protocol, session, &msg, msgtxt, lines);
				/* if(startup->options&MAIL_OPT_DEBUG_POP3) */
				if(lines!=-1 && lines_sent<lines)	/* could send *more* lines */
					lprintf(LOG_WARNING,"%04d %s <%s> !ERROR sending message text (sent %ld of %ld lines)"
						,socket, client.protocol, user.alias, lines_sent, lines);
					lprintf(LOG_DEBUG,"%04d %s <%s> message transfer complete (%lu lines)"
						,socket, client.protocol, user.alias, lines_sent);
					if((i=smb_locksmbhdr(&smb))!=SMB_SUCCESS) {
						lprintf(LOG_ERR,"%04d %s <%s> !ERROR %d (%s) locking message base"
							,socket, client.protocol, user.alias, i, smb.last_error);
					} else {
						if((i=smb_getmsgidx(&smb,&msg))!=SMB_SUCCESS) {
							lprintf(LOG_ERR,"%04d %s <%s> !ERROR %d (%s) getting message index"
								,socket, client.protocol, user.alias, i, smb.last_error);
						} else {
							msg.hdr.attr|=MSG_READ;

							if((i=smb_lockmsghdr(&smb,&msg))!=SMB_SUCCESS) 
								lprintf(LOG_ERR,"%04d %s <%s> !ERROR %d (%s) locking message header #%u"
									,socket, client.protocol, user.alias, i, smb.last_error, msg.hdr.number);
							if((i=smb_putmsg(&smb,&msg))!=SMB_SUCCESS)
								lprintf(LOG_ERR,"%04d %s <%s> !ERROR %d (%s) marking message #%u as read"
									,socket, client.protocol, user.alias, i, smb.last_error, msg.hdr.number);
							smb_unlockmsghdr(&smb,&msg);
						}
						smb_unlocksmbhdr(&smb);
					}
				smb_freemsgmem(&msg);
				smb_freemsgtxt(msgtxt);
				continue;
			}
			if(!strnicmp(buf, "DELE ",5)) {
				p=buf+5;
				msgnum=strtoul(p, NULL, 10);

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

				lprintf(LOG_INFO,"%04d %s <%s> deleting message #%u"
					,socket, client.protocol, user.alias, msg.hdr.number);
				if((i=smb_locksmbhdr(&smb))!=SMB_SUCCESS) {
					lprintf(LOG_ERR,"%04d %s <%s> !ERROR %d (%s) locking message base"
						,socket, client.protocol, user.alias, i, smb.last_error);
					sockprintf(socket,client.protocol,session,"-ERR %d locking message base",i);
				if((i=smb_getmsgidx(&smb,&msg))!=SMB_SUCCESS) {
					lprintf(LOG_ERR,"%04d %s <%s> !ERROR %d (%s) getting message index"
						,socket, client.protocol, user.alias, i, smb.last_error);
					sockprintf(socket,client.protocol,session,"-ERR %d getting message index",i);
				if((i=smb_lockmsghdr(&smb,&msg))!=SMB_SUCCESS) {
					lprintf(LOG_WARNING,"%04d %s <%s> !ERROR %d (%s) locking message header #%u"
						,socket, client.protocol, user.alias, i, smb.last_error, msg.hdr.number);
					sockprintf(socket,client.protocol,session,"-ERR %d locking message header",i);
				if((i=smb_getmsghdr(&smb,&msg))!=SMB_SUCCESS) {
					smb_unlockmsghdr(&smb,&msg);
					lprintf(LOG_ERR,"%04d %s <%s> !ERROR %d (%s) line %u, msg #%u"
						,socket, client.protocol, user.alias, i, smb.last_error, __LINE__, msg.idx.number);
					sockprintf(socket,client.protocol,session,"-ERR %d getting message header",i);
					continue;
				}
				msg.hdr.attr|=MSG_DELETE;

				if((i=smb_putmsg(&smb,&msg))==SMB_SUCCESS && msg.hdr.auxattr&MSG_FILEATTACH)
					delfattach(&scfg,&msg);
				smb_unlockmsghdr(&smb,&msg);
				smb_unlocksmbhdr(&smb);
				smb_freemsgmem(&msg);
				if(i!=SMB_SUCCESS) {
					lprintf(LOG_ERR,"%04d %s <%s> !ERROR %d (%s) marking message for deletion"
						, socket, client.protocol, user.alias, i, smb.last_error);
					sockprintf(socket,client.protocol,session,"-ERR %d marking message for deletion",i);
				sockprintf(socket,client.protocol,session,"+OK");
				if(startup->options&MAIL_OPT_DEBUG_POP3)
					lprintf(LOG_INFO,"%04d %s <%s> message deleted", socket, client.protocol, user.alias);
			lprintf(LOG_NOTICE,"%04d %s <%s> !UNSUPPORTED COMMAND: '%s'"
				,socket, client.protocol, user.alias, buf);
			sockprintf(socket,client.protocol,session,"-ERR UNSUPPORTED COMMAND: %s",buf);
		if(user.number) {
			if(!logoutuserdat(&scfg,&user,time(NULL),client.time))
				lprintf(LOG_ERR,"%04d %s <%s> !ERROR in logoutuserdat", socket, client.protocol, user.alias);
	if(activity) {
		if(user.number)
			lprintf(LOG_INFO,"%04d %s <%s> logged-out from port %u on %s [%s]"
				,socket, client.protocol, user.alias, inet_addrport(&pop3.client_addr), host_name, host_ip);
			lprintf(LOG_INFO,"%04d %s [%s] client disconnected from port %u on %s"
				,socket, client.protocol, host_ip, inet_addrport(&pop3.client_addr), host_name);
	status(STATUS_WFC);

	/* Free up resources here */
	if(mail!=NULL)
		freemail(mail);

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

	listRemoveTaggedNode(&current_logins, socket, /* free_data */TRUE);
	protected_uint32_adjust(&active_clients, -1);
	client_off(socket);

	{ 
		int32_t remain = thread_down();
		if(startup->options&MAIL_OPT_DEBUG_POP3)
			lprintf(LOG_DEBUG,"%04d %s [%s] session thread terminated (%u threads remain, %lu clients served)"
				,socket, client.protocol, host_ip, remain, ++stats.pop3_served);
	mail_close_socket(&socket, &session);
static ulong rblchk(SOCKET sock, const char* prot, union xp_sockaddr *addr, const char* rbl_addr)
{
	char		name[256];
	DWORD		mail_addr;
deuce's avatar
deuce committed
	unsigned char	*addr6;

	switch(addr->addr.sa_family) {
		case AF_INET:
			mail_addr=ntohl(addr->in.sin_addr.s_addr);
			safe_snprintf(name,sizeof(name),"%lu.%lu.%lu.%lu.%.128s"
				,(ulong)(mail_addr&0xff)
				,(ulong)(mail_addr>>8)&0xff
				,(ulong)(mail_addr>>16)&0xff
				,(ulong)(mail_addr>>24)&0xff
deuce's avatar
deuce committed
				,rbl_addr
				);
			break;
		case AF_INET6:
			addr6 = (unsigned char *)&addr->in6.sin6_addr;
			safe_snprintf(name,sizeof(name),"%1x.%1x.%1x.%1x.%1x.%1x.%1x.%1x."
											"%1x.%1x.%1x.%1x.%1x.%1x.%1x.%1x."
											"%1x.%1x.%1x.%1x.%1x.%1x.%1x.%1x."
											"%1x.%1x.%1x.%1x.%1x.%1x.%1x.%1x.%.128s"
				,addr6[15]&0x0f
				,addr6[15]>>4
				,addr6[14]&0x0f
				,addr6[14]>>4
				,addr6[13]&0x0f
				,addr6[13]>>4
				,addr6[12]&0x0f
				,addr6[12]>>4
				,addr6[11]&0x0f
				,addr6[11]>>4
				,addr6[10]&0x0f
				,addr6[10]>>4
				,addr6[9]&0x0f
				,addr6[9]>>4
				,addr6[8]&0x0f
				,addr6[8]>>4
				,addr6[7]&0x0f
				,addr6[7]>>4
				,addr6[6]&0x0f
				,addr6[6]>>4
				,addr6[5]&0x0f
				,addr6[5]>>4
				,addr6[4]&0x0f
				,addr6[4]>>4
				,addr6[3]&0x0f
				,addr6[3]>>4
				,addr6[2]&0x0f
				,addr6[2]>>4
				,addr6[1]&0x0f
				,addr6[1]>>4
				,addr6[0]&0x0f
				,addr6[0]>>4
				,rbl_addr
				);
			break;
		default:
			return 0;
	}
	lprintf(LOG_DEBUG,"%04d %s DNSBL Query: %s",sock,prot,name);
	if((host=gethostbyname(name))==NULL)
		return(0);
	dnsbl_result.s_addr = *((ulong*)host->h_addr_list[0]);
	lprintf(LOG_INFO,"%04d %s DNSBL Query: %s resolved to: %s"
		,sock,prot,name,inet_ntoa(dnsbl_result));
static ulong dns_blacklisted(SOCKET sock, const char* prot, union xp_sockaddr *addr, char* host_name, char* list, char* dnsbl_ip)
{
	char	fname[MAX_PATH+1];
	char	str[256];
	char*	p;
	char*	tp;
	FILE*	fp;
deuce's avatar
deuce committed
	char	ip[INET6_ADDRSTRLEN];
	SAFEPRINTF(fname,"%sdnsbl_exempt.cfg",scfg.ctrl_dir);
deuce's avatar
deuce committed
	inet_addrtop(addr, ip, sizeof(ip));
	if(findstr(ip,fname))
	if(findstr(host_name,fname))
		return(FALSE);
	SAFEPRINTF(fname,"%sdns_blacklist.cfg", scfg.ctrl_dir);
	if((fp=fopen(fname,"r"))==NULL)
		return(FALSE);

	while(!feof(fp) && !found) {
		if(fgets(str,sizeof(str),fp)==NULL)
		if(*p==';' || *p==0) /* comment or blank line */
			continue;

		sprintf(list,"%.100s",p);

		/* terminate */
		tp = p;
deuce's avatar
deuce committed
		strcpy(dnsbl_ip, ip);
static BOOL chk_email_addr(SOCKET socket, const char* prot, char* p, char* host_name, char* host_ip
						   ,char* to, char* from, char* source)
{
	char	addr[64];
	char	tmp[128];

	SAFECOPY(addr,p);

	if(!trashcan(&scfg,addr,"email"))
		return(TRUE);

	lprintf(LOG_NOTICE,"%04d %s [%s] !BLOCKED %s e-mail address: %s"
		,socket, prot, host_ip, source, addr);
	SAFEPRINTF2(tmp,"Blocked %s e-mail address: %s", source, addr);
	spamlog(&scfg, (char*)prot, "REFUSED", tmp, host_name, host_ip, to, from);
static BOOL email_addr_is_exempt(const char* addr)
{
	char fname[MAX_PATH+1];
	char netmail[128];
	char* p;

	if(*addr==0 || strcmp(addr,"<>")==0)
		return FALSE;
	SAFEPRINTF(fname,"%sdnsbl_exempt.cfg",scfg.ctrl_dir);
	if(findstr((char*)addr,fname))
		return TRUE;
	SAFECOPY(netmail, addr);
	if(*(p=netmail)=='<')
		p++;
	truncstr(p,">");
	return userdatdupe(&scfg, 0, U_NETMAIL, LEN_NETMAIL, p, /* del */FALSE, /* next */FALSE, NULL, NULL);
static void exempt_email_addr(const char* comment
	char	fname[MAX_PATH+1];
	char	tmp[128];
	FILE*	fp;
	angle_bracket(to, sizeof(to), toaddr);
	if(!email_addr_is_exempt(to)) {
		SAFEPRINTF(fname,"%sdnsbl_exempt.cfg",scfg.ctrl_dir);
		if((fp=fopen(fname,"a"))==NULL)
			lprintf(LOG_ERR,"0000 !Error opening file: %s", fname);
		else {
			lprintf(LOG_INFO,"0000 %s: %s", comment, to);
			fprintf(fp,"\n;%s from %s on %s\n%s\n"
				,comment, sender_info
				,timestr(&scfg,time32(NULL),tmp), to);
static void signal_smtp_sem(void)
	if(scfg.smtpmail_sem[0]==0) 
	if((file=open(scfg.smtpmail_sem,O_WRONLY|O_CREAT|O_TRUNC,DEFFILEMODE))!=-1)
/*****************************************************************************/
/* Returns command line generated from instr with %c replacements            */
/*****************************************************************************/
static char* mailcmdstr(char* instr, char* msgpath, char* newpath, char* logpath
						,char* lstpath, char* errpath
						,char* host, char* ip, uint usernum
						,char* sender, char* sender_addr, char* reverse_path, char* cmd)
    int		i,j,len;

    len=strlen(instr);
    for(i=j=0;i<len;i++) {
        if(instr[i]=='%') {
            i++;
            cmd[j]=0;
            switch(toupper(instr[i])) {
				case 'H':
					strcat(cmd,host);
					break;
				case 'I':
					strcat(cmd,ip);
					break;
                case 'G':   /* Temp directory */
                    strcat(cmd,scfg.temp_dir);
                    break;
                case 'J':
                    strcat(cmd,scfg.data_dir);
                    break;
                case 'K':
                    strcat(cmd,scfg.ctrl_dir);
                    break;
				case 'L':
					strcat(cmd,lstpath);
					break;
				case 'F':
				case 'M':
					strcat(cmd,msgpath);
					break;
                case 'O':   /* SysOp */
                    strcat(cmd,scfg.sys_op);
                    break;
                case 'Q':   /* QWK ID */
                    strcat(cmd,scfg.sys_id);
                    break;
				case 'R':	/* reverse path */
					strcat(cmd,reverse_path);
					break;
				case 'S':	/* sender name */
				case 'T':	/* recipient */
					strcat(cmd,rcpt_addr);
					break;
				case 'A':	/* sender address */
					strcat(cmd,sender_addr);
					break;
                    SAFEPRINTF2(str,"%s%c",VERSION,REVISION);
                    break;
                case 'Z':
                    strcat(cmd,scfg.text_dir);
                    break;
                case '!':   /* EXEC Directory */
                    strcat(cmd,scfg.exec_dir);
                    break;
                case '@':   /* EXEC Directory for DOS/OS2/Win32, blank for Unix */
#ifndef __unix__
                    strcat(cmd,scfg.exec_dir);
#endif
                    break;
                case '%':   /* %% for percent sign */
                    strcat(cmd,"%");
                    break;
				case '?':	/* Platform */
#ifdef __OS2__
					SAFECOPY(str,"OS2");
#else
					SAFECOPY(str,PLATFORM_DESC);
#endif
					strlwr(str);
					strcat(cmd,str);
					break;
					SAFEPRINTF(str,"%u",usernum);
                default:    /* unknown specification */
                    break; 
			}
            j=strlen(cmd); 
		}
        else
            cmd[j++]=instr[i]; 
	}
    cmd[j]=0;

    return(cmd);
}
rswindell's avatar
rswindell committed
typedef struct {
	SOCKET		sock;
	const char*	log_prefix;
	const char*	proc_name;
} private_t;

static void
js_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
{
	char	line[64];
	char	file[MAX_PATH+1];
	char*	warning;
rswindell's avatar
rswindell committed
	private_t*	p;
rswindell's avatar
rswindell committed
	if((p=(private_t*)JS_GetContextPrivate(cx))==NULL)
rswindell's avatar
rswindell committed
		lprintf(LOG_ERR,"%04d %s %s !JavaScript: %s"
			, p->sock, p->log_prefix, p->proc_name, message);
		SAFEPRINTF(file," %s",report->filename);
		SAFEPRINTF(line," line %u",report->lineno);
	else
		line[0]=0;

	if(JSREPORT_IS_WARNING(report->flags)) {
		if(JSREPORT_IS_STRICT(report->flags))
			warning="strict warning";
		else
			warning="warning";
		log_level=LOG_WARNING;
	} else {
		log_level=LOG_ERR;
	lprintf(log_level,"%04d %s %s !JavaScript %s%s%s: %s"
rswindell's avatar
rswindell committed
		,p->sock, p->log_prefix, p->proc_name
		,warning ,file, line, message);
js_log(JSContext *cx, uintN argc, jsval *arglist)
	jsval *argv=JS_ARGV(cx, arglist);
rswindell's avatar
rswindell committed
	private_t*	p;
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

rswindell's avatar
rswindell committed
	if((p=(private_t*)JS_GetContextPrivate(cx))==NULL)
	if(JSVAL_IS_NUMBER(argv[i])) {
		if(!JS_ValueToInt32(cx,argv[i++],&level))
			return JS_FALSE;
	}
		JSVALUE_TO_RASTRING(cx, argv[i], lstr, &lstr_sz, NULL);
		HANDLE_PENDING(cx, lstr);
deuce's avatar
deuce committed
		if(lstr==NULL)
		lprintf(level,"%04d %s %s %s"
deuce's avatar
deuce committed
			,p->sock,p->log_prefix,p->proc_name,lstr);
		JS_SET_RVAL(cx, arglist, argv[i]);
	if(lstr)
		free(lstr);
static JSBool
js_alert(JSContext *cx, uintN argc, jsval *arglist)
{
	jsval *argv=JS_ARGV(cx, arglist);
	private_t*	p;
	jsrefcount	rc;
	char		*line;

	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if((p=(private_t*)JS_GetContextPrivate(cx))==NULL)
		return(JS_FALSE);

	JSVALUE_TO_MSTRING(cx, argv[0], line, NULL);
	if(line==NULL)
	    return(JS_FALSE);

	rc=JS_SUSPENDREQUEST(cx);
	lprintf(LOG_ERR,"%04d %s %s %s"
		,p->sock, p->log_prefix, p->proc_name, line);
	free(line);
	JS_RESUMEREQUEST(cx, rc);

	JS_SET_RVAL(cx, arglist, argv[0]);

    return(JS_TRUE);
}


static JSFunctionSpec js_global_functions[] = {
	{"write",			js_log,				0},
	{"writeln",			js_log,				0},
	{"print",			js_log,				0},
	{"log",				js_log,				0},
rswindell's avatar
rswindell committed
js_mailproc(SOCKET sock, client_t* client, user_t* user, struct mailproc* mailproc
			,char* msgtxt_fname, char* newtxt_fname, char* logtxt_fname
			,char* rcptlst_fname, char* proc_err_fname
			,char* sender, char* sender_addr, char* reverse_path, char* hello_name
rswindell's avatar
rswindell committed
			,JSRuntime**	js_runtime
			,JSContext**	js_cx
			,JSObject**		js_glob
			,const char*	log_prefix
)
	char		path[MAX_PATH+1];
	char		arg[MAX_PATH+1];
	BOOL		success=FALSE;
rswindell's avatar
rswindell committed
	JSObject*	js_scope=NULL;
rswindell's avatar
rswindell committed
	private_t	priv;

	SAFECOPY(fname,cmdline);
	truncstr(fname," \t");
	if(getfext(fname)==NULL) /* No extension specified, assume '.js' */
		strcat(fname,".js");

	SAFECOPY(path,fname);
	if(getfname(path)==path) { /* No path specified, assume mods or exec dir */
		SAFEPRINTF2(path,"%s%s",scfg.mods_dir,fname);
		if(scfg.mods_dir[0]==0 || !fexist(path))
			SAFEPRINTF2(path,"%s%s",scfg.exec_dir,fname);
rswindell's avatar
rswindell committed
		if(*js_runtime==NULL) {
			lprintf(LOG_DEBUG,"%04d %s JavaScript: Creating runtime: %lu bytes\n"
				,sock, log_prefix, startup->js.max_bytes);
rswindell's avatar
rswindell committed
			if((*js_runtime = jsrt_GetNew(startup->js.max_bytes, 1000, __FILE__, __LINE__))==NULL)
rswindell's avatar
rswindell committed
		}
rswindell's avatar
rswindell committed
		if(*js_cx==NULL) {
			if((*js_cx = JS_NewContext(*js_runtime, JAVASCRIPT_CONTEXT_STACK))==NULL)
rswindell's avatar
rswindell committed
		}
		JS_BEGINREQUEST(*js_cx);

		JS_SetErrorReporter(*js_cx, js_ErrorReporter);

		priv.sock=sock;
		priv.log_prefix=log_prefix;
		priv.proc_name=mailproc->name;
		JS_SetContextPrivate(*js_cx, &priv);

		if(*js_glob==NULL) {
			/* Global Objects (including system, js, client, Socket, MsgBase, File, User, etc. */
			if(!js_CreateCommonObjects(*js_cx, &scfg, &scfg, NULL
						,uptime, server_host_name(), SOCKLIB_DESC	/* system */
						,&startup->js
						,client, sock, -1							/* client */
rswindell's avatar
rswindell committed
						,&js_server_props							/* server */
rswindell's avatar
rswindell committed
				break;
rswindell's avatar
rswindell committed
			if(!JS_DefineFunctions(*js_cx, *js_glob, js_global_functions))
				break;
rswindell's avatar
rswindell committed
			/* Area and "user" Objects */
rswindell's avatar
rswindell committed
			if(!js_CreateUserObjects(*js_cx, *js_glob, &scfg, user, client, NULL, NULL)) 
rswindell's avatar
rswindell committed
				break;
rswindell's avatar
rswindell committed
			/* Mailproc "API" filenames */
			JS_DefineProperty(*js_cx, *js_glob, "message_text_filename"
				,STRING_TO_JSVAL(JS_NewStringCopyZ(*js_cx,msgtxt_fname))
				,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
rswindell's avatar
rswindell committed
			JS_DefineProperty(*js_cx, *js_glob, "new_message_text_filename"
				,STRING_TO_JSVAL(JS_NewStringCopyZ(*js_cx,newtxt_fname))
				,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
rswindell's avatar
rswindell committed
			JS_DefineProperty(*js_cx, *js_glob, "log_text_filename"
				,STRING_TO_JSVAL(JS_NewStringCopyZ(*js_cx,logtxt_fname))
				,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);

			JS_DefineProperty(*js_cx, *js_glob, "recipient_address"
				,STRING_TO_JSVAL(JS_NewStringCopyZ(*js_cx,rcpt_addr))
				,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);

rswindell's avatar
rswindell committed
			JS_DefineProperty(*js_cx, *js_glob, "recipient_list_filename"
				,STRING_TO_JSVAL(JS_NewStringCopyZ(*js_cx,rcptlst_fname))
				,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);

			JS_DefineProperty(*js_cx, *js_glob, "processing_error_filename"
				,STRING_TO_JSVAL(JS_NewStringCopyZ(*js_cx,proc_err_fname))
				,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);

			JS_DefineProperty(*js_cx, *js_glob, "sender_name"
				,STRING_TO_JSVAL(JS_NewStringCopyZ(*js_cx,sender))
				,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);

			JS_DefineProperty(*js_cx, *js_glob, "sender_address"
				,STRING_TO_JSVAL(JS_NewStringCopyZ(*js_cx,sender_addr))
				,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
rswindell's avatar
rswindell committed
			JS_DefineProperty(*js_cx, *js_glob, "reverse_path"
				,STRING_TO_JSVAL(JS_NewStringCopyZ(*js_cx,reverse_path))
				,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);

			JS_DefineProperty(*js_cx, *js_glob, "hello_name"
				,STRING_TO_JSVAL(JS_NewStringCopyZ(*js_cx,hello_name))
				,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);

		}

		if((js_scope=JS_NewObject(*js_cx, NULL, NULL, *js_glob))==NULL)
			break;

		/* Convert command-line to argv/argc */
rswindell's avatar
rswindell committed
		argv=JS_NewArrayObject(*js_cx, 0, NULL);
		JS_DefineProperty(*js_cx, js_scope, "argv", OBJECT_TO_JSVAL(argv)
			,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);

		p=cmdline;
		FIND_WHITESPACE(p); 
		SKIP_WHITESPACE(p);
		for(argc=0;*p;argc++) {
			SAFECOPY(arg,p);
			truncstr(arg," \t");
rswindell's avatar
rswindell committed
			val=STRING_TO_JSVAL(JS_NewStringCopyZ(*js_cx,arg));
			if(!JS_SetElement(*js_cx, argv, argc, &val))
				break;
			FIND_WHITESPACE(p);
			SKIP_WHITESPACE(p);
		}
rswindell's avatar
rswindell committed
		JS_DefineProperty(*js_cx, js_scope, "argc", INT_TO_JSVAL(argc)
			,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);

deuce's avatar
deuce committed
		if(*mailproc->eval!=0) {
			lprintf(LOG_DEBUG,"%04d %s Evaluating: %s"
rswindell's avatar
rswindell committed
				,sock, log_prefix, mailproc->eval);
			js_script=JS_CompileScript(*js_cx, js_scope, mailproc->eval, strlen(mailproc->eval), NULL, 1);
		} else {
			lprintf(LOG_DEBUG,"%04d %s Executing: %s"
rswindell's avatar
rswindell committed
				,sock, log_prefix, cmdline);
			if((js_script=JS_CompileFile(*js_cx, js_scope, path)) != NULL)
				js_PrepareToExecute(*js_cx, js_scope, path, /* startup_dir: */NULL, js_scope);
rswindell's avatar
rswindell committed
		}
		if(js_script==NULL)
			break;
		/* ToDo: Set operational callback */
rswindell's avatar
rswindell committed
		success=JS_ExecuteScript(*js_cx, js_scope, js_script, &rval);
		JS_GetProperty(*js_cx, js_scope, "exit_code", &rval);

		if(rval!=JSVAL_VOID && JSVAL_IS_NUMBER(rval))
			JS_ValueToInt32(*js_cx,rval,result);
		js_EvalOnExit(*js_cx, js_scope, &js_callback);
		JS_ReportPendingException(*js_cx);
rswindell's avatar
rswindell committed
		JS_ClearScope(*js_cx, js_scope);
rswindell's avatar
rswindell committed
		JS_GC(*js_cx);
rswindell's avatar
rswindell committed

void js_cleanup(JSRuntime* js_runtime, JSContext* js_cx, JSObject** js_glob)
rswindell's avatar
rswindell committed
{
	if(js_cx!=NULL) {
		JS_BEGINREQUEST(js_cx);
		JS_RemoveObjectRoot(js_cx, js_glob);
		JS_ENDREQUEST(js_cx);
rswindell's avatar
rswindell committed
		JS_DestroyContext(js_cx);
rswindell's avatar
rswindell committed
	if(js_runtime!=NULL)
		jsrt_Release(js_runtime);
}
static size_t strStartsWith_i(const char* buf, const char* match)
{
	size_t len = strlen(match);
	if (strnicmp(buf, match, len) == 0)
		return len;
	return 0;
}

/* Decode RFC2047 'Q' encoded-word (in-place), "similar to" Quoted-printable */
char* mimehdr_q_decode(char* buf)
{
	uchar*	p=(uchar*)buf;
	uchar*	dest=p;

	for(;*p != 0; p++) {
		if(*p == '=') {
			p++;
			if(IS_HEXDIGIT(*p) && IS_HEXDIGIT(*(p + 1))) {
				uchar ch = HEX_CHAR_TO_INT(*p) << 4;
				p++;
				ch |= HEX_CHAR_TO_INT(*p);
				if(ch >= ' ')
					*dest++ = ch;
			} else {	/* bad encoding */
				*dest++ = '=';
				*dest++ = *p;
			}
		}
		else if(*p == '_')
			*dest++ = ' ';
		else if(*p >= '!' && *p <= '~')
			*dest++ = *p;
	}
	*dest=0;
	return buf;
}

enum mimehdr_charset {
	MIMEHDR_CHARSET_ASCII,
	MIMEHDR_CHARSET_UTF8,
	MIMEHDR_CHARSET_CP437,
	MIMEHDR_CHARSET_OTHER
};

static enum mimehdr_charset mimehdr_charset_decode(const char* str)
{
	if (strStartsWith_i(str, "ascii?") || strStartsWith_i(str, "us-ascii?"))
		return MIMEHDR_CHARSET_ASCII;
	if (strStartsWith_i(str, "utf-8?"))
		return MIMEHDR_CHARSET_UTF8;
	if (strStartsWith_i(str, "cp437?") || strStartsWith_i(str, "ibm437?"))
		return MIMEHDR_CHARSET_CP437;
	return MIMEHDR_CHARSET_OTHER;
}

// Replace MIME (RFC 2047) "encoded-words" with their decoded-values
// Returns true if the value was MIME-encoded
bool mimehdr_value_decode(char* str, smbmsg_t* msg)
	if (str == NULL)
		return false;
	char* buf = strdup(str);
	if (buf == NULL)
		return false;
	char* state = NULL;
	*str = 0;
	char tmp[256]; // "An 'encoded-word' may not be more than 75 characters long"
	for(char* p = strtok_r(buf, " \t", &state); p != NULL; p = strtok_r(NULL, " \t", &state)) {
		char* end = lastchar(p);
		if(*p == '=' && *(p+1) == '?' && *(end - 1) == '?' && *end == '=' && end - p < sizeof(tmp)) {
			if(*str && !encoded_word)
				strcat(str, " ");
			char* cp = p + 2;
			enum mimehdr_charset charset = mimehdr_charset_decode(cp);
			if(*cp == '?' && *(cp + 1) != 0 && *(cp + 2) == '?') {
				cp++;
				char encoding = toupper(*cp);
				cp += 2;
				SAFECOPY(tmp, cp);
				char* tp = lastchar(tmp);
				*(tp - 1) = 0;	// remove the terminating "?="
				if(encoding == 'Q') {
					mimehdr_q_decode(tmp);
					if(charset == MIMEHDR_CHARSET_UTF8 && !str_is_ascii(tmp) && utf8_str_is_valid(tmp))
						msg->hdr.auxattr |= MSG_HFIELDS_UTF8;
					// TODO consider converting other 8-bit charsets (e.g. CP437, ISO-8859-1) to UTF-8 here
					p = tmp;
				}
				else if(encoding == 'B' 
					&& b64_decode(tmp, sizeof(tmp), tmp, strlen(tmp)) > 0) { // base64
					if(charset == MIMEHDR_CHARSET_UTF8 && !str_is_ascii(tmp) && utf8_str_is_valid(tmp))
						msg->hdr.auxattr |= MSG_HFIELDS_UTF8;
					p = tmp;
		} else {
			if(*str)
				strcat(str, " ");
			encoded_word = false;
static char* get_header_field(char* buf, char* name, size_t maxlen)
	size_t	len;

	if(buf[0]<=' ')	/* folded header */
		return NULL;

	if((p=strchr(buf,':'))==NULL)
		return NULL;

	len = p-buf;
deuce's avatar
deuce committed
	sprintf(name,"%.*s",(int)len,buf);
	truncsp(name);

	p++;	/* skip colon */
	SKIP_WHITESPACE(p);
	return p;
}
static int parse_header_field(char* buf, smbmsg_t* msg, ushort* type)
	if(buf[0]<=' ' && *type!=UNKNOWN) {	/* folded header, append to previous */
		p=buf;
		truncsp(p);
		if(*type==RFC822HEADER || *type==SMTPRECEIVED)
			smb_hfield_append_str(msg,*type,"\r\n");
		else { /* Unfold other common header field types (e.g. Subject, From, To) */
			smb_hfield_append_str(msg,*type," ");
		return smb_hfield_append_str(msg, *type, p);
	if((p=strchr(buf,':'))==NULL)
		return smb_hfield_str(msg, *type=RFC822HEADER, buf);

	len=(ulong)p-(ulong)buf;
	if(len>sizeof(field)-1)
		len=sizeof(field)-1;
	sprintf(field,"%.*s",len,buf);
	truncsp(field);

	p++;	/* skip colon */
	SKIP_WHITESPACE(p);
	truncsp(p);

	if(!stricmp(field, "TO"))
		return smb_hfield_str(msg, *type=RFC822TO, p);
		smb_hfield_str(msg, *type=RFC822REPLYTO, p);
		if((tp=strrchr(p,'<'))!=NULL)  {
			tp++;
			truncstr(tp,">");
			p=tp;
		}
		nettype=NET_INTERNET;
		smb_hfield(msg, REPLYTONETTYPE, sizeof(nettype), &nettype);
		return smb_hfield_str(msg, *type=REPLYTONETADDR, p);
		return smb_hfield_str(msg, *type=RFC822FROM, p);
		return smb_hfield_str(msg, *type=RFC822ORG, p);
		msg->hdr.when_written=rfc822date(p);
		return smb_hfield_str(msg, *type=RFC822MSGID, p);
		return smb_hfield_str(msg, *type=RFC822REPLYID, p);
		return smb_hfield_str(msg, *type=RFC822CC, p);
	if(!stricmp(field, "RECEIVED"))
		return smb_hfield_str(msg, *type=SMTPRECEIVED, p);

	if(!stricmp(field, "RETURN-PATH")) {
		*type=UNKNOWN;
		return SMB_SUCCESS;	/* Ignore existing "Return-Path" header fields */
	}

	if(!stricmp(field, "X-PRIORITY")) {
		msg->hdr.priority = atoi(p);
		if(msg->hdr.priority > SMB_PRIORITY_LOWEST)
			msg->hdr.priority = SMB_PRIORITY_UNSPECIFIED;
		*type=UNKNOWN;
		return SMB_SUCCESS;
	}

	return smb_hfield_str(msg, *type=RFC822HEADER, buf);
static int chk_received_hdr(SOCKET socket,const char* prot,const char *buf,IN_ADDR *dnsbl_result, char *dnsbl, char *dnsbl_ip)
{
	char		host_name[128];
	char		*fromstr;
	char		ip[16];
	char		*p;
	char		*p2;
deuce's avatar
deuce committed
	union xp_sockaddr addr;
	struct addrinfo ai,*res;
deuce's avatar
deuce committed
	fromstr=strdup(buf);
	if(fromstr==NULL)
		return(0);
	strlwr(fromstr);
	do {
		p=strstr(fromstr,"from ");
		if(p==NULL)
			break;
		p+=4;
		for(;*p && !IS_WHITESPACE(*p) && p2<host_name+126;p++)  {
		p=strtok_r(fromstr,"[",&last);
		p=strtok_r(NULL,"]",&last);
deuce's avatar
deuce committed
		if(strnicmp("IPv6:", p, 5)) {
			p+=5;
			SKIP_WHITESPACE(p);
			memset(&ai, 0, sizeof(ai));
			ai.ai_flags = AI_NUMERICHOST|AI_NUMERICSERV|AI_PASSIVE;
			if(getaddrinfo(p, NULL, &ai, &res)!=0)
				break;
			if(res->ai_family == AF_INET6) {
deuce's avatar
deuce committed
				memcpy(&addr, res->ai_addr, res->ai_addrlen);
				freeaddrinfo(res);
			} else {
				freeaddrinfo(res);
deuce's avatar
deuce committed
				break;
deuce's avatar
deuce committed
		}
		else {
			strncpy(ip,p,16);
			ip[15]=0;
			addr.in.sin_family=AF_INET;
			addr.in.sin_addr.s_addr=inet_addr(ip);
			lprintf(LOG_DEBUG,"%04d %s DNSBL checking received header address %s [%s]",socket,prot,host_name,ip);
deuce's avatar
deuce committed
		}

		if((dnsbl_result->s_addr=dns_blacklisted(socket,prot,&addr,host_name,dnsbl,dnsbl_ip))!=0)
				lprintf(LOG_NOTICE,"%04d %s [%s] BLACKLISTED SERVER on %s: %s = %s"
					,socket, prot, ip, dnsbl, host_name, inet_ntoa(*dnsbl_result));
	} while(0);
	free(fromstr);
	return(dnsbl_result->s_addr);
}

static void parse_mail_address(char* p
							   ,char* name, size_t name_len
							   ,char* addr, size_t addr_len)
{
	char*	tp;
deuce's avatar
deuce committed
	sprintf(addr,"%.*s",(int)addr_len,tp);
	truncstr(addr,">( ");

	SAFECOPY(tmp,p);
	p=tmp;
	/* Get the "name" (if possible) */
rswindell's avatar
rswindell committed
	if((tp=strchr(p,'"'))!=NULL) {	/* name in quotes? */
rswindell's avatar
rswindell committed
	} else if((tp=strchr(p,'('))!=NULL) {	/* name in parenthesis? */
	} else if(*p=='<') {					/* address in brackets? */
		p++;
	} else									/* name, then address in brackets */
		tp=strchr(p,'<');
	if(tp) *tp=0;
deuce's avatar
deuce committed
	sprintf(name,"%.*s",(int)name_len,p);
/* Decode quoted-printable content-transfer-encoded text */
/* Ignores (strips) unsupported ctrl chars and non-ASCII chars */
/* Does not enforce 76 char line length limit */
static char* qp_decode(char* buf)
	uchar*	p=(uchar*)buf;
	uchar*	dest=p;

	for(;;p++) {
		if(*p==0) {
			*dest++='\r';
			*dest++='\n';
			break;
		}
		if(*p==' ' || (*p>='!' && *p<='~' && *p!='=') || *p=='\t')
			*dest++=*p;
		else if(*p=='=') {
			p++;
			if(*p==0) 	/* soft link break */
				break;
			if(IS_HEXDIGIT(*p) && IS_HEXDIGIT(*(p+1))) {
				char hex[3];
				hex[0]=*p;
				hex[1]=*(p+1);
				hex[2]=0;
				/* ToDo: what about encoded NULs and the like? */
				*dest++=(uchar)strtoul(hex,NULL,16);
				p++;
			} else {	/* bad encoding */
				*dest++='=';
				*dest++=*p;
			}
		}
	}
	*dest=0;
	return buf;
}

deuce's avatar
deuce committed
static BOOL checktag(scfg_t *scfg, char *tag, uint usernum)
{
	char	fname[MAX_PATH+1];

	if(tag==NULL)
		return(FALSE);
	SAFEPRINTF2(fname,"%suser/%04d.smtpblock",scfg->data_dir,usernum);
deuce's avatar
deuce committed
	return(findstr(tag, fname));
}

static BOOL smtp_splittag(char *in, char **name, char **tag)
{
	char	*last;

	if(in==NULL)
		return(FALSE);

	*name=strtok_r(in, "#", &last);
deuce's avatar
deuce committed
	if(*name) {
		*tag=strtok_r(NULL, "", &last);
		return(TRUE);
	}
	return(FALSE);
}

static uint smtp_matchuser(scfg_t *scfg, char *str, BOOL aliases, BOOL datdupe)
{
	char	*user=strdup(str);
	char	*name;
	char	*tag=NULL;
	uint	usernum=0;

	if(!user)
		return(0);

	if(!smtp_splittag(user, &name, &tag))
		goto end;

	if(datdupe)
		usernum=userdatdupe(scfg, 0, U_NAME, LEN_NAME, name, /* del */FALSE, /* next */FALSE, NULL, NULL);
deuce's avatar
deuce committed
	else
		usernum=matchuser(scfg, name, aliases);

	if(!usernum)
		goto end;

	if(checktag(scfg, tag, usernum))
		usernum=UINT_MAX;

end:
	free(user);
	return(usernum);
}

#define WITH_ESMTP	(1<<0)
#define WITH_AUTH	(1<<1)
#define WITH_TLS	(1<<2)

char *with_clauses[] = {
	"SMTP",			// No WITH_*
	"ESMTP",		// WITH_ESMTP
	"SMTP",			// WITH_AUTH
	"ESMTPA",		// WITH_ESMTP | WITH_AUTH
	"SMTP",			// WITH_TLS
	"ESMTPS",		// WITH_ESMTP | WITH_TLS
	"SMTP",			// WITH_TLS | WITH_AUTH
	"ESMTPSA"		// WITH_TLS | WITH_AUTH | WITH_ESMTP
};

static void smtp_thread(void* arg)
{
	int			rd;
	char		str[512];
	char		buf[1024],*p,*tp,*cp;
	char		hdrfield[512];
	char		alias_buf[128];
rswindell's avatar
rswindell committed
	char		name_alias_buf[128];
	char		reverse_path[128];
	char		date[64];
	char		rcpt_name[128];
	char		rcpt_addr[128];
	char		hello_name[128];
	char		relay_list[MAX_PATH+1];
	char		domain_list[MAX_PATH+1];
	char		spam_bait[MAX_PATH+1];
	BOOL		spam_bait_result=FALSE;
rswindell's avatar
rswindell committed
	char		spam_block_exemptions[MAX_PATH+1];
	BOOL		spam_block_exempt=FALSE;
	char		host_name[128];
deuce's avatar
deuce committed
	char		host_ip[INET6_ADDRSTRLEN];
	char		server_ip[INET6_ADDRSTRLEN];
deuce's avatar
deuce committed
	char		dnsbl_ip[INET6_ADDRSTRLEN];
	char		challenge[256];
	char		response[128];
	char		secret[64];
	char		md5_data[384];
	uchar		digest[MD5_DIGEST_SIZE];
	socklen_t	addr_len;
	ulong		login_attempts;
rswindell's avatar
rswindell committed
	ulong		waiting;
	BOOL		esmtp=FALSE;
	BOOL		forward=FALSE;
	BOOL		no_forward=FALSE;
	BOOL		routed=FALSE;
	BOOL		dnsbl_recvhdr;
	FILE*		msgtxt=NULL;
	char		newtxt_fname[MAX_PATH+1];
	char		logtxt_fname[MAX_PATH+1];
	FILE*		rcptlst;
	char		proc_err_fname[MAX_PATH+1];
	char		session_id[MAX_PATH+1];
	FILE*		spy=NULL;
	SOCKET		socket;
	smbmsg_t	msg;
	smbmsg_t	newmsg;
	user_t		user;
	client_t	client;
	smtp_t		smtp=*(smtp_t*)arg;
deuce's avatar
deuce committed
	union xp_sockaddr	server_addr;
	IN_ADDR		dnsbl_result;
rswindell's avatar
rswindell committed
	BOOL*		mailproc_to_match;
rswindell's avatar
rswindell committed
	JSRuntime*	js_runtime=NULL;
	JSContext*	js_cx=NULL;
	JSObject*	js_glob=NULL;
	int32		js_result;
	login_attempt_t attempted;
	int session = -1;
	BOOL nodelay=TRUE;
	ulong nb = 0;
	int level;
	int cstat;
	char *estr;
rswindell's avatar
rswindell committed

	enum {
			 SMTP_STATE_INITIAL
			,SMTP_STATE_HELO
rswindell's avatar
rswindell committed
			,SMTP_STATE_MAIL_FROM
			,SMTP_STATE_RCPT_TO
			,SMTP_STATE_DATA_HEADER
			,SMTP_STATE_DATA_BODY

	} state = SMTP_STATE_INITIAL;

rswindell's avatar
rswindell committed
	enum {
			 SMTP_CMD_NONE
			,SMTP_CMD_MAIL
			,SMTP_CMD_SEND
			,SMTP_CMD_SOML
			,SMTP_CMD_SAML

	} cmd = SMTP_CMD_NONE;

	enum {
			 ENCODING_NONE
			,ENCODING_BASE64
			,ENCODING_QUOTED_PRINTABLE
	} content_encoding = ENCODING_NONE;

	client.protocol = smtp.tls_port ? "SMTPS" : "SMTP";

	lprintf(LOG_DEBUG,"%04d %s Session thread started", socket, client.protocol);
#ifdef _WIN32
	if(startup->inbound_sound[0] && !(startup->options&MAIL_OPT_MUTE)) 
		PlaySound(startup->inbound_sound, NULL, SND_ASYNC|SND_FILENAME);

	addr_len=sizeof(server_addr);
		if (get_ssl_cert(&scfg, &estr, &level) == -1) {
			if (estr) {
				lprintf(level, "%04d %s !%s", socket, client.protocol, estr);
				free_crypt_attrstr(estr);
			mail_close_socket(&socket, &session);
		if ((cstat = cryptCreateSession(&session, CRYPT_UNUSED, CRYPT_SESSION_SSL_SERVER)) != CRYPT_OK) {
			GCES(cstat, client.protocol, socket, CRYPT_UNUSED, "creating session");
			mail_close_socket(&socket, &session);
		if ((cstat = cryptSetAttribute(session, CRYPT_SESSINFO_SSL_OPTIONS, CRYPT_SSLOPTION_DISABLE_CERTVERIFY)) != CRYPT_OK) {
			GCES(cstat, client.protocol, socket, CRYPT_UNUSED, "disabling certificate verification");
			mail_close_socket(&socket, &session);
		if ((cstat = cryptSetAttribute(session, CRYPT_SESSINFO_PRIVATEKEY, scfg.tls_certificate)) != CRYPT_OK) {
			GCES(cstat, client.protocol, socket, CRYPT_UNUSED, "setting private key");
			mail_close_socket(&socket, &session);
			thread_down();
			return;
		}
		nodelay = TRUE;
		setsockopt(socket,IPPROTO_TCP,TCP_NODELAY,(char*)&nodelay,sizeof(nodelay));
		nb=0;
		ioctlsocket(socket,FIONBIO,&nb);
		if ((cstat = cryptSetAttribute(session, CRYPT_SESSINFO_NETWORKSOCKET, socket)) != CRYPT_OK) {
			GCES(cstat, client.protocol, socket, CRYPT_UNUSED, "setting network socket");
			mail_close_socket(&socket, &session);
		if ((cstat = cryptSetAttribute(session, CRYPT_SESSINFO_ACTIVE, 1)) != CRYPT_OK) {
			GCES(cstat, client.protocol, socket, CRYPT_UNUSED, "setting session active");
			mail_close_socket(&socket, &session);
		if (startup->max_inactivity) {
			if ((cstat = cryptSetAttribute(session, CRYPT_OPTION_NET_READTIMEOUT, startup->max_inactivity)) != CRYPT_OK) {
				GCES(cstat, client.protocol, socket, CRYPT_UNUSED, "setting read timeout");
				mail_close_socket(&socket, &session);
deuce's avatar
deuce committed
	if((i=getsockname(socket, &server_addr.addr, &addr_len))!=0) {
		lprintf(LOG_CRIT,"%04d %s !ERROR %d (%d) getting address/port"
			,socket, client.protocol, i, ERROR_VALUE);
		sockprintf(socket,client.protocol,session,smtp_error, "getsockname failure");
		mail_close_socket(&socket, &session);
deuce's avatar
deuce committed
	}
deuce's avatar
deuce committed
	if((mailproc_to_match=malloc(sizeof(BOOL)*mailproc_count))==NULL) {
		lprintf(LOG_CRIT,"%04d %s !ERROR allocating memory for mailproc_to_match", socket, client.protocol);
		sockprintf(socket,client.protocol,session,smtp_error, "malloc failure");
		mail_close_socket(&socket, &session);
rswindell's avatar
rswindell committed
	memset(mailproc_to_match,FALSE,sizeof(BOOL)*mailproc_count);
	memset(&smb,0,sizeof(smb));
	memset(&spam,0,sizeof(spam));
	memset(&relay_user,0,sizeof(relay_user));
deuce's avatar
deuce committed
	inet_addrtop(&smtp.client_addr,host_ip,sizeof(host_ip));
	inet_addrtop(&server_addr,server_ip,sizeof(server_ip));
	lprintf(LOG_INFO,"%04d %s [%s] Connection accepted on %s port %u from port %u"
		,socket, client.protocol, host_ip, server_ip, inet_addrport(&server_addr), inet_addrport(&smtp.client_addr));
	SAFEPRINTF(client_id, "[%s]", host_ip);
	SAFECOPY(host_name, STR_NO_HOSTNAME);
	if(!(startup->options&MAIL_OPT_NO_HOST_LOOKUP)) {
		getnameinfo(&smtp.client_addr.addr, smtp.client_addr_len, host_name, sizeof(host_name), NULL, 0, NI_NAMEREQD);
		lprintf(LOG_INFO,"%04d %s %s Hostname: %s", socket, client.protocol, client_id, host_name);
	protected_uint32_adjust(&active_clients, 1);
	SAFEPRINTF(spam_bait,"%sspambait.cfg",scfg.ctrl_dir);
	SAFEPRINTF(spam_block,"%sspamblock.cfg",scfg.ctrl_dir);
rswindell's avatar
rswindell committed
	SAFEPRINTF(spam_block_exemptions,"%sspamblock_exempt.cfg",scfg.ctrl_dir);
deuce's avatar
deuce committed
	if(strcmp(server_ip, host_ip)==0) {
		/* local connection */
		dnsbl_result.s_addr=0;
	} else {
		ulong banned = loginBanned(&scfg, startup->login_attempt_list, socket, host_name, startup->login_attempt, &attempted);
rswindell's avatar
rswindell committed
		if(banned) {
			char ban_duration[128];
Rob Swindell's avatar
Rob Swindell committed
			lprintf(LOG_NOTICE, "%04d %s [%s] !TEMPORARY BAN (%lu login attempts, last: %s) - remaining: %s"
				,socket, client.protocol, host_ip, attempted.count-attempted.dupes, attempted.user, seconds_to_str(banned, ban_duration));
			mail_close_socket(&socket, &session);
rswindell's avatar
rswindell committed
			thread_down();
			protected_uint32_adjust(&active_clients, -1);
			update_clients();
			free(mailproc_to_match);
			return;
		}

		spam_block_exempt = findstr(host_ip,spam_block_exemptions) || findstr(host_name,spam_block_exemptions);
		if(trashcan(&scfg,host_ip,"ip") 
			|| ((!spam_block_exempt) && findstr(host_ip,spam_block))) {
			lprintf(LOG_NOTICE,"%04d %s !CLIENT IP ADDRESS BLOCKED: %s (%lu total)"
				,socket, client.protocol, host_ip, ++stats.sessions_refused);
			sockprintf(socket,client.protocol,session,"550 CLIENT IP ADDRESS BLOCKED: %s", host_ip);
			mail_close_socket(&socket, &session);
			protected_uint32_adjust(&active_clients, -1);
deuce's avatar
deuce committed
			free(mailproc_to_match);
		if(trashcan(&scfg,host_name,"host")) {
			lprintf(LOG_NOTICE,"%04d %s [%s] !CLIENT HOSTNAME BLOCKED: %s (%lu total)"
				,socket, client.protocol, host_ip, host_name, ++stats.sessions_refused);
			sockprintf(socket,client.protocol,session,"550 CLIENT HOSTNAME BLOCKED: %s", host_name);
			mail_close_socket(&socket, &session);
			protected_uint32_adjust(&active_clients, -1);
deuce's avatar
deuce committed
			free(mailproc_to_match);
		dnsbl_result.s_addr = dns_blacklisted(socket,client.protocol,&smtp.client_addr,host_name,dnsbl,dnsbl_ip);
Rob Swindell's avatar
Rob Swindell committed
			lprintf(LOG_NOTICE,"%04d %s [%s] BLACKLISTED SERVER on %s: %s = %s"
				,socket, client.protocol, dnsbl_ip, dnsbl, host_name, inet_ntoa(dnsbl_result));
			if(startup->options&MAIL_OPT_DNSBL_REFUSE) {
				SAFEPRINTF2(str,"Listed on %s as %s", dnsbl, inet_ntoa(dnsbl_result));
				spamlog(&scfg, (char*)client.protocol, "SESSION REFUSED", str, host_name, dnsbl_ip, NULL, NULL);
				sockprintf(socket,client.protocol,session
					,"550 Mail from %s refused due to listing at %s"
					,dnsbl_ip, dnsbl);
				mail_close_socket(&socket, &session);
				lprintf(LOG_NOTICE,"%04d %s !REFUSED SESSION from blacklisted server (%lu total)"
					,socket, client.protocol, ++stats.sessions_refused);
				protected_uint32_adjust(&active_clients, -1);
deuce's avatar
deuce committed
				free(mailproc_to_match);
	SAFEPRINTF(smb.file,"%smail",scfg.data_dir);
		lprintf(LOG_WARNING,"%04d %s [%s] !MAIL BASE LOCKED: %s"
			,socket, client.protocol, host_ip, smb.last_error);
		sockprintf(socket,client.protocol,session, smtp_error, "mail base locked");
		mail_close_socket(&socket, &session);
		protected_uint32_adjust(&active_clients, -1);
deuce's avatar
deuce committed
		free(mailproc_to_match);
	SAFEPRINTF(spam.file,"%sspam",scfg.data_dir);
	spam.retry_time=scfg.smb_retry_time;
	spam.subnum=INVALID_SUB;
	srand((unsigned int)(time(NULL) ^ (time_t)GetCurrentThreadId()));	/* seed random number generator */
	rand();	/* throw-away first result */
	SAFEPRINTF4(session_id,"%x%x%x%lx",getpid(),socket,rand(),(long)clock());
	lprintf(LOG_DEBUG,"%04d %s [%s] Session ID=%s", socket, client.protocol, host_ip, session_id);
	SAFEPRINTF3(msgtxt_fname,"%sSBBS_%s.%s.msg", scfg.temp_dir, client.protocol, session_id);
	SAFEPRINTF3(newtxt_fname,"%sSBBS_%s.%s.new", scfg.temp_dir, client.protocol, session_id);
	SAFEPRINTF3(logtxt_fname,"%sSBBS_%s.%s.log", scfg.temp_dir, client.protocol, session_id);
	SAFEPRINTF3(rcptlst_fname,"%sSBBS_%s.%s.lst", scfg.temp_dir, client.protocol, session_id);
	rcptlst=fopen(rcptlst_fname,"w+");
	if(rcptlst==NULL) {
		lprintf(LOG_CRIT,"%04d %s [%s] !ERROR %d creating recipient list: %s"
			,socket, client.protocol, host_ip, errno, rcptlst_fname);
		sockprintf(socket,client.protocol,session,smtp_error, "fopen error");
		mail_close_socket(&socket, &session);
		thread_down();
		protected_uint32_adjust(&active_clients, -1);
deuce's avatar
deuce committed
		free(mailproc_to_match);
	if(trashcan(&scfg,host_name,"smtpspy") 
		|| trashcan(&scfg,host_ip,"smtpspy")) {
		SAFEPRINTF(str,"%ssmtpspy.txt", scfg.logs_dir);
		spy=fopen(str,"a");
	}

	/* 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(&smtp.client_addr);
	client_on(socket,&client,FALSE /* update */);
	SAFEPRINTF(str,"SMTP: %s",host_ip);
rswindell's avatar
rswindell committed
	if(startup->login_attempt.throttle
		&& (login_attempts=loginAttempts(startup->login_attempt_list, &smtp.client_addr)) > 1) {
		lprintf(LOG_DEBUG,"%04d %s Throttling suspicious connection from: %s (%lu login attempts)"
			,socket, client.protocol, host_ip, login_attempts);
rswindell's avatar
rswindell committed
		mswait(login_attempts*startup->login_attempt.throttle);
	sockprintf(socket,client.protocol,session,"220 %s Synchronet %s Server %s-%s Ready"
		,server_host_name(), client.protocol, revision, PLATFORM_DESC);
		rd = sockreadline(socket, client.protocol, session, buf, sizeof(buf));
		if(spy!=NULL)
			fprintf(spy,"%s\n",buf);
		if(relay_user.number==0 && dnsbl_result.s_addr && startup->options&MAIL_OPT_DNSBL_THROTTLE)
			mswait(DNSBL_THROTTLE_VALUE);
		if(state>=SMTP_STATE_DATA_HEADER) {
			if(!strcmp(buf,".")) {

				state=SMTP_STATE_HELO;	/* RESET state machine here in case of error */
rswindell's avatar
rswindell committed
				cmd=SMTP_CMD_NONE;
					lprintf(LOG_ERR,"%04d %s %s !NO MESSAGE TEXT FILE POINTER?", socket, client.protocol, client_id);
					sockprintf(socket,client.protocol,session,"554 No message text");
					lprintf(LOG_ERR,"%04d %s %s !INVALID MESSAGE LENGTH: %ld (%lu lines)"
						, socket, client.protocol, client_id, ftell(msgtxt), lines);
					sockprintf(socket,client.protocol,session,"554 No message text");
				lprintf(LOG_INFO,"%04d %s %s End of message (body: %lu lines, %lu bytes, header: %lu lines, %lu bytes)"
					, socket, client.protocol, client_id, lines, ftell(msgtxt)-hdr_len, hdr_lines, hdr_len);
				if(!socket_check(socket, NULL, NULL, 0)) {
					lprintf(LOG_WARNING,"%04d %s %s !Sender disconnected (premature evacuation)", socket, client.protocol, client_id);
				/* Twit-listing (sender's name and e-mail addresses) here */
				SAFEPRINTF(path,"%stwitlist.cfg",scfg.ctrl_dir);
				if(fexist(path) && (findstr(sender,path) || findstr(sender_addr,path))) {
					lprintf(LOG_NOTICE,"%04d %s %s !FILTERING TWIT-LISTED SENDER: %s <%s> (%lu total)"
						,socket, client.protocol, client_id, sender, sender_addr, ++stats.msgs_refused);
					SAFEPRINTF2(tmp,"Twit-listed sender: %s <%s>", sender, sender_addr);
					spamlog(&scfg, (char*)client.protocol, "REFUSED", tmp, host_name, host_ip, rcpt_addr, reverse_path);
					sockprintf(socket,client.protocol,session, "554 Sender not allowed.");
				if(telegram==TRUE) {		/* Telegram */
					const char* head="\1n\1h\1cInstant Message\1n from \1h\1y";
					const char* tail="\1n:\r\n\1h";
deuce's avatar
deuce committed
					struct addrinfo ai;
					struct addrinfo *res,*cur;
					BOOL matched=FALSE;

					rewind(msgtxt);
					length=filelength(fileno(msgtxt));
					
					p=strchr(sender_addr,'@');
deuce's avatar
deuce committed
					memset(&ai, 0, sizeof(ai));
					ai.ai_flags = AI_PASSIVE;
					ai.ai_family = smtp.client_addr.addr.sa_family;
					if(getaddrinfo(p+1, NULL, &ai, &res) != 0)
						p=NULL;
					else {
						for(cur=res; cur; cur=cur->ai_next) {
							char cur_ip[INET6_ADDRSTRLEN];

							if(inet_addrtop((void *)cur->ai_addr, cur_ip, sizeof(cur_ip))) {
								if(strcmp(host_ip, cur_ip)==0)
									matched=TRUE;
							}
						}
						freeaddrinfo(res);
						if(!matched)
							p=NULL;
					}
					if(p==NULL)
						/* Append real IP and hostname if different */
						safe_snprintf(str,sizeof(str),"%s%s\r\n\1w[\1n%s\1h] (\1n%s\1h)%s"
							,head,sender_addr,host_ip,host_name,tail);
					else
						safe_snprintf(str,sizeof(str),"%s%s%s",head,sender_addr,tail);
					if((telegram_buf=(char*)malloc(length+strlen(str)+1))==NULL) {
						lprintf(LOG_CRIT,"%04d %s %s !ERROR allocating %lu bytes of memory for telegram from %s"
							,socket, client.protocol, client_id, length+strlen(str)+1,sender_addr);
						sockprintf(socket,client.protocol,session, insuf_stor);
						continue; 
					}
					strcpy(telegram_buf,str);	/* can't use SAFECOPY here */
					if(fread(telegram_buf+strlen(str),1,length,msgtxt)!=length) {
						lprintf(LOG_ERR,"%04d %s %s !ERROR reading %lu bytes from telegram file"
							,socket, client.protocol, client_id, length);
						sockprintf(socket,client.protocol,session, insuf_stor);
						free(telegram_buf);
						continue; 
					}
					telegram_buf[length+strlen(str)]=0;	/* Need ASCIIZ */

					/* Send telegram to users */
					sec_list=iniReadSectionList(rcptlst,NULL);	/* Each section is a recipient */
					for(rcpt_count=0; sec_list!=NULL
						&& sec_list[rcpt_count]!=NULL 
rswindell's avatar
rswindell committed
						&& (startup->max_recipients==0 || rcpt_count<startup->max_recipients); rcpt_count++) {
						SAFECOPY(rcpt_to,iniReadString(rcptlst,section	,smb_hfieldtype(RECIPIENT),"unknown",value));
						usernum=iniReadInteger(rcptlst,section				,smb_hfieldtype(RECIPIENTEXT),0);
						SAFECOPY(rcpt_addr,iniReadString(rcptlst,section	,smb_hfieldtype(RECIPIENTNETADDR),rcpt_to,value));

						if((i=putsmsg(&scfg,usernum,telegram_buf))==0)
							lprintf(LOG_INFO,"%04d %s %s Created telegram (%ld/%lu bytes) from %s to %s <%s>"
								,socket, client.protocol, client_id, length, (ulong)strlen(telegram_buf), sender_addr, rcpt_to, rcpt_addr);
							lprintf(LOG_ERR,"%04d %s %s !ERROR %d creating telegram from %s to %s <%s>"
								,socket, client.protocol, client_id, i, sender_addr, rcpt_to, rcpt_addr);
					sockprintf(socket,client.protocol,session,ok_rsp);
				fclose(msgtxt), msgtxt=NULL;
				fclose(rcptlst), rcptlst=NULL;
					SAFEPRINTF3(proc_err_fname,"%sSBBS_%s.%s.err", scfg.temp_dir, client.protocol, session_id);
					for(i=0;i<mailproc_count && !msg_handled;i++) {
						struct mailproc* mp=&mailproc_list[i];
						if(mp->disabled)
rswindell's avatar
rswindell committed
							continue;
						if(!mp->process_dnsbl && dnsbl_result.s_addr)
						if(!mp->process_spam && spam_bait_result)
						if(!chk_ar(&scfg,mp->ar,&relay_user,&client))
rswindell's avatar
rswindell committed
							continue;

						if(mp->to!=NULL && !mailproc_to_match[i])
						if(mp->from!=NULL 
							&& !findstr_in_list(sender_addr, mp->from))
							,msgtxt_fname, newtxt_fname, logtxt_fname
							,rcptlst_fname, proc_err_fname
							,host_name, host_ip, relay_user.number
							,sender, sender_addr, reverse_path, str);
						lprintf(LOG_INFO,"%04d %s %s Executing external mail processor: %s"
							,socket, client.protocol, client_id, mp->name);
							lprintf(LOG_DEBUG,"%04d %s %s Executing external command: %s"
								,socket, client.protocol, client_id, str);
								lprintf(LOG_NOTICE,"%04d %s %s system(%s) returned %d (errno: %d)"
									,socket, client.protocol, client_id, str, j, errno);
								if(mp->ignore_on_error) {
									lprintf(LOG_WARNING,"%04d %s %s !IGNORED MAIL due to mail processor (%s) error: %d"
										,socket, client.protocol, client_id, mp->name, j);
rswindell's avatar
rswindell committed
							if(!js_mailproc(socket, &client, &relay_user
rswindell's avatar
rswindell committed
								,str /* cmdline */
								,msgtxt_fname, newtxt_fname, logtxt_fname
								,sender, sender_addr, reverse_path, hello_name, &js_result
rswindell's avatar
rswindell committed
								,&js_runtime, &js_cx, &js_glob
#if 0 /* calling exit() in a script causes js_mailproc to return FALSE */
								lprintf(LOG_NOTICE,"%04d !SMTP JavaScript mailproc command (%s) failed (returned: %d)"
									,socket, str, js_result);
									lprintf(LOG_WARNING,"%04d !SMTP IGNORED MAIL due to mail processor (%s) failure"
						/* Log debug output (file) from mailproc: */
						if(flength(logtxt_fname) > 0 && (proc_out=fopen(logtxt_fname,"r"))!=NULL) {
							while(!feof(proc_out)) {
								if(!fgets(str,sizeof(str),proc_out))
									break;
								truncsp(str);
								lprintf(LOG_DEBUG,"%04d %s %s External mail processor (%s) debug: %s"
									,socket, client.protocol, client_id, mp->name, str);
						if(!mp->passthru || flength(proc_err_fname)>0 || !fexist(msgtxt_fname) || !fexist(rcptlst_fname)) {
							mailproc=mp;
							msg_handled=TRUE;
						&& (proc_out=fopen(proc_err_fname,"r"))!=NULL) {
						lprintf(LOG_WARNING,"%04d %s %s !External mail processor (%s) created: %s"
								,socket, client.protocol, client_id, mailproc->name, proc_err_fname);
							if(!fgets(str,sizeof(str),proc_out))
							lprintf(LOG_WARNING,"%04d %s %s !External mail processor (%s) error: %s"
								,socket, client.protocol, client_id, mailproc->name, str);
								sockprintf(socket,client.protocol,session,"%s", str);
rswindell's avatar
Loading
Loading full blame...