/*
 *	Copyright 1988 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 */
/*
 *	Several extensive changes by Matti Aarnio <mea@nic.funet.fi>
 *	Copyright 1991-1996.
 */

/*
 * ZMailer SMTP server.
 */

char *VerbID = "ZMailer SMTP server %s";
char *Copyright = "Copyright 1990 Rayan S. Zachariassen";
char *Copyright2= "Copyright 1991-1996 Matti Aarnio";

/* Timing parameters -- when expired, session is killed ... */

#define SMTP_COMMAND_ALARM_IVAL 1200 /* 20 minutes.. */
#define SMTP_DATA_TIME_PER_LINE  600 /* 10 minutes of life.. */

/*
 * 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 "hostenv.h"
#ifdef HAVE_RESOLVER
# define USE_INET
#endif
#include <stdio.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  HAVE_WAITPID
# include <sys/wait.h>
#else
# ifdef HAVE_WAIT3
#  include <sys/wait.h> /* Has BSD wait3() */
# else
#  ifdef HAVE_SYS_WAIT_H /* POSIX.1 compatible */
#   include <sys/wait.h>
#  else /* Not POSIX.1 compatible, lets fake it.. */
extern int wait();
#  endif
# endif
#endif

#if	defined(HAVE_NETDB_H)
# include <netdb.h>
#endif
#ifdef	HAVE_SYS_SOCKET_H
# include <sys/socket.h>
# include <netinet/in.h>
#endif
#ifdef	HAVE_SYSLOG
# include <syslog.h>
#endif	/* HAVE_SYSLOG */
#include <identuser.h>

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

#ifndef	MAXHOSTNAMELEN
#define	MAXHOSTNAMELEN 256
#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 */
	/* [mea@utu.fi] - 4-Feb-95, I disabled this.. */

typedef enum {	Null, Hello, Mail, MailOrHello, Recipient,
		RecipientOrData, Data, Send, SendOrMail,
		SendAndMail, Reset, Verify, Expand, Help,
		NoOp, Quit, Turn, Tick, Verbose, DebugMode,
	        Turnme, BData,
		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		},
			{	"BDAT",		BData,		},
			{	"RSET",		Reset		},
			{	"VRFY",		Verify		},
			{	"EXPN",		Expand		},
			{	"HELP",		Help		},
			{	"NOOP",		NoOp		},
			{	"QUIT",		Quit		},
			/* ZMailer speciality, and an alias for it */
			{	"ETRN",		Turnme		},
			{	"TURNME",	Turnme		},
			/* sendmail extensions */
			{	"VERB",		Verbose		},
			{	"ONEX",		NoOp		},
			/* Depreciated */
			{	"SEND",		Send		},
			{	"SOML",		SendOrMail	},
			{	"SAML",		SendAndMail	},
			{	"TURN",		Turn		},
			/* bsmtp extensions */
			{	"TICK",		Tick		},
			/* 8-bit smtp extensions -- depreciated */
			{	"EMAL",		Mail2		},
			{	"ESND",		Send2		},
			{	"ESOM",		Send2		},
			{	"ESAM",		Send2		},
			{	"EVFY",		Verify2		},
			/* To fool loosers.. */
			{	"DEBUG",	DebugMode	},
			/* End of the list */
			{	0,		Null		}
};

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

extern char *rfc821_domain __((char *s, int strict));
extern char *rfc821_path   __((char *s, int strict));
extern char *rfc821_path2  __((char *s, int strict));
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	checkhelo  = 0;
int	verbose    = 0;
int	daemon_flg = 1;
int	rcpt_count = 0;
int	sender_ok  = 0;
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;
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) */
char helobuf[SMTPLINESIZE];




unsigned short	ident_flag = 0;
# ifndef	IDENT_TIMEOUT
#  define	IDENT_TIMEOUT	5
# endif	/* IDENT_TIMEOUT */
char		ident_username[BUFSIZ]; /* It can be mighty big.. */
static void	setrfc1413ident();


extern char *strerror __((int));
extern int getmyhostname(), isit42inetd();
extern int getpeername();
static RETSIGTYPE reaper __((int sig));
static RETSIGTYPE timedout __((int sig));
extern void detach __((void)), prversion __(());
static void smtpserver __((int insecure));
static void setrhostname __((struct sockaddr_in *));
extern int  killprevious __(());
extern void settrusteduser __(());
static void killr __((int pid));
extern char *dottedquad __(());
extern char *Version;
extern char *VersionNumb;
extern u_char *rfc822date __((time_t *));
static int mvdata __((FILE *, char *));
static int mvbdata __((FILE *, char *, long, int *));
extern void type __(());
static void report __(());
extern void debug_report __(());
static void help __(());
extern time_t time __((time_t *));
static char *router __((char*, int, char*));
static int partridge __((char*, struct smtpconf *));
#ifndef MALLOC_TRACE /* turns these into macroes.. */
# ifndef __XMALLOC_H__ /* at ../include/malloc.h */
extern univptr_t emalloc __((size_t));
extern univptr_t erealloc __((void *, size_t));
# endif
#endif
extern void runasrootuser __((void));
extern int runastrusteduser __((void));
extern char **environ;
extern int kill __((pid_t,int));
static void reporterr __((long, char*));

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

extern char *strchr(), *strrchr();
extern int cistrcmp(), cistrncmp();

#ifndef CISTREQN
# define   CISTREQN(x, y, n)  (cistrncmp((char *)(x), (char *)(y), n) == 0)
#endif
#ifndef CISTREQ
# define   CISTREQ(x, y)  (cistrcmp((char *)(x), (char *)(y)) == 0)
#endif

#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");
/* XX: */		setvbuf(logfp,NULL,_IOLBF,BUFSIZ); /* Line-buffered */
		}
	} 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;
	int port_set = 0;

	SIGNAL_HANDLE(SIGPIPE, SIG_IGN);

