/*
 *	Copyright 1988 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 *	Copyright 1994-2000,2002-2003 by Matti Aarnio -- MIME processings,
 *	etc.
 */

#define DefCharset "ISO-8859-1"

#include "hostenv.h"
#include <ctype.h>
#include <pwd.h>
#include "zmsignal.h"
#include <sysexits.h>
#include <sys/file.h>
#include <sys/param.h>
#include <sys/stat.h>
#include "ta.h"
#include "mail.h"
#include "zmalloc.h"
#include "zsyslog.h"
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <errno.h>

#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif

#include "libz.h"
#include "libc.h"

#include "shmmib.h"

#ifdef HAVE_SYS_WAIT_H /* POSIX.1 compatible */
# include <sys/wait.h>
#else /* Not POSIX.1 compatible, lets fake it.. */
extern int wait();
#endif

#ifndef WEXITSTATUS
# define WEXITSTATUS(s) (((s) >> 8) & 0377)
#endif
#ifndef WSIGNALSTATUS
# define WSIGNALSTATUS(s) ((s) & 0177)
#endif

#ifndef	SEEK_SET
#define	SEEK_SET 0
#endif	/* !SEEK_SET */

/* as in: SKIPWHILE(isascii,cp) */
#define SKIPSPACE(Y) while (*Y == ' ' || *Y == '\t' || *Y == '\n') ++Y
#define SKIPTEXT(Y)  while (*Y && *Y != ' ' && *Y != '\t' && *Y != '\n') ++Y

#define	FROM_	"From "

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

char uucpname[MAXHOSTNAMELEN+1];

const char	*progname;
int	readalready = 0;	/* does buffer contain valid message data? */
int	mimeqpnarrow = 0;	/* Can't send TAB thru without MIME-QP */
FILE	*logfp   = NULL;
int	maxwidth = 0;
int	can_8bit = 0;		/* Can do 8-bit stuff! */
int	decode_qp = 0;
int	keep_header8 = 0;	/* Don't do "MIME-2" to the headers */

int	D_alloc = 0;		/* Memory debugging */
const char *defcharset;


extern RETSIGTYPE sigpipe();
int gotsigpipe = 0;

RETSIGTYPE
sigpipe(sig)
int sig;
{
	gotsigpipe = 1;
	SIGNAL_HANDLE(SIGPIPE, sigpipe);
}

struct maildesc {
	char	*name;
	long	flags;
	char	*command;
#define MD_ARGVMAX 20
	char	*argv[MD_ARGVMAX];
};


extern RETSIGTYPE wantout();
#ifndef MALLOC_TRACE
extern univptr_t emalloc();
extern univptr_t erealloc();
#endif
extern char *optarg;
extern int optind;
extern int getmyuucpname();
extern struct maildesc *readsmcf __((char *file, char *mailer));
extern void prversion();
extern void process __((struct ctldesc *dp, struct maildesc *mp, FILE *verboselog));
extern void deliver __((struct ctldesc *dp, struct maildesc *mp, struct rcpt *startrp, struct rcpt *endrp, FILE *verboselog));
extern int appendlet __((struct ctldesc *dp, struct maildesc *mp, FILE *fp, FILE *verboselog, int convertmode));
extern int writebuf __((struct maildesc *mp, FILE *fp, const char *buf, int len));
extern time_t time();
#ifndef strchr
extern char *strchr(), *strrchr();
#endif

static int zsfsetfd(fp, fd)
     Sfio_t *fp;
     int fd;
{
  /* This is *NOT* the SFIO's sfsetfd() -- we do no sfsync() at any point.. */
  fp->file = fd;
  return fd;
}

static void
decodeXtext(mfp,xtext)
	FILE *mfp;
	const char *xtext;
{
	for (;*xtext;++xtext) {
	  if (*xtext == '+') {
	    int c = '?';
	    sscanf(xtext+1,"%02X",&c);
	    fputc(c, mfp);
	    if (*xtext) ++xtext;
	    if (*xtext) ++xtext;
	  } else
	    fputc(*xtext, mfp);
	}
}


static void MIBcountCleanup __((void))
{
	MIBMtaEntry->tasmcm.TaProcCountG -= 1;
}

static void SM_MIB_diag(rc)
     const int rc;
{
  switch (rc) {
  case EX_OK:
    /* OK */
    MIBMtaEntry->tasmcm.TaRcptsOk ++;
    break;
  case EX_TEMPFAIL:
  case EX_IOERR:
  case EX_OSERR:
  case EX_CANTCREAT:
  case EX_SOFTWARE:
  case EX_DEFERALL:
    /* DEFER */
    MIBMtaEntry->tasmcm.TaRcptsRetry ++;
    break;
  case EX_NOPERM:
  case EX_PROTOCOL:
  case EX_USAGE:
  case EX_NOUSER:
  case EX_NOHOST:
  case EX_UNAVAILABLE:
  default:
    /* FAIL */
    MIBMtaEntry->tasmcm.TaRcptsFail ++;
    break;
  }
}



extern int check_7bit_cleanness __((struct ctldesc *dp));

extern int writemimeline __(( struct maildesc *mp, FILE *fp, const char *buf, int len, int convertmode));

#define	MO_FFROMFLAG		0x00001
#define	MO_RFROMFLAG		0x00002
#define	MO_NORESETUID		0x00004
#define	MO_STRIPQUOTES		0x00008
#define	MO_MANYUSERS		0x00010
#define	MO_RETURNPATH		0x00020
#define	MO_UNIXFROM		0x00040
#define	MO_HIDDENDOT		0x00080 /* SMTP dot-duplication */
#define	MO_ESCAPEFROM		0x00100
#define	MO_STRIPHIBIT		0x00200
#define	MO_REMOTEFROM		0x00400
#define	MO_CRLF			0x00800
#define MO_BSMTP		0x01000 /* BSMTP-wrapping -- with HIDDENDOT.. */
#define MO_BESMTP		0x02000 /* Extended BSMTP -- SIZE+8BITMIME    */
#define MO_BEDSMTP		0x04000 /* EBSMTP + DSN */
#define MO_BEBSMTP		0x04000 /* ESMTP + DELIVERBY */
#define MO_WANTSDATE		0x08000 /* Wants "Date:" -header */
#define MO_WANTSFROM		0x10000 /* Wants "From:" -header */
#define MO_BSMTPHELO		0x20000 /* Add HELO/EHLO to the BSMTP */

#define MO_XENVELOPES		0x40000 /* Write various X-Envelope-*: headers to mesage */
#define MO_XORCPT		0x80000 /* Write X-Orcpt : header to message */

