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

/* History:
 *
 * Based on code by Geoff Collyer.
 * Rewritten for Sun environment by Dennis Ferguson and Rayan Zachariassen.
 * RBIFF code originally added by Jean-Francois Lamy.
 * Heavily modified for ZMailer by Rayan Zachariassen.
 *
 */

#include <stdio.h>
#include "hostenv.h"
#include <ctype.h>
#include <errno.h>
#include <pwd.h>
#include <signal.h>
#include <sysexits.h>
#include <varargs.h>
#include <sys/param.h>
#include <fcntl.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <mail.h>
#include "ta.h"
#include "splay.h"
#include "malloc.h"

#ifdef	USE_LOCKF
#ifdef	F_OK
#undef	F_OK
#endif	/* F_OK */
#ifdef	X_OK
#undef	X_OK
#endif	/* X_OK */
#ifdef	W_OK
#undef	W_OK
#endif	/* W_OK */
#ifdef	R_OK
#undef	R_OK
#endif	/* R_OK */
#include <unistd.h>
#endif	/* USE_LOCKF */

#ifdef	USE_UNIONWAIT
#include <sys/wait.h>
#endif	/* USE_UNIONWAIT */

#ifndef	USE_LSTAT
#define	lstat stat
#endif	/* !USE_LSTAT */

#ifdef	USE_MAILLOCK
# ifdef	MAILLOCK_H
#  include MAILLOCK_H
# else
#  include <maillock.h>
# endif
#endif	/* USE_MAILLOCK */

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

#ifdef  USE_TIMEVAL
#include <sys/time.h>
#else   /* !USE_TIMEVAL */
/*#include <unistd.h> */
/* some systems have utimbuf defined in unistd.h, some don't... */
struct utimbuf {
	time_t  actime;
	time_t  modtime;
};
#endif  /* USE_TIMEVAL */


#ifndef	L_SET
#define	L_SET	0
#endif	/* !L_SET */
#ifndef	L_XTND
#define	L_XTND	2
#endif	/* !L_XTND */
#ifndef	F_OK
#define	F_OK	0
#endif	/* !F_OK */

#ifdef	BIFF
#include <netdb.h>
#include <sys/socket.h>
#ifdef	SYS_STREAM_H
#include SYS_STREAM_H
#endif	/* SYS_STREAM_H */
#include <netinet/in.h>
#include <net/if.h>
#endif	/* BIFF */

#ifdef RBIFF
#include NDIR_H
#include <utmp.h>
#include <protocols/rwhod.h>
#define RWHODIR		"/var/spool/rwho"
#define	WHDRSIZE	(sizeof (wd) - sizeof (wd.wd_we))
#define RBIFFRC		".rbiff"
#endif	/* RBIFF */

#define	PROGNAME	"mailbox"	/* for logging */
#define	CHANNEL		"local"	/* the default channel name we deliver for */
#define TO_FILE		'/'	/* first character of file username */
#define TO_PIPE		'|'	/* first character of program username */
#define TO_USER		'\\'	/* first character of escaped username */
#define	QUOTE		'"'	/* some usernames have these around them */
#define	FROM_		"From "	/* mailbox separator string (q.v. writebuf) */

#define MAILMODE        0600    /* prevent snoopers from looking at mail */
#define	NONUIDSTR	"6963"	/* X:magic nonsense uid if nobody uid is bad */

/*
 * The following is stuck in for reference only.  You could add
 * alternate spool directories to the list (they are checked in
 * order, first to last) but if the MAILBOX zenvariable exists it will
 * override the entire list.
 */
char *maildirs[] = {
	"/var/mail",
	0 
};

/*
 * Macro to determine from the given error number whether this
 * should be considered a temporary failure or not.
 */
#ifdef	USE_NFSMBOX
#define	TEMPFAIL(err)	(err == EIO || err == ENETRESET || \
			 err == ECONNRESET || err == ENOBUFS || \
			 err == ETIMEDOUT || err == ECONNREFUSED || \
			 err == EHOSTDOWN || err == ENOTCONN)
#else	/* !USE_NFSMBOX */
#define TEMPFAIL(err)	(err == EIO)
#endif	/* USE_NFSMBOX */


#define	DIAGNOSTIC(R,E,A1,A2)	diagnostic((R), \
					(*((R)->addr->user) == TO_PIPE \
					 && (R)->status != EX_OK \
					 && (R)->status != EX_TEMPFAIL) ? \
					      EX_UNAVAILABLE : (E), (A1), (A2))

#define	DIAGNOSTIC3(R,E,A1,A2,A3)   diagnostic((R), \
					(*((R)->addr->user) == TO_PIPE \
					 && (R)->status != EX_OK \
					 && (R)->status != EX_TEMPFAIL) ? \
					      EX_UNAVAILABLE : (E), \
						(A1), (A2), (A3))

#if	defined(BIFF) || defined(RBIFF)
/*
 * Biff strategy:
 *
 * If any biff is desired, add user to list with empty structure.
 * For all users in list, if remote biff is desired, read rwho files
 * to determine the hosts user is on.  Add user to list for each host.
 * The "user" is really a struct containing username and offset into
 * their mailbox.
 */

struct biffer {
	struct biffer	*next;
	char		*user;
	long		offset;
	int		wantrbiff;
};

struct biffer *biffs = NULL;
struct biffer *eobiffs = NULL;

struct userhost {
	struct userhost *next;
	char		*hostname;
};

struct sptree *spt_users = NULL;
#endif	/* BIFF || RBIFF */

char *progname;
char *channel;
char *logfile;
FILE *logfp;
FILE *verboselog = NULL;
int readalready = 0;		/* does buffer contain valid message data? */
int currenteuid;		/* the current euid */
extern int nobody;		/* safe uid for file/program delivery */
int dobiff = 1;			/* enable biff notification */
int dorbiff = 0;		/*    "   rbiff   " */
int keepatime = 1;		/* preserve mbox st_atime, for login etc. */
int creatflag = 1;		/* attempt to create files that don't exist */
int mime8bit = 0;		/* Has code which translates  MIME text/plain
				   Quoted-Printable  to 8BIT.. */
int convert_qp = 0;		/* Flag: We have a job.. */

extern SIGNAL_TYPE wantout();
extern time_t time();
extern int optind;
extern char *optarg;
extern int emptyline();
extern void prversion(), getnobody(), process(), biff(), rbiff();
extern void deliver();
extern struct passwd *getpwnam();
extern int setupuidgid(), createfile();
extern int exstat(), creatembox();
extern char *exists(), *strerror();
extern FILE *putmail();
extern void program(), setrootuid();
extern int appendlet();
extern char **environ;
extern int exstat(), setupuidgid(), createfile();
extern int writebuf();

