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