/*
 *	Copyright 1988 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 */

#include "hostenv.h"
#include <stdio.h>
#include <ctype.h>
#include <pwd.h>
#include <signal.h>
#include <sysexits.h>
#include <varargs.h>
#include <sys/file.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <mail.h>
#include "malloc.h"
#include "ta.h"
#ifdef	USE_UNIONWAIT
#include <sys/wait.h>
#endif	/* USE_UNIONWAIT */

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

/* as in: SKIPWHILE(isascii,cp) */
#define	SKIPWHILE(X,Y)	while (*Y != '\0' && isascii(*Y) && X(*Y)) { ++Y; }

#define	FROM_	"From "

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

char uucpname[MAXHOSTNAMELEN+1];

char	*progname;
int	readalready = 0;	/* does buffer contain valid message data? */
int	can_8bit = 0;		/* Can do 8-bit stuff! */
FILE	*verboselog = NULL;
FILE	*logfp   = NULL;
int	maxwidth = 0;

int	D_alloc = 0;		/* Memory debugging */

#ifndef MALLOC_TRACE
extern univptr_t emalloc();
extern univptr_t erealloc();
#endif
extern void warning();
extern char *optarg;
extern int optind;
extern int getmyuucpname(), emptyline();
extern struct maildesc *readsmcf();
extern void prversion(), process();
extern void process(), deliver();
extern int appendlet();
extern int writebuf();
extern time_t time();
extern char *strchr(), *strrchr();

extern int  cte_check __((struct rcpt *rp));
extern void downgrade_headers __((struct rcpt *rp, int downgrade, FILE *verboselog));
extern void check_7bit_downgrade __((int mailfd, int *flag_ptr));

struct maildesc {
	char	*name;
	short	flags;
	char	*command;
	char	*argv[20];
};

#define	MO_FFROMFLAG		   01
#define	MO_RFROMFLAG		   02
#define	MO_NORESETUID		   04
#define	MO_STRIPQUOTES		  010
#define	MO_MANYUSERS		  020
#define	MO_RETURNPATH		  040
#define	MO_UNIXFROM		 0100
#define	MO_HIDDENDOT		 0200
#define	MO_ESCAPEFROM		 0400
#define	MO_STRIPHIBIT		01000
#define	MO_REMOTEFROM		02000
#define	MO_CRLF			04000

struct exmapinfo {
	int	origstatus;
	char	*statusmsg;
	int	newstatus;
} exmap[] = {
{	EX_USAGE,	"command line usage error",	EX_TEMPFAIL	},
{	EX_DATAERR,	"data format error",		EX_DATAERR	},
{	EX_NOINPUT,	"cannot open input",		EX_TEMPFAIL	},
{	EX_NOUSER,	"addressee unknown",		EX_NOUSER	},
{	EX_NOHOST,	"host name unknown",		EX_NOHOST	},
{	EX_UNAVAILABLE,	"service unavailable",		EX_UNAVAILABLE	},
{	EX_SOFTWARE,	"internal software error",	EX_TEMPFAIL	},
{	EX_OSERR,	"system error",			EX_TEMPFAIL	},
{	EX_OSFILE,	"critical OS file missing",	EX_TEMPFAIL	},
{	EX_CANTCREAT,	"can't create output file",	EX_TEMPFAIL	},
{	EX_IOERR,	"input/output error",		EX_TEMPFAIL	},
{	EX_TEMPFAIL,	"temporary failure",		EX_TEMPFAIL	},
{	EX_PROTOCOL,	"remote error in protocol",	EX_TEMPFAIL	},
{	EX_NOPERM,	"permission denied",		EX_NOPERM	},
{	0,		NULL,				EX_TEMPFAIL	}
};

