chiark / gitweb /
Debianization and various other fixes.
[ezmlm] / ezmlm-request.c
1 /*$Id: ezmlm-request.c,v 1.34 1999/08/18 01:50:04 lindberg Exp $*/
2 /*$Name: ezmlm-idx-040 $*/
3 #include "stralloc.h"
4 #include "subfd.h"
5 #include "strerr.h"
6 #include "error.h"
7 #include "qmail.h"
8 #include "env.h"
9 #include "sig.h"
10 #include "open.h"
11 #include "getln.h"
12 #include "case.h"
13 #include "str.h"
14 #include "datetime.h"
15 #include "date822fmt.h"
16 #include "now.h"
17 #include "quote.h"
18 #include "readwrite.h"
19 #include "exit.h"
20 #include "substdio.h"
21 #include "getconf.h"
22 #include "constmap.h"
23 #include "fmt.h"
24 #include "sgetopt.h"
25 #include "byte.h"
26 #include "seek.h"
27 #include "errtxt.h"
28 #include "copy.h"
29 #include "idx.h"
30
31 #define FATAL "ezmlm-request: fatal: "
32 #define INFO "ezmlm-request: info: "
33
34 void die_usage()
35 {
36   strerr_die1x(100,"ezmlm-request: usage: ezmlm-request [-f lists.cfg] dir");
37 }
38
39 void die_nomem()
40 {
41   strerr_die2x(111,FATAL,ERR_NOMEM);
42 }
43
44 void die_badaddr()
45 {
46   strerr_die2x(100,FATAL,ERR_BAD_ADDRESS);
47 }
48
49 char strnum[FMT_ULONG];
50
51 void *psql = (void *) 0;
52
53 char *userlocal = (char *) 0;
54 char *userhost = (char *) 0;
55 char *listlocal = (char *) 0;
56 char *listhost = (char *) 0;
57 char *cfname = (char *) 0;
58 char *command = "help";
59 stralloc line = {0};
60 stralloc qline = {0};
61 stralloc usr = {0};
62 stralloc lhost = {0};
63 stralloc subject = {0};
64 stralloc inlocal = {0};
65 stralloc outlocal = {0};
66 stralloc listname = {0};
67 stralloc hostname = {0};
68 stralloc outhost = {0};
69 stralloc headerremove = {0};
70 stralloc mailinglist = {0};
71 stralloc cmds = {0};
72 stralloc from = {0};
73 stralloc to = {0};
74 stralloc charset = {0};
75 char *boundary = "zxcaeedrqcrtrvthbdty";        /* cheap "rnd" MIME boundary */
76 int flagcd = '\0';                              /* no encoding by default */
77
78 struct constmap headerremovemap;
79 struct constmap commandmap;
80 int flaggotsub = 0;             /* Found a subject */
81         /* cmdstring has all commands seperated by '\'. cmdxlate maps each */
82         /* command alias to the basic command, which is used to construct  */
83         /* the command address (positive numbers) or handled by this       */
84         /* program (negative numbers). Note: Any command not matched is    */
85         /* used to make a command address, so ezmlm request can handle     */
86         /* ("transmit") user-added commands.                               */
87 const char *cmdstring =
88                 "system\\help\\"                        /* 1,2 */
89                 "subscribe\\unsubscribe\\index\\"       /* 3,4,5 */
90                 "info\\list\\query\\"                   /* 6,7,8 */
91                 "sub\\unsub\\remove\\signoff\\"         /* 9,10,11,12 */
92                 "lists\\which\\"                        /* 13,14 */
93                 "ind\\rev\\review\\recipients\\"        /* 15,16,17,18 */
94                 "who\\showdist\\"                       /* 19,20 */
95                 "put\\set";                             /* 21,22 */
96
97         /* map aliases. -> 0 not recognized. -> 1 recognized will be made    */
98         /* help and arguments scrapped. < 0 handled locally. HELP without    */
99         /* args also handled locally */
100                         /* the last are not supported -> help */
101 const int cmdxlate[] = { 0,1,2,3,4,5,6,7,8,3,4,4,4,-13,-14,5,7,7,7,7,7,
102                         1,1 };
103
104         /* If there are no arguments (listlocal = 0) then commands are mapped*/
105         /* through this. This way, help, list, query, ... can mean something */
106         /* here even though they have local funcions at the lists if used    */
107         /* with arguments. (Made same lengh as cmdxlate in case of bugs.)    */
108         /* Note: This is used ONLY for the global interface */
109 const int noargsxlate[] = { 0,1,-2,3,4,5,-2,-13,-14,9,10,11,12,13,14,15,16,17,
110                         18,19,20,21,22 };
111
112         /* these need to be defined as the index of the corresponding      */
113         /* commands. They are handled by ezmlm-request. NOTE: Help is >0!  */
114 #define EZREQ_LISTS 13
115 #define EZREQ_WHICH 14
116 #define EZREQ_HELP  2
117 #define EZREQ_BAD 1
118
119 substdio sstext;
120 char textbuf[1024];
121 datetime_sec when;
122 struct datetime dt;
123 char date[DATE822FMT];
124
125 struct qmail qq;
126
127 int qqwrite(fd,buf,len) int fd; char *buf; unsigned int len;
128 {
129   qmail_put(&qq,buf,len);
130   return len;
131 }
132
133 char qqbuf[1];
134 substdio ssqq = SUBSTDIO_FDBUF(qqwrite,-1,qqbuf,(int) sizeof(qqbuf));
135
136 char inbuf[1024];
137 substdio ssin = SUBSTDIO_FDBUF(read,0,inbuf,(int) sizeof(inbuf));
138 substdio ssin2 = SUBSTDIO_FDBUF(read,0,inbuf,(int) sizeof(inbuf));
139
140 substdio ssout;
141 char outbuf[1];
142
143 stralloc mydtline = {0};
144
145 void transferenc()
146 {
147         if (flagcd) {
148           qmail_puts(&qq,"\n--");
149           qmail_puts(&qq,boundary);
150           qmail_puts(&qq,"\nContent-Type: text/plain; charset=");
151           qmail_puts(&qq,charset.s);
152           qmail_puts(&qq,"\nContent-Transfer-Encoding: ");
153           if (flagcd == 'Q')
154             qmail_puts(&qq,"quoted-printable\n\n");
155           else
156             qmail_puts(&qq,"base64\n\n");
157         }
158 }
159
160 int code_qput(s,n)
161 char *s;
162 unsigned int n;
163 {
164     if (!flagcd)
165       qmail_put(&qq,s,n);
166     else {
167       if (flagcd == 'B')
168         encodeB(s,n,&qline,0,FATAL);
169       else
170         encodeQ(s,n,&qline,FATAL);
171       qmail_put(&qq,qline.s,qline.len);
172     }
173     return 0;           /* always succeeds */
174 }
175
176 /* Checks the argument. Only  us-ascii letters, numbers, ".+-_" are ok. */
177 /* NOTE: For addresses this is more restrictive than rfc821/822.        */
178 void checkarg(s)
179 char *s;
180 {
181   register char *cp;
182   register char ch;
183   cp = s;
184   if (!cp) return;                              /* undef is ok */
185   while ((ch = *cp++)) {
186     if (ch >= 'a' && ch <= 'z')
187          continue;                              /* lc letters */
188     if (ch >= '0' && ch <='9')                  /* digits */
189         continue;
190     if (ch == '.' || ch == '-' || ch == '_' || ch == '+')
191         continue;                               /* ok chars */
192     if (ch >= 'A' && ch <= 'Z') continue;       /* UC LETTERS */
193     strerr_die4x(100,ERR_NOT_CLEAN,": \"",s,"\"");
194   }
195   return;
196 }
197
198 /* parses line poited to by cp into sz:s as per:                        */
199 /* 1. listlocal-command-userlocal=userhost@listhost                     */
200 /* 2. command userlocal@userhost                                        */
201 /* 3. command userlocal@userhost listlocal@listhost                     */
202 /* 4. command listlocal@listhost                                        */
203 /* 5. command listlocal[@listhost] userlocal@userhost                   */
204 /* 6. which [userlocal@userhost]                                        */
205 /* The first 3 are valid only if !cfname, i.e. -request operation and   */
206 /* listlocal and listhost are always set to outlocal@outhost. Options   */
207 /* 4-5 are for the global address (cfname is set). Here listhost is     */
208 /* taken from the first list in *cfname matching listlocal, or set to   */
209 /* outhost, if not specified. If specified, it's accepted if it matches */
210 /* a list in *cfname and silently set to outhost otherwise. Pointers to */
211 /* unspecified parts are set to NULL in this routine to be dealt with   */
212 /* elsewhere. "Which" special argument order (6) is fixed elsewhere.    */
213 /* If listhost is not given, "@outhost" is added. Absence of 'userhost' */
214 /* is accepted to allow commands that take arguments that are not       */
215 /* addresses (e.g. -get12-34).                                          */
216
217 void parseline(cp)
218 char *cp;
219
220 {
221   register char *cp1, *cp2;
222   char *cp3;
223
224   cp1 = cp;
225   while (*cp1) {                                /* make tabs into spaces */
226     if (*cp1 == '\t') *cp1 = ' ';
227     ++cp1;
228   }
229                                         /* NOTE: outlocal has '\0' added! */
230   if (outlocal.len < str_len(cp) && cp[outlocal.len -1] == '-' &&
231         case_starts(cp,outlocal.s))      {      /* normal ezmlm cmd */
232     command = cp + outlocal.len;                /* after the '-' */
233     listlocal = outlocal.s;
234     listhost = outhost.s;
235     cp1 = command;
236     while (*cp1 && *cp1 != '-') ++cp1;          /* find next '-' */
237     if (*cp1) {
238       *cp1 = '\0';
239       userlocal = ++cp1;                        /* after '-' */
240       cp1 = cp1 + str_rchr(cp1,'@');            /* @ _or_ end */
241       *cp1 = '\0';                              /* last '=' in userlocal */
242       cp1 = userlocal + str_rchr(userlocal,'=');
243       if (*cp1) {                               /* found '=' */
244         *cp1 = '\0';                            /* zap */
245         userhost = cp1 + 1;                     /* char after '=' */
246       }
247     }
248   } else {                              /* '@' before ' ' means complete cmd */
249     if (str_chr(cp,'@') < str_chr(cp,' '))      /* addr where inlocal failed */
250         strerr_die2x(100,FATAL,ERR_REQ_LOCAL);
251                                                 /* to match */
252     command = cp;
253     cp1 = cp + str_chr(cp,' ');
254     if (*cp1) {
255       *cp1++ = '\0';
256       while (*cp1 && *cp1 == ' ') ++cp1;        /* skip spaces */
257     }
258     cp2 = 0;
259     if (*cp1) {                                 /* argument */
260       cp2 = cp1 + str_chr(cp1,' ');
261       cp3 = cp2;
262       while (*cp2 && *cp2 == ' ') ++cp2;        /* skip spaces */
263       *cp3 = '\0';
264
265       if (!*cp2)
266         cp2 = 0;
267       else {
268         cp3 = cp2 + str_chr(cp2,' ');
269         *cp3 = '\0';
270       }
271     } else
272       cp1 = 0;
273
274     if (!cfname && !cp2) {      /* the single arg is user if we serve a */
275       cp2 = cp1;                /* list. It's list if we serve "domo@" */
276       cp1 = 0;
277     }
278     if (cp2) {
279       userlocal = cp2;
280       cp2 += str_chr(cp2,'@');
281       if (*cp2) {
282         *cp2++ = '\0';
283         userhost = cp2;
284       }
285     }
286     if (cp1) {
287       listlocal = cp1;
288       cp1 += str_chr(cp1,'@');
289       if (*cp1) {
290         *cp1++ = '\0';
291         listhost = cp1;
292       }
293     }
294   }
295   checkarg(command);                    /* better safe than sorry */
296   checkarg(userlocal); checkarg(userhost);
297   checkarg(listlocal); checkarg(listhost);
298 }
299
300 void main(argc,argv)
301 int argc;
302 char **argv;
303 {
304   char *dir;
305   char *local;
306   char *action;
307   char *def;
308   char *sender;
309   char *psz;
310   char *err;
311   int cmdidx;
312   int flagsub;
313   int flagok;
314   int flagnosubject;
315   int match;
316   int flaginheader;
317   int flagbadfield;
318   int flagmultipart = 0;
319   int fd;
320   int opt;
321   unsigned int pos,pos1,len,last;
322
323   (void)umask(022);
324   sig_pipeignore();
325
326   while ((opt = getopt(argc,argv,"f:F:vV")) != opteof)
327     switch(opt) {
328       case 'F':
329       case 'f': if (optarg) cfname = optarg; break;
330       case 'v':
331       case 'V': strerr_die2x(0,"ezmlm-request version: ",EZIDX_VERSION);
332       default:
333         die_usage();
334     }
335
336   dir = argv[optind];
337   if (!dir) die_usage();
338
339   if (chdir(dir) == -1)
340     strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": ");
341
342         /* do minimum to identify request for this program in case */
343         /* it's invoked in line with e.g. ezmlm-manage */
344
345   def = env_get("DEFAULT");
346   if (def) {                    /* qmail>=1.02 */
347     action = def;
348   } else if (cfname) {          /* older qmail OR just list-mdomo */
349     local = env_get("LOCAL");
350     if (!local) strerr_die2x(100,FATAL,ERR_NOLOCAL);
351     len = str_len(local);
352     if (len >= 8 && !case_diffb(local + len - 8,8,"-return-")) {
353       action = "return-";       /* our bounce with qmail<1.02 */
354     } else
355       action = "";              /* list-mdomo-xxx won't work for older lists */
356   } else {                      /* older qmail versions */
357     local = env_get("LOCAL");
358     if (!local) strerr_die2x(100,FATAL,ERR_NOLOCAL);
359     getconf_line(&inlocal,"inlocal",1,FATAL,dir);
360     if (inlocal.len > str_len(local)) die_badaddr();
361     if (case_diffb(inlocal.s,inlocal.len,local)) die_badaddr();
362     action = local + inlocal.len;
363     if (*action)
364       if (*(action++) != '-') die_badaddr();    /* check anyway */
365   }
366         /* at this point action = "request" or "request-..." for std use; */
367         /* "" for majordomo@ */
368   if (!cfname) {                                /* expect request */
369     if (case_starts(action,ACTION_REQUEST))
370       action += str_len(ACTION_REQUEST);
371     else if (case_starts(action,ALT_REQUEST))
372       action += str_len(ALT_REQUEST);
373     else
374       _exit(0);                                 /* not for us */
375   }
376   getconf_line(&outlocal,"outlocal",1,FATAL,dir);
377   getconf_line(&outhost,"outhost",1,FATAL,dir);
378
379   if (!stralloc_copy(&listname,&outlocal)) die_nomem();
380   if (!stralloc_copy(&hostname,&outhost)) die_nomem();
381   if (!stralloc_0(&outlocal)) die_nomem();
382   if (!stralloc_0(&outhost)) die_nomem();
383
384   sender = env_get("SENDER");
385   if (!sender) strerr_die2x(99,INFO,ERR_NOSENDER);
386   if (!*sender)
387     strerr_die2x(99,INFO,ERR_BOUNCE);
388   if (!sender[str_chr(sender,'@')])
389     strerr_die2x(99,INFO,ERR_ANONYMOUS);
390   if (str_equal(sender,"#@[]"))
391     strerr_die2x(99,INFO,ERR_BOUNCE);
392
393   getconf(&headerremove,"headerremove",1,FATAL,dir);
394   constmap_init(&headerremovemap,headerremove.s,headerremove.len,0);
395
396   if (!stralloc_copys(&mydtline,
397        "Delivered-To: request processor for ")) die_nomem();
398   if (!stralloc_cats(&mydtline,outlocal.s)) die_nomem();
399   if (!stralloc_cats(&mydtline,"@")) die_nomem();
400   if (!stralloc_cats(&mydtline,outhost.s)) die_nomem();
401   if (!stralloc_cats(&mydtline,"\n")) die_nomem();
402
403   flagnosubject = 1;
404   if (action[0]) {      /* mainly to allow ezmlm-lists or ezmlm-which with */
405     flagnosubject = 0;  /* a command address rather than a complete msg */
406     command = action;
407     if (str_start(action,"return"))             /* kill bounces */
408       strerr_die2x(0,INFO,ERR_BOUNCE);
409     pos = 1 + str_chr(action + 1,'-');
410     if (action[pos]) {                          /* start of target */
411       action[pos] = '\0';
412       userlocal = action + pos + 1;
413       pos = str_rchr(userlocal,'=');            /* the "pseudo-@" */
414       if (userlocal[pos]) {
415         userlocal[pos] = '\0';
416         userhost = userlocal + pos + 1;
417       }
418     }
419   } else {
420     for (;;) {                                  /* Get Subject: */
421       if (getln(&ssin,&line,&match,'\n') == -1)
422         strerr_die2sys(111,FATAL,ERR_READ_INPUT);
423         if (line.len == 1)
424         break;
425         if ((line.s[0] != ' ') && (line.s[0] != '\t')) {
426           flagsub = 0;
427
428           if (case_startb(line.s,line.len,"mailing-list:"))
429             strerr_die2x(100,FATAL,ERR_MAILING_LIST);
430           else if (case_startb(line.s,line.len,"Subject:")) {
431             flaggotsub = 1;
432             pos = 8;
433             last = line.len - 2;                /* skip terminal '\n' */
434             while (line.s[last] == ' ' || line.s[last] == '\t') --last;
435             while (pos <= last &&
436                 (line.s[pos] == ' ' || line.s[pos] == '\t')) ++pos;
437             if (!stralloc_copyb(&subject,line.s+pos,last-pos+1)) die_nomem();
438           } else if (case_startb(line.s,line.len,"content-type:")) {
439             pos = 13; last = line.len - 2;      /* not cont-line - ok */
440             while (pos <= last &&
441                 (line.s[pos] == ' ' || line.s[pos] == '\t')) ++pos;
442             if (case_startb(line.s+pos,line.len - pos,"multipart/"))
443               flagmultipart = 1;
444           } else if (line.len == mydtline.len)
445             if (!byte_diff(line.s,line.len,mydtline.s))
446                strerr_die2x(100,FATAL,ERR_LOOPING);
447         } else if (flagsub) {   /* Continuation line */
448           pos = 1;
449           len = line.len - 2;   /* skip terminal '\n' */
450           while (line.s[len] == ' ' || line.s[len] == '\t') --len;
451           while (pos < len &&
452                 (line.s[pos] == ' ' || line.s[pos] == '\t')) ++pos;
453           if (!stralloc_append(&subject," ")) die_nomem();
454           if (!stralloc_copy(&subject,line.s+pos,len-pos+1)) die_nomem();
455       }
456       if (!match)
457         break;
458     }
459     if (!cfname) {               /* listserv@/majordomo@ ignore */
460       register char ch;
461       if (!stralloc_0(&subject)) die_nomem();
462       ch = *subject.s;          /* valid commands/list names start w letter */
463       if ((ch <= 'z' && ch >= 'a') || (ch <= 'Z' && ch >= 'A')) {
464         parseline(subject.s);
465         flagnosubject = 0;
466       }
467     }
468     if (cfname || flagnosubject) {
469       for (;;) {                                        /* parse body */
470         if (getln(&ssin,&line,&match,'\n') == -1)
471         strerr_die2sys(111,FATAL,ERR_READ_INPUT);
472         if (!match) break;
473         if (line.len == 1 && flagmultipart != 2) continue;
474                 /* lazy MIME cludge assumes first '--...' is start border */
475                 /* which is virtually always true */
476         if (flagmultipart == 1) {               /* skip to first border */
477           if (*line.s != '-' || line.s[1] != '-') continue;
478           flagmultipart = 2;
479           continue;
480         } else if (flagmultipart == 2) {        /* skip content info */
481           if (line.len != 1) continue;
482           flagmultipart = 3;                    /* may be part within part */
483           continue;                             /* and blank line */
484         } else if (flagmultipart == 3) {
485           if (*line.s == '-' && line.s[1] == '-') {
486             flagmultipart = 2;                  /* part within part */
487             continue;
488           }
489         }
490         {
491          register char ch;
492          ch = *line.s;
493         if (line.len == 1 ||
494           !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')))
495           continue;                             /* skip if not letter pos 1 */
496         }
497                         /* Here we have a body line with something */
498         if (!stralloc_copy(&subject,&line)) die_nomem();        /* save it */
499         subject.s[subject.len-1] = '\0';
500         parseline(subject.s);
501         break;
502       }
503     }
504   }
505         /* Do command substitution */
506   if (!stralloc_copys(&cmds,cmdstring)) die_nomem();
507   if (!stralloc_0(&cmds)) die_nomem();
508   psz = cmds.s;
509   while (*psz) {
510     if (*psz == '\\') *psz = '\0';
511     ++psz;
512   }
513   if (!constmap_init(&commandmap,cmds.s,cmds.len,0)) die_nomem();
514   cmdidx = cmdxlate[constmap_index(&commandmap,command,str_len(command))];
515   if (cmdidx == EZREQ_BAD) {    /* recognized, but not supported -> help */
516     listlocal = 0;              /* needed 'cause arguments are who-knows-what */
517     listhost = 0;
518     userlocal = 0;
519     userhost = 0;
520     cmdidx = EZREQ_HELP;
521   }
522   if (cfname && !listlocal && !userlocal && cmdidx > 0)
523     cmdidx = noargsxlate[cmdidx];        /* some done differently if no args */
524
525         /* =0 not found. This is treated as a list command! */
526   if (cmdidx < 0 && !cfname) {
527     cmdidx = EZREQ_HELP;
528   }
529   if (qmail_open(&qq,(stralloc *) 0) == -1)
530     strerr_die2sys(111,FATAL,ERR_QMAIL_QUEUE);
531
532   if (cmdidx >= 0) {
533         /* Things handled elsewhere. We do want to handle a simple HELP */
534         /* without arguments for e.g. majordomo@ from our own help file */
535
536     if (!stralloc_copys(&from,sender)) die_nomem();
537     if (!stralloc_0(&from)) die_nomem();
538     if (!listlocal) {
539       if (cfname)
540         strerr_die1x(100,ERR_REQ_LISTNAME);
541       else
542        listlocal = outlocal.s;  /* This is at the -request address */
543     }
544         /* if !cfname listhost is made outhost. If cfname, listhost=outhost */
545         /* is ok. listhost=0 => first match in config. Other listhost is ok */
546         /* only if match is found. Otherwise it's set to outhost. */
547
548     if (!cfname || (listhost && !case_diffs(listhost,outhost.s)))
549       listhost = outhost.s;
550     else {                       /* Check listhost against config file */
551       pos = str_len(listlocal);
552       fd = open_read(cfname);
553       if (fd == -1)
554         strerr_die4sys(111,FATAL,ERR_OPEN,cfname,": ");
555       substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf));
556       flagok = 0;                       /* got listhost match */
557       for (;;) {
558         if (getln(&sstext,&line,&match,'\n') == -1)
559           strerr_die3sys(111,FATAL,ERR_READ,cfname);
560         if (!match)
561           break;
562         if (line.len <= 1 || line.s[0] == '#')
563           continue;
564         if ((pos < line.len) && (line.s[pos] == '@') &&
565                 !byte_diff(line.s,pos,listlocal)) {
566           last = byte_chr(line.s,line.len,':');
567           if (!stralloc_copyb(&lhost,line.s+pos+1,last-pos-1)) die_nomem();
568           if (!stralloc_0(&lhost)) die_nomem();
569           if (listhost) {
570             if (!case_diffs(listhost,lhost.s)) {
571               flagok = 1;
572               break;                    /* host did match */
573             } else
574               continue;                 /* host didn't match */
575           } else {                      /* none given - grab first */
576             listhost = lhost.s;
577             flagok = 1;
578             break;
579           }
580         }
581       }
582       if (!flagok)
583         listhost = outhost.s;
584       close(fd);
585     }
586     if (!listhost)
587       listhost = outhost.s;
588     if (!userlocal) {
589       if (!stralloc_copys(&usr,sender)) die_nomem();
590       if (!stralloc_0(&usr)) die_nomem();
591       userlocal = usr.s;
592       userhost = usr.s + byte_rchr(usr.s,usr.len-1,'@');
593       if (!*userhost)
594         userhost = 0;
595       else {
596         *userhost = '\0';
597         ++userhost;
598       }
599     }
600
601     if (!stralloc_copys(&to,listlocal)) die_nomem();
602     if (!stralloc_cats(&to,"-")) die_nomem();
603     if (cmdidx) {                       /* recognized - substitute */
604       if (!stralloc_cats(&to,constmap_get(&commandmap,cmdidx)))
605                  die_nomem();
606     } else                              /* not recognized - use as is */
607       if (!stralloc_cats(&to,command)) die_nomem();
608
609     if (!stralloc_cats(&to,"-")) die_nomem();
610     if (!stralloc_cats(&to,userlocal)) die_nomem();
611     if (userhost) {                     /* doesn't exist for e.g. -get */
612       if (!stralloc_cats(&to,"=")) die_nomem();
613       if (!stralloc_cats(&to,userhost)) die_nomem();
614     }
615     if (!stralloc_cats(&to,"@")) die_nomem();
616     if (!stralloc_cats(&to,listhost)) die_nomem();
617     if (!stralloc_0(&to)) die_nomem();
618
619     qmail_put(&qq,mydtline.s,mydtline.len);
620
621     flaginheader = 1;
622     flagbadfield = 0;
623
624     if (seek_begin(0) == -1)
625       strerr_die2sys(111,FATAL,ERR_SEEK_INPUT);
626     substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf));
627
628     for (;;) {
629       if (getln(&ssin,&line,&match,'\n') == -1)
630         strerr_die2sys(111,FATAL,ERR_READ_INPUT);
631
632       if (flaginheader && match) {
633         if (line.len == 1)
634           flaginheader = 0;
635         if ((line.s[0] != ' ') && (line.s[0] != '\t')) {
636           flagbadfield = 0;
637           if (constmap(&headerremovemap,line.s,byte_chr(line.s,line.len,':')))
638             flagbadfield = 1;
639         }
640       }
641       if (!(flaginheader && flagbadfield))
642         qmail_put(&qq,line.s,line.len);
643       if (!match)
644         break;
645     }
646   } else {                              /* commands we deal with */
647     cmdidx = - cmdidx;                  /* now positive */
648     if (cmdidx == EZREQ_WHICH) {        /* arg is user, not list */
649       userlocal = listlocal; listlocal = 0;
650       userhost = listhost; listhost = 0;
651     }
652     if (!stralloc_copys(&from,outlocal.s)) die_nomem();
653     if (!stralloc_cats(&from,"-return-@")) die_nomem();
654     if (!stralloc_cats(&from,outhost.s)) die_nomem();
655     if (!stralloc_0(&from)) die_nomem();
656
657     if (userlocal) {
658       if (!stralloc_copys(&to,userlocal)) die_nomem();
659       if (!stralloc_cats(&to,"@")) die_nomem();
660       if (userhost) {
661         if (!stralloc_cats(&to,userhost)) die_nomem();
662        } else {
663         if (!stralloc_cats(&to,outhost.s)) die_nomem();
664       }
665     } else
666       if (!stralloc_copys(&to,sender)) die_nomem();
667     if (!stralloc_0(&to)) die_nomem();
668
669         /* now we need to look for charset and set flagcd appropriately */
670
671     if (getconf_line(&charset,"charset",0,FATAL,dir)) {
672       if (charset.len >= 2 && charset.s[charset.len - 2] == ':') {
673         if (charset.s[charset.len - 1] == 'B' ||
674                 charset.s[charset.len - 1] == 'Q') {
675           flagcd = charset.s[charset.len - 1];
676           charset.s[charset.len - 2] = '\0';
677         }
678       }
679     } else
680       if (!stralloc_copys(&charset,TXT_DEF_CHARSET)) die_nomem();
681     if (!stralloc_0(&charset)) die_nomem();
682     set_cpoutlocal(&listname);          /* necessary in case there are <#l#> */
683     set_cpouthost(&hostname);           /* necessary in case there are <#h#> */
684                                         /* we don't want to be send to a list*/
685     qmail_puts(&qq,"Mailing-List: ezmlm-request");
686     if (getconf(&line,"listid",0,FATAL)) {
687       qmail_puts(&qq,"List-ID: ");
688       qmail_put(&qq,line.s,line.len);
689     }
690     qmail_puts(&qq,"\nDate: ");
691     when = now();
692     datetime_tai(&dt,when);
693     qmail_put(&qq,date,date822fmt(date,&dt));
694     qmail_puts(&qq,"Message-ID: <");
695     if (!stralloc_copyb(&line,strnum,fmt_ulong(strnum,(unsigned long) when)))
696         die_nomem();
697     if (!stralloc_append(&line,".")) die_nomem();
698     if (!stralloc_catb(&line,strnum,
699                 fmt_ulong(strnum,(unsigned long) getpid()))) die_nomem();
700     if (!stralloc_cats(&line,".ezmlm@")) die_nomem();
701     if (!stralloc_cats(&line,outhost.s)) die_nomem();
702     if (!stralloc_0(&line)) die_nomem();
703     qmail_puts(&qq,line.s);
704     qmail_puts(&qq,">\nFrom: ");
705     if (!quote2(&line,outlocal.s)) die_nomem();
706     qmail_put(&qq,line.s,line.len);
707     if (cmdidx == EZREQ_HELP)
708       qmail_puts(&qq,"-return-@");
709     else
710       qmail_puts(&qq,"-help@");
711     qmail_puts(&qq,outhost.s);
712     qmail_puts(&qq,"\n");
713     qmail_put(&qq,mydtline.s,mydtline.len);
714     qmail_puts(&qq,"To: ");
715     if (!quote2(&line,to.s)) die_nomem();
716     qmail_put(&qq,line.s,line.len);
717     qmail_puts(&qq,"\n");
718     qmail_puts(&qq,"MIME-Version: 1.0\n");
719     if (flagcd) {
720       qmail_puts(&qq,"Content-Type: multipart/mixed; charset=");
721       qmail_puts(&qq,charset.s);
722       qmail_puts(&qq,";\n\tboundary=");
723       qmail_puts(&qq,boundary);
724     } else {
725       qmail_puts(&qq,"Content-type: text/plain; charset=");
726       qmail_puts(&qq,charset.s);
727     }
728     qmail_puts(&qq,"\nSubject: ");
729     if (!quote2(&line,outlocal.s)) die_nomem();
730     qmail_put(&qq,line.s,line.len);
731     qmail_puts(&qq,TXT_RESULTS);
732     transferenc();
733     copy(&qq,"text/top",flagcd,FATAL);
734    if (cmdidx == EZREQ_LISTS || cmdidx == EZREQ_WHICH) {
735       switch (cmdidx) {
736         case EZREQ_LISTS:
737           code_qput("LISTS:",6);
738           break;
739         case EZREQ_WHICH:
740           code_qput("WHICH (",7);
741           code_qput(to.s,to.len - 1);
742           code_qput("):\n\n",4);
743           break;
744         default: break;
745       }
746       fd = open_read(cfname);
747       if (fd == -1)
748         strerr_die4sys(111,FATAL,ERR_OPEN,cfname,": ");
749       substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf));
750       for (;;) {
751         if (getln(&sstext,&line,&match,'\n') == -1)
752           strerr_die3sys(111,FATAL,ERR_READ,cfname);
753         if (!match)
754           break;
755         if (line.len <= 1 || line.s[0] == '#')
756           continue;
757         if (!stralloc_0(&line)) die_nomem();
758         pos = str_chr(line.s,':');
759         if (!line.s[pos])
760           break;
761         line.s[pos] = '\0';
762         ++pos;
763         pos1 = pos + str_chr(line.s + pos,':');
764         if (line.s[pos1]) {
765           line.s[pos1] = '\0';
766           ++pos1;
767         } else
768           pos1 = 0;
769
770         switch (cmdidx) {
771           case EZREQ_LISTS:
772             code_qput("\n\n\t",3);
773             code_qput(line.s,pos-1);
774             code_qput("\n",1);
775             if (pos1) {
776               code_qput(line.s+pos1,line.len-2-pos1);
777             }
778             break;
779           case EZREQ_WHICH:
780             if (issub(line.s+pos,to.s,(char *) 0,FATAL)) {
781               code_qput(line.s,pos-1);
782               code_qput("\n",1);
783             }
784             closesql();         /* likely different dbs for different lists */
785             break;
786         }
787       }
788       code_qput("\n",1);
789       close(fd);
790     } else
791       copy(&qq,"text/help",flagcd,FATAL);
792
793     copy(&qq,"text/bottom",flagcd,FATAL);
794     if (flagcd) {
795       if (flagcd == 'B') {
796         encodeB("",0,&line,2,FATAL);    /* flush */
797         qmail_put(&qq,line.s,line.len);
798       }
799        qmail_puts(&qq,"\n--");
800        qmail_puts(&qq,boundary);
801        qmail_puts(&qq,"\nContent-Type: message/rfc822");
802        qmail_puts(&qq,
803                 "\nContent-Disposition: inline; filename=request.msg\n\n");
804     }
805     qmail_puts(&qq,"Return-Path: <");
806     if (!quote2(&line,sender)) die_nomem();
807     qmail_put(&qq,line.s,line.len);
808     qmail_puts(&qq,">\n");
809     if (seek_begin(0) == -1)
810       strerr_die2sys(111,FATAL,ERR_SEEK_INPUT);
811     if (substdio_copy(&ssqq,&ssin2) != 0)
812       strerr_die2sys(111,FATAL,ERR_READ_INPUT);
813     if (flagcd) {
814       qmail_puts(&qq,"\n--");
815       qmail_puts(&qq,boundary);
816       qmail_puts(&qq,"--\n");
817     }
818   }
819   qmail_from(&qq,from.s);
820   qmail_to(&qq,to.s);
821   if (*(err = qmail_close(&qq)) != '\0')
822       strerr_die3x(111,FATAL,ERR_TMP_QMAIL_QUEUE,err + 1);
823
824   strnum[fmt_ulong(strnum,qmail_qp(&qq))] = 0;
825   strerr_die3x(99,INFO, "qp ",strnum);
826 }