struct exmapinfo {
	int	origstatus;
	const char *statusmsg;
	int	newstatus;
	const char *dsnstatus;
	const char *dsndiags;
};
struct exmapinfo exmap[] = {
  { EX_USAGE,	"command line usage error",	EX_TEMPFAIL,	"5.3.0", "x-local; 500 (Command line usage error)"	},
  { EX_DATAERR,	"data format error",		EX_DATAERR,	"5.3.0", "x-local; 500 (Data format error)"	},
  { EX_NOINPUT,	"cannot open input",		EX_TEMPFAIL,	"5.3.0", "x-local; 530 (Cannot open input)"	},
  { EX_NOUSER,	"addressee unknown",		EX_NOUSER,	"5.1.1", "x-local; 521 (No such target user)"	},
  { EX_NOHOST,	"host name unknown",		EX_NOHOST,	"5.3.0", "x-local; 500 (Target host unknown)"	},
  { EX_UNAVAILABLE, "service unavailable",	EX_UNAVAILABLE,	"5.3.0", "x-local; 500 (Service unavailable)"	},
  { EX_SOFTWARE, "internal software error",	EX_TEMPFAIL,	"5.3.0", "x-local; 500 (Internal software error)"	},
  { EX_OSERR,	"system error",			EX_TEMPFAIL,	"5.3.0", "x-local; 500 (System error)"	},
  { EX_OSFILE,	"critical OS file missing",	EX_TEMPFAIL,	"5.3.0", "x-local; 500 (Critical OS file missing)"	},
  { EX_CANTCREAT, "can't create output file",	EX_TEMPFAIL,	"5.2.1", "x-local; 500 (Can't create output file)"	},
  { EX_IOERR,	"input/output error",		EX_TEMPFAIL,	"5.2.2", "x-local; 500 (Input/Output error)"	},
  { EX_TEMPFAIL, "temporary failure",		EX_TEMPFAIL,	"5.3.0", "x-local; 500 (Temporary failure)"	},
  { EX_PROTOCOL, "remote error in protocol",	EX_TEMPFAIL,	"5.3.0", "x-local; 500 (Remote error in protocol)"	},
  { EX_NOPERM,	"permission denied",		EX_NOPERM,	"5.2.0", "x-local; 520 (Permission denied)"	},
  { 0,		NULL,				EX_TEMPFAIL,	NULL,	NULL	}
};

char myhostname[MAXHOSTNAMELEN+1];

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

#ifndef	MAXPATHLEN
#define	MAXPATHLEN 1024
#endif	/* MAXPATHLEN */

int
main(argc, argv)
	int argc;
	char *argv[];
{
	char file[MAXPATHLEN+1];
	char *channel, *host = NULL, *mailer, *cf;
	struct ctldesc *dp;
	int errflg, c;
	struct maildesc *mp;
	RETSIGTYPE (*oldsig)();
	FILE *verboselog = NULL;

	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(SIGQUIT, SIG_IGN, oldsig);
	if (oldsig != SIG_IGN)
	  SIGNAL_HANDLE(SIGQUIT, wantout);
	SIGNAL_HANDLESAVE(SIGHUP, SIG_IGN, oldsig);
	if (oldsig != SIG_IGN)
	  SIGNAL_HANDLE(SIGHUP, wantout);

	SIGNAL_HANDLE(SIGPIPE, sigpipe);

#if defined(HAVE_LOCALE_H) && defined(HAVE_SETLOCALE) && defined(LC_ALL)
	setlocale(LC_ALL, "C");
#endif

	if (getenv("ZCONFIG")) readzenv(getenv("ZCONFIG"));

	Z_SHM_MIB_Attach(1); /* we don't care if it succeeds or fails.. */

	MIBMtaEntry->tasmcm.TaProcessStarts += 1;
	MIBMtaEntry->tasmcm.TaProcCountG    += 1;

	atexit(MIBcountCleanup);


	if ((progname = strrchr(argv[0], '/')) == NULL)
	  progname = argv[0];
	else
	  ++progname;
	errflg = 0;
	host = channel = NULL;
	myhostname[0] = 0;
	cf = NULL;
	while (1) {
	  c = getopt(argc, argv, "f:c:h:HQvVw:8");
	  if (c == EOF)
	    break;

	  switch (c) {
	  case 'f':
	    cf = optarg;
	    break;
	  case 'c':		/* remote hostname */
	    channel = optarg;
	    break;
	  case 'h':		/* remote hostname */
	    host = strdup(optarg);
	    break;
	  case 'Q':
	    mimeqpnarrow = 1;
	    break;
	  case 'V':
	    prversion("sm");
	    exit(0);
	    break;
	  case 'v':
	    verboselog = stdout;
	    break;
	  case '8':
	    can_8bit = decode_qp = 1;
	    break;
	  case 'H':
	    keep_header8 = 1;
	    break;
	  case 'w':
	    maxwidth = atoi(optarg);
	    if (maxwidth < 0)
	      maxwidth = 0;
	  default:
	    ++errflg;
	    break;
	  }
	}
	if (errflg || optind != argc - 1 || host == channel) {
	  fprintf(stderr,
		  "Usage: %s [-V][-v][-H][-8 | -Q][-f cfgfile][-w maxwidth][-c channel | -h host] mailer\n", argv[0]);
	  exit(EX_USAGE);
	}
	mailer = argv[optind];

	/* We need this later on .. */
	zopenlog("sm", LOG_PID, LOG_MAIL);

	defcharset = getzenv("DEFCHARSET");
	if (!defcharset)
	  defcharset = DefCharset;

	if ((mp = readsmcf(cf, mailer)) == NULL)
	  exit(EX_OSFILE);
	if (mp->flags & MO_REMOTEFROM)
	  getmyuucpname(uucpname, sizeof uucpname);	/*XX*/
	while (!getout) {
	  char *s;

	  /* Input:
	       spool/file/name [ \t host.info ] \n
	   */

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

	  if (fgets(file, sizeof file, stdin) == NULL)
	    break;
	  if (strchr(file, '\n') == NULL) break; /* No ending '\n' !  Must
						    have been partial input! */
	  if (strcmp(file, "#idle\n") == 0) {
	    MIBMtaEntry->tasmcm.TaIdleStates += 1;
	    continue; /* Ah well, we can stay idle.. */
	  }
	  if (emptyline(file, sizeof file))
	    break;

	  MIBMtaEntry->tasmcm.TaMessages += 1;

	  s = strchr(file,'\t');
	  if (s != NULL) {
	    if (host) free(host);
	    host = strdup(s+1);
	    *s = 0;
	  }

	  ctlsticky(NULL, NULL, NULL); /* reset */
	  dp = ctlopen(file, channel, host, &getout, ctlsticky, NULL);
	  if (dp == NULL) {
	    printf("#resync %s\n",file);
	    fflush(stdout);
	    continue;
	  }
	  if (verboselog != stdout && verboselog != NULL) {
	    fclose(verboselog);
	    verboselog = NULL;
	  }
	  if (verboselog != stdout && dp->verbose) {
	    verboselog = fopen(dp->verbose,"a");
	    if (verboselog) setbuf(verboselog,NULL);
	  }
	  process(dp, mp, verboselog);
	  ctlclose(dp);
	}
	if (verboselog != NULL)
	  fclose(verboselog);
	if (logfp != NULL)
	  fclose(logfp);
	exit(EX_OK);
	/* NOTREACHED */
	return 0;
}

