/*
 *	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";
char *Copyright2= "Copyright 1991-1994 Matti Aarnio";

/*
 * 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 */

#define SMTPLINESIZE	8192

#include <stdio.h>
#include "hostenv.h"
#include "malloc.h"
#include <sys/types.h>
#include <ctype.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/file.h>
#ifdef FCNTL_H
# include FCNTL_H
#endif
#include "zmsignal.h"
#include <errno.h>
#include <varargs.h>
#include "mail.h"

#ifdef	USE_UNIONWAIT
# include <sys/wait.h>
# ifdef	WNOHANG
#  include <sys/resource.h>
# endif
#endif

#ifdef USE_WAITPID
# include <sys/wait.h>
#endif

#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 */
#ifdef	USE_AUTH
# include <authuser.h>
#endif	/* USE_AUTH */

#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, DebugMode,
		Hello2, Mail2, Send2, Verify2 /* 8-bit extensions */
} Command;

Command state;

struct command {
	char	*verb;
	Command	cmd;	
} command_list[] = {
			/* 8-bit smtp extensions */
			{	"EHLO",		Hello2		},
			/* Normal stuff.. */
			{	"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",		Mail2		},
			{	"ESND",		Send2		},
			{	"ESOM",		Send2		},
			{	"ESAM",		Send2		},
			{	"EVFY",		Verify2		},
			/* To fool loosers.. */
			{	"DEBUG",	DebugMode	},
			{	0,		Null		}
};

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

extern char *rfc821_domain();
extern char *rfc821_path();
extern char *rfc821_error;
extern char *rfc821_error_ptr;
extern void type821err();

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	verbose = 0;
int	daemon	= 1;
int	pid, routerpid;
FILE	*logfp = NULL;
int	D_alloc = 0;

/*
 * 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
 * R:   Demand strict conformance to RFC821/822 at address arguments
 */

char	*style = "ve";

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

#define	WITH_SMTP	"SMTP"
#define	WITH_ESMTP	"ESMTP"
#define	WITH_BSMTP	"BSMTP"
char	*with_protocol = WITH_SMTP;

struct command *carp = NULL;
FILE *mfp = NULL;
struct smtpconf *cfinfo = NULL;
long availspace = -1;	/* available diskspace/2 in bytes	*/
long maxsize    = 0;
long sizeoptval = 0;	/* "MAIL FROM:<xxx> SIZE=nnn" -value	*/
long sizeoptsum = 0;
char buf[SMTPLINESIZE];	/* limits size of SMTP commands...
			   On the other hand, limit is asked
			   to be only 1000 chars, not 8k.. */
char helobuf[SMTPLINESIZE];
int maxloadavg = 999;	/* Maximum load-average that is tolerated
			   with smtp-server actively receiving..
			   Default value of 999 is high enough
			   so that it will never block -- use
			   "-L 10" to define lower limit (10) */



unsigned short	auth_flag = 0;
#ifdef	USE_AUTH
# ifndef	AUTH_TIMEOUT
#  define	AUTH_TIMEOUT	5
# endif	/* AUTH_TIMEOUT */
char		auth_username[MAXHOSTNAMELEN+1];
unsigned long	auth_inlocal;
unsigned long	auth_inremote;
unsigned short	auth_local;
unsigned short	auth_remote;
extern void	setrfc1413auth();
#endif	/* USE_AUTH */


extern char *strerror();
extern int getmyhostname(), isit42inetd();
extern int getpeername();
extern SIGNAL_TYPE reaper();
extern SIGNAL_TYPE timedout();
extern void detach(), prversion(), smtpserver();
extern void setrhostname(), killprevious(), settrusteduser(), killr();
extern char *dottedquad();
extern char *Version;
extern char *VersionNumb;
extern u_char *rfc822date();
extern int mvdata();
extern void type(), report(), debug_report();
extern void help();
extern time_t time();
extern char *router();
extern int cistrcmp(), partridge(), cistrncmp(), runastrusteduser();
#ifndef MALLOC_TRACE /* turns these into macroes.. */
extern univptr_t emalloc();
extern univptr_t erealloc();
#endif
extern void runasrootuser();
extern char **environ;
extern int kill();
extern void reporterr();

extern int errno;
extern int optind;
extern char *optarg;

extern char *strchr();

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

char *msg_toohighload = "421 Sorry, the system is too loaded for email reception at the moment\r\n"; /* XX: ??? */


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