/* XX: */ setvbuf(logfp,NULL,_IOFBF,BUFSIZ); /* Block-buffered */

	daemon_flg = 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);
	}
	/* optarg = NULL; */
	while ((c = getopt(argc, argv, "aBighl:np:L:M:P:R:s:Vv")) != EOF) {
	  switch (c) {
	    case 'a':
		ident_flag = 1;
		break;
	    case 'B':
		with_protocol = WITH_BSMTP;
		break;
	    case 'M':
		maxsize = atoi(optarg);
		if (maxsize < 0)
		  maxsize = 0;
		break;
	    case 'i':	/* interactive */
		daemon_flg = 0;
		break;
	    case 'v':
		verbose = 1; /* in conjunction with -i */
		break;
	    case 'g':	/* gullible */
		skeptical = 0;
		break;
	    case 'h':	/* checkhelo */
		checkhelo = 1;
		break;
	    case 'l':	/* log file */
		logfile = strdup(optarg);
		break;
	    case 'n':	/* running under inetd */
		inetd = 1;
		break;
	    case 's':	/* checking style */
		style = strdup(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));
		port_set = 1;
		break;
#endif	/* USE_INET */
	    case 'R':	/* router binary used for verification */
		routerprog = strdup(optarg);
		break;
	    case 'P':
		postoffice = strdup(optarg);
		break;
	    case 'V':
		prversion("smtpserver");
		exit(0);
		break;	/* paranoia */
	    default:
		fprintf(stderr,
			"%s: Unknown option, c=%d ('%c')\n",progname,c,c);
		++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_flg && 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);
	}

	pid = getpid();
	if (!logfp)
	  openlogfp(daemon_flg);

	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_flg) {
	  strcpy(rhostname, "stdin");
	  ihostaddr[0] = '\0';
	  if (ident_flag)
	    sprintf(ident_username, "uid#%d", getuid());
	  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);
	    if (ident_flag != 0) {
#ifdef LOG_MAIL
#ifdef  LOG_PID
	      openlog("smtpserver", LOG_PID|LOG_CONS, LOG_MAIL);
#else  /* !LOG_PID */
	      openlog("smtpserver", 0);
#endif /* LOG_PID */
#else
	      openlog("smtpserver", 0);
#endif /* LOG_MAIL */
	      setrfc1413ident(0);
#ifdef	LOG_INFO
	      syslog(LOG_INFO,
		     "connection from %s@%s (port %d)",
		     ident_username, rhostname, rport);
#endif /* LOG_INFO */
	      pid = getpid();
	      if (!logfp)
		openlogfp(daemon_flg);
	      if (logfp != NULL)
		fprintf(logfp,
			"%d#\tconnection from %s@%s (port %d)\n",
			pid, ident_username, rhostname,
			rport);
	      if (ident_flag)
		sprintf(ident_username+strlen(ident_username),
			"@%s (port %d)",rhostname, rport);
	    }
#if 0
	    SIGNAL_HANDLE(SIGCHLD, SIG_DFL);
#else
	    SIGNAL_HANDLE(SIGCHLD, reaper);
#endif
	    SIGNAL_HANDLE(SIGALRM, timedout);
	    SIGNAL_HANDLE(SIGHUP,  SIG_IGN);
	    SIGNAL_HANDLE(SIGTERM, SIG_DFL);
	    smtpserver(1);
	  } else { /* Not from under the inetd -- standalone server */
	    if (postoffice == NULL
		&& (postoffice = getzenv("POSTOFFICE")) == NULL)
	      postoffice = POSTOFFICE;
	    if (!port_set)
	      if (killprevious(SIGTERM, PID_SMTPSERVER) != 0) {
		fprintf(stderr,
			"%s: Can't write my pidfile!  Disk full ?\n",
			progname);
		exit(2);
	      }
	    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) {
	      struct servent *service;
#ifdef	IPPORT_SMTP
	      port = htons(IPPORT_SMTP);
#endif /* !IPPORT_SMTP */
	      if ((service = getservbyname("smtp", "tcp")) == NULL) {
		fprintf(stderr,
			"%s: no SMTP service entry, using default\n",
			progname);
	      } else
		port = service->s_port;
	    }
	    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, 20) < 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 */
	    
	    if (!port_set || port != htons(25))
	      killprevious(0, PID_SMTPSERVER); /* deposit pid */
#if 1
	    close(0); close(1); close(2);
	    open("/dev/null",O_RDONLY);
	    open("/dev/null",O_RDONLY);
#else
	    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);
	      }
#endif
#if 1
	    pid = getpid();
	    if (!logfp)
	      openlogfp(1);
	    if (logfp != NULL) {
	      time_t now;
	      char *cp;
	      time(&now);
	      cp = (char*) rfc822date(&now);
	      fprintf(logfp,"00000#\tstarted server pid %d at %s", pid, cp);
	      /*fprintf(logfp,"00000#\tfileno(logfp) = %d\n",fileno(logfp));*/
	      fflush(logfp);
	    }
#endif
#if 0
	    SIGNAL_HANDLE(SIGCHLD, SIG_DFL);
