/* SMTP Server state machine - see RFC 821
 *  enhanced 4/88 Dave Trulli nn2z
 */

/****************************************************************************
*	$Id: smtpserv.c 1.13 94/01/04 14:10:28 ROOT_DOS Exp $
*
*	25 Jun 92    		paul@wolf.demon.co.uk added automated mail bouncing	
*	13 Jul 92	1.3	GT	Remove 1.2 changes.							
*  27 Aug 92   1.6    mt@kram.demon.co.uk added smtp separator 
*	03 Sep 92	1.7	GT	Fix missing BOUNCER conditional.			
*	08 Dec 92	1.8	mt@kram.org: Occasional pwait() during copy
*						cms@home:	 Alias-File Mail Bouncing System	
*	19 Mar 93	1.9	GT		Debugging mailit.							
*	04 Apr 93	1.10	GT		Re-enable beep.									
*	05 Apr 93	1.11	GT		Reinstate delay call.							
*	08 May 93	1.12	GT		Fix warnings.									
*							IAY	Improve behaviour of rest of system				
*									while this module is copying files,				
*									particularly while in SMTP MODE QUEUE.			
*							IAY	Fix dot transparency as per RFC 821.			
*							GBD	Fix alias expansion to allow multiple spaces.
*	08 Dec 93	1.13	GT		Fix bounce message envelope From.				
*									Version and compilation date in SMTP banner.	
*									Implement VRFY.									
*									Fix wandering pointer in getmsgtxt ().			
*									Close mail spool file before ACK.				
*									Fix "." at column 256.							
*
*  ATARI Version by David Nash - dnash@chaos.demon.co.uk
*
*	__stdargs smtpserv
*
****************************************************************************/

/*
** The following definition controls how often the copy_data
** function calls pwait().  Lower numbers mean better response
** to other things that may be going on at the expense of the
** speed of that particular function.
*/
#define COPY_DATA_WAIT 1

#include <stdio.h>
#include <time.h>

#undef	DEBUG_LOCKS

#ifdef NOVELL

#include "nit.h"
#include <io.h>
#include <dos.h>
#include <dir.h>
#include <time.h>

#endif

extern	char	novell_server_name[128];
extern	char	novell_mail_ext[4];
extern	unsigned short	novell_start;
extern	char	*smtp_separator;
extern	int		Smtpbeep;

#ifdef UNIX
#include <sys/types.h>
#endif
#if	defined(__STDC__) || defined(__TURBOC__)
#include <stdarg.h>
#endif
#include <ctype.h>
#include <setjmp.h>
#include "global.h"
#include "mbuf.h"
#include "cmdparse.h"
#include "socket.h"
#include "iface.h"
#include "proc.h"
#include "smtp.h"
#include "commands.h"
#include "dirutil.h"
#include "bm.h"
#include "mailbox.h"
#include "domain.h"
#include "files.h"
#include "ip.h"

char *Days[7] =
{
	"Sun","Mon","Tue","Wed","Thu","Fri","Sat"
}
;
char *Months[12] =
{
	"Jan","Feb","Mar","Apr","May","Jun",
	"Jul","Aug","Sep","Oct","Nov","Dec"
}
;



static void copy_data __ARGS((FILE *from, FILE *to));
static struct list *expandalias __ARGS((struct list **head,char *user));
static int  getmsgtxt __ARGS((struct smtpsv *mp));
static struct smtpsv *mail_create __ARGS((void));
static void mail_clean __ARGS((struct smtpsv *mp));
static int mailit __ARGS((FILE *data,char *from,struct list *tolist));
static int router_queue __ARGS((FILE *data,char *from,struct list *to));
static void smtplog __ARGS((char *fmt,...));
static void __stdargs smtpserv __ARGS((int s,void *unused,void *p));
static int mailuser __ARGS((FILE *data,char *from,char *to));
static int validate_user __ARGS((char *user));
static int validate_sender __ARGS((char *sender));
static int remlist __ARGS((struct list **head, struct list *unwanted));

/* Command table */
static char *commands[] =
{
	"helo",
#define	HELO_CMD	0
	"noop",
#define	NOOP_CMD	1
	"mail from:",
#define	MAIL_CMD	2
	"quit",
#define	QUIT_CMD	3
	"rcpt to:",
#define	RCPT_CMD	4
	"help",
#define	HELP_CMD	5
	"data",
#define	DATA_CMD	6
	"rset",
#define	RSET_CMD	7
	"expn",
#define EXPN_CMD	8
	"vrfy",
#define VRFY_CMD	9	
	NULLCHAR
}
;

/* Reply messages */
static char Help[] 			= "214-Commands:\n214-HELO NOOP MAIL QUIT RCPT HELP DATA RSET EXPN\n214 End\n";
static char Banner[] 		= "220 %s %s %s %s SMTP ready\n";
static char Closing[] 		= "221 Closing\n";
static char Ok[] 				= "250 Ok\n";
static char Reset[] 			= "250 Reset state\n";
static char Sent[] 			= "250 Sent\n";
static char Ourname[] 		= "250 %s, Pleased to meet you\n";
static char Unwanted[] 		= "250 Local policy blocks mail from <%s>\n";
static char Enter[] 			= "354 Enter mail, end with .\n";
static char Ioerr[] 			= "452 Temp file write error\n";
static char Badcmd[] 		= "500 Command unrecognized\n";
static char Lowmem[] 		= "421 System overloaded, try again later\n";
static char Syntax[] 		= "501 Syntax error\n";
static char Needrcpt[] 		= "503 Need RCPT (recipient)\n";
static char Needsender[]	= "503 Local policy blocking mail from you\n";
static char Unknown[] 		= "550 <%s> address unknown\n";
static char Noalias[] 		= "550 No alias for <%s>\n";
static char UnknownRcpt[]	= "550 Unknown recipient <%s>\n";