int
main(argc, argv)
	int argc;
	char *argv[];
{
	int inetd, errflg, c, raddrlen, s, msgfd, version, i;
	char *mailshare, path[1024];
#ifdef	USE_INET
	struct sockaddr_in sad, raddr;
#endif	/* USE_INET */
	u_short port = 0;

	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;
	*myhostname = 0;
	if (getmyhostname(myhostname, sizeof myhostname) < 0) {
		fprintf(stderr, "%s: gethostname('%s'): %s\n",
				       progname, myhostname, strerror(errno));
		exit(1);
	}
	while ((c = getopt(argc, argv, "aBigl:np:L:M:P:R:s:Vv")) != EOF) {
		switch (c) {
#ifdef	USE_AUTH
		case 'a':
			auth_flag = 1;
			break;
#endif	/* USE_AUTH */
		case 'B':
			with_protocol = WITH_BSMTP;
			break;
		case 'M':
			maxsize = atoi(optarg);
			if (maxsize < 0)
			  maxsize = 0;
			break;
		case 'i':	/* interactive */
			daemon = 0;
			break;
		case 'v':
			verbose = 1; /* in conjunction with -i */
			break;
		case 'g':	/* gullible */
			skeptical = 0;
			break;
		case 'l':	/* log file */
			logfile = emalloc(strlen(optarg)+1);
			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;
		case 'L':	/* Max LoadAverage */
			maxloadavg = atoi(optarg);
			if (maxloadavg < 1)
			  maxloadavg = 10; /* Humph.. */
			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
		fprintf(stderr,
			"Usage: %s [-aBivgnV] [-s xx] [-L maxLoadAvg] [-M SMTPmaxsize] [-R rtrprog] [-p port#] [-P postoffice] [-l logfile]\n",
			progname);
#else	/* !USE_INET */
		fprintf(stderr,
			"Usage: %s [-aBivgnV] [-s xx] [-L maxLoadAvg] [-M SMTPmaxsize] [-R rtrprog] [-P postoffice] [-l logfile]\n",
			progname);
#endif	/* USE_INET */
		exit(1);
	}

	openlogfp(daemon);


	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) {
		strcpy(rhostname, "stdin");
		ihostaddr[0] = '\0';
#ifdef	USE_AUTH
		if (auth_flag)
		  sprintf(auth_username, "uid#%d", getuid());
#endif
		smtpserver(0);
	} else
#ifdef	USE_INET
	if (inetd) {
		if (maxloadavg < loadavg_current()) {
		  write(1, msg_toohighload, strlen(msg_toohighload));
		  exit(1);
		}

		raddrlen = sizeof raddr;
		if (getpeername(0,(struct sockaddr *)&raddr, &raddrlen) == -1) {
			fprintf(stderr, "%s: getpeername(0): %s\n",
				progname, strerror(errno));
			exit(1);
		}
		setrhostname(&raddr);
#ifdef	USE_AUTH
		if (auth_flag != 0) {
#ifdef  LOG_PID
#ifdef  LOG_MAIL
			openlog("smtpserver", LOG_PID|LOG_CONS, LOG_MAIL);
#else  /* !LOG_MAIL */
			openlog("smtpserver", 0);
#endif /* LOG_MAIL */
#endif /* LOG_PID */
			setrfc1413auth(0);
#ifdef	LOG_INFO
			syslog(LOG_INFO,
			       "connection from %s@%s (port %d)",
			       auth_username, rhostname, rport);
#endif /* LOG_INFO */
			closelog();
			openlogfp();
			if (logfp != NULL)
				fprintf(logfp,
					"%d#\tconnection from %s@%s (port %d)\n",
					pid, auth_username, rhostname,
					rport);
			if (auth_flag)
			  sprintf(auth_username+strlen(auth_username),
				  "@%s (port %d)",rhostname, rport);
		}
#endif	/* USE_AUTH */
		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) {
			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) {
			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) {
				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) {
			fprintf(stderr, "%s: bind(): %s\n",
				progname, strerror(errno));
			exit(1);
		}
		if (listen(s, 5) < 0) {
			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) {
				/* junk any buffered output... */
				if (msgfd == fileno(stdout))
					fflush(stdout);
				else if (msgfd == fileno(stderr))
					fflush(stderr);
				close(msgfd);
			}
#if 0
		SIGNAL_HANDLE(SIGCHLD, SIG_IGN);
#else
		SIGNAL_HANDLE(SIGCHLD, reaper);
#endif
		SIGNAL_HANDLE(SIGALRM, timedout);
		SIGNAL_HANDLE(SIGHUP,  SIG_IGN);
		SIGNAL_HANDLE(SIGTERM, SIG_DFL);
		while (1) {
			int childpid;
			raddrlen = sizeof raddr;
			msgfd = accept(s, (struct sockaddr *)&raddr, &raddrlen);
			if (msgfd < 0) {
				if (errno == EINTR)
					continue;
				else {
					fprintf(stderr,
						"%s: accept(): %s\n",
						progname,
						strerror(errno));
					exit(1);
				}
			}
			if ((childpid = fork()) < 0) {	/* can't fork! */
				close(msgfd);
				fprintf(stderr,
					"%s: fork(): %s\n",
					progname, strerror(errno));
				sleep(5);
				continue;
			} else if (childpid == 0) {		/* child */
				close(s);

				if (maxloadavg < loadavg_current()) {
				  write(msgfd, msg_toohighload,
					strlen(msg_toohighload));
				  exit(1);
				}

				SIGNAL_HANDLE(SIGTERM, SIG_IGN);
				if (msgfd != 0)
					dup2(msgfd, 0);
				if (msgfd != 1)
					dup2(msgfd, 1);
				if (msgfd > 1)
					close(msgfd);
				rport = raddr.sin_port;
				setrhostname(&raddr);
#ifdef	USE_AUTH
				if (auth_flag != 0) {
#ifdef  LOG_PID
#ifdef  LOG_MAIL
				  openlog("smtpserver", LOG_PID|LOG_CONS,
					  LOG_MAIL);
#else  /* !LOG_MAIL */
				  openlog("smtpserver", 0);
#endif /* LOG_MAIL */
#endif /* LOG_PID */
				  setrfc1413auth(0);
#ifdef	LOG_INFO
				  syslog(LOG_INFO,
					 "connection from %s@%s (port %d)",
					 auth_username, rhostname, rport);
#endif /* LOG_INFO */
				  closelog();
				  openlogfp();
				  if (logfp != NULL)
				    fprintf(logfp,
					    "%d#\tconnection from %s@%s (port %d)\n",
					    pid, auth_username, rhostname,
					    rport);
				  if (auth_flag)
				    sprintf(auth_username+strlen(auth_username),
					    "@%s (port %d)",rhostname,rport);
				}
#endif	/* USE_AUTH */
				smtpserver(1);
				if (routerpid > 0)
					killr(routerpid);
				_exit(0);
			} else
				close(msgfd);
		}
	}