void
process(dp, mp, verboselog)
	struct ctldesc *dp;
	struct maildesc *mp;
	FILE *verboselog;
{
	struct rcpt *rp, *rphead;

	readalready = 0; /* ignore any previous message data cache */

	if (mp->flags & MO_MANYUSERS) {
	  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) {
	      deliver(dp, mp, rphead, rp->next, verboselog);
	      rphead = rp->next;
	    }
	  }
	} else {
	  for (rp = dp->recipients; rp != NULL; rp = rp->next) {
	    deliver(dp, mp, rp, rp->next, verboselog);
	  }
	}
}

/*
 * deliver - deliver the letter in to user's mail box.  Return
 *	     errors and requests for further processing in the structure
 */

void
deliver(dp, mp, startrp, endrp, verboselog)
	struct ctldesc *dp;
	struct maildesc *mp;
	struct rcpt *startrp, *endrp;
	FILE *verboselog;
{
	struct rcpt *rp = NULL;
	struct exmapinfo *exp;
	const char *exs, *exd;
	int i, j, pid = 0, in[2], out[2], ii = 0;
	unsigned int avsize;
	FILE *tafp = NULL, *errfp = NULL;
	char *cp = NULL, buf[BUFSIZ], buf2[BUFSIZ];
	char *ws = NULL, *we = NULL;
	const char *ds, **av, *s;
	int status;
	int content_kind, conversion_prohibited, ascii_clean = 0;
	time_t now;
	char *timestring;
	CONVERTMODE convertmode = _CONVERT_NONE;
	char *lineendseq = "\n";

	MIBMtaEntry->tasmcm.TaDeliveryStarts += 1;

	now = time((time_t *)0);
	timestring = ctime(&now);
	*(timestring+strlen(timestring)-1) = '\0';

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

	i = 0;
	avsize = 5;
	av = (const char **)emalloc(sizeof av[0] * avsize);
	av[i++] = mp->argv[0];
	if (mp->flags & MO_FFROMFLAG) {
	  av[i++] = "-f";
	  if (strcmp(startrp->addr->link->channel,"error")==0)
	    av[i++] = "<>";
	  else
	    av[i++] = startrp->addr->link->user;
	} else if (mp->flags & MO_RFROMFLAG) {
	  av[i++] = "-r";
	  if (strcmp(startrp->addr->link->channel,"error")==0)
	    av[i++] = "<>";
	  else
	    av[i++] = startrp->addr->link->user;
	}
	for (j = 0; mp->argv[j] != NULL; ++j) {
	  while (i+2 >= avsize) {
	    avsize *= 2;
	    av = (const char **)erealloc((char *)av,
					 sizeof av[0] * avsize);
	  }
	  if (strchr(mp->argv[j], '$') == NULL) {
	    if (j > 0)
	      av[i++] = mp->argv[j];
	    continue;
	  }
	  rp = startrp;
	  do {

	    /* Even argv[0] MAY have $-expansions.. */
	    ii = i; if (j == 0) ii = 0;

	    while (i+2 >= avsize) {
	      avsize <<= 1;
	      av = (const char **)erealloc((char *)av,
					   sizeof av[0] * avsize);
	    }
	    ws = buf;
	    we = buf + sizeof(buf);
	    for (cp = mp->argv[j]; *cp != '\0'; ++cp) {
	      if (*cp == '$') {
		switch (*++cp) {
		case 'g':
		  ds = rp->addr->link->user;
		  break;
		case 'h':
		  ds = rp->addr->host;
		  break;
		case 'u':
		  ds = rp->addr->user;
		  rp = rp->next;
		  break;
		case 'U':
		  strncpy(buf2, rp->addr->user, sizeof(buf2));
		  buf2[sizeof(buf2)-1] = 0;
		  strlower(buf2);
		  ds = buf2;
		  rp = rp->next;
		  break;
		case '{':
		  s = ++cp;
		  while (*cp != 0 && *cp != '}') ++cp;
		  if (*cp != 0) {
		    *cp = 0;
		    ds = getzenv(s);
		    *cp = '}';
		  } else {
		    ds = getzenv(s);
		  }
		  break;
		default:
		  ds = NULL;
		  break;
		}
		if (ds == NULL || *ds == '\0') {
		  char msg[BUFSIZ];

		  sprintf(msg,
			  "Null value for $%c (%%s) (msgfile: %s)!",
			  *cp, dp->msgfile);
		  warning(msg, mp->name);
		} else {
		  int len = strlen(ds);
		  if (ws + len >= we)
		    break; /* D'uh :-( */
		  memcpy(ws, ds, len+1);
		  ws += len;
		}
	      } else
		if (ws < we)
		  *ws++ = *cp;
	    }
	    if (ws < we)
	      *ws = '\0';
	    else
	      we[-1] = '\0'; /* Trunk in all cases */
	    /* not worth freeing this stuff */
	    av[ii] = strdup(buf);
	    if (j > 0)
	      ++i;
	  } while (rp != startrp && rp != endrp);
	  /* End of: "do {" */
	}
	/* End of: "for (j = ...) {" */
	av[i] = NULL;

	gotsigpipe = 0;

	/* now we can fork off and run the command... */
	if (pipe(out) < 0) {
	  for (rp = startrp; rp != endrp; rp = rp->next) {
	    notaryreport(rp->addr->user,"failed",
			 "5.3.0 (Out of system resources, pipe creation failed)",
			 "x-local; 500 (pipe creation error, out of system resources ?)");
	    diagnostic(verboselog, rp, EX_OSERR, 0,
		       "cannot create pipe from \"%s\"",
		       mp->command);
	    SM_MIB_diag(EX_OSERR);
	  }
	  return;
	}
	if (pipe(in) < 0) {
	  for (rp = startrp; rp != endrp; rp = rp->next) {
	    notaryreport(rp->addr->user,"failed",
			 "5.3.0 (Out of system resources, pipe creation failed)",
			 "x-local; 500 (pipe creation error, out of system resources ?)");
	    diagnostic(verboselog, rp, EX_OSERR, 0,
		       "cannot create pipe to \"%s\"",
		       mp->command);
	    SM_MIB_diag(EX_OSERR);
	  }
	  return;
	}
	if (verboselog) {
	  const char **p = av;
	  fprintf(verboselog,"To run UID=%d GID=%d ARGV[] =",
		  (int)getuid(), (int)getgid());
	  for ( ;*p != NULL; ++p) {
	    fprintf(verboselog," '%s'", *p);
	  }
	  fprintf(verboselog,"\n");
	  fflush(verboselog);
	}
	if ((pid = fork()) == 0) { /* child, run the command */
	  if (!(mp->flags & MO_NORESETUID)) {
	    /* struct passwd *pw = getpwuid(); */
	    setuid(getuid());
	  }
	  if (in[1] > 0) {
	    /* its stdout and stderr is the pipe, its stdin is our tafp */
	    if (out[0] > 0) dup2(out[0], 0);
	    if (in[1]  > 1) dup2(in[1], 1);
	    dup2(1, 2);
	    if (in[0] > 2) close(in[0]);
	    if (in[1] > 2) close(in[1]);
	    if (out[0] > 2) close(out[0]);
	    if (out[1] > 2) close(out[1]);
	  } else {
	    close(in[0]);
	    close(out[1]);
	    /* its stdout and stderr is the pipe, its stdin is our tafp */
	    close(0);
	    close(1);
	    close(2);
	    dup2(out[0], 0);
	    close(out[0]);
	    dup2(in[1], 1);
	    dup2(in[1], 2);
	    close(in[1]);
	  }
	  execv(mp->command, (char**) av);
	  _exit(254);
	} else if (pid < 0) {	/* couldn't fork, complain */
	  for (rp = startrp; rp != endrp; rp = rp->next) {
	    notaryreport(rp->addr->user,"failed",
			 "5.3.0 (Out of system resources, fork failed)",
			 "x-local; 500 (fork failure, out of system resources ?)");
	    diagnostic(verboselog, rp, EX_OSERR, 0, "cannot fork");
	    SM_MIB_diag(EX_OSERR);
	  }
	  return;
	}
	close(out[0]); /* child ends.. */
	close(in[1]);
	tafp = fdopen(out[1], "w"); /* parent ends .. */
	errfp = fdopen(in[0], "r");
	/* read any messages from its stdout/err on in[0] */

	if (verboselog) {
	  fprintf(verboselog,"%s\n\t", mp->command);
	  for (i = 0; av[i] != NULL; ++i)
	    fprintf(verboselog,"%s ", av[i]);
	  fprintf(verboselog,"\n");
	}

	free((char *)av);
	/* ... having forked and set up the pipe, we quickly continue */

	/* BSMTP et.al. envelope formation here! */

	if (mp->flags & MO_BSMTP) {

	  if (mp->flags & MO_BSMTPHELO) {
	    if (mp->flags & MO_BESMTP)
	      fprintf(tafp,"EHLO %s",myhostname);
	    else
	      fprintf(tafp,"HELO %s",myhostname);
	    if (mp->flags & MO_CRLF) putc('\r',tafp);
	    putc('\n',tafp);
	  }

	  if (strcmp(startrp->addr->link->channel,"error")==0)
	    fprintf(tafp,"MAIL From:<>");
	  else
	    fprintf(tafp,"MAIL From:<%s>", startrp->addr->link->user);
	  if (mp->flags & MO_BESMTP) {
	      fprintf(tafp," SIZE=%ld",startrp->desc->msgsizeestimate);
	    if (can_8bit)
	      fprintf(tafp," BODY=8BITMIME");
	  }
	  if (mp->flags & MO_BEDSMTP) {
	    if (startrp->desc->envid != NULL)
	      fprintf(tafp," ENVID=%s",startrp->desc->envid);
	    if (startrp->desc->dsnretmode != NULL)
	      fprintf(tafp, " RET=%s", startrp->desc->dsnretmode);
	  }
	  if (mp->flags & MO_CRLF) lineendseq = "\r\n";
	  fputs(lineendseq, tafp);
	  for (rp = startrp; rp != endrp; rp = rp->next) {
	    fprintf(tafp,"RCPT TO:<%s>",rp->addr->user);
	    /* if (mp->flags & MO_BESMTP) { } */
	    if (mp->flags & MO_BEDSMTP) {
	      if (rp->notifyflgs) {
		char *s = "";
		fprintf(tafp," NOTIFY=");
		if (rp->notifyflgs & _DSN_NOTIFY_NEVER) {
		  fprintf(tafp,"NEVER");
		}
		if (rp->notifyflgs & _DSN_NOTIFY_SUCCESS) {
		  fprintf(tafp,"SUCCESS");
		  s = ",";
		}
		if (rp->notifyflgs & _DSN_NOTIFY_FAILURE) {
		  fprintf(tafp,"%sFAILURE",s);
		  s = ",";
		}
		if (rp->notifyflgs & _DSN_NOTIFY_DELAY) {
		  fprintf(tafp,"%sDELAY",s);
		}
	      }
	      if (rp->orcpt)
		fprintf(tafp," ORCPT=%s",rp->orcpt);
	    }
	    if (mp->flags & MO_BEBSMTP) {
	      if (rp->deliverby) {
		fprintf(tafp," BY=%ld;", (long)(rp->deliverby - now));
		if (rp->deliverbyflgs & _DELIVERBY_R) fputc('R',tafp);
		if (rp->deliverbyflgs & _DELIVERBY_N) fputc('N',tafp);
		if (rp->deliverbyflgs & _DELIVERBY_T) fputc('T',tafp);
	      }
	    }
	    fputs(lineendseq, tafp);
	  }

	  fprintf(tafp,"DATA");
	  fputs(lineendseq, tafp);
	}

	/* Now continue with inside stuff -- well, normal UUCP stuff */

	if (mp->flags & (MO_UNIXFROM|MO_REMOTEFROM)) {
	  const char *uu = startrp->addr->link->user;

	  if (strcmp(startrp->addr->link->channel,"error")==0)
	    uu = "<>";
	  fprintf(tafp, "%s%s %s", FROM_, uu, timestring);
	  if (mp->flags & MO_REMOTEFROM)
	    fprintf(tafp, " remote from %s", uucpname);
	  if (verboselog) {
	    fprintf(verboselog, "%s%s %s", FROM_, uu, timestring);
	    if (mp->flags & MO_REMOTEFROM)
	      fprintf(verboselog, " remote from %s", uucpname);
	    putc('\n',verboselog);
	  }
	  putc('\n', tafp);
	}

	conversion_prohibited = check_conv_prohibit(startrp);

	/* 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 (!conversion_prohibited && ascii_clean && content_kind == 8) {
	  if (downgrade_charset(startrp, verboselog))
	    content_kind = 7;
	}

	convertmode = _CONVERT_NONE;
	if (!conversion_prohibited) {
	  switch (content_kind) {
	  case 0:		/* No MIME headers defined */
	    if (!can_8bit && !ascii_clean) {
	      convertmode = _CONVERT_UNKNOWN;
	      downgrade_headers(startrp, convertmode, verboselog);
	    }
	    break;
	  case 2:		/* MIME, but no C-T-E: ? */
	  case 1:		/* MIME BASE64 ? some MIME anyway.. */
	  case 7:		/* 7BIT */
	    convertmode = _CONVERT_NONE;
	    break;
	  case 8:		/* 8BIT */
	    if (!can_8bit && !ascii_clean) {
	      convertmode = _CONVERT_QP;
	      if (!downgrade_headers(startrp, convertmode, verboselog))
		convertmode = _CONVERT_NONE;
	    }
	    break;
	  case 9:		/* QUOTED-PRINTABLE */
	    if (decode_qp) {
	      /* Force(d) to decode Q-P while transfer.. */
	      convertmode = _CONVERT_8BIT;
	      /*  UPGRADE TO 8BIT !  */
	      if (!qp_to_8bit(startrp))
		convertmode = _CONVERT_NONE;
	      content_kind = 10;
	      ascii_clean = 0;
	    }
	    break;
	  default:		/* ?? should not happen.. */
	    break;
	  }
	  
	  if (!keep_header8 && headers_need_mime2(startrp)) {
	    headers_to_mime2(startrp,defcharset,verboselog);
	  }
	}

	/* Snub the "Return-Path:" header, if it exists.. */
	if (1) {
	  char **hdrs;
	  do {
	    hdrs = has_header(startrp,"Return-Path:");
	    if (hdrs) delete_header(startrp, hdrs);
	  } while (hdrs);
	}

	/* Snub stuff that we add later below.. */

	if (mp->flags & MO_XENVELOPES) {
	  char **hdrs;

	  do {
	    hdrs = has_header(startrp,"X-Envelope-To:");
	    if (hdrs) delete_header(startrp, hdrs);
	  } while (hdrs);

	  do {
	    hdrs = has_header(startrp,"X-Envid:");
	    if (hdrs) delete_header(rp,hdrs);
	  } while (hdrs);

	  do {
	    hdrs = has_header(startrp,"Envelope-Id:");
	    if (hdrs) delete_header(rp,hdrs);
	  } while (hdrs);

	}

