/*
 *	Copyright 1988 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 *	Copyright 1991-1994 by Matti Aarnio -- modifications, including MIME
 */

#define	RFC974		/* If BIND, check that TCP SMTP service is enabled */

#define	TIMEOUT		(10*60)		/* timeout in seconds, per exchange */

#include <stdio.h>
#include "hostenv.h"
#include <ctype.h>
#include <errno.h>
#include <pwd.h>
#include "zmsignal.h"
#include <sysexits.h>
/* #include <strings.h> */ /* poorly portable.. */
#include <varargs.h>
#include <fcntl.h>
#include <sys/file.h>
#include <sys/param.h>
#include <sys/stat.h>
#include NDIR_H

#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "mail.h"
#include "ta.h"
#include "malloc.h"

#ifndef	USE_SETREUID
#ifdef	USE_SETEUID
#define	setreuid(x,y)	seteuid(y)
#else
#define	setreuid(x,y)	setuid(y)
#endif	/* !USE_SETEUID */
#endif	/* !USE_SETREUID */

#if	defined(TRY_AGAIN) && defined(USE_RESOLV)
#define	BIND		/* Want BIND (named) nameserver support enabled */
#endif	/* TRY_AGAIN */
#ifdef	BIND
#undef NOERROR				/* pinhead SunOS 5.1 */
#include <arpa/nameser.h>
#include <resolv.h>

#ifndef	BIND_VER
#ifdef	GETLONG
/* 4.7.3 introduced the {GET,PUT}{LONG,SHORT} macros in nameser.h */
#define	BIND_VER	473
#else	/* !GETLONG */
#define	BIND_VER	472
#endif	/* GETLONG */
#endif	/* !BIND_VER */
#endif	/* BIND */

#if	defined(BIND_VER) && (BIND_VER >= 473)
typedef u_char msgdata;
#else	/* !defined(BIND_VER) || (BIND_VER < 473) */
typedef char msgdata;
#endif	/* defined(BIND_VER) && (BIND_VER >= 473) */

#ifdef	__alpha	/* This is ugly kludge due to the nature of the IP
		   addresses.. They are 32 bit entities, not 64 bit! */
# define  u_long u_int
#endif

/* Define all those things which exist on newer BINDs, and which may
   get returned to us, when we make a query with  T_ANY ... */

#ifndef	T_TXT
# define T_TXT 16	/* Text strings */
#endif
#ifndef T_RP
# define T_RP 17	/* Responsible person */
#endif
#ifndef T_AFSDB
# define T_AFSDB 18	/* AFS cell database */
#endif
#ifndef T_X25
# define T_X25 19	/* X.25 calling address */
#endif
#ifndef T_ISDN
# define T_ISDN 20	/* ISDN calling address */
#endif
#ifndef T_RT
# define T_RT 21	/* router */
#endif
#ifndef T_NSAP
# define T_NSAP 22	/* NSAP address */
#endif
#ifndef T_NSAP_PTR
# define T_NSAP_PTR 23	/* reverse NSAP lookup (depreciated) */
#endif
#ifndef	T_UINFO
# define T_UINFO 100
#endif
#ifndef T_UID
# define T_UID 101
#endif
#ifndef T_GID
# define T_GID 102
#endif
#ifndef T_UNSPEC
# define T_UNSPEC 103
#endif
#ifndef T_SA
# define T_SA 200		/* Shuffle addresses */
#endif



#ifndef	SEEK_SET
#define	SEEK_SET	0
#endif	/* SEEK_SET */
#ifndef SEEK_CUR
#define SEEK_CUR   1
#endif
#ifndef SEEK_XTND
#define SEEK_XTND  2
#endif

#ifndef	IPPORT_SMTP
#define	IPPORT_SMTP	25
#endif 	/* IPPORT_SMTP */

#define	PROGNAME	"smtpclient"	/* for logging */
#define	CHANNEL		"smtp"	/* the default channel name we deliver for */

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

#define MAXFORWARDERS	128	/* Max number of MX rr's that can be listed */

char myhostname[MAXHOSTNAMELEN+1];
char remotehost[MAXHOSTNAMELEN+1];
char remotemsg[2*BUFSIZ];
char ipaddress[30];
char *progname;
char *cmdline, *eocmdline, *logfile, *msgfile;
char *myaddrs = NULL;
int nmyaddrs = 0;
int pid;
int debug = 0;
int conndebug = 0;
int timeout = 0;		/* how long do we wait for response? (sec.) */
int gotalarm =0;		/* indicate that alarm happened! */
#ifndef	USE_MMAP
int readalready = 0;		/* does buffer contain valid message data? */
#endif
int wantreserved = 0;		/* open connection on secure (reserved) port */
int statusreport = 0;		/* put status reports on the command line */
int noMX = 0;			/* do not use MX records to deliver mail */
int firstmx = 0;		/* [Matti Aarnio] error in smtpwrite("HELO".. */
int mxcount = 0;
int force_8bit = 0;		/* Claim to the remote to be 8-bit system, even
				   when it doesn't report itself as such..*/
int force_7bit = 0;		/* and reverse the previous.. */
int checkwks = 0;
FILE *logfp;
extern int nobody;
int daemon_uid = -1;
int first_uid = 0;		/* Make the opening connect with the UID of the
				   sender (atoi(rp->addr->misc)), unless it is
				   "nobody", in which case use "daemon"      */

int D_alloc = 0;		/* Memory usage debug */

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

/* Extended SMTP flags -- can downgrade from 8-bit to 7-bit while in transport
   IF  MIME-Version: is present, AND Content-Transfer-Encoding: 8BIT
   For selected "force_8bit" remotes can also DECODE Q-P MIME MSGS! */
/* If there is header:  Content-Conversion: prohibited
   DO NOT do conversions no matter what
   (even when it violates the protocol..) */

/* Following options can be declared in ESMTP  EHLO responce  */
#define ESMTP_SIZEOPT  0x0001
#define ESMTP_8BITMIME 0x0002
#define ESMTP_DSN      0x0004

int  ehlo_capabilities = 0;
long ehlo_sizeval      = 0;
char esmtp_on_banner = -1;


extern int errno;
extern void warning(), report();
#ifndef MALLOC_TRACE
extern univptr_t emalloc();
#endif
extern char *strerror();
extern char *strchr();
extern char *strrchr();
extern char *dottedquad();
extern char *optarg;
extern int optind;
extern int deliver __((struct ctldesc *dp, FILE *smtpfp[2], FILE *verboselog, struct rcpt *startrp, struct rcpt *endrp));
extern int writebuf __((FILE *fp, char *buf, int len));
extern int writemimeline __((FILE *fp, char *buf, int len, int convertmode, int*lastchp));
extern int appendlet __((struct ctldesc *dp, FILE *fp, FILE *verboselog, int hsize, int msize, int convertmode));
extern int smtpopen __((char *host, FILE *fparr[], FILE *verboselog));
extern int smtpconn __((char *host, FILE *fparr[], FILE *verboselog));
extern int smtp_ehlo __((FILE *smtpfp[], FILE *verboselog, char *strbuf));
extern int ehlo_check __((char *buf));
extern int smtpwrite __((FILE *smtpfp[2], FILE *verboselog, int saverpt, char *linebuf));
extern int emptyline(), process();
extern void diagnostic();
extern void notaryreport __((char *rcpt, char *acct, char *status));
extern void notarystatsave __((char *smtpstatline));
extern char *notaryacct __((int rc, char *ok_string));

extern int  cte_check __((struct rcpt *rp));
extern void downgrade_headers __((struct rcpt *rp, int convertmode, FILE *verboselog));
extern int check_7bit_cleanness __((struct ctldesc *dp));

extern void prversion();
extern void getnobody();
extern int makeconn();
extern int vcsetup();
extern void hp_init();
extern char **hp_getaddr(), **hp_nextaddr();
extern void hp_setalist();
#ifdef	BIND
extern int rightmx();
extern int h_errno;
extern int res_mkquery(), res_send(), dn_skipname(), dn_expand();
# ifdef RFC974
extern int getrr(), getmxrr();
extern int getrrtype();
# endif /* RFC974 */
#endif	/* BIND */
extern int matchroutermxes();
extern SIGNAL_TYPE sig_pipe();
extern SIGNAL_TYPE sig_alarm();
extern int getmyhostname();
extern void stashmyaddresses();
extern int cistrcmp(), cistrncmp();
extern void getdaemon();

FILE *verboselog = NULL;
int smtp_isopen = 0;

