Logo Search packages:      
Sourcecode: zmailer version File versions  Download package

mailbox.c

/*
 *    Copyright 1988 by Rayan S. Zachariassen, all rights reserved.
 *    This will be free software, but only when it is finished.
 *    Copyright 1992-2003 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>
 */

/* ZENV variables used by this program:
 *
 *    ZCONFIG           -- passed to programs run at pipes
 *    MAILBOX           -- same
 *    MAILBIN           -- same
 *    MAILSHARE   -- same
 *    PATH        -- used to find programs, passed to programs
 *    MBOXLOCKS   -- defines file/mbox lock mechanisms; a string with:
 *          '.' -- "dotlock" mechanism for mail spool files
 *          ':' -- separates mailbox, and file lock schemes
 *          'L' -- lockf() lock
 *          'F' -- flock() lock
 *          'N' -- NFSMBOX (uses special locking daemon at remote system)
 */

#define DefCharset "ISO-8859-1"

#include "mailer.h"
#include <ctype.h>
#include <errno.h>
#include <sysexits.h>
#include <sys/param.h>
#include <fcntl.h>
#include <sys/file.h>
#include <sys/stat.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h> /* F_LOCK is there at some systems.. */
#endif
#include <string.h>

#include "ta.h"

#include "mail.h"
#include "zsyslog.h"
#include "zmsignal.h"

#include "zmalloc.h"
#include "libz.h"
#include "libc.h"
#include "splay.h"
#include "sieve.h"

#include "shmmib.h"

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

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

#ifdef HAVE_LOCALE_H
#include <locale.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

extern time_t time __((time_t *));

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

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

#ifndef     SEEK_SET
#define     SEEK_SET 0
#endif      /* !SEEK_SET */
#ifndef     SEEK_END
#define     SEEK_END    2
#endif      /* !SEEK_END */
#ifndef     F_OK
#define     F_OK  0
#endif      /* !F_OK */

#ifdef HAVE_LOCKF
const char *MBOXLOCKS_default = ".L:L";
#else
#ifdef HAVE_FLOCK
const char *MBOXLOCKS_default = ".F:F";
#else
const char *MBOXLOCKS_default = ".:"; /* Huh ?? No lockf() nor flock() ??? */
#endif
#endif

#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>
#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 */

/* [Thomas Knott] [mea]
 * The form for "-S" (sendmail-style-behaviour) enabled
 * "Return-Receipt-To:" -header processing.
 * This is a file in $MAILSHARE/FORMSDIR/ -directory
 */
#define RETURN_RECEIPT_FORM "return-receipt"

/*
 * 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.
 */
const 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,U,E,A1,A2) diagnostic(verboselog, (R), \
                              (*(U) == TO_PIPE \
                               && (R)->status != EX_OK \
                               && (R)->status != EX_TEMPFAIL) ? \
                                    EX_UNAVAILABLE : (E), 0, (A1), (A2)); \
      SHM_MIB_diag(E)
#define     DIAGNOSTIC3(R,U,E,A1,A2,A3)   diagnostic(verboselog, (R), \
                              (*(U) == TO_PIPE \
                               && (R)->status != EX_OK \
                               && (R)->status != EX_TEMPFAIL) ? \
                                    EX_UNAVAILABLE : (E), 0, \
                                    (A1), (A2), (A3)); \
      SHM_MIB_diag(E)

#define     DIAGNOSTIC4(R,U,E,A1,A2,A3,A4) diagnostic(verboselog, (R), \
                              (*(U) == TO_PIPE \
                               && (R)->status != EX_OK \
                               && (R)->status != EX_TEMPFAIL) ? \
                                    EX_UNAVAILABLE : (E), 0, \
                                    (A1), (A2), (A3), (A4)); \
      SHM_MIB_diag(E)


#ifdef CHECK_MB_SIZE
extern int checkmbsize(const char *uname, const char *host, const char *user,
                   size_t cursize, struct Zpasswd *pw);
#endif

#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 wsdisc {
  Sfdisc_t D;           /* Sfio Discipline structure        */
  void *WS;       /* Ptr to SS context                */
};

struct writestate {
      Sfio_t *fp;
      int  lastch;
      int  lasterrno;
      char expect;
      char frombuf[8];
      char *fromp;
      char *buf2; /* writemimeline() processing aux buffer */
      int buf2len;
      int epipe_seen;
      struct wsdisc WSdisc;
};


struct sptree *spt_users = NULL;
#endif /* HAVE_SOCKET -- no use to biff, if no socket available .. */

const char *defcharset;
const char *progname;
const char *channel;
const char *logfile;
FILE *logfp = NULL;
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 keep_header8 = 0;         /* Headers can have 8-bit stuff ? */
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() */
int  dirhashes = 0;
int  pjwhashes = 0;
int  crchashes = 0;
int  canonify_user = 0;
int  do_xuidl = 0;            /* Store our own  X-UIDL: header to allow
                           POP3 server to have some unique id for
                           the messages..  IMAP4 does require
                           something different -- 32-bit unique
                           counter..  See RFC 2060 for IMAP4. */
int edquot_is_fatal = 0;

static const char *s_delivered = "delivered";
static const char *s_delayed   = "delayed";
static const char *s_failed    = "failed";

extern int fmtmbox __((char *, int, const char *, const char *, \
                  const struct Zpasswd *));

extern RETSIGTYPE wantout __((int));
extern int optind;
extern char *optarg;
extern void biff __((const char *, const char *, long));
extern void rbiff __((struct biffer *));
extern void prversion __((const char *));

extern int setupuidgid __((struct rcpt *, int, int));
extern int createfile __((struct rcpt *, const char *, int, int));
extern int exstat __((struct rcpt *, const char *, struct stat *, int (*)(const char*, struct stat*) ));
extern int creatembox __((struct rcpt *, const char *, char **, uid_t*, gid_t*, struct Zpasswd *));
extern char *exists __((const char *, const char *, struct Zpasswd *, struct rcpt *));
extern void setrootuid __((struct rcpt *));
extern void process __((struct ctldesc *dp));
extern void deliver __((struct ctldesc *dp, struct rcpt *rp, const char *userbuf, const char *timestring));
extern Sfio_t *putmail __((struct ctldesc *dp, struct rcpt *rp, int fdmail, const char *fdopmode, const char *timestring, const char *file, uid_t));
extern int appendlet __((struct ctldesc *dp, struct rcpt *rp, struct writestate *WS, const char *file, int ismime));
extern char **environ;
extern int writebuf __((struct writestate *, const char *buf, int len));
extern int writemimeline __((struct writestate *, const char *buf, int len));


extern int program __((struct ctldesc *dp, struct rcpt *rp, const char *cmdbuf, const char *usernam, const char *timestring, int pipeuid));

static int do_return_receipt_to = 0;
static void  return_receipt __((struct ctldesc *dp, const char *retrecpaddr, const char *uidstr));
static const char *find_return_receipt_hdr __((struct rcpt *rp));

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;
#ifndef strchr
extern char *strchr(), *strrchr();
#endif
extern int  lstat __((const char *, struct stat *)); /* remaps to  stat() if USE_LSTAT is not defined.. */
extern FILE *fdopen();
#ifndef MALLOC_TRACE
extern void * emalloc __((size_t));
#else
struct conshell *envarlist = NULL;
#endif
int   D_alloc = 0;

static int domain_aware_getpwnam;

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

static void zsfclose(fp)
     Sfio_t *fp;
{
  sfpurge(fp);
  sfclose(fp);
}


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

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


static void decodeXtext __((Sfio_t *, const char *));

#if defined(HAVE_SOCKET) && defined(HAVE_PROTOCOLS_RWHOD_H)
static int readrwho __((void));
#endif

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

static int free_spu __((void *));
static int
free_spu(p)
     void *p;
{
      free(p);
      return 0;
}


#ifndef     MAXPATHLEN
#define     MAXPATHLEN 1024
#endif

char filename[MAXPATHLEN+8000];

int
main(argc, argv)
      int argc;
      const char *argv[];
{
      char *s;
      const char *cs;
      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) __((int));

      SIGNAL_HANDLESAVE(SIGINT, SIG_IGN, oldsig);
      if (oldsig != SIG_IGN)
        SIGNAL_HANDLE(SIGINT, wantout);
      SIGNAL_HANDLESAVE(SIGTERM, SIG_IGN, oldsig);
      if (oldsig != SIG_IGN)
        SIGNAL_HANDLE(SIGTERM, wantout);
      SIGNAL_HANDLESAVE(SIGQUIT, SIG_IGN, oldsig);
      if (oldsig != SIG_IGN)
        SIGNAL_HANDLE(SIGQUIT, wantout);
      SIGNAL_HANDLESAVE(SIGHUP, SIG_IGN, oldsig);
      if (oldsig != SIG_IGN)
        SIGNAL_HANDLE(SIGHUP, wantout);
      SIGNAL_HANDLE(SIGALRM, sig_alrm); /* Actually ignored, but
                                   fcntl() will break ? */

      SIGNAL_IGNORE(SIGPIPE);

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


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

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

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

      atexit(MIBcountCleanup);


      progname = strrchr(argv[0], '/');
      if (progname == NULL)
        progname = argv[0];
      else
        ++progname;

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

      umask(002);

      errflg = 0;
      logfile = NULL;
      channel = CHANNEL;
      while (1) {
        c = getopt(argc, (char*const*)argv, "abc:Cd:DF:gh:Hl:MPrRSVUX8");
        if (c == EOF)
          break;
        switch (c) {
        case 'c':       /* specify channel scanned for */
          channel = optarg;
          break;
        case 'C':
          canonify_user = 1;
          break;
        case 'd':             /* mail directory */
          maildirs[0] = strdup(optarg);
          maildirs[1] = NULL;
          break;
        case 'D':
          ++dirhashes;  /* For user "abcdefg" the mailbox path is:
                           MAILBOX/a/b/abcdefg -- supported by
                           qpopper, for example... */
          break;
        case 'P':       /* pjwhash32() from the last component of the
                           mailbox file path (filename) is used to
                           create hashes by calculating N levels
                           (one or two) of modulo 26 ('A'..'Z') alike
                           the scheduler does its directory hashes. */
          ++pjwhashes;
          break;
        case 'X':       /* Like pjwhash32() above, but with crc32() */
          ++crchashes;
          break;
        case 'F':
          if (STREQ(optarg,"edquot"))
            edquot_is_fatal = 1;
          break;
        case 'U':
          do_xuidl = 1;
          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 'S':
          do_return_receipt_to = 1;
          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 'H':
          keep_header8 = 1;
          break;
        case '8':
          mime8bit = 1;
          break;
        default:
          ++errflg;
          break;
        }
      }
      if (errflg || optind != argc) {
        fprintf(stderr, "Usage: %s [-8abDPXgHMrSV] [-F edquot] [-l logfile] [-c channel] [-h host] [-d mailboxdir]\n",
              argv[0]);
        exit(EX_USAGE);
      }

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

#if 0 /* hmm.. weird obsolete code ? */
      SETEUID(0);       /* make us root all over */
      currenteuid = 0;