#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) {
		switch (errno) {
		  case EINTR:		/* very common.. */
		      continue;
		  case ECONNRESET:	/* seen to happen! */
		  case ECONNABORTED:
		  case ENETUNREACH:
		  case ENETRESET:
		  case ETIMEDOUT:	/* unlikely.. */
		  case ECONNREFUSED:	/* unlikely.. */
		  case EHOSTDOWN:
		  case EHOSTUNREACH:
		      continue;
		  default:
		      break;
		}
		fprintf(stderr,
			"%s: accept(): %s\n", progname, strerror(errno));
		if (!logfp)
		  openlogfp(1);
		fprintf(logfp, "000000#\taccept(): %s\n", strerror(errno));
		fflush(logfp);
		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); /* Listening socket.. */
		pid = getpid();

		if (logfp) /* Open the logfp latter.. */
		  fclose(logfp);
		logfp = NULL;

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

		SIGNAL_HANDLE(SIGTERM, SIG_IGN);
		rport = raddr.sin_port;
		setrhostname(&raddr);

		if (ident_flag != 0) {
#ifdef  LOG_MAIL
#ifdef  LOG_PID
		  openlog("smtpserver", LOG_PID|LOG_CONS, LOG_MAIL);
#else  /* !LOG_PID */
		  openlog("smtpserver", 0);
#endif /* LOG_PID */
#else  /* !LOG_MAIL */
		  openlog("smtpserver", 0);
#endif /* !LOG_MAIL */
		  setrfc1413ident(0);
#ifdef	LOG_INFO
		  syslog(LOG_INFO,
			 "connection from %s@%s (port %d)",
			 ident_username, rhostname, rport);
#endif /* LOG_INFO */
		  pid = getpid();
		  if (!logfp)
		    openlogfp(1);
		  if (logfp != NULL)
		    fprintf(logfp,
			    "%d#\tconnection from %s@%s (port %d)\n",
			    pid, ident_username, rhostname,
			    rport);
		  if (ident_flag)
		    sprintf(ident_username+strlen(ident_username),
			    "@%s (port %d)",rhostname,rport);
		}

/* if (logfp) fprintf(logfp,"%d#\tInput fd=%d\n",getpid(),msgfd); */
		
		if (msgfd != 0)
		  dup2(msgfd, 0);
		dup2(0, 1);
		if (msgfd > 1)
		  close(msgfd);

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

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

static RETSIGTYPE
timedout(sig)
int sig;
{
	reporterr(0,"SMTP protocol timed out");

	/* If there is something going on, kill the file.. */
	if (mfp != NULL)
	  mail_abort(mfp);
	mfp = NULL;

	if (routerpid > 0)
	  killr(routerpid);
	exit(0);
}

static RETSIGTYPE
reaper(sig)
int sig;
{
	int status;

#ifdef	HAVE_WAITPID
	while (waitpid(-1, &status, WNOHANG) > 0)
#else
#ifdef	HAVE_WAIT3
	while (wait3(&status, WNOHANG, (struct rusage *)NULL) > 0)
#else	/* ... plain simple waiting wait() ... */
	/* This can freeze at wait() ?  Who could test ?  A system
	   without wait3()/waitpid(), but with BSD networking ??? */
	while (wait(&status) > 0)
#endif	/* WNOHANG */
#endif
		continue;
	SIGNAL_HANDLE(SIGCHLD, reaper);
}

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


/* Support routine: Our own buffering for stdinput */

int  s_bufread = 0;
int  s_readout = 0;
char s_buffer[SMTPLINESIZE];
int  s_inputfd = 0; /* STDIN */
int  s_status  = 0;

int s_feof()
{
	return s_status;
}

int s_getc()
{
	int rc = 0;

	if (s_status) return s_status;
	if (s_readout >= s_bufread) {
	  int rc = read(s_inputfd, s_buffer, sizeof(s_buffer));
	  if (rc <= 0) {
	    s_status = EOF;
	    return EOF;
	  }
	  s_bufread = rc;
	  s_readout = 0;
	}
	if (rc) return EOF;
	return (unsigned char) s_buffer[s_readout++];
}

int s_hasinput()
{
	if (s_readout >= s_bufread) {
	  /* So if it did dry up, try non-blocking read */
	  int flags = fcntl(s_inputfd,F_GETFL,0);
	  s_readout = 0;
#if defined(O_NONBLOCK) || defined(FNDELAY)
#ifdef O_NONBLOCK
	  fcntl(s_inputfd,F_SETFL,flags | O_NONBLOCK);
#else
	  fcntl(s_inputfd,F_SETFL,flags | FNDELAY);
#endif
	  s_bufread = read(s_inputfd,s_buffer,sizeof(s_buffer));
	  fcntl(s_inputfd,F_SETFL,flags);
	  if (s_bufread > 0) return s_bufread;
#endif
	  return 0;
	}
	return (s_bufread - s_readout);
}

void s_setup(fd)
	int fd;
{
	s_status = 0;
	s_inputfd = fd;
	s_bufread = -1;
	s_readout = 0;
}


/* SMTP-server verb subroutines */

void
smtp_helo(buf,cp)
char *buf, *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 (checkhelo && skeptical && partridge(helobuf, cfinfo)) {
	  rfc821_error_ptr += (cp - helobuf);
	  type821err(-501, buf,
		     "Invalid `%s' parameter!",
		     buf);
	  type(501,"Err: %s", rfc821_error);
	  strcpy(helobuf,"Bad.Helo.Input");
	  return;
	}
	/* At least check the input, though say "Ok, Master" while
	   complaining.. */
	if (!checkhelo && partridge(helobuf, cfinfo)) {
	  rfc821_error_ptr += (cp - helobuf);
	  type821err(-250, buf, "Invalid `%s' parameter!", buf);
	  type(-250,"Err: %s", rfc821_error);
	  if (carp->cmd == Hello)
	    strcpy(helobuf,"Bad.HELO.Input");
	  else
	    strcpy(helobuf,"Bad.EHLO.Input");
	}
	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 (skeptical && ihostaddr[0] != '\0'
	    && rhostname[0] != '\0' && rhostname[0] != '['
	    && !CISTREQ(helobuf, rhostname)) {
	  if (checkhelo) {
	    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 %ld",maxsize); /* 0: No fixed max size
						  in force, else:
						  The FIXED maximum */
	  type(-250,sizebuf);
	  type(-250,"8BITMIME");
	  type(-250,"PIPELINING");
	  type(-250,"CHUNKING"); /* RFC 1830 */
	  type(-250,"EXPN");
	  type(-250,"VRFY");
	  type(-250,"XDSN"); /* XX: Change to "DSN" when all parts of it function! */
	  type(-250,"X-RCPTLIMIT 10000"); /* VERY HIGH figure, normal is 100 */
	  type(-250,"X-TURNME");
	  type(-250,"ETRN");
	  type( 250,"HELP");
	  with_protocol = WITH_ESMTP;
	}
	state = MailOrHello;
}

