1 /*$Id: ezmlm-moderate.c,v 1.42 1999/10/09 16:49:56 lindberg Exp $*/
2 /*$Name: ezmlm-idx-040 $*/
22 #include "readwrite.h"
27 #include "date822fmt.h"
36 int flagmime = MOD_MIME; /* default is message as attachment */
37 char flagcd = '\0'; /* default: do not use transfer encoding */
39 #define FATAL "ezmlm-moderate: fatal: "
40 #define INFO "ezmlm-moderate: info: "
42 void die_usage() { strerr_die1x(100,
43 "ezmlm-moderate: usage: ezmlm-moderate [-cCmMrRvV] [-t replyto] "
44 "dir [/path/ezmlm-send]"); }
46 void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); }
48 void die_badformat() { strerr_die2x(100,FATAL,ERR_BAD_REQUEST); }
51 strerr_die2x(100,FATAL,ERR_BAD_ADDRESS);
54 stralloc outhost = {0};
55 stralloc inlocal = {0};
56 stralloc outlocal = {0};
58 stralloc mydtline = {0};
59 stralloc mailinglist = {0};
60 stralloc accept = {0};
61 stralloc reject = {0};
64 stralloc sendopt = {0};
65 stralloc comment = {0};
66 stralloc charset = {0};
70 char strnum[FMT_ULONG];
71 char date[DATE822FMT];
73 char boundary[COOKIE];
77 stralloc quoted = {0};
78 stralloc fnbase = {0};
99 encodeB(s,n,&qline,0,FATAL);
101 encodeQ(s,n,&qline,FATAL);
102 qmail_put(&qq,qline.s,qline.len);
109 qmail_puts(&qq,"\nContent-Transfer-Encoding: ");
111 qmail_puts(&qq,"Quoted-printable\n\n");
113 qmail_puts(&qq,"base64\n\n");
115 qmail_puts(&qq,"\n\n");
120 /* looks for DIR/mod/{pending|rejected|accept}/fn.*/
122 /* 1 found in pending */
124 /* -1 found in accepted */
125 /* -2 found in rejected */
126 /* Handles errors. */
127 /* ALSO: if found, fnmsg contains the o-terminated*/
131 if (!stralloc_copys(&fnmsg,"mod/pending/")) die_nomem();
132 if (!stralloc_cats(&fnmsg,fn)) die_nomem();
133 if (!stralloc_0(&fnmsg)) die_nomem();
134 if (stat(fnmsg.s,&st) == -1) {
135 if (errno != error_noent)
136 strerr_die6sys(111,FATAL,ERR_STAT,dir,"/",fnmsg.s,": ");
140 if (!stralloc_copys(&fnmsg,"mod/accepted/")) die_nomem();
141 if (!stralloc_cats(&fnmsg,fn)) die_nomem();
142 if (!stralloc_0(&fnmsg)) die_nomem();
143 if (stat(fnmsg.s,&st) == -1) {
144 if (errno != error_noent)
145 strerr_die6sys(111,FATAL,ERR_STAT,dir,"/",fnmsg.s,": ");
149 if (!stralloc_copys(&fnmsg,"mod/rejected/")) die_nomem();
150 if (!stralloc_cats(&fnmsg,fn)) die_nomem();
151 if (!stralloc_0(&fnmsg)) die_nomem();
152 if (stat(fnmsg.s,&st) == -1) {
153 if (errno != error_noent)
154 strerr_die6sys(111,FATAL,ERR_STAT,dir,"/",fnmsg.s,": ");
160 int qqwrite(fd,buf,len) int fd; char *buf; unsigned int len;
162 qmail_put(&qq,buf,len);
166 substdio ssqq = SUBSTDIO_FDBUF(qqwrite,-1,qqbuf,sizeof(qqbuf));
169 substdio ssin = SUBSTDIO_FDBUF(read,0,inbuf,sizeof(inbuf));
175 /* expects line to be a return-path line. If it is and the format is valid */
176 /* to is set to to the sender. Otherwise, to is left untouched. Assuming */
177 /* to is empty to start with, it will remain empty if no sender is found. */
181 if (case_startb(line.s,line.len,"return-path:")) {
182 x = 12 + byte_chr(line.s + 12,line.len-12,'<');
184 y = byte_rchr(line.s + x,line.len-x,'>');
185 if (y + x != line.len) {
186 if (!stralloc_copyb(&to,line.s+x+1,y-1)) die_nomem();
187 if (!stralloc_0(&to)) die_nomem();
188 } /* no return path-> no addressee. A NUL in the sender */
189 } /* is no worse than a faked sender, so no problem */
209 char szchar[2] = "-";
210 char *replyto = (char *) 0;
211 unsigned int start,confnum;
216 char *cp,*cpnext,*cpfirst,*cplast,*cpafter;
223 if (!stralloc_copys(&sendopt," -")) die_nomem();
224 while ((opt = getopt(argc,argv,"cCmMrRt:T:vV")) != opteof)
225 switch(opt) { /* pass on ezmlm-send options */
226 case 'c': /* ezmlm-send flags */
230 szchar[0] = (char) opt & 0xff;
231 if (!stralloc_append(&sendopt,szchar)) die_nomem();
233 case 'm': flagmime = 1; break;
234 case 'M': flagmime = 0; break;
236 case 'T': if (optarg) replyto = optarg; break;
238 case 'V': strerr_die2x(0,"ezmlm-moderate version: ",EZIDX_VERSION);
243 dir = argv[optind++];
244 if (!dir) die_usage();
246 sender = env_get("SENDER");
247 if (!sender) strerr_die2x(100,FATAL,ERR_NOSENDER);
248 local = env_get("LOCAL");
249 if (!local) strerr_die2x(100,FATAL,ERR_NOLOCAL);
250 def = env_get("DEFAULT");
253 strerr_die2x(100,FATAL,ERR_BOUNCE);
254 if (!sender[str_chr(sender,'@')])
255 strerr_die2x(100,FATAL,ERR_ANONYMOUS);
256 if (str_equal(sender,"#@[]"))
257 strerr_die2x(100,FATAL,ERR_BOUNCE);
259 if (chdir(dir) == -1)
260 strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": ");
262 switch(slurp("key",&key,32)) {
264 strerr_die4sys(111,FATAL,ERR_READ,dir,"/key: ");
266 strerr_die4x(100,FATAL,dir,"/key",ERR_NOEXIST);
268 getconf_line(&mailinglist,"mailinglist",1,FATAL,dir);
269 getconf_line(&outhost,"outhost",1,FATAL,dir);
270 getconf_line(&outlocal,"outlocal",1,FATAL,dir);
271 set_cpoutlocal(&outlocal); /* for copy() */
272 set_cpouthost(&outhost); /* for copy() */
274 if (def) { /* qmail>=1.02 */
275 /* local should be >= def, but who knows ... */
276 cp = local + str_len(local) - str_len(def) - 2;
277 if (cp < local) die_badformat();
278 action = local + byte_rchr(local,cp - local,'-');
279 if (action == cp) die_badformat();
281 } else { /* older versions of qmail */
282 getconf_line(&inlocal,"inlocal",1,FATAL,dir);
283 if (inlocal.len > str_len(local)) die_badaddr();
284 if (case_diffb(inlocal.s,inlocal.len,local)) die_badaddr();
285 action = local + inlocal.len;
286 if (*(action++) != '-') die_badaddr();
289 if (!action[0]) die_badformat();
290 if (!str_start(action,ACTION_ACCEPT) && !str_start(action,ACTION_REJECT))
292 start = str_chr(action,'-');
293 if (!action[start]) die_badformat();
294 confnum = 1 + start + str_chr(action + start + 1,'.');
295 if (!action[confnum]) die_badformat();
296 confnum += 1 + str_chr(action + confnum + 1,'.');
297 if (!action[confnum]) die_badformat();
298 if (!stralloc_copyb(&fnbase,action+start+1,confnum-start-1)) die_nomem();
299 if (!stralloc_0(&fnbase)) die_nomem();
300 cookie(hash,key.s,key.len,fnbase.s,"","a");
301 if (byte_diff(hash,COOKIE,action+confnum+1))
304 fdlock = open_append("mod/lock");
306 strerr_die4sys(111,FATAL,ERR_OPEN,dir,"/mod/lock: ");
307 if (lock_ex(fdlock) == -1)
308 strerr_die4sys(111,FATAL,ERR_OBTAIN,dir,"/mod/lock: ");
310 switch(checkfile(fnbase.s)) {
312 strerr_die2x(100,FATAL,ERR_MOD_TIMEOUT);
313 case -1: /* only error if new request != action taken */
314 if (str_start(action,ACTION_ACCEPT))
315 strerr_die2x(0,INFO,ERR_MOD_ACCEPTED);
317 strerr_die2x(100,FATAL,ERR_MOD_ACCEPTED);
319 if (str_start(action,ACTION_REJECT))
320 strerr_die2x(0,INFO,ERR_MOD_REJECTED);
322 strerr_die2x(100,FATAL,ERR_MOD_REJECTED);
326 /* Here, we have an existing filename in fnbase with the complete path */
327 /* from the current dir in fnmsg. */
329 if (str_start(action,ACTION_REJECT)) {
331 if (qmail_open(&qq, (stralloc *) 0) == -1)
332 strerr_die2sys(111,FATAL,ERR_QMAIL_QUEUE);
335 /* Build recipient from msg return-path */
336 fd = open_read(fnmsg.s);
338 if (errno != error_noent)
339 strerr_die4sys(111,FATAL,ERR_OPEN,fnmsg.s,": ");
341 strerr_die2x(100,FATAL,ERR_MOD_TIMEOUT);
343 substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf));
345 if (getln(&sstext,&line,&match,'\n') == -1 || !match)
346 strerr_die2sys(111,FATAL,ERR_READ_INPUT);
347 maketo(); /* extract SENDER from return-path */
349 qmail_puts(&qq,"Mailing-List: ");
350 qmail_put(&qq,mailinglist.s,mailinglist.len);
351 if(getconf_line(&line,"listid",0,FATAL,dir)) {
352 qmail_puts(&qq,"\nList-ID: ");
353 qmail_put(&qq,line.s,line.len);
355 qmail_puts(&qq,"\nDate: ");
356 datetime_tai(&dt,when);
357 qmail_put(&qq,date,date822fmt(date,&dt));
358 qmail_puts(&qq,"Message-ID: <");
359 if (!stralloc_copyb(&line,strnum,fmt_ulong(strnum,(unsigned long) when)))
361 if (!stralloc_append(&line,".")) die_nomem();
362 if (!stralloc_catb(&line,strnum,
363 fmt_ulong(strnum,(unsigned long) getpid()))) die_nomem();
364 if (!stralloc_cats(&line,".ezmlm@")) die_nomem();
365 if (!stralloc_cat(&line,&outhost)) die_nomem();
366 if (!stralloc_0(&line)) die_nomem();
367 qmail_puts(&qq,line.s);
368 /* "unique" MIME boundary as hash of messageid */
369 cookie(boundary,"",0,"",line.s,"");
370 qmail_puts(&qq,">\nFrom: ");
371 if (!quote("ed,&outlocal)) die_nomem();
372 qmail_put(&qq,quoted.s,quoted.len);
373 qmail_puts(&qq,"-owner@");
374 qmail_put(&qq,outhost.s,outhost.len);
376 qmail_puts(&qq,"\nReply-To: ");
377 qmail_puts(&qq,replyto);
379 qmail_puts(&qq, "\nTo: ");
380 qmail_puts(&qq,to.s);
381 qmail_puts(&qq,"\nSubject: ");
382 qmail_puts(&qq,TXT_RETURNED_POST);
383 qmail_put(&qq,quoted.s,quoted.len);
385 qmail_put(&qq,outhost.s,outhost.len);
388 if (getconf_line(&charset,"charset",0,FATAL,dir)) {
389 if (charset.len >= 2 && charset.s[charset.len - 2] == ':') {
390 if (charset.s[charset.len - 1] == 'B' ||
391 charset.s[charset.len - 1] == 'Q') {
392 flagcd = charset.s[charset.len - 1];
393 charset.s[charset.len - 2] = '\0';
397 if (!stralloc_copys(&charset,TXT_DEF_CHARSET)) die_nomem();
398 if (!stralloc_0(&charset)) die_nomem();
399 qmail_puts(&qq,"\nMIME-Version: 1.0\n");
400 qmail_puts(&qq,"Content-Type: multipart/mixed;\n\tboundary=");
401 qmail_put(&qq,boundary,COOKIE);
402 qmail_puts(&qq,"\n\n--");
403 qmail_put(&qq,boundary,COOKIE);
404 qmail_puts(&qq,"\nContent-Type: text/plain; charset=");
405 qmail_puts(&qq,charset.s);
408 copy(&qq,"text/top",flagcd,FATAL);
409 copy(&qq,"text/mod-reject",flagcd,FATAL);
413 if (!stralloc_copys(&text,"")) die_nomem();
414 if (!stralloc_ready(&text,1024)) die_nomem();
415 for (;;) { /* copy moderator's rejection comment */
416 if (getln(&ssin,&line,&match,'\n') == -1)
417 strerr_die2sys(111,FATAL,ERR_READ_INPUT);
420 if (case_startb(line.s,line.len,"Content-Transfer-Encoding:")) {
422 while (line.s[pos] == ' ' || line.s[pos] == '\t') ++pos;
423 if (case_startb(line.s+pos,line.len-pos,"base64"))
425 else if (case_startb(line.s+pos,line.len-pos,"quoted-printable"))
431 if (!stralloc_cat(&text,&line)) die_nomem();
435 decodeB(text.s,text.len,&line,FATAL);
437 decodeQ(text.s,text.len,&line,FATAL);
438 if (!stralloc_copy(&text,&line)) die_nomem();
441 cpafter = text.s + text.len;
442 if (!stralloc_copys(&line,"\n>>>>> -------------------- >>>>>\n"))
446 while ((cpnext = cp + byte_chr(cp,cpafter-cp,'\n')) != cpafter) {
447 i = byte_chr(cp,cpnext-cp,'%');
448 if (i <= 5 && cpnext-cp >= 8) {
449 /* max 5 "quote characters" and space for %%% */
450 if (cp[i+1] == '%' && cp[i+2] == '%') {
451 if (!flaggoodfield) { /* Start tag */
452 if (!stralloc_copyb("ed,cp,i)) die_nomem(); /* quote chars*/
457 } else { /* end tag */
458 if (flagdone) /* 0 no comment lines, 1 comment line */
459 flagdone = 2; /* 2 at least 1 comment line & end tag */
466 if (*cplast == '\r') /* CRLF -> '\n' for base64 encoding */
470 /* NUL is now ok, so the test for it was removed */
473 if (quoted.len && quoted.len <= i &&
474 !str_diffn(cp,quoted.s,quoted.len)) { /* quote chars */
475 if (!stralloc_catb(&line,cp+quoted.len,i-quoted.len)) die_nomem();
477 if (!stralloc_catb(&line,cp,i)) die_nomem(); /* no quote chars */
482 if (!stralloc_cats(&line,"<<<<< -------------------- <<<<<\n")) die_nomem();
483 code_qput(line.s,line.len);
486 encodeB("",0,&line,2,FATAL);
487 qmail_put(&qq,line.s,line.len);
490 qmail_puts(&qq,"\n--");
491 qmail_put(&qq,boundary,COOKIE);
492 qmail_puts(&qq,"\nContent-Type: message/rfc822\n\n");
494 qmail_puts(&qq,"\n");
495 if (seek_begin(fd) == -1)
496 strerr_die4sys(111,FATAL,ERR_SEEK,fnmsg.s,": ");
498 substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf));
499 if (substdio_copy(&ssqq,&sstext) != 0)
500 strerr_die4sys(111,FATAL,ERR_READ,fnmsg.s,": ");
504 qmail_puts(&qq,"\n--");
505 qmail_put(&qq,boundary,COOKIE);
506 qmail_puts(&qq,"--\n");
509 if (!stralloc_copy(&line,&outlocal)) die_nomem();
510 if (!stralloc_cats(&line,"-return-@")) die_nomem();
511 if (!stralloc_cat(&line,&outhost)) die_nomem();
512 if (!stralloc_0(&line)) die_nomem();
513 qmail_from(&qq,line.s);
517 if (!stralloc_copys(&fnnew,"mod/rejected/")) die_nomem();
518 if (!stralloc_cats(&fnnew,fnbase.s)) die_nomem();
519 if (!stralloc_0(&fnnew)) die_nomem();
521 /* this is strictly to track what happended to a message to give informative */
522 /* messages to the 2nd-nth moderator that acts on the same message. Since */
523 /* this isn't vital we ignore errors. Also, it is no big ideal if unlinking */
524 /* the old file fails. In the worst case it gets acted on again. If we issue */
525 /* a temp error the reject will be redone, which is slightly worse. */
527 if (*(err = qmail_close(&qq)) == '\0') {
528 fd = open_trunc(fnnew.s);
532 strnum[fmt_ulong(strnum,qmail_qp(&qq))] = 0;
533 strerr_die2x(0,"ezmlm-moderate: info: qp ",strnum);
535 strerr_die3x(111,FATAL,ERR_TMP_QMAIL_QUEUE,err + 1);
537 } else if (str_start(action,ACTION_ACCEPT)) {
538 fd = open_read(fnmsg.s);
540 if (errno !=error_noent)
541 strerr_die4sys(111,FATAL,ERR_OPEN,fnmsg.s,": ");
542 else /* shouldn't happen since we've got lock */
543 strerr_die3x(100,FATAL,fnmsg.s,ERR_MOD_TIMEOUT);
545 substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf));
546 /* read "Return-Path:" line */
547 if (getln(&sstext,&line,&match,'\n') == -1 || !match)
548 strerr_die2sys(111,FATAL,ERR_READ_INPUT);
549 maketo(); /* extract SENDER to "to" */
550 env_put2("SENDER",to.s); /* set SENDER */
551 if (seek_begin(fd) == -1) /* rewind, since we read an entire buffer */
552 strerr_die4sys(111,FATAL,ERR_SEEK,fnmsg.s,": ");
554 /* ##### NO REASON TO USE SH HERE ##### */
555 sendargs[0] = "/bin/sh";
558 sendargs[2] = argv[optind];
560 if (!stralloc_copys(&send,auto_bin)) die_nomem();
561 if (!stralloc_cats(&send,"/ezmlm-send")) die_nomem();
563 if (!stralloc_cat(&send,&sendopt)) die_nomem();
564 if (!stralloc_cats(&send," '")) die_nomem();
565 if (!stralloc_cats(&send,dir)) die_nomem();
566 if (!stralloc_cats(&send,"'")) die_nomem();
567 if (!stralloc_0(&send)) die_nomem();
568 sendargs[2] = send.s;
572 switch(child = fork()) {
574 strerr_die2sys(111,FATAL,ERR_FORK);
577 dup(fd); /* make fnmsg.s stdin */
578 execv(*sendargs,sendargs);
579 if (errno == error_txtbsy || errno == error_nomem ||
581 strerr_die5sys(111,FATAL,ERR_EXECUTE,"/bin/sh -c ",sendargs[2],": ");
583 strerr_die5sys(100,FATAL,ERR_EXECUTE,"/bin/sh -c ",sendargs[2],": ");
586 wait_pid(&wstat,child);
588 if (wait_crashed(wstat))
589 strerr_die3x(111,FATAL,sendargs[2],ERR_CHILD_CRASHED);
590 switch(wait_exitcode(wstat)) {
592 strerr_die2x(100,FATAL,"Fatal error from child");
594 strerr_die2x(111,FATAL,"Temporary error from child");
598 strerr_die2x(111,FATAL,"Unknown temporary error from child");
600 if (!stralloc_copys(&fnnew,"mod/accepted/")) die_nomem();
602 if (!stralloc_cats(&fnnew,fnbase.s)) die_nomem();
603 if (!stralloc_0(&fnnew)) die_nomem();
605 fd = open_trunc(fnnew.s);