extern int  qp_to_8bit   __((struct rcpt *rp));
extern int  qptext_check __((struct rcpt *rp));

extern void hp_init();
extern char **hp_getaddr();
extern int errno;
extern void warning();
extern char *strchr(), *strrchr();
extern int  lstat(); /* remaps to  stat() if USE_LSTAT is not defined.. */
extern FILE *fdopen();
#ifndef MALLOC_TRACE
extern unsigned int symbol();
extern univptr_t emalloc();
#else
struct conshell *envarlist = NULL;
#endif
extern int stickymem;	/* for strsave() */
int	D_alloc = 0;


#ifndef	MAXPATHLEN
#define	MAXPATHLEN 1024
#endif

int
main(argc, argv)
	int argc;
	char *argv[];
{
	char file[MAXPATHLEN+1];
	char *s;
	int c, errflg, fd;
	struct ctldesc *dp;
#if	defined(BIFF) || defined(RBIFF)
	struct biffer *nbp;
#endif

	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;

	if (geteuid() != 0 || getuid() != 0) {
	  fprintf(stderr, "%s: not running as root!\n", progname);
	  exit(EX_NOPERM);
	}

	errflg = 0;
	logfile = NULL;
	channel = CHANNEL;
	while ((c = getopt(argc, argv, "abc:gl:rV8")) != EOF) {
	  switch (c) {
	  case 'c':		/* specify channel scanned for */
	    channel = optarg;
	    break;
	  case 'V':
	    prversion(PROGNAME);
	    exit(EX_OK);
	    break;
	  case 'b':		/* toggle biffing */
	    dobiff = !dobiff;
	    break;
	  case 'r':		/* toggle rbiffing */
	    dorbiff = !dorbiff;
	    break;
	  case 'a':		/* toggle mbox st_atime preservation */
	    keepatime = !keepatime;
	    break;
	  case 'g':		/* toggle file creation */
	    creatflag = !creatflag;
	    break;
	  case 'l':		/* log file */
	    logfile = strsave(optarg);
	    break;
	  case '8':
	    mime8bit = 1;
	    break;
	  default:
	    ++errflg;
	    break;
	  }
	}
	if (errflg || optind != argc) {
	  fprintf(stderr, "Usage: %s [-V8abr] [-c channel]\n",
		  argv[0]);
	  exit(EX_USAGE);
	}
	setreuid(0, 0);		/* make us root all over */
	currenteuid = 0;

	if ((s = getzenv("MAILBOX")) != NULL) {
	  maildirs[0] = s;
	  maildirs[1] = NULL;
	}

	if (logfile != NULL) {
	  if ((fd = open(logfile, O_CREAT|O_APPEND|O_WRONLY, 0644)) < 0)
	    fprintf(stderr,
		    "%s: open(\"%s\") failed: %s\n",
		    progname, logfile, strerror(errno));
	  else
	    logfp = fdopen(fd, "a");
	} else
	  logfp = NULL;

	getnobody();

	while (!getout && fgets(file, sizeof file, stdin)) {
	  if (emptyline(file, sizeof file))
	    break;
	  dp = ctlopen(file, channel, (char *)NULL, &getout, NULL, NULL);
	  if (dp == NULL)
	    continue;
	  if (verboselog) {
	    fclose(verboselog);
	    verboselog = NULL;
	  }
	  if (dp->verbose) {
	    verboselog = (FILE*)fopen(dp->verbose,"a");
	    if (verboselog)
	      setbuf(verboselog,NULL);
	  }
	  process(dp);
	  ctlclose(dp);
	}

#if	defined(BIFF) || defined(RBIFF)
	/* now that the delivery phase is over, hoot'n wave */
	eobiffs = NULL;
	for (nbp = biffs ; nbp != NULL; nbp = nbp->next) {
	  if (dobiff && nbp->offset >= 0)
	    biff("localhost", nbp->user, nbp->offset);
#ifdef	RBIFF
	  if (nbp->wantrbiff != 0) {
	    if (spt_users == NULL)
	      spt_users = sp_init();
	    sp_install(symbol(nbp->user),
		       (char *)NULL, 0, spt_users);
	  }
#endif /* RBIFF */
	}
#ifdef	RBIFF
	if (spt_users != NULL) {
	  readrwho();
	  for (nbp = biffs ; nbp != NULL; nbp = nbp->next) {
	    if (nbp->offset >= 0 && nbp->wantrbiff)
	      rbiff(nbp);
	  }
	}
#endif	/* RBIFF */
#endif	/* BIFF || RBIFF */
	exit(EX_OK);
	/* NOTREACHED */
	return 0;
}

void
process(dp)
	struct ctldesc *dp;
{
	char *cp, *ts, *at;
	struct rcpt *rp;
	time_t curtime;
	int is_mime_qptext;

	is_mime_qptext = qptext_check(dp->recipients);

	/*
	 * Strip backslashes prefixed to the user name,
	 * and strip the quotes from around a name.
	 */
	for (rp = dp->recipients; rp != NULL; rp = rp->next) {
	  while (*(rp->addr->user) == TO_USER)
	    rp->addr->user++;
	  if ((at = strrchr(rp->addr->user,'@')) != NULL) *at = 0;
	  if (*(rp->addr->user) == QUOTE) {
	    cp = rp->addr->user + strlen(rp->addr->user) - 1;
	    if (*cp == QUOTE) {
	      *cp = '\0';
	      rp->addr->user++;
	    }
	  }
	  if (at) *at = '@';	/* If it followed quotes, a null prefixes it.. */
#ifdef	RBIFF
	  /*
	   * "contents" will be freed before rbiff done:
	   * must copy username somewhere.
	   */
	  rp->addr->user = strsave(rp->addr->user);
#endif /* RBIFF */
	}

	readalready = 0; /* ignore any previous message data cache */
	time(&curtime);
	ts = ctime(&curtime);

	for (rp = dp->recipients; rp != NULL; rp = rp->next) {
	  /* seek to message body start on each iteration */
	  if (lseek(dp->msgfd, dp->msgbodyoffset, L_SET) < 0L) {
	    warning("Cannot seek to message body in %s! (%m)",
		    dp->msgfile);
	    DIAGNOSTIC(rp, EX_TEMPFAIL,
		       "cannot seek to message body!", 0);
	  } else {
	    if (dp->msgbodyoffset == 0)
	      warning("Null message offset in \"%s\"!",
		      dp->msgfile);
	    convert_qp = is_mime_qptext && mime8bit;
	    deliver(rp, dp, ts);
	  }
	}
}

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