#endif

      logfp = 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");
          fcntl(fd, F_SETFD, 1);
        }
      }

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

      getnobody();

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

      if (domain_aware_getpwnam == 0) {
        const char *s = getzenv("DOMAIN_AWARE_GETPWNAM");
        if (s && (*s == '1')) {
          domain_aware_getpwnam=1;
        } else {
          domain_aware_getpwnam=-1;
        }
      }

      while (!getout) {

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

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

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

        MIBMtaEntry->tambox.TaMessages += 1;

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

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

        notaryflush();
        notary_setxdelay(0); /* Our initial speed estimate is
                          overtly optimistic.. */

        dp = ctlopen(filename, channel, host, &getout, NULL, NULL);
        if (dp == NULL) {
          printf("#resync %s\n",filename);
          fflush(stdout);
          continue;
        }
        if (verboselog) {
          fclose(verboselog);
          verboselog = NULL;
        }
        if (dp->verbose) {
          verboselog = fopen(dp->verbose,"a");
          if (verboselog) {
            /* Buffering, and Close-On-Exec bit! */
            setbuf(verboselog,NULL);
            fcntl(FILENO(verboselog), F_SETFD, 1);
          }
        }
        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((void*)nbp->user), 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);
        }
        sp_scan( (int(*)__((struct spblk*))) free_spu, NULL, spt_users);
        free((void*)spt_users);
        spt_users = NULL;
#endif      /* HAVE_SOCKET */
      }
      exit(EX_OK);
      /* NOTREACHED */
      return 0;
}

static void rfc822localize __((char *));
static void
rfc822localize(user)
      char *user;
{
      char *s = user;

      if (*s == '"') {
        /* Quoted local part */
        ++user;
        while (*user && *user != '"') {
          if (*user == '\\' && user[1] != 0)
            ++user;
          *s = *user;
          ++s; ++user;
        }
        *s = 0;
        if (*user == '"')
          ++user;
        /* End of local part, now it MUST be '@' */
      } else {
        /* Non-quoted local part */
        while (*user && *user != '@') {
          if (*user == '\\' && user[1] != 0)
            ++user;
          *s = *user;
          ++s; ++user;
        }
        *s = 0;
      }
      /* Now the '*user' points to a NIL, or '@' */
}

static void rfc822dequote __((char *));
static void
rfc822dequote(user)
      char *user;
{
      char *s = user;
      int escaped=0;

      for ( ; *user; ++user) {
        if (escaped) {
          *s++=*user;
          escaped=0;
        } else {
          if (*user == '"') continue;
          if (*user == '\\') {
            escaped=1;
            continue;
          }
          *s++=*user;
        }
      }
      *s='\0';
}

void
process(dp)
      struct ctldesc *dp;
{
      char *ts;
      struct rcpt *rp;
      time_t curtime;
      int is_mime_qptext = 0;
      char *userbuf = NULL;
      char *user;
      int userlen;
      int userspace = 0;
      struct ct_data  *CT  = NULL;
      struct cte_data *CTE = NULL;
      char **hdr;

      rp = dp->recipients;

      conversion_prohibited = check_conv_prohibit(rp);

      hdr = has_header(rp,"Content-Type:");
      if (hdr)
        CT = parse_content_type(*hdr);
      hdr = has_header(rp,"Content-Transfer-Encoding:");
      if (hdr)
        CTE = parse_content_encoding(*hdr);
      if (CT) {
        if (CT->basetype == NULL ||
            CT->subtype  == NULL ||
            cistrcmp(CT->basetype,"text") != 0 ||
            cistrcmp(CT->subtype,"plain") != 0)

          /* Not TEXT/PLAIN! */
          conversion_prohibited = -1;
        /* We don't know how to convert anything BUT  TEXT/PLAIN :-(  */
      }

      if (!conversion_prohibited)
        is_mime_qptext = qptext_check(dp->recipients);

      if (!keep_header8 && headers_need_mime2(dp->recipients)) {
        headers_to_mime2(dp->recipients,defcharset,verboselog);
      }

      /*
       * Strip backslashes prefixed to the user name,
       * and strip the quotes from around a name.
       */

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

      for (rp = dp->recipients; rp != NULL; rp = rp->next) {

        userlen = strlen(rp->addr->user);
        if (userlen >= userspace) {
          userspace = userlen + 1;
          userbuf = realloc(userbuf, userspace);
        }
        memcpy(userbuf, rp->addr->user, userlen+1);
        user = userbuf;

        while (*user == TO_USER)
          ++user;

        if (domain_aware_getpwnam == 1)
          rfc822dequote(user);
        else
          rfc822localize(user);

        time(&curtime);
        ts = ctime(&curtime);

        /* seek to message body start on each iteration */
        if (lseek(dp->msgfd, (off_t)dp->msgbodyoffset, SEEK_SET) < 0L) {
          warning("Cannot seek to message body in %s! (%m)",
                dp->msgfile);
          DIAGNOSTIC(rp, user, EX_TEMPFAIL,
                   "cannot seek to message body!", 0);
        } else {

          if (verboselog)
            fprintf(verboselog,"mailbox: %s/%s %s %s\n",
                  rp->addr->channel, rp->addr->host,
                  rp->addr->user,    rp->addr->misc  );

          if (dp->msgbodyoffset == 0)
            warning("Null message offset in \"%s\"!",
                  dp->msgfile);
          convert_qp = is_mime_qptext && mime8bit;
          deliver(dp, rp, userbuf, ts);
        }
      }
      if (userbuf != NULL)
        free(userbuf);
      if (CT)  free_content_type(CT);
      if (CTE) free_content_encoding(CTE);
}

/*
 * probably_x400() -- some heuristics to see if this is likely
 *                a mis-written X.400 address
 */
int probably_x400 __((const char *));
int
probably_x400(addr)
      const 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;
}

/* Various lock acquisition routines */

int havedotlock = 0, havemaillock = 0;

int acquire_mboxlock __((struct rcpt *, const char *, int));
int
acquire_mboxlock(rp,file,iuid)
struct rcpt *rp;
const char *file;
int iuid;
{
#ifdef      HAVE_MAILLOCK
      const char *maillockuser;
      struct Zpasswd *pw;
      int i;
      char errbuf[30];
      const char *cp;
      uid_t uid = (uid_t) iuid;

      pw = zgetpwuid(uid);

      if (*(rp->addr->user) == TO_FILE) {
        if (pw == NULL) {
          notaryreport(file, notaryacct(EX_NOPERM, NULL),
                   "5.2.1 (Target file has no known owner)",
                   "x-local; 510 (Target file has no known owner)");
          DIAGNOSTIC(rp, file, EX_NOPERM,
                   "File \"%s\" has no owner", file);
          return 1;
        }
        maillockuser = pw->pw_name;
      } else {
        char *at, *plus;
        maillockuser = rp->addr->user;
        at = strchr(maillockuser, '@');
        if (at) *at = 0;
        plus = strchr(maillockuser, '+');
        if (plus) *plus = 0;

        pw = zgetpwnam(maillockuser);

        if (plus) *plus = '+';
        if (at) *at = '@';

        if (pw)
          maillockuser = pw->pw_name;
      }

      i = maillock((char*)maillockuser, 2); /* Sigh.. proto vs. man-page */
      switch (i) {
      case L_SUCCESS:
        cp = NULL;
        break;
      case L_MAXTRYS:
        notaryreport(file, s_failed,
                   "5.4.5 (Mailbox unlocking fails)",
                   "x-local; 550 (Mailbox unlocking fails)");
        DIAGNOSTIC(rp, file, EX_TEMPFAIL,
                 "\"%s\" is locked", file);
        return 1;
      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;
#ifdef L_MANLOCK /* Some Debian version has copied this mechanism,
                but has done imperfect job on it..  I hope they
                don't do ``enum'' on these error codes. [mea] */
      case L_MANLOCK:
        cp = "cannot set mandatory lock on temp lockfile";
        break;
#endif
      default:
        sprintf(errbuf, "maillock() error %d", i);
        cp = errbuf;
        break;
      }
      if (cp != NULL) {
        DIAGNOSTIC3(rp, file, EX_UNAVAILABLE,
                  "Error maillock()ing \"%s\": %s", file, cp);
        return 1;
      }
      havemaillock = 1;
#endif      /* HAVE_MAILLOCK */
#ifdef  HAVE_DOTLOCK
      havedotlock = (dotlock(file) == 0);
      if (!havedotlock) {
        char mbuf[256];
        int rc, err = errno;
        sprintf(mbuf, "\"%s\": %s", file, strerror(err));
        notaryreport(file, s_failed,
                   "5.4.5 (File locking with dotlock failed)",
                   "x-local; 550 (File locking with dotlock failed)");
        rc = EX_UNAVAILABLE;
        switch (err) {
        case EBUSY:
          rc = EX_TEMPFAIL;
          break;
        case EACCES:
          rc = EX_NOPERM;
          break;
        default:
          break;
        }
        DIAGNOSTIC(rp, file, rc, "can't dotlock %s", mbuf);
        return 1;
      }
#endif  /* HAVE_DOTLOCK */
      return 0;
}

int acquire_nfsmboxlock __((struct rcpt *, const char *));
int
acquire_nfsmboxlock(rp,file)
struct rcpt *rp;
const char *file;
{
#ifdef      USE_NFSMBOX
      if (nfslock(file, LOCK_EX) != 0) {
        alarm(0);
        notaryreport(file, s_failed,
                   "5.4.5 (File locking with NFS lock failed)",
                   "x-local; 550 (File locking with NFS lock failed)");
        DIAGNOSTIC(rp, file, EX_TEMPFAIL,
                 "nfs lock() can't nfslock \"%s\"", file);
        return 1;
      }
#endif      /* USE_NFSMBOX */
      return 0;
}


int acquire_lockflock __((int, struct rcpt *, const char *));
int
acquire_lockflock(fdmail,rp,file)
int fdmail;
struct rcpt *rp;
const char *file;
{
#if defined(HAVE_LOCKF) && defined(F_LOCK)
      /* Seek to begining for locking! [mea@utu.fi] */
      if (lseek(fdmail, (off_t)0, SEEK_SET) < 0 ||
          lockf(fdmail, F_LOCK, 0) < 0) {
        alarm(0);
        notaryreport(file, s_failed,
                   "5.4.5 (File locking with  lockf  failed)",
                   "x-local; 550 (File locking with  lockf  failed)");
        DIAGNOSTIC(rp, file, EX_TEMPFAIL,
                 "can't lockf() \"%s\"", file);
        return 1;
      }
      lseek(fdmail, (off_t)0, SEEK_END); /* To the end of the file */
#endif      /* HAVE_LOCKF */
      return 0;
}

int acquire_flocklock __((int, struct rcpt *, const char *));
int
acquire_flocklock(fdmail,rp,file)
int fdmail;
struct rcpt *rp;
const char *file;
{
#ifdef      HAVE_FLOCK
      if (flock(fdmail, LOCK_EX) < 0) {
        alarm(0);
        notaryreport(file, s_failed,
                   "5.4.5 (File locking with  flock  failed)",
                   "x-local; 550 (File locking with  flock  failed)");
        DIAGNOSTIC(rp, file, EX_TEMPFAIL,
                 "can't flock() \"%s\"", file);
        return 1;
      }
#endif      /* HAVE_FLOCK */
      return 0;
}

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

#ifndef __STDC__
extern void store_to_file();
#else
extern void store_to_file(struct ctldesc *dp, struct rcpt *rp,
                    const char *file, int ismbox, const char *usernam,
                    struct stat *st, uid_t uid,
#if   defined(HAVE_SOCKET)
                    struct biffer *nbp,
#endif
                    time_t starttime,
                    const char *timestring
                    );
#endif

