/*  SMTP Server state machine - see RFC 821
 *  enhanced 4/88 Dave Trulli nn2z
 *
 * Mods by G1EMM, PA0GRI and KO4KS
 */
#include "global.h"
#include "ctype.h"
#include "commands.h"
#ifndef MSDOS
#include <time.h>
#include <setjmp.h>
#include "session.h"
#endif
#include <stdarg.h>
#include "socket.h"
#ifdef	LZW
#include "lzw.h"
#endif
#include "smtp.h"
#include "mailbox.h"
#include "mailutil.h"
#include "bm.h"
#include "domain.h"
#include "delegate.h"
#ifdef  NNTPS
#include "nntp.h"
extern int NNpbbs2nntp;
void mail2news (FILE *fp, const char *from, const char *to, const char *area);
#endif
#include "x.h"
#include "reject.h"
#include "stats.h"

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: smtpserv.c,v 1.45 2000/06/28 22:31:36 gareth Exp $";
#endif

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

#ifdef HOLDMONITOR
#ifdef SOUNDS
extern char *HOLDsound;
#endif
extern int HOLDmode, HOLDindicator;
#endif

#ifdef __FreeBSD__
/*  timezone is a function that does something else in FreeBSD	*/
#define timezone (-ltm->tm_gmtoff)
#endif

extern int SMTPnotify;
#if 0
extern int PBBSmaildomain;
#endif

#ifdef WPAGES
int wpserver (FILE * fp, const char *from);
#endif

extern void bid_add (char *bid, time_t now, int tofile, char *to);
char *host_or_wpage_exp (char *to, int hier, int exphome, int *dns);
extern int isaPBBShost (char *str);
extern char *wpage_exp (char *to, int hier, int exphome);
extern void wpageAdd (char *entry, int bbs, int updateit, char which);
extern int isgroup (char *group);
extern int BBSlookup (char *bbs);
struct list *expandalias (struct list ** head, const char *user, const char *orig);
static int getmsgtxt (struct smtpsv * mp, int enablehold);
static struct smtpsv *mail_create (void);
static void mail_clean (struct smtpsv * mp);
static int mailit (FILE * data, const char *from, struct list * tolist, char *sender, int enablehold, char **destarea);
static int router_queue (FILE * data, const char *from, struct list * to);
static int __queuejob (FILE *dfile, char *host, struct list *to, const char *from, const char *qdir);
static void smtplog (const char *fmt,...);
static void smtpserv (int s, void *unused, void *p);
int mailuser (FILE * data, const char *from, const char *to, const char *origto);
extern int msgidcheck (char *string);

#ifdef TUTOR
extern int proxy (FILE * fp, const char *from);
#endif

extern FILE *subdir_fopen (char *name, const char *mode);
extern char *nntp_name_expansion (char *name);
void updateCtl (const char *who, struct let * info);
int makeBBSbid (char *bid, int bidlen, char *line);
void updateFwd (char *who, char *area, long bid, long size);
static int isanIPhost (char *str);
static struct list *expandgroup (struct list ** head, const char *user, const char *orig);
int relaycheck (struct smtpsv * mp, uint32 addr, char *orig);
uint32 getaddr (struct smtpsv * mp);


#ifdef TRANSLATE
char *translate (register char *searchfor, int *didwe);
#endif

#ifdef BBSIMPORT
extern void importcmd (int mode, char *bbsname);
#endif

#ifdef HOLDMODS
int holdjob (FILE *dfile, char *host, struct list *to, const char *from);
#endif

/* Command table */
static const 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
	"vrfy",
#define VRFY_CMD	10
	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 VRFY XLZW\n214 End\n";
#else
static char Help[] = "214-Commands:\n214-HELO NOOP MAIL QUIT RCPT HELP DATA RSET EXPN VRFY\n214 End\n";
#endif

static char Enter[] = "354 Enter mail, end with .\n";

#ifdef MBFWD
static char DupBidMsg[] = "550 Duplicate BID found in Message-Id line\n";
#endif

#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 Refused[] = "550 <%s> address refused\n";
static char Noalias[] = "550 No alias for <%s>\n";
static char ListNoUser[] = "550 That is a mailing list, not a user\n";
static char Relaydenied[] = "550 Relaying denied\n";
static char VrfyName[] = "250 <%s@%s>\n";
static char DAEMONSTR[] = "%sMAILER-DAEMON@%s (Mail Delivery Subsystem)\n";

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

#ifdef WPAGES
extern int MbWpages;
#endif

#ifdef MBFWD
extern int Smtpbidcheck;
#endif

extern int Sholdscanall;
extern char *BIDsuffix;
extern int ExpireActive;
extern char *ExpireArea;
extern int Smtpslzw;
extern int Smtpquiet;
extern int BIDuknos;
extern int SMTPnorelay;

#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 (int ch);
#endif



static int16 calc_crc (char *buf);



#define FCS_START       0xffff  /* Starting bit string for FCS calculation */
#define FCS(fcs, c)     (((fcs) >> 8) ^ fcstab[((fcs) ^ (c)) & 0x00ff])
extern int16 fcstab[];

/* Compute CCITT CRC-16 over a buffer */
static int16
calc_crc (char *buf)
{
int16 crc;
int16 len;

	/* 16 bit CRC-CCITT stuff. Extracted from Bill Simpson's PPP */
	crc = FCS_START;
	len = (int16) strlen (buf);

	while (len--)
		crc = FCS(crc, *buf++);

	crc ^= 0xffff;
	return crc;
}



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



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



