chiark / gitweb /
Import ezmlm 0.53
[ezmlm] / ezmlm-manage.c
1 #include <sys/types.h>
2 #include <sys/stat.h>
3 #include "error.h"
4 #include "stralloc.h"
5 #include "str.h"
6 #include "env.h"
7 #include "sig.h"
8 #include "slurp.h"
9 #include "getconf.h"
10 #include "strerr.h"
11 #include "byte.h"
12 #include "getln.h"
13 #include "case.h"
14 #include "qmail.h"
15 #include "substdio.h"
16 #include "readwrite.h"
17 #include "seek.h"
18 #include "quote.h"
19 #include "datetime.h"
20 #include "now.h"
21 #include "date822fmt.h"
22 #include "fmt.h"
23 #include "subscribe.h"
24 #include "cookie.h"
25
26 #define FATAL "ezmlm-manage: fatal: "
27 void die_usage() { strerr_die1x(100,"ezmlm-manage: usage: ezmlm-manage dir"); }
28 void die_nomem() { strerr_die2x(111,FATAL,"out of memory"); }
29 void die_badaddr()
30 {
31   strerr_die2x(100,FATAL,"I do not accept messages at this address (#5.1.1)");
32 }
33
34 stralloc inhost = {0};
35 stralloc outhost = {0};
36 stralloc inlocal = {0};
37 stralloc outlocal = {0};
38 stralloc key = {0};
39 stralloc mailinglist = {0};
40
41 datetime_sec when;
42 struct datetime dt;
43
44 char strnum[FMT_ULONG];
45 char date[DATE822FMT];
46 char hash[COOKIE];
47 datetime_sec hashdate;
48 stralloc target = {0};
49 stralloc confirm = {0};
50 stralloc line = {0};
51 stralloc quoted = {0};
52
53 int hashok(action)
54 char *action;
55 {
56   char *x;
57   unsigned long u;
58
59   x = action + 4;
60   x += scan_ulong(x,&u);
61   hashdate = u;
62   if (hashdate > when) return 0;
63   if (hashdate < when - 1000000) return 0;
64
65   u = hashdate;
66   strnum[fmt_ulong(strnum,u)] = 0;
67   cookie(hash,key.s,key.len,strnum,target.s,action + 1);
68
69   if (*x == '.') ++x;
70   if (str_len(x) != COOKIE) return 0;
71   return byte_equal(hash,COOKIE,x);
72 }
73
74 struct qmail qq;
75 int qqwrite(fd,buf,len) int fd; char *buf; unsigned int len;
76 {
77   qmail_put(&qq,buf,len);
78   return len;
79 }
80 char qqbuf[1];
81 substdio ssqq = SUBSTDIO_FDBUF(qqwrite,-1,qqbuf,sizeof(qqbuf));
82
83 char inbuf[1024];
84 substdio ssin = SUBSTDIO_FDBUF(read,0,inbuf,sizeof(inbuf));
85 substdio ssin2 = SUBSTDIO_FDBUF(read,0,inbuf,sizeof(inbuf));
86
87 substdio sstext;
88 char textbuf[1024];
89
90 void copy(fn)
91 char *fn;
92 {
93   int fd;
94   int match;
95
96   fd = open_read(fn);
97   if (fd == -1)
98     strerr_die4sys(111,FATAL,"unable to open ",fn,": ");
99
100   substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf));
101   for (;;) {
102     if (getln(&sstext,&line,&match,'\n') == -1)
103       strerr_die4sys(111,FATAL,"unable to read ",fn,": ");
104
105     if (match)
106       if (line.s[0] == '!') {
107         if (line.s[1] == 'R') {
108           qmail_puts(&qq,"   ");
109           qmail_puts(&qq,confirm.s);
110           qmail_puts(&qq,"\n");
111           continue;
112         }
113         if (line.s[1] == 'A') {
114           qmail_puts(&qq,"   ");
115           qmail_puts(&qq,target.s);
116           qmail_puts(&qq,"\n");
117           continue;
118         }
119       }
120
121     qmail_put(&qq,line.s,line.len);
122
123     if (!match)
124       break;
125   }
126
127   close(fd);
128 }
129
130 stralloc mydtline = {0};
131
132 void main(argc,argv)
133 int argc;
134 char **argv;
135 {
136   char *dir;
137   char *sender;
138   char *host;
139   char *local;
140   char *action;
141   int fd;
142   int i;
143   int flagconfirm;
144   int flaghashok;
145   int flaggoodfield;
146   int match;
147
148   umask(022);
149   sig_pipeignore();
150   when = now();
151
152   dir = argv[1];
153   if (!dir) die_usage();
154
155   sender = env_get("SENDER");
156   if (!sender) strerr_die2x(100,FATAL,"SENDER not set");
157   local = env_get("LOCAL");
158   if (!local) strerr_die2x(100,FATAL,"LOCAL not set");
159   host = env_get("HOST");
160   if (!host) strerr_die2x(100,FATAL,"HOST not set");
161
162   if (!*sender)
163     strerr_die2x(100,FATAL,"I don't reply to bounce messages (#5.7.2)");
164   if (!sender[str_chr(sender,'@')])
165     strerr_die2x(100,FATAL,"I don't reply to senders without host names (#5.7.2)");
166   if (str_equal(sender,"#@[]"))
167     strerr_die2x(100,FATAL,"I don't reply to bounce messages (#5.7.2)");
168
169   if (chdir(dir) == -1)
170     strerr_die4sys(111,FATAL,"unable to switch to ",dir,": ");
171
172   switch(slurp("key",&key,32)) {
173     case -1:
174       strerr_die4sys(111,FATAL,"unable to read ",dir,"/key: ");
175     case 0:
176       strerr_die3x(100,FATAL,dir,"/key does not exist");
177   }
178   getconf_line(&mailinglist,"mailinglist",1,FATAL,dir);
179   getconf_line(&inhost,"inhost",1,FATAL,dir);
180   getconf_line(&inlocal,"inlocal",1,FATAL,dir);
181   getconf_line(&outhost,"outhost",1,FATAL,dir);
182   getconf_line(&outlocal,"outlocal",1,FATAL,dir);
183
184   if (inhost.len != str_len(host)) die_badaddr();
185   if (case_diffb(inhost.s,inhost.len,host)) die_badaddr();
186   if (inlocal.len > str_len(local)) die_badaddr();
187   if (case_diffb(inlocal.s,inlocal.len,local)) die_badaddr();
188
189   action = local + inlocal.len;
190
191   switch(slurp("public",&line,1)) {
192     case -1:
193       strerr_die4sys(111,FATAL,"unable to read ",dir,"/public: ");
194     case 0:
195       strerr_die2x(100,FATAL,"sorry, I've been told to reject all requests (#5.7.2)");
196   }
197
198   if (!stralloc_copys(&target,sender)) die_nomem();
199   if (action[0]) {
200     i = 1 + str_chr(action + 1,'-');
201     if (action[i]) {
202       action[i] = 0;
203       if (!stralloc_copys(&target,action + i + 1)) die_nomem();
204       i = byte_rchr(target.s,target.len,'=');
205       if (i < target.len)
206         target.s[i] = '@';
207     }
208   }
209   if (!stralloc_0(&target)) die_nomem();
210   if (!stralloc_copys(&confirm,"")) die_nomem();
211
212   if (qmail_open(&qq) == -1)
213     strerr_die2sys(111,FATAL,"unable to run qmail-queue: ");
214
215   qmail_puts(&qq,"Mailing-List: ");
216   qmail_put(&qq,mailinglist.s,mailinglist.len);
217   qmail_puts(&qq,"\nDate: ");
218   datetime_tai(&dt,when);
219   qmail_put(&qq,date,date822fmt(date,&dt));
220   qmail_puts(&qq,"Message-ID: <");
221   qmail_put(&qq,strnum,fmt_ulong(strnum,(unsigned long) when));
222   qmail_puts(&qq,".");
223   qmail_put(&qq,strnum,fmt_ulong(strnum,(unsigned long) getpid()));
224   qmail_puts(&qq,".ezmlm@");
225   qmail_put(&qq,outhost.s,outhost.len);
226   qmail_puts(&qq,">\nFrom: ");
227   if (!quote(&quoted,&outlocal)) die_nomem();
228   qmail_put(&qq,quoted.s,quoted.len);
229   qmail_puts(&qq,"-help@");
230   qmail_put(&qq,outhost.s,outhost.len);
231   qmail_puts(&qq,"\nTo: ");
232   if (!quote2(&quoted,target.s)) die_nomem();
233   qmail_put(&qq,quoted.s,quoted.len);
234   qmail_puts(&qq,"\n");
235
236   flaghashok = 1;
237   if (str_start(action,"-sc.")) flaghashok = hashok(action);
238   if (str_start(action,"-uc.")) flaghashok = hashok(action);
239
240   flagconfirm = 0;
241   if (str_equal(action,"-subscribe")) flagconfirm = 1;
242   if (str_equal(action,"-unsubscribe")) flagconfirm = 1;
243   if (!flaghashok) flagconfirm = 1;
244   
245   if (flagconfirm) {
246     strnum[fmt_ulong(strnum,(unsigned long) when)] = 0;
247     cookie(hash,key.s,key.len,strnum,target.s,action + 1);
248     if (!stralloc_copy(&confirm,&outlocal)) die_nomem();
249     if (!stralloc_cats(&confirm,"-")) die_nomem();
250     if (!stralloc_catb(&confirm,action + 1,1)) die_nomem();
251     if (!stralloc_cats(&confirm,"c.")) die_nomem();
252     if (!stralloc_cats(&confirm,strnum)) die_nomem();
253     if (!stralloc_cats(&confirm,".")) die_nomem();
254     if (!stralloc_catb(&confirm,hash,COOKIE)) die_nomem();
255     if (!stralloc_cats(&confirm,"-")) die_nomem();
256     i = str_rchr(target.s,'@');
257     if (!stralloc_catb(&confirm,target.s,i)) die_nomem();
258     if (target.s[i]) {
259       if (!stralloc_cats(&confirm,"=")) die_nomem();
260       if (!stralloc_cats(&confirm,target.s + i + 1)) die_nomem();
261     }
262     if (!stralloc_cats(&confirm,"@")) die_nomem();
263     if (!stralloc_cat(&confirm,&outhost)) die_nomem();
264     if (!stralloc_0(&confirm)) die_nomem();
265
266     qmail_puts(&qq,"Reply-To: ");
267     if (!quote2(&quoted,confirm.s)) die_nomem();
268     qmail_put(&qq,quoted.s,quoted.len);
269     qmail_puts(&qq,"\n");
270   }
271   if (!stralloc_0(&confirm)) die_nomem();
272
273   qmail_puts(&qq,"Subject: ezmlm response\n");
274
275   if (!stralloc_copys(&mydtline,"Delivered-To: responder for ")) die_nomem();
276   if (!stralloc_catb(&mydtline,outlocal.s,outlocal.len)) die_nomem();
277   if (!stralloc_cats(&mydtline,"@")) die_nomem();
278   if (!stralloc_catb(&mydtline,outhost.s,outhost.len)) die_nomem();
279   if (!stralloc_cats(&mydtline,"\n")) die_nomem();
280
281   qmail_put(&qq,mydtline.s,mydtline.len);
282
283   flaggoodfield = 0;
284   for (;;) {
285     if (getln(&ssin,&line,&match,'\n') == -1)
286       strerr_die2sys(111,FATAL,"unable to read input: ");
287     if (!match) break;
288     if (line.len == 1) break;
289     if ((line.s[0] != ' ') && (line.s[0] != '\t')) {
290       flaggoodfield = 0;
291       if (case_startb(line.s,line.len,"mailing-list:"))
292         strerr_die2x(100,FATAL,"incoming message has Mailing-List (#5.7.2)");
293       if (line.len == mydtline.len)
294         if (byte_equal(line.s,line.len,mydtline.s))
295           strerr_die2x(100,FATAL,"this message is looping: it already has my Delivered-To line (#5.4.6)");
296       if (case_startb(line.s,line.len,"delivered-to:"))
297         flaggoodfield = 1;
298       if (case_startb(line.s,line.len,"received:"))
299         flaggoodfield = 1;
300     }
301     if (flaggoodfield)
302       qmail_put(&qq,line.s,line.len);
303   }
304   if (seek_begin(0) == -1)
305     strerr_die2sys(111,FATAL,"unable to seek input: ");
306
307   qmail_puts(&qq,"\n");
308   copy("text/top");
309   if (str_equal(action,"-subscribe"))
310     copy("text/sub-confirm");
311   else if (str_equal(action,"-unsubscribe"))
312     copy("text/unsub-confirm");
313   else if (str_start(action,"-sc.")) {
314     if (!flaghashok)
315       copy("text/sub-bad");
316     else
317       switch(subscribe(target.s,1)) {
318         case -1: strerr_die1(111,FATAL,&subscribe_err);
319         case -2: strerr_die1(100,FATAL,&subscribe_err);
320         case 1: log("+",target.s); copy("text/sub-ok"); break;
321         default: copy("text/sub-nop"); break;
322       }
323   }
324   else if (str_start(action,"-uc.")) {
325     if (!flaghashok)
326       copy("text/unsub-bad");
327     else
328       switch(subscribe(target.s,0)) {
329         case -1: strerr_die1(111,FATAL,&subscribe_err);
330         case -2: strerr_die1(100,FATAL,&subscribe_err);
331         case 1: log("-",target.s); copy("text/unsub-ok"); break;
332         default: copy("text/unsub-nop"); break;
333       }
334   }
335   else if (str_start(action,"-get.")) {
336     unsigned long u;
337     struct stat st;
338     char ch;
339     int r;
340
341     scan_ulong(action + 5,&u);
342
343     if (!stralloc_copys(&line,"archive/")) die_nomem();
344     if (!stralloc_catb(&line,strnum,fmt_ulong(strnum,u / 100))) die_nomem();
345     if (!stralloc_cats(&line,"/")) die_nomem();
346     if (!stralloc_catb(&line,strnum,fmt_uint0(strnum,(unsigned int) (u % 100),2))) die_nomem();
347     if (!stralloc_0(&line)) die_nomem();
348
349     fd = open_read(line.s);
350     if (fd == -1)
351       if (errno != error_noent)
352         strerr_die4sys(111,FATAL,"unable to open ",line.s,": ");
353       else
354         copy("text/get-bad");
355     else {
356       if (fstat(fd,&st) == -1)
357         copy("text/get-bad");
358       else if (!(st.st_mode & 0100))
359         copy("text/get-bad");
360       else {
361         substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf));
362         qmail_puts(&qq,"> ");
363         for (;;) {
364           r = substdio_get(&sstext,&ch,1);
365           if (r == -1) strerr_die4sys(111,FATAL,"unable to read ",line.s,": ");
366           if (r == 0) break;
367           qmail_put(&qq,&ch,1);
368           if (ch == '\n') qmail_puts(&qq,"> ");
369         }
370         qmail_puts(&qq,"\n");
371       }
372       close(fd);
373     }
374   }
375   else
376     copy("text/help");
377
378   copy("text/bottom");
379
380   qmail_puts(&qq,"Return-Path: <");
381   if (!quote2(&quoted,sender)) die_nomem();
382   qmail_put(&qq,quoted.s,quoted.len);
383   qmail_puts(&qq,">\n");
384   if (substdio_copy(&ssqq,&ssin2) != 0)
385     strerr_die2sys(111,FATAL,"unable to read input: ");
386
387   if (!stralloc_copy(&line,&outlocal)) die_nomem();
388   if (!stralloc_cats(&line,"-return-@")) die_nomem();
389   if (!stralloc_cat(&line,&outhost)) die_nomem();
390   if (!stralloc_0(&line)) die_nomem();
391   qmail_from(&qq,line.s);
392
393   qmail_to(&qq,target.s);
394
395   switch(qmail_close(&qq)) {
396     case 0:
397       strnum[fmt_ulong(strnum,qmail_qp(&qq))] = 0;
398       strerr_die2x(0,"ezmlm-manage: info: qp ",strnum);
399     default:
400       /* don't worry about undoing actions; everything is idempotent */
401       strerr_die2x(111,FATAL,"temporary qmail-queue error");
402   }
403 }