void
deliver(dp, rp, usernam, timestring)
      struct ctldesc *dp;
      struct rcpt *rp;
      const char *usernam;
      const char *timestring;
{
      register const char **maild;
      int fdmail;
      long uid;
      int hasdir;
      struct stat st, s2;
      const char *file = NULL;
      char *cp, *plus;
      const char *retrecptaddr;
      int ismbox = 0;
#if   defined(HAVE_SOCKET)
      struct biffer *nbp = NULL;
#ifdef      HAVE_PROTOCOLS_RWHOD_H
      char *path;
#endif
#endif
      const char *unam = usernam;
      struct Zpasswd *pw = NULL;
      time_t starttime;

      MIBMtaEntry->tambox.TaDeliveryStarts += 1;

      time(&starttime);
      notary_setxdelay(0); /* Our initial speed estimate is
                        overtly optimistic.. */

      if (sscanf(rp->addr->misc,"%ld",&uid) != 1) {
        char buf[1000];
        if (verboselog) {
          fprintf(verboselog,"mailbox: User recipient address privilege code invalid (non-numeric!): '%s'\n",rp->addr->misc);
        }
        sprintf(buf,"x-local; 500 (User recipient address privilege code invalid [non-numeric!]: '%.200s')", rp->addr->misc);
        notaryreport(NULL, s_failed,
                   "5.3.0 (User address recipient privilege code invalid)",
                   buf);
        DIAGNOSTIC(rp, usernam, EX_SOFTWARE,
                 "Non-numeric recipient privilege code: \"%s\"", rp->addr->misc);
        return;
      }

      plus = strchr(usernam,'+');
      switch (*usernam) {
      case TO_PIPE:           /* pipe to program */
        /* one should disallow this if uid == nobody? */
        if (uid == nobody) {

          if (verboselog)
            fprintf(verboselog,
                  "   the recipient address privilege == NOBODY!\n");

          notaryreport("?program?", s_failed,
                   "5.2.1 (Mail to program disallowed w/o proper privileges)",
                   "x-local; 550 (Mail to program disallowed w/o proper privileges)");
          DIAGNOSTIC(rp, usernam, EX_UNAVAILABLE,
                   "mail to program disallowed", 0);
          return;
        }
        program(dp, rp, usernam, "", timestring, uid);
        /* DIAGNOSTIC(rp, usernam, EX_UNAVAILABLE,
           "mailing to programs (%s) is not supported",
           rp->addr->user); */
        return;

      case TO_FILE:           /* append to 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(usernam,"/dev/null") == 0) {
          notaryreport(rp->addr->user, s_delivered,
                   "2.2.0 (delivered successfully)",
                   "x-local; 250 (Delivered successfully)");
          DIAGNOSTIC(rp, usernam, EX_OK, "Ok", 0);
          return;
        }

        if (uid == nobody) {
          if (probably_x400(usernam)) {

            if (verboselog)
            fprintf(verboselog,
                  "   This smells of misdirected X.400 message? Rejected due to priviledge == NOBODY\n");

            notaryreport(rp->addr->user,
                     s_failed,
                     "5.1.4 (this feels like a misplaced X.400 address -- no support for them)",
                     "x-local; 550 (this feels like a misplaced X.400 address -- no support for them)");
            DIAGNOSTIC(rp, usernam, EX_UNAVAILABLE,
                   "This appears to be a misplaced X.400 address, no support for them", 0);
          } else {

            if (verboselog)
            fprintf(verboselog,
                  "   Mail to file rejected due to priviledge == NOBODY\n");

            notaryreport(rp->addr->user,
                     s_failed,
                     "5.2.1 (Mail to file disallowed w/o proper privileges)",
                     "x-local; 550 (mail to file disallowed w/o proper privileges)");
            DIAGNOSTIC(rp, usernam, EX_UNAVAILABLE,
                   "mail to file disallowed", 0);
          }
          return;
        }
        if (!setupuidgid(rp, uid, -3 /* GID of user with UID=='uid' */)) {
          setrootuid(rp);
          return;
        }

        file = strdup(usernam);

        if (access(file, F_OK) < 0) {
          fdmail = createfile(rp, file, uid, 0);
          /* Did the create fail for some other reason, than
             that the file exists already ? (Due to a race in
             between two processes creating the files.) */
          if (fdmail < 0 && errno != EEXIST) {
            setrootuid(rp);
            return;
          }
#ifdef HAVE_FSYNC
          while (fsync(fdmail) < 0)
            if (errno != EINTR && errno != EAGAIN)
            break;
#endif
          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 (plus) *plus = 0;
#if 0
        setpwent(); /* Effectively rewind the database,
                   needed for multi-recipient processing ? */
#endif
        unam = usernam;

        pw = zgetpwnam(usernam);
        if (pw == NULL) {

          /* No match as is ?  Lowercasify, and try again! */
          strlower((char*)usernam);

          pw = zgetpwnam(usernam);
          if (pw == NULL) {
            if (plus) *plus = '+';

            /* Linux, very least, seems to sometimes yield
             NULL and errno=ENOENT, when Single Unix Spec
             tells it to yield NULL and errno==0 :-|

             The problem seems to be quite broad, as most
             systems don't do sensible things, when two
             conditions hold: All database lookups were
             without errors (although did yield notning),
             and nothing was found!  The sensible thing
             would be to yield NULL along with   errno==0 ! */

            /* Now given the above, how the hell are we going
             to detect when any of the databases used for
             the username resolution has a hickup, and the
             lack of found username is simply due to the db
             problem, which time will solve (as with system
             operator taking some action) ?  */

            if (errno != 0) { /* zgetpwnam() failed for some other
                           reason than simply not finding the
                           given user... */
            int err = errno;

            if (verboselog)
              fprintf(verboselog,
                  "   zgetpwnam(\"%s\") failed (%d)\n", usernam, errno);

            notaryreport(rp->addr->user,
                       s_failed,
                       "5.3.0 (Error getting user identity)",
                       "x-local; 550 (Error getting user identity)");
            DIAGNOSTIC3(rp, usernam, EX_TEMPFAIL,
                     "zgetpwnam for user \"%s\" failed; errno=%d",
                     usernam, err);

            } else if (probably_x400(usernam)) {

            if (verboselog)
              fprintf(verboselog,
                    "   User '%s' unknown, feels like misplaced X.400 address?\n",
                    usernam);

            notaryreport(rp->addr->user,
                       s_failed,
                       "5.1.4 (this feels like a misplaced X.400 address -- no support for them)",
                       "x-local; 550 (this feels like a misplaced X.400 address -- no support for them)");
            DIAGNOSTIC(rp, usernam, EX_UNAVAILABLE,
                     "This appears to be a misplaced X.400 address, no support for them", 0);
            } else {

            if (verboselog)
              fprintf(verboselog, "   User '%s' unknown\n", usernam);

            notaryreport(rp->addr->user,
                       s_failed,
                       "5.1.1 (User does not exist)",
                       "x-local; 550 (User does not exist)");
            DIAGNOSTIC(rp, usernam, EX_NOUSER,
                     "user \"%s\" doesn't exist", usernam);

            }
            return;
          }
        }

        if (canonify_user)
          unam = pw->pw_name;

        hasdir = 0;
        for (maild = maildirs; *maild != 0; maild++) {
          if ((strchr(*maild,'%') == NULL) &&
              (stat(*maild,&st) < 0 ||
            !S_ISDIR(st.st_mode)))
            /* Does not exist, or is not a directory */
            continue;
          hasdir = 1;
          file = exists(*maild, unam, pw, rp);
          if (file != NULL)
            break;            /* found it */
          if (rp->status != EX_OK)
            return;
        }

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

          const char *mailbox = getzenv("MAILBOX");
          notaryreport(rp->addr->user,
                   s_failed,
                   "5.3.5 (System mailbox configuration is wrong, we are in deep trouble..)",
                   "x-local; 566 (System mailbox configuration is wrong!  No such directory!  Aargh!)");
          DIAGNOSTIC(rp, usernam, EX_TEMPFAIL,
                   "System mailbox configuration is wrong!  No such directory (%s)!  Aargh!",maildirs[0]);

          if (!mailbox) 
            zsyslog((LOG_ALERT, "ZMailer mailbox can't open local mail folder directory! (Using BUILTIN list of choices)\n"));
          else
            zsyslog((LOG_ALERT, "ZMailer mailbox can't open local mail folder directory!  $MAILBOX='%s'\n", mailbox));
          zcloselog();
          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 (plus) *plus = '+';
        break;

      } /* end of  switch (*username)  */

      
      /* 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, s_delivered,
                   "2.2.0 (delivered successfully)",
                   "x-local; 250 (Delivered successfully)");
        DIAGNOSTIC(rp, usernam, EX_OK, "Ok", 0);
        free((void*)file);
        return;
      }

      /* we only deliver to singly-linked, regular file */

      if (exstat(rp, file, &st, lstat) < 0) {
        notaryreport(rp->addr->user,
                   s_failed,
                   "5.2.0 (User's mailbox disappeared, will retry)",
                   "x-local; 566 (User's mailbox disappeared, will retry)");
        DIAGNOSTIC(rp, usernam, EX_TEMPFAIL,
                 "mailbox file \"%s\" disappeared", file);
        free((void*)file);
        return;
      }

      if (!S_ISREG(st.st_mode)) {
        /* XX: may want to deliver to named pipes */
        notaryreport(rp->addr->user,
                   s_failed,
                   "5.2.1 (Attempting to deliver into non-regular file)",
                   "x-local; 500 (Attempting to deliver into non-regular file)");
        DIAGNOSTIC(rp, usernam, EX_UNAVAILABLE,
                 "attempted delivery to special file \"%s\"",
                 file);
        free((void*)file);
        return;
      }

#ifdef CHECK_MB_SIZE
      if (1) {
        extern  int checkmbsize __((const char *uname,
                              const char *host, const char *user,
                              size_t cursize, struct Zpasswd *pw));

        /* external procedure checkmbsize() accepts user name, "host"
           name as on routing result, "user" part of routed data,
           and current mailbox size.  It should return 0 if it is OK
           to write to the mailbox, or non-zero if `mailbox full'
           condition encountered.  The procedure itself is not included
           in ZMailer distribution; you need to write it yourself and
           modify the Makefile to pass -DCHECK_MB_SIZE to the compiler
           and to link with the object containing your custom
           checkmbsize() procedure. == <crosser@average.org> */

        if (checkmbsize(usernam, rp->addr->host, rp->addr->user,
                    st.st_size, pw)) {
          notaryreport(usernam, 
                   s_failed,
                   "4.2.2 (Destination mailbox full)",
                   "x-local; 500 (Attempting to deliver to full mailbox)");
          DIAGNOSTIC(rp, usernam, EX_UNAVAILABLE,
                   "size of mailbox \"%s\" exceeds quota for the user",
                   file);
          free((void*)file);
          return;
        }
      }
