Import ezmlm-idx 0.40
[ezmlm] / ezmlm-send.c
index 5748891..fa2913e 100644 (file)
@@ -1,3 +1,5 @@
+/* $Id: ezmlm-send.c,v 1.77 1999/10/29 02:49:14 lindberg Exp $*/
+/* $Name: ezmlm-idx-040 $*/
 #include "stralloc.h"
 #include "subfd.h"
 #include "strerr.h"
 #include "substdio.h"
 #include "getconf.h"
 #include "constmap.h"
-
+#include "byte.h"
+#include "sgetopt.h"
+#include "quote.h"
+#include "subscribe.h"
+#include "mime.h"
+#include "errtxt.h"
+#include "makehash.h"
+#include "cookie.h"
+#include "idx.h"
+#include "copy.h"
+
+int flagnoreceived = 1;                /* suppress received headers by default. They*/
+                               /* are still archived. =0 => archived and */
+                               /* copied. */
+int flaglog = 1;               /* for lists with mysql support, use tags */
+                               /* and log traffic to the database */
 #define FATAL "ezmlm-send: fatal: "
 
 void die_usage()
 {
-  strerr_die1x(100,"ezmlm-send: usage: ezmlm-send dir");
+  strerr_die1x(100,"ezmlm-send: usage: ezmlm-send [-cClLqQrR] [-h header] dir");
 }
 void die_nomem()
 {
-  strerr_die2x(111,FATAL,"out of memory");
+  strerr_die2x(111,FATAL,ERR_NOMEM);
 }
 
+       /* for writing new index file indexn later moved to index. */
+substdio ssindexn;
+char indexnbuf[1024];
+
 char strnum[FMT_ULONG];
+char szmsgnum[FMT_ULONG];
+char hash[HASHLEN];
 
 stralloc fnadir = {0};
 stralloc fnaf = {0};
+stralloc fnif = {0};
+stralloc fnifn = {0};
 stralloc fnsub = {0};
 stralloc line = {0};
+stralloc qline = {0};
+stralloc lines = {0};
+stralloc subject = {0};
+stralloc from = {0};
+stralloc received = {0};
+stralloc prefix = {0};
+stralloc content = {0};
+stralloc boundary = {0};
+stralloc charset = {0};
+stralloc dcprefix = {0};
+stralloc dummy = {0};
+stralloc qmqpservers = {0};
+
+void die_indexn()
+{
+  strerr_die4x(111,FATAL,ERR_WRITE,fnifn.s,": ");
+}
 
+void *psql = (void *) 0;
+
+unsigned long innum;
+unsigned long outnum;
+unsigned long msgnum;
+unsigned long hash_lo = 0L;
+unsigned long hash_hi = 52L;
+unsigned long msgsize = 0L;
+unsigned long cumsize = 0L;    /* cumulative archive size bytes / 256 */
+char flagcd = '\0';            /* no transfer-encoding for trailer */
+char encin = '\0';
+int flagindexed;
+int flagfoundokpart;           /* Found something to pass on. If multipart */
+                               /* we set to 0 and then set to 1 for any */
+                               /* acceptable mime part. If 0 -> reject */
+int flagreceived;
+int flagprefixed;
+unsigned int serial = 0;
 int flagarchived;
 int fdarchive;
+int fdindex;
+int fdindexn;
+char hashout[COOKIE+1];
+
 substdio ssarchive;
 char archivebuf[1024];
 
@@ -48,7 +112,9 @@ stralloc outlocal = {0};
 stralloc outhost = {0};
 stralloc headerremove = {0};
 struct constmap headerremovemap;
-stralloc headeradd = {0};
+stralloc mimeremove = {0};
+struct constmap mimeremovemap;
+char *dir;
 
 struct qmail qq;
 substdio ssin;
@@ -56,7 +122,10 @@ char inbuf[1024];
 substdio ssout;
 char outbuf[1];
 
-int mywrite(fd,buf,len)
+char textbuf[512];
+substdio sstext;
+
+unsigned int mywrite(fd,buf,len)
 int fd;
 char *buf;
 unsigned int len;
@@ -65,23 +134,33 @@ unsigned int len;
   return len;
 }
 
+int subto(s,l)
+char *s;
+unsigned int l;
+{
+  qmail_put(&qq,"T",1);
+  qmail_put(&qq,s,l);
+  qmail_put(&qq,"",1);
+  return (int) l;
+}
+
 void die_archive()
 {
-  strerr_die4sys(111,FATAL,"unable to write to ",fnaf.s,": ");
+  strerr_die4sys(111,FATAL,ERR_WRITE,fnaf.s,": ");
 }
 void die_numnew()
 {
-  strerr_die2sys(111,FATAL,"unable to create numnew: ");
+  strerr_die3sys(111,FATAL,ERR_CREATE,"numnew: ");
 }
 
-void put(buf,len) char *buf; int len;
+void qa_put(buf,len) char *buf; unsigned int len;
 {
   qmail_put(&qq,buf,len);
   if (flagarchived)
     if (substdio_put(&ssarchive,buf,len) == -1) die_archive();
 }
 