#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, *mailer, *cf;
	struct ctldesc *dp;
	int errflg, c;
	struct maildesc *mp;

	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
	  signal(SIGINT, wantout);
	if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
	  signal(SIGHUP, wantout);
	if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
	  signal(SIGTERM, wantout);
	signal(SIGPIPE, SIG_IGN);

	if ((progname = strrchr(argv[0], '/')) == NULL)
	  progname = argv[0];
	else
	  ++progname;
	errflg = 0;
	host = channel = NULL;
	cf = NULL;
	while ((c = getopt(argc, argv, "f:c:h:vVw:8")) != EOF) {
	  switch (c) {
	  case 'f':
	    cf = optarg;
	    break;
	  case 'c':		/* remote hostname */
	    channel = optarg;
	    break;
	  case 'h':		/* remote hostname */
	    host = optarg;
	    break;
	  case 'V':
	    prversion("sm");
	    exit(0);
	    break;
	  case 'v':
	    verboselog = stdout;
	    break;
	  case '8':
	    can_8bit = 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 [-c channel || -h host] mailer\n", argv[0]);
	  exit(EX_USAGE);
	}
	mailer = argv[optind];

	if ((mp = readsmcf(cf, mailer)) == NULL)
	  exit(EX_OSFILE);
	if (mp->flags & MO_REMOTEFROM)
	  getmyuucpname(uucpname, sizeof uucpname);	/*XX*/
	while (!getout && fgets(file, sizeof file, stdin)) {
	  if (emptyline(file, sizeof file))
	    break;
	  ctlsticky((char *)NULL, (char *)NULL); /* reset */
	  dp = ctlopen(file, channel, host, &getout, ctlsticky, NULL);
	  if (dp == NULL)
	    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(mp, rphead, rp->next,
		      dp->msgfd, dp->msgbodyoffset,
		      verboselog);
	      rphead = rp->next;
	    }
	  }
	} else {
	  for (rp = dp->recipients; rp != NULL; rp = rp->next)
	    deliver(mp, rp, rp->next, dp->msgfd, dp->msgbodyoffset,
		    verboselog);
	}
}

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

