/*  SMTP Server state machine - see RFC 821
 *  enhanced 4/88 Dave Trulli nn2z
 *
 * Mods by G1EMM, PA0GRI and KO4KS
 */
#include <stdio.h>
#include <time.h>
#ifdef UNIX
#include <sys/types.h>
#include <fcntl.h>
#endif
#if	defined(__STDC__) || defined(__TURBOC__)
#include <stdarg.h>
#endif
#include <ctype.h>
#include <setjmp.h>
#include "global.h"
#include "config.h"
#include "mbuf.h"
#include "cmdparse.h"
#include "socket.h"
#ifdef	LZW
#include "lzw.h"
#endif
#include "iface.h"
#include "proc.h"
#include "smtp.h"
#include "commands.h"
#include "dirutil.h"
#include "mailbox.h"
#include "mailutil.h"
#include "bm.h"
#include "domain.h"
#include "session.h"
#include "files.h"
#include "mailfor.h"
#ifdef DELEGATE
#include "delegate.h"
#endif
#ifdef  NNTPS
#include "nntp.h"
#endif

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" };



char *host_or_wpage_exp __ARGS((char *to,int hier,int exphome));
extern char *wpage_exp __ARGS((char *to,int hier,int exphome));
extern FILE *fopennew __ARGS((char *fname, char *mode));
extern void wpageAdd __ARGS((char *entry, int bbs, int updateit));
extern int isgroup __ARGS((char *group));
extern int BBSlookup __ARGS((char *bbs));
struct list *expandalias __ARGS((struct list **head,char *user,char *orig));
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 smtpserv __ARGS((int s,void *unused,void *p));
int mailuser __ARGS((FILE *data,char *from,char *to,char *origto));
extern int msgidcheck __ARGS((char *string));
extern int rdaemon __ARGS((FILE *data,char *from,char *to,char *msg,char msgtype, int mode));
#ifdef RMAIL
extern int rmail __ARGS((FILE *fp, char *from));
#endif
#ifdef REQSVR
extern int reqsvr __ARGS((FILE *fp, char *from));
#endif
#ifdef TUTOR
extern int proxy __ARGS((FILE *fp,char *from));
#endif
extern FILE *subdir_fopen __ARGS((char *name, char *mode));
extern char *nntp_name_expansion __ARGS((char *name));
void statusCtl __ARGS((char *who, char *extension,struct let *info,int entry, int readit));
void updateCtl __ARGS((char *who,struct let *info));
int makeBBSbid __ARGS((char *bid, char *line,int ours));
void updateFwd __ARGS((char *who, char *area,long bid));
int isanIPhost __ARGS((char *str));
static struct list *expandgroup __ARGS((struct list **head,char *user, char *orig));
#ifdef TRANSLATE
char *translate __ARGS((register char *searchfor, int *didwe));
#endif


/* 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
#ifdef	LZW
	"xlzw",
#define XLZW_CMD	9
#endif
	NULLCHAR
};

/* Reply messages */
static char Banner[] = "220 %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, Share and Enjoy!\n";
#ifdef	LZW
static char LZWOk[] = "250 %d %d LZW Ok\n";
static char Help[] = "214-Commands:\n214-HELO NOOP MAIL QUIT RCPT HELP DATA RSET EXPN XLZW\n214 End\n";
#else
static char Help[] = "214-Commands:\n214-HELO NOOP MAIL QUIT RCPT HELP DATA RSET EXPN\n214 End\n";
#endif
static char Enter[] = "354 Enter mail, end with .\n";
static char DupBidMsg[] = "550 Duplicate BID found in Message-Id line\n";
#if 0
static char Lowmem[] = "421 System overloaded, try again later\n";
#endif
static char Ioerr[] = "452 Temp file write error\n";
static char Badcmd[] = "500 Command unrecognized\n";
static char Syntax[] = "501 Syntax error\n";
static char Needrcpt[] = "503 Need RCPT (recipient)\n";
static char Unknown[] = "550 <%s> address unknown\n";
static char Noalias[] = "550 No alias for <%s>\n";
char DAEMONSTR[] = "%sMAILER-DAEMON@%s (Mail Delivery Subsystem)\n";

