1 /*$Id: ezmlm-store.c,v 1.52 1999/10/09 16:49:56 lindberg Exp $*/
2 /*$Name: ezmlm-idx-040 $*/
18 #include "readwrite.h"
27 #include "date822fmt.h"
33 #include "subscribe.h"
36 int flagmime
= MOD_MIME
; /* default is message as attachment */
37 int flagpublic
= 1; /* default anyone can post */
38 /* =0 for only moderators can */
39 int flagself
= 0; /* `modpost` mods approve own posts */
40 /* but mod/ is used for moderators */
41 /* of other posts. Def=no=0 */
42 char flagcd
= '\0'; /* default: don't use quoted-printable */
43 int flagbody
= 1; /* body of message enclosed with mod request */
44 /* 0 => headers only */
46 #define FATAL "ezmlm-store: fatal: "
50 strerr_die1x(100,"ezmlm-store: usage: ezmlm-store [-cCmMpPrRsSvV] dir");
52 void die_nomem() { strerr_die2x(111,FATAL
,ERR_NOMEM
); }
56 void die_msg() { strerr_die4sys(111,FATAL
,ERR_WRITE
,fnmsg
.s
,": "); }
63 char strnum
[FMT_ULONG
];
64 char date
[DATE822FMT
];
66 char boundary
[COOKIE
];
71 void *psql
= (void *) 0;
73 stralloc fnbase
= {0};
75 stralloc mailinglist
= {0};
76 stralloc outlocal
= {0};
77 stralloc outhost
= {0};
78 stralloc mydtline
= {0};
79 stralloc returnpath
= {0};
80 stralloc accept
= {0};
81 stralloc action
= {0};
82 stralloc reject
= {0};
83 stralloc quoted
= {0};
85 stralloc subject
= {0};
86 stralloc moderators
= {0};
87 stralloc charset
= {0};
88 stralloc sendopt
= {0};
91 int qqwrite(fd
,buf
,len
) int fd
; char *buf
; unsigned int len
;
93 qmail_put(&qq
,buf
,len
);
101 qmail_put(&qq
,"T",1);
108 substdio ssqq
= SUBSTDIO_FDBUF(qqwrite
,-1,qqbuf
,sizeof(qqbuf
));
125 qmail_puts(&qq
,"\nContent-Transfer-Encoding: ");
127 qmail_puts(&qq
,"Quoted-Printable\n\n");
129 qmail_puts(&qq
,"base64\n\n");
131 qmail_puts(&qq
,"\n\n");
135 stralloc
*act
; /* has to be 0-terminated */
136 /* act is expected to be -reject-ddddd.ttttt or -accept-ddddd.ttttt */
137 /* The routine will add .hash@outhost to act. act will NOT be 0-terminated */
141 d
= 2 + str_chr(act
->s
+ 1,'-');
142 cookie(hash
,key
.s
,key
.len
,act
->s
+ d
,"","a");
143 *(act
->s
+ act
->len
- 1) = '.'; /* we put a '.' Bad, but works */
144 if (!stralloc_catb(act
,hash
,COOKIE
)) die_nomem();
145 if (!stralloc_cats(act
,"@")) die_nomem();
146 if (!stralloc_cat(act
,&outhost
)) die_nomem();
164 char szchar
[2] = "-";
171 if (!stralloc_copys(&sendopt
," -")) die_nomem();
172 while ((opt
= getopt(argc
,argv
,"bBcCmMpPrRsSvV")) != opteof
)
174 case 'b': flagbody
= 1; break;
175 case 'B': flagbody
= 0; break;
176 case 'm': flagmime
= 1; break;
177 case 'M': flagmime
= 0; break;
178 case 'p': flagpublic
= 1; break; /* anyone can post (still moderated)*/
179 case 'P': flagpublic
= 0; break; /* only moderators can post */
180 case 's': flagself
= 1; break; /* modpost and DIR/mod diff fxns */
181 case 'S': flagself
= 0; break; /* same fxn */
182 case 'c': /* ezmlm-send flags */
186 szchar
[0] = (char) opt
& 0xff;
187 if (!stralloc_append(&sendopt
,szchar
)) die_nomem();
190 case 'V': strerr_die2x(0,"ezmlm-store version: ",EZIDX_VERSION
);
195 sender
= env_get("SENDER");
198 if (!*sender
|| str_equal(sender
,"#@[]"))
199 strerr_die2x(100,FATAL
,ERR_BOUNCE
);
203 if (!dir
) die_usage();
205 if (chdir(dir
) == -1)
206 strerr_die4sys(111,FATAL
,ERR_SWITCH
,dir
,": ");
208 flagmodpost
= getconf_line(&moderators
,"modpost",0,FATAL
,dir
);
209 flagremote
= getconf_line(&line
,"remote",0,FATAL
,dir
);
210 if (!flagmodpost
) { /* not msg-mod. Pipe to ezmlm-send */
211 sendargs
[0] = "/bin/sh";
213 if (!stralloc_copys(&line
,auto_bin
)) die_nomem();
214 if (!stralloc_cats(&line
,"/ezmlm-send")) die_nomem();
216 if (!stralloc_cat(&line
,&sendopt
)) die_nomem();
217 if (!stralloc_cats(&line
," '")) die_nomem();
218 if (!stralloc_cats(&line
,dir
)) die_nomem();
219 if (!stralloc_cats(&line
,"'")) die_nomem();
220 if (!stralloc_0(&line
)) die_nomem();
221 sendargs
[2] = line
.s
;
223 switch(child
= fork()) {
225 strerr_die2sys(111,FATAL
,ERR_FORK
);
227 execvp(*sendargs
,sendargs
);
228 if (errno
== error_txtbsy
|| errno
== error_nomem
||
230 strerr_die5sys(111,FATAL
,ERR_EXECUTE
,"/bin/sh -c ",sendargs
[2],": ");
232 strerr_die5sys(100,FATAL
,ERR_EXECUTE
,"/bin/sh -c ",sendargs
[2],": ");
235 wait_pid(&wstat
,child
);
236 if (wait_crashed(wstat
))
237 strerr_die2x(111,FATAL
,ERR_CHILD_CRASHED
);
238 switch(wait_exitcode(wstat
)) {
240 strerr_die2x(100,FATAL
,"Fatal error from child");
242 strerr_die2x(111,FATAL
,"Temporary error from child");
246 strerr_die2x(111,FATAL
,"Unknown temporary error from child");
250 if (!moderators
.len
|| !(moderators
.s
[0] == '/')) {
251 if (!stralloc_copys(&moderators
,dir
)) die_nomem();
252 if (!stralloc_cats(&moderators
,"/mod")) die_nomem();
254 if (!stralloc_0(&moderators
)) die_nomem();
257 pmod
= issub(moderators
.s
,sender
,(char *) 0,FATAL
);
259 /* sender = moderator? */
263 if (!pmod
&& !flagpublic
)
264 strerr_die2x(100,FATAL
,ERR_NO_POST
);
266 switch(slurp("key",&key
,32)) {
268 strerr_die4sys(111,FATAL
,ERR_READ
,dir
,"/key: ");
270 strerr_die4x(100,FATAL
,dir
,"/key",ERR_NOEXIST
);
273 getconf_line(&outhost
,"outhost",1,FATAL
,dir
);
274 getconf_line(&outlocal
,"outlocal",1,FATAL
,dir
);
275 getconf_line(&mailinglist
,"mailinglist",1,FATAL
,dir
);
277 fdlock
= open_append("mod/lock");
279 strerr_die4sys(111,FATAL
,ERR_OPEN
,dir
,"/mod/lock: ");
280 if (lock_ex(fdlock
) == -1)
281 strerr_die4sys(111,FATAL
,ERR_OBTAIN
,dir
,"/mod/lock: ");
283 if (!stralloc_copys(&mydtline
,"Delivered-To: moderator for ")) die_nomem();
284 if (!stralloc_catb(&mydtline
,outlocal
.s
,outlocal
.len
)) die_nomem();
285 if (!stralloc_append(&mydtline
,"@")) die_nomem();
286 if (!stralloc_catb(&mydtline
,outhost
.s
,outhost
.len
)) die_nomem();
287 if (!stralloc_cats(&mydtline
,"\n")) die_nomem();
289 if (!stralloc_copys(&returnpath
,"Return-Path: <")) die_nomem();
291 if (!stralloc_cats(&returnpath
,sender
)) die_nomem();
292 for (i
= 14; i
< returnpath
.len
;++i
)
293 if (returnpath
.s
[i
] == '\n' || !returnpath
.s
[i
] )
294 returnpath
.s
[i
] = '_';
295 /* NUL and '\n' are bad, but we don't quote since this is */
296 /* only for ezmlm-moderate, NOT for SMTP */
298 if (!stralloc_cats(&returnpath
,">\n")) die_nomem();
300 pid
= getpid(); /* unique file name */
301 for (i
= 0;;++i
) /* got lock - nobody else can add files */
303 when
= now(); /* when is also used later for date! */
304 if (!stralloc_copys(&fnmsg
,"mod/pending/")) die_nomem();
305 if (!stralloc_copyb(&fnbase
,strnum
,fmt_ulong(strnum
,when
))) die_nomem();
306 if (!stralloc_append(&fnbase
,".")) die_nomem();
307 if (!stralloc_catb(&fnbase
,strnum
,fmt_ulong(strnum
,pid
))) die_nomem();
308 if (!stralloc_cat(&fnmsg
,&fnbase
)) die_nomem();
309 if (!stralloc_0(&fnmsg
)) die_nomem();
310 if (stat(fnmsg
.s
,&st
) == -1) if (errno
== error_noent
) break;
311 /* really should never get to this point */
313 strerr_die2x(111,FATAL
,ERR_UNIQUE
);
317 if (!stralloc_copys(&action
,"-")) die_nomem();
318 if (!stralloc_cats(&action
,ACTION_REJECT
)) die_nomem();
319 if (!stralloc_cat(&action
,&fnbase
)) die_nomem();
320 if (!stralloc_0(&action
)) die_nomem();
322 if (!quote("ed
,&outlocal
)) die_nomem();
323 if (!stralloc_copy(&reject
,"ed
)) die_nomem();
324 if (!stralloc_cat(&reject
,&action
)) die_nomem();
325 if (!stralloc_0(&reject
)) die_nomem();
327 if (!stralloc_copys(&action
,"-")) die_nomem();
328 if (!stralloc_cats(&action
,ACTION_ACCEPT
)) die_nomem();
329 if (!stralloc_cat(&action
,&fnbase
)) die_nomem();
330 if (!stralloc_0(&action
)) die_nomem();
332 if (!stralloc_copy(&accept
,"ed
)) die_nomem();
333 if (!stralloc_cat(&accept
,&action
)) die_nomem();
334 if (!stralloc_0(&accept
)) die_nomem();
336 set_cpoutlocal(&outlocal
);
337 set_cpouthost(&outhost
);
338 set_cptarget(accept
.s
); /* for copy () */
339 set_cpconfirm(reject
.s
);
341 fdmsg
= open_trunc(fnmsg
.s
);
343 strerr_die6sys(111,FATAL
,ERR_WRITE
,dir
,"/",fnmsg
.s
,": ");
344 substdio_fdbuf(&ssmsg
,write
,fdmsg
,msgbuf
,sizeof(msgbuf
));
346 if (qmail_open(&qq
, (stralloc
*) 0) == -1) /* Open mailer */
347 strerr_die2sys(111,FATAL
,ERR_QMAIL_QUEUE
);
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
,"List-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 qmail_puts(&qq
,reject
.s
);
372 qmail_puts(&qq
,"\nReply-To: ");
373 qmail_puts(&qq
,accept
.s
);
374 if (!pmod
&& flagremote
) { /* if remote admin add -allow- address */
375 qmail_puts(&qq
,"\nCc: "); /* for ezmlm-gate users */
376 strnum
[fmt_ulong(strnum
,(unsigned long) when
)] = 0;
377 cookie(hash
,key
.s
,key
.len
-FLD_ALLOW
,strnum
,sender
,"t");
378 if (!stralloc_copy(&line
,&outlocal
)) die_nomem();
379 if (!stralloc_cats(&line
,"-allow-tc.")) die_nomem();
380 if (!stralloc_cats(&line
,strnum
)) die_nomem();
381 if (!stralloc_append(&line
,".")) die_nomem();
382 if (!stralloc_catb(&line
,hash
,COOKIE
)) die_nomem();
383 if (!stralloc_append(&line
,"-")) die_nomem();
384 i
= str_rchr(sender
,'@');
385 if (!stralloc_catb(&line
,sender
,i
)) die_nomem();
387 if (!stralloc_append(&line
,"=")) die_nomem();
388 if (!stralloc_cats(&line
,sender
+ i
+ 1)) die_nomem();
390 qmail_put(&qq
,line
.s
,line
.len
);
392 qmail_put(&qq
,outhost
.s
,outhost
.len
);
394 qmail_puts(&qq
,"\nTo: Recipient list not shown: ;");
395 if (!stralloc_copys(&subject
,"\nSubject: ")) die_nomem();
396 if (!stralloc_cats(&subject
,TXT_MODERATE
)) die_nomem();
397 if (!quote("ed
,&outlocal
)) die_nomem();
398 if (!stralloc_cat(&subject
,"ed
)) die_nomem();
399 if (!stralloc_append(&subject
,"@")) die_nomem();
400 if (!stralloc_cat(&subject
,&outhost
)) die_nomem();
402 if (getconf_line(&charset
,"charset",0,FATAL
,dir
)) {
403 if (charset
.len
>= 2 && charset
.s
[charset
.len
- 2] == ':') {
404 if (charset
.s
[charset
.len
- 1] == 'B' ||
405 charset
.s
[charset
.len
- 1] == 'Q') {
406 flagcd
= charset
.s
[charset
.len
- 1];
407 charset
.s
[charset
.len
- 2] = '\0';
411 if (!stralloc_copys(&charset
,TXT_DEF_CHARSET
)) die_nomem();
412 if (!stralloc_0(&charset
)) die_nomem();
413 qmail_puts(&qq
,"\nMIME-Version: 1.0\n");
414 qmail_puts(&qq
,"Content-Type: multipart/mixed;\n\tboundary=");
415 qmail_put(&qq
,boundary
,COOKIE
);
416 qmail_put(&qq
,subject
.s
,subject
.len
);
417 qmail_puts(&qq
,"\n\n--");
418 qmail_put(&qq
,boundary
,COOKIE
);
419 qmail_puts(&qq
,"\nContent-Type: text/plain; charset=");
420 qmail_puts(&qq
,charset
.s
);
423 qmail_put(&qq
,subject
.s
,subject
.len
);
424 qmail_puts(&qq
,"\n\n");
426 copy(&qq
,"text/mod-request",flagcd
,FATAL
);
428 encodeB("",0,&line
,2,FATAL
);
429 qmail_put(&qq
,line
.s
,line
.len
);
431 if (substdio_put(&ssmsg
,returnpath
.s
,returnpath
.len
) == -1) die_msg();
432 if (substdio_put(&ssmsg
,mydtline
.s
,mydtline
.len
) == -1) die_msg();
433 substdio_fdbuf(&ssin
,read
,0,inbuf
,sizeof(inbuf
));
436 qmail_puts(&qq
,"\n--");
437 qmail_put(&qq
,boundary
,COOKIE
);
438 qmail_puts(&qq
,"\nContent-Type: message/rfc822\n\n");
441 qmail_put(&qq
,returnpath
.s
,returnpath
.len
);
442 qmail_put(&qq
,mydtline
.s
,mydtline
.len
);
445 if (getln(&ssin
,&line
,&match
,'\n') == -1)
446 strerr_die2sys(111,FATAL
,ERR_READ_INPUT
);
448 if (line
.len
== 1) flaginheader
= 0;
450 if ((line
.len
== mydtline
.len
) &&
451 !byte_diff(line
.s
,line
.len
,mydtline
.s
)) {
452 close(fdmsg
); /* be nice - clean up */
454 strerr_die2x(100,FATAL
,ERR_LOOPING
);
456 if (case_startb(line
.s
,line
.len
,"mailing-list:")) {
457 close(fdmsg
); /* be nice - clean up */
459 strerr_die2x(100,FATAL
,ERR_MAILING_LIST
);
463 if (flagbody
|| flaginheader
) /* skip body if !flagbody */
464 qmail_put(&qq
,line
.s
,line
.len
);
465 if (substdio_put(&ssmsg
,line
.s
,line
.len
) == -1) die_msg();
469 qmail_puts(&qq
,"\n--");
470 qmail_put(&qq
,boundary
,COOKIE
);
471 qmail_puts(&qq
,"--\n");
474 /* close archive before qmail. Loss of qmail will result in re-run, and */
475 /* worst case this results in a duplicate msg sitting orphaned until it's */
478 if (substdio_flush(&ssmsg
) == -1) die_msg();
479 if (fsync(fdmsg
) == -1) die_msg();
480 if (fchmod(fdmsg
,MODE_MOD_MSG
| 0700) == -1) die_msg();
481 if (close(fdmsg
) == -1) die_msg(); /* NFS stupidity */
485 if (!stralloc_copy(&line
,&outlocal
)) die_nomem();
486 if (!stralloc_cats(&line
,"-return-@")) die_nomem();
487 if (!stralloc_cat(&line
,&outhost
)) die_nomem();
488 if (!stralloc_0(&line
)) die_nomem();
489 qmail_from(&qq
,line
.s
); /* envelope sender */
490 if (pmod
) /* to moderator only */
493 if (flagself
) { /* to all moderators */
494 if (!stralloc_copys(&moderators
,dir
)) die_nomem();
495 if (!stralloc_cats(&moderators
,"/mod")) die_nomem();
496 if (!stralloc_0(&moderators
)) die_nomem();
498 putsubs(moderators
.s
,0,52,subto
,1,FATAL
);
501 if (*(err
= qmail_close(&qq
)) == '\0') {
502 strnum
[fmt_ulong(strnum
,qmail_qp(&qq
))] = 0;
503 strerr_die2x(0,"ezmlm-store: info: qp ",strnum
);
505 strerr_die3x(111,FATAL
,ERR_TMP_QMAIL_QUEUE
,err
+1);