void
deliver(rp, dp, timestring)
	struct rcpt *rp;
	struct ctldesc *dp;
	char *timestring;
{
	register char **maild;
	int fdmail, uid, messagefd;
	FILE *fp;
	struct stat st, s2;
	char *file = NULL, *cp, *at;
#if	defined(BIFF) || defined(RBIFF)
	struct biffer *nbp = NULL;
#ifdef	RBIFF
	char *path;
#endif	/* RBIFF */
#endif	/* BIFF || RBIFF */
	struct passwd *pw;
#ifdef	USE_MAILLOCK
	char *maillockuser;
#endif

	messagefd = dp->msgfd;
	uid = atoi(rp->addr->misc);
	at = strchr(rp->addr->user,'@');
	switch (*(rp->addr->user)) {
	case TO_PIPE:		/* pipe to program */
	  /* one should disallow this if uid == nobody? */
	  if (uid == nobody) {
	    notaryreport("?program?", "failed", "550 (Mail to program disallowed w/o proper priviledges)");
	    DIAGNOSTIC(rp, EX_UNAVAILABLE,
		       "mail to program disallowed", 0);
	    return;
	  }
	  program(rp, messagefd, timestring, dp);
	  /* DIAGNOSTIC(rp, EX_UNAVAILABLE,
	     "mailing to programs (%s) is not supported",
	     rp->addr->user+1); */
	  return;
	case TO_FILE:		/* append to file */
	  if (uid == nobody) {
	    notaryreport("?appendfile?","failed",
			 "550 (mail to file disallowed w/o priviledges)");
	    DIAGNOSTIC(rp, EX_UNAVAILABLE,
		       "mail to file disallowed", 0);
	    return;
	  }
	  if (!setupuidgid(rp, uid, 1)) {
	    setrootuid(rp);
	    return;
	  }
	  file = strsave(rp->addr->user);
	  if (access(file, F_OK) < 0) {
	      fdmail = createfile(rp, file, uid);
	      if (fdmail < 0) {
		  free(file);
		  setrootuid(rp);
		  return;
	      }
	      close(fdmail);
	  }
	  setrootuid(rp);
	  break;
	default:		/* local user */
	  /* Zap the possible '@' for a moment -- and restore later
	     [mea@utu.fi] */
	  if (at) *at = 0;
	  if ((pw = getpwnam(rp->addr->user)) == NULL) {
	    if (isascii(*(rp->addr->user))
		&& isupper(*(rp->addr->user))) {
	      for (cp = rp->addr->user ; *cp != '\0'; ++cp)
		if (isascii(*cp) && isupper(*cp))
		  *cp = tolower(*cp);
	    }
	    if ((pw = getpwnam(rp->addr->user)) == NULL) {
	      if (at) *at = '@';
	      notaryreport(rp->addr->user,"failed","550 (User does not exist)");
	      if (at) *at = 0;
	      DIAGNOSTIC(rp, EX_NOUSER,
			 "user \"%s\" doesn't exist", rp->addr->user);
	      return;
	    }
	  }
	  for (maild = &maildirs[0]; *maild != 0; maild++) {
	    if ((file = exists(*maild, rp)) != NULL)
	      break;		/* found it */
	    if (rp->status != EX_OK)
	      return;
	  }
	  if (*maild == 0)	/* didn't find it? */
	    if (!creatembox(rp, &file, &st.st_uid, &st.st_gid, pw))
	      return;		/* creatembox sets status */
#if	defined(BIFF) || defined(RBIFF)
	  if (!dobiff && !dorbiff)
	    break;
	  nbp = (struct biffer *)emalloc(sizeof (struct biffer));
	  nbp->next = NULL;
	  nbp->user = strsave(rp->addr->user);
	  nbp->offset = -1;
	  nbp->wantrbiff = 0;
#ifdef	RBIFF
	  if (dorbiff) {
#ifdef	RBIFF_ALWAYS
	    nbp->wantrbiff = 1;
#else
	    path = emalloc(2+strlen(pw->pw_dir)+strlen(RBIFFRC));
	    sprintf(path, "%s/%s", pw->pw_dir, RBIFFRC);
	    nbp->wantrbiff = (access(path, F_OK) == 0);
#endif /* RBIFF_ALWAYS */
	  }
#endif /* RBIFF */
	  if (biffs == NULL)
	    biffs = eobiffs = nbp;
	  else {
	    eobiffs->next = nbp;
	    eobiffs = nbp;
	  }
#endif /* BIFF || RBIFF */
	  if (at) *at = '@';
	  break;
	}
	if (exstat(rp, file, &st, lstat) < 0) {
	  notaryreport(rp->addr->user,"failed",
		       "466 (User's mailbox disappeared, will retry)");
	  DIAGNOSTIC(rp, EX_TEMPFAIL,
		     "mailbox file \"%s\" disappeared", file);
	  return;
	}
	/* we only deliver to singly-linked, regular file */
	
	/* Solaris has "interesting" /dev/null -- it is a symlink
	   to the actual device.. So lets just use that magic
	   name and create a FAST write to  /dev/null..  */
	if (strcmp(file,"/dev/null") == 0) {
	  notaryreport(rp->addr->user,"delivered",
		       "250 (Delivered successfully)");
	  DIAGNOSTIC(rp, EX_OK, (char *)NULL, 0);
	  return;
	}

	if (!S_ISREG(st.st_mode)) {
	  /* XX: may want to deliver to named pipes */
	  notaryreport("?special-file?","failed",
		       "500 (Attempting to deliver into non-regular file)");
	  DIAGNOSTIC(rp, EX_UNAVAILABLE,
		     "attempted delivery to special file \"%s\"",
		     file);
	  return;
	}
	
	if (st.st_nlink > 1) {
	  notaryreport("?file-links?","failed",
		       "500 (Destination file has more than one name..)");
	  DIAGNOSTIC(rp, EX_UNAVAILABLE,
		     "too many links to file \"%s\"", file);
	  return;
	}
	if (!setupuidgid(rp, st.st_uid, st.st_gid)) {
	  setrootuid(rp);
	  return;			/* setupuidgid sets status */
	}

	if ((fdmail = open(file, O_RDWR|O_APPEND)) < 0) {
	  char fmtbuf[512];

	  sprintf(fmtbuf, "open(\"%%s\") failed: %s",
		  strerror(errno));
	  if (TEMPFAIL(errno))
	    rp->status = EX_TEMPFAIL;
	  else if (errno == EACCES)
	    rp->status = EX_NOPERM;
	  else
	    rp->status = EX_UNAVAILABLE;
	  notaryreport(file,"failed","500 (File open for append failed)");
	  DIAGNOSTIC(rp, rp->status, fmtbuf, file);
	  setrootuid(rp);
	  return;
	}
	/*
	 * The mbox may have been removed and symlinked
	 * after the exstat() above, but before the open(),
	 * so verify that we have the same inode.
	 */
	if (fstat(fdmail, &s2) < 0) {
	    /* This simply CAN'T HAPPEN! Somebody has broken our
	       fdmail -channel... */
	    DIAGNOSTIC(rp, EX_TEMPFAIL,
		       "can't fstat mailbox \"%s\"", file);
	    close(fdmail);
	    setrootuid(rp);
	    return;
	}
	if (st.st_ino != s2.st_ino || st.st_dev != s2.st_dev ||
	    s2.st_nlink != 1) {
	    notaryreport(file,"delayed","450 (Lost race for mailbox)");
	    DIAGNOSTIC(rp, EX_TEMPFAIL,
		       "lost race for mailbox \"%s\"", file);
	    close(fdmail);
	    setrootuid(rp);
	    return;
	}

	alarm(180); /* Set an timed interrupt coming to us to break
		       overlengthy file lock acquisition.. */

	if (!S_ISREG(st.st_mode))
	  /* don't lock non-files */;
	else {
#ifdef	USE_NFSMBOX
	  if (lock(file) != 0) {
	    alarm(0);
	    notaryreport(file,"delayed","450 (File locking with NFS lock failed)");
	    DIAGNOSTIC(rp, EX_TEMPFAIL, "nfs lock() can't lock \"%s\"", file);
	    setrootuid(rp);
	    close(fdmail);
	    return;
	  }
#endif	/* USE_NFSMBOX */
#ifdef	USE_LOCKF
	  /* Seek to begining for locking! [mea@utu.fi] */
	  if (lseek(fdmail,0,0) < 0 ||
	      lockf(fdmail, F_LOCK, 0) < 0) {
	    alarm(0);
	    notaryreport(file,"delayed","450 (File locking with  lockf  failed)");
	    DIAGNOSTIC(rp, EX_TEMPFAIL, "lockf() can't lock \"%s\"", file);
	    setrootuid(rp);
	    close(fdmail);
	    return;
	  }

#endif	/* USE_LOCKF */
#ifdef	USE_FLOCK
	  if (flock(fdmail, LOCK_EX) < 0) {
	    alarm(0);
	    notaryreport(file,"delayed","450 (File locking with  flock  failed)");
	    DIAGNOSTIC(rp, EX_TEMPFAIL, "flock() can't lock \"%s\"", file);
	    setrootuid(rp);
	    close(fdmail);
	    return;
	  }
#endif	/* USE_LOCKF */
#ifdef	USE_MAILLOCK
	  if (*(rp->addr->user) == TO_FILE) {
	    if ((pw = getpwuid(uid)) == NULL) {
	      notaryreport(file,"failed","510 (File file has no owner)");
	      DIAGNOSTIC(rp, EX_NOPERM, "File \"%s\" has no owner", file);
	      return;
	    }
	    maillockuser = pw->pw_name;
	  }
	  else
	    maillockuser = rp->addr->user;

	  switch (maillock(maillockuser, 2)) {
	    case L_SUCCESS:
	      cp = NULL;
	      break;
	    case L_MAXTRYS:
	      notaryreport(file,"delayed","450 (File unlocking fails)");
	      DIAGNOSTIC(rp, EX_TEMPFAIL, "\"%s\" is locked", file);
	      return;
	    case L_NAMELEN:
	      cp = "recipient name > 13 chars";
	      break;
	    case L_TMPLOCK:
	      cp = "problem creating temp lockfile";
	      break;
	    case L_TMPWRITE:
	      cp = "problem writing pid into temp lockfile";
	      break;
	    case L_ERROR:
	      cp = "Something other than EEXIST happened";
	      break;
	    case L_MANLOCK:
	      cp = "cannot set mandatory lock on temp lockfile";
	      break;
	    default:
	      cp = "unknown maillock() error";
	      break;
          }
          if (cp != NULL) {
	    DIAGNOSTIC3(rp, EX_UNAVAILABLE,
			"Error locking \"%s\": %s", file, cp);
	    return;
	  }
#endif	/* USE_MAILLOCK */
	}

	/* Turn off the alarm */
	alarm(0);


#if	defined(BIFF) || defined(RBIFF)
	if (nbp != NULL)
	  nbp->offset = lseek(fdmail, 0L, L_XTND);
#endif	/* BIFF || RBIFF */
	fp = putmail(rp, fdmail, messagefd, timestring, file, dp);
	if (S_ISREG(st.st_mode)) {

#ifdef	USE_NFSMBOX
	  unlock(file);
#endif	/* USE_NFSMBOX */
#ifdef	USE_LOCKF
	  lseek(fdmail,0,0);
	  lockf(fdmail, F_ULOCK, 0);/* XX: does this really work?*/
	  /* Yes it does, but it needs seek to begining of file
	     first! [mea@utu.fi] */
#endif	/* USE_LOCKF */
#ifdef	USE_FLOCK
	  flock(fdmail, LOCK_UN);
#endif	/* USE_FLOCK */
#ifdef	USE_MAILLOCK
	  mailunlock();
#endif	/* USE_MAILLOCK */

	}
	setrootuid(rp);
	if (fp != NULL) {
	  fclose(fp);		/* this closes fdmail */
	  notaryreport(rp->addr->user,"delivered",
		       "250 (Delivered successfully)");
	  DIAGNOSTIC(rp, EX_OK, (char *)NULL, 0);
	}
#if	defined(BIFF) || defined(RBIFF)
	else if (nbp != NULL) /* putmail() has produced a DIAGNOSTIC */
	  nbp->offset = -1;
#endif	/* BIFF || RBIFF */
	return;
}

