Import ezmlm-idx 0.40
[ezmlm] / ezmlm-manage.c
CommitLineData
f8beb284
MW
1/*$Id: ezmlm-manage.c,v 1.86 1999/12/23 02:43:55 lindberg Exp $*/
2/*$Name: ezmlm-idx-040 $*/
3
5b62e993
MW
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"
f8beb284
MW
28#include "sgetopt.h"
29#include "copy.h"
30#include "errtxt.h"
31#include "idx.h"
5b62e993
MW
32
33#define FATAL "ezmlm-manage: fatal: "
f8beb284
MW
34#define INFO "ezmlm-manage: info: "
35
36int flagverbose = 0; /* default: Owner not informed about subdb changes */
37 /* 1 => notified for failed unsub, 2 => for all */
38int flagnotify = 1; /* notify subscriber of completed events. 0 also */
39 /* suppresses all subscriber communication for */
40 /* [un]sub if -U/-S is used */
41int flagbottom = 1; /* default: copy request & admin info to message */
42int flaglist = 0; /* default: do not reply to -list */
43int flagget = 1; /* default: service -get requests */
44int flagsubconf = 1; /* default: require user-confirm for subscribe */
45int flagunsubconf = 1; /* default: require user-confirm for unsubscribe */
46int flagunsubismod = 0; /* default: do not require moderator approval to */
47 /* unsubscribe from moderated list */
48int flagedit = 0; /* default: text file edit not allowed */
49int flagstorefrom = 1; /* default: store from: line for subscribes */
50char flagcd = '\0'; /* default: do not use _Q_uoted printable or _B_ase64 */
51char encin = '\0'; /* encoding of incoming message */
52int flagdig = 0; /* request is not for digest list */
53
54static const char hex[]="0123456789ABCDEF";
55char urlstr[] = "%00"; /* to build a url-encoded version of a char */
56
57int act = AC_NONE; /* desired action */
58unsigned int actlen = 0;/* str_len of above */
59char *dir;
60char *workdir;
61char *sender;
62void *psql = (void *) 0;
63
64void die_usage() {
65 strerr_die1x(100,"ezmlm-manage: usage: ezmlm-manage "
66 "[-bBcCdDeEfFlLmMnNqQsSuUvV] dir"); }
67
68void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); }
69
5b62e993
MW
70void die_badaddr()
71{
f8beb284
MW
72 strerr_die2x(100,FATAL,ERR_BAD_ADDRESS);
73}
74
75void die_cookie()
76{
77 strerr_die2x(100,FATAL,ERR_MOD_COOKIE);
5b62e993
MW
78}
79
80stralloc inhost = {0};
81stralloc outhost = {0};
82stralloc inlocal = {0};
83stralloc outlocal = {0};
84stralloc key = {0};
85stralloc mailinglist = {0};
f8beb284
MW
86stralloc mydtline = {0};
87stralloc target = {0};
88stralloc verptarget = {0};
89stralloc confirm = {0};
90stralloc line = {0};
91stralloc qline = {0};
92stralloc quoted = {0};
93stralloc moddir = {0};
94stralloc ddir = {0};
95stralloc modsub = {0};
96stralloc remote = {0};
97stralloc from = {0};
98stralloc to = {0};
99stralloc owner = {0};
100stralloc fromline = {0};
101stralloc text = {0};
102stralloc fnedit = {0};
103stralloc fneditn = {0};
104stralloc charset = {0};
5b62e993
MW
105
106datetime_sec when;
107struct datetime dt;
f8beb284
MW
108int match;
109unsigned int max;
5b62e993
MW
110
111char strnum[FMT_ULONG];
112char date[DATE822FMT];
113char hash[COOKIE];
f8beb284 114char boundary[COOKIE];
5b62e993 115datetime_sec hashdate;
5b62e993 116
f8beb284
MW
117char inbuf[1024];
118substdio ssin = SUBSTDIO_FDBUF(read,0,inbuf,(int) sizeof(inbuf));
119substdio ssin2 = SUBSTDIO_FDBUF(read,0,inbuf,(int) sizeof(inbuf));
120
121substdio sstext; /* editing texts and reading "from" */
122char textbuf[512];
123
124substdio ssfrom; /* writing "from" */
125char frombuf[512];
126
127int fdlock;
128
129void 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
138void unlock()
139{
140 close(fdlock);
141}
142
143void 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
159void 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! */
163stralloc *frl; /* from line */
164char *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
210char *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
222char *adr; /* target address */
223char *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
264int hashok(action,ac)
5b62e993 265char *action;
f8beb284 266char *ac;
5b62e993
MW
267{
268 char *x;
f8beb284 269 datetime_sec u;
5b62e993 270
f8beb284 271 x = action + 3;
5b62e993
MW
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;
f8beb284
MW
278 strnum[fmt_ulong(strnum,(unsigned long) u)] = 0;
279 cookie(hash,key.s,key.len - flagdig,strnum,target.s,ac);
5b62e993
MW
280
281 if (*x == '.') ++x;
282 if (str_len(x) != COOKIE) return 0;
283 return byte_equal(hash,COOKIE,x);
284}
285
286struct qmail qq;
287int qqwrite(fd,buf,len) int fd; char *buf; unsigned int len;
288{
289 qmail_put(&qq,buf,len);
290 return len;
291}
f8beb284 292
5b62e993 293char qqbuf[1];
f8beb284 294substdio ssqq = SUBSTDIO_FDBUF(qqwrite,-1,qqbuf,(int) sizeof(qqbuf));
5b62e993 295
f8beb284
MW
296int code_qput(s,n)
297char *s;
298unsigned 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}
5b62e993 311
f8beb284
MW
312int subto(s,l)
313char *s;
314unsigned 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}
5b62e993 321
f8beb284
MW
322int code_subto(s,l)
323char *s;
324unsigned int l;
5b62e993 325{
f8beb284
MW
326 code_qput(s,l);
327 code_qput("\n",1);
328 return (int) l;
329}
5b62e993 330
f8beb284
MW
331int dummy_to(s,l)
332char *s; /* ignored */
333unsigned int l;
334{
335 return (int) l;
336}
5b62e993 337
f8beb284
MW
338void 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
354void 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
363void 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");
5b62e993 372 }
f8beb284
MW
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}
379void 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;
5b62e993 386
f8beb284
MW
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: ");
5b62e993 391 qmail_put(&qq,line.s,line.len);
f8beb284
MW
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);
5b62e993 438
f8beb284
MW
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);
5b62e993 491 }
f8beb284
MW
492 qmail_puts(&qq,"\n");
493}
5b62e993 494
f8beb284
MW
495int geton(action)
496char *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;
5b62e993
MW
554}
555
f8beb284
MW
556int getoff(action)
557char *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}
5b62e993 585
f8beb284
MW
586void 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.*/
589char *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
633void sendtomods()
634{
635 putsubs(moddir.s,0L,52L,subto,1,FATAL);
636}
637
638void 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
675int main(argc,argv)
5b62e993
MW
676int argc;
677char **argv;
678{
5b62e993 679 char *local;
f8beb284 680 char *def;
5b62e993 681 char *action;
f8beb284
MW
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;
5b62e993 693 int fd;
f8beb284
MW
694 int flagdone;
695 register char ch;
5b62e993 696
f8beb284 697 (void) umask(022);
5b62e993
MW
698 sig_pipeignore();
699 when = now();
700
f8beb284
MW
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];
5b62e993
MW
733 if (!dir) die_usage();
734
735 sender = env_get("SENDER");
f8beb284 736 if (!sender) strerr_die2x(100,FATAL,ERR_NOSENDER);
5b62e993 737 local = env_get("LOCAL");
f8beb284
MW
738 if (!local) strerr_die2x(100,FATAL,ERR_NOLOCAL);
739 def = env_get("DEFAULT");
5b62e993
MW
740
741 if (!*sender)
f8beb284 742 strerr_die2x(100,FATAL,ERR_BOUNCE);
5b62e993 743 if (!sender[str_chr(sender,'@')])
f8beb284 744 strerr_die2x(100,FATAL,ERR_ANONYMOUS);
5b62e993 745 if (str_equal(sender,"#@[]"))
f8beb284 746 strerr_die2x(100,FATAL,ERR_BOUNCE);
5b62e993
MW
747
748 if (chdir(dir) == -1)
f8beb284 749 strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": ");
5b62e993
MW
750
751 switch(slurp("key",&key,32)) {
752 case -1:
f8beb284 753 strerr_die4sys(111,FATAL,ERR_READ,dir,"/key: ");
5b62e993 754 case 0:
f8beb284 755 strerr_die4x(100,FATAL,dir,"/key",ERR_NOEXIST);
5b62e993
MW
756 }
757 getconf_line(&mailinglist,"mailinglist",1,FATAL,dir);
5b62e993
MW
758 getconf_line(&outhost,"outhost",1,FATAL,dir);
759 getconf_line(&outlocal,"outlocal",1,FATAL,dir);
f8beb284
MW
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();
5b62e993 772
f8beb284
MW
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 }
5b62e993 783
f8beb284 784 if (!stralloc_copys(&ddir,dir)) die_nomem();
5b62e993 785
f8beb284
MW
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;
5b62e993 803 }
f8beb284
MW
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);
5b62e993
MW
810
811 if (!stralloc_copys(&target,sender)) die_nomem();
812 if (action[0]) {
f8beb284 813 i = str_chr(action,'-');
5b62e993
MW
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();
f8beb284
MW
823 set_cptarget(target.s); /* for copy() */
824 make_verptarget();
5b62e993 825
f8beb284
MW
826 flagmod = getconf_line(&modsub,"modsub",0,FATAL,dir);
827 flagremote = getconf_line(&remote,"remote",0,FATAL,dir);
5b62e993 828
f8beb284
MW
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); }
5b62e993 848
f8beb284
MW
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();
5b62e993 860 }
f8beb284
MW
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);
5b62e993 883
f8beb284
MW
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;
5b62e993 896 }
5b62e993 897
f8beb284
MW
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();
5b62e993 902
f8beb284
MW
903 if (qmail_open(&qq,(stralloc *) 0) == -1)
904 strerr_die2sys(111,FATAL,ERR_QMAIL_QUEUE);
905 msg_headers();
5b62e993 906
f8beb284
MW
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 }
5b62e993 924
f8beb284
MW
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);
5b62e993 945 }
5b62e993 946
f8beb284
MW
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);
5b62e993 974 }
f8beb284
MW
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();
5b62e993 1003 }
f8beb284
MW
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
5b62e993
MW
1287 unsigned long u;
1288 struct stat st;
1289 char ch;
1290 int r;
f8beb284
MW
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);
5b62e993 1302
f8beb284
MW
1303 if (action[pos] == '.' || action [pos] == '_') pos++;
1304 scan_ulong(action + pos,&u);
5b62e993
MW
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)
f8beb284 1315 strerr_die4sys(111,FATAL,ERR_OPEN,line.s,": ");
5b62e993 1316 else
f8beb284 1317 copy(&qq,"text/get-bad",flagcd,FATAL);
5b62e993
MW
1318 else {
1319 if (fstat(fd,&st) == -1)
f8beb284 1320 copy(&qq,"text/get-bad",flagcd,FATAL);
5b62e993 1321 else if (!(st.st_mode & 0100))
f8beb284 1322 copy(&qq,"text/get-bad",flagcd,FATAL);
5b62e993
MW
1323 else {
1324 substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf));
1325 qmail_puts(&qq,"> ");
1326 for (;;) {
1327 r = substdio_get(&sstext,&ch,1);
f8beb284 1328 if (r == -1) strerr_die4sys(111,FATAL,ERR_READ,line.s,": ");
5b62e993
MW
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 }
f8beb284
MW
1337 copybottom();
1338 qmail_to(&qq,target.s);
5b62e993 1339
f8beb284
MW
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);
5b62e993 1358
f8beb284
MW
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);
5b62e993 1367
f8beb284
MW
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);
5b62e993 1376
f8beb284
MW
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);
5b62e993 1385
f8beb284
MW
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') {
5b62e993 1397 strnum[fmt_ulong(strnum,qmail_qp(&qq))] = 0;
f8beb284 1398 closesql();
5b62e993 1399 strerr_die2x(0,"ezmlm-manage: info: qp ",strnum);
f8beb284
MW
1400 } else {
1401 closesql();
1402 strerr_die3x(111,FATAL,ERR_TMP_QMAIL_QUEUE,err + 1);
5b62e993
MW
1403 }
1404}
f8beb284 1405