#else	/* !USE_INET */
	{
		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;

	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)
			strcpy(rhostname, hp->h_name);
		else
			strcpy(rhostname, ihostaddr);
	} else {
		strcpy(rhostname, ihostaddr);
	}
}
#endif	/* USE_INET */

SIGNAL_TYPE
timedout()
{
	reporterr(0,"SMTP protocol timed out");

	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	USE_WAITPID
	while (waitpid(-1, &status, WNOHANG) > 0)
#else
#ifdef	WNOHANG /* UNIONWAIT with WNOHANG .. */
	while (wait3(&status, WNOHANG, (struct rusage *)NULL) > 0)
#else	/* !WNOHANG */
	while (wait(&status) > 0)
#endif	/* WNOHANG */
#endif
		continue;
	SIGNAL_HANDLE(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) {
		fprintf(logfp, "%d#\taborted: %s\n", pid, msg);
		fflush(logfp);
	}
}

void
smtp_helo(cp)
char *cp;
{
	if (state != Hello && state != MailOrHello) {
		switch (state) {
		case Mail:
			cp = "Waiting for MAIL command";
			break;
		case Recipient:
			cp = "Waiting for RCPT command";
			break;
		default:
			cp = NULL;
			break;
		}
		type(503, cp);
		return;
	}
	while (*cp == ' ' || *cp == '\t') ++cp;
	if (sscanf(cp, "%s", helobuf) != 1) {
		type(501, "What is your domain name?");
		return;
	}
	/*
	 * 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.
	 */
	/*
	 * Matti A. says we have a more-than-half-hearted
	 * tokenizer -- RFC821SCN.C, lets use it :)
	 * We need it for proper handling of ESMTP anyway
	 */
	if (partridge(helobuf, cfinfo)) {
		rfc821_error_ptr += (cp - helobuf);
		type821err(501, buf,
			   "Invalid `%s' parameter!  Err: %s",
			   buf, rfc821_error);
		return;
	}
	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.");
	  return;
	}
	/* Check helobuf corresponds to the reverse address */
	if (ihostaddr[0] != '\0'
	    && rhostname[0] != '\0' && rhostname[0] != '['
	    && cistrcmp(helobuf, rhostname) != 0) {
		type(-250, " %s That hostname is inconsistent with",
		     myhostname);
		type(-250, " %s your address to name mapping.",
		     myhostname);
		type(carp->cmd==Hello2 ? -250: 250,
		     " %s expected \"%s %s\"", myhostname,
		     carp->verb, rhostname);
	} else
	  type(carp->cmd==Hello2 ? -250: 250,
	       " %s Hello %s", myhostname, helobuf);
	if (carp->cmd == Hello2) {

		/* ESMTP -- RFC 1651,1652,1653,1428 thingies */
		char sizebuf[20];

		sprintf(sizebuf,"SIZE %d",maxsize); /* 0: No fixed max size
						          in force, else:
							  The FIXED maximum */
		type(-250,sizebuf);
		type(-250,"8BITMIME");
		type(-250,"EXPN");
		type(-250,"VRFY");
		type(-250,"XDSN"); /* XX: Change to "DSN" when all parts of it function! */
		type( 250,"HELP");
		with_protocol = WITH_ESMTP;
	}
	state = MailOrHello;
}