FILE *
putmail(rp, fdmail, messagefd, timestring, file, dp)
	struct rcpt *rp;
	int fdmail, messagefd;
	char *timestring, *file;
	struct ctldesc *dp;
{
	FILE *fp;
	int len;
	long eofindex;
	char buf[2];
	struct stat st;

	if ((fp = fdopen(fdmail, "a+")) == NULL) {
	  notaryreport(NULL,NULL,NULL);
	  DIAGNOSTIC(rp, EX_TEMPFAIL, "cannot fdopen(%d)", fdmail);
	  return NULL;
	}
	/* append From_ line and print out the header */
	eofindex = ftell(fp);
	if (*(rp->addr->user) != TO_PIPE && eofindex > 0L &&
	    fseek(fp, -2L, 2) != -1) {
	  /*
	   * Determine how many newlines to append to previous text.
	   *
	   * If the last characters are:	\n\n, append 0 newlines,
	   *				[^\n]\n or \n append 1 newline,
	   *				else append 2 newlines.
	   *
	   * Optionally preserve the mbox's atime, so
	   * login etc. can distinguish new mail from old.
	   * The mtime will be set to now by the following write() calls.
	   */
	  if (!keepatime || fstat(fileno(fp), &st) < 0)
	    st.st_atime = 0;
	  len = fread(buf, 1, 2, fp);
	  if (st.st_atime != 0) {
#ifdef	USE_TIMEVAL
	    struct timeval tv[2];
	    tv[0].tv_sec = st.st_atime;
	    tv[1].tv_sec = st.st_mtime;
	    tv[0].tv_usec = tv[1].tv_usec = 0;
	    utimes(file, tv);
#else  /* !USE_TIMEVAL */
	    struct utimbuf tv;
	    tv.actime = st.st_atime;
	    tv.modtime = st.st_mtime;
	    utime(file, &tv);
#endif /* USE_TIMEVAL */
	  }
	  if (len == 1 || len == 2) {
	    --len;
	    len = (buf[len]!='\n') + (len == 1 ? buf[0]!='\n' : 1);
	    fseek(fp, 0, 2);	/* to end of file, again */
	    if (len > 0 && (fwrite("\n\n", 1, len, fp) != len
			    || fflush(fp) == EOF)) {

	      notaryreport(NULL,NULL,NULL);
	      DIAGNOSTIC(rp, EX_IOERR,
			 "cleansing of \"%s\" failed", file);
	      /* XX: should I really do this? */
#ifdef	USE_FTRUNCATE
	      fflush(fp);
	      ftruncate(fileno(fp), (u_long)eofindex);
#endif /* USE_FTRUNCATE */
	      fclose(fp);
	      return NULL;
	    }
	  } else
	    fseek(fp, 0L, 2);	/* to end of file, again */
	} else if (eofindex > 0L && eofindex < (sizeof "From x\n")) {
	  /* A mail message *cannot* be this small.  It must be
	     a corrupted mailbox file.  Ignore them trash bytes. */
	  fseek(fp, 0L, 0);
	  eofindex = 0;
	}
	if (!(convert_qp && qp_to_8bit(rp)))
	  convert_qp = 0; /* If malloc failed, clear this too,
			     if it was not set, it doesn't change.. */

