chiark / gitweb /
Debianization and various other fixes.
[ezmlm] / ezmlm-store.c
1 /*$Id: ezmlm-store.c,v 1.52 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 "stralloc.h"
7 #include "subfd.h"
8 #include "strerr.h"
9 #include "error.h"
10 #include "qmail.h"
11 #include "env.h"
12 #include "lock.h"
13 #include "sig.h"
14 #include "open.h"
15 #include "getln.h"
16 #include "str.h"
17 #include "fmt.h"
18 #include "readwrite.h"
19 #include "auto_bin.h"
20 #include "fork.h"
21 #include "wait.h"
22 #include "exit.h"
23 #include "substdio.h"
24 #include "getconf.h"
25 #include "datetime.h"
26 #include "now.h"
27 #include "date822fmt.h"
28 #include "cookie.h"
29 #include "sgetopt.h"
30 #include "errtxt.h"
31 #include "idx.h"
32 #include "copy.h"
33 #include "subscribe.h"
34 #include "mime.h"
35
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 */
45
46 #define FATAL "ezmlm-store: fatal: "
47
48 void die_usage()
49 {
50   strerr_die1x(100,"ezmlm-store: usage: ezmlm-store [-cCmMpPrRsSvV] dir");
51 }
52 void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); }
53
54 stralloc fnmsg = {0};
55
56 void die_msg() { strerr_die4sys(111,FATAL,ERR_WRITE,fnmsg.s,": "); }
57
58 int fdmsg;
59 int fdmod;
60 int pid;
61 int match;
62
63 char strnum[FMT_ULONG];
64 char date[DATE822FMT];
65 char hash[COOKIE];
66 char boundary[COOKIE];
67 datetime_sec when;
68 struct datetime dt;
69 struct stat st;
70
71 void *psql = (void *) 0;
72
73 stralloc fnbase = {0};
74 stralloc line = {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};
84 stralloc key = {0};
85 stralloc subject = {0};
86 stralloc moderators = {0};
87 stralloc charset = {0};
88 stralloc sendopt = {0};
89
90 struct qmail qq;
91 int qqwrite(fd,buf,len) int fd; char *buf; unsigned int len;
92 {
93   qmail_put(&qq,buf,len);
94   return len;
95 }
96
97 int subto(s,l)
98 char *s;
99 unsigned int l;
100 {
101   qmail_put(&qq,"T",1);
102   qmail_put(&qq,s,l);
103   qmail_put(&qq,"",1);
104   return (int) l;
105 }
106
107 char qqbuf[1];
108 substdio ssqq = SUBSTDIO_FDBUF(qqwrite,-1,qqbuf,sizeof(qqbuf));
109
110 substdio ssin;
111 char inbuf[1024];
112
113 substdio ssmsg;
114 char msgbuf[1024];
115
116 substdio sstext;
117 char textbuf[512];
118
119 substdio sssub;
120 char subbuf[512];
121
122 void transferenc()
123 {
124         if (flagcd) {
125           qmail_puts(&qq,"\nContent-Transfer-Encoding: ");
126           if (flagcd == 'Q')
127             qmail_puts(&qq,"Quoted-Printable\n\n");
128           else
129             qmail_puts(&qq,"base64\n\n");
130         } else
131           qmail_puts(&qq,"\n\n");
132 }
133
134 void makehash(act)
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 */
138 {
139   int d;
140
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();
147 }
148
149 void main(argc,argv)
150 int argc;
151 char **argv;
152 {
153   char *dir;
154   int fdlock;
155   char *sender;
156   int match;
157   int flaginheader;
158   int flagmodpost;
159   int flagremote;
160   char *pmod;
161   char *err;
162   int opt;
163   unsigned int i;
164   char szchar[2] = "-";
165   char *sendargs[4];
166   int child,wstat;
167
168   (void) umask(022);
169   sig_pipeignore();
170
171   if (!stralloc_copys(&sendopt," -")) die_nomem();
172   while ((opt = getopt(argc,argv,"bBcCmMpPrRsSvV")) != opteof)
173     switch(opt) {
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 */
183       case 'C':
184       case 'r':
185       case 'R':
186         szchar[0] = (char) opt & 0xff;
187         if (!stralloc_append(&sendopt,szchar)) die_nomem();
188         break;
189       case 'v':
190       case 'V': strerr_die2x(0,"ezmlm-store version: ",EZIDX_VERSION);
191       default:
192         die_usage();
193     }
194
195   sender = env_get("SENDER");
196
197   if (sender) {
198     if (!*sender || str_equal(sender,"#@[]"))
199       strerr_die2x(100,FATAL,ERR_BOUNCE);
200   }
201
202   dir = argv[optind];
203   if (!dir) die_usage();
204
205   if (chdir(dir) == -1)
206     strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": ");
207
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";
212     sendargs[1] = "-c";
213     if (!stralloc_copys(&line,auto_bin)) die_nomem();
214     if (!stralloc_cats(&line,"/ezmlm-send")) die_nomem();
215     if (sendopt.len > 2)
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;
222     sendargs[3] = 0;
223     switch(child = fork()) {
224       case -1:
225         strerr_die2sys(111,FATAL,ERR_FORK);
226       case 0:
227         execvp(*sendargs,sendargs);
228         if (errno == error_txtbsy || errno == error_nomem ||
229             errno == error_io)
230           strerr_die5sys(111,FATAL,ERR_EXECUTE,"/bin/sh -c ",sendargs[2],": ");
231         else
232           strerr_die5sys(100,FATAL,ERR_EXECUTE,"/bin/sh -c ",sendargs[2],": ");
233     }
234          /* parent */
235     wait_pid(&wstat,child);
236     if (wait_crashed(wstat))
237       strerr_die2x(111,FATAL,ERR_CHILD_CRASHED);
238     switch(wait_exitcode(wstat)) {
239       case 100:
240         strerr_die2x(100,FATAL,"Fatal error from child");
241       case 111:
242          strerr_die2x(111,FATAL,"Temporary error from child");
243       case 0:
244         _exit(0);
245       default:
246         strerr_die2x(111,FATAL,"Unknown temporary error from child");
247     }
248   }
249
250   if (!moderators.len || !(moderators.s[0] == '/')) {
251     if (!stralloc_copys(&moderators,dir)) die_nomem();
252     if (!stralloc_cats(&moderators,"/mod")) die_nomem();
253   }
254   if (!stralloc_0(&moderators)) die_nomem();
255
256   if (sender) {
257       pmod = issub(moderators.s,sender,(char *) 0,FATAL);
258       closesql();
259                                 /* sender = moderator? */
260   } else
261     pmod = 0;
262
263   if (!pmod && !flagpublic)
264     strerr_die2x(100,FATAL,ERR_NO_POST);
265
266   switch(slurp("key",&key,32)) {
267     case -1:
268       strerr_die4sys(111,FATAL,ERR_READ,dir,"/key: ");
269     case 0:
270       strerr_die4x(100,FATAL,dir,"/key",ERR_NOEXIST);
271   }
272
273   getconf_line(&outhost,"outhost",1,FATAL,dir);
274   getconf_line(&outlocal,"outlocal",1,FATAL,dir);
275   getconf_line(&mailinglist,"mailinglist",1,FATAL,dir);
276
277   fdlock = open_append("mod/lock");
278   if (fdlock == -1)
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: ");
282
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();
288
289   if (!stralloc_copys(&returnpath,"Return-Path: <")) die_nomem();
290   if (sender) {
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 */
297   }
298   if (!stralloc_cats(&returnpath,">\n")) die_nomem();
299
300  pid = getpid();                /* unique file name */
301  for (i = 0;;++i)               /* got lock - nobody else can add files */
302   {
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 */
312    if (i == 2)
313      strerr_die2x(111,FATAL,ERR_UNIQUE);
314    sleep(2);
315   }
316
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();
321   makehash(&action);
322   if (!quote(&quoted,&outlocal)) die_nomem();
323   if (!stralloc_copy(&reject,&quoted)) die_nomem();
324   if (!stralloc_cat(&reject,&action)) die_nomem();
325   if (!stralloc_0(&reject)) die_nomem();
326
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();
331   makehash(&action);
332   if (!stralloc_copy(&accept,&quoted)) die_nomem();
333   if (!stralloc_cat(&accept,&action)) die_nomem();
334   if (!stralloc_0(&accept)) die_nomem();
335
336   set_cpoutlocal(&outlocal);
337   set_cpouthost(&outhost);
338   set_cptarget(accept.s);       /* for copy () */
339   set_cpconfirm(reject.s);
340
341   fdmsg = open_trunc(fnmsg.s);
342   if (fdmsg == -1)
343     strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnmsg.s,": ");
344   substdio_fdbuf(&ssmsg,write,fdmsg,msgbuf,sizeof(msgbuf));
345
346   if (qmail_open(&qq, (stralloc *) 0) == -1)            /* Open mailer */
347     strerr_die2sys(111,FATAL,ERR_QMAIL_QUEUE);
348
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);
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   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();
386     if (sender[i]) {
387       if (!stralloc_append(&line,"=")) die_nomem();
388       if (!stralloc_cats(&line,sender + i + 1)) die_nomem();
389     }
390     qmail_put(&qq,line.s,line.len);
391     qmail_puts(&qq,"@");
392     qmail_put(&qq,outhost.s,outhost.len);
393   }
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(&quoted,&outlocal)) die_nomem();
398   if (!stralloc_cat(&subject,&quoted)) die_nomem();
399   if (!stralloc_append(&subject,"@")) die_nomem();
400   if (!stralloc_cat(&subject,&outhost)) die_nomem();
401   if (flagmime) {
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';
408         }
409       }
410     } else
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);
421     transferenc();
422   } else {
423     qmail_put(&qq,subject.s,subject.len);
424     qmail_puts(&qq,"\n\n");
425   }
426   copy(&qq,"text/mod-request",flagcd,FATAL);
427   if (flagcd == 'B') {
428     encodeB("",0,&line,2,FATAL);
429     qmail_put(&qq,line.s,line.len);
430   }
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));
434
435   if (flagmime) {
436     qmail_puts(&qq,"\n--");
437     qmail_put(&qq,boundary,COOKIE);
438     qmail_puts(&qq,"\nContent-Type: message/rfc822\n\n");
439   }
440
441   qmail_put(&qq,returnpath.s,returnpath.len);
442   qmail_put(&qq,mydtline.s,mydtline.len);
443   flaginheader = 1;
444   for (;;) {
445     if (getln(&ssin,&line,&match,'\n') == -1)
446       strerr_die2sys(111,FATAL,ERR_READ_INPUT);
447     if (!match) break;
448     if (line.len == 1) flaginheader = 0;
449     if (flaginheader) {
450       if ((line.len == mydtline.len) &&
451                 !byte_diff(line.s,line.len,mydtline.s)) {
452         close(fdmsg);                   /* be nice - clean up */
453         unlink(fnmsg.s);
454         strerr_die2x(100,FATAL,ERR_LOOPING);
455       }
456       if (case_startb(line.s,line.len,"mailing-list:")) {
457         close(fdmsg);                   /* be nice - clean up */
458         unlink(fnmsg.s);
459         strerr_die2x(100,FATAL,ERR_MAILING_LIST);
460       }
461     }
462
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();
466   }
467
468   if (flagmime) {
469     qmail_puts(&qq,"\n--");
470     qmail_put(&qq,boundary,COOKIE);
471     qmail_puts(&qq,"--\n");
472   }
473
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 */
476 /* cleaned out.                                                           */
477
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 */
482
483   close(fdlock);
484
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 */
491     qmail_to(&qq,pmod);
492   else {
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();
497     }
498     putsubs(moderators.s,0,52,subto,1,FATAL);
499   }
500
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);
504   } else
505       strerr_die3x(111,FATAL,ERR_TMP_QMAIL_QUEUE,err+1);
506 }