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