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
);