Newer
Older
/* mailsrvr.c */
/* Synchronet Mail (SMTP/POP3) server and sendmail threads */
/* $Id$ */
/****************************************************************************
* @format.tab-size 4 (Plain Text/Source Code File Header) *
* @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) *
* *
* Copyright 2014 Rob Swindell - http://www.synchro.net/copyright.html *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* See the GNU General Public License for more details: gpl.txt or *
* http://www.fsf.org/copyleft/gpl.html *
* *
* Anonymous FTP access to the most recent released source is available at *
* ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net *
* *
* Anonymous CVS access to the development source and modification history *
* is available at cvs.synchro.net:/cvsroot/sbbs, example: *
* cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login *
* (just hit return, no password is necessary) *
* cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src *
* *
* For Synchronet coding style and modification guidelines, see *
* http://www.synchro.net/source.html *
* *
* You are encouraged to submit any modifications (preferably in Unix diff *
* format) via e-mail to mods@synchro.net *
* *
* Note: If this box doesn't appear square, then you need to fix your tabs. *
****************************************************************************/
#include <stdlib.h> /* ltoa in GNU C lib */
#include <stdarg.h> /* va_list */
#include <string.h> /* strrchr */
#include <ctype.h> /* isdigit */
#include <fcntl.h> /* Open flags */
#include <errno.h> /* errno */
#undef SBBS /* this shouldn't be defined unless building sbbs.dll/libsbbs.so */
#include "sbbs.h"
#include "mime.h"
#include "md5.h"
#include "base64.h"
#include "ini_file.h"
#include "netwrap.h" /* getNameServerList() */
#include "xpendian.h"
#include "js_rtpool.h"
#include "js_request.h"
static const char* server_name="Synchronet Mail Server";
#define FORWARD "forward:"
#define NO_FORWARD "local:"