int
main(argc, argv)
	int argc;
	char *argv[];
{
	char file[MAXPATHLEN+1];
	char *channel, *host;
	int i, fd, errflg, c, smtpstatus;
	FILE *smtpfp[2];
	struct ctldesc *dp;
#ifdef	BIND
	int checkmx = 0;	/* check all destination hosts for MXness */
#endif	/* BIND */
	SIGNAL_TYPE (*oldsig)();

	smtpfp[0] = NULL;
	smtpfp[1] = NULL;

	pid = getpid();
	ipaddress[0] = '\0';
	msgfile = "?";
	getout = 0;
	cmdline = &argv[0][0];
	eocmdline = cmdline;
	for (i = 0; i < argc; ++i)
	  eocmdline += strlen(argv[i]) + 1;

	SIGNAL_HANDLESAVE(SIGINT, SIG_IGN, oldsig);
	if (oldsig != SIG_IGN)
	  SIGNAL_HANDLE(SIGINT, wantout);
	SIGNAL_HANDLESAVE(SIGTERM, SIG_IGN, oldsig);
	if (oldsig != SIG_IGN)
	  SIGNAL_HANDLE(SIGTERM, wantout);
	SIGNAL_HANDLESAVE(SIGHUP, SIG_IGN, oldsig);
	if (oldsig != SIG_IGN)
	  SIGNAL_HANDLE(SIGHUP, wantout);

#if 0
	SIGNAL_HANDLE(SIGPIPE, sig_pipe);
#else
	SIGNAL_IGNORE(SIGPIPE);
#endif
	SIGNAL_HANDLE(SIGALRM, sig_alarm);
	timeout = TIMEOUT;

	progname = PROGNAME;
	errflg = 0;
	channel = CHANNEL;
	wantreserved = debug = statusreport = 0;
	logfile = NULL;
	myhostname[0] = '\0';
	remotemsg[0] = '\0';
	remotehost[0] = '\0';
	while ((c = getopt(argc, argv, "c:deh:l:rsxDE:T:VW78")) != EOF) {
	  switch (c) {
	  case 'c':		/* specify channel scanned for */
	    channel = emalloc(strlen(optarg)+1);
	    strcpy(channel, optarg);
	    break;
	  case 'd':		/* turn on debugging output */
	    debug = 1;
	    break;
	  case 'e':		/* expensive MX checking for *all* addresses */
#ifdef	BIND
	    checkmx = 1;
#else  /* !BIND */
	    ++errflg;
	    fprintf(stderr,
		    "%s: -e unavailable, no nameserver support!\n",
		    progname);
#endif /* BIND */
	    break;
	  case 'h':		/* my hostname */
	    strcpy(myhostname, optarg);
	    break;
	  case 'l':		/* log file */
	    logfile = emalloc(strlen(optarg)+1);
	    strcpy(logfile, optarg);
	    break;
	  case 'r':		/* use reserved port for SMTP connection */
	    wantreserved = 1;
	    break;
	  case 's':		/* report status to command line */
	    statusreport = 1;
	    break;
	  case 'x':		/* don't use MX records lookups */
	    noMX = 1;
	    break;
	  case 'D':		/* only try connecting to remote host */
	    conndebug = 1;
	    break;
	  case 'E':		/* don't do EHLO, unless target system
				   has "ESMTP" on its banner */
	    esmtp_on_banner = 0;
	    break;
	  case 'T':		/* specify Timeout in seconds */
	    if ((timeout = atoi(optarg)) < 5) {
	      fprintf(stderr,
		      "%s: illegal timeout: %d\n",
		      argv[0], timeout);
	      ++errflg;
	    }
	    break;
	  case 'V':
	    prversion("smtp");
	    exit(0);
	    break;
	  case 'W':		/* Enable RFC974 WKS checks */
	    checkwks = 1;
	    break;
	  case '8':
	    force_8bit = 1;
	    force_7bit = 0;
	    break;
	  case '7':
	    force_7bit = 1;
	    force_8bit = 0;
	  default:
	    ++errflg;
	    break;
	  }
	}
	if (errflg || optind != argc - 1) {
	  fprintf(stderr,
		  "Usage: %s [-c channel] host\n", argv[0]);
	  exit(EX_USAGE);
	}
	host = emalloc(strlen(argv[optind]) + 1);
	strcpy(host, argv[optind]);

	if (myhostname[0] == 0) {
	  /* Default it only when not having an explicite value
	     in it..   James S MacKinnon <jmack@Phys.UAlberta.Ca> */
	  getmyhostname(myhostname, sizeof myhostname);
	  stashmyaddresses(myhostname);
	}

	if (conndebug && !debug) {
	  firstmx = 0;
	  smtpconn(host, smtpfp, verboselog);
	  exit(0);
	}

	if (logfile != NULL) {
	  if ((fd = open(logfile, O_CREAT|O_APPEND|O_WRONLY, 0644)) < 0)
	    fprintf(stderr,
		    "%s: cannot open logfile \"%s\"!\n",
		    argv[0], logfile);
	  else
	    logfp = (FILE *)fdopen(fd, "a");
	} else
		logfp = NULL;

	getnobody();
	getdaemon();


	/* defer opening a connection until we know there is work */
	smtpstatus = EX_DATAERR;
	smtp_isopen = 0;

	while (!getout && fgets(file, sizeof file, stdin)) {
	  if (emptyline(file, sizeof file))
	    break;

	  /* if(logfp) fprintf(logfp,
	     "%d#\t(fdcnt=%d, file:%s)\n",pid,countfds(),file); */

#ifdef	BIND
	  if (checkmx)
	    dp = ctlopen(file, channel, host, &getout, rightmx, matchroutermxes);
	  else
#endif /* BIND */
	    dp = ctlopen(file, channel, host, &getout, NULL, matchroutermxes);
	  if (dp == NULL)
	    continue;
	  if (verboselog) {
	    fclose(verboselog);
	    verboselog = NULL;
	  }
	  if (dp->verbose) {
	    verboselog = (FILE *)fopen(dp->verbose,"a");
	    if (verboselog)
	      setbuf(verboselog,NULL);
	  }
	  if (!smtp_isopen) {
	    if ((first_uid = atoi(dp->senders->misc)) < 0 ||
		first_uid == nobody)
	      first_uid = daemon_uid;
	    smtpstatus = smtpopen(host, smtpfp, verboselog);
	    if (smtpstatus == EX_OK)
	      smtp_isopen = 1;
	  }

	  smtpstatus = process(dp, smtpstatus, host, smtpfp, verboselog);
	  ctlclose(dp);
	}
	if (smtpstatus == EX_OK) {
	  smtpstatus = smtpwrite(smtpfp, verboselog, 0, "QUIT");
	  fclose(smtpfp[0]);
	  fclose(smtpfp[1]);
	  smtp_isopen = 0;
	}
	if (verboselog != NULL)
		fclose(verboselog);
	if (logfp != NULL)
		fclose(logfp);
	if (smtpstatus = EX_DATAERR)
		smtpstatus = EX_OK; /* Nothing processed ?? */
	exit(smtpstatus);
	/* NOTREACHED */
	return smtpstatus;
}

int
process(dp, smtpstatus, host, smtpfp, verboselog)
	struct ctldesc *dp;
	int smtpstatus;
	FILE *smtpfp[], *verboselog;
	char *host;
{
	struct rcpt *rp, *rphead;
	int loggedid;

#ifndef	USE_MMAP
	readalready = 0; /* ignore any previous message data cache */
#endif
	loggedid = 0;

	for (rp = rphead = dp->recipients; rp != NULL; rp = rp->next) {
	  if (rp->next == NULL
	      || rp->addr->link != rp->next->addr->link
	      || rp->newmsgheader != rp->next->newmsgheader) {
	    if (smtpstatus == EX_OK) {
	      if (logfp != NULL && !loggedid) {
		loggedid = 1;
		fprintf(logfp,
			"%d#\t%s: %s\n",
			pid, dp->msgfile,
			dp->logident);
		fflush(logfp);
	      }
	      if (!smtp_isopen) {
		if ((first_uid = atoi(dp->senders->misc)) < 0 ||
		    first_uid == nobody)
		  first_uid = daemon_uid;
		smtpstatus = smtpopen(host, smtpfp, verboselog);
		if (smtpstatus == EX_OK)
		  smtp_isopen = 1;
	      }
	      smtpstatus = deliver(dp, smtpfp, verboselog, rphead, rp->next);
	      if (smtpstatus != EX_OK) {
		fclose(smtpfp[0]); smtpfp[0] = NULL;
		fclose(smtpfp[1]); smtpfp[1] = NULL;
		smtp_isopen = 0;
	      }
	      rphead = rp->next;
	    } else {
	      while (rphead != rp->next) {
		notaryreport(rp->addr->user,"failed",NULL);
		diagnostic(rphead, smtpstatus, "%s", remotemsg);
		rphead = rphead->next;
	      }
	    }
	  }
	}
	return smtpstatus;
}
/*
 * deliver - deliver the letter in to user's mail box.  Return
 *	     errors and requests for further processing in the structure
 */

int
deliver(dp, smtpfp, verboselog, startrp, endrp)
	struct ctldesc *dp;
	FILE *smtpfp[2], *verboselog;
	struct rcpt *startrp, *endrp;
{
	struct rcpt *rp = NULL;
	int r, nrcpt, hsize, size, tout;
	int content_kind, convertmode;
	int ascii_clean = 0;
	struct stat stbuf;
	char SMTPbuf[2000];
	char *s;
	int conv_prohibit = check_conv_prohibit(startrp);

	if (conv_prohibit) {
	  convertmode = _CONVERT_NONE;
	} else {

	  /* Content-Transfer-Encoding: 8BIT ? */
	  content_kind = cte_check(startrp);

	  /* If the header says '8BIT' and ISO-8859-* something,
	     but body is plain 7-bit, turn it to '7BIT', and US-ASCII */
	  ascii_clean = check_7bit_cleanness(dp);
	  if (ascii_clean && content_kind == 8) {
	    if (downgrade_charset(startrp, verboselog))
	      content_kind = 7;
	  }

	  if (force_7bit)	/* Mark off the 8BIT MIME capability.. */
	    ehlo_capabilities &= ~ESMTP_8BITMIME;

	  convertmode = _CONVERT_NONE;
	  switch (content_kind) {
	  case 0:		/* Not MIME */
	    if ((ehlo_capabilities & ESMTP_8BITMIME) == 0 &&
		!ascii_clean) {
	      convertmode = _CONVERT_UNKNOWN;
	      downgrade_headers(startrp, convertmode, verboselog);
	    }
	    break;
	  case 2:		/* MIME, but no C-T-E: -> defaults to 7BIT */
	  case 1:		/* C-T-E: BASE64  ?? */
	  case 7:		/* C-T-E: 7BIT */
	    convertmode = _CONVERT_NONE;
	    break;
	  case 8:		/* C-T-E: 8BIT */
	    if ((force_7bit || (ehlo_capabilities & ESMTP_8BITMIME) == 0) &&
		!ascii_clean && !force_8bit) {
	      convertmode = _CONVERT_QP;
	      downgrade_headers(startrp, convertmode, verboselog);
	    }
	    break;
	  case 9:		/* C-T-E: Quoted-Printable */
	    if (force_8bit || (ehlo_capabilities & ESMTP_8BITMIME)) {
	      /* Force(d) to decode Q-P while transfer.. */
	      convertmode = _CONVERT_8BIT;
	      /*  UPGRADE TO 8BIT !  */
	      qp_to_8bit(startrp);
	      content_kind = 10;
	      ascii_clean = 0;
	    }
	    break;
	  default:
	    /* ???? This should NOT happen! */
	    break;
	  } /* switch().. */
	}

	sprintf(SMTPbuf, "MAIL From:<%s>", startrp->addr->link->user);
	if (ehlo_capabilities & ESMTP_8BITMIME)
	  strcat(SMTPbuf, " BODY=8BITMIME");
	s = SMTPbuf + strlen(SMTPbuf);

	/* Size estimate is calculated in the  ctlopen()  by
	   adding msg-body size to the largest known header size,
	   though excluding possible header and body rewrites.. */
	if (ehlo_capabilities & ESMTP_SIZEOPT) {
	  sprintf(s," SIZE=%d",startrp->desc->msgsizeestimate);
	  s += strlen(s);
	}
	/* DSN parameters ... */
	if ((ehlo_capabilities & ESMTP_DSN) &&
	    startrp->desc->envid != NULL) {
	  sprintf(s," ENVID=%s",startrp->desc->envid);
	  s += strlen(s);
	}