	if (fprintf(fp, "%s%s %s",
		    FROM_,rp->addr->link->user,timestring) == EOF
	    || writeheaders(rp,fp,"\n",convert_qp,0) < 0) {
	  notaryreport(rp->addr->link->user, "failed",
		       "500 (Write to user's mailbox failed)");
	  DIAGNOSTIC(rp, EX_IOERR,
		     "header write to \"%s\" failed", file);
	  /* XX: should I really do this? */
	  fflush(fp);
	  ftruncate(fileno(fp), (u_long)eofindex);
	  fclose(fp);
	  return NULL;
	}
	if (appendlet(rp, fp, file, messagefd) == EOF) {
	  /* XX: should I really do this? */
	  fflush(fp);
	  ftruncate(fileno(fp), (u_long)eofindex);
	  fclose(fp);
	  return NULL;
	}
	if (fputc('\n', fp) == EOF || fflush(fp) == EOF) {
	  notaryreport(NULL,NULL,NULL);
	  DIAGNOSTIC3(rp, EX_IOERR,
		      "message write to \"%s\" failed: %s",
		      file, strerror(errno));
	  /* XX: should I really do this? */
	  fflush(fp);
	  ftruncate(fileno(fp), (u_long)eofindex);
	  fclose(fp);
	  return NULL;
	}
	if (logfp != NULL) {
	  /* [haa@cs.hut.fi] added more info here to catch errors
	     in delivery  */
	  fprintf(logfp,
		  "%s: %ld + %ld : %s (pid %d user %s)\n",
		  dp->logident, eofindex,
		  ftell(fp) - eofindex, file,
		  getpid(), rp->addr->user);
#if 0
	  fprintf(logfp, "%s: %ld + %ld : %s\n",
		  dp->logident, eofindex,
		  ftell(fp) - eofindex, file);
#endif
	  fflush(logfp);
	}
	return fp;
}

void
program(rp, messagefd, timestring, dp)
	struct rcpt *rp;
	int messagefd;
	char *timestring;
	struct ctldesc *dp;
{
	int i, pid, uid, gid, in[2], out[2];
	char *env[20], buf[8192], *cp, *s;
#ifdef	USE_UNIONWAIT
	union wait status;
#else	/* !USE_UNIONWAIT */
	int status;
#endif	/* USE_UNIONWAIT */
	struct passwd *pw;
	FILE *errfp;
	FILE *fp;

	i = 0;
	env[i++] = "SHELL=/bin/sh";
	env[i++] = "IFS= \t\n";
	cp = buf;
	if ((s = getzenv("PATH")) == NULL)
	  env[i++] = "PATH=/usr/ucb:/usr/bin:/bin";
	else {
	  sprintf(cp, "PATH=%s", s);
	  env[i++] = cp;
	  cp += strlen(cp) + 1;
	}
	uid = atoi(rp->addr->misc);
	if ((pw = getpwuid(uid)) == NULL) {
	  gid = 0;
	  env[i++] = "HOME=/tmp";
	  env[i++] = "USER=anonymous";
	} else {
	  gid = pw->pw_gid;
	  sprintf(cp, "HOME=%s", pw->pw_dir);
	  env[i++] = cp;
	  cp += strlen(cp) + 1;
	  sprintf(cp, "USER=%s", pw->pw_name);
	  env[i++] = cp;
	  cp += strlen(cp) + 1;
	}
	sprintf(cp, "SENDER=%s", rp->addr->link->user);
	env[i++] = cp;
	cp += strlen(cp) + 1;
	sprintf(cp, "UID=%d", uid);
	env[i++] = cp;
	if ((s = getzenv("ZCONFIG")) == NULL)
	  s = ZMAILER_ENV_FILE;
	cp += strlen(cp) + 1;
	sprintf(cp, "ZCONFIG=%s", s);
	env[i++] = cp;
	if ((s = getzenv("MAILBIN")) == NULL)
		s = MAILBIN;
	cp += strlen(cp) + 1;
	sprintf(cp, "MAILBIN=%s", s);
	env[i++] = cp;
	if ((s = getzenv("MAILSHARE")) == NULL)
		s = MAILSHARE;
	cp += strlen(cp) + 1;
	sprintf(cp, "MAILSHARE=%s", s);
	env[i++] = cp;
	env[i] = NULL;