static void
smtpserv (int s, void *unused OPTIONAL, void *p OPTIONAL)
{
struct smtpsv *mp;
char const **cmdp;
char buf[LINELEN], *arg, *cp, *cmd, *newaddr;
struct list *ap, *list;
int cnt;
char address_type;
char *orig;
int talkingtoself = 0;
uint32 addr = 0;
#ifdef	LZW
int lzwbits, lzwmode;
#endif

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

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

#ifdef STATS_USE
	STATS_adduse (1);
	MiscUsers++;
#endif
	usprintf (s, Banner, Hostname);

loop:
	kwait (NULL);
	if ((cnt = recvline (s, (unsigned char *) 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 = (char) 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) {
		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);
			usprintf (mp->s, Ourname, Hostname);
#if 1
		    if ((addr = getaddr(mp)) == Ip_addr)   /* Check we really are talking to self */
			    talkingtoself = 1;
#else
			if (!strcmp (arg, Hostname))
				talkingtoself = 1;
#endif
			break;
		case NOOP_CMD:
			usprintf (mp->s, Ok);
			break;
		case MAIL_CMD:
			if ((cp = getname (arg)) == NULLCHAR) {
				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
			usprintf (mp->s, Ok);
			break;
		case QUIT_CMD:
			usprintf (mp->s, Closing);
			goto quit;
		case RCPT_CMD:	/* Specify recipient */
			if ((cp = getname (arg)) == NULLCHAR) {
				usprintf (mp->s, Syntax);
				break;
			}
			orig = strdup (cp);
			/* rewrite address if possible */
			if ((newaddr = rewrite_address (cp, 0)) != NULLCHAR) {
				if (!stricmp (newaddr, "refuse")) {
					usprintf (mp->s, Refused, cp);
					free (orig);
					free (newaddr);
					break;
				}
				strncpy (buf, newaddr, LINELEN);
				cp = buf;
				free (newaddr);
			}
#if 0
			/* check if address is ok */
			if ((address_type = validate_address (cp)) == BADADDR) {
#else
			/* check if address is ok (and short enough for us to handle) */
			if ((address_type = (char) validate_address (cp)) == BADADDR ||
			    (address_type == LOCAL && strlen (cp) > FILE_PATH_SIZE - strlen (Mailspool) - 6)) {
#endif
				usprintf (mp->s, Unknown, cp);
				free (orig);
				break;
			}
			/* if a local address check for an alias */
			if (address_type == LOCAL)
				(void) expandalias (&mp->to, cp, orig);
			else {			
				/* This message is not for local delivery
				 * Check to see if we can relay it */
				if  (!SMTPnorelay || talkingtoself || relaycheck(mp, addr, cp))	
					/* a remote address is added to the list */
					(void) addlist (&mp->to, cp, address_type, orig);
				else {
					usprintf (mp->s, Relaydenied);
					log (mp->s, "SMTP relay denied. Msg from: %s to: %s rewritten as: %s", mp->from, orig, cp);
					free (orig);
					break;
				}
			}

			usprintf (mp->s, Ok);
			free (orig);
			break;
		case HELP_CMD:
			usprintf (mp->s, Help);
			break;
		case DATA_CMD:
			if (mp->to == NULLLIST)
				usprintf (mp->s, Needrcpt);
			else if ((mp->data = tmpfile ()) == NULLFILE)
				usprintf (mp->s, Ioerr);
			else {
				(void) getmsgtxt (mp, talkingtoself + Sholdscanall);
#ifdef STATS_MSG
				STATS_addmsg (0, 1);
				if (talkingtoself)
					STATS_addmsg (1, -1);
#endif
			}
			break;
		case RSET_CMD:
			del_list (mp->to);
			mp->to = NULLLIST;
			usprintf (mp->s, Reset);
			break;
		case VRFY_CMD:
		case EXPN_CMD:
			if (*arg == '\0') {
				usprintf (mp->s, Syntax);
				break;
			}
			list = NULLLIST;
			orig = strdup (arg);
			/* rewrite address if possible */
			if ((newaddr = rewrite_address (arg, 0)) != NULLCHAR)
				if (strcmp (newaddr, arg) == 0) {
					free (newaddr);
					newaddr = NULLCHAR;
				} else {
					strncpy (buf, newaddr, LINELEN);
					arg = buf;
				}
			list = NULLLIST;
			(void) expandalias (&list, arg, orig);
			free (orig);
			if (list == (struct list *)0)	{
				usprintf (mp->s, Noalias, arg);
				break;
			}
			if (strcmp (list->val, arg) == 0 && list->next == NULLLIST)
				if (newaddr == NULLCHAR) {
					if (cmdp - commands == VRFY_CMD)
						usprintf (mp->s, VrfyName, arg, Hostname);
					else
						usprintf (mp->s, Noalias, arg);
					del_list (list);
					break;
				}
			ap = list;
			/* if more than one, and VRFY, then just tell them it's a mailing list */
			if (cmdp - commands == VRFY_CMD && ap->next != NULLLIST)
				usprintf (mp->s, ListNoUser);
			else {
				while (ap->next != NULLLIST) {
					usprintf (mp->s, "250-%s\n", (strnicmp (ap->val, "cc:", 3)) ? ap->val : &ap->val[3]);
					ap = ap->next;
				}
				usprintf (mp->s, "250 %s\n", (strnicmp (ap->val, "cc:", 3)) ? ap->val : &ap->val[3]);
			}
			del_list (list);
			free (newaddr);
			break;
		default:
			break;
	}
	goto loop;

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



int
makeBBSbid (char *bid, int bidlen, char *line)
{
char *cp, *cptr;
int retval = 0;
int16 crc;

	*bid = 0;
	if ((cptr = getname (line)) == NULLCHAR)
		return retval;

	strncpy (bid, cptr, (unsigned int) bidlen);
	bid[bidlen] = '\0';
	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)	{
		if (stricmp (&bid[strlen (bid) - 4], ".bbs") == 0) {
			*cp = '\0';	/*retain the bid given by user*/
			retval = 1;
		} else if (stricmp (&cp[1], "hamradio") == 0) {
			*cp = '\0';	/* retain the uknos bid given by user */
			retval = 1;	/* Indicate we found a 'real' bid */
		} else {
			/*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';

			if (strlen (bid) > 12)	{
				/* need to recalc a better bid than this... */
				crc = calc_crc (cptr);
				sprintf (bid, "%u%s", crc, Hostname);
				if ((cp = strchr (bid, '.')) != 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 (struct smtpsv *mp, int enablehold)
{
char buf[LINELEN];
register char *p = buf;
time_t t;
int foundsender = 0;
int newline = 1, nextnewline;
char *destarea = NULLCHAR;
#ifdef WPAGES
char *cp, *cp2;
#endif
#ifdef MBFWD
char bid[LINELEN];
int foundbid = 0;
#endif
#ifdef MSDOS
char *cp3;
#endif
#ifdef TRANSLATE
int firstfrom = 0;
#endif

	/* Add timestamp; ptime adds newline */
	(void) time (&t);
	fprintf (mp->data, Hdrs[RECEIVED]);
	if (mp->system != NULLCHAR)
		fprintf (mp->data, "from %s [%s] ", mp->system, inet_ntoa (getaddr(mp)));
#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)) {
		usprintf (mp->s, Ioerr);
		return 1;
	} else
		usprintf (mp->s, Enter);

	for ( ; ; ) {
		kwait (NULL);
		p = buf;
		if (recvline (mp->s, (unsigned char *) p, sizeof (buf)) == -1)
			return 1;
		nextnewline = (p[strlen (p) - 1] == '\n');
		rip (p);
		/* check for end of message */
		if (newline && *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, mp->sender, enablehold, &destarea) != 0)
					(void) usputs (mp->s, Ioerr);
				else
					usprintf (mp->s, Sent);
				(void) 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 (destarea && !mp->dupbid && mp->bid != NULLCHAR) {
					time_t now;

					(void) time (&now);
					bid_add (mp->bid, now, 1, destarea);
				}
				/* Free this bid ! */
				free (mp->bid);
				mp->bid = NULL;
#endif
				return 0;
			}
		}
		newline = nextnewline;
		/* for UNIX mail compatiblity */
		if (!strncmp (p, "From ", 5))
			(void) putc ('>', mp->data);
		/* Append to data file */
		if (!strnicmp (p, "Sender: ", 8))
			foundsender = 1;
#ifdef TRANSLATE
#if 0
		if (mp->translated && !strnicmp (p, Hdrs[FROM], 6))
			sprintf (p, "%s%s", Hdrs[FROM], mp->from);
#else
		if (!firstfrom && !strnicmp (p, Hdrs[FROM], 6)) {
			if (!foundsender)
				fprintf (mp->data, "Sender: %s\n", &p[6]);
			mp->sender = strdup (&p[6]);
			if (mp->translated)
				sprintf (p, "%s%s", Hdrs[FROM], mp->from);
			firstfrom = 1;
		}
#endif
#endif

#ifdef MSDOS
		/* change suggested by "Erik Olson" <erik@marge.phys.washington.edu> */
		while ((cp3 = strchr (p, CTLZ)) != NULL)	/* Change control-Z's to \n */
			*cp3 = '\n';
#endif
		if (fprintf (mp->data, "%s\n", p) < 0) {
			usprintf (mp->s, Ioerr);
			return 1;
		}
		kwait (NULL);
#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, sizeof (bid), p)) {
				/* now check it */
				if (Smtpbidcheck)
					mp->dupbid = (msgidcheck (bid) == 1);
				else
					mp->dupbid = 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++;
				kwait (NULL);	/* just to be nice to others */
				wpageAdd (cp, 1, 1, 'I');
			}
		}
#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 (register struct smtpsv *mp)
{
	if (mp == NULLSMTPSV)
		return;
	free (mp->system);
	free (mp->from);
	if (mp->sender)
		free (mp->sender);
	free (mp->bid);
	if (mp->data != NULLFILE)
		(void) fclose (mp->data);
	del_list (mp->to);
	free ((char *) mp);
}



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

void
statusCtl (const char *who, const char *extension, struct let *info, int entry, int 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) ((unsigned int) (entry - 1) * sizeof (struct let)), 0);

		if (readit)
			(void) 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 = (int16) oldstatus;
		}
		(void) fclose (fp);
	}
}



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

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

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

		(void) fclose (fp);
	}
}



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

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

	kwait (NULL);
	sprintf (mailbox, "%s/%s.fwd", Mailspool, who);
	(void) strlwr (mailbox);