void
smtp_mail(cp,insecure)
char *cp;
int insecure;
{
	char *s;
	int rc;
	char *drpt_envid;

	/* For ESMTP SIZE-option use we need to know
	   how much space we have, it is easiest by
	   opening the spool file here, and asking
	   filesystem to report free space..
	   We use at most half of the free space,
	   and each recipient will use the claimed
	   size, thus marking up its reception..	*/

	if (mfp == NULL
	    && (mfp = mail_open(MSG_RFC822)) == NULL) {
		type(452, (char *)NULL);
		return;
	}

	availspace = fd_statfs(fileno(mfp));
	if (availspace < 0)
	  availspace = 2000000000; /* Over 2G ? */
	availspace >>= 1;

	if (carp->cmd == Mail2 || carp->cmd == Send2) {
	  with_protocol = WITH_ESMTP;
	}
	if (state != Mail && state != MailOrHello) {
		switch (state) {
		case Hello:
			cp = "Waiting for HELO/EHLO command";
			break;
		case Recipient:
			cp = "Waiting for RCPT command";
			break;
		default:
			cp = NULL;
			break;
		}
		type(503, cp);
		return;
	}
	if (cistrncmp(cp, "From:", 5) != 0) {
		type(501, "where is From: in that?");
		return;
	}
	for (cp = cp+5; *cp != '\0' && *cp != '<'; ++cp)
		if (!isascii(*cp) || !isspace(*cp))
			return;
	if (*cp == '\0') {
		type(501, "where is <...> in that?");
		return;
	} else if (*cp != '<') {
		type(501, "strangeness between : and <");
		return;
	}
	if (*(cp+1) == '<') {
		type(501, "there are too many <'s in that!");
		return;
	}

	/* "<" [ <a-t-l> ":" ] <localpart> "@" <domain> ">" */
	s = rfc821_path(cp,STYLE(cfinfo, 'R'));
	if (s == cp) {
	  /* Failure.. */
	  type821err(501,buf,"Path data: %s",rfc821_error);
	  return;
	}
	if (*s == '>') {
		type(501, "there are too many >'s in that!");
		return;
	}
	++cp;		/* Skip the initial '<' */
	*(s-1) = 0;	/* Scrub the final  '>' */

	/* BODY=8BITMIME SIZE=nnnn ENVID=xxxxxx */
	sizeoptval = -1;
	sizeoptsum = -1;
	drpt_envid = NULL;
	rc = 0;
	while (*s) {
		while (*s && (*s == ' '||*s == '\t')) ++s;
		if (cistrncmp("BODY=",s,5)==0) {
			/* Actually we do not use this data. */
			s += 5;
			if (cistrncmp(s,"8BITMIME",8)==0) {
			  s += 8;
			} else if (cistrncmp(s,"7BIT",4)==0) {
			  s += 4;
			}
			if (*s && *s != ' ' && *s != '\t') {
			  type(455,"BODY-param data error, must be either 8BITMIME, or 7BIT");
			  rc = 1;
			  break;
			}
			continue;
		}
		if (cistrncmp("SIZE=",s,5)==0) {
			s += 5;
			if (sizeoptval != -1) {
			  type(455,"SIZE-param double definition!");
			  rc = 1;
			  break;
			}
			/* This data we use, gather the value*/
			sizeoptval = 0;
			while (isascii(*s) && isdigit(*s)) {
				sizeoptval *= 10;
				sizeoptval += (*s - '0');
				++s;
			}
			if (*s && *s != ' ' && *s != '\t') {
			  type(501,"SIZE-param data error");
			  rc = 1;
			  break;
			}
			continue;
		}
		/* IETF-NOTARY  SMTP-DRPT extensions */
		if (cistrncmp("ENVID=",s,6)==0) {
			if (drpt_envid != NULL) {
			  type(501,"ENVID double definition!");
			  rc = 1;
			  break;
			}
			drpt_envid = s+6;
			while (*s && *s != ' ' && *s != '\t') {
			  if (*s < 33 || *s > 126) {
			    type(501,"ENVID data contains illegal characters!");
			    rc = 1;
			    break;
			  }
			  ++s;
			}
			*s++ = 0;
			if (strlen(drpt_envid) < 6) {
			  type(501,"ENVID= missing data!");
			  rc = 1;
			  break;
			}
			continue;
		}
		type(555,"Unknown MAIL FROM:<> parameter: %s",s);
		break;
	}
	if (rc != 0) return; /* Error(s) in previous loop.. */

	/*printf("  <path>: len=%d \"%s\"\n",cp-s,cp);*/

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

	if (mfp == NULL &&
	    (mfp = mail_open(MSG_RFC822)) == NULL) {
		if (s)
			free(s);
		type(452, (char *)NULL);
		return;
	}
	rewind(mfp);
	if (insecure)
		fprintf(mfp, "external\n");
	if (ihostaddr[0] != '\0')
		fprintf(mfp, "rcvdfrom %s (%s)\n",
			helobuf, ihostaddr);
	else
		fprintf(mfp, "rcvdfrom %s\n", helobuf);
	fprintf(mfp, "with %s\n", with_protocol);
#ifdef USE_AUTH
	if (auth_flag)
		fprintf(mfp, "authinfo %s\n", auth_username);
#endif
	if (*cp == '\0')
		fprintf(mfp, "channel error\n");
	else
		fprintf(mfp, "from <%s>\n", cp);
	if (drpt_envid != NULL)
		fprintf(mfp, "envid %s\n", drpt_envid);
	if (ferror(mfp)) {
		type(452, (char *)NULL);
		clearerr(mfp);
	} else if (sizeoptval > maxsize && maxsize > 0) {
		type(552, "The message size exceeds fixed maximum message size");
	} else if (sizeoptval > availspace) {
		type(452, "Try again later, insufficient storage available at the moment");
	} else if (s)
		type(atoi(s), s+4, "Ok", "Ok");
	else
		type(250, (char *)NULL);
	if (s)
		free(s);
	state = Recipient;
}