static failure fail;
static int bad_sender;
char default_address[SLINELEN];

static int Ssmtp = -1; /* prototype socket for service */

/* Start up SMTP receiver service */
int
smtp1(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	struct sockaddr_in lsocket;
	int s;

	if(Ssmtp != -1)
	{
		return 0;
	}
	psignal(Curproc,0);	/* Don't keep the parser waiting */
	chname(Curproc,"SMTP listener");

	lsocket.sin_family = AF_INET;
	lsocket.sin_addr.s_addr = INADDR_ANY;
	if(argc < 2)
		lsocket.sin_port = IPPORT_SMTP;
	else
		lsocket.sin_port = atoi(argv[1]);

	Ssmtp = socket(AF_INET,SOCK_STREAM,0);
	bind(Ssmtp,(char *)&lsocket,sizeof(lsocket));
	listen(Ssmtp,1);
	for(;;)
	{
		if((s = accept(Ssmtp,NULLCHAR,(int *)NULL)) == -1)
			break;	/* Service is shutting down */

		if(availmem() < Memthresh)
		{
			usprintf(s,Lowmem);
			shutdown(s,1);
		}
		else
		{
			/* Spawn a server */
			newproc("SMTP server",2048,smtpserv,s,NULL,NULL,0);
		}
	}
	return 0;
}

/* Shutdown SMTP service (existing connections are allowed to finish) */
int
smtp0(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	close_s(Ssmtp);
	Ssmtp = -1;
	return 0;
}

static void __stdargs smtpserv(s,unused,p)
int s;
void *unused;
void *p;
{
	struct smtpsv *mp;
	char **cmdp,buf[LINELEN],*arg,*cp,*cmd,*newaddr;
	struct list *ap,*list;
	int cnt;
	char address_type;

	sockmode(s,SOCK_ASCII);
	sockowner(s,Curproc);		/* We own it now */
	log(s,"open SMTP");

	if((mp = mail_create()) == NULLSMTPSV)
	{
		tprintf(Nospace);
		log(s,"close SMTP - no space");
		close_s(s);
		return;
	}
	mp->s = s;

	(void) usprintf(s, Banner, Hostname, Version, __DATE__, __TIME__);

loop:
	if ((cnt = recvline(s,buf,sizeof(buf))) == -1)
	{
		/* He closed on us */
		goto quit;
	}
	if(cnt < 4)
	{
		/* Can't be a legal command */
		usprintf(mp->s,Badcmd);
		goto loop;
	}	
	rip(buf);
	cmd = buf;

	/* Translate entire buffer to lower case */
	for(cp = cmd;*cp != '\0';cp++)
		*cp = tolower(*cp);

	/* Find command in table; if not present, return syntax error */
	for(cmdp = commands;*cmdp != NULLCHAR;cmdp++)
		if(strncmp(*cmdp,cmd,strlen(*cmdp)) == 0)
			break;
	if(*cmdp == NULLCHAR)
	{
		(void) usprintf(mp->s,Badcmd);
		goto loop;
	}
	arg = &cmd[strlen(*cmdp)];
	/* Skip spaces after command */
	while(*arg == ' ')
		arg++;
		
	/* Execute specific command */

	switch(cmdp-commands) {
	case HELO_CMD:
		free(mp->system);
		mp->system = strdup(arg);
		(void) usprintf(mp->s,Ourname,Hostname);
		break;
	case NOOP_CMD:
		(void) usprintf(mp->s,Ok);
		break;
	case MAIL_CMD:
		bad_sender = 0;
		if((cp = getname(arg)) == NULLCHAR)
		{
			(void) usprintf(mp->s,Syntax);
			break;
		}
		if (!validate_sender(cp))
		{
			(void) usprintf(mp->s,Unwanted,cp);
			smtplog("rejected: from: %s",cp);
			bad_sender = 1;
			break;
		}
		free(mp->from);
		mp->from = strdup(cp);
		(void) usprintf(mp->s,Ok);
		break;
	case QUIT_CMD:
		(void) usprintf(mp->s,Closing);
		goto quit;
	case RCPT_CMD:
		/* Specify recipient */
		fail = NO_FAIL;
		if((cp = getname(arg)) == NULLCHAR)
		{
			(void) usprintf(mp->s,Syntax);
			break;
		}

		/* rewrite address if possible */
		if((newaddr = rewrite_address(cp)) != NULLCHAR)
		{
			strcpy(buf,newaddr);
			cp = buf;
			free(newaddr);
		}

		/* check if address is ok */
		if ((address_type = validate_address(cp)) == BADADDR)
		{
			(void) usprintf(mp->s,Unknown,cp);
			break;
		}

		/* if a local address check for an alias */
		if (address_type == LOCAL)
		{
			expandalias(&mp->to, cp);
			for (ap = mp->to; ap != NULLLIST; ap = ap->next)
			{
				if (ap->type == LOCAL)
				{
					if (!validate_user(ap->val))
					{
						switch (fail)
						{
						case FAIL_DELIVER:
							break;
						case FAIL_DEFAULT:
							expandalias(&ap->next, default_address);
							if (validate_user(ap->next->val))
								{
								remlist(&mp->to, ap);
								break;
								}
								/* else fall through to fail */
						default:
							usprintf(mp->s,UnknownRcpt,ap->val);
							fail = FAIL_BAD;
							break;
						}
					if (fail == FAIL_BAD)
						break;
					}
				}
			}
			if (fail == FAIL_BAD)
				break;

		}
		else
			/* a remote address is added to the list */
			addlist(&mp->to, cp, address_type);

		(void) usprintf(mp->s,Ok);
		break;
	case HELP_CMD:
		(void) usprintf(mp->s,Help);
		break;
	case DATA_CMD:
		if ((mp->to == NULLLIST) || fail == FAIL_BAD)
			(void) usprintf(mp->s,Needrcpt);
		else if (bad_sender)
			(void) usprintf(mp->s,Needsender);
		else if ((mp->data = tmpfile()) == NULLFILE)
			(void) usprintf(mp->s,Ioerr);
		else
			getmsgtxt(mp);
		break;
	case RSET_CMD:
		del_list(mp->to);
		mp->to = NULLLIST;
		fail = NO_FAIL;
		(void) usprintf(mp->s,Reset);
		break;
	case EXPN_CMD:
		if (*arg == '\0')
		{
			(void) usprintf(mp->s,Syntax);
			break;
		}

		list = NULLLIST;
		/* rewrite address if possible */
		if((newaddr = rewrite_address(arg)) != NULLCHAR)
			if(strcmp(newaddr,arg) == 0)
			{
				free(newaddr);
				newaddr = NULLCHAR;
			}
			else
			{
				strcpy(buf,newaddr);
				arg = buf;
			}
		list = NULLLIST;
		expandalias(&list,arg);
		if (strcmp(list->val,arg) == 0 && list->next == NULLLIST)
			if(newaddr == NULLCHAR)
			{
				(void) usprintf(mp->s,Noalias,arg);
				del_list(list);
				break;
			}
		ap = list;
		while (ap->next != NULLLIST)
		{
			(void) usprintf(mp->s,"250-%s\n",ap->val);
			ap = ap->next;
		}
		usprintf(mp->s,"250 %s\n",ap->val);
		del_list(list);
		free(newaddr);
		break;

    case VRFY_CMD:
		if (*arg == '\0') {
			usprintf(mp->s, Syntax);
			break;
		}

		if (validate_user(arg) == 0)
			usprintf(mp->s, UnknownRcpt, arg);
		else
	    	usprintf(mp->s, Ok);

		break;

	}
	goto loop;

quit:
	log(mp->s,"close SMTP");
	close_s(mp->s);
	mail_clean(mp);
	smtptick(NULL);			/* start SMTP daemon immediately */
}