rswindell
committed
int dns_getmx(char* name, char* mx, char* mx2
,DWORD intf, DWORD ip_addr, BOOL use_tcp, int timeout);
static char* ok_rsp = "250 OK";
static char* auth_ok = "235 User Authenticated";
static char* sys_error = "421 System error";
static char* sys_unavail= "421 System unavailable, try again later";
static char* insuf_stor = "452 Insufficient system storage";
static char* badarg_rsp = "501 Bad argument";
static char* badseq_rsp = "503 Bad sequence of commands";
static char* badauth_rsp= "535 Authentication failure";
static char* badrsp_err = "%s replied with:\r\n\"%s\"\r\n"
"instead of the expected reply:\r\n\"%s ...\"";
#define TIMEOUT_THREAD_WAIT 60 /* Seconds */
#define DNSBL_THROTTLE_VALUE 1000 /* Milliseconds */
#define STATUS_WFC "Listening"
static mail_startup_t* startup=NULL;
static scfg_t scfg;
static SOCKET server_socket=INVALID_SOCKET;
static SOCKET submission_socket=INVALID_SOCKET;
static SOCKET pop3_socket=INVALID_SOCKET;
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 volatile time_t uptime;
static str_list_t recycle_semfiles;
static str_list_t shutdown_semfiles;
static int mailproc_count;
static js_server_props_t js_server_props;
struct {
volatile ulong sockets;
volatile ulong errors;
volatile ulong crit_errors;
volatile ulong connections_ignored;
volatile ulong connections_refused;
volatile ulong connections_served;
volatile ulong pop3_served;
volatile ulong smtp_served;
/* SMTP: */
volatile ulong sessions_refused;
volatile ulong msgs_ignored;
volatile ulong msgs_refused;
volatile ulong msgs_received;
} stats;
struct mailproc {
char cmdline[INI_MAX_VALUE_LEN];
str_list_t to;
str_list_t from;
BOOL passthru;
BOOL native;
BOOL ignore_on_error; /* Ignore mail message if cmdline fails */
BOOL disabled;
BOOL process_spam;
BOOL process_dnsbl;
uint8_t* ar;
ulong handled; /* counter (for stats display) */
} *mailproc_list;
typedef struct {
SOCKET socket;
SOCKADDR_IN client_addr;
} smtp_t,pop3_t;
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;
if(level <= LOG_ERR) {
errorlog(&scfg,startup==NULL ? NULL:startup->host_name,sbuf), stats.errors++;
if(startup!=NULL && startup->errormsg!=NULL)
startup->errormsg(startup->cbdata,level,sbuf);
}
if(level <= LOG_CRIT)
stats.crit_errors++;
if(startup==NULL || startup->lputs==NULL || level > startup->log_level)
return(0);
#if defined(_WIN32)
if(IsBadCodePtr((FARPROC)startup->lputs))
return(0);
#endif
return(startup->lputs(startup->cbdata,level,sbuf));
}
#ifdef _WINSOCKAPI_
static WSADATA WSAData;
#define SOCKLIB_DESC WSAData.szDescription
static BOOL WSAInitialized=FALSE;
static BOOL winsock_startup(void)
{
int status; /* Status Code */
if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
lprintf(LOG_DEBUG,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
WSAInitialized=TRUE;
lprintf(LOG_CRIT,"!WinSock startup ERROR %d", status);
return (FALSE);
}
#else /* No WINSOCK */
#define SOCKLIB_DESC NULL
#endif
static void update_clients(void)
{
if(startup!=NULL && startup->clients!=NULL)
startup->clients(startup->cbdata,protected_uint32_value(active_clients)+active_sendmail);
static void client_on(SOCKET sock, client_t* client, BOOL update)
if(startup!=NULL && startup->client_on!=NULL)
startup->client_on(startup->cbdata,TRUE,sock,client,update);
}
static void client_off(SOCKET sock)
{
if(startup!=NULL && startup->client_on!=NULL)
startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
static void thread_up(BOOL setuid)
{
if(startup!=NULL && startup->thread_up!=NULL)
startup->thread_up(startup->cbdata,TRUE,setuid);
static int32_t thread_down(void)
int32_t count = protected_uint32_adjust(&thread_count,-1);
if(startup!=NULL && startup->thread_up!=NULL)
startup->thread_up(startup->cbdata,FALSE,FALSE);
return count;
SOCKET mail_open_socket(int type, const char* protocol)
char error[256];
char section[128];
SOCKET sock;
sock=socket(AF_INET, type, IPPROTO_IP);
if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL)
startup->socket_open(startup->cbdata,TRUE);
SAFEPRINTF(section,"mail|%s",protocol);
if(set_socket_options(&scfg, sock, section, error, sizeof(error)))
lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
stats.sockets++;
lprintf(LOG_DEBUG,"%04d Socket opened (%d sockets in use)",sock,stats.sockets);
#endif
}
return(sock);
}