	r = smtpwrite(smtpfp, verboselog, 1, SMTPbuf);
	if (r != EX_OK) {
	  for (rp = startrp; rp != endrp; rp = rp->next) {
	    notaryreport(rp->addr->user,notaryacct(r,"*NOTOK*"),NULL);
	    diagnostic(rp, r, "%s", remotemsg);
	  }
	  smtpwrite(smtpfp, verboselog, 0, "RSET");
	  return r;
	}
	nrcpt = 0;
	for (rp = startrp; rp != endrp; rp = rp->next) {
	  sprintf(SMTPbuf, "RCPT To:<%s>", rp->addr->user);
	  s = SMTPbuf + strlen(SMTPbuf);

	  if (ehlo_capabilities & ESMTP_DSN) {
	    if (rp->dsnflags & _DSN_RET_NO)
	      strcat(s, " RET=NO");
	    else if (rp->dsnflags & _DSN_RET_YES)
	      strcat(s, " RET=YES");

	    if (rp->dsnflags & _DSN_NOTIFY_SUCCESS)
	      strcat(s, " NOTIFY=SUCCESS");
	    else if (rp->dsnflags & _DSN_NOTIFY_FAILURE)
	      strcat(s, " NOTIFY=FAILURE");
	    else if (rp->dsnflags & _DSN_NOTIFY_ALWAYS)
	      strcat(s, " NOTIFY=ALWAYS");
	    else if (rp->dsnflags & _DSN_NOTIFY_NEVER)
	      strcat(s, " NOTIFY=NEVER");

	    s += strlen(s);
	    if (rp->orcpt != NULL) {
	      sprintf(s, " ORCPT=%s", rp->orcpt);
	    }
	  }
	  
	  r = smtpwrite(smtpfp, verboselog, 1, SMTPbuf);
	  if (r != EX_OK) {
	    notaryreport(rp->addr->user, notaryacct(r,"*NOTOK"),NULL);
	    diagnostic(rp, r, "%s", remotemsg);
	  } else {
	    ++nrcpt;
	    rp->status = EX_OK;
	  }
	}
	if (nrcpt == 0) {
	  /* all the RCPT To addresses were rejected, so reset server */
	  smtpwrite(smtpfp, verboselog, 0, "RSET");
	  return EX_UNAVAILABLE;
	}
	if ((r = smtpwrite(smtpfp, verboselog, 1, "DATA")) != EX_OK) {
	  for (rp = startrp; rp != endrp; rp = rp->next)
	    if (rp->status == EX_OK) {
	      notaryreport(rp->addr->user,notaryacct(r,"*NOTOK"),NULL);
	      diagnostic(rp, EX_TEMPFAIL, "%s", remotemsg);
	    }
	  smtpwrite(smtpfp, verboselog, 0, "RSET");
	  return r;
	}
	/* Headers are 7-bit stuff -- says MIME specs */


	if (verboselog) {
	  char **hdrs = *(startrp->newmsgheader);
	  if (*(startrp->newmsgheadercvt) != NULL &&
	      convertmode != _CONVERT_NONE)
	    hdrs = *(startrp->newmsgheadercvt);
	  fprintf(verboselog,
		  "Processed headers:  ContentKind=%d, CvtMode=%d\n------\n",
		  content_kind,convertmode);
	  while (hdrs && *hdrs)
	    fprintf(verboselog,"%s\n",*hdrs++);
	}

	if ((hsize = writeheaders(startrp, smtpfp[1], "\r\n",
				  convertmode, 0)) < 0) {
	  for (rp = startrp; rp != endrp; rp = rp->next)
	    if (rp->status == EX_OK) {
	      notaryreport(rp->addr->user,notaryacct(EX_TEMPFAIL,"*NOTOK*"),
			   "466 (Message write timed out)"); /* XX: FIX THE STATUS? */
	      diagnostic(rp, EX_TEMPFAIL, "%s", "header write error");
	    }
	  if (verboselog)
	    fprintf(verboselog,"Writing headers after DATA failed\n");
	  smtpwrite(smtpfp, verboselog, 0, "RSET");
	  return EX_TEMPFAIL;
	}
	
	/* Append the message body itself */
	if (fstat(dp->msgfd, &stbuf) >= 0)
	  size = hsize + stbuf.st_size - dp->msgbodyoffset;
	else
	  size = -hsize;

	if (lseek(dp->msgfd, dp->msgbodyoffset, SEEK_SET) < 0L)
	  warning("Cannot seek to message body! (%m)", (char *)NULL);

	if ((r = appendlet(dp, smtpfp[1], verboselog,
			   hsize, size, convertmode)) != EX_OK) {
	fail:
	  for (rp = startrp; rp != endrp; rp = rp->next)
	    if (rp->status == EX_OK) {
	      notaryreport(rp->addr->user, notaryacct(r,"*NOTOK*"),
			   "466 (Message write timed out)"); /* XX: FIX THE STATUS? */
	      diagnostic(rp, r, "%s", remotemsg);
	    }
	  /* The failure occurred during processing and was due to an I/O
	   * error.  The safe thing to do is to just abort processing.
	   * Don't send the dot! 2/June/94 edwin@cs.toronto.edu
	   */
	  return;
	}
	/*
	 * This is the one place where we *have* to wait forever because
	 * there is no reliable way of aborting the transaction.
	 * Note that a good and useful approximation to "forever" is one day.
	 * Murphy's Law you know: Connections will hang even when they can't.
	 */
	tout = timeout, timeout = 60*60*24;
	r = smtpwrite(smtpfp, verboselog, 1, ".");
	timeout = tout;
	if (r != EX_OK)
	  goto fail;

	if (logfp != NULL) {
	  if (r != EX_OK)
	    fprintf(logfp, "%d#\t%s\n", pid, remotemsg);
	  else
	    fprintf(logfp, "%d#\t%d bytes, %d in header\n",
		    pid, size, hsize);
	  fflush(logfp);
	}
	for (rp = startrp; rp != endrp; rp = rp->next)
	  if (rp->status == EX_OK) {
	    notaryreport(rp->addr->user,
			 notaryacct(r,"relayed"), /* XXX: NOTARY DRPT ?? */
			 NULL);
	    diagnostic(rp, r, "%s", remotemsg);
	  }
	if (r != EX_OK)
	  smtpwrite(smtpfp, verboselog, 0, "RSET");
	return r;
}

/*
 * appendlet - append letter to file pointed at by fd
 */

#ifndef	USE_MMAP
static char let_buffer[BUFSIZ*8];
#endif

int
appendlet(dp, fp, verboselog, hsize, msize, convertmode)
	struct ctldesc *dp;
	FILE *fp, *verboselog;
	int hsize, msize, convertmode;
{
	/* `convertmode' controls the behaviour of the message conversion:
	     _CONVERT_NONE (0): send as is
	     _CONVERT_QP   (1): Convert 8-bit chars to QUOTED-PRINTABLE
	     _CONVERT_8BIT (2): Convert QP-encoded chars to 8-bit
	     _CONVERT_UNKNOWN (3): Turn message to charset=UNKNOWN-8BIT, Q-P..
	 */

	register int i;
	register int bufferfull;
	int position = dp->msgbodyoffset;
	int lastwasnl;
	int lastch = '\n'; /* WriteMIMELine() can decode-QP and then the
			      "lastwasnl" is no longer valid .. */

	writebuf(fp, (char *)NULL, 0);	/* magic init. */

	alarm(1*60*60); /* Timeout in 1 hour per let_buffer full..
			   BUFSIZ/h is damn slow.. It might not be that
			   slow for some monster-files, but.. */
	gotalarm = 0;
#ifndef	USE_MMAP
	/* can we use cache of message body data */
	if (convertmode == _CONVERT_NONE &&
	    readalready != 0) {
	  if (writebuf(fp, let_buffer, readalready) != readalready) {
	    if (gotalarm) {
	      sprintf(remotemsg,"500 (let_buffer write timeout!  DATA %d/%d [%d%%])",
		      hsize, msize, (hsize*100+msize/2)/msize);
	      /* return EX_IOERR; */
	    }
	    return EX_IOERR;
	  }
	  if (let_buffer[readalready-1] != '\n')
	    writebuf(fp, "\n", 1);
	  if (statusreport)
	    report("DATA %d (100%%)", readalready+hsize);
	  if (fflush(fp) != 0)
	    return EX_IOERR;
	  return EX_OK;
	}
#endif
	/* we are assumed to be positioned properly at start of message body */
	bufferfull = 0;
	lastwasnl = 1;	/* we are guaranteed to have a \n after the header */
	if (convertmode == _CONVERT_NONE) {
#ifdef	USE_MMAP
	  char *let_buffer = dp->let_buffer + dp->msgbodyoffset;
	  i = dp->let_end - dp->let_buffer - dp->msgbodyoffset;
#else /* !USE_MMAP */
	  while (1) {
	    if ((i = read(dp->msgfd, let_buffer, sizeof(let_buffer))) == 0)
	      break;
	    if (i < 0) {
	      strcpy(remotemsg, "500 (Read error from message file!?)");
	      return EX_IOERR;
	    }
#endif /* !USE_MMAP */
	    lastwasnl = (let_buffer[i-1] == '\n');
	    if (writebuf(fp, let_buffer, i) != i) {
	      if (gotalarm) {
		sprintf(remotemsg,"500 (let_buffer write timeout!  DATA %d/%d [%d%%])",
			hsize, msize, (hsize*100+msize/2)/msize);
		return EX_IOERR;
	      }
	      sprintf(remotemsg,
		      "500 (let_buffer write IO-error! [%s] DATA %d/%d [%d%%])",
		      strerror(errno), hsize, msize, (hsize*100+msize/2)/msize);
	      return EX_IOERR;
	    }
	    if (statusreport) {
	      hsize += i;
	      report("DATA %d/%d (%d%%)",
		     hsize, msize, (hsize*100+msize/2)/msize);
	    }
#ifndef USE_MMAP
	    bufferfull++;
	    readalready = i;
	  }
#endif
	} else {
	  /* Various esoteric conversion modes..
	     We are better to feed writemimeline() with LINES
	     instead of blocks of data.. */
#ifndef	USE_MMAP /* Classical way to read in things */
	  char iobuf[BUFSIZ];
	  FILE *mfp = fdopen(dp->msgfd,"r");
	  setvbuf(mfp, iobuf, _IOFBF, sizeof(iobuf));
#define MFPCLOSE i = dup(dp->msgfd); fclose(mfp); dup2(i,dp->msgfd); close(i);
	  bufferfull = 0;
#else /* USE_MMAP */
	  char *s2, *s = dp->let_buffer + dp->msgbodyoffset;

#define MFPCLOSE
#endif

	  writemimeline(fp, (char *)NULL, 0, 0, NULL);

	  /* we are assuming to be positioned properly
	     at the start of the message body */
#ifndef	USE_MMAP
	  readalready = 0;
#endif
	  lastwasnl = 0;
	  for (;;) {
#ifndef USE_MMAP
	    if ((i = cfgets(let_buffer, sizeof(let_buffer), mfp)) == EOF)
	      break;
	    /* It MAY be malformed -- if it has a BUFSIZ*8 length
	       line in it, IT CAN'T BE MIME  :-/		*/
	    lastwasnl = (let_buffer[i-1] == '\n');
#else /* USE_MMAP */
	    char *let_buffer = s, *s2 = s;
	    if (s >= dp->let_end) break; /* EOF */
	    i = 0;
	    while (s2 < dp->let_end && *s2 != '\n')
	      ++s2, ++i;
	    if ((lastwasnl = (*s2 == '\n')))
	      ++s2, ++i;
	    s = s2;
#endif
	    /* XX: Detect multiparts !! */

	    /* Ok, write the line -- decoding QP can alter the "lastwasnl" */
	    if (writemimeline(fp, let_buffer, i, convertmode, &lastch) != i) {
	      sprintf(remotemsg,
		      "500 (let_buffer write IO-error! [%s] DATA %d/%d [%d%%])",
		      strerror(errno), hsize, msize, (hsize*100+msize/2)/msize);
	      MFPCLOSE
	      return EX_IOERR;
	    }
#ifdef USE_MMAP
	    s = s2; /* Advance one linefull.. */
#endif
	  }
#ifndef	USE_MMAP
	  if (i == EOF && !feof(mfp)) {
	    strcpy(remotemsg, "500 (Read error from message file!?)");
	    MFPCLOSE
	    return EX_IOERR;
	  }
	  MFPCLOSE
#endif
	} /* ... end of conversion modes */

	/* we must make sure the last thing we transmit is a CRLF sequence */
	if (!lastwasnl || lastch != '\n')
	  if (writebuf(fp, "\n", 1) != 1) {
	    if (gotalarm) {
	      sprintf(remotemsg,"500 (let_buffer write timeout!  DATA %d/%d [%d%%])",
		      hsize, msize, (hsize*100+msize/2)/msize);
	      return EX_IOERR;
	    }
	    sprintf(remotemsg,
		    "500 (let_buffer write IO-error! [%s] DATA %d/%d [%d%%])",
		    strerror(errno), hsize, msize, (hsize*100+msize/2)/msize);
#ifndef	USE_MMAP
	    if (bufferfull > 1) readalready = 0;
#endif
	    return EX_IOERR;
	  }

#ifndef USE_MMAP
	if (bufferfull > 1)	/* not all in memory, need to reread */
	  readalready = 0;
#endif
	if (fflush(fp) != 0)
	  return EX_IOERR;

	return EX_OK;
}

