1 #include <sys/types.h>
2 #include <sys/stat.h>
3 #include <unistd.h>
4 #include <time.h>
5 #include <sys/time.h>
6 #include "sig.h"
7 #include "qconfirm.h"
8 #include "qconfirm_conf_get.h"
9 #include "qconfirm_inject.h"
10 #include "qconfirm_key.h"
11 #include "next_paragraph.h"
12 #include "getline.h"
13 #include "strerr.h"
14 #include "error.h"
15 #include "buffer.h"
16 #include "env.h"
17 #include "str.h"
18 #include "fd.h"
19 #include "pathexec.h"
20 #include "wait.h"
21 #include "stralloc.h"
22 #include "byte.h"
23 #include "open.h"
24 #include "fmt.h"
25 #include "scan.h"
26 #include "sgetopt.h"
27 
28 #define USAGE " [-v] [-t sec] [-d dir ] "
29 #define VERSION "$Id: qconfirm-control.c,v 1.10 2003/11/21 13:45:53 pape Exp $"
30 #define FATAL "qconfirm-control: fatal: "
31 #define WARNING "qconfirm-control: warning: "
32 #define INFO "qconfirm-control: info: "
33 
34 #define QCONTROLQUOTE "> "
35 
36 const char *progname;
37 
38 char *qconfirm_dir;
39 char *qcontrol_owner;
40 char *qconfirm_prepend;
41 char *qcontrol_quote;
42 char *mailname;
43 char *dflt;
44 char *local;
45 char *host;
46 char *sender;
47 
48 int verbose =0;
49 unsigned long timeout =300;
50 
51 char *key;
52 stralloc sa ={0};
53 stralloc command ={0};
54 struct stat st;
55 buffer b;
56 char b_space[BUFFER_OUTSIZE];
57 int fd;
58 int pid;
59 int wstat;
60 char inode[FMT_ULONG];
61 
usage()62 void usage() { strerr_die4x(111, "usage: ", progname, USAGE, "\n"); }
die_nomem()63 void die_nomem() { strerr_die2x(111, FATAL, "out of memory."); }
fatal(char * m1,char * m2)64 void fatal(char *m1, char *m2) { strerr_die4sys(111, FATAL, m1, m2, ": "); }
warn(char * m1,char * m2)65 void warn(char *m1, char *m2) { strerr_warn3(WARNING, m1, m2, 0); }
info(char * m1,char * m2)66 void info(char *m1, char *m2) { strerr_die3x(0, INFO, m1, m2); }
defer(char * m1,char * m2)67 void defer(char *m1, char *m2) { strerr_die3x(111, INFO, m1, m2); }
bounce(char * m)68 void bounce(char *m) { strerr_die1x(100, m); }
69 
create_ctrl()70 char* create_ctrl() {
71   if (fstat(0, &st) == -1) fatal("unable to fstat stdin", 0);
72   inode[fmt_ulong(inode, st.st_ino)] = 0;
73   if (! inode) fatal("bad inode", 0);
74 
75   do {
76     if (! stralloc_copyb(&sa, (char *) &st.st_ino, 1)) die_nomem();
77     if (! stralloc_cats(&sa, sender)) die_nomem();
78     if (! (key =qconfirm_key(&sa))) die_nomem();
79     if (! stralloc_copys(&sa, qconfirm_dir)) die_nomem();
80     if (! stralloc_cats(&sa, "/control/")) die_nomem();
81     if (! stralloc_cats(&sa, key)) die_nomem();
82     if (! stralloc_0(&sa)) die_nomem();
83   } while (stat(sa.s, &st) != -1);
84   if (errno != error_noent) fatal("unable to stat: ", sa.s);
85   if (open_trunc(sa.s) == -1) fatal("unable to create: ", sa.s);
86   if (! stralloc_copys(&sa, qconfirm_dir)) die_nomem();
87   if (! stralloc_cats(&sa, "/control/")) die_nomem();
88   if (! stralloc_cats(&sa, inode)) die_nomem();
89   if (! stralloc_0(&sa)) die_nomem();
90   if (symlink(key, sa.s) == -1)
91     fatal("unable to create symbolic link: ", sa.s);
92   return(key);
93 }
94 
check_ctrl()95 int check_ctrl() {
96   int stamp;
97   time_t now;
98   char buf[33];
99 
100   if (fstat(0, &st) == -1) fatal("unable to fstat stdin", 0);
101   inode[fmt_ulong(inode, st.st_ino)] = 0;
102   if (! inode) fatal("bad inode", 0);
103   stamp =st.st_mtime;
104 
105   if (! stralloc_copys(&sa, qconfirm_dir)) die_nomem();
106   if (! stralloc_cats(&sa, "/control/")) die_nomem();
107   if (! stralloc_cats(&sa, inode)) die_nomem();
108   if (! stralloc_0(&sa)) die_nomem();
109   if (stat(sa.s, &st) == -1) {
110     if (errno != error_noent) fatal("unable to stat: ", sa.s);
111     return(1);
112   }
113   /* check timeout and mode */
114   now =time(NULL);
115   if (((now -stamp) > timeout) || ((st.st_mode & S_IRWXU) == S_IWUSR)) {
116     /* remove */
117     if (readlink(sa.s, buf, 32) != 32)
118       fatal("unable to read key from inode: ", inode);
119     buf[32] =0;
120     if (unlink(sa.s) == -1) warn("unable to unlink: ", sa.s);
121     if (! stralloc_copys(&sa, qconfirm_dir)) die_nomem();
122     if (! stralloc_cats(&sa, "/control/")) die_nomem();
123     if (! stralloc_catb(&sa, buf, 32)) die_nomem();
124     if (! stralloc_0(&sa)) die_nomem();
125     if (unlink(sa.s) == -1) warn("unable to unlink: ", sa.s);
126     info("remove: ", buf);
127   }
128   defer("active: ", inode);
129   return(-1);
130 }
131 
check_key()132 int check_key() {
133   if (! stralloc_copys(&sa, qconfirm_dir)) die_nomem();
134   if (! stralloc_cats(&sa, "/control/")) die_nomem();
135   if (! stralloc_cats(&sa, dflt)) die_nomem();
136   if (! stralloc_0(&sa)) die_nomem();
137   if (stat(sa.s, &st) == -1) bounce("not authorized.");
138   if ((st.st_mode & S_IRWXU) == S_IWUSR) bounce("not authorized.");
139   return(1);
140 }
141 
run_qconfirm(char * arg,char * addresses,buffer * out)142 int run_qconfirm(char *arg, char *addresses, buffer *out) {
143   char *prog[1000];
144   int progc;
145   int i;
146 
147   prog[0] ="qconfirm";
148   prog[1] =arg;
149   prog[2] =0;
150   progc =2;
151 
152   if (addresses) {
153     i =0;
154     for (;;) {
155       while ((addresses[i] == ' ') || (addresses[i] == '\t')) ++i;
156       if (! addresses[i]) break;
157       prog[progc++] =&addresses[i];
158       if (progc == 999) {
159 	warn("too many addresses, truncate.", 0);
160 	break;
161       }
162       while (addresses[i] && (addresses[i] != ' ') && (addresses[i] != '\t'))
163 	++i;
164       if (! addresses[i]) break;
165       addresses[i++] =0;
166     }
167     prog[progc] =0;
168   }
169   if (verbose) {
170     buffer_puts(out, " ");
171     for (i =0; i < progc; ++i) {
172       buffer_puts(out, " ");
173       buffer_puts(out, prog[i]);
174     }
175     buffer_puts(out, "\n");
176   }
177   buffer_flush(out);
178 
179   if ((pid =fork()) == -1) fatal("unable to fork", 0);
180   if (! pid) {
181     /* child */
182     if (fd_move(1, out->fd) == -1) fatal("unable to set filedescriptor", 0);
183     if (fd_copy(2, 1) == -1) fatal("unable to set filedescriptor", 0);
184     pathexec((const char **)prog);
185     fatal("unable to run: ", prog[0]);
186   }
187   if (wait_pid(&wstat, pid) == -1) fatal(prog[0], ": wait failed");
188   if (wait_exitcode(wstat) != 0) warn(prog[0], ": exited abnormally.");
189   buffer_putsflush(out, "\n");
190   return(1);
191 }
192 
qconfirm_list(char * arg,char * extension)193 int qconfirm_list(char *arg, char *extension) {
194   if (open_qmail_inject(&fd, qcontrol_owner) == -1)
195     fatal("unable to start qmail-inject", 0);
196   buffer_init(&b, buffer_unixwrite, fd, b_space, sizeof b_space);
197   buffer_puts(&b, "Return-Path: <>\n");
198   buffer_puts(&b, "Subject: qconfirm list\n");
199   buffer_puts(&b, "From: \"");
200   buffer_puts(&b, mailname);
201   buffer_puts(&b, "\" <");
202   buffer_puts(&b, local);
203   if (extension) buffer_puts(&b, extension);
204   buffer_puts(&b, "@");
205   buffer_puts(&b, host);
206   buffer_puts(&b, ">\nTo: ");
207   buffer_puts(&b, qcontrol_owner);
208   buffer_puts(&b, "\n\n");
209 
210   if (! stralloc_copys(&sa, "qconfirm list ")) die_nomem();
211   if (! stralloc_cats(&sa, arg)) die_nomem();
212   if (! stralloc_cats(&sa, " | sort -r")) die_nomem();
213   if (! stralloc_0(&sa)) die_nomem();
214   if (verbose) {
215     buffer_puts(&b, "  ");
216     buffer_puts(&b, sa.s);
217     buffer_puts(&b, "\n");
218   }
219   buffer_flush(&b);
220   if ((pid =fork()) == -1) fatal("unable to fork", 0);
221   if (! pid) {
222     /* child */
223     const char *prog[4];
224 
225     if (fd_move(1, b.fd) == -1) fatal("unable to set filedescriptor", 0);
226     if (fd_copy(2, 1) == -1) fatal("unable to set filedescriptor", 0);
227     prog[0] ="sh";
228     prog[1] ="-c";
229     prog[2] =sa.s;
230     prog[3] =0;
231     pathexec(prog);
232     fatal("unable to run: ", sa.s);
233   }
234   if (wait_pid(&wstat, pid) == -1) fatal(sa.s, ": wait failed");
235   if (wait_exitcode(wstat) != 0) warn(sa.s, ": exited abnormally.");
236   buffer_putsflush(&b, "\n");
237   return(1);
238 }
239 
process()240 int process() {
241   int r;
242   stralloc args ={0};
243 
244   if (open_qmail_inject(&fd, qcontrol_owner) == -1)
245     fatal("unable to start qmail-inject", 0);
246   buffer_init(&b, buffer_unixwrite, fd, b_space, sizeof b_space);
247   buffer_puts(&b, "Return-Path: <>\n");
248   buffer_puts(&b, "Subject: qconfirm control\n");
249   buffer_puts(&b, "From: \"");
250   buffer_puts(&b, mailname);
251   buffer_puts(&b, "\" <");
252   buffer_puts(&b, local);
253   buffer_puts(&b, "@");
254   buffer_puts(&b, host);
255   buffer_puts(&b, ">\nTo: ");
256   buffer_puts(&b, qcontrol_owner);
257   buffer_putsflush(&b, "\n\n");
258 
259   /* process message */
260   /* skip header */
261   if (next_paragraph(buffer_0) == -1) strerr_die2x(0, WARNING, "no body.");
262   if (! stralloc_copys(&args, "")) die_nomem();
263 
264   /* lines */
265   while ((r =getline(buffer_0, &sa)) > 0) {
266     if (sa.s[0] == '\n') continue;
267     if ((sa.len > 2) && str_start(sa.s, qcontrol_quote)) {
268       int i, j;
269 
270       /* quoted qconfirm list item */
271       buffer_puts(&b, qcontrol_quote);
272       buffer_put(&b, sa.s, sa.len);
273       if ((sa.s[2] < '1') || (sa.s[2] > '9')) continue;
274       if ((i =byte_chr(&sa.s[2], sa.len -2, ' ')) == sa.len -2) {
275 	if (verbose) buffer_putsflush(&b, "parse error.\n");
276 	continue;
277       }
278       i +=3;
279       for (j =i; sa.s[j]; ++j) {
280 	if ((sa.s[j] == ' ') || (sa.s[j] == '\n')) break;
281       }
282       if (j > i) {
283 	if (! stralloc_catb(&args, &sa.s[i], j -i)) die_nomem();
284 	if (! stralloc_cats(&args, " ")) die_nomem();
285 	if (verbose) {
286 	  buffer_puts(&b, "  ");
287 	  buffer_put(&b, &sa.s[i], j -i);
288 	  buffer_puts(&b, "\n");
289 	}
290       }
291       buffer_flush(&b);
292       continue;
293     }
294     if ((sa.len >= 6) && str_start(sa.s, "accept")) {
295       if ((sa.s[6] == ' ') && sa.s[7]) {
296 	if (! stralloc_catb(&args, &sa.s[7], sa.len -7)) die_nomem();
297 	if (args.s[args.len -1] == '\n')
298 	  args.s[args.len -1] =0;
299       }
300       if (! stralloc_0(&args)) die_nomem();
301       buffer_puts(&b, qcontrol_quote);
302       buffer_putflush(&b, sa.s, sa.len);
303       run_qconfirm("accept", args.s, &b);
304       if (! stralloc_copys(&args, "")) die_nomem();
305       continue;
306     }
307     if ((sa.len >= 4) && str_start(sa.s, "drop")) {
308       if ((sa.s[4] == ' ') && sa.s[5]) {
309 	if (! stralloc_catb(&args, &sa.s[5], sa.len -5)) die_nomem();
310 	if (args.s[args.len -1] == '\n')
311 	  args.s[args.len -1] =0;
312       }
313       if (! stralloc_0(&args)) die_nomem();
314       buffer_puts(&b, qcontrol_quote);
315       buffer_putflush(&b, sa.s, sa.len);
316       run_qconfirm("drop", args.s, &b);
317       if (! stralloc_copys(&args, "")) die_nomem();
318       continue;
319     }
320     if ((sa.len >= 6) && str_start(sa.s, "bounce")) {
321       if ((sa.s[6] == ' ') && sa.s[7]) {
322 	if (! stralloc_catb(&args, &sa.s[7], sa.len -7)) die_nomem();
323 	if (args.s[args.len -1] == '\n')
324 	  args.s[args.len -1] =0;
325       }
326       if (! stralloc_0(&args)) die_nomem();
327       buffer_puts(&b, qcontrol_quote);
328       buffer_putflush(&b, sa.s, sa.len);
329       run_qconfirm("bounce", args.s, &b);
330       if (! stralloc_copys(&args, "")) die_nomem();
331       continue;
332     }
333     if ((sa.len >= 3) && str_start(sa.s, "bad")) {
334       if ((sa.s[3] == ' ') && sa.s[4]) {
335 	if (! stralloc_catb(&args, &sa.s[4], sa.len -4)) die_nomem();
336 	if (args.s[args.len -1] == '\n')
337 	  args.s[args.len -1] =0;
338       }
339       if (! stralloc_0(&args)) die_nomem();
340       buffer_puts(&b, qcontrol_quote);
341       buffer_putflush(&b, sa.s, sa.len);
342       run_qconfirm("bad", args.s, &b);
343       if (! stralloc_copys(&args, "")) die_nomem();
344       continue;
345     }
346     if ((sa.len >= 7) && str_start(sa.s, "pending")) {
347       if ((sa.s[7] == ' ') && sa.s[8]) {
348 	if (! stralloc_catb(&args, &sa.s[8], sa.len -8)) die_nomem();
349 	if (args.s[args.len -1] == '\n')
350 	  args.s[args.len -1] =0;
351       }
352       if (! stralloc_0(&args)) die_nomem();
353       buffer_puts(&b, qcontrol_quote);
354       buffer_putflush(&b, sa.s, sa.len);
355       run_qconfirm("pending", args.s, &b);
356       if (! stralloc_copys(&args, "")) die_nomem();
357       continue;
358     }
359     if ((sa.len >= 6) && str_start(sa.s, "remove")) {
360       if ((sa.s[6] == ' ') && sa.s[7]) {
361 	if (! stralloc_catb(&args, &sa.s[7], sa.len -7)) die_nomem();
362 	if (args.s[args.len -1] == '\n')
363 	  args.s[args.len -1] =0;
364       }
365       if (! stralloc_0(&args)) die_nomem();
366       buffer_puts(&b, qcontrol_quote);
367       buffer_putflush(&b, sa.s, sa.len);
368       run_qconfirm("remove", args.s, &b);
369       if (! stralloc_copys(&args, "")) die_nomem();
370       continue;
371     }
372     if ((sa.len >= 5) && str_start(sa.s, "sneak")) {
373       if ((sa.s[5] == ' ') && sa.s[6]) {
374 	if (! stralloc_catb(&args, &sa.s[6], sa.len -6)) die_nomem();
375 	if (args.s[args.len -1] == '\n')
376 	  args.s[args.len -1] =0;
377       }
378       if (! stralloc_0(&args)) die_nomem();
379       buffer_puts(&b, qcontrol_quote);
380       buffer_putflush(&b, sa.s, sa.len);
381       run_qconfirm("sneak", args.s, &b);
382       if (! stralloc_copys(&args, "")) die_nomem();
383       continue;
384     }
385     if ((sa.len >= 4) && str_start(sa.s, "list")) {
386       if ((sa.s[4] == ' ') && sa.s[5]) {
387 	if (! stralloc_copyb(&args, &sa.s[5], sa.len -5)) die_nomem();
388 	if (args.s[args.len -1] == '\n')
389 	  args.s[args.len -1] =0;
390       }
391       if (! stralloc_0(&args)) die_nomem();
392       buffer_puts(&b, qcontrol_quote);
393       buffer_putflush(&b, sa.s, sa.len);
394       run_qconfirm("list", args.s, &b);
395       if (! stralloc_copys(&args, "")) die_nomem();
396       continue;
397     }
398     if ((sa.len >= 6) && str_start(sa.s, "thanks")) {
399       buffer_puts(&b, qcontrol_quote);
400       buffer_put(&b, sa.s, sa.len);
401       /* remove this key */
402       if (! stralloc_copys(&sa, qconfirm_dir)) die_nomem();
403       if (! stralloc_cats(&sa, "/control/")) die_nomem();
404       if (! stralloc_cats(&sa, dflt)) die_nomem();
405       if (! stralloc_0(&sa)) die_nomem();
406       if (chmod(sa.s, S_IWUSR) == -1)
407 	fatal("unable to chmod: ", sa.s);
408       buffer_puts(&b, "Removing temporary authorization.\n");
409       break;
410     }
411     if ((sa.len >= 4) && str_start(sa.s, "stop")) {
412       buffer_puts(&b, qcontrol_quote);
413       buffer_put(&b, sa.s, sa.len);
414       break;
415     }
416     buffer_puts(&b, qcontrol_quote);
417     buffer_put(&b, sa.s, sa.len);
418     buffer_putsflush(&b, "unknown command.\n\n");
419   }
420   buffer_putsflush(&b, "Stopping processing here.\n");
421   close(fd);
422   if (! close_qmail_inject()) fatal("unable to run qmail-inject", 0);
423   return(1);
424 }
425 
main(int argc,const char ** argv)426 int main(int argc, const char **argv) {
427   int i;
428   int opt;
429 
430   progname =*argv;
431   umask(0177);
432   sig_ignore(sig_pipe);
433 
434   qconfirm_dir =env_get("QCONFIRM_DIR");
435 
436   while ((opt =getopt(argc, argv, "vVt:d:")) != opteof) {
437     switch(opt) {
438     case 'v': verbose =1; break;
439     case 't': scan_ulong(optarg, &timeout); break;
440     case 'd': qconfirm_dir =(char *)optarg; break;
441     case 'V': strerr_warn1(VERSION, 0);
442     case '?': usage();
443     }
444   }
445   argv +=optind;
446 
447   sender =env_get("SENDER");
448   if (! sender)
449     strerr_die2x(100, FATAL, "environment variable SENDER not set.\n");
450   dflt =env_get("DEFAULT");
451   if (! dflt)
452     strerr_die2x(100, FATAL, "environment variable DEFAULT not set.\n");
453   local =env_get("LOCAL");
454   if (! local)
455     strerr_die2x(100, FATAL, "environment variable LOCAL not set.\n");
456   host =env_get("HOST");
457   if (! host)
458     strerr_die2x(100, FATAL, "environment variable HOST not set.\n");
459 
460   if (! qconfirm_dir) qconfirm_dir =QCONFIRMDIR;
461   qconfirm_prepend =conf_get_dflt(qconfirm_dir, "QCONFIRM_PREPEND", "");
462   if (! qconfirm_prepend)
463     fatal("unable to read config: ", "QCONFIRM_PREPEND");
464   qcontrol_quote =conf_get_dflt(qconfirm_dir, "QCONTROL_QUOTE", QCONTROLQUOTE);
465   if (! qcontrol_quote)
466     fatal("unable to read config: ", "QCONTROL_QUOTE");
467   qcontrol_owner =conf_get(qconfirm_dir, "QCONTROL_OWNER");
468   if (! qcontrol_owner)
469     fatal("unable to read config: ", "QCONTROL_OWNER");
470   if (! *qcontrol_owner)
471     strerr_die2x(100, FATAL, "QCONTROL_OWNER is empty.\n");
472   mailname =conf_get_dflt(qconfirm_dir, "QCONFIRM_MAILNAME", QCONFIRMMAILNAME);
473   if (! mailname)
474     fatal("unable to read config: ", "QCONFIRM_MAILNAME");
475 
476   if (! pathexec_env("QCONFIRM_DIR", qconfirm_dir)) die_nomem();
477 
478   i =str_len(qconfirm_prepend);
479   if (byte_equal(qconfirm_prepend, i, local)) local +=i;
480 
481   i =str_len(dflt);
482   switch(i) {
483   case 2: /* ok */
484     if (str_equal("ok", dflt)) {
485       check_ctrl();
486       key =create_ctrl();
487       local[str_len(local) -i] =0;
488       qconfirm_list("ok", key);
489       defer("create: ", inode);
490     }
491     break;
492   case 3: /* bad */
493     if (str_equal("bad", dflt)) {
494       check_ctrl();
495       key =create_ctrl();
496       local[str_len(local) -i] =0;
497       qconfirm_list("bad", key);
498       defer("create: ", inode);
499     }
500     break;
501   case 6:
502     if (str_equal("return", dflt)) {
503       check_ctrl();
504       key =create_ctrl();
505       local[str_len(local) -i] =0;
506       qconfirm_list("return", key);
507       defer("create: ", inode);
508     }
509     break;
510   case 0: case 7: /* create control and list pending */
511     if (! *dflt || str_equal("pending", dflt)) {
512       check_ctrl();
513       key =create_ctrl();
514       local[str_len(local) -i] =0;
515       qconfirm_list("", key);
516       defer("create: ", inode);
517     }
518     break;
519   case 32: /* key */
520     check_key();
521     process();
522     info("processed", 0);
523     break;
524   }
525   bounce("Sorry, no mailbox here by that name.");
526   _exit(0);
527 }
528