void
smtp_rcpt(cp)
char *cp;
{
	char *s;
	char *drpt_notify, *drpt_ret, *drpt_orcpt;

	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);
		return;
	}
	if (cistrncmp(cp, "To:", 3) != 0) {
		type(501, "where is To: in that?");
		return;
	}
	for (cp = cp+3; *cp != '\0' && *cp != '<'; ++cp)
		if (!isascii(*cp) || !isspace(*cp))
			return;
	if (*cp == '\0') {
		type(501, "where is <...> in that?");
		return;
	} else if (*cp != '<') {
		type(501, "strangeness between : and <");
		return;
	}
	if (*(cp+1) == '<') {
		type(501, "there are too many <'s in that!");
		return;
	}

	/* "<" [ <a-t-l> ":" ] <localpart> "@" <domain> ">" */
	s = rfc821_path(cp,STYLE(cfinfo, 'R'));
	if (s == cp) {
	  /* Failure.. */
	  type821err(501,buf,"Path data: %s",rfc821_error);
	  return;
	}
	if (*s == '>') {
		type(501, "there are too many >'s in that!");
		return;
	}
	++cp;		/* Skip the initial '<' */
	*(s-1) = 0;	/* Scrub the final  '>' */

	/*printf("  <path>: len=%d \"%s\"\n",cp-s,cp);*/

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

	drpt_notify = NULL;
	drpt_ret    = NULL;
	drpt_orcpt  = NULL;

	while (*s) {
		while (isascii(*s) && isspace(*s)) ++s;
		/* IETF-NOTARY  SMTP-RCPT-DRPT extensions */
		if (cistrncmp("NOTIFY=",s,7)==0) {
		  if (drpt_notify) {
		    type(501,"NOTIFY-param double defined!");
		    return;
		  }
		  drpt_notify = s;
		  s += 7;
		  if (cistrncmp("SUCCESS",s,7)==0)
		    s += 7;
		  else if (cistrncmp("FAILURE",s,7)==0)
		    s += 7;
		  else if (cistrncmp("ALWAYS",s,6)==0)
		    s += 6;
		  else if (cistrncmp("NEVER",s,5)==0)
		    s += 5;
		  if (*s && *s != ' ' && *s != '\t') {
		    type(455,"NOTIFY-param data error");
		    return;
		  }
		  if (*s) *s++ = '\0';
		  continue;
		}
		if (cistrncmp("RET=",s,4)==0) {
		  if (drpt_ret) {
		    type(501,"RET-param double defined!");
		    return;
		  }
		  drpt_ret = s;
		  s += 4;
		  if (cistrncmp("YES",s,3)==0)
		    s += 3;
		  else if (cistrncmp("NO",s,2)==0)
		    s += 2;
		  if (*s && *s != ' ' && *s != '\t') {
		    type(501,"RET-param data error");
		    return;
		  }
		  if (*s) *s++ = '\0';
		  continue;
		}
		if (cistrncmp("ORCPT=",s,6)==0) {
		  if (drpt_orcpt) {
		    type(501,"ORCPT-param double defined!");
		    return;
		  }
		  drpt_orcpt = s;
		  while (*s && *s != ' ' && *s != '\t')
		    ++s;
		  if (*s) *s++ = '\0';
		  if (strlen(drpt_orcpt) < 7) {
		    type(501,"ORCPT-param data error!");
		    return;
		  }
		  continue;
		}
		type(555,"Unknown RCPT TO:<> parameter: %s",s);
		return;
	}


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

	/* IETF-NOTARY DSN data: */
	if (drpt_orcpt || drpt_notify || drpt_ret) {
	  fputs("todsn",mfp);
	  if (drpt_notify) fprintf(mfp, " %s", drpt_notify);
	  if (drpt_ret)    fprintf(mfp, " %s", drpt_ret);
	  if (drpt_orcpt)  fprintf(mfp, " %s", drpt_orcpt);
	  fputs("\n",mfp);
	}
	/* Normal "RCPT TO:<>" data: */
	fprintf(mfp, "to <%s>\n", cp);
	if (sizeoptval < 0)
		sizeoptval = 0;
	sizeoptsum += sizeoptval;
	if (ferror(mfp)) {
		type(452, (char *)NULL);
		clearerr(mfp);
	} else if (sizeoptsum > availspace) {
		type(452, "insufficient storage space, try again later");
	} else if (s) {
		type(atoi(s), s+4, "Ok", "Ok");
	} else {
		if (sizeoptval)
			type(250, "Ok; can accomodate %d byte message",sizeoptval);
		else
			type(250, (char *)NULL);
	}
	if (s)
		free(s);
	state = RecipientOrData;
}


void
smtpserver(insecure)
	int insecure;
{
	char *cp, *s;
	time_t now;
	long tell, filsiz;
	int inum;
	struct stat stbuf;
	struct sockaddr_in localsock;
	int localsocksize;
	int rc;
	int c, co;

	char *eobuf;
	int VerboseCommand = 0;

	openlogfp();

	runastrusteduser();

	mfp = NULL;
	report("(connected)");
	now = time((time_t *)0);
	cp = (char *)rfc822date(&now);
	if (*(cp + strlen(cp) - 1) == '\n')
		*(cp + strlen(cp) - 1) = '\0';

	localsocksize = sizeof(localsock);
	if (getsockname(fileno(stdin),
			(struct sockaddr *)&localsock,&localsocksize) == 0 &&
	    localsocksize == sizeof(localsock)) {
	  /* Success with it :) */
	  struct hostent *hostent = gethostbyaddr((void*)&localsock.sin_addr,4,
						  localsock.sin_family);
	  if (hostent)
	    strcpy(myhostname,hostent->h_name);
	}

	type(220, "%s Server ESMTP%s %s ready at %s",
	     myhostname, auth_flag ? "+AUTH":"", VersionNumb, cp);
#ifdef	LOG_PID
#ifdef	LOG_MAIL
	openlog("smtpserver", LOG_PID|LOG_CONS, 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)
			fprintf(logfp, "%d#\tremote from %s\n",
				      pid, ihostaddr);
		else
			fprintf(logfp, "%d#\tlocal from uid#%d\n",
				      pid, getuid());
		fflush(logfp);
	}