	/* now we can fork off and run the command... */
	if (pipe(in) < 0) {
	  notaryreport(NULL,NULL,NULL);
	  DIAGNOSTIC(rp, EX_OSERR, "cannot create pipe from \"%s\"",
		     rp->addr->user+1);
	  return;
	}
	if (pipe(out) < 0) {
	  notaryreport(NULL,NULL,NULL);
	  DIAGNOSTIC(rp, EX_OSERR, "cannot create pipe to \"%s\"",
		     rp->addr->user+1);
	  close(in[0]);
	  close(in[1]);
	  return;
	}
	if ((pid = fork()) == 0) { /* child */
	  environ = env;
	  setgid(gid);
	  setuid(uid);
	  close(in[0]);
	  close(out[1]);
	  /* its stdout and stderr is the pipe, its stdin is our fdmail */
	  close(0);
	  dup(out[0]);		/* in fd 0 */
	  close(1);
	  dup(in[1]);		/* in fd 1 */
	  close(2);
	  dup(in[1]);		/* in fd 2 */
	  close(out[0]);
	  close(in[1]);
	  signal(SIGINT, SIG_IGN);
	  signal(SIGHUP, SIG_IGN);
	  signal(SIGTERM, SIG_DFL);
	  /*
	   * Note that argv[0] is set to the command we are running.
	   * That way, we should get some better error messages, at
	   * least more understandable in rejection messages.
	   * Some bourne shells may go into restricted mode if the
	   * stuff to run contains an 'r'. XX: investigate.
	   */
	  execl("/bin/sh", rp->addr->user+1,
		"-c", rp->addr->user+1, (char *)NULL);
	  execl("/sbin/sh", rp->addr->user+1,
		"-c", rp->addr->user+1, (char *)NULL);
	  write(2, "Cannot exec /bin/sh\n", 20);
	  _exit(128);
	} else if (pid < 0) {	/* fork failed */
	  notaryreport(NULL,NULL,NULL);
	  DIAGNOSTIC(rp, EX_OSERR, "cannot fork", 0);
	  return;
	} /* parent */
	close(out[0]);
	close(in[1]);
	errfp = fdopen(in[0], "r");
	/* write the message */
	fp = putmail(rp, out[1], messagefd, timestring, rp->addr->user+1, dp);
	if (fp == NULL) {
	  pid = wait(&status);
	  close(out[1]);
	  fclose(errfp);
	  close(in[0]);
	  return;
	}
	fclose(fp);
	/* read any messages from its stdout/err on in[0] */
	/* ... having forked and set up the pipe, we quickly continue */
	if (fgets(buf, sizeof buf, errfp) == NULL)
		buf[0] = '\0';
	else if ((cp = strchr(buf, '\n')) != NULL)
		*cp = '\0';
	pid = wait(&status);
	close(out[1]);
	fclose(errfp);
	close(in[0]);
	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) {
	  if (cp != buf)
	    *cp++ = ' ';
	  sprintf(cp, "[exit status %d]", status.w_retcode);
	  i = EX_UNAVAILABLE;
	} else
		i = EX_OK;
#else	/* !USE_UNIONWAIT */
	if ((status&0177) > 0) {
	  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) {
	  if (cp != buf)
	    *cp++ = ' ';
	  sprintf(cp, "[exit status %d]", (status>>8)&0377);
	  i = EX_UNAVAILABLE;
	} else
		i = EX_OK;
#endif	/* USE_UNIONWAIT */
	notaryreport(NULL,NULL,NULL); /* XX: proper values ? */
	DIAGNOSTIC(rp, i, "%s", buf);
	return;
}

/*
 * creatembox - see if we can create the mailbox
 */
int
creatembox(rp, filep, uid, gid, pw)
	struct rcpt *rp;
	char **filep;
	short *uid, *gid;
	struct passwd *pw;
{
	char **maild;
	int fd;

	*uid = (short)pw->pw_uid;
	*gid = (short)pw->pw_gid;

	for (maild = &maildirs[0]; *maild != 0; maild++) {
	  if (*filep != NULL)
	    free(*filep);
	  *filep = emalloc(2+strlen(*maild)+strlen(rp->addr->user));
	  sprintf(*filep, "%s/%s", *maild, rp->addr->user);
	  if ((fd = createfile(rp, *filep, *uid)) >= 0) {
#ifdef	USE_FCHOWN
	    fchown(fd, *uid, *gid);
#else  /* !USE_FCHOWN */
	    chown(*filep, *uid, *gid);
#endif /* USE_FCHOWN */
	    close(fd);
	    return 1;
	  }
	}

	/* assert *filep != NULL */
	if (fd == -1)
	  DIAGNOSTIC(rp, EX_CANTCREAT, "can't create \"%s\"", *filep);
	/* otherwise the message was printed by createfile() */
	free(*filep);
	return 0;
}

int
createfile(rp, file, uid)
	struct rcpt *rp;
	char *file;
	short uid;
{
	int fd, i = 0, saverrno;
	struct stat st;
	char *cp, msg[BUFSIZ];

	if ((fd = open(file, O_RDWR|O_CREAT|O_EXCL, MAILMODE)) < 0) {
	  saverrno = errno;
	  if ((cp = strrchr(file, '/')) != NULL) {
	    *cp = '\0';
	    if (exstat(rp, file, &st, stat) < 0) {
	      DIAGNOSTIC(rp, i, "stat failed on %s", file);
	      return -2;
	    }
	    if (st.st_mode & 020) { /* group writable? */
	      if (!setupuidgid(rp, uid, st.st_gid)) {
		DIAGNOSTIC(rp, i,
			   "failed changing group id to create file in \"%s\"",
			   file);
		return -2;
	      }
	    }
	    *cp = '/';
	  }
	  if ((fd = open(file, O_RDWR|O_CREAT|O_EXCL, MAILMODE)) < 0) {
	    setrootuid(rp);
	    saverrno = errno;
	  } else {
	    setrootuid(rp);
	    return fd;
	  }
	} else
		return fd;

	/* No system calls in this spot -- must preserve errno */
	if (TEMPFAIL(saverrno))	/* temporary error? */
	  i = EX_TEMPFAIL;
	else if (saverrno != EACCES && saverrno != ENOENT)
	  i = EX_UNAVAILABLE;
	else if (saverrno == EACCES)
	  i = EX_NOPERM;
	else /* if (saverrno == ENOENT) */
	  i = EX_CANTCREAT;
	/* convoluted to maintain 4 arguments to DIAGNOSTIC */
	sprintf(msg, "error (%s) creating \"%%s\"", strerror(saverrno));
	DIAGNOSTIC(rp, i, msg, file);
	return -2;
}

/*
 * setupuidgid - set the euid and gid of the process
 */
int
setupuidgid(rp, uid, gid)
	struct rcpt *rp;
	int uid;
	int gid;
{
	setrootuid(rp);
	if (setgid(gid) < 0) {
	  DIAGNOSTIC(rp, EX_OSERR, "can't setgid to %d", gid);
	  return 0;
	}
	if (*(rp->addr->user) == TO_FILE || *(rp->addr->user) == TO_PIPE)
	  uid = atoi(rp->addr->misc);
	if (setreuid(-1, uid) < 0) {
	  if (uid < 0 && atoi(rp->addr->misc) < 0) {
	    /* use magic non-sense +ve uid < MAXSHORT */
	    rp->addr->misc = NONUIDSTR;
	    return setupuidgid(rp, atoi(NONUIDSTR), gid);
	  }
	  DIAGNOSTIC(rp, EX_OSERR, "can't seteuid to %d", uid);
	  return 0;
	}
	currenteuid = uid;
	return 1;
}


