chiark / gitweb /
Debianization and various other fixes.
[ezmlm] / ezmlm-manage.c
1 /*$Id: ezmlm-manage.c,v 1.86 1999/12/23 02:43:55 lindberg Exp $*/
2 /*$Name: ezmlm-idx-040 $*/
3
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include "error.h"
7 #include "stralloc.h"
8 #include "str.h"
9 #include "env.h"
10 #include "sig.h"
11 #include "slurp.h"
12 #include "getconf.h"
13 #include "strerr.h"
14 #include "byte.h"
15 #include "getln.h"
16 #include "case.h"
17 #include "qmail.h"
18 #include "substdio.h"
19 #include "readwrite.h"
20 #include "seek.h"
21 #include "quote.h"
22 #include "datetime.h"
23 #include "now.h"
24 #include "date822fmt.h"
25 #include "fmt.h"
26 #include "subscribe.h"
27 #include "cookie.h"
28 #include "sgetopt.h"
29 #include "copy.h"
30 #include "errtxt.h"
31 #include "idx.h"
32
33 #define FATAL "ezmlm-manage: fatal: "
34 #define INFO "ezmlm-manage: info: "
35
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 */
53
54 static const char hex[]="0123456789ABCDEF";
55 char urlstr[] = "%00";  /* to build a url-encoded version of a char */
56
57 int act = AC_NONE;      /* desired action */
58 unsigned int actlen = 0;/* str_len of above */
59 char *dir;
60 char *workdir;
61 char *sender;
62 void *psql = (void *) 0;
63
64 void die_usage() {
65   strerr_die1x(100,"ezmlm-manage: usage: ezmlm-manage "
66                 "[-bBcCdDeEfFlLmMnNqQsSuUvV] dir"); }
67
68 void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); }
69
70 void die_badaddr()
71 {
72   strerr_die2x(100,FATAL,ERR_BAD_ADDRESS);
73 }
74
75 void die_cookie()
76 {
77   strerr_die2x(100,FATAL,ERR_MOD_COOKIE);
78 }
79
80 stralloc inhost = {0};
81 stralloc outhost = {0};
82 stralloc inlocal = {0};
83 stralloc outlocal = {0};
84 stralloc key = {0};
85 stralloc mailinglist = {0};
86 stralloc mydtline = {0};
87 stralloc target = {0};
88 stralloc verptarget = {0};
89 stralloc confirm = {0};
90 stralloc line = {0};
91 stralloc qline = {0};
92 stralloc quoted = {0};
93 stralloc moddir = {0};
94 stralloc ddir = {0};
95 stralloc modsub = {0};
96 stralloc remote = {0};
97 stralloc from = {0};
98 stralloc to = {0};
99 stralloc owner = {0};
100 stralloc fromline = {0};
101 stralloc text = {0};
102 stralloc fnedit = {0};
103 stralloc fneditn = {0};
104 stralloc charset = {0};
105
106 datetime_sec when;
107 struct datetime dt;
108 int match;
109 unsigned int max;
110
111 char strnum[FMT_ULONG];
112 char date[DATE822FMT];
113 char hash[COOKIE];
114 char boundary[COOKIE];
115 datetime_sec hashdate;
116
117 char inbuf[1024];
118 substdio ssin = SUBSTDIO_FDBUF(read,0,inbuf,(int) sizeof(inbuf));
119 substdio ssin2 = SUBSTDIO_FDBUF(read,0,inbuf,(int) sizeof(inbuf));
120
121 substdio sstext;        /* editing texts and reading "from" */
122 char textbuf[512];
123
124 substdio ssfrom;        /* writing "from" */
125 char frombuf[512];
126
127 int fdlock;
128
129 void lock()
130 {
131     fdlock = open_append("lock");
132     if (fdlock == -1)
133       strerr_die4sys(111,FATAL,ERR_OPEN,dir,"/lock: ");
134     if (lock_ex(fdlock) == -1)
135       strerr_die4sys(111,FATAL,ERR_OBTAIN,dir,"/lock: ");
136 }
137
138 void unlock()
139 {
140     close(fdlock);
141 }
142
143 void make_verptarget()
144 /* puts target with '=' instead of last '@' into stralloc verptarget */
145 /* and does set_cpverptarget */
146 {
147   unsigned int i;
148
149   i = str_rchr(target.s,'@');
150   if (!stralloc_copyb(&verptarget,target.s,i)) die_nomem();
151   if (target.s[i]) {
152     if (!stralloc_append(&verptarget,"=")) die_nomem();
153     if (!stralloc_cats(&verptarget,target.s + i + 1)) die_nomem();
154   }
155   if (!stralloc_0(&verptarget)) die_nomem();
156   set_cpverptarget(verptarget.s);
157 }
158
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 */
164 char *adr;
165 {
166   int fdin;
167   int fdout;
168   unsigned long linetime;
169
170   if (!flagstorefrom || !frl->len) return;      /* nothing to store */
171   lock();
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: ");
178   } else {
179       substdio_fdbuf(&sstext,read,fdin,textbuf,(int) sizeof(textbuf));
180       for (;;) {
181         if (getln(&sstext,&line,&match,'\n') == -1)
182         strerr_die3sys(111,FATAL,ERR_READ,"from: ");
183         if (!match) break;
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: ");
188       }
189       close(fdin);
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: ");
207   unlock();
208 }
209
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.*/
221
222 char *adr;              /* target address */
223 char *act;              /* action */
224 {
225   int fd;
226   char *fl;
227   unsigned int pos;
228   unsigned long thistime;
229   unsigned long linetime;
230
231   if (!flagstorefrom) return 0;
232   if (fromline.len) {   /* easy! We got it in this message */
233     if (!stralloc_0(&fromline)) die_nomem(FATAL);
234     return fromline.s;
235   }                     /* need to recover it from DIR/from */
236   fl = 0;
237   (void) scan_ulong(act+3,&thistime);
238   if ((fd = open_read("from")) == -1)
239     if (errno == error_noent)
240       return 0;
241     else
242       strerr_die3x(111,FATAL,ERR_READ,"from: ");
243   substdio_fdbuf(&sstext,read,fd,textbuf,(int) sizeof(textbuf));
244   for (;;) {
245     if (getln(&sstext,&fromline,&match,'\n') == -1)
246       strerr_die3sys(111,FATAL,ERR_READ,"from: ");
247     if (!match) break;
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;
256         break;
257       }
258     }
259   }
260   close(fd);
261   return fl;
262 }
263
264 int hashok(action,ac)
265 char *action;
266 char *ac;
267 {
268   char *x;
269   datetime_sec u;
270
271   x = action + 3;
272   x += scan_ulong(x,&u);
273   hashdate = u;
274   if (hashdate > when) return 0;
275   if (hashdate < when - 1000000) return 0;
276
277   u = hashdate;
278   strnum[fmt_ulong(strnum,(unsigned long) u)] = 0;
279   cookie(hash,key.s,key.len - flagdig,strnum,target.s,ac);
280
281   if (*x == '.') ++x;
282   if (str_len(x) != COOKIE) return 0;
283   return byte_equal(hash,COOKIE,x);
284 }
285
286 struct qmail qq;
287 int qqwrite(fd,buf,len) int fd; char *buf; unsigned int len;
288 {
289   qmail_put(&qq,buf,len);
290   return len;
291 }
292
293 char qqbuf[1];
294 substdio ssqq = SUBSTDIO_FDBUF(qqwrite,-1,qqbuf,(int) sizeof(qqbuf));
295
296 int code_qput(s,n)
297 char *s;
298 unsigned int n;
299 {
300     if (!flagcd)
301       qmail_put(&qq,s,n);
302     else {
303       if (flagcd == 'B')
304         encodeB(s,n,&qline,0,FATAL);
305       else
306         encodeQ(s,n,&qline,FATAL);
307       qmail_put(&qq,qline.s,qline.len);
308     }
309     return 0;           /* always succeeds */
310 }
311
312 int subto(s,l)
313 char *s;
314 unsigned int l;
315 {
316   qmail_put(&qq,"T",1);
317   qmail_put(&qq,s,l);
318   qmail_put(&qq,"",1);
319   return (int) l;
320 }
321
322 int code_subto(s,l)
323 char *s;
324 unsigned int l;
325 {
326   code_qput(s,l);
327   code_qput("\n",1);
328   return (int) l;
329 }
330
331 int dummy_to(s,l)
332 char *s;        /* ignored */
333 unsigned int l;
334 {
335   return (int) l;
336 }
337
338 void transferenc()
339 {
340         if (flagcd) {
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: ");
346           if (flagcd == 'Q')
347             qmail_puts(&qq,"quoted-printable\n\n");
348           else
349             qmail_puts(&qq,"base64\n\n");
350         } else
351           qmail_puts(&qq,"\n");
352 }
353
354 void to_owner()
355 {
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);
361 }
362
363 void mod_bottom()
364 {
365       copy(&qq,"text/mod-sub",flagcd,FATAL);
366       copy(&qq,"text/bottom",flagcd,FATAL);
367       code_qput(TXT_SUPPRESSED,str_len(TXT_SUPPRESSED));
368       if (flagcd) {
369         qmail_puts(&qq,"\n--");
370         qmail_put(&qq,boundary,COOKIE);
371         qmail_puts(&qq,"--\n");
372       }
373       if (flagcd == 'B') {
374         encodeB("",0,&line,2,FATAL);    /* flush */
375         qmail_put(&qq,line.s,line.len);
376       }
377       qmail_from(&qq,from.s);
378 }
379 void msg_headers()
380                 /* Writes all the headers up to but not including subject */
381 {
382   int flaggoodfield;
383   int flagfromline;
384   int flaggetfrom;
385   unsigned int pos;
386
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);
392   }
393   if (!quote(&quoted,&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);
400   qmail_puts(&qq,"@");
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)))
411      die_nomem();
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 */
425   else
426     qmail_puts(&qq,"-help@");
427   qmail_put(&qq,outhost.s,outhost.len);
428   qmail_puts(&qq,"\nTo: ");
429   if (!quote2(&quoted,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);
438
439   flaggoodfield = 0;
440   flagfromline = 0;
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));
444   for (;;) {
445     if (getln(&ssin,&line,&match,'\n') == -1)
446       strerr_die2sys(111,FATAL,ERR_READ_INPUT);
447     if (!match) break;
448     if (line.len == 1) break;
449     if ((line.s[0] != ' ') && (line.s[0] != '\t')) {
450       flagfromline = 0;
451       flaggoodfield = 0;
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:"))
458         flaggoodfield = 1;
459       else if (case_startb(line.s,line.len,"received:"))
460         flaggoodfield = 1;
461       else if (case_startb(line.s,line.len,"content-transfer-encoding:")) {
462         pos = 26;
463         while (line.s[pos] == ' ' || line.s[pos] == '\t') ++pos;
464         if (case_startb(line.s+pos,line.len-pos,"base64"))
465           encin = 'B';
466         else if (case_startb(line.s+pos,line.len-pos,"quoted-printable"))
467           encin = 'Q';
468       } else if (flaggetfrom && case_startb(line.s,line.len,"from:")) {
469         flagfromline = 1;               /* for logging subscriber data */
470         pos = 5;
471         while (line.s[pos] == ' ' || line.s[pos] == '\t') ++pos;
472         if (!stralloc_copyb(&fromline,line.s + pos,line.len - pos - 1))
473           die_nomem();
474       }
475     } else {
476       if (flagfromline == 1)            /* scrap terminal '\n' */
477         if (!stralloc_catb(&fromline,line.s,line.len - 1)) die_nomem();
478     }
479     if (flaggoodfield)
480       qmail_put(&qq,line.s,line.len);
481   }
482   qmail_puts(&qq,"MIME-Version: 1.0\n");
483   if (flagcd) {
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);
488   } else {
489     qmail_puts(&qq,"Content-type: text/plain; charset=");
490     qmail_puts(&qq,charset.s);
491   }
492   qmail_puts(&qq,"\n");
493 }
494
495 int geton(action)
496 char *action;
497 {
498   char *fl;
499   int r;
500   unsigned int i;
501   unsigned char ch;
502
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))) {
505     case 1:
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);
518               } else {
519                 qmail_put(&qq,verptarget.s + i, 1);
520               }
521             }
522             qmail_puts(&qq,"@");
523             qmail_put(&qq,outhost.s,outhost.len);       /* safe */
524             qmail_puts(&qq,">\n");
525             qmail_puts(&qq,TXT_WELCOME);
526             if (!quote(&quoted,&outlocal)) die_nomem();
527             qmail_put(&qq,quoted.s,quoted.len);
528             qmail_puts(&qq,"@");
529             qmail_put(&qq,outhost.s,outhost.len);
530             qmail_puts(&qq,"\n");
531             transferenc();
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);
541             break;
542     default:
543             if (str_start(action,ACTION_TC))
544               strerr_die2x(0,INFO,ERR_SUB_NOP);
545             qmail_puts(&qq,TXT_EZMLM_RESPONSE);
546             transferenc();
547             copy(&qq,"text/top",flagcd,FATAL);
548             copy(&qq,"text/sub-nop",flagcd,FATAL);
549             break;
550   }
551   if (flagdig == FLD_DENY || flagdig == FLD_ALLOW)
552     strerr_die3x(0,INFO,ERR_EXTRA_SUB,target.s);
553   return r;
554 }
555
556 int getoff(action)
557 char *action;
558 {
559   int r;
560
561   switch((r = subscribe(workdir,target.s,0,"","-",1,-1,(char *) 0,FATAL))) {
562                         /* no comment for unsubscribe */
563     case 1:
564             qmail_puts(&qq,TXT_GOODBYE);
565             if (!quote(&quoted,&outlocal)) die_nomem();
566             qmail_put(&qq,quoted.s,quoted.len);
567             qmail_puts(&qq,"@");
568             qmail_put(&qq,outhost.s,outhost.len);
569             qmail_puts(&qq,"\n\n");
570             transferenc();
571             copy(&qq,"text/top",flagcd,FATAL);
572             copy(&qq,"text/unsub-ok",flagcd,FATAL);
573             break;
574     default:
575             qmail_puts(&qq,TXT_EZMLM_RESPONSE);
576             transferenc();
577             copy(&qq,"text/top",flagcd,FATAL);
578             copy(&qq,"text/unsub-nop",flagcd,FATAL);
579             break;
580   }
581   if (flagdig == FLD_DENY || flagdig == FLD_ALLOW)
582     strerr_die3x(0,INFO,ERR_EXTRA_UNSUB,target.s);
583   return r;
584 }
585
586 void doconfirm(act)
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! */
590 {
591   unsigned int i;
592
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 */
608
609   qmail_puts(&qq,"Reply-To: ");
610   if (!quote2(&quoted,confirm.s)) die_nomem();
611   qmail_put(&qq,quoted.s,quoted.len);
612   qmail_puts(&qq,"\n");
613   if (!stralloc_0(&confirm)) die_nomem();
614
615   qmail_puts(&qq,"Subject: ");
616   if (*act == ACTION_SC[0] || *act == ACTION_UC[0])
617     qmail_puts(&qq,TXT_USRCONFIRM);
618   else
619     qmail_puts(&qq,TXT_MODCONFIRM);
620   if (*act == ACTION_SC[0] || *act == ACTION_TC[0])
621     qmail_puts(&qq,TXT_SUBSCRIBE_TO);
622   else
623     qmail_puts(&qq,TXT_UNSUBSCRIBE_FROM);
624   if (!quote(&quoted,&outlocal)) die_nomem();
625   qmail_put(&qq,quoted.s,quoted.len);
626   qmail_puts(&qq,"@");
627   qmail_put(&qq,outhost.s,outhost.len);
628   qmail_puts(&qq,"\n");
629   transferenc();
630     copy(&qq,"text/top",flagcd,FATAL);
631 }
632
633 void sendtomods()
634 {
635   putsubs(moddir.s,0L,52L,subto,1,FATAL);
636 }
637
638 void copybottom()
639 {
640   if (flagbottom || act == AC_HELP) {
641     copy(&qq,"text/bottom",flagcd,FATAL);
642     if (flagcd) {
643       if (flagcd == 'B') {
644         encodeB("",0,&line,2,FATAL);    /* flush */
645         qmail_put(&qq,line.s,line.len);
646       }
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");
651     }
652     qmail_puts(&qq,"Return-Path: <");
653     if (!quote2(&quoted,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);
660     if (flagcd) {
661       qmail_puts(&qq,"\n--");
662       qmail_put(&qq,boundary,COOKIE);
663       qmail_puts(&qq,"--\n");
664     }
665   } else {
666     if (flagcd == 'B') {
667       encodeB("",0,&line,2,FATAL);      /* flush even if no bottom */
668       qmail_put(&qq,line.s,line.len);
669     }
670   }
671
672   qmail_from(&qq,from.s);
673 }
674
675 int main(argc,argv)
676 int argc;
677 char **argv;
678 {
679   char *local;
680   char *def;
681   char *action;
682   char *x, *y;
683   char *fname;
684   char *pmod;
685   char *err;
686   char *cp,*cpfirst,*cplast,*cpnext,*cpafter;
687   int flagmod;
688   int flagremote;
689   int flagpublic;
690   int opt,r;
691   unsigned int i;
692   unsigned int len;
693   int fd;
694   int flagdone;
695   register char ch;
696
697   (void) umask(022);
698   sig_pipeignore();
699   when = now();
700
701   while ((opt = getopt(argc,argv,"bBcCdDeEfFlLmMnNqQsSuUvV")) != opteof)
702     switch(opt) {
703       case 'b': flagbottom = 1; break;
704       case 'B': flagbottom = 0; break;
705       case 'c': flagget = 1; break;
706       case 'C': flagget = 0; break;
707       case 'd':
708       case 'e': flagedit = 1; break;
709       case 'D':
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;
725       case 'v':
726       case 'V': strerr_die2x(0,
727                 "ezmlm-manage version: ezmlm-0.53+",EZIDX_VERSION);
728       default:
729         die_usage();
730     }
731
732   dir = argv[optind];
733   if (!dir) die_usage();
734
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");
740
741   if (!*sender)
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);
747
748   if (chdir(dir) == -1)
749     strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": ");
750
751   switch(slurp("key",&key,32)) {
752     case -1:
753       strerr_die4sys(111,FATAL,ERR_READ,dir,"/key: ");
754     case 0:
755       strerr_die4x(100,FATAL,dir,"/key",ERR_NOEXIST);
756   }
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';
767       }
768     }
769   } else
770     if (!stralloc_copys(&charset,TXT_DEF_CHARSET)) die_nomem();
771   if (!stralloc_0(&charset)) die_nomem();
772
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 */
782   }
783
784   if (!stralloc_copys(&ddir,dir)) die_nomem();
785
786   if (case_starts(action,"digest")) {                   /* digest */
787     action += 6;
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();
796     flagdig = FLD_ALLOW;
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();
802     flagdig = FLD_DENY;
803   }
804   if (flagdig)                          /* zap '-' after db specifier */
805     if (*(action++) != '-') die_badaddr();
806
807   if (!stralloc_0(&ddir)) die_nomem();
808   workdir = ddir.s;
809   set_cpoutlocal(&outlocal);
810
811   if (!stralloc_copys(&target,sender)) die_nomem();
812   if (action[0]) {
813     i = str_chr(action,'-');
814     if (action[i]) {
815       action[i] = 0;
816       if (!stralloc_copys(&target,action + i + 1)) die_nomem();
817       i = byte_rchr(target.s,target.len,'=');
818       if (i < target.len)
819         target.s[i] = '@';
820     }
821   }
822   if (!stralloc_0(&target)) die_nomem();
823   set_cptarget(target.s);       /* for copy() */
824   make_verptarget();
825
826   flagmod = getconf_line(&modsub,"modsub",0,FATAL,dir);
827   flagremote = getconf_line(&remote,"remote",0,FATAL,dir);
828
829   if (case_equals(action,ACTION_LISTN) ||
830                 case_equals(action,ALT_LISTN))
831     act = AC_LISTN;
832   else if (case_equals(action,ACTION_LIST) ||
833                 case_equals(action,ALT_LIST))
834     act = AC_LIST;
835   else if (case_starts(action,ACTION_GET) ||
836                 case_starts(action,ALT_GET))
837     act = AC_GET;
838   else if (case_equals(action,ACTION_HELP) ||
839                 case_equals(action,ALT_HELP))
840     act = AC_HELP;
841   else if (case_starts(action,ACTION_EDIT) ||
842                 case_starts(action,ALT_EDIT))
843     act = AC_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); }
848
849                         /* NOTE: act is needed in msg_headers(). */
850                         /* Yes, this needs to be cleaned up! */
851
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();
857     } else {
858       if (!stralloc_copys(&moddir,dir)) die_nomem();
859       if (!stralloc_cats(&moddir,"/mod")) die_nomem();
860     }
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? */
868     else
869       pmod = issub(moddir.s,target.s,(char *) 0,FATAL);
870                                 /* target = moderator? */
871    } else
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);
883
884   if (flagdig == FLD_DENY)
885     if (!pmod || !flagremote)   /* only mods can do */
886       strerr_die1x(100,ERR_NOT_ALLOWED);
887
888   if (act == AC_NONE) {         /* none of the above */
889     if (case_equals(action,ACTION_SUBSCRIBE) ||
890                 case_equals(action,ALT_SUBSCRIBE))
891       act = AC_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;
896   }
897
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();
902
903   if (qmail_open(&qq,(stralloc *) 0) == -1)
904     strerr_die2sys(111,FATAL,ERR_QMAIL_QUEUE);
905   msg_headers();
906
907   if (act == AC_SUBSCRIBE) {
908     if (pmod && flagremote) {
909       doconfirm(ACTION_TC);
910       copy(&qq,"text/mod-sub-confirm",flagcd,FATAL);
911       copybottom();
912       qmail_to(&qq,pmod);
913     } else if (flagsubconf) {
914       doconfirm(ACTION_SC);
915       copy(&qq,"text/sub-confirm",flagcd,FATAL);
916       copybottom();
917       qmail_to(&qq,target.s);
918     } else {                            /* normal subscribe, no confirm */
919       r = geton(action);                /* should be rarely used. */
920       copybottom();
921       if (flagnotify) qmail_to(&qq,target.s);
922       if (r && flagverbose > 1) to_owner();
923     }
924
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);
932         copybottom();
933         sendtomods();
934       } else {
935         r = geton(action);
936         copybottom();
937         qmail_to(&qq,target.s);
938         if (r && flagverbose > 1) to_owner();
939       }
940     } else {
941       doconfirm(ACTION_SC);
942       copy(&qq,"text/sub-bad",flagcd,FATAL);
943       copybottom();
944       qmail_to(&qq,target.s);
945     }
946
947   } else if (str_start(action,ACTION_TC)) {
948     if (hashok(action,ACTION_TC)) {
949       r = geton(action);
950       mod_bottom();
951       if (flagnotify) qmail_to(&qq,target.s);   /* unless suppressed */
952       if (r && flagverbose > 1) to_owner();
953     } else {
954       if (!pmod || !flagremote) /* else anyone can get a good -tc. */
955         die_cookie();
956       doconfirm(ACTION_TC);
957       copy(&qq,"text/sub-bad",flagcd,FATAL);
958       copybottom();
959       qmail_to(&qq,pmod);
960     }
961
962   } else if (act == AC_UNSUBSCRIBE) {
963     if (flagunsubconf) {
964       if (pmod && flagremote) {
965         doconfirm(ACTION_VC);
966         copy(&qq,"text/mod-unsub-confirm",flagcd,FATAL);
967         copybottom();
968         qmail_to(&qq,pmod);
969       } else {
970         doconfirm(ACTION_UC);
971         copy(&qq,"text/unsub-confirm",flagcd,FATAL);
972         copybottom();
973         qmail_to(&qq,target.s);
974       }
975     } else if (flagunsubismod && flagmod) {
976         doconfirm(ACTION_VC);
977         copy(&qq,"text/mod-unsub-confirm",flagcd,FATAL);
978         copybottom();
979         sendtomods();
980     } else {
981       r = getoff(action);
982       copybottom();
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();
986     }
987
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);
995         copybottom();
996         sendtomods();
997       } else {
998         r = getoff(action);
999         copybottom();
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();
1003       }
1004     } else {
1005       doconfirm(ACTION_UC);
1006       copy(&qq,"text/unsub-bad",flagcd,FATAL);
1007       copybottom();
1008       qmail_to(&qq,target.s);
1009     }
1010
1011   } else if (str_start(action,ACTION_VC)) {
1012     if (hashok(action,ACTION_VC)) {
1013       r = getoff(action);
1014       if (!r && flagmod)
1015         strerr_die2x(0,INFO,ERR_UNSUB_NOP);
1016       mod_bottom();
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 */
1023     } else {
1024       if (!pmod || !flagremote) /* else anyone can get a good -vc. */
1025         die_cookie();
1026       doconfirm(ACTION_VC);
1027       copy(&qq,"text/unsub-bad",flagcd,FATAL);
1028       copybottom();
1029       qmail_to(&qq,pmod);
1030     }
1031
1032   } else if (act == AC_LIST || act == AC_LISTN) {
1033
1034     if (!flaglist || (!flagmod && !flagremote))
1035       strerr_die2x(100,FATAL,ERR_NOT_AVAILABLE);
1036     if (!pmod)
1037       strerr_die2x(100,FATAL,ERR_NOT_ALLOWED);
1038     qmail_puts(&qq,TXT_EZMLM_RESPONSE);
1039     transferenc();
1040     copy(&qq,"text/top",flagcd,FATAL);
1041
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);
1045     } else                      /* listn */
1046       i = putsubs(workdir,0L,52L,dummy_to,1,FATAL);
1047
1048     (void) code_qput("\n  ======> ",11);
1049     (void) code_qput(strnum,fmt_ulong(strnum,i));
1050     (void) code_qput("\n",1);
1051     copybottom();
1052     qmail_to(&qq,pmod);
1053
1054   } else if (act == AC_LOG) {
1055     action += actlen;
1056     if (*action == '.' || *action == '_') ++action;
1057     if (!flaglist || !flagremote)
1058       strerr_die2x(100,FATAL,ERR_NOT_AVAILABLE);
1059     if (!pmod)
1060       strerr_die2x(100,FATAL,ERR_NOT_ALLOWED);
1061     qmail_puts(&qq,TXT_EZMLM_RESPONSE);
1062     transferenc();
1063     searchlog(workdir,action,code_subto,FATAL);
1064     copybottom();
1065     qmail_to(&qq,pmod);
1066
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);
1071     if (!pmod)
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] = '-';
1088       }
1089       switch(slurp(fnedit.s,&text,1024)) {      /* entire file! */
1090         case -1:
1091           strerr_die6sys(111,FATAL,ERR_READ,dir,"/",fnedit.s,": ");
1092         case 0:
1093           strerr_die5x(100,FATAL,dir,"/",fnedit.s,ERR_NOEXIST);
1094       }
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; }
1100       }
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);
1117
1118       qmail_puts(&qq,"Reply-To: ");
1119       if (!quote2(&quoted,confirm.s)) die_nomem();
1120       qmail_put(&qq,quoted.s,quoted.len);
1121       qmail_puts(&qq,"\n");
1122       if (!stralloc_0(&confirm)) die_nomem();
1123
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(&quoted,&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");
1132       transferenc();
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);
1140
1141     } else {    /* -edit only, so output list of editable files */
1142       qmail_puts(&qq,TXT_EDIT_LIST);
1143       transferenc();
1144       copy(&qq,"text/top",flagcd,FATAL);
1145       copy(&qq,"text/edit-list",flagcd,FATAL);
1146     }
1147     qmail_puts(&qq,"\n\n");
1148     copybottom();
1149     qmail_to(&qq,pmod);
1150
1151   } else if (str_start(action,ACTION_ED)) {
1152     datetime_sec u;
1153     int flaggoodfield;
1154     x = action + LENGTH_ED;
1155     x += scan_ulong(x,&u);
1156     if ((u > when) || (u < when - 100000)) die_cookie();
1157     if (*x == '.') ++x;
1158     fname = x;
1159     x += str_chr(x,'.');
1160     if (!*x) die_cookie();
1161     *x = (char) 0;
1162     ++x;
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 = '-';
1172     }
1173
1174     lock();                     /* file must not change while here */
1175
1176     switch (slurp(fnedit.s,&text,1024)) {
1177       case -1:
1178         strerr_die6sys(111,FATAL,ERR_READ,dir,"/",fnedit.s,": ");
1179       case 0:
1180         strerr_die5x(100,FATAL,dir,"/",fnedit.s,ERR_NOEXIST);
1181     }
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; }
1187     }
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);
1198     if (fd == -1)
1199       strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fneditn.s,": ");
1200     substdio_fdbuf(&sstext,write,fd,textbuf,sizeof(textbuf));
1201     if (!stralloc_copys(&quoted,"")) die_nomem();       /* clear */
1202     if (!stralloc_copys(&text,"")) die_nomem();
1203
1204     for (;;) {                  /* get message body */
1205       if (getln(&ssin,&line,&match,'\n') == -1)
1206         strerr_die2sys(111,FATAL,ERR_READ_INPUT);
1207       if (!match) break;
1208       if (!stralloc_cat(&text,&line)) die_nomem();
1209     }
1210     if (encin) {        /* decode if necessary */
1211       if (encin == 'B')
1212         decodeB(text.s,text.len,&line,FATAL);
1213       else
1214         decodeQ(text.s,text.len,&line,FATAL);
1215       if (!stralloc_copy(&text,&line)) die_nomem();
1216     }
1217     cp = text.s;
1218     cpafter = text.s+text.len;
1219     flaggoodfield = 0;
1220     flagdone = 0;
1221     len = 0;
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(&quoted,cp,i)) die_nomem();
1229             flaggoodfield = 1;
1230             cp = cpnext + 1;
1231             cpfirst = cp;
1232             continue;
1233           }
1234         } else
1235           if (case_startb(cp+i,cpnext-cp-i,TXT_EDIT_END)) {
1236             flagdone = 1;
1237             break;
1238           }
1239       }
1240       if (flaggoodfield) {
1241         if ((len += cpnext - cp - quoted.len + 1) > MAXEDIT)
1242           strerr_die1x(100,ERR_EDSIZE);
1243
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 */
1249           *cplast = '\n';
1250         else
1251           ++cplast;
1252         if (substdio_put(&sstext,cp,cplast-cp+1) == -1)
1253             strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fneditn.s,": ");
1254       }
1255       cp = cpnext + 1;
1256     }
1257     if (!flagdone)
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,": ");
1269
1270     unlock();
1271     qmail_puts(&qq,TXT_EDIT_SUCCESS);
1272     qmail_puts(&qq,fname);
1273     qmail_puts(&qq,TXT_EDIT_FOR);
1274     if (!quote(&quoted,&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");
1279     transferenc();
1280     copy(&qq,"text/top",flagcd,FATAL);
1281     copy(&qq,"text/edit-done",flagcd,FATAL);
1282     copybottom();
1283     qmail_to(&qq,sender);       /* not necessarily from mod */
1284
1285   } else if (act == AC_GET) {
1286
1287     unsigned long u;
1288     struct stat st;
1289     char ch;
1290     int r;
1291     unsigned int pos;
1292
1293     if (!flagget)
1294       strerr_die2x(100,FATAL,ERR_NOT_AVAILABLE);
1295     qmail_puts(&qq,TXT_EZMLM_RESPONSE);
1296     transferenc();
1297     copy(&qq,"text/top",flagcd,FATAL);
1298
1299     pos = str_len(ACTION_GET);
1300     if (!case_starts(action,ACTION_GET))
1301       pos = str_len(ALT_GET);
1302
1303     if (action[pos] == '.' || action [pos] == '_') pos++;
1304     scan_ulong(action + pos,&u);
1305
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();
1311
1312     fd = open_read(line.s);
1313     if (fd == -1)
1314       if (errno != error_noent)
1315         strerr_die4sys(111,FATAL,ERR_OPEN,line.s,": ");
1316       else
1317         copy(&qq,"text/get-bad",flagcd,FATAL);
1318     else {
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);
1323       else {
1324         substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf));
1325         qmail_puts(&qq,"> ");
1326         for (;;) {
1327           r = substdio_get(&sstext,&ch,1);
1328           if (r == -1) strerr_die4sys(111,FATAL,ERR_READ,line.s,": ");
1329           if (r == 0) break;
1330           qmail_put(&qq,&ch,1);
1331           if (ch == '\n') qmail_puts(&qq,"> ");
1332         }
1333         qmail_puts(&qq,"\n");
1334       }
1335       close(fd);
1336     }
1337     copybottom();
1338     qmail_to(&qq,target.s);
1339
1340   } else if (case_starts(action,ACTION_QUERY) ||
1341                 case_starts(action,ALT_QUERY)) {
1342     qmail_puts(&qq,TXT_EZMLM_RESPONSE);
1343     transferenc();
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();
1349     } else {
1350       if (!stralloc_copy(&to,&target)) die_nomem();
1351     }
1352     if (issub(workdir,target.s,(char *) 0,FATAL))
1353       copy(&qq,"text/sub-nop",flagcd,FATAL);
1354     else
1355       copy(&qq,"text/unsub-nop",flagcd,FATAL);
1356     copybottom();
1357     qmail_to(&qq,to.s);
1358
1359   } else if (case_starts(action,ACTION_INFO) ||
1360                 case_starts(action,ALT_INFO)) {
1361     qmail_puts(&qq,TXT_EZMLM_RESPONSE);
1362     transferenc();
1363     copy(&qq,"text/top",flagcd,FATAL);
1364     copy(&qq,"text/info",flagcd,FATAL);
1365     copybottom();
1366     qmail_to(&qq,target.s);
1367
1368   } else if (case_starts(action,ACTION_FAQ) ||
1369                 case_starts(action,ALT_FAQ)) {
1370     qmail_puts(&qq,TXT_EZMLM_RESPONSE);
1371     transferenc();
1372     copy(&qq,"text/top",flagcd,FATAL);
1373     copy(&qq,"text/faq",flagcd,FATAL);
1374     copybottom();
1375     qmail_to(&qq,target.s);
1376
1377   } else if (pmod && (act == AC_HELP)) {
1378     qmail_puts(&qq,TXT_EZMLM_RESPONSE);
1379     transferenc();
1380     copy(&qq,"text/top",flagcd,FATAL);
1381     copy(&qq,"text/mod-help",flagcd,FATAL);
1382     copy(&qq,"text/help",flagcd,FATAL);
1383     copybottom();
1384     qmail_to(&qq,pmod);
1385
1386   } else {
1387     act = AC_HELP;
1388     qmail_puts(&qq,TXT_EZMLM_RESPONSE);
1389     transferenc();
1390     copy(&qq,"text/top",flagcd,FATAL);
1391     copy(&qq,"text/help",flagcd,FATAL);
1392     copybottom();
1393     qmail_to(&qq,sender);
1394   }
1395
1396   if (*(err = qmail_close(&qq)) == '\0') {
1397       strnum[fmt_ulong(strnum,qmail_qp(&qq))] = 0;
1398       closesql();
1399       strerr_die2x(0,"ezmlm-manage: info: qp ",strnum);
1400   } else {
1401       closesql();
1402       strerr_die3x(111,FATAL,ERR_TMP_QMAIL_QUEUE,err + 1);
1403   }
1404 }
1405