#define SMTP_ALARM_IVAL (20*60) /* 20 minutes.. */
	while (1) {

		/* Alarm processing on the SMTP protocol channel */
		alarm(SMTP_ALARM_IVAL);

		*buf = 0;
		s = buf;
		eobuf = buf + sizeof(buf) -1;
		rc = -1;
		co = -1;
		/* Our own  fgets() -- gets also NULs, flags illegals.. */
		while ((c = getc(stdin)) != EOF && s < eobuf) {
			if (c == '\n') {
			  /* *s++ = c; */ /* Don't save it! No need */
			  break;
			} else if (co == '\r' && rc < 0)
			  rc = (s - buf); /* Spurious CR on the input.. */

			if (c == '\0'       && rc < 0) rc = (s - buf);
			if ((c & 0x80) != 0 && rc < 0) rc = (s - buf);
			if (c != '\r' && c != '\t' &&
			    (c < 32 || c == 127) && rc < 0)
			  rc = (s - buf);
			*s++ = c;
		}
		*s = '\0';
		eobuf = s; /* Buf end ptr.. */
		alarm(0);	/* Cancel the alarm */
		if (c == EOF && s == buf) {
			/* XX: ???  Uh, Hung up on us ? */
			if (mfp != NULL)
			  mail_abort(mfp);
			mfp = NULL;
			break;
		}
		if (c != '\n') {
		    type(500, "Line too long/not terminated with CRLF..");
		    continue;
		}

		/* Zap the possible trailing  \r */
		if ((eobuf > buf) && (eobuf[-1] == '\r'))
		  *--eobuf = '\0';

		/* Chop the trailing spaces */
		while ((eobuf > buf) && (eobuf[-1] == ' ' ||
					 eobuf[-1] == '\t'))
		  *--eobuf = '\0';

		if (logfp != NULL) {
			fprintf(logfp, "%dr\t%s\n", pid, buf);
			fflush(logfp);
		}

		if (rc >= 0) {
		  rfc821_error_ptr = buf + rc;
		  type821err(500, buf,
			     "Illegal input characters: %s",
			     ((buf[rc]=='\0') ? "NUL on SMTP input" :
			      ((buf[rc]&0x80) ? "8-bit char on SMTP input":
			       "Control chars on SMTP input")));
		  continue;
		}

		if (verbose && !daemon)
			fprintf(stdout,"%s\n",buf); /* XX: trace.. */
		report("%.100s", buf);

		for (cp = buf; isascii(*cp) && isalpha(*cp); ++cp)
			continue;
		if (cp >  buf + 6) { /* "DEBUG" is longest of them.. */
			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;
		}
		*cp++ = ' ';
		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:
		case Hello2:
			/* This code is LONG.. */
			smtp_helo(cp);
			break;
		case Mail:
		case Mail2:
		case Send:
		case Send2:
		case SendOrMail:
		case SendAndMail:
			/* This code is LONG.. */
			smtp_mail(cp,insecure);
			break;
		case Recipient:
			/* This code is LONG.. */
			smtp_rcpt(cp);
			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);
			alarm(60*60*24);	/* one day should be enough! */
			filsiz = mvdata(mfp);
			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 */
				mail_abort(mfp);
				reporterr(tell, "premature EOF on DATA input");
				return;
			} else if (ferror(mfp)) {
				type(452, (char *)NULL);
				clearerr(mfp);
				mail_abort(mfp);
				reporterr(tell, "message file error");
			} else if (maxsize > 0 && filsiz > maxsize) {
				mail_abort(mfp);
				type(552, "Message size exceeded fixed maximum size for acceptable email");

			} 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) {
					fprintf(logfp,
						       "%d#\t%d: %ld bytes\n",
						       pid, inum, tell);
					fflush(logfp);
				}
			}
			fclose(mfp);	/* just in case */
			mfp = NULL;
			state = MailOrHello;
			break;
		case Reset:
			if (mfp != NULL) {
				mail_abort(mfp);
				mfp = NULL;
			}
			if (state != Hello)
				state = MailOrHello;
			type(250, (char *)NULL);
			break;
		case Help:
			help(cfinfo, cp);
			break;
		case Verify:
		case Verify2:
			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);
			type(250, Copyright2);
			VerboseCommand = 1;
			break;
		case DebugMode:
			debug_report(VerboseCommand,rhostname,buf);
			break;
		case Tick:
			if (*cp != '\0')
				*--cp = ' ';
			type(250, "%s", buf);
			with_protocol = WITH_BSMTP;
			break;
		case Quit:
			if (mfp != NULL)
				mail_abort(mfp);
			type(221, (char *)NULL, "Out");
			return;
		default:
			break;
		}
	}
	if (mfp != NULL)
		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;

	for (carp = &command_list[0]; carp->verb != NULL; ++carp) {
		if (cistrcmp(carp->verb, query) == 0)
			break;
	}
	switch (carp->cmd) {
	case Hello:
	case Hello2:
		TYPE_("EHLO your.domain.name");
		TYPE_("HELO your.domain.name");
		TYPE_("\tThe 'EHLO' is for Extended SMTP feature recognition, and is preferred!.");
		TYPE_("\tIt is polite to introduce yourself before talking.");
		TYPE ("\tI will in fact ignore you until you do!");
		break;
	case Mail:
	case Mail2:
		TYPE_("MAIL FROM:<sender> (ESMTP parameters)");
		TYPE_("EMAL 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> (ESMTP parameters)");
		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 \"EHLO\"/\"HELO\" command";
			break;
		case Recipient:
			cp = "Waiting for \"RCPT\" command";
			break;
		case RecipientOrData:
			cp = "Waiting for \"RCPT\" or \"DATA\" command";
		default:
			cp = "Unknown";
			break;
		}
		printf("241 The current state is: %s.\r\n", cp);
		break;
	case Send:
	case Send2:
	case SendOrMail:
	case SendAndMail:
	case Turn:
		TYPE_(carp->verb);
		TYPE ("\tThis command is not implemented.");
		break;
	case Verify:
	case Verify2:
		TYPE_("VRFY <recipient>");
		TYPE_("EVFY <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_(Copyright2);
		TYPE_("");
		printf("241-The following commands are recognized:");
		col = 100;
		for (carp = &command_list[0]; carp->verb != NULL; ++carp) {
			if (col > 70) {
				col = 12;
				printf("\r\n241-\t%s", carp->verb);
			} else {
				printf(", %s", carp->verb);
				col += 6;
			}
		}
		printf("\r\n");
		TYPE_("");
		TYPE_("The normal sequence is: EHLO/HELO (MAIL RCPT+ DATA)+ QUIT.");
		TYPE_("");
		TYPE_("This mailer always accepts 8-bit and binary message data");
		TYPE_("though you are better to use MIME format!");
		TYPE_("");
		printf("241-For local information contact: ");
		printf("postmaster@%s\r\n", myhostname);
		printf("241 SMTP server comments and bug reports to: ");
		printf("zmhacks@nic.funet.fi\r\n");
		break;
	}
	fflush(stdout);
}

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

