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

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

#define	TIMEOUT		(5*60)		/* timeout in seconds, per exchange */
#define ALARM_BLOCKSIZE 1024
#define ALARM_DOTTOOK   (60*60)
			/* RFC 1123 recommends:
			     - Initial 220: 5 minutes
			     - MAIL, RCPT : 5 minutes
			     - DATA initialization (until "354.."): 2 minutes
			     - While writing data, a block
			       at the time: 3 minutes  (How large a block ?)
			     - From "." to "250 OK": 10 minutes

			   I think we simplify:  5 minutes each, except "."
			   to "250 ok" which is 60 (!) minutes. (sendmail's
			   default value.)
			   Block-size is 1kB.   4-Feb-95: [mea@utu.fi]

			 */

#include "hostenv.h"
#include <stdio.h>
#ifdef linux
#define __USE_BSD 1
#endif
#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 <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "mail.h"
#include "ta.h"
#include "malloc.h"

#if	defined(TRY_AGAIN) && defined(HAVE_RESOLVER)
#define	BIND		/* Want BIND (named) nameserver support enabled */
#endif	/* TRY_AGAIN */
#ifdef	BIND
#ifdef NOERROR
#undef NOERROR		/* Several SysV-streams using systems have NOERROR,
			   which is not the same as  <arpa/nameser.h> has! */
#endif
#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 errormsg[BUFSIZ];
char *progname;
char *cmdline, *eocmdline, *logfile, *msgfile;
char *myaddrs = NULL;
int nmyaddrs = 0;
int pid;
int debug = 0;
int verbosity = 0;
int conndebug = 0;
int timeout = 0;		/* how long do we wait for response? (sec.) */
int gotalarm =0;		/* indicate that alarm happened! */
#ifndef	HAVE_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 keep_header8 = 0;		/* Don't do "MIME-2" to the headers */
int checkwks = 0;
int headers_contain_8bit = 0;
FILE *logfp = NULL;
extern int nobody;
int rcpt_limit = 100;		/* Number of recipients that can be sent on
				   one session.. */
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 */
int no_pipelining = 0;		/* In case the system just doesn't cope with it */

static const char FAILED[] = "failed";

#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
#define ESMTP_PIPELINING 0x0008

int  ehlo_capabilities = 0;
long ehlo_sizeval      = 0;
int  esmtp_on_banner = -1;
int  main_esmtp_on_banner = -1;
int  servport = -1;

time_t now;

extern int errno;
extern void warning(), report();
#ifndef MALLOC_TRACE
extern univptr_t emalloc();
#endif
/*
   extern int  atoi __((char*));
   extern long atol __((char*));
 */
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 void smtp_flush __((void));
extern int smtp_sync __((FILE *smtpfp[2], FILE *verboselog, int));
extern int smtpwrite __((FILE *smtpfp[2], FILE *verboselog, int saverpt, char *linebuf, struct rcpt *syncrp));
extern int process __((struct ctldesc*,int,char*,FILE**,FILE*));

extern int check_7bit_cleanness __((struct ctldesc *dp));
extern void notarystatsave __((char *smtpstatline, char *status));

extern void prversion();
extern void getnobody();
extern int makeconn __((struct hostent*, int*, FILE*, FILE**));
extern int vcsetup __((char*,int,int*,char*,FILE*));
extern void hp_init __((struct hostent*));
extern char **hp_getaddr __((void)), **hp_nextaddr __((void));
extern void hp_setalist __((struct hostent*,char**));
#ifdef	BIND
extern int rightmx __((char*,char*));
extern int h_errno;
extern int res_mkquery(), res_send(), dn_skipname(), dn_expand();
# ifdef RFC974
struct mxdata {
	msgdata	*host;
	u_short	pref;
};
extern int getmxrr __((char*,struct mxdata*,int,int,int*));
# endif /* RFC974 */
#endif	/* BIND */
extern int matchroutermxes __((char*,struct address*));
extern RETSIGTYPE sig_pipe __((int));
extern RETSIGTYPE sig_alarm __((int));
extern int getmyhostname();
extern void stashmyaddresses();
extern int cistrcmp(), cistrncmp();
extern void getdaemon();

FILE *verboselog = NULL;
int smtp_isopen = 0;

static char *logtag()
{
	static char buf[30];
	static int logcnt = 0;
	static char id = 0;

	/* The `id' is pseudo-random character inteded to lessen
	   the propablility of reused PID matching same prefix-
	   string between two SMTP sessions, and thus making the
	   resulting output  sort(1)able in flat ascii mode.
	   Timeorder would not be valid, perhaps, but 
	   For debugging uses, of course  */

	if (id == 0) {
	  id = '0' + (time(NULL) % 58);
	  if (id > '9') id += ('A'-'9'+1);
	  if (id > 'Z') id += ('a'-'Z'+1);
	}

	time(&now);

	sprintf(buf,"%05d%c%05d%05d",pid,id,logcnt,now % 100000);
	++logcnt;
	return buf;
}