/*
 * exists - see if a mail box exists.  Looks at the exit status
 *	    to guess whether failure is due to nfs server being
 *	    down.
 */

char *
exists(maildir, rp)
	char *maildir;
	struct rcpt *rp;
{
	char *file;

	file = emalloc(2+strlen(maildir)+strlen(rp->addr->user));
	sprintf(file, "%s/%s", maildir, rp->addr->user);
	if (access(file, F_OK) == 0) { /* file exists */
	  rp->status = EX_OK;
	  return file;
	}
	free(file);

	if (TEMPFAIL(errno)) {	/* temporary error? */
	  DIAGNOSTIC(rp, EX_TEMPFAIL, "error accessing \"%s\"", file);
	} else if (errno == ENOENT || errno == EACCES) /* really not there? */
	  rp->status = EX_OK;
	else {					/* who knows? */
	  DIAGNOSTIC(rp, EX_SOFTWARE,
		     "unexpected error accessing \"%s\"", file);
	}
	return NULL;
}


/*
 * setrootuid - set us back to root uid
 */

void
setrootuid(rp)
	struct rcpt *rp;
{
	if (currenteuid != 0) {
	  if (setreuid(-1, 0) < 0)
	    DIAGNOSTIC(rp, EX_OSERR, "can't reset uid to root", 0);
	}
	currenteuid = 0;
}


/*
 * appendlet - append letter to file pointed at by fd
 */
