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

/* 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.
 * Still more modifications by Matti Aarnio <mea@nic.funet.fi>
 */

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

#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

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef	HAVE_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 */
#endif	/* HAVE_LOCKF */

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

#ifdef	HAVE_MAILLOCK_H
# include <maillock.h>
#endif

#ifdef HAVE_DOTLOCK
#include "dotlock.c"
#endif

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


#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 HAVE_DIRENT_H
# include <dirent.h>
#else /* not HAVE_DIRENT_H */
# define dirent direct
# ifdef HAVE_SYS_NDIR_H
#  include <sys/ndir.h>
# endif /* HAVE_SYS_NDIR_H */
# ifdef HAVE_SYS_DIR_H
#  include <sys/dir.h>
# endif /* HAVE_SYS_DIR_H */
# ifdef HAVE_NDIR_H
#  include <ndir.h>
# endif /* HAVE_NDIR_H */
#endif /* HAVE_DIRENT_H */

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

#ifdef HAVE_PROTOCOLS_RWHOD_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	/* HAVE_PROTOCOLS_RWHOD_H */

#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",
	"/usr/mail",
	"/var/spool/mail",
	"/usr/spool/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), 0, (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), 0, \
						(A1), (A2), (A3))

#if	defined(HAVE_SOCKET)
/*
 * 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 /* HAVE_SOCKET -- no use to biff, if no socket available .. */

char *progname;
char *channel;
char *logfile;
FILE *logfp;
FILE *verboselog = NULL;
int readalready = 0;		/* does buffer contain valid message data? */
uid_t currenteuid;		/* the current euid */
extern int nobody;		/* safe uid for file/program delivery */
int dobiff = 1;			/* enable biff notification */
int dorbiff = 0;		/*    "   rbiff   " */
int dorbiff_always = 0;
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.. */
int conversion_prohibited = 0;	/* Flag: Under no circumstances touch on it.. */
int is_mime    = 0;		/* Flag: Msg is MIME msg .. */
char *mime_boundary = NULL;
int   mime_boundary_len = 0;
int mmdf_mode = 0;		/* Write out MMDF-style mail folder
				   ("\001\001\001\001" as the separator..)  */
long eofindex = -1;		/* When negative, putmail() can't truncate() */

extern RETSIGTYPE wantout();
extern time_t time();
extern int optind;
extern char *optarg;
extern int emptyline();
extern void prversion(), getnobody(), biff(), rbiff();
extern struct passwd *getpwnam();
extern struct passwd *getpwuid();
extern int setupuidgid();
extern int createfile();
extern int exstat();
extern int creatembox();
extern char *exists(), *strerror();
extern void setrootuid();
extern void process __((struct ctldesc *dp));
extern void deliver __((struct ctldesc *dp, struct rcpt *rp, char *timestring));
extern void program __((struct ctldesc *dp, struct rcpt *rp, char *timestring));
extern FILE *putmail __((struct ctldesc *dp, struct rcpt *rp, int fdmail, char *fdopmode, char *timestring, char *file));
extern int appendlet __((struct ctldesc *dp, struct rcpt *rp, FILE *fp, char *file, int ismime));
extern char **environ;
extern int writebuf __((FILE *fp, char *buf, int len, int *lastchp));
extern int writemimeline __((FILE *fp, char *buf, int len, int *lastchp));

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 univptr_t emalloc();
#else
struct conshell *envarlist = NULL;
#endif
extern int stickymem;	/* for strsave() */
int	D_alloc = 0;

static void sig_alrm(sig)
int sig;
{
	/* Sigh, actually dummy routine.. */
}


#ifndef	MAXPATHLEN
#define	MAXPATHLEN 1024
#endif

