chiark / gitweb /
Call @xfree@ rather than @free@.
[fwd] / exec.c
1 /* -*-c-*-
2  *
3  * $Id: exec.c,v 1.6 2002/02/22 23:43:32 mdw Exp $
4  *
5  * Source and target for executable programs
6  *
7  * (c) 1999 Straylight/Edgeware
8  */
9
10 /*----- Licensing notice --------------------------------------------------* 
11  *
12  * This file is part of the `fw' port forwarder.
13  *
14  * `fw' is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later version.
18  * 
19  * `fw' is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  * 
24  * You should have received a copy of the GNU General Public License
25  * along with `fw'; if not, write to the Free Software Foundation,
26  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27  */
28
29 /*----- Revision history --------------------------------------------------* 
30  *
31  * $Log: exec.c,v $
32  * Revision 1.6  2002/02/22 23:43:32  mdw
33  * Call @xfree@ rather than @free@.
34  *
35  * Revision 1.5  2002/01/13 14:49:03  mdw
36  * Track @lbuf@ changes in mLib.
37  *
38  * Revision 1.4  2001/02/03 20:30:03  mdw
39  * Support re-reading config files on SIGHUP.
40  *
41  * Revision 1.3  2000/07/01 11:28:52  mdw
42  * Use new mLib selbuf features.
43  *
44  * Revision 1.2  1999/10/22 22:46:17  mdw
45  * When a non-file endpoint is attached to a file, keep the file endpoint
46  * open until the nonfile is done.  This stops socket sources from
47  * resetting their connection limits too early.
48  *
49  * Revision 1.1  1999/07/26 23:33:32  mdw
50  * New sources and targets.
51  *
52  */
53
54 /*----- Header files ------------------------------------------------------*/
55
56 #include "config.h"
57
58 #define _GNU_SOURCE
59
60 #include <ctype.h>
61 #include <errno.h>
62 #include <signal.h>
63 #include <stdio.h>
64 #include <stdlib.h>
65 #include <string.h>
66
67 #include <sys/types.h>
68 #include <sys/stat.h>
69 #include <unistd.h>
70 #include <fcntl.h>
71 #include <sys/wait.h>
72
73 #ifdef HAVE_SETRLIMIT
74 #  include <sys/resource.h>
75 #endif
76
77 #ifndef DECL_ENVIRON
78   extern char **environ;
79 #endif
80
81 #include <pwd.h>
82 #include <grp.h>
83
84 #include <syslog.h>
85
86 #include <mLib/alloc.h>
87 #include <mLib/dstr.h>
88 #include <mLib/env.h>
89 #include <mLib/fdflags.h>
90 #include <mLib/report.h>
91 #include <mLib/sel.h>
92 #include <mLib/selbuf.h>
93 #include <mLib/sig.h>
94 #include <mLib/sub.h>
95 #include <mLib/sym.h>
96
97 #include "conf.h"
98 #include "endpt.h"
99 #include "exec.h"
100 #include "fattr.h"
101 #include "fw.h"
102 #include "reffd.h"
103 #include "scan.h"
104 #include "source.h"
105 #include "target.h"
106
107 /*----- Data structures ---------------------------------------------------*/
108
109 /* --- Resource usage --- */
110
111 #ifdef HAVE_SETRLIMIT
112
113 typedef struct xlimit {
114 #define R(r, n) struct rlimit n;
115 #include "rlimits.h"
116 } xlimit;
117
118 #endif
119
120 /* --- Environment variable modifications --- */
121
122 typedef struct xenv {
123   struct xenv *next;
124   unsigned act;
125   char *name;
126   char *value;
127 } xenv;
128
129 #define XEA_SET 0u
130 #define XEA_CLEAR 1u
131
132 /* --- Program options --- */
133
134 typedef struct xopts {
135   unsigned ref;
136   unsigned f;
137   const char *dir;
138   const char *root;
139   uid_t uid;
140   gid_t gid;
141   xenv *env;
142   xenv **etail;
143 #ifdef HAVE_SETRLIMIT
144   xlimit xl;
145 #endif
146 } xopts;
147
148 #define XF_NOLOG 1u
149
150 /* --- Executable program arguments --- */
151
152 typedef struct xargs {
153   unsigned ref;
154   char *file;
155   char *argv[1];
156 } xargs;
157
158 #define XARGS_SZ(n) (sizeof(xargs) + sizeof(char *) * (n))
159
160 /* --- Executable endpoints --- */
161
162 typedef struct xept {
163   endpt e;
164   struct xept *next, *prev;
165   pid_t kid;
166   endpt *f;
167   char *desc;
168   int st;
169   xargs *xa;
170   xopts *xo;
171   selbuf err;
172 } xept;
173
174 #define XEF_CLOSE 16u
175 #define XEF_EXIT 32u
176
177 /* --- Executable program data --- */
178
179 typedef struct xdata {
180   xargs *xa;
181   xopts *xo;
182 } xdata;
183
184 /* --- Executable source block --- */
185
186 typedef struct xsource {
187   source s;
188   xdata x;
189 } xsource;
190
191 /* --- Executable target block --- */
192
193 typedef struct xtarget {
194   target t;
195   xdata x;
196 } xtarget;
197
198 /*----- Static variables --------------------------------------------------*/
199
200 static xopts exec_opts = { 1, 0, 0, 0, -1, -1, 0, &exec_opts.env };
201 static xept *xept_list;
202 static sig xept_sig;
203 static sym_table env;
204
205 /*----- Fiddling about with resource limits -------------------------------*/
206
207 #ifdef HAVE_SETRLIMIT
208
209 /* --- Table mapping user-level names to OS interface bits --- */
210
211 typedef struct rlimit_ent {
212   const char *name;
213   const char *rname;
214   int r;
215   size_t off;
216 } rlimit_ent;
217
218 static rlimit_ent rlimits[] = {
219 #define R(r, n) { #n, #r, r, offsetof(xlimit, n) },
220 #include "rlimits.h"
221   { 0, 0, 0, 0 }
222 };
223
224 #define RLIMIT(xl, o) ((struct rlimit *)((char *)(xl) + (o)))
225
226 /* --- @rlimit_get@ --- *
227  *
228  * Arguments:   @xlimit *xl@ = pointer to limit structure
229  *
230  * Returns:     ---
231  *
232  * Use:         Initializes a limit structure from the current limits.
233  */
234
235 static void rlimit_get(xlimit *xl)
236 {
237   rlimit_ent *r;
238
239   for (r = rlimits; r->name; r++) {
240     if (getrlimit(r->r, RLIMIT(xl, r->off))) {
241       moan("couldn't read %s: %s", r->rname, strerror(errno));
242       _exit(1);
243     }
244   }
245 }
246
247 /* --- @rlimit_set@ --- *
248  *
249  * Arguments:   @xlimit *xl@ = pointer to limit structure
250  *
251  * Returns:     ---
252  *
253  * Use:         Sets resource limits from the supplied limits structure.
254  */
255
256 static void rlimit_set(xlimit *xl)
257 {
258   rlimit_ent *r;
259
260   for (r = rlimits; r->name; r++) {
261     if (setrlimit(r->r, RLIMIT(xl, r->off))) {
262       moan("couldn't set %s: %s", r->rname, strerror(errno));
263       _exit(1);
264     }
265   }
266 }
267
268 /* --- @rlimit_option@ --- */
269
270 static int rlimit_option(xlimit *xl, scanner *sc)
271 {
272   CONF_BEGIN(sc, "rlimit", "resource limit")
273   enum { w_soft, w_hard, w_both } which = w_both;
274   rlimit_ent *chosen;
275   struct rlimit *rl;
276   long v;
277
278   /* --- Find out which resource is being fiddled --- */
279
280   {
281     rlimit_ent *r;
282
283     chosen = 0;
284     for (r = rlimits; r->name; r++) {
285       if (strncmp(sc->d.buf, r->name, sc->d.len) == 0) {
286         if (r->name[sc->d.len] == 0) {
287           chosen = r;
288           break;
289         } else if (chosen)
290           error(sc, "ambiguous resource limit name `%s'", sc->d.buf);
291         else if (CONF_QUAL)
292           chosen = r;
293       }
294     }
295     if (!chosen)
296       CONF_REJECT;
297     token(sc);
298     rl = RLIMIT(xl, chosen->off);
299   }
300
301   /* --- Look for hard or soft restrictions --- */
302
303   {
304     int i;
305     if (sc->t == '.')
306       token(sc);
307     if (sc->t == CTOK_WORD) {
308       if ((i = conf_enum(sc, "soft,hard",
309                          ENUM_ABBREV | ENUM_NONE, "limit type")) != -1)
310         which = i;
311     }
312   }
313
314   /* --- Now read the new value --- */
315
316   if (sc->t == '=')
317     token(sc);
318   if (sc->t != CTOK_WORD)
319     error(sc, "parse error, expected limit value");
320
321   if (conf_enum(sc, "unlimited,infinity",
322                 ENUM_ABBREV | ENUM_NONE, "limit value") > -1)
323     v = RLIM_INFINITY;
324   else {
325     char *p;
326
327     v = strtol(sc->d.buf, &p, 0);
328     if (p == sc->d.buf)
329       error(sc, "parse error, invalid limit value `%s'", sc->d.buf);
330     switch (tolower((unsigned char)*p)) {
331       case 0: break;
332       case 'b': v *= 512; break;
333       case 'g': v *= 1024;
334       case 'm': v *= 1024;
335       case 'k': v *= 1024; break;
336       default: error(sc, "parse error, invalid limit scale `%c'", *p);
337     }
338     token(sc);
339   }
340
341   /* --- Store the limit value away --- */
342
343   switch (which) {
344     case w_both:
345       rl->rlim_cur = v;
346       rl->rlim_max = v;
347       break;
348     case w_soft:
349       if (v > rl->rlim_max)
350         error(sc, "soft limit %l exceeds hard limit %l for %s",
351               v, rl->rlim_max, chosen->rname);
352       rl->rlim_cur = v;
353       break;
354     case w_hard:
355       rl->rlim_max = v;
356       if (rl->rlim_cur > v)
357         rl->rlim_cur = v;
358       break;
359   }
360
361   CONF_ACCEPT;
362   CONF_END;
363 }
364
365 #endif
366
367 /*----- Environment fiddling ----------------------------------------------*/
368
369 /* --- @xenv_option@ --- *
370  *
371  * Arguments:   @xopts *xo@ = pointer to options block
372  *              @scanner *sc@ = pointer to scanner
373  *
374  * Returns:     Nonzero if claimed
375  *
376  * Use:         Parses environment variable assignments.
377  */
378
379 static int xenv_option(xopts *xo, scanner *sc)
380 {
381   CONF_BEGIN(sc, "env", "environment")
382   xenv *xe;
383
384   /* --- Unset a variable --- */
385
386   if (strcmp(sc->d.buf, "unset") == 0) {
387     token(sc);
388     if (sc->t != CTOK_WORD)
389       error(sc, "parse error, expected environment variable name");
390     xe = CREATE(xenv);
391     xe->name = xstrdup(sc->d.buf);
392     xe->value = 0;
393     xe->act = XEA_SET;
394     token(sc);
395     goto link;
396   }
397
398   /* --- Clear the entire environment --- */
399
400   if (strcmp(sc->d.buf, "clear") == 0) {
401     token(sc);
402     xe = CREATE(xenv);
403     xe->act = XEA_CLEAR;
404     goto link;
405   }
406
407   /* --- Allow `set' to be omitted if there's a prefix --- */
408
409   if (strcmp(sc->d.buf, "set") == 0)
410     token(sc);
411   else if (!CONF_QUAL)
412     CONF_REJECT;
413
414   /* --- Set a variable --- */
415
416   if (sc->t != CTOK_WORD)
417     error(sc, "parse error, expected environment variable name");
418   xe = CREATE(xenv);
419   xe->name = xstrdup(sc->d.buf);
420   token(sc);
421   if (sc->t == '=')
422     token(sc);
423   if (sc->t != CTOK_WORD)
424     error(sc, "parse error, expected environment variable value");
425   xe->value = xstrdup(sc->d.buf);
426   xe->act = XEA_SET;
427   token(sc);
428   goto link;
429
430 link:
431   xe->next = 0;
432   *xo->etail = xe;
433   xo->etail = &xe->next;
434   CONF_ACCEPT;
435
436   /* --- Nothing else to try --- */
437
438   CONF_END;
439 }
440
441 /* --- @xenv_apply@ --- *
442  *
443  * Arguments:   @xenv *xe@ = pointer to a variable change list
444  *
445  * Returns:     ---
446  *
447  * Use:         Modifies the environment (in @env@) according to the list.
448  */
449
450 static void xenv_apply(xenv *xe)
451 {
452   while (xe) {
453     switch (xe->act) {
454       case XEA_SET:
455         env_put(&env, xe->name, xe->value);
456         break;
457       case XEA_CLEAR:
458         env_destroy(&env);
459         sym_create(&env);
460         break;
461     }
462     xe = xe->next;
463   }
464 }
465
466 /* --- @xenv_destroy@ --- *
467  *
468  * Arguments:   @xenv *xe@ = pointer to a variable change list
469  *
470  * Returns:     ---
471  *
472  * Use:         Frees the memory used by an environment variable change list.
473  */
474
475 static void xenv_destroy(xenv *xe)
476 {
477   while (xe) {
478     xenv *xxe = xe;
479     xe = xe->next;
480     xfree(xxe->name);
481     if (xxe->value)
482       xfree(xxe->value);
483     DESTROY(xxe);
484   }
485 }
486
487 /*----- Miscellaneous good things -----------------------------------------*/
488
489 /* --- @x_tidy@ --- *
490  *
491  * Arguments:   @xargs *xa@ = pointer to an arguments block
492  *              @xopts *xo@ = pointer to an options block
493  *
494  * Returns:     ---
495  *
496  * Use:         Releases a reference to argument and options blocks.
497  */
498
499 static void x_tidy(xargs *xa, xopts *xo)
500 {
501   xa->ref--;
502   if (!xa->ref)
503     xfree(xa);
504
505   xo->ref--;
506   if (!xo->ref) {
507     xenv_destroy(xo->env);
508     DESTROY(xo);
509   }
510 }
511
512 /*----- Executable endpoints ----------------------------------------------*/
513
514 /* --- @attach@ --- */
515
516 static void xept_error(char */*p*/, size_t /*len*/, void */*v*/);
517
518 static void xept_attach(endpt *e, reffd *in, reffd *out)
519 {
520   xept *xe = (xept *)e;
521   pid_t kid;
522   int fd[2];
523
524   /* --- Make a pipe for standard error --- */
525
526   if (pipe(fd)) {
527     fw_log(-1, "[%s] couldn't create pipe: %s", xe->desc, strerror(errno));
528     return;
529   }
530   fdflags(fd[0], O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
531
532   /* --- Fork a child, and handle an error if there was one --- */
533
534   if ((kid = fork()) == -1) {
535     fw_log(-1, "[%s] couldn't fork: %s", xe->desc, strerror(errno));
536     close(fd[0]);
537     close(fd[1]);
538     return;
539   }
540
541   /* --- Do the child thing --- */
542
543   if (kid == 0) {
544     xopts *xo = xe->xo;
545
546     /* --- Fiddle with the file descriptors --- *
547      *
548      * Attach the other endpoint's descriptors to standard input and output.
549      * Attach my pipe to standard error.  Mark everything as blocking and
550      * not-to-be-closed-on-exec at this end.
551      */
552
553     close(fd[0]);
554     if (dup2(in->fd, STDIN_FILENO) < 0 ||
555         dup2(out->fd, STDOUT_FILENO) < 0 ||
556         dup2(fd[1], STDERR_FILENO) < 0) {
557       moan("couldn't manipulate file descriptors: %s", strerror(errno));
558       _exit(1);
559     }
560
561     if (in->fd > 2)
562       close(in->fd);
563     if (out->fd > 2)
564       close(out->fd);
565
566     fdflags(STDIN_FILENO, O_NONBLOCK, 0, FD_CLOEXEC, 0);
567     fdflags(STDOUT_FILENO, O_NONBLOCK, 0, FD_CLOEXEC, 0);
568     fdflags(STDERR_FILENO, O_NONBLOCK, 0, FD_CLOEXEC, 0);
569
570     /* --- First of all set the @chroot@ prison --- */
571
572     if (xo->root && chroot(xo->root)) {
573       moan("couldn't set `%s' as filesystem root: %s",
574            xo->root, strerror(errno));
575       _exit(1);
576     }
577
578     /* --- Now set the current directory --- */
579
580     if (xo->dir ? chdir(xo->dir) : xo->root ? chdir("/") : 0) {
581       moan("couldn't set `%s' as current directory: %s",
582            xo->dir ? xo->dir : "/", strerror(errno));
583       _exit(1);
584     }
585
586     /* --- Set the resource limits --- */
587
588 #ifdef HAVE_SETRLIMIT
589     rlimit_set(&xo->xl);
590 #endif
591
592     /* --- Set group id --- */
593
594     if (xo->gid != -1) {
595       if (setgid(xo->gid)) {
596         moan("couldn't set gid %i: %s", xo->gid, strerror(errno));
597         _exit(1);
598       }
599 #ifdef HAVE_SETGROUPS
600       if (setgroups(1, &xo->gid))
601         moan("warning: couldn't set group list to %i: %s", xo->gid,
602              strerror(errno));
603 #endif
604     }
605
606     /* --- Set uid --- */
607
608     if (xo->uid != -1) {
609       if (setuid(xo->uid)) {
610         moan("couldn't set uid %i: %s", xo->uid, strerror(errno));
611         _exit(1);
612       }
613     }
614
615     /* --- Play with signal dispositions --- */
616
617     signal(SIGPIPE, SIG_DFL);
618
619     /* --- Fiddle with the environment --- */
620
621     xenv_apply(exec_opts.env);
622     xenv_apply(xe->xo->env);
623     environ = env_export(&env);
624
625     /* --- Run the program --- */
626
627     execvp(xe->xa->file, xe->xa->argv);
628     moan("couldn't execute `%s': %s", xe->xa->file, strerror(errno));
629     _exit(127);
630   }
631
632   /* --- The child's done; see to the parent --- */
633
634   xe->kid = kid;
635   selbuf_init(&xe->err, sel, fd[0], xept_error, xe);
636   close(fd[1]);
637   xe->next = xept_list;
638   xe->prev = 0;
639   if (xept_list)
640     xept_list->prev = xe;
641   xept_list = xe;
642   if (!(xe->xo->f & XF_NOLOG))
643     fw_log(-1, "[%s] started with pid %i", xe->desc, kid);
644   fw_inc();
645   return;
646 }
647
648 /* --- @xept_file@ --- */
649
650 static void xept_file(endpt *e, endpt *f)
651 {
652   xept *xe = (xept *)e;
653   xe->f = f;
654 }
655
656 /* --- @xept_close@ --- */
657
658 static void xept_close(endpt *e)
659 {
660   xept *xe = (xept *)e;
661   if (xe->kid == -1) {
662     if (xe->f)
663       xe->f->ops->close(xe->f);
664     x_tidy(xe->xa, xe->xo);
665     DESTROY(xe);
666     fw_dec();
667   }
668 }
669
670 /* --- @xept_destroy@ --- */
671
672 static void xept_destroy(xept *xe)
673 {
674   /* --- First emit the news about the process --- */
675
676   if (xe->xo->f & XF_NOLOG)
677     /* Nothin' doin' */;
678   else if (WIFEXITED(xe->st)) {
679     if (WEXITSTATUS(xe->st) == 0)
680       fw_log(-1, "[%s] pid %i exited successfully", xe->desc, xe->kid);
681     else {
682       fw_log(-1, "[%s] pid %i failed: status %i",
683              xe->desc, xe->kid, WEXITSTATUS(xe->st));
684     }
685   } else if (WIFSIGNALED(xe->st)) {
686     const char *s;
687 #ifdef HAVE_STRSIGNAL
688     s = strsignal(WTERMSIG(xe->st));
689 #elif HAVE__SYS_SIGLIST
690     s = _sys_siglist[WTERMSIG(xe->st)];
691 #else
692     char buf[32];
693     sprintf(buf, "signal %i", WTERMSIG(xe->st));
694     s = buf;
695 #endif
696     fw_log(-1, "[%s] pid %i failed: %s", xe->desc, xe->kid, s);
697   } else
698     fw_log(-1, "[%s] pid %i failed: unrecognized status", xe->desc, xe->kid);
699
700   /* --- Free up the parent-side resources --- */
701
702   if (xe->next)
703     xe->next->prev = xe->prev;
704   if (xe->prev)
705     xe->prev->next = xe->next;
706   else
707     xept_list = xe->next;
708
709   xfree(xe->desc);
710   if (xe->f)
711     xe->f->ops->close(xe->f);
712   x_tidy(xe->xa, xe->xo);
713   fw_dec();
714   DESTROY(xe);
715 }
716
717 /* --- @xept_chld@ --- *
718  *
719  * Arguments:   @int n@ = signal number
720  *              @void *p@ = uninteresting pointer
721  *
722  * Returns:     ---
723  *
724  * Use:         Deals with child death situations.
725  */
726
727 static void xept_chld(int n, void *p)
728 {
729   pid_t kid;
730   int st;
731
732   while ((kid = waitpid(-1, &st, WNOHANG)) > 0) {
733     xept *xe = xept_list;
734     while (xe) {
735       xept *xxe = xe;
736       xe = xe->next;
737       if (kid == xxe->kid) {
738         xxe->st = st;
739         xxe->e.f |= XEF_EXIT;
740         if (xxe->e.f & XEF_CLOSE)
741           xept_destroy(xxe);
742         break;
743       }
744     }
745   }
746 }
747
748 /* --- @xept_error@ --- *
749  *
750  * Arguments:   @char *p@ = pointer to string read from stderr
751  *              @size_t len@ = length of the string
752  *              @void *v@ = pointer to by endpoint
753  *
754  * Returns:     ---
755  *
756  * Use:         Handles error reports from a child process.
757  */
758
759 static void xept_error(char *p, size_t len, void *v)
760 {
761   xept *xe = v;
762   if (p)
763     fw_log(-1, "[%s] pid %i: %s", xe->desc, xe->kid, p);
764   else {
765     close(xe->err.reader.fd);
766     selbuf_destroy(&xe->err);
767     xe->e.f |= XEF_CLOSE;
768     if (xe->e.f & XEF_EXIT)
769       xept_destroy(xe);
770   }
771 }
772
773 /* --- Endpoint operations --- */
774
775 static endpt_ops xept_ops = { xept_attach, xept_file, 0, xept_close };
776
777 /*----- General operations on sources and targets -------------------------*/
778
779 /* --- @exec_init@ --- *
780  *
781  * Arguments:   ---
782  *
783  * Returns:     ---
784  *
785  * Use:         Initializes the executable problem source and target.
786  */
787
788 void exec_init(void)
789 {
790   rlimit_get(&exec_opts.xl);
791   sig_add(&xept_sig, SIGCHLD, xept_chld, 0);
792   sym_create(&env);
793   env_import(&env, environ);
794 }
795
796 /* --- @exec_option@ --- */
797
798 static int exec_option(xdata *x, scanner *sc)
799 {
800   xopts *xo = x ? x->xo : &exec_opts;
801
802   CONF_BEGIN(sc, "exec", "executable");
803
804   /* --- Logging settings --- */
805
806   if (strcmp(sc->d.buf, "logging") == 0 ||
807       strcmp(sc->d.buf, "log") == 0) {
808     token(sc);
809     if (sc->t == '=')
810       token(sc);
811     if (conf_enum(sc, "no,yes", ENUM_ABBREV, "logging status"))
812       xo->f &= ~XF_NOLOG;
813     else
814       xo->f |= XF_NOLOG;
815     CONF_ACCEPT;
816   }
817
818   /* --- Current directory setting --- *
819    *
820    * Lots of possibilities to guard against possible brainoes.
821    */
822
823   if (strcmp(sc->d.buf, "dir") == 0 ||
824       strcmp(sc->d.buf, "cd") == 0 ||
825       strcmp(sc->d.buf, "chdir") == 0 ||
826       strcmp(sc->d.buf, "cwd") == 0) {
827     dstr d = DSTR_INIT;
828     token(sc);
829     if (sc->t == '=')
830       token(sc);
831     conf_name(sc, '/', &d);
832     xo->dir = xstrdup(d.buf);
833     dstr_destroy(&d);
834     CONF_ACCEPT;
835   }
836
837   /* --- Set a chroot prison --- */
838
839   if (strcmp(sc->d.buf, "root") == 0 ||
840       strcmp(sc->d.buf, "chroot") == 0) { 
841     dstr d = DSTR_INIT;
842     token(sc);
843     if (sc->t == '=')
844       token(sc);
845     conf_name(sc, '/', &d);
846     xo->root = xstrdup(d.buf);
847     dstr_destroy(&d);
848     CONF_ACCEPT;
849   }
850
851   /* --- Set the target user id --- */
852
853   if (strcmp(sc->d.buf, "uid") == 0 ||
854       strcmp(sc->d.buf, "user") == 0) {
855     token(sc);
856     if (sc->t == '=')
857       token(sc);
858     if (sc->t != CTOK_WORD)
859       error(sc, "parse error, expected user name or uid");
860     if (isdigit((unsigned char)*sc->d.buf))
861       xo->uid = atoi(sc->d.buf);
862     else {
863       struct passwd *pw = getpwnam(sc->d.buf);
864       if (!pw)
865         error(sc, "unknown user name `%s'", sc->d.buf);
866       xo->uid = pw->pw_uid;
867     }
868     token(sc);
869     CONF_ACCEPT;
870   }
871
872   /* --- Set the target group id --- */
873
874   if (strcmp(sc->d.buf, "gid") == 0 ||
875       strcmp(sc->d.buf, "group") == 0) {
876     token(sc);
877     if (sc->t == '=')
878       token(sc);
879     if (sc->t != CTOK_WORD)
880       error(sc, "parse error, expected group name or gid");
881     if (isdigit((unsigned char)*sc->d.buf))
882       xo->gid = atoi(sc->d.buf);
883     else {
884       struct group *gr = getgrnam(sc->d.buf);
885       if (!gr)
886         error(sc, "unknown user name `%s'", sc->d.buf);
887       xo->gid = gr->gr_gid;
888     }
889     token(sc);
890     CONF_ACCEPT;
891   }
892
893   /* --- Now try resource limit settings --- */
894
895   if (rlimit_option(&xo->xl, sc))
896     CONF_ACCEPT;
897
898   /* --- And then environment settings --- */
899
900   if (xenv_option(xo, sc))
901     CONF_ACCEPT;
902
903   /* --- Nothing found --- */
904
905   CONF_END;
906 }
907
908 /* --- @exec_desc@ --- */
909
910 static void exec_desc(xdata *x, dstr *d)
911 {
912   char **p;
913   char sep = '[';
914   dstr_puts(d, "exec ");
915   if (strcmp(x->xa->file, x->xa->argv[0]) != 0) {
916     dstr_puts(d, x->xa->file);
917     dstr_putc(d, ' ');
918   }
919   for (p = x->xa->argv; *p; p++) {
920     dstr_putc(d, sep);
921     dstr_puts(d, *p);
922     sep = ' ';
923   }
924   dstr_putc(d, ']');
925   dstr_putz(d);
926 }
927
928 /* --- @exec_read@ --- */
929
930 static void exec_read(xdata *x, scanner *sc)
931 {
932   size_t base = 0;
933   dstr d = DSTR_INIT;
934   xargs *xa;
935
936   /* --- Read the first word --- *
937    *
938    * This is either a shell command or the actual program to run.
939    */
940
941   if (sc->t == CTOK_WORD) {
942     dstr_putd(&d, &sc->d); d.len++;
943     base = d.len;
944     token(sc);
945   }
946
947   /* --- See if there's a list of arguments --- *
948    *
949    * If not, then the thing I saw was a shell command, so build the proper
950    * arguments for that.
951    */
952
953   if (sc->t != '[') {
954     char *p;
955     if (!base)
956       error(sc, "parse error, expected shell command or argument list");
957     xa = xmalloc(XARGS_SZ(3) + 8 + 3 + d.len);
958     p = (char *)(xa->argv + 4);
959     xa->ref = 1;
960     xa->file = p;
961     xa->argv[0] = p; memcpy(p, "/bin/sh", 8); p += 8;
962     xa->argv[1] = p; memcpy(p, "-c", 3); p += 3;
963     xa->argv[2] = p; memcpy(p, d.buf, d.len); p += d.len;
964     xa->argv[3] = 0;
965   }
966
967   /* --- Snarf in a list of arguments --- */
968
969   else {
970     int argc = 0;
971     char *p, *q;
972     char **v;
973
974     /* --- Strip off the leading `[' --- */
975
976     token(sc);
977
978     /* --- Read a sequence of arguments --- */
979
980     while (sc->t == CTOK_WORD) {
981       dstr_putd(&d, &sc->d); d.len++;
982       token(sc);
983       argc++;
984     }
985
986     /* --- Expect the closing `]' --- */
987
988     if (sc->t != ']')
989       error(sc, "parse error, missing `]'");
990     token(sc);
991
992     /* --- If there are no arguments, whinge --- */
993
994     if (!argc)
995       error(sc, "must specify at least one argument");
996
997     /* --- Allocate a lump of memory for the array --- */
998
999     xa = xmalloc(XARGS_SZ(argc) + d.len);
1000     xa->ref = 1;
1001     v = xa->argv;
1002     p = (char *)(v + argc + 1);
1003     memcpy(p, d.buf, d.len);
1004     q = p + d.len;
1005     xa->file = p;
1006     p += base;
1007
1008     /* --- Start dumping addresses into the @argv@ array --- */
1009
1010     for (;;) {
1011       *v++ = p;
1012       while (*p++ && p < q)
1013         ;
1014       if (p >= q)
1015         break;
1016     }
1017     *v++ = 0;
1018   }
1019
1020   /* --- Do some other setting up --- */
1021
1022   dstr_destroy(&d);
1023   x->xa = xa;
1024   x->xo = CREATE(xopts);
1025   *x->xo = exec_opts;
1026   x->xo->ref = 1;
1027   return;
1028 }
1029
1030 /* --- @exec_endpt@ --- */
1031
1032 static endpt *exec_endpt(xdata *x, const char *desc)
1033 {
1034   xept *xe = CREATE(xept);
1035   xe->e.ops = &xept_ops;
1036   xe->e.other = 0;
1037   xe->e.t = 0;
1038   xe->e.f = 0;
1039   xe->xa = x->xa; xe->xa->ref++;
1040   xe->xo = x->xo; xe->xo->ref++;
1041   xe->kid = -1;
1042   xe->f = 0;
1043   xe->desc = xstrdup(desc);
1044   return (&xe->e);
1045 }
1046
1047 /* --- @exec_destroy@ --- */
1048
1049 static void exec_destroy(xdata *x)
1050 {
1051   x_tidy(x->xa, x->xo);
1052 }
1053
1054 /*----- Source definition -------------------------------------------------*/
1055
1056 /* --- @option@ --- */
1057
1058 static int xsource_option(source *s, scanner *sc)
1059 {
1060   xsource *xs = (xsource *)s;
1061   return (exec_option(xs ? &xs->x : 0, sc));
1062 }
1063
1064 /* --- @read@ --- */
1065
1066 static source *xsource_read(scanner *sc)
1067 {
1068   xsource *xs;
1069
1070   if (!conf_prefix(sc, "exec"))
1071     return (0);
1072   xs = CREATE(xsource);
1073   xs->s.ops = &xsource_ops;
1074   xs->s.desc = 0;
1075   exec_read(&xs->x, sc);
1076   return (&xs->s);
1077 }
1078
1079 /* --- @attach@ --- */
1080
1081 static void xsource_destroy(source */*s*/);
1082
1083 static void xsource_attach(source *s, scanner *sc, target *t)
1084 {
1085   xsource *xs = (xsource *)s;
1086   endpt *e, *ee;
1087
1088   /* --- Set up the source description string --- */
1089
1090   {
1091     dstr d = DSTR_INIT;
1092     exec_desc(&xs->x, &d);
1093     dstr_puts(&d, " -> ");
1094     dstr_puts(&d, t->desc);
1095     xs->s.desc = xstrdup(d.buf);
1096     dstr_destroy(&d);
1097   }
1098
1099   /* --- Create the endpoints --- */
1100
1101   if ((ee = t->ops->create(t, xs->s.desc)) == 0)
1102     goto tidy;
1103   if ((e = exec_endpt(&xs->x, xs->s.desc)) == 0) {
1104     ee->ops->close(ee);
1105     goto tidy;
1106   }
1107   endpt_join(e, ee);
1108
1109   /* --- Dispose of source and target --- */
1110
1111 tidy:
1112   t->ops->destroy(t);
1113   xsource_destroy(&xs->s);
1114 }
1115
1116 /* --- @destroy@ --- */
1117
1118 static void xsource_destroy(source *s)
1119 {
1120   xsource *xs = (xsource *)s;
1121   xfree(xs->s.desc);
1122   exec_destroy(&xs->x);
1123   DESTROY(xs);
1124 }
1125
1126 /* --- Executable source operations --- */
1127
1128 source_ops xsource_ops = {
1129   "exec",
1130   xsource_option, xsource_read, xsource_attach, xsource_destroy
1131 };
1132
1133 /*----- Exec target description -------------------------------------------*/
1134
1135 /* --- @option@ --- */
1136
1137 static int xtarget_option(target *t, scanner *sc)
1138 {
1139   xtarget *xt = (xtarget *)t;
1140   return (exec_option(xt ? &xt->x : 0, sc));
1141 }
1142
1143 /* --- @read@ --- */
1144
1145 static target *xtarget_read(scanner *sc)
1146 {
1147   xtarget *xt;
1148   dstr d = DSTR_INIT;
1149
1150   if (!conf_prefix(sc, "exec"))
1151     return (0);
1152   xt = CREATE(xtarget);
1153   xt->t.ops = &xtarget_ops;
1154   exec_read(&xt->x, sc);
1155   exec_desc(&xt->x, &d);
1156   xt->t.desc = xstrdup(d.buf);
1157   dstr_destroy(&d);
1158   return (&xt->t);
1159 }
1160
1161 /* --- @create@ --- */
1162
1163 static endpt *xtarget_create(target *t, const char *desc)
1164 {
1165   xtarget *xt = (xtarget *)t;
1166   endpt *e = exec_endpt(&xt->x, desc);
1167   return (e);
1168 }
1169
1170 /* --- @destroy@ --- */
1171
1172 static void xtarget_destroy(target *t)
1173 {
1174   xtarget *xt = (xtarget *)t;
1175   xfree(xt->t.desc);
1176   exec_destroy(&xt->x);
1177   DESTROY(xt);
1178 }
1179
1180 /* --- Exec target operations --- */
1181
1182 target_ops xtarget_ops = {
1183   "exec",
1184   xtarget_option, xtarget_read, xtarget_create, xtarget_destroy
1185 };
1186
1187 /*----- That's all, folks -------------------------------------------------*/