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

#include "hostenv.h"
#include <stdio.h>
#include <ctype.h>
#include <pwd.h>
#include "zmsignal.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 HAVE_UNISTD_H
#include <unistd.h>
#endif

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

#ifndef WIFEXITED
# define WIFEXITED(s) (((s) >> 8) & 0377)
#endif
#ifndef WIFSIGNALED
# define WIFSIGNALED(s) ((s) & 0177)
#endif

#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! */
int	mimeqpnarrow = 0;	/* Can't send TAB thru without MIME-QP */
FILE	*verboselog = NULL;
FILE	*logfp   = NULL;
int	maxwidth = 0;

int	D_alloc = 0;		/* Memory debugging */

extern RETSIGTYPE wantout();
#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();
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, char *buf, int len));
extern time_t time();
extern char *strchr(), *strrchr();

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

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 /* SMTP dot-duplication */
#define	MO_ESCAPEFROM		  0400
#define	MO_STRIPHIBIT		 01000
#define	MO_REMOTEFROM		 02000
#define	MO_CRLF			 04000
#define MO_BSMTP		010000 /* BSMTP-wrapping -- with HIDDENDOT.. */
#define MO_BESMTP		020000 /* Extended BSMTP -- SIZE+8BITMIME+? */

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 = NULL, *mailer, *cf;
	struct ctldesc *dp;
	int errflg, c;
	struct maildesc *mp;
	RETSIGTYPE (*oldsig)();

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

	SIGNAL_IGNORE(SIGPIPE);

	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:QvVw:8")) != EOF) {
	  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 = 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][-8 | -Q][-f cfgfile][-w maxwidth][-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) {
	  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)
	    continue; /* Ah well, we can stay idle.. */
	  if (emptyline(file, sizeof file))
	    break;

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

	  ctlsticky((char *)NULL, (char *)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;
	struct exmapinfo *exp;
	int i, j, pid, in[2], out[2];
	unsigned int avsize;
	FILE *tafp, *errfp;
	char *cp, *ds, *s, buf[BUFSIZ], buf2[BUFSIZ];
	char **av;
	int	status;
	int content_kind, conversion_prohibited, convertmode, ascii_clean = 0;

	if (lseek(dp->msgfd, dp->msgbodyoffset, 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 (ds = buf2; *ds != 0; ++ds)
		    if (isupper(*ds))
		      *ds = tolower(*ds);
		  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) (msgfile: %s)!",
			  *cp, dp->msgfile);
		  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) {
	    notaryreport(rp->addr->user,"failed",
			 "4.3.0 (Out of system resources, pipe creation failed)",
			 "400 (pipe creation error, out of system resources ?)");
	    diagnostic(rp, EX_OSERR, 0,
		       "cannot create pipe from \"%s\"",
		       mp->command);
	  }
	  return;
	}
	if (pipe(out) < 0) {
	  for (rp = startrp; rp != endrp; rp = rp->next) {
	    notaryreport(rp->addr->user,"failed",
			 "4.3.0 (Out of system resources, pipe creation failed)",
			 "400 (pipe creation error, out of system resources ?)");
	    diagnostic(rp, EX_OSERR, 0,
		       "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) {
	    notaryreport(rp->addr->user,"failed",
			 "4.3.0 (Out of system resources, fork failed)",
			 "400 (fork failure, out of system resources ?)");
	    diagnostic(rp, EX_OSERR, 0, "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);
	}

	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;
	      downgrade_headers(startrp, convertmode, verboselog);
	    }
	    break;
	  case 9:		/* QUOTED-PRINTABLE */
	    if (can_8bit) {
	      /* 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:		/* ?? should not happen.. */
	    break;
	  }

 	/* 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", convertmode, maxwidth);
	else
	  writeheaders(startrp, tafp, "\n",   convertmode, maxwidth);

	if (verboselog)
	  writeheaders(startrp, verboselog, "\n", convertmode, maxwidth);

	/* append message body itself */
	if ((i = appendlet(dp, mp, tafp, verboselog, convertmode)) != EX_OK) {
	  for (rp = startrp; rp != endrp; rp = rp->next) {
	    notaryreport(rp->addr->user,"failed",
			 /* Could indicate: 4.3.1 - mail system full ?? */
			 "4.3.0 (Write to target failed for some reason)",
			 "400 (Write to target failed for some reason)");
	    diagnostic(rp, i, 0, "write error");
	  }
	  /* just to make sure nothing will get delivered */
	  kill(pid, SIGTERM);
	  sleep(1);
	  kill(pid, SIGKILL);
	  wait(NULL);
	  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);
	  if (WIFSIGNALED(status)) {
	    if (cp != buf)
	      *cp++ = ' ';
	    sprintf(cp, "[signal %d", WIFSIGNALED(status));
	    if (status&0200)
	      strcat(cp, " (Core dumped)");
	    strcat(cp, "]");
	    i = EX_TEMPFAIL;
	  } else if (WIFEXITED(status) == 0 || WIFEXITED(status) == EX_OK) {
	    i = EX_OK;
	  } else {
	    i = WIFEXITED(status);
	    for (exp = exmap; exp->origstatus != 0; ++exp)
	      if (exp->origstatus == i)
		break;

	    s = exp->statusmsg;
	    i = exp->newstatus;
	    sprintf(cp, "[exit status %d", WIFEXITED(status));
	    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) {
	    if (i == EX_OK)
	      notaryreport(rp->addr->user, "relayed",
			   "2.2.0",
			   "250 Delivered");
	    else
	      notaryreport(rp->addr->user, "failed",
			   "4.3.0",
			   "430 Unknown error");
	    diagnostic(rp, i, 0, "%s", buf);
	  }
	  /* XX: still need to deal with MO_STRIPQUOTES */
	}

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

#ifndef	HAVE_MMAP
static char let_buffer[BUFSIZ];
#endif

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;
	register int bufferfull;
	int lastch;
#ifndef HAVE_MMAP
	int mfd = dp->msgfd;
#endif

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

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

	/* we are assumed to be positioned properly at start of message body */
	bufferfull = 0;
	lastch = -1;
	if (convertmode == _CONVERT_NONE) {
#ifndef	HAVE_MMAP
	  while ((i = read(mfd, let_buffer, sizeof let_buffer)) != 0) {
#else
	    char *let_buffer = dp->let_buffer + dp->msgbodyoffset;
	    i = dp->let_end - (dp->let_buffer + dp->msgbodyoffset);
#endif
	    if (i < 0)
	      return EX_IOERR;
	    lastch = let_buffer[i-1];
	    if (writebuf(mp, fp, let_buffer, i) != i)
	      return EX_IOERR;
	    readalready = i;
	    bufferfull++;
#ifndef	HAVE_MMAP
	  }
#endif
	} else {
	  /* convertmode something else, than _CONVERT_NONE */
	  /* Various esoteric conversion modes..
	     We are better to feed writemimeline() with LINES
	     instead of blocks of data.. */
#ifndef	HAVE_MMAP
	  char iobuf[BUFSIZ];
	  FILE *mfp = fdopen(mfd,"r");

	  fseek(mfp,dp->msgbodyoffset,L_SET);

	  setvbuf(mfp, iobuf, _IOFBF, sizeof(iobuf));

#define MFPCLOSE i = dup(mfd); fclose(mfp); dup2(i,mfd); close(i);

	  readalready = 0;
#else
#define MFPCLOSE
	  char *s = dp->let_buffer + dp->msgbodyoffset;
#endif
	  writemimeline(mp, fp, (char *)NULL, 0, 0, NULL);

	  /*
	     if(verboselog) fprintf(verboselog,
	     "sm: Convert mode: %d, fd=%d, fdoffset=%d, bodyoffset=%d\n",
	     convertmode, mfd, (int)lseek(mfd,0,1),
	     dp->msgbodyoffset);
	   */

	  /* we are assuming to be positioned properly
	     at the start of the message body */
	  bufferfull = 0;
	  lastch = -1;
	  i = 0;

	  for (;;) {
#ifndef HAVE_MMAP
	    if ((i = cfgets(let_buffer, sizeof(let_buffer), mfp)) == EOF)
	      break;
#else
	    char *let_buffer = s, *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;
	    s = s2;
#endif
	    /* 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, let_buffer, i,
			      convertmode, &lastch) != i) {
	      return EX_IOERR;
	    }
	  }
#ifndef HAVE_MMAP
	  if (i == EOF && !feof(mfp)) {
	    MFPCLOSE
	    return EX_IOERR;
	  }
	  MFPCLOSE
#endif
	}

	/* we must make sure the last thing we transmit is a CRLF sequence */
	if (lastch != '\n')
	  writebuf(mp, fp, "\n", 1);

#ifndef HAVE_MMAP
	if (bufferfull > 1)	/* not all in memory, need to reread */
	  readalready = 0;
#endif
	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)
	struct maildesc *mp;
	FILE *fp;
	char *buf;
	int len;
{
	register 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;
	if ((mp->flags & MO_STRIPHIBIT)) {
	  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;
	  ++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, lastchp)
	struct maildesc *mp;
	FILE *fp;
	char *buf;
	int len, convertmode, *lastchp;
{
	/* `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 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 = *(unsigned char *)cp;
	  ++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 *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 'b':	m.flags |= MO_BSMTP;		break;
	  case 'B':	m.flags |= MO_BESMTP|MO_BSMTP;	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, return 1.. (zero == non-clean) */
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) {
    /*
       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;
	return 1;
#else
	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;

	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,0);
	      if (bufferfull > 1) readalready = 0;
	      return 0;		/* Not clean ! */
	    }
	}
	/* Got to EOF, and still it is clean 7-BIT! */
	lseek(mfd,dp->msgbodyoffset,0);
	if (bufferfull > 1)	/* not all in memory, need to reread */
	  readalready = 0;

	return 1;
#endif
}
