Import ezmlm-idx 0.40
[ezmlm] / ezmlm-get.c
diff --git a/ezmlm-get.c b/ezmlm-get.c
new file mode 100644 (file)
index 0000000..f8146ac
--- /dev/null
@@ -0,0 +1,1459 @@
+/*$Id: ezmlm-get.c,v 1.113 1999/11/22 01:47:45 lindberg Exp $*/
+/*$Name: ezmlm-idx-040 $*/
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "alloc.h"
+#include "error.h"
+#include "stralloc.h"
+#include "str.h"
+#include "env.h"
+#include "sig.h"
+#include "slurp.h"
+#include "getconf.h"
+#include "strerr.h"
+#include "byte.h"
+#include "getln.h"
+#include "case.h"
+#include "qmail.h"
+#include "substdio.h"
+#include "readwrite.h"
+#include "seek.h"
+#include "quote.h"
+#include "datetime.h"
+#include "now.h"
+#include "date822fmt.h"
+#include "fmt.h"
+#include "sgetopt.h"
+#include "cookie.h"
+#include "makehash.h"
+#include "copy.h"
+#include "constmap.h"
+#include "subscribe.h"
+#include "idxthread.h"
+#include "idx.h"
+#include "mime.h"
+#include "errtxt.h"
+
+int flagdo = 1;                        /* React to commands (doesn't affect -dig)*/
+int flagbottom = 1;            /* copy text/bottom + request */
+int flagpublic = 2;            /* 0 = non-public, 1 = public, 2 = respect*/
+                               /* dir/public. */
+char flagcd = '\0';            /* default: don't use quoted-printable */
+int flagsub = 0;               /* =1 subscribers only for get/index/thread */
+char *digsz =
+               "from\\to\\subject\\reply-to\\date\\message-id\\cc\\"
+               "mime-version\\content-type\\content-transfer-encoding";
+
+#define FATAL "ezmlm-get: fatal: "
+
+void die_usage() {
+  strerr_die1x(100,
+    "ezmlm-get: usage: "
+       "ezmlm-get [-bBcClLpPsSvV] [-f fmt] [digestcode]");
+}
+
+void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); }
+
+void die_badaddr()
+{
+  strerr_die2x(100,FATAL,ERR_BAD_ADDRESS);
+}
+
+stralloc inhost = {0};
+stralloc outhost = {0};
+stralloc inlocal = {0};
+stralloc outlocal = {0};
+stralloc listname = {0};
+stralloc mailinglist = {0};
+stralloc qmqpservers = {0};
+stralloc fn = {0};
+stralloc moddir = {0};
+stralloc charset = {0};
+stralloc mydtline = {0};
+stralloc digheaders = {0};
+stralloc seed = {0};
+struct constmap digheadersmap;
+
+char schar[] = "00_";
+stralloc listno = {0};
+void *psql = (void *) 0;
+
+datetime_sec when;
+struct datetime dt;
+unsigned long cumsize = 0L;    /* cumulative msgs / 256 */
+unsigned long cumsizen = 0L;   /* new cumulative msgs / 256 */
+unsigned long max = 0L;                /* Last message in archive */
+unsigned long msgsize = 0L;    /* for digest accounting */
+datetime_sec digwhen;          /* last digest */
+
+char strnum[FMT_ULONG];
+char szmsgnum[FMT_ULONG];
+char date[DATE822FMT];
+char boundary[COOKIE];
+char hashout[COOKIE];
+stralloc line = {0};
+stralloc line2 = {0};
+stralloc qline = {0};
+stralloc quoted = {0};
+stralloc msgnum = {0};
+stralloc num = {0};
+stralloc subject = {0};
+
+/* for copy archive */
+stralloc archdate = {0};
+stralloc archfrom = {0};
+stralloc archto = {0};
+stralloc archcc = {0};
+stralloc archsubject = {0};
+stralloc archmessageid = {0};
+stralloc archkeywords = {0};
+stralloc archblanklines = {0};
+char archtype=' ';
+
+/* for mods on non-public lists (needed for future fuzzy sub dbs) */
+stralloc mod = {0};            /* moderator addr for non-public lists */
+char *pmod = (char *) 0;       /* pointer to above */
+
+/* for digest */
+stralloc ddir = {0};
+stralloc edir = {0};
+
+int act = AC_NONE;             /* Action we do */
+int flageditor = 0;            /* if we're invoked for within dir/editor */
+struct stat st;
+
+int flaglocked = 0;            /* if directory is locked */
+int flagarchived;              /* if list is archived */
+int flagindexed;               /* if list is indexed */
+int flagq = 0;                 /* don't use 'quoted-printable' */
+
+char *dir;
+char *workdir;
+char *sender;
+char *digestcode;
+
+struct qmail qq;
+int qqwrite(fd,buf,len) int fd; char *buf; unsigned int len;
+{
+  qmail_put(&qq,buf,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;
+}
+
+char qqbuf[1];
+substdio ssqq = SUBSTDIO_FDBUF(qqwrite,-1,qqbuf,sizeof(qqbuf));
+
+char inbuf[1024];
+substdio ssin = SUBSTDIO_FDBUF(read,0,inbuf,sizeof(inbuf));
+substdio ssin2 = SUBSTDIO_FDBUF(read,0,inbuf,sizeof(inbuf));
+
+substdio ssnum;
+char numbuf[16];
+
+substdio sstext;
+char textbuf[1024];
+
+substdio ssindex;
+char indexbuf[1024];
+
+int fdlock;
+
+void lockup()
+/* lock unless locked */
+{
+  if(!flaglocked) {
+    fdlock = open_append("lock");
+    if (fdlock == -1)
+      strerr_die2sys(111,FATAL,ERR_OPEN_LOCK);
+    if (lock_ex(fdlock) == -1) {
+      close(fdlock);
+      strerr_die2sys(111,FATAL,ERR_OBTAIN_LOCK);
+    }
+    flaglocked = 1;
+  }
+}
+
+void unlock()
+/* unlock if locked */
+{
+  if (flaglocked) {
+    close(fdlock);
+    flaglocked = 0;
+  }
+}
+
+void code_qput(s,n)
+char *s;
+unsigned int n;
+{
+    if (!flagcd)
+      qmail_put(&qq,s,n);
+    else {
+      if (flagcd == 'B')
+        encodeB(s,n,&qline,0,FATAL);
+      else
+        encodeQ(s,n,&qline,FATAL);
+      qmail_put(&qq,qline.s,qline.len);
+      msgsize += qline.len;
+    }
+}
+
+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 zapnonsub(szerr)
+/* fatal error if flagsub is set and sender is not a subscriber */
+/* expects the current dir to be the list dir. Error is szerr */
+/* added check for undefined sender as a precaution */
+char *szerr;
+{
+  if (sender && *sender) {     /* "no sender" is not a subscriber */
+    if (!flagsub)
+      return;
+    if (issub(dir,sender,(char *) 0,FATAL))
+      return;          /* subscriber */
+    if (issub(ddir.s,sender,(char *) 0,FATAL))
+      return;          /* digest subscriber */
+    if (issub(edir.s,sender,(char *) 0,FATAL))
+      return;          /* allow addresses */
+  }
+  strerr_die4x(100,FATAL,ERR_SUBSCRIBER_CAN,szerr,ERR_571);
+}
+
+void tosender()
+{
+  qmail_puts(&qq,"To: ");
+  if (!quote2(&quoted,sender)) die_nomem();
+  qmail_put(&qq,quoted.s,quoted.len);
+  qmail_puts(&qq,"\n");
+}
+
+void get_num()
+{
+/* read dir/num -> max. max/cumsizen left alone if not present */
+/* Both of these should have been initialized to 0L */
+
+  unsigned int pos;
+  if (getconf_line(&num,"num",0,FATAL,dir)) {
+    if(!stralloc_0(&num)) die_nomem();
+    pos = scan_ulong(num.s,&max);
+    if (num.s[pos] == ':') pos++;
+    scan_ulong(num.s+pos,&cumsizen);
+  }
+}
+
+unsigned long dignum()
+{
+/* return dignum if exists, 0 otherwise. */
+
+  unsigned long retval;
+  if (!stralloc_copys(&num,"")) die_nomem();   /* zap */
+  getconf_line(&num,"dignum",0,FATAL,dir);
+  if(!stralloc_0(&num)) die_nomem();
+  scan_ulong(num.s,&retval);
+  return retval;
+}
+
+void write_ulong(num,cum,dat,fn,fnn)
+/* write num to "fnn" add ':' & cum if cum <>0, then move "fnn" to "fn" */
+char *fn, *fnn;
+unsigned long num,cum,dat;
+{
+  int fd;
+
+  fd = open_trunc(fnn);
+  if (fd == -1)
+     strerr_die6sys(111,FATAL,ERR_CREATE,dir,"/",fnn,": ");
+  substdio_fdbuf(&ssnum,write,fd,numbuf,sizeof(numbuf));
+  if (substdio_put(&ssnum,strnum,fmt_ulong(strnum,num)) == -1)
+     strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn,": ");
+  if (substdio_puts(&ssnum,":") == -1)
+     strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn,": ");
+  if (substdio_put(&ssnum,strnum,fmt_ulong(strnum,cum)) == -1)
+     strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn,": ");
+  if (dat) {
+    if (substdio_puts(&ssnum,":") == -1)
+       strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn,": ");
+    if (substdio_put(&ssnum,strnum,fmt_ulong(strnum,dat)) == -1)
+       strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn,": ");
+  }
+  if (substdio_puts(&ssnum,"\n") == -1)
+     strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn,": ");
+  if (substdio_flush(&ssnum) == -1)
+     strerr_die6sys(111,FATAL,ERR_FLUSH,dir,"/",fnn,": ");
+  if (fsync(fd) == -1)
+     strerr_die6sys(111,FATAL,ERR_SYNC,dir,"/",fnn,": ");
+  if (close(fd) == -1)
+     strerr_die6sys(111,FATAL,ERR_CLOSE,dir,"/",fnn,": ");
+  if (rename(fnn,fn) == -1)
+     strerr_die4sys(111,FATAL,ERR_MOVE,fnn,": ");
+}
+
+void normal_bottom(format)
+char format;
+/* Copies bottom text and the original message to the new message */
+{
+  if (flagbottom) {
+    copy(&qq,"text/bottom",flagcd,FATAL);
+    if (flagcd && format != RFC1153) {
+      if (flagcd == 'B') {
+       encodeB("",0,&line,2,FATAL);    /* flush */
+       qmail_put(&qq,line.s,line.len);
+      }
+      qmail_puts(&qq,"\n--");
+      qmail_put(&qq,boundary,COOKIE);
+      qmail_puts(&qq,"\nContent-Type: message/rfc822");
+      qmail_puts(&qq,"\nContent-Disposition: inline; filename=request.msg\n\n");
+    }
+    qmail_puts(&qq,"Return-Path: <");
+    if (!quote2(&quoted,sender)) die_nomem();
+    qmail_put(&qq,quoted.s,quoted.len);
+    qmail_puts(&qq,">\n");
+    if (seek_begin(0) == -1)
+      strerr_die2sys(111,FATAL,ERR_SEEK_INPUT);
+    if (substdio_copy(&ssqq,&ssin2) != 0)
+      strerr_die2sys(111,FATAL,ERR_READ_INPUT);
+  } else {
+    if (flagcd == 'B' && format != RFC1153) {
+      encodeB("",0,&line,2,FATAL);     /* flush */
+      qmail_put(&qq,line.s,line.len);
+    }
+  }
+}
+
+void presub(from,to,subject,factype,format)
+/* Starts within header, outputs "subject" and optional headers, terminates*/
+/* header and handles output before table-of-contents                      */
+unsigned long from,to;
+stralloc *subject;
+int factype;           /* action type (AC_THREAD, AC_GET, AC_DIGEST) */
+char format;           /* output format type (see idx.h) */
+{
+  qmail_puts(&qq,"MIME-Version: 1.0\n");
+  switch(format) {
+    case MIME:
+    case VIRGIN:
+    case NATIVE:
+    case MIXED:
+        qmail_puts(&qq,"Content-Type: multipart/");
+        if (format == MIXED)
+         qmail_puts(&qq,"mixed");
+        else
+          qmail_puts(&qq,"digest");
+       qmail_puts(&qq,"; boundary=");
+        qmail_put(&qq,boundary,COOKIE);
+       qmail_puts(&qq,"\nSubject: ");
+       qmail_put(&qq,subject->s,subject->len);
+       qmail_puts(&qq,"\n\n\n--");
+        qmail_put(&qq,boundary,COOKIE);
+        qmail_puts(&qq,"\nContent-Type: text/plain; charset=");
+       qmail_puts(&qq,charset.s);
+       transferenc();  /* content-transfer-enc header if needed */
+        qmail_puts(&qq,"\n");
+       break;
+    case RFC1153:
+       qmail_puts(&qq,"Content-type: text/plain; charset=");
+       qmail_puts(&qq,charset.s);
+       qmail_puts(&qq,"\nSubject: ");
+       qmail_put(&qq,subject->s,subject->len);
+       qmail_puts(&qq,"\n\n");
+       flagcd = '\0';  /* We make 8-bit messages, not QP/bas64 for rfc1153 */
+        break;         /* Since messages themselves aren't encoded */
+    }
+    if (!stralloc_cats(subject,"\n\n")) die_nomem();
+    code_qput(subject->s,subject->len);
+    if (format != NATIVE && factype != AC_THREAD && factype != AC_INDEX) {
+      if (!stralloc_copys(&line,TXT_TOP_TOPICS)) die_nomem();
+      if (!stralloc_cats(&line,TXT_TOP_MESSAGES)) die_nomem();
+      if (!stralloc_catb(&line,strnum,fmt_ulong(strnum,from))) die_nomem();
+      if (!stralloc_cats(&line,TXT_TOP_THROUGH)) die_nomem();
+      if (!stralloc_catb(&line,strnum,fmt_ulong(strnum,to))) die_nomem();
+      if (!stralloc_cats(&line,TXT_TOP_LAST)) die_nomem();
+      code_qput(line.s,line.len);
+    }
+}
+
+void postsub(factype,format)
+/* output after TOC and before first message. */
+int factype;           /* action type (AC_THREAD, AC_GET, AC_DIGEST) */
+char format;           /* output format type (see idx.h) */
+{
+    code_qput(TXT_ADMINISTRIVIA,str_len(TXT_ADMINISTRIVIA));
+    if(factype == AC_DIGEST) {
+      copy(&qq,"text/digest",flagcd,FATAL);
+      if (flagcd == 'B') {
+        encodeB("",0,&line,2,FATAL);   /* flush */
+        qmail_put(&qq,line.s,line.len);
+      }
+     } else
+      normal_bottom(format);
+    if (!flagcd || format == RFC1153)
+      qmail_puts(&qq,"\n----------------------------------------------------------------------\n");
+    else
+      qmail_puts(&qq,"\n");
+}
+
+void postmsg(format)
+char format;
+{
+    switch(format) {
+       case MIME:
+       case VIRGIN:
+       case NATIVE:
+        case MIXED:
+                qmail_puts(&qq,"\n--");
+                qmail_put(&qq,boundary,COOKIE);                /* digest boundary */
+                qmail_puts(&qq,"--\n");
+               break;
+       case RFC1153:
+               qmail_puts(&qq,"End of ");
+               qmail_put(&qq,listname.s,listname.len);
+               qmail_puts(&qq," Digest");
+               qmail_puts(&qq,"\n***********************************\n");
+               break;
+    }
+}
+
+void copymsg(msg,fd,format)
+/* Copy archive message "msg" itself from open file handle fd, in "format" */
+unsigned long msg; int fd; char format;
+{
+  int match;
+  int flaginheader;
+  int flagskipblanks;
+  int flaggoodfield;
+
+  switch(format) {
+    case VIRGIN:
+    case NATIVE:
+      substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf));
+      for (;;) {
+        if (getln(&sstext,&line,&match,'\n') == -1)
+           strerr_die4sys(111,FATAL,ERR_READ,line.s,": ");
+        if (match) {
+           qmail_put(&qq,line.s,line.len);
+          msgsize += line.len;
+        } else
+           break;
+      }
+      break;
+    case MIME:
+    case MIXED:
+      flaginheader = 1;
+      flaggoodfield = 0;
+      substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf));
+      for (;;) {
+        if (getln(&sstext,&line,&match,'\n') == -1)
+           strerr_die4sys(111,FATAL,ERR_READ,line.s,": ");
+        if (match) {
+          if (flaginheader) {
+            if (line.len == 1) {
+              flaginheader = 0;
+              flaggoodfield = 1;
+            } else if (line.s[0] != ' ' && line.s[0] != '\t') {
+              flaggoodfield = 0;
+              if (constmap(&digheadersmap,line.s,
+                       byte_chr(line.s,line.len,':')))
+                flaggoodfield = 1;
+            }
+            if (flaggoodfield) {
+              qmail_put(&qq,line.s,line.len);          /* header */
+             msgsize += line.len;
+           }
+          } else {
+            qmail_put(&qq,line.s,line.len);            /* body */
+           msgsize += line.len;
+         }
+        } else
+          break;
+      }
+      break;
+    case RFC1153:              /* Not worth optimizing. Rarely used */
+      flaginheader = 1;
+      flagskipblanks = 1;      /* must skip terminal blanks acc to rfc1153 */
+      archtype = ' ';          /* rfc1153 requires ordered headers */
+      if (!stralloc_copys(&archblanklines,"")) die_nomem();
+      substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf));
+      for (;;) {
+        if (getln(&sstext,&line,&match,'\n') == -1)
+           strerr_die4sys(111,FATAL,ERR_READ,line.s,": ");
+        if (match) {
+          if (flaginheader) {
+            if (line.len == 1) {
+              flaginheader = 0;
+              if (archdate.len) {
+                qmail_put(&qq,archdate.s,archdate.len);
+                archdate.len = 0;
+               msgsize += archdate.len;
+              }
+              if (archto.len) {
+                qmail_put(&qq,archto.s,archto.len);
+               msgsize += archto.len;
+                archto.len = 0;
+              }
+              if (archfrom.len) {
+                qmail_put(&qq,archfrom.s,archfrom.len);
+               msgsize += archfrom.len;
+                archfrom.len = 0;
+              }
+              if (archcc.len) {
+                qmail_put(&qq,archcc.s,archcc.len);
+               msgsize += archcc.len;
+                archcc.len = 0;
+              }
+              if (archsubject.len) {
+                qmail_put(&qq,archsubject.s,archsubject.len);
+               msgsize += archsubject.len;
+                archsubject.len = 0;
+              }
+              if (archmessageid.len) {
+                qmail_put(&qq,archmessageid.s,archmessageid.len);
+               msgsize += archmessageid.len;
+                archmessageid.len = 0;
+              }
+              if (archkeywords.len) {
+                qmail_put(&qq,archkeywords.s,archkeywords.len);
+               msgsize += archkeywords.len;
+                archkeywords.len = 0;
+              }
+              qmail_puts(&qq,"\n");
+            } else if (line.s[0] == ' ' || line.s[0] == '\t') {
+              switch (archtype) {      /* continuation lines */
+                case ' ':
+                  break;
+                case 'D':
+                  if (!stralloc_cat(&archdate,&line)) die_nomem(); break;
+                case 'F':
+                  if (!stralloc_cat(&archfrom,&line)) die_nomem(); break;
+                case 'T':
+                  if (!stralloc_cat(&archto,&line)) die_nomem(); break;
+                case 'C':
+                  if (!stralloc_cat(&archcc,&line)) die_nomem(); break;
+                case 'S':
+                  if (!stralloc_cat(&archsubject,&line)) die_nomem(); break;
+                case 'M':
+                  if (!stralloc_cat(&archmessageid,&line)) die_nomem(); break;
+                case 'K':
+                  if (!stralloc_cat(&archkeywords,&line)) die_nomem(); break;
+                default:
+                  strerr_die2x(111,FATAL,
+                      "Program error: Bad archive header type");
+              }
+            } else {
+              archtype = ' ';
+              if (case_startb(line.s,line.len,"cc:")) {
+                archtype='C';
+                if (!stralloc_copy(&archcc,&line)) die_nomem();
+              }
+              else if (case_startb(line.s,line.len,"date:")) {
+                archtype='D';
+                if (!stralloc_copy(&archdate,&line)) die_nomem();
+              }
+              else if (case_startb(line.s,line.len,"from:")) {
+                archtype='F';
+                if (!stralloc_copy(&archfrom,&line)) die_nomem();
+              }
+              else if (case_startb(line.s,line.len,"keywords:")) {
+                archtype='K';
+                if (!stralloc_copy(&archkeywords,&line)) die_nomem();
+              }
+              else if (case_startb(line.s,line.len,"message-id:")) {
+                archtype='M';
+                if (!stralloc_copy(&archmessageid,&line)) die_nomem();
+              }
+              else if (case_startb(line.s,line.len,"subject:")) {
+                archtype='S';
+                if (!stralloc_copy(&archsubject,&line)) die_nomem();
+              }
+              else if (case_startb(line.s,line.len,"to:")) {
+                archtype='T';
+                if (!stralloc_copy(&archto,&line)) die_nomem();
+              }
+            }
+          } else if (line.len == 1) {
+            if (!flagskipblanks)
+              if (!stralloc_copys(&archblanklines,"\n")) die_nomem();
+          } else {
+            if (archblanklines.len) {
+              qmail_put(&qq,archblanklines.s,archblanklines.len);
+              archblanklines.len = 0;
+            }
+            flagskipblanks = 0;
+            qmail_put(&qq,line.s,line.len);
+           msgsize += line.len;
+          }
+        } else
+          break;
+      }
+      break;
+    default:
+      strerr_die2x(100,FATAL,"Program error: bad format in copymsg()");
+  }
+}
+
+void mime_getbad(msg)
+/* Message not found as a MIME multipart */
+unsigned long msg;
+{
+   qmail_puts(&qq,"\n--");
+   qmail_put(&qq,boundary,COOKIE);
+   qmail_puts(&qq,"\nContent-Type: text/plain; charset=");
+   qmail_puts(&qq,charset.s);
+   qmail_puts(&qq,"\nContent-Disposition: inline; filename=\"");
+   qmail_put(&qq,listname.s,listname.len);
+   qmail_puts(&qq,"_");
+   qmail_put(&qq,strnum,fmt_ulong(strnum,msg));
+   qmail_puts(&qq,".ezm\"\n");
+   transferenc();
+   copy(&qq,"text/get-bad",flagcd,FATAL);
+}
+
+void msgout(msg,format)
+/* Outputs message (everything that's needed per message) */
+unsigned long msg; char format;
+{
+  int fd;
+  unsigned int len;
+
+    if (!stralloc_copys(&fn,"archive/")) die_nomem();
+
+    len = fmt_ulong(strnum, msg / 100);
+    if (!stralloc_catb(&fn,strnum,len)) die_nomem();
+    if (!stralloc_cats(&fn,"/")) die_nomem();
+    len = fmt_uint0(strnum, (unsigned int) (msg % 100),2);
+    if (!stralloc_catb(&fn,strnum,len)) die_nomem();
+    if (!stralloc_0(&fn)) die_nomem();
+
+    switch(format) {
+      case MIME:
+      case VIRGIN:
+      case NATIVE:
+      case MIXED:
+       fd = open_read(fn.s);
+       if (fd == -1) {
+         if (errno != error_noent)
+           strerr_die4sys(111,FATAL,ERR_OPEN,fn.s,": ");
+          else
+            mime_getbad(msg);
+        } else if (fstat(fd,&st) == -1 || (!(st.st_mode & 0100))) {
+         close(fd);
+          mime_getbad(msg);
+        } else {
+          qmail_puts(&qq,"\n--");
+          qmail_put(&qq,boundary,COOKIE);
+          qmail_puts(&qq,"\nContent-Type: message/rfc822");
+          qmail_puts(&qq,"\nContent-Disposition: inline; filename=\"");
+         qmail_put(&qq,listname.s,listname.len);
+         qmail_puts(&qq,"_");
+         qmail_put(&qq,strnum,fmt_ulong(strnum,msg));
+         qmail_puts(&qq,".ezm\"\n\n");
+          copymsg(msg,fd,format);
+         close(fd);
+        }
+       break;
+      case RFC1153:
+       fd = open_read(fn.s);
+       if (fd == -1) {
+         if (errno != error_noent)
+           strerr_die4sys(111,FATAL,ERR_OPEN,fn.s,": ");
+         else {
+           qmail_puts(&qq,"\n== ");
+           qmail_put(&qq,strnum,fmt_ulong(strnum,msg));
+           qmail_puts(&qq," ==\n\n");
+           copy(&qq,"text/get-bad",flagcd,FATAL);
+          }
+        } else {
+          if (fstat(fd,&st) == -1 || (!(st.st_mode & 0100))) {
+           close(fd);
+           qmail_puts(&qq,"\n== ");
+           qmail_put(&qq,strnum,fmt_ulong(strnum,msg));
+           qmail_puts(&qq," ==\n\n");
+           copy(&qq,"text/get-bad",flagcd,FATAL);
+         } else {
+           copymsg(msg,fd,format);
+           close(fd);
+          }
+       }
+       qmail_puts(&qq,"\n------------------------------\n\n");
+       break;
+      default:
+        strerr_die2x(100,FATAL,"Program error: Unrecognized format in msgout");
+        break;
+    }
+}
+
+void digest(msgtable,subtable,authtable,from,to,subj,factype,format)
+/* Output digest range from-to as per msgtable/subtable (from mkthread(s)). */
+/* "Subject is the subject of the _entire_ digest/set. */
+msgentry *msgtable; subentry *subtable; authentry *authtable;
+unsigned long from,to; stralloc *subj; int factype; char format;
+{
+  msgentry *pmsgt;
+  subentry *psubt;
+  char *cp;
+  int ffirstmsg;
+  unsigned int len;
+  unsigned long msg;
+  unsigned long subnum;
+
+  psubt = subtable;
+  presub(from,to,subj,factype,format);
+
+  if (format != NATIVE) {
+    while (psubt->sub) {
+      ffirstmsg = 1;
+               /* ptr to first message with this subject */
+      pmsgt = msgtable + psubt->firstmsg - from;
+      subnum = (unsigned long) (psubt - subtable +1);
+      for (msg=psubt->firstmsg; msg<=to; msg++) {
+        if (pmsgt->subnum == subnum) {
+          if(ffirstmsg) {
+            ffirstmsg = 0;
+            if (!stralloc_copys(&line,"\n")) die_nomem();
+           if (psubt->sublen <= HASHLEN + 2) {
+              if (!stralloc_cats(&line,"(null)\n")) die_nomem();
+           } else
+              if (!stralloc_catb(&line,psubt->sub + HASHLEN + 1,
+               psubt->sublen - HASHLEN - 1)) die_nomem();
+          } else
+            if (!stralloc_copys(&line,"")) die_nomem();
+          if (!stralloc_cats(&line,"\t")) die_nomem();
+          if (!stralloc_catb(&line,strnum,fmt_ulong(strnum,msg))) die_nomem();
+          if (!stralloc_cats(&line,TXT_BY)) die_nomem();
+          if (pmsgt->authnum) {
+           cp = authtable[pmsgt->authnum - 1].auth;
+           len = authtable[pmsgt->authnum - 1].authlen;
+           if (len > HASHLEN) {
+              if (!stralloc_catb(&line,cp + HASHLEN + 1,
+               len - HASHLEN - 1)) die_nomem();
+           } else
+             if (!stralloc_catb(&line,cp,len)) die_nomem();
+         } else
+            if (!stralloc_cats(&line,"\n")) die_nomem();
+          code_qput(line.s,line.len);
+        }
+        pmsgt++;
+      }
+      psubt++;
+    }
+  }
+  postsub(factype,format);
+
+  psubt = subtable;
+  while (psubt->sub) {
+    pmsgt = msgtable + psubt->firstmsg - from;
+    subnum = (unsigned long) (psubt - subtable +1);
+    for (msg=psubt->firstmsg; msg<=to; msg++) {
+      if (pmsgt->subnum == subnum)
+        msgout(msg,format);
+      pmsgt++;
+    }
+    psubt++;
+  }
+  postmsg(format);
+  idx_destroythread(msgtable,subtable,authtable);
+}
+
+void doheaders()
+{
+  int flaggoodfield,match;
+
+  if (act == AC_DIGEST)
+    copy(&qq,"headeradd",'H',FATAL);
+
+  qmail_puts(&qq,"Mailing-List: ");
+  qmail_put(&qq,mailinglist.s,mailinglist.len);
+  if (getconf_line(&line,"listid",0,FATAL,dir)) {
+    qmail_puts(&qq,"\nList-ID: ");
+    qmail_put(&qq,line.s,line.len);
+  }
+  qmail_puts(&qq,"\nDate: ");
+  qmail_put(&qq,date,date822fmt(date,&dt));
+  qmail_puts(&qq,"Message-ID: <");
+  if (!stralloc_copyb(&line,strnum,fmt_ulong(strnum,(unsigned long) when)))
+     die_nomem();
+  if (!stralloc_append(&line,".")) die_nomem();
+  if (!stralloc_catb(&line,strnum,
+               fmt_ulong(strnum,(unsigned long) getpid()))) die_nomem();
+  if (!stralloc_cats(&line,".ezmlm@")) die_nomem();
+  if (!stralloc_cat(&line,&outhost)) die_nomem();
+  if (!stralloc_0(&line)) die_nomem();
+  qmail_puts(&qq,line.s);
+               /* "unique" MIME boundary as hash of messageid */
+  makehash(line.s,line.len,boundary);
+  qmail_puts(&qq,">\nFrom: ");
+  if (!quote(&quoted,&outlocal)) die_nomem();
+  qmail_put(&qq,quoted.s,quoted.len);
+  qmail_puts(&qq,"-help@");
+  qmail_put(&qq,outhost.s,outhost.len);
+  qmail_puts(&qq,"\n");
+  if (!stralloc_copys(&mydtline,"Delivered-To: responder for ")) die_nomem();
+  if (!stralloc_catb(&mydtline,outlocal.s,outlocal.len)) die_nomem();
+  if (!stralloc_cats(&mydtline,"@")) die_nomem();
+  if (!stralloc_catb(&mydtline,outhost.s,outhost.len)) die_nomem();
+  if (!stralloc_cats(&mydtline,"\n")) die_nomem();
+
+  qmail_put(&qq,mydtline.s,mydtline.len);
+
+  flaggoodfield = 0;
+  if (act != AC_DIGEST)
+    for (;;) {
+    if (getln(&ssin,&line,&match,'\n') == -1)
+      strerr_die2sys(111,FATAL,ERR_READ_INPUT);
+    if (!match) break;
+    if (line.len == 1) break;
+    if ((line.s[0] != ' ') && (line.s[0] != '\t')) {
+      flaggoodfield = 0;
+      if (case_startb(line.s,line.len,"mailing-list:"))
+        if (flageditor)                        /* we may be running from a sublist */
+          flaggoodfield = 0;
+        else
+          strerr_die2x(100,FATAL,ERR_MAILING_LIST);
+      if (line.len == mydtline.len)
+       if (byte_equal(line.s,line.len,mydtline.s))
+          strerr_die2x(100,FATAL,ERR_LOOPING);
+      if (case_startb(line.s,line.len,"delivered-to:"))
+        flaggoodfield = 1;
+      if (case_startb(line.s,line.len,"received:"))
+        flaggoodfield = 1;
+    }
+    if (flaggoodfield)
+      qmail_put(&qq,line.s,line.len);
+  }
+}
+
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+  char *def;
+  char *local;
+  char *action = "";
+  char *psz;
+  char *err;
+  int fd;
+  unsigned int i,j;
+  int flagremote;
+  int flagqmqp = 0;
+  int match;
+  int goodexit = 0;                    /* exit code for normal exit */
+                                       /* in manager this will be set to 0 */
+  unsigned long from,u,to,issue,prevmax,mno;
+  unsigned long chunk;
+  unsigned long subs;
+  unsigned int pos,pos1;
+  unsigned int len;
+  int opt;
+  char outformat = DEFAULT_FORMAT;     /* default output format */
+  msgentry *msgtable;
+  subentry *subtable;
+  authentry *authtable;
+  dateentry *datetable;
+
+  (void) umask(022);
+  sig_pipeignore();
+  when = now();
+  datetime_tai(&dt,when);
+
+  while ((opt = getopt(argc,argv,"bBcCf:pPsSt:vV")) != opteof)
+    switch (opt) {
+      case 'b': flagbottom = 1; break; /* add text/bottom (default) */
+      case 'B': flagbottom = 0; break; /* suppress text/bottom */
+      case 'c': flagdo = 1; break;     /* do commands */
+      case 'C': flagdo = 0; break;     /* ignore commands X dig */
+      case 'f': if (FORMATS[str_chr(FORMATS,optarg[0])])
+                   outformat = optarg[0];
+                break;
+      case 'p': flagpublic = 1; break; /* always public */
+      case 'P': flagpublic = 0; break; /* never public = only mods do cmd*/
+                                       /* def = 2: respect DIR/public */
+      case 's': flagsub = 1; break;    /* only subs have archive access */
+      case 'S': flagsub = 0; break;    /* everyone has archive access */
+      case 'v':
+      case 'V': strerr_die2x(0,"ezmlm-get version: ",EZIDX_VERSION);
+      default:
+        die_usage();
+    }
+
+  dir = argv[optind++];
+  if (!dir) die_usage();
+  if (chdir(dir) == -1)
+    strerr_die4x(111,FATAL,ERR_SWITCH,dir,": ");
+
+  digestcode = argv[optind];   /* code to activate digest (-digest-code)*/
+                               /* ignore any extra args */
+
+  getconf_line(&outlocal,"outlocal",1,FATAL,dir);
+  if (!stralloc_copy(&subject,&outlocal)) die_nomem(); /* for subjects */
+  if (!stralloc_copy(&listname,&outlocal)) die_nomem();        /* for content disp */
+
+  local = env_get("LOCAL");
+  def = env_get("DEFAULT");
+  sender = env_get("SENDER");
+  if (local && *local) {       /* in editor local = inlocal */
+    if (!sender) strerr_die2x(100,FATAL,ERR_NOSENDER);
+    if (!*sender)
+      strerr_die2x(100,FATAL,ERR_BOUNCE);
+    if (str_equal(sender,"#@[]"))
+      strerr_die2x(100,FATAL,ERR_BOUNCE);
+    if (!sender[str_chr(sender,'@')])
+      strerr_die2x(100,FATAL,ERR_ANONYMOUS);
+    if (def) {                 /* qmail>=1.02 support only */
+      if (*def) {
+       action = def;
+       goodexit = 99;
+      } else
+       _exit(0);               /* list-@host should do -help from manager */
+    } else {                   /* editor */
+      act = AC_DIGEST;         /* on list-@host ! */
+      flageditor = 1;          /* to avoid Mailing-list error on sublists */
+                               /* when running out of dir/editor. */
+    }
+    if (case_starts(action,"dig")) {
+      action += 3;
+      if (action[0] == '-' || action [0] == '.') {
+        action++;
+       if (!digestcode)
+            strerr_die2x(100,FATAL,ERR_BAD_DIGCODE);
+        len = str_len(digestcode);
+        if (len <= str_len(action) && case_startb(action,len,digestcode)) {
+          if (FORMATS[str_chr(FORMATS,*(action+len))])
+            outformat = *(action+len);
+          act = AC_DIGEST;
+        } else
+          strerr_die2x(100,FATAL,ERR_BAD_DIGCODE);
+      }
+    }
+  } else                       /* Command line operation */
+    act = AC_DIGEST;
+
+       /* Things we deal with. If anything else just die with success!   */
+       /* At the moment this is -index, -thread, and -get.               */
+       /* If flagdo = 0 we only service -dig commands. This is to support*/
+       /* "secret" lists that are still archived and digested. -c on     */
+       /* cmd line. */
+
+  if (act == AC_NONE) {
+    if (case_equals(action,ACTION_DIGEST)) {
+      act = AC_GET;            /* list-digest@ => msg since last digest */
+      action = ACTION_GET;
+    } else if (case_starts(action,ACTION_GET) || case_starts(action,ALT_GET))
+      act = AC_GET;
+    else if (case_starts(action,ACTION_INDEX) || case_starts(action,ALT_INDEX))
+      act = AC_INDEX;
+    else if (case_starts(action,ACTION_THREAD) ||
+        case_starts(action,ALT_THREAD))
+      act = AC_THREAD;
+  }
+  if (act == AC_NONE)                  /* not for us. Pass the buck. */
+    _exit(0);
+  if (act != AC_INDEX) {               /* need to do header processing */
+    if(!getconf(&digheaders,"digheaders",0,FATAL,dir)) {
+      if(!stralloc_copys(&digheaders,digsz)) die_nomem();
+      if (!stralloc_0(&digheaders)) die_nomem();
+      psz = digheaders.s;
+      while (*psz) {
+        if (*psz == '\\') *psz = '\0';
+        ++psz;
+      }
+    }
+    if (!constmap_init(&digheadersmap,digheaders.s,digheaders.len,0))
+       die_nomem();
+  }
+  if (act != AC_DIGEST) {
+    if (!flagdo)                       /* only do digests */
+      strerr_die2x(100,FATAL,ERR_NOCMD);
+    if (flagpublic == 2)
+      flagpublic = getconf_line(&line,"public",0,FATAL,dir);
+    if (!flagpublic) {
+               /* This all to take care of non-public lists. They should*/
+               /* still do digests, but do other things only for        */
+               /* moderators that have remote access. Since this is rare*/
+               /* efforts have been made to keep everything that's not  */
+               /* needed elsewhere in here.                   */
+      getconf_line(&moddir,"modsub",0,FATAL,dir);
+      flagremote = getconf_line(&line,"remote",0,FATAL,dir);
+      if (!flagremote)
+        strerr_die2x(100,FATAL,ERR_NOT_PUBLIC);
+      if (!moddir.len || moddir.s[0] != '/') {
+        if (line.len && line.s[0] == '/') {
+          if (!stralloc_copy(&moddir,&line)) die_nomem();
+        } else {
+          if (!stralloc_copys(&moddir,dir)) die_nomem();
+          if (!stralloc_cats(&moddir,"/mod")) die_nomem();
+        }
+      }
+      if (!stralloc_0(&moddir)) die_nomem();
+      pmod = issub(moddir.s,sender,(char *) 0,FATAL);
+      if (!pmod)                       /* sender = moderator? */
+        strerr_die2x(100,FATAL,ERR_NOT_PUBLIC);
+      else {
+        if (!stralloc_copys(&mod,pmod)) die_nomem();
+        if (!stralloc_0(&mod)) die_nomem();
+        pmod = mod.s;          /* send to address in list not matching bait */
+      }
+    }
+  }
+
+  flagindexed = getconf_line(&line,"indexed",0,FATAL,dir);
+  flagarchived = getconf_line(&line,"archived",0,FATAL,dir);
+
+  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();
+  getconf_line(&mailinglist,"mailinglist",1,FATAL,dir);
+  getconf_line(&outhost,"outhost",1,FATAL,dir);
+  set_cpouthost(&outhost);
+
+    if (!stralloc_copys(&ddir,dir)) die_nomem();
+    if (!stralloc_cats(&ddir,"/digest")) die_nomem();
+    if (!stralloc_0(&ddir)) die_nomem();
+  if (act == AC_DIGEST) {
+    workdir = ddir.s;
+    if (!stralloc_cats(&outlocal,"-digest")) die_nomem();
+    if (getconf_line(&line,"chunk",0,FATAL,dir)) {
+      if (!stralloc_0(&line)) die_nomem();
+      (void) scan_ulong(line.s,&chunk);                /* same chunk as main list */
+      if (chunk == 0)                          /* limit range to 1-53 */
+       chunk = 1L;
+      else if (chunk > 52)
+       chunk = 52L;
+    } else {
+      chunk = 0L;                              /* maybe direct qmqp? */
+      if (!stralloc_copys(&line,QMQPSERVERS)) die_nomem();
+      if (!stralloc_cats(&line,"/0")) die_nomem();
+      if (!stralloc_0(&line)) die_nomem();
+      flagqmqp = getconf(&qmqpservers,line.s,0,FATAL,dir);
+    }
+  } else
+    workdir = dir;
+
+  if (!stralloc_copys(&edir,dir)) die_nomem(); /* not needed for -dig, but */
+  if (!stralloc_cats(&edir,"/allow")) die_nomem();     /* be safe */
+  if (!stralloc_0(&edir)) die_nomem();
+  set_cpoutlocal(&outlocal);           /* needed for copy */
+
+  if (flagqmqp) {
+    if (qmail_open(&qq,&qmqpservers) == -1)            /* open qmail */
+      strerr_die2sys(111,FATAL,ERR_QMAIL_QUEUE);
+  } else if (qmail_open(&qq,(stralloc *) 0) == -1)     /* open qmail */
+      strerr_die2sys(111,FATAL,ERR_QMAIL_QUEUE);
+
+  set_cpnum("");       /* default for <#n#> replacement */
+
+  if (act == AC_DIGEST) {
+/* -dig{.|-}'digestcode'[f] returns an rfc1153 digest                        */
+/* of messages from the archive. Messages                                    */
+/* dignum+1 through the last message received by the list are processed and  */
+/* dignum is updated to the last message processed. digissue is advanced.    */
+
+    if (!flagarchived)
+      strerr_die2x(100,FATAL,ERR_NOT_ARCHIVED);
+
+    get_num();                         /* max = last successful message */
+    to = max;
+    lockup();                  /* another digest could corrupt dignum */
+                               /* but will be saved only if flagdigrange==0 */
+    if(getconf_line(&num,"dignum",0,FATAL,dir)) {
+      if(!stralloc_0(&num)) die_nomem();
+      pos = scan_ulong(num.s,&prevmax);
+      if (num.s[pos] == ':') pos++;
+      pos += 1 + scan_ulong(num.s+pos,&cumsize);       /* last cumsize */
+      if (num.s[pos] == ':') pos++;
+      scan_ulong(num.s+pos,&digwhen);                  /* last reg dig */
+    } else {
+      prevmax = 0L;
+      cumsize = 0L;
+      digwhen = 0L;
+    }
+    mno = prevmax + 1L;
+    if(!max || mno > max)      /* if a digest-list is "sending" the request, */
+                               /* don't make noise: errors go to postmaster!*/
+      strerr_die2x(goodexit,FATAL,ERR_EMPTY_DIGEST);
+    szmsgnum[fmt_ulong(szmsgnum,mno)] = '\0';
+    set_cpnum(szmsgnum);       /* for copy */
+                               /* prepare subject to get entropy for tagmsg*/
+    if (!stralloc_cats(&subject," Digest ")) die_nomem();
+    if (!stralloc_catb(&subject,date,date822fmt(date,&dt)-1))
+          die_nomem();         /* skip trailing in date '\n' */
+    if (!stralloc_cats(&subject," Issue ")) die_nomem();
+    if (getconf_line(&num,"digissue",0,FATAL,dir)) {
+      if(!stralloc_0(&num)) die_nomem();
+      scan_ulong(num.s,&issue);
+      issue++;
+    } else {
+      issue = 1;
+    }
+    if (!stralloc_catb(&subject,strnum,fmt_ulong(strnum,issue)))
+      die_nomem();
+                                       /* use the subject as entropy */
+    if (!stralloc_copy(&line,&subject)) die_nomem();
+    if (!stralloc_0(&line)) die_nomem();
+
+    if (!stralloc_ready(&seed,HASHLEN+1)) die_nomem();
+    seed.len = HASHLEN + 1;
+    seed.s[HASHLEN] = '\0';
+    makehash(line.s,line.len,seed.s);
+    if (chunk) {                       /* only if slaves are used */
+      qmail_puts(&qq,"Ezauth: ");
+      qmail_put(&qq,seed.s,HASHLEN);
+      qmail_puts(&qq,"\n");
+    }
+
+    doheaders();
+    qmail_puts(&qq,"To: ");
+    if (!quote(&quoted,&listname)) die_nomem();
+    qmail_put(&qq,quoted.s,quoted.len);
+    qmail_puts(&qq,"@");
+    qmail_put(&qq,outhost.s,outhost.len);
+    qmail_puts(&qq,"\n");
+    if (flagindexed && (outformat != NATIVE))
+      idx_mkthreads(&msgtable,&subtable,&authtable,&datetable,
+       mno,to,max,flaglocked,FATAL);
+    else
+      idx_mklist(&msgtable,&subtable,&authtable,mno,to,FATAL);
+    digest(msgtable,subtable,authtable,mno,to,&subject,AC_DIGEST,outformat);
+
+    write_ulong(issue,0L,0L,"digissue","digissuen");
+    write_ulong(max,cumsizen, (unsigned long) when,"dignum","dignumn");
+  }
+
+  else if (act == AC_GET) {
+
+/* -get[-|\.][[num].num2] copies archive num-num2. num & num2 are adjusted   */
+/* to be > 0 and <= last message, to num2 >= num and to num2-num <= MAXGET.  */
+
+    if (!flagarchived)
+      strerr_die2x(100,FATAL,ERR_NOT_ARCHIVED);
+    zapnonsub(ACTION_GET);             /* restrict to subs if requested */
+    tosender();
+                               /* for rfc1153 */
+    if (!stralloc_cats(&subject," Digest of: ")) die_nomem();
+    if (!stralloc_cats(&subject,action)) die_nomem();
+
+    to = 0;
+    pos = str_len(ACTION_GET);
+    if (!case_starts(action,ACTION_GET))
+      pos = str_len(ALT_GET);
+    if (FORMATS[str_chr(FORMATS,action[pos])]) {
+       outformat = action[pos];
+       ++pos;
+    }
+                                       /* optional - or . after '-get' */
+    if (action[pos] == '-' || action[pos] == '.') pos++;
+    get_num();                         /* max = last successful message */
+                                       /* accept any separator. It may be  */
+                                       /* the terminal '\n', but then      */
+                                       /* scan will = 0 on the \0 so should*/
+                                       /* be safe                          */
+    if (!max)
+      strerr_die2x(100,FATAL,ERR_EMPTY_LIST);
+    szmsgnum[fmt_ulong(szmsgnum,max)] = '\0';
+    set_cpnum(szmsgnum);       /* for copy this is the latest message arch'd*/
+    doheaders();
+    if(action[pos += scan_ulong(action + pos,&u)])
+      scan_ulong(action + pos + 1, &to);
+    if (u == 0 && to == 0) {           /* default: messages since last */
+                                       /* digest, or last MAXGET if too many */
+      to= max;
+      u = dignum();
+      if (u == 0) {            /* no digest => last up to HISTGET msgs */
+       to = max;
+       if (max > HISTGET) u = max - HISTGET; else u = 1;
+      }
+      if (to - u >= MAXGET) u = to - MAXGET + 1;       /* max MAXGET */
+    } else if (u > max) {
+      if (to) {                        /* -get.999999_x returns 30 and msg since last*/
+       to = max;               /* digest 30*/
+        u = dignum();
+       if (u > HISTGET) u -= HISTGET; else u = 1;
+        if (to - u >= MAXGET) u = to - MAXGET + 1;
+      } else
+       u = max;
+    }
+    if (u == 0) u = 1;                 /* -get.5 => 1-5 */
+    if (to < u) to = u;                        /* -get23_2 => 23 */
+    if (to >= u + MAXGET) to = u + MAXGET - 1;
+                                       /* no more than MAXGET at a time */
+    if (to > max) to = max;
+    if (flagindexed && (outformat != NATIVE))  /* fake out threading */
+      idx_mkthreads(&msgtable,&subtable,&authtable,&datetable,
+       u,to,max,0,FATAL);
+    else
+      idx_mklist(&msgtable,&subtable,&authtable,u,to,FATAL);
+    digest(msgtable,subtable,authtable,u,to,&subject,AC_GET,outformat);
+  }
+
+  else if (act == AC_INDEX) {
+
+/* -index[f][#|-|\.][[num][.num2] Show subject index for messages num-num2 in*/
+/* sets of 100.                                                              */
+/* Default last 2 sets. num and num2 are made reasonable as for get. num2 is */
+/* limited to num+MAXINDEX to limit the amount of data sent.                 */
+
+    if (!flagindexed)
+      strerr_die2x(100,FATAL,ERR_NOT_INDEXED);
+    if (flagsub)
+    zapnonsub(ACTION_INDEX);   /* restrict to subs if requested */
+    to = 0;
+    pos = str_len(ACTION_INDEX);
+    if (!case_starts(action,ACTION_INDEX))
+      pos = str_len(ALT_INDEX);
+    if (FORMATS[str_chr(FORMATS,action[pos])]) {
+       outformat = action[pos];                /* ignored, but be nice ... */
+       ++pos;
+    }
+    get_num();                         /* max = last successful message */
+    if (!max)
+      strerr_die2x(100,FATAL,ERR_EMPTY_LIST);
+    szmsgnum[fmt_ulong(szmsgnum,max)] = '\0';
+    set_cpnum(szmsgnum);       /* for copy this is the latest message arch'd*/
+
+    doheaders();
+    tosender();
+    if (!stralloc_cats(&subject," Result of: ")) die_nomem();
+    if (!stralloc_cats(&subject,action)) die_nomem();
+    presub(1,1,&subject,AC_INDEX,outformat);
+
+    if (action[pos] == '-' || action[pos] == '.') pos++;
+    if(action[pos += scan_ulong(action + pos,&u)])
+      scan_ulong(action + pos + 1, &to);
+
+    if (u == 0 && to == 0) { to = max; u = max - 100; }
+    if (u <= 0) u = 1;
+    if (u > max) u = max;
+    if (to < u) to = u;
+    if (to > u + MAXINDEX) to = u+MAXINDEX;    /* max MAXINDEX index files */
+    if (to > max) to = max;
+    u /= 100;
+    to /= 100;
+    while (u <= to) {
+      if (!stralloc_copys(&fn,"archive/")) die_nomem();
+      if (!stralloc_catb(&fn,strnum,fmt_ulong(strnum,u))) die_nomem();
+      if (!stralloc_cats(&fn,"/index")) die_nomem();
+      if (!stralloc_0(&fn)) die_nomem();
+
+      if (u == max/100)        /* lock if last index file in archive */
+        lockup();
+
+      fd = open_read(fn.s);
+      if (fd == -1)
+        if (errno != error_noent)
+          strerr_die4sys(111,FATAL,ERR_OPEN,fn.s,": ");
+        else
+          code_qput(TXT_NOINDEX,str_len(TXT_NOINDEX));
+      else {
+        substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf));
+        for (;;) {
+          if (getln(&sstext,&line,&match,'\n') == -1)
+            strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
+          if (match) {
+            if (line.s[0] != '\t') {   /* subject line */
+              pos = byte_chr(line.s,line.len,' ');
+             if (pos && pos != line.len && line.s[pos - 1] == ':')
+                pos1 = pos + HASHLEN + 1;      /* after hash */
+              if (pos1 >= line.len) {  /* bad! */
+                pos = 0;
+                pos1 = 0;              /* output as is */
+              }
+              if (!stralloc_copyb(&line2,line.s,pos)) die_nomem();
+              if (!stralloc_catb(&line2,line.s+pos1,line.len-pos1)) die_nomem();
+            } else {
+             pos = byte_chr(line.s,line.len,';');
+             if (pos + HASHLEN + 1 < line.len && pos > 15 &&
+                               line.s[pos + 1] != ' ') {
+                 if (!stralloc_copyb(&line2,line.s,pos - 15)) die_nomem();
+                 pos++;
+                 if (!stralloc_catb(&line2,line.s + pos + HASHLEN,
+                       line.len - pos - HASHLEN)) die_nomem();
+             } else                    /* old format - no author hash */
+                if (!stralloc_copyb(&line2,line.s,line.len)) die_nomem();
+           }
+            code_qput(line2.s,line2.len);
+          } else
+            break;
+        }
+        close(fd);
+      }
+
+      if (u == max/100)        /* unlock if last index in archive file */
+        unlock();
+
+      u++;
+    }
+    normal_bottom(outformat);
+    postmsg(outformat);
+  }
+
+  else if (act == AC_THREAD) {
+
+/* -thread[f][-|.]num returns messages with subject matching message        */
+/* 'num' in the subject index. If 'num' is not in[1..last_message] an error */
+/* message is returned.                                                     */
+
+    if (!flagarchived)
+      strerr_die2x(100,FATAL,ERR_NOT_ARCHIVED);
+    if (!flagindexed)
+      strerr_die2x(100,FATAL,ERR_NOT_INDEXED);
+
+    zapnonsub(ACTION_THREAD);          /* restrict to subs if requested*/
+
+    get_num();                         /* max = last successful message */
+    if (!max)
+      strerr_die2x(100,FATAL,ERR_EMPTY_LIST);
+    szmsgnum[fmt_ulong(szmsgnum,max)] = '\0';
+    set_cpnum(szmsgnum);       /* for copy this is the latest message arch'd*/
+
+    doheaders();
+    tosender();
+                               /* for rfc1153 */
+    if (!stralloc_cats(&subject," Digest of: ")) die_nomem();
+    if (!stralloc_cats(&subject,action)) die_nomem();
+
+    to = 0;
+    pos = str_len(ACTION_THREAD);
+    if (!case_starts(action,ACTION_THREAD))
+      pos = str_len(ALT_THREAD);
+    if (FORMATS[str_chr(FORMATS,action[pos])]) {
+       outformat = action[pos];
+       ++pos;
+    }
+    if (action[pos] == '-' || action[pos] == '.') pos++;
+    if(action[pos += scan_ulong(action + pos,&u)])
+      scan_ulong(action + pos + 1, &to);
+
+    if(u == 0 || u > max) {
+      if (!stralloc_cats(&subject,"\n\n")) die_nomem();
+      qmail_puts(&qq,"Subject: ");
+      qmail_put(&qq,subject.s,subject.len);
+      copy(&qq,"text/get-bad",flagcd,FATAL);
+    } else {   /* limit range to at most u-THREAD_BEFORE to u+THREAD_AFTER */
+      if (u > THREAD_BEFORE)
+        from = u-THREAD_BEFORE;
+      else
+        from = 1L;
+      if (u + THREAD_AFTER > max) {
+        idx_mkthread(&msgtable,&subtable,&authtable,from,max,u,max,0,FATAL);
+        digest(msgtable,subtable,authtable,from,max,&subject,
+               AC_THREAD,outformat);
+      } else {
+        idx_mkthread(&msgtable,&subtable,&authtable,
+               from,u+THREAD_AFTER,u,max,0,FATAL);
+        digest(msgtable,subtable,authtable,from,u+THREAD_AFTER,
+                       &subject,AC_THREAD,outformat);
+      }
+    }
+  }
+
+  else
+       /* This happens if the initial check at the beginning of 'main'    */
+       /* matches something that isn't matched here. Conversely, just     */
+       /* adding an action here is not enough - it has to be added to the */
+       /* initial check as well.                                          */
+
+    strerr_die2x(100,FATAL,
+      "Program error: I'm supposed to deal with this but I didn't");
+
+  if (!stralloc_copy(&line,&outlocal)) die_nomem();
+  if (act == AC_DIGEST) {
+    if (chunk) {
+      if (!stralloc_cats(&line,"-return-g-")) die_nomem();
+    } else
+      if (!stralloc_cats(&line,"-return-")) die_nomem();
+    strnum[fmt_ulong(strnum,mno)] = '\0';
+    if (!stralloc_cats(&line,strnum)) die_nomem();
+    if (!stralloc_cats(&line,"-@")) die_nomem();
+
+    if (!stralloc_cat(&line,&outhost)) die_nomem();
+    if (!stralloc_cats(&line,"-@[]")) die_nomem();
+  } else {
+    if (!stralloc_cats(&line,"-return-@")) die_nomem();
+    if (!stralloc_cat(&line,&outhost)) die_nomem();
+  }
+  if (!stralloc_0(&line)) die_nomem();
+
+  qmail_from(&qq,line.s);
+  if (act == AC_DIGEST) {       /* Do recipients */
+    tagmsg(workdir,mno,seed.s,"d",hashout,qq.msgbytes,chunk,FATAL);
+    if (chunk) {
+      if (!stralloc_copys(&line,"T")) die_nomem();
+      if (!stralloc_cat(&line,&outlocal)) die_nomem();
+      if (!stralloc_cats(&line,"-s-d-")) die_nomem();
+      if (!stralloc_catb(&line,hashout,COOKIE)) die_nomem();
+      if (!stralloc_cats(&line,"-")) die_nomem();
+      if (!stralloc_cats(&line,strnum)) die_nomem();
+      if (!stralloc_cats(&line,"-")) die_nomem();
+      if (!stralloc_copys(&line2,"@")) die_nomem();
+      if (!stralloc_cat(&line2,&outhost)) die_nomem();
+      if (!stralloc_0(&line2)) die_nomem();
+      j = 0;
+      for (i = 0; i <= 52; i += chunk) {               /* To slaves */
+        qmail_put(&qq,line.s,line.len);
+        schar[0] = '0' + i / 10;
+        schar[1] = '0' + (i % 10);
+        qmail_put(&qq,schar,3);
+        j += (chunk - 1);
+        if (j > 52) j = 52;
+        schar[0] = '0' + j / 10;
+        schar[1] = '0' + (j % 10);
+        qmail_put(&qq,schar,2);
+        qmail_put(&qq,line2.s,line2.len);
+      }
+    } else
+      subs = putsubs(workdir,0L,52L,&subto,1,FATAL);
+  } else {                     /* if local is set, sender is checked */
+    if (pmod)
+      qmail_to(&qq,pmod);
+    else
+      qmail_to(&qq,sender);
+  }
+
+  if (*(err = qmail_close(&qq)) == '\0') {     /* Done. Skip rest. */
+    if (act == AC_DIGEST) {
+      if (chunk)
+       (void) logmsg(workdir,mno,0L,0L,2);
+      else
+        (void) logmsg(workdir,mno,0L,subs,4);
+    }
+    closesql();                        /* close db connection */
+    unlock();                  /* NOP if nothing locked */
+    strnum[fmt_ulong(strnum,qmail_qp(&qq))] = 0;
+    strerr_die2x(goodexit,"ezmlm-get: info: qp ",strnum);
+  } else {                     /* failed. Reset last msg & issue for digest */
+    if(act == AC_DIGEST) {
+      issue--;
+      write_ulong(issue,0L,0L,"digissue","digissuen");
+      write_ulong(prevmax,cumsize,(unsigned long) digwhen,"dignum","dignumn");
+    }
+    unlock();                  /* NOP if nothing locked */
+    strerr_die3x(111,FATAL,ERR_TMP_QMAIL_QUEUE,err + 1);
+  }
+}