/*
 * Writebuf() is like write(), except all '\n' are converted to "\r\n"
 * (CRLF), and the sequence "\n." is converted to "\r\n..".
 * writebuf() has a cousin: writemimeline(), which does some more esoteric
 * conversions on flight..
 */
int
writebuf(fp, buf, len)
	FILE *fp;
	char *buf;
	int len;
{
	register char *cp;
	register int n;
	register int state;
	static int save = 0;

	if (buf == NULL) {		/* magic initialization */
	  save = 1;
	  return 0;
	}
	state  = save;
	for (cp = buf, n = len; n > 0; --n, ++cp) {
	  unsigned char c = *(unsigned char *)cp;

	  if (state && c != '\n') {
	    state = 0;
	    if (c == '.') {
	      if (putc(c, fp) == EOF || putc(c, fp) == EOF) {
		notaryreport(NULL,NULL,"500 (body write error, 1)");
		strcpy(remotemsg, "write error 1");
		return EOF;
	      }
	      /* if (verboselog) fprintf(verboselog,".."); */
	    } else {
	      if (putc(c, fp) == EOF) {
		notaryreport(NULL,NULL,"500 (body write error, 2)");
		strcpy(remotemsg, "write error 2");
		return EOF;
	      }
	      /* if (verboselog) putc(c, verboselog); */
	    }
	  } else if (c == '\n') {
	    if (putc('\r', fp) == EOF || putc(c, fp) == EOF) {
	      notaryreport(NULL,NULL,"500 (body write error, 3)");
	      strcpy(remotemsg, "write error 3");
	      return EOF;
	    }
	    /* if (verboselog) putc(c, verboselog); */
	    state = 1;
	  } else {
	    if (putc(c, fp) == EOF) {
	      notaryreport(NULL,NULL,"500 (body write error, 4)");
	      strcpy(remotemsg, "write error 4");
	      return EOF;
	    }
	    /* if (verboselog) putc(c, verboselog); */
	  }
	}
	save = state;
	return len;
}


int
writemimeline(fp, buf, len, convertmode, lastcharp)
	FILE *fp;
	char *buf;
	int len, *lastcharp;
	int convertmode;
{
	register char *cp;
	register int n;
	static int column;
	char *i2h = "0123456789ABCDEF";
	int qp_chrs = 0;
	int qp_val = 0;
	int qp_conv;

	/* `convertmode' controls the behaviour of the message conversion:
	     _CONVERT_NONE (0): send as is
	     _CONVERT_QP   (1): Convert 8-bit chars to QUOTED-PRINTABLE
	     _CONVERT_8BIT (2): Convert QP-encoded chars to 8-bit
	     _CONVERT_UNKNOWN (3): Turn message to charset=UNKNOWN-8BIT, Q-P..
	 */

	if (buf == NULL) {
	  column = -1;
	  return 0;
	}

	qp_conv = (convertmode == _CONVERT_QP ||
		   convertmode == _CONVERT_UNKNOWN);

	if (buf == NULL) {		/* magic initialization */
	  /* No magics here.. we are linemode.. */
	  return 0;
	}
	*lastcharp = -1;
	for (cp = buf, n = len; n > 0; --n, ++cp) {
	  int c = *(unsigned char *)cp;
	  ++column;
	  if (convertmode == _CONVERT_8BIT) {
	    if (c == '=' && qp_chrs == 0) {
	      qp_val = 0;
	      qp_chrs = 2;
	      continue;
	    }
	    if (qp_chrs != 0) {
	      if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
		n = 0;	/* We have the line-end wrapper mode */
		continue; /* It should NEVER be present except at the end
			     of the line, thus we are safe to do this ? */
	      }
	      --column;
	      if ((c >= '0' && c <= '9') ||
		  (c >= 'a' && c <= 'f') ||
		  (c >= 'A' && c <= 'F')) {
		/* The first char was HEX digit, assume the second one
		   to be also, and convert all three (=XX) to be a char
		   of given value.. */
		if (c >= 'a') c -= ('a' - 'A');
		if (c > '9') c -= ('A' - '9' - 1);
		qp_val <<= 4;
		qp_val |= (c & 0x0F);
	      }
	      --qp_chrs;
	      if (qp_chrs == 0)
		c = qp_val;
	      else
		continue;
	    }
	    *lastcharp = c;
	  } else {
	    if (qp_conv && column > 70 && c != '\n') {
	      putc('=',fp);
	      putc('\r',fp);
	      putc('\n',fp);
	      *lastcharp = '\n';
	      /* if (verboselog) fprintf(verboselog,"=\n"); */
	      column = -1;
	    }
	    if (qp_conv &&
		(c == '='  ||  c > 126 ||  (c != '\n' && c < 32))) {

	      putc('=',fp);
	      putc(i2h[(c >> 4) & 15],fp);
	      putc(i2h[(c)      & 15],fp);
	      *lastcharp = i2h[(c) & 15];
	      column += 3;
	      /* if (verboselog) fprintf(verboselog,"=%02X",c); */
	      continue;
	    }
	  }

	  if (column == 0 && c == '.') {
	    if (putc(c, fp) == EOF) {
	      notaryreport(NULL,NULL,"500 (body write error, 1)");
	      strcpy(remotemsg, "write error 1");
	      return EOF;
	    }
	    /* if (verboselog) fprintf(verboselog,".."); */
	  }

	  if (c == '\n') {
	    if (putc('\r', fp) == EOF) {
	      notaryreport(NULL,NULL,"500 (body write error, 3)");
	      strcpy(remotemsg, "write error 3");
	      return EOF;
	    }
	    /* if (verboselog) putc(c, verboselog); */
	    column = -1;
	  }
	  if (putc(c, fp) == EOF) {
	    notaryreport(NULL,NULL,"500 (body write error, 4)");
	    strcpy(remotemsg, "write error 4");
	    return EOF;
	  }
	  *lastcharp = c;
	  /* if (verboselog) putc(c, verboselog); */

	}
	return len;
}


int
ehlo_check(buf)
char *buf;
{
	char *r = strchr(buf,'\r');
	if (r != NULL) *r = 0;
	if (strcmp(buf,"8BITMIME")==0) {
	  ehlo_capabilities |= ESMTP_8BITMIME;
	} else if (strcmp(buf,"XDSN")==0 ||
		   strcmp(buf,"DSN")==0) {
	  ehlo_capabilities |= ESMTP_DSN;
	} else if (strncmp(buf,"SIZE ",5)==0 ||
		   strcmp(buf,"SIZE") == 0) {
	  ehlo_capabilities |= ESMTP_SIZEOPT;
	  ehlo_sizeval = -1;
	  if (buf[5] == ' ')
	    sscanf(buf+5,"%d",&ehlo_sizeval);
	}
	return 0;
}

/* Flag that banner contained "ESMTP" (case insensitive) */
void
esmtp_banner_check(str)
char *str;
{
	char *s = str;
	while (*s) {
	  while (*s && *s != 'e' && *s != 'E') ++s;
	  if (!*s) return;
	  if (cistrncmp(s,"ESMTP",5)==0) {
	    esmtp_on_banner = 1;
	    return;
	  }
	  ++s;
	}
}


int
smtpopen(host, fparr, verboselog)
	char *host;
	FILE *fparr[], *verboselog;
{
	int i;
	char SMTPbuf[1000];

	if (debug) {
	  fprintf(logfp, "%d#\tsmtpopen: connecting to %s\n", pid, host);
	  fflush(logfp);
	}
	firstmx = 0;
	mxcount = 0;
	do {
	  if ((i = smtpconn(host, fparr, verboselog)) != EX_OK)
	    return i;
	  if (esmtp_on_banner) {
	    /* Either it is not tested, or it is explicitely
	       desired to be tested, and was found! */
	    sprintf(SMTPbuf, "EHLO %s", myhostname);
	    i = smtp_ehlo(fparr, verboselog, SMTPbuf);
	    if (i == EX_TEMPFAIL) {
	      /* There are systems, which hang up on us, when we
		 greet them with an "EHLO".. Do here a normal "HELO".. */
	      if ((i = smtpconn(host, fparr, verboselog)) != EX_OK)
		return i;
	      i = EX_TEMPFAIL;
	    }
	  }

	  if (esmtp_on_banner && i == EX_OK ) {
	    if (verboselog)
	      fprintf(verboselog,
		      "  EHLO responce flags = 0x%02x\n",
		      ehlo_capabilities);
	  } else {
	    sprintf(SMTPbuf, "HELO %s", myhostname);
	    i = smtpwrite(fparr, verboselog, 1, SMTPbuf);
	  }
	} while (i == EX_TEMPFAIL && firstmx <= mxcount);
	if (debug) {
	  fprintf(logfp, "%d#\tsmtpopen: status = %d\n", pid, i);
	  fflush(logfp);
	}
	return i;
}

struct mxdata {
	msgdata	*host;
	u_short	pref;
};

