/*
 *	Copyright 1988 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 */

/*
 * ZMailer SMTP server.
 */

char *VerbID = "ZMailer SMTP server %s";
char *Copyright = "Copyright 1990 Rayan S. Zachariassen";

/*
 * The smtpserver connects to the router to ask it various questions, like,
 * is this a valid address?  What is the alias expansion of that? etc.
 * This is done through a portal function called "server".  Its only standard
 * argument is a keyword for what we want done.  These are the definitions:
 */

#define	ROUTER_SERVER	"server"	/* name of portal function */

#define	RKEY_INIT	"init"		/* initialize state of server */
#define	RKEY_FROM	"from"		/* mail from address verification */
#define	RKEY_TO		"to"		/* recipient to address verification */
#define	RKEY_VERIFY	"verify"	/* verify this address */
#define	RKEY_EXPAND	"expand"	/* expand this address */


#include <stdio.h>
#include "hostenv.h"
#include <sys/stat.h>
#ifdef FCNTL_H
#include <fcntl.h>
#endif
#include <sys/file.h>
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <varargs.h>
#include <mail.h>
#include <sys/time.h>
#ifdef	USE_UNIONWAIT
#include <sys/wait.h>
#endif	/* USE_UNIONWAIT */
#ifdef	WNOHANG
#include <sys/resource.h>
#endif	/* WNOHANG */
#if	defined(USE_HOSTS) || defined(USE_INET)
#include <netdb.h>
#endif	/* USE_HOSTS */
#ifdef	USE_INET
#include <sys/socket.h>
#include <netinet/in.h>
#endif	/* USE_INET */
#ifdef	USE_SYSLOG
#include <syslog.h>
#endif	/* USE_SYSLOG */

#ifndef	SIGCHLD
#define	SIGCHLD	SIGCLD
#endif	/* SIGCHLD */

#ifndef	MAXHOSTNAMELEN
#define	MAXHOSTNAMELEN 64
#endif	/* MAXHOSTNAMELEN */

/*
 * Early inetd's, which may be found on 4.2BSD based systems (e.g.
 * Sun OS 3.x), are incapable of passing a flag to indicate we are
 * being run by inetd rather than directly.  Some hackery to detect
 * when we are being run by the 4.2 inetd is included by defining
 * CHECK42INETD.  Should be deleted at the earliest possible opportunity.
 */
#define	CHECK42INETD	/* heuristics to detect the 4.2 inetd */


typedef enum {	Null, Hello, Mail, MailOrHello, Recipient,
		RecipientOrData, Data, Send, SendOrMail,
		SendAndMail, Reset, Verify, Expand, Help,
		NoOp, Quit, Turn, Tick, Verbose
} Command;

Command state;

struct command {
	char	*verb;
	Command	cmd;	
} command_list[] = {	{	"helo",		Hello		},
			{	"mail",		Mail		},
			{	"rcpt",		Recipient	},
			{	"data",		Data		},
			{	"send",		Send		},
			{	"soml",		SendOrMail	},
			{	"saml",		SendAndMail	},
			{	"rset",		Reset		},
			{	"vrfy",		Verify		},
			{	"expn",		Expand		},
			{	"help",		Help		},
			{	"noop",		NoOp		},
			{	"quit",		Quit		},
			{	"turn",		Turn		},
			/* sendmail extensions */
			{	"onex",		NoOp		},
			{	"verb",		Verbose		},
			/* bsmtp extensions */
			{	"tick",		Tick		},
			/* 8-bit smtp extensions */
			{	"emal",		Mail		},
			{	"esnd",		Send		},
			{	"esom",		Send		},
			{	"esam",		Send		},
			{	"evfy",		Verify		},
			{	0,		Null		}
};

struct smtpconf {
	char	*pattern;
	char	*flags;
	struct smtpconf *next;
};

extern struct smtpconf *readcffile(), *findcf();
struct smtpconf *cfhead = NULL;

char	*progname, *cmdline, *eocmdline, *logfile;
char	*postoffice = NULL;
char	*routerprog = NULL;
char	myhostname[MAXHOSTNAMELEN+1];
char	rhostname[MAXHOSTNAMELEN+1];
char	ihostaddr[3*4+1*3+2+1 /* == 18 for '[255.255.255.255]' */ +10/*slack*/];
int	rport;
int	skeptical = 1;
int	pid, routerpid;
FILE	*logfp = NULL;

/*
 * The "style" variable controls when the router is interrogated about the
 * validity of something.  It is a string of letter-flags:
 * f:	check MAIL FROM addresses
 * t:	check RCPT TO addresses
 * v:	check VRFY command argument
 * e:	check EXPN command argument
 */

char	*style = "ve";

#define STYLE(i,c)	(strchr(((i)==NULL ? style : (i)->flags), (c)) != NULL)

#define	WITH_SMTP	"SMTP"
#define	WITH_BSMTP	"BSMTP"
char	*with_protocol;

extern int optind;
extern char *optarg;

#ifdef	lint
#undef	putc
#define	putc	fputc
#endif	/* lint */

