1 #include "sgetopt.h"
2 #include "substdio.h"
3 #include "readwrite.h"
4 #include "stralloc.h"
5 #include "getln.h"
6 #include "strerr.h"
7 #include "error.h"
8 #include "exit.h"
9 #include "open.h"
10 #include "wait.h"
11 #include "seek.h"
12 #include "env.h"
13 #include "str.h"
14 #include "fmt.h"
15 #include "token822.h"
16 #include "control.h"
17 #include "qmail.h"
18 #include "auto_qmail.h"
19 
20 #define FATAL "dot-forward: fatal: "
21 #define INFO "dot-forward: info: "
22 
die_nomem()23 void die_nomem()
24 { strerr_die2x(111,FATAL,"out of memory"); }
die_control()25 void die_control()
26 { strerr_die2sys(111,FATAL,"unable to read controls: "); }
die_qq()27 void die_qq()
28 { strerr_die2sys(111,FATAL,"unable to run qq: "); }
die_readmess()29 void die_readmess()
30 { strerr_die2sys(111,FATAL,"unable to read message: "); }
31 
32 stralloc line = {0};
33 
die_parse()34 void die_parse()
35 {
36   if (!stralloc_0(&line)) die_nomem();
37   strerr_die3x(111,FATAL,"unable to parse this line: ",line.s);
38 }
39 
40 int flagdoit = 1;
41 int flagacted;
42 int flagdirect;
43 
44 char *ufline;
45 char *rpline;
46 char *dtline;
47 char *sender;
48 char *user;
49 int userlen;
50 char *host;
51 int hostlen;
52 
53 char messbuf[1024];
54 substdio ssmess;
55 char childbuf[1024];
56 substdio sschild;
57 
blindwrite(fd,buf,len)58 int blindwrite(fd,buf,len)
59 int fd; char *buf; int len;
60 {
61   write(fd,buf,len);
62   return len;
63 }
64 
run(cmd)65 void run(cmd)
66 char *cmd;
67 {
68   int child;
69   int pi[2];
70   char *args[4];
71   int wstat;
72 
73   if (!flagdoit) {
74     strerr_warn2("pipe through ",cmd,0);
75     return;
76   }
77 
78   if (pipe(pi) == -1)
79     strerr_die2sys(111,FATAL,"unable to create pipe: ");
80 
81   switch (child = fork()) {
82     case -1:
83       strerr_die2sys(111,FATAL,"unable to fork: ");
84     case 0:
85       close(pi[1]);
86       if (fd_move(0,pi[0]) == -1)
87         strerr_die2sys(111,FATAL,"unable to set fd: ");
88       args[0] = "/bin/sh"; args[1] = "-c"; args[2] = cmd; args[3] = 0;
89       sig_pipedefault();
90       execv(*args,args);
91       strerr_die2sys(111,FATAL,"unable to run /bin/sh: ");
92   }
93 
94   close(pi[0]);
95 
96   substdio_fdbuf(&ssmess,read,0,messbuf,sizeof messbuf);
97   substdio_fdbuf(&sschild,blindwrite,pi[1],childbuf,sizeof childbuf);
98 
99   substdio_puts(&sschild,ufline);
100   substdio_puts(&sschild,rpline);
101   substdio_puts(&sschild,dtline);
102   if (substdio_copy(&sschild,&ssmess) != 0) die_readmess();
103   substdio_flush(&sschild);
104 
105   close(pi[1]);
106 
107   wait_pid(&wstat,child);
108   if (wait_crashed(wstat))
109     strerr_die2x(111,FATAL,"child crashed");
110 
111   switch(wait_exitcode(wstat)) {
112     case 100:
113     case 64: case 65: case 70: case 76: case 77: case 78: case 112:
114       _exit(100);
115     case 0:
116       break;
117     default:
118       _exit(111);
119   }
120 
121   if (seek_begin(0) == -1)
122     strerr_die2sys(111,FATAL,"unable to rewind input: ");
123 }
124 
125 stralloc targets = {0};
126 
127 stralloc me = {0};
128 stralloc defaulthost = {0};
129 stralloc defaultdomain = {0};
130 stralloc plusdomain = {0};
131 
readcontrols()132 void readcontrols()
133 {
134   int r;
135   int fddir;
136 
137   fddir = open_read(".");
138   if (fddir == -1)
139     strerr_die2sys(111,FATAL,"unable to open current directory: ");
140 
141   if (chdir(auto_qmail) == -1)
142     strerr_die4sys(111,FATAL,"unable to chdir to ",auto_qmail,": ");
143 
144   r = control_readline(&me,"control/me");
145   if (r == -1) die_control();
146   if (!r) if (!stralloc_copys(&me,"me")) die_nomem();
147 
148   r = control_readline(&defaultdomain,"control/defaultdomain");
149   if (r == -1) die_control();
150   if (!r) if (!stralloc_copy(&defaultdomain,&me)) die_nomem();
151 
152   r = control_readline(&defaulthost,"control/defaulthost");
153   if (r == -1) die_control();
154   if (!r) if (!stralloc_copy(&defaulthost,&me)) die_nomem();
155 
156   r = control_readline(&plusdomain,"control/plusdomain");
157   if (r == -1) die_control();
158   if (!r) if (!stralloc_copy(&plusdomain,&me)) die_nomem();
159 
160   if (fchdir(fddir) == -1)
161     strerr_die2sys(111,FATAL,"unable to set current directory: ");
162 }
163 
164 stralloc cbuf = {0};
165 token822_alloc toks = {0};
166 token822_alloc tokaddr = {0};
167 stralloc address = {0};
168 
gotaddr()169 void gotaddr()
170 {
171   int i;
172   int j;
173   int flaghasat;
174 
175   token822_reverse(&tokaddr);
176   if (token822_unquote(&address,&tokaddr) != 1) die_nomem();
177 
178   flaghasat = 0;
179   for (i = 0;i < tokaddr.len;++i)
180     if (tokaddr.t[i].type == TOKEN822_AT)
181       flaghasat = 1;
182 
183   tokaddr.len = 0;
184 
185   if (!address.len) return;
186 
187   if (!flaghasat)
188     if (address.len == userlen)
189       if (!case_diffb(address.s,address.len,user)) {
190         flagacted = 1;
191         flagdirect = 1;
192         return;
193       }
194 
195   if (flaghasat)
196     if (address.len == userlen + 1 + hostlen)
197       if (!case_diffb(address.s,userlen,user))
198         if (address.s[userlen] == '@')
199           if (!case_diffb(address.s + userlen + 1,hostlen,host)) {
200             flagacted = 1;
201             flagdirect = 1;
202             return;
203           }
204 
205   if (!flaghasat)
206     if (address.s[0] == '/') {
207       if (!stralloc_0(&address)) die_nomem();
208       strerr_die4x(111,FATAL,"file delivery ",address.s," not supported");
209     }
210 
211   if (!flaghasat)
212     if (address.s[0] == '|') {
213       if (!stralloc_0(&address)) die_nomem();
214       flagacted = 1;
215       run(address.s + 1);
216       return;
217     }
218 
219   if (!flaghasat) {
220     if (!stralloc_cats(&address,"@")) die_nomem();
221     if (!stralloc_cat(&address,&defaulthost)) die_nomem();
222   }
223   if (address.s[address.len - 1] == '+') {
224     address.s[address.len - 1] = '.';
225     if (!stralloc_cat(&address,&plusdomain)) die_nomem();
226   }
227   j = 0;
228   for (i = 0;i < address.len;++i) if (address.s[i] == '@') j = i;
229   for (i = j;i < address.len;++i) if (address.s[i] == '.') break;
230   if (i == address.len) {
231     if (!stralloc_cats(&address,".")) die_nomem();
232     if (!stralloc_cat(&address,&defaultdomain)) die_nomem();
233   }
234 
235   if (!stralloc_0(&address)) die_nomem();
236 
237   if (!stralloc_cats(&targets,"T")) die_nomem();
238   if (!stralloc_cats(&targets,address.s)) die_nomem();
239   if (!stralloc_0(&targets)) die_nomem();
240 
241   if (!flagdoit)
242     strerr_warn2("forward ",address.s,0);
243 }
244 
parseline()245 void parseline()
246 {
247   int wordok;
248   struct token822 *t;
249   struct token822 *beginning;
250   int r;
251 
252   r = token822_parse(&toks,&line,&cbuf);
253   if (r == -1) die_nomem();
254   if (r == 0) die_parse();
255 
256   beginning = toks.t;
257   t = toks.t + toks.len;
258   wordok = 1;
259 
260   if (!token822_readyplus(&tokaddr,1)) die_nomem();
261   tokaddr.len = 0;
262 
263   while (t > beginning)
264     switch((--t)->type) {
265       case TOKEN822_SEMI:
266         break; /*XXX*/
267       case TOKEN822_COLON:
268         break; /*XXX*/
269       case TOKEN822_RIGHT:
270         if (tokaddr.len) gotaddr();
271         while ((t > beginning) && (t[-1].type != TOKEN822_LEFT))
272           if (!token822_append(&tokaddr,--t)) die_nomem();
273         gotaddr();
274         if (t <= beginning) die_parse();
275         --t;
276         while ((t > beginning) && ((t[-1].type == TOKEN822_COMMENT) || (t[-1].type == TOKEN822_ATOM) || (t[-1].type == TOKEN822_QUOTE) || (t[-1].type == TOKEN822_AT) || (t[-1].type == TOKEN822_DOT)))
277           --t;
278         wordok = 0;
279         continue;
280       case TOKEN822_ATOM: case TOKEN822_QUOTE: case TOKEN822_LITERAL:
281         if (!wordok) if (tokaddr.len) gotaddr();
282         wordok = 0;
283         if (!token822_append(&tokaddr,t)) die_nomem();
284         continue;
285       case TOKEN822_COMMENT:
286         /* comment is lexically a space; shouldn't affect wordok */
287         break;
288       case TOKEN822_COMMA:
289         if (tokaddr.len) gotaddr();
290         wordok = 1;
291         break;
292       default:
293         wordok = 1;
294         if (!token822_append(&tokaddr,t)) die_nomem();
295         continue;
296     }
297   if (tokaddr.len) gotaddr();
298 }
299 
300 struct qmail qq;
301 unsigned long qp;
302 char *qqx;
303 char strnum[FMT_ULONG];
304 
mywrite(fd,buf,len)305 int mywrite(fd,buf,len)
306 int fd; char *buf; int len;
307 {
308   qmail_put(&qq,buf,len);
309   return len;
310 }
311 
312 char qqbuf[256];
313 substdio ssqq = SUBSTDIO_FDBUF(mywrite,-1,qqbuf,sizeof qqbuf);
314 
315 char inbuf[256];
316 
try(fn)317 void try(fn)
318 char *fn;
319 {
320   int fd;
321   int match;
322   substdio ss;
323 
324   fd = open_read(fn);
325   if (fd == -1) {
326     if (errno == error_noent) return;
327     strerr_die4sys(111,FATAL,"unable to open ",fn,": ");
328   }
329 
330   if (!stralloc_copys(&targets,"")) die_nomem();
331   flagacted = 0;
332   flagdirect = 0;
333 
334   substdio_fdbuf(&ss,read,fd,inbuf,sizeof inbuf);
335 
336   for (;;) {
337     if (getln(&ss,&line,&match,'\n') == -1)
338       strerr_die4sys(111,FATAL,"unable to read ",fn,": ");
339     if (!line.len) break;
340     if (line.s[0] != '#') parseline();
341     if (!match) break;
342   }
343 
344   close(fd);
345 
346   if (targets.len) {
347     flagacted = 1;
348     if (flagdoit) {
349       if (qmail_open(&qq) == -1)
350         strerr_die2sys(111,FATAL,"unable to run qmail-queue: ");
351       qp = qmail_qp(&qq);
352       qmail_puts(&qq,dtline);
353 
354       substdio_fdbuf(&ssmess,read,0,messbuf,sizeof messbuf);
355       if (substdio_copy(&ssqq,&ssmess) != 0) die_readmess();
356       substdio_flush(&ssqq);
357 
358       qmail_from(&qq,sender);
359       qmail_put(&qq,targets.s,targets.len);
360 
361       qqx = qmail_close(&qq);
362       if (*qqx == 'D')
363         strerr_die3x(100,FATAL,"unable to forward message: ",qqx + 1);
364       if (*qqx)
365         strerr_die3x(111,FATAL,"unable to forward message: ",qqx + 1);
366       strnum[fmt_ulong(strnum,qp)] = 0;
367       strerr_warn3(INFO,"qp ",strnum,0);
368     }
369   }
370 
371   if (flagdirect) {
372     if (!flagdoit) strerr_warn1("direct delivery",0);
373     _exit(0);
374   }
375   if (!flagacted) {
376     if (!flagdoit) strerr_warn2("skipping empty file ",fn,0);
377     return;
378   }
379   _exit(99);
380 }
381 
main(argc,argv)382 void main(argc,argv)
383 int argc;
384 char **argv;
385 {
386   int opt;
387   int fddir;
388 
389   sig_pipeignore();
390 
391   while ((opt = getopt(argc,argv,"nN")) != opteof)
392     switch(opt) {
393       case 'n':
394         flagdoit = 0; break;
395       case 'N':
396         flagdoit = 1; break;
397       default:
398         strerr_die1x(100,"dot-forward: usage: dot-forward [ -nN ] file ...");
399     }
400   argv += optind;
401 
402   ufline = env_get("UFLINE"); if (!ufline) ufline = "";
403   rpline = env_get("RPLINE"); if (!rpline) rpline = "";
404   dtline = env_get("DTLINE"); if (!dtline) dtline = "";
405   sender = env_get("NEWSENDER"); if (!sender) sender = "";
406 
407   user = env_get("USER"); if (!user) user = "";
408   userlen = str_len(user);
409   host = env_get("HOST"); if (!host) host = "";
410   hostlen = str_len(host);
411 
412   readcontrols();
413 
414   while (*argv)
415     try(*argv++);
416 
417   if (!flagdoit) strerr_warn1("direct delivery",0);
418   _exit(0);
419 }
420