int
smtpconn(host, fparr, verboselog)
	char *host;
	FILE *fparr[], *verboselog;
{
	int	i, r, retval, smtpfd;
	u_long	ipaddr;
	struct hostent *hp, hent;
	char	hbuf[MAXHOSTNAMELEN+1], *addrs[2];
	struct mxdata mxh[MAXFORWARDERS];
	int	mxcnt = 0;

	hp = NULL;
#ifdef	BIND
	h_errno = 0;
#endif	/* BIND */

	if (debug) {
	  fprintf(logfp, "%d#\tsmtpconn: host = %s\n", pid, host);
	  fflush(logfp);
	}
	if (verboselog)
	  fprintf(verboselog,"SMTP: Connecting to host: %s\n",host);
	if (*host == '[') {	/* hostname is IP address domain literal */
	  char *cp, *hcp, buf[BUFSIZ];

	  for (cp = buf, hcp = host + 1;
	       isascii(*hcp) && (isdigit(*hcp) || *hcp == '.');
	       ++cp, ++hcp)
	    *cp = *hcp;
	  *cp = '\0';
	  ipaddr = inet_addr(buf);
	  if (((int)ipaddr) == -1) {
	    sprintf(remotemsg, "500 (bad IP address: %s)", host);
	    notaryreport(NULL,NULL,remotemsg);
	    if (verboselog)
	      fprintf(verboselog,"%s\n", remotemsg);
	    return EX_NOHOST;
	  }
	  hp = &hent;
	  /* don't really care about gethostbyaddr() here */
	  strcpy(hbuf,host);
	  hp->h_name = hbuf;
	  hp->h_addrtype = AF_INET;
	  hp->h_aliases = NULL;
	  hp->h_length = sizeof ipaddr;
	  addrs[0] = (char *)&ipaddr;
	  addrs[1] = NULL;
	  hp_setalist(hp, addrs);
	  retval = makeconn(hp, &smtpfd, verboselog, fparr, mxcnt);
	} else {
	  hbuf[0] = '\0';
	  errno = 0;
#if 0  /* ---------- rip off this gethostbyname() sequence -------- */
	  if ((hp = gethostbyname(host)) != NULL) {
	    strcpy(hbuf,hp->h_name); /* Get canonic form of target name */
	    host = hbuf;	/* Save it, we reuse gethostbyname() */
	  } else {		/* Failed somehow, blah! */
	    if (errno == ETIMEDOUT
#ifdef	XBIND
		|| h_errno == TRY_AGAIN
#endif /* BIND */
		) {
	      sprintf(remotemsg,"466 (gethostbyname(%s): try later)",host);
	      notaryreport(NULL,NULL,remotemsg);
	      if (verboselog)
		fprintf(verboselog,"%s\n",remotemsg);
	      return EX_TEMPFAIL;
	    }
#ifdef	BIND
	    /*
	     * Find the canonical name
	     * (needed when target of a CNAME has no other RR's)
	     */
	    strcpy(hbuf, host);
	    if ((i = getrr(hbuf, sizeof hbuf, (u_short)T_CNAME),2) == 1) {
	      host = hbuf;
	    } else if (h_errno == TRY_AGAIN) {
	      sprintf(remotemsg, "466 (Canonicalize, try later: %s)", host);
	      notaryreport(NULL,NULL,remotemsg);
	      if (verboselog)
		fprintf(verboselog,"%s\n",remotemsg);
	      return EX_TEMPFAIL;
	    } /* else [Matti Aarnio says:]
		 it is possible that MXs or As are available
		 even though CNAME lookups fail (see .UK) */
#else  /* !BIND */
	    sprintf(remotemsg, "466 (canonicalize lookup failed on %s)", host);
	    notaryreport(NULL,NULL,remotemsg);
	    if (verboselog)
	      fprintf(verboselog,"%s\n", remotemsg);
	    /* temporary failure since the router knew it */
	    return EX_TEMPFAIL;	/* was EX_NOHOST */
#endif /* BIND */
	  }
#endif /* ---------- rip off this gethostbyname() sequence -------- */

	  mxh[0].host = NULL;	/* use the hostent we got */
#ifdef	BIND
	  /*
	   * Look for MX RR's. If none found, use the hostentry in hp.
	   * Otherwise loop through all the mxhosts doing gethostbyname's.
	   */
	  mxcnt = 0;
	  if (!noMX) {
	    int rc;
	    if (verboselog)
	      fprintf(verboselog," getmxrr(%s)",host);
	    rc = getmxrr(host, mxh, sizeof mxh/sizeof mxh[0],0,&mxcnt);
	    if (verboselog)
	      if (mxcnt == 0)
		fprintf(verboselog, " rc=%d, no MXes (host=%s)\n", rc, host);
	      else
		fprintf(verboselog," rc=%d, mxh[0].host=%s (host=%s)\n",
			rc,mxh[0].host,host);
	    hp = NULL;		/* Ruined HP within getmxrr() */
	    switch (rc) {
	      /* remotemsg is generated within getmxrr */
	    case EX_SOFTWARE:
	    case EX_TEMPFAIL:
	    case EX_UNAVAILABLE:
	      return EX_TEMPFAIL;
	    case EX_NOHOST:
	      return EX_NOHOST;
	    case EX_NOPERM:
	      return EX_NOPERM;
	    }
	  }
#endif /* BIND */
	  if (noMX || mxh[0].host == NULL) {
	    errno = 0;
	    /* Either forbidden MX usage, or does not have MX entries! */
	    if (hp == NULL && (hp = gethostbyname(host)) == NULL) {
	      if (errno == ETIMEDOUT
#ifdef	BIND
		  || h_errno == TRY_AGAIN
#endif /* BIND */
		  ) {
		sprintf(remotemsg,"466 (gethostbyname<%s>: try later)",host);
		notaryreport(NULL,NULL,remotemsg);
		if (verboselog)
		  fprintf(verboselog,"%s\n",remotemsg);
		return EX_TEMPFAIL;
	      }

	      if (noMX)
		sprintf(remotemsg,
			"500 (configuration inconsistency. MX usage forbidden, no address in DNS: '%s', errno=%s)",
			host,strerror(errno));
	      else
		sprintf(remotemsg,
			"500 (nameserver data inconsistency. No MX, no address: '%s', errno=%s)",
			host,strerror(errno));
	      notaryreport(NULL,NULL,remotemsg);
	      if (verboselog)
		fprintf(verboselog,"%s\n",remotemsg);
	      /* it was: EX_UNAVAILABLE, but such blocks retrying, thus
		 current EX_TEMPFAIL, which will cause timeout latter on.. */
	      return EX_TEMPFAIL;
	    }
	    retval = makeconn(hp, &smtpfd, verboselog, fparr, mxcnt);
	  } else {
	    retval = EX_UNAVAILABLE;
	    for (i = 0; mxh[i].host != NULL; ++i) {
	      if ((hp = gethostbyname(mxh[i].host)) == NULL) {
		free(mxh[i].host);
		mxh[i].host = NULL;
		firstmx = i+1;
		continue;
	      }
	      r = makeconn(hp, &smtpfd, verboselog, fparr, mxcnt);
	      if (r == EX_OK) {
		while (mxh[i].host != NULL) {
		  free(mxh[i].host);
		  mxh[i++].host = NULL;
		}
		retval = EX_OK;
		break;
	      } else if (r == EX_TEMPFAIL)
		retval = EX_TEMPFAIL;
	      free(mxh[i].host);
	      mxh[i].host = NULL;
	      firstmx = i+1;
	    }
	  }
	}
	if (retval == EX_OK) {
	  strcpy(remotehost, hp->h_name);
	} else
		strcpy(remotehost, host);
	if (debug) {
	  fprintf(logfp,
		  "%d#\tsmtpconn: retval = %d\n", pid, retval);
	  fflush(logfp);
	}
	return retval;
}

int
makeconn(hp, fdp, verboselog, fparr, mxcnt)
	struct hostent *hp;
	int *fdp;
	FILE *fparr[];
	FILE *verboselog;
	int mxcnt;
{
	int retval;

#ifdef	BIND
#ifdef	RFC974
	char hostbuf[MAXHOSTNAMELEN+1];

	if (checkwks && verboselog)
	  fprintf(verboselog,"  makeconn(): checkwks of host %s\n",hp->h_name);

	strcpy(hostbuf, hp->h_name);
	if (checkwks &&
	    getrr(hostbuf, sizeof hostbuf, (u_short)T_WKS, 2) != 1) {
	  sprintf(remotemsg,"550 (WKS checks: no SMTP capability for host %s)",
		  hostbuf);
	  notaryreport(NULL,NULL,remotemsg);
	  if (verboselog)
	    fprintf(verboselog,"%s\n",remotemsg);
	  return EX_UNAVAILABLE;
	}
#endif	/* RFC974 */
#endif	/* BIND */
	
	retval = EX_UNAVAILABLE;
	for (hp_init(hp); *hp_getaddr() != NULL; hp_nextaddr()) {
	  if (matchmyaddress(*hp_getaddr(),hp->h_length)) {
	    notaryreport(NULL,NULL,"500 (Trying to talk with myself!)");
	    sprintf(remotemsg,"Trying to talk with myself!");
	    break;		/* TEMPFAIL or UNAVAILABLE.. */
	  }
	  switch (vcsetup(*hp_getaddr(), hp->h_length, fdp, hostbuf, verboselog)) {
	  case EX_OK:
	    /* Clean (close) these fds -- they have noted to leak.. */
	    if (fparr[0] != NULL) fclose(fparr[0]);
	    if (fparr[1] != NULL) fclose(fparr[1]);

	    fparr[0] = (FILE*) fdopen(*fdp, "r");
	    fparr[1] = (FILE*) fdopen(*fdp, "w");
	    if (esmtp_on_banner > 0)
	      esmtp_on_banner = 0;
	    retval = smtpwrite(fparr, verboselog, 1, NULL);
	    if (retval != EX_OK)
	      /*
	       * If you want to continue with the next host,
	       * the below should be 'return EX_TEMPFAIL'.
	       */
	      break;		/* try another host address */
	    return EX_OK;
	  case EX_TEMPFAIL:
	    retval = EX_TEMPFAIL;
	    break;
	  }
	}
	return retval;
}

int
vcsetup(ipaddrcp, ipaddrlen, fdp, hostname, verboselog)
	char *ipaddrcp;
	int ipaddrlen, *fdp;
	char *hostname;
	FILE *verboselog;
{
	register int s;
	struct sockaddr_in sad;
	u_short p;
	int errnosave;
	char *se;

	strcpy(ipaddress, dottedquad((struct in_addr *)ipaddrcp));
	if (conndebug)
	  fprintf(stderr, "Trying %s [%s] ... ", hostname, ipaddress);
	/* try grabbing a port */
	sad.sin_family = AF_INET;
	sad.sin_addr.s_addr = 0;
	if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	  se = strerror(errno);
	  sprintf(remotemsg, "500 (Internal error, socket: %s)", se);
	  notaryreport(NULL,NULL,remotemsg);
	  if (conndebug)
	    fprintf(stderr, "%s\n", remotemsg);
	  if (verboselog)
	    fprintf(verboselog,"%s\n",remotemsg);
	  return EX_TEMPFAIL;
	}
	if (wantreserved && getuid() == 0) {
	  for (p = IPPORT_RESERVED-1; p >= IPPORT_RESERVED/2; --p) {
	    sad.sin_port = htons(p);
	    if (bind(s, (struct sockaddr *)&sad, sizeof sad) >= 0)
	      break;
	    if (errno != EADDRINUSE && errno != EADDRNOTAVAIL) {
	      char *s = strerror(errno);
	      sprintf(remotemsg, "500 (Internal error, bind: %s)", s);
	      notaryreport(NULL,NULL,remotemsg);
	      if (verboselog)
		fprintf(verboselog,"%s\n", remotemsg);
	      if (conndebug)
		fprintf(stderr, "%s\n", remotemsg);
	      return EX_UNAVAILABLE;
	    }
	  }
	  if (p < IPPORT_RESERVED/2) {
	    sprintf(remotemsg, "too many busy ports");
	    notaryreport(NULL,NULL,"500 (Internal error, too many busy ports)");
	    if (conndebug)
	      fprintf(stderr, "%s\n", remotemsg);
	    if (verboselog)
	      fprintf(verboselog,"%s\n",remotemsg);
	    return EX_TEMPFAIL;
	  }
	}
	memcpy((char *)&sad.sin_addr, ipaddrcp, ipaddrlen);
	sad.sin_port = htons((u_short)IPPORT_SMTP);