int
main(argc, argv)
	int argc;
	char *argv[];
{
	int daemon, inetd, errflg, c, raddrlen, s, msgfd, pid, version, i;
	char *mailshare, path[1024];
#ifdef	USE_INET
	struct sockaddr_in sad, raddr;
#endif	/* USE_INET */
	u_short port = 0;
	extern char *emalloc(), *strerror();
	extern int getmyhostname(), isit42inetd();
	extern int getpeername();
	extern SIGNAL_TYPE reaper(), timedout();
	extern void detach(), prversion(), smtpserver();
	extern void setrhostname(), killprevious(), settrusteduser(), killr();
	extern int errno;

	daemon = 1;
	inetd = errflg = version = 0;
	logfile = NULL;
	progname = argv[0];
	cmdline = &argv[0][0];
	eocmdline = cmdline;
	for (i = 0; i < argc; ++i)
		eocmdline += strlen(argv[i]) + 1;
	if (getmyhostname(myhostname, sizeof myhostname) < 0) {
		(void) fprintf(stderr, "%s: gethostname(): %s\n",
				       progname, strerror(errno));
		exit(1);
	}
	while ((c = getopt(argc, argv, "igl:np:P:R:s:V")) != EOF) {
		switch (c) {
		case 'i':	/* interactive */
			daemon = 0;
			break;
		case 'g':	/* gullible */
			skeptical = 0;
			break;
		case 'l':	/* log file */
			logfile = emalloc(strlen(optarg)+1);
			(void) strcpy(logfile, optarg);
			break;
		case 'n':	/* running under inetd */
			inetd = 1;
			break;
		case 's':	/* checking style */
			style = emalloc(strlen(optarg)+1);
			strcpy(style, optarg);
			break;
#ifdef	USE_INET
		case 'p':
			port = htons((u_short)atoi(optarg));
			break;
#endif	/* USE_INET */
		case 'R':	/* router binary used for verification */
			routerprog = emalloc(strlen(optarg)+1);
			strcpy(routerprog, optarg);
			break;
		case 'P':
			postoffice = emalloc(strlen(optarg)+1);
			strcpy(postoffice, optarg);
			break;
		case 'V':
			prversion("smtpserver");
			exit(0);
			break;	/* paranoia */
		default:
			++errflg;
			break;
		}
	}
#ifdef CHECK42INETD
	/*
	 * If no flags set and we have one argument, check
	 * argument format to see if it's from the 4.2 inetd.
	 */
	if (!errflg && daemon && skeptical
	    && !inetd && port == 0 && optind == argc-1)
		if (isit42inetd(argv[optind])) {
			inetd = 1;
			optind++;
		}
#endif	/* CHECK42INETD */
	if (errflg || optind != argc) {
#ifdef	USE_INET
		(void) fprintf(stderr,
			       "Usage: %s [-ign] [-p port#] [-l logfile]\n",
			       progname);
#else	/* !USE_INET */
		(void) fprintf(stderr,
			       "Usage: %s [-ign] [-l logfile]\n", progname);
#endif	/* USE_INET */
		exit(1);
	}

	if ((mailshare = getzenv("MAILSHARE")) == NULL)
		mailshare = MAILSHARE;
	if (strchr(progname, '/') != NULL)
		sprintf(path, "%s/%s.conf", mailshare, strrchr(progname,'/')+1);
	else
		sprintf(path, "%s/%s.conf", mailshare, progname);

	cfhead = readcffile(path);

	rport = -1;
	if (!daemon) {
		(void) strcpy(rhostname, "stdin");
		ihostaddr[0] = '\0';
		smtpserver(0);
	} else
#ifdef	USE_INET
	if (inetd) {
		raddrlen = sizeof raddr;
		if (getpeername(0,(struct sockaddr *)&raddr, &raddrlen) == -1) {
			(void) fprintf(stderr, "%s: getpeername(0): %s\n",
					       progname, strerror(errno));
			exit(1);
		}
		setrhostname(&raddr);
		smtpserver(1);
	} else {
		if (postoffice == NULL
		    && (postoffice = getzenv("POSTOFFICE")) == NULL)
			postoffice = POSTOFFICE;
		killprevious(SIGTERM, PID_SMTPSERVER);
		if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
			(void) fprintf(stderr,
				       "%s: socket(AF_INET, SOCK_STREAM): %s\n",
				       progname, strerror(errno));
			exit(1);
		}
		i = 1;
		if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (caddr_t)&i, sizeof i) < 0) {
			(void) fprintf(stderr,
				       "%s: setsockopt(SO_REUSEADDR): %s\n",
				       progname, strerror(errno));
			exit(1);
		}
		if (port <= 0) {
#ifdef	IPPORT_SMTP
			port = htons(IPPORT_SMTP);
#else	/* !IPPORT_SMTP */
			struct servent *service;

			if ((service = getservbyname("smtp", "tcp")) == NULL) {
				(void) fprintf(stderr,
				    "%s: no SMTP service entry\n", progname);
				exit(1);
			}
			port = service->s_port;
#endif	/* IPPORT_SMTP */
		}
		sad.sin_family = AF_INET;
		sad.sin_addr.s_addr = INADDR_ANY;
		sad.sin_port = port;
		if (bind(s, (struct sockaddr *)&sad, sizeof sad) < 0) {
			(void) fprintf(stderr, "%s: bind(): %s\n",
					       progname, strerror(errno));
			exit(1);
		}
		if (listen(s, 5) < 0) {
			(void) fprintf(stderr, "%s: listen(): %s\n",
					       progname, strerror(errno));
			exit(1);
		}
		settrusteduser();	/* dig out the trusted user ID */
		detach();	/* this must NOT close fd's */
		killprevious(0, PID_SMTPSERVER);	/* deposit pid */
		for (msgfd = getdtablesize() - 1; msgfd >= 0; --msgfd)
			if (msgfd != s) {
				(void) close(msgfd);
				/* junk any buffered output... */
				if (msgfd == fileno(stdout))
					(void) fflush(stdout);
				else if (msgfd == fileno(stderr))
					(void) fflush(stderr);
			}
#ifdef USE_UNIONWAIT
		(void) signal(SIGCHLD, reaper);
#else
		(void) signal(SIGCHLD, SIG_IGN);
#endif
		(void) signal(SIGALRM, timedout);
		(void) signal(SIGHUP, SIG_IGN);
		(void) signal(SIGTERM, SIG_DFL);
		while (1) {
			raddrlen = sizeof raddr;
			msgfd = accept(s, (struct sockaddr *)&raddr, &raddrlen);
			if (msgfd < 0) {
				if (errno == EINTR)
					continue;
				else {
					(void) fprintf(stderr,
						       "%s: accept(): %s\n",
						       progname,
						       strerror(errno));
					exit(1);
				}
			}
			if ((pid = fork()) < 0) {	/* can't fork! */
				(void) close(msgfd);
				(void) fprintf(stderr,
					       "%s: fork(): %s\n",
					       progname, strerror(errno));
				(void) sleep(5);
				continue;
			} else if (pid == 0) {		/* child */
				(void) close(s);
				(void) signal(SIGTERM, SIG_IGN);
				if (msgfd != 0)
					(void) dup2(msgfd, 0);
				if (msgfd != 1)
					(void) dup2(msgfd, 1);
				if (msgfd > 1)
					(void) close(msgfd);
				rport = raddr.sin_port;
				setrhostname(&raddr);
				smtpserver(1);
				if (routerpid > 0)
					killr(routerpid);
				_exit(0);
			} else
				(void) close(msgfd);
		}
	}
#else	/* !USE_INET */
	{
		(void) fprintf(stderr,
			       "%s: no daemon mode since no IPC available\n",
			       argv[0]);
		exit(1);
	}
#endif	/* USE_INET */
	if (routerpid > 0)
		killr(routerpid);
	exit(0);
	/* NOTREACHED */
	return 0;
}