int
main(argc, argv)
	int argc;
	char *argv[];
{
	char file[MAXPATHLEN+128];
	char *s;
	int c, errflg, fd;
	char *host = NULL;	/* .. and what is my host ? */
	int matchhost = 0;
#if	defined(HAVE_SOCKET)
	struct biffer *nbp;
#endif
	struct ctldesc *dp;

	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_HANDLE(SIGALRM, sig_alrm); /* Actually ignored, but
					     fcntl() will break ? */

	SIGNAL_IGNORE(SIGPIPE);

	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:gh:l:rRMV8")) != EOF) {
	  switch (c) {
	  case 'c':		/* specify channel scanned for */
	    channel = optarg;
	    break;
	  case 'M':
	    mmdf_mode = 1;
	    break;
	  case 'V':
	    prversion(PROGNAME);
	    exit(EX_OK);
	    break;
	  case 'b':		/* toggle biffing */
	    dobiff = !dobiff;
	    break;
	  case 'r':		/* toggle rbiffing */
	    dorbiff = !dorbiff;
	    break;
	  case 'R':
	    dorbiff_always = !dorbiff_always;
	    break;
	  case 'a':		/* toggle mbox st_atime preservation */
	    keepatime = !keepatime;
	    break;
	  case 'g':		/* toggle file creation */
	    creatflag = !creatflag;
	    break;
	  case 'h':
	    host = strdup(optarg);
	    matchhost = 1;
	    break;
	  case 'l':		/* log file */
	    logfile = strdup(optarg);
	    break;
	  case '8':
	    mime8bit = 1;
	    break;
	  default:
	    ++errflg;
	    break;
	  }
	}
	if (errflg || optind != argc) {
	  fprintf(stderr, "Usage: %s [-V8abr] [-c channel] [-h host]\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) {

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

	  setreuid(0,0); /* We begin as roots..  process() may change us */
	  currenteuid = 0;

	  dp = ctlopen(file, channel, host, &getout, NULL, NULL);
	  if (dp == NULL) {
	    printf("#resync %s\n",file);
	    fflush(stdout);
	    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(HAVE_SOCKET)
	  /* now that the delivery phase is over, hoot'n wave */
	  for (nbp = biffs ; nbp != NULL; nbp = nbp->next) {
	    if (dobiff && nbp->offset >= 0)
	      biff("localhost", nbp->user, nbp->offset);
#ifdef	HAVE_PROTOCOLS_RWHOD_H /* 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	HAVE_PROTOCOLS_RWHOD_H /* RBIFF */
	  if (spt_users != NULL) {
	    if (readrwho())
	      for (nbp = biffs ; nbp != NULL; nbp = nbp->next) {
		if (nbp->offset >= 0 && nbp->wantrbiff)
		  rbiff(nbp);
	      }
	  }
#endif	/* RBIFF */
	  while ((nbp = biffs) != NULL) {
	    biffs = nbp->next;
	    free(nbp->user);
	    free(nbp);
	  }
	  {
	    extern void free();
	    sp_scan(free,NULL,spt_users);
	    free(spt_users);
	    spt_users = NULL;
	  }
#endif	/* HAVE_SOCKET */
	}
	exit(EX_OK);
	/* NOTREACHED */
	return 0;
}

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

	conversion_prohibited = check_conv_prohibit(dp->recipients);
	if (!conversion_prohibited)
	  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 ((plus =  strchr(rp->addr->user,'+')) != NULL) *plus = 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.. */
	  if (plus) *plus = '+';
#ifdef  HAVE_SOCKET
#ifdef	HAVE_PROTOCOLS_RWHOD_H /* RBIFF */
	  /*
	   * "contents" will be freed before rbiff done:
	   * must copy username somewhere.
	   */
	  rp->addr->user = strdup(rp->addr->user);
#endif /* RBIFF */
#endif /* BIFF */
	}

	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(dp, rp, ts);
	  }
	}
}

/*
 * propably_x400() -- some heuristics to see if this is likely
 *		      a mis-written X.400 address
 */
int
propably_x400(addr)
	char *addr;
{
	int slashes = 0;
	int equs = 0;

	while (*addr) {
	  if (*addr == '/') {
	    ++slashes;
	  } else if (*addr == '=') {
	    ++equs;
	  } else {
	    ; /* Hmm...  No other testings */
	  }
	  ++addr;
	}

	/* Match things like:
		/X=yyy/Z=xxx/
		/G=fff/S=kkk/O=gggg/@foo.faa
		/=/
	   But not:
		/foo/faa/path
	 */

	if (equs > 0 && equs <= slashes)
		return 1;

	return 0;
}


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