/* PERT-NB X-Orcpt if O option */
	if (mp->flags & (MO_XORCPT|MO_XENVELOPES)) {
	  char **hdrs;
	  do {
	    hdrs = has_header(startrp,"X-Orcpt:");
	    if (hdrs) delete_header(startrp, hdrs);
	  } while (hdrs);
	  do { /* RFC 2298 section  2.3 */
	    hdrs = has_header(startrp,"Original-Recipient:");
	    if (hdrs) delete_header(startrp, hdrs);
	  } while (hdrs);
	}
/* PERT-NB END */


	/* Write headers: */

	header_received_for_clause(startrp, 0, verboselog);

	fwriteheaders(startrp, tafp, lineendseq, convertmode, maxwidth, NULL);
	if (verboselog)
	  fwriteheaders(startrp, verboselog, "\n", convertmode, maxwidth, NULL);

	/*
	 * NOTE: Following header writers make sense only for SINGLE
	 *       RECIPIENT DELIVERIES!
	 *
	 */

	if (mp->flags & MO_XENVELOPES) {
	  if (dp->envid) {
	    fprintf(tafp, "Envelope-Id: ");
	    decodeXtext(tafp, dp->envid);
	    fputs(lineendseq, tafp);

	    if (verboselog) {
	      fprintf(verboselog, "Envelope-Id: ");
	      decodeXtext(verboselog, dp->envid);
	      fputs("\n", verboselog);
	    }
	  }
	}
	if (mp->flags & (MO_XORCPT|MO_XENVELOPES)) {
	  /* Put out a sequence of  X-Envelope-To: and
	     Original-Recipient:  headers -- in order.. */
	  for (rp = startrp; rp != endrp; rp = rp->next) {

	    const char *uu = rp->addr->user;
	    if (strcmp(rp->addr->link->channel,"error")==0)
	      uu = "";
	    fprintf(tafp, "X-Envelope-To: <%s> (uid %s)%s",
		    uu, rp->addr->misc, lineendseq);
	    if (rp->orcpt) {
	      /* RFC 2298: section 2.3 */
	      fprintf(tafp, "Original-Recipient: ");
	      decodeXtext(tafp, rp->orcpt);
	      fputs(lineendseq, tafp);
	    }
	    if (verboselog) {
	      fprintf(verboselog, "X-Envelope-To: <%s> (uid %s)\n",
		    uu, rp->addr->misc);
	      if (rp->orcpt) {
		/* RFC 2298: section 2.3 */
		fprintf(verboselog, "Original-Recipient: ");
		decodeXtext(verboselog, rp->orcpt);
		fputs("\n", verboselog);
	      }
	    }
	  }
	}

	if (mp->flags & MO_RETURNPATH) {
	  const char *uu = startrp->addr->link->user;
	  if (strcmp(startrp->addr->link->channel,"error")==0)
	    uu = "";

	  fprintf(tafp, "Return-Path: <%s>%s", uu, lineendseq);
	}


	/* Header-Body separator: */

	fputs(lineendseq, tafp);

	if (verboselog) fprintf(verboselog, "\n");


	/* Append message body itself */

	i = appendlet(dp, mp, tafp, verboselog, convertmode);
	if (i != EX_OK && !gotsigpipe) {
	  for (rp = startrp; rp != endrp; rp = rp->next) {
	    notaryreport(rp->addr->user,"failed",
			 /* Could indicate: 4.3.1 - mail system full ?? */
			 "5.3.0 (Write to target failed for some reason)",
			 "x-local; 500 (Write to target failed for some reason)");
	    diagnostic(verboselog, rp, i, 0, "write error");
	    SM_MIB_diag(i);
	  }
	  /* just to make sure nothing will get delivered */
	  kill(pid, SIGTERM);
	  sleep(1);
	  kill(pid, SIGKILL);
	  wait(NULL);
	  fclose(tafp); /* FP/pipe cleanups */
	  fclose(errfp);
	  return;
	}

	if (mp->flags & MO_BSMTP) {
	  fputs(lineendseq, tafp);
	}
	
	fclose(tafp);
	close(out[1]);	/* paranoia */
	if (fgets(buf, sizeof buf, errfp) == NULL)
	  buf[0] = '\0';
	else if ((cp = strchr(buf, '\n')) != NULL)
	  *cp = '\0';
	fclose(errfp);
	close(in[0]);	/* more paranoia */
	cp = buf + strlen(buf);

	exd = exs = NULL;
	pid = wait(&status);
	if (WSIGNALSTATUS(status) != 0) {
	  if (cp != buf)
	    *cp++ = ' ';
	  sprintf(cp, "[signal %d", WSIGNALSTATUS(status));
	  if (status&0200)
	    strcat(cp, " (Core dumped)");
	  strcat(cp, "]");
	  i = EX_TEMPFAIL;
	  exd = "x-local; 500 (failed on signal)";
	  exs = "5.3.0";
	} else if (WEXITSTATUS(status) == 0
#if EX_OK != 0
		   || WEXITSTATUS(status) == EX_OK
#endif
		   ) {
	  i = EX_OK;
	} else {
	  i = WEXITSTATUS(status);
	  s = NULL;
	  for (exp = & exmap[0]; exp->origstatus != 0; ++exp)
	    if (exp->origstatus == i) {
	      s = exp->statusmsg;
	      i = exp->newstatus;
	      exs = exp->dsnstatus;
	      exd = exp->dsndiags;
	      break;
	    }
	  sprintf(cp, "[exit status %d/%d", WEXITSTATUS(status), i);
	  if (s)
	    sprintf(cp+strlen(cp), " (%s)", s);
	  /* sprintf(cp+strlen(cp), " of command: %s", mp->command); */
	  strcat(cp, "]");
	}
	for (rp = startrp; rp != endrp; rp = rp->next) {
	  if (i == EX_OK)
	    notaryreport(rp->addr->user, "relayed",
			 "2.5.0", "smtp;250 (Delivered)");
	  else
	    notaryreport(rp->addr->user, "failed", exs, exd);
	  diagnostic(verboselog, rp, i, 0, "%s", buf);
	  SM_MIB_diag(i);
	}
	/* XX: still need to deal with MO_STRIPQUOTES */
}

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

