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