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