#endif
      
      if (st.st_nlink > 1) {
        notaryreport(rp->addr->user,
                   s_failed,
                   "5.2.1 (Destination file ambiguous)",
                   "x-local; 500 (Destination file has more than one name..)");
        DIAGNOSTIC(rp, usernam, EX_UNAVAILABLE,
                 "too many links to file \"%s\"", file);
        free((void*)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);
        free((void*)file);
        return;               /* setupuidgid sets status */
      }

      if (verboselog)
        fprintf(verboselog, " ismbox=%d file='%s' usernam='%s'\n",
              ismbox, file, usernam);

      if (ismbox) {

        struct sieve sv;
        memset(&sv,0,sizeof(sv));
        sv.pw       = pw;
        sv.uid      = uid;
        sv.pipeuid  = uid;
        sv.username = usernam;
        sv.spoolfile = file;

        rp->notifyflgs |= _DSN__DIAGDELAYMODE;

        if (sieve_start(&sv) == 0) {
          for (;
             sv.state != 0;
             sieve_iterate(&sv)) {

            /* XX: SIEVE PROCESSOR/ITERATOR */
            int cmd = sieve_command(&sv);

            switch(cmd) {
            case SIEVE_NOOP:
            /* Absolutely NOTHING done with this label; end of iterator */
            break;
            case SIEVE_RUNPIPE:
            if (program(dp, rp, sv.pipecmdbuf, usernam, timestring, sv.pipeuid) != EX_OK) {
              /*  Possible FALSE negative ?? */
              /*  Tell also about SUCCESSES! */
              rp->notifyflgs |= _DSN_NOTIFY_SUCCESS;
            }
            break;
            case SIEVE_USERSTORE:
            store_to_file(dp, rp, file, ismbox, usernam, &st, uid,
#ifdef HAVE_SOCKET
                        nbp,
#endif
                        starttime, timestring );
            break;
            case SIEVE_DISCARD:
            break;
            }
          }
          /* Cleanup of sieve processor */
          sieve_end(&sv);
        }

        /* Sieve-filter sets state mode -- keep_or_discard (<=>0) to
           tell what we should do to the message regarding its storage
           to the local message store. */

        rp->notifyflgs &= ~ _DSN__DIAGDELAYMODE;

        if (sv.keep_or_discard >= 0) {
          store_to_file(dp, rp, file, ismbox, usernam, &st, uid,
#ifdef HAVE_SOCKET
                    nbp,
#endif
                    starttime, timestring );
        } else {
          /* This happens only ONCE per recipient, if ever */
          notaryreport(rp->addr->user, s_delivered,
                   "2.2.0 (Discarded successfully)",
                   "x-local; 250 (Discarded successfully)");
          DIAGNOSTIC(rp, usernam, EX_OK, "Ok", 0);
        }

      } else {

        store_to_file(dp, rp, file, ismbox, usernam, &st, uid,
#ifdef HAVE_SOCKET
                  nbp,
#endif
                  starttime, timestring );
      }

      if (file) free((void*)file);

      /* [Thomas Knott]  "Return-Receipt-To:" */
      if (do_return_receipt_to) {
        retrecptaddr = find_return_receipt_hdr(rp);
        if (retrecptaddr != NULL)
          return_receipt(dp,retrecptaddr,rp->addr->misc);
      }
}

void store_to_file(dp,rp,file,ismbox,usernam,st,uid,
#ifdef HAVE_SOCKET
               nbp,
#endif
               starttime,
               timestring
               )
     struct ctldesc *dp;
     struct rcpt *rp;
     const char *file, *usernam;
     int ismbox;
     struct stat *st;
     uid_t uid;
#ifdef HAVE_SOCKET
     struct biffer *nbp;