static int Ssmtp = -1; /* prototype socket for service */
int MbHolding = 0;
extern char *Historyfile;
#ifdef WPAGES
extern int MbWpages;
#endif
extern int Smtpbidcheck;

#ifdef	MSDOS
/* Valid characters in a DOS filename matrix */
static unsigned char doschars[] = {
	0x00, 0x00, 0x00, 0x00, 0xfa, 0x23, 0xff, 0x03,
	0xff, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xff, 0x6f,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
	};
static int dosfnchr __ARGS((int ch));
#endif

/* Start up SMTP receiver service */
int
smtp1(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return (installserver (argc, argv, &Ssmtp, "SMTP listener", IPPORT_SMTP,
		"SMTP server", smtpserv, 2048, NULL));
}

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

static void
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;
    	char *orig;
#ifdef	LZW
	extern int Smtpslzw;
	int lzwbits, lzwmode;
#endif

	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);

loop:	pwait (NULL);
	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){
#ifdef	LZW
	case XLZW_CMD:
        if(!Smtpslzw) {
            usprintf(mp->s,Badcmd);
        } else {
			lzwmode = lzwbits = 0;
			sscanf(arg,"%d %d",&lzwbits,&lzwmode);
			if(!((lzwmode == 0 || lzwmode == 1)
			  && (lzwbits > 8 && lzwbits < 17))) {
				lzwmode = LZWCOMPACT;
				lzwbits = LZWBITS;
                usprintf(mp->s,Badcmd);
            } else {
                usprintf(mp->s,LZWOk,lzwbits,lzwmode);
                lzwinit(mp->s,lzwbits,lzwmode);
            }
        }
		break;
#endif
	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:
		if((cp = getname(arg)) == NULLCHAR){
			(void) usprintf(mp->s,Syntax);
			break;
		}
#ifdef TRANSLATE
		cp = translate (cp, &mp->translated);
#endif
		free(mp->from);
#ifdef TRANSLATE
		mp->from = cp;
#else
		mp->from = strdup(cp);
#endif
		(void) usprintf(mp->s,Ok);
		break;
	case QUIT_CMD:
		(void) usprintf(mp->s,Closing);
		goto quit;
	case RCPT_CMD:	/* Specify recipient */
		if((cp = getname(arg)) == NULLCHAR){
			(void) usprintf(mp->s,Syntax);
			break;
		}
		orig = strdup (cp);
		/* 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);
			free (orig);
			break;
		}
		/* if a local address check for an alias */
		if (address_type == LOCAL)
			expandalias(&mp->to, cp, orig);
		else
			/* a remote address is added to the list */
			addlist(&mp->to, cp, address_type, orig);

		(void) usprintf(mp->s,Ok);
		free (orig);
		break;
	case HELP_CMD:
		(void) usprintf(mp->s,Help);
		break;
	case DATA_CMD:
		if(mp->to == NULLLIST)
			(void) usprintf(mp->s,Needrcpt);
		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;
		(void) usprintf(mp->s,Reset);
		break;
	case EXPN_CMD:
		if (*arg == '\0'){
			(void) usprintf(mp->s,Syntax);
			break;
		}

		list = NULLLIST;
		orig = strdup (arg);
        /* 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, orig);
		free (orig);
		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;
	}
	goto loop;

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


int
makeBBSbid (bid, line, ours)
char *bid, *line;
int ours;
{
char *cp;
int retval = 0;

	*bid = 0;
	if((cp = getname(line)) == NULLCHAR)
		return retval;
	strcpy(bid,cp);
	cp = strchr(bid,'@');
	/* A trailing ".bbs" indicates that the Message-ID was generated
	 * from a BBS style message, and not a RFC-822 message.
	 */
	if(cp != NULLCHAR && stricmp(&bid[strlen(bid) - 4], ".bbs") == 0) {
		*cp = '\0'; /*retain the bid given by user*/
		retval = 1;
	} else {
		if (ours)	{
			/*This is a message with no bid, MSGID is <msg#@hostname>
			 *make this BID style 'msg#_host'
			 *ie. replace @ with _ and
			 *cut off after first part of hostname  - WG7J
			 */
			*cp = '_';
			if((cp = strchr(cp,'.')) != NULLCHAR)
				*cp = '\0';
			retval = 1;
		}
	}
	bid[13] = '\0'; /* BIDs should be no longer than 13 bytes */
	return retval;
}