void
smtp_mail(buf,cp,insecure)
char *buf, *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 (!CISTREQN(cp, "From:", 5)) {
	  type(501, "where is From: in that?");
	  return;
	}
	for (cp = cp+5; *cp != '\0' && *cp != '<'; ++cp)
	  if (!isascii(*cp) || !isspace(*cp)) {
	    type(501, "where is <...> in that?");
	    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 (CISTREQN("BODY=",s,5)) {
	    /* Actually we do not use this data. */
	    s += 5;
	    if (CISTREQN(s,"8BITMIME",8)) {
	      s += 8;
	    } else if (CISTREQN(s,"7BIT",4)) {
	      s += 4;
	    }
	    if (*s && *s != ' ' && *s != '\t') {
	      type(455,"BODY-param data error, must be either 8BITMIME, or 7BIT");
	      rc = 1;
	      break;
	    }
	    continue;
	  }
	  if (CISTREQN("SIZE=",s,5)) {
	    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 (CISTREQN("ENVID=",s,6)) {
	    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);
	if (ident_flag)
	  fprintf(mfp, "identinfo %s\n", ident_username);
	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);

	sender_ok = 0; /* Set it, when we are sure.. */

	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);
	  sender_ok = 1;
	}
	if (s)
	  free(s);
	state = Recipient;
	rcpt_count = 0;
}

void
smtp_rcpt(buf,cp)
char *buf, *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 (!CISTREQN(cp, "To:", 3)) {
	  type(501, "where is To: in that?");
	  return;
	}
	for (cp = cp+3; *cp != '\0' && *cp != '<'; ++cp)
	  if (!isascii(*cp) || !isspace(*cp)) {
	    type(501, "where is <...> in that?");
	    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 (CISTREQN("NOTIFY=",s,7)) {
	    if (drpt_notify) {
	      type(501,"NOTIFY-param double defined!");
	      return;
	    }
	    drpt_notify = s;
	    s += 7;
	    if (CISTREQN("SUCCESS",s,7))
	      s += 7;
	    else if (CISTREQN("FAILURE",s,7))
	      s += 7;
	    else if (CISTREQN("ALWAYS",s,6))
	      s += 6;
	    else if (CISTREQN("NEVER",s,5))
	      s += 5;
	    if (*s && *s != ' ' && *s != '\t') {
	      type(455,"NOTIFY-param data error");
	      return;
	    }
	    if (*s) *s++ = '\0';
	    continue;
	  }
	  if (CISTREQN("RET=",s,4)) {
	    if (drpt_ret) {
	      type(501,"RET-param double defined!");
	      return;
	    }
	    drpt_ret = s;
	    s += 4;
	    if (CISTREQN("YES",s,3))
	      s += 3;
	    else if (CISTREQN("NO",s,2))
	      s += 2;
	    if (*s && *s != ' ' && *s != '\t') {
	      type(501,"RET-param data error");
	      return;
	    }
	    if (*s) *s++ = '\0';
	    continue;
	  }
	  if (CISTREQN("ORCPT=",s,6)) {
	    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 (maxsize > 0 && sizeoptsum > maxsize) {
	  type(552, "Message size exceeds fixed maximum size for acceptable email");
	} else if (sizeoptsum > availspace) {
	  type(452, "insufficient storage space, try again later");
	} else if (s) {
	  type(atoi(s), s+4, "Ok", "Ok");
	  ++rcpt_count;
	} else {
	  if (sizeoptval)
	    type(250, "Ok; can accomodate %d byte message",sizeoptval);
	  else
	    type(250, (char *)NULL);
	  ++rcpt_count;
	}
	if (s)
	  free(s);
	state = RecipientOrData;
}

static void
smtp_turnme(name,cp)
char *name, *cp;
{
	FILE *mfp = mail_open(MSG_RFC822);
	if (!mfp) {
	  type(452, "Failed to initiate TURNME request;  Disk full?");
	  fflush(stdout);
	  return;
	}
	fprintf(mfp,"%c%c%s\n",_CF_TURNME,_CFTAG_NORMAL,cp);
	/* printf("050-My uid=%d/%d\r\n",getuid(),geteuid()); */
	runasrootuser();
	if (mail_close_scheduler(mfp)) {
	  type(452, "Failed to initiate TURNME request;  Permission denied?");
	} else {
	  type(-250,"A TURNME request is initiated - lets hope the system");
	  type(-250,"has resources to honour it.   We call the remote, if");
	  type( 250,"we have anything to send there.");
	}
	runastrusteduser();
	fflush(stdout);
}

static int
smtp_data(buf,cp)
     char *buf, *cp;
{
	int filsiz, inum;
	struct stat stbuf;
	long tell;
	char msg[2048];

	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;
	    case BData:
		cp = "Must not intermix BDAT and DATA in same transaction!";
		break;
	    default:
		cp = NULL;
		break;
	  }
	  type(503, cp);
	  fflush(stdout);
	  return 0;
	}
	if (sender_ok == 0) {
	  type(550, "No valid sender, rejecting all recipients");
	  fflush(stdout);
	  state = MailOrHello;
	  return 0;
	}
	if (rcpt_count == 0) {
	  /* No valid recipients! */
	  type(550, "No valid recipient at RCPT addresses, or no RCPT addresses at all");
	  fflush(stdout);
	  state = MailOrHello;
	  return 0;
	}
	rcpt_count = 0; /* now we can zero it.. */
	type(354, (char *)NULL);
	fflush(stdout);
	fputs("env-end\n",mfp);
	/* We set alarm()s inside the mvdata() */
	*msg = 0;
	filsiz = mvdata(mfp, msg);
	alarm(0);		/* cancel the alarm() */
	tell = ftell(mfp);

	if (fstat(FILENO(mfp), &stbuf) < 0)
	  inum = 0;
	else
	  inum = stbuf.st_ino;

	if (*msg != 0) {
	  mail_abort(mfp);
	  mfp = NULL;
	  type(452,"%s",msg);
	  fflush(stdout);
	} else if (s_feof()) {
	  /* [mea@utu.fi] says this can happen */
	  mail_abort(mfp);
	  mfp = NULL;
	  reporterr(tell, "premature EOF on DATA input");
	  fflush(stdout);
	  return;
	} else if (ferror(mfp)) {
	  type(452, (char *)NULL);
	  fflush(stdout);
	  clearerr(mfp);
	  mail_abort(mfp);
	  mfp = NULL;
	  reporterr(tell, "message file error");
	} else if (maxsize > 0 && filsiz > maxsize) {
	  mail_abort(mfp);
	  mfp = NULL;
	  type(552, "Message size exceeded fixed maximum size for acceptable email");
	  fflush(stdout);
	} else if (mail_close(mfp) == EOF) {
	  type(452, (char *)NULL);
	  fflush(stdout);
	  mfp = NULL;
	  reporterr(tell, "message file close failed");
	} else {
	  mfp = NULL;
	  type(250, "Roger");
	  fflush(stdout);
#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);
	  }
	}
	state = MailOrHello;
	fflush(stdout);
	return 0;
}