int
main(argc, argv, envp)
	int argc;
	char *argv[];
	char *envp[];
{
	char file[MAXPATHLEN+128];
	char *channel = NULL, *host = NULL;
	int i, fd, errflg, c, smtpstatus;
	int need_host = 0;
	int idle;
	FILE *smtpfp[2];
	struct ctldesc *dp;
#ifdef	BIND
	int checkmx = 0;	/* check all destination hosts for MXness */
#endif	/* BIND */
	RETSIGTYPE (*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;
	/* Can overwrite also the environment strings.. */
	for (i = 0; envp[i] != NULL; ++i)
	  eocmdline += strlen(envp[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:p:rsvxDEHPT:VW78")) != EOF) {
	  switch (c) {
	  case 'c':		/* specify channel scanned for */
	    channel = strdup(optarg);
	    break;
	  case 'd':		/* turn on debugging output */
	    ++debug;
	    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 = strdup(optarg);
	    break;
	  case 'p':		/* server port */
	    servport = atoi(optarg);
	    break;
	  case 'P':
	    no_pipelining = 1;	/* It doesn't handle it.. */
	    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 */
	    main_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':
	    ++verbosity;
	    break;
	  case 'V':
	    prversion("smtp");
	    exit(0);
	    break;
	  case 'W':		/* Enable RFC974 WKS checks */
	    checkwks = 1;
	    break;
	  case 'H':
	    keep_header8 = 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) {
	  fprintf(stderr,
		  "Usage: %s [-8|-8H|-7][-e][-r][-x][-E][-P][-W][-T timeout][-h myhostname][-l logfile][-p portnum][-c channel] [host]\n", argv[0]);
	  exit(EX_USAGE);
	}
	if (optind < argc) {
	  host = strdup(argv[optind]);
	} else
	  need_host = 1;

	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 && host) {
	  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;

	if (logfp)
	  setvbuf(logfp, (char *)NULL, _IOLBF, 0);

	getnobody();
	getdaemon();


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

	while (!getout) {
	  /* Input:
	       spool/file/name [ \t host.info ] \n
	   */
	  char *s;

	  printf("#hungry\n");
	  fflush(stdout);

	  if (statusreport)
	    if (idle)
	      report("#idle");
	    else
	      report("#hungry");

	  if (fgets(file, sizeof file, stdin) == NULL) break;
	  idle = 0;
	  if (strchr(file, '\n') == NULL) break; /* No ending '\n' !  Must
						    have been partial input! */
	  if (logfp) {
	    fprintf(logfp,"%s#\tjobspec: %s",logtag(),file);
	  }
	  if (strcmp(file, "#idle\n") == 0) {
	    idle = 1;
	    continue; /* XX: We can't stay idle for very long, but.. */
	  }
	  if (emptyline(file, sizeof file))
	    break;

	  time(&now);

	  s = strchr(file,'\t');
	  if (s != NULL) {
	    *s++ = 0;

	    if (host && strcasecmp(host,s)==0) {
	      extern time_t retryat_time;

	      if (now < retryat_time) {
		/* Same host, we don't touch on it for a while.. */
		sleep(2);
		if (logfp && verbosity > 1) {
		  fprintf(logfp,"%s#\t(too soon trying to touch on host with 'retryat' diagnostic -- flushing job queue..host='%s')\n",logtag(),host);
		}
		continue;
	      }
	    }

	    /* If different target host, close the old connection.
	       In theory we could use same host via MX, but...     */
	    if (host && strcmp(s,host) != 0) {
	      if (smtp_isopen) {
		smtpstatus = smtpwrite(smtpfp, verboselog, 0, "QUIT", NULL);
		fclose(smtpfp[0]); smtpfp[0] = NULL;
		fclose(smtpfp[1]); smtpfp[1] = NULL;
		notary_setwtt(NULL);
		if (logfp) {
		  fprintf(logfp, "%s#\t(closed SMTP channel - new host)\n", logtag());
		}
		strcpy(remotehost, host);
		smtp_isopen = 0;
	      }
	    }
	    if (host) free(host);
	    host = strdup(s);
	  } else
	    if (need_host) {
	      printf("# smtp needs defined host!\n");
	      fflush(stdout);
	      continue;
	    }

	  if (debug > 1) { /* "DBGdiag:"-output */
	    printf("# (fdcnt=%d, file:%s, host:%s)\n", countfds(), file, host);
	    fflush(stdout);
	  }

#ifdef	BIND
	  res_init();

	  if (checkmx)
	    dp = ctlopen(file, channel, host, &getout, rightmx, matchroutermxes);
	  else
#endif /* BIND */
	    dp = ctlopen(file, channel, host, &getout, NULL, matchroutermxes);
	  if (dp == NULL) {
	    printf("#resync %s\n",file);
	    fflush(stdout);
	    continue;
	  }

	  headers_contain_8bit = 0;

	  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;
	    else {
	      /* SMTPOPEN() made diagnostics data -- we report.. */
	      struct rcpt *rphead;
	      /* NOTARY: addres / action / status / diagnostic */
	      for (rphead = dp->recipients;
		   rphead != NULL;
		   rphead = rphead->next) {
		notaryreport(rphead->addr->user,NULL,NULL,NULL);
		diagnostic(rphead, smtpstatus, 60, "%s", remotemsg);
	      }
	    }
	  } else
	    smtpstatus = EX_OK; /* Already open, we have a new job file */

	  if (smtpstatus == EX_OK)
	    smtpstatus = process(dp, smtpstatus, host, smtpfp, verboselog);
	  if (verboselog)
	    fclose(verboselog);
	  verboselog = NULL;

	  ctlclose(dp);
	} /* while (!getout) ... */

	if (smtpstatus == EX_OK) {
	  if (smtp_isopen)
	    smtpstatus = smtpwrite(smtpfp, verboselog, 0, "QUIT", NULL);
	}
	/* Close the channel -- if it is open anymore .. */
	if (smtp_isopen) {
	  fclose(smtpfp[0]);
	  fclose(smtpfp[1]);
	  if (logfp) {
	    fprintf(logfp, "%s#\t(closed SMTP channel - final close)\n", logtag());
	  }
	}
	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;
	int openstatus = EX_OK;

#ifndef	HAVE_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 && openstatus == EX_OK) {
	      if (logfp != NULL && !loggedid) {
		loggedid = 1;
		fprintf(logfp,
			"%s#\t%s: %s\n",
			logtag(), dp->msgfile,
			dp->logident);
	      }
	      if (!smtp_isopen) {
		if ((first_uid = atoi(dp->senders->misc)) < 0 ||
		    first_uid == nobody)
		  first_uid = daemon_uid;
		openstatus = smtpopen(host, smtpfp, verboselog);
		if (openstatus == EX_OK)
		  smtp_isopen = 1;
	      }
	      if (openstatus == EX_OK)
		smtpstatus = deliver(dp, smtpfp, verboselog, rphead, rp->next);
	      /*
		 if (openstatus != EX_OK) {
		 fclose(smtpfp[0]); smtpfp[0] = NULL;
		 fclose(smtpfp[1]); smtpfp[1] = NULL;
		 notary_setwtt(NULL);
		 if (logfp)
		 fprintf(logfp, "%s#\t(closed SMTP channel - after delivery failure)\n", logtag());
		 smtp_isopen = 0;
		 }
	       */
	      rphead = rp->next;
	    } else {
	      while (rphead != rp->next) {
		/* SMTP open -- meaning (propably) that we got reject
		   from the remote server */
		/* NOTARY: addres / action / status / diagnostic */
		notaryreport(rp->addr->user,FAILED,
			     "5.0.0 (Target status indeterminable)",
			     NULL);
		diagnostic(rphead, smtpstatus, 60, "%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 = EX_TEMPFAIL;
	int nrcpt, rcpt_cnt, hsize, size, tout;
	int content_kind = 0, convertmode;
	int ascii_clean = 0;
	struct stat stbuf;
	char SMTPbuf[2000];
	char *s;
	int conv_prohibit = check_conv_prohibit(startrp);
	int hdr_mime2 = 0;
	int pipelining = ( ehlo_capabilities & ESMTP_PIPELINING );
	time_t env_start, body_start, body_end;
	struct rcpt *more_rp = NULL;

	if (no_pipelining) pipelining = 0;

	convertmode = _CONVERT_NONE;
	if (conv_prohibit >= 0) {

	  /* 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 (conv_prohibit == 7)
	    force_7bit = 1;

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

	  switch (content_kind) {
	  case 0:		/* Not MIME */
	    if ((ehlo_capabilities & ESMTP_8BITMIME) == 0 &&
		!ascii_clean && !force_8bit) {
	      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().. */

	  hdr_mime2 = headers_need_mime2(startrp);
	  if (hdr_mime2 && !keep_header8) {
	    headers_to_mime2(startrp,verboselog);
	  }

	}


    more_recipients:
	if (more_rp != NULL) {
	  startrp = more_rp;
	  more_rp = NULL;
	}

	if (strcmp(startrp->addr->link->channel,"error")==0)
	  sprintf(SMTPbuf, "MAIL From:<>");
	else
	  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=%ld",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);
	}

	smtp_flush(); /* Flush in every case */

	time(&env_start); /* Mark the timestamp */

	/* MAIL FROM:<...> -- pipelineable.. */
	r = smtpwrite(smtpfp, verboselog, 1, SMTPbuf,
		      pipelining ? startrp : NULL);
	if (r != EX_OK) {
	  if (pipelining && smtp_isopen /* might have timed out */)
	    r = smtp_sync(smtpfp, verboselog, r);
	  for (rp = startrp; rp && rp != endrp; rp = rp->next) {
	    /* NOTARY: addres / action / status / diagnostic */
	    notaryreport(rp->addr->user,notaryacct(r,"*NOTOK*"),
			 "4.5.0 (Undetermined protocol error)",NULL);
	    diagnostic(rp, r, 0, "%s", remotemsg);
	  }
	  if (smtp_isopen)
	    smtpwrite(smtpfp, verboselog, 0, "RSET", NULL);
	  return r;
	}
	nrcpt = 0;
	rcpt_cnt = 0;
	for (rp = startrp; rp && rp != endrp; rp = rp->next) {
	  if (++rcpt_cnt >= rcpt_limit) {
	    more_rp = rp->next;
	    rp->next = NULL;
	  }
	  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);
	    }
	  }
	  
	  /* NOTARY: addres / action / status / diagnostic */
	  notaryreport(rp->addr->user, NULL, NULL, NULL);
	  /* RCPT TO:<...> -- pipelineable */
 	  r = smtpwrite(smtpfp, verboselog, 1, SMTPbuf,
			pipelining ? rp : NULL);
	  if (r != EX_OK) {
	    if (pipelining && smtp_isopen)
	      r = smtp_sync(smtpfp, verboselog, r);
	    /* NOTARY: addres / action / status / diagnostic / wtt */
	    notaryreport(NULL, notaryacct(r,"*NOTOK"),NULL,NULL);
	    diagnostic(rp, r, 0, "%s", remotemsg);
	    if (!smtp_isopen)
	      break;
	  } else {
	    ++nrcpt;
	    /* Actually we DO NOT KNOW, we need to sync this latter on.. */
	    rp->status = EX_OK;
	  }
	}
	if (nrcpt == 0) {
	  /* all the RCPT To addresses were rejected, so reset server */
	  if (smtp_isopen)
	    r = smtpwrite(smtpfp, verboselog, 0, "RSET", NULL);

	  if (r == EX_OK && more_rp)
	    /* we have more recipients,
	       and things have worked ok so far.. */
	    goto more_recipients;

	  return EX_UNAVAILABLE;
	}
	if (!smtp_isopen)
	  return EX_TEMPFAIL;

	if (pipelining) {
	  /* In PIPELINING mode ... send "DATA" */
	  r = smtpwrite(smtpfp, verboselog, 1, "DATA", startrp);
	  if (r != EX_OK) { /* failure on pipes ? */
	    if (smtp_isopen)
	      r = smtp_sync(smtpfp, verboselog, r); /* Sync it.. */
	    for (rp = startrp; rp && rp != endrp; rp = rp->next)
	      if (rp->status == EX_OK) {
		/* NOTARY: addres / action / status / diagnostic / wtt */
		notaryreport(rp->addr->user,notaryacct(r,"*NOTOK"),NULL,NULL);
		diagnostic(rp, EX_TEMPFAIL, 0, "%s", remotemsg);
	      }
	    if (smtp_isopen)
	      smtpwrite(smtpfp, verboselog, 0, "RSET", NULL);
	    return r;
	  }
	  /* Now it is time to do synchronization .. */
	  if (smtp_isopen)
	    r = smtp_sync(smtpfp, verboselog, EX_OK); /* Up & until "DATA".. */
	  if (r != EX_OK) {
	    /* XX:
	       #error  Uncertain of what to do ...
	       ... reports were given at each recipient, and if all failed,
	       we failed too..
	     */
	    for (rp = startrp; rp && rp != endrp; rp = rp->next)
	      if (rp->status == EX_OK) {
		/* NOTARY: addres / action / status / diagnostic / wtt */
		notaryreport(rp->addr->user,notaryacct(r,"*NOTOK*"),NULL,NULL);
		diagnostic(rp, EX_TEMPFAIL, 0, "%s", remotemsg);
	      }
	    return r;
	  }
	  /* Successes are reported AFTER the DATA-transfer is ok */
	} else {
	  /* Non-PIPELINING sync mode */
	  if ((r = smtpwrite(smtpfp, verboselog, 1, "DATA", NULL)) != EX_OK) {
	    for (rp = startrp; rp && rp != endrp; rp = rp->next)
	      if (rp->status == EX_OK) {
		/* NOTARY: addres / action / status / diagnostic / wtt */
		notaryreport(rp->addr->user,notaryacct(r,"*NOTOK*"),NULL,NULL);
		diagnostic(rp, EX_TEMPFAIL, 0, "%s", remotemsg);
	      }
	    if (smtp_isopen)
	      smtpwrite(smtpfp, verboselog, 0, "RSET", NULL);
	    return r;
	  }
	}
	/* Headers are 7-bit stuff -- says MIME specs */

	time(&body_start); /* "DATA" issued, and synced */

	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) {
	      /* NOTARY: addres / action / status / diagnostic / wtt */
	      notaryreport(rp->addr->user,notaryacct(EX_TEMPFAIL,"*NOTOK*"),
			   "4.4.2 (Message write timed out)",
			   "smtp; 466 (Message write timed out)"); /* XX: FIX THE STATUS? */
	      diagnostic(rp, EX_TEMPFAIL, 0, "%s", "header write error");
	    }
	  if (verboselog)
	    fprintf(verboselog,"Writing headers after DATA failed\n");
	  if (smtp_isopen)
	    smtpwrite(smtpfp, verboselog, 0, "RSET", NULL);
	  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 ((r = appendlet(dp, smtpfp[1], verboselog,
			   hsize, size, convertmode)) != EX_OK) {
	fail:
	  for (rp = startrp; rp && rp != endrp; rp = rp->next)
	    if (rp->status == EX_OK) {
	      notaryreport(rp->addr->user, notaryacct(r,"*NOTOK*"),
			   "4.4.2 (Message write timed out;2)",
			   "smtp; 466 (Message write timed out;2)"); /* XX: FIX THE STATUS? */
	      diagnostic(rp, r, 0, "%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
	   */
	  if (smtp_isopen) {
	    fclose(smtpfp[0]); smtpfp[0] = NULL;
	    fclose(smtpfp[1]); smtpfp[1] = NULL;
	    if (logfp)
	      fprintf(logfp, "%s#\t(closed SMTP channel - appendlet() failure)\n", logtag());
	    smtp_isopen = 0;
	  }
	  return EX_TEMPFAIL;
	}
	/*
	 * 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.
	 */
	/* RFC-1123 says: 10 minutes! */
	tout = timeout;
	timeout = ALARM_DOTTOOK;
	r = smtpwrite(smtpfp, verboselog, 1, ".", NULL);
	timeout = tout;
	if (r != EX_OK)
	  goto fail;

	time(&body_end); /* body endtime */

	if (logfp != NULL) {
	  if (r != EX_OK)
	    fprintf(logfp, "%s#\t%s\n", logtag(), remotemsg);
	  else
	    fprintf(logfp, "%s#\t%d bytes, %d in header, %d recipients, %d secs for envelope, %d secs for body xfer\n",
		    logtag(), size, hsize, nrcpt,
		    body_start - env_start, body_end - body_start);
	}
	for (rp = startrp; rp && rp != endrp; rp = rp->next)
	  if (rp->status == EX_OK) {
	    notaryreport(rp->addr->user,
			 notaryacct(r,"relayed"), /* XXX: NOTARY DRPT ?? */
			 NULL,NULL);
	    diagnostic(rp, r, 0, "%s", remotemsg);
	  }

	/* More recipients to send ? */
	if (r == EX_OK && more_rp != NULL)
	  goto more_recipients;

	if (r != EX_OK && smtp_isopen)
	  smtpwrite(smtpfp, verboselog, 0, "RSET", NULL);
	return r;
}

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

#ifndef	HAVE_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, rc;
	register int bufferfull;
	int lastwasnl = 0;
	int lastch = '\n'; /* WriteMIMELine() can decode-QP and then the
			      "lastwasnl" is no longer valid .. */

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

	fflush(fp);
	alarm(timeout);
	gotalarm = 0;

#if 0 /* This is broken - the lseek() below broke it, but it fixed a lot
	 of other problems..  Thus possible 'cache' goes away.. */
#ifndef	HAVE_MMAP
	/* can we use cache of message body data */
	if (convertmode == _CONVERT_NONE &&
	    readalready != 0) {
	  rc = writebuf(fp, let_buffer, readalready);
	  hsize += rc;
	  if (rc != readalready) {
	    if (gotalarm) {
	      sprintf(remotemsg,"smtp; 500 (let_buffer write timeout!  DATA %d/%d [%d%%])",
		      hsize, msize, (hsize*100+msize/2)/msize);
	      /* return EX_IOERR; */
	    }
	    return EX_IOERR;
	  }
	  lastwasnl = (let_buffer[readalready-1] != '\n');
	  if (statusreport)
	    report("DATA %d (100%%)", readalready+hsize);
	  if (fflush(fp) != 0)
	    return EX_IOERR;
	}
#endif /* !HAVE_MMAP */
#endif /* 0 */

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

	bufferfull = 0;
	lastwasnl = 1;	/* we are guaranteed to have a \n after the header */
	if (convertmode == _CONVERT_NONE) {
#ifdef	HAVE_MMAP
	  char *let_buffer = dp->let_buffer + dp->msgbodyoffset;
	  i = dp->let_end - dp->let_buffer - dp->msgbodyoffset;
#else /* !HAVE_MMAP */
	  while (1) {
	    if ((i = read(dp->msgfd, let_buffer, sizeof(let_buffer))) == 0)
	      break;
	    if (i < 0) {
	      strcpy(remotemsg, "smtp; 500 (Read error from message file!?)");
	      return EX_IOERR;
	    }
#endif /* !HAVE_MMAP */
	    lastwasnl = (let_buffer[i-1] == '\n');
	    rc = writebuf(fp, let_buffer, i);
	    hsize += rc;
	    if (statusreport)
	      report("DATA %d/%d (%d%%)",
		     hsize, msize, (hsize*100+msize/2)/msize);
	    if (rc != i) {
	      if (gotalarm) {
		sprintf(remotemsg,"smtp; 500 (let_buffer write timeout!  DATA %d/%d [%d%%])",
			hsize, msize, (hsize*100+msize/2)/msize);
		return EX_IOERR;
	      }
	      sprintf(remotemsg,
		      "smtp; 500 (let_buffer write IO-error! [%s] DATA %d/%d [%d%%])",
		      strerror(errno), hsize, msize, (hsize*100+msize/2)/msize);
	      return EX_IOERR;
	    }
#ifndef HAVE_MMAP
	    bufferfull++;
	    readalready = i;
	  }
#endif
	} else {
	  /* Various esoteric conversion modes..
	     We are better to feed writemimeline() with LINES
	     instead of blocks of data.. */
#ifndef	HAVE_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 /* HAVE_MMAP */
	  char *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	HAVE_MMAP
	  readalready = 0;
#endif
	  lastwasnl = 0;
	  for (;;) {
#ifndef HAVE_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 /* HAVE_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" */
	    rc = writemimeline(fp, let_buffer, i, convertmode, &lastch);
	    hsize += rc;
	    if (statusreport)
	      report("DATA %d/%d (%d%%)",
		     hsize, msize, (hsize*100+msize/2)/msize);
	    if (rc != 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 HAVE_MMAP
	    s = s2; /* Advance one linefull.. */
#endif
	  }
#ifndef	HAVE_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	HAVE_MMAP
	    if (bufferfull > 1) readalready = 0;
#endif
	    return EX_IOERR;
	  }

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

	return EX_OK;
}


#if 0
# define VLFPRINTF(x) if(verboselog)fprintf x
#else
# define VLFPRINTF(x)
#endif
/*
 * 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;
	static int alarmcnt = 0;

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

	  if (--alarmcnt <= 0) {
	    alarmcnt = ALARM_BLOCKSIZE;
	    fflush(fp);
	    alarm(timeout);	/* re-arm it */
	  }

	  if (state && c != '\n') {
	    state = 0;
	    if (c == '.') {
	      if (putc(c, fp) == EOF || putc(c, fp) == EOF) {
		notaryreport(NULL,FAILED,"4.4.2 (body write error, 1)",
			     "smtp; 500 (body write error, 1)");
		strcpy(remotemsg, "write error 1");
		return EOF;
	      }
	      VLFPRINTF((verboselog,".."));
	    } else {
	      if (putc(c, fp) == EOF) {
		notaryreport(NULL,FAILED,"4.4.2 (body write error, 2)",
			     "smtp; 500 (body write error, 2)");
		strcpy(remotemsg, "write error 2");
		return EOF;
	      }
	      VLFPRINTF((verboselog,"%c",c));
	    }
	  } else if (c == '\n') {
	    if (putc('\r', fp) == EOF || putc(c, fp) == EOF) {
	      notaryreport(NULL,FAILED,"4.4.2 (body write error, 3)",
			   "smtp; 500 (body write error, 3)");
	      strcpy(remotemsg, "write error 3");
	      return EOF;
	    }
	    VLFPRINTF((verboselog,"\r\n"));
	    state = 1;
	  } else {
	    if (putc(c, fp) == EOF) {
	      notaryreport(NULL,FAILED,"4.4.2 (body write error, 4)",
			   "smtp; 500 (body write error, 4)");
	      strcpy(remotemsg, "write error 4");
	      return EOF;
	    }
	    VLFPRINTF((verboselog,"%c",c));
	  }
	}
	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;
	static int alarmcnt;
	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; 
	  alarmcnt = ALARM_BLOCKSIZE;
	  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 (--alarmcnt <= 0) {
	    alarmcnt = ALARM_BLOCKSIZE;
	    fflush(fp);
	    alarm(timeout);
	  }

	  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) {
	    if (column > 70 && c != '\n') {
	      putc('=',fp);
	      putc('\r',fp);
	      putc('\n',fp);
	      *lastcharp = '\n';
	      VLFPRINTF((verboselog,"=\r\n"));
	      column = 0;
	    }
	    /* Trailing SPACE/TAB ? */
	    if (n < 3 && (c == ' ' || c == '\t')) {
	      putc('=',fp);
	      putc(i2h[(c >> 4) & 15],fp);
	      putc(i2h[(c)      & 15],fp);
	      *lastcharp = i2h[(c) & 15];
	      column += 2;
	      VLFPRINTF((verboselog,"=%02X",c));
	      continue;
	    }
	    /* Any other char which needs quoting ? */
	    if (c == '='  ||  c > 126 ||
		(c != '\n' && c != '\t' && c < 32)) {

	      putc('=',fp);
	      putc(i2h[(c >> 4) & 15],fp);
	      putc(i2h[(c)      & 15],fp);
	      *lastcharp = i2h[(c) & 15];
	      column += 2;
	      VLFPRINTF((verboselog,"=%02X",c));
	      continue;
	    }
	  } /* .... end convertmode	*/

	  if (column == 0 && c == '.') {
	    if (putc(c, fp) == EOF) {
	      notaryreport(NULL,FAILED,"4.4.2 (body write error, 5)",
			   "smtp; 500 (body write error, 5)");
	      strcpy(remotemsg, "write error 5");
	      return EOF;
	    }
	    VLFPRINTF((verboselog,".."));
	  }

	  if (c == '\n') {
	    if (putc('\r', fp) == EOF) {
	      notaryreport(NULL,FAILED,"4.4.2 (body write error, 6)",
			   "smtp; 500 (body write error, 6)");
	      strcpy(remotemsg, "write error 6");
	      return EOF;
	    }
	    VLFPRINTF((verboselog,"\r"));
	    column = -1;
	  }
	  if (putc(c, fp) == EOF) {
	      notaryreport(NULL,FAILED,"4.4.2 (body write error, 7)",
			   "smtp; 500 (body write error, 7)");
	    strcpy(remotemsg, "write error 7");
	    return EOF;
	  }
	  *lastcharp = c;
	  VLFPRINTF((verboselog,"%c",c));

	}
	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,"PIPELINING",10)==0) {
	  ehlo_capabilities |= ESMTP_PIPELINING;
	} else if (strncmp(buf,"SIZE ",5)==0 ||
		   strcmp(buf,"SIZE") == 0) {
	  ehlo_capabilities |= ESMTP_SIZEOPT;
	  ehlo_sizeval = -1;
	  if (buf[5] == ' ')
	    sscanf(buf+5,"%ld",&ehlo_sizeval);
	} else if (strncmp(buf,"X-RCPTLIMIT",11)==0) {
	  int nn = atoi(buf+12);
	  if (nn < 100 || nn > 100000)
	    nn = 100;
	  rcpt_limit = nn;
	}
	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, "%s#\tsmtpopen: connecting to %s\n", logtag(), host);
	}
	firstmx = 0;
	mxcount = 0;
	do {

	  esmtp_on_banner = main_esmtp_on_banner;
	  ehlo_capabilities = 0;
	  ehlo_sizeval = 0;
	  rcpt_limit = 100; /* Max number of recipients per message */

	  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, rcptlimit=%d\n",
		      ehlo_capabilities, rcpt_limit);
	  } else {
	    sprintf(SMTPbuf, "HELO %s", myhostname);
	    i = smtpwrite(fparr, verboselog, 1, SMTPbuf, NULL);
	    if (i == EX_TEMPFAIL && smtp_isopen) {
	      fclose(fparr[0]); fparr[0] = NULL;
	      fclose(fparr[1]); fparr[1] = NULL;
	      if (logfp)
		fprintf(logfp, "%s#\t(closed SMTP channel - HELO failed??)\n", logtag());
	      smtp_isopen = 0;
	    }
	    if (i == EX_TEMPFAIL) {
	      /* Ok, sometimes EHLO+HELO cause crash, open and do HELO only */
	      if ((i = smtpconn(host, fparr, verboselog)) != EX_OK)
		return i;
	      i = smtpwrite(fparr, verboselog, 1, SMTPbuf, NULL);
	      if (i == EX_TEMPFAIL && smtp_isopen) {
		fclose(fparr[0]); fparr[0] = NULL;
		fclose(fparr[1]); fparr[1] = NULL;
		if (logfp)
		  fprintf(logfp, "%s#\t(closed SMTP channel - HELO failed(2))\n", logtag());
		smtp_isopen = 0;
	      }
	    }
	  }
	} while (i == EX_TEMPFAIL && firstmx <= mxcount);
	if (debug) {
	  fprintf(logfp, "%s#\tsmtpopen: status = %d\n", logtag(), i);
	}
	return i;
}

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

	hp = NULL;
	memset(mxh,0,sizeof(mxh));