extern char shortversion[];
extern char *Mbfwdinfo;

/* read the message text
 * This now checks for Message-Id's that could be bids
 * and deletes the message if so. - WG7J
 */
static int
getmsgtxt(mp)
struct smtpsv *mp;
{
char buf[LINELEN];
register char *p = buf;
long t;
#ifdef MBFWD
char *cp, *cp2;
char bid[LINELEN];
time_t now;
FILE *fp;
int foundbid = 0;
#endif

	/* Add timestamp; ptime adds newline */
	time(&t);
	fprintf(mp->data,Hdrs[RECEIVED]);
	if(mp->system != NULLCHAR)
		fprintf(mp->data,"from %s ",mp->system);
#ifdef MBFWD
	fprintf(mp->data,"by %s (%s) with SMTP\n\tid AA%ld ; %s",
            Hostname, (Mbfwdinfo != NULLCHAR) ? Mbfwdinfo : shortversion, \
            get_msgid(0), ptime(&t));
#else
	fprintf(mp->data,"by %s (%s) with SMTP\n\tid AA%ld ; %s",
            Hostname, shortversion, get_msgid(0), ptime(&t));
#endif
	if(ferror(mp->data))	{
		(void) usprintf(mp->s,Ioerr);
		return 1;
	} else {
		(void) usprintf(mp->s,Enter);
	}
	while(1)	{
		pwait (NULL);
		if(recvline(mp->s,p,sizeof(buf)) == -1)
			return 1;
		rip(p);
		/* check for end of message ie a . or escaped .. */
		if (*p == '.')	{
			if (*++p == '\0')	{
#ifdef MBFWD
				/* Also sends appropriate response */
				/* if duplicate BID, do not send - WG7J */
				if (mp->dupbid)
					(void) usputs(mp->s,DupBidMsg);
				else
#endif
					if(mailit(mp->data,mp->from,mp->to) != 0)
						(void) usputs(mp->s,Ioerr);
				else
					(void) usprintf(mp->s,Sent);
				fclose(mp->data);
				mp->data = NULLFILE;
				del_list(mp->to);
				mp->to = NULLLIST;
#ifdef MBFWD
		                /* If there is a BID set, save it in the history file - WG7J */
                		if(mp->bid != NULLCHAR)
		                    if((fp = fopennew(Historyfile,APPEND_TEXT)) != NULL) {
                		        /* Timestamp added to allow automatic expiry
		                         * of bid file - WG7J
		                         */
		                        time(&now);
                		        fprintf(fp,"%-13.13s %-14ld\n",mp->bid,now); /* Save BID */
		                        fclose(fp);
                		        /* Free this bid ! */
		                        free(mp->bid);
                		        mp->bid = NULL;
		                    }
#endif
				return 0;
			} else if (!(*p == '.' && *(p+1) == '\0'))
				p--;
		}
		/* for UNIX mail compatiblity */
		if (!strncmp(p,"From ",5))
			(void) putc('>',mp->data);
 		/* Append to data file */
#ifdef TRANSLATE
	if (mp->translated && !strnicmp(p, Hdrs[FROM], 6))
		sprintf (p, "%s%s", Hdrs[FROM], mp->from);
#endif

#ifdef MSDOS
	/* change suggested by "Erik Olson" <erik@marge.phys.washington.edu> */
	while ((cp = strchr(p,CTLZ))!=NULL) /* Change control-Z's to \n */
            *cp='\n';
#endif
		if(fprintf(mp->data,"%s\n",p) < 0)	{
			(void) usprintf(mp->s,Ioerr);
			return 1;
		}
#ifdef MBFWD
		/* Check for Message-Id string - WG7J */
		if(!foundbid && !strnicmp(p,Hdrs[MSGID], 11)) 	{
			/* so we ignore any "Message ID:" in the text body,
			   which occurs when we are inserting a message */
			foundbid = 1;
			if (makeBBSbid (bid, p, 0))	{
				/* now check it */
				if (Smtpbidcheck && (mp->dupbid = msgidcheck(bid)) == 0)
        				mp->bid = strdup(bid);
        		}
		}
#endif
#ifdef WPAGES
		if(MbWpages && (!strnicmp(p,"R:",2)) && (*(p+8) == '/')) {	/* found one */
			/* Find the '@[:]CALL.STATE.COUNTRY'or
			 * or the '?[:]CALL.STATE.COUNTRY' string
			 * The : is optional.     */
			if( ((cp=strchr(p,'@')) != NULLCHAR) || ((cp=strchr(p,'?')) != NULLCHAR) ) {
				if((cp2=strpbrk(cp," \n\t")) != NULLCHAR)
					*cp2 = '\0';
				/* Some bbs's send @bbs instead of @:bbs*/
				if (*++cp == ':')
					cp++;
				pwait (NULL);		/* just to be nice to others */
				wpageAdd (cp, 1, 1);
			}
		}
#endif
	}
}