/* read the message text */
static int
getmsgtxt(mp)
struct smtpsv *mp;
{
	char buf[LINELEN];
	register char *p = buf;
	long t;

	/* Add timestamp; ptime adds newline */

	time(&t);
	fprintf(mp->data,"Received: ");

	if (mp->system != NULLCHAR)
		fprintf(mp->data, "from %s ", mp->system);
	fprintf(mp->data, "by %s with SMTP\n\tid AA%ld ; %s",
	  Hostname, get_msgid(), ptime(&t));
	  
	if (ferror(mp->data)) {
		usprintf(mp->s, Ioerr);
		return 1;
	}
	else
		usprintf(mp->s, Enter);
		
	while(1) {
		int no_check_dot = 0;					/* don't check for "."				*/
		char *has_newline;						/* nz - line contains newline		*/

		p = buf;		/* GT 11 Dec 93						*/

		if (recvline(mp->s, p, sizeof(buf)) == -1)
			return 1;

		has_newline = strchr(p, '\n');
		rip(p);

		/*
		** If the line starts with a '.', this is either
		** the end of the message or a line where a dot
		** has been added for "dot transparency"
		*/

		if (no_check_dot == 0 && *p == '.') {
			/*
			** Strip off leading '.'; if there is nothing
			** else on the line, this is the end of the
			** data.  Otherwise, we have managed to remove
			** the protecting dot and p now points at the
			** unprotected message line.
			*/
			
			if (*++p == '\0') {
				int rc;		/* result code						*/

				/* Also sends appropriate response */

		 		rc = mailit(mp->data, mp->from, mp->to);
				if (fclose(mp->data) != 0)
 		    		rc = 1;
 
 				mp->data = NULLFILE;

 				if (rc != 0)
					(void) usprintf(mp->s,Ioerr);
				else
					(void) usprintf(mp->s,Sent);

				del_list(mp->to);
				mp->to = NULLLIST;
				return 0;
			}

		}

	 	if (has_newline != 0)
 			no_check_dot = 0;						/* check "." next time				*/
 		else
 	   	no_check_dot = 1;						/* don't check "." next time		*/
 
		/* for UNIX mail compatiblity */

		if (strncmp(p,"From ",5) == 0)
			(void) putc('>',mp->data);

		/* Append to data file */

		if(fprintf(mp->data,"%s\n",p) < 0) {
			(void) usprintf(mp->s,Ioerr);
			return 1;
		}
	}
}