#ifdef CHECK42INETD
/*
 * The 4.2 BSD inetd runs its servers with exactly one argument having
 * the form:
 *		xxxxxxxx.dddd
 *
 * where xxxxxxxxx is the remote IP host address in hexadecimal and
 * dddd is the remote port number in decimal.  While we don't use these
 * (the host has to support getpeername()), this routine checks for
 * the correct form of the argument.
 */

int
isit42inetd(arg)
char *arg;
{
	register int i;

	for (i = 0; i < 8; i++)		/* exactly 8 hex digits */
		if (!isxdigit(arg[i]))
			return 0;
	if (arg[8] != '.')		/* period next */
		return 0;
	for (i = 9; arg[i] != '\0'; i++)  /* now one or more decimal digits */
		if (!isdigit(arg[i]))
			return 0;
	if (i == 9)
		return 0;
	return 1;			/* okay! */
}
#endif	/* CHECK42INETD */


#ifdef	USE_INET
/*
 * set the (default) remote host name, possibly based on the remote IP
 * host address if we are feeling untrusting.
 */

void
setrhostname(sin)
struct sockaddr_in *sin;
{
	struct hostent *hp;
	extern char *dottedquad();

	(void) sprintf(ihostaddr, "[%s]", dottedquad(&sin->sin_addr));
	if (skeptical) {
		hp = gethostbyaddr((char *)&(sin->sin_addr),
			sizeof (struct in_addr), sin->sin_family);
		if (hp != NULL)
			(void) strcpy(rhostname, hp->h_name);
		else
			(void) strcpy(rhostname, ihostaddr);
	} else
		(void) strcpy(rhostname, ihostaddr);
}
#endif	/* USE_INET */

SIGNAL_TYPE
timedout()
{
	extern void killr();
	
	if (routerpid > 0)
		killr(routerpid);
	exit(0);
}

SIGNAL_TYPE
reaper()
{
#ifdef	USE_UNIONWAIT
	union wait status;
#else	/* !USE_UNIONWAIT */
	int status;
#endif	/* USE_UNIONWAIT */

#ifdef	WNOHANG
	while (wait3(&status, WNOHANG, (struct rusage *)NULL) > 0)
#else	/* !WNOHANG */
	while (wait(&status) > 0)
#endif	/* WNOHANG */
		continue;
	(void) signal(SIGCHLD, reaper);
}

void
reporterr(tell, msg)
	long tell;
	char *msg;
{
#ifdef	LOG_INFO
	syslog(LOG_ERR,
		"aborted (%ldc) from %s/%d: %s", tell, rhostname, rport, msg);
#endif	/* LOG_INFO */
	if (logfp != NULL) {
		(void) fprintf(logfp, "%d#\taborted: %s\n", pid, msg);
		(void) fflush(logfp);
	}
}