#ifdef	BIND
	h_errno = 0;
#endif	/* BIND */

	if (debug) {
	  fprintf(logfp, "%s#\tsmtpconn: host = %s\n", logtag(), host);
	}
	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.s_addr = inet_addr(buf);
	  if (ipaddr.s_addr == 0xFFFFFFFF) {
	    sprintf(remotemsg, "smtp; 500 (bad IP address: %s)", host);
	    notaryreport(NULL,FAILED,"5.1.2 (bad literal IP address)",
			 remotemsg);
	    if (verboselog)
	      fprintf(verboselog,"%s\n", remotemsg+6);
	    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] = (void *)&ipaddr;
	  addrs[1] = NULL;
	  hp_setalist(hp, addrs);
	  {
	    char nbuf[512];
	    sprintf(nbuf,"X-IP-addr; [%s]", buf);
	    notary_setwtt(nbuf);
	  }
	  retval = makeconn(hp, &smtpfd, verboselog, fparr);
	} else {
	  hbuf[0] = '\0';
	  errno = 0;
#if 0  /* ---------- chopped 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,"smtp; 466 (gethostbyname(%s): try later)",host);
	      notaryreport(NULL,FAILED,"4.4.3 (dns lookup timeout)",remotemsg);
	      if (verboselog)
		fprintf(verboselog,"%s\n",remotemsg+6);
	      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, "smtp; 466 (Canonicalize, try later: %s)", host);
	      notaryreport(NULL,FAILED,"4.4.3 (dns lookup 'try again')",remotemsg);
	      if (verboselog)
		fprintf(verboselog,"%s\n",remotemsg+6);
	      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, "smtp; 466 (object lookup failed on %s)", host);
	    notaryreport(NULL,FAILED,"4.4.3 (unspecified dns error)", remotemsg);
	    if (verboselog)
	      fprintf(verboselog,"%s\n", remotemsg+6);
	    /* temporary failure since the router knew it */
	    return EX_TEMPFAIL;	/* was EX_NOHOST */
