chiark / gitweb /
Debianization and various other fixes.
[ezmlm] / ezmlm-moderate.c
1 /*$Id: ezmlm-moderate.c,v 1.42 1999/10/09 16:49:56 lindberg Exp $*/
2 /*$Name: ezmlm-idx-040 $*/
3
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include "error.h"
7 #include "case.h"
8 #include "stralloc.h"
9 #include "str.h"
10 #include "env.h"
11 #include "error.h"
12 #include "sig.h"
13 #include "fork.h"
14 #include "wait.h"
15 #include "slurp.h"
16 #include "getconf.h"
17 #include "strerr.h"
18 #include "byte.h"
19 #include "getln.h"
20 #include "qmail.h"
21 #include "substdio.h"
22 #include "readwrite.h"
23 #include "seek.h"
24 #include "quote.h"
25 #include "datetime.h"
26 #include "now.h"
27 #include "date822fmt.h"
28 #include "fmt.h"
29 #include "sgetopt.h"
30 #include "auto_bin.h"
31 #include "cookie.h"
32 #include "errtxt.h"
33 #include "copy.h"
34 #include "idx.h"
35
36 int flagmime = MOD_MIME;        /* default is message as attachment */
37 char flagcd = '\0';             /* default: do not use transfer encoding */
38
39 #define FATAL "ezmlm-moderate: fatal: "
40 #define INFO "ezmlm-moderate: info: "
41
42 void die_usage() { strerr_die1x(100,
43     "ezmlm-moderate: usage: ezmlm-moderate [-cCmMrRvV] [-t replyto] "
44     "dir [/path/ezmlm-send]"); }
45
46 void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); }
47
48 void die_badformat() { strerr_die2x(100,FATAL,ERR_BAD_REQUEST); }
49
50 void die_badaddr() {
51   strerr_die2x(100,FATAL,ERR_BAD_ADDRESS);
52 }
53
54 stralloc outhost = {0};
55 stralloc inlocal = {0};
56 stralloc outlocal = {0};
57 stralloc key = {0};
58 stralloc mydtline = {0};
59 stralloc mailinglist = {0};
60 stralloc accept = {0};
61 stralloc reject = {0};
62 stralloc to = {0};
63 stralloc send = {0};
64 stralloc sendopt = {0};
65 stralloc comment = {0};
66 stralloc charset = {0};
67 datetime_sec when;
68 struct datetime dt;
69
70 char strnum[FMT_ULONG];
71 char date[DATE822FMT];
72 char hash[COOKIE];
73 char boundary[COOKIE];
74 stralloc line = {0};
75 stralloc qline = {0};
76 stralloc text = {0};
77 stralloc quoted = {0};
78 stralloc fnbase = {0};
79 stralloc fnmsg = {0};
80 stralloc fnnew = {0};
81 stralloc fnsub = {0};
82 char subbuf[256];
83 substdio sssub;
84
85 char *dir;
86
87 struct stat st;
88
89 struct qmail qq;
90
91 void code_qput(s,n)
92 char *s;
93 unsigned int n;
94 {
95     if (!flagcd)
96       qmail_put(&qq,s,n);
97     else {
98       if (flagcd == 'B')
99         encodeB(s,n,&qline,0,FATAL);
100       else
101         encodeQ(s,n,&qline,FATAL);
102       qmail_put(&qq,qline.s,qline.len);
103     }
104 }
105
106 void transferenc()
107 {
108         if (flagcd) {
109           qmail_puts(&qq,"\nContent-Transfer-Encoding: ");
110           if (flagcd == 'Q')
111             qmail_puts(&qq,"Quoted-printable\n\n");
112           else
113             qmail_puts(&qq,"base64\n\n");
114         } else
115           qmail_puts(&qq,"\n\n");
116 }
117
118 int checkfile(fn)
119 char *fn;
120 /* looks for DIR/mod/{pending|rejected|accept}/fn.*/
121 /* Returns:                                       */
122 /*          1 found in pending                    */
123 /*          0 not found                           */
124 /*         -1 found in accepted                   */
125 /*         -2 found in rejected                   */
126 /* Handles errors.                                */
127 /* ALSO: if found, fnmsg contains the o-terminated*/
128 /* file name.                                     */
129 {
130   
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,": ");
137   } else
138       return 1;
139
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,": ");
146   } else
147       return -1;
148
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,": ");
155   } else
156       return -2;
157   return 0;
158 }
159
160 int qqwrite(fd,buf,len) int fd; char *buf; unsigned int len;
161 {
162   qmail_put(&qq,buf,len);
163   return len;
164 }
165 char qqbuf[1];
166 substdio ssqq = SUBSTDIO_FDBUF(qqwrite,-1,qqbuf,sizeof(qqbuf));
167
168 char inbuf[512];
169 substdio ssin = SUBSTDIO_FDBUF(read,0,inbuf,sizeof(inbuf));
170
171 substdio sstext;
172 char textbuf[1024];
173
174 void maketo()
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.  */
178 {
179   unsigned int x, y;
180
181     if (case_startb(line.s,line.len,"return-path:")) {
182       x = 12 + byte_chr(line.s + 12,line.len-12,'<');
183       if (x != line.len) {
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 */
190     }
191 }
192
193 void main(argc,argv)
194 int argc;
195 char **argv;
196 {
197   char *sender;
198   char *def;
199   char *local;
200   char *action;
201   int flaginheader;
202   int flagcomment;
203   int flaggoodfield;
204   int flagdone;
205   int fd, fdlock;
206   int match;
207   char *err;
208   char encin = '\0';
209   char szchar[2] = "-";
210   char *replyto = (char *) 0;
211   unsigned int start,confnum;
212   unsigned int pos,i;
213   int child;
214   int opt;
215   char *sendargs[4];
216   char *cp,*cpnext,*cpfirst,*cplast,*cpafter;
217   int wstat;
218
219   (void) umask(022);
220   sig_pipeignore();
221   when = now();
222
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 */
227       case 'C':
228       case 'r':
229       case 'R':
230         szchar[0] = (char) opt & 0xff;
231         if (!stralloc_append(&sendopt,szchar)) die_nomem();
232         break;
233       case 'm': flagmime = 1; break;
234       case 'M': flagmime = 0; break;
235       case 't':
236       case 'T': if (optarg) replyto = optarg; break;
237       case 'v':
238       case 'V': strerr_die2x(0,"ezmlm-moderate version: ",EZIDX_VERSION);
239       default:
240         die_usage();
241     }
242
243   dir = argv[optind++];
244   if (!dir) die_usage();
245
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");
251
252   if (!*sender)
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);
258
259   if (chdir(dir) == -1)
260     strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": ");
261
262   switch(slurp("key",&key,32)) {
263     case -1:
264       strerr_die4sys(111,FATAL,ERR_READ,dir,"/key: ");
265     case 0:
266       strerr_die4x(100,FATAL,dir,"/key",ERR_NOEXIST);
267   }
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() */
273
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();
280     action++;
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();
287   }
288
289   if (!action[0]) die_badformat();
290   if (!str_start(action,ACTION_ACCEPT) && !str_start(action,ACTION_REJECT))
291     die_badformat();
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))
302     die_badformat();
303
304   fdlock = open_append("mod/lock");
305   if (fdlock == -1)
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: ");
309
310   switch(checkfile(fnbase.s)) {
311     case 0:
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);
316       else
317         strerr_die2x(100,FATAL,ERR_MOD_ACCEPTED);
318     case -2:
319       if (str_start(action,ACTION_REJECT))
320         strerr_die2x(0,INFO,ERR_MOD_REJECTED);
321       else
322         strerr_die2x(100,FATAL,ERR_MOD_REJECTED);
323     default:
324       break;
325   }
326 /* Here, we have an existing filename in fnbase with the complete path */
327 /* from the current dir in fnmsg. */
328
329   if (str_start(action,ACTION_REJECT)) {
330
331     if (qmail_open(&qq, (stralloc *) 0) == -1)
332       strerr_die2sys(111,FATAL,ERR_QMAIL_QUEUE);
333
334
335                                 /* Build recipient from msg return-path */
336     fd = open_read(fnmsg.s);
337     if (fd == -1) {
338       if (errno != error_noent)
339         strerr_die4sys(111,FATAL,ERR_OPEN,fnmsg.s,": ");
340       else
341         strerr_die2x(100,FATAL,ERR_MOD_TIMEOUT);
342     }
343     substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf));
344
345     if (getln(&sstext,&line,&match,'\n') == -1 || !match)
346       strerr_die2sys(111,FATAL,ERR_READ_INPUT);
347     maketo();                   /* extract SENDER from return-path */
348                                                 /* Build message */
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);
354     }
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)))
360        die_nomem();
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(&quoted,&outlocal)) die_nomem();
372     qmail_put(&qq,quoted.s,quoted.len);
373     qmail_puts(&qq,"-owner@");
374     qmail_put(&qq,outhost.s,outhost.len);
375     if (replyto) {
376       qmail_puts(&qq,"\nReply-To: ");
377       qmail_puts(&qq,replyto);
378     }
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);
384     qmail_puts(&qq,"@");
385     qmail_put(&qq,outhost.s,outhost.len);
386
387     if (flagmime) {
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';
394           }
395         }
396       } else
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);
406       transferenc();
407     }
408     copy(&qq,"text/top",flagcd,FATAL);
409     copy(&qq,"text/mod-reject",flagcd,FATAL);
410
411     flagcomment = 0;
412     flaginheader = 1;
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);
418       if (!match) break;
419       if (flaginheader) {
420         if (case_startb(line.s,line.len,"Content-Transfer-Encoding:")) {
421           pos = 26;
422           while (line.s[pos] == ' ' || line.s[pos] == '\t') ++pos;
423           if (case_startb(line.s+pos,line.len-pos,"base64"))
424             encin = 'B';
425           else if (case_startb(line.s+pos,line.len-pos,"quoted-printable"))
426             encin = 'Q';
427         }
428         if (line.len == 1)
429           flaginheader = 0;
430       } else
431         if (!stralloc_cat(&text,&line)) die_nomem();
432     }   /* got body */
433     if (encin) {
434       if (encin == 'B')
435         decodeB(text.s,text.len,&line,FATAL);
436       else
437         decodeQ(text.s,text.len,&line,FATAL);
438       if (!stralloc_copy(&text,&line)) die_nomem();
439     }
440     cp = text.s;
441     cpafter = text.s + text.len;
442     if (!stralloc_copys(&line,"\n>>>>> -------------------- >>>>>\n"))
443                         die_nomem();
444     flaggoodfield = 0;
445     flagdone = 0;
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(&quoted,cp,i)) die_nomem();     /* quote chars*/
453             flaggoodfield = 1;
454             cp = cpnext + 1;
455             cpfirst = cp;
456             continue;
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 */
460             break;
461           }
462         }
463       }
464       if (flaggoodfield) {
465         cplast = cpnext - 1;
466         if (*cplast == '\r')    /* CRLF -> '\n' for base64 encoding */
467           *cplast = '\n';
468         else
469           ++cplast;
470                         /* NUL is now ok, so the test for it was removed */
471         flagdone = 1;
472         i = cplast - cp + 1;
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();
476         } else
477           if (!stralloc_catb(&line,cp,i)) die_nomem();  /* no quote chars */
478       }
479       cp = cpnext + 1;
480     }
481     if (flagdone == 2) {
482     if (!stralloc_cats(&line,"<<<<< -------------------- <<<<<\n")) die_nomem();
483       code_qput(line.s,line.len);
484     }
485     if (flagcd == 'B') {
486       encodeB("",0,&line,2,FATAL);
487       qmail_put(&qq,line.s,line.len);
488     }
489     if (flagmime) {
490       qmail_puts(&qq,"\n--");
491       qmail_put(&qq,boundary,COOKIE);
492       qmail_puts(&qq,"\nContent-Type: message/rfc822\n\n");
493     } else
494       qmail_puts(&qq,"\n");
495     if (seek_begin(fd) == -1)
496       strerr_die4sys(111,FATAL,ERR_SEEK,fnmsg.s,": ");
497
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,": ");
501     close(fd);
502
503     if (flagmime) {
504       qmail_puts(&qq,"\n--");
505       qmail_put(&qq,boundary,COOKIE);
506       qmail_puts(&qq,"--\n");
507     }
508
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);
514     if (to.len)
515       qmail_to(&qq,to.s);
516
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();
520
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.         */
526
527     if (*(err = qmail_close(&qq)) == '\0') {
528         fd = open_trunc(fnnew.s);
529         if (fd != -1)
530           close(fd);
531         unlink(fnmsg.s);
532         strnum[fmt_ulong(strnum,qmail_qp(&qq))] = 0;
533         strerr_die2x(0,"ezmlm-moderate: info: qp ",strnum);
534     } else
535         strerr_die3x(111,FATAL,ERR_TMP_QMAIL_QUEUE,err + 1);
536
537   } else if (str_start(action,ACTION_ACCEPT)) {
538         fd = open_read(fnmsg.s);
539         if (fd == -1)
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);
544
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,": ");
553
554 /* ##### NO REASON TO USE SH HERE ##### */
555         sendargs[0] = "/bin/sh";
556         sendargs[1] = "-c";
557         if (argc > optind) {
558           sendargs[2] = argv[optind];
559         } else {
560           if (!stralloc_copys(&send,auto_bin)) die_nomem();
561           if (!stralloc_cats(&send,"/ezmlm-send")) die_nomem();
562           if (sendopt.len > 2)
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;
569         }
570         sendargs[3] = 0;
571
572     switch(child = fork()) {
573       case -1:
574         strerr_die2sys(111,FATAL,ERR_FORK);
575       case 0:           /* child */
576         close(0);
577         dup(fd);        /* make fnmsg.s stdin */
578         execv(*sendargs,sendargs);
579         if (errno == error_txtbsy || errno == error_nomem ||
580             errno == error_io)
581           strerr_die5sys(111,FATAL,ERR_EXECUTE,"/bin/sh -c ",sendargs[2],": ");
582         else
583           strerr_die5sys(100,FATAL,ERR_EXECUTE,"/bin/sh -c ",sendargs[2],": ");
584        }
585          /* parent */
586       wait_pid(&wstat,child);
587       close(fd);
588       if (wait_crashed(wstat))
589         strerr_die3x(111,FATAL,sendargs[2],ERR_CHILD_CRASHED);
590       switch(wait_exitcode(wstat)) {
591         case 100:
592           strerr_die2x(100,FATAL,"Fatal error from child");
593         case 111:
594            strerr_die2x(111,FATAL,"Temporary error from child");
595         case 0:
596           break;
597         default:
598           strerr_die2x(111,FATAL,"Unknown temporary error from child");
599       }
600       if (!stralloc_copys(&fnnew,"mod/accepted/")) die_nomem();
601
602       if (!stralloc_cats(&fnnew,fnbase.s)) die_nomem();
603       if (!stralloc_0(&fnnew)) die_nomem();
604 /* ignore errors */
605       fd = open_trunc(fnnew.s);
606       if (fd != -1)
607         close(fd);
608       unlink(fnmsg.s);
609       _exit(0);
610    }
611 }