/* 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);
	free(mp->bid);
	if(mp->data != NULLFILE)
		fclose(mp->data);
	del_list(mp->to);
	free((char *)mp);
}


/* get status of an entry in the *.ctl file */

void
statusCtl (who, extension, info, entry, readit)
char *who, *extension;
struct let *info;
int entry, readit;
{
register FILE *fp;
char mailbox[100];
int oldstatus;

	sprintf(mailbox,"%s/control/%s.%s",Mailspool,who, extension);
	if((fp = subdir_fopen(mailbox,(readit) ? READ_BINARY : UPDATE_TEXT)) != NULLFILE){
		fseek (fp, (long) ((entry - 1) * sizeof (struct let)), 0);
		if (readit)
			fread (info, sizeof(struct let), 1, fp);
		else	{
			oldstatus = info->status;
			if (issysarea (who))
				info->status &= ~BM_READ;
			fwrite (info, sizeof(struct let), 1, fp);
			info->status = oldstatus;
		}
		fclose(fp);
	}
}

/* handle updating of the *.ctl file, here */

void
updateCtl (who, info)
char *who;
struct let *info;
{
register FILE *fp;
char mailbox[100];

	pwait (NULL);
	sprintf(mailbox,"%s/control/%s.ctl",Mailspool,who);
	if((fp = subdir_fopen(mailbox,APPEND_BINARY)) != NULLFILE){
		fwrite (info, sizeof(struct let), 1, fp);
		fclose(fp);
	}
}


/* handle updating of the *.fwd files, here */

void
updateFwd (who, area, bid)
char *who, *area;
long bid;
{
register FILE *fp;
char mailbox[100];

	pwait (NULL);
	sprintf(mailbox,"%s/%s.fwd",Mailspool,who);
	strlwr (mailbox);
	if((fp = fopen(mailbox,APPEND_TEXT)) != NULLFILE)	{
		fprintf (fp, " %s %-ld\n", area, bid);
		fclose(fp);
	}
}


/* 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;
FILE *fp;
#if 0
FILE *out;
#endif
char	mailbox[50], *cp, *host, *qhost;
char forwardto[32];
char origaddressee[40];
#if 0
int	c;
#endif
int fail = 0, lines, k;
time_t	t;
extern int Smtpquiet;
int lastWasCR = 0, wasForwarded;
#ifdef USERLOG
long msgid;
int nextisBID;
#endif
char *realnm, *realarea;
int index;
int Holdit = 0, firstIDline;
int skipped = 0;

	struct let lt;
	long startedat, endedat;

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

	do {
		qhost = NULLCHAR;
		for(ap = tolist;ap != NULLLIST;ap = ap->next)	{
			pwait (NULL);
			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, ap->val);
				}
			}
		}
		if(qhost != NULLCHAR){
			rewind(data);
			queuejob(data,qhost,dlist,from);
			del_list(dlist);
			dlist = NULLLIST;
		}
		pwait (NULL);
	} while(qhost != NULLCHAR);

#ifdef	NNTPS
	for(ap = tolist;ap != NULLLIST;ap = ap->next){
		if(ap->type != NNTP_GATE)
			continue;
		nnGpost(data,from,ap);
		ap->type = BADADDR;
	}
#endif

	index = 0;
	for(ap = tolist;ap != NULLLIST;ap = ap->next,index++){
		skipped = 0;
		pwait (NULL);
		if(ap->type != LOCAL){
			ap->type = DOMAIN;
			continue;
		}
		/* If local rewrite to CHECK, then tell SYSOP */
		if (!stricmp (ap->val, "check"))	{
			rewind (data);
			rdaemon (data, NULLCHAR, NULLCHAR, "Unknown Routing", 'P', 1);
		}