void
smtpserver(insecure)
	int insecure;
{
	char *cp, *s;
	struct command *carp;
	time_t now;
	long tell;
	int fd, inum;
	FILE *mfp;
	struct stat stbuf;
	struct smtpconf *cfinfo;
	char buf[8192];		/* XX: limits size of SMTP commands... */
	char helobuf[sizeof buf/sizeof(char)];
	extern char *Version;
	extern u_char *rfc822date();
	extern void mvdata(), type(), report();
	extern void help();
	extern time_t time();
	extern char *router(), *strerror();
	extern int errno;
	extern int cistrcmp(), partridge(), cistrncmp(), runastrusteduser();

	/* opening the logfile should be done before we reset the uid */
	if (logfile != NULL) {
		if ((fd = open(logfile, O_CREAT|O_APPEND|O_WRONLY, 0644)) < 0) {
			if (!insecure)
				(void) fprintf(stderr,
				       "%s: cannot open logfile \"%s\": %s\n",
				       progname, logfile, strerror(errno));
		} else
			logfp = fdopen(fd, "a");
		pid = getpid();
	} else
		logfp = NULL;
	(void) runastrusteduser();

	mfp = NULL;
	with_protocol = WITH_SMTP;
	report("(connected)");
	now = time((time_t *)0);
	cp = (char *)rfc822date(&now);
	if (*(cp + strlen(cp) - 1) == '\n')
		*(cp + strlen(cp) - 1) = '\0';
	type(220, "%s Server SMTP ready at %s", myhostname, cp);
#ifdef	LOG_PID
#ifdef	LOG_MAIL
	openlog("smtpserver", 0, LOG_MAIL);
#else	/* !LOG_MAIL */
	openlog("smtpserver", 0);
#endif	/* LOG_MAIL */
#endif	/* LOG_PID */

	state = Hello;
	if ((!insecure
	     || (ihostaddr[0] != '\0' && strcmp(ihostaddr, "[127.0.0.1]") == 0))
	    && ((cfinfo = findcf("127.0.0.1")) == NULL
		|| strcmp(cfinfo->flags, "-") == 0))
		state = MailOrHello;

	cfinfo = NULL;
	if (logfp != NULL) {
		if (insecure)
			(void) fprintf(logfp, "%d#\tremote from %s\n",
					      pid, ihostaddr);
		else
			(void) fprintf(logfp, "%d#\tlocal from uid#%d\n",
					      pid, getuid());
		(void) fflush(logfp);
	}
	while (fgets(buf, sizeof buf, stdin)) {
                /*
                 * We should handle input of arbitrary length.
                 * For now, at least detect the error.
                 */
                if (strchr(buf, '\n') == NULL) {
                        type(500, "Line too long");
                        continue;
                }
                report("%.100s", buf);
		for (cp = buf + strlen(buf) - 1;
		     cp > buf && isascii(*cp) && isspace(*cp); --cp)
			continue;
		*++cp = '\0';
		if (logfp != NULL) {
			(void) fprintf(logfp, "%dr\t%s\n", pid, buf);
			(void) fflush(logfp);
		}
		for (cp = buf; isascii(*cp) && isalpha(*cp); ++cp)
			continue;
		if (cp !=  buf + 4) {
			type(550, "Syntax error");
			continue;
		}
		if (*cp != '\0')
			*cp++ = '\0';
		for (carp = &command_list[0]; carp->verb != NULL; ++carp) {
			if (cistrcmp(carp->verb, buf) == 0)
				break;
		}
		if (carp->verb == NULL) {
			type(550, "Unknown command '%s'", buf);
#ifdef	LOG_WARNING
			syslog(LOG_WARNING,
			       "unknown SMTP command '%s' from %s/%d",
			       buf, rhostname, rport);
#endif	/* LOG_WARNING */
			continue;
		}
		switch (carp->cmd) {
		case Null:
			type(550, "panic!");
			break;
		case Hello:
			if (state != Hello && state != MailOrHello) {
				switch (state) {
				case Mail:
					cp = "Waiting for MAIL command";
					break;
				case Recipient:
					cp = "Waiting for RCPT or DATA command";
					break;
				default:
					cp = NULL;
					break;
				}
				type(503, cp);
				break;
			}
			if (sscanf(cp, "%s", helobuf) != 1) {
				type(501, "What is your domain name?");
				break;
			}
			/*
			 * Craig P. says we have to spit back syntactically
			 * invalid helo parameters at this stage, which is
			 * hard to do right since it requires a full '822
			 * tokenizer.  We do a half-hearted attempt here.
			 */
			if (partridge(helobuf)) {
				type(501, "Invalid %s parameter!", buf);
				break;
			}
			cfinfo = findcf(helobuf);
			if (cfinfo != NULL && *(cfinfo->flags) == '!') {
				if (cfinfo->flags[1] != '\0')
					type(501, "%s", (cfinfo->flags)+1);
				else
					type(501, "Sorry, access denied.");
				break;
			}
			/* check helobuf corresponds to the reverse address */
			if (ihostaddr[0] != '\0'
			    && rhostname[0] != '\0' && rhostname[0] != '['
			    && cistrcmp(helobuf, rhostname) != 0) {
				type(-250, "That hostname is inconsistent with your address to name mapping.");
				type(250, "%s expected \"%s %s\"",
					myhostname, buf, rhostname);
			} else
				type(250, "%s Hello %s", myhostname, helobuf);
			state = MailOrHello;
			break;
		case Mail:
		case Send:
		case SendOrMail:
		case SendAndMail:
			if (state != Mail && state != MailOrHello) {
				switch (state) {
				case Hello:
					cp = "Waiting for HELO command";
					break;
				case Recipient:
					cp = "Waiting for RCPT or DATA command";
					break;
				default:
					cp = NULL;
					break;
				}
				type(503, cp);
				break;
			}
			if (cistrncmp(cp, "From:", 5) != 0) {
				type(501, "where is From: in that?");
				break;
			}
			for (cp = cp+5; *cp != '\0' && *cp != '<'; ++cp)
				if (!isascii(*cp) || !isspace(*cp))
					break;
			if (*cp == '\0') {
				type(501, "where is <...> in that?");
				break;
			} else if (*cp != '<') {
				type(501, "strangeness between : and <");
				break;
			}
			if (*++cp == '<') {
				type(501, "there are too many <'s in that!");
				break;
			}
			for (s = cp+strlen(cp)-1; s >= cp; --s)
				if (isascii(*s) && !isspace(*s) && *s != '>')
					break;
			++s;
			if (*s == '\0') {
				type(501, "missing closing > character");
				break;
			} else if (*s != '>' || *(s+1) != '\0') {
				type(501, "try without the junk at the end");
				break;
			}
			*s = '\0';

			s = NULL;
			if (*cp != '\0' && STYLE(cfinfo, 'f')) {
				if ((s = router(RKEY_FROM, 1, cp)) == NULL)
					/* the error was printed in router() */
					break;
				if (atoi(s) / 100 != 2) {
					/* verification failed */
					type(atoi(s), s+4, "Failed", "Failed");
					free(s);
					break;
				}
			}

			if (mfp == NULL
			    && (mfp = mail_open(MSG_RFC822)) == NULL) {
				if (s)
					free(s);
				type(452, (char *)NULL);
				break;
			}
			rewind(mfp);
			if (insecure)
				(void) fprintf(mfp, "external\n");
			if (ihostaddr[0] != '\0')
				(void) fprintf(mfp, "rcvdfrom %s (%s)\n",
						    helobuf, ihostaddr);
			else
				(void) fprintf(mfp, "rcvdfrom %s\n", helobuf);
			(void) fprintf(mfp, "with %s\n", with_protocol);
			if (*cp == '\0')
				(void) fprintf(mfp, "channel error\n");
			else
				(void) fprintf(mfp, "from <%s>\n", cp);
			if (ferror(mfp)) {
				type(452, (char *)NULL);
				clearerr(mfp);
			} else if (s)
				type(atoi(s), s+4, "Ok", "Ok");
			else
				type(250, (char *)NULL);
			if (s)
				free(s);
			state = Recipient;
			break;
		case Recipient:
			if (state != Recipient && state != RecipientOrData) {
				switch (state) {
				case Hello:
					cp = "Waiting for HELO command";
					break;
				case Mail:
				case MailOrHello:
					cp = "Waiting for MAIL command";
					break;
				default:
					cp = NULL;
					break;
				}
				type(503, cp);
				break;
			}
			if (cistrncmp(cp, "To:", 3) != 0) {
				type(501, "where is To: in that?");
				break;
			}
			for (cp = cp+3; *cp != '\0' && *cp != '<'; ++cp)
				if (!isascii(*cp) || !isspace(*cp))
					break;
			if (*cp == '\0') {
				type(501, "where is <...> in that?");
				break;
			} else if (*cp != '<') {
				type(501, "strangeness between : and <");
				break;
			}
			if (*++cp == '<') {
				type(501, "there are too many <'s in that!");
				break;
			}
			for (s = cp+strlen(cp)-1; s >= cp; --s)
				if (isascii(*s) && !isspace(*s) && *s != '>')
					break;
			if (s > cp)
				++s;
			if (*s == '\0') {
				type(501, "missing closing > character");
				break;
			} else if (*s != '>' || *(s+1) != '\0') {
				type(501, "try without the junk at the end");
				break;
			}
			*s = '\0';

			if (*cp == '\0') {
				type(501, "what is a null recipient?");
				break;
			}

			s = NULL;
			if (STYLE(cfinfo, 't')) {
				if ((s = router(RKEY_TO, 1, cp)) == NULL)
					/* the error was printed in router() */
					break;
				if (atoi(s) / 100 != 2) {
					/* verification failed */
					type(atoi(s), s+4, "Failed", "Failed");
					free(s);
					break;
				}
			}

			(void) fprintf(mfp, "to <%s>\n", cp);
			if (ferror(mfp)) {
				type(452, (char *)NULL);
				clearerr(mfp);
			} else if (s)
				type(atoi(s), s+4, "Ok", "Ok");
			else
				type(250, (char *)NULL);
			if (s)
				free(s);
			state = RecipientOrData;
			break;
		case Data:
			if (state != RecipientOrData) {
				switch (state) {
				case Hello:
					cp = "Waiting for HELO command";
					break;
				case Mail:
				case MailOrHello:
					cp = "Waiting for MAIL command";
					break;
				case Recipient:
					cp = "Waiting for RCPT command";
					break;
				default:
					cp = NULL;
					break;
				}
				type(503, cp);
				break;
			}
			type(354, (char *)NULL);
			(void) alarm(60*60*24);	/* one day should be enough! */
			mvdata(mfp);
			(void) alarm(0);	/* cancel alarm */
			tell = ftell(mfp);
			if (fstat(fileno(mfp), &stbuf) < 0)
				inum = 0;
			else
				inum = stbuf.st_ino;

			if (feof(stdin) || ferror(stdin)) {
				/* [mea@utu.fi] says this can happen */
				(void) mail_abort(mfp);
				reporterr(tell, "premature EOF on DATA input");
				return;
			} else if (ferror(mfp)) {
				type(452, (char *)NULL);
				clearerr(mfp);
				(void) mail_abort(mfp);
				reporterr(tell, "message file error");
			} else if (mail_close(mfp) == EOF) {
				type(452, (char *)NULL);
				reporterr(tell, "message file close failed");
			} else {
				type(250, "Roger");
#ifdef	LOG_INFO
				syslog(LOG_INFO,
				       "accepted id %d (%ldc) from %s/%d",
				       inum, tell, rhostname, rport);
#endif	/* LOG_INFO */
				if (logfp != NULL) {
					(void) fprintf(logfp,
						       "%d#\t%d: %d bytes\n",
						       pid, inum, tell);
					(void) fflush(logfp);
				}
			}
			(void) fclose(mfp);	/* just in case */
			mfp = NULL;
			state = MailOrHello;
			break;
		case Reset:
			if (mfp != NULL) {
				(void) mail_abort(mfp);
				mfp = NULL;
			}
			if (state != Hello)
				state = MailOrHello;
			type(250, (char *)NULL);
			break;
		case Help:
			help(cfinfo, cp);
			break;
		case Verify:
			if (STYLE(cfinfo, 'v')) {
				if ((s = router(RKEY_VERIFY, 0, cp)) != NULL) {
					printf("%s\r\n", s);
					free(s);
				}
			} else
				type(252, (char *)NULL);
			break;
		case Expand:
			if (STYLE(cfinfo, 'e')) {
				if ((s = router(RKEY_EXPAND, 0, cp)) != NULL) {
					printf("%s\r\n", s);
					free(s);
				}
			} else
				type(502, (char *)NULL);
			break;
		case Turn:
			type(502, (char *)NULL);
			break;
		case NoOp:
			type(250, (char *)NULL);
			break;
		case Verbose:
			type(-250, VerbID, Version);
			type(250, Copyright);
			break;
		case Tick:
			if (*cp != '\0')
				*--cp = ' ';
			type(250, "%s", buf);
			with_protocol = WITH_BSMTP;
			break;
		case Quit:
			if (mfp != NULL)
				(void) mail_abort(mfp);
			type(221, (char *)NULL, "Out");
			return;
		default:
			break;
		}
	}
	if (mfp != NULL)
		(void) mail_abort(mfp);
	reporterr(ftell(stdin), "session terminated");
}

