1 #include <sys/types.h>
2 #include <sys/stat.h>
3 #include <unistd.h>
4 #include "stralloc.h"
5 #include "subfd.h"
6 #include "strerr.h"
7 #include "error.h"
8 #include "qmail.h"
9 #include "env.h"
10 #include "sender.h"
11 #include "lock.h"
12 #include "sig.h"
13 #include "open.h"
14 #include "getln.h"
15 #include "str.h"
16 #include "fmt.h"
17 #include "readwrite.h"
18 #include "wait.h"
19 #include "exit.h"
20 #include "substdio.h"
21 #include "getconf.h"
22 #include "datetime.h"
23 #include "now.h"
24 #include "cookie.h"
25 #include "getconfopt.h"
26 #include "messages.h"
27 #include "byte.h"
28 #include "case.h"
29 #include "quote.h"
30 #include "hdr.h"
31 #include "die.h"
32 #include "idx.h"
33 #include "copy.h"
34 #include "subdb.h"
35 #include "mime.h"
36 #include "wrap.h"
37 #include "config.h"
38 #include "auto_version.h"
39 
40 static int flagmime = MOD_MIME;		/* default is message as attachment */
41 static int flagmodpostonly = -1;	/* default anyone can post */
42 					/* =1 for only moderators can */
43 static int flagself = 0;		/* `modpost` mods approve own posts */
44 					/* but mod/ is used for moderators */
45 					/* of other posts. Def=no=0 */
46 static int flagconfirm = -1;           /* if true, sender must approve its own posts */
47 static int flagbody = 1;		/* body of message enclosed with mod request */
48 					/* 0 => headers only */
49 
50 const char FATAL[] = "ezmlm-store: fatal: ";
51 const char USAGE[] =
52 "ezmlm-store: usage: ezmlm-store [-cCmMpPrRsSvV] dir";
53 
54 static stralloc sendopt = {0};
55 
56 static struct option options[] = {
57   OPT_FLAG(flagbody,'b',1,0),
58   OPT_FLAG(flagbody,'B',0,0),
59   OPT_FLAG(flagmime,'m',1,0),
60   OPT_FLAG(flagmime,'M',0,0),
61   OPT_FLAG(flagmodpostonly,'p',0,0), /* anyone can post (still mod'd) */
62   OPT_FLAG(flagmodpostonly,'P',1,"modpostonly"), /* only moderators can post */
63   OPT_FLAG(flagself,'s',1,0),	     /* modpost and DIR/mod diff fxns */
64   OPT_FLAG(flagself,'S',0,0),	     /* same fxn */
65   OPT_FLAG(flagconfirm,'y',1,"confirmpost"), /* force post confirmation */
66   OPT_FLAG(flagconfirm,'Y',0,0),     /* disable post confirmation */
67   OPT_COPY_FLAG(sendopt,'c'),
68   OPT_COPY_FLAG(sendopt,'C'),
69   OPT_COPY_FLAG(sendopt,'r'),
70   OPT_COPY_FLAG(sendopt,'R'),
71   OPT_END
72 };
73 
74 static stralloc fnmsg = {0};
75 
die_msg(void)76 static void die_msg(void) { strerr_die2sys(111,FATAL,MSG1(ERR_WRITE,fnmsg.s)); }
77 
78 char boundary[COOKIE];
79 stralloc line = {0};
80 stralloc quoted = {0};
81 
82 struct qmail qq;
83 
subto(const char * s,unsigned int l)84 static int subto(const char *s,unsigned int l)
85 {
86   qmail_put(&qq,"T",1);
87   qmail_put(&qq,s,l);
88   qmail_put(&qq,"",1);
89   return (int) l;
90 }
91 
makeacthash(stralloc * act)92 static void makeacthash(stralloc *act)
93 /* act is expected to be -reject-ddddd.ttttt or -accept-ddddd.ttttt and
94  * has to be 0-terminated. */
95 /* The routine will add .hash@outhost to act. act will NOT be 0-terminated */
96 {
97   char hash[COOKIE];
98   int d;
99 
100   d = 2 + str_chr(act->s + 1,'-');
101   cookie(hash,key.s,key.len,act->s + d,"","a");
102   *(act->s + act->len - 1) = '.';	/* we put a '.' Bad, but works */
103   if (!stralloc_catb(act,hash,COOKIE)) die_nomem();
104   if (!stralloc_cats(act,"@")) die_nomem();
105   if (!stralloc_cat(act,&outhost)) die_nomem();
106   set_cphash(hash);
107 }
108 
109 static substdio ssin;
110 static char inbuf[1024];
111 
112 static substdio ssmsg;
113 static char msgbuf[1024];
114 
main(int argc,char ** argv)115 int main(int argc,char **argv)
116 {
117   char strnum[FMT_ULONG];
118   datetime_sec when;
119   struct stat st;
120 
121   stralloc fnbase = {0};
122   stralloc mydtline = {0};
123   stralloc returnpath = {0};
124   stralloc accept = {0};
125   stralloc action = {0};
126   stralloc reject = {0};
127   stralloc moderators = {0};
128 
129   char hash[COOKIE];
130   const char *dir;
131   int fdlock;
132   const char *sender;
133   int match;
134   int flaginheader;
135   int flagmodpost;
136   int flagremote;
137   int ismod;
138   stralloc mod = {0};
139   const char *err;
140   unsigned int i;
141   int fdmsg;
142   int pid;
143 
144   (void) umask(022);
145   sig_pipeignore();
146 
147   sender = get_sender();
148 
149   if (sender) {
150     if (!*sender || str_equal(sender,"#@[]"))
151       strerr_die2x(100,FATAL,MSG(ERR_BOUNCE));
152   }
153 
154   if (!stralloc_copys(&sendopt,"-")) die_nomem();
155   getconfopt(argc,argv,options,1,&dir);
156   initsub(0);
157 
158   flagmodpost = getconf_line(&moderators,"modpost",0);
159   flagremote = getconf_isset("remote");
160   if (!flagmodpost && !flagconfirm) /* not msg-mod. Exec ezmlm-send */
161     wrap_execbin("/ezmlm-send", &sendopt, dir);
162 
163   if (!moderators.len) {
164     if (!stralloc_copys(&moderators,"mod")) die_nomem();
165   }
166   if (!stralloc_0(&moderators)) die_nomem();
167 
168   if (sender) {
169     ismod = issub(moderators.s,sender,&mod);
170     closesub();
171 				/* sender = moderator? */
172   } else
173     ismod = 0;
174 
175   if (!ismod && flagmodpostonly)
176     strerr_die2x(100,FATAL,MSG(ERR_NO_POST));
177 
178   fdlock = lockfile("mod/lock");
179 
180   if (!stralloc_copys(&mydtline, flagconfirm
181     ? "Delivered-To: confirm to "
182     : "Delivered-To: moderator for ")) die_nomem();
183   if (!stralloc_catb(&mydtline,outlocal.s,outlocal.len)) die_nomem();
184   if (!stralloc_append(&mydtline,'@')) die_nomem();
185   if (!stralloc_catb(&mydtline,outhost.s,outhost.len)) die_nomem();
186   if (!stralloc_cats(&mydtline,"\n")) die_nomem();
187 
188   if (!stralloc_copys(&returnpath,"Return-Path: <")) die_nomem();
189   if (sender) {
190     if (!stralloc_cats(&returnpath,sender)) die_nomem();
191     for (i = 14; i < returnpath.len;++i)
192       if (returnpath.s[i] == '\n' || !returnpath.s[i] )
193         returnpath.s[i] = '_';
194 		/* NUL and '\n' are bad, but we don't quote since this is */
195 		/* only for ezmlm-moderate, NOT for SMTP */
196   }
197   if (!stralloc_cats(&returnpath,">\n")) die_nomem();
198 
199  pid = getpid();		/* unique file name */
200  for (i = 0;;++i)		/* got lock - nobody else can add files */
201   {
202    when = now();		/* when is also used later for date! */
203    if (!stralloc_copys(&fnmsg, flagconfirm?"mod/unconfirmed/":"mod/pending/")) die_nomem();
204    if (!stralloc_copyb(&fnbase,strnum,fmt_ulong(strnum,when))) die_nomem();
205    if (!stralloc_append(&fnbase,'.')) die_nomem();
206    if (!stralloc_catb(&fnbase,strnum,fmt_ulong(strnum,pid))) die_nomem();
207    if (!stralloc_cat(&fnmsg,&fnbase)) die_nomem();
208    if (!stralloc_0(&fnmsg)) die_nomem();
209    if (stat(fnmsg.s,&st) == -1) if (errno == error_noent) break;
210    /* really should never get to this point */
211    if (i == 2)
212      strerr_die2x(111,FATAL,MSG(ERR_UNIQUE));
213    sleep(2);
214   }
215 
216   if (!stralloc_copys(&action,"-")) die_nomem();
217   if (!stralloc_cats(&action,flagconfirm?ACTION_DISCARD:ACTION_REJECT)) die_nomem();
218   if (!stralloc_cat(&action,&fnbase)) die_nomem();
219   if (!stralloc_0(&action)) die_nomem();
220   makeacthash(&action);
221   if (!quote(&quoted,&outlocal)) die_nomem();
222   if (!stralloc_copy(&reject,&quoted)) die_nomem();
223   if (!stralloc_cat(&reject,&action)) die_nomem();
224   if (!stralloc_0(&reject)) die_nomem();
225 
226   if (!stralloc_copys(&action,"-")) die_nomem();
227   if (!stralloc_cats(&action,flagconfirm?ACTION_CONFIRM:ACTION_ACCEPT)) die_nomem();
228   if (!stralloc_cat(&action,&fnbase)) die_nomem();
229   if (!stralloc_0(&action)) die_nomem();
230   makeacthash(&action);
231   if (!stralloc_copy(&accept,&quoted)) die_nomem();
232   if (!stralloc_cat(&accept,&action)) die_nomem();
233   if (!stralloc_0(&accept)) die_nomem();
234 
235   set_cptarget(accept.s);	/* for copy () */
236   set_cpaction(flagconfirm?ACTION_CONFIRM:ACTION_ACCEPT);
237   set_cpconfirm(reject.s,quoted.len);
238   set_cpwhen(when);
239 
240   fdmsg = open_trunc(fnmsg.s);
241   if (fdmsg == -1)
242     strerr_die2sys(111,FATAL,MSG1(ERR_WRITE,fnmsg.s));
243   substdio_fdbuf(&ssmsg,write,fdmsg,msgbuf,sizeof(msgbuf));
244 
245   if (qmail_open(&qq) == -1)		/* Open mailer */
246     strerr_die2sys(111,FATAL,MSG(ERR_QMAIL_QUEUE));
247 
248   hdr_add2s("Mailing-List: ",MSG(TXT_MAILING_LIST));
249   if (listid.len > 0)
250     hdr_add2("List-ID: ",listid.s,listid.len);
251   hdr_datemsgid(when);
252   if (flagconfirm)
253     hdr_from("-owner");
254   else
255     hdr_add2s("From: ",reject.s);
256   hdr_add2s("Reply-To: ",accept.s);
257   if (!flagconfirm && !ismod && flagremote) {	/* if remote admin add -allow- address */
258     qmail_puts(&qq,"Cc: ");	/* for ezmlm-gate users */
259     strnum[fmt_ulong(strnum,(unsigned long) when)] = 0;
260     cookie(hash,key.s,key.len-FLD_ALLOW,strnum,sender,"t");
261     if (!stralloc_copy(&line,&outlocal)) die_nomem();
262     if (!stralloc_cats(&line,"-allow-tc.")) die_nomem();
263     if (!stralloc_cats(&line,strnum)) die_nomem();
264     if (!stralloc_append(&line,'.')) die_nomem();
265     if (!stralloc_catb(&line,hash,COOKIE)) die_nomem();
266     if (!stralloc_append(&line,'-')) die_nomem();
267     i = str_rchr(sender,'@');
268     if (!stralloc_catb(&line,sender,i)) die_nomem();
269     if (sender[i]) {
270       if (!stralloc_append(&line,'=')) die_nomem();
271       if (!stralloc_cats(&line,sender + i + 1)) die_nomem();
272     }
273     qmail_put(&qq,line.s,line.len);
274     qmail_puts(&qq,"@");
275     qmail_put(&qq,outhost.s,outhost.len);
276     qmail_puts(&qq,"\n");
277   }
278   qmail_puts(&qq,"To: <");
279   if (flagconfirm) {
280     if (sender)
281       qmail_puts(&qq, sender);
282   } else {
283     if (!quote(&quoted,&outlocal))
284       die_nomem();
285     qmail_put(&qq,quoted.s,quoted.len);
286     qmail_puts(&qq,"-moderators@");
287     qmail_put(&qq,outhost.s,outhost.len);
288   }
289   qmail_puts(&qq,">\n");
290   /* FIXME: Drop the custom subject hack and use hdr_listsubject1 */
291   hdr_subject(flagconfirm ? MSG(SUB_CONFIRM_POST) : MSG(SUB_MODERATE));
292   if (flagmime) {
293     hdr_mime(CTYPE_MULTIPART);
294     hdr_boundary(0);
295     hdr_ctype(CTYPE_TEXT);
296     hdr_transferenc();
297   } else
298     qmail_puts(&qq,"\n\n");
299   copy(&qq,flagconfirm?"text/post-confirm":"text/mod-request",flagcd);
300   if (flagcd == 'B') {
301     encodeB("",0,&line,2);
302     qmail_put(&qq,line.s,line.len);
303   }
304   if (substdio_put(&ssmsg,returnpath.s,returnpath.len) == -1) die_msg();
305   if (substdio_put(&ssmsg,mydtline.s,mydtline.len) == -1) die_msg();
306   substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf));
307 
308   if (flagmime) {
309     hdr_boundary(0);
310     hdr_ctype(CTYPE_MESSAGE);
311     qmail_puts(&qq, "\n");
312   }
313 
314   qmail_put(&qq,returnpath.s,returnpath.len);
315   qmail_put(&qq,mydtline.s,mydtline.len);
316   flaginheader = 1;
317   for (;;) {
318     if (getln(&ssin,&line,&match,'\n') == -1)
319       strerr_die2sys(111,FATAL,MSG(ERR_READ_INPUT));
320     if (!match) break;
321     if (line.len == 1) flaginheader = 0;
322     if (flaginheader) {
323       if ((line.len == mydtline.len) &&
324 		!byte_diff(line.s,line.len,mydtline.s)) {
325 	close(fdmsg);			/* be nice - clean up */
326 	unlink(fnmsg.s);
327         strerr_die2x(100,FATAL,MSG(ERR_LOOPING));
328       }
329       if (case_startb(line.s,line.len,"mailing-list:")) {
330 	close(fdmsg);			/* be nice - clean up */
331 	unlink(fnmsg.s);
332         strerr_die2x(100,FATAL,MSG(ERR_MAILING_LIST));
333       }
334     }
335 
336     if (flagbody || flaginheader)	/* skip body if !flagbody */
337       qmail_put(&qq,line.s,line.len);
338     if (substdio_put(&ssmsg,line.s,line.len) == -1) die_msg();
339   }
340 
341   if (flagmime)
342     hdr_boundary(1);
343 
344 /* close archive before qmail. Loss of qmail will result in re-run, and   */
345 /* worst case this results in a duplicate msg sitting orphaned until it's */
346 /* cleaned out.                                                           */
347 
348   if (substdio_flush(&ssmsg) == -1) die_msg();
349   if (fsync(fdmsg) == -1) die_msg();
350   if (fchmod(fdmsg,MODE_MOD_MSG | 0700) == -1) die_msg();
351   if (close(fdmsg) == -1) die_msg(); /* NFS stupidity */
352 
353   close(fdlock);
354 
355   if (flagconfirm) {
356     qmail_from(&qq,reject.s);			/* envelope sender */
357   } else {
358     if (!stralloc_copy(&line,&outlocal)) die_nomem();
359     if (!stralloc_cats(&line,"-return-@")) die_nomem();
360     if (!stralloc_cat(&line,&outhost)) die_nomem();
361     if (!stralloc_0(&line)) die_nomem();
362     qmail_from(&qq,line.s);			/* envelope sender */
363   }
364   if (flagconfirm)				/* to sender */
365     qmail_to(&qq,sender);
366   else if (ismod)				/* to moderator only */
367     qmail_to(&qq,mod.s);
368   else {
369     if (flagself) {				/* to all moderators */
370       if (!stralloc_copys(&moderators,"mod")) die_nomem();
371       if (!stralloc_0(&moderators)) die_nomem();
372     }
373     putsubs(moderators.s,0,52,subto);
374   }
375 
376   if (*(err = qmail_close(&qq)) == '\0') {
377       strnum[fmt_ulong(strnum,qmail_qp(&qq))] = 0;
378       strerr_die2x(0,"ezmlm-store: info: qp ",strnum);
379   } else
380       strerr_die4x(111,FATAL,MSG(ERR_TMP_QMAIL_QUEUE),": ",err+1);
381 }
382