void
deliver(mp, startrp, endrp, messagefd, msgoffset, verboselog)
	struct maildesc *mp;
	struct rcpt *startrp, *endrp;
	int messagefd;
	long msgoffset;
	FILE *verboselog;
{
	struct rcpt *rp;
	struct exmapinfo *exp;
	int i, j, pid, in[2], out[2];
	unsigned int avsize;
	FILE *tafp, *errfp;
	char *cp, *dp, *ds, *s, buf[BUFSIZ], buf2[BUFSIZ];
	char **av;
#ifdef	USE_UNIONWAIT
	union wait status;
#else	/* !USE_UNIONWAIT */
	int	status;
#endif	/* USE_UNIONWAIT */
	int content_kind, downgrade;

	if (lseek(messagefd, msgoffset, L_SET) < 0L)
		warning("Cannot seek to message body! (%m)", (char *)NULL);
	i = 0;
	avsize = 5;
	av = (char **)emalloc(sizeof av[0] * avsize);
	av[i++] = mp->argv[0];
	if (mp->flags & MO_FFROMFLAG) {
	  av[i++] = "-f";
	  av[i++] = startrp->addr->link->user;
	} else if (mp->flags & MO_RFROMFLAG) {
	  av[i++] = "-r";
	  av[i++] = startrp->addr->link->user;
	}
	for (j = 1; mp->argv[j] != NULL; ++j) {
	  while (i+2 >= avsize) {
	    avsize *= 2;
	    av = (char **)erealloc((char *)av,
				   sizeof av[0] * avsize);
	  }
	  if (strchr(mp->argv[j], '$') == 0) {
	    av[i++] = mp->argv[j];
	    continue;
	  }
	  rp = startrp;
	  do {
	    while (i+2 >= avsize) {
	      avsize *= 2;
	      av = (char **)erealloc((char *)av,
				     sizeof av[0] * avsize);
	    }
	    for (cp = mp->argv[j], s = buf; *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':
		  strcpy(buf2, rp->addr->user);
		  for (dp = buf2; *dp != 0; ++dp)
		    if (isupper(*dp))
		      *dp = tolower(*dp);
		  ds = buf2;
		  rp = rp->next;
		  break;
		default:
		  ds = NULL;
		  break;
		}
		if (ds == NULL || *ds == '\0') {
		  char msg[BUFSIZ];

		  sprintf(msg,
			  "Null value for $%c (%%s)!",
			  *cp);
		  warning(msg, mp->name);
		} else {
		  strcpy(s, ds);
		  s += strlen(s);
		}
	      } else
		*s++ = *cp;
	    }
	    *s = '\0';
	    av[i] = emalloc((u_int)(strlen(buf)+1));
	    /* not worth freeing this stuff */
	    strcpy(av[i], buf);
	    ++i;
	  } while (rp != startrp && rp != endrp);
	}
	av[i] = NULL;
	/* now we can fork off and run the command... */
	if (pipe(in) < 0) {
	  for (rp = startrp; rp != endrp; rp = rp->next)
	    diagnostic(rp, EX_OSERR,
		       "cannot create pipe from \"%s\"",
		       mp->command);
	  return;
	}
	if (pipe(out) < 0) {
	  for (rp = startrp; rp != endrp; rp = rp->next)
	    diagnostic(rp, EX_OSERR,
		       "cannot create pipe to \"%s\"",
		       mp->command);
	  return;
	}
	if ((pid = fork()) == 0) { /* child, run the command */
	  close(in[0]);
	  close(out[1]);
	  if (!(mp->flags & MO_NORESETUID))
	    setuid(getuid());
	  /* 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, av);
	  _exit(254);
	} else if (pid < 0) {	/* couldn't fork, complain */
	  for (rp = startrp; rp != endrp; rp = rp->next)
	    diagnostic(rp, EX_OSERR, "cannot fork");
	  return;
	}
	close(out[0]);
	close(in[1]);
	tafp = fdopen(out[1], "w");
	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 */
	if (mp->flags & (MO_UNIXFROM|MO_REMOTEFROM)) {
	  char *timestring;
	  time_t now;

	  now = time((time_t *)0);
	  timestring = ctime(&now);
	  *(timestring+strlen(timestring)-1) = '\0';
	  fprintf(tafp, "%s%s %s", FROM_,
		  startrp->addr->link->user, timestring);
	  if (mp->flags & MO_REMOTEFROM)
	    fprintf(tafp, " remote from %s", uucpname);
	  if (verboselog) {
	    fprintf(verboselog, "%s%s %s", FROM_,
		    startrp->addr->link->user, timestring);
	    if (mp->flags & MO_REMOTEFROM)
	      fprintf(verboselog, " remote from %s", uucpname);
	    putc('\n',verboselog);
	  }
	  putc('\n', tafp);
	}

	/* Content-Transfer-Encoding: 8BIT ? */
	content_kind = cte_check(startrp);
	downgrade = (content_kind  == 8) && !can_8bit;
	/* If it does not contain MIME headers, and it contains 8-bit
	   chars, it is in trouble!  Mark the checkup with  -1       */
	if (content_kind == 0 && !can_8bit)
	  downgrade = -1;
	if (downgrade) {
	  check_7bit_downgrade(messagefd, &downgrade);
	  downgrade_headers(startrp, downgrade, verboselog);
	}

 	/* Add the "Return-Path:" is it is desired, but does not yet
	   exist.. */
	if (mp->flags & MO_RETURNPATH)
	  if (!has_header(startrp,"Return-Path:"))
	    append_header(startrp,"Return-Path: <%s>\n",
			  startrp->addr->link->user);
	if (mp->flags & MO_CRLF)
	  writeheaders(startrp,tafp, "\r\n",downgrade & 1, maxwidth);
	else
	  writeheaders(startrp,tafp, "\n",  downgrade & 1, maxwidth);

	/* append message body itself */
	if ((i = appendlet(mp, tafp, messagefd, verboselog, downgrade)) != EX_OK) {
	  for (rp = startrp; rp != endrp; rp = rp->next)
	    diagnostic(rp, i, "write error");
	  /* just to make sure nothing will get delivered */
	  kill(pid, SIGTERM);
	  sleep(1);
	  kill(pid, SIGKILL);
	  wait((union wait *)0);
	  return;
	}
	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 */
	pid = wait(&status);
	cp = buf + strlen(buf);