#define	TYPE_(m)	type(-241, "%s", m);
#define	TYPE(m)		type(241, "%s", m);

/*
 * parse the query string and print an appropriate help message.
 */

void
help(cfinfo, query)
	struct smtpconf *cfinfo;
	char *query;
{
	int	col;
	char	*cp;
	struct command *carp;
	extern int cistrcmp();
	extern void type();

	for (carp = &command_list[0]; carp->verb != NULL; ++carp) {
		if (cistrcmp(carp->verb, query) == 0)
			break;
	}
	switch (carp->cmd) {
	case Hello:
		TYPE_("helo your.domain.name");
		TYPE_("\tIt is polite to introduce yourself before talking.");
		TYPE ("\tI will in fact ignore you until you do!");
		break;
	case Mail:
		TYPE_("mail from:<sender>");
		TYPE_("\tSpecify the originator address for the next message.");
		if (STYLE(cfinfo, 'f')) {
			TYPE ("\tThe address will be checked before it is accepted.");
		} else {
			TYPE ("\tAny address will be accepted here, but may be rejected later.");
		}
		break;
	case Recipient:
		TYPE_("rcpt to:<recipient>");
		TYPE_("\tSpecify a destination address for the next message.");
		if (STYLE(cfinfo, 't')) {
			TYPE ("\tThe address will be checked before it is accepted.");
		} else {
			TYPE ("\tAny address will be accepted here, but may be rejected later.");
		}
		break;
	case Data:
		TYPE_("data");
		TYPE_("\tStart collecting the message itself.  The text data");
		TYPE ("\tis terminated by a <CRLF>.<CRLF> combination.");
		break;
	case Reset:
		TYPE_("rset");
		TYPE_("\tReset the state of the SMTP server to be ready for");
		TYPE_("\tthe next message, and abort any current transaction.");
		TYPE_("");
		switch (state) {
		case Hello:
			cp = "Waiting for \"helo\" command";
			break;
		case Mail:
			cp = "Waiting for \"mail\" command";
			break;
		case MailOrHello:
			cp = "Waiting for \"mail\" or \"helo\" command";
			break;
		case Recipient:
			cp = "Waiting for \"rcpt\" command";
			break;
		case RecipientOrData:
			cp = "Waiting for \"rcpt\" or \"data\" command";
		default:
			cp = "Unknown";
			break;
		}
		(void) printf("241 The current state is: %s.\r\n", cp);
		break;
	case Send:
	case SendOrMail:
	case SendAndMail:
	case Turn:
		TYPE_(carp->verb);
		TYPE ("\tThis command is not implemented.");
		break;
	case Verify:
		TYPE_("vrfy <recipient>");
		if (STYLE(cfinfo, 'v')) {
			TYPE_("\tPrints the recipients for the given address.")
			TYPE ("\tIf the address is local, it is not expanded.");
		} else {
			TYPE ("\tThis command is disabled.");
		}
		break;
	case Expand:
		TYPE_("expn <recipient>");
		if (STYLE(cfinfo, 'e')) {
			TYPE_("\tPrints the recipients for the given address.")
			TYPE ("\tIf the address is local, it is expanded.");
		} else {
			TYPE ("\tThis command is disabled.");
		}
		break;
	case NoOp:
		TYPE_(carp->verb);
		TYPE ("\tThis command does nothing.");
		break;
	case Quit:
		TYPE_("quit");
		TYPE ("\tTerminate the SMTP protocol conversation.");
		break;
	case Verbose:
		TYPE_("verb");
		TYPE_("\tPrints out the SMTP server version and copyright notice.");
		TYPE ("\tThis command has no other effect.");
		break;
	case Tick:
		TYPE_("tick id");
		TYPE ("\tThis BSMTP command is just reflected back at you.");
		break;
	case Help:
		TYPE_("help [command]");
		TYPE_("\tReminder of what the SMTP command does, or prints:");
		TYPE_("");
		/* fall through */
	case Null:
	default:
		TYPE_(Copyright);
		TYPE_("");
		(void) printf("241-The following commands are recognized:");
		col = 100;
		for (carp = &command_list[0]; carp->verb != NULL; ++carp) {
			if (col > 70) {
				col = 12;
				(void) printf("\r\n241-\t%s", carp->verb);
			} else {
				(void) printf(", %s", carp->verb);
				col += 6;
			}
		}
		(void) printf("\r\n");
		TYPE_("");
		TYPE_("The normal sequence is: helo (mail rcpt+ data)+ quit.");
		TYPE_("");
		TYPE_("This mailer always accepts 8-bit and binary message data.");
		TYPE_("");
		(void) printf("241-For local information contact: ");
		(void) printf("postmaster@%s\r\n", myhostname);
		(void) printf("241 SMTP server comments and bug reports to: ");
		(void) printf("zmhacks@cs.toronto.edu\r\n");
		break;
	}
	(void) fflush(stdout);
}