#endif /* BIND */
	  }
#endif /* ---------- chopped 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, MAXFORWARDERS, 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,"smtp; 466 (gethostbyname<%s>: try later)",host);
		notaryreport(NULL,FAILED,"4.4.3 (dns lookup 'try again')", remotemsg);
		if (verboselog)
		  fprintf(verboselog,"%s\n",remotemsg+6);
		return EX_TEMPFAIL;
	      }

	      if (noMX)
		sprintf(remotemsg,
			"smtp; 500 (configuration inconsistency. MX usage forbidden, no address in DNS: '%s', errno=%s)",
			host,strerror(errno));
	      else
		sprintf(remotemsg,
			"smtp; 500 (nameserver data inconsistency. No MX, no address: '%s', errno=%s)",
			host,strerror(errno));
	      notaryreport(NULL,FAILED,"4.4.4 (nameserver data inconsistency)",
			   remotemsg);
	      if (verboselog)
		fprintf(verboselog,"%s\n",remotemsg+6);
	      /* it was: EX_UNAVAILABLE, but such blocks retrying, thus
		 current EX_TEMPFAIL, which will cause timeout latter on.. */
	      return EX_TEMPFAIL;
	    }
	    hp_addr_randomize(hp);
	    {
	      char buf[512];
	      sprintf(buf,"dns; %s", host);
	      notary_setwtt(buf);
	    }
	    retval = makeconn(hp, &smtpfd, verboselog, fparr);
	  } else {
	    /* Has valid MX records, they have been suitably randomized
	       at  getmxrr(), and are now ready for use.  */
	    retval = EX_UNAVAILABLE;
	    for (i = 0; mxh[i].host != NULL; ++i) {
	      if ((hp = gethostbyname((char*)mxh[i].host)) == NULL) {
		free(mxh[i].host);
		mxh[i].host = NULL;
		firstmx = i+1;
		continue;
	      }
	      hp_addr_randomize(hp);
	      {
		char buf[512];
		sprintf(buf,"dns; %s", mxh[i].host);
		notary_setwtt(buf);
	      }
	      r = makeconn(hp, &smtpfd, verboselog, fparr);
	      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,
		  "%s#\tsmtpconn: retval = %d\n", logtag(), retval);
	}
	return retval;
}

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