#ifdef RMAIL
		/* If local RMAIL, then leave here */
		if (!stricmp (ap->val, "rmail"))		{
			if (rmail (data, from))
				continue;	/* if handled by rmail() */
		}
#endif
#ifdef REQSVR
		/* If local REQSVR, then see if this is one supported */
		if (!stricmp (ap->val, "reqsvr"))		{
			if (reqsvr (data, from))
				continue;	/* if handled by reqsvr() */
		}
		pwait (NULL);
#endif
#ifdef TUTOR
		/* If Proxy server, then call it */
		if (!stricmp (ap->val, "proxy"))		{
			if (proxy (data, from))
				continue;	/* if handled by reqsvr() */
		}
#endif
		pwait (NULL);

#ifdef DELEGATE
		/* See if the local BBS user has forwarding delegation enabled */
		if (!delegate (data, from, ap->val))	{
			skipped = 1;
			continue;	/* if handled by delegate() */
		}
#endif
		pwait (NULL);
		/* strip off host name of LOCAL addresses */
		if ((cp = strchr(ap->val,'@')) != NULLCHAR)
			*cp = '\0';

		rewind(data);
		/* replace '\' and '.' with '/',
		 * this allows mailing into subdirs of spool/mail - WG7J
		 */
		cp = ap->val;
		while((cp=strchr(cp,'.')) != NULLCHAR)
			*cp++ = '/';
		cp = ap->val;
		while((cp=strchr(cp,'\\')) != NULLCHAR)
			*cp++ = '/';

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

		if ((realarea = rewrite_address (ap->orig)) == NULLCHAR)
			realarea = strdup (ap->val);

		pwait (NULL);
		/* if mail file is busy save it in our smtp queue
		 * and let the smtp daemon try later.
		 */
		if (mlock(Mailspool,realarea)){
			addlist(&dlist,realarea,0, realarea);
			fail = queuejob(data,Hostname,dlist,from);
			del_list(dlist);
			dlist = NULLLIST;
			free (realarea);
		} else {
			char buf[LINELEN];
			int tocnt = 0;
			int chkwpages = 1;
			pwait (NULL);
			sprintf(mailbox,"%s/%s",Mailspool,realarea);
			nntp_name_expansion (mailbox);
			strcat(mailbox,".txt");
#ifndef	AMIGA
			if((fp = subdir_fopen(mailbox,APPEND_TEXT)) != NULLFILE){
#else
			if((fp = subdir_fopen(mailbox,"r+")) != NULLFILE){
#endif
				pwait (NULL);
				(void) fseek(fp, 0L, 2);
				time(&t);
#ifdef USERLOG
				wasForwarded = nextisBID = firstIDline = 0;
				origaddressee[0] = 0;
				msgid = 0;
				lines = 0;
				for (k = 0; k < 32; k++)
					forwardto[k] = 0;
#endif
				startedat = ftell(fp);
				fprintf(fp,"From %s %s",from,ctime(&t));
				host = NULLCHAR;
				while(fgets(buf,sizeof(buf),data) != NULLCHAR){
					pwait (NULL);
#ifndef TNOS_68K
					lines++;
#endif
					if(buf[0] == '\n'){
						if(tocnt == 0)	{
							fprintf(fp,"%s%s\n",Hdrs[APPARTO],ap->orig);
#ifndef TNOS_68K
							lines++;
#endif
						}
						fputc('\n',fp);
						break;
					}
					rip(buf);
					if (htype(buf) == TO)	{
						strncpy (origaddressee, &buf[4], 40);
						origaddressee[39] = 0;
						if (isgroup (origaddressee))	{
							fprintf (fp, "%s%s@%s\n%s%s-relay@%s\n%s%s-admin@%s\n",  Hdrs[XMAILGROUP], origaddressee, Hostname, Hdrs[SENDER], origaddressee, Hostname, Hdrs[ERRORSTO], origaddressee, Hostname);
#ifndef TNOS_68K
							lines += 2;
#endif
						}
						if (chkwpages)
							realnm = wpage_exp (strdup (ap->orig), 1, 0);
						else
							realnm = strdup (ap->orig);
						fprintf (fp, "%s%s", Hdrs[TO], realnm);
						free (realnm);
					} else if (htype(buf) == MSGID)	{
						long nerf;
						if (index)	{
#if 0
							msgid = get_msgid(1);
							fprintf (fp, "%s<%ld@%s>",Hdrs[MSGID],msgid,Hostname);
#else
							nerf = get_msgid(1);
							fprintf (fp, "%s<%ld@%s>",Hdrs[MSGID],nerf,Hostname);
#endif
						} else	{
							cp = &buf[strlen(Hdrs[MSGID])];
							if (*cp == '<')
								cp++;
							nerf = atol(cp);
#if 0
							if (nerf)
								msgid = nerf;
#endif
							fputs(buf, fp);
						}
					} else
						fputs(buf, fp);
					pwait (NULL);
#ifdef USERLOG
					if (!firstIDline && nextisBID && (cp=strstr(buf,"AA")) != NULLCHAR) {
						/*what follows is the message-number*/
						msgid = atol(cp+2);
						nextisBID = 0;
						firstIDline = 1;
					}
#endif						
					switch(htype(buf)){
					case XFORWARD: 	k = BBSlookup (&buf[strlen(Hdrs[XFORWARD])]);
							if (k != -1)
								forwardto[k] = 1;
							wasForwarded = 1;
							break;
					case RECEIVED:  
#ifdef USERLOG
						       nextisBID = 1;
#endif
						       break;
					case BBSTYPE:  
						       chkwpages = 0;
						       break;
					case XBBSHOLD:	{
							long here;
							here = ftell (data);
							rewind (data);
							rdaemon (data, NULLCHAR, NULLCHAR, "Held Message - Forward Loop", 'P', 1);
							fseek (data, here, SEEK_SET);
							Holdit = 1;
							}
							break;
					case CC:	if (origaddressee[0])	{
								if ((cp = strchr (origaddressee, '.')) != NULLCHAR)
									*cp = 0;
								fprintf (fp, ", %s", origaddressee);
							}
					case TO:
						++tocnt;
						break;
					case RRECEIPT:
						if((cp = getaddress(buf,0))
						   != NULLCHAR){
							free(host);
							host = strdup(cp);
						}
						break;
					}
					fputc('\n', fp);
					pwait (NULL);
				}
				while(fgets(buf,sizeof(buf),data) != NULLCHAR)	{
					pwait (NULL);
					fputs(buf,fp);
#ifndef TNOS_68K
					lines++;
#endif
					lastWasCR = (buf[0] == '\n') ? 1 : 0;
					}
				if(ferror(fp))
					fail = 1;
				else if (!lastWasCR)	{
					fprintf(fp,"\n");
#ifndef TNOS_68K
					lines++;
#endif
				}
				/* Leave a blank line between msgs */
#ifdef USERLOG
				fflush (fp);
				endedat = ftell (fp);
#endif
				fclose(fp);

#ifdef USERLOG
#ifdef MSDOS
			lt.size = (endedat - startedat - lines - 1);
#else
			lt.size = (endedat - startedat);
#endif
			lt.status = ((isarea (realarea) && MbHolding && !wasForwarded) || Holdit) ? BM_ONHOLD : 0;
			lt.start = startedat;
			lt.bid = msgid;
			updateCtl (realarea, &lt);
#endif
			pwait (NULL);
			if (msgid)
				for (k = 0; k < Numfwds; k++)
					if ((!forwardto[k]) && AREAlookup (realarea, k))	{
						updateFwd (MyFwds[k], realarea, lt.bid);
						pwait (NULL);
					}

/* If we use tprintf here, instead of printf, flowcontrol
 * in the command screen is used; if the system is unattended for
 * more then 24 messages coming in, it will lock up mail delivery.
 * Make sure this only goes to the command screen - WG7J
 */
#ifdef DELEGATE
			if (!skipped)
#endif
#ifdef UNIX
/* true, but we defeat that when using the trace interface anyway.  KF8NH */
		tcmdprintf("New mail for %s from <%s>%c\n",ap->val,from,(Smtpquiet? ' ': '\007'));
#else
                	if(Current->output == Command->output)
	                    printf("New mail for %s from <%s>%c\n",realarea,from, Smtpquiet ? ' ' : '\007');
#endif
			if(host != NULLCHAR){
				rewind(data); /* Send return receipt */
				mdaemon(data,host,NULLLIST,0);
				free(host);
				}
			} else 
				fail = 1;
			(void) rmlock(Mailspool,realarea);
			if (fail)
				break;
#ifdef DELEGATE
			if (!skipped)
#endif
			smtplog("deliver: To: %s From: %s Size: %ld",realarea,from, lt.size);
			pwait (NULL);
			free (realarea);
		}
	}
	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;
	/* Read the system time */
	ltm = localtime(t);

	if (*tz == '\0')
		if ((p = getenv("TZ")) == NULL)
			strcpy(tz,"UTC");
		else
			strncpy(tz,p,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(type)
int type;	/* is this a sequence number (0) or a MID (1)? */
{
	char sfilename[LINELEN];
	char s[20];
	register long sequence = 0;
	FILE *sfile;

	sprintf(sfilename,"%s/%s.seq",Mailqdir, (type) ? "message" : "sequence");
	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);
		if (type)	{	/* use 1-65535, only */
			/* Keep it in range of and 5 digit number to use for dos name prefix with room to spare. */
			if (sequence < 0L || sequence > 65534L )
				sequence = 0;
		} else	{
			/* Keep it in range of and 8 digit number to use for dos name prefix. */
			if (sequence < 0L || sequence > 99999998L )
				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;
}

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

	if(*s == '!'){
#ifdef	NNTPS
		if((cp = strpbrk(s,"%@.,/")) != NULLCHAR)
			*cp = '\0';
		return NNTP_GATE;
#else
		return BADADDR;
#endif
	}
	/* if address has @ in it then check dest address */
	if ((cp = strrchr(s,'@')) != NULLCHAR){
		cp++;
		/* 1st check if it is our hostname.
		* if not then check the hosts file and see if we can
		* resolve the address to a known site or one of our aliases.
		*/
		if(stricmp(cp,Hostname) != 0){
			if ((addr = mailroute(cp)) == 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)
			return BADADDR;
		else
			return DOMAIN;
	}

#ifdef MSDOS	/* dos file name checks */
	/* Check for characters illegal in MS-DOS file names */
	for(cp = s;*cp != '\0';cp++)	{
		/* Accept '.', '/', and '\' !
		 * that way we can mail into subdirs - WG7J
		 */
		if(*cp == '.' || *cp == '\\' || *cp == '/')
			continue;
		if(dosfnchr(*cp) == 0)
			return BADADDR;	
	}
#endif
	return LOCAL;
}

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

	sprintf(prefix,"%ld",get_msgid(0));
	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)	{
		totalcnt += cnt;
		pwait (NULL);
		if(fwrite(buf, 1, (unsigned)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;
	}
	pwait (NULL);
	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 Size: %ld",prefix,ap->val,from, totalcnt);
	}
	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;
{
	int c;
	register struct list *ap;
	FILE *fp;
	char tmpstring[50];
	char prefix[9];

	sprintf(prefix,"%ld",get_msgid(0));
	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);
	pwait (NULL);
	while((c = getc(data)) != EOF)
		if(putc(c,fp) == EOF)
			break;
	pwait (NULL);
	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,orig)