/* If the argument is not in valid domain name syntax, return 1 */

int
partridge(s)
	char *s;
{
	if (s == NULL || *s == '\0')
		return 1;
	if (*s == '[') {
		/* recognize dotted quad */
		++s;
		do {
			if (!(isascii(*s) && isdigit(*s)))
				return 1;
			while (isascii(*s) && isdigit(*s))
				++s;
		} while (*s == '.' && ++s);
		if (*s++ != ']')
			return 1;
	} else {
		/* recognize domain name */
		do {
			/* start with a letter or digit */
			if (!(isascii(*s) && isalnum(*s)))
				return 1;
			while (isascii(*s) && (isalnum(*s) || *s == '-')) {
				/* don't end with a dash */
				if (*s == '-' && *(s+1) == '.')
					return 1;
				++s;
			}
		} while (*s == '.' && ++s);
	}
	return (*s != '\0');
}

char *
tmalloc(n)
	int n;
{
	extern char *emalloc();

	return emalloc((u_int)n);
}


/*
 * In theory, this should modify the command that ps shows for this process.
 * This is known to not be portable, hopefully it will break badly on systems
 * where it doesn't work.
 */

/* VARARGS */
void
report(va_alist)
	va_dcl
{
	va_list	ap;
	char *cp, buf[8192];

	va_start(ap);
	(void) sprintf(buf, "-%s ", rhostname);
	cp = va_arg(ap, char *);
#ifdef	USE_VFPRINTF
	(void) vsprintf(buf+strlen(buf), cp, ap);
#else	/* !USE_VFPRINTF */
	(void) sprintf(buf+strlen(buf), cp, va_arg(ap, char *));
#endif	/* USE_VFPRINTF */
	for (cp = buf+strlen(buf); cp < buf + (eocmdline - cmdline); ++cp)
		*cp = ' ';
	buf[eocmdline-cmdline] = '\0';
	(void) strcpy(cmdline, buf);
	va_end(ap);
}


/*
 * The way VRFY and EXPN are implemented, and even MAIL FROM and RCPT TO
 * checking, we somehow connect to a router and ask it to do stuff for us.
 * There are three routines, one to connect to the router, one to kill it
 * off again, and the line-getting routine that gets everything the router
 * prints at us, one line at a time.
 */

char promptbuf[30];
int  promptlen;
FILE *tofp, *fromfp;

char *
mgets(buf, flagp, fp)
	char *buf;	/* this is what I returned earlier, if non-null */
	int *flagp;
	FILE *fp;
{
	register int bufsize;
	register char *cp;
	char *s;
	static char *obuf = NULL;
	extern char *emalloc(), *erealloc();
	
	if (buf != NULL)
		free(buf);
	if (*flagp) {
		*flagp = 0;
		return NULL;
	}

	bufsize = 10;
	cp = buf = emalloc(bufsize);
	while ((*cp = getc(fp)) != EOF) {
		if (*cp == '\n') {
			*flagp = 0;
			break;
		}
		++cp;
		if (strncmp(cp - promptlen, promptbuf, promptlen) == 0) {
			free(buf);
			*flagp = 1;
			break;
		}
		if (bufsize - 2 < cp - buf) {
			bufsize *= 2;
			s = erealloc(buf, bufsize);
			cp += s - buf;
			buf = s;
		}
	}
	if (*cp == EOF) {
		(void) printf("got EOF!\n");
		free(buf);
		return NULL;
	}
	*cp = '\0';
	if (obuf == NULL) {
		if (*flagp)
			return obuf;
		obuf = buf;
		return mgets((char *)NULL, flagp, fp);
	}
	cp = obuf;
	obuf = *flagp ? NULL : buf;
	return cp;
}

char *newenviron[] = { "SMTPSERVER=y", (char *)0 };