#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,"smtp; 550 (WKS checks: no SMTP capability for host %s)",
		  hostbuf);
	  notaryreport(NULL,FAILED,"4.4.4 (WKS Checks: no SMTP capability)", remotemsg);
	  if (verboselog)
	    fprintf(verboselog,"%s\n",remotemsg+6);
	  return EX_UNAVAILABLE;
	}
#endif	/* RFC974 */
#endif	/* BIND */

	retval = EX_UNAVAILABLE;
#if 0
	if (verboselog) {
	  fprintf(verboselog,"makeconn('%s') to IP addresses:", hostbuf);
	  for (hp_init(hp); *hp_getaddr() != NULL; hp_nextaddr()) {
	    fprintf(verboselog," %s",
		    dottedquad((struct in_addr*)*hp_getaddr()));
	  }
	  fprintf(verboselog,"\n");
	}
#endif
	for (hp_init(hp); *hp_getaddr() != NULL; hp_nextaddr()) {
	  if (matchmyaddress(*hp_getaddr(),hp->h_length)) {
	    notaryreport(NULL,FAILED,"5.4.6 (trying to talk with myself!)","smtp; 500 (Trying to talk with myself!)");
	    sprintf(remotemsg,"Trying to talk with myself!");
	    break;		/* TEMPFAIL or UNAVAILABLE.. */
	  }

	  if (smtp_isopen) {
	    /* Clean (close) these fds -- they have noted to leak.. */
	    if (fparr[0] != NULL) fclose(fparr[0]); fparr[0] = NULL;
	    if (fparr[1] != NULL) fclose(fparr[1]); fparr[1] = NULL;
	    if (logfp)
	      fprintf(logfp,"%s#\t(closed SMTP channel at makeconn(), isopen=%d)\n",logtag(),smtp_isopen);
	    smtp_isopen = 0;
	  }

	  switch (vcsetup(*hp_getaddr(), hp->h_length, fdp, hostbuf, verboselog)) {
	  case EX_OK:
	      if ((fparr[0] = fdopen(*fdp, "r")) == NULL) {
		int err;
fdopen_fail:
		err = errno;
		printf("smtp: Failed to fdopen() a socket stream, errno=%d, err='%s' Hmm ??\n",err, strerror(err));
		fflush(stdout);
		abort();
		if (fparr[0] != NULL) fclose(fparr[0]); fparr[0] = NULL;
		if (fparr[1] != NULL) fclose(fparr[1]); fparr[1] = NULL;
	      }
	      fd2 = dup(*fdp);
	      if (fd2 < 0 || (fparr[1] = fdopen(fd2, "w")) == NULL)
		goto fdopen_fail;

	      smtp_isopen = 1;

	      if (esmtp_on_banner > 0)
		esmtp_on_banner = 0;

	      retval = smtpwrite(fparr, verboselog, 1, NULL, 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;

	time(&now);

	strcpy(ipaddress, dottedquad((struct in_addr *)ipaddrcp));
	if (conndebug)
	  fprintf(stderr, "Trying %s [%s] ... ", hostname, ipaddress);
	if (logfp)
	  fprintf(logfp, "%s#\t(Connecting to `%s' [%s] %24.24s)\n",
		  logtag(), hostname, ipaddress, ctime(&now));
	if (statusreport) {
	  strcpy(remotehost,hostname);
	  report("connecting to [%s]",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, "smtp; 500 (Internal error, socket: %s)", se);
	  notaryreport(NULL,FAILED,"4.4.0 (internal error)",remotemsg);
	  if (conndebug)
	    fprintf(stderr, "%s\n", remotemsg+6);
	  if (verboselog)
	    fprintf(verboselog,"%s\n",remotemsg+6);
	  if (logfp)
	    fprintf(logfp,"%s#\t(Internal error, socket: %s)\n",logtag(),se);
	  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, "smtp; 500 (Internal error, bind: %s)", s);
	      notaryreport(NULL,FAILED,"4.4.0 (internal error, bind)",remotemsg);
	      if (verboselog)
		fprintf(verboselog,"%s\n", remotemsg+6);
	      if (conndebug)
		fprintf(stderr, "%s\n", remotemsg+6);
	      if (logfp)
		fprintf(logfp, "%s#\t(Internal error, bind: %s)\n", logtag(), s);
	      return EX_UNAVAILABLE;
	    }
	  }
	  if (p < IPPORT_RESERVED/2) {
	    sprintf(remotemsg, "too many busy ports");
	    notaryreport(NULL,FAILED,"4.4.0 (internal error, too many busy ports)","smtp; 500 (Internal error, too many busy ports)");
	    if (conndebug)
	      fprintf(stderr, "%s\n", remotemsg+6);
	    if (verboselog)
	      fprintf(verboselog,"%s\n",remotemsg+6);
	    if (logfp)
	      fprintf(logfp,"%s#\t(Internal error, too many busy ports)\n", logtag());
	    return EX_TEMPFAIL;
	  }
	}
	memcpy((char *)&sad.sin_addr, ipaddrcp, ipaddrlen);
	if (servport < 0)
	  sad.sin_port = htons((u_short)IPPORT_SMTP);
	else
	  sad.sin_port = htons(servport);
/* 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	defined(__svr4__) || defined(BSD) && (BSD-0) >= 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, "smtp; 500 (connect to %s [%s]: %s)",
		hostname, ipaddress, se);
	if (statusreport)
	  report("%s",remotemsg+4);
	notaryreport(NULL,FAILED,"5.4.1 (TCP/IP-connection failure)", remotemsg);

	if (conndebug)
		fprintf(stderr, "%s\n", remotemsg);
	if (verboselog)
		fprintf(verboselog,"%s\n",remotemsg);
	if (logfp)
	  fprintf(logfp,"%s#\t%s\n", logtag(), remotemsg+4);
	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;
}

RETSIGTYPE
sig_pipe(sig)
int sig;
{
	if (logfp != NULL) {
	  fprintf(logfp, "%s#\t*** Received SIGPIPE!\n", logtag());
	  abort();
	}
}

RETSIGTYPE
sig_alarm(sig)
int sig;
{
	gotalarm = 1;
}

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

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

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

int dflag = 0;
int within_ehlo = 0;

/*
 *  SMTP PIPELINING (RFC 1854) support uses model of:
 *       1st RCPT is for "MAIL FROM:<>".. -line
 *       2..n-1: are actual RCPT TO:<> -lines
 *	 n:th is the "DATA"-line.
 */

static int pipebufsize;
static char *pipebuf = NULL;
static int pipeindex = 0;
static int pipespace = 0;
static char **pipecmds = NULL;
static struct rcpt **pipercpts = NULL;