rswindell
committed
int mail_close_socket(SOCKET sock)
if(sock==INVALID_SOCKET)
return(-1);
shutdown(sock,SHUT_RDWR); /* required on Unix */
if(startup!=NULL && startup->socket_open!=NULL)
startup->socket_open(startup->cbdata,FALSE);
stats.sockets--;
if(result!=0) {
if(ERROR_VALUE!=ENOTSOCK)
lprintf(LOG_WARNING,"%04d !ERROR %d closing socket",sock, ERROR_VALUE);
lprintf(LOG_DEBUG,"%04d Socket closed (%d sockets in use)",sock,stats.sockets);
#endif
return(result);
}
static void status(char* str)
{
if(startup!=NULL && startup->status!=NULL)
startup->status(startup->cbdata,str);
int sockprintf(SOCKET sock, char *fmt, ...)
int maxlen;
int result;
va_list argptr;
char sbuf[1024];
fd_set socket_set;
struct timeval tv;
len=vsnprintf(sbuf,maxlen=sizeof(sbuf)-2,fmt,argptr);
va_end(argptr);
if(len<0 || len > maxlen) /* format error or output truncated */
len=maxlen;
if(startup->options&MAIL_OPT_DEBUG_TX)
lprintf(LOG_DEBUG,"%04d TX: %.*s", sock, len, sbuf);
memcpy(sbuf+len,"\r\n",2);
if(sock==INVALID_SOCKET) {
lprintf(LOG_WARNING,"!INVALID SOCKET in call to sockprintf");
return(0);
}
/* Check socket for writability (using select) */
tv.tv_usec=0;
FD_ZERO(&socket_set);
FD_SET(sock,&socket_set);
if((result=select(sock+1,NULL,&socket_set,NULL,&tv))<1) {
lprintf(LOG_NOTICE,"%04d !TIMEOUT selecting socket for send"
lprintf(LOG_NOTICE,"%04d !ERROR %d selecting socket for send"
,sock, ERROR_VALUE);
return(0);
}
while((result=sendsocket(sock,sbuf,len))!=len) {
YIELD();
lprintf(LOG_NOTICE,"%04d Connection reset by peer on send",sock);
lprintf(LOG_NOTICE,"%04d Connection aborted by peer on send",sock);
lprintf(LOG_NOTICE,"%04d !ERROR %d sending on socket",sock,ERROR_VALUE);
lprintf(LOG_WARNING,"%04d !ERROR: short send on socket: %d instead of %d",sock,result,len);
static void sockerror(SOCKET socket, int rd, const char* action)
lprintf(LOG_NOTICE,"%04d Socket closed by peer on %s"
,socket, action);
lprintf(LOG_NOTICE,"%04d Connection reset by peer on %s"
,socket, action);
lprintf(LOG_NOTICE,"%04d Connection aborted by peer on %s"
,socket, action);
lprintf(LOG_NOTICE,"%04d !SOCKET ERROR %d on %s"
,socket, ERROR_VALUE, action);
lprintf(LOG_WARNING,"%04d !SOCKET ERROR: unexpected return value %d from %s"
,socket, rd, action);

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

rswindell
committed
fd_set socket_set;
struct timeval tv;
if(socket==INVALID_SOCKET) {
lprintf(LOG_WARNING,"!INVALID SOCKET in call to sockreadline");
return(-1);

rswindell
committed
if(server_socket==INVALID_SOCKET || terminate_server) {
lprintf(LOG_WARNING,"%04d !ABORTING sockreadline",socket);
tv.tv_sec=startup->max_inactivity;

rswindell
committed
tv.tv_usec=0;
FD_ZERO(&socket_set);
FD_SET(socket,&socket_set);
i=select(socket+1,&socket_set,NULL,NULL,&tv);

rswindell
committed
if(i==0) {
if(startup->max_inactivity && (time(NULL)-start)>startup->max_inactivity) {
lprintf(LOG_WARNING,"%04d !TIMEOUT in sockreadline (%u seconds): INACTIVE SOCKET",socket,startup->max_inactivity);
return(-1);
return(-1);

rswindell
committed
}
i=recv(socket, &ch, 1, 0);
if(i<1) {
return(-1);
if(ch=='\n' /* && rd>=1 */ ) { /* Mar-9-2003: terminate on sole LF */
break;
}
buf[rd++]=ch;
}
if(rd>0 && buf[rd-1]=='\r')
rd--;
buf[rd]=0;
return(rd);
}
static BOOL sockgetrsp(SOCKET socket, char* rsp, char *buf, int len)
{
int rd;
while(1) {
rd = sockreadline(socket, buf, len);
if(rd<1) {
if(rd==0)
lprintf(LOG_WARNING,"%04d !RECEIVED BLANK RESPONSE, Expected '%s'", socket, rsp);
if(buf[3]=='-') { /* Multi-line response */
if(startup->options&MAIL_OPT_DEBUG_RX_RSP)
lprintf(LOG_DEBUG,"%04d RX: %s",socket,buf);
if(rsp!=NULL && strnicmp(buf,rsp,strlen(rsp))) {
lprintf(LOG_WARNING,"%04d !INVALID RESPONSE: '%s' Expected: '%s'", socket, buf, rsp);
return(FALSE);
}
break;
}
if(startup->options&MAIL_OPT_DEBUG_RX_RSP)
lprintf(LOG_DEBUG,"%04d RX: %s",socket,buf);
/* RFC822: The maximum total length of a text line including the
<CRLF> is 1000 characters (but not counting the leading
dot duplicated for transparency).
POP3 (RFC1939) actually calls for a 512 byte line length limit!
*/
#define MAX_LINE_LEN 998
static ulong sockmimetext(SOCKET socket, smbmsg_t* msg, char* msgtxt, ulong maxlines
,str_list_t file_list, char* mime_boundary)
char toaddr[256]="";
char fromaddr[256]="";
char fromhost[256];
uchar* p;
char* content_type=NULL;
int len,tlen;
/* HEADERS (in recommended order per RFC822 4.1) */
if(msg->reverse_path!=NULL)
if(!sockprintf(socket,"Return-Path: %s", msg->reverse_path))
return(0);
for(i=0;i<msg->total_hfields;i++)
if(msg->hfield[i].type == SMTPRECEIVED && msg->hfield_dat[i]!=NULL)
if(!sockprintf(socket,"Received: %s", msg->hfield_dat[i]))
return(0);
if(!sockprintf(socket,"Date: %s",msgdate(msg->hdr.when_written,date)))
return(0);
if((p=smb_get_hfield(msg,RFC822FROM,NULL))!=NULL)
s=sockprintf(socket,"From: %s",p); /* use original RFC822 header field */
else {
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));
else if(msg->from_net.type==NET_FIDO && msg->from_net.addr!=NULL)
SAFECOPY(fromaddr,smb_faddrtoa((faddr_t *)msg->from_net.addr,NULL));
else if(msg->from_net.type!=NET_NONE && msg->from_net.addr!=NULL)
SAFECOPY(fromaddr,(char*)msg->from_net.addr);
else
usermailaddr(&scfg,fromaddr,msg->from);
if(fromaddr[0]=='<')
s=sockprintf(socket,"From: \"%s\" %s",msg->from,fromaddr);
else
s=sockprintf(socket,"From: \"%s\" <%s>",msg->from,fromaddr);
}
if(!s)
return(0);
if(msg->from_org!=NULL || msg->from_net.type==NET_NONE)
if(!sockprintf(socket,"Organization: %s"
,msg->from_org==NULL ? scfg.sys_name : msg->from_org))
return(0);
if(!sockprintf(socket,"Subject: %s",msg->subj))
return(0);
if((p=smb_get_hfield(msg,RFC822TO,NULL))!=NULL)
s=sockprintf(socket,"To: %s",p); /* use original RFC822 header field */
else {
if(strchr(msg->to,'@')!=NULL || msg->to_net.addr==NULL)
s=sockprintf(socket,"To: %s",msg->to); /* Avoid double-@ */
else if(msg->to_net.type==NET_INTERNET || msg->to_net.type==NET_QWK) {
if(strchr((char*)msg->to_net.addr,'<')!=NULL)
s=sockprintf(socket,"To: %s",(char*)msg->to_net.addr);
else
s=sockprintf(socket,"To: \"%s\" <%s>",msg->to,(char*)msg->to_net.addr);
} else {
usermailaddr(&scfg,toaddr,msg->to);
s=sockprintf(socket,"To: \"%s\" <%s>",msg->to,toaddr);
}
if(!s)
return(0);
if((p=smb_get_hfield(msg,SMB_CARBONCOPY,NULL))!=NULL)
if(!sockprintf(socket,"CC: %s",p))
return(0);
if((p=smb_get_hfield(msg,RFC822REPLYTO,NULL))==NULL) {
if(msg->replyto_net.type==NET_INTERNET)
if(p!=NULL) {
if(np!=NULL)
s=sockprintf(socket,"Reply-To: \"%s\" <%s>",np,p);
else
s=sockprintf(socket,"Reply-To: %s",p);
}
if(!s)
return(0);
if(!sockprintf(socket,"Message-ID: %s",get_msgid(&scfg,INVALID_SUB,msg,msgid,sizeof(msgid))))
if(msg->reply_id!=NULL)
if(!sockprintf(socket,"In-Reply-To: %s",msg->reply_id))
return(0);
/* non-standard, but documented (mostly) in draft-newman-msgheader-originfo-05 */
sockprintf(socket,"Originator-Info: account=%s; login-id=%s; server=%s; client=%s; addr=%s; prot=%s; port=%s; time=%s"
,msg->from_ext
,smb_get_hfield(msg,SENDERUSERID,NULL)
,smb_get_hfield(msg,SENDERSERVER,NULL)
,smb_get_hfield(msg,SENDERHOSTNAME,NULL)
,smb_get_hfield(msg,SENDERIPADDR,NULL)
,smb_get_hfield(msg,SENDERPROTOCOL,NULL)
,smb_get_hfield(msg,SENDERPORT,NULL)
,smb_get_hfield(msg,SENDERTIME,NULL)
);
for(i=0;i<msg->total_hfields;i++) {
if(msg->hfield[i].type==RFC822HEADER) {
if(strnicmp((char*)msg->hfield_dat[i],"Content-Type:",13)==0)
content_type=msg->hfield_dat[i];
if(!sockprintf(socket,"%s",(char*)msg->hfield_dat[i]))
return(0);
/* Default MIME Content-Type for non-Internet messages */
if(msg->from_net.type!=NET_INTERNET && content_type==NULL && startup->default_charset[0]) {
/* No content-type specified, so assume IBM code-page 437 (full ex-ASCII) */
sockprintf(socket,"Content-Type: text/plain; charset=%s", startup->default_charset);
sockprintf(socket,"Content-Transfer-Encoding: 8bit");
}
if(strListCount(file_list)) { /* File attachments */
mimeheaders(socket,mime_boundary);
sockprintf(socket,"");
mimeblurb(socket,mime_boundary);
sockprintf(socket,"");
mimetextpartheader(socket,mime_boundary);
if(!sockprintf(socket,"")) /* Header Terminator */
return(0);
np=msgtxt;
while(*np && lines<maxlines) {
len=0;
while(len<MAX_LINE_LEN && *(np+len)!=0 && *(np+len)!='\n')
len++;
tlen=len;
while(tlen && *(np+(tlen-1))<=' ') /* Takes care of '\r' or spaces */
tlen--;
if(!sockprintf(socket, "%s%.*s", *np=='.' ? ".":"", tlen, np))
if(*(np+len)=='\r')
len++;
if(*(np+len)=='\n')
len++;
/* release time-slices every x lines */
if(startup->lines_per_yield
&& !(lines%startup->lines_per_yield))
YIELD();
if(file_list!=NULL) {
for(i=0;file_list[i];i++) {
sockprintf(socket,"");
lprintf(LOG_INFO,"%04u MIME Encoding and sending %s",socket,file_list[i]);
if(!mimeattach(socket,mime_boundary,file_list[i]))
lprintf(LOG_ERR,"%04u !ERROR opening/encoding/sending %s",socket,file_list[i]);
else {
endmime(socket,mime_boundary);
if(msg->hdr.auxattr&MSG_KILLFILE)
if(remove(file_list[i])!=0)
lprintf(LOG_WARNING,"%04u !ERROR %d removing %s",socket,errno,file_list[i]);
}
sockprintf(socket,"."); /* End of text */
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
static ulong sockmsgtxt(SOCKET socket, smbmsg_t* msg, char* msgtxt, ulong maxlines)
{
char filepath[MAX_PATH+1];
ulong retval;
char* boundary=NULL;
unsigned i;
str_list_t file_list=NULL;
str_list_t split;
if(msg->hdr.auxattr&MSG_FILEATTACH) {
boundary = mimegetboundary();
file_list = strListInit();
/* Parse header fields */
for(i=0;i<msg->total_hfields;i++)
if(msg->hfield[i].type==FILEATTACH)
strListPush(&file_list,(char*)msg->hfield_dat[i]);
/* Parse subject (if necessary) */
if(!strListCount(file_list)) { /* filename(s) stored in subject */
split=strListSplitCopy(NULL,msg->subj," ");
if(split!=NULL) {
for(i=0;split[i];i++) {
if(msg->idx.to!=0)
SAFEPRINTF3(filepath,"%sfile/%04u.in/%s"
,scfg.data_dir,msg->idx.to,getfname(truncsp(split[i])));
else
SAFEPRINTF3(filepath,"%sfile/%04u.out/%s"
,scfg.data_dir,msg->idx.from,getfname(truncsp(split[i])));
strListPush(&file_list,filepath);
}
strListFree(&split);
}
}
}
retval = sockmimetext(socket,msg,msgtxt,maxlines,file_list,boundary);
strListFree(&file_list);
if(boundary!=NULL)
free(boundary);
return(retval);
}
static u_long resolve_ip(char *inaddr)
char* p;
char* addr;
char buf[128];
HOSTENT* host;
SAFECOPY(buf,inaddr);
addr=buf;
if(*addr=='[' && *(p=lastchar(addr))==']') { /* Support [ip_address] notation */
addr++;
*p=0;
}
if(*addr==0)
return((u_long)INADDR_NONE);
for(p=addr;*p;p++)
if(*p!='.' && !isdigit(*p))
break;
if(!(*p))
if((host=gethostbyname(inaddr))==NULL)
return((u_long)INADDR_NONE);
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, const char* prot, const char* resp, char* user, char* passwd, char* host, SOCKADDR_IN* addr)
{
char reason[128];
ulong count;
if(addr!=NULL) {
SAFEPRINTF(reason,"%s LOGIN", prot);
count=loginFailure(startup->login_attempt_list, addr, prot, user, passwd);
if(startup->login_attempt_hack_threshold && count>=startup->login_attempt_hack_threshold)
hacklog(&scfg, reason, user, passwd, host, addr);
if(startup->login_attempt_filter_threshold && count>=startup->login_attempt_filter_threshold)
filter_ip(&scfg, (char*)prot, "- TOO MANY CONSECUTIVE FAILED LOGIN ATTEMPTS"
,host, inet_ntoa(addr->sin_addr), user, /* fname: */NULL);
}
mswait(startup->login_attempt_delay);
sockprintf(sock,(char*)resp);
}
static void pop3_thread(void* arg)
{
char* p;
char str[128];
char buf[512];
char host_name[128];
char host_ip[64];
char username[128];
char password[128];
uchar digest[MD5_DIGEST_SIZE];
char* response="";
char* msgtxt;
int i;
int rd;
long l;
ulong bytes;
SOCKET socket;
HOSTENT* host;
smb_t smb;
smbmsg_t msg;
user_t user;
client_t client;
mail_t* mail;
pop3_t pop3=*(pop3_t*)arg;
SetThreadName("POP3");
thread_up(TRUE /* setuid */);
free(arg);
socket=pop3.socket;
if(startup->options&MAIL_OPT_DEBUG_POP3)
lprintf(LOG_DEBUG,"%04d POP3 session thread started", socket);
if(startup->pop3_sound[0] && !(startup->options&MAIL_OPT_MUTE))
PlaySound(startup->pop3_sound, NULL, SND_ASYNC|SND_FILENAME);
SAFECOPY(host_ip,inet_ntoa(pop3.client_addr.sin_addr));
if(startup->options&MAIL_OPT_DEBUG_POP3)
lprintf(LOG_INFO,"%04d POP3 connection accepted from: %s port %u"
,socket, host_ip, ntohs(pop3.client_addr.sin_port));
if(startup->options&MAIL_OPT_NO_HOST_LOOKUP)
host=NULL;
else
host=gethostbyaddr((char *)&pop3.client_addr.sin_addr
if(host!=NULL && host->h_name!=NULL)
else
strcpy(host_name,"<no name>");
if(!(startup->options&MAIL_OPT_NO_HOST_LOOKUP) && (startup->options&MAIL_OPT_DEBUG_POP3))
lprintf(LOG_INFO,"%04d POP3 Hostname: %s", socket, host_name);
if(trashcan(&scfg,host_ip,"ip")) {
lprintf(LOG_NOTICE,"%04d !POP3 CLIENT IP ADDRESS BLOCKED: %s"
,socket, host_ip);
sockprintf(socket,"-ERR Access denied.");

rswindell
committed
mail_close_socket(socket);
thread_down();
return;
}
if(trashcan(&scfg,host_name,"host")) {
lprintf(LOG_NOTICE,"%04d !POP3 CLIENT HOSTNAME BLOCKED: %s"
,socket, host_name);
sockprintf(socket,"-ERR Access denied.");

rswindell
committed
mail_close_socket(socket);
thread_down();
return;
}
protected_uint32_adjust(&active_clients, 1);
update_clients();
/* Initialize client display */
client.size=sizeof(client);
client.time=time32(NULL);
SAFECOPY(client.addr,host_ip);
SAFECOPY(client.host,host_name);
client.port=ntohs(pop3.client_addr.sin_port);
client.protocol="POP3";
client.user="<unknown>";
client_on(socket,&client,FALSE /* update */);
SAFEPRINTF(str,"POP3: %s", host_ip);
if(startup->login_attempt_throttle
&& (login_attempts=loginAttempts(startup->login_attempt_list, &pop3.client_addr)) > 1) {
lprintf(LOG_DEBUG,"%04d POP3 Throttling suspicious connection from: %s (%u login attempts)"
,socket, inet_ntoa(pop3.client_addr.sin_addr), login_attempts);
mswait(login_attempts*startup->login_attempt_throttle);
}
do {
memset(&smb,0,sizeof(smb));
memset(&msg,0,sizeof(msg));

rswindell
committed
memset(&user,0,sizeof(user));
password[0]=0;
srand((unsigned int)(time(NULL) ^ (time_t)GetCurrentThreadId())); /* seed random number generator */
rand(); /* throw-away first result */
safe_snprintf(challenge,sizeof(challenge),"<%x%x%lx%lx@%.128s>"
,rand(),socket,(ulong)time(NULL),clock(),startup->host_name);
sockprintf(socket,"+OK Synchronet POP3 Server %s-%s Ready %s"
,revision,PLATFORM_DESC,challenge);
/* Requires USER command first */
for(i=3;i;i--) {
if(!sockgetrsp(socket,NULL,buf,sizeof(buf)))
break;
if(!strnicmp(buf,"USER ",5))
break;
if(!strnicmp(buf,"APOP ",5)) {
apop=TRUE;
break;
}
sockprintf(socket,"-ERR USER or APOP command expected");
if(!i || buf[0]==0) /* no USER or APOP command received */
break;
SKIP_WHITESPACE(p);
if(apop) {
if((response=strrchr(p,' '))!=NULL)
*(response++)=0;
else
response=p;
}
if(!apop) {
sockprintf(socket,"+OK");
if(!sockgetrsp(socket,"PASS ",buf,sizeof(buf))) {
sockprintf(socket,"-ERR PASS command expected");
break;
}
p=buf+5;
SKIP_WHITESPACE(p);
user.number=matchuser(&scfg,username,FALSE /*sysop_alias*/);

rswindell
committed
if(scfg.sys_misc&SM_ECHO_PW)

rswindell
committed
lprintf(LOG_NOTICE,"%04d !POP3 UNKNOWN USER: '%s' (password: %s)"

rswindell
committed
,socket, username, password);
else

rswindell
committed
lprintf(LOG_NOTICE,"%04d !POP3 UNKNOWN USER: '%s'"

rswindell
committed
,socket, username);
badlogin(socket, client.protocol, pop_err, username, password, host_name, &pop3.client_addr);
break;
}
if((i=getuserdat(&scfg, &user))!=0) {
lprintf(LOG_ERR,"%04d !POP3 ERROR %d getting data on user (%s)"
badlogin(socket, client.protocol, pop_err, NULL, NULL, NULL, NULL);
break;
}
if(user.misc&(DELETED|INACTIVE)) {
lprintf(LOG_NOTICE,"%04d !POP3 DELETED or INACTIVE user #%u (%s)"
,socket, user.number, username);
badlogin(socket, client.protocol, pop_err, NULL, NULL, NULL, NULL);
if(apop) {
strlwr(user.pass); /* this is case-sensitive, so convert to lowercase */
strcat(challenge,user.pass);
MD5_calc(digest,challenge,strlen(challenge));
MD5_hex((BYTE*)str,digest);
lprintf(LOG_NOTICE,"%04d !POP3 %s FAILED APOP authentication"
lprintf(LOG_DEBUG,"%04d !POP3 digest data: %s",socket,challenge);
lprintf(LOG_DEBUG,"%04d !POP3 calc digest: %s",socket,str);
lprintf(LOG_DEBUG,"%04d !POP3 resp digest: %s",socket,response);
badlogin(socket, client.protocol, pop_err, username, response, host_name, &pop3.client_addr);
break;
}
} else if(stricmp(password,user.pass)) {

rswindell
committed
if(scfg.sys_misc&SM_ECHO_PW)
lprintf(LOG_NOTICE,"%04d !POP3 FAILED Password attempt for user %s: '%s' expected '%s'"

rswindell
committed
,socket, username, password, user.pass);
else
lprintf(LOG_NOTICE,"%04d !POP3 FAILED Password attempt for user %s"

rswindell
committed
,socket, username);
badlogin(socket, client.protocol, pop_err, username, password, host_name, &pop3.client_addr);
if(user.pass[0])
loginSuccess(startup->login_attempt_list, &pop3.client_addr);
putuserrec(&scfg,user.number,U_COMP,LEN_COMP,host_name);
putuserrec(&scfg,user.number,U_NOTE,LEN_NOTE,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 POP3 %s logged in %s", socket, user.alias, apop ? "via APOP":"");
SAFEPRINTF(str,"POP3: %s",user.alias);
SAFEPRINTF(smb.file,"%smail",scfg.data_dir);
if(smb_islocked(&smb)) {
lprintf(LOG_WARNING,"%04d !POP3 MAIL BASE LOCKED: %s",socket,smb.last_error);
sockprintf(socket,"-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 !POP3 ERROR %d (%s) opening %s",socket,i,smb.last_error,smb.file);
sockprintf(socket,"-ERR %d opening %s",i,smb.file);
break;
}
mail=loadmail(&smb,&msgs,user.number,MAIL_YOUR,0);
for(l=bytes=0;l<msgs;l++) {
msg.hdr.number=mail[l].number;
if((i=smb_getmsgidx(&smb,&msg))!=SMB_SUCCESS) {
lprintf(LOG_ERR,"%04d !POP3 ERROR %d (%s) getting message index"
,socket, i, smb.last_error);
if((i=smb_lockmsghdr(&smb,&msg))!=SMB_SUCCESS) {
lprintf(LOG_WARNING,"%04d !POP3 ERROR %d (%s) locking message header #%lu"
,socket, i, smb.last_error, msg.hdr.number);