int
partridge(s, cfinfo)
	char *s;
	struct smtpconf *cfinfo;
{
	char *p = rfc821_domain(s,STYLE(cfinfo, 'R'));
	if (p == s) return 1;
	while (*p == ' ' || *p == '\n') ++p;
	if (*p == 0) return 0;

	rfc821_error_ptr = p;
	rfc821_error = "Spurious junk after the DOMAIN in HELO/EHLO argument";

	return 1;
}

#if 0 /* tmalloc() is in the library, isn't it ? */
univptr_t
tmalloc(n)
	int n;
{
	return emalloc((u_int)n);
}
#endif

/*
 * 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);
	sprintf(buf, "-%s ", rhostname);
	cp = va_arg(ap, char *);
#ifdef	USE_VFPRINTF
	vsprintf(buf+strlen(buf), cp, ap);
#else	/* !USE_VFPRINTF */
	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';
	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;
	
	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) {
		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;

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

#if 0
	SIGNAL_HANDLE(SIGCHLD, SIG_DFL); /* ?? SIG_DFL  on SVR4 ? */
#else
	SIGNAL_HANDLE(SIGCHLD, reaper);
#endif
	SIGNAL_HANDLE(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);
		sprintf(routerprog, "%s/router", buf);
	}

	if ((pid = fork()) == 0) {	/* child */
		dup2(to[0], 0);
		dup2(from[1], 1);
		dup2(from[1], 2);
		close(to[0]); close(to[1]);
		close(from[0]); close(from[1]);
		runasrootuser();	/* XXX: security alert! */
		environ = newenviron;
		execl(routerprog, "router", "-io-i", (char *)NULL);
#define	BADEXEC	"8@$#&(\n\n"
		write(1, BADEXEC, strlen(BADEXEC));
		_exit(1);
	} else if (pid < 0)
		return -1;
	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 */
	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] = '?';

	fprintf(tofp, "PS1='%s' ; %s %s '%s' '%s'\n", promptbuf,
		      ROUTER_SERVER, RKEY_INIT, rhostname, ihostaddr);
	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;
{
	if (pid > 0) {
		fclose(tofp);
		fclose(fromfp);
		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;

	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;
	}
	fprintf(tofp, "%s %s '%s'\n", ROUTER_SERVER, function, args);
	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
				printf("%s\r\n", buf);
		} else {
			if (holdlast && sawend)
				holdcp = buf;
			else
				printf("250%c%s\r\n",
				       sawend == 1 ? ' ' : '-', buf);
		}
	}
	fflush(stdout);
	return holdcp;
}


/* VARARGS2 */

void
type(code, fmt, s1, s2, s3, s4, s5)
	int code;
	char *fmt, *s1, *s2, *s3, *s4, *s5;
{
	char format[BUFSIZ];
	char *text = NULL;
	char c;

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

	printf("%03d%c", code, c);
	if (logfp != NULL)
		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 */
		sprintf(format, "%s %%s", myhostname);
		text = format;
		break;
	case 221: /* Service closing transmission channel */
		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 */
		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;
	}
	if (fmt != NULL)
		printf(fmt, s1, s2, s3, s4, s5);
	else
		printf(text, s1, s2, s3, s4, s5);
	printf("\r\n");
	fflush(stdout);
	if (logfp != NULL) {
		if (fmt != NULL)
			fprintf(logfp, fmt, s1, s2, s3, s4, s5);
		else
			fprintf(logfp, text, s1, s2, s3, s4, s5);
		fprintf(logfp, "\n");
		fflush(logfp);
	}
}


void
type821err(code,inbuf,msg,s1,s2,s3)
	int code;
	char *msg, *inbuf;
	void *s1, *s2, *s3; /* XX: Propably not portable.. */
{
	char *s = inbuf+4;

	printf("%03d-",code);
	if (logfp != NULL)
	  fprintf(logfp, "%dw\t%03d-", pid, code);
	if (strlen(inbuf)>3)
	  while (*s && s < rfc821_error_ptr) {
	    ++s;
	    putc(' ',stdout);
	    if (logfp != NULL)
	      putc(' ',logfp);
	  }
	printf("^\n");
	if (logfp != NULL)
	  fprintf(logfp,"^\n");
	type(code,msg,s1,s2,s3);
}


