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