static int
smtp_bdata(buf,cp)
     char *buf, *cp;
{
	int filsiz, inum, rc;
	struct stat stbuf;
	long tell;
	char msg[2048];
	long bdata_chunksize;
	int bdata_last;
	static int bdata_blocknum = 0;
	static int mvb_state;

	if (state == RecipientOrData) {
	  state = BData;
	  bdata_blocknum = 0;
	  mvb_state = -1;
	}

	*msg = 0;
	rc = sscanf(cp,"%ld %s %s",&bdata_chunksize,msg, msg+8);
	++bdata_blocknum;
	bdata_last = CISTREQ(msg,"LAST");
	if (!(bdata_chunksize > 0L
	      && (rc == 1 || (rc == 2 && bdata_last)))) {
	  type(501,NULL);
	  return 0;
	}
	
	if (bdata_blocknum == 1) {
	  fputs("env-end\n",mfp);
	}

	/* We set alarm()s inside the mvdata() */
	*msg = 0;
	filsiz = mvbdata(mfp, msg, bdata_chunksize, &mvb_state);
	alarm(0);		/* cancel the alarm() */
	if (mfp)
	  tell = ftell(mfp);
	else
	  tell = 0;

	if (state != BData) {
	  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);
	  fflush(stdout);
	  if (mfp) mail_abort(mfp);
	  mfp = NULL;
	  return 0;
	}

	if (bdata_blocknum == 1) {
	  if (sender_ok == 0 || rcpt_count == 0) {
	    char *cp = "No valid sender, rejecting all recipients";
	    if (sender_ok != 0)
	      cp = "No valid recipient at RCPT addresses, or no RCPT addresses at all";
	    type(550, cp);
	    fflush(stdout);
	    state = MailOrHello;
	    if (mfp) mail_abort(mfp);
	    mfp = NULL;
	    return 0;
	  }

	  rcpt_count = 0; /* now we can zero them.. */
	  sender_ok = 0;
	}

	if (mfp == NULL) {
	  type(503,"BDAT block discarded due to earlier error");
	} else if (*msg != 0) {
	  mail_abort(mfp);
	  clearerr(mfp);
	  mfp = NULL;
	  type(452,"%s",msg);
	} else if (s_feof()) {
	  /* [mea@utu.fi] says this can happen */
	  mail_abort(mfp);
	  clearerr(mfp);
	  mfp = NULL;
	  reporterr(tell, "premature EOF on BDAT input");
	  return -1;
	} else if (ferror(mfp)) {
	  type(452, (char *)NULL);
	  clearerr(mfp);
	  mail_abort(mfp);
	  clearerr(mfp);
	  mfp = NULL;
	  reporterr(tell, "write to spool file failed");
	} else if (maxsize > 0 && tell > maxsize) {
	  mail_abort(mfp);
	  mfp = NULL;
	  type(552, "Message size exceeded fixed maximum size for acceptable email");
	} else if (bdata_last) {
	  if (fstat(FILENO(mfp), &stbuf) < 0)
	    inum = 0;
	  else
	    inum = stbuf.st_ino;
	  if (mail_close(mfp) == EOF) {
	    type(452, (char *)NULL);
	    mfp = NULL;
	    reporterr(tell, "message file close failed");
	  } else {
	    mfp = NULL;
	    type(250, "Roger, stored %ld bytes into spool", tell);
#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);
	    }
	  }
	} else { /* Not last chunk! */
	  type(250,"Received %ld bytes",bdata_chunksize);
	}
	if (bdata_last) {
	  state = MailOrHello;
	}
	/* Flush, if no more input available -- pipelining optimization */
	if (!s_hasinput())
	  fflush(stdout);
	return 0;
}

/* The SMTP-server itself */

