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