void
deliver(dp, rp, timestring)
	struct ctldesc *dp;
	struct rcpt *rp;
	char *timestring;
{
	register char **maild;
	int fdmail;
	uid_t uid;
	FILE *fp;
	int hasdir;
	struct stat st, s2;
	char *file = NULL, *cp, *at, *plus;
	int havedotlock = 0, havemaillock = 0;
	int ismbox = 0;
#if	defined(HAVE_SOCKET)
	struct biffer *nbp = NULL;
#ifdef	HAVE_PROTOCOLS_RWHOD_H
	char *path;
#endif
#endif
	struct passwd *pw;
#ifdef	HAVE_MAILLOCK_H
	char *maillockuser;
#endif

	uid  = atol(rp->addr->misc);
	at   = strchr(rp->addr->user,'@');
	plus = 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",
			 "5.2.1 (Mail to program disallowed w/o proper privileges)",
			 "550 (Mail to program disallowed w/o proper privileges)");
	    DIAGNOSTIC(rp, EX_UNAVAILABLE,
		       "mail to program disallowed", 0);
	    return;
	  }
	  program(dp, rp, timestring);
	  /* 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) {
	    if (propably_x400(rp->addr->user)) {
	      notaryreport(rp->addr->user,"failed",
			   "5.1.4 (this feels like a misplaced X.400 address -- no support for them)",
			   "550 (this feels like a misplaced X.400 address -- no support for them)");
	      DIAGNOSTIC(rp, EX_UNAVAILABLE,
			 "This appears to be a misplaced X.400 address, no support for them", 0);
	    } else {
	      notaryreport(rp->addr->user,"failed",
			   "5.2.1 (Mail to file disallowed w/o proper privileges)",
			   "550 (mail to file disallowed w/o proper privileges)");
	      DIAGNOSTIC(rp, EX_UNAVAILABLE,
			 "mail to file disallowed", 0);
	    }
	    return;
	  }
	  if (!setupuidgid(rp, uid, 1)) {
	    setrootuid(rp);
	    return;
	  }
	  file = strdup(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] -- same with '+' --  user+localspecs */
	  ismbox = 1;
	  if (at)   *at   = 0;
	  if (plus) *plus = 0;
	  setpwent(); /* Effectively rewind the database,
			 needed for multi-recipient processing ? */
	  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 = '@';
	      if (plus) *plus = '+';
	      if (propably_x400(rp->addr->user)) {
		notaryreport(rp->addr->user,"failed",
			     "5.1.4 (this feels like a misplaced X.400 address -- no support for them)",
			     "550 (this feels like a misplaced X.400 address -- no support for them)");
		DIAGNOSTIC(rp, EX_UNAVAILABLE,
			   "This appears to be a misplaced X.400 address, no support for them", 0);
	      } else {
		notaryreport(rp->addr->user,"failed",
			     "5.1.1 (User does not exist)",
			     "550 (User does not exist)");
		if (at) *at = 0;
		if (plus) *plus = '+';
		DIAGNOSTIC(rp, EX_NOUSER,
			   "user \"%s\" doesn't exist", rp->addr->user);
	      }
	      if (at) *at = '@';
	      if (plus) *plus = '+';
	      return;
	    }
	  }
	  hasdir = 0;
	  for (maild = &maildirs[0]; *maild != 0; maild++) {
	    if (stat(*maild,&st) < 0 ||
		!S_ISDIR(st.st_mode))
	      /* Does not exist, or is not a directory */
	      continue;
	    hasdir = 1;
	    if ((file = exists(*maild, rp)) != NULL)
	      break;		/* found it */
	    if (rp->status != EX_OK)
	      return;
	  }

	  if (hasdir && *maild == 0 &&
	      !creatembox(rp, &file, &st.st_uid, &st.st_gid, pw))
	    return; /* creatembox() sets status */

	  if (!hasdir) {	/* No directory ?? */

	    char *mailbox = getzenv("MAILBOX");
	    notaryreport(rp->addr->user,"failed",
			 "4.3.5 (System mailbox configuration is wrong, we are in deep trouble..)",
			 "466 (System mailbox configuration is wrong!  No such directory!  Aargh!)");
	    DIAGNOSTIC(rp, EX_TEMPFAIL,
		       "System mailbox configuration is wrong!  No such directory (%s)!  Aargh!",maildirs[0]);
#ifdef  LOG_PID
#ifdef  LOG_MAIL
	    openlog("scheduler", LOG_PID, LOG_MAIL);
#else   /* !LOG_MAIL */
	    openlog("scheduler", LOG_PID);
#endif  /* LOG_MAIL */
#endif  /* LOG_PID */
	    if (!mailbox) 
	      syslog(LOG_ALERT, "ZMailer mailbox can't open local mail folder directory! (Using BUILTIN list of choices)\n");
	    else
	      syslog(LOG_ALERT, "ZMailer mailbox can't open local mail folder directory!  $MAILBOX='%s'\n", mailbox);
	    closelog();
	    return;
	  }
#if	defined(HAVE_SOCKET)
	  if (!dobiff && !dorbiff && !dorbiff_always)
	    break;
	  nbp = (struct biffer *)emalloc(sizeof (struct biffer));
	  nbp->next = NULL;
	  nbp->user = strdup(rp->addr->user);
	  nbp->offset = -1;
	  nbp->wantrbiff = 0;