/* setreuid(0,first_uid);
   if(verboselog) fprintf(verboselog,"setreuid: first_uid=%d, ruid=%d, euid=%d\n",first_uid,getuid(),geteuid()); */
	if (connect(s, (struct sockaddr *)&sad, sizeof sad) >= 0) {
	  int on = 1;
/* setreuid(0,0); */
	  *fdp = s;
#if	BSD >= 43
	  setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof on);
#else  /* BSD < 43 */
	  setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, 0, 0);
#endif /* BSD >= 43 */
	  if (conndebug)
	    fprintf(stderr, "connected!\n");
	  return EX_OK;
	}
/* setreuid(0,0); */

	errnosave = errno;
	se = strerror(errno);
	sprintf(remotemsg, "500 (connect to %s [%s]: %s)",
		hostname, ipaddress, se);
	notaryreport(NULL,NULL,remotemsg);

	if (conndebug)
		fprintf(stderr, "%s\n", remotemsg);
	if (verboselog)
		fprintf(verboselog,"%s\n",remotemsg);
	switch (errnosave) {	/* from sendmail... */
	case EISCONN:
	case ETIMEDOUT:
	case EINPROGRESS:
	case EALREADY:
	case EADDRINUSE:
	case EHOSTDOWN:
	case ENETDOWN:
	case ENETRESET:
	case ENOBUFS:
	case ECONNREFUSED:
	case ECONNRESET:
	case EHOSTUNREACH:
	case ENETUNREACH:
	case EPERM:
	/* wonder how Sendmail missed this one... */
	case EINTR:
		close(s);
		return EX_TEMPFAIL;
	}
	close(s);
	return EX_UNAVAILABLE;
}

SIGNAL_TYPE
sig_pipe()
{
	if (logfp != NULL) {
	  fprintf(logfp, "%d#\t*** Received SIGPIPE!\n", pid);
	  fflush(logfp);
	  abort();
	}
}

SIGNAL_TYPE
sig_alarm()
{
	gotalarm = 1;
}

void
rmsgappend(va_alist)
	va_dcl
{
	va_list ap;
	char *s, *arg;
	char *cp = remotemsg + strlen(remotemsg);
	char *cpend = remotemsg + sizeof remotemsg -1;

	va_start(ap);
	s   = va_arg(ap, char *);

	if (!s) s="(NULL)";
	for (; *s != 0; ++s) {
	  if (*s == '%' && *++s == 's') {
	    arg = va_arg(ap, char *);
	    while (*arg && cp < cpend)
	      *cp++ = *arg++;
	  } else
	    if (cp < cpend)
	      *cp++ = *s;
	}
	*cp = 0;
	va_end(ap);
}

int dflag = 0;

int within_ehlo = 0;

/* VARARGS */
int
smtpwrite(smtpfp, verboselog, saverpt, strbuf)
	FILE *smtpfp[2], *verboselog;
	int saverpt;
	char *strbuf;
{
	register char *s, *cp;
	int i, response, r, infd, outfd;
	char *se;
	char buf[8192]; /* XX: Eh... Static buffer.. */
	char ch;

	alarm(timeout);	/* This much total to write and get answer */
	gotalarm = 0;
	/* we don't need to turn alarms off again (yay!) */
	fflush(smtpfp[0]);
	fflush(smtpfp[1]);
	infd = fileno(smtpfp[0]);
	outfd = fileno(smtpfp[1]);

	if (strbuf != NULL) {
	  int len = strlen(strbuf) + 2;

	  memcpy(buf,strbuf,len-2);
	  memcpy(buf+len-2,"\r\n",2);

	  if (verboselog)
	    fwrite(buf, len, 1, verboselog);

	  if ((r = write(outfd, buf, len)) == -1) {
	    if (gotalarm) {
	      strcpy(remotemsg, "Timeout on cmd write");
	      notaryreport(NULL,NULL,"500 (timeout on cmd write)");
	    } else
	      {
		se = strerror(errno);
		sprintf(remotemsg, "500 (write to server error: %s)", se);
		notaryreport(NULL,NULL,remotemsg);
	      }
	    if (verboselog)
	      fprintf(verboselog,"%s\n",remotemsg);
	    return EX_TEMPFAIL;
	  } else if (r != len) {
	    sprintf(remotemsg, "500 (SMTP cmd write failure: Only wrote %d of %d bytes!)", r, len);
	    notaryreport(NULL,NULL,remotemsg);
	    if (verboselog)
	      fprintf(verboselog,"%s\n",remotemsg);
	    return EX_TEMPFAIL;
	  }
	  if (logfp != NULL) {
	    if (dflag) abort();
	    fprintf(logfp, "%dw\t%s\n", pid, strbuf);
	    fflush(logfp);
	    dflag = 1;
	  }
	}
	i = 2;	/* state variable, beginning of new line */

	if (strbuf) {
	  if (strncmp(strbuf,"RSET",4) != 0) /* If RSET, append to previous! */
	    *remotemsg = 0;
	  rmsgappend("\r<<- ");
	  rmsgappend("%s", strbuf);
	} else {
	  strcpy(remotemsg,"\r<<- (null)");
	}

	if (debug) {
	  fprintf(logfp, "%d#\tAttempting to read reply\n",pid);
	  fflush(logfp);
	}
	if (statusreport && strbuf != NULL) {
	  report("%s", strbuf);
	}
	cp = buf;
	do {
	  r = read(infd, cp, sizeof buf - (cp - buf));
	  if (r > 0) {
	    if (verboselog)
	      fwrite(cp,r,1,verboselog);
	    for (s = cp, cp += r; s < cp; ++s) {
	      switch (i) {
	      	/* i == 0 means we're on last line */
	      case 1:		/* looking for \n */
		if (*s != '\n')
		  break;
		*s = '\0';
		if (within_ehlo)
		  ehlo_check(&buf[4]);
		if (!strbuf && !esmtp_on_banner)
		  esmtp_banner_check(&buf[4]);
		if (logfp != NULL) {
		  if (debug)
		    putc('\n',logfp);
		  fprintf(logfp,
			  "%dr\t%s\n", pid, buf);
		  fflush(logfp);
		}

		if (s + 1 < cp)
		  memcpy(buf, s+1, cp-s-1);
		cp = buf + (cp-s-1);
		s = buf;
		--s;		/* incremented in for() stmt */
		/* fall through */
	      case 2:		/* saw \n, 1st char on line */
	      case 3:		/* 2nd char on line */
	      case 4:		/* 3rd char on line */
		if (i == 1
		    || (isascii(*s) && isdigit(*s)))
		  ++i;
		else
		  /* silently look for num. code lines */
		  i = 1;
		break;
	      case 5:		/* 4th char on line */
		i = (*s == '-');
		break;
	      }
	      if (debug) {
		if (i%8 == 0) {
		  if (i > 0)
		    putc('\n',logfp);
		  fprintf(logfp,
			  "%d#\t", pid);
		}
		if (isprint(*s)) 
		  putc(*s, logfp);
		else
		  fprintf(logfp,
			  "\\%03o", *s);
	      }
	    }
	    if (debug) {
	      fprintf(logfp, "\n");
	      fflush(logfp);
	    }
	  } else if (r == -1) {
	    if (gotalarm) {
	      sprintf(remotemsg, "466 (Timeout on %sSMTP responce read, Cmd: %s)",
		      strbuf == NULL ? "initial ":"", strbuf);
	      notaryreport(NULL,NULL,remotemsg);
	    } else
	      {
		se = strerror(errno);
		sprintf(remotemsg, "500 (Error on %sSMTP responce read: %s, Cmd: %s)",
			strbuf == NULL ? "initial ":"", se,
			strbuf == NULL ? "(null)" : strbuf);
		notaryreport(NULL,NULL,remotemsg);
	      }
	    dflag = 0;
	    firstmx = 0;
	    if (verboselog)
	      fprintf(verboselog,"%s\n",remotemsg);
	    return EX_TEMPFAIL;
	  } else {
	    sprintf(remotemsg, "500 (Server hung up on us! Cmd: %s)",strbuf);
	    notaryreport(NULL,NULL,remotemsg);
	    dflag = 0;
	    if (verboselog)
	      fprintf(verboselog,"%s\n",remotemsg);
	    return EX_TEMPFAIL;
	  }
	  /* Exit if the last thing we read was a LF and we're on the
	     last line (in case of multiline response).  This
	     also takes care of the required CRLF termination */
	} while (cp < buf+sizeof buf && !(i == 0 && *(cp-1) == '\n'));

	if (cp >= buf+sizeof buf) {
	  strcpy(remotemsg,"500 (SMTP Response overran input buffer!)");
	  notaryreport(NULL,NULL,remotemsg);
	  dflag = 0;
	  if (verboselog)
	    fprintf(verboselog,"%s\n",remotemsg);
	  return EX_TEMPFAIL;
	}
	*--cp = '\0';	/* kill the LF */
	if (cp - buf < 5) {
	  sprintf(remotemsg, "500 (SMTP response '%s' unexpected!)", buf);
	  notaryreport(NULL,NULL,remotemsg);
	  dflag = 0;
	  if (verboselog)
	    fprintf(verboselog,"%s\n",remotemsg);
	  return EX_TEMPFAIL;
	}
	--cp;
	/* trim trailing whitespace */
	while (isascii(*cp) && isspace(*cp))
	  --cp;
	*++cp = '\0';
	for (i = 0; i < 4; ++i)		/* can't happen, right? wrong... */
	  if (buf[i] == ' ')
	    break;
	if (i == 4) --i;
	ch = buf[i];
	buf[i] = '\0';
	response = atoi(buf);
	if (logfp != NULL) {
	  fprintf(logfp, "%dr\t%s%c%s\n", pid, buf, ch, &buf[i+1]);
	  fflush(logfp);
	}
	buf[i] = ch;

	if (within_ehlo)
	  ehlo_check(&buf[4]);
	if (!strbuf && !esmtp_on_banner)
	  esmtp_banner_check(&buf[4]);

	rmsgappend("\r->> %s",buf);
	if (saverpt)
	  notarystatsave(buf);

	
	dflag = 0;
	switch (response) {
	case 211: /* System status, or system help reply */
	case 214: /* Help message */
	case 220: /* <domain> Service ready */
	case 221: /* <domain> Service closing transmission channel */
	case 250: /* Requested mail action okay, completed */
	case 251: /* User not local; will forward to <forward-path> */
        case 255: /* Something the PMDF 4.1 returns.. for EHLO */
	case 354: /* Start mail input; end with <CRLF>.<CRLF> */
		return EX_OK;
	case 421: /* <domain> Service not available, closing transmission channel */
	case 450: /* Requested mail action not taken: mailbox unavailable */
	case 451: /* Requested action aborted: local error in processing */
	case 452: /* Requested action not taken: insufficient system storage */
		return EX_TEMPFAIL;
	case 500: /* Syntax error, command unrecognized */
	case 501: /* Syntax error in parameters or arguments */
		return EX_USAGE;
	case 502: /* Command not implemented */
		return EX_PROTOCOL;
	case 503: /* Bad sequence of commands */
		return EX_TEMPFAIL;
	case 504: /* Command parameter not implemented */
		return EX_PROTOCOL;
	case 550: /* Requested action not taken: mailbox unavailable */
	case 551: /* User not local; please try <forward-path> */
		return EX_NOUSER;
	case 552: /* Requested mail action aborted: exceeded storage allocation */
		return EX_TEMPFAIL;
	case 553: /* Requested action not taken: mailbox name not allowed */
		return EX_NOUSER;
	case 554: /* Transaction failed */
		return EX_UNAVAILABLE;
	}
	switch (response/100) {
	case 2:
	case 3:
		return EX_OK;
	case 4:
		return EX_TEMPFAIL;
	case 5:
		return EX_UNAVAILABLE;
	}
	return EX_TEMPFAIL;
}