#ifdef MBFWD
	fwdlockit (who);
#endif
	(void) time (&now);
	if ((fp = fopen (mailbox, APPEND_TEXT)) != NULLFILE) {
		fprintf (fp, " %s %-ld %-ld %-ld\n", area, bid, now, size);
		(void) fclose (fp);
	}
#ifdef MBFWD
	fwdunlockit (who);
#endif
}



#if defined(HOLDMODS) || defined(NNTPFILTER)
int holdscan (char *buf, char *badbuf, char *badwords[]);

int
holdscan (char *buf, char *badbuf, char *badwords[])
{
int k, Holdit = 0;

	strcpy (badbuf, buf);
	(void) strlwr (badbuf);
	for (k = 0; k < 100 && badwords[k]; k++) {
		if (strstr (badbuf, badwords[k])) {
			Holdit = 1;
			log (-1, "Holdscan found: %s in: %s", badwords[k], badbuf);
			break;
		}
	}
	return (Holdit);
}
#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 (
FILE *data,
const char *from,
struct list *tolist,
char *sender OPTIONAL,
int enablehold,
char **destarea
) {
struct list *ap, *dlist = NULLLIST;
FILE *fp;
char mailbox[FILE_PATH_SIZE], *cp, *host, *qhost;
char forwardto[32];
char origaddressee[256];
int fail = 0, k;
time_t t;
int lastWasCR = 0, wasForwarded;
char *realnm, *realarea;
int theindex;
int Holdit = 0;
int skipped = 0;
char bbstype = 'P';
struct let lt;
long startedat;
int MBholdingOverride = 0;
#ifdef MSDOS
int lines;
#endif
#if 0
FILE *out;
int c;
#endif
#if defined(USERLOG) || defined(MBFWD)
long msgid;
int nextisBID = 0;
#endif
#if defined(USERLOG) || defined(MBFWD)
int firstIDline = 0;
#endif
#ifdef HOLDMODS
char *badwords[100];
char badbuf[LINELEN];
int totalindex;
#endif
#ifdef USERLOG
long endedat;
#endif
#ifdef EDITHEADERS
int padding = 0;
#endif
#ifdef REJECT
char rejfrom[256];
char rejmid[20];
#endif
#if defined(REJECT) || defined(NNTPS)
char rejto[256];
#endif

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

#if defined(REJECT) || defined(NNTPS)
	rejto[0] = 0;
#endif

#ifdef HOLDMODS
	totalindex = load_badwords (badbuf, badwords, sizeof(badbuf));
#endif
	do {
		qhost = NULLCHAR;
		for (ap = tolist; ap != NULLLIST; ap = ap->next) {
			kwait (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;
					(void) addlist (&dlist, ap->val, 0, ap->val);
				}
			}
		}
		if (qhost != NULLCHAR) {
#ifdef HOLDMODS
			if ((Sholdscanall > 1) && (scan_message(data, badbuf, badwords))) {
				rewind (data);
				(void) rdaemon (data, NULLCHAR, NULLCHAR, NULLCHAR, "Message with naughty word in hqueue", 'P', 0);
				return (holdjob (data, qhost, dlist, from));
			}
#endif	
			rewind (data);
			(void) queuejob (data, qhost, dlist, from);
			del_list (dlist);
			dlist = NULLLIST;
		}
		kwait (NULL);
	} while (qhost != NULLCHAR);

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

	theindex = 0;
	for (ap = tolist; ap != NULLLIST; ap = ap->next, theindex++) {
		skipped = 0;
		kwait (NULL);
		if (ap->type != LOCAL) {
			ap->type = DOMAIN;
			continue;
		}
		/* If local rewrite to CHECK, then tell SYSOP */
		if (!stricmp (ap->val, "check")) {
			rewind (data);
			(void) rdaemon (data, NULLCHAR, NULLCHAR, NULLCHAR, "Unknown Routing", 'P', 1);
		}
#ifdef WPAGES
		/* If local WP, then leave here */
		if (!stricmp (ap->val, "wp")) {
			if (wpserver (data, from))
				continue;	/* if handled by wpserver() */
		}
#endif
#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() */
		}
		kwait (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
		kwait (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
#ifdef MAILFILTER
		/* See if the local BBS needs to filter this message */
		if (!mbfilter (data, from, ap->val)) {
			skipped = 1;
			continue;	/* if handled by mbfilter() */
		}
#endif
		kwait (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 (destarea != (char **) 0)
			*destarea = NULLCHAR;
		if ((realarea = rewrite_address ((!strnicmp (ap->orig, "cc:", 3)) ? &ap->orig[3] : ap->orig, 0)) == NULLCHAR)	{
			if (!strnicmp (ap->val, "cc:", 3))
				realarea = strdup (&ap->val[3]);
			else
				realarea = strdup (ap->val);
		}

		kwait (NULL);
		/* if mail file is busy save it in our smtp queue
		 * and let the smtp daemon try later.
		 */
		if (mlock (Mailspool, realarea)) {
			(void) 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;

			lt.size = 0;
			if (destarea != (char **) 0)
				*destarea = realarea;
			kwait (NULL);
			sprintf (mailbox, "%s/%s", Mailspool, realarea);
			(void) nntp_name_expansion (mailbox);
			if (ExpireActive && ExpireArea && !stricmp (realarea, ExpireArea))
				strcat (mailbox, ".bak");
			else
				strcat (mailbox, ".txt");
			if ((fp = subdir_fopen (mailbox, APPEND_TEXT)) != NULLFILE) {
#ifdef STATS_AREA
				STATS_addarea (2, 1, realarea);
#endif

				kwait (NULL);
				(void) fseek (fp, 0L, 2);
				(void) time (&t);
#ifdef USERLOG
				wasForwarded = nextisBID = firstIDline = 0;
				origaddressee[0] = 0;
#ifdef MSDOS
				lines = 0;
#endif
				for (k = 0; k < 32; k++)
					forwardto[k] = 0;
#endif
#if defined(MBFWD) || defined(USERLOG)
				msgid = 0;
#endif
				startedat = ftell (fp);
				fprintf (fp, "From %s %s", from, ctime (&t));
				host = NULLCHAR;
#ifdef REJECT
				rejmid[0] = rejfrom[0] = 0;
#endif
#if defined(REJECT) || defined(NNTPS)
				rejto[0] = 0;
#endif
				while (fgets (buf, sizeof (buf), data) != NULLCHAR) {
					kwait (NULL);
#ifdef HOLDMODS
					if (!Holdit) {
						if (strcmp (realarea, "sysop"))
							Holdit = holdscan (buf, badbuf, badwords);
						if (Holdit)
							log (-1, "WordHold 1 New mail for %s from <%s>\n", realarea, from);
					}
#endif

#ifdef MSDOS
					lines++;
#endif
					if (buf[0] == '\n') {
						if (tocnt == 0) {
							fprintf (fp, "%s%s\n", Hdrs[APPARTO], ap->orig);
#ifdef MSDOS
							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);
#ifdef MSDOS
							lines += 2;
#endif
						}
						if ((*ap->orig == '!') || (!strnicmp (ap->orig, "Cc:", 3))) {
							log (-1, "CC: found - now '%s'", origaddressee);
							realnm = strdup (origaddressee);
						} else if (chkwpages)
							realnm = wpage_exp (strdup (ap->orig), 1, 0);
						else
							realnm = strdup (ap->orig);
						fprintf (fp, "%s%s", Hdrs[TO], realnm);
#ifdef EDITHEADERS
						padding = (int) (78 - strlen (realnm) - strlen (Hdrs[TO]));
						if (padding < 0)
							padding = 0;
#endif
						free (realnm);
					} else if (htype (buf) == MSGID) {
						long nerf;

						if (theindex) {
							nerf = get_msgid (1);
							fprintf (fp,
#ifdef MBFWD
								 (BIDuknos) ? "%s<%ld_%s@hamradio>" :
#endif
								 "%s<%ld@%s>", Hdrs[MSGID], nerf,
#ifdef MBFWD
								 (BIDsuffix) ? BIDsuffix :
#endif
								 Hostname);
						} else {
							cp = &buf[strlen (Hdrs[MSGID])];
							if (*cp == '<')
								cp++;
							nerf = atol (cp);
							fputs (buf, fp);
#ifdef REJECT
							rejmid[0] = '$';
							strncpy (&rejmid[1], cp, 18);
							rejmid[19] = 0;
							if ((cp = strpbrk (rejmid, "@<")) != NULLCHAR)
								*cp = 0;
#endif
						}
#ifdef EDITHEADERS
						padding = 20;
#endif
					} else
						fputs (buf, fp);
					kwait (NULL);
#if defined(USERLOG) || defined(MBFWD)
					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:
							bbstype = buf[16];
							chkwpages = 0;
							break;
						case XBBSHOLD:
							if (!strncmp (&buf[12], "No", 2)) {
								MBholdingOverride = 1;
							} else if (!strncmp (&buf[12], "Yes", 3))
								Holdit = 1;
							else {
								long here;

								here = ftell (data);
								rewind (data);
								(void) rdaemon (data, NULLCHAR, NULLCHAR, NULLCHAR, "Held Message", 'P', 1);
								fseek (data, here, SEEK_SET);
								Holdit = 1;
							}
							if (Holdit)
								log (-1, "XBBSHold New mail for %s from <%s>\n", realarea, from);
							break;
						case CC:
#if 0
							if (origaddressee[0]) {
								if ((cp = strchr (origaddressee, '.')) != NULLCHAR)
									*cp = 0;
								fprintf (fp, ", %s", origaddressee);
							}
#endif
							++tocnt;
							break;

						case TO:
							++tocnt;
#if defined(REJECT) || defined(NNTPS)
							cp = getaddress (buf, 0);
							if (cp)
								strncpy (rejto, cp, 256);
#endif
							break;
#ifdef EDITHEADERS
						case FROM:
							padding = (int) (78 - strlen (buf));
							if (padding < 0)
								padding = 0;
#ifdef REJECT
							cp = getaddress (buf, 0);
							if (cp)
								strncpy (rejfrom, cp, 256);
							if ((cp = strchr (rejfrom, '%')) != NULLCHAR)
								*cp = 0;
#endif
							break;
						case SUBJECT:
							padding = (int) (78 - strlen (buf));
							if (padding < 0)
								padding = 0;
							break;
#endif
						case RRECEIPT:
							if ((cp = getaddress (buf, 0)) != NULLCHAR) {
								free (host);
								host = strdup (cp);
							}
							break;
						default:
							break;
					}
#ifdef EDITHEADERS
					while (padding) {
						fputc (' ', fp);
						padding--;
					}
#endif
					fputc ('\n', fp);
					kwait (NULL);
				}
				while (fgets (buf, sizeof (buf), data) != NULLCHAR) {
					kwait (NULL);
#ifdef HOLDMODS
					if (!Holdit) {
						if (strcmp (realarea, "sysop"))
							Holdit = holdscan (buf, badbuf, badwords);
						if (Holdit)
							log (-1, "WordHold 2 New mail for %s from <%s>\n", realarea, from);
					}
#endif
					fputs (buf, fp);
#ifdef MSDOS
					lines++;
#endif
					lastWasCR = (buf[0] == '\n') ? 1 : 0;
				}
				if (ferror (fp))
					fail = 1;
				else if (!lastWasCR) {
					fprintf (fp, "\n");
#ifdef MSDOS
					lines++;
#endif
				}
				/* Leave a blank line between msgs */
#ifdef USERLOG
				(void) fflush (fp);
				endedat = ftell (fp);
#endif
				(void) fclose (fp);

#ifdef ALERTMONITOR
				alertarea (realarea);
#endif
#ifdef HOLDMODS
				{
					FILE *fpp;

					if (!Holdit)
						if ((fpp = fopen (UserHoldFile, "r")) != NULLFILE) {
							while (fgets (buf, sizeof (buf), fpp) != NULLCHAR) {
								rip (buf);
								if (strlen (buf) < 3)
									continue;
								(void) strlwr (buf);
								strncpy (badbuf, from, LINELEN);
								(void) strlwr (badbuf);
								if (strstr (badbuf, buf)) {
									Holdit = 1;
									break;
								}
								if (sender) {
									strncpy (badbuf, sender, LINELEN);
									(void) strlwr (badbuf);
									if (strstr (badbuf, buf)) {
										Holdit = 1;
										break;
									}
								}
							}
							(void) fclose (fpp);
							if (Holdit)
								log (-1, "UserHold New mail for %s from <%s>\n", realarea, from);
						}
				}
#endif
#ifdef USERLOG
#ifdef MSDOS
				lt.size = (endedat - startedat - lines - 1);
#else
				lt.size = (endedat - startedat);
#endif
				lt.status = 0;
#ifdef BBSIMPORT
				if (strcmp (realarea, "import")) {
#endif
#ifdef REJECT
					{
						char rejbf[256];
						int rej;

						sprintf (rejbf, "S%c %s <%s %s", bbstype, rejto, rejfrom, rejmid);
						rej = reject (rejbf, 1, 0);
						if (rej == REJ_REJECT || rej == REJ_DEFER) {
							lt.status = BM_DELETE;
							log (-1, "REJECTED New mail for %s from <%s>\n", realarea, from);
						}
						if (rej == REJ_HOLD || (rej == REJ_LOCALHOLD && !strstr (rejfrom, Hostname))) {
							log (-1, "REJECTHold New mail for %s from <%s>\n", realarea, from);
							Holdit = 1;
						}
					}
#endif
					if (enablehold)	/* if local smtp */
						if ((isarea (realarea) && (MbHolding && !MBholdingOverride) && !wasForwarded) || Holdit || (MbHoldall && !MBholdingOverride)) {
							lt.status = BM_ONHOLD;
#ifdef HOLDMONITOR
							if (HOLDmode) {
#ifdef SOUNDS
								if (HOLDsound)
									(void) playsound (HOLDsound);
#endif
								HOLDindicator = 1;
#ifdef XSERVER
								xnotify (X_HOLD);
#endif
							}
#endif
						}
					lt.start = startedat;
					lt.bid = msgid;
					updateCtl (realarea, &lt);
#endif
#ifdef BBSIMPORT
				} else if (newproc ("BBS import", (unsigned) 1024, (void (*)(int, void *, void *)) importcmd, 1, (void *) 0, (void *) 0, 0) == NULLPROC)
					log (-1, "Couldn't start Import process");
#endif
				kwait (NULL);
#ifdef MBFWD
				if (msgid)
					for (k = 0; k < Numfwds; k++)
						if ((!forwardto[k]) && AREAlookup (realarea, k)) {
							updateFwd (MyFwds[k].name, realarea, msgid, lt.size);
							kwait (NULL);
						}
#endif

				/* 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
					if (SMTPnotify)
#ifdef UNIX
						/* true, but we defeat that when using the trace interface anyway.  KF8NH */
						tcmdprintf ("New mail for %s from <%s>%c\n", realarea, from, (Smtpquiet ? ' ' : '\007'));
#else
						if (Current->output == Command->output)
							tprintf ("New mail for %s from <%s>%c\n", realarea, from, Smtpquiet ? ' ' : '\007');
#endif
				if (host != NULLCHAR) {
					rewind (data);	/* Send return receipt */
					(void) mdaemon (data, host, NULLLIST, 0);
					free (host);
				}
			} else
				fail = 1;
			rmlock (Mailspool, realarea);
			if (fail)
				break;
#ifdef DELEGATE
			if (!skipped)
#endif
				smtplog ("deliver %c: To: %s From: %s Size: %ld", bbstype, realarea, from, lt.size);
#ifdef STATS_TFC
			STATS_addtfc ((bbstype == 'B') ? 0 : (bbstype == 'T') ? 2 : 1, 1);
#endif
			kwait (NULL);
#if 0
			free (realarea);
#endif
#ifdef	NNTPS
			if (NNpbbs2nntp)
				mail2news (data, from, rejto, realarea);
#endif
		}

	}