void
smtp_flush()
{
	pipebufsize = 0;
	if (pipebuf == NULL)
	  pipebuf = emalloc(1);
	for (;pipeindex > 0; --pipeindex) {
	  free(pipecmds[pipeindex-1]);
	}
	pipeindex = 0;
}

#ifdef	HAVE_SELECT

#ifdef _AIX /* Defines NFDBITS, et.al. */
#include <sys/types.h>
#include <sys/select.h>
#endif


#if	defined(BSD4_3) || defined(sun)
#include <sys/file.h>
#endif
#include <errno.h>
#include <sys/time.h>

#ifndef	NFDBITS
/*
 * This stuff taken from the 4.3bsd /usr/include/sys/types.h, but on the
 * assumption we are dealing with pre-4.3bsd select().
 */

typedef long	fd_mask;

#ifndef	NBBY
#define	NBBY	8
#endif	/* NBBY */
#define	NFDBITS		((sizeof fd_mask) * NBBY)

/* SunOS 3.x and 4.x>2 BSD already defines this in /usr/include/sys/types.h */
#ifdef	notdef
typedef	struct fd_set { fd_mask	fds_bits[1]; } fd_set;
#endif	/* notdef */

#ifndef	FD_SET
#define	FD_SET(n, p)	((p)->fds_bits[0] |= (1 << (n)))
#define	FD_CLR(n, p)	((p)->fds_bits[0] &= ~(1 << (n)))
#define	FD_ISSET(n, p)	((p)->fds_bits[0] & (1 << (n)))
#define FD_ZERO(p)	bzero((char *)(p), sizeof(*(p)))
#endif	/* !FD_SET */
#endif	/* !NFDBITS */

#ifdef FD_SET
#define _FD_SET(sock,var) FD_SET(sock,&var)
#define _FD_CLR(sock,var) FD_CLR(sock,&var)
#define _FD_ZERO(var)     FD_ZERO(&var)
#define _FD_ISSET(i,var)  FD_ISSET(i,&var)
#else
#define _FD_SET(sock,var) var |= (1 << sock)
#define _FD_CLR(sock,var) var &= ~(1 << sock)
#define _FD_ZERO(var)     var = 0
#define _FD_ISSET(i,var) ((var & (1 << i)) != 0)
#endif

int select_sleep(fd,timeout)
int fd, timeout;
{
	struct timeval tv;
	int rc;
	fd_set rdmask;

	tv.tv_sec = timeout;
	tv.tv_usec = 0;
	_FD_ZERO(rdmask);
	_FD_SET(fd,rdmask);

	rc = select(fd+1,&rdmask,NULL,NULL,&tv);
	if (rc == 0) /* Timeout w/o input */
	  return -1;
	if (rc == 1) /* There is something to read! */
	  return 0;
	return 1;    /* interrupt, or some such.. */
}
#else /* not HAVE_SELECT */
int select_sleep(fd, timeout)
int fd, timeout;
{
	return -1;
}
#endif

static int code_to_status(code,statusp)
int code;
char **statusp;
{
	int rc;
	char *status;

	switch (code) {
	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> */
		status = "2.0.0";
		rc = EX_OK;
		break;
	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 */
		status = "4.0.0";
		rc = EX_TEMPFAIL;
		break;
	case 455: /* ESMTP parameter failure */
		status = "5.5.4";
		rc = EX_USAGE;
		break;
	case 501: /* Syntax error in parameters or arguments */
		status = "5.5.2";
		rc = EX_USAGE;
		break;
	case 500: /* Syntax error, command unrecognized */
	case 502: /* Command not implemented */
		status = "5.5.1";
		rc = EX_PROTOCOL;
		break;
	case 503: /* Bad sequence of commands */
		status = "5.5.0";
		rc = EX_TEMPFAIL;
		break;
	case 504: /* Command parameter not implemented */
		status = "5.5.4";
		rc = EX_PROTOCOL;
		break;
	case 550: /* Requested action not taken: mailbox unavailable */
		status = "5.1.1 (bad destination mailbox)";
		rc = EX_NOUSER;
		break;
	case 551: /* User not local; please try <forward-path> */
		status = "5.1.6 (mailbox has moved)";
		rc = EX_NOUSER;
		break;
	case 552: /* Requested mail action aborted: exceeded storage allocation */
		status = "5.2.3 (message length exceeds administrative limit)";
		rc = EX_UNAVAILABLE;
		break;
	case 553: /* Requested action not taken: mailbox name not allowed */
		status = "5.1.3 (bad destination mailbox address syntax)";
		rc = EX_NOUSER;
		break;
	case 554:
		status = "5.1.1 (No acceptable recipients given)";
		rc = EX_NOUSER;
		break;
	case 555: /* Unknown MAIL FROM:<>/RCPT TO:<> parameter */
		status = "5.5.4 (invalid parameters)";
		rc = EX_USAGE;
		break;
	default:
		switch (code/100) {
		case 2:
		case 3:
			status = "2.0.0 (generic ok)";
			rc = EX_OK;
			break;
		case 4:
			status = "4.0.0 (generic temporary failure)";
			rc = EX_TEMPFAIL;
			break;
		case 5:
			status = "5.0.0 (generic permanent failure)";
			rc = EX_UNAVAILABLE;
			break;
		default:
			status = "5.5.0 (generic protocol failure)";
			rc = EX_TEMPFAIL;
			break;
		}
		break;
	}
	*statusp = status;
	return rc;
}


int smtp_sync(smtpfp,verboselog,r)
	FILE *smtpfp[2], *verboselog;
	int r;
{
	char *s, *eof, *eol, *s2;
	int infd = FILENO(smtpfp[0]);
	int idx = 0, code;
	int rc = EX_OK, len;
	int some_ok = 0;
	int err;
	char buf[8192];

	alarm(timeout);
	fflush(smtpfp[1]);
	alarm(0);

	s = pipebuf;
	eol = s;
	for (idx = 0; idx < pipeindex; ++idx) {
      rescan_line_0: /* processed some continuation line */
	  s = eol;
      rescan_line:   /* Got additional input */
	  eof = pipebuf + pipebufsize;
	  for (eol = s; eol < eof; ++eol)
	    if (*eol == '\n') break;
	  if (*eol == '\n') {
	    ++eol; /* points to char AFTER the newline */
	    if (debug && logfp)
	      fprintf(logfp,"%s#\t(pipebufsize=%d, s=%d, eol=%d)\n",
		      logtag(), pipebufsize,s-pipebuf,eol-pipebuf);
	  } else { /* No newline.. Read more.. */
	    int en;
	    err = select_sleep(infd, timeout);
	    en = errno;
	    if (debug && logfp)
	      fprintf(logfp,"%s#\tselect_sleep(%d,%d); rc=%d\n",
		      logtag(),infd,timeout,err);
	    if (err < 0) {
	      if (logfp)
		fprintf(logfp,"%s#\tTimeout (%d sec) while waiting responces from remote (errno=%d)\n",logtag(),timeout,en);
	      if (verboselog)
		fprintf(verboselog,"Timeout (%d sec) while waiting responces from remote\n",timeout);
	      break;
	    }
	    len = read(infd,buf,sizeof(buf));
	    err = errno;
	    
	    if (len < 0) {
	      /* Some error ?? How come ?
		 We have select() confirmed input! */
	      if (err == EINTR)
		goto rescan_line;
	      /* XX: what to do with the error ? */
	      if (logfp)
		fprintf(logfp,"%s#\tRemote gave error %d (%s) while %d responces missing\n",
			logtag(), err, strerror(err), pipeindex - 1 - idx);
	      if (verboselog)
		fprintf(logfp,"Remote gave error %d (%s) while %d responces missing\n",
			err, strerror(err), pipeindex - 1 - idx);
	      break;
	    } else if (len == 0) {
	      /* The remote hung up! */
	      if (logfp)
		fprintf(logfp,"%s#\tRemote hung up on us while %d responces missing\n",
			logtag(), pipeindex - idx);
	      if (verboselog)
		fprintf(logfp,"Remote hung up on us while %d responces missing\n",
			pipeindex - idx);
	      break;
	    } else {
	      /* more data for processing.. munch munch.. */
	      eof = pipebuf;
	      pipebuf = (char*)realloc(pipebuf,pipebufsize+len+1);
	      if (pipebuf != eof) {
		/* Block changed.. Reset those pointers */
		long offsetchange = pipebuf - eof;
		eol += offsetchange;
		s   += offsetchange;
	      }
	      memcpy(pipebuf+pipebufsize,buf,len);
	      pipebufsize += len;
	      goto rescan_line;
	    }
	  } /* ... globbing more input */
	  if (logfp != NULL) {
	    char *p = eol-1; /* The '\n' at the end of the line */
	    if (p[-1] == '\r') --p;
	    *p = 0;
	    if (debug)
	      putc('\n',logfp);
	    fprintf(logfp,
		    "%sr\t%s\n", logtag(), s);
	  }
	  if (verboselog)
	    fprintf(verboselog,"%s\n",s);

	  if (s[0] >= '0' && s[0] <= '9' &&
	      s[1] >= '0' && s[1] <= '9' &&
	      s[2] >= '0' && s[2] <= '9' &&
	      s[3] == ' ') {
	    code = atoi(s);

	    /* We have a 'terminal' line */

	  } else /* it is 'continuation line', or some such, ignore */
	    goto rescan_line_0;

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

	  if (code >= 400) {
	    /* Errors */
	    char *status;
	    rc = EX_TEMPFAIL; /* Hmm.. */
	    if (idx == 0) {
	      /* MAIL FROM:<*>: ... */
	      rc = code_to_status(code, &status);
	    } else if (idx == pipeindex-2) {
	      /* DATA: 354/ 451/554/ 500/501/503/421 */
	      rc = code_to_status(code, &status);
	    } else {
	      /* RCPT TO:<*>: 250/251/ 550/551/552/553/450/451/452/455/ 500/501/503/421 */
	      rc = code_to_status(code, &status);
	    }
	    /* Diagnose the errors, we report successes AFTER the DATA phase.. */
	    notarystatsave(s,NULL);
	    notaryreport(pipercpts[idx]->addr->user,FAILED,
			 status,NULL);
	    diagnostic(pipercpts[idx],rc, 0, "%s", remotemsg);
	  } else {
	    /* Ok results */
	    pipercpts[idx]->status = EX_OK;
	    if (idx != 0 && idx != pipeindex-2) {
	      some_ok = 1;
if (verboselog) fprintf(verboselog,"[Some OK - code=%d, idx=%d, pipeindex=%d]\n",code,idx,pipeindex-1);
	    }
	  }
	  if (pipecmds[idx] != NULL)
	    free(pipecmds[idx]);
	  else
	    if (logfp) fprintf(logfp,"%s#\t[Freeing free object at pipecmds[%d] ??]\n",logtag(),idx);
	  pipecmds[idx] = NULL;
	} /* for(..; idx < pipeindex ; ..) */
	for (; idx < pipeindex; ++idx) {
	  if (pipecmds[idx] != NULL)
	    free(pipecmds[idx]);
	  else
	    if (logfp) fprintf(logfp,"%s#\t[Freeing free object at pipecmds[%d] ??]\n",logtag(),idx);
	  pipecmds[idx] = NULL;
	}
	if (some_ok) rc = EX_OK;
	return rc;
}