/* Create control block, initialize */
static struct smtpsv *
mail_create()
{
	register struct smtpsv *mp;

	mp = (struct smtpsv *)callocw(1,sizeof(struct smtpsv));
	mp->from = strdup("");	/* Default to null From address */
	return mp;
}

/* Free resources, delete control block */
static void
mail_clean(mp)
register struct smtpsv *mp;
{
	if (mp == NULLSMTPSV)
		return;
	free(mp->system);
	free(mp->from);
	if(mp->data != NULLFILE)
		fclose(mp->data);
	del_list(mp->to);
	free((char *)mp);
}


/* Given a string of the form <user@host>, extract the part inside the
 * brackets and return a pointer to it.
 */
char *
getname(cp)
register char *cp;
{
	register char *cp1;

	if ((cp = strchr(cp,'<')) == NULLCHAR)
		return NULLCHAR;
	cp++;	/* cp -> first char of name */
	if ((cp1 = strchr(cp,'>')) == NULLCHAR)
		return NULLCHAR;
	*cp1 = '\0';
	return cp;
}

#ifdef NOVELL

long int get_mail_id(char *user,char *server)
{
	int		connectionID=1;
	int		cCode=0;
	int		DefCon;
	long	int retval;
	int		err;

	cCode=GetConnectionID(server,&connectionID);

	DefCon=GetDefaultConnectionID();

	if(DefCon!=connectionID)
	{
		SetPreferredConnectionID(connectionID);
	}

	cCode=GetBinderyObjectID(user,OT_USER,&retval);

	if(cCode!=0)
	{
		retval=0;
	}

	if(DefCon!=connectionID)
	{
		SetPrimaryConnectionID(DefCon);
	}
	return	retval;
}

void send_alert(char *user,char *server,char *from)
{
	int		numcon,conlist[100],reslist[100];
	int		connectionID=1;
	int		cCode=0;
	int		DefCon;
	long	int retval;
	int		err;
	char	message[128];

	cCode=GetConnectionID(server,&connectionID);

	DefCon=GetDefaultConnectionID();

	if(DefCon!=connectionID)
	{
		SetPreferredConnectionID(connectionID);
	}

	cCode=GetObjectConnectionNumbers(user,OT_USER,&numcon,conlist,100);

	sprintf(message,"You have mail from %s",from);

	message[55]=0; /* Nasty truncation to cope with bindings */

	cCode=SendBroadcastMessage(message,conlist,reslist,numcon);

	if(DefCon!=connectionID)
	{
		SetPrimaryConnectionID(DefCon);
	}
}

#endif

/*
** copy_data
**
** This function copies all available data from an input file to an
** output file.  This is done in chunks of LINELEN for efficiency.
** In addition, the function deschedules itself every so often so
** that large copy operations of this type do not completely block
** processing of other operations.
*/
static void
copy_data ( from, to )
FILE *from, *to;
{
	int c;
	#if COPY_DATA_WAIT != 1
		int counter = 0;
	#endif
	char buf[LINELEN];
	while ( (c = fread(buf, 1, sizeof(buf), from) ) > 0)
	{
		if (fwrite(buf, 1, c, to) != c)
			break;
		#if COPY_DATA_WAIT == 1
			pwait(NULL);
		#else
			if (++counter == COPY_DATA_WAIT)
			{
				pwait(NULL);
				counter = 0;
			}
		#endif
	}
}

/* General mailit function. It takes a list of addresses which have already
** been verified and expanded for aliases. Base on the current mode the message
** is place in an mbox, the outbound smtp queue or the rqueue interface
*/
static int
mailit(data,from,tolist)
FILE *data;
char *from;
struct list *tolist;
{
	struct list *ap, *dlist = NULLLIST;
	register FILE *fp;
	char	mailbox[85], *cp, *host, *qhost;
	int	fail = 0;
	time_t	t;

	if ((Smtpmode & QUEUE) != 0)
		return(router_queue(data,from,tolist));

	do
	{
		qhost = NULLCHAR;
		for(ap = tolist;ap != NULLLIST;ap = ap->next)
		{
#if	defined (DEBUG_LOCKS)
			smtplog ("mailit (): ap->val = %s", ap->val);
			smtplog ("mailit (): ap->type = %d", ap->type);
#endif
			if (ap->type == DOMAIN)
			{
				if ((host = strrchr(ap->val,'@')) != NULLCHAR)
					host++;
				else
					host = Hostname;
				if(qhost == NULLCHAR)
					qhost = host;
				if(stricmp(qhost,host) == 0)
				{
					ap->type = BADADDR;
					addlist(&dlist,ap->val,0);
				}
			}
		}
		if(qhost != NULLCHAR)
		{
#if	defined (DEBUG_LOCKS)
			smtplog ("mailit (): qhost = %s, queueing", qhost);
#endif
			rewind(data);
			queuejob(data,qhost,dlist,from,NULL);
			del_list(dlist);
			dlist = NULLLIST;
		}
	}
	while(qhost != NULLCHAR)
		;