-void puts(buf) char *buf;
+void qa_puts(buf) char *buf;
 {
   qmail_puts(&qq,buf);
   if (flagarchived)
@@ -91,8 +170,8 @@ void puts(buf) char *buf;
 int sublistmatch(sender)
 char *sender;
 {
-  int i;
-  int j;
+  unsigned int i;
+  unsigned int j;
 
   j = str_len(sender);
   if (j < sublist.len) return 0;
@@ -109,14 +188,12 @@ char *sender;
 
 substdio ssnumnew;
 char numnewbuf[16];
-unsigned long msgnum;
-stralloc num = {0};
 
 char buf0[256];
 substdio ss0 = SUBSTDIO_FDBUF(read,0,buf0,sizeof(buf0));
 
 void numwrite()
-{
+{              /* this one deals with msgnum, not outnum! */
   int fd;
 
   fd = open_trunc("numnew");
@@ -124,70 +201,277 @@ void numwrite()
   substdio_fdbuf(&ssnumnew,write,fd,numnewbuf,sizeof(numnewbuf));
   if (substdio_put(&ssnumnew,strnum,fmt_ulong(strnum,msgnum)) == -1)
     die_numnew();
+  if (substdio_puts(&ssnumnew,":") == -1) die_numnew();
+  if (substdio_put(&ssnumnew,strnum,fmt_ulong(strnum,cumsize)) == -1)
+    die_numnew();
+
   if (substdio_puts(&ssnumnew,"\n") == -1) die_numnew();
   if (substdio_flush(&ssnumnew) == -1) die_numnew();
   if (fsync(fd) == -1) die_numnew();
   if (close(fd) == -1) die_numnew(); /* NFS stupidity */
   if (rename("numnew","num") == -1)
-    strerr_die2sys(111,FATAL,"unable to move numnew to num: ");
+    strerr_die3sys(111,FATAL,ERR_MOVE,"numnew: ");
 }
 
 stralloc mydtline = {0};
 
+int idx_copy_insertsubject()
+/* copies old index file up to but not including msg, then adds a line with */
+/* 'sub' trimmed of reply indicators, then closes the new index and moves it*/
+/* to the name 'index'. Errors are dealt with directly, and if the routine  */
+/* returns, it was successful. 'fatal' points to a program-specific error   */
+/* string. Sub is not destroyed, but from is!!!                             */
+/* returns 1 if reply-indicators were found, 0 otherwise.                   */
+/* no terminal \n or \0 in any of the strallocs! */
+{
+  char *cp;
+  unsigned long idx;
+  int match;
+  int r;
+  unsigned int pos;
+
+  if (!stralloc_copys(&fnadir,"archive/")) die_nomem();
+  if (!stralloc_catb(&fnadir,strnum,fmt_ulong(strnum,outnum / 100)))
+       die_nomem();
+  if (!stralloc_copy(&fnif,&fnadir)) die_nomem();
+  if (!stralloc_copy(&fnifn,&fnif)) die_nomem();
+  if (!stralloc_cats(&fnif,"/index")) die_nomem();
+  if (!stralloc_cats(&fnifn,"/indexn")) die_nomem();
+  if (!stralloc_0(&fnif)) die_nomem();
+  if (!stralloc_0(&fnifn)) die_nomem();
+  if (!stralloc_0(&fnadir)) die_nomem();
+
+                       /* may not exists since we run before ezmlm-send */
+  if (mkdir(fnadir.s,0755) == -1)
+    if (errno != error_exist)
+      strerr_die4x(111,FATAL,ERR_CREATE,fnadir.s,": ");
+
+                       /* Open indexn */
+  fdindexn = open_trunc(fnifn.s);
+  if (fdindexn == -1)
+    strerr_die4x(111,FATAL,ERR_WRITE,fnifn.s,": ");
+
+                       /* set up buffers for indexn */
+  substdio_fdbuf(&ssindexn,write,fdindexn,indexnbuf,sizeof(indexnbuf));
+
+  concatHDR(subject.s,subject.len,&lines,FATAL);       /* make 1 line */
+  decodeHDR(lines.s,lines.len,&qline,charset.s,FATAL); /* decode mime */
+  r = unfoldHDR(qline.s,qline.len,&lines,charset.s,&dcprefix,1,FATAL);
+                                                /* trim mime */
+
+  fdindex = open_read(fnif.s);
+  if (fdindex == -1) {
+    if (errno != error_noent)
+      strerr_die4x(111,FATAL,ERR_OPEN, fnif.s, ": ");
+  } else {
+    substdio_fdbuf(&ssin,read,fdindex,inbuf,sizeof(inbuf));
+    for(;;) {
+      if (getln(&ssin,&qline,&match,'\n') == -1)
+        strerr_die4sys(111,FATAL,ERR_READ, fnif.s, ": ");
+      if (!match)
+        break;
+      pos = scan_ulong(qline.s,&idx);
+      if (!idx)                                /* "impossible!" */
+        strerr_die2x(111,FATAL,ERR_BAD_INDEX);
+      if (idx >= outnum)
+        break;                         /* messages always come in order */
+      if (substdio_put(&ssindexn,qline.s,qline.len) == -1)
+        die_indexn();
+      if (qline.s[pos] == ':') {       /* has author line */
+        if (getln(&ssin,&qline,&match,'\n') == -1)
+          strerr_die4x(111,FATAL,ERR_READ, fnif.s, ": ");
+        if (!match && qline.s[0] != '\t')      /* "impossible! */
+          strerr_die2x(111,FATAL,ERR_BAD_INDEX);
+        if (substdio_put(&ssindexn,qline.s,qline.len) == -1)
+          die_indexn();
+      }
+    }
+    close(fdindex);
+  }
+  if (!stralloc_copyb(&qline,strnum,fmt_ulong(strnum,outnum))) die_nomem();
+  if (!stralloc_cats(&qline,": ")) die_nomem();        /* ':' for new ver */
+  makehash(lines.s,lines.len,hash);
+  if (!stralloc_catb(&qline,hash,HASHLEN)) die_nomem();
+  if (!stralloc_cats(&qline," ")) die_nomem();
+  if (r & 1)           /* reply */
+    if (!stralloc_cats(&qline,"Re: ")) die_nomem();
+  if (!stralloc_cat(&qline,&lines)) die_nomem();
+  if (!stralloc_cats(&qline,"\n\t")) die_nomem();
+  if (!stralloc_cat(&qline,&received)) die_nomem();
+  if (!stralloc_cats(&qline,";")) die_nomem();
+
+  concatHDR(from.s,from.len,&lines,FATAL);
+  mkauthhash(lines.s,lines.len,hash);
+
+  if (!stralloc_catb(&qline,hash,HASHLEN)) die_nomem();
+  if (!stralloc_cats(&qline," ")) die_nomem();
+
+  decodeHDR(cp,author_name(&cp,lines.s,lines.len),&from,charset.s,FATAL);
+  (void) unfoldHDR(from.s,from.len,&lines,charset.s,&dcprefix,0,FATAL);
+  if (!stralloc_cat(&qline,&lines)) die_nomem();
+
+  if (!stralloc_cats(&qline,"\n")) die_nomem();
+  if (substdio_put(&ssindexn,qline.s,qline.len) == -1) die_indexn();
+  if (substdio_flush(&ssindexn) == -1) die_indexn();
+  if (fsync(fdindexn) == -1) die_indexn();
+  if (fchmod(fdindexn,MODE_ARCHIVE | 0700) == -1) die_indexn();
+  if (close(fdindexn) == -1) die_indexn(); /* NFS stupidity */
+  if (rename(fnifn.s,fnif.s) == -1)
+    strerr_die4x(111,FATAL,ERR_MOVE,fnifn.s,": ");
+  return r;
+}
+
+void transferenc()
+{
+       if (flagcd) {
+         qmail_puts(&qq,"\nContent-Transfer-Encoding: ");
+          if (flagcd == 'Q')
+            qmail_puts(&qq,"Quoted-printable\n\n");
+          else
+           qmail_puts(&qq,"base64\n\n");
+        } else
+          qmail_puts(&qq,"\n\n");
+}
+
+void getcharset()
+{
+    if (getconf_line(&charset,"charset",0,FATAL,dir)) {
+      if (charset.len >= 2 && charset.s[charset.len - 2] == ':') {
+        if (charset.s[charset.len - 1] == 'B' ||
+                       charset.s[charset.len - 1] == 'Q') {
+          flagcd = charset.s[charset.len - 1];
+          charset.s[charset.len - 2] = '\0';
+        }
+      }
+    } else
+      if (!stralloc_copys(&charset,TXT_DEF_CHARSET)) die_nomem();
+
+    if (!stralloc_0(&charset)) die_nomem();
+}
+
 void main(argc,argv)
 int argc;
 char **argv;
 {
-  int fd;
-  char *dir;
+  unsigned long subs;
   int fdlock;
   char *sender;
+  char *mlheader = (char *) 0;
+  char *ret;
+  char *err;
   int flagmlwasthere;
+  int flagqmqp = 0;    /* don't use qmqp by default */
+  int flaglistid = 0;  /* no listid header added */
   int match;
-  int i;
-  char ch;
+  unsigned int i;
+  int r,fd;
   int flaginheader;
   int flagbadfield;
+  int flagbadpart;
+  int flagseenext;
+  int flagsubline;
+  int flagfromline;
+  int flagcontline;
+  int flagarchiveonly;
+  int flagtrailer;
+  unsigned int pos;
+  int opt;
+  char *cp, *cpstart, *cpafter;
 
   umask(022);
   sig_pipeignore();
 
-  dir = argv[1];
+  while ((opt = getopt(argc,argv,"cCh:H:lLrRqQs:S:vV")) != opteof)
+    switch(opt) {
+      case 'c': case 'C': break;       /* ignore for backwards compat */
+      case 'h':
+      case 'H': mlheader = optarg;     /* Alternative sublist check header */
+                mlheader[str_chr(mlheader,':')] = '\0';
+                break;
+      case 'l': flaglog = 1; break;
+      case 'L': flaglog = 0; break;
+      case 'r': flagnoreceived = 0; break;
+      case 'R': flagnoreceived = 1; break;
+      case 's':
+      case 'S':        pos = scan_ulong(optarg,&hash_lo);
+               if (!optarg[pos++]) break;
+               (void) scan_ulong(optarg+pos,&hash_hi);
+               if (hash_hi > 52L) hash_hi = 52L;
+               if (hash_lo > hash_hi) hash_lo = hash_hi;
+
+ break;
+      case 'q': flagqmqp = 0; break;
+      case 'Q': flagqmqp = 1; break;
+      case 'v':
+      case 'V': strerr_die2x(0,
+               "ezmlm-send version: ezmlm-0.53+",EZIDX_VERSION);
+      default:
+       die_usage();
+    }
+
+
+  dir = argv[optind++];
   if (!dir) die_usage();
 
   sender = env_get("SENDER");
 
   if (chdir(dir) == -1)
-    strerr_die4sys(111,FATAL,"unable to switch to ",dir,": ");
+    strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": ");
 
   fdlock = open_append("lock");
   if (fdlock == -1)
-    strerr_die4sys(111,FATAL,"unable to open ",dir,"/lock: ");
+    strerr_die4sys(111,FATAL,ERR_OPEN,dir,"/lock: ");
   if (lock_ex(fdlock) == -1)
-    strerr_die4sys(111,FATAL,"unable to obtain ",dir,"/lock: ");
-
-  if (qmail_open(&qq) == -1)
-    strerr_die2sys(111,FATAL,"unable to run qmail-queue: ");
+    strerr_die4sys(111,FATAL,ERR_OBTAIN,dir,"/lock: ");
 
   flagarchived = getconf_line(&line,"archived",0,FATAL,dir);
+  flagindexed = getconf_line(&line,"indexed",0,FATAL,dir);
+  getcharset();
+  flagprefixed = getconf_line(&prefix,"prefix",0,FATAL,dir);
+  if (prefix.len) {            /* encoding and serial # support */
+                               /* no sanity checks - you put '\n' or '\0' */
+                               /* into the coded string, you pay */
+
+    decodeHDR(prefix.s,prefix.len,&line,charset.s,FATAL);
+    (void) unfoldHDR(line.s,line.len,&dcprefix,charset.s,&dummy,0,FATAL);
+    if (!stralloc_copy(&dcprefix,&line)) die_nomem();
+    serial = byte_rchr(prefix.s,prefix.len,'#');
+  }
+  if ((fd = open_read("text/trailer")) == -1) {        /* see if there is a trailer */
+    if (errno == error_noent) flagtrailer = 0;
+    else strerr_die2sys(111,ERR_OPEN,"text/trailer: ");
+  } else {
+    close(fd);
+    flagtrailer = 1;
+  }
 
-  getconf_line(&num,"num",1,FATAL,dir);
-  if (!stralloc_0(&num)) die_nomem();
-  scan_ulong(num.s,&msgnum);
-  ++msgnum;
+  getconf(&mimeremove,"mimeremove",0,FATAL,dir);
+
+  if (getconf_line(&line,"num",0,FATAL,dir)) { /* Now non-FATAL, def=0 */
+    if (!stralloc_0(&line)) die_nomem();
+    cp = line.s + scan_ulong(line.s,&msgnum);
+    ++msgnum;
+    if (*cp++ == ':')
+      scan_ulong(cp,&cumsize);
+  } else
+    msgnum = 1L;                       /* if num not there */
 
   getconf_line(&outhost,"outhost",1,FATAL,dir);
   getconf_line(&outlocal,"outlocal",1,FATAL,dir);
-  getconf_line(&mailinglist,"mailinglist",1,FATAL,dir);
+  set_cpoutlocal(&outlocal);
+  set_cpouthost(&outhost);
   flagsublist = getconf_line(&sublist,"sublist",0,FATAL,dir);
 
-  getconf(&headerremove,"headerremove",1,FATAL,dir);
-  constmap_init(&headerremovemap,headerremove.s,headerremove.len,0);
+  if (flagqmqp) {                      /* forward compatibility ;-) */
+    if (!stralloc_copys(&line,QMQPSERVERS)) die_nomem();
+    if (!stralloc_cats(&line,"/0")) die_nomem();
+    if (!stralloc_0(&line)) die_nomem();
+    (void) getconf_line(&qmqpservers,line.s,0,FATAL,dir);
+  }
 
-  getconf(&headeradd,"headeradd",1,FATAL,dir);
-  for (i = 0;i < headeradd.len;++i)
-    if (!headeradd.s[i])
-      headeradd.s[i] = '\n';
+  getconf(&headerremove,"headerremove",1,FATAL,dir);
+  if (!constmap_init(&headerremovemap,headerremove.s,headerremove.len,0))
+       die_nomem();
 
   if (!stralloc_copys(&mydtline,"Delivered-To: mailing list ")) die_nomem();
   if (!stralloc_catb(&mydtline,outlocal.s,outlocal.len)) die_nomem();
@@ -197,123 +481,326 @@ char **argv;
 
   if (sender) {
     if (!*sender)
-      strerr_die2x(100,FATAL,"I don't distribute bounce messages (#5.7.2)");
+      strerr_die2x(100,FATAL,ERR_BOUNCE);
     if (str_equal(sender,"#@[]"))
-      strerr_die2x(100,FATAL,"I don't distribute bounce messages (#5.7.2)");
+      strerr_die2x(100,FATAL,ERR_BOUNCE);
     if (flagsublist)
       if (!sublistmatch(sender))
-        strerr_die2x(100,FATAL,"this message is not from my parent list (#5.7.2)");
+        strerr_die2x(100,FATAL,ERR_NOT_PARENT);
   }
+  innum = msgnum;                              /* innum = incoming */
+  outnum = msgnum;                             /* outnum = outgoing */
+  if (flagsublist && !flagarchived) {          /* msgnum = archive */
+    pos = byte_rchr(sublist.s,sublist.len,'@');        /* checked in sublistmatch */
+    if (str_start(sender+pos,"-return-"))
+      pos += 8;
+      pos += scan_ulong(sender+pos,&innum);
+      if (!flagarchived && innum && sender[pos] == '-')
+        outnum = innum;
+  }
+  szmsgnum[fmt_ulong(szmsgnum,outnum)] = '\0';
+  set_cpnum(szmsgnum);                         /* for copy */
 
   if (flagarchived) {
     if (!stralloc_copys(&fnadir,"archive/")) die_nomem();
-    if (!stralloc_catb(&fnadir,strnum,fmt_ulong(strnum,msgnum / 100))) die_nomem();
+    if (!stralloc_catb(&fnadir,strnum,
+               fmt_ulong(strnum,outnum / 100))) die_nomem();
     if (!stralloc_copy(&fnaf,&fnadir)) die_nomem();
     if (!stralloc_cats(&fnaf,"/")) die_nomem();
-    if (!stralloc_catb(&fnaf,strnum,fmt_uint0(strnum,(unsigned int) (msgnum % 100),2))) die_nomem();
+    if (!stralloc_catb(&fnaf,strnum,fmt_uint0(strnum,
+               (unsigned int) (outnum % 100),2))) die_nomem();
     if (!stralloc_0(&fnadir)) die_nomem();
     if (!stralloc_0(&fnaf)) die_nomem();
 
     if (mkdir(fnadir.s,0755) == -1)
       if (errno != error_exist)
-       strerr_die4sys(111,FATAL,"unable to create ",fnadir.s,": ");
+       strerr_die4sys(111,FATAL,ERR_CREATE,fnadir.s,": ");
     fdarchive = open_trunc(fnaf.s);
     if (fdarchive == -1)
-      strerr_die4sys(111,FATAL,"unable to write ",fnaf.s,": ");
+      strerr_die4sys(111,FATAL,ERR_WRITE,fnaf.s,": ");
 
     substdio_fdbuf(&ssarchive,write,fdarchive,archivebuf,sizeof(archivebuf));
+                                               /* return-path to archive */
+    if (!stralloc_copys(&line,"Return-Path: <")) die_nomem();
+    if (sender) {                              /* same as qmail-local */
+      if (!quote2(&qline,sender)) die_nomem();
+      for (i = 0;i < qline.len;++i) if (qline.s[i] == '\n') qline.s[i] = '_';
+      if (!stralloc_cat(&line,&qline)) die_nomem();
+    }
+    if (!stralloc_cats(&line,">\n")) die_nomem();
+    if (substdio_put(&ssarchive,line.s,line.len) == -1) die_archive();
   }
 
+  if (flagqmqp) {
+    if (qmail_open(&qq,&qmqpservers) == -1)            /* open qmqp */
+      strerr_die2sys(111,FATAL,ERR_QMAIL_QUEUE);
+  } else if (qmail_open(&qq,(stralloc *) 0) == -1)     /* open queue */
+      strerr_die2sys(111,FATAL,ERR_QMAIL_QUEUE);
+
   if (!flagsublist) {
-    puts("Mailing-List: ");
-    put(mailinglist.s,mailinglist.len);
-    puts("\n");
+    getconf_line(&mailinglist,"mailinglist",1,FATAL,dir);
+    qa_puts("Mailing-List: ");
+    qa_put(mailinglist.s,mailinglist.len);
+    if (getconf_line(&line,"listid",0,FATAL,dir)) {
+      flaglistid = 1;
+      qmail_puts(&qq,"\nList-ID: ");
+      qmail_put(&qq,line.s,line.len);
+    }
+    qa_puts("\n");
   }
-  put(headeradd.s,headeradd.len);
-  put(mydtline.s,mydtline.len);
+  copy(&qq,"headeradd",'H',FATAL);
+  qa_put(mydtline.s,mydtline.len);
 
   flagmlwasthere = 0;
   flaginheader = 1;
+  flagfoundokpart = 1;
   flagbadfield = 0;
-
+  flagbadpart = 0;
+  flagseenext = 0;
+  flagsubline = 0;
+  flagfromline = 0;
+  flagreceived = 0;
   for (;;) {
     if (getln(&ss0,&line,&match,'\n') == -1)
-      strerr_die2sys(111,FATAL,"unable to read input: ");
-
+      strerr_die2sys(111,FATAL,ERR_READ_INPUT);
     if (flaginheader && match) {
-      if (line.len == 1)
+      if (line.len == 1) {             /* end of header */
        flaginheader = 0;
-      if ((line.s[0] != ' ') && (line.s[0] != '\t')) {
-       flagbadfield = 0;
+        if (flagindexed)               /* std entry */
+          r = idx_copy_insertsubject();        /* all indexed lists */
+        if (flagprefixed && !flagsublist) {
+          qa_puts("Subject:");
+          if (!flagindexed) {          /* non-indexed prefixed lists */
+            concatHDR(subject.s,subject.len,&lines,FATAL);
+            decodeHDR(lines.s,lines.len,&qline,charset.s,FATAL);
+            r = unfoldHDR(qline.s,qline.len,&lines,
+                       charset.s,&dcprefix,1,FATAL);
+          }
+          if (!(r & 2)) {
+            qmail_puts(&qq," ");
+            if (serial == prefix.len)
+              qmail_put(&qq,prefix.s,prefix.len);
+            else {
+              qmail_put(&qq,prefix.s,serial);
+              qmail_puts(&qq,szmsgnum);
+              qmail_put(&qq,prefix.s+serial+1,prefix.len-serial-1);
+            }
+          }
+          qa_put(subject.s,subject.len);
+        }
+               /* do other stuff to do with post header processing here */
+       if (content.len) {              /* get MIME boundary, if exists */
+          concatHDR(content.s,content.len,&qline,FATAL);
+          if (!stralloc_copy(&content,&qline)) die_nomem();
+
+         if (flagtrailer &&            /* trailer only for some multipart */
+               case_startb(content.s,content.len,"multipart/"))
+           if (!case_startb(content.s+10,content.len-10,"mixed") &&
+               !case_startb(content.s+10,content.len-10,"digest") &&
+               !case_startb(content.s+10,content.len-10,"parallel"))
+             flagtrailer = 0;
+
+           cp = content.s;
+           cpafter = cp + content.len; /* check after each ';' */
+           while ((cp += byte_chr(cp,cpafter-cp,';')) != cpafter) {
+             ++cp;
+             while (cp < cpafter &&
+                       (*cp == ' ' || *cp == '\t' || *cp == '\n')) ++cp;
+             if (case_startb(cp,cpafter-cp,"boundary=")) {
+               cp += 9;                        /* after boundary= */
+               if (*cp == '"') {       /* quoted boundary */
+                 ++cp;
+                 cpstart = cp;
+                 while (cp < cpafter && *cp != '"') ++cp;
+                if (cp == cpafter)
+                       strerr_die1x(100,ERR_MIME_QUOTE);
+               } else {                        /* non-quoted boundary */
+                 cpstart = cp;         /* find terminator of boundary */
+                 while (cp < cpafter && *cp != ';' &&
+                       *cp != ' ' && *cp != '\t' && *cp != '\n') ++cp;
+               }
+               if (!stralloc_copys(&boundary,"--")) die_nomem();
+               if (!stralloc_catb(&boundary,cpstart,cp-cpstart))
+                       die_nomem();
+                flagfoundokpart = 0;
+               if (!constmap_init(&mimeremovemap,mimeremove.s,mimeremove.len,0))
+                       die_nomem();
+               flagbadpart = 1;                /* skip before first boundary */
+               qa_puts("\n");          /* to make up for the lost '\n' */
+            }
+          }
+        }
+      } else if ((*line.s != ' ') && (*line.s != '\t')) {
+        flagsubline = 0;
+        flagfromline = 0;
+        flagbadfield = 0;
+        flagarchiveonly = 0;
+        flagcontline = 0;
        if (constmap(&headerremovemap,line.s,byte_chr(line.s,line.len,':')))
          flagbadfield = 1;
-       if (case_startb(line.s,line.len,"mailing-list:"))
-         flagmlwasthere = 1;
-       if (line.len == mydtline.len)
+        if ((flagnoreceived || !flagreceived) &&
+               case_startb(line.s,line.len,"Received:")) {
+            if (!flagreceived) {               /* get date from first rec'd */
+              flagreceived = 1;                        /* line (done by qmail) */
+              pos = byte_chr(line.s,line.len,';');
+              if (pos != line.len)             /* has '\n' */
+                if (!stralloc_copyb(&received,line.s+pos+2,line.len - pos - 3))
+                  die_nomem();
+            } else {                           /* suppress, but archive */
+              flagarchiveonly = 1;             /* but do not suppress the */
+              flagbadfield = 1;                        /* top one added by qmail */
+            }
+       } else if (case_startb(line.s,line.len,"Mailing-List:"))
+         flagmlwasthere = 1;           /* sublists always ok ezmlm masters */
+       else if (mlheader && case_startb(line.s,line.len,mlheader))
+         flagmlwasthere = 1;           /* mlheader treated as ML */
+        else if ((mimeremove.len || flagtrailer) &&    /* else no MIME need*/
+               case_startb(line.s,line.len,"Content-Type:")) {
+          if (!stralloc_copyb(&content,line.s+13,line.len-13)) die_nomem();
+          flagcontline = 1;
+       } else if (case_startb(line.s,line.len,"Subject:")) {
+          if (!stralloc_copyb(&subject,line.s+8,line.len-8)) die_nomem();
+         flagsubline = 1;
+          if (flagprefixed && !flagsublist)    /* don't prefix for sublists */
+           flagbadfield = 1;                   /* we'll print our own */
+        } else if (flagtrailer &&
+                case_startb(line.s,line.len,"Content-Transfer-Encoding:")) {
+          cp = line.s + 26;
+          cpafter = cp + line.len;
+          while (cp < cpafter && (*cp == ' ' || *cp == '\t')) ++cp;
+          if (case_startb(cp,cpafter-cp,"base64")) encin = 'B';
+          else if (case_startb(cp,cpafter-cp,"Quoted-Printable")) encin = 'Q';
+        } else if (flaglistid && case_startb(line.s,line.len,"list-id:"))
+         flagbadfield = 1;             /* suppress if we added our own */
+       else if (flagindexed) {
+
+          if (case_startb(line.s,line.len,"From:")) {
+            flagfromline = 1;
+            if (!stralloc_copyb(&from,line.s+5,line.len-5)) die_nomem();
+          }
+        } else if (line.len == mydtline.len)
          if (!byte_diff(line.s,line.len,mydtline.s))
-            strerr_die2x(100,FATAL,"this message is looping: it already has my Delivered-To line (#5.4.6)");
+            strerr_die2x(100,FATAL,ERR_LOOPING);
+      } else {                 /* continuation lines */
+        if (flagsubline) {
+         if (!stralloc_cat(&subject,&line)) die_nomem();
+        } else if (flagfromline) {
+         if (!stralloc_cat(&from,&line)) die_nomem();
+        } else if (flagcontline) {
+          if (!stralloc_cat(&content,&line)) die_nomem();
+        }
       }
-    }
-
-    if (!(flaginheader && flagbadfield))
-      put(line.s,line.len);
+    } else                             /* body */
+      msgsize += line.len;             /* always for tstdig support */
+
+    if (!(flaginheader && flagbadfield)) {
+      if (boundary.len && line.len > boundary.len &&
+               !str_diffn(line.s,boundary.s,boundary.len)) {
+        if (line.s[boundary.len] == '-' && line.s[boundary.len+1] == '-') {
+          flagbadpart = 0;             /* end boundary should be output */
+          if (flagtrailer) {
+            qmail_puts(&qq,"\n");
+            qmail_put(&qq,boundary.s,boundary.len);
+            qmail_puts(&qq,"\nContent-Type: text/plain; charset=");
+            qmail_puts(&qq,charset.s);
+            transferenc();             /* trailer for multipart message */
+           copy(&qq,"text/trailer",flagcd,FATAL);
+            if (flagcd == 'B') {       /* need to do our own flushing */
+              encodeB("",0,&qline,2,FATAL);
+              qmail_put(&qq,qline.s,qline.len);
+            }
+         }
+        } else {                       /* new part */
+            flagbadpart = 1;           /* skip lines */
+            if (!stralloc_copy(&lines,&line)) die_nomem();     /* but save */
+            flagseenext = 1;           /* need to check Cont-type */
+        }
+      } else if (flagseenext) {                /* last was boundary, now stored */
+        if (case_startb(line.s,line.len,"content-type:")) {
+          flagseenext = 0;             /* done thinking about it */
+          cp = line.s + 13;                    /* start of type */
+          while (*cp == ' ' || *cp == '\t') ++cp;
+          cpstart = cp;                        /* end of type */
+          while (*cp != '\n' && *cp != '\t' && *cp != ' ' && *cp != ';') ++cp;
+         if (constmap(&mimeremovemap,cpstart,cp-cpstart)) {
+            flagbadpart = 1;
+          } else {
+           flagfoundokpart = 1;
+            qa_put(lines.s,lines.len); /* saved lines */
+            flagbadpart = 0;           /* do this part */
+          }
+        } else if (line.len == 1) {    /* end of content desc */
+          flagbadpart = 0;             /* default type, so ok */
+          flagseenext = 0;             /* done thinking about it */
+        } else                         /* save line in cont desc */
+          if (!stralloc_cat(&lines,&line)) die_nomem();
+      }
+      if (!flagbadpart)
+        qa_put(line.s,line.len);
 
+    } else if (flagarchiveonly && flagarchived)        /* received headers */
+      if (substdio_put(&ssarchive,line.s,line.len) == -1) die_archive();
     if (!match)
       break;
   }
+  if (!boundary.len && flagtrailer) {
+    qmail_puts(&qq,"\n");              /* trailer for non-multipart message */
+    if (!encin || encin == 'Q') {      /* can add for QP, but not for base64 */
+      copy(&qq,"text/trailer",encin,FATAL);
+      qmail_puts(&qq,"\n");            /* no need to flush for plain/QP */
+    }
+  }
 
-  if (flagsublist)
-    if (!flagmlwasthere)
-      strerr_die2x(100,FATAL,"sublist messages must have Mailing-List (#5.7.2)");
-  if (!flagsublist)
+  cumsize += (msgsize + 128L) >> 8;    /* round to 256 byte 'records' */
+                                       /* check message tag */
+  if (flagsublist) {                   /* sublists need tag if selected/suppt*/
+    if (flaglog)
+      if ((ret = checktag(dir,innum,hash_lo+1L,"m",(char *) 0,hashout))) {
+        if (*ret) strerr_die2x(111,FATAL,ret);
+        else strerr_die2x(100,FATAL,ERR_NOT_PARENT);
+      }
+    if (!flagmlwasthere)               /* sublists need ML header */
+      strerr_die2x(100,FATAL,ERR_SUBLIST);
+  } else                               /* others are not allowed to have one */
     if (flagmlwasthere)
-      strerr_die2x(100,FATAL,"message already has Mailing-List (#5.7.2)");
+      strerr_die2x(100,FATAL,ERR_MAILING_LIST);
+  if (!flagfoundokpart)                        /* all parts were on the strip list */
+      strerr_die2x(100,FATAL,ERR_BAD_ALL);
 
   if (flagarchived) {
     if (substdio_flush(&ssarchive) == -1) die_archive();
     if (fsync(fdarchive) == -1) die_archive();
-    if (fchmod(fdarchive,0744) == -1) die_archive();
+    if (fchmod(fdarchive,MODE_ARCHIVE | 0700) == -1) die_archive();
     if (close(fdarchive) == -1) die_archive(); /* NFS stupidity */
   }
 
-  numwrite();
+  if (flaglog) {
+    tagmsg(dir,innum,sender,"m",hashout,qq.msgbytes,53L,FATAL);
+    hashout[COOKIE] = '\0';
+  }
 
+  numwrite();
   if (!stralloc_copy(&line,&outlocal)) die_nomem();
   if (!stralloc_cats(&line,"-return-")) die_nomem();
-  if (!stralloc_catb(&line,strnum,fmt_ulong(strnum,msgnum))) die_nomem();
+  if (!stralloc_cats(&line,szmsgnum)) die_nomem();
   if (!stralloc_cats(&line,"-@")) die_nomem();
   if (!stralloc_cat(&line,&outhost)) die_nomem();
   if (!stralloc_cats(&line,"-@[]")) die_nomem();
   if (!stralloc_0(&line)) die_nomem();
-
-  qmail_from(&qq,line.s);
-
-  for (i = 0;i < 53;++i) {
-    ch = 64 + i;
-    if (!stralloc_copys(&fnsub,"subscribers/")) die_nomem();
-    if (!stralloc_catb(&fnsub,&ch,1)) strerr_die2x(111,FATAL,"out of memory");
-    if (!stralloc_0(&fnsub)) strerr_die2x(111,FATAL,"out of memory");
-    fd = open_read(fnsub.s);
-    if (fd == -1) {
-      if (errno != error_noent)
-       strerr_die4sys(111,FATAL,"unable to read ",fnsub.s,": ");
-    }
-    else {
-      substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
-      substdio_fdbuf(&ssout,mywrite,-1,outbuf,sizeof(outbuf));
-      if (substdio_copy(&ssout,&ssin) != 0)
-       strerr_die4sys(111,FATAL,"unable to read ",fnsub.s,": ");
-      close(fd);
-    }
-  }
-
-  switch(qmail_close(&qq)) {
-    case 0:
+  qmail_from(&qq,line.s);                      /* envelope sender */
+  subs = putsubs(dir,hash_lo,hash_hi,subto,1,FATAL);   /* subscribers */
+  if (flagsublist) hash_lo++;
+
+  if (*(err = qmail_close(&qq)) == '\0') {
+      if (flaglog)                             /* mysql logging */
+       (void) logmsg(dir,outnum,hash_lo,subs,flagsublist ? 3 : 4);
+      closesql();
       strnum[fmt_ulong(strnum,qmail_qp(&qq))] = 0;
       strerr_die2x(0,"ezmlm-send: info: qp ",strnum);
-    default:
+  } else {
       --msgnum;
+      cumsize -= (msgsize + 128L) >> 8;
       numwrite();
-      strerr_die2x(111,FATAL,"temporary qmail-queue error");
+      strerr_die3x(111,FATAL,ERR_TMP_QMAIL_QUEUE,err + 1);
   }
 }