static void
smtpserver(insecure)
	int insecure;
{
	char *cp;
	time_t now;
	int cfi;
	struct sockaddr_in localsock;
	int localsocksize;

	int VerboseCommand = 0;

	pid = getpid();
	if (!logfp)
	  openlogfp(insecure);

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

	s_setup(FILENO(stdin));

	localsocksize = sizeof(localsock);
	if (getsockname(s_inputfd,
			(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 ZMailer Server %s ESMTP%s ready at %s",
	     myhostname, VersionNumb, ident_flag ? "+IDENT":"", cp);
	fflush(stdout);

#ifdef	LOG_MAIL
#ifdef	LOG_PID
	openlog("smtpserver", LOG_PID|LOG_CONS, LOG_MAIL);
#else
	openlog("smtpserver", 0);
#endif	/* LOG_PID */
#else	/* !LOG_MAIL */
	openlog("smtpserver", 0);
#endif	/* LOG_MAIL */

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

	while (1) {

	  char buf[SMTPLINESIZE];	/* limits size of SMTP commands...
					   On the other hand, limit is asked
					   to be only 1000 chars, not 8k.. */
	  int rc = -1;
	  int c, co = -1;
	  int i;
	  char *eobuf;

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

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

	    if (c == '\0'       && rc < 0) rc = i;
	    if ((c & 0x80) != 0 && rc < 0) rc = i;
	    if (c != '\r' && c != '\t' &&
		(c < 32 || c == 127) && rc < 0)
	      rc = i;
	    buf[i++] = c;
	  }
	  buf[i] = '\0';
	  eobuf = &buf[i];	/* Buf end ptr.. */
	  alarm(0);		/* Cancel the alarm */
	  if (c == EOF && i == 0) {
	    /* 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")));
	    fflush(stdout);
	    continue;
	  }

	  if (verbose && !daemon_flg)
	    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");
	    fflush(stdout);
	    continue;
	  }
	  c = *cp;
	  if (c != '\0')
	    *cp = '\0';
	  for (carp = &command_list[0]; carp->verb != NULL; ++carp) {
	    if (CISTREQ(carp->verb, buf))
	      break;
	  }
	  *cp++ = c;
	  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 */
	    fflush(stdout);
	    continue;
	  }
	  switch (carp->cmd) {
	    case Null:
		type(550, "panic!");
		fflush(stdout);
		break;
	    case Hello:
	    case Hello2:
		/* This code is LONG.. */
		smtp_helo(buf,cp);
		fflush(stdout);
		break;
	    case Mail:
	    case Mail2:
	    case Send:
	    case Send2:
	    case SendOrMail:
	    case SendAndMail:
		/* This code is LONG.. */
		smtp_mail(buf,cp,insecure);
		if (!s_hasinput())
		  fflush(stdout);
	        else
		  /* if (verbose) */
		    fprintf(logfp,"%d#\t-- pipeline input exists %d bytes\n", pid, s_hasinput());
		break;
	    case Recipient:
		/* This code is LONG.. */
		smtp_rcpt(buf,cp);
		if (!s_hasinput())
		  fflush(stdout);
		else
		  /* if (verbose) */
		    fprintf(logfp,"%d#\t-- pipeline input exists %d bytes\n", pid, s_hasinput());
		break;
	    case Data:
		if (smtp_data(buf,cp) < 0)
		  return;
		break;
	    case BData:
		if (smtp_bdata(buf,cp) < 0)
		  return;
		if (!s_hasinput())
		  fflush(stdout);
	        else
		  /* if (verbose) */
		    fprintf(logfp,"%d#\t-- pipeline input exists %d bytes\n", pid, s_hasinput());
		break;
	    case Reset:
		if (mfp != NULL) {
		  mail_abort(mfp);
		  mfp = NULL;
		}
		if (state != Hello)
		  state = MailOrHello;
		type(250, (char *)NULL);
		fflush(stdout);
		break;
	    case Help:
		help(cfinfo, cp);
		fflush(stdout);
		break;
	    case Verify:
	    case Verify2:
		if (state == Hello) {
		  type (503, "Waiting for HELO/EHLO command");
		  fflush(stdout);
		  break;
		}
		if ((cfi = STYLE(cfinfo, 'v'))) {
		  char *s;
		  while (*cp == ' ' || *cp == '\t') ++cp;
		  if (*cp == '<')
		    s = rfc821_path(cp,cfi); /* with < > */
		  else
		    s = rfc821_path2(cp,cfi); /* Without < > */
		  if (s == cp) {
		    type821err(501,buf,"Path data: %s",rfc821_error);
		    break;
		  }
		  while (*s == ' ' || *s == '\t') ++s;
		  if (*s != 0) {
		    type(501, "Growl! Extra junk after the VRFY argument!");
		    break;
		  }
		  if ((s = router(RKEY_VERIFY, 0, cp)) != NULL) {
		    printf("%s\r\n", s);
		    free(s);
		  } else
		    type(501, "Unable to verify that address");
		} else
		  type(252, (char *)NULL);
		fflush(stdout);
		break;
	    case Expand:
		if (state == Hello) {
		  type (503, "Waiting for HELO/EHLO command");
		  fflush(stdout);
		  break;
		}
		if ((cfi = STYLE(cfinfo, 'e'))) {
		  char *s;
		  while (*cp == ' ' || *cp == '\t') ++cp;
		  if (*cp == '<')
		    s = rfc821_path(cp,cfi); /* with < > */
		  else
		    s = rfc821_path2(cp,cfi); /* Without < > */
		  if (s == cp) {
		    type821err(501,buf,"Path data: %s",rfc821_error);
		    break;
		  }
		  while (*s == ' ' || *s == '\t') ++s;
		  if (*s != 0) {
		    type(501, "Growl! Extra junk after the EXPN argument!");
		    break;
		  }
		  if ((s = router(RKEY_EXPAND, 0, cp)) != NULL) {
		    printf("%s\r\n", s);
		    free(s);
		  } else
		    type(501, "Unable to verify that address");
		} else
		  type(502, (char *)NULL);
		fflush(stdout);
		break;
	    case Turnme:
		smtp_turnme(carp->verb,cp);
		fflush(stdout);
		break;
	    case Turn:
		type(502, (char *)NULL);
		fflush(stdout);
		break;
	    case NoOp:
		type(250, (char *)NULL);
		fflush(stdout);
		break;
	    case Verbose:
		type(-250, VerbID, Version);
		type(-250, Copyright);
		type(250, Copyright2);
		fflush(stdout);
		VerboseCommand = 1;
		break;
	    case DebugMode:
		debug_report(VerboseCommand,rhostname,buf);
		fflush(stdout);
		break;
	    case Tick:
		type(250, "%s", buf);
		fflush(stdout);
		with_protocol = WITH_BSMTP;
		break;
	    case Quit:
		if (mfp != NULL)
		  mail_abort(mfp);
		type(221, (char *)NULL, "Out");
		fflush(stdout);
		return;
	    default:
		break;
	  }
	}
	if (mfp != NULL)
	  mail_abort(mfp);
	reporterr(lseek(0,0,SEEK_CUR), "session terminated");
}

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

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

static 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 (CISTREQ(carp->verb, query))
			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 BData:
		TYPE_("BDAT nnn [LAST]");
		TYPE_("\tESMTP \"CHUNKING\" service extension; See RFC 1830");
		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;
		}
		type(214,"The current state is: %s.", cp);
		break;
	case Send:
	case Send2:
	case SendOrMail:
	case SendAndMail:
	case Turn:
		TYPE_(carp->verb);
		TYPE ("\tThis command will never be implemented.");
		break;
	case Turnme:
		type(-214,"%s hostname", carp->verb);
		TYPE_("\tThis command schedules (at least tries to) all");
		TYPE_("\toutbound traffic to ``hostname'' host.");
		TYPE_("\tFor security reasons this server will initiate the");
		TYPE ("\tSMTP-transport towards relay/recipient SMTP-server.");
		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("214-The following commands are recognized:");
		if (logfp)
		  fprintf(logfp,"%dw\t214-The following commands are recognized:",pid);
		col = 100;
		for (carp = &command_list[0]; carp->verb != NULL; ++carp) {
			if (col > 70) {
				col = 12;
				printf("\r\n214-\t%s", carp->verb);
				if (logfp)
				  fprintf(logfp, "\n%dw\t214\t%s", pid, carp->verb);
			} else {
				printf(", %s", carp->verb);
				if (logfp)
				  fprintf(logfp,", %s", carp->verb);
				col += 6;
			}
		}
		printf("\r\n");
		if (logfp)
		  fprintf(logfp,"\n");
		TYPE_("");
		TYPE_("The normal sequence is: EHLO/HELO (MAIL RCPT+ DATA)+ QUIT.");
		TYPE_("");
		TYPE_("This mailer will always accept 8-bit and binary message data");
		TYPE_("though you are better to use MIME format!");
		TYPE_("");
		type(-214,"For local information contact: postmaster@%s",
		     myhostname);
		type(214,"SMTP server comments and bug reports to: <zmhacks@nic.funet.fi>");
		break;
	}
	fflush(stdout);
	if (logfp)
	  fflush(logfp);
}

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