	for(ap = tolist;ap != NULLLIST;ap = ap->next)
	{
		if(ap->type != LOCAL)
		{
			ap->type = DOMAIN;
			continue;
		}
		rewind(data);
		/* strip off host name of LOCAL addresses */
		if ((cp = strchr(ap->val,'@')) != NULLCHAR)
			*cp = '\0';

		/* truncate long user names */
		if (strlen(ap->val) > MBOXLEN)
			ap->val[MBOXLEN] = '\0';

		/* if mail file is busy save it in our smtp queue
		 * and let the smtp daemon try later.
		 */

#ifdef NOVELL
		if(!novell_start)
		{
#endif
			if (mlock(Mailspool,ap->val))
			{
				int32 msgid;

#if	defined (DEBUG_LOCKS)
				smtplog ("mailit (): mail spool file %s, %s locked, queueing",
						 Mailspool, ap->val);
#endif
				addlist(&dlist,ap->val,0);
				fail = queuejob(data,Hostname,dlist,from,&msgid);
				if (!fail)
					delay_job(msgid);

				del_list(dlist);
				dlist = NULLLIST;
			}
#ifdef NOVELL
		}
#endif
		else
		{
			char buf[LINELEN];
			int tocnt = 0;
            extern int smtpverbose;

#ifdef NOVELL
			long int oid;
			char	box[13];
			struct	ffblk	fblock;
			static	int	done_rand=0;

			if(novell_start)
			{
				oid=get_mail_id(ap->val,novell_server_name);

				ltoa(oid,box,16);

				if(!done_rand)
				{
					randomize(); /* This is non-deterministic */
					done_rand=1;
				}

				do
				{
					sprintf(mailbox,"%s/%s/%s/%04x%04x.%s",novell_server_name,"SYS:/MAIL",box,rand(),rand(),novell_mail_ext);
				}
				while(!findfirst(mailbox,&fblock,0))
					;
			}
			else
#endif
			{
				sprintf(mailbox,"%s/%s.txt",Mailspool,ap->val);
			}

#ifndef	AMIGA
			if((fp = fopen(mailbox,APPEND_TEXT)) != NULLFILE)
			{
#	ifdef NOVELL
				if(novell_start)
				{
					send_alert(ap->val,novell_server_name,from);
				}
#	endif

#else
			if((fp = fopen(mailbox,"r+")) != NULLFILE)
			{
				(void) fseek(fp, 0L, 2);
#endif
				time(&t);
				if (smtp_separator)
					fprintf(fp, "%s\n", smtp_separator);
				fprintf(fp,"From %s %s",from,ctime(&t));
				host = NULLCHAR;
				while(fgets(buf,sizeof(buf),data) != NULLCHAR)
				{
					if(buf[0] == '\n')
					{
						if(tocnt == 0)
							fprintf(fp,"%s%s\n",
							  Hdrs[APPARTO],
							  ap->val);
						fputc('\n',fp);
						break;
					}
					fputs(buf,fp);
					rip(buf);
					switch(htype(buf))
					{
					case TO:
					case CC:
						++tocnt;
						break;
					case RRECEIPT:
						if((cp = getaddress(buf,0))
						  != NULLCHAR)
						{
							free(host);
							host = strdup(cp);
						}
						break;
					}
				}
				copy_data(data, fp);
				if(ferror(fp))
					fail = 1;
				else
					fprintf(fp,"\n");
				/* Leave a blank line between msgs */
				fclose(fp);

				if (smtpverbose)
				{
					tprintf("New mail arrived for %s from %s\n",ap->val, from);
				}

				if (Smtpbeep)
					printf("\a\a");

				if(host != NULLCHAR)
				{
					rewind(data); /* Send return receipt */
					mdaemon(data,host,NULLLIST,0);
					free(host);
				}
			}
			else
				fail = 1;
#ifdef NOVELL
			if(!novell_start)
			{
				(void) rmlock(Mailspool,ap->val);
			}
#else
			(void) rmlock(Mailspool,ap->val);
#endif
			if (fail)
			{
				break;
			}
			smtplog("deliver: To: %s From: %s",ap->val,from);
		}
	}
	return fail;
}

/* Return Date/Time in Arpanet format in passed string */
char *
ptime(t)
long *t;
{
	/* Print out the time and date field as
	 *		"DAY day MONTH year hh:mm:ss ZONE"
	 */
	register struct tm *ltm;
	static char tz[4];
	static char str[40];
	char *p;
#ifndef ATARI
	 *getenv();
#endif	 
	/* Read the system time */

	ltm = localtime(t);

	if (*tz == '\0') {
		if ((p = getenv("TZ")) == NULL)
			strcpy(tz, "UTC");
		else {
			if (ltm->tm_isdst == 0)
				strncpy(tz, p, 3);
	    	else
				strncpy(tz, p + 4, 3);
		}
	}

	/* rfc 822 format */

	sprintf(str,"%s, %.2d %s %02d %02d:%02d:%02d %.3s\n",
	  Days[ltm->tm_wday],
	  ltm->tm_mday,
	  Months[ltm->tm_mon],
	  ltm->tm_year,
	  ltm->tm_hour,
	  ltm->tm_min,
	  ltm->tm_sec,
	  tz);
	return(str);
}

long
get_msgid()
{
	char sfilename[LINELEN];
	char s[20];
	register long sequence = 0;
	FILE *sfile;

	sprintf(sfilename,"%s/sequence.seq",Mailqdir);
	sfile = fopen(sfilename,READ_TEXT);

	/* if sequence file exists, get the value, otherwise set it */
	if (sfile != NULL)
	{
		(void) fgets(s,sizeof(s),sfile);
		sequence = atol(s);
		/* Keep it in range of and 8 digit number to use for dos name prefix. */
		if (sequence < 0L || sequence > 99999999L )
			sequence = 0;
		fclose(sfile);
	}

	/* increment sequence number, and write to sequence file */
	sfile = fopen(sfilename,WRITE_TEXT);
	fprintf(sfile,"%ld",++sequence);
	fclose(sfile);
	return sequence;
}

#if defined(MSDOS) || defined(ATARI)
/* Illegal characters in a DOS filename */
static char baddoschars[] = "\"[]:|<>+=;,";
#endif

/* test if mail address is valid */
int
validate_address(s)
char *s;
{
	char *cp, *t;
	int32 addr;

	/* if address has @ in it the check dest address */
	if (*s == '@' && (cp = strrchr(s,':')) != NULLCHAR)
		for (t = s,cp++; (*t++ = *cp++) != 0; )
			;
	if ((cp = strrchr(s,'@')) != NULLCHAR)
	{
		cp++;
		/* 1st check if its our hostname
		* if not then check the hosts file and see
		* if we can resolve ther address to a know site
		* or one of our aliases
		*/
		if (strcmp(cp,Hostname) != 0)
		{
			if ((addr = mailroute(cp, 0)) == 0
			  && (Smtpmode & QUEUE) == 0)
				return BADADDR;
			if (ismyaddr(addr) == NULLIF)
				return DOMAIN;
		}

		/* on a local address remove the host name part */
		*--cp = '\0';
	}

	/* if using an external router leave address alone */
	if ((Smtpmode & QUEUE) != 0)
		return LOCAL;

	/* check for the user%host hack */
	if ((cp = strrchr(s,'%')) != NULLCHAR)
	{
		*cp = '@';
		cp++;
		/* reroute based on host name following the % seperator */
		if (mailroute(cp, 0) == 0)
			return BADADDR;
		else
			return DOMAIN;
	}

#if defined (MSDOS) || defined(ATARI)	/* dos file name checks */
	/* Check for characters illegal in MS-DOS file names */
	for(cp = baddoschars;*cp != '\0';cp++)
	{
		if(strchr(s,*cp) != NULLCHAR)
			return BADADDR;	
	}
#endif
	return LOCAL;
}

/* place a mail job in the outbound queue */
int
queuejob(dfile,host,to,from,msgid)
FILE *dfile;
char *host;
struct list *to;
char *from;
int32 *msgid;
{
	FILE *fp;
	struct list *ap;
	char tmpstring[50], prefix[9], buf[LINELEN];
	register int cnt;
	int32 this_msgid;

	this_msgid = get_msgid();
	sprintf(prefix,"%ld",this_msgid);
	if (msgid)
		*msgid = this_msgid;
	mlock(Mailqdir,prefix);
	sprintf(tmpstring,"%s/%s.txt",Mailqdir,prefix);
	if((fp = fopen(tmpstring,WRITE_TEXT)) == NULLFILE)
	{
		(void) rmlock(Mailqdir,prefix);
		return 1;
	}
	while((cnt = fread(buf, 1, LINELEN, dfile)) > 0)
		if(fwrite(buf, 1, cnt, fp) != cnt)
			break;
	if(ferror(fp))
	{
		fclose(fp);
		(void) rmlock(Mailqdir,prefix);
		return 1;
	}
	fclose(fp);
	sprintf(tmpstring,"%s/%s.wrk",Mailqdir,prefix);
	if((fp = fopen(tmpstring,WRITE_TEXT)) == NULLFILE)
	{
		(void) rmlock(Mailqdir,prefix);
		return 1;
	}
	fprintf(fp,"%s\n%s\n",host,from);
	for(ap = to; ap != NULLLIST; ap = ap->next)
	{
		fprintf(fp,"%s\n",ap->val);
		smtplog("queue job %s To: %s From: %s",prefix,ap->val,from);
	}
	fclose(fp);
	(void) rmlock(Mailqdir,prefix);
	return 0;
}

/* Deliver mail to the appropriate mail boxes */
static int
router_queue(data,from,to)
FILE *data;
char *from;
struct list *to;
{
	register struct list *ap;
	FILE *fp;
	char tmpstring[50];
	char prefix[9];