int
appendlet(dp, mp, fp, verboselog, convertmode)
	struct ctldesc *dp;
	struct maildesc *mp;
	FILE *fp;
	FILE *verboselog;
	int 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;
      int lastch, rc;
      register int bufferfull;
      int mfd = dp->msgfd;

      writebuf(mp, fp, (char *)NULL, 0);  /* magic initialization */

      lastch = -1;
      if (convertmode == _CONVERT_NONE) {

	if (ta_use_mmap <= 0) {

	  /* can we use cache of message body data */
	  if (readalready > 0) {
	    lastch = dp->let_buffer[readalready-1];
	    if (writebuf(mp, fp, dp->let_buffer, readalready) != readalready)
	      return EX_IOERR;
	    if (lastch != '\n')
	      if (writebuf(mp, fp, "\n", 1) != 1)
		return EX_IOERR;
	    return EX_OK;
	  }

	  bufferfull = 0;
	  readalready = 0;
	  lastch = -256;
	  lseek(mfd, dp->msgbodyoffset, SEEK_SET);
	  for (;;) {
	    i = read(mfd, (void*)(dp->let_buffer), dp->let_buffer_size);
	    if (i == 0) break; /* EOF */
	    if (i < 0 && (errno == EINTR || errno == EAGAIN))
	      continue;
	    if (i < 0)
	      return EX_IOERR;
	    lastch = dp->let_buffer[i-1];
	    if (writebuf(mp, fp, dp->let_buffer, i) != i)
	      return EX_IOERR;
	    readalready = i;
	    bufferfull++;
	  }
	  if (lastch != '\n')
	    if (writebuf(mp, fp, "\n", 1) != 1)
	      return EX_IOERR;
	  if (bufferfull > 1)	/* not all in memory, need to reread */
	    readalready = 0;

	} else { /* mmap()ed message buffer */

	  const char *p = dp->let_buffer + dp->msgbodyoffset;
	  i = dp->let_end - p;
	  if (i < 0)
	    return EX_IOERR;
	  lastch = dp->let_end[-1];
	  if (writebuf(mp, fp, p, i) != i)
	    return EX_IOERR;
	  if (lastch != '\n')
	    if (writebuf(mp, fp, "\n", 1) != 1)
	      return EX_IOERR;
	}

	rc = EX_OK;

      } else {

	/* convertmode something else, than _CONVERT_NONE */
	/* Various esoteric conversion modes..
	   We are better to feed writemimeline() with LINES
	   instead of blocks of data.. */

	writemimeline(mp, fp, (char *)NULL, 0, 0); /* reset */

	/*
	  if(verboselog) fprintf(verboselog,
	  "sm: Convert mode: %d, fd=%d, fdoffset=%d, bodyoffset=%d\n",
	  convertmode, mfd, (int)lseek(mfd, (off_t)0, SEEK_CUR),
	  dp->msgbodyoffset);
	*/
	
	/* we are assuming to be positioned properly
	   at the start of the message body */
	lastch = -1;
	i = 0;

	if (ta_use_mmap <= 0) {
	  /* Locally read, line by line */

	  char sfio_buf[16*1024];
	  Sfio_t *mfp = NULL;

	  mfp = sfnew(NULL, sfio_buf, sizeof(sfio_buf), mfd, SF_READ);
	  sfseek(mfp, dp->msgbodyoffset, SEEK_SET);
	  readalready = 0;

	  for (;;) {
	    i = csfgets((void*)(dp->let_buffer), dp->let_buffer_size, mfp);
	    if (i == EOF)
	      break;
	    lastch = dp->let_buffer[i-1];
	    /* It MAY be malformed -- if it has a BUFSIZ length
	       line in it, IT CAN'T BE MIME  :-/		*/
	    /* Ok, write the line */
	    if (writemimeline(mp, fp, dp->let_buffer, i, convertmode) != i)
	      return EX_IOERR;
	  }
	  if (mfp) {
	    if (i == EOF && !sfeof(mfp) && !sferror(mfp)) {
	      rc = EX_IOERR;
	    }
	    zsfsetfd(mfp, -1);
	    sfclose(mfp);
	  }
	  rc = EX_OK;

	} else {
	  /* MMAP()ED buffer */
	  const char *s = dp->let_buffer + dp->msgbodyoffset;

	  for (;;) {
	    const char *s2 = s;
	    i = 0;
	    if (s >= dp->let_end) break;	/* "EOF" */
	    while (s2 < dp->let_end && *s2 != '\n')
	      ++s2, ++i;
	    if ((lastch = *s2) == '\n')
	      ++s2, ++i;
	    /* Ok, write the line */
	    if (writemimeline(mp, fp, s, i, convertmode) != i)
	      return EX_IOERR;
	    s = s2;
	  }
	}
	rc = EX_OK;
      }
     
      /* we must make sure the last thing we transmit is a CRLF sequence */
      if (lastch != '\n')
	writebuf(mp, fp, "\n", 1);
      
      /* Failure in that last CRLF writing does not
	 affect our return code -- should it ?? */
      
      return rc;
}