#ifdef HOLDMODS
	for (k = 0; k < totalindex; k++)
		free (badwords[k]);	/*lint !e644 */
#endif

	return fail;
}



/* Return Date/Time in Arpanet format in passed string */
char *
ptime (time_t *t)
{
	/* Print out the time and date field as
	 *		"DAY day MONTH year hh:mm:ss ZONE"
	 */
	register struct tm *ltm;
	static char str[40];

#ifdef USE_TZOFFSET
	long offset;

#endif

	/* Read the system time */
	ltm = localtime (t);

	/* rfc 822 specifies several formats for the date (see Section 5).
	 * Either a 2/3 letter zone name (from a specified, and USA biassed, list),
	 * a single letter military code, or +/- followed by a 4 digit HHMM offset
	 * from UTC.
	 * The following code allows the use of either the timezone name or the
	 * offset. The offset method is preferred, unless your timezone name is
	 * explicitly mentioned in part 5 of RFC822.
	 * Note that the offset from UTC may be a non-integral number of
	 * hours (there's no charge for this, Newfoundland ). However, daylight
	 * saving time is assumed to be always a change of 1 hour east.
	 *
	 * Note that, for DOS users, the TZ environmental variable needs to be set
	 * correctly. With Borland C++, if TZ isn't set then it assumes TZ=EST5EDT.
	 * Note that if you specify a daylight saving timezone, then Borland C++
	 * uses the USA algorithm for the changeover date to/from daylight saving
	 * time.
	 *
	 * G8FSL 951014
	 *
	 */
#ifndef USE_TZOFFSET
	sprintf (str, "%s, %.2d %s %02d %02d:%02d:%02d %s\n",
#else
#if defined(HAVE_TM_ISDST) && !defined(MSDOS)
	offset = (timezone - (ltm->tm_isdst * 3600L)) / (-36L);
#else
	offset = ltm->tm_gmtoff / -36;
#endif
	if (offset % 100L)
		offset = (100L * (offset / 100L)) + (60L * (offset % 100L)) / 100L;
	sprintf (str, "%s, %.2d %s %02d %02d:%02d:%02d %+05ld\n",
#endif
		 Days[ltm->tm_wday],
		 ltm->tm_mday,
		 Months[ltm->tm_mon],
		 (ltm->tm_year % 100),
		 ltm->tm_hour,
		 ltm->tm_min,
		 ltm->tm_sec,
#ifdef USE_TZOFFSET
		 offset);
#else
#ifdef MSDOS
		 ltm->__tm_zone);
#else
#ifdef HAVE_TM_ISDST
		 ltm->tm_isdst ? tzname[1] : tzname[0]);