int
callr()
{
	int sawend, pid, to[2], from[2];
	char *buf, *cp;
	extern char *emalloc();
	extern void runasrootuser(), killr();
	extern char **environ;

	if (pipe(to) < 0 || pipe(from) < 0)
		return -1;

#ifdef USE_UNIONWAIT
	(void) signal(SIGCHLD, reaper);
#else
	(void) signal(SIGCHLD, SIG_IGN);
#endif
	(void) signal(SIGPIPE, SIG_IGN);

	if (routerprog == NULL) {
		if ((buf = getzenv("MAILBIN")) == NULL) {
#ifdef	LOG_ERR
			syslog(LOG_ERR, "MAILBIN unspecified in zmailer.conf");
#endif	/* LOG_ERR */
			return -1;
		}
		routerprog = emalloc(strlen(buf)+sizeof "router"+2);
		(void) sprintf(routerprog, "%s/router", buf);
	}

	if ((pid = fork()) == 0) {	/* child */
		(void) dup2(to[0], 0);
		(void) dup2(from[1], 1);
		(void) dup2(from[1], 2);
		(void) close(to[0]); close(to[1]);
		(void) close(from[0]); close(from[1]);
		runasrootuser();	/* XXX: security alert! */
		environ = newenviron;
		(void) execl(routerprog, "router", "-io-i", (char *)NULL);
#define	BADEXEC	"8@$#&(\n\n"
		(void) write(1, BADEXEC, strlen(BADEXEC));
		_exit(1);
	} else if (pid < 0)
		return -1;
	(void) close(to[0]); close(from[1]);
	tofp = fdopen(to[1], "w");
	fromfp = fdopen(from[0], "r");

	/* stuff a command to print something recognizable down the pipe */
	(void) sprintf(promptbuf, "%d# ", getpid());
	promptlen = strlen(promptbuf);

	/*
	 * Gotta be careful here: ihostaddr is generated within the program
	 * from the IP address, but rhostname is specified by the remote host.
	 * Although it may be caught by partridge(), lets make extra sure.
	 */
	for (cp = rhostname; *cp; ++cp)
		if (*cp == '\'')
			*cp = '?';
	if (rhostname[strlen(rhostname)-1] == '\\')
		rhostname[strlen(rhostname)-1] = '?';

	(void) fprintf(tofp, "PS1='%s' ; %s %s '%s' '%s'\n", promptbuf,
		      ROUTER_SERVER, RKEY_INIT, rhostname, ihostaddr);
	(void) fflush(tofp);

	buf = NULL;
	sawend = 0;
	while ((buf = mgets(buf, &sawend, fromfp)) != NULL) {
		if (strncmp(buf, BADEXEC, strlen(BADEXEC)-2) == 0) {
			free(buf);
			killr(pid);
			return -1;
		}
		/*printf("241%c%s\n", sawend == 1 ? ' ' : '-', buf);*/
	}

	return pid;
}

void
killr(pid)
	int pid;
{
	extern int kill();
	
	if (pid > 0) {
		(void) fclose(tofp);
		(void) fclose(fromfp);
		(void) kill(pid, SIGKILL);
	}
}


/*
 * Now we can do VRFY et al using the router we have connected to.
 */

char *
router(function, holdlast, args)
	char *function, *args;
	int holdlast;
{
	char *buf, *holdcp;
	int sawend;
	extern void type();

	if (routerpid <= 0 && (routerpid = callr()) <= 0) {
		type(451, (char *)NULL);
		return NULL;
	}

	if (args == NULL
	    || strchr(args, '\'') != NULL || args[strlen(args)-1] == '\\') {
		type(501, (char *)NULL);
		return NULL;
	}
	(void) fprintf(tofp, "%s %s '%s'\n", ROUTER_SERVER, function, args);
	(void) fflush(tofp);
	buf = holdcp = NULL;
	sawend = 0;
	while (!sawend && (buf = mgets(buf, &sawend, fromfp)) != NULL) {
		/*
		 * We want to give the router the opportunity to report
		 * result codes, e.g. for boolean requests.  If the first
		 * three characters are digits and the 4th a space or '-',
		 * then pass through.
		 */
		if (strlen(buf) > 4 &&
		    isdigit(buf[0]) && isdigit(buf[1]) && isdigit(buf[2])
		    && (buf[3] == ' ' || buf[3] == '-')) {
			if (holdlast && buf[3] == ' ')
				holdcp = buf;
			else
				(void) printf("%s\r\n", buf);
		} else {
			if (holdlast && sawend)
				holdcp = buf;
			else
				(void) printf("250%c%s\r\n",
				       sawend == 1 ? ' ' : '-', buf);
		}
	}
	fflush(stdout);
	return holdcp;
}

/* VARARGS2 */

void
type(code, fmt, va_alist)
	int code;
	char *fmt;
	va_dcl
{
	va_list ap;
	char format[BUFSIZ];
	char *text = NULL;
	char *s1, *s2, *s3;
	char c;

	va_start(ap);
	if (code < 0) {
		code = -code;
		c = '-';
	} else
		c = ' ';

	(void) printf("%03d%c", code, c);
	if (logfp != NULL)
		(void) fprintf(logfp, "%dw\t%03d%c", pid, code, c);

	switch (code) {
	case 211: /* System status */
		text = "%s";
		break;
	case 214: /* Help message */
		text = "%s";
		break;
	case 220: /* Service ready */
		(void) sprintf(format, "%s %%s", myhostname);
		text = format;
		break;
	case 221: /* Service closing transmission channel */
		(void) sprintf(format, "%s %%s", myhostname);
		text = format;
		break;
	case 250: /* Requested mail action okay, completed */
		text = "Ok";
		break;
	case 251: /* User not local; will forward to <forward-path> */
		text = "User not local; will forward to <%s>";
		break;
	case 252: /* Cannot VRFY user, but will accept message and attempt delivery */
		text = "Cannot VRFY user, but will accept message and attempt delivery";
		break;
	case 354: /* Start mail input; end with <CRLF>.<CRLF> */
		text = "Start mail input; end with <CRLF>.<CRLF>";
		break;
	case 421: /* Service not available, closing transmission channel */
		(void) sprintf(format, "%s %%s", myhostname);
		text = format;
		break;
	case 450: /* Requested mail action not taken: mailbox unavailable */
		text = "Requested mail action not taken: mailbox unavailable";
		break;
	case 451: /* Requested action aborted: local error in processing */
		text = "Requested action aborted: local error in processing";
		break;
	case 452: /* Requested action not taken: insufficient system storage */
		text = "Requested action not taken: insufficient storage";
		break;
	case 500: /* Syntax error, command unrecognized */
		text = "Syntax error, command unrecognized";
		break;
	case 501: /* Syntax error in parameters or arguments */
		text = "Syntax error in parameters or arguments";
		break;
	case 502: /* Command not implemented */
		text = "Command not implemented";
		break;
	case 503: /* Bad sequence of commands */
		text = "Bad sequence of commands";
		break;
	case 504: /* Command parameter not implemented */
		text = "Command parameter not implemented";
		break;
	case 550: /* Requested action not taken: mailbox unavailable */
		text = "Requested action not taken: mailbox unavailable";
		break;
	case 551: /* User not local; please try <forward-path> */
		text = "User not local; please try <%s>";
		break;
	case 552: /* Requested mail action aborted: exceeded storage allocation */
		text = "Requested mail action aborted: exceeded storage allocation";
		break;
	case 553: /* Requested action not taken: mailbox name not allowed */
		text = "Requested action not taken: mailbox name not allowed";
		break;
	case 554: /* Transaction failed */
		text = "Transaction failed";
		break;
	}
#ifdef  USE_VFPRINTF
        if (fmt != NULL)
                (void) vprintf(fmt, ap);
        else
                (void) vprintf(text, ap);
        (void) printf("\r\n");
        (void) fflush(stdout);
        if (logfp != NULL) {
                if (fmt != NULL)
                        (void) vfprintf(logfp, fmt, ap);
                else
                        (void) vfprintf(logfp, text, ap);
                (void) fprintf(logfp, "\n");
                (void) fflush(logfp);
        }
#else   /* !USE_VFPRINTF */
        s1 = va_arg(ap, char *);
        s2 = va_arg(ap, char *);
        s3 = va_arg(ap, char *);
        if (fmt != NULL)
                (void) printf(fmt, s1, s2, s3);
        else
                (void) printf(text, s1, s2, s3);
        (void) printf("\r\n");
        (void) fflush(stdout);
        if (logfp != NULL) {
                if (fmt != NULL)
                        (void) fprintf(logfp, fmt, s1, s2, s3);
                else
                        (void) fprintf(logfp, text, s1, s2, s3);
                (void) fprintf(logfp, "\n");
                (void) fflush(logfp);
        }
#endif  /* USE_VFPRINTF */
        va_end(ap);
}