int
smtp_ehlo(smtpfp, verboselog, strbuf)
	FILE *smtpfp[2], *verboselog;
	char *strbuf;
{
	int rc;
	within_ehlo = 1;
	ehlo_capabilities = 0;
	rc = smtpwrite(smtpfp, verboselog, 1, strbuf);
	within_ehlo = 0;
	return rc;
}

/*
 * 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 <%ld ", remotehost, atol(msgfile));
#ifdef	notdef
	if (logfp)
	  sprintf(buf+strlen(buf), ">>%s ", logfile);
	strcat(buf, "# ");
#endif
	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);
}

void
stashmyaddresses(host)
	char *host;
{
	int naddrs;
	struct hostent *hp;

	if ((hp = gethostbyname(host)) == NULL)
	  return;

	naddrs = 0;
	for (hp_init(hp); *hp_getaddr() != NULL; hp_nextaddr())
	  ++naddrs;
	nmyaddrs = naddrs;
	if ((myaddrs = (char *)malloc(nmyaddrs * hp->h_length)) == NULL)
	  return;
	for (hp_init(hp); *hp_getaddr() != NULL; hp_nextaddr())
	  memcpy(&myaddrs[(--naddrs)*hp->h_length], *hp_getaddr(), hp->h_length);
}

int
matchmyaddress(addr,len)
	char *addr;
	int len;
{
	int i;

	if (!myaddrs) return 0; /* Don't know my addresses ! */
	for (i = 0; i < nmyaddrs; ++i) {
	  /* if this is myself, skip to next MX host */
	  if (memcmp(addr, &myaddrs[i*len],len) == 0)
	    return 1;
	}
	return 0;
}

int
matchmyaddresses(hp)
	struct hostent *hp;
{
	char **alist;
#ifndef	h_addr	/* no h_addr macro -- presumably 4.2BSD or earlier.. */
	char *fakealist[2];

	alist = fakealist;
	fakealist[0] = hp->h_addr;
	fakealist[1] = NULL;
#else
	alist = hp->h_addr_list;
#endif
	while (alist && *alist) {
	  if (matchmyaddress(*alist,hp->h_length))
	    return 1;
	  ++alist;
	}
	return 0;
}

#ifdef	BIND

typedef union {
	HEADER qb1;
	char qb2[PACKETSZ];
} querybuf;

int
getmxrr(host, mx, maxmx, depth, mxcntp)
	char *host;
	struct mxdata mx[];
	int maxmx, depth;
	int *mxcntp; /* Return the NUMBER of MXes that were found,
			including those that were considered faulty.
			Will count all MXes. */
{
	HEADER *hp;
	msgdata *eom, *cp;
	struct hostent *hent;
	querybuf qbuf, answer;
	struct mxdata mxtemp;
	msgdata buf[8192], realname[8192];
	int qlen, n, i, j, nmx, ancount, qdcount, maxpref;
	u_short type;
	int saw_cname = 0;

	if (depth == 0)
	  h_errno = 0;

	if (depth > 3) {
	  sprintf(remotemsg,"500 (DNS: Recursive CNAME on '%s')",host);
	  notaryreport(NULL,NULL,remotemsg);
	  fprintf(stderr, "%s\n", remotemsg);
	  return EX_NOHOST;
	}


	qlen = res_mkquery(QUERY, host, C_IN, T_MX, (char *)NULL, 0, NULL,
			(char *)&qbuf, sizeof qbuf);
	if (qlen < 0) {
	  fprintf(stderr, "res_mkquery failed\n");
	  sprintf(remotemsg,
		  "466 (Internal: res_mkquery failed on host: %s)",host);
	  notaryreport(NULL,NULL,remotemsg);
	  return EX_SOFTWARE;
	}
	n = res_send((char *)&qbuf, qlen, (char *)&answer, sizeof answer);
	if (n < 0) {
	  sprintf(remotemsg,
		  "466 (Internal: res_send failed on host: %s)",host);
	  notaryreport(NULL,NULL,remotemsg);
	  return EX_TEMPFAIL;
	}
	eom = (msgdata *)&answer + n;
	/*
	 * find first satisfactory answer
	 */
	hp = (HEADER *) &answer;
	ancount = ntohs(hp->ancount);
	qdcount = ntohs(hp->qdcount);
	if (hp->rcode != NOERROR || ancount == 0) {
	  switch (hp->rcode) {
	  case NXDOMAIN:
	    /* Non-authoritative iff response from cache.
	     * Old BINDs used to return non-auth NXDOMAINs
	     * due to a bug; if that is the case by you,
	     * change to return EX_TEMPFAIL iff hp->aa == 0.
	     */
	    sprintf(remotemsg, "500 (Bind: no such domain: %s)", host);
	    notaryreport(NULL,NULL,remotemsg);
	    return EX_NOHOST;
	  case SERVFAIL:
	    sprintf(remotemsg, "500 (Bind: server failure: %s)", host);
	    notaryreport(NULL,NULL,remotemsg);
	    return EX_TEMPFAIL;
#ifdef OLDJEEVES
	    /*
	     * Jeeves (TOPS-20 server) still does not
	     * support MX records.  For the time being,
	     * we must accept FORMERRs as the same as
	     * NOERROR.
	     */
	  case FORMERR:
#endif
	  case NOERROR:
	    mx[0].host = NULL;
	    mxcount = 0;
	    return EX_OK;
#ifndef OLDJEEVES
	  case FORMERR:
#endif
	  case NOTIMP:
	  case REFUSED:
	    sprintf(remotemsg, "500 (Bind: unsupported query: %s)", host);
	    notaryreport(NULL,NULL,remotemsg);
	    return EX_NOPERM;
	  }
	  sprintf(remotemsg, "500 (Bind: unknown error, MX info unavailable: %s)", host);
	  notaryreport(NULL,NULL,remotemsg);
	  return EX_UNAVAILABLE;
	}
	nmx = 0;
	cp = (msgdata *)&answer + sizeof(HEADER);
	for (; qdcount > 0; --qdcount)
#if	defined(BIND_VER) && (BIND_VER >= 473)
		cp += dn_skipname(cp, eom) + QFIXEDSZ;
#else	/* !defined(BIND_VER) || (BIND_VER < 473) */
		cp += dn_skip(cp) + QFIXEDSZ;
#endif	/* defined(BIND_VER) && (BIND_VER >= 473) */
	realname[0] = '\0';
	maxpref = -1;
	while (--ancount >= 0 && cp < eom && nmx < maxmx-1) {
	  n = dn_expand((msgdata *)&answer, eom, cp, buf, sizeof buf);
	  if (n < 0)
	    break;
	  cp += n;
	  type = _getshort(cp);
	  cp += sizeof(u_short);
	  /*
	     class = _getshort(cp);
	     */
	  cp += sizeof(u_short) + sizeof(u_long);
	  n = _getshort(cp);
	  cp += sizeof(u_short);
	  if (type == T_CNAME) {
	    cp += dn_expand((msgdata *)&answer, eom, cp,
			    realname, sizeof realname);
	    saw_cname = 1;
	    continue;
	  } else if (type != T_MX)  {
	    cp += n;
	    continue;
	  }
	  mx[nmx].pref = _getshort(cp);
	  cp += sizeof(u_short); /* MX preference value */
	  n = dn_expand((msgdata *)&answer, eom, cp, buf, sizeof buf);
	  if (n < 0)
	    break;
	  cp += n;

	  hent = gethostbyname(buf); /* This resolves CNAME, should it
					should not in case of MX server */
	  if (!hent) {
	    *mxcntp += 1;
	    continue;		/* Well well.. spurious! */
	  }
	  if (cistrcmp(hent->h_name, myhostname) == 0)
	    maxpref = mx[nmx].pref;
	  if (matchmyaddresses(hent))
	    maxpref = mx[nmx].pref;

	  if ((mx[nmx].host = (msgdata *)emalloc(strlen(buf)+1)) == NULL) {
	    fprintf(stderr, "Out of virtual memory!\n");
	    exit(EX_OSERR);
	  }
	  strcpy(mx[nmx].host, buf);
	  /* [mea] Canonicalize this target & get A records.. */
	  ++nmx;
	}
	if (nmx == 0 && realname[0] != '\0' &&
	    cistrcmp(host,(char*)realname)) {
	  /* do it recursively for the real name */
	  return getmxrr((char *)realname, mx, maxmx, depth+1, mxcntp);
	} else if (nmx == 0) {
	  /* "give it benefit of doubt" */
	  mx[0].host = NULL;
	  mxcount = 0;
	  return EX_OK;
	}
	for (i = 0; i < nmx; ++i) {
	}

	/* discard MX RRs with a value >= that of  myhost */
	if (maxpref >= 0) {
	  for (n = i = 0; n < nmx; ++n) {
	    if (mx[n].pref >= maxpref) {
	      free(mx[n].host);
	      mx[n].host = NULL;
	      ++i;
	    }
	  }
	  if (i == nmx) {	/* we are the best MX, do it another way */
	    mx[0].host = NULL;
	    mxcount = 0;
	    return EX_OK;
	  }
	}
#ifdef	RFC974
	/* discard MX's that do not support SMTP service */
	for (n = 0; checkwks && n < nmx; ++n) {
	  if (mx[n].host == NULL)
	    continue;
	  strcpy(buf, mx[n].host);
	  /* It is an MX, it CAN'T have CNAME ! */
	  if (!getrrtype(buf, sizeof buf, T_WKS, 0)) {
	    free(mx[n].host);
	    mx[n].host = NULL;
	  }
	}
#endif	/* RFC974 */
	/* determine how many are left */
	for (i = 0, n = 0; i < nmx; ++i) {
	  if (mx[i].host == NULL)
	    continue;
	  *mxcntp += 1;
	  if (n < i) {
	    mx[n] = mx[i];
	    mx[i].host = NULL;
	  }
	  ++n;			/* found one! */
	}
	if (n == 0) {/* MX's exist, but their WKS's show no TCP smtp service */
	  sprintf(remotemsg, "500 (Bind: MX host does not support SMTP: %s)", host);
	  notaryreport(NULL,NULL,remotemsg);
	  return EX_UNAVAILABLE;
	}
	nmx = n;
	/* sort the records */
	for (i = 0; i < nmx; i++) {
	  for (j = i + 1; j < nmx; j++) {
	    if (mx[i].pref > mx[j].pref) {
	      mxtemp = mx[i];
	      mx[i] = mx[j];
	      mx[j] = mxtemp;
	    }
	  }
	}
	mx[nmx].host = NULL;
	mxcount = nmx;
	return EX_OK;
}