#else
		 ltm->tm_zone);
#endif
#endif
#endif
	return (str);
}



long
get_msgid (
int type			/* is this a sequence number (0) or a MID (1)? */
) {
char sfilename[LINELEN];
char s[20];
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 != NULLFILE) {
		(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;
		}
		(void) fclose (sfile);
	}
	/* increment sequence number, and write to sequence file */
	sfile = fopen (sfilename, WRITE_TEXT);
	if (sfile != NULLFILE)	{
		setbuf (sfile, NULL);
		fprintf (sfile, "%ld", ++sequence);
		(void) fclose (sfile);
	}
	return sequence;
}



/* test if mail address is valid */
int
validate_address (const char *s)
{
char *cp, *cp1;
uint32 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++;
		/* If smtp norelay is on, check for more than one '@' character */
		if (SMTPnorelay) {
			cp1 = strchr (s, '@');
			cp1 ++;
			if (cp != cp1)
				return BADADDR;
		}
		/* 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 */
	{
		const char *cp2;

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



/* place a mail job in one of the queues */
static int
__queuejob (
register FILE *dfile,
char *host,		/* equal to NULLCHAR, if placing in rqueue dir */
struct list *to,
const char *from,
const char *qdir
) {
register FILE *fp;
register struct list *ap;
char tmpstring[50], prefix[9], buf[LINELEN];
register int cnt;
long totalcnt = 0;

	sprintf (prefix, "%ld", get_msgid (0));
	(void) mlock (qdir, prefix);
	sprintf (tmpstring, "%s/%s.txt", qdir, prefix);
	if ((fp = fopen (tmpstring, WRITE_TEXT)) == NULLFILE) {
		rmlock (qdir, prefix);
		return 1;
	}
	
	rewind (dfile);
	while ((cnt = (int) fread (buf, 1, LINELEN, dfile)) > 0) {
		totalcnt += cnt;
		kwait (NULL);
		if ((int) fwrite (buf, 1, (unsigned) cnt, fp) != cnt)
			break;
	}
	if (ferror (fp)) {
		(void) fclose (fp);
		rmlock (qdir, prefix);
		return 1;
	}
	(void) fclose (fp);

	sprintf (tmpstring, "%s/%s.wrk", qdir, prefix);
	if ((fp = fopen (tmpstring, WRITE_TEXT)) == NULLFILE) {
		rmlock (qdir, prefix);
		return 1;
	}
	kwait (NULL);

	if (host != NULLCHAR)
		fprintf (fp, "%s\n%s\n", host, from);
	else
		fprintf (fp, "From: %s\n", from);

	for (ap = to; ap != NULLLIST; ap = ap->next) {
		if (host == NULLCHAR)
			fprintf (fp, "To: ");
		else {
			if (!strcmp (qdir, Holdqdir))
				smtplog ("hqueue job %s To: %s From: %s Size: %ld", prefix, ap->val, from, totalcnt);
			else
				smtplog ("queue job %s To: %s From: %s Size: %ld", prefix, ap->val, from, totalcnt);
		}
		fprintf (fp, "%s\n", ap->val);
	}
	(void) fclose (fp);

	rmlock (qdir, prefix);
	if (host == NULLCHAR)
		smtplog ("rqueue job %s From: %s Size: %ld", prefix, from, totalcnt);
	return 0;
}



/* Deliver mail to the appropriate mail boxes */
static int
router_queue (
FILE *data,
const char *from,
struct list *to
) {
	return (__queuejob (data, NULLCHAR, to, from, Routeqdir));
}



/* place a mail job in the outbound queue */
int
queuejob (
register FILE *dfile,
char *host,
struct list *to,
const char *from
) {
	return (__queuejob (dfile, host, to, from, Mailqdir));
}

#ifdef HOLDMODS
/* place a mail job in the hold queue */
int
holdjob (
FILE *dfile,
char *host,
struct list *to,
const char *from
) {
	return (__queuejob (dfile, host, to, from, Holdqdir));
}
#endif

/* add an element to the front of the list pointed to by head
** return NULLLIST if out of memory.
*/
struct list *
addlist (
struct list **head,
const char *val,
int type,
const char *orig
) {
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 = (char) 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;
}



static int
isanIPhost (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)) {
			/* host resolved, but is this an ampr address, without
			   the 'ampr.org'? If it HAD ampr.org, the first
			   section of this routine would have caught it */
			if (strstr (domainsuffix (host), ".ampr.org"))
				return 2;	/* yep, we will add it */
			return 1;
		}
	}
	return 0;
}