#ifdef	USE_UNIONWAIT
	if (status.w_termsig) {
	  if (cp != buf)
	    *cp++ = ' ';
	  sprintf(cp, "[signal %d", status.w_termsig);
	  if (status.w_coredump)
	    strcat(cp, " (Core dumped)");
	  strcat(cp, "]");
	  i = EX_TEMPFAIL;
	} else if (status.w_retcode == 0 || status.w_retcode == EX_OK) {
	  i = EX_OK;
	} else {
	  i = status.w_retcode;
#else  /* !USE_UNIONWAIT */
	  if (status&0177) {
	    if (cp != buf)
	      *cp++ = ' ';
	    sprintf(cp, "[signal %d", status&0177);
	    if (status&0200)
	      strcat(cp, " (Core dumped)");
	    strcat(cp, "]");
	    i = EX_TEMPFAIL;
	  } else if (((status>>8)&0377) == 0 || ((status>>8)&0377) == EX_OK) {
	    i = EX_OK;
	  } else {
	    i = (status>>8)&0377;
#endif /* USE_UNIONWAIT */
	    for (exp = exmap; exp->origstatus != 0; ++exp)
	      if (exp->origstatus == i)
		break;

	    s = exp->statusmsg;
	    i = exp->newstatus;
#ifdef	USE_UNIONWAIT
	    sprintf(cp, "[exit status %d", status.w_retcode);
#else  /* !USE_UNIONWAIT */
	    sprintf(cp, "[exit status %d", ((status>>8)&0377));
#endif /* USE_UNIONWAIT */
	    if (s)
	      sprintf(cp+strlen(cp), " (%s)", s);
	    sprintf(cp+strlen(cp), " of command: %s", mp->command);
	    strcat(cp, "]");
	  }
	  if (verboselog)
	    fprintf(verboselog,"Diagnostic: %s\n",cp);
	  for (rp = startrp; rp != endrp; rp = rp->next)
	    diagnostic(rp, i, "%s", buf);
	  /* XX: still need to deal with MO_STRIPQUOTES */
	}

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

static char let_buffer[BUFSIZ];