int
getrr(host, hbsize, rrtype, cnamelevel)	/* getrrtype with completion */
	char *host;
	int hbsize;
	u_short rrtype;
	int cnamelevel;
{
	int rval;
	char buf[BUFSIZ], **domain;

	if ((rval = getrrtype(host, hbsize, rrtype, cnamelevel)) > 0)
	  return rval;
	for (domain = _res.dnsrch; *domain != NULL; domain++) {
	  sprintf(buf, "%s.%s", host, *domain);
	  if ((rval = getrrtype(buf, BUFSIZ, rrtype, cnamelevel)) > 0) {
	    strncpy(host, buf, hbsize<BUFSIZ?hbsize:BUFSIZ);
	    host[hbsize - 1] = '\0';
	    return rval;
	  }
	}
	return 0;
}

int
getrrtype(host, hbsize, rrtype, cnamelevel)
	char *host;
	int hbsize;
	int cnamelevel;
	u_short rrtype;
{

	HEADER *hp;
	msgdata *eom, *cp;
	querybuf buf, answer;
	int qlen, n, ancount, qdcount, ok;
	u_short type;
	msgdata nbuf[BUFSIZ];
	int first;
	char *cnamestr = NULL;

	qlen = res_mkquery(QUERY, host, C_IN, rrtype, (char *)NULL, 0, NULL,
			   (char *)&buf, sizeof(buf));
	if (qlen < 0) {
	  fprintf(stderr, "res_mkquery failed\n");
	  h_errno = NO_RECOVERY;
	  return -2;
	}
	n = res_send((char *)&buf, qlen, (char *)&answer, sizeof(answer));
	if (n < 0) {
	  h_errno = TRY_AGAIN;
	  return -1;
	}
	eom = (msgdata *)&answer + n;
	/*
	 * find first satisfactory answer
	 */
	hp = (HEADER *) &answer;
	ancount = ntohs(hp->ancount);
	qdcount = ntohs(hp->qdcount);
	h_errno = 0;
	/*
	 * We don't care about errors here, only if we got an answer
	 */
	if (ancount == 0) {
	  if (rrtype == T_CNAME && hp->rcode == NOERROR) {
	    if (qdcount > 0 && strchr(host, '.') == NULL) {
	      cp = (msgdata *)&answer + sizeof(HEADER);
	      if (dn_expand((msgdata *)&answer,
			    eom, cp, host, hbsize) >= 0) {
		if (host[0] == '\0') {
		  host[0] = '.';
		  host[1] = '\0';
		}
	      }
	    }
	    return 1;
	  }
	  return 0;
	}
	cp = (msgdata *)&answer + sizeof(HEADER);
	for (; qdcount > 0; --qdcount) {
#if	defined(BIND_VER) && (BIND_VER >= 473)
	  cp += dn_skipname(cp, eom) + QFIXEDSZ;
#else	/* !defined(BIND_VER) || (BIND_VER < 473) */
	  cp += dn_skip(cp) + QFIXEDSZ;
#endif	/* defined(BIND_VER) && (BIND_VER >= 473) */
	}
	first = 1;
	ok = rrtype != T_WKS;
	while (--ancount >= 0 && cp < eom) {
	  if ((n = dn_expand((msgdata *)&answer, eom, cp, nbuf,
			     sizeof(nbuf))) < 0)
	    break;
	  if (first) {
	    strncpy(host, nbuf, hbsize);
	    host[hbsize - 1] = '\0';
	    first = 0;
	  }
	  cp += n;
	  type = _getshort(cp);
	  cp += sizeof(u_short); /* type */
	  cp += sizeof(u_short); /* class */
	  cp += sizeof(u_long);	/* ttl */
	  n = _getshort(cp);
	  cp += sizeof(u_short); /* dlen */
	  if (type != rrtype) {
	    cp += n;
	    continue;
	  }
	  /*
	   * Assume that only one rrtype will be found.  More
	   * than one is undefined.
	   */
	  if (type == T_WKS) {
	    msgdata *nextcp = cp + n;
	    /* If we have seen a WKS, it had better have SMTP,
	     * however in absence of a WKS, assume SMTP.
	     */
	    if (n < sizeof (u_long) + 1) {
	      cp = nextcp;	/* ? */
	      continue;
	    }
	    ok = 0;
	    cp += sizeof(u_long); /* skip IP address */
	    if (*cp++ == IPPROTO_TCP) {	/* check protocol */
	      if (cp + (IPPORT_SMTP/8) < nextcp
		  && (*(cp+(IPPORT_SMTP/8))
		      & (0x80>>IPPORT_SMTP%8)))
		return 1;
	    }
	    cp = nextcp;
	    continue;
	  }
	  /* Special processing on T_CNAME ??? */
	  if ((n = dn_expand((msgdata *)&answer, eom, cp, nbuf,
			     sizeof(nbuf))) < 0)
	    break;
	  strncpy(host, nbuf, hbsize);
	  host[hbsize - 1] = '\0';
	  return ok;
	}
	return 0;
}

/*
 * This is the callback function for ctlopen.  It should return 0 to reject
 * an address, and 1 to accept it.  This routine will only be used if we've
 * been asked to check MX RR's for all hosts for applicability. Therefore we
 * check whether the addr_host has an MX RR pointing at the host that we have
 * an SMTP connection open with.  Return 1 if it is so.
 * [mea] This also understands routermxes data.
 */

int
rightmx(spec_host, addr_host)
	char	*spec_host, *addr_host;
{
	struct mxdata mxh[MAXFORWARDERS];
	int	i;
	int	mxcnt;

	if (cistrcmp(spec_host, addr_host) == 0)
	  return 1;
	if (remotehost[0] == '\0')
	  return 0;

	mxh[0].host = NULL;
	mxcnt = 0;
	switch (getmxrr(addr_host, mxh, sizeof mxh/sizeof mxh[0], 0, &mxcnt)) {
	case EX_OK:
	  if (mxh[0].host == NULL)
	    return cistrcmp(addr_host, remotehost) == 0;
	  break;
	default:
	  return 0;
	}
	for (i = 0; mxh[i].host != NULL; ++i) {
	  if (cistrcmp(mxh[i].host, remotehost) == 0) {
	    while (mxh[i].host != NULL)
	      free(mxh[i++].host);
	    return 1;
	  }
	  free(mxh[i].host);
	}
	return 0;
}
#endif	/* BIND */

/*
 * [mea] matchroutermxes()
 * like rightmx above, only a lot more light-weight...
 */
int
matchroutermxes(spec_host, ap)
	char *spec_host;
	struct address *ap;
{
	char **mxes = ap->routermxes;

	if (cistrcmp(spec_host, ap->host) == 0)
	  return 1;
	if (*remotehost == 0)
	  return 0;

	while (*mxes) {
	  if (cistrcmp(spec_host,*mxes)==0) return 1; /* Found it */
	  ++mxes;
	}
	return 0;
}


/* When data is clean 7-BIT, do:  *flag_ptr = (*flag_ptr) << 1  */
int 
check_7bit_cleanness(dp)
struct ctldesc *dp;
{
#ifdef	USE_MMAP
	/* With MMAP()ed spool file it is sweet and simple.. */
	register char *s = dp->let_buffer + dp->msgbodyoffset;
	while (s < dp->let_end)
	  if (128 & *s) return 0;
	  else ++s;
	return 1;
#else /* !USE_MMAP */

	register int i;
	register int bufferfull;
	int lastwasnl;
	long mfd_pos;
	int mfd = dp->msgfd;

	/* can we use cache of message body data ? */
	if (readalready != 0) {
	  for (i=0; i<readalready; ++i)
	    if (128 & (let_buffer[i]))
	      return 0;		/* Not clean ! */
	}

	/* we are assumed to be positioned properly at start of message body */
	bufferfull = 0;

	mfd_pos = lseek(mfd, dp->msgbodyoffset, SEEK_SET);
	
	while ((i = read(mfd, let_buffer, sizeof let_buffer)) != 0) {
	  if (i < 0) {
	    /* ERROR ?!?!? */
	    if (bufferfull > 1) readalready = 0;
	    return 0;
	  }
	  lastwasnl = (let_buffer[i-1] == '\n');
	  readalready = i;
	  bufferfull++;
	  for (i=0; i<readalready; ++i)
	    if (128 & (let_buffer[i])) {
	      lseek(mfd, mfd_pos, SEEK_SET);
	      if (bufferfull > 1) readalready = 0;
	      return 0;		/* Not clean ! */
	    }
	}
	/* Got to EOF, and still it is clean 7-BIT! */
	lseek(mfd, mfd_pos, SEEK_SET);
	if (bufferfull > 1)	/* not all in memory, need to reread */
	  readalready = 0;

	/* Set indication! */
	return 1;
#endif /* !USE_MMAP */
}

void
notarystatsave(smtpline)
char *smtpline;
{
	int len = strlen(smtpline)+8;
	char *str = alloca(len);
	char *s = str;

	*s++ = *smtpline++;
	*s++ = *smtpline++;
	*s++ = *smtpline++;
	*s++ = ' ';
	if (len < 11) {
	  notaryreport(NULL,NULL,str);
	  return;
	}
	*s++ = '(';
	while (*smtpline) {
	  switch (*smtpline) {
	    case '(':
	        *s++ = '[';
		break;
	    case ')':
		*s++ = ']';
		break;
	    default:
		*s++ = *smtpline;
	  }
	  ++smtpline;
	}
	*s++ = ')';
	*s = 0;
	notaryreport(NULL,NULL,str);
}


void getdaemon()
{
	struct passwd *pw = getpwnam("daemon");
	if (!pw) pw = getpwnam("uucp");

	if (!pw) daemon_uid = 0; /* Let it be root, if nothing else */
	else     daemon_uid = pw->pw_uid;
}

#if 0
/* Debugging stuff.. */
int countfds()
{
  int fds = getdtablesize();
  int i;
  int cnt = 0;

  for (i=0; i<fds; ++i)
    if (fcntl(i,F_GETFL) >= 0)
      ++cnt;

  return cnt;
}
#endif