struct list **head;
char *val, *orig;
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;
	tp->orig = strdup (orig);

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

}

int
isanIPhost (str)
char *str;
{
char *host;

	/* this routine returns 1 if it looks like an IP hostname,
	   otherwise it returns 0 which means it is probably an AX25 BBS name */
	if (strstr (str, ".org") && strstr (str, ".com") &&
	    strstr (str, ".gov") && strstr (str, ".mil") &&
	    strstr (str, ".net") && strstr (str, ".edu"))
	    	return 1;
	if ((host = strchr (str, '@')) != 0)	{
		host++;
		if(resolve(host))
			return 1;
	}
	return 0;
}


char *
host_or_wpage_exp (to, hier, exphome)
char *to;
int hier;
int exphome;	/* append and expand home bbs name ? */
{
	if (isanIPhost (to))
		return (to);
	return (wpage_exp (to, hier, exphome));
}


#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 a group and expand group into an address list */
static struct list *
expandgroup(head, user, orig)
struct list **head;
char *user, *orig;
{
FILE *fp;
register char *s,*p;
int ingroup = 0;
struct list *tp = (struct list *) 0;
char buf[LINELEN], *t;
	
	/* no group file found */
	if ((fp = fopen(Group, READ_TEXT)) != NULLFILE)		{
		while (fgets(buf,LINELEN,fp) != NULLCHAR){
			pwait (NULL);
			p = buf;
			if (*p == '#' || *p == '\n')
				continue;
			rip(p);

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

			/* when processing an active group check for a continuation */
			if (ingroup){
				if (!isspace(*p)) 
					break;	/* done */
				SKIPSPACE(p);
				s = p;
				SKIPWORD(p);
				if (*p != '\0')
					*p++ = '\0';
				SKIPSPACE(p);
				if (*p != 'S')
					continue;
				/* find hostname */
#ifdef	NNTPS
				if(*s == '!')
					tp = addlist(head,s,NNTP_GATE, s);
				else
#endif
				if (strchr(s,'@') != NULLCHAR)
					tp = addlist(head,s,DOMAIN, s);
				else	{
					t = wpage_exp (strdup (s), 0, 0);
					tp = addlist(head,t,LOCAL, t);
					free (t);
				}
			} else {
				s = p;
				if ((p = strchr (s, ':')) == NULLCHAR)	/* say what! */
					continue;
				*p = '\0';	/* end the group name */
				if (strcmp(s,user) != 0)
					continue;	/* no match go on */
				ingroup = 1;
			}
		}
	}

	pwait (NULL);
	if (fp)
		(void) fclose(fp);
	if (ingroup)	/* found and processed an group. */
		return tp;

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

/* check for and alias and expand alias into a address list */
struct list *
expandalias(head, user, orig)
struct list **head;
char *user, *orig;
{
	FILE *fp;
	register char *s,*p;
	struct rr *rrp, *rrlp;
	int inalias = 0;
	struct list *tp;
	char buf[LINELEN], *t;
	
#ifdef __GNUC__
	tp = 0;			/* silence "uninitialized" warning */
#endif
	/* no alias file found */
	if ((fp = fopen(Alias, READ_TEXT)) == NULLFILE){
		/* 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, rrp->rdata.name);
				else
					tp = addlist(head,rrp->rdata.name,
						     LOCAL, rrp->rdata.name);
				++inalias;
			}
			rrp = rrp->next;
		}
		free_rr(rrlp);
		if(inalias)
			return tp;
		else
			return expandgroup(head, user, orig);
	}

	while (fgets(buf,LINELEN,fp) != NULLCHAR){
		p = buf;
		if ( *p == '#' || *p == '\0' || *p == '\n')
			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 */
			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 */
#ifdef	NNTPS
			if(*s == '!')
				tp = addlist(head,s,NNTP_GATE, s);
			else
#endif
			if (strchr(s,'@') != NULLCHAR)
				tp = addlist(head,s,DOMAIN, s);
			else	{
				t = wpage_exp (strdup (s), 0, 0);
				tp = addlist(head,t,LOCAL, t);
				free (t);
			}
			SKIPSPACE(p);
		}
	}
	(void) fclose(fp);

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

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

#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 ax25 mailbox or
** from the return mail function in the smtp client 
*/
int
mailuser(data,from,to, origto)
FILE *data;
char *from;
char *to, *origto;
{

		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, origto);
		else
			/* a remote address is added to the list */
			addlist(&tolist, to, address_type, origto);
		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(1),Hostname);
	fprintf(tfile,DAEMONSTR,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,(unsigned)cnt,tfile);
	}
	fseek(tfile,0L,0);
	/* A null From<> so no looping replys to MAIL-DAEMONS */
	(void) mailuser(tfile,"",newto != NULLCHAR ? newto : to,newto != NULLCHAR ? newto : to);
	fclose(tfile);
	free(newto);
	return 0;
}
#ifdef MSDOS
static int
dosfnchr(ch)
int ch;
{
	int i, j;

	i = (ch & 0xf8) >> 3;
	j = doschars[i] & (1 << (ch & 0x07));
	return j;
}
#endif