#if 1
char *
host_or_wpage_exp (
char *to,
int hier,
int exphome,			/* append and expand home bbs name ? */
int *dns			/* return if this is a DNS address */
) {
	*dns = 1;
	if (!isaPBBShost (to))
		if (isanIPhost (to))
			return (to);
	*dns = 0;
	return (wpage_exp (to, hier, exphome));
}

#else


/* This routine check the white pages and the DNS to determine a match.
   If both match, the white pages address is used, unless 'pbbs maildomain' is on.
   If user@host and host is an ampr.org host, then 'ampr.org' is added to it.
 */
char *
host_or_wpage_exp (
char *to,
int hier,
int exphome,			/* append and expand home bbs name ? */
int *dns			/* return if this is a DNS address */
) {
char *wp;
char *retval = NULLCHAR;
char *backup;

	*dns = 1;
	if (!isaPBBShost (to))
		switch (isanIPhost (to)) {
			case 1:/* non-ampr or ampr with complete hostname */
				return (to);

			case 2:/* an abbreviated ampr hostname */
				retval = mallocw (strlen (to) + 10);
				if (retval)
					sprintf (retval, "%s.ampr.org", to);
				break;

			default:
				break;
		}
	/* now, override DNS with WP, if 'pbbs maildomain' is off */
	if (!PBBSmaildomain || retval == NULLCHAR) {
		backup = strdup (to);
		wp = wpage_exp (backup, hier, exphome);

		if (stricmp (wp, to)) {
			*dns = 0;
			retval = wp;
		} else
			free (backup);
	}
	if (retval != NULLCHAR) {
		if (retval != to)
			free (to);
	} else
		retval = to;
	return retval;

}

