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