/* VARARGS */
int
smtpwrite(smtpfp, verboselog, saverpt, strbuf, syncrp)
	FILE *smtpfp[2], *verboselog;
	int saverpt;
	char *strbuf;
	struct rcpt *syncrp;
{
	register char *s, *cp;
	int i, response, r, infd, outfd, rc;
	char *se;
	char *status = NULL;
	char *diags  = NULL;
	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!) */
	if (!syncrp) {
	  /* fflush(smtpfp[0]); */
	  fflush(smtpfp[1]);
	}
	infd = FILENO(smtpfp[0]);
	outfd = FILENO(smtpfp[1]);

	if (syncrp) {
	  if (pipespace <= pipeindex) {
	    pipespace += 8;
	    if (pipecmds == NULL) {
	      pipecmds  = (char**)emalloc(pipespace * sizeof(char*));
	      pipercpts = (struct rcpt **)emalloc(pipespace *
						  sizeof(struct rcpt*));
	    } else {
	      pipecmds = (char**)erealloc(pipecmds,
					  pipespace * sizeof(char*));
	      pipercpts = (struct rcpt **)erealloc(pipercpts,
						   pipespace *
						   sizeof(struct rcpt*));
	    }
	  }
	  pipecmds[pipeindex] = strdup(strbuf);
	  pipercpts[pipeindex] = syncrp;
	  ++pipeindex;

	  /* Read and buffer all so far accumulated responces.. */
	  fd_nonblockingmode(infd);
	  for (;;) {
	    r = read(infd, buf, sizeof buf);
	    if (r == 0) break; /* EOF ??!! */
	    if (r < 0)
	      break;
	    if (pipebuf == NULL) {
	      pipebuf = (char*)emalloc(r+1);
	      pipebufsize = 0;
	    } else
	      pipebuf = (char*)erealloc(pipebuf,pipebufsize+r+2);
	    memcpy(pipebuf+pipebufsize,buf,r);
	    pipebufsize += r;
	  }
	  fd_blockingmode(infd);
	  /* Continue the processing... */
	} /* ... end of if(syncrp) */

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

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

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

	  if (syncrp) {
	    r = fwrite(buf,1,len,smtpfp[1]);
	    err = (r != len);
	  } else {
	    r = write(outfd, buf, len);
	    err = (r != len);
	  }
	  if (err) {
	    if (gotalarm) {
	      strcpy(remotemsg, "Timeout on cmd write");
	      notaryreport(NULL,FAILED,"5.4.2 (timeout on cmd write)",
			   "smtp; 500 (timeout on cmd write)");
	    } else
	      {
		se = strerror(errno);
		sprintf(remotemsg, "smtp; 500 (write to server error: %s)", se);
		notaryreport(NULL,FAILED,"5.4.2 (write to server, err)",remotemsg);
	      }
	    if (verboselog)
	      fprintf(verboselog,"%s\n",remotemsg);
	    fclose(smtpfp[0]); smtpfp[0] = NULL;
	    fclose(smtpfp[1]); smtpfp[1] = NULL;
	    smtp_isopen = 0;
	    if (logfp)
	      fprintf(logfp, "%s#\t(closed SMTP channel - timeout on smtpwrite())\n", logtag());
	    return EX_TEMPFAIL;
	  } else if (r != len) {
	    sprintf(remotemsg, "smtp; 500 (SMTP cmd write failure: Only wrote %d of %d bytes!)", r, len);
	    notaryreport(NULL,FAILED,"5.4.2 (SMTP cmd partial write failure)",remotemsg);
	    if (verboselog)
	      fprintf(verboselog,"%s\n",remotemsg);
	    fclose(smtpfp[0]); smtpfp[0] = NULL;
	    fclose(smtpfp[1]); smtpfp[1] = NULL;
	    smtp_isopen = 0;
	    if (logfp)
	      fprintf(logfp, "%s#\t(closed SMTP channel - second timeout on smtpwrite() )\n", logtag());
	    return EX_TEMPFAIL;
	  }
	  if (logfp != NULL) {
	    if (dflag) abort();
	    fprintf(logfp, "%sw\t%s\n", logtag(), strbuf);
	    if (!syncrp)
	      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, "%s#\tAttempting to read reply\n",logtag());
	}
	if (statusreport && strbuf != NULL) {
	  report("%s", strbuf);
	}
	cp = buf;

	if (syncrp) {
	  /* Read and buffer all so far accumulated responces.. */
	  fd_nonblockingmode(infd);
	  for (;;) {
	    r = read(infd, buf, sizeof buf);
	    if (r == 0) break; /* EOF ??!! */
	    if (r < 0)
	      break;
	    if (pipebuf == NULL) {
	      pipebuf = (char*)emalloc(r+1);
	      pipebufsize = 0;
	    } else
	      pipebuf = (char*)erealloc(pipebuf,pipebufsize+r+1);
	    memcpy(pipebuf+pipebufsize,buf,r);
	    pipebufsize += r;
	  }
	  fd_blockingmode(infd);
	  /* .. return to the caller, we have no status except
	        write timeout.. */
	  return EX_OK;
	}

	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,
			  "%sr\t%s\n", logtag(), buf);
		}

		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;
	      }
	    }
	  } else if (r == -1) {
	    if (gotalarm) {
	      if (strbuf == NULL)
		sprintf(remotemsg,
			"smtp; 466 (Timeout on initial SMTP responce read)");
	      else
		sprintf(remotemsg,
			"smtp; 466 (Timeout on SMTP responce read, Cmd: %s)",
			strbuf);
	      notaryreport(NULL,FAILED,"5.4.2 (smtp transaction read timeout)",remotemsg);
	    } else {
		se = strerror(errno);
		if (strbuf == NULL)
		  sprintf(remotemsg,
			  "smtp; 500 (Error on initial SMTP responce read: %s)",se);

		else
		  sprintf(remotemsg,
			  "smtp; 500 (Error on SMTP responce read: %s, Cmd: %s)",
			  se, strbuf);
		notaryreport(NULL,FAILED,"5.4.2 (smtp transaction read timeout)",remotemsg);
	      }
	    dflag = 0;
	    firstmx = 0;
	    if (verboselog)
	      fprintf(verboselog,"%s\n",remotemsg);
	    fclose(smtpfp[0]); smtpfp[0] = NULL;
	    fclose(smtpfp[1]); smtpfp[1] = NULL;
	    smtp_isopen = 0;
	    if (logfp)
	      fprintf(logfp, "%s#\t(closed SMTP channel - bad responce on smtpwrite() )\n", logtag());
	    return EX_TEMPFAIL;
	  } else {
	    /* read() returned 0 .. usually meaning EOF .. */
	    sprintf(remotemsg, "smtp; 500 (Server hung up on us! Cmd: %s)",
		    strbuf == NULL ? "(null cmd)" : strbuf);
	    notaryreport(NULL,FAILED,"5.4.2 (server hung-up on us)",remotemsg);
	    dflag = 0;
	    if (verboselog)
	      fprintf(verboselog,"%s\n",remotemsg);
	    fclose(smtpfp[0]); smtpfp[0] = NULL;
	    fclose(smtpfp[1]); smtpfp[1] = NULL;
	    smtp_isopen = 0;
	    if (logfp)
	      fprintf(logfp, "%s#\t(closed SMTP channel - hangup on smtpwrite() )\n", logtag());
	    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,"smtp; 500 (SMTP Response overran input buffer!)");
	  notaryreport(NULL,"X-BUG","5.5.0 (SMTP-responce overran input buffer!)",remotemsg);
	  dflag = 0;
	  if (verboselog)
	    fprintf(verboselog,"%s\n",remotemsg);
	  fclose(smtpfp[0]); smtpfp[0] = NULL;
	  fclose(smtpfp[1]); smtpfp[1] = NULL;
	  smtp_isopen = 0;
	  if (logfp)
	    fprintf(logfp, "%s#\t(closed SMTP channel - responce overrun on smtpwrite() )\n", logtag());
	  return EX_TEMPFAIL;
	}
	*--cp = '\0';	/* kill the LF */
	if (cp - buf < 5) {
	  sprintf(remotemsg, "smtp; 500 (SMTP response '%s' unexpected!)", buf);
	  notaryreport(NULL,"X-BUG","5.5.0 (SMTP responce unexpected)",remotemsg);
	  dflag = 0;
	  if (verboselog)
	    fprintf(verboselog,"%s\n",remotemsg);
	  fclose(smtpfp[0]); smtpfp[0] = NULL;
	  fclose(smtpfp[1]); smtpfp[1] = NULL;
	  smtp_isopen = 0;
	  if (logfp)
	    fprintf(logfp, "%s#\t(closed SMTP channel - unexpected responce on smtpwrite() )\n", logtag());
	  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, "%sr\t%s%c%s\n", logtag(), buf, ch, &buf[i+1]);
	}
	buf[i] = ch;

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

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

	
	dflag = 0;

	rc = code_to_status(response, &status);

	if (saverpt)
	  notarystatsave(buf,status);
	return rc;}


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, NULL);
	within_ehlo = 0;
	return rc;
}

