Skip to content
Snippets Groups Projects
mailsrvr.c 69.8 KiB
Newer Older

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

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

	thread_down();
}

BOOL rblchk(DWORD mail_addr_n, char* rbl_addr)
{
	char		name[256];
	DWORD		mail_addr;

	mail_addr=ntohl(mail_addr_n);
	sprintf(name,"%ld.%ld.%ld.%ld.%s"
		,mail_addr&0xff
		,(mail_addr>>8)&0xff
		,(mail_addr>>16)&0xff
		,(mail_addr>>24)&0xff
		,rbl_addr
		);

	if(gethostbyname(name)==NULL)
		return(FALSE);

	return(TRUE);
}


static void smtp_thread(void* arg)
{
	int			i,j,x;
	int			rd;
	char		str[512];
	char		buf[1024],*p,*tp;
	char		hdrfield[512];
	char		alias_buf[128];
	char		reverse_path[128];
	char		date[64];
	char		month[16];
	char		rcpt_name[128];
	char		rcpt_addr[128];
	char		sender[128]={0};
	char		sender_addr[128]={0};
	char		hello_name[128];
	char		host_name[128];
	char		host_ip[64];
	int			addr_len;
	ushort		xlat;
	ushort		nettype=NET_NONE;
	uint		usernum;
	ulong		crc;
	ulong		length;
	ulong		offset;
	BOOL		esmtp=FALSE;
	FILE*		msgtxt=NULL;
	char		msgtxt_fname[MAX_PATH];
	FILE*		rcptlst;
	char		rcptlst_fname[MAX_PATH];
	FILE*		spy=NULL;
	SOCKET		socket;
	HOSTENT*	host;
	smb_t		smb;
	smbmsg_t	msg;
	smbmsg_t	newmsg;
	user_t		user;
	client_t	client;
	struct tm	tm;
	smtp_t		smtp=*(smtp_t*)arg;
	SOCKADDR_IN server_addr;
	enum {
			 SMTP_STATE_INITIAL
			,SMTP_STATE_HELO
			,SMTP_STATE_MAIL_FROM
			,SMTP_STATE_RCPT_TO
			,SMTP_STATE_DATA_HEADER
			,SMTP_STATE_DATA_BODY

	} state = SMTP_STATE_INITIAL;

	thread_up();

	free(arg);

	socket=smtp.socket;

	lprintf("%04d SMTP RX Session thread started", socket);
#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((i=getsockname(socket, (struct sockaddr *)&server_addr,&addr_len))!=0) {
		lprintf("%04d !SMTP ERROR %d (%d) getting address/port"
			,socket, i, ERROR_VALUE);
		sockprintf(socket,"421 System error");
		mail_close_socket(socket);
		thread_down();
		return;
	} 

	memset(&msg,0,sizeof(smbmsg_t));

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

	lprintf("%04d SMTP connection accepted from: %s port %u"
		, socket, host_ip, ntohs(smtp.client_addr.sin_port));

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

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

	lprintf("%04d SMTP host name: %s", socket, host_name);

	strcpy(hello_name,host_name);

	if(trashcan(&scfg,host_ip,"ip")) {
		lprintf("%04d !SMTP server blocked in ip.can: %s"
		sockprintf(socket,"550 Access denied.");
	if(trashcan(&scfg,host_name,"host")) {
		lprintf("%04d !SMTP server blocked in host.can: %s"
		sockprintf(socket,"550 Access denied.");
	/*  SPAM Filters (mail-abuse.org) */
	if(startup->options&MAIL_OPT_USE_RBL
		&& rblchk(smtp.client_addr.sin_addr.s_addr
		lprintf("%04d !SMTP SPAM server filtered (RBL): %s [%s]"
			,socket, host_name, host_ip);
		spamlog(&scfg, "Listed on RBL (http://mail-abuse.org/rbl)"
			,host_name, host_ip);
		sockprintf(socket
			,"571 Mail from %s refused, see http://mail-abuse.org/rbl"
		thread_down();
		return;
	}
	if(startup->options&MAIL_OPT_USE_DUL
		&& rblchk(smtp.client_addr.sin_addr.s_addr
		lprintf("%04d !SMTP SPAM server filtered (DUL): %s [%s]"
			,socket, host_name, host_ip);
		spamlog(&scfg, "Listed on DUL (http://mail-abuse.org/dul)"
			,host_name, host_ip);
		sockprintf(socket
			,"571 Mail from %s refused, see http://mail-abuse.org/dul"
		thread_down();
		return;
	}
	if(startup->options&MAIL_OPT_USE_RSS
		&& rblchk(smtp.client_addr.sin_addr.s_addr
			,"relays.mail-abuse.org"
			)==TRUE) {
		lprintf("%04d !SMTP SPAM server filtered (RSS): %s [%s]"
			,socket, host_name, host_ip);
		spamlog(&scfg, "Listed on RSS (http://mail-abuse.org/rss)"
			,host_name, host_ip);
		sockprintf(socket
			,"571 Mail from %s refused, see http://mail-abuse.org/rss"
		thread_down();
		return;
	}

	sprintf(rcptlst_fname,"%sSMTP%d.LST", scfg.data_dir, socket);
	rcptlst=fopen(rcptlst_fname,"w+");
	if(rcptlst==NULL) {
		lprintf("%04d !SMTP ERROR %d creating recipient list: %s"
			,socket, errno, rcptlst_fname);
		sockprintf(socket,"421 System error");
	if(trashcan(&scfg,host_name,"smtpspy") 
		|| trashcan(&scfg,host_ip,"smtpspy")) {
		sprintf(str,"%sSMTPSPY.TXT", scfg.data_dir);
		spy=fopen(str,"a");
	}

	active_clients++;
	update_clients();

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

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

	sockprintf(socket,"220 %s Synchronet SMTP Server for %s v%s Ready"
		,scfg.sys_inetaddr,PLATFORM_DESC,MAIL_VERSION);
	while(1) {
		rd = sockreadline(socket, buf, sizeof(buf));
		if(rd<1) 
			break;
		if(spy!=NULL)
			fprintf(spy,"%s\n",buf);
		if(state>=SMTP_STATE_DATA_HEADER) {
			if(!strcmp(buf,".")) {
				lprintf("%04d SMTP End of message (%lu bytes)", socket, ftell(msgtxt));
					lprintf("%04d !SMTP Mail header missing 'FROM' field", socket);
					sockprintf(socket, "554 Mail header missing 'FROM' field");
					state=SMTP_STATE_HELO;
					continue;
				}

				if(msgtxt!=NULL) {
					rewind(msgtxt);
					smb.retry_time=scfg.smb_retry_time;
					if((i=smb_open(&smb))!=0) {
						lprintf("%04d !SMTP ERROR %d (%s) opening %s"
							,socket, i, smb.last_error, smb.file);
						sockprintf(socket, "452 Insufficient system storage");
						continue;
					}

					if(smb_fgetlength(smb.shd_fp)<1) {	 /* Create it if it doesn't exist */
						smb.status.max_crcs=scfg.mail_maxcrcs;
						smb.status.max_age=scfg.mail_maxage;
						smb.status.max_msgs=MAX_SYSMAIL;
						smb.status.attr=SMB_EMAIL;
						if((i=smb_create(&smb))!=0) {
							smb_close(&smb);
							lprintf("%04d !SMTP ERROR %d creating %s"
								,socket, i, smb.file);
							sockprintf(socket, "452 Insufficient system storage");
							continue;
						} 
					}

					if((i=smb_locksmbhdr(&smb))!=0) {
						smb_close(&smb);
						lprintf("%04d !SMTP ERROR %d locking %s"
							,socket, i, smb.file);
						sockprintf(socket, "452 Insufficient system storage");
						continue; }

					length=filelength(fileno(msgtxt))+2;	 /* +2 for translation string */

					if((i=smb_open_da(&smb))!=0) {
						smb_unlocksmbhdr(&smb);
						smb_close(&smb);
						lprintf("%04d !SMTP ERROR %d (%s) opening %s.sda"
							,socket, i, smb.last_error, smb.file);
						sockprintf(socket, "452 Insufficient system storage");
						continue; }

					if(scfg.sys_misc&SM_FASTMAIL)
						offset=smb_fallocdat(&smb,length,1);
					else
						offset=smb_allocdat(&smb,length,1);
					smb_close_da(&smb);

					smb_fseek(smb.sdt_fp,offset,SEEK_SET);
					xlat=XLAT_NONE;
					smb_fwrite(&xlat,2,smb.sdt_fp);
					x=SDT_BLOCK_LEN-2;				/* Don't read/write more than 255 */
					while(!feof(msgtxt)) {
						memset(buf,0,x);
						j=fread(buf,1,x,msgtxt);
						if(j<1)
							break;
						if((j!=x || feof(msgtxt)) && buf[j-1]=='\n' && buf[j-2]=='\r')
							buf[j-1]=buf[j-2]=0;
						if(scfg.mail_maxcrcs) {
							for(i=0;i<j;i++)
								crc=ucrc32(buf[i],crc); 
						}
						smb_fwrite(buf,j,smb.sdt_fp);
						x=SDT_BLOCK_LEN; 
					}
					smb_fflush(smb.sdt_fp);
					crc=~crc;

					if(scfg.mail_maxcrcs) {
						i=smb_addcrc(&smb,crc);
						if(i) {
							smb_freemsgdat(&smb,offset,length,1);
							smb_unlocksmbhdr(&smb);
							smb_close(&smb);
							lprintf("%04d !SMTP Duplicate message", socket);
							sockprintf(socket, "554 Duplicate Message");
							continue; 
						} 
					}

					msg.hdr.offset=offset;

					smb_dfield(&msg,TEXT_BODY,length);

					smb_unlocksmbhdr(&smb);
					if(msg.idx.subj==0) {
						p="";
						smb_hfield(&msg, SUBJECT, 0, p);
						msg.idx.subj=crc16(p);
					}
					rewind(rcptlst);
					while(!feof(rcptlst)) {
						if((i=smb_copymsgmem(&newmsg,&msg))!=0) {
							lprintf("%04d !SMTP ERROR %d copying message"
								,socket, i);
							break;
						}
						if(fgets(str,sizeof(str)-1,rcptlst)==NULL)
							break;
						usernum=atoi(str);
						fgets(rcpt_name,sizeof(rcpt_name)-1,rcptlst);
						if(fgets(rcpt_addr,sizeof(rcpt_addr)-1,rcptlst)==NULL)
							break;
						truncsp(rcpt_name);
						truncsp(rcpt_addr);

						snprintf(hdrfield,sizeof(hdrfield),
							"Received: from %s (%s [%s])\r\n"
							"          by %s [%s] (Synchronet Mail Server for %s v%s) with %s\r\n"
							"          for %s; %s"
							,host_name,hello_name,host_ip
							,scfg.sys_inetaddr,inet_ntoa(server_addr.sin_addr)
							,PLATFORM_DESC,MAIL_VERSION
							,esmtp ? "ESMTP" : "SMTP"
							,rcpt_addr,msgdate(msg.hdr.when_imported,date));
						smb_hfield(&newmsg, RFC822HEADER, (ushort)strlen(hdrfield), hdrfield);

						smb_hfield(&newmsg, RECIPIENT, (ushort)strlen(rcpt_name), rcpt_name);

						if(rcpt_addr[0]=='#') {	/* Local destination */
							newmsg.idx.to=atoi(rcpt_addr+1);
							smb_hfield(&newmsg, RECIPIENTEXT
								,(ushort)strlen(rcpt_addr+1), rcpt_addr+1);
						} else {
							newmsg.idx.to=0;
							nettype=NET_INTERNET;
							smb_hfield(&newmsg, RECIPIENTNETTYPE, nettype, &nettype);
							smb_hfield(&newmsg, RECIPIENTNETADDR
								,(ushort)strlen(rcpt_addr), rcpt_addr);
						}

						i=smb_addmsghdr(&smb,&newmsg,SMB_SELFPACK);
						smb_freemsgmem(&newmsg);
						if(i!=0) {
							lprintf("%04d !SMTP ERROR %d adding message header"
						lprintf("%04d SMTP Created message #%ld from %s to %s <%s>"
							,socket, newmsg.hdr.number, sender, rcpt_name, rcpt_addr);
						if(usernum) {
							sprintf(str,"\7\1n\1hOn %.24s\r\n\1m%s \1n\1msent you e-mail from: "
								"\1h%s\1n\r\n"
								,ctime((const time_t*)&newmsg.hdr.when_imported.time)
								,sender,sender_addr);
							if(!newmsg.idx.to) {	/* Forwarding */
								strcat(str,"\1mand it was automatically forwaded to: \1h");
								strcat(str,user.netmail);
								strcat(str,"\1n\r\n");
							}
							putsmsg(&scfg, usernum, str);
						}
					}
					smb_close(&smb);
					if(i) {
						smb_freemsgdat(&smb,offset,length,1);
						sockprintf(socket, "452 Insufficient system storage");
						continue;
					}
				}
				sockprintf(socket,SMTP_OK);
				state=SMTP_STATE_HELO;
				continue;
			}
			if(buf[0]==0 && state==SMTP_STATE_DATA_HEADER) {	
				state=SMTP_STATE_DATA_BODY;	/* Null line separates header and body */
				continue;
			}
			if(state==SMTP_STATE_DATA_BODY) {
				p=buf;
				if(*p=='.') p++;	/* Transparency (RFC821 4.5.2) */
				if(msgtxt!=NULL) 
					fprintf(msgtxt, "%s\r\n", p);
				continue;
			}
			/* RFC822 Header parsing */
			if(startup->options&MAIL_OPT_DEBUG_RX_HEADER)
				lprintf("%04d SMTP %s",socket, buf);

			if(!strnicmp(buf, "SUBJECT:",8)) {
				p=buf+8;
				while(*p && *p<=' ') p++;
				smb_hfield(&msg, SUBJECT, (ushort)strlen(p), p);
				strlwr(p);
				msg.idx.subj=crc16(p);
				continue;
			}
			if(!strnicmp(buf, "TO:",3)) {
				p=buf+3;
				while(*p && *p<=' ') p++;
				if(*p=='<')  {
					p++;
					tp=strrchr(p,'>');
					if(tp) *tp=0;
				}
				continue;
			}
			if(!strnicmp(buf, "REPLY-TO:",9)) {
				p=buf+9;
				while(*p && *p<=' ') p++;
				if(*p=='<')  {
					p++;
					tp=strchr(p,'>');
					if(tp) *tp=0;
				}
				nettype=NET_INTERNET;
				smb_hfield(&msg, REPLYTONETTYPE, nettype, &nettype);
				smb_hfield(&msg, REPLYTONETADDR, (ushort)strlen(p), p);
				continue;
			}
			if(!strnicmp(buf, "FROM:",5)) {
				p=strchr(buf+5,'<');
				if(p) {
					p++;
					tp=strchr(p,'>');
					if(tp) *tp=0;
					sprintf(sender_addr,"%.*s",(int)sizeof(sender_addr)-1,p);
				} else {
					p=buf+5;
					while(*p && *p<=' ') p++;
					sprintf(sender_addr,"%.*s",(int)sizeof(sender_addr)-1,p);
				}
				nettype=NET_INTERNET;
				smb_hfield(&msg, SENDERNETTYPE, sizeof(nettype), &nettype);
				smb_hfield(&msg, SENDERNETADDR, (ushort)strlen(sender_addr), sender_addr);
			
				p=buf+5;
				while(*p && *p<=' ') p++;
				if(*p=='"') { 
					p++;
					tp=strchr(p,'"');
				} else if(*p=='<') {
					p++;
					tp=strchr(p,'>');
				} else
					tp=strchr(p,'<');
				if(tp) *tp=0;
				truncsp(p);
				sprintf(sender,"%.*s",(int)sizeof(sender)-1,p);
				smb_hfield(&msg, SENDER, (ushort)strlen(sender), sender);
				continue;
			}
			if(!strnicmp(buf, "DATE:",5)) {
				p=buf+5;
				while(*p && *p<=' ') p++;
/*				lprintf("Sent: %s",p); */
				memset(&tm,0,sizeof(tm));
				while(*p && !isdigit(*p)) p++;
				/* DAY */
				tm.tm_mday=atoi(p);
				while(*p && isdigit(*p)) p++;
				/* MONTH */
				while(*p && *p<=' ') p++;
				sprintf(month,"%3.3s",p);
				if(!stricmp(month,"jan"))
					tm.tm_mon=0;
				else if(!stricmp(month,"feb"))
					tm.tm_mon=1;
				else if(!stricmp(month,"mar"))
					tm.tm_mon=2;
				else if(!stricmp(month,"apr"))
					tm.tm_mon=3;
				else if(!stricmp(month,"may"))
					tm.tm_mon=4;
				else if(!stricmp(month,"jun"))
					tm.tm_mon=5;
				else if(!stricmp(month,"jul"))
					tm.tm_mon=6;
				else if(!stricmp(month,"aug"))
					tm.tm_mon=7;
				else if(!stricmp(month,"sep"))
					tm.tm_mon=8;
				else if(!stricmp(month,"oct"))
					tm.tm_mon=9;
				else if(!stricmp(month,"nov"))
					tm.tm_mon=10;
				else
					tm.tm_mon=11;
				p+=4;
				/* YEAR */
				tm.tm_year=atoi(p);
				if(tm.tm_year>1900)
					tm.tm_year-=1900;
				while(*p && isdigit(*p)) p++;
				/* HOUR */
				while(*p && *p<=' ') p++;
				tm.tm_hour=atoi(p);
				while(*p && isdigit(*p)) p++;
				/* MINUTE */
				if(*p) p++;
				tm.tm_min=atoi(p);
				while(*p && isdigit(*p)) p++;
				/* SECONDS */
				if(*p) p++;
				tm.tm_sec=atoi(p);
				while(*p && isdigit(*p)) p++;
				/* TIME ZONE */
				while(*p && *p<=' ') p++;
				if(*p && (isdigit(*p) || *p=='-')) { /* HHMM or -HHMM format */
					sprintf(str,"%.*s",*p=='-'? 3:2,p);
					msg.hdr.when_written.zone=atoi(str)*60;
					p+=(*p=='-') ? 3:2;
					msg.hdr.when_written.zone+=atoi(p);
				}
				else if(!strnicmp(p,"PDT",3))
					msg.hdr.when_written.zone=(short)PDT;
				else if(!strnicmp(p,"MDT",3))
					msg.hdr.when_written.zone=(short)MDT;
				else if(!strnicmp(p,"CDT",3))
					msg.hdr.when_written.zone=(short)CDT;
				else if(!strnicmp(p,"EDT",3))
					msg.hdr.when_written.zone=(short)EDT;
				else if(!strnicmp(p,"PST",3))
					msg.hdr.when_written.zone=(short)PST;
				else if(!strnicmp(p,"MST",3))
					msg.hdr.when_written.zone=(short)MST;
				else if(!strnicmp(p,"CST",3))
					msg.hdr.when_written.zone=(short)CST;
				else if(!strnicmp(p,"EST",3))
					msg.hdr.when_written.zone=(short)EST;
				msg.hdr.when_written.time=mktime(&tm);
				continue;
			}
			if(!strnicmp(buf, "MESSAGE-ID:",11)) {
				p=buf+11;
				while(*p && *p<=' ') p++;
				smb_hfield(&msg, RFC822MSGID, (ushort)strlen(p), p);
				continue;
			}
			if(!strnicmp(buf, "IN-REPLY-TO:",12)) {
				p=buf+12;
				while(*p && *p<=' ') p++;
				smb_hfield(&msg, RFC822REPLYID, (ushort)strlen(p), p);
				continue;
			}
			/* Fall-through */
			smb_hfield(&msg, RFC822HEADER, (ushort)strlen(buf), buf);
			continue;
		}
		lprintf("%04d SMTP RX: %s", socket, buf);
		if(!strnicmp(buf,"HELO",4)) {
			p=buf+4;
			while(*p && *p<=' ') p++;
			sprintf(hello_name,"%.*s",(int)sizeof(hello_name)-1,p);
			sockprintf(socket,"250 %s",scfg.sys_inetaddr);
			esmtp=FALSE;
			state=SMTP_STATE_HELO;
			continue;
		}
		if(!strnicmp(buf,"EHLO",4)) {
			p=buf+4;
			while(*p && *p<=' ') p++;
			sprintf(hello_name,"%.*s",(int)sizeof(hello_name)-1,p);
			sockprintf(socket,"250 %s",scfg.sys_inetaddr);
			esmtp=TRUE;
			state=SMTP_STATE_HELO;
			continue;
		}
		if(!stricmp(buf,"QUIT")) {
			sockprintf(socket,"221 %s Service closing transmission channel",scfg.sys_inetaddr);
			break;
		} 
		if(!stricmp(buf,"NOOP")) {
			sockprintf(socket, SMTP_OK);
			continue;
		}
		if(state<SMTP_STATE_HELO) {
			/* RFC 821 4.1.1 "The first command in a session must be the HELO command." */
			lprintf("%04d !SMTP Missing HELO command",socket);
			sockprintf(socket, SMTP_BADSEQ);
			continue;
		}
		if(!stricmp(buf,"TURN")) {
			sockprintf(socket,"502 command not supported");
			continue;
		}
		if(!stricmp(buf,"RSET")) {
			smb_freemsgmem(&msg);
			memset(&msg,0,sizeof(smbmsg_t));		/* Initialize message header */
			reverse_path[0]=0;
			sender[0]=0;
			sender_addr[0]=0;
			sockprintf(socket,SMTP_OK);
			rewind(rcptlst);
			chsize(fileno(rcptlst),0);
			state=SMTP_STATE_HELO;
			continue;
		}
		if(!strnicmp(buf,"MAIL FROM:",10)) {
			smb_freemsgmem(&msg);
			memset(&msg,0,sizeof(smbmsg_t));		/* Initialize message header */
			memcpy(msg.hdr.id,"SHD\x1a",4);
			msg.hdr.version=smb_ver();
			msg.hdr.when_imported.time=time(NULL);
			msg.hdr.when_imported.zone=scfg.sys_timezone;
			p=buf+10;
			while(*p && *p<=' ') p++;
			sprintf(reverse_path,"%.*s",(int)sizeof(reverse_path)-1,p);
			sender[0]=0;
			sender_addr[0]=0;
			if(spy==NULL && trashcan(&scfg,reverse_path,"smtpspy")) {
				sprintf(str,"%sSMTPSPY.TXT", scfg.data_dir);
				spy=fopen(str,"a");
			}
			sockprintf(socket,SMTP_OK);
			state=SMTP_STATE_MAIL_FROM;
			continue;
		}
#if 0	/* No one uses this command */
		if(!strnicmp(buf,"VRFY",4)) {
			p=buf+4;
			while(*p && *p<=' ') p++;
			if(*p==0) {
				sockprintf(socket,"550 No user specified.");
				continue;
			}
#endif



		if(!strnicmp(buf,"RCPT TO:",8)) {

			if(state<SMTP_STATE_MAIL_FROM) {
				lprintf("%04d !SMTP Missing 'MAIL' command",socket);
				sockprintf(socket, SMTP_BADSEQ);
				continue;
			}

			sprintf(str,"%.*s",(int)sizeof(str)-1,buf+8);
			p=str;
			while(*p && *p<=' ') p++;

			if(*p=='<') p++;				/* Skip '<' */
			tp=strchr(str,'>');				/* Truncate '>' */
			if(tp!=NULL) *tp=0;

			tp=strrchr(str,'@');
			if(tp!=NULL) {
				
				/* RELAY */
				if(stricmp(tp+1,scfg.sys_inetaddr) && 
					resolve_ip(tp+1)!=server_addr.sin_addr.s_addr) {
					if(!trashcan(&scfg,host_name,"relay") && 
						!trashcan(&scfg,host_ip,"relay")) {
						lprintf("%04d !SMTP ILLEGAL RELAY ATTEMPT from %s [%s] to %s"
							,socket, host_name, host_ip, tp+1);
						sockprintf(socket, "550 Relay not allowed.");
						continue;
					}

					lprintf("%04d SMTP Relaying to external mail service: %s"
						,socket, tp+1);

					fprintf(rcptlst,"0\n%.*s\n%.*s\n"
						,(int)sizeof(rcpt_name)-1,p
						,(int)sizeof(rcpt_addr)-1,p);
					
					sockprintf(socket,SMTP_OK);
					state=SMTP_STATE_RCPT_TO;
					continue;
				}
			}
			while(*p && !isalnum(*p)) p++;	/* Skip '<' or '"' */
			tp=strrchr(p,'"');	
			if(tp!=NULL) *tp=0;	/* truncate at '"' */
			if(!stricmp(p,"SYSOP") || !stricmp(p,scfg.sys_id) 
				|| !stricmp(p,"POSTMASTER") || !stricmp(p,scfg.sys_op))
				usernum=1;
			else if(startup->options&MAIL_OPT_ALLOW_RX_BY_NUMBER 
				&& isdigit(*p)) {
				usernum=atoi(p);			/* RX by user number */
				/* verify usernum */
				username(&scfg,usernum,str);
				if(!str[0] || !stricmp(str,"DELETED USER"))
					usernum=0;
				p=str;
			} else {
				usernum=matchuser(&scfg,p);	/* RX by "user alias", "user.alias" or "user_alias" */

				if(!usernum) { /* RX by "real name", "real.name", or "sysop.alias" */
					
					/* convert "user.name" to "user name" */
					sprintf(rcpt_name,"%.*s",sizeof(rcpt_name)-1,p);
					for(tp=rcpt_name;*tp;tp++)	
						if(*tp=='.') *tp=' ';

					if(!stricmp(rcpt_name,scfg.sys_op))
						usernum=1;			/* RX by "sysop.alias" */

					if(!usernum)			/* RX by "real name" */
						usernum=userdatdupe(&scfg, 0, U_NAME, LEN_NAME, p, FALSE);

					if(!usernum)			/* RX by "real.name" */
						usernum=userdatdupe(&scfg, 0, U_NAME, LEN_NAME, rcpt_name, FALSE);
				lprintf("%04d !SMTP UNKNOWN USER: %s", socket, buf+8);
				sockprintf(socket, "550 Unknown User: %s", buf+8);
				continue;
			}
			user.number=usernum;
			if((i=getuserdat(&scfg, &user))!=0) {
				lprintf("%04d !SMTP ERROR %d getting data on user #%u (%s)"
					,socket, i, usernum, p);
				sockprintf(socket, "550 Unknown User: %s", buf+8);
				continue;
			}
			if(user.misc&(DELETED|INACTIVE)) {
				lprintf("%04d !SMTP DELETED or INACTIVE user #%u (%s)"
					,socket, usernum, p);
				sockprintf(socket, "550 Unknown User: %s", buf+8);
				continue;
			}
#if 0	/* implement later */
			if(useron.etoday>=cfg.level_emailperday[useron.level]
				&& !(useron.rest&FLAG('Q'))) {
				bputs(text[TooManyEmailsToday]);
				continue; }
#endif

			fprintf(rcptlst,"%u\n%.*s\n",user.number,(int)sizeof(rcpt_name)-1,p);

			/* Forward to Internet */
			tp=strrchr(user.netmail,'@');
			if(scfg.sys_misc&SM_FWDTONET && user.misc&NETMAIL 
				&& tp && strchr(tp,'.') && !strchr(tp,'/') 
				&& !strstr(tp,scfg.sys_inetaddr)) {
				lprintf("%04d SMTP Forwarding to: %s"
					,socket, user.netmail);
				fprintf(rcptlst,"%s\n",user.netmail);
				sockprintf(socket,"251 User not local; will forward to %s", user.netmail);
			} else { /* Local (no-forward) */
				fprintf(rcptlst,"#%u\n",usernum);
				sockprintf(socket,SMTP_OK);
			}
			state=SMTP_STATE_RCPT_TO;
			continue;
		}
		if(!strnicmp(buf,"DATA",4)) {
			if(state<SMTP_STATE_RCPT_TO) {
				lprintf("%04d !SMTP Missing 'RCPT TO' command", socket);
				sockprintf(socket, SMTP_BADSEQ);
				continue;
			}
			if(msgtxt!=NULL)
				fclose(msgtxt);
			sprintf(msgtxt_fname,"%sSMTP%d.RX", scfg.data_dir, socket);
			if((msgtxt=fopen(msgtxt_fname,"w+b"))==NULL) {
				lprintf("%04d !SMTP Error %d opening %s"
					,socket, errno, msgtxt_fname);
				sockprintf(socket, "452 Insufficient system storage");
				continue;
			}
			sockprintf(socket, "354 send the mail data, end with <CRLF>.<CRLF>");
			state=SMTP_STATE_DATA_HEADER;
			continue;
		}
		sockprintf(socket,"500 Syntax error");
		lprintf("%04d !SMTP UNSUPPORTED COMMAND: '%s'", socket, buf);
	}

	/* Free up resources here */

	smb_freemsgmem(&msg);

	if(msgtxt!=NULL) {
		fclose(msgtxt);
		if(!(startup->options&MAIL_OPT_DEBUG_RX_BODY))
			unlink(msgtxt_fname);
	}
	if(rcptlst!=NULL) {
		fclose(rcptlst);
		unlink(rcptlst_fname);
	}
	if(spy!=NULL)
		fclose(spy);

	status(STATUS_WFC);

	lprintf("%04d SMTP RX Session thread terminated", socket);
	active_clients--;
	update_clients();
	client_off(socket);

	thread_down();
}

BOOL bounce(smb_t* smb, smbmsg_t* msg, char* err, BOOL immediate)
{
	char		str[128],full_err[512];
	int			i;
	ushort		agent=AGENT_PROCESS;
	smbmsg_t	newmsg;

	if((i=smb_lockmsghdr(smb,msg))!=0) {
		lprintf("0000 !BOUNCE ERROR %d locking message header #%lu"
			,i,msg->hdr.number);
		return(FALSE);
	}

	msg->hdr.delivery_attempts++;
	if((i=smb_putmsg(smb,msg))!=0) {
		lprintf("0000 !BOUNCE ERROR %d incrementing delivery attempt counter",i);
		smb_unlockmsghdr(smb,msg);
		return(FALSE);
	}

	lprintf("0000 !Delivery attempt #%u FAILED for message #%lu from %s to %s"
		,msg->hdr.delivery_attempts, msg->hdr.number
		,msg->from, msg->to_net.addr);

	if(!immediate && msg->hdr.delivery_attempts<startup->max_delivery_attempts) {
		smb_unlockmsghdr(smb,msg);
		return(TRUE);
	}
	
	lprintf("0000 !Bouncing message back to %s", msg->from);

	newmsg=*msg;
	/* Mark original message as deleted */
	msg->hdr.attr|=MSG_DELETE;
	msg->idx.attr=msg->hdr.attr;
	if((i=smb_putmsg(smb,msg))!=0) {
		lprintf("0000 !BOUNCE ERROR %d deleting message",i);
		smb_unlockmsghdr(smb,msg);
		return(FALSE);
	}
	smb_unlockmsghdr(smb,msg);

	newmsg.hfield=NULL;
	newmsg.hfield_dat=NULL;
	newmsg.total_hfields=0;
	newmsg.idx.to=newmsg.idx.from;
	newmsg.idx.from=0;
	smb_hfield(&newmsg, RECIPIENT, (ushort)strlen(newmsg.from), newmsg.from);
	if(newmsg.idx.to) {
		sprintf(str,"%u",newmsg.idx.to);
		smb_hfield(&newmsg, RECIPIENTEXT, (ushort)strlen(str), str);
	}
	smb_hfield(&newmsg, RECIPIENTAGENT, sizeof(ushort), &newmsg.from_agent);
	smb_hfield(&newmsg, RECIPIENTNETTYPE, sizeof(newmsg.from_net.type), &newmsg.from_net.type);
	if(newmsg.from_net.type) 
		smb_hfield(&newmsg, RECIPIENTNETADDR, (ushort)strlen(newmsg.from_net.addr)
			,newmsg.from_net.addr);
	strcpy(str,"Mail Delivery Subsystem");
	smb_hfield(&newmsg, SENDER, (ushort)strlen(str), str);
	smb_hfield(&newmsg, SENDERAGENT, sizeof(agent), &agent);
	
	/* Put error message in subject for now */
	sprintf(full_err,"Delivery failure of message to %s after %u attempts: %s"
		,(char*)msg->to_net.addr, msg->hdr.delivery_attempts, err);
	smb_hfield(&newmsg, SUBJECT, (ushort)strlen(full_err), full_err);

	if((i=smb_addmsghdr(smb,&newmsg,SMB_SELFPACK))!=0)
		lprintf("0000 !BOUNCE ERROR %d adding message header",i);

	newmsg.dfield=NULL;				/* Don't double-free the data fields */
	newmsg.hdr.total_dfields=0;
	smb_freemsgmem(&newmsg);

	return(TRUE);
}

#ifdef __BORLANDC__
#pragma argsused
#endif
static void sendmail_thread(void* arg)
{
	int			i,j;
	char		to[128];
	char		mx[128];
	char		mx2[128];
	char		err[128];
	char		buf[512];
	char		fromaddr[256];
	char*		server;
	char*		msgtxt=NULL;
	char*		p;
	ulong		offset;
	ulong		last_msg=0;
	ulong		total_msgs;
	ulong		ip_addr;
	ulong		dns;
	BOOL		success;
	SOCKET		sock=INVALID_SOCKET;
	SOCKADDR_IN	addr;
	SOCKADDR_IN	server_addr;
	time_t		last_scan=0;
	smb_t		smb;
	smbmsg_t	msg;

	sendmail_running=TRUE;

	thread_up();

	lprintf("0000 SendMail thread started");

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

	while(server_socket!=INVALID_SOCKET) {

		if(startup->options&MAIL_OPT_NO_SENDMAIL) {
			mswait(1000);
			continue;
		}

		if(active_sendmail!=0) {
			active_sendmail=0;
			update_clients();
		}

		smb_close(&smb);

		if(sock!=INVALID_SOCKET) {
			sock=INVALID_SOCKET;
		}

		if(msgtxt!=NULL) {
			smb_freemsgtxt(msgtxt);
			msgtxt=NULL;
		}

		smb_freemsgmem(&msg);

		mswait(3000);
		if((i=smb_open(&smb))!=0) 
			continue;
		if((i=smb_locksmbhdr(&smb))!=0)
			continue;
		if((i=smb_getstatus(&smb))!=0) {
			smb_unlocksmbhdr(&smb);
			continue;
		}
		smb_unlocksmbhdr(&smb);
		if(smb.status.last_msg==last_msg && time(NULL)-last_scan<startup->rescan_frequency)
			continue;
		last_msg=smb.status.last_msg;
		last_scan=time(NULL);
		total_msgs=smb.status.total_msgs;