1 /*$Id: ezmlm-manage.c,v 1.86 1999/12/23 02:43:55 lindberg Exp $*/
2 /*$Name: ezmlm-idx-040 $*/
19 #include "readwrite.h"
24 #include "date822fmt.h"
26 #include "subscribe.h"
33 #define FATAL "ezmlm-manage: fatal: "
34 #define INFO "ezmlm-manage: info: "
36 int flagverbose = 0; /* default: Owner not informed about subdb changes */
37 /* 1 => notified for failed unsub, 2 => for all */
38 int flagnotify = 1; /* notify subscriber of completed events. 0 also */
39 /* suppresses all subscriber communication for */
40 /* [un]sub if -U/-S is used */
41 int flagbottom = 1; /* default: copy request & admin info to message */
42 int flaglist = 0; /* default: do not reply to -list */
43 int flagget = 1; /* default: service -get requests */
44 int flagsubconf = 1; /* default: require user-confirm for subscribe */
45 int flagunsubconf = 1; /* default: require user-confirm for unsubscribe */
46 int flagunsubismod = 0; /* default: do not require moderator approval to */
47 /* unsubscribe from moderated list */
48 int flagedit = 0; /* default: text file edit not allowed */
49 int flagstorefrom = 1; /* default: store from: line for subscribes */
50 char flagcd = '\0'; /* default: do not use _Q_uoted printable or _B_ase64 */
51 char encin = '\0'; /* encoding of incoming message */
52 int flagdig = 0; /* request is not for digest list */
54 static const char hex[]="0123456789ABCDEF";
55 char urlstr[] = "%00"; /* to build a url-encoded version of a char */
57 int act = AC_NONE; /* desired action */
58 unsigned int actlen = 0;/* str_len of above */
62 void *psql = (void *) 0;
65 strerr_die1x(100,"ezmlm-manage: usage: ezmlm-manage "
66 "[-bBcCdDeEfFlLmMnNqQsSuUvV] dir"); }
68 void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); }
72 strerr_die2x(100,FATAL,ERR_BAD_ADDRESS);
77 strerr_die2x(100,FATAL,ERR_MOD_COOKIE);
80 stralloc inhost = {0};
81 stralloc outhost = {0};
82 stralloc inlocal = {0};
83 stralloc outlocal = {0};
85 stralloc mailinglist = {0};
86 stralloc mydtline = {0};
87 stralloc target = {0};
88 stralloc verptarget = {0};
89 stralloc confirm = {0};
92 stralloc quoted = {0};
93 stralloc moddir = {0};
95 stralloc modsub = {0};
96 stralloc remote = {0};
100 stralloc fromline = {0};
102 stralloc fnedit = {0};
103 stralloc fneditn = {0};
104 stralloc charset = {0};
111 char strnum[FMT_ULONG];
112 char date[DATE822FMT];
114 char boundary[COOKIE];
115 datetime_sec hashdate;
118 substdio ssin = SUBSTDIO_FDBUF(read,0,inbuf,(int) sizeof(inbuf));
119 substdio ssin2 = SUBSTDIO_FDBUF(read,0,inbuf,(int) sizeof(inbuf));
121 substdio sstext; /* editing texts and reading "from" */
124 substdio ssfrom; /* writing "from" */
131 fdlock = open_append("lock");
133 strerr_die4sys(111,FATAL,ERR_OPEN,dir,"/lock: ");
134 if (lock_ex(fdlock) == -1)
135 strerr_die4sys(111,FATAL,ERR_OBTAIN,dir,"/lock: ");
143 void make_verptarget()
144 /* puts target with '=' instead of last '@' into stralloc verptarget */
145 /* and does set_cpverptarget */
149 i = str_rchr(target.s,'@');
150 if (!stralloc_copyb(&verptarget,target.s,i)) die_nomem();
152 if (!stralloc_append(&verptarget,"=")) die_nomem();
153 if (!stralloc_cats(&verptarget,target.s + i + 1)) die_nomem();
155 if (!stralloc_0(&verptarget)) die_nomem();
156 set_cpverptarget(verptarget.s);
159 void store_from(frl,adr)
160 /* rewrites the from file removing all that is older than 1000000 secs */
161 /* and add the curent from line (frl). Forget it if there is none there.*/
162 /* NOTE: This is used only for subscribes to moderated lists! */
163 stralloc *frl; /* from line */
168 unsigned long linetime;
170 if (!flagstorefrom || !frl->len) return; /* nothing to store */
172 if ((fdout = open_trunc("fromn")) == -1)
173 strerr_die3sys(111,FATAL,ERR_OPEN,"fromn: ");
174 substdio_fdbuf(&ssfrom,write,fdout,frombuf,(int) sizeof(frombuf));
175 if ((fdin = open_read("from")) == -1) {
176 if (errno != error_noent)
177 strerr_die3sys(111,FATAL,ERR_OPEN,"from: ");
179 substdio_fdbuf(&sstext,read,fdin,textbuf,(int) sizeof(textbuf));
181 if (getln(&sstext,&line,&match,'\n') == -1)
182 strerr_die3sys(111,FATAL,ERR_READ,"from: ");
184 (void) scan_ulong(line.s,&linetime);
185 if (linetime + 1000000 > when && linetime <= when)
186 if (substdio_bput(&ssfrom,line.s,line.len))
187 strerr_die3sys(111,FATAL,ERR_WRITE,"fromn: ");
190 } /* build new entry */
191 if (!stralloc_copyb(&line,strnum,fmt_ulong(strnum,when))) die_nomem();
192 if (!stralloc_append(&line," ")) die_nomem();
193 if (!stralloc_cats(&line,adr)) die_nomem();
194 if (!stralloc_0(&line)) die_nomem();
195 if (!stralloc_catb(&line,frl->s,frl->len)) die_nomem();
196 if (!stralloc_append(&line,"\n")) die_nomem();
197 if (substdio_bput(&ssfrom,line.s,line.len) == -1)
198 strerr_die3sys(111,FATAL,ERR_WRITE,"fromn: ");
199 if (substdio_flush(&ssfrom) == -1)
200 strerr_die3sys(111,FATAL,ERR_WRITE,"fromn: ");
201 if (fsync(fdout) == -1)
202 strerr_die3sys(111,FATAL,ERR_SYNC,"fromn: ");
203 if (close(fdout) == -1)
204 strerr_die3sys(111,FATAL,ERR_CLOSE,"fromn: ");
205 if (rename("fromn","from") == -1)
206 strerr_die3sys(111,FATAL,ERR_MOVE,"from: ");
210 char *get_from(adr,act)
211 /* If we captured a from line, it will be from the subscriber, except */
212 /* when -S is used when it's usually from the subscriber, but of course */
213 /* could be from anyone. The matching to stored data is required only */
214 /* to support moderated lists, and in cases where a new -sc is issued */
215 /* because an old one was invalid. In this case, we read through the */
216 /* from file trying to match up a timestamp with that starting in */
217 /* *(act+3). If the time stamp matches, we compare the target address */
218 /* itself. act + 3 must be a legal part of the string returns pointer to*/
219 /* fromline, NULL if not found. Since the execution time from when to */
220 /* storage may differ, we can't assume that the timestamps are in order.*/
222 char *adr; /* target address */
223 char *act; /* action */
228 unsigned long thistime;
229 unsigned long linetime;
231 if (!flagstorefrom) return 0;
232 if (fromline.len) { /* easy! We got it in this message */
233 if (!stralloc_0(&fromline)) die_nomem(FATAL);
235 } /* need to recover it from DIR/from */
237 (void) scan_ulong(act+3,&thistime);
238 if ((fd = open_read("from")) == -1)
239 if (errno == error_noent)
242 strerr_die3x(111,FATAL,ERR_READ,"from: ");
243 substdio_fdbuf(&sstext,read,fd,textbuf,(int) sizeof(textbuf));
245 if (getln(&sstext,&fromline,&match,'\n') == -1)
246 strerr_die3sys(111,FATAL,ERR_READ,"from: ");
248 fromline.s[fromline.len - 1] = (char) 0;
249 /* now:time addr\0fromline\0 read all. They can be out of order! */
250 pos = scan_ulong(fromline.s,&linetime);
251 if (linetime != thistime) continue;
252 if (!str_diff(fromline.s + pos + 1,adr)) {
253 pos = str_len(fromline.s);
254 if (pos < fromline.len) {
255 fl = fromline.s + pos + 1;
264 int hashok(action,ac)
272 x += scan_ulong(x,&u);
274 if (hashdate > when) return 0;
275 if (hashdate < when - 1000000) return 0;
278 strnum[fmt_ulong(strnum,(unsigned long) u)] = 0;
279 cookie(hash,key.s,key.len - flagdig,strnum,target.s,ac);
282 if (str_len(x) != COOKIE) return 0;
283 return byte_equal(hash,COOKIE,x);
287 int qqwrite(fd,buf,len) int fd; char *buf; unsigned int len;
289 qmail_put(&qq,buf,len);
294 substdio ssqq = SUBSTDIO_FDBUF(qqwrite,-1,qqbuf,(int) sizeof(qqbuf));
304 encodeB(s,n,&qline,0,FATAL);
306 encodeQ(s,n,&qline,FATAL);
307 qmail_put(&qq,qline.s,qline.len);
309 return 0; /* always succeeds */
316 qmail_put(&qq,"T",1);
332 char *s; /* ignored */
341 qmail_puts(&qq,"\n--");
342 qmail_put(&qq,boundary,COOKIE);
343 qmail_puts(&qq,"\nContent-Type: text/plain; charset=");
344 qmail_puts(&qq,charset.s);
345 qmail_puts(&qq,"\nContent-Transfer-Encoding: ");
347 qmail_puts(&qq,"quoted-printable\n\n");
349 qmail_puts(&qq,"base64\n\n");
351 qmail_puts(&qq,"\n");
356 if (!stralloc_copy(&owner,&outlocal)) die_nomem();
357 if (!stralloc_cats(&owner,"-owner@")) die_nomem();
358 if (!stralloc_cat(&owner,&outhost)) die_nomem();
359 if (!stralloc_0(&owner)) die_nomem();
360 qmail_to(&qq,owner.s);
365 copy(&qq,"text/mod-sub",flagcd,FATAL);
366 copy(&qq,"text/bottom",flagcd,FATAL);
367 code_qput(TXT_SUPPRESSED,str_len(TXT_SUPPRESSED));
369 qmail_puts(&qq,"\n--");
370 qmail_put(&qq,boundary,COOKIE);
371 qmail_puts(&qq,"--\n");
374 encodeB("",0,&line,2,FATAL); /* flush */
375 qmail_put(&qq,line.s,line.len);
377 qmail_from(&qq,from.s);
380 /* Writes all the headers up to but not including subject */
387 qmail_puts(&qq,"Mailing-List: ");
388 qmail_put(&qq,mailinglist.s,mailinglist.len);
389 if(getconf_line(&line,"listid",0,FATAL,dir)) {
390 qmail_puts(&qq,"\nList-ID: ");
391 qmail_put(&qq,line.s,line.len);
393 if (!quote("ed,&outlocal)) die_nomem(); /* quoted has outlocal */
394 qmail_puts(&qq,"\nList-Help: <mailto:"); /* General rfc2369 headers */
395 qmail_put(&qq,quoted.s,quoted.len);
396 qmail_puts(&qq,"-help@");
397 qmail_put(&qq,outhost.s,outhost.len);
398 qmail_puts(&qq,">\nList-Post: <mailto:");
399 qmail_put(&qq,quoted.s,quoted.len);
401 qmail_put(&qq,outhost.s,outhost.len);
402 qmail_puts(&qq,">\nList-Subscribe: <mailto:");
403 qmail_put(&qq,quoted.s,quoted.len);
404 qmail_puts(&qq,"-subscribe@");
405 qmail_put(&qq,outhost.s,outhost.len);
406 qmail_puts(&qq,">\nDate: ");
407 datetime_tai(&dt,when);
408 qmail_put(&qq,date,date822fmt(date,&dt));
409 qmail_puts(&qq,"Message-ID: <");
410 if (!stralloc_copyb(&line,strnum,fmt_ulong(strnum,(unsigned long) when)))
412 if (!stralloc_append(&line,".")) die_nomem();
413 if (!stralloc_catb(&line,strnum,
414 fmt_ulong(strnum,(unsigned long) getpid()))) die_nomem();
415 if (!stralloc_cats(&line,".ezmlm@")) die_nomem();
416 if (!stralloc_cat(&line,&outhost)) die_nomem();
417 if (!stralloc_0(&line)) die_nomem();
418 qmail_puts(&qq,line.s);
419 /* "unique" MIME boundary as hash of messageid */
420 cookie(boundary,"",0,"",line.s,"");
421 qmail_puts(&qq,">\nFrom: ");
422 qmail_put(&qq,quoted.s,quoted.len);
423 if (act == AC_HELP) /* differnt "From:" for help to break auto- */
424 qmail_puts(&qq,"-return-@"); /* responder loops */
426 qmail_puts(&qq,"-help@");
427 qmail_put(&qq,outhost.s,outhost.len);
428 qmail_puts(&qq,"\nTo: ");
429 if (!quote2("ed,target.s)) die_nomem();
430 qmail_put(&qq,quoted.s,quoted.len);
431 qmail_puts(&qq,"\n");
432 if (!stralloc_copys(&mydtline,"Delivered-To: responder for ")) die_nomem();
433 if (!stralloc_catb(&mydtline,outlocal.s,outlocal.len)) die_nomem();
434 if (!stralloc_cats(&mydtline,"@")) die_nomem();
435 if (!stralloc_catb(&mydtline,outhost.s,outhost.len)) die_nomem();
436 if (!stralloc_cats(&mydtline,"\n")) die_nomem();
437 qmail_put(&qq,mydtline.s,mydtline.len);
441 /* do it for -sc, but if the -S flag is used, do it for -subscribe */
442 flaggetfrom = flagstorefrom &&
443 ((act == AC_SC) || ((act == AC_SUBSCRIBE) && !flagsubconf));
445 if (getln(&ssin,&line,&match,'\n') == -1)
446 strerr_die2sys(111,FATAL,ERR_READ_INPUT);
448 if (line.len == 1) break;
449 if ((line.s[0] != ' ') && (line.s[0] != '\t')) {
452 if (case_startb(line.s,line.len,"mailing-list:"))
453 strerr_die2x(100,FATAL,ERR_MAILING_LIST);
454 if (line.len == mydtline.len)
455 if (byte_equal(line.s,line.len,mydtline.s))
456 strerr_die2x(100,FATAL,ERR_LOOPING);
457 if (case_startb(line.s,line.len,"delivered-to:"))
459 else if (case_startb(line.s,line.len,"received:"))
461 else if (case_startb(line.s,line.len,"content-transfer-encoding:")) {
463 while (line.s[pos] == ' ' || line.s[pos] == '\t') ++pos;
464 if (case_startb(line.s+pos,line.len-pos,"base64"))
466 else if (case_startb(line.s+pos,line.len-pos,"quoted-printable"))
468 } else if (flaggetfrom && case_startb(line.s,line.len,"from:")) {
469 flagfromline = 1; /* for logging subscriber data */
471 while (line.s[pos] == ' ' || line.s[pos] == '\t') ++pos;
472 if (!stralloc_copyb(&fromline,line.s + pos,line.len - pos - 1))
476 if (flagfromline == 1) /* scrap terminal '\n' */
477 if (!stralloc_catb(&fromline,line.s,line.len - 1)) die_nomem();
480 qmail_put(&qq,line.s,line.len);
482 qmail_puts(&qq,"MIME-Version: 1.0\n");
484 qmail_puts(&qq,"Content-Type: multipart/mixed; charset=");
485 qmail_puts(&qq,charset.s);
486 qmail_puts(&qq,";\n\tboundary=");
487 qmail_put(&qq,boundary,COOKIE);
489 qmail_puts(&qq,"Content-type: text/plain; charset=");
490 qmail_puts(&qq,charset.s);
492 qmail_puts(&qq,"\n");
503 fl = get_from(target.s,action); /* try to match up */
504 switch((r = subscribe(workdir,target.s,1,fl,"+",1,-1,(char *) 0,FATAL))) {
506 qmail_puts(&qq,"List-Unsubscribe: <mailto:"); /*rfc2369 */
507 qmail_put(&qq,outlocal.s,outlocal.len);
508 qmail_puts(&qq,"-unsubscribe-");
509 /* url-encode since verptarget is controlled by sender */
510 /* note &verptarget ends in '\0', hence len - 1! */
511 for (i = 0; i < verptarget.len - 1; i++) {
512 ch = verptarget.s[i];
513 if (str_chr("\"?;<>&/:%+#",ch) < 10 ||
514 (ch <= ' ') || (ch & 0x80)) {
515 urlstr[1] = hex[ch / 16];
516 urlstr[2] = hex[ch & 0xf];
517 qmail_put(&qq,urlstr,3);
519 qmail_put(&qq,verptarget.s + i, 1);
523 qmail_put(&qq,outhost.s,outhost.len); /* safe */
524 qmail_puts(&qq,">\n");
525 qmail_puts(&qq,TXT_WELCOME);
526 if (!quote("ed,&outlocal)) die_nomem();
527 qmail_put(&qq,quoted.s,quoted.len);
529 qmail_put(&qq,outhost.s,outhost.len);
530 qmail_puts(&qq,"\n");
532 if (!stralloc_copy(&confirm,&outlocal)) die_nomem();
533 if (!stralloc_append(&confirm,"unsubscribe-")) die_nomem();
534 if (!stralloc_cats(&confirm,verptarget.s)) die_nomem();
535 if (!stralloc_append(&confirm,"@")) die_nomem();
536 if (!stralloc_cat(&confirm,&outhost)) die_nomem();
537 if (!stralloc_0(&confirm)) die_nomem();
538 set_cpconfirm(confirm.s); /* for !R in copy */
539 copy(&qq,"text/top",flagcd,FATAL);
540 copy(&qq,"text/sub-ok",flagcd,FATAL);
543 if (str_start(action,ACTION_TC))
544 strerr_die2x(0,INFO,ERR_SUB_NOP);
545 qmail_puts(&qq,TXT_EZMLM_RESPONSE);
547 copy(&qq,"text/top",flagcd,FATAL);
548 copy(&qq,"text/sub-nop",flagcd,FATAL);
551 if (flagdig == FLD_DENY || flagdig == FLD_ALLOW)
552 strerr_die3x(0,INFO,ERR_EXTRA_SUB,target.s);
561 switch((r = subscribe(workdir,target.s,0,"","-",1,-1,(char *) 0,FATAL))) {
562 /* no comment for unsubscribe */
564 qmail_puts(&qq,TXT_GOODBYE);
565 if (!quote("ed,&outlocal)) die_nomem();
566 qmail_put(&qq,quoted.s,quoted.len);
568 qmail_put(&qq,outhost.s,outhost.len);
569 qmail_puts(&qq,"\n\n");
571 copy(&qq,"text/top",flagcd,FATAL);
572 copy(&qq,"text/unsub-ok",flagcd,FATAL);
575 qmail_puts(&qq,TXT_EZMLM_RESPONSE);
577 copy(&qq,"text/top",flagcd,FATAL);
578 copy(&qq,"text/unsub-nop",flagcd,FATAL);
581 if (flagdig == FLD_DENY || flagdig == FLD_ALLOW)
582 strerr_die3x(0,INFO,ERR_EXTRA_UNSUB,target.s);
587 /* This should only be called with valid act for sub/unsub confirms. If act */
588 /* is not ACTION_SC or ACTION_TC, it is assumed to be an unsubscribe conf.*/
589 char *act; /* first letter of desired confirm request only as STRING! */
593 strnum[fmt_ulong(strnum,(unsigned long) when)] = 0;
594 cookie(hash,key.s,key.len-flagdig,strnum,target.s,act);
595 if (!stralloc_copy(&confirm,&outlocal)) die_nomem();
596 if (!stralloc_append(&confirm,"-")) die_nomem();
597 if (!stralloc_catb(&confirm,act,1)) die_nomem();
598 if (!stralloc_cats(&confirm,"c.")) die_nomem();
599 if (!stralloc_cats(&confirm,strnum)) die_nomem();
600 if (!stralloc_append(&confirm,".")) die_nomem();
601 if (!stralloc_catb(&confirm,hash,COOKIE)) die_nomem();
602 if (!stralloc_append(&confirm,"-")) die_nomem();
603 if (!stralloc_cats(&confirm,verptarget.s)) die_nomem();
604 if (!stralloc_append(&confirm,"@")) die_nomem();
605 if (!stralloc_cat(&confirm,&outhost)) die_nomem();
606 if (!stralloc_0(&confirm)) die_nomem();
607 set_cpconfirm(confirm.s); /* for copy */
609 qmail_puts(&qq,"Reply-To: ");
610 if (!quote2("ed,confirm.s)) die_nomem();
611 qmail_put(&qq,quoted.s,quoted.len);
612 qmail_puts(&qq,"\n");
613 if (!stralloc_0(&confirm)) die_nomem();
615 qmail_puts(&qq,"Subject: ");
616 if (*act == ACTION_SC[0] || *act == ACTION_UC[0])
617 qmail_puts(&qq,TXT_USRCONFIRM);
619 qmail_puts(&qq,TXT_MODCONFIRM);
620 if (*act == ACTION_SC[0] || *act == ACTION_TC[0])
621 qmail_puts(&qq,TXT_SUBSCRIBE_TO);
623 qmail_puts(&qq,TXT_UNSUBSCRIBE_FROM);
624 if (!quote("ed,&outlocal)) die_nomem();
625 qmail_put(&qq,quoted.s,quoted.len);
627 qmail_put(&qq,outhost.s,outhost.len);
628 qmail_puts(&qq,"\n");
630 copy(&qq,"text/top",flagcd,FATAL);
635 putsubs(moddir.s,0L,52L,subto,1,FATAL);
640 if (flagbottom || act == AC_HELP) {
641 copy(&qq,"text/bottom",flagcd,FATAL);
644 encodeB("",0,&line,2,FATAL); /* flush */
645 qmail_put(&qq,line.s,line.len);
647 qmail_puts(&qq,"\n--");
648 qmail_put(&qq,boundary,COOKIE);
649 qmail_puts(&qq,"\nContent-Type: message/rfc822");
650 qmail_puts(&qq,"\nContent-Disposition: inline; filename=request.msg\n\n");
652 qmail_puts(&qq,"Return-Path: <");
653 if (!quote2("ed,sender)) die_nomem();
654 qmail_put(&qq,quoted.s,quoted.len);
655 qmail_puts(&qq,">\n");
656 if (seek_begin(0) == -1)
657 strerr_die2sys(111,FATAL,ERR_SEEK_INPUT);
658 if (substdio_copy(&ssqq,&ssin2) != 0)
659 strerr_die2sys(111,FATAL,ERR_READ_INPUT);
661 qmail_puts(&qq,"\n--");
662 qmail_put(&qq,boundary,COOKIE);
663 qmail_puts(&qq,"--\n");
667 encodeB("",0,&line,2,FATAL); /* flush even if no bottom */
668 qmail_put(&qq,line.s,line.len);
672 qmail_from(&qq,from.s);
686 char *cp,*cpfirst,*cplast,*cpnext,*cpafter;
701 while ((opt = getopt(argc,argv,"bBcCdDeEfFlLmMnNqQsSuUvV")) != opteof)
703 case 'b': flagbottom = 1; break;
704 case 'B': flagbottom = 0; break;
705 case 'c': flagget = 1; break;
706 case 'C': flagget = 0; break;
708 case 'e': flagedit = 1; break;
710 case 'E': flagedit = 0; break;
711 case 'f': flagstorefrom = 1; break;
712 case 'F': flagstorefrom = 0; break;
713 case 'l': flaglist = 1; break;
714 case 'L': flaglist = 0; break;
715 case 'm': flagunsubismod = 1; break;
716 case 'M': flagunsubismod = 0; break;
717 case 'n': flagnotify = 1; break;
718 case 'N': flagnotify = 0; break;
719 case 's': flagsubconf = 1; break;
720 case 'S': flagsubconf = 0; break;
721 case 'q': flagverbose = 0; break;
722 case 'Q': flagverbose++; break;
723 case 'u': flagunsubconf = 1; break;
724 case 'U': flagunsubconf = 0; break;
726 case 'V': strerr_die2x(0,
727 "ezmlm-manage version: ezmlm-0.53+",EZIDX_VERSION);
733 if (!dir) die_usage();
735 sender = env_get("SENDER");
736 if (!sender) strerr_die2x(100,FATAL,ERR_NOSENDER);
737 local = env_get("LOCAL");
738 if (!local) strerr_die2x(100,FATAL,ERR_NOLOCAL);
739 def = env_get("DEFAULT");
742 strerr_die2x(100,FATAL,ERR_BOUNCE);
743 if (!sender[str_chr(sender,'@')])
744 strerr_die2x(100,FATAL,ERR_ANONYMOUS);
745 if (str_equal(sender,"#@[]"))
746 strerr_die2x(100,FATAL,ERR_BOUNCE);
748 if (chdir(dir) == -1)
749 strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": ");
751 switch(slurp("key",&key,32)) {
753 strerr_die4sys(111,FATAL,ERR_READ,dir,"/key: ");
755 strerr_die4x(100,FATAL,dir,"/key",ERR_NOEXIST);
757 getconf_line(&mailinglist,"mailinglist",1,FATAL,dir);
758 getconf_line(&outhost,"outhost",1,FATAL,dir);
759 getconf_line(&outlocal,"outlocal",1,FATAL,dir);
760 set_cpouthost(&outhost);
761 if (getconf_line(&charset,"charset",0,FATAL,dir)) {
762 if (charset.len >= 2 && charset.s[charset.len - 2] == ':') {
763 if (charset.s[charset.len - 1] == 'B' ||
764 charset.s[charset.len - 1] == 'Q') {
765 flagcd = charset.s[charset.len - 1];
766 charset.s[charset.len - 2] = '\0';
770 if (!stralloc_copys(&charset,TXT_DEF_CHARSET)) die_nomem();
771 if (!stralloc_0(&charset)) die_nomem();
773 if (def) /* qmail-1.02 */
774 action = def; /* .qmail-list-default */
775 else { /* older version of qmail */
776 getconf_line(&inlocal,"inlocal",1,FATAL,dir);
777 if (inlocal.len > str_len(local)) die_badaddr();
778 if (case_diffb(inlocal.s,inlocal.len,local)) die_badaddr();
779 action = local + inlocal.len;
780 if (*(action++) != '-') die_badaddr();
781 /* has to be '-' to match link. Check anyway */
784 if (!stralloc_copys(&ddir,dir)) die_nomem();
786 if (case_starts(action,"digest")) { /* digest */
788 if (!stralloc_cats(&outlocal,"-digest")) die_nomem();
789 if (!stralloc_cats(&ddir,"/digest")) die_nomem();
790 flagdig = FLD_DIGEST;
791 } else if (case_starts(action,ACTION_ALLOW)) { /* allow */
792 action += str_len(ACTION_ALLOW);
793 if (!stralloc_append(&outlocal,"-")) die_nomem();
794 if (!stralloc_cats(&outlocal,ACTION_ALLOW)) die_nomem();
795 if (!stralloc_cats(&ddir,"/allow")) die_nomem();
797 } else if (case_starts(action,ACTION_DENY)) { /* deny */
798 action += str_len(ACTION_DENY);
799 if (!stralloc_append(&outlocal,"-")) die_nomem();
800 if (!stralloc_cats(&outlocal,ACTION_DENY)) die_nomem();
801 if (!stralloc_cats(&ddir,"/deny")) die_nomem();
804 if (flagdig) /* zap '-' after db specifier */
805 if (*(action++) != '-') die_badaddr();
807 if (!stralloc_0(&ddir)) die_nomem();
809 set_cpoutlocal(&outlocal);
811 if (!stralloc_copys(&target,sender)) die_nomem();
813 i = str_chr(action,'-');
816 if (!stralloc_copys(&target,action + i + 1)) die_nomem();
817 i = byte_rchr(target.s,target.len,'=');
822 if (!stralloc_0(&target)) die_nomem();
823 set_cptarget(target.s); /* for copy() */
826 flagmod = getconf_line(&modsub,"modsub",0,FATAL,dir);
827 flagremote = getconf_line(&remote,"remote",0,FATAL,dir);
829 if (case_equals(action,ACTION_LISTN) ||
830 case_equals(action,ALT_LISTN))
832 else if (case_equals(action,ACTION_LIST) ||
833 case_equals(action,ALT_LIST))
835 else if (case_starts(action,ACTION_GET) ||
836 case_starts(action,ALT_GET))
838 else if (case_equals(action,ACTION_HELP) ||
839 case_equals(action,ALT_HELP))
841 else if (case_starts(action,ACTION_EDIT) ||
842 case_starts(action,ALT_EDIT))
844 else if (case_starts(action,ACTION_LOG))
845 { act = AC_LOG; actlen = str_len(ACTION_LOG); }
846 else if (case_starts(action,ALT_LOG))
847 { act = AC_LOG; actlen = str_len(ALT_LOG); }
849 /* NOTE: act is needed in msg_headers(). */
850 /* Yes, this needs to be cleaned up! */
852 if (flagmod || flagremote) {
853 if (modsub.len && modsub.s[0] == '/') {
854 if (!stralloc_copy(&moddir,&modsub)) die_nomem();
855 } else if (remote.len && remote.s[0] == '/') {
856 if (!stralloc_copy(&moddir,&remote)) die_nomem();
858 if (!stralloc_copys(&moddir,dir)) die_nomem();
859 if (!stralloc_cats(&moddir,"/mod")) die_nomem();
861 if (!stralloc_0(&moddir)) die_nomem();
862 /* for these the reply is 'secret' and goes to sender */
863 /* This means that they can be triggered from a SENDER */
864 /* that is not a mod, but never send to a non-mod */
865 if (act == AC_NONE || flagdig == FLD_DENY) /* None of the above */
866 pmod = issub(moddir.s,sender,(char *) 0,FATAL);
867 /* sender = moderator? */
869 pmod = issub(moddir.s,target.s,(char *) 0,FATAL);
870 /* target = moderator? */
872 pmod = 0; /* always 0 for non-mod/remote lists */
873 /* if DIR/public is missing, we still respond*/
874 /* to requests from moderators for remote */
875 /* admin and modsub lists. Since pmod */
876 /* is false for all non-mod lists, only it */
877 /* needs to be tested. */
878 if ((flagpublic = slurp("public",&line,1)) == -1)
879 strerr_die4sys(111,FATAL,ERR_READ,dir,"/public: ");
880 if (!flagpublic && !(pmod && flagremote) &&
881 !case_equals(action,ACTION_HELP))
882 strerr_die2x(100,FATAL,ERR_NOT_PUBLIC);
884 if (flagdig == FLD_DENY)
885 if (!pmod || !flagremote) /* only mods can do */
886 strerr_die1x(100,ERR_NOT_ALLOWED);
888 if (act == AC_NONE) { /* none of the above */
889 if (case_equals(action,ACTION_SUBSCRIBE) ||
890 case_equals(action,ALT_SUBSCRIBE))
892 else if (case_equals(action,ACTION_UNSUBSCRIBE)
893 || case_equals(action,ALT_UNSUBSCRIBE))
894 act = AC_UNSUBSCRIBE;
895 else if (str_start(action,ACTION_SC)) act = AC_SC;
898 if (!stralloc_copy(&from,&outlocal)) die_nomem();
899 if (!stralloc_cats(&from,"-return-@")) die_nomem();
900 if (!stralloc_cat(&from,&outhost)) die_nomem();
901 if (!stralloc_0(&from)) die_nomem();
903 if (qmail_open(&qq,(stralloc *) 0) == -1)
904 strerr_die2sys(111,FATAL,ERR_QMAIL_QUEUE);
907 if (act == AC_SUBSCRIBE) {
908 if (pmod && flagremote) {
909 doconfirm(ACTION_TC);
910 copy(&qq,"text/mod-sub-confirm",flagcd,FATAL);
913 } else if (flagsubconf) {
914 doconfirm(ACTION_SC);
915 copy(&qq,"text/sub-confirm",flagcd,FATAL);
917 qmail_to(&qq,target.s);
918 } else { /* normal subscribe, no confirm */
919 r = geton(action); /* should be rarely used. */
921 if (flagnotify) qmail_to(&qq,target.s);
922 if (r && flagverbose > 1) to_owner();
925 } else if (act == AC_SC) {
926 if (hashok(action,ACTION_SC)) {
927 if (flagmod && !(pmod && str_equal(sender,target.s))) {
928 store_from(&fromline,target.s); /* save from line, if requested */
929 /* since transaction not complete */
930 doconfirm(ACTION_TC);
931 copy(&qq,"text/mod-sub-confirm",flagcd,FATAL);
937 qmail_to(&qq,target.s);
938 if (r && flagverbose > 1) to_owner();
941 doconfirm(ACTION_SC);
942 copy(&qq,"text/sub-bad",flagcd,FATAL);
944 qmail_to(&qq,target.s);
947 } else if (str_start(action,ACTION_TC)) {
948 if (hashok(action,ACTION_TC)) {
951 if (flagnotify) qmail_to(&qq,target.s); /* unless suppressed */
952 if (r && flagverbose > 1) to_owner();
954 if (!pmod || !flagremote) /* else anyone can get a good -tc. */
956 doconfirm(ACTION_TC);
957 copy(&qq,"text/sub-bad",flagcd,FATAL);
962 } else if (act == AC_UNSUBSCRIBE) {
964 if (pmod && flagremote) {
965 doconfirm(ACTION_VC);
966 copy(&qq,"text/mod-unsub-confirm",flagcd,FATAL);
970 doconfirm(ACTION_UC);
971 copy(&qq,"text/unsub-confirm",flagcd,FATAL);
973 qmail_to(&qq,target.s);
975 } else if (flagunsubismod && flagmod) {
976 doconfirm(ACTION_VC);
977 copy(&qq,"text/mod-unsub-confirm",flagcd,FATAL);
983 if (!r || flagnotify) qmail_to(&qq,target.s);
984 /* tell owner if problems (-Q) or anyway (-QQ) */
985 if (flagverbose && (!r || flagverbose > 1)) to_owner();
988 } else if (str_start(action,ACTION_UC)) {
989 if (hashok(action,ACTION_UC)) {
990 /* unsub is moderated only on moderated list if -m unless the */
991 /* target == sender == a moderator */
992 if (flagunsubismod && flagmod) {
993 doconfirm(ACTION_VC);
994 copy(&qq,"text/mod-unsub-confirm",flagcd,FATAL);
1000 if (!r || flagnotify) qmail_to(&qq,target.s);
1001 /* tell owner if problems (-Q) or anyway (-QQ) */
1002 if (flagverbose && (!r || flagverbose > 1)) to_owner();
1005 doconfirm(ACTION_UC);
1006 copy(&qq,"text/unsub-bad",flagcd,FATAL);
1008 qmail_to(&qq,target.s);
1011 } else if (str_start(action,ACTION_VC)) {
1012 if (hashok(action,ACTION_VC)) {
1015 strerr_die2x(0,INFO,ERR_UNSUB_NOP);
1017 if (r) { /* success to target */
1018 qmail_to(&qq,target.s);
1019 if (flagverbose > 1) to_owner();
1020 } else /* NOP to sender = admin. Will take */
1021 qmail_to(&qq,sender); /* care of it. No need to tell owner */
1022 /* if list is moderated skip - otherwise bad with > 1 mod */
1024 if (!pmod || !flagremote) /* else anyone can get a good -vc. */
1026 doconfirm(ACTION_VC);
1027 copy(&qq,"text/unsub-bad",flagcd,FATAL);
1032 } else if (act == AC_LIST || act == AC_LISTN) {
1034 if (!flaglist || (!flagmod && !flagremote))
1035 strerr_die2x(100,FATAL,ERR_NOT_AVAILABLE);
1037 strerr_die2x(100,FATAL,ERR_NOT_ALLOWED);
1038 qmail_puts(&qq,TXT_EZMLM_RESPONSE);
1040 copy(&qq,"text/top",flagcd,FATAL);
1042 if (act == AC_LIST) {
1043 (void) code_qput(TXT_LISTMEMBERS,str_len(TXT_LISTMEMBERS));
1044 i = putsubs(workdir,0L,52L,code_subto,1,FATAL);
1046 i = putsubs(workdir,0L,52L,dummy_to,1,FATAL);
1048 (void) code_qput("\n ======> ",11);
1049 (void) code_qput(strnum,fmt_ulong(strnum,i));
1050 (void) code_qput("\n",1);
1054 } else if (act == AC_LOG) {
1056 if (*action == '.' || *action == '_') ++action;
1057 if (!flaglist || !flagremote)
1058 strerr_die2x(100,FATAL,ERR_NOT_AVAILABLE);
1060 strerr_die2x(100,FATAL,ERR_NOT_ALLOWED);
1061 qmail_puts(&qq,TXT_EZMLM_RESPONSE);
1063 searchlog(workdir,action,code_subto,FATAL);
1067 } else if (act == AC_EDIT) {
1068 /* only remote admins and only if -e is specified may edit */
1069 if (!flagedit || !flagremote)
1070 strerr_die2x(100,FATAL,ERR_NOT_AVAILABLE);
1072 strerr_die2x(100,FATAL,ERR_NOT_ALLOWED);
1073 len = str_len(ACTION_EDIT);
1074 if (!case_starts(action,ACTION_EDIT))
1075 len = str_len(ALT_EDIT);
1076 if (action[len]) { /* -edit.file, not just -edit */
1077 if (action[len] != '.')
1078 strerr_die2x(100,FATAL,ERR_BAD_REQUEST);
1079 if (!stralloc_copys(&fnedit,"text/")) die_nomem();
1080 if (!stralloc_cats(&fnedit,action+len+1)) die_nomem();
1081 if (!stralloc_0(&fnedit)) die_nomem();
1082 case_lowerb(fnedit.s,fnedit.len);
1083 i = 5; /* after the "text/" */
1084 while ((ch = fnedit.s[i++])) {
1085 if (((ch > 'z') || (ch < 'a')) && (ch != '_'))
1086 strerr_die2x(100,FATAL,ERR_BAD_NAME);
1087 if (ch == '_') fnedit.s[i-1] = '-';
1089 switch(slurp(fnedit.s,&text,1024)) { /* entire file! */
1091 strerr_die6sys(111,FATAL,ERR_READ,dir,"/",fnedit.s,": ");
1093 strerr_die5x(100,FATAL,dir,"/",fnedit.s,ERR_NOEXIST);
1095 if (!stralloc_copy(&line,&text)) die_nomem();
1096 { /* get rid of nulls to use cookie */
1097 register char *s; register unsigned int n;
1098 s = line.s; n = line.len;
1099 while(n--) { if (!*s) *s = '_'; ++s; }
1101 if (!stralloc_cat(&line,&fnedit)) die_nomem(); /* including '\0' */
1102 strnum[fmt_ulong(strnum,(unsigned long) when)] = 0;
1103 cookie(hash,key.s,key.len,strnum,line.s,"-e");
1104 if (!stralloc_copy(&confirm,&outlocal)) die_nomem();
1105 if (!stralloc_append(&confirm,"-")) die_nomem();
1106 if (!stralloc_catb(&confirm,ACTION_ED,LENGTH_ED)) die_nomem();
1107 if (!stralloc_cats(&confirm,strnum)) die_nomem();
1108 if (!stralloc_append(&confirm,".")) die_nomem();
1109 /* action part has been checked for bad chars */
1110 if (!stralloc_cats(&confirm,action + len + 1)) die_nomem();
1111 if (!stralloc_append(&confirm,".")) die_nomem();
1112 if (!stralloc_catb(&confirm,hash,COOKIE)) die_nomem();
1113 if (!stralloc_append(&confirm,"@")) die_nomem();
1114 if (!stralloc_cat(&confirm,&outhost)) die_nomem();
1115 if (!stralloc_0(&confirm)) die_nomem();
1116 set_cpconfirm(confirm.s);
1118 qmail_puts(&qq,"Reply-To: ");
1119 if (!quote2("ed,confirm.s)) die_nomem();
1120 qmail_put(&qq,quoted.s,quoted.len);
1121 qmail_puts(&qq,"\n");
1122 if (!stralloc_0(&confirm)) die_nomem();
1124 qmail_puts(&qq,TXT_EDIT_RESPONSE);
1125 qmail_puts(&qq,action+len+1); /* has the '_' not '-' */
1126 qmail_puts(&qq,TXT_EDIT_FOR);
1127 if (!quote("ed,&outlocal)) die_nomem();
1128 qmail_put(&qq,quoted.s,quoted.len);
1129 qmail_puts(&qq,"@");
1130 qmail_put(&qq,outhost.s,outhost.len);
1131 qmail_puts(&qq,"\n");
1133 copy(&qq,"text/top",flagcd,FATAL);
1134 copy(&qq,"text/edit-do",flagcd,FATAL);
1135 (void) code_qput(TXT_EDIT_START,str_len(TXT_EDIT_START));
1136 (void) code_qput("\n",1);
1137 (void) code_qput(text.s,text.len);
1138 (void) code_qput(TXT_EDIT_END,str_len(TXT_EDIT_END));
1139 (void) code_qput("\n",1);
1141 } else { /* -edit only, so output list of editable files */
1142 qmail_puts(&qq,TXT_EDIT_LIST);
1144 copy(&qq,"text/top",flagcd,FATAL);
1145 copy(&qq,"text/edit-list",flagcd,FATAL);
1147 qmail_puts(&qq,"\n\n");
1151 } else if (str_start(action,ACTION_ED)) {
1154 x = action + LENGTH_ED;
1155 x += scan_ulong(x,&u);
1156 if ((u > when) || (u < when - 100000)) die_cookie();
1159 x += str_chr(x,'.');
1160 if (!*x) die_cookie();
1163 if (!stralloc_copys(&fnedit,"text/")) die_nomem();
1164 if (!stralloc_cats(&fnedit,fname)) die_nomem();
1165 if (!stralloc_0(&fnedit)) die_nomem();
1166 y = fnedit.s + 5; /* after "text/" */
1167 while (*++y) { /* Name should be guaranteed by the cookie, */
1168 /* but better safe than sorry ... */
1169 if (((*y > 'z') || (*y < 'a')) && (*y != '_'))
1170 strerr_die2x(100,FATAL,ERR_BAD_NAME);
1171 if (*y == '_') *y = '-';
1174 lock(); /* file must not change while here */
1176 switch (slurp(fnedit.s,&text,1024)) {
1178 strerr_die6sys(111,FATAL,ERR_READ,dir,"/",fnedit.s,": ");
1180 strerr_die5x(100,FATAL,dir,"/",fnedit.s,ERR_NOEXIST);
1182 if (!stralloc_copy(&line,&text)) die_nomem();
1183 { /* get rid of nulls to use cookie */
1184 register char *s; register unsigned int n;
1185 s = line.s; n = line.len;
1186 while(n--) { if (!*s) *s = '_'; ++s; }
1188 if (!stralloc_cat(&line,&fnedit)) die_nomem(); /* including '\0' */
1189 strnum[fmt_ulong(strnum,(unsigned long) u)] = 0;
1190 cookie(hash,key.s,key.len,strnum,line.s,"-e");
1191 if (str_len(x) != COOKIE) die_cookie();
1192 if (byte_diff(hash,COOKIE,x)) die_cookie();
1193 /* cookie is ok, file exists, lock's on, new file ends in '_' */
1194 if (!stralloc_copys(&fneditn,fnedit.s)) die_nomem();
1195 if (!stralloc_append(&fneditn,"_")) die_nomem();
1196 if (!stralloc_0(&fneditn)) die_nomem();
1197 fd = open_trunc(fneditn.s);
1199 strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fneditn.s,": ");
1200 substdio_fdbuf(&sstext,write,fd,textbuf,sizeof(textbuf));
1201 if (!stralloc_copys("ed,"")) die_nomem(); /* clear */
1202 if (!stralloc_copys(&text,"")) die_nomem();
1204 for (;;) { /* get message body */
1205 if (getln(&ssin,&line,&match,'\n') == -1)
1206 strerr_die2sys(111,FATAL,ERR_READ_INPUT);
1208 if (!stralloc_cat(&text,&line)) die_nomem();
1210 if (encin) { /* decode if necessary */
1212 decodeB(text.s,text.len,&line,FATAL);
1214 decodeQ(text.s,text.len,&line,FATAL);
1215 if (!stralloc_copy(&text,&line)) die_nomem();
1218 cpafter = text.s+text.len;
1222 while ((cpnext = cp + byte_chr(cp,cpafter-cp,'\n')) != cpafter) {
1223 i = byte_chr(cp,cpnext-cp,'%');
1224 if (i != (unsigned int) (cpnext - cp)) {
1225 if (!flaggoodfield) { /* TXT_EDIT_START/END */
1226 if (case_startb(cp+i,cpnext-cp-i,TXT_EDIT_START)) {
1227 /* start tag. Store users 'quote characters', e.g. '> ' */
1228 if (!stralloc_copyb("ed,cp,i)) die_nomem();
1235 if (case_startb(cp+i,cpnext-cp-i,TXT_EDIT_END)) {
1240 if (flaggoodfield) {
1241 if ((len += cpnext - cp - quoted.len + 1) > MAXEDIT)
1242 strerr_die1x(100,ERR_EDSIZE);
1244 if (quoted.len && cpnext-cp >= (int) quoted.len &&
1245 !str_diffn(cp,quoted.s,quoted.len))
1246 cp += quoted.len; /* skip quoting characters */
1247 cplast = cpnext - 1;
1248 if (*cplast == '\r') /* CRLF -> '\n' for base64 encoding */
1252 if (substdio_put(&sstext,cp,cplast-cp+1) == -1)
1253 strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fneditn.s,": ");
1258 strerr_die2x(100,FATAL,ERR_NO_MARK);
1259 if (substdio_flush(&sstext) == -1)
1260 strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fneditn.s,": ");
1261 if (fsync(fd) == -1)
1262 strerr_die6sys(111,FATAL,ERR_SYNC,dir,"/",fneditn.s,": ");
1263 if (fchmod(fd, 0600) == -1)
1264 strerr_die6sys(111,FATAL,ERR_CHMOD,dir,"/",fneditn.s,": ");
1265 if (close(fd) == -1)
1266 strerr_die6sys(111,FATAL,ERR_CLOSE,dir,"/",fneditn.s,": ");
1267 if (rename(fneditn.s,fnedit.s) == -1)
1268 strerr_die6sys(111,FATAL,ERR_MOVE,dir,"/",fneditn.s,": ");
1271 qmail_puts(&qq,TXT_EDIT_SUCCESS);
1272 qmail_puts(&qq,fname);
1273 qmail_puts(&qq,TXT_EDIT_FOR);
1274 if (!quote("ed,&outlocal)) die_nomem();
1275 qmail_put(&qq,quoted.s,quoted.len);
1276 qmail_puts(&qq,"@");
1277 qmail_put(&qq,outhost.s,outhost.len);
1278 qmail_puts(&qq,"\n");
1280 copy(&qq,"text/top",flagcd,FATAL);
1281 copy(&qq,"text/edit-done",flagcd,FATAL);
1283 qmail_to(&qq,sender); /* not necessarily from mod */
1285 } else if (act == AC_GET) {
1294 strerr_die2x(100,FATAL,ERR_NOT_AVAILABLE);
1295 qmail_puts(&qq,TXT_EZMLM_RESPONSE);
1297 copy(&qq,"text/top",flagcd,FATAL);
1299 pos = str_len(ACTION_GET);
1300 if (!case_starts(action,ACTION_GET))
1301 pos = str_len(ALT_GET);
1303 if (action[pos] == '.' || action [pos] == '_') pos++;
1304 scan_ulong(action + pos,&u);
1306 if (!stralloc_copys(&line,"archive/")) die_nomem();
1307 if (!stralloc_catb(&line,strnum,fmt_ulong(strnum,u / 100))) die_nomem();
1308 if (!stralloc_cats(&line,"/")) die_nomem();
1309 if (!stralloc_catb(&line,strnum,fmt_uint0(strnum,(unsigned int) (u % 100),2))) die_nomem();
1310 if (!stralloc_0(&line)) die_nomem();
1312 fd = open_read(line.s);
1314 if (errno != error_noent)
1315 strerr_die4sys(111,FATAL,ERR_OPEN,line.s,": ");
1317 copy(&qq,"text/get-bad",flagcd,FATAL);
1319 if (fstat(fd,&st) == -1)
1320 copy(&qq,"text/get-bad",flagcd,FATAL);
1321 else if (!(st.st_mode & 0100))
1322 copy(&qq,"text/get-bad",flagcd,FATAL);
1324 substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf));
1325 qmail_puts(&qq,"> ");
1327 r = substdio_get(&sstext,&ch,1);
1328 if (r == -1) strerr_die4sys(111,FATAL,ERR_READ,line.s,": ");
1330 qmail_put(&qq,&ch,1);
1331 if (ch == '\n') qmail_puts(&qq,"> ");
1333 qmail_puts(&qq,"\n");
1338 qmail_to(&qq,target.s);
1340 } else if (case_starts(action,ACTION_QUERY) ||
1341 case_starts(action,ALT_QUERY)) {
1342 qmail_puts(&qq,TXT_EZMLM_RESPONSE);
1344 copy(&qq,"text/top",flagcd,FATAL);
1345 if (pmod) { /* pmod points to static storage in issub(). Need to do this */
1346 /* before calling issub() again */
1347 if (!stralloc_copys(&to,pmod)) die_nomem();
1348 if (!stralloc_0(&to)) die_nomem();
1350 if (!stralloc_copy(&to,&target)) die_nomem();
1352 if (issub(workdir,target.s,(char *) 0,FATAL))
1353 copy(&qq,"text/sub-nop",flagcd,FATAL);
1355 copy(&qq,"text/unsub-nop",flagcd,FATAL);
1359 } else if (case_starts(action,ACTION_INFO) ||
1360 case_starts(action,ALT_INFO)) {
1361 qmail_puts(&qq,TXT_EZMLM_RESPONSE);
1363 copy(&qq,"text/top",flagcd,FATAL);
1364 copy(&qq,"text/info",flagcd,FATAL);
1366 qmail_to(&qq,target.s);
1368 } else if (case_starts(action,ACTION_FAQ) ||
1369 case_starts(action,ALT_FAQ)) {
1370 qmail_puts(&qq,TXT_EZMLM_RESPONSE);
1372 copy(&qq,"text/top",flagcd,FATAL);
1373 copy(&qq,"text/faq",flagcd,FATAL);
1375 qmail_to(&qq,target.s);
1377 } else if (pmod && (act == AC_HELP)) {
1378 qmail_puts(&qq,TXT_EZMLM_RESPONSE);
1380 copy(&qq,"text/top",flagcd,FATAL);
1381 copy(&qq,"text/mod-help",flagcd,FATAL);
1382 copy(&qq,"text/help",flagcd,FATAL);
1388 qmail_puts(&qq,TXT_EZMLM_RESPONSE);
1390 copy(&qq,"text/top",flagcd,FATAL);
1391 copy(&qq,"text/help",flagcd,FATAL);
1393 qmail_to(&qq,sender);
1396 if (*(err = qmail_close(&qq)) == '\0') {
1397 strnum[fmt_ulong(strnum,qmail_qp(&qq))] = 0;
1399 strerr_die2x(0,"ezmlm-manage: info: qp ",strnum);
1402 strerr_die3x(111,FATAL,ERR_TMP_QMAIL_QUEUE,err + 1);