	sprintf(prefix,"%ld",get_msgid());
	mlock(Routeqdir,prefix);
	sprintf(tmpstring,"%s/%s.txt",Routeqdir,prefix);
	if((fp = fopen(tmpstring,WRITE_TEXT)) == NULLFILE)
	{
		(void) rmlock(Routeqdir,prefix);
		return 1;
	}
	rewind(data);
	copy_data(data, fp);
	if(ferror(fp))
	{
		fclose(fp);
		(void) rmlock(Routeqdir,prefix);
		return 1;
	}
	fclose(fp);
	sprintf(tmpstring,"%s/%s.wrk",Routeqdir,prefix);
	if((fp = fopen(tmpstring,WRITE_TEXT)) == NULLFILE)
	{
		(void) rmlock(Routeqdir,prefix);
		return 1;
	}
	fprintf(fp,"From: %s\n",from);
	for(ap = to;ap != NULLLIST;ap = ap->next)
	{
		fprintf(fp,"To: %s\n",ap->val);
	}
	fclose(fp);
	(void) rmlock(Routeqdir,prefix);
	smtplog("rqueue job %s From: %s",prefix,from);
	return 0;
}

/* add an element to the front of the list pointed to by head
** return NULLLIST if out of memory.
*/
struct list *
addlist(head,val,type)
struct list **head;
char *val;
int type;
{
	register struct list *tp;

	tp = (struct list *)callocw(1,sizeof(struct list));

	tp->next = NULLLIST;

	/* allocate storage for the char string */
	tp->val = strdup(val);
	tp->type = type;

	/* add entry to front of existing list */
	if (*head == NULLLIST)
		*head = tp;
	else
	{
		tp->next = *head;
		*head = tp;
	}
	return tp;

}

/* Remove and entry from the list ...			CMS 19/12/92
	return TRUE on success or FALSE on failure */
static int remlist(struct list **head, struct list *unwanted)
	{
	struct list *ptr, **prev;
	prev = head;
	for (ptr = *head; ptr; ptr = ptr->next)
		if (ptr == unwanted)
			{
			*prev = ptr->next;
			free (ptr);
			break;
			}
		else prev = &ptr->next;
	return ptr == unwanted;
	}

#define SKIPWORD(X) while(*X && *X!=' ' && *X!='\t' && *X!='\n' && *X!= ',') X++;
#define SKIPSPACE(X) while(*X ==' ' || *X =='\t' || *X =='\n' || *X == ',') X++;

/* check for and alias and expand alias into a address list */
static struct list *
expandalias(head, user)
struct list **head;
char *user;
{
	FILE *fp;
	register char *s,*p;
	struct rr *rrp, *rrlp;
	int inalias = 0;
	struct list *tp;
	char buf[LINELEN];

	/* no alias file found */
	if ((fp = fopen(Alias, READ_TEXT)) == NULLFILE)
	{
		fail = FAIL_DELIVER;
		/* Try MB, MG or MR domain name records */
		rrlp = rrp = resolve_mailb(user);
		while(rrp != NULLRR)
		{
			if(rrp->rdlength > 0)
			{
				/* remove the trailing dot */
				rrp->rdata.name[rrp->rdlength-1] = '\0';
				/* replace first dot with @ if there is no @ */
				if(strchr(rrp->rdata.name,'@') == NULLCHAR
				  && (p = strchr(rrp->rdata.name,'.')) !=
				  NULLCHAR)
					*p = '@';
				if(strchr(rrp->rdata.name,'@') != NULLCHAR)
					tp = addlist(head,rrp->rdata.name,
					  DOMAIN);
				else
					tp = addlist(head,rrp->rdata.name,
					  LOCAL);
				++inalias;
			}
			rrp = rrp->next;
		}
		free_rr(rrlp);
		if(inalias)
			return tp;
		else
			return addlist(head, user, LOCAL);
	}

	while (fgets(buf,LINELEN,fp) != NULLCHAR)
	{
		p = buf;
		if ( *p == '#' || *p == '\0')
			continue;
		rip(p);

		/* if not in an matching entry skip continuation lines */
		if (!inalias && isspace(*p))
			continue;

		/* when processing an active alias check for a continuation */
		if (inalias)
		{
			if (!isspace(*p))
				break;	/* done */
		}
		else
		{
			s = p;
			SKIPWORD(p);
			*p++ = '\0';	/* end the alias name */
			SKIPSPACE (p);
			if (fail == NO_FAIL && strcmp(s, "default") == 0)
				if (strncmp(p, "deliver", 3) == 0)
					fail = FAIL_DELIVER;
				else {
					fail = FAIL_DEFAULT;
					strcpy(default_address, p);
					}
			if (strcmp(s,user) != 0)
				continue;	/* no match go on */
			inalias = 1;
		}

		/* process the recipients on the alias line */
		SKIPSPACE(p);
		while(*p != '\0' && *p != '#')
		{
			s = p;
			SKIPWORD(p);
			if (*p != '\0')
				*p++ = '\0';

			/* find hostname */
			if (strchr(s,'@') != NULLCHAR)
				tp = addlist(head,s,DOMAIN);
			else
				tp = addlist(head,s,LOCAL);
			SKIPSPACE(p);
		}
	}
	(void) fclose(fp);

	if (inalias)	/* found and processed and alias. */
		return tp;

	/* no alias found treat as a local address */
	return addlist(head, user, LOCAL);
}

#if	defined(ANSIPROTO)
static void
smtplog(char *fmt, ...)
{
	va_list ap;
	char *cp;
	long t;
	FILE *fp;

	if ((fp = fopen(Maillog,APPEND_TEXT)) == NULLFILE)
		return;
	time(&t);
	cp = ctime(&t);
	rip(cp);
	fprintf(fp,"%s ",cp);
	va_start(ap,fmt);
	vfprintf(fp,fmt,ap);
	va_end(ap);
	fprintf(fp,"\n");
	fclose(fp);
}

#else

static void
smtplog(fmt,arg1,arg2,arg3,arg4)
char *fmt;
int arg1,arg2,arg3,arg4;
{
	char *cp;
	long t;
	FILE *fp;

	if ((fp = fopen(Maillog,APPEND_TEXT)) == NULLFILE)
		return;
	time(&t);
	cp = ctime(&t);
	rip(cp);
	fprintf(fp,"%s ",cp);
	fprintf(fp,fmt,arg1,arg2,arg3,arg4);
	fprintf(fp,"\n");
	fclose(fp);
}
#endif

/* send mail to a single user. Can be called from the ax24 mailbox or
** from the return mail function in the smtp client
*/
static int
mailuser(data,from,to)
FILE *data;
char *from;
char *to;
{

	int address_type, ret;
	struct list *tolist = NULLLIST;

	/* check if address is ok */
	if ((address_type = validate_address(to)) == BADADDR)
	{
		return 1;
	}
	/* if a local address check for an alias */
	if (address_type == LOCAL)
		expandalias(&tolist, to);
	else
		/* a remote address is added to the list */
		addlist(&tolist, to, address_type);
	ret = mailit(data,from,tolist);
	del_list(tolist);
	return ret;

}

/* Mailer daemon return mail mechanism */
int
mdaemon(data,to,lp,bounce)
FILE *data;		/* pointer to rewound data file */
char *to;		/* Overridden by Errors-To: line if bounce is true */
struct list *lp;	/* error log for failed mail */
int bounce;		/* True for failed mail, otherwise return receipt */
{
	time_t t;
	FILE *tfile;
	char buf[LINELEN], *cp, *newto = NULLCHAR;
	int cnt;
	if(to == NULLCHAR || (to != NULLCHAR && *to == '\0') || bounce)
	{
		while(fgets(buf,sizeof(buf),data) != NULLCHAR)
		{
			if(buf[0] == '\n')
				break;
			/* Look for Errors-To: */
			if(htype(buf) == ERRORSTO &&
			  (cp = getaddress(buf,0)) != NULLCHAR)
			{
				free(newto);
				newto = strdup(cp);
				break;
			}
		}
		if(newto == NULLCHAR && ((to != NULLCHAR && *to == '\0') ||
		  to == NULLCHAR))
			return -1;
		rewind(data);
	}
	if((tfile = tmpfile()) == NULLFILE)
		return -1;
	time(&t);
	fprintf(tfile,"%s%s",Hdrs[DATE],ptime(&t));
	fprintf(tfile,"%s<%ld@%s>\n",Hdrs[MSGID],get_msgid(),Hostname);
	fprintf(tfile,"%sMAILER-DAEMON@%s (Mail Delivery Subsystem)\n",
	  Hdrs[FROM],Hostname);
	fprintf(tfile,"%s%s\n",Hdrs[TO],newto != NULLCHAR ? newto : to);
	fprintf(tfile,"%s%s\n\n",Hdrs[SUBJECT],
	  bounce ? "Failed mail" : "Return receipt");
	if(bounce)
	{
		fprintf(tfile,"  ===== transcript follows =====\n\n");
		for (; lp != NULLLIST; lp = lp->next)
			fprintf(tfile,"%s\n",lp->val);
		fprintf(tfile,"\n");
	}
	fprintf(tfile,"  ===== %s follows ====\n",
	  bounce ? "Unsent message" : "Message header");

	while(fgets(buf,sizeof(buf),data) != NULLCHAR)
	{
		if(buf[0] == '\n')
			break;
		fputs(buf,tfile);
	}
	if(bounce)
	{
		fputc('\n',tfile);
		while((cnt = fread(buf,1,sizeof(buf),data)) > 0)
			fwrite(buf,1,cnt,tfile);
	}
	fseek(tfile,0L,0);

	/* A null From<> so no looping replys to MAIL-DAEMONS */

	sprintf(buf, "postmaster@%s", Hostname);
	mailuser(tfile, buf, newto != NULLCHAR ? newto : to);

	fclose(tfile);
	free(newto);
	return 0;
}

/* Check to see that we are accepting mail from this sender. If we aren't
 * then tell 'em that mail from them is Unwanted. I added this facility to
 * allow auto-rejection of messages from auto-reply daemons. G7LEU
 */
static int
validate_sender(sender)
char *sender;
{
	FILE *fp;
	char buf[LINELEN + 1], *p;
	if ((fp = fopen(Mailkill, READ_TEXT)) == NULLFILE)
		/* Can't open SMTP reject file so let 'em through */
		return 1;
	while (fgets(buf,LINELEN,fp) != NULLCHAR)
		{
		p = buf;
		if (*p == '#' || *p == '\0')
			continue;
		rip(p);
		if (!stricmp(p, sender))
			{
            extern int smtpverbose;
			if (smtpverbose)
				tprintf("SMTP: Rejected mail from '%s'\n", sender);
			fclose(fp);
			return 0;
			}
		}
	fclose(fp);
	/* Sender OK */
	return 1;
}


/* Check to see if a local user actually exists - use the FTPUSERS file
 * Returns 0 if the user doesn't exist, 1 otherwise
 */
static int
validate_user(user)
char *user;
{
	char *cp;

/* always allow 'postmaster' */
	if (stricmp(user,"postmaster")==0)
	{
		return 1;
	}
/* otherwise check the ftpusers file */

#	ifdef NOVELL
	if(novell_start)
	{
		if(!get_mail_id(user,novell_server_name))
		{
			return	0;
		}
	}
	else
#	endif
	{
		if ((cp=userlookup(user,NULL,NULLCHARP,NULLINT,NULL)) == NULLCHAR)
		{
			return 0;
		}
		free(cp);
	}
	return 1;
}