static 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 */
static 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	HAVE_VPRINTF
	vsprintf(buf+strlen(buf), cp, ap);
#else	/* !HAVE_VPRINTF */
	sprintf(buf+strlen(buf), cp, va_arg(ap, char *));
#endif	/* HAVE_VPRINTF */
	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, c;
	register char *cp;
	char *s;
	static char *obuf = NULL;
	
	if (buf != NULL)
	  free(buf);
	if (*flagp) {
	  *flagp = 0;
	  return NULL;
	}

	bufsize = 16;
	cp = buf = emalloc(bufsize);
	while ((c = getc(fp)) != EOF) {
	  *cp = c;
	  if (c == '\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 + (cp - buf);
	    buf = s;
	  }
	}
	if (c == 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 */
	  pid = getpid();
	  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;
}

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

static char *
router(function, holdlast, args)
	char *function, *args;
	int holdlast;
{
	char *buf, *holdcp;
	int sawend;

	if (args == NULL) {
	  type(501, (char *)NULL);
	  return NULL;
	}

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

	fprintf(tofp, "%s %s \"", ROUTER_SERVER, function);

	/* Process all double-quotes and backslashes so that
	   no surprises happen.. */

	for ( ; *args ; ++args ) {
	  if (*args == '\\' || *args == '"')
	    putc('\\', tofp);
	  putc(*args, tofp);
	}
	fprintf(tofp, "\"\n");
	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, s6)
	int code;
	char *fmt, *s1, *s2, *s3, *s4, *s5, *s6;
{
	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, s6);
	else
		printf(text, s1, s2, s3, s4, s5, s6);
	printf("\r\n");
	if (logfp != NULL) {
		if (fmt != NULL)
			fprintf(logfp, fmt,  s1, s2, s3, s4, s5, s6);
		else
			fprintf(logfp, text, s1, s2, s3, s4, s5, s6);
		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 < 0 ? -code : 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);
}

static void header_to_mime(buf, lenptr, maxlen)
	char *buf;
	int *lenptr;
	int maxlen;
{
	/* XXX: HEADERS -> MIME-2 */
}

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