#ifdef	HAVE_PROTOCOLS_RWHOD_H
	  if (dorbiff) {
	    if (dorbiff_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 */
	  if (biffs == NULL)
	    biffs = eobiffs = nbp;
	  else {
	    eobiffs->next = nbp;
	    eobiffs = nbp;
	  }
#endif /* BIFF || RBIFF */
	  if (at)   *at   = '@';
	  if (plus) *plus = '+';
	  break;
	}
	if (exstat(rp, file, &st, lstat) < 0) {
	  notaryreport(rp->addr->user,"failed",
		       "4.2.0 (User's mailbox disappeared, will retry)",
		       "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,"delivery",
		       "2.2.0 (delivery successfull)",
		       "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(rp->addr->user,"failed",
		       "5.2.1 (Attempting to deliver into non-regular file)",
		       "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(rp->addr->user,"failed",
		       "4.2.1 (Destination file ambiguous)",
		       "500 (Destination file has more than one name..)");
	  DIAGNOSTIC(rp, EX_UNAVAILABLE,
		     "too many links to file \"%s\"", file);
	  return;
	}
	/* [Edwin Allum]
	 * If we're delivering to a mailbox, and the mail spool directory
	 * is group writable, set our gid to that of the directory.
	 * This allows us to create lock files if need be, without
	 * making the spool dir world-writable.
	 */
	if (ismbox && (cp = strrchr(file, '/')) != NULL) {
	  *cp = '\0';
	  if (stat(file, &s2) == 0 && (s2.st_mode&020))
	    st.st_gid = s2.st_gid;
	  *cp = '/';
	}
	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",
		       "4.2.1 (File open for append 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",
			 "4.4.5 (Lost race for mailbox file)",
			 "450 (Lost race for mailbox file)");
	    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	HAVE_MAILLOCK_H
	  if (*(rp->addr->user) == TO_FILE) {
	    if ((pw = getpwuid(uid)) == NULL) {
	      notaryreport(file,"failed",
			   "5.2.1 (Target file has no known owner)",
			   "510 (Target file has no known owner)");
	      DIAGNOSTIC(rp, EX_NOPERM,
			 "File \"%s\" has no owner", file);
	      return;
	    }
	    maillockuser = pw->pw_name;
	  }
	  else
	    maillockuser = rp->addr->user;

	  if (ismbox) {
	    int i;
	    char errbuf[30];

	    switch ((i = maillock(maillockuser, 2))) {
	    case L_SUCCESS:
	      cp = NULL;
	      break;
	    case L_MAXTRYS:
	      notaryreport(file,"delayed",
			   "4.4.5 (Mailbox unlocking fails)",
			   "450 (Mailbox 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:
	      sprintf(errbuf, "maillock() error %d", i);
	      cp = errbuf;
	      break;
	    }
	    if (cp != NULL) {
	      DIAGNOSTIC3(rp, EX_UNAVAILABLE,
			  "Error locking \"%s\": %s", file, cp);
	      return;
	    }
	    havemaillock = 1;
	  }
#endif	/* HAVE_MAILLOCK_H */
#ifdef  HAVE_DOTLOCK
	  if (ismbox &&
	      !(havedotlock = dotlock(file) == 0)) {
	    char mbuf[256];
	    sprintf(mbuf, "\"%s\", errno %d", file, errno);
	    DIAGNOSTIC(rp, errno == EBUSY ? EX_TEMPFAIL :
		       (errno == EACCES ? EX_NOPERM : EX_UNAVAILABLE),
		       "can't lock %s", mbuf);
#ifdef	HAVE_MAILLOCK_H
	    if (havemaillock)
	      mailunlock();
#endif
	    return;
	  }
#endif  /* HAVE_DOTLOCK */
#ifdef	USE_NFSMBOX
	  if (nfslock(file, LOCK_EX) != 0) {
	    alarm(0);
#ifdef	HAVE_MAILLOCK_H
	    if (havemaillock)
	      mailunlock();
#endif
#ifdef  HAVE_DOTLOCK
	    if (havedotlock)
	      dotunlock(file);
#endif /* HAVE_DOTLOCK */
	    notaryreport(file,"delayed",
			 "4.4.5 (File locking with NFS lock failed)",
			 "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	HAVE_LOCKF
	  /* Seek to begining for locking! [mea@utu.fi] */
	  if (lseek(fdmail,0L,L_SET) < 0 ||
	      lockf(fdmail, F_LOCK, 0) < 0) {
	    alarm(0);
#ifdef	HAVE_MAILLOCK_H
	    if (havemaillock)
	      mailunlock();
#endif
#ifdef  HAVE_DOTLOCK
	    if (havedotlock)
	      dotunlock(file);
#endif /* HAVE_DOTLOCK */
	    notaryreport(file,"delayed",
			 "4.4.5 (File locking with  lockf  failed)",
			 "450 (File locking with  lockf  failed)");
	    DIAGNOSTIC(rp, EX_TEMPFAIL,
		       "lockf() can't lock \"%s\"", file);
	    setrootuid(rp);
	    close(fdmail);
	    return;
	  }

#endif	/* HAVE_LOCKF */
#ifdef	HAVE_FLOCK
	  if (flock(fdmail, LOCK_EX) < 0) {
	    alarm(0);
#ifdef	HAVE_MAILLOCK_H
	    if (havemaillock)
	      mailunlock();
#endif
#ifdef  HAVE_DOTLOCK
	    if (havedotlock)
	      dotunlock(file);
#endif /* HAVE_DOTLOCK */
	    notaryreport(file,"delayed",
			 "4.4.5 (File locking with  flock  failed)",
			 "450 (File locking with  flock  failed)");
	    DIAGNOSTIC(rp, EX_TEMPFAIL,
		       "flock() can't lock \"%s\"", file);
	    setrootuid(rp);
	    close(fdmail);
	    return;
	  }
#endif	/* HAVE_LOCKF */
	}

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


	/* Where is the end of file -- before we write anything */
	eofindex = lseek(fdmail, 0L, L_XTND);
#if	defined(HAVE_SOCKET)
	if (nbp != NULL)
	  nbp->offset = eofindex;
#endif	/* BIFF || RBIFF */
	fp = putmail(dp, rp, fdmail, "a+", timestring, file);
	
	if (S_ISREG(st.st_mode)) {
#ifdef	USE_NFSMBOX
	  unlock(file);
#endif	/* USE_NFSMBOX */
#ifdef	HAVE_LOCKF
	  lseek(fdmail,0L,L_SET);
	  lockf(fdmail, F_ULOCK, 0);/* XX: does this really work?*/
	  /* Yes it does, but it needs seek to begining of
	     the file at first! [mea@utu.fi] */
#endif	/* HAVE_LOCKF */
#if	defined(HAVE_FLOCK) || (!defined(USE_NFSMBOX) && !defined(HAVE_LOCKF) && !defined(HAVE_MAILLOCK_H))
	  flock(fdmail, LOCK_UN);
#endif	/* HAVE_FLOCK */
#ifdef	HAVE_MAILLOCK_H
	  if (havemaillock)
	    mailunlock();
#endif	/* HAVE_MAILLOCK_H */
#ifdef	HAVE_DOTLOCK
	  if (havedotlock)
	    dotunlock(file);
#endif /*HAVE_DOTLOCK*/
	}
	setrootuid(rp);
	if (fp != NULL) {
	  fclose(fp);		/* this closes fdmail */
	  notaryreport(rp->addr->user,"delivery",
		       "2.2.0 (Delivered successfully)",
		       "250 (Delivered successfully)");
	  DIAGNOSTIC(rp, EX_OK, (char *)NULL, 0);
	}
#if	defined(HAVE_SOCKET)
	else
	  if (nbp != NULL) /* putmail() has produced a DIAGNOSTIC */
	    nbp->offset = -1;
#endif	/* BIFF || RBIFF */
	return;
}

FILE *
putmail(dp, rp, fdmail, fdopmode, timestring, file)
	struct ctldesc *dp;
	struct rcpt *rp;
	int fdmail;
	char *fdopmode, *timestring, *file;
{
	FILE *fp;
	int len, rc;
	char buf[2];
	struct stat st;
	char *fromuser;
	int lastch;

	if ((fp = fdopen(fdmail, fdopmode)) == NULL) {
	  notaryreport(NULL,NULL,NULL,NULL);
	  DIAGNOSTIC3(rp, EX_TEMPFAIL, "cannot fdopen(%d,\"%s\")",
		      fdmail,fdopmode);
	  return NULL;
	}

	/* append From_ line and print out the header */

	if (!mmdf_mode &&
	    *(rp->addr->user) != TO_PIPE && eofindex > 0L) {

	  fstat(FILENO(fp), &st);
	  if (!keepatime) st.st_atime = 0;

	  if (st.st_atime != 0) {
#ifdef	HAVE_UTIMES
	    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;
	    rc = utimes(file, tv);
#else  /* !HAVE_UTIMES */
	    struct utimbuf tv;
	    tv.actime  = st.st_atime;
	    tv.modtime = st.st_mtime;
	    rc = utime(file, &tv);
#endif /* !HAVE_UTIMES */
	  }
	} else if (eofindex > 0L && eofindex < (sizeof "From x\n")) {
	  /* A mail message *cannot* be this small.  It must be
	     a corrupted mailbox file.  Ignore the trash bytes. */
	  fseek(fp, 0L, L_SET);
	  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.. */

	/* Begin of the file, and MMDF-style ? */
	if (mmdf_mode == 1 /* Values 2 and 3 exist on PIPEs only.. */   )
	  fputs("\001\001\001\001\n",fp);

	fromuser = rp->addr->link->user;
	if (*fromuser == 0) fromuser = "postmaster";
	if (fprintf(fp, "%s%s  %s",
		    FROM_,fromuser,timestring) == EOF
	    || writeheaders(rp,fp,"\n",convert_qp,0) < 0) {
	  notaryreport(rp->addr->link->user, "failed",
		       "4.2.2 (Write to user's mailbox failed)",
		       "500 (Write to user's mailbox failed)");
	  DIAGNOSTIC(rp, EX_IOERR,
		     "header write to \"%s\" failed", file);
#ifdef HAVE_FTRUNCATE
	  /* XX: should I really do this? */
	  fflush(fp);
	  if (eofindex >= 0)
	    ftruncate(FILENO(fp), (u_long)eofindex);
#endif
	  fclose(fp);
	  fp = NULL;
	  goto time_reset;
	}
	lastch = appendlet(dp, rp, fp, file, is_mime);

	if (lastch == EOF || ferror(fp) || fflush(fp) == EOF) {
      write_failure:
	  notaryreport(NULL,NULL,NULL,NULL);
	  DIAGNOSTIC3(rp, EX_IOERR,
		      "message write to \"%s\" failed: %s",
		      file, strerror(errno));
#ifdef HAVE_FTRUNCATE
	  /* XX: should I really do this? */
	  fflush(fp);
	  if (eofindex >= 0)
	    ftruncate(FILENO(fp), (u_long)eofindex);
#endif
	  fclose(fp);
	  fp = NULL;
	  goto time_reset;
	}

	/*
	 * Determine how many newlines to append to the 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.
	 */
	fseek(fp, -2L, L_XTND);
	len = fread(buf, 1, 2, fp);
	fseek(fp, 0L, L_XTND);	/* to end of file, again */

	if (len == 1 || len == 2) {
	  --len;
	  len = (buf[len]!='\n') + (len == 1 ? buf[0]!='\n' : 1);
	  if (len > 0 && (fwrite("\n\n", 1, len, fp) != len
			  || fflush(fp) == EOF)) {

	    notaryreport(NULL,NULL,NULL,NULL);
	    DIAGNOSTIC(rp, EX_IOERR,
		       "cleansing of \"%s\" failed", file);
#ifdef	HAVE_FTRUNCATE
	    /* XX: should I really do this? */
	    fflush(fp);
	    if (eofindex >= 0)
	      ftruncate(FILENO(fp), (u_long)eofindex);
#endif /* HAVE_FTRUNCATE */
	    fclose(fp);
	    fp = NULL;
	    goto time_reset;
	  }
	}

	/* End of the file, and MMDF-style ? */
	if (mmdf_mode == 1 /* Values 2 and 3 exist on PIPEs only.. */ ) {
	  if (fputs("\001\001\001\001\n",fp) == EOF)
	    goto write_failure;
	}

	/* This is actually Linux-thing, we reset the last access time
	   to be that of before we did read the file, last modification
	   into current moment -- At Linux 1.3.8x the system is a bit
	   over-eager to update atime information at  O_RDWR -file.. */
	time(&st.st_mtime); /* Set the modification time to be now.. */

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

      time_reset:

	if (st.st_atime != 0) {
#ifdef	HAVE_UTIMES
	  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;
	  rc = utimes(file, tv);
#else  /* !HAVE_UTIMES */
	  struct utimbuf tv;
	  tv.actime  = st.st_atime;
	  tv.modtime = st.st_mtime;
	  rc = utime(file, &tv);
#endif /* !HAVE_UTIMES */
	}


	return fp;
}

void
program(dp, rp, timestring)
	struct ctldesc *dp;
	struct rcpt *rp;
	char *timestring;
{
	int i, pid, in[2], out[2];
	uid_t uid;
	gid_t gid;
	char *env[20], buf[8192], *cp, *s;
	int status;
	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/bin:/bin:/usr/ucb";
	else {
	  sprintf(cp, "PATH=%s", s);
	  env[i++] = cp;
	  cp += strlen(cp) + 1;
	}
	uid = atol(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", (int)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,NULL);
	  DIAGNOSTIC(rp, EX_OSERR, "cannot create pipe from \"%s\"",
		     rp->addr->user+1);
	  return;
	}
	if (pipe(out) < 0) {
	  notaryreport(NULL,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_IGNORE(SIGINT);
	  SIGNAL_IGNORE(SIGHUP);
	  SIGNAL_HANDLE(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,NULL);
	  DIAGNOSTIC(rp, EX_OSERR, "cannot fork", 0);
	  return;
	} /* parent */
	close(out[0]);
	close(in[1]);
	errfp = fdopen(in[0], "r");
	/* write the message */
	mmdf_mode += 2;
	eofindex = -1; /* NOT truncatable! */
	fp = putmail(dp, rp, out[1], "w", timestring, rp->addr->user+1);
	mmdf_mode -= 2;
	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);

	/* Union or not, we treat it as if it were an integer.. */

	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;

	notaryreport(NULL,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;
	uid_t *uid;
	gid_t *gid;
	struct passwd *pw;
{
	char **maild;
	int fd = -1;

	*uid = pw->pw_uid;
	*gid = 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	HAVE_FCHOWN
	    fchown(fd, *uid, *gid);
#else  /* !HAVE_FCHOWN */
	    chown(*filep, *uid, *gid);
#endif /* HAVE_FCHOWN */
	    close(fd);
	    {
	      struct stat st;
#ifdef	HAVE_UTIMES
	      struct timeval tv[2];
	      stat(*filep,&st); /* This by all propability will not fail.. */
	      tv[0].tv_sec = 1; /* Not zero, but close.. */
	      tv[1].tv_sec = st.st_mtime;
	      tv[0].tv_usec = tv[1].tv_usec = 0;
	      utimes(*filep, tv);
#else
	      struct utimbuf tv;
	      stat(*filep,&st); /* This by all propability will not fail.. */
	      tv.actime  = 1;	/* Not zero, but close.. */
	      tv.modtime = st.st_mtime;
	      utime(*filep, &tv);
#endif
	    }
	    return 1;
	  }
	}

	/* assert *filep != NULL */
	if (fd == -1) {
	  notaryreport(rp->addr->user, "failed",
		       "4.3.1 (can't create user spool mailbox file)",
		       "466 (can't create user spool mailbox file)");
	  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;
	uid_t 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) {
	      *cp = '/';
	      sprintf(msg,"466 (*INVOCATION BUG* Can't create user spool mailbox file: \"%s\", Directory stat() error: %s)",
		      file,strerror(errno));
	      *cp = 0;
	      notaryreport(rp->addr->user, "failed",
			   "4.3.5 (Something wrong, bad config ?)", msg);
	      DIAGNOSTIC(rp, i, "stat failed on %s", file);
	      return -2;
	    }
	    *cp = '/';
	    if (st.st_mode & 020) { /* group writable? */
	      if (!setupuidgid(rp, uid, st.st_gid)) {
		notaryreport(rp->addr->user, "failed",
			     "4.3.5 (The mailbox configuration is faulty)",
			     "466 (*INVOCATION BUG* The mailbox directory is group writable, but can't change my gid to it)");
		DIAGNOSTIC(rp, i,
			   "failed changing group id to create file in \"%s\"",
			   file);
		return -2;
	      }
	    }
	  }
	  if ((fd = open(file, O_RDWR|O_CREAT|O_EXCL, MAILMODE)) < 0) {
	    saverrno = errno;
	    setrootuid(rp);
	  } 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, "466 (error [%s] creating \"%%s\")", strerror(saverrno));
	notaryreport(rp->addr->user, "failed",
		     "4.3.0 (Other mailsystem error)",
		     msg);
	sprintf(msg, "error (%s) creating \"%%s\"", strerror(saverrno));
	DIAGNOSTIC(rp, i, msg, file);
	errno = saverrno;
	return -2;
}