int
appendlet(mp, fp, mfd, verboselog, downgrade)
	struct maildesc *mp;
	FILE *fp;
	int mfd;
	FILE *verboselog;
	int downgrade;
{
	register int i;
	register int bufferfull;
	int lastwasnl;

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

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

	/* we are assumed to be positioned properly at start of message body */
	bufferfull = lastwasnl = 0;
	while ((i = read(mfd, let_buffer, sizeof let_buffer)) != 0) {
	  if (i < 0)
	    return EX_IOERR;
	  lastwasnl = (let_buffer[i-1] == '\n');
	  if (writebuf(mp, fp, let_buffer, i, downgrade) != i)
	    return EX_IOERR;
	  readalready = i;
	  bufferfull++;
	}
	/* we must make sure the last thing we transmit is a CRLF sequence */
	if (!lastwasnl)
	  writebuf(mp, fp, "\n", 1, downgrade);

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

/*
 * 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, downgrade)
	struct maildesc *mp;
	FILE *fp;
	char *buf;
	int len;
	int downgrade;
{
	register char *cp;
	register int n;
	int tlen;
	register char expect;
	register int  column;
	static char save = '\0';
	static int  col  = 0;
	static char frombuf[8];
	static char *fromp;

	if (buf == NULL) {	/* magic initialization */
	  save = '.';
	  col  = 0;
	  frombuf[0] = 0;
	  fromp = frombuf;
	  return 0;
	}
	expect = save;
	column = col;
	downgrade = (downgrade & 1) != 0;
	if ((mp->flags & MO_STRIPHIBIT) && !downgrade) {
	  for (cp = buf, n = len; n > 0; --n, ++cp)
	    if (*cp & 0200)
	      *cp &= 0177;
	}
	for (cp = buf, n = len, tlen = 0; n > 0; --n, ++cp) {
	  int c = *(unsigned char *)cp;
	  ++column;
	  ++tlen;
	  if (downgrade && column > 70 && c != '\n') {
	    putc('=',fp);
	    if (mp->flags & MO_CRLF)
	      putc('\r', fp);
	    putc('\n',fp);
	    column = 1;
	    expect = '.';
	  }
	  if (downgrade && c != '\n' && (c < 32 || c > 126)) {
	    /* Downgrade it by translating it to Quoted-Printable.. */
	    fprintf(fp,"=%02X",c);
	    column += 2;
	    buf = cp;
	  } else 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 = '.';
	    column = 0;
	  } 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';
		  ++column;
		  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;
	col  = column;

	return tlen;
}

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

	if (file == NULL) {
	  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 = 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 = cp;
	  strcpy(entry, buf);
	  SKIPWHILE(!isspace, 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;
	SKIPWHILE(isspace, cp);
	/* process mailer option flags */
	for (;isascii(*cp) && !isspace(*cp); ++cp) {
	  switch (*cp) {
	  case '7':	m.flags |= MO_STRIPHIBIT;	break;
	  case 'E':	m.flags |= MO_ESCAPEFROM;	break;
	  case 'P':	m.flags |= MO_RETURNPATH;	break;
	  case 'R':	m.flags |= MO_CRLF;		break;
	  case 'S':	m.flags |= MO_NORESETUID;	break;
	  case 'U':	m.flags |= MO_REMOTEFROM;	break;
	  case 'X':	m.flags |= MO_HIDDENDOT;	break;
	  case 'f':	m.flags |= MO_FFROMFLAG;	break;
	  case 'm':	m.flags |= MO_MANYUSERS;	break;
	  case 'n':	m.flags &= ~MO_UNIXFROM;	break;
	  case 'r':	m.flags |= MO_RFROMFLAG;	break;
	  case 's':	m.flags |= MO_STRIPQUOTES;	break;
	  case 'A':		/* arpanet-compatibility */
	  case 'C':		/* canonicalize remote hostnames */
	  case 'D':		/* this mailer wants a Date: line */
	  case 'F':		/* this mailer wants a From: line */
	  case 'I':		/* talking to a clone of I */
	  case 'L':		/* limit line length */
	  case 'M':		/* this mailer wants a Message-Id: line */
	  case 'e':		/* expensive mailer */
	  case 'h':		/* preserve upper case in host names */
	  case 'l':		/* this is a local mailer */
	  case 'p':		/* use SMTP return path */
	  case 'u':		/* preserve upper case in user names */
	  case 'x':		/* this mailer wants a Full-Name: line */
	    fprintf(stderr,
		    "%s: the '%c' sendmail mailer option does not make sense in this environment\n",
		    progname, *cp);
	    break;
	  case '-':		/* ignore */
	    break;
	  default:
	    fprintf(stderr,
		    "%s: unknown sendmail mailer option '%c'\n",
		    progname, *cp);
	    break;
	  }
	}
	SKIPWHILE(isspace, cp);
	m.command = cp;
	SKIPWHILE(!isspace, cp);
	if (cp == m.command) {
		fprintf(stderr,"%s: bad entry for %s\n",progname, m.name);
		return NULL;
	}
	*cp++ = '\0';
	if (*m.command != '/') {
	  char *nmc, *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;
	}
	SKIPWHILE(isspace, cp);
	i = 0;
	while (isascii(*cp) && !isspace(*cp)) {
	  if (*cp == '\0')
	    break;
	  m.argv[i++] = cp;
	  SKIPWHILE(!isspace, cp);
	  if (isascii(*cp)) {
	    *cp++ = '\0';
	    SKIPWHILE(isspace, 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, do:  *flag_ptr = (*flag_ptr) << 1  */
void
check_7bit_downgrade(mfd, flag_ptr)
int *flag_ptr;
int mfd;
{
	register int i;
	register int bufferfull;
	int lastwasnl;
	long mfd_pos;

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

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

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

	/* Set indication! */
	*flag_ptr = (*flag_ptr) << 1;
}