/*
 * Writebuf() is like write(), except all '\n' are converted to "\r\n"
 * (CRLF), and the sequence "\n.\n" is converted to "\r\n..\r\n".
 */

int
writebuf(mp, fp, buf, len)
	struct maildesc *mp;
	FILE *fp;
	const char *buf;
	int len;
{
	register const char *cp;
	register int n;
	int tlen;
	register char expect;
	static char save = '\0';
	static char frombuf[8];
	static char *fromp;

	if (buf == NULL) {	/* magic initialization */
	  save = '.';
	  frombuf[0] = 0;
	  fromp = frombuf;
	  return 0;
	}
	expect = save;
	for (cp = buf, n = len, tlen = 0; n > 0; --n, ++cp) {
	  int c = (*cp) & 0xFF;
	  if (mp->flags & MO_STRIPHIBIT)
	    c &= 0x7F;
	  ++tlen;
	  if (c == '\n') {
	    frombuf[0] = 0;
	    fromp = frombuf;
	    if (expect == '\n' && (mp->flags & MO_HIDDENDOT))
	      /* "\n.\n" sequence */
	      if (putc('.', fp) == EOF) { tlen = -1; break; }
	    if (mp->flags & MO_CRLF)
	      if (putc('\r', fp) == EOF) { tlen = -1; break; }
	    if (putc(c,fp) == EOF) { tlen = -1; break; }
	    expect = '.';
	  } else if (expect != '\0') {
	    if (expect == '.') {
	      if ((mp->flags & MO_ESCAPEFROM) && c == 'F')
		expect = 'F';
	      else if (c == '.' && (mp->flags & MO_HIDDENDOT)) {
		if (putc('.', fp) == EOF || putc('.', fp) == EOF)
		  { tlen = -1; break; }
		expect = '\0';
		continue;
	      } else {
		if (putc(c, fp) == EOF)
		  { tlen = -1; break; }
		expect = '\0';
		continue;
	      }
	    }
	    if (c == expect) {
	      *fromp++ = c;
	      *fromp   = 0;
	      switch (expect) {
		case 'F':	expect = 'r'; break;
	        case 'r':	expect = 'o'; break;
		case 'o':	expect = 'm'; break;
		case 'm':	expect = ' '; break;
		case ' ':
		  /* Write the separator, and the word.. */
		  if (fwrite(">From ", 6, 1, fp) == 0)
		    { tlen = -1; break; }
		  /* anticipate future instances */
		  expect = '\0';
		  break;
	      }
	    } else {
	      expect = '\0';
	      fromp = frombuf;
	      while (*fromp) {
		if (putc(*fromp,fp) == EOF)
		  { tlen = -1; break; }
		++fromp;
	      }
	      frombuf[0] = 0;
	      if (putc(c,fp) == EOF)
		{ tlen = -1; break; }
	    }
	  } else {
	    /* expect == 0 */
	      if (putc(c,fp) == EOF)
		{ tlen = -1; break; }
	  }
	}
	save = expect;

	return tlen;
}