static int /* count of bytes */
mvdata(out,msg)
	register FILE *out;
	char *msg;
{
	register int c, state, *sts, endstate, cnt;
	register char *idxnum;
	int timercnt = 1024;

#ifdef NO_INCOMING_HEADER_PROCESSING
	idxnum = indexnum+1;

	state = 10;
	endstate = X_;
	sts = states;
	cnt = 0;
#else
	char linebuf[4000], *s, *eol;
	int col;
	int insubject = 0;
	int has8bit = 0; /* In headers */
	int has8bitsum = 0;
	int from__err = 0;
	int linecnt = 0;
	int was8bit = 0;

	fflush(stdout);

	idxnum = indexnum+1;

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

	alarm(SMTP_DATA_TIME_PER_LINE);

	/* ================ Input the email headers ================ */
	/*	     and analyze them a bit (Precedence:)	     */
	/*	  ... that only "Subject:" has 8-bit chars ...	     */
	mail_priority = _MAILPRIO_NORMAL;
	for (;;) {
		c = s_getc();
		/* An EOF in here is an error! */
#if EOF != -1
		if (c == EOF) return EOF;
#else
		if (c < 0) return EOF;
#endif
		if (--timercnt <= 0) {
		  /* Re-arm for every kilobyte */
		  alarm(SMTP_DATA_TIME_PER_LINE);
		  timercnt = 1024;
		}
		++cnt;
		if ((state = sts[state+idxnum[c]]) & ~0xff) {
		  if (state & O_) {
		    if (state == endstate) {
		      if (col > 0) {
			if (has8bit)
			  header_to_mime(linebuf,&col,sizeof(linebuf));
			fwrite(linebuf,1,col,out);
		      }
		      return cnt;
		    }
		    state = (char)state;
		    continue;
		  }
		  if (col < (sizeof(linebuf)-1))
		    linebuf[col++] = (state >> 8);
		  state = (char)state;
		}
		if (0x80 & c)
		  has8bit = 1;

		if (col < (sizeof(linebuf)-1))
		  linebuf[col++] = c;

		/* LF, or something else ?  Here the else.. */
		if (c != '\n')
		  continue;

		if (col < sizeof(linebuf))
		  linebuf[col] = 0;

		/* We have a string - header line - in ``col'' first
		   char positions of the ``linebuf'' array  */

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

		/* See if the line is all white-space: */
		for (s = linebuf; s < eol; ++s)
		  if (*s != ' ' && *s != '\t' &&
		      *s != '\r' && *s != '\n')
		    break;
		if (s == eol) {	/* All-blank line */
		  if (col > 0)
		    fwrite(linebuf,1,col,out);
		  break;	/* Into the body processing */
		}
		++linecnt;
		if (*linebuf == ' ' || *linebuf == '\t') {
		  if (has8bit)
		    header_to_mime(linebuf,&col,sizeof(linebuf));
		  has8bit = 0;
		  if (col > 0)
		    fwrite(linebuf,1,col,out);
		  col = 0;
		  continue;	/* continuation line.. */
		}

		/* ================ PROCESS THE HEADERS! ================ */
		if (CISTREQN(linebuf,"Subject:",8)) {
		  insubject = 1;
		} else {
		  if (!insubject) {
#if 0
		    if (has8bit)
		      sprintf(msg,"Header line \"%s\" contains illegal 8-bit chars", linebuf);
#endif
		    has8bitsum += has8bit;
		  }
		}
		if (linecnt == 1 && (strncmp("From ",linebuf,5)==0 ||
				     strncmp(">From ",linebuf,6)==0)) {
#if 0
		  from__err = 1;
		  sprintf(msg,"Message starts with illegal \"%s\" line",linebuf);
#endif
		  /* DO NOT WRITE THIS LINE OUT! */
		  col = 0;
		  has8bit = 0;
		  continue;
		}
		if (has8bit)
		  header_to_mime(linebuf,&col,sizeof(linebuf));
		has8bit = 0;
		/* Write the line out */
		if (col > 0)
		  fwrite(linebuf,1,col,out);
		col = 0;

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

	/* ================ Normal email BODY input.. ================ */
	for (;;) {
		c = s_getc();
#if EOF != -1
		if (c == EOF)		/* a little slower... */
			break;
#endif
		
		if (--timercnt <= 0) {
			/* Re-arm for every kilobyte */
			alarm(SMTP_DATA_TIME_PER_LINE);
			timercnt = 1024;
		}
		++cnt;
		if ((state = sts[state+idxnum[c]]) & ~0xff) {
			if (state & O_) {
				if (state == endstate)
					break;
				state = (char)state;
				continue;
			}
			if (!ferror(out))
			  putc((state>>8), out);
			state = (char)state;
		}
		if (!ferror(out))
			putc(c, out);
	}
	fflush(stdout);
	return cnt;
}

/*
 *  BDAT -- For ESMTP CHUNKING extension (rfc 1830)
 */
static int /* count of bytes */
mvbdata(out,msg, incount, statep)
	register FILE *out;
	char *msg;
	register long incount;
	int *statep;
{
	register int c, state, cnt;
	int timercnt = 1024;

	state = *statep;
	cnt = 0;

	/* XX: header processing REMOVED from BDAT processing */

	/* ================ Normal email BODY input.. ================ */
	for ( ; incount > 0; --incount) {
		c = s_getc();
#if EOF != -1
		if (c == EOF)		/* a little slower... */
			break;
#endif
		if (--timercnt <= 0) {
			/* Re-arm for every kilobyte */
			alarm(SMTP_DATA_TIME_PER_LINE);
			timercnt = 1024;
		}
		++cnt;
		/* Canonize CR+LF --> LF (UNIX style) */
		if (c == '\r') { /* Suspend sending, this is our 'state' */
		  /* do nothing, 'state = c' is done after this if-else */
		} else if (state == '\r') {
		  if (c != '\n') {
		    /* Suspended lone CR */
		    if (out) { /* We just discard it, if no output stream */
		      if (!ferror(out))
			putc(state,out);
		      if (!ferror(out))
			putc(c,out);
		    }
		  } else {
		    /* CR + LF -- forget the CR */
		    if (out)
		      if (!ferror(out))
			putc(c,out);
		  }
		} else {
		  /* Anything else, just output it! */
		  if (out)
		    if (!ferror(out))
		      putc(c,out);
		}
		state = c;
	}
	*statep = state;
	return cnt;
}

static void
setrfc1413ident(sd)
unsigned int sd;
{
    char *cp;
    struct in_addr	ident_inlocal;
    struct in_addr	ident_inremote;
    unsigned short	ident_local;
    unsigned short	ident_remote;

    strncpy(ident_username, "UNKNOWN", MAXHOSTNAMELEN);
    if (ident_fd2(sd, &ident_inlocal, &ident_inremote,
		 &ident_local, &ident_remote) == -1) {
	    return;
    } else {
	    cp = ident_tcpuser3(&ident_inlocal, &ident_inremote,
			       ident_local,   ident_remote,
			       IDENT_TIMEOUT);
	    if ( cp != (char *) NULL ) {
		    strncpy(ident_username, cp, MAXHOSTNAMELEN);
	    }
    }
}

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