/* Implement SMTP DATA filter */

/*
 * The state table is indexed by the current character (across), and
 * the current state (down). Column 0 is for any character that is not
 * a '\r', '\n', or '.'.  The table entries encode the next state, and
 * what to output. An entry of EOF means exit. The next state is encoded
 * in the l.s.byte, and what to output is encoded in the next-l.s.byte.
 * If the next-l.s.byte is null, the current input character is output,
 * if the high bit is set, nothing is output, else the entire byte is
 * output followed by the current character.
 */

#define	O_	(0200 << 8)		/* don't print anything flag */
#define N_	('\r' << 8)		/* print '\r' then current input */
#define X_	~0			/* exit. must have (X_&O_) != 0 */

int	states[] = {
/*	  current input character	*/
/*	*	'\r'	'\n'	'.'	EOF	   states */
	0,   O_|15,      10,	0,	X_,	/* 0: during line */
        0,   O_|20,	 X_,	0,	X_,	/* 5: "^." */
	0,   O_|15,      10,	O_|5,	X_,	/* 10: "^" (start state) */
     N_|0,	15,	 10,	N_|0,	X_,	/* 15: seen a \r */
     N_|0,	15,	 X_,	N_|0,	X_,	/* 20: "^.\r" */
};

/*
 * Quick way of getting the column number of the state table,
 * that corresponds to the input character.
 */

int	indexnum[256+1] = { 4 };

/*
 * Copy bytes from stdin to out, obeying sensible SMTP DATA input heuristics.
 *
 * If you can improve on this heavily optimized routine, I'd like to see it.
 * This version goes at better than 100kB/cpu-sec on a Sun 3/180.
 */

void
mvdata(out)
	register FILE *out;
{
	register FILE *input;
	register int c, state, *idxnum, *sts, endstate;

	idxnum = indexnum+1;
	idxnum['\r'] = 1;
	idxnum['\n'] = 2;
	idxnum['.'] = 3;
	/* idxnum[EOF] = 4; */
	state = 10;
	endstate = X_;
	input = stdin;
	sts = states;
	for (;;) {
		c = getc(input);
#if EOF != -1
		if (c == EOF)		/* a little slower... */
			return;
#endif
		if ((state = sts[state+idxnum[c]]) & ~0xff) {
			if (state & O_) {
				if (state == endstate)
					return;
				state = (char)state;
				continue;
			}
			(void) putc((state>>8), out);
			state = (char)state;
		}
		(void) putc(c, out);
	}
}

/* as in: SKIPWHILE(isascii,cp) */
#define SKIPWHILE(X,Y)  while (*Y != '\0' && isascii(*Y) && X(*Y)) { ++Y; }

struct smtpconf *
readcffile(name)
	char *name;
{
	FILE *fp;
	struct smtpconf scf, *head, *tail;
	char c, *cp, buf[1024];
	extern char *emalloc();

	if ((fp = fopen(name, "r")) == NULL)
		return NULL;
	head = NULL;
	while (fgets(buf, sizeof buf, fp) != NULL) {
		if (buf[0] == '#' || (isascii(buf[0]) && isspace(buf[0])))
			continue;
		scf.flags = "";
		scf.next = NULL;
		cp = buf;
		SKIPWHILE(!isspace,cp);
		c = *cp;
		*cp = '\0';
		scf.pattern = emalloc(strlen(buf)+1);
		strcpy(scf.pattern, buf);
		if (c != '\0') {
			++cp;
			SKIPWHILE(isspace,cp);
			scf.flags = emalloc(strlen(cp)+1);
			strcpy(scf.flags, cp);
			if ((cp = strchr(scf.flags, '\n')) != NULL)
				*cp = '\0';
		}
		for (cp = scf.pattern; *cp != '\0'; ++cp)
			if (isascii(*cp) && isalpha(*cp) && isupper(*cp))
				*cp = tolower(*cp);
		if (head == NULL) {
			head = tail = (struct smtpconf *)emalloc(sizeof scf);
			*head = scf;
		} else {
			tail->next = (struct smtpconf *)emalloc(sizeof scf);
			*(tail->next) = scf;
			tail = tail->next;
		}
	}
	(void) fclose(fp);
	return head;
}

struct smtpconf *
findcf(h)
	char *h;
{
	struct smtpconf *scfp;
	register char *cp, *s;

	cp = emalloc(strlen(h)+1);
	for (s = cp; *h != '\0'; ++h) {
		if (isascii(*h) && isalpha(*h) && isupper(*h))
			*s++ = tolower(*h);
		else
			*s++ = *h;
	}
	*s = '\0';
	for (scfp = cfhead; scfp != NULL; scfp = scfp->next) {
		if (strmatch(scfp->pattern, cp)) {
			(void) free(cp);
			return scfp;
		}
	}
	(void) free(cp);
	return NULL;
}