int
writemimeline(mp, fp, buf, len, convertmode)
	struct maildesc *mp;
	FILE *fp;
	const char *buf;
	int len, 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 const char *cp;
	register int n;
	static int  column;
	register int qp_conv;
	register int qp_chrs = 0;

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

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

	for (cp = buf, n = len; n > 0; --n, ++cp) {
	  int c = (*cp) & 0xFF;
	  ++column;

	  if (qp_conv) {
	    /* ENCODE to QUOTED-PRINTABLE ... */ 
	    if (column > 70 && c != '\n') {
	      putc('=',fp);
	      if (mp->flags & MO_CRLF)
		putc('\r', fp);
	      putc('\n',fp);
	      column = 0;
	    }

	    if (column == 0 && (mp->flags & MO_HIDDENDOT) && c == '.') {
	      /* Duplicate the line initial dot.. */
	      if (putc(c,fp)==EOF) return EOF;
	    } else if (column == 0  &&  (mp->flags & MO_ESCAPEFROM)  &&
		       c == 'F'  &&  n >= 4  &&  strncmp(cp,"From",4)==0) {
	      /* We Q-P encode the leading 'F'.. */
	      if (fputs("=46",fp) != 3) return EOF;
	      column += 2;
	    } else if ((n < 3 || mimeqpnarrow) && c != '\n' &&
		       (c <= 32 || c > 126 || c == '=')) {
	      /* Downgrade it by translating it to Quoted-Printable.. */
	      /* Translate also trailing spaces/TABs */
	      if (fprintf(fp,"=%02X",c) != 3) return EOF;
	      column += 2;
	    } else if (c != '\n' && c != '\t' &&
		       (c < 32 || c > 126 || c == '=')) {
	      /* Downgrade it by translating it to Quoted-Printable.. */
	      /* SPACE and TAB are left untranslated */
	      if (fprintf(fp,"=%02X",c) != 3) return EOF;
	      column += 2;
	      buf = cp;
	    } else if (c == '\n') { /* This is most likely the LAST char */
	      if (mp->flags & MO_CRLF)
		if (putc('\r', fp) == EOF) return EOF;
	      if (putc(c,fp) == EOF) return EOF;
	      column = -1;
	    } else {
	      if (putc(c, fp) == EOF) return EOF;
	    }
	  } else if (convertmode == _CONVERT_8BIT) {
	    /* DECODE from QUOTED-PRINTABLE text.. */
	    static int qp_val = 0;
	    if (!qp_chrs && c == '=') { /* Q-P -prefix */
	      qp_chrs = 2;
	      qp_val = 0;
	      continue;
	    } else if (qp_chrs) {
	      --column;
	      if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
		break; /* Done with it, it was soft end-of-line */
	      if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') ||
		  (c >= 'a' && c <= 'f')) {
		/* A HEX digit ? QP char coming up ? */
		if (c >= 'a') c -= ('a' - 'A');
		if (c >= 'A') c -= ('A' - '9' -1);
		qp_val <<= 4;
		qp_val |= (c & 0x0F);
		if (--qp_chrs)
		  continue;	/* Not yet last.. */
		else
		  c = qp_val;	/* The second (=last) hex digit */
	      } else
		qp_chrs = 0;	/* While in this mode, NOT QP-hex-digit! */
	    } /* Ok, decoded possible Q-P chars.  Now normal processing.. */

	    if (column == 0 && c == '.' && (mp->flags & MO_HIDDENDOT)) {
	      if (putc(c,fp)==EOF) return EOF;
	    } else if (column == 0 && (mp->flags & MO_ESCAPEFROM) &&
		       c == 'F' && strncmp(cp+1,"rom",3)==0) {
	      if (putc('>',fp)==EOF) return EOF;
	      ++column;
	    } else if (c == '\n') {
	      if (mp->flags & MO_CRLF)
		if (putc('\r',fp)==EOF) return EOF;
	      column = -1;
	    }
	    /* And output the char.. */
	    if (putc(c,fp)==EOF) return EOF;
	  } else
	    abort(); /* WOO! We should not be called for '_CONVERT_NONE'! */
	}

	if (feof(fp) || ferror(fp)) return EOF;
	return len;
}