int
appendlet(rp, fp, file, mfd)
	struct rcpt *rp;
	FILE *fp;
	int mfd;
	char *file;
{
	static char buffer[BUFSIZ];
	register int i;
	register int bufferfull;

	writebuf((FILE *)NULL, (char *)NULL, 0);	/* initialize state */

	/* can we use cache of message body data */
	if (readalready != 0) {
	  if (writebuf(fp, buffer, readalready) != readalready) {
	    DIAGNOSTIC(rp, EX_IOERR,
		       "write to \"%s\" failed", file);
	    return EOF;
	  }
	  rp->status = EX_OK;
	  return 0;
	}

	/* we are assumed to be positioned properly at start of message body */
	bufferfull = 0;
	while ((i = read(mfd, buffer, sizeof buffer)) != 0) {
	  if (i < 0) {
	    DIAGNOSTIC(rp, EX_IOERR,
		       "read error from message file", 0);
	    return EOF;
	  }
	  if (writebuf(fp, buffer, i) != i) {
	    DIAGNOSTIC(rp, EX_IOERR,
		       "write to \"%s\" failed", file);
	    return EOF;
	  }
	  readalready = i;
	  bufferfull++;
	}

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


/*
 * estat - stat with error checking
 */
int
exstat(rp, file, stbufp, statfcn)
	struct rcpt *rp;
	char *file;
	struct stat *stbufp;
	int (*statfcn)();
{
	if (statfcn(file, stbufp) < 0) {
	  if (TEMPFAIL(errno))
	    rp->status = EX_TEMPFAIL;
	  else
	    rp->status = EX_SOFTWARE;
	  DIAGNOSTIC(rp, rp->status, "can't stat \"%s\"", file);
	  return -1;
	}
	return 0;
}

#if	defined(BIFF) || defined(RBIFF)
void
biff(hostname, username, offset)
	char *hostname, *username;
	long offset;
{
	int f, symid;
	struct hostent *hp;
	static struct servent *sp = NULL;
	struct sockaddr_in biffaddr, *bap;
	struct spblk *spl;
	static struct sptree *spt_hosts = NULL;
	char *buf;

	if (sp == NULL && (sp = getservbyname("biff", "udp")) == NULL)
	  return;
	symid = symbol(hostname);
	if (spt_hosts == NULL)
	  spt_hosts = sp_init();
	spl = sp_lookup(symid, spt_hosts);
	if (spl == NULL) {
	  if ((hp = gethostbyname(hostname)) == NULL)
	    return;
	  bzero((char *)&biffaddr, sizeof biffaddr);
	  biffaddr.sin_family = hp->h_addrtype;
	  hp_init(hp);
	  bcopy(*hp_getaddr(), (char *)&biffaddr.sin_addr, hp->h_length);
	  biffaddr.sin_port = sp->s_port;
	  bap = (struct sockaddr_in *)emalloc(sizeof(struct sockaddr_in));
	  *bap = biffaddr;
	  sp_install(symid, (char *)bap, 0, spt_hosts);
	} else
	  biffaddr = *(struct sockaddr_in *)spl->data;
	buf = emalloc(3+strlen(username)+20);
	sprintf(buf, "%s@%ld\n", username, offset);
	f = socket(AF_INET, SOCK_DGRAM, 0);
	sendto(f, buf, strlen(buf)+1, 0,
	       (struct sockaddr *)&biffaddr, sizeof biffaddr);
	free(buf);
	close(f);
}
#endif	/* BIFF || RBIFF */


#ifdef RBIFF
readrwho()
{
	struct whod wd;
	struct outmp outmp;
#define NMAX sizeof(outmp.out_name)
#define LMAX sizeof(outmp.out_line)

	int cc, width, f, n, i, symid;
	register struct whod *w = &wd;
	register struct whoent *we;
	register char *tp, *s;
	register FILE *fi;
	long now;
	char username[NMAX+1];
	DIR *dirp, *opendir();
	struct NDIR_TYPE *dp, *readdir();
	struct userhost *uhp;
	struct spblk *spl;
	
	if (spt_users == NULL)
	  return;
	now = time(0);
	if (chdir(RWHODIR) || (dirp = opendir(".")) == NULL ) {
	  perror(RWHODIR);
	  exit(EX_OSFILE);
	}
	while (dp = readdir(dirp)) {
	  if (dp->d_ino == 0
	      || strncmp(dp->d_name, "whod.", 5)
	      || (f = open(dp->d_name, 0)) < 0 )
	    continue;
	  cc = read(f, (char *)&wd, sizeof (struct whod));
	  if (cc < WHDRSIZE) {
	    close(f);
	    continue;
	  }
	  if (now - w->wd_recvtime > 5 * 60) {
	    close(f);
	    continue;
	  }
	  cc -= WHDRSIZE;
	  we = w->wd_we;
	  for (n = cc / sizeof (struct whoent); n > 0; n--,we++){
	    /* make sure name null terminated */
	    strncpy(username, we->we_utmp.out_name, NMAX);
	    username[NMAX] = 0;
	    /* add to data structure */

	    symid = symbol(username);
	    if ((spl = sp_lookup(symid, spt_users)) == NULL)
	      continue;
	    uhp = (struct userhost *)spl->data;
	    if (uhp != NULL
		&& strcmp(uhp->hostname, w->wd_hostname) == 0)
	      continue;

	    uhp = (struct userhost *)emalloc(sizeof (struct userhost));
	    uhp->next = (struct userhost *)spl->data;
	    uhp->hostname = strsave(w->wd_hostname);
	    spl->data = (unsigned char *)uhp;
	  }
	  close(f);
	}
	closedir(dirp);
}

void
rbiff(nbp)
	struct biffer *nbp;
{
	struct spblk *spl;
	struct userhost *uhp;
	int symid;

	symid = symbol(nbp->user);
	if ((spl = sp_lookup(symid, spt_users)) == NULL)
	  return;
	for (uhp = (struct userhost *)spl->data; uhp != NULL; uhp = uhp->next)
	  biff(uhp->hostname, nbp->user, nbp->offset);
}
#endif	/* RBIFF */

/*
 * Writebuf() is like write(), except any instances of "From " at the
 * beginning of a line cause insertion of '>' at that point.
 */

#if  1 /* Modified code! */
int
writebuf(fp, buf, len)
	FILE *fp;
	char *buf;
	int len;
{
	register char *cp, *s;
	register int n;
	int wlen, tlen, i = 0;
	register char expect;
	static char save = '\0';
	static int qp_hex = 0;
	static int qp_chrs = 0;
	static char frombuf[8];
	static char *fromp = NULL;

	if (buf == NULL) {	/* magic initialization */
	  save = 'F';
	  qp_hex = 0;
	  qp_chrs = 0;
	  frombuf[0] = 0;
	  fromp = frombuf;
	  return 0;
	}
	expect = save;
	for (cp = buf, n = len, tlen = 0, s = NULL; n > 0; --n, ++cp) {
	  register int c = *cp;
	  ++tlen;

	  /* At first, convert the possible QP character.. */
	  if (convert_qp) {
	    if (!qp_chrs && c == '=') {
	      qp_chrs = 2;
	      qp_hex  = 0;
	      continue; /* '=' starts something */
	    }
	    if (qp_chrs && c == '\n') {
	      qp_chrs = 0;  /* It was "=[ \t]*\n", which was thrown away. */
	      continue;
	    }
	    if (qp_chrs == 2 && (c == ' ' || c == '\t'))
	      continue; /* It is "=[ \t]*\n", throw it away */
	    /* Ok, this may be of  =HexHex ? */
	    if (qp_chrs && ((c >= '0' && c <= '9') ||
			    (c >= 'A' && c <= 'F') ||
			    (c >= 'a' && c <= 'f'))) {
	      qp_hex <<= 4;
	      if (c >= '0' && c <= '9') qp_hex += (c - '0');
	      if (c >= 'A' && c <= 'F') qp_hex += (c - 'A' + 10);
	      if (c >= 'a' && c <= 'f') qp_hex += (c - 'a' + 10);
	      --qp_chrs;
	      if (!qp_chrs) c = qp_hex;
	      else continue; /* Go collect another digit */
	    } else if (qp_chrs)
	      qp_chrs = 0; /* Failed a HEX check.. */
	  }
	  /* Now we have a converted character.. */

	  if (c == '\n') {
	    expect = 'F';
	    frombuf[0] = 0;
	    fromp = frombuf;
	    if (putc(c,fp) == EOF)
	      { tlen = -1; break; }
	  } else if (expect != '\0') {
	    if (c == expect) {
	      *fromp++ = c;
	      *fromp   = 0;
	      switch (expect) {
	        case 'F':	expect = 'r'; s = cp; 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; }
	  }
	}
	if (tlen >= 0) return len; /* It worked ok */
	return -1;	/* errno is correct if tlen == -1 ! */
}
#else	/* The original code.. */

int
writebuf(fp, buf, len)
	FILE *fp;
	char *buf;
	int len;
{
	register char *cp, *s;
	register int n;
	int wlen, tlen, i = 0;
	register char expect;
	static char save = '\0';

	if (buf == NULL) {		/* magic initialization */
	  save = 'F';
	  return 0;
	}
	expect = save;
	for (cp = buf, n = len, wlen = tlen = 0, s = NULL; n > 0; --n, ++cp) {
	  if (*cp == '\n')
	    expect = 'F';
	  else if (expect != '\0') {
	    if (*cp == expect) {
	      switch (expect) {
	      case 'F':	expect = 'r'; s = cp; break;
	      case 'r':	expect = 'o'; break;
	      case 'o':	expect = 'm'; break;
	      case 'm':	expect = ' '; break;
	      case ' ':
		/* write from start of buffer to s */
		if (s > buf && tlen >= 0) {
		  wlen = fwrite(buf,
				1, s-buf, fp);
		  if (wlen > 0)
		    tlen += wlen;
		  else
		    tlen = -1;
		} else if (s == NULL) {
		  i = sizeof FROM_ -(cp-buf)-2;
		  fseek(fp, -i, 1);
		}
		/* write separator character */
		if (tlen >= 0) {
		  save = '>';
		  fwrite(&save, 1, 1, fp);
		}
		expect = '\0';
		if (s == NULL) {
		  fwrite(FROM_, 1, i, fp);
		  s = buf;
		}
		/* anticipate future instances */
		buf = s;
		break;
	      }
	    } else
	      expect = '\0';
	  }
	}
	save = expect;
	if (cp > buf && tlen >= 0) {
	  if ((wlen = fwrite(buf, 1, cp-buf, fp)) > 0)
	    tlen += wlen;
	  else
	    tlen = -1;
	}
	return tlen;	/* errno is correct if tlen == -1 ! */
}
#endif


/* [mea] See if we have a MIME  TEXT/PLAIN message encoded with
         QUOTED-PRINTABLE... */

int
qptext_check(rp)
	struct rcpt *rp;
{
	/* "Content-Transfer-Encoding: 8BIT" */

	char **hdrs = *(rp->newmsgheader);
	char *hdr;

	int cte = 0;
	int mime = 0;

	if (hdrs == NULL) return 0;

	while (hdrs && *hdrs && (!mime || !cte)) {
	  hdr = *hdrs;
	  if (!cte && cistrncmp(hdr,"Content-Transfer-Encoding:",26)==0) {
	    hdr += 26;
	    while (*hdr == ' ' || *hdr == '\t') ++hdr;
	    if (cistrncmp(hdr,"QUOTED-PRINTABLE",16)==0)
	      cte = 1;
	  } else if (!mime && cistrncmp(hdr,"MIME-Version:",13)==0)
	    mime = 1;

	  ++hdrs;
	}
	if (!mime) cte = 0;
	return cte;
}