#endif


#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 (
struct list **head,
const char *user,
const char *orig
) {
FILE *fp;
register char *s, *p;
int ingroup = 0, foundone = 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) {
			kwait (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);
				}
				foundone = 1;
			} 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;
			}
		}
	}
	kwait (NULL);
	if (fp)
		(void) fclose (fp);
	if (ingroup && foundone)/* 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 (
struct list **head,
const char *user,
const char *orig
) {
FILE *fp;
register char *s, *p;
struct rr *rrp, *rrlp;
int inalias = 0;
struct list *tp = 0;
char buf[LINELEN], *t;

	/* 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 (stricmp (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 ((t = strchr (s, '@')) != NULLCHAR) {
				if (!stricmp (t + 1, Hostname)) {
					*t = 0;
					tp = addlist (head, s, LOCAL, s);
				} else
					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);
}



static void
smtplog (const char *fmt,...)
{
va_list ap;
char *cp;
time_t t;
FILE *fp;

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



/* 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 (
FILE *data,
const char *from,
const char *to,
const char *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)
		(void) expandalias (&tolist, to, origto);
	else
		/* a remote address is added to the list */
		(void) addlist (&tolist, to, address_type, origto);
	ret = mailit (data, from, tolist, NULLCHAR, 1, (char **) 0);
	del_list (tolist);
	return ret;

}