struct maildesc *
readsmcf(file, mailer)
	char *file, *mailer;
{
	char *entry, buf[BUFSIZ];
	unsigned char *cp;
	FILE *fp;
	int i;
	static struct maildesc m;

	if (file == NULL) {
	  const char *mailshare = getzenv("MAILSHARE");

	  if (mailshare == NULL)
	    mailshare = MAILSHARE;
	  sprintf(buf, "%s/%s.conf", mailshare, progname);
	  file = buf;
	}
	if ((fp = fopen(file, "r")) == NULL) {
	  fprintf(stderr, "%s: cannot open ", progname);
	  perror(file);
	  exit(EX_OSFILE);
	}
	entry = NULL;
	cp    = NULL;
	while (fgets(buf, sizeof buf, fp) != NULL) {
	  if (buf[0] == '#' || buf[0] == '\n')
	    continue;
	  if ((cp = emalloc(strlen(buf)+1)) == NULL) {
	    fprintf(stderr, "%s: Out of Virtual Memory!\n",
		    progname);
	    exit(EX_OSERR);
	  }
	  entry = (char*)cp;
	  strcpy(entry, buf);
	  SKIPTEXT(cp);
	  if (isascii(*cp) && isspace(*cp)) {
	    if (*cp == '\n') {
	      fprintf(stderr, "%s: %s: bad entry: %s",
		      progname, file, entry);
	    } else
	      *cp = '\0';
	  } else {
	    fprintf(stderr, "%s: %s: bad entry: %s",
		    progname, file, entry);
	  }
	  if (strcmp(entry, mailer) == 0)
	    break;
	  free(entry);
	  entry = NULL;
	}
	fclose(fp);
	if (entry == NULL)
		return NULL;
	m.name = entry;
	m.flags = MO_UNIXFROM;
	++cp;
	SKIPSPACE(cp);
	/* process mailer option flags */
	for (;*cp && *cp != ' ' && *cp != '\t' && *cp != '\n'; ++cp) {
	  int no = 0;
	  switch (*cp) {
	  case '7':	m.flags |= MO_STRIPHIBIT;	break;
	  case '8':	can_8bit = 1;			break;
	  case '9':	decode_qp = 1;			break;
	  case 'A': no=*cp; break;	/* arpanet-compatibility */
	  case 'b':	m.flags |= (MO_BSMTP|MO_HIDDENDOT); break;
	  case 'B':	if (m.flags & MO_BESMTP) /* -BB */
			    m.flags |= MO_BEDSMTP;
			else
			    m.flags |= MO_BESMTP|MO_BSMTP|MO_HIDDENDOT;
			break;
	  case 'C': no=*cp; break; /* canonicalize remote hostnames */
	  case 'D':		/* this mailer wants a Date: line */
	    	m.flags |= MO_WANTSDATE;	break;
	  case 'e':	m.flags |= MO_XENVELOPES;	break;
/* PERT-NB O option for X-Orcpt */
	  case 'O':	m.flags |= MO_XORCPT;		break;
	  case 'E':	m.flags |= MO_ESCAPEFROM;	break;
	  case 'f':	m.flags |= MO_FFROMFLAG;	break;
	  case 'F':		/* this mailer wants a From: line */
	    	m.flags |= MO_WANTSFROM;	break;
	  case 'h': no=*cp; break; /* preserve upper case in host names */
	  case 'H':     m.flags |= MO_BSMTPHELO;	break;
	  case 'I': no=*cp; break; /* talking to a clone of I */
	  case 'l': no=*cp; break; /* this is a local mailer */
	  case 'L': no=*cp; break; /* limit line length */
	  case 'm':	m.flags |= MO_MANYUSERS;	break;
	  case 'M': no=*cp; break; /* this mailer wants a Message-Id: line */
	  case 'n':	m.flags &= ~MO_UNIXFROM;	break;
	  case 'p': no=*cp; break; /* use SMTP return path */
	  case 'P':	m.flags |= MO_RETURNPATH;	break;
	  case 'r':	m.flags |= MO_RFROMFLAG;	break;
	  case 'R':	m.flags |= MO_CRLF;		break;
	  case 's':	m.flags |= MO_STRIPQUOTES;	break;
	  case 'S':	m.flags |= MO_NORESETUID;	break;
	  case 'u': no=*cp; break; /* preserve upper case in user names */
	  case 'U':	m.flags |= MO_REMOTEFROM;	break;
	  case 'x': no=*cp; break; /* this mailer wants a Full-Name: line */
	  case 'X':	m.flags |= MO_HIDDENDOT;	break;

	  case '-':	break;	/* ignore */
	  default:
	    fprintf(stderr,
		    "%s: unknown sendmail mailer option '%c'\n",
		    progname, *cp);
	    break;
	  }
	  if (no) {
	    fprintf(stderr,
		    "%s: the '%c' sendmail mailer option does not make sense in this environment\n",
		    progname,no);
	  }
	}
	SKIPSPACE(cp);
	m.command = (char*) cp;
	SKIPTEXT(cp);
	if ((char*)cp == m.command) {
		fprintf(stderr,"%s: bad entry for %s\n",progname, m.name);
		return NULL;
	}
	*cp++ = '\0';
	if (*m.command != '/') {
	  char *nmc;
	  const char *mailbin = getzenv("MAILBIN");

	  if (mailbin == NULL)
	    mailbin = MAILBIN;
		
	  nmc = emalloc(strlen(mailbin)+1+strlen(m.command)+1);
	  sprintf(nmc, "%s/%s", mailbin, m.command);
	  m.command = nmc;
	}
	SKIPSPACE(cp);
	i = 0;
	while (isascii(*cp) && !isspace(*cp) && i < MD_ARGVMAX) {
	  if (*cp == '\0')
	    break;
	  m.argv[i++] = (char*) cp;
	  SKIPTEXT(cp);
	  if (*cp) {
	    *cp++ = '\0';
	    SKIPSPACE(cp);
	  }
	}
	if (i == 0) {
	  fprintf(stderr,
		  "%s: bad command for %s\n", progname, m.name);
	  return NULL;
	}
	m.argv[i] = NULL;
	return &m;
}

/* When data is clean 7-BIT, return 1.. (zero == non-clean) */
int
check_7bit_cleanness(dp)
struct ctldesc *dp;
{
      if (ta_use_mmap > 0) {

	/* With MMAP()ed spool file it is sweet and simple.. */

	register const char *s = dp->let_buffer + dp->msgbodyoffset;
	while (s < dp->let_end)
	  if (128 & *s) {
    /*
       if (verboselog)
       fprintf(verboselog,
       "check_7bit_cleanness() non-clean byte on offset %d, str=\"%-8s\"\n",
       s-(dp->let_buffer + dp->msgbodyoffset), s);
     */
	    return 0; /* Not clean ! */
	  }
	  else ++s;

      } else {

	/* Without MMAP() we work... */

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

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

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

	for (;;) {
	  i = read(mfd, (void*)(dp->let_buffer), dp->let_buffer_size);
	  if (i == 0) break; /* EOF */
	  if (i < 0) {
	    /* ERROR ?!?!? */
	    if (errno == EINTR || errno == EAGAIN)
	      continue;
	    readalready = 0;
	    lseek(mfd, dp->msgbodyoffset, SEEK_SET);
	    return 0;
	  }
	  lastwasnl = (dp->let_buffer[i-1] == '\n');
	  readalready = i;
	  bufferfull++;
	  for (i=0; i < readalready; ++i)
	    if (128 & (dp->let_buffer[i])) {
	      lseek(mfd, dp->msgbodyoffset, SEEK_SET);
	      /* We probably have not read everything of the file! */
	      readalready = 0;
	      return 0;		/* Not clean ! */
	    }
	}
	/* Got to EOF, and still it is clean 7-BIT! */
	lseek(mfd, dp->msgbodyoffset, SEEK_SET);

	if (bufferfull > 1)	/* not all in memory, need to reread */
	  readalready = 0;

      }
      return 1;
}


syntax highlighted by Code2HTML, v. 0.9.1