/*
 * In theory, this should modify the command that ps shows for this process.
 * This is known not to 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);
	if (smtp_isopen)
	  sprintf(buf, "-%s ", remotehost);
	else
	  sprintf(buf, "-[%s] ", remotehost);
#ifdef	notdef
	if (logfp)
	  sprintf(buf+strlen(buf), ">>%s ", logfile);
	strcat(buf, "# ");
#endif
	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);
}

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;

	notary_setwtt(NULL);

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


	qlen = res_mkquery(QUERY, host, C_IN, T_MX, NULL, 0, NULL,
			   (void*)&qbuf, sizeof qbuf);
	if (qlen < 0) {
	  fprintf(stderr, "res_mkquery failed\n");
	  sprintf(remotemsg,
		  "smtp; 466 (Internal: res_mkquery failed on host: %s)",host);
	  notaryreport(NULL,FAILED,"5.4.3 (DNS-failure)",remotemsg);
	  return EX_SOFTWARE;
	}
	n = res_send((void*)&qbuf, qlen, (void*)&answer, sizeof answer);
	if (n < 0) {
	  sprintf(remotemsg,
		  "smtp; 466 (Internal: res_send failed on host: %s)",host);
	  notaryreport(NULL,FAILED,"5.4.3 (DNS-failure)",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, "smtp; 500 (Bind: no such domain: %s)", host);
	    notaryreport(NULL,FAILED,"5.4.4 (DNS lookup report)",remotemsg);
	    return EX_NOHOST;
	  case SERVFAIL:
	    sprintf(remotemsg, "smtp; 500 (Bind: server failure: %s)", host);
	    notaryreport(NULL,FAILED,"5.4.4 (DNS lookup report)",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, "smtp; 500 (Bind: unsupported query: %s)", host);
	    notaryreport(NULL,FAILED,"5.4.4 (DNS lookup report)",remotemsg);
	    return EX_NOPERM;
	  }
	  sprintf(remotemsg, "smtp; 500 (Bind: unknown error, MX info unavailable: %s)", host);
	  notaryreport(NULL,FAILED,"5.4.4 (DNS lookup report)",remotemsg);
	  return EX_UNAVAILABLE;
	}
	nmx = *mxcntp;
	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, (void*)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,
			    (void*)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, (void*)buf, sizeof buf);
	  if (n < 0)
	    break;
	  cp += n;

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

	  if ((mx[nmx].host = (msgdata *)emalloc(strlen((void*)buf)+1)) == NULL) {
	    fprintf(stderr, "Out of virtual memory!\n");
	    exit(EX_OSERR);
	  }
	  strcpy((char*)mx[nmx].host, (char*)buf);
	  /* [mea] Canonicalize this target & get A records.. */
	  ++nmx;
	  *mxcntp = 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;
	}

	/* 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;
	    *mxcntp = 0;
	    return EX_OK;
	  }
	}
#ifdef	RFC974
	/* discard MX's that do not support SMTP service */
	if (checkwks)
	  for (n = 0; n < nmx; ++n) {
	    if (mx[n].host == NULL)
	      continue;
	    strcpy((char*)buf, (char*)mx[n].host);
	    /* It is an MX, it CAN'T have CNAME ! */
	    if (!getrrtype((void*)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;
	  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, "smtp; 500 (Bind: MX host does not support SMTP: %s)", host);
	  notaryreport(NULL,FAILED,"5.4.4 (DNS lookup report)",remotemsg);
	  return EX_UNAVAILABLE;
	}
	nmx = n;
	*mxcntp = nmx;
	/* 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;
	    }
	  }
	}

	/* Randomize the order of those of same preferrence [mea]
	   This will do some sort of load-balancing on large sites
	   which have multiple mail-servers at the same priority.  */
	for (i = 0, maxpref = mx[0].pref; i < nmx; ++i) {
	  /* They are in numerical order, now we can
	     detect when a new preferrence group steps in */
	  j = i;
	  while (j < nmx && maxpref == mx[j].pref) ++j;
	  if ((j-i) > 1) {
	    /* At least two of the same preferrence */
	    int k, len = j-i;
	    for (k = 0; k < len; ++k) {
	      int l = ranny(len);
	      mxtemp = mx[i+k];
	      mx[i+k] = mx[i+l];
	      mx[i+l] = mxtemp;
	    }
	  }
	  /* Processed that preference, now next */
	  i = j-1;
	  if (j < nmx)		/* If within the array */
	    maxpref = mx[j].pref;
	}
#if 0
	if (verboselog) {
	  fprintf(verboselog,"Target has following MXes (cnt=%d):\n",nmx);
	  for (i=0; i<nmx; ++i)
	    fprintf(verboselog,"  MX %3d %s\n", mx[i].pref, mx[i].host);
	}
#endif
	mx[nmx].host = NULL;
	mxcount = nmx;
	*mxcntp = nmx;
	return EX_OK;
}


/*
 * 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, MAXFORWARDERS, 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	HAVE_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 /* !HAVE_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 /* !HAVE_MMAP */
}

void
notarystatsave(smtpline,status)
char *smtpline, *status;
{
	int len = strlen(smtpline)+8+6;
#ifdef USE_ALLOCA
	char *str = alloca(len);
#else
	char *str = malloc(len);
#endif
	char *s = str;

	strcpy(s,"smtp; ");
	s += 6;

	*s++ = *smtpline++;
	*s++ = *smtpline++;
	*s++ = *smtpline++;
	*s++ = ' ';
	if (len >= 11) {
	  *s++ = '(';
	  while (*smtpline) {
	    switch (*smtpline) {
	    case '(':
	        *s++ = '[';
		break;
	    case ')':
		*s++ = ']';
		break;
	    default:
		*s++ = *smtpline;
	    }
	    ++smtpline;
	  }
	  *s++ = ')';
	}
	*s = 0;
	notaryreport(NULL,NULL,status,str);
#ifndef USE_ALLOCA
	free(str);
#endif
}


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