/* 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.
 */

static int	indexnum[256+1] = {
#if 0
	idxnum['\r'] = 1;
	idxnum['\n'] = 2;
	idxnum['.'] = 3;
#if EOF == -1
	idxnum[EOF] = 4;
#endif
#endif
	4, /* EOF */
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 1, 0, 0, /* ...'\n'..'\r'.. */
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, /* ... '.' .. */
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

/*
 * 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.
 */

int /* count of bytes */
mvdata(out)
	register FILE *out;
{
	register FILE *input;
	register int c, state, *idxnum, *sts, endstate, cnt;

#ifdef NO_INCOMING_HEADER_PROCESSING
	idxnum = indexnum+1;

	state = 10;
	endstate = X_;
	input = stdin;
	sts = states;
	cnt = 0;
#else
	char linebuf[2000], *s, *eol;
	int col;

	idxnum = indexnum+1;

	state = 10;
	endstate = X_;
	input = stdin;
	sts = states;
	cnt = 0;
	col = 0;

	/* ================ Input the email headers ================ */
	/*	     and analyze them a bit (Precedence:)	     */
	mail_priority = _MAILPRIO_NORMAL;
	for (;;) {
		c = getc(input);
		/* An EOF in here is an error! */
#if EOF != -1
		if (c == EOF) return cnt;
#endif
		if ((state = sts[state+idxnum[c]]) & ~0xff) {
			if (state & O_) {
				if (state == endstate)
					return cnt;
				state = (char)state;
				continue;
			}
			if (!ferror(out))
				putc((state>>8), out);
			if (col < (sizeof(linebuf)-1))
				linebuf[col++] = (state >> 8);
			state = (char)state;
		}
		if (!ferror(out))
			putc(c, out);
		if (col < (sizeof(linebuf)-1))
			linebuf[col++] = c;
		/* LF, or something else ?  Here the else.. */
		if (c != '\n')
			continue;

		/* See what this header is about -- or if the body starts.. */
		if (col > sizeof(linebuf)-1) col = sizeof(linebuf)-1;
		eol = linebuf + col;
		*eol = 0;
		for (s = linebuf; s < eol; ++s)
			if (*s != ' ' && *s != '\t' &&
			    *s != '\r' && *s != '\n')
				break;
		if (s == eol) { /* All-blank line */
			break;  /* Into the body processing */
		}
		col = 0;
		if (*linebuf == ' ' || *linebuf == '\t')
			continue;	/* continuation line.. */

		/* ================ PROCESS THE HEADERS! ================ */
		if (strncasecmp(linebuf,"Precedence:",11)==0) {
			s = linebuf+11;
			while (*s == ' ' || *s == '\t') ++s;
			if ((eol - s) < 4) continue; /* Hmm.. */
			if (strncasecmp("junk",s,4)==0) {
				mail_priority = _MAILPRIO_JUNK;
			} else if (strncasecmp("bulk",s,5)==0) {
				mail_priority = _MAILPRIO_BULK;
			}
		}
	}
	/* if(logfp)
	   fprintf(logfp,"%d#\t(mail_priority=%d)\n",pid,mail_priority);
	 */
#endif

	/* ================ Normal email BODY input.. ================ */
	for (;;) {
		c = getc(input);
#if EOF != -1
		if (c == EOF)		/* a little slower... */
			return cnt;
#endif
		++cnt;
		if ((state = sts[state+idxnum[c]]) & ~0xff) {
			if (state & O_) {
				if (state == endstate)
					return cnt;
				state = (char)state;
				continue;
			}
			if (!ferror(out))
			  putc((state>>8), out);
			state = (char)state;
		}
		if (!ferror(out))
		  putc(c, out);
	}
	return cnt;
}

#ifdef	USE_AUTH
void
setrfc1413auth(sd)
unsigned int sd;
{
    char *cp;
    strncpy(auth_username, "UNKNOWN", MAXHOSTNAMELEN);
    if (auth_fd2(sd, &auth_inlocal, &auth_inremote,
		 &auth_local, &auth_remote) == -1) {
	    return;
    } else {
	    cp = auth_tcpuser3(auth_inlocal, auth_inremote,
			       auth_local,   auth_remote,
			       AUTH_TIMEOUT);
	    if ( cp != (char *) NULL ) {
		    strncpy(auth_username, cp, MAXHOSTNAMELEN);
	    }
    }
}
#endif	/* USE_AUTH */

/* 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 = NULL;
	char c, *cp, buf[1024];

	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 = strdup(buf);
		scf.maxloadavg = 999;
		if (c != '\0') {
		  ++cp;
		  SKIPWHILE(isspace,cp);
		  if (*cp && isascii(*cp) && isdigit(*cp)) {
		    /* Sanity-check -- 2 is VERY LOW */
		    if ((scf.maxloadavg = atoi(cp)) < 2)
		      scf.maxloadavg = 2;
		    SKIPWHILE(isdigit,cp);
		    SKIPWHILE(isspace,cp);
		  }
		  scf.flags = strdup(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;
		}
	}
	fclose(fp);
	return head;
}

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

#ifndef	USE_ALLOCA
	cp = emalloc(strlen(h)+1);
#else
	cp = alloca(strlen(h)+1);
#endif
	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)) {
#ifndef USE_ALLOCA
			free(cp);
#endif
			return scfp;
		}
	}
#ifndef USE_ALLOCA
	free(cp);
#endif
	return NULL;
}