#endif
     time_t starttime;
     const char *timestring;
{
      int fdmail;
      struct stat s2;
      Sfio_t *fp = NULL;
      const char *mboxlocks = getzenv("MBOXLOCKS");
      const char *filelocks = NULL;
      const char *locks     = NULL;
      time_t endtime;

      if (mboxlocks == NULL || *mboxlocks == 0 )
        mboxlocks = MBOXLOCKS_default;

      if ((filelocks = strchr(mboxlocks,':')))
        ++filelocks;
      else
        filelocks = "";


      if (verboselog)
        fprintf(verboselog,
              "To open a file with euid=%d egid=%d ismbox=%d file='%s'\n",
              (int)geteuid(), (int)getegid(), ismbox, file);

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

        sprintf(fmtbuf, "open(\"%%s\") failed: %s",
              strerror(saverrno));

        if (verboselog)
          fprintf(verboselog,
                " ... failed, errno = %d (%s)\n",
                saverrno, strerror(saverrno));

        if (TEMPFAIL(saverrno))
          rp->status = EX_TEMPFAIL;
        else if (errno == EACCES)
          rp->status = EX_NOPERM;
        else
          rp->status = EX_UNAVAILABLE;
        notaryreport(file,
                   s_failed,
                   "5.2.1 (File open for append failed)",
                   "x-local; 500 (File open for append failed)");
        DIAGNOSTIC(rp, usernam, 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, usernam, 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,
                   s_failed,
                   "5.4.5 (Lost race for mailbox file)",
                   "x-local; 550 (Lost race for mailbox file)");
          DIAGNOSTIC(rp, usernam, 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 {

        int err;


        if (!ismbox)    /* Not mailbox, use file-locks */
          mboxlocks = filelocks;

        locks = mboxlocks;

        if (verboselog)
          fprintf(verboselog,"Locking sequence: '%s'\n",locks);

        err = 0;
        while (*locks != 0) {
          switch(*locks) {
            case '"': /* ZENV variable may have quotes with it.. */
            break;
            case ':':
            err = -1; /* negative error is no REAL error */
            break;
            case '.':
            if (ismbox)
              err = acquire_mboxlock(rp,file,uid);
            break;
            case 'N':
            case 'n':
            err = acquire_nfsmboxlock(rp,file);
            break;
            case 'L':
            case 'l':
            err = acquire_lockflock(fdmail,rp,file);
            break;
            case 'F':
            case 'f':
            err = acquire_flocklock(fdmail,rp,file);
            break;
            default:
            err = 1;
            break;
          }
          if (err) break;
          ++locks; /* Advance on success only! */
        }

        if (err > 0) {
          --locks; /* Don't try to revert the failed (= last) lock! */
          while (locks >= mboxlocks) {
            switch (*locks) {
            case '"':
              break;
            case '.':
#ifdef      HAVE_MAILLOCK
              if (havemaillock && ismbox)
                mailunlock();
              havemaillock = 0;
#endif      /* HAVE_MAILLOCK */
#ifdef      HAVE_DOTLOCK
              if (ismbox)
                dotunlock(file);
              havedotlock = 0;
#endif /*HAVE_DOTLOCK*/
              break;
              case 'N':
            case 'n':
#ifdef      USE_NFSMBOX
              unlock(file);
#endif      /* USE_NFSMBOX */
              break;
              case 'L':
            case 'l':
#if defined(HAVE_LOCKF) && defined(F_LOCK) /* If one, also the other */
              lseek(fdmail,(off_t)0,SEEK_SET);
              lockf(fdmail, F_ULOCK, 0);
#endif      /* HAVE_LOCKF */
              break;
              case 'F':
            case 'f':
#ifdef HAVE_FLOCK
              flock(fdmail, LOCK_UN);
#endif
              break;
            default:
              break;
            }
            --locks;
          }
          close(fdmail);
          setrootuid(rp);
          return;
        }
      }

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


      /* Where is the end of file -- before we write anything */
      eofindex = lseek(fdmail, (off_t)0, SEEK_END);
#if   defined(HAVE_SOCKET)
      if (nbp != NULL)
        nbp->offset = eofindex;
#endif      /* BIFF || RBIFF */

      fp = putmail(dp, rp, fdmail, "a+", timestring, file, uid);
      
      if (S_ISREG(st->st_mode)) {

        /* Previously acquired locks need to be released,
           preferrably in reverse order */

        --locks;

        while (locks >= mboxlocks) {
          /* if (verboselog)
             fprintf(verboselog, "Unlock char: '%c'\n",*locks); */
          switch (*locks) {
          case '"':
            break;
          case '.':
#ifdef      HAVE_MAILLOCK
            if (ismbox && havemaillock)
            mailunlock();
            havemaillock = 0;
#endif      /* HAVE_MAILLOCK */
#ifdef      HAVE_DOTLOCK
            if (ismbox)
            dotunlock(file);
            havedotlock = 0;
#endif /*HAVE_DOTLOCK*/
            break;
          case 'N':
          case 'n':
#ifdef      USE_NFSMBOX
            unlock(file);
#endif      /* USE_NFSMBOX */
            break;
          case 'L':
          case 'l':
#if defined(HAVE_LOCKF) && defined(F_LOCK)
            lseek(fdmail,(off_t)0,SEEK_SET);
            lockf(fdmail, F_ULOCK, 0);
#endif      /* HAVE_LOCKF */

            break;
          case 'F':
          case 'f':
#ifdef HAVE_FLOCK
            flock(fdmail, LOCK_UN);
#endif
            break;
          default:
            break;
          }
          --locks;
        }
      }

      setrootuid(rp);
      time(&endtime);

      close(fdmail);
      if (fp != NULL) { /* Dummy marker! */
        notary_setxdelay((int)(endtime-starttime));
        notaryreport(rp->addr->user, s_delivered,
                   "2.2.0 (Delivered successfully)",
                   "x-local; 250 (Delivered successfully)");
        DIAGNOSTIC(rp, usernam, EX_OK, "Ok", 0);
      } else {
#if 0 /* garbage.. */
#if   defined(HAVE_SOCKET)
        if (fp) {
          if (nbp != NULL) /* putmail() has produced a DIAGNOSTIC */
            nbp->offset = -1;
      }
#endif      /* BIFF || RBIFF */
#endif
      }
      
      return;
}


/*
 * SFIO write discipline which ignores PIPE write errors (EPIPE)
 * and just claims success at them.  Otherwise quite normal
 * error processing.
 *
 */

ssize_t
mbox_sfwrite(sfp, vp, len, discp)
     Sfio_t *sfp;
     const void * vp;
     size_t len;
     Sfdisc_t *discp;
{
    struct wsdisc *wd = (struct wsdisc *)discp;
    struct writestate *WS = wd->WS;
    const char * p = (const char *)vp;
    int outlen = 0;

    /* If knowingly write to a pipe, and failing with EPIPE,
       *then* silently ignore it.. */

    if (WS->epipe_seen > 0) return len; /* We ignore - fast - the EPIPE */
    if (WS->lasterrno != 0) return -1;

    while (len > 0) {
      int rc = write(sffileno(sfp), p, len);
      int e  = errno;

if (verboselog)
  fprintf(verboselog, " mbox_sfwrite(ptr, len=%d) rc=%d errno=%d\n",
        (int)len, rc, e);

      if (rc < 0) {
      if (e == EPIPE && WS->epipe_seen >= 0) {
        WS->epipe_seen = 1;
        return len + outlen; /* CLAIM success */
      }
      /* Retry on interrupts */
      if (e == EINTR)
        continue;

      WS->lasterrno = errno = e;
      /* All other errors, return written amount, or if none, then error! */
      if (outlen == 0)
        return rc;
      return outlen;
      }
      outlen += rc;
      len    -= rc;
      p      += rc;
    }
    return outlen;
}


Sfio_t *
putmail(dp, rp, fdmail, fdopmode, timestring, file, uid)
     struct ctldesc *dp;
     struct rcpt *rp;
     int fdmail;
     const char *fdopmode, *timestring, *file;
     uid_t uid;
{
      int len, rc, err;
      Sfio_t *fp;
      char buf[2];
      struct stat st;
      const char *fromuser;
      const char *mw = "msg headers";
      int lastch = 0xFFF;
      int topipe = (*(file) == TO_PIPE);
      int failed = 0;
      char sfio_buf[1024*16];

      struct writestate WS;
      char **hdrs;

      fstat(fdmail, &st);

      fp = sfnew(NULL, sfio_buf, sizeof(sfio_buf), fdmail, SF_READ|SF_WRITE|SF_APPENDWR);
      if (fp == NULL) {
        notaryreport(file, s_delayed ,NULL,NULL);
        DIAGNOSTIC3(rp, file, EX_TEMPFAIL, "cannot fdopen(%d,\"%s\")",
                  fdmail,fdopmode);
        return NULL;
      }

      memset( &WS, 0, sizeof(WS) );


      WS.fp     = fp;
      WS.expect = mmdf_mode ? 0 : 'F';
      WS.lastch = 256;  /* Something no character can be,
                           and not "-1" either.. */
      WS.frombuf[0] = 0;
      WS.fromp = WS.frombuf;
      WS.epipe_seen = topipe ? 0 : -1;
      WS.lasterrno = 0;

      memset(&WS.WSdisc, 0, sizeof(WS.WSdisc));
      WS.WSdisc.D.readf   = NULL;
      WS.WSdisc.D.writef  = mbox_sfwrite;
      WS.WSdisc.D.seekf   = NULL;
      WS.WSdisc.D.exceptf = NULL;
      WS.WSdisc.WS        = &WS;

      sfdisc(WS.fp, &WS.WSdisc.D);


      if (!topipe && eofindex > 0L) {

        if (keepatime) {
#ifdef      HAVE_UTIME
          struct utimbuf tv;
          tv.actime  = st.st_atime;
          tv.modtime = st.st_mtime;
          rc = utime(file, &tv);
#else  /* 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);
#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. */
        sfseek(fp, (off_t)0, SEEK_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.. */   )
        sfprintf(fp,"\001\001\001\001\n");

      fromuser = rp->addr->link->user;
      if (*fromuser == 0 ||
          STREQ(rp->addr->link->channel, "error"))
        fromuser = "";

      do {
        hdrs = has_header(rp,"Return-Path:");
        if (hdrs) delete_header(rp,hdrs);
      } while (hdrs);

      append_header(rp,"Return-Path: <%.999s>", fromuser);


      if (*fromuser == 0)
        fromuser = "MAILER-DAEMON";

      hdrs = has_header(rp,"To:");
      if (!hdrs) {
        /* No "To:" -header ?  Rewrite possible "Apparently-To:" header! */

        /* Sendmailism... */
        do {
          hdrs = has_header(rp,"Apparently-To:");
          if (hdrs) delete_header(rp,hdrs);
        } while (hdrs);

        append_header(rp,"Apparently-To: <%.999s>", rp->addr->link->user);
      }

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

      do {
        hdrs = has_header(rp,"X-Orcpt:");
        if (hdrs) delete_header(rp,hdrs);
      } while (hdrs);
      do { /* RFC 2298 section  2.3 */
        hdrs = has_header(rp,"Original-Recipient:");
        if (hdrs) delete_header(rp,hdrs);
      } while (hdrs);

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

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

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

      sfprintf(fp, "%s%s %s", FROM_, fromuser, timestring);

      header_received_for_clause(rp, 0, verboselog);

      swriteheaders(rp, fp, "\n", convert_qp, 0, NULL);

      sfprintf(fp, "X-Envelope-To: <%s> (uid %d)\n", rp->addr->user, uid);
      
      if (rp->orcpt) {
        sfprintf(fp, "X-Orcpt: ");
        decodeXtext(fp, rp->orcpt);
        sfprintf(fp, "\n");

        /* RFC 2298: section 2.3 */
        sfprintf(fp, "Original-Recipient: ");
        decodeXtext(fp, rp->orcpt);
        sfprintf(fp, "\n");
      }

      if (dp->envid) {
        sfprintf(fp, "X-Envid: ");
        decodeXtext(fp, dp->envid);
        sfprintf(fp, "\n");

        sfprintf(fp, "Envelope-Id: ");
        decodeXtext(fp, dp->envid);
        sfprintf(fp, "\n");
      }

      if (do_xuidl && !topipe) {
        struct timeval tv;
        gettimeofday(&tv, NULL);

        sfprintf(fp, "X-UIDL: %ld.%ld.%d\n",
              (long)tv.tv_sec, (long)tv.tv_usec, (int)getpid());
      }

      sfprintf(fp, "\n");

      sfsync(fp); /* Headers written, sync possible errors here! */

      if (sferror(fp) || WS.lasterrno != 0) failed = 1;
      if (failed) goto write_failure;


      /* From now on, write errors to PIPE will not cause errors */
      mw = "msg body";

      if (!failed)
        lastch = appendlet(dp, rp, &WS, file, is_mime);
      
      sfsync(fp);
      if (!failed && (sferror(fp) || WS.lasterrno)) failed = 1;

      if (!topipe) { /* We skip this is we are writing to a pipe */

        if (lastch < -128 || failed) {

        write_failure:

          /* Does the 'errno' really have correct data in all paths ? */
          err = WS.lasterrno;

          rc = EX_IOERR;
          if (err == EDQUOT && edquot_is_fatal) {
            rc = EX_UNAVAILABLE; /* Quota-exceeded is instant permanent reject ? */

            notaryreport(file, notaryacct(rc, s_delivered),
                     "5.2.2 Mailbox Quota Exceeded",
                     "x-local; 550 (Mailbox Quota Exceeded)");
          } else {
            notaryreport(file, notaryacct(rc, s_delivered),
                     "4.2.0 mailbox write failure",
                     "x-local; 450 (Mailbox write failure)");
          }
          DIAGNOSTIC4(rp, file, rc,
                  "message write[%s] to \"%s\" failed: %s",
                  mw, file, strerror(err));
#ifdef HAVE_FTRUNCATE
          if (eofindex >= 0)
            while (ftruncate(sffileno(fp), (off_t)eofindex) < 0)
            if (errno != EINTR && errno != EAGAIN)
              break;

          /* Some syscall traces seem to tell that Solaris 7/8
             implements  ftruncate()  as:

> 9497:   fcntl(7, F_FREESP, 0xFFBEE5BC)                  = 0
> 9497:     typ=F_WRLCK whence=SEEK_SET start=0 len=0
> 9497:     sys=428042295   pid=-4266544  

             Also notable details being that this moves
             the read/write cursor into SEEK_SET/start=0
             location, which did later cause some trouble..

           */
#endif /* HAVE_FTRUNCATE */

#ifdef HAVE_FSYNC
          while (fsync(sffileno(fp)) < 0)
            if (errno != EINTR && errno != EAGAIN)
            break;
#endif
          /* Discard and close! */
          zsfclose(fp);
          eofindex = -1;
          fp = NULL;
          goto time_reset;
        }
      } /* if (!topipe) */

      if (verboselog)
        fprintf(verboselog," end of putmail(file='%s'), topipe=%d\n",
              file,topipe);

      if (!topipe) {
        /*
         * Ok, we are NOT writing to a pipe, and thus we can do
         * fseek(), and play with things...
         */
        /*
         * 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.
         */
        err = errno;
        sfseek(fp, (Sfoff_t)-2LL, SEEK_END);
        len = sfread(fp, buf, 2);
        sfseek(fp, (Sfoff_t)0,    SEEK_END);
        /* to end of file, again */
        errno = err;

        if (verboselog)
          fprintf(verboselog," .. EOF read did yield %d bytes\n", len);

        if (len == 1 || len == 2) {
          --len;
          len = (buf[len]!='\n') + (len == 1 ? buf[0]!='\n' : 1);

          if (len > 0 && sfwrite(fp, "\n\n", len) != len)
            failed = 1;

          if (!failed) sfsync(fp);
          if (!failed && (sferror(fp) || WS.lasterrno)) failed = 1;

          if (verboselog)
            fprintf(verboselog," .. wrote %d newlines to the end%s\n",
                  len, failed ? " FAILED!":"");

          mw="tail newlines";

          if (failed)
            goto write_failure;

        }

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

      } /* !topipe */

      /* Flush everything out */
      sfsync(fp);
      /* Raise an error only if it is non-pipe target */
      if (!topipe && (sferror(fp) || WS.lasterrno)) {
        mw="final flushout";
        goto write_failure;
      }

#ifdef HAVE_FSYNC
      if (!topipe) {
        while (fsync(fdmail) < 0) {
          if (errno == EINTR || errno == EAGAIN)
            continue;

#if 0 /* No, don't err if fsync() fails. */
          mw=5;
          goto write_failure;
#else
          break;
#endif
        }
      }
#endif


      time_reset:

      /* 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: %d + %d : %s (pid %d user %s)\n",
              dp->logident, (int)eofindex,
              (int)((fp ? sftell(fp): 0) - eofindex), file,
              (int)getpid(), rp->addr->user);
        fflush(logfp);
      }

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


      if (fp) {
        /* Discard and close */
        zsfclose(fp);
        /* The pointer is needed later as a marker! (NULL or not) */
      }


      if (WS.buf2) free(WS.buf2);

      return fp; /* Dummy marker! */
}

int
program(dp, rp, cmdbuf, user, timestring, uid)
      struct ctldesc *dp;
      struct rcpt *rp;
      const char *cmdbuf;
      const char *user;
      const char *timestring;
      int uid;
{
      int envi, rc, pid, in[2], out[2];
      int gid = -1;
      char *env[40];
      const char *s;
      char *cp, *cpe;
      int status;
      struct Zpasswd *pw;
      Sfio_t *errfp;
      Sfio_t *fp;
      time_t starttime, endtime;
      char safe1[1100]; /* Stack safety buffer zones.. */
      char buf[8192];
      char safe2[1100]; /* second stack safety buffer */
      char errbuf[4*1024];

      time(&starttime);
      cp = safe1; cp = safe2; /* Silence compiler... */

      notaryreport(rp->addr->user, NULL, NULL, NULL);

      envi = 0;
      env[envi++] = "SHELL=/bin/sh";
      env[envi++] = "IFS= \t\n";

      while (1) {
        /* Zone in which:
             if (cp > cpe) break;
           statement can be used as buffer overflow safety.. */


        cp = buf;
        *cp = 0; /* Trunc the buf string... */
        cpe = buf + sizeof(buf) -2;
        if ((s = getzenv("PATH")) == NULL)
          env[envi++] = "PATH=/usr/bin:/bin:/usr/ucb";
        else {
          sprintf(cp, "PATH=%.999s", s);
          env[envi++] = cp;
          cp += strlen(cp) + 1;
        }
        s = getenv("TZ");
        if (s != NULL) {
          sprintf(cp,"TZ=%.99s", s);
          env[envi++] = cp;
          cp += strlen(cp) + 1;
        }

        pw = zgetpwuid(uid);
        if (pw == NULL) {

          if (verboselog) {
            fprintf(verboselog,"mailbox: User recipient address privilege code invalid (no user with this uid?): '%s'\n", rp->addr->misc);
          }
          sprintf(buf,"x-local; 500 (User recipient address privilege code invalid [no user with this uid?]: '%.200s')", rp->addr->misc);
          notaryreport(NULL,
                   s_failed,
                   "5.3.0 (User address recipient privilege code invalid)",
                   buf);
          DIAGNOSTIC(rp, cmdbuf, EX_SOFTWARE,
                   "Bad privilege for a pipe \"%s\"", rp->addr->misc);
          return EX_SOFTWARE;
        } else {
          gid = pw->pw_gid;
          sprintf(cp, "HOME=%.500s", pw->pw_dir);
          env[envi++] = cp;
          cp += strlen(cp) + 1;

          if (user[0] == 0)
            sprintf(cp, "USER=%.100s", pw->pw_name);
          else
            sprintf(cp, "USER=%.100s", user);
          env[envi++] = cp;
          cp += strlen(cp) + 1;
        }
        if (STREQ(rp->addr->link->channel,"error"))
          sprintf(cp, "SENDER=<>");
        else
          sprintf(cp, "SENDER=%.999s", rp->addr->link->user);
        env[envi++] = cp;
        cp += strlen(cp) + 1;

        sprintf(cp, "UID=%d", (int)uid);
        env[envi++] = cp;
        cp += strlen(cp) + 1;
        if (cp > cpe) break;

        if ((s = getzenv("ZCONFIG")) == NULL)
          s = ZMAILER_ENV_FILE;

        sprintf(cp, "ZCONFIG=%.200s", s);
        env[envi++] = cp;
        cp += strlen(cp) + 1;
        if (cp > cpe) break;

        if ((s = getzenv("MAILBIN")) == NULL)
          s = MAILBIN;
        sprintf(cp, "MAILBIN=%.200s", s);
        env[envi++] = cp;
        cp += strlen(cp) + 1;
        if (cp > cpe) break;

        if ((s = getzenv("MAILSHARE")) == NULL)
          s = MAILSHARE;
        env[envi++] = cp;
        sprintf(cp, "MAILSHARE=%.200s", s);
        cp += strlen(cp) + 1;
        if (cp > cpe) break;

        if (rp->notifyflgs) {
          char *p = "", *p2 = ",";
          env[envi++] = cp;
          sprintf(cp,"NOTIFY="); cp += strlen(cp);
          if (rp->notifyflgs & _DSN_NOTIFY_NEVER) {
            strcpy(cp, "NEVER"); p = p2; cp += strlen(cp); }
          if (rp->notifyflgs & _DSN_NOTIFY_DELAY) {
            sprintf(cp, "%sDELAY", p); p = p2; cp += strlen(cp); }
          if (rp->notifyflgs & _DSN_NOTIFY_FAILURE) {
            sprintf(cp, "%sFAILURE", p); p = p2; cp += strlen(cp); }
          if (rp->notifyflgs & _DSN_NOTIFY_SUCCESS) {
            sprintf(cp, "%sSUCCESS", p); p = p2; cp += strlen(cp); }
          if (rp->notifyflgs & _DSN_NOTIFY_TRACE) {
            sprintf(cp, "%sTRACE", p); p = p2; cp += strlen(cp); }
          ++cp;
        }
        if (rp->deliverby || rp->deliverbyflgs) {
          env[envi++] = cp;
          sprintf(cp,"BY=%ld;",rp->deliverby); cp += strlen(cp);
          if (rp->deliverbyflgs & _DELIVERBY_R) *cp++ = 'R';
          if (rp->deliverbyflgs & _DELIVERBY_N) *cp++ = 'N';
          if (rp->deliverbyflgs & _DELIVERBY_T) *cp++ = 'T';
          *cp++ = 0;
        }
        if (rp->orcpt) {
          sprintf(cp, "ORCPT=%.999s", rp->orcpt);
          env[envi++] = cp;
          cp += strlen(cp) + 1;
          if (cp > cpe) break;
        }
        if (rp->inrcpt) {
          sprintf(cp, "INRCPT=%.999s", rp->inrcpt);
          env[envi++] = cp;
          cp += strlen(cp) + 1;
          if (cp > cpe) break;
        }
        if (rp->infrom) {
          sprintf(cp, "INFROM=%.999s", rp->infrom);
          env[envi++] = cp;
          cp += strlen(cp) + 1;
          if (cp > cpe) break;
        }
        if (rp->ezmlm) {
          sprintf(cp, "EZMLM=%.999s", rp->ezmlm);
          env[envi++] = cp;
          cp += strlen(cp) + 1;
          if (cp > cpe) break;
        }
        if (dp->envid) {
          sprintf(cp, "ENVID=%.999s", dp->envid);
          env[envi++] = cp;
          cp += strlen(cp) + 1;
          if (cp > cpe) break;
        }
      
        env[envi++] = cp;
        strcpy(cp, "MSGSPOOLID="); cp += 11;
        taspoolid(cp, rp->desc->msgmtime, rp->desc->msginonumber);
        cp += strlen(cp) + 1;
        if (cp > cpe) break;

        if (rp->desc->msgfile) {
          /* Put also the message-id of the message into variables. */
          env[envi++] = cp;
          sprintf(cp, "MESSAGEID=%.199s", rp->desc->msgfile);
          cp += strlen(cp) + 1;
          if (cp > cpe) break;
        }

        if (verboselog)
          fprintf(verboselog,"To run a pipe with uid=%d gid=%d cmd='%s'\n",
                uid, gid, cmdbuf);

        /* Here the CP should be less than about 5000 bytes
           into the 8k buffer .. */

        break;
      }

      env[envi] = NULL;

      if (cp >= cpe)
        /* OVERFLOWED THE 8kB BUFFER FOR THE NEW ENVIRONMENT VARIABLES! */
        exit(EX_SOFTWARE);

      /* now we can fork off and run the command... */
      if (pipe(in) < 0) {
        notaryreport(NULL,
                   s_failed,
                   "5.3.0 (out of pipe resources)",
                   "x-local; 500 (out of pipe resources)");
        DIAGNOSTIC(rp, cmdbuf, EX_OSERR,
                 "cannot create pipe from \"%s\"", cmdbuf);
        return EX_OSERR;
      }
      if (pipe(out) < 0) {
        notaryreport(NULL,
                   s_failed,
                   "5.3.0 (out of pipe resources)",
                   "x-local; 500 (out of pipe resources)");
        DIAGNOSTIC(rp, cmdbuf, EX_OSERR,
                 "cannot create pipe to \"%s\"", cmdbuf);
        close(in[0]);
        close(in[1]);
        return EX_OSERR;
      }

      pid = fork();
      if (pid == 0) { /* child */
        char * argv[100];
        int i;

        SETGID(gid);
        SETEGID(gid);
        SETUID(uid);
        SETEUID(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);

        /* hmm.. must split the command line to inputs for  execve();
           I have uses for a strict environment without  /bin/sh ... [mea] */

        cp = (char*)cmdbuf+1;
        s = strchr(cp,'$');
        if (!s)
          s = strchr(cp,'>');

        if (*cp == '/' && s == NULL) {
          /* Starts with an ABSOLUTE PATH -- at least "/" */
          /* ... and does *NOT* contain '$', nor '>' */
          i = 0;
          while (*cp != 0) {
            while (*cp == ' ') ++cp;
            if (*cp == '\'') {
            argv[i] = ++cp;
            while (*cp != 0 && *cp != '\'') ++cp;
            if (*cp == '\'') *cp++ = 0;
            } else if (*cp != 0)
            argv[i] = cp;
            ++i;
            while (*cp != 0 && *cp != ' ') ++cp;
            if (*cp == ' ') *cp++ = 0;
          }
          argv[i] = NULL;
          if (verboselog) {
            fprintf(verboselog," argv:");
            for (i = 0; argv[i] != NULL; ++i)
            fprintf(verboselog," [%d]<%s>", i, argv[i]);
            fprintf(verboselog,"\n");
          }
          execve(argv[0], argv, env);

        } else {

          /* Duh, probably something like:
             "|IFS=' '&&.... "
          */

          /*
           * 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.
           */

          argv[0] = (char*)cmdbuf+1;
          argv[1] = "-c";
          argv[2] = (char*)cmdbuf+1;
          argv[3] = NULL;

          execve("/bin/sh", argv, env);
          /* execle(argv[0], cmdbuf+1,"-c",cmdbuf+1,(char*)NULL,env);*/
          execve("/sbin/sh", argv, env);
          /* execle(argv[0], cmdbuf+1, "-c", cmdbuf+1, (char *)NULL, env); */
        }

        write(2, "Cannot exec '", 13);
        write(2, argv[0], strlen(argv[0]));
        write(2, "'\n", 2);
        _exit(128);

      } else if (pid < 0) {   /* fork failed */

        notaryreport(NULL,
                   s_failed,
                   "5.3.0 (fork failure)",
                   "x-local; 500 (fork failure)");
        DIAGNOSTIC(rp, cmdbuf, EX_OSERR, "cannot fork", 0);
        return EX_OSERR;

      } /* parent */

      close(out[0]);
      close(in[1]);

      errfp = sfnew(NULL, errbuf, sizeof(errbuf), in[0], SF_READ);
      /* write the message */
      mmdf_mode += 2;
      eofindex = -1; /* NOT truncatable! */
      fp = putmail(dp, rp, out[1], "a", timestring, cmdbuf, uid);
      /* ``fp'' is dummy marker */
      mmdf_mode -= 2;
      if (fp == NULL) {
        pid = wait(&status);
        close(out[1]);
        zsfclose(errfp);
        return status;
      }
      close(out[1]);
      /* read any messages from its stdout/err on in[0] */
      /* ... having forked and set up the pipe, we quickly continue */
      buf[sizeof(buf)-100] = 0; /* Chop it just to make sure */
      if (csfgets(buf, (sizeof buf) - 100, errfp) < 0)
            buf[0] = '\0';
      else if ((cp = strchr(buf, '\n')) != NULL)
            *cp = '\0';
      pid = wait(&status);
      zsfclose(errfp);
      cp = buf + strlen(buf);

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

      if (status == 0) {
        rc = EX_OK;
        if (cp != buf)
          *cp++ = ' ';
        strcpy(cp, "[Exit Status 0]");
      } else if ((status & 0177) > 0) {
        if (cp != buf)
          *cp++ = ' ';
        sprintf(cp, "[signal %d", status & 0177);
        if (status & 0200)
          strcat(cp, " (Core dumped)");
        strcat(cp, "]");
        rc = EX_TEMPFAIL;
      } else if ((rc = (status >> 8) & 0377) > 0) {
        if (cp != buf)
          *cp++ = ' ';
        sprintf(cp, "[exit status %d]", rc);
        /* We report following status codes to the system as is,
           all the rest are treated as EX_TEMPFAIL, and retried.. */
        if (rc != EX_NOPERM && rc != EX_UNAVAILABLE && rc != EX_NOHOST &&
            rc != EX_NOUSER && rc != EX_DATAERR     && rc != EX_OK )
          rc = EX_TEMPFAIL;
      }

      if (verboselog)
        fprintf(verboselog,"Run result: '%s'\n", buf);

      time(&endtime);
      notary_setxdelay((int)(endtime-starttime));
      if (rc == EX_OK) {
        notaryreport(NULL, s_delivered,
                   "2.2.0 (Delivered successfully)",
                   "x-local; 250 (Delivered successfully)");
      } else {
        char buf2[sizeof(buf)+10];
        sprintf(buf2,"x-local; 500 (%s)", buf);
        notaryreport(NULL,
                   s_failed,
                   "5.3.0 (subprogram non-zero termination code)",
                   buf2);
      }
      
      DIAGNOSTIC(rp, cmdbuf, rc, "%s", buf);
      return rc;
}

static void mkhashpath __((char *, const char *));
static void mkhashpath(s, uname)
     char *s;
     const char *uname;
{
      extern long pjwhash32 __((const char *));
      extern long crc32     __((const char *));

      if (pjwhashes) {
        int h = pjwhash32(uname);
        switch (pjwhashes) {
        case 1:
          h %= 26;
          sprintf(s,"%c/", ('A' + h));
          break;
        default:
          h %= (26*26);
          sprintf(s,"%c/%c/", ('A' + (h / 26)), ('A' + (h % 26)));
          break;
        }
      }
      if (crchashes) {
        int h = crc32(uname);
        switch (crchashes) {
        case 1:
          h %= 26;
          sprintf(s,"%c/", ('A' + h));
          break;
        default:
          h %= (26*26);
          sprintf(s,"%c/%c/", ('A' + (h / 26)), ('A' + (h % 26)));
          break;
        }
      }
      if (dirhashes) {
        switch (dirhashes) {
        case 1:
          sprintf(s,"%c/",uname[0]);
          s += 2;
          break;
        case 2:
          if (uname[1])
            sprintf(s,"%c/%c/",uname[0],uname[1]);
          else /* Err.... One char userid ?? TROUBLE TIME! */
            sprintf(s,"%c/%c/",uname[0],uname[0]);
          s += 4;
          break;
        default:
          break;
        }
      }
      strcat(s, uname);
}


/*
 * creatembox - see if we can create the mailbox
 */
int
creatembox(rp, uname, filep, uid, gid, pw)
      struct rcpt *rp;
      const char *uname;
      char **filep;
      uid_t *uid;
      gid_t *gid;
      struct Zpasswd *pw;
{
      const char **maild;
      int fd = -1;
      char *s;

      *uid = pw->pw_uid;
      *gid = pw->pw_gid;

      for (maild = &maildirs[0]; *maild != 0; maild++) {
        if (*filep != NULL)
          free(*filep);
        *filep = NULL;
        if (strchr(*maild,'%')) {
          *filep = emalloc(2048);
          if (fmtmbox(*filep,2048,*maild,uname,pw)) {
            (*filep)[70]='\0';
            strcat(*filep,"...");
            notaryreport(rp->addr->user,
                     s_failed,
                   "5.3.1 (too long path for user spool mailbox file)",
                   "x-local; 566 (too long path for user spool mailbox file)");
            DIAGNOSTIC(rp, *filep, EX_CANTCREAT, "Too long path \"%s\"", *filep);
            free(*filep);
            *filep = NULL;
            return 0;
          }
/*
  FIXME! Need to create intermediate directories here
*/
        } else {
          *filep = emalloc(8+2+strlen(*maild)+strlen(uname));
          sprintf(*filep, "%s/", *maild);
          s = *filep + strlen(*filep);

          mkhashpath(s, uname);
        }

        fd = createfile(rp, *filep, *uid, 1);
        if (fd >= 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_UTIME
            struct utimbuf tv;
            stat(*filep,&st); /* This by all probability will not fail.. */
            tv.actime  = 0;   /* never read */
            tv.modtime = st.st_mtime;
            utime(*filep, &tv);
#else
            struct timeval tv[2];
            stat(*filep,&st); /* This by all probability will not fail.. */
            tv[0].tv_sec = 0; /* never read */
            tv[1].tv_sec = st.st_mtime;
            tv[0].tv_usec = tv[1].tv_usec = 0;
            utimes(*filep, tv);
#endif
          }
          return 1;
        }
        if (errno == EEXIST) { /* It exists -- probably a race between
                            two file creators caused this */
          
          return 1;
        }
      }

      /* assert *filep != NULL */
      if (fd == -1) {
        notaryreport(rp->addr->user,
                   s_failed,
                   "5.3.1 (can't create user spool mailbox file)",
                   "x-local; 566 (can't create user spool mailbox file)");
        DIAGNOSTIC(rp, *filep, EX_CANTCREAT, "can't create \"%s\"", *filep);
      }
      /* otherwise the message was printed by createfile() */
      free(*filep);
      *filep = NULL;
      return 0;
}

int
createfile(rp, file, iuid, ismbox)
      struct rcpt *rp;
      const char *file;
      int iuid, ismbox;
{
      int fd, i = 0, saverrno;
      struct stat st;
      char *cp, msg[BUFSIZ];
      uid_t uid = iuid;
      int mailmode = MAILMODE;

      if (verboselog)
        fprintf(verboselog,
              "To create a file with euid=%d egid=%d file='%s', mode=%o\n",
              (int)geteuid(), (int)getegid(), file, mailmode);

      fd = open(file, O_RDWR|O_CREAT|O_EXCL, mailmode);
      if (fd < 0) {
        saverrno = errno;
        if (verboselog)
          fprintf(verboselog,
                " ... failed, errno = %d (%s)\n",
                saverrno, strerror(saverrno));
        
        if (errno == EEXIST)
          return -3;
        cp = strrchr(file, '/');
        if (cp != NULL) {
          *cp = '\0';
          if (exstat(rp, file, &st, stat) < 0) {
            *cp = '/';
            sprintf(msg,"x-local; 566 (*INVOCATION BUG* Can't create user spool mailbox file: \"%s\", Directory stat() error: %s)",
                  file,strerror(errno));
            *cp = 0;
            notaryreport(rp->addr->user,
                     s_failed,
                     "5.3.5 (Something wrong, bad config ?)", msg);
            DIAGNOSTIC(rp, file, i, "stat failed on %s", file);
            return -2;
          }
          *cp = '/';
          if (ismbox && st.st_mode & 020) { /* group writable? */
            if (!setupuidgid(rp, uid, st.st_gid)) {
            notaryreport(rp->addr->user,
                       s_failed,
                       "5.3.5 (The mailbox configuration is faulty)",
                       "x-local; 566 (*INVOCATION BUG* The mailbox directory is group writable, but can't change my gid to it)");
            DIAGNOSTIC(rp, file, i,
                     "failed changing group id to create file in \"%s\"",
                     file);
            return -2;
            }
            mailmode += 0060;
          }
        }

        if (verboselog)
          fprintf(verboselog,
                "To create a file with euid=%d egid=%d file='%s' mode=%o\n",
                (int)geteuid(), (int)getegid(), file, mailmode);

        fd = open(file, O_RDWR|O_CREAT|O_EXCL, mailmode);
        if (fd < 0) {
          saverrno = errno;
          if (verboselog)
            fprintf(verboselog,
                  " ... failed, errno = %d (%s)\n",
                  saverrno, strerror(saverrno));
          setrootuid(rp);
          errno = saverrno;
          if (errno == EEXIST)
            return -3;
        } 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, "x-local; 566 (error [%s] creating \"%%s\")", strerror(saverrno));
      notaryreport(rp->addr->user,
                 s_failed,
                 "5.3.0 (Other mailsystem error)",
                 msg);
      sprintf(msg, "error (%s) creating \"%%s\"", strerror(saverrno));
      DIAGNOSTIC(rp, file, i, msg, file);
      errno = saverrno;
      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 (gid == -3) {
        /* MAGIC! Ask GID of the UID of 'uid' */
        struct Zpasswd *pw = zgetpwuid(uid);
        if (pw != NULL)
          gid = pw->pw_gid;
      }
      
      if (gid >= 0)
        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 (SETEUID(uid) < 0) {
        if (uid < 0 && atol(rp->addr->misc) < 0) {
          /* use magic non-sense +ve uid < MAXSHORT */
          rp->addr->misc = NONUIDSTR;
          return setupuidgid(rp, (uid_t)atoi(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, uname, pw, rp)
      const char *maildir;
      const char *uname;
      struct Zpasswd *pw;
      struct rcpt *rp;
{
      char *file, *s;

      if (strchr(maildir, '%') != NULL) {
        file = emalloc(2048);
        if (fmtmbox(file, 2048, maildir, uname, pw)) {
          file[70]='\0';
          strcat(file,"...");
          DIAGNOSTIC(rp, file, EX_SOFTWARE,
                 "mailbox path does not fit in buffer \"%s\"", file);
          free(file);
          return NULL;
        }
      } else {
        file = emalloc(8+strlen(maildir)+strlen(uname));
        sprintf(file, "%s/", maildir);

        s = file + strlen(file);
        mkhashpath(s, uname);
      }

      if (access(file, F_OK) == 0) { /* file exists */
        rp->status = EX_OK;
        return file;
      }

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


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

void
setrootuid(rp)
      struct rcpt *rp;
{
      if (currenteuid != 0) {
        if (SETEUID(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, WS, file, ismime)
     struct ctldesc *dp;
     struct rcpt *rp;
     struct writestate *WS;
     int ismime;
     const char *file;
{
      if (ta_use_mmap <= 0) {

      register int i;
      register int bufferfull;
      Sfio_t *mfp;
      int mfd = dp->msgfd;
      char sfio_buf[16*1024];

      if (ismime) {
        /* can we use cache of message body data ? */
        /* Split it to lines.. */
        if (readalready > 0) {
          int linelen, readidx = 0;
          const char *s0 = dp->let_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(WS, s0, linelen) != linelen) {
#if 0
            DIAGNOSTIC3(rp, file, EX_IOERR,
                      "write to \"%s\" failed(1); %s",
                      file, strerror(WS->lasterrno));
#endif
            return -256;
            }
            s0 = s;
          }
          rp->status = EX_OK;
          return WS->lastch;
        }

        lseek(mfd, (off_t)dp->msgbodyoffset, SEEK_SET);
        mfp = sfnew(NULL, sfio_buf, sizeof(sfio_buf), mfd, SF_READ);

#define MFPCLOSE zsfsetfd(mfp,-1); zsfclose(mfp);

        /* we are assuming to be positioned properly
           at the start of the message body */
        bufferfull = 0;
        /* We really can't use the 'let_buffer' cache here */
        readalready = 0;
        i = 0;
        for (;;) {
          i = csfgets((void*)dp->let_buffer, dp->let_buffer_size, mfp);
          if (i == EOF)  break;

          /* It MAY be malformed -- if it has a BUFSIZ length
             line in it, IT CAN'T BE MIME  :-/        */
          if (i == dp->let_buffer_size &&
            dp->let_buffer[dp->let_buffer_size-1] != '\n')
            ismime = 0;
          /* Ok, write the line */
          if (writemimeline(WS, dp->let_buffer, i) != i) {
#if 0
            DIAGNOSTIC3(rp, file, EX_IOERR, "write to \"%s\" failed(2); %s",
                    file, strerror(WS->lasterrno));
#endif
            MFPCLOSE;
            return -256;
          }
        }
        if (i == EOF && !sfeof(mfp) && !sferror(mfp)) {
          DIAGNOSTIC(rp, file, EX_IOERR, "read error from message file", 0);
          MFPCLOSE;
          return -256;
        }

        MFPCLOSE;

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

        /* can we use cache of message body data ? */
        if (readalready > 0) {
          if (writebuf(WS, dp->let_buffer, readalready) != readalready) {
#if 0
            DIAGNOSTIC3(rp, file, EX_IOERR, "write to \"%s\" failed(3); %s",
                    file, strerror(WS->lasterrno));
#endif
            return -256;
          }
          rp->status = EX_OK;
          return WS->lastch;
        }

        /* Make sure we are properly positioned at the start
           of the message body */
        lseek(mfd, dp->msgbodyoffset, SEEK_SET);
        bufferfull = 0;
        while (1) {
          i = read(mfd, (void*)dp->let_buffer, dp->let_buffer_size);
          if (i == 0)
            break;
          if (i < 0) {
            DIAGNOSTIC(rp, file, EX_IOERR,
                   "read error from message file", 0);
            readalready = 0;
            return -256;
          }
          if (writebuf(WS, dp->let_buffer, i) != i) {
#if 0
            DIAGNOSTIC3(rp, file, EX_IOERR, "write to \"%s\" failed(4); %s",
                    file, strerror(WS->lasterrno));
#endif
            readalready = 0;
            return -256;
          }
          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.. */

      const char *s;

      s = dp->let_buffer + dp->msgbodyoffset;
      if (ismime) {
        int i;

        while (s < dp->let_end) {
          const 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(WS, s, i) != i) {
#if 0
            DIAGNOSTIC3(rp, file, EX_IOERR, "write to \"%s\" failed(5); %s",
                    file, strerror(WS->lasterrno));
#endif
            return -256;
          }
          s = s2;
        }
      } else {
        if (writebuf(WS, s, dp->let_end - s) != (dp->let_end - s)) {
#if 0
            DIAGNOSTIC3(rp, file, EX_IOERR, "write to \"%s\" failed(6); %s",
              file, strerror(WS->lasterrno));
#endif
            return -256;
        }
      }
      }
      return WS->lastch;
}


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

#if   defined(HAVE_SOCKET)
void
biff(hostname, username, offset)
      const 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((const void*)hostname);
      if (spt_hosts == NULL)
        spt_hosts = sp_init();
      spl = sp_lookup(symid, spt_hosts);
      if (spl == NULL) {
        if ((hp = gethostbyname(hostname)) == NULL)
          return;
        memset((char *)&biffaddr, 0, sizeof biffaddr);
        biffaddr.sin_family = hp->h_addrtype;
        hp_init(hp);
        if (hp_getaddr() && *hp_getaddr())
          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, (void*) 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(PF_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)
static int
readrwho()
{
      struct whod wd;
      struct outmp outmp;
#define NMAX sizeof(outmp.out_name)
#define LMAX sizeof(outmp.out_line)

      int cc, f, n;
      spkey_t symid;
      register struct whod *w = &wd;
      register struct whoent *we;
      long now;
      char username[NMAX+1];
      DIR *dirp;
      struct dirent *dp;
      struct userhost *uhp;
      struct spblk *spl;
      
      if (spt_users == NULL)
        return 0;
      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((void*)username);
          spl = sp_lookup(symid, spt_users);
          if (spl == 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     = (void *)uhp;
        }
        close(f);
      }
      closedir(dirp);
      return 1;
}

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

      symid = symbol((void*)(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(WS, buf, len)
      struct writestate *WS;
      const char *buf;
      int len;
{
      register const char *cp;
      register int n;
      int   tlen;
      register char expect;
      char *fromp;

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

      expect = WS->expect;
      fromp  = WS->fromp;

      WS->lastch = buf[len-1];

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

        /* This is our error analysis, all SFIO calls below
           are without any... */
        if (sferror(WS->fp) || WS->lasterrno != 0) { tlen = -1; break; }

        if (c == '\n') {
          expect = mmdf_mode ? 0 : 'F';
          fromp = WS->frombuf;
          *fromp = 0;
          sfputc(WS->fp,c);

        } else if (expect != '\0') {
          if (c == expect) {
            *fromp++ = c;
            *fromp   = 0;
            switch (expect) {
              case 'F': expect = 'r'; break;
            case 'r':   expect = 'o'; break;
            case 'o':   expect = 'm'; break;
            case 'm':   expect = ' '; break;
            case ' ':
              /* Write the separator, and the word.. */
              sfwrite(WS->fp, ">From ", 6);
              /* anticipate future instances */
              expect = '\0';
              break;
            }
          } else {
            expect = '\0';
            if (WS->frombuf[0] != 0)
            sfprintf(WS->fp, "%s", WS->frombuf);
            WS->frombuf[0] = 0;
            fromp = WS->frombuf;
            sfputc(WS->fp, c);
          }
        } else { /* expect == '\0'; */
          sfputc(WS->fp, c);
        }
      }
      WS->expect = expect;
      WS->fromp  = fromp;
      if (tlen >= 0) return len; /* It worked ok */
      return -1;  /* errno is correct if tlen == -1 ! */
}

int
writemimeline(WS, buf, len)
      struct writestate *WS;
      const char *buf;
      int len;
{
      register char *s;
      register const char *cp;
      register int n;
      int   tlen = 0, i = 0;
      char *buf2;

      if (WS->buf2len < len || WS->buf2 == NULL) {
        WS->buf2 = (char*)realloc(WS->buf2, len+1);
        WS->buf2len = len;
      }

      buf2 = WS->buf2;

#if 0 /* Not yet */
      /* ------------------------------------------------------------ */
      /* 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! */
        WS->lastch = buf[len-1];
        return fwrite(buf, len, WS->fp);

      } else
#endif
        if (convert_qp) {
          /* ------------------------------------------------------------ */
          /* Process Q-P chars of this line */
          int qp_hex = 0;
          int qp_chrs = 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 && tlen >= 5 &&
            buf2[0] == 'F' && memcmp(buf2,"From ",5)==0)
            if (WS->lastch >= 256 || WS->lastch == '\n')
            if (sfputc(WS->fp, '>') == EOF)
              return -1;
          if (tlen > 0) {
            i = sfwrite(WS->fp, buf2, tlen);
            WS->lastch = 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)
        if (WS->lastch >= 256 || WS->lastch == '\n')
          if (sfputc(WS->fp,'>') == EOF)
            return -1;
      WS->lastch = buf[len-1];
      return sfwrite(WS->fp, buf, len);
}


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

      const char **hdrs = *((const char ***)rp->newmsgheader);
      const 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;
}

static const char *
find_return_receipt_hdr (rp)
    struct rcpt *rp;
{
    const char **ptr, *hdr;

    for (ptr = *((const char ***)rp->newmsgheader); *ptr != NULL; ptr++) {
        if (CISTREQN(*ptr, "Return-Receipt-To:", 18))
            break;
    }

    if (*ptr == NULL)
        return (NULL);

    hdr = *ptr + 18;
    while (*hdr != '\0' && isspace(*(unsigned char *)hdr))
        hdr++;

    return(hdr);
}

static void encodeXtext __((Sfio_t *, const char *));
static void encodeXtext(fp,str)
     Sfio_t *fp;
     const char *str;
{
      while (*str) {
        u_char c = *str;
        if ('!' <= c && c <= '~' && c != '+' && c != '=')
          sfputc(fp,c);
        else
          sfprintf(fp,"+%02X",c);
        ++str;
      }
}

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

static const char *dfltform[7] = {
      "Subject: Returned mail: Return receipt",
      "MIME-Version: 1.0",
      "Priority: junk",
      "Content-Type: multipart/report; report-type=delivery-status;",
      "",
      "Your mail message has been delivered properly to the following recipients:",
      NULL
};

static void
return_receipt (dp, retrecptaddr, uidstr)
      struct ctldesc *dp;
      const char *retrecptaddr;
      const char *uidstr;
{
      char buf[BUFSIZ];
      const char **cpp, *mailshare, *mfpath;
      Sfio_t *mfp, *efp;
      struct rcpt *rp;
      int n;
      char boundarystr[400];
      struct Zpasswd *pw;
      int uid;
      struct stat stb;
      const char *username = "unknown";
      char *retaddr = strsave(retrecptaddr);
      char *s;

      while ((s = strchr(retaddr, '\n'))) *s = ' ';
      while ((s = strchr(retaddr, '\r'))) *s = ' ';
      while ((s = strchr(retaddr, '\t'))) *s = ' ';

      uid = atoi(uidstr);
      pw = zgetpwuid(uid);
      if (pw)
        username = pw->pw_name;

      mfp = sfmail_open(MSG_RFC822);
      if (mfp == NULL) {
        for (rp = dp->recipients; rp != NULL; rp = rp->next)
          DIAGNOSTIC(rp, "", EX_TEMPFAIL, "sfmail_open failure", 0);
        warning("Cannot open mail file!");
        return;
      }
      sfprintf(mfp, "channel error\n");

      rp = dp->recipients;

      /* copy To: from return-receipt address */
      sfprintf(mfp, "todsn NOTIFY=NEVER ORCPT=rfc822;");
      encodeXtext(mfp, retaddr);
      sfprintf(mfp, "\nto %s\n",retaddr);
      sfprintf(mfp, "env-end\n");
      sfprintf(mfp, "To: %s\n", retaddr);
      sfprintf(mfp, "From: Automatically on behalf of the user <%s>\n",
             username);

      free(retaddr);

      /* copy error message file itself */
      mailshare = getzenv("MAILSHARE");
      if (mailshare == NULL)
        mailshare = MAILSHARE;

      mfpath = emalloc(3+strlen(mailshare)+strlen(FORMSDIR)
                   +strlen(RETURN_RECEIPT_FORM));
      sprintf((char*)mfpath, "%s/%s/%s",
            mailshare, FORMSDIR, RETURN_RECEIPT_FORM);


      efp = sfopen(NULL, mfpath, "r");

      free((void*)mfpath);

      if (efp) {
        char *dom = mydomain(); /* transports/libta/buildbndry.c */
        struct stat stbuf;

        fstat(sffileno(mfp),&stbuf);
        taspoolid(boundarystr, stbuf.st_ctime, (long)stbuf.st_ino);
        strcat(boundarystr, "=_/return-receipt/");
        strcat(boundarystr, dom);
      }

      if (efp) {
        int inhdr = 1;
        buf[sizeof(buf)-1] = 0;
        while (csfgets(buf,sizeof(buf)-1,efp) >= 0) {
          if (strncmp(buf,"HDR",3)==0) {
            sfprintf(mfp, "%s", buf+4);
          } else if (strncmp(buf,"SUB",3)==0) {
            sfprintf(mfp, "%s", buf+4);
          } else {
            if (inhdr) {
            inhdr = 0;
            sfprintf(mfp,"MIME-Version: 1.0\n");
            sfprintf(mfp,"Content-Type: multipart/report; report-type=delivery-status;\n");
            sfprintf(mfp,"\tboundary=\"%s\"\n\n\n",boundarystr);
            sfprintf(mfp, "--%s\n", boundarystr);
            sfprintf(mfp, "Content-Type: text/plain\n");
            }
            sfprintf(mfp, "%s", buf);
          }
        } /* ... while() ends.. */
        sfclose(efp);
      } else {
        for (cpp = dfltform; *cpp != NULL; ++cpp)
          if (*cpp[0] == 0) {
            sfprintf(mfp, "\tboundary=\"%s\"\n\n", boundarystr);
            sfprintf(mfp, "--%s\n", boundarystr);
            sfprintf(mfp, "Content-Type: text/plain\n\n");
          } else
            sfprintf(mfp, "%s\n", *cpp);
      }
      /* print out errors in standard format */
      for (rp = dp->recipients; rp != NULL; rp = rp->next) {
        sfprintf(mfp, "\t%s\n", rp->addr->user);
      }
      sfprintf(mfp, "\n--%s\n", boundarystr);
      sfprintf(mfp, "Content-Type: message/delivery-status\n\n");

      if (mydomain() != NULL) {
        sfprintf(mfp, "Reporting-MTA: dns;%s\n", mydomain() );
      } else {
        sfprintf(mfp, "Reporting-MTA: x-local-hostname; -unknown-\n");
      }
      if (dp->envid != NULL) {
        sfprintf(mfp, "Original-Envelope-Id: ");
        decodeXtext(mfp,dp->envid);
        sfputc(mfp, '\n');
      }
      /* rfc822date() returns a string with trailing newline! */
      fstat(dp->msgfd,&stb);
      sfprintf(mfp, "Arrival-Date: %s", rfc822date(&stb.st_mtime));
      sfprintf(mfp, "\n");

      for (rp = dp->recipients; rp != NULL; rp = rp->next) {
        if (rp->orcpt != NULL) {
          sfprintf(mfp, "Original-Recipient: ");
          decodeXtext(mfp,rp->orcpt);
          sfprintf(mfp, "\n");
        }
        sfprintf(mfp, "Final-Recipient: X-LOCAL;%s\n", rp->addr->user);
        sfprintf(mfp, "Action: delivered\n");
        sfprintf(mfp, "Status: 2.2.0 (delivered successfully)\n");
        sfprintf(mfp, "Diagnostic-Code: smtp; 250 ('%s' delivered)\n", rp->addr->user );
        sfprintf(mfp, "\n");
      }

      sfprintf(mfp, "--%s\n", boundarystr);
      sfprintf(mfp, "Content-Type: message/rfc822\n\n");

      rp = dp->recipients;
      /* write the (new) headers with local "Received:"-line.. */
      swriteheaders(rp, mfp, "\n", 0, 0, NULL);
      sfprintf(mfp, "\n");

      sfprintf(mfp, "--%s--\n", boundarystr);
      if (sferror(mfp)) {
        sfmail_abort(mfp);
        n = EX_IOERR;
      } else if (sfmail_close(mfp) == EOF)
        n = EX_IOERR;
      else
        n = EX_OK;
      for (rp = dp->recipients; rp != NULL; rp = rp->next)
        DIAGNOSTIC(rp, "", n, (char *)NULL, 0);
}

Generated by  Doxygen 1.6.0   Back to index