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