/* Mailer daemon return mail mechanism */
int
mdaemon (
FILE *data,			/* pointer to rewound data file */
const 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;
	(void) time (&t);
	fprintf (tfile, "%s%s", Hdrs[DATE], ptime (&t));
	fprintf (tfile,
#ifdef MBFWD
		(BIDuknos) ? "%s<%ld_%s@hamradio>\n" :
#endif
		"%s<%ld@%s>\n", Hdrs[MSGID], get_msgid (1),
#ifdef MBFWD
		(BIDsuffix) ? BIDsuffix :
#endif
		 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 = (int) fread (buf, 1, sizeof (buf), data)) > 0) {
			fwrite (buf, 1, (unsigned) cnt, tfile);
			kwait (NULL);
		}
	}
	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);
	(void) fclose (tfile);
	free (newto);
	return 0;
}



#ifdef MSDOS
static int
dosfnchr (int ch)
{
int i, j;

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

#endif

/* Following are 'smtp norelay' enhancments - G4HIP */

/* Decide whether a message can be relayed - See RFC 2505 */
int
relaycheck (struct smtpsv * mp, uint32 addr, char *toaddr)
{
FILE *fp;
char *cp, *cp1, *cp2;
char buf[LINELEN];
char toaddr1[LINELEN];
int smaskbits, action = 0;
uint32 checkaddr, addr1;
	
	/* Check out which callers are specifically mentioned */
	
	if ((fp = fopen (Mailrelay, READ_TEXT)) != NULLFILE) {
		while (fgets (buf, sizeof (buf), fp) != NULLCHAR) {
			rip (buf);
			cp = skipwhite (buf);
			if (*cp == '#' || !*cp)
				continue;
			if (*cp == '!') {
				action = -1;
				cp ++;
			}
			else
				action = 1;
			if (strcspn (cp, "0123456789./") == 0) { /* Dotted quad */
				cp++;
				cp = skipwhite (buf);
				if ((cp1 = strchr (buf, '/')) != NULLCHAR) {
					*cp1 = '\0';
					cp1 ++;
					smaskbits = atoi (cp1);
				}
				else
					smaskbits = 32;
				checkaddr = aton (cp);
				addr1 = addr;
				addr1 &= ~0L << (32-smaskbits);
			
				if ((addr1 == checkaddr) || smaskbits == 0) {
					fclose (fp);
					if (action == -1)
						return 0;
					else
						return 1;
				}
			}
		}
		fclose (fp);
}
    /* Null 'Mail From:' commands are exploited by spammers   */
#if 0
	/* If the 'mail from' address is 'null' we must accept it */
	if (strlen (mp->from) == 0)				
		return 1;
#endif
	
	/* If apparently from someone at our local domain we should accept it */
	strcpy (buf, mp->from);
	if ((cp = strrchr (buf, '@')) != NULLCHAR) {  /* This may need work later to   */
		if (strcmp (cp, Hostname) == 0) /* encompass true 'local domain' */
			return 1;
	}
	
	/* Check the 'mailrelay' file to see if it is for a host we relay for */
	strcpy (toaddr1, toaddr);	
	if ((fp = fopen (Mailrelay, READ_TEXT)) != NULLFILE) {
		while (fgets (buf, sizeof (buf), fp) != NULLCHAR) {
			rip (buf);
			cp = skipwhite (buf);
			if (*cp == '#' || !*cp)
				continue;

			/* Is the mail from a user at host we relay for ? */
			if ((cp1 = strrchr(mp->from, '@')) != NULLCHAR) {
				if (wildmat (cp1, cp, NULLCHARP)) {
					fclose(fp);
					return 1;
				}
			}
			
			/* Is the mail to a user at a host we relay for ? */
			if ((cp1 = strrchr(toaddr1, '@')) != NULLCHAR) {
				if (wildmat (cp1, cp, NULLCHARP)) {
					if ((cp2 = strrchr(toaddr1, '%')) != NULLCHAR) {
						*cp1 = '\0';   /* Go and check any % directives */
						*cp2 = '@';
						rewind (fp);
						continue;
					} else {
						fclose(fp);
						return 1;
					}
				}
			}
		}
	}
	fclose (fp);
	return 0;
}

/* Returns address of host connected to SMTP port */
uint32
getaddr (struct smtpsv * mp)
{
int sizesock;
struct sockaddr_in fsock;
	
	sizesock = sizeof(fsock);
	
	if (getpeername(mp->s, (char *)&fsock, &sizesock) == 0 && sizesock !=0 && fsock.sin_family == AF_INET)
		return fsock.sin_addr.s_addr;
	else
		return 0;
}

#ifdef HOLDMODS
/* Scans a message about to be queued by SMTP for 'naughty words' */
int
scan_message (FILE *data, char *badbuf, char *badwords[])
{
int Holdit = 0;
char buf[LINELEN];
	rewind (data);
	while (fgets (buf, sizeof (buf), data) != NULLCHAR) {
		kwait (NULL);
		Holdit = holdscan (buf, badbuf, badwords);
		if (Holdit)
			break;
	}
	return Holdit;
}

int
load_badwords (char *badbuf, char *badwords[], int lnbadbuf)
{
FILE *fpp;
int	theindex = 0;
	if ((fpp = fopen (WordHoldFile, "r")) != NULLFILE) {
		while (fgets (badbuf, lnbadbuf, fpp) != NULLCHAR) {
			rip (badbuf);
			if (strlen (badbuf) < 3)
				continue;
			(void) strlwr (badbuf);
			badwords[theindex++] = strdup (badbuf);
			if (theindex == 99)
				break;
		}
		(void) fclose (fpp);
	}
	badwords[theindex] = NULLCHAR;
	return (theindex);
}
#endif