/*
 * setupuidgid - set the euid and gid of the process
 */
int
setupuidgid(rp, uid, gid)
	struct rcpt *rp;
	uid_t uid;
	gid_t gid;
{
	setrootuid(rp);
	if (setgid(gid) < 0) {
	  DIAGNOSTIC(rp, EX_OSERR, "can't setgid to %d", (int)gid);
	  return 0;
	}
	if (*(rp->addr->user) == TO_FILE || *(rp->addr->user) == TO_PIPE)
	  uid = atol(rp->addr->misc);
	if (setreuid(-1, uid) < 0) {
	  if (uid < 0 && atol(rp->addr->misc) < 0) {
	    /* use magic non-sense +ve uid < MAXSHORT */
	    rp->addr->misc = NONUIDSTR;
	    return setupuidgid(rp, atol(NONUIDSTR), gid);
	  }
	  DIAGNOSTIC(rp, EX_OSERR, "can't seteuid to %d", (int)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
 *
 *	Return the last character written..
 *
 */
int
appendlet(dp, rp, fp, file, ismime)
	struct ctldesc *dp;
	struct rcpt *rp;
	FILE *fp;
	int ismime;
	char *file;
{
	int lastch = 0xFFF; /* Something no character can be,
			       and not "-1" either.. */
#ifndef	HAVE_MMAP
	static char buffer[BUFSIZ+1];
	static char *bufend = buffer+BUFSIZ;
	register int i;
	register int bufferfull;
	char iobuf[BUFSIZ];
	FILE *mfp;
	int mfd = dp->msgfd;

	if (ismime)
	  writemimeline(NULL, NULL, 0, NULL);	/* initialize state */
	else
	  writebuf(NULL, NULL, 0, NULL);	/* initialize state */

	if (ismime) {
	  /* can we use cache of message body data ? */
	  /* Split it to lines.. */
	  if (readalready > 0) {
	    int linelen, readidx = 0;
	    char *s0 = buffer, *s;
	    while (readidx < readalready) {
	      s = s0;
	      linelen = 0;
	      while (*s != 0 && *s != '\n' && readidx < readalready) {
		++s; ++linelen; ++readidx;
	      }
	      if (*s == '\n' && readidx < readalready) {
		++s; ++linelen; ++readidx;
	      }
	      if (writemimeline(fp, s0, linelen, &lastch) != linelen) {
		DIAGNOSTIC(rp, EX_IOERR,
			   "write to \"%s\" failed", file);
		return EOF;
	      }
	      s0 = s;
	    }
	    rp->status = EX_OK;
	    return lastch;
	  }

	  mfp = fdopen(mfd,"r");
	  setvbuf(mfp, iobuf, _IOFBF, sizeof(iobuf));

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

	  /* we are assuming to be positioned properly
	     at the start of the message body */
	  bufferfull = 0;
	  readalready = 0;
	  i = 0;
	  while ((i = cfgets(buffer, BUFSIZ, mfp)) != EOF) {
	    /* It MAY be malformed -- if it has a BUFSIZ length
	       line in it, IT CAN'T BE MIME  :-/		*/
	    if (i == BUFSIZ && buffer[BUFSIZ-1] != '\n')
	      ismime = 0;
	    /* Ok, write the line */
	    if (writemimeline(fp, buffer, i, &lastch) != i) {
	      DIAGNOSTIC(rp, EX_IOERR,
			 "write to \"%s\" failed", file);
 	      MFPCLOSE;
	      return EOF;
	    }
	  }
	  if (i == EOF && !feof(mfp)) {
	    DIAGNOSTIC(rp, EX_IOERR,
		       "read error from message file", 0);
	    MFPCLOSE;
	    return EOF;
	  }

	  MFPCLOSE;

	} else {		/* is NOT MIME message.. */

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

	  /* we are assumed to be positioned properly
	     at the start of the message body */
	  bufferfull = 0;
	  while ((i = read(mfd, buffer, BUFSIZ)) != 0) {
	    if (i < 0) {
	      DIAGNOSTIC(rp, EX_IOERR,
			 "read error from message file", 0);
	      return EOF;
	    }
	    if (writebuf(fp, buffer, i, &lastch) != 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;
	}
#else /* HAVE_MMAP  --  get the input from  mmap():ed memory area.. */
	char *s; /* message body start */
	s = dp->let_buffer + dp->msgbodyoffset;
	if (ismime) {
	  int i;
	  writemimeline(NULL, NULL, 0, NULL);	/* initialize state */
	  while (s < dp->let_end) {
	    char *s2 = s;
	    i = 0;
	    while (s2 < dp->let_end && *s2 != '\n') ++s2, ++i;
	    if (s2 < dp->let_end && *s2 == '\n') ++s2, ++i;
	    if (writemimeline(fp, s, i, &lastch) != i) {
	      DIAGNOSTIC(rp, EX_IOERR,
			 "write to \"%s\" failed", file);
	      return EOF;
	    }
	    s = s2;
	  }
	} else {
	  writebuf(NULL, NULL, 0, NULL);	/* initialize state */
	  if (writebuf(fp, s, dp->let_end - s, &lastch) != (dp->let_end - s)) {
	      DIAGNOSTIC(rp, EX_IOERR,
			 "write to \"%s\" failed", file);
	      return EOF;
	  }
	}
#endif
	return lastch;
}


/*
 * 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(HAVE_SOCKET)
void
biff(hostname, username, offset)
	char *hostname, *username;
	long offset;
{
	int f;
	spkey_t symid;
	struct hostent *hp;
	struct sockaddr_in biffaddr, *bap;
	struct spblk *spl;
	static struct sptree *spt_hosts = NULL;
	char *buf;

#define BIFF_PORT 512 /* A well-known UDP port.. */
	/* Could do this with  getservbyname() - but sometimes (with NIS
	   on loaded Suns) it takes AGES...  Hardwiring works as well.   */

	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);
	  memcpy((char *)&biffaddr.sin_addr, *hp_getaddr(),  hp->h_length);
	  biffaddr.sin_port = htons(BIFF_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);
	close(f);
	free(buf);
}
#endif	/* BIFF || RBIFF */


#if defined(HAVE_SOCKET) && defined(HAVE_PROTOCOLS_RWHOD_H)
int
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;
	spkey_t 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 dirent *dp, *readdir();
	struct userhost *uhp;
	struct spblk *spl;
	
	if (spt_users == NULL)
	  return;
	now = time(NULL);
	if (chdir(RWHODIR) || (dirp = opendir(".")) == NULL ) {
	  /* BE SILENT -- failing RBIFF is no fault! */
	  return 0;
	  /*
	     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 = strdup(w->wd_hostname);
	    spl->data = (unsigned char *)uhp;
	  }
	  close(f);
	}
	closedir(dirp);
	return 1;
}

void
rbiff(nbp)
	struct biffer *nbp;
{
	struct spblk *spl;
	struct userhost *uhp;
	spkey_t 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.
 * (Except with MMDF_MODE!)
 */

int
writebuf(fp, buf, len, lastchp)
	FILE *fp;
	char *buf;
	int len;
	int *lastchp;
{
	register char *cp, *s;
	register int n;
	int wlen, tlen, i = 0;
	register char expect;
	static char save = '\0';
	static char frombuf[8];
	static char *fromp = NULL;

	if (buf == NULL) {	/* magic initialization */
	  save = mmdf_mode ? 0 : 'F';
	  frombuf[0] = 0;
	  fromp = frombuf;
	  return 0;
	}

	/* -------------------------------------------- */
	/* Non-MIME processing				*/

	expect = save;

	*lastchp = buf[len-1];

	for (cp = buf, n = len, tlen = 0, s = NULL; n > 0; --n, ++cp) {
	  register int c = *cp;
	  ++tlen;

	  if (c == '\n') {
	    expect = mmdf_mode ? 0 : '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 || ferror(fp))
		    { 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 ! */
}

int
writemimeline(fp, buf, len, lastchp)
	FILE *fp;
	char *buf;
	int len;
	int *lastchp;
{
	register char *cp, *s;
	register int n;
	int wlen, tlen, i = 0;
	static char save = '\0';
	static char frombuf[8];
	static char *fromp = NULL;

	static char *buf2 = NULL;
	static int buf2len = 0;

	if (buf == NULL) {	/* magic initialization */
	  save = mmdf_mode ? 0 : 'F';
	  frombuf[0] = 0;
	  fromp = frombuf;
	  return 0;
	}

#ifdef	USE_ALLOCA
	buf2 = alloca(len+1);
#else
	if (buf2 == NULL) {
	  buf2 = emalloc(len+1);
	  buf2len = len;
	}
	if (buf2len < len) {
	  free(buf2);
	  buf2 = emalloc(len+1);
	  buf2len = len;
	}
#endif

	/* ------------------------------------------------------------ */
	/* MIME format processing, the input is LINE BY LINE */
	if (mime_boundary != NULL && len > mime_boundary_len+2 &&
	    buf[0] == '-' && buf[1] == '-' &&
	    strncmp(buf+2,mime_boundary,mime_boundary_len) == 0) {
	  /* ------------------------------------------------------------ */
	  /* XX: process MIME boundary! */
	  *lastchp = buf[len-1];
	  return fwrite(buf, 1, len, fp);

	} else if (convert_qp) {
	  /* ------------------------------------------------------------ */
	  /* Process Q-P chars of this line */
	  int qp_hex = 0;
	  int qp_chrs = 0;
	  int tlen = 0;

	  for (cp = buf, s = buf2, n = len; n > 0; --n, ++cp) {
	    register int c = *cp;
	    /* At first, convert the possible QP character.. */
	    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 at `c', save it! */
	    *s++ = c;
	    ++tlen;
	  }
	  /* Uhh... Stored the conversion result into buf2[], length  tlen */
	  if (!mmdf_mode && buf2[0] == 'F' && strncmp(buf2,"From ",5)==0)
	    if (putc('>',fp) == EOF)
	      return -1;
	  if (tlen > 0) {
	    i = fwrite(buf2, 1, tlen, fp);
	    *lastchp = buf2[tlen-1];
	  } else
	    i = 0;
	  if (i != tlen) return -1;
	  return len;		/* Return the incoming length,
				   NOT the true length! */
	}
	/* ------------------------------------------------------------ */
	/* Well, no other processings known.. */
	if (!mmdf_mode && buf[0] == 'F' && strncmp(buf,"From ",5)==0)
	  putc('>',fp);
	*lastchp = buf[len-1];
	return fwrite(buf, 1, len, fp);
}


/* [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;

	is_mime = 0;

	if (hdrs == NULL) return 0; /* Oh ?? */

	while (hdrs && *hdrs && (!is_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 (!is_mime && cistrncmp(hdr,"MIME-Version:",13)==0)
	    is_mime = 1;

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