Import ezmlm-idx 0.40
[ezmlm] / ezmlm-request.c
diff --git a/ezmlm-request.c b/ezmlm-request.c
new file mode 100644 (file)
index 0000000..661616d
--- /dev/null
@@ -0,0 +1,826 @@
+/*$Id: ezmlm-request.c,v 1.34 1999/08/18 01:50:04 lindberg Exp $*/
+/*$Name: ezmlm-idx-040 $*/
+#include "stralloc.h"
+#include "subfd.h"
+#include "strerr.h"
+#include "error.h"
+#include "qmail.h"
+#include "env.h"
+#include "sig.h"
+#include "open.h"
+#include "getln.h"
+#include "case.h"
+#include "str.h"
+#include "datetime.h"
+#include "date822fmt.h"
+#include "now.h"
+#include "quote.h"
+#include "readwrite.h"
+#include "exit.h"
+#include "substdio.h"
+#include "getconf.h"
+#include "constmap.h"
+#include "fmt.h"
+#include "sgetopt.h"
+#include "byte.h"
+#include "seek.h"
+#include "errtxt.h"
+#include "copy.h"
+#include "idx.h"
+
+#define FATAL "ezmlm-request: fatal: "
+#define INFO "ezmlm-request: info: "
+
+void die_usage()
+{
+  strerr_die1x(100,"ezmlm-request: usage: ezmlm-request [-f lists.cfg] dir");
+}
+
+void die_nomem()
+{
+  strerr_die2x(111,FATAL,ERR_NOMEM);
+}
+
+void die_badaddr()
+{
+  strerr_die2x(100,FATAL,ERR_BAD_ADDRESS);
+}
+
+char strnum[FMT_ULONG];
+
+void *psql = (void *) 0;
+
+char *userlocal = (char *) 0;
+char *userhost = (char *) 0;
+char *listlocal = (char *) 0;
+char *listhost = (char *) 0;
+char *cfname = (char *) 0;
+char *command = "help";
+stralloc line = {0};
+stralloc qline = {0};
+stralloc usr = {0};
+stralloc lhost = {0};
+stralloc subject = {0};
+stralloc inlocal = {0};
+stralloc outlocal = {0};
+stralloc listname = {0};
+stralloc hostname = {0};
+stralloc outhost = {0};
+stralloc headerremove = {0};
+stralloc mailinglist = {0};
+stralloc cmds = {0};
+stralloc from = {0};
+stralloc to = {0};
+stralloc charset = {0};
+char *boundary = "zxcaeedrqcrtrvthbdty";       /* cheap "rnd" MIME boundary */
+int flagcd = '\0';                             /* no encoding by default */
+
+struct constmap headerremovemap;
+struct constmap commandmap;
+int flaggotsub = 0;            /* Found a subject */
+       /* cmdstring has all commands seperated by '\'. cmdxlate maps each */
+       /* command alias to the basic command, which is used to construct  */
+       /* the command address (positive numbers) or handled by this       */
+       /* program (negative numbers). Note: Any command not matched is    */
+       /* used to make a command address, so ezmlm request can handle     */
+       /* ("transmit") user-added commands.                               */
+const char *cmdstring =
+               "system\\help\\"                        /* 1,2 */
+               "subscribe\\unsubscribe\\index\\"       /* 3,4,5 */
+               "info\\list\\query\\"                   /* 6,7,8 */
+               "sub\\unsub\\remove\\signoff\\"         /* 9,10,11,12 */
+               "lists\\which\\"                        /* 13,14 */
+               "ind\\rev\\review\\recipients\\"        /* 15,16,17,18 */
+               "who\\showdist\\"                       /* 19,20 */
+               "put\\set";                             /* 21,22 */
+
+       /* map aliases. -> 0 not recognized. -> 1 recognized will be made    */
+       /* help and arguments scrapped. < 0 handled locally. HELP without    */
+       /* args also handled locally */
+                       /* the last are not supported -> help */
+const int cmdxlate[] = { 0,1,2,3,4,5,6,7,8,3,4,4,4,-13,-14,5,7,7,7,7,7,
+                       1,1 };
+
+       /* If there are no arguments (listlocal = 0) then commands are mapped*/
+       /* through this. This way, help, list, query, ... can mean something */
+       /* here even though they have local funcions at the lists if used    */
+       /* with arguments. (Made same lengh as cmdxlate in case of bugs.)    */
+       /* Note: This is used ONLY for the global interface */
+const int noargsxlate[] = { 0,1,-2,3,4,5,-2,-13,-14,9,10,11,12,13,14,15,16,17,
+                       18,19,20,21,22 };
+
+       /* these need to be defined as the index of the corresponding      */
+       /* commands. They are handled by ezmlm-request. NOTE: Help is >0!  */
+#define EZREQ_LISTS 13
+#define EZREQ_WHICH 14
+#define EZREQ_HELP  2
+#define EZREQ_BAD 1
+
+substdio sstext;
+char textbuf[1024];
+datetime_sec when;
+struct datetime dt;
+char date[DATE822FMT];
+
+struct qmail qq;
+
+int qqwrite(fd,buf,len) int fd; char *buf; unsigned int len;
+{
+  qmail_put(&qq,buf,len);
+  return len;
+}
+
+char qqbuf[1];
+substdio ssqq = SUBSTDIO_FDBUF(qqwrite,-1,qqbuf,(int) sizeof(qqbuf));
+
+char inbuf[1024];
+substdio ssin = SUBSTDIO_FDBUF(read,0,inbuf,(int) sizeof(inbuf));
+substdio ssin2 = SUBSTDIO_FDBUF(read,0,inbuf,(int) sizeof(inbuf));
+
+substdio ssout;
+char outbuf[1];
+
+stralloc mydtline = {0};
+
+void transferenc()
+{
+       if (flagcd) {
+          qmail_puts(&qq,"\n--");
+          qmail_puts(&qq,boundary);
+          qmail_puts(&qq,"\nContent-Type: text/plain; charset=");
+          qmail_puts(&qq,charset.s);
+         qmail_puts(&qq,"\nContent-Transfer-Encoding: ");
+          if (flagcd == 'Q')
+            qmail_puts(&qq,"quoted-printable\n\n");
+          else
+           qmail_puts(&qq,"base64\n\n");
+        }
+}
+
+int 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);
+    }
+    return 0;          /* always succeeds */
+}
+
+/* Checks the argument. Only  us-ascii letters, numbers, ".+-_" are ok. */
+/* NOTE: For addresses this is more restrictive than rfc821/822.        */
+void checkarg(s)
+char *s;
+{
+  register char *cp;
+  register char ch;
+  cp = s;
+  if (!cp) return;                             /* undef is ok */
+  while ((ch = *cp++)) {
+    if (ch >= 'a' && ch <= 'z')
+        continue;                              /* lc letters */
+    if (ch >= '0' && ch <='9')                 /* digits */
+       continue;
+    if (ch == '.' || ch == '-' || ch == '_' || ch == '+')
+       continue;                               /* ok chars */
+    if (ch >= 'A' && ch <= 'Z') continue;      /* UC LETTERS */
+    strerr_die4x(100,ERR_NOT_CLEAN,": \"",s,"\"");
+  }
+  return;
+}
+
+/* parses line poited to by cp into sz:s as per:                        */
+/* 1. listlocal-command-userlocal=userhost@listhost                     */
+/* 2. command userlocal@userhost                                        */
+/* 3. command userlocal@userhost listlocal@listhost                     */
+/* 4. command listlocal@listhost                                        */
+/* 5. command listlocal[@listhost] userlocal@userhost                   */
+/* 6. which [userlocal@userhost]                                       */
+/* The first 3 are valid only if !cfname, i.e. -request operation and   */
+/* listlocal and listhost are always set to outlocal@outhost. Options   */
+/* 4-5 are for the global address (cfname is set). Here listhost is     */
+/* taken from the first list in *cfname matching listlocal, or set to   */
+/* outhost, if not specified. If specified, it's accepted if it matches */
+/* a list in *cfname and silently set to outhost otherwise. Pointers to */
+/* unspecified parts are set to NULL in this routine to be dealt with   */
+/* elsewhere. "Which" special argument order (6) is fixed elsewhere.    */
+/* If listhost is not given, "@outhost" is added. Absence of 'userhost' */
+/* is accepted to allow commands that take arguments that are not       */
+/* addresses (e.g. -get12-34).                                          */
+
+void parseline(cp)
+char *cp;
+
+{
+  register char *cp1, *cp2;
+  char *cp3;
+
+  cp1 = cp;
+  while (*cp1) {                               /* make tabs into spaces */
+    if (*cp1 == '\t') *cp1 = ' ';
+    ++cp1;
+  }
+                                       /* NOTE: outlocal has '\0' added! */
+  if (outlocal.len < str_len(cp) && cp[outlocal.len -1] == '-' &&
+       case_starts(cp,outlocal.s))      {      /* normal ezmlm cmd */
+    command = cp + outlocal.len;               /* after the '-' */
+    listlocal = outlocal.s;
+    listhost = outhost.s;
+    cp1 = command;
+    while (*cp1 && *cp1 != '-') ++cp1;         /* find next '-' */
+    if (*cp1) {
+      *cp1 = '\0';
+      userlocal = ++cp1;                       /* after '-' */
+      cp1 = cp1 + str_rchr(cp1,'@');           /* @ _or_ end */
+      *cp1 = '\0';                             /* last '=' in userlocal */
+      cp1 = userlocal + str_rchr(userlocal,'=');
+      if (*cp1) {                              /* found '=' */
+        *cp1 = '\0';                           /* zap */
+        userhost = cp1 + 1;                    /* char after '=' */
+      }
+    }
+  } else {                             /* '@' before ' ' means complete cmd */
+    if (str_chr(cp,'@') < str_chr(cp,' '))     /* addr where inlocal failed */
+       strerr_die2x(100,FATAL,ERR_REQ_LOCAL);
+                                               /* to match */
+    command = cp;
+    cp1 = cp + str_chr(cp,' ');
+    if (*cp1) {
+      *cp1++ = '\0';
+      while (*cp1 && *cp1 == ' ') ++cp1;       /* skip spaces */
+    }
+    cp2 = 0;
+    if (*cp1) {                                        /* argument */
+      cp2 = cp1 + str_chr(cp1,' ');
+      cp3 = cp2;
+      while (*cp2 && *cp2 == ' ') ++cp2;       /* skip spaces */
+      *cp3 = '\0';
+
+      if (!*cp2)
+        cp2 = 0;
+      else {
+        cp3 = cp2 + str_chr(cp2,' ');
+        *cp3 = '\0';
+      }
+    } else
+      cp1 = 0;
+
+    if (!cfname && !cp2) {     /* the single arg is user if we serve a */
+      cp2 = cp1;               /* list. It's list if we serve "domo@" */
+      cp1 = 0;
+    }
+    if (cp2) {
+      userlocal = cp2;
+      cp2 += str_chr(cp2,'@');
+      if (*cp2) {
+        *cp2++ = '\0';
+        userhost = cp2;
+      }
+    }
+    if (cp1) {
+      listlocal = cp1;
+      cp1 += str_chr(cp1,'@');
+      if (*cp1) {
+        *cp1++ = '\0';
+        listhost = cp1;
+      }
+    }
+  }
+  checkarg(command);                   /* better safe than sorry */
+  checkarg(userlocal); checkarg(userhost);
+  checkarg(listlocal); checkarg(listhost);
+}
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+  char *dir;
+  char *local;
+  char *action;
+  char *def;
+  char *sender;
+  char *psz;
+  char *err;
+  int cmdidx;
+  int flagsub;
+  int flagok;
+  int flagnosubject;
+  int match;
+  int flaginheader;
+  int flagbadfield;
+  int flagmultipart = 0;
+  int fd;
+  int opt;
+  unsigned int pos,pos1,len,last;
+
+  (void)umask(022);
+  sig_pipeignore();
+
+  while ((opt = getopt(argc,argv,"f:F:vV")) != opteof)
+    switch(opt) {
+      case 'F':
+      case 'f': if (optarg) cfname = optarg; break;
+      case 'v':
+      case 'V': strerr_die2x(0,"ezmlm-request version: ",EZIDX_VERSION);
+      default:
+       die_usage();
+    }
+
+  dir = argv[optind];
+  if (!dir) die_usage();
+
+  if (chdir(dir) == -1)
+    strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": ");
+
+       /* do minimum to identify request for this program in case */
+       /* it's invoked in line with e.g. ezmlm-manage */
+
+  def = env_get("DEFAULT");
+  if (def) {                   /* qmail>=1.02 */
+    action = def;
+  } else if (cfname) {         /* older qmail OR just list-mdomo */
+    local = env_get("LOCAL");
+    if (!local) strerr_die2x(100,FATAL,ERR_NOLOCAL);
+    len = str_len(local);
+    if (len >= 8 && !case_diffb(local + len - 8,8,"-return-")) {
+      action = "return-";      /* our bounce with qmail<1.02 */
+    } else
+      action = "";             /* list-mdomo-xxx won't work for older lists */
+  } else {                     /* older qmail versions */
+    local = env_get("LOCAL");
+    if (!local) strerr_die2x(100,FATAL,ERR_NOLOCAL);
+    getconf_line(&inlocal,"inlocal",1,FATAL,dir);
+    if (inlocal.len > str_len(local)) die_badaddr();
+    if (case_diffb(inlocal.s,inlocal.len,local)) die_badaddr();
+    action = local + inlocal.len;
+    if (*action)
+      if (*(action++) != '-') die_badaddr();   /* check anyway */
+  }
+       /* at this point action = "request" or "request-..." for std use; */
+       /* "" for majordomo@ */
+  if (!cfname) {                               /* expect request */
+    if (case_starts(action,ACTION_REQUEST))
+      action += str_len(ACTION_REQUEST);
+    else if (case_starts(action,ALT_REQUEST))
+      action += str_len(ALT_REQUEST);
+    else
+      _exit(0);                                        /* not for us */
+  }
+  getconf_line(&outlocal,"outlocal",1,FATAL,dir);
+  getconf_line(&outhost,"outhost",1,FATAL,dir);
+
+  if (!stralloc_copy(&listname,&outlocal)) die_nomem();
+  if (!stralloc_copy(&hostname,&outhost)) die_nomem();
+  if (!stralloc_0(&outlocal)) die_nomem();
+  if (!stralloc_0(&outhost)) die_nomem();
+
+  sender = env_get("SENDER");
+  if (!sender) strerr_die2x(99,INFO,ERR_NOSENDER);
+  if (!*sender)
+    strerr_die2x(99,INFO,ERR_BOUNCE);
+  if (!sender[str_chr(sender,'@')])
+    strerr_die2x(99,INFO,ERR_ANONYMOUS);
+  if (str_equal(sender,"#@[]"))
+    strerr_die2x(99,INFO,ERR_BOUNCE);
+
+  getconf(&headerremove,"headerremove",1,FATAL,dir);
+  constmap_init(&headerremovemap,headerremove.s,headerremove.len,0);
+
+  if (!stralloc_copys(&mydtline,
+       "Delivered-To: request processor for ")) die_nomem();
+  if (!stralloc_cats(&mydtline,outlocal.s)) die_nomem();
+  if (!stralloc_cats(&mydtline,"@")) die_nomem();
+  if (!stralloc_cats(&mydtline,outhost.s)) die_nomem();
+  if (!stralloc_cats(&mydtline,"\n")) die_nomem();
+
+  flagnosubject = 1;
+  if (action[0]) {     /* mainly to allow ezmlm-lists or ezmlm-which with */
+    flagnosubject = 0; /* a command address rather than a complete msg */
+    command = action;
+    if (str_start(action,"return"))            /* kill bounces */
+      strerr_die2x(0,INFO,ERR_BOUNCE);
+    pos = 1 + str_chr(action + 1,'-');
+    if (action[pos]) {                         /* start of target */
+      action[pos] = '\0';
+      userlocal = action + pos + 1;
+      pos = str_rchr(userlocal,'=');           /* the "pseudo-@" */
+      if (userlocal[pos]) {
+       userlocal[pos] = '\0';
+        userhost = userlocal + pos + 1;
+      }
+    }
+  } else {
+    for (;;) {                                 /* Get Subject: */
+      if (getln(&ssin,&line,&match,'\n') == -1)
+        strerr_die2sys(111,FATAL,ERR_READ_INPUT);
+        if (line.len == 1)
+        break;
+        if ((line.s[0] != ' ') && (line.s[0] != '\t')) {
+          flagsub = 0;
+
+          if (case_startb(line.s,line.len,"mailing-list:"))
+            strerr_die2x(100,FATAL,ERR_MAILING_LIST);
+          else if (case_startb(line.s,line.len,"Subject:")) {
+            flaggotsub = 1;
+            pos = 8;
+            last = line.len - 2;               /* skip terminal '\n' */
+            while (line.s[last] == ' ' || line.s[last] == '\t') --last;
+            while (pos <= last &&
+               (line.s[pos] == ' ' || line.s[pos] == '\t')) ++pos;
+            if (!stralloc_copyb(&subject,line.s+pos,last-pos+1)) die_nomem();
+          } else if (case_startb(line.s,line.len,"content-type:")) {
+           pos = 13; last = line.len - 2;      /* not cont-line - ok */
+            while (pos <= last &&
+               (line.s[pos] == ' ' || line.s[pos] == '\t')) ++pos;
+           if (case_startb(line.s+pos,line.len - pos,"multipart/"))
+             flagmultipart = 1;
+         } else if (line.len == mydtline.len)
+            if (!byte_diff(line.s,line.len,mydtline.s))
+               strerr_die2x(100,FATAL,ERR_LOOPING);
+        } else if (flagsub) {  /* Continuation line */
+          pos = 1;
+          len = line.len - 2;  /* skip terminal '\n' */
+          while (line.s[len] == ' ' || line.s[len] == '\t') --len;
+          while (pos < len &&
+               (line.s[pos] == ' ' || line.s[pos] == '\t')) ++pos;
+          if (!stralloc_append(&subject," ")) die_nomem();
+          if (!stralloc_copy(&subject,line.s+pos,len-pos+1)) die_nomem();
+      }
+      if (!match)
+        break;
+    }
+    if (!cfname) {              /* listserv@/majordomo@ ignore */
+      register char ch;
+      if (!stralloc_0(&subject)) die_nomem();
+      ch = *subject.s;         /* valid commands/list names start w letter */
+      if ((ch <= 'z' && ch >= 'a') || (ch <= 'Z' && ch >= 'A')) {
+        parseline(subject.s);
+        flagnosubject = 0;
+      }
+    }
+    if (cfname || flagnosubject) {
+      for (;;) {                                       /* parse body */
+        if (getln(&ssin,&line,&match,'\n') == -1)
+        strerr_die2sys(111,FATAL,ERR_READ_INPUT);
+        if (!match) break;
+       if (line.len == 1 && flagmultipart != 2) continue;
+               /* lazy MIME cludge assumes first '--...' is start border */
+               /* which is virtually always true */
+       if (flagmultipart == 1) {               /* skip to first border */
+         if (*line.s != '-' || line.s[1] != '-') continue;
+         flagmultipart = 2;
+         continue;
+       } else if (flagmultipart == 2) {        /* skip content info */
+         if (line.len != 1) continue;
+         flagmultipart = 3;                    /* may be part within part */
+         continue;                             /* and blank line */
+       } else if (flagmultipart == 3) {
+         if (*line.s == '-' && line.s[1] == '-') {
+           flagmultipart = 2;                  /* part within part */
+           continue;
+         }
+       }
+        {
+         register char ch;
+         ch = *line.s;
+        if (line.len == 1 ||
+          !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')))
+          continue;                            /* skip if not letter pos 1 */
+        }
+                       /* Here we have a body line with something */
+        if (!stralloc_copy(&subject,&line)) die_nomem();       /* save it */
+        subject.s[subject.len-1] = '\0';
+        parseline(subject.s);
+        break;
+      }
+    }
+  }
+       /* Do command substitution */
+  if (!stralloc_copys(&cmds,cmdstring)) die_nomem();
+  if (!stralloc_0(&cmds)) die_nomem();
+  psz = cmds.s;
+  while (*psz) {
+    if (*psz == '\\') *psz = '\0';
+    ++psz;
+  }
+  if (!constmap_init(&commandmap,cmds.s,cmds.len,0)) die_nomem();
+  cmdidx = cmdxlate[constmap_index(&commandmap,command,str_len(command))];
+  if (cmdidx == EZREQ_BAD) {   /* recognized, but not supported -> help */
+    listlocal = 0;             /* needed 'cause arguments are who-knows-what */
+    listhost = 0;
+    userlocal = 0;
+    userhost = 0;
+    cmdidx = EZREQ_HELP;
+  }
+  if (cfname && !listlocal && !userlocal && cmdidx > 0)
+    cmdidx = noargsxlate[cmdidx];       /* some done differently if no args */
+
+       /* =0 not found. This is treated as a list command! */
+  if (cmdidx < 0 && !cfname) {
+    cmdidx = EZREQ_HELP;
+  }
+  if (qmail_open(&qq,(stralloc *) 0) == -1)
+    strerr_die2sys(111,FATAL,ERR_QMAIL_QUEUE);
+
+  if (cmdidx >= 0) {
+       /* Things handled elsewhere. We do want to handle a simple HELP */
+       /* without arguments for e.g. majordomo@ from our own help file */
+
+    if (!stralloc_copys(&from,sender)) die_nomem();
+    if (!stralloc_0(&from)) die_nomem();
+    if (!listlocal) {
+      if (cfname)
+        strerr_die1x(100,ERR_REQ_LISTNAME);
+      else
+       listlocal = outlocal.s; /* This is at the -request address */
+    }
+       /* if !cfname listhost is made outhost. If cfname, listhost=outhost */
+       /* is ok. listhost=0 => first match in config. Other listhost is ok */
+       /* only if match is found. Otherwise it's set to outhost. */
+
+    if (!cfname || (listhost && !case_diffs(listhost,outhost.s)))
+      listhost = outhost.s;
+    else {                      /* Check listhost against config file */
+      pos = str_len(listlocal);
+      fd = open_read(cfname);
+      if (fd == -1)
+        strerr_die4sys(111,FATAL,ERR_OPEN,cfname,": ");
+      substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf));
+      flagok = 0;                      /* got listhost match */
+      for (;;) {
+        if (getln(&sstext,&line,&match,'\n') == -1)
+          strerr_die3sys(111,FATAL,ERR_READ,cfname);
+        if (!match)
+          break;
+        if (line.len <= 1 || line.s[0] == '#')
+          continue;
+        if ((pos < line.len) && (line.s[pos] == '@') &&
+               !byte_diff(line.s,pos,listlocal)) {
+          last = byte_chr(line.s,line.len,':');
+          if (!stralloc_copyb(&lhost,line.s+pos+1,last-pos-1)) die_nomem();
+          if (!stralloc_0(&lhost)) die_nomem();
+          if (listhost) {
+            if (!case_diffs(listhost,lhost.s)) {
+              flagok = 1;
+              break;                   /* host did match */
+            } else
+              continue;                        /* host didn't match */
+          } else {                     /* none given - grab first */
+            listhost = lhost.s;
+            flagok = 1;
+            break;
+          }
+        }
+      }
+      if (!flagok)
+        listhost = outhost.s;
+      close(fd);
+    }
+    if (!listhost)
+      listhost = outhost.s;
+    if (!userlocal) {
+      if (!stralloc_copys(&usr,sender)) die_nomem();
+      if (!stralloc_0(&usr)) die_nomem();
+      userlocal = usr.s;
+      userhost = usr.s + byte_rchr(usr.s,usr.len-1,'@');
+      if (!*userhost)
+        userhost = 0;
+      else {
+        *userhost = '\0';
+        ++userhost;
+      }
+    }
+
+    if (!stralloc_copys(&to,listlocal)) die_nomem();
+    if (!stralloc_cats(&to,"-")) die_nomem();
+    if (cmdidx) {                      /* recognized - substitute */
+      if (!stralloc_cats(&to,constmap_get(&commandmap,cmdidx)))
+                die_nomem();
+    } else                             /* not recognized - use as is */
+      if (!stralloc_cats(&to,command)) die_nomem();
+
+    if (!stralloc_cats(&to,"-")) die_nomem();
+    if (!stralloc_cats(&to,userlocal)) die_nomem();
+    if (userhost) {                    /* doesn't exist for e.g. -get */
+      if (!stralloc_cats(&to,"=")) die_nomem();
+      if (!stralloc_cats(&to,userhost)) die_nomem();
+    }
+    if (!stralloc_cats(&to,"@")) die_nomem();
+    if (!stralloc_cats(&to,listhost)) die_nomem();
+    if (!stralloc_0(&to)) die_nomem();
+
+    qmail_put(&qq,mydtline.s,mydtline.len);
+
+    flaginheader = 1;
+    flagbadfield = 0;
+
+    if (seek_begin(0) == -1)
+      strerr_die2sys(111,FATAL,ERR_SEEK_INPUT);
+    substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf));
+
+    for (;;) {
+      if (getln(&ssin,&line,&match,'\n') == -1)
+        strerr_die2sys(111,FATAL,ERR_READ_INPUT);
+
+      if (flaginheader && match) {
+        if (line.len == 1)
+          flaginheader = 0;
+        if ((line.s[0] != ' ') && (line.s[0] != '\t')) {
+          flagbadfield = 0;
+          if (constmap(&headerremovemap,line.s,byte_chr(line.s,line.len,':')))
+           flagbadfield = 1;
+        }
+      }
+      if (!(flaginheader && flagbadfield))
+        qmail_put(&qq,line.s,line.len);
+      if (!match)
+        break;
+    }
+  } else {                             /* commands we deal with */
+    cmdidx = - cmdidx;                 /* now positive */
+    if (cmdidx == EZREQ_WHICH) {       /* arg is user, not list */
+      userlocal = listlocal; listlocal = 0;
+      userhost = listhost; listhost = 0;
+    }
+    if (!stralloc_copys(&from,outlocal.s)) die_nomem();
+    if (!stralloc_cats(&from,"-return-@")) die_nomem();
+    if (!stralloc_cats(&from,outhost.s)) die_nomem();
+    if (!stralloc_0(&from)) die_nomem();
+
+    if (userlocal) {
+      if (!stralloc_copys(&to,userlocal)) die_nomem();
+      if (!stralloc_cats(&to,"@")) die_nomem();
+      if (userhost) {
+        if (!stralloc_cats(&to,userhost)) die_nomem();
+       } else {
+        if (!stralloc_cats(&to,outhost.s)) die_nomem();
+      }
+    } else
+      if (!stralloc_copys(&to,sender)) die_nomem();
+    if (!stralloc_0(&to)) die_nomem();
+
+       /* now we need to look for charset and set flagcd appropriately */
+
+    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();
+    set_cpoutlocal(&listname);         /* necessary in case there are <#l#> */
+    set_cpouthost(&hostname);          /* necessary in case there are <#h#> */
+                                       /* we don't want to be send to a list*/
+    qmail_puts(&qq,"Mailing-List: ezmlm-request");
+    if (getconf(&line,"listid",0,FATAL)) {
+      qmail_puts(&qq,"List-ID: ");
+      qmail_put(&qq,line.s,line.len);
+    }
+    qmail_puts(&qq,"\nDate: ");
+    when = now();
+    datetime_tai(&dt,when);
+    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_cats(&line,outhost.s)) die_nomem();
+    if (!stralloc_0(&line)) die_nomem();
+    qmail_puts(&qq,line.s);
+    qmail_puts(&qq,">\nFrom: ");
+    if (!quote2(&line,outlocal.s)) die_nomem();
+    qmail_put(&qq,line.s,line.len);
+    if (cmdidx == EZREQ_HELP)
+      qmail_puts(&qq,"-return-@");
+    else
+      qmail_puts(&qq,"-help@");
+    qmail_puts(&qq,outhost.s);
+    qmail_puts(&qq,"\n");
+    qmail_put(&qq,mydtline.s,mydtline.len);
+    qmail_puts(&qq,"To: ");
+    if (!quote2(&line,to.s)) die_nomem();
+    qmail_put(&qq,line.s,line.len);
+    qmail_puts(&qq,"\n");
+    qmail_puts(&qq,"MIME-Version: 1.0\n");
+    if (flagcd) {
+      qmail_puts(&qq,"Content-Type: multipart/mixed; charset=");
+      qmail_puts(&qq,charset.s);
+      qmail_puts(&qq,";\n\tboundary=");
+      qmail_puts(&qq,boundary);
+    } else {
+      qmail_puts(&qq,"Content-type: text/plain; charset=");
+      qmail_puts(&qq,charset.s);
+    }
+    qmail_puts(&qq,"\nSubject: ");
+    if (!quote2(&line,outlocal.s)) die_nomem();
+    qmail_put(&qq,line.s,line.len);
+    qmail_puts(&qq,TXT_RESULTS);
+    transferenc();
+    copy(&qq,"text/top",flagcd,FATAL);
+   if (cmdidx == EZREQ_LISTS || cmdidx == EZREQ_WHICH) {
+      switch (cmdidx) {
+        case EZREQ_LISTS:
+          code_qput("LISTS:",6);
+          break;
+        case EZREQ_WHICH:
+          code_qput("WHICH (",7);
+          code_qput(to.s,to.len - 1);
+          code_qput("):\n\n",4);
+          break;
+        default: break;
+      }
+      fd = open_read(cfname);
+      if (fd == -1)
+        strerr_die4sys(111,FATAL,ERR_OPEN,cfname,": ");
+      substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf));
+      for (;;) {
+        if (getln(&sstext,&line,&match,'\n') == -1)
+          strerr_die3sys(111,FATAL,ERR_READ,cfname);
+        if (!match)
+          break;
+        if (line.len <= 1 || line.s[0] == '#')
+          continue;
+        if (!stralloc_0(&line)) die_nomem();
+        pos = str_chr(line.s,':');
+        if (!line.s[pos])
+          break;
+        line.s[pos] = '\0';
+        ++pos;
+        pos1 = pos + str_chr(line.s + pos,':');
+        if (line.s[pos1]) {
+          line.s[pos1] = '\0';
+          ++pos1;
+        } else
+          pos1 = 0;
+
+        switch (cmdidx) {
+          case EZREQ_LISTS:
+            code_qput("\n\n\t",3);
+            code_qput(line.s,pos-1);
+            code_qput("\n",1);
+            if (pos1) {
+              code_qput(line.s+pos1,line.len-2-pos1);
+            }
+            break;
+          case EZREQ_WHICH:
+            if (issub(line.s+pos,to.s,(char *) 0,FATAL)) {
+              code_qput(line.s,pos-1);
+              code_qput("\n",1);
+            }
+           closesql();         /* likely different dbs for different lists */
+            break;
+        }
+      }
+      code_qput("\n",1);
+      close(fd);
+    } else
+      copy(&qq,"text/help",flagcd,FATAL);
+
+    copy(&qq,"text/bottom",flagcd,FATAL);
+    if (flagcd) {
+      if (flagcd == 'B') {
+        encodeB("",0,&line,2,FATAL);   /* flush */
+        qmail_put(&qq,line.s,line.len);
+      }
+       qmail_puts(&qq,"\n--");
+       qmail_puts(&qq,boundary);
+       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(&line,sender)) die_nomem();
+    qmail_put(&qq,line.s,line.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);
+    if (flagcd) {
+      qmail_puts(&qq,"\n--");
+      qmail_puts(&qq,boundary);
+      qmail_puts(&qq,"--\n");
+    }
+  }
+  qmail_from(&qq,from.s);
+  qmail_to(&qq,to.s);
+  if (*(err = qmail_close(&qq)) != '\0')
+      strerr_die3x(111,FATAL,ERR_TMP_QMAIL_QUEUE,err + 1);
+
+  strnum[fmt_ulong(strnum,qmail_qp(&qq))] = 0;
+  strerr_die3x(99,INFO, "qp ",strnum);
+}