+/* -*-c-*-
+ *
+ * $Id: exec.c,v 1.1 1999/07/26 23:33:32 mdw Exp $
+ *
+ * Source and target for executable programs
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the `fw' port forwarder.
+ *
+ * `fw' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * `fw' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with `fw'; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: exec.c,v $
+ * Revision 1.1 1999/07/26 23:33:32 mdw
+ * New sources and targets.
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "config.h"
+
+#define _GNU_SOURCE
+
+#include <ctype.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+
+#ifdef HAVE_SETRLIMIT
+# include <sys/resource.h>
+#endif
+
+#ifndef DECL_ENVIRON
+ extern char **environ;
+#endif
+
+#include <pwd.h>
+#include <grp.h>
+
+#include <syslog.h>
+
+#include <mLib/alloc.h>
+#include <mLib/dstr.h>
+#include <mLib/env.h>
+#include <mLib/fdflags.h>
+#include <mLib/report.h>
+#include <mLib/sel.h>
+#include <mLib/selbuf.h>
+#include <mLib/sig.h>
+#include <mLib/sub.h>
+#include <mLib/sym.h>
+
+#include "conf.h"
+#include "endpt.h"
+#include "exec.h"
+#include "fattr.h"
+#include "fw.h"
+#include "reffd.h"
+#include "scan.h"
+#include "source.h"
+#include "target.h"
+
+/*----- Data structures ---------------------------------------------------*/
+
+/* --- Resource usage --- */
+
+#ifdef HAVE_SETRLIMIT
+
+typedef struct xlimit {
+#define R(r, n) struct rlimit n;
+#include "rlimits.h"
+} xlimit;
+
+#endif
+
+/* --- Environment variable modifications --- */
+
+typedef struct xenv {
+ struct xenv *next;
+ unsigned act;
+ char *name;
+ char *value;
+} xenv;
+
+#define XEA_SET 0u
+#define XEA_CLEAR 1u
+
+/* --- Program options --- */
+
+typedef struct xopts {
+ unsigned ref;
+ unsigned f;
+ const char *dir;
+ const char *root;
+ uid_t uid;
+ gid_t gid;
+ xenv *env;
+ xenv **etail;
+#ifdef HAVE_SETRLIMIT
+ xlimit xl;
+#endif
+} xopts;
+
+#define XF_NOLOG 1u
+
+/* --- Executable program arguments --- */
+
+typedef struct xargs {
+ unsigned ref;
+ char *file;
+ char *argv[1];
+} xargs;
+
+#define XARGS_SZ(n) (sizeof(xargs) + sizeof(char *) * (n))
+
+/* --- Executable endpoints --- */
+
+typedef struct xept {
+ endpt e;
+ struct xept *next, *prev;
+ pid_t kid;
+ const char *desc;
+ int st;
+ xargs *xa;
+ xopts *xo;
+ selbuf err;
+} xept;
+
+#define XEF_CLOSE 16u
+#define XEF_EXIT 32u
+
+/* --- Executable program data --- */
+
+typedef struct xdata {
+ xargs *xa;
+ xopts *xo;
+} xdata;
+
+/* --- Executable source block --- */
+
+typedef struct xsource {
+ source s;
+ xdata x;
+} xsource;
+
+/* --- Executable target block --- */
+
+typedef struct xtarget {
+ target t;
+ xdata x;
+} xtarget;
+
+/*----- Static variables --------------------------------------------------*/
+
+static xopts exec_opts = { 1, 0, 0, 0, -1, -1, 0, &exec_opts.env };
+static xept *xept_list;
+static sig xept_sig;
+static sym_table env;
+
+/*----- Fiddling about with resource limits -------------------------------*/
+
+#ifdef HAVE_SETRLIMIT
+
+/* --- Table mapping user-level names to OS interface bits --- */
+
+typedef struct rlimit_ent {
+ const char *name;
+ const char *rname;
+ int r;
+ size_t off;
+} rlimit_ent;
+
+static rlimit_ent rlimits[] = {
+#define R(r, n) { #n, #r, r, offsetof(xlimit, n) },
+#include "rlimits.h"
+ { 0, 0, 0, 0 }
+};
+
+#define RLIMIT(xl, o) ((struct rlimit *)((char *)(xl) + (o)))
+
+/* --- @rlimit_get@ --- *
+ *
+ * Arguments: @xlimit *xl@ = pointer to limit structure
+ *
+ * Returns: ---
+ *
+ * Use: Initializes a limit structure from the current limits.
+ */
+
+static void rlimit_get(xlimit *xl)
+{
+ rlimit_ent *r;
+
+ for (r = rlimits; r->name; r++) {
+ if (getrlimit(r->r, RLIMIT(xl, r->off))) {
+ moan("couldn't read %s: %s", r->rname, strerror(errno));
+ _exit(1);
+ }
+ }
+}
+
+/* --- @rlimit_set@ --- *
+ *
+ * Arguments: @xlimit *xl@ = pointer to limit structure
+ *
+ * Returns: ---
+ *
+ * Use: Sets resource limits from the supplied limits structure.
+ */
+
+static void rlimit_set(xlimit *xl)
+{
+ rlimit_ent *r;
+
+ for (r = rlimits; r->name; r++) {
+ if (setrlimit(r->r, RLIMIT(xl, r->off))) {
+ moan("couldn't set %s: %s", r->rname, strerror(errno));
+ _exit(1);
+ }
+ }
+}
+
+/* --- @rlimit_option@ --- */
+
+static int rlimit_option(xlimit *xl, scanner *sc)
+{
+ CONF_BEGIN(sc, "rlimit", "resource limit")
+ enum { w_soft, w_hard, w_both } which = w_both;
+ rlimit_ent *chosen;
+ struct rlimit *rl;
+ long v;
+
+ /* --- Find out which resource is being fiddled --- */
+
+ {
+ rlimit_ent *r;
+
+ chosen = 0;
+ for (r = rlimits; r->name; r++) {
+ if (strncmp(sc->d.buf, r->name, sc->d.len) == 0) {
+ if (r->name[sc->d.len] == 0) {
+ chosen = r;
+ break;
+ } else if (chosen)
+ error(sc, "ambiguous resource limit name `%s'", sc->d.buf);
+ else if (CONF_QUAL)
+ chosen = r;
+ }
+ }
+ if (!chosen)
+ CONF_REJECT;
+ token(sc);
+ rl = RLIMIT(xl, chosen->off);
+ }
+
+ /* --- Look for hard or soft restrictions --- */
+
+ {
+ int i;
+ if (sc->t == '.')
+ token(sc);
+ if (sc->t == CTOK_WORD) {
+ if ((i = conf_enum(sc, "soft,hard",
+ ENUM_ABBREV | ENUM_NONE, "limit type")) != -1)
+ which = i;
+ }
+ }
+
+ /* --- Now read the new value --- */
+
+ if (sc->t == '=')
+ token(sc);
+ if (sc->t != CTOK_WORD)
+ error(sc, "parse error, expected limit value");
+
+ if (conf_enum(sc, "unlimited,infinity",
+ ENUM_ABBREV | ENUM_NONE, "limit value") > -1)
+ v = RLIM_INFINITY;
+ else {
+ char *p;
+
+ v = strtol(sc->d.buf, &p, 0);
+ if (p == sc->d.buf)
+ error(sc, "parse error, invalid limit value `%s'", sc->d.buf);
+ switch (tolower((unsigned char)*p)) {
+ case 0: break;
+ case 'b': v *= 512; break;
+ case 'g': v *= 1024;
+ case 'm': v *= 1024;
+ case 'k': v *= 1024; break;
+ default: error(sc, "parse error, invalid limit scale `%c'", *p);
+ }
+ token(sc);
+ }
+
+ /* --- Store the limit value away --- */
+
+ switch (which) {
+ case w_both:
+ rl->rlim_cur = v;
+ rl->rlim_max = v;
+ break;
+ case w_soft:
+ if (v > rl->rlim_max)
+ error(sc, "soft limit %l exceeds hard limit %l for %s",
+ v, rl->rlim_max, chosen->rname);
+ rl->rlim_cur = v;
+ break;
+ case w_hard:
+ rl->rlim_max = v;
+ if (rl->rlim_cur > v)
+ rl->rlim_cur = v;
+ break;
+ }
+
+ CONF_ACCEPT;
+ CONF_END;
+}
+
+#endif
+
+/*----- Environment fiddling ----------------------------------------------*/
+
+/* --- @xenv_option@ --- *
+ *
+ * Arguments: @xopts *xo@ = pointer to options block
+ * @scanner *sc@ = pointer to scanner
+ *
+ * Returns: Nonzero if claimed
+ *
+ * Use: Parses environment variable assignments.
+ */
+
+static int xenv_option(xopts *xo, scanner *sc)
+{
+ CONF_BEGIN(sc, "env", "environment")
+ xenv *xe;
+
+ /* --- Unset a variable --- */
+
+ if (strcmp(sc->d.buf, "unset") == 0) {
+ token(sc);
+ if (sc->t != CTOK_WORD)
+ error(sc, "parse error, expected environment variable name");
+ xe = CREATE(xenv);
+ xe->name = xstrdup(sc->d.buf);
+ xe->value = 0;
+ xe->act = XEA_SET;
+ token(sc);
+ goto link;
+ }
+
+ /* --- Clear the entire environment --- */
+
+ if (strcmp(sc->d.buf, "clear") == 0) {
+ token(sc);
+ xe = CREATE(xenv);
+ xe->act = XEA_CLEAR;
+ goto link;
+ }
+
+ /* --- Allow `set' to be omitted if there's a prefix --- */
+
+ if (strcmp(sc->d.buf, "set") == 0)
+ token(sc);
+ else if (!CONF_QUAL)
+ CONF_REJECT;
+
+ /* --- Set a variable --- */
+
+ if (sc->t != CTOK_WORD)
+ error(sc, "parse error, expected environment variable name");
+ xe = CREATE(xenv);
+ xe->name = xstrdup(sc->d.buf);
+ token(sc);
+ if (sc->t == '=')
+ token(sc);
+ if (sc->t != CTOK_WORD)
+ error(sc, "parse error, expected environment variable value");
+ xe->value = xstrdup(sc->d.buf);
+ xe->act = XEA_SET;
+ token(sc);
+ goto link;
+
+link:
+ xe->next = 0;
+ *xo->etail = xe;
+ xo->etail = &xe->next;
+ CONF_ACCEPT;
+
+ /* --- Nothing else to try --- */
+
+ CONF_END;
+}
+
+/* --- @xenv_apply@ --- *
+ *
+ * Arguments: @xenv *xe@ = pointer to a variable change list
+ *
+ * Returns: ---
+ *
+ * Use: Modifies the environment (in @env@) according to the list.
+ */
+
+static void xenv_apply(xenv *xe)
+{
+ while (xe) {
+ switch (xe->act) {
+ case XEA_SET:
+ env_put(&env, xe->name, xe->value);
+ break;
+ case XEA_CLEAR:
+ env_destroy(&env);
+ sym_create(&env);
+ break;
+ }
+ xe = xe->next;
+ }
+}
+
+/* --- @xenv_destroy@ --- *
+ *
+ * Arguments: @xenv *xe@ = pointer to a variable change list
+ *
+ * Returns: ---
+ *
+ * Use: Frees the memory used by an environment variable change list.
+ */
+
+static void xenv_destroy(xenv *xe)
+{
+ while (xe) {
+ xenv *xxe = xe;
+ xe = xe->next;
+ free(xxe->name);
+ if (xxe->value)
+ free(xxe->value);
+ DESTROY(xxe);
+ }
+}
+
+/*----- Miscellaneous good things -----------------------------------------*/
+
+/* --- @x_tidy@ --- *
+ *
+ * Arguments: @xargs *xa@ = pointer to an arguments block
+ * @xopts *xo@ = pointer to an options block
+ *
+ * Returns: ---
+ *
+ * Use: Releases a reference to argument and options blocks.
+ */
+
+static void x_tidy(xargs *xa, xopts *xo)
+{
+ xa->ref--;
+ if (!xa->ref)
+ free(xa);
+
+ xo->ref--;
+ if (!xo->ref) {
+ xenv_destroy(xo->env);
+ DESTROY(xo);
+ }
+}
+
+/*----- Executable endpoints ----------------------------------------------*/
+
+/* --- @attach@ --- */
+
+static void xept_error(char */*p*/, void */*v*/);
+
+static void xept_attach(endpt *e, reffd *in, reffd *out)
+{
+ xept *xe = (xept *)e;
+ pid_t kid;
+ int fd[2];
+
+ /* --- Make a pipe for standard error --- */
+
+ if (pipe(fd)) {
+ fw_log(-1, "[%s] couldn't create pipe: %s", xe->desc, strerror(errno));
+ return;
+ }
+ fdflags(fd[0], O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
+
+ /* --- Fork a child, and handle an error if there was one --- */
+
+ if ((kid = fork()) == -1) {
+ fw_log(-1, "[%s] couldn't fork: %s", xe->desc, strerror(errno));
+ close(fd[0]);
+ close(fd[1]);
+ return;
+ }
+
+ /* --- Do the child thing --- */
+
+ if (kid == 0) {
+ xopts *xo = xe->xo;
+
+ /* --- Fiddle with the file descriptors --- *
+ *
+ * Attach the other endpoint's descriptors to standard input and output.
+ * Attach my pipe to standard error. Mark everything as blocking and
+ * not-to-be-closed-on-exec at this end.
+ */
+
+ close(fd[0]);
+ if (dup2(in->fd, STDIN_FILENO) < 0 ||
+ dup2(out->fd, STDOUT_FILENO) < 0 ||
+ dup2(fd[1], STDERR_FILENO) < 0) {
+ moan("couldn't manipulate file descriptors: %s", strerror(errno));
+ _exit(1);
+ }
+
+ if (in->fd > 2)
+ close(in->fd);
+ if (out->fd > 2)
+ close(out->fd);
+
+ fdflags(STDIN_FILENO, O_NONBLOCK, 0, FD_CLOEXEC, 0);
+ fdflags(STDOUT_FILENO, O_NONBLOCK, 0, FD_CLOEXEC, 0);
+ fdflags(STDERR_FILENO, O_NONBLOCK, 0, FD_CLOEXEC, 0);
+
+ /* --- First of all set the @chroot@ prison --- */
+
+ if (xo->root && chroot(xo->root)) {
+ moan("couldn't set `%s' as filesystem root: %s",
+ xo->root, strerror(errno));
+ _exit(1);
+ }
+
+ /* --- Now set the current directory --- */
+
+ if (xo->dir ? chdir(xo->dir) : xo->root ? chdir("/") : 0) {
+ moan("couldn't set `%s' as current directory: %s",
+ xo->dir ? xo->dir : "/", strerror(errno));
+ _exit(1);
+ }
+
+ /* --- Set the resource limits --- */
+
+#ifdef HAVE_SETRLIMIT
+ rlimit_set(&xo->xl);
+#endif
+
+ /* --- Set group id --- */
+
+ if (xo->gid != -1) {
+ if (setgid(xo->gid)) {
+ moan("couldn't set gid %i: %s", xo->gid, strerror(errno));
+ _exit(1);
+ }
+#ifdef HAVE_SETGROUPS
+ if (setgroups(1, &xo->gid))
+ moan("warning: couldn't set group list to %i: %s", xo->gid,
+ strerror(errno));
+#endif
+ }
+
+ /* --- Set uid --- */
+
+ if (xo->uid != -1) {
+ if (setuid(xo->uid)) {
+ moan("couldn't set uid %i: %s", xo->uid, strerror(errno));
+ _exit(1);
+ }
+ }
+
+ /* --- Play with signal dispositions --- */
+
+ signal(SIGPIPE, SIG_DFL);
+
+ /* --- Fiddle with the environment --- */
+
+ xenv_apply(exec_opts.env);
+ xenv_apply(xe->xo->env);
+ environ = env_export(&env);
+
+ /* --- Run the program --- */
+
+ execvp(xe->xa->file, xe->xa->argv);
+ moan("couldn't execute `%s': %s", xe->xa->file, strerror(errno));
+ _exit(127);
+ }
+
+ /* --- The child's done; see to the parent --- */
+
+ xe->kid = kid;
+ selbuf_init(&xe->err, sel, fd[0], xept_error, xe);
+ close(fd[1]);
+ xe->next = xept_list;
+ xe->prev = 0;
+ if (xept_list)
+ xept_list->prev = xe;
+ xept_list = xe;
+ if (!(xe->xo->f & XF_NOLOG))
+ fw_log(-1, "[%s] started with pid %i", xe->desc, kid);
+ fw_inc();
+ return;
+}
+
+/* --- @xept_close@ --- */
+
+static void xept_close(endpt *e)
+{
+ xept *xe = (xept *)e;
+ if (xe->kid == -1) {
+ x_tidy(xe->xa, xe->xo);
+ DESTROY(xe);
+ fw_dec();
+ }
+}
+
+/* --- @xept_destroy@ --- */
+
+static void xept_destroy(xept *xe)
+{
+ /* --- First emit the news about the process --- */
+
+ if (xe->xo->f & XF_NOLOG)
+ /* Nothin' doin' */;
+ else if (WIFEXITED(xe->st)) {
+ if (WEXITSTATUS(xe->st) == 0)
+ fw_log(-1, "[%s] pid %i exited successfully", xe->desc, xe->kid);
+ else {
+ fw_log(-1, "[%s] pid %i failed: status %i",
+ xe->desc, xe->kid, WEXITSTATUS(xe->st));
+ }
+ } else if (WIFSIGNALED(xe->st)) {
+ const char *s;
+#ifdef HAVE_STRSIGNAL
+ s = strsignal(WTERMSIG(xe->st));
+#elif HAVE__SYS_SIGLIST
+ s = _sys_siglist[WTERMSIG(xe->st)];
+#else
+ char buf[32];
+ sprintf(buf, "signal %i", WTERMSIG(xe->st));
+ s = buf;
+#endif
+ fw_log(-1, "[%s] pid %i failed: %s", xe->desc, xe->kid, s);
+ } else
+ fw_log(-1, "[%s] pid %i failed: unrecognized status", xe->desc, xe->kid);
+
+ /* --- Free up the parent-side resources --- */
+
+ if (xe->next)
+ xe->next->prev = xe->prev;
+ if (xe->prev)
+ xe->prev->next = xe->next;
+ else
+ xept_list = xe->next;
+
+ x_tidy(xe->xa, xe->xo);
+ fw_dec();
+ DESTROY(xe);
+}
+
+/* --- @xept_chld@ --- *
+ *
+ * Arguments: @int n@ = signal number
+ * @void *p@ = uninteresting pointer
+ *
+ * Returns: ---
+ *
+ * Use: Deals with child death situations.
+ */
+
+static void xept_chld(int n, void *p)
+{
+ pid_t kid;
+ int st;
+
+ while ((kid = waitpid(-1, &st, WNOHANG)) > 0) {
+ xept *xe = xept_list;
+ while (xe) {
+ xept *xxe = xe;
+ xe = xe->next;
+ if (kid == xxe->kid) {
+ xxe->st = st;
+ xxe->e.f |= XEF_EXIT;
+ if (xxe->e.f & XEF_CLOSE)
+ xept_destroy(xxe);
+ break;
+ }
+ }
+ }
+}
+
+/* --- @xept_error@ --- *
+ *
+ * Arguments: @char *p@ = pointer to string read from stderr
+ * @void *v@ = pointer to by endpoint
+ *
+ * Returns: ---
+ *
+ * Use: Handles error reports from a child process.
+ */
+
+static void xept_error(char *p, void *v)
+{
+ xept *xe = v;
+ if (p)
+ fw_log(-1, "[%s] pid %i: %s", xe->desc, xe->kid, p);
+ else {
+ selbuf_disable(&xe->err);
+ close(xe->err.reader.fd);
+ xe->e.f |= XEF_CLOSE;
+ if (xe->e.f & XEF_EXIT)
+ xept_destroy(xe);
+ }
+}
+
+/* --- Endpoint operations --- */
+
+static endpt_ops xept_ops = { xept_attach, 0, xept_close };
+
+/*----- General operations on sources and targets -------------------------*/
+
+/* --- @exec_init@ --- *
+ *
+ * Arguments: ---
+ *
+ * Returns: ---
+ *
+ * Use: Initializes the executable problem source and target.
+ */
+
+void exec_init(void)
+{
+ rlimit_get(&exec_opts.xl);
+ sig_add(&xept_sig, SIGCHLD, xept_chld, 0);
+ sym_create(&env);
+ env_import(&env, environ);
+}
+
+/* --- @exec_option@ --- */
+
+static int exec_option(xdata *x, scanner *sc)
+{
+ xopts *xo = x ? x->xo : &exec_opts;
+
+ CONF_BEGIN(sc, "exec", "executable");
+
+ /* --- Logging settings --- */
+
+ if (strcmp(sc->d.buf, "logging") == 0 ||
+ strcmp(sc->d.buf, "log") == 0) {
+ token(sc);
+ if (sc->t == '=')
+ token(sc);
+ if (conf_enum(sc, "no,yes", ENUM_ABBREV, "logging status"))
+ xo->f &= ~XF_NOLOG;
+ else
+ xo->f |= XF_NOLOG;
+ CONF_ACCEPT;
+ }
+
+ /* --- Current directory setting --- *
+ *
+ * Lots of possibilities to guard against possible brainoes.
+ */
+
+ if (strcmp(sc->d.buf, "dir") == 0 ||
+ strcmp(sc->d.buf, "cd") == 0 ||
+ strcmp(sc->d.buf, "chdir") == 0 ||
+ strcmp(sc->d.buf, "cwd") == 0) {
+ dstr d = DSTR_INIT;
+ token(sc);
+ if (sc->t == '=')
+ token(sc);
+ conf_name(sc, '/', &d);
+ xo->dir = xstrdup(d.buf);
+ dstr_destroy(&d);
+ CONF_ACCEPT;
+ }
+
+ /* --- Set a chroot prison --- */
+
+ if (strcmp(sc->d.buf, "root") == 0 ||
+ strcmp(sc->d.buf, "chroot") == 0) {
+ dstr d = DSTR_INIT;
+ token(sc);
+ if (sc->t == '=')
+ token(sc);
+ conf_name(sc, '/', &d);
+ xo->root = xstrdup(d.buf);
+ dstr_destroy(&d);
+ CONF_ACCEPT;
+ }
+
+ /* --- Set the target user id --- */
+
+ if (strcmp(sc->d.buf, "uid") == 0 ||
+ strcmp(sc->d.buf, "user") == 0) {
+ token(sc);
+ if (sc->t == '=')
+ token(sc);
+ if (sc->t != CTOK_WORD)
+ error(sc, "parse error, expected user name or uid");
+ if (isdigit((unsigned char)*sc->d.buf))
+ xo->uid = atoi(sc->d.buf);
+ else {
+ struct passwd *pw = getpwnam(sc->d.buf);
+ if (!pw)
+ error(sc, "unknown user name `%s'", sc->d.buf);
+ xo->uid = pw->pw_uid;
+ }
+ token(sc);
+ CONF_ACCEPT;
+ }
+
+ /* --- Set the target group id --- */
+
+ if (strcmp(sc->d.buf, "gid") == 0 ||
+ strcmp(sc->d.buf, "group") == 0) {
+ token(sc);
+ if (sc->t == '=')
+ token(sc);
+ if (sc->t != CTOK_WORD)
+ error(sc, "parse error, expected group name or gid");
+ if (isdigit((unsigned char)*sc->d.buf))
+ xo->gid = atoi(sc->d.buf);
+ else {
+ struct group *gr = getgrnam(sc->d.buf);
+ if (!gr)
+ error(sc, "unknown user name `%s'", sc->d.buf);
+ xo->gid = gr->gr_gid;
+ }
+ token(sc);
+ CONF_ACCEPT;
+ }
+
+ /* --- Now try resource limit settings --- */
+
+ if (rlimit_option(&xo->xl, sc))
+ CONF_ACCEPT;
+
+ /* --- And then environment settings --- */
+
+ if (xenv_option(xo, sc))
+ CONF_ACCEPT;
+
+ /* --- Nothing found --- */
+
+ CONF_END;
+}
+
+/* --- @exec_desc@ --- */
+
+static void exec_desc(xdata *x, dstr *d)
+{
+ char **p;
+ char sep = '[';
+ dstr_puts(d, "exec ");
+ if (strcmp(x->xa->file, x->xa->argv[0]) != 0) {
+ dstr_puts(d, x->xa->file);
+ dstr_putc(d, ' ');
+ }
+ for (p = x->xa->argv; *p; p++) {
+ dstr_putc(d, sep);
+ dstr_puts(d, *p);
+ sep = ' ';
+ }
+ dstr_putc(d, ']');
+ dstr_putz(d);
+}
+
+/* --- @exec_read@ --- */
+
+static void exec_read(xdata *x, scanner *sc)
+{
+ size_t base = 0;
+ dstr d = DSTR_INIT;
+ xargs *xa;
+
+ /* --- Read the first word --- *
+ *
+ * This is either a shell command or the actual program to run.
+ */
+
+ if (sc->t == CTOK_WORD) {
+ dstr_putd(&d, &sc->d); d.len++;
+ base = d.len;
+ token(sc);
+ }
+
+ /* --- See if there's a list of arguments --- *
+ *
+ * If not, then the thing I saw was a shell command, so build the proper
+ * arguments for that.
+ */
+
+ if (sc->t != '[') {
+ char *p;
+ if (!base)
+ error(sc, "parse error, expected shell command or argument list");
+ xa = xmalloc(XARGS_SZ(3) + 8 + 3 + d.len);
+ p = (char *)(xa->argv + 4);
+ xa->ref = 1;
+ xa->file = p;
+ xa->argv[0] = p; memcpy(p, "/bin/sh", 8); p += 8;
+ xa->argv[1] = p; memcpy(p, "-c", 3); p += 3;
+ xa->argv[2] = p; memcpy(p, d.buf, d.len); p += d.len;
+ xa->argv[3] = 0;
+ }
+
+ /* --- Snarf in a list of arguments --- */
+
+ else {
+ int argc = 0;
+ char *p, *q;
+ char **v;
+
+ /* --- Strip off the leading `[' --- */
+
+ token(sc);
+
+ /* --- Read a sequence of arguments --- */
+
+ while (sc->t == CTOK_WORD) {
+ dstr_putd(&d, &sc->d); d.len++;
+ token(sc);
+ argc++;
+ }
+
+ /* --- Expect the closing `]' --- */
+
+ if (sc->t != ']')
+ error(sc, "parse error, missing `]'");
+ token(sc);
+
+ /* --- If there are no arguments, whinge --- */
+
+ if (!argc)
+ error(sc, "must specify at least one argument");
+
+ /* --- Allocate a lump of memory for the array --- */
+
+ xa = xmalloc(XARGS_SZ(argc) + d.len);
+ xa->ref = 1;
+ v = xa->argv;
+ p = (char *)(v + argc + 1);
+ memcpy(p, d.buf, d.len);
+ q = p + d.len;
+ xa->file = p;
+ p += base;
+
+ /* --- Start dumping addresses into the @argv@ array --- */
+
+ for (;;) {
+ *v++ = p;
+ while (*p++ && p < q)
+ ;
+ if (p >= q)
+ break;
+ }
+ *v++ = 0;
+ }
+
+ /* --- Do some other setting up --- */
+
+ dstr_destroy(&d);
+ x->xa = xa;
+ x->xo = CREATE(xopts);
+ *x->xo = exec_opts;
+ x->xo->ref = 1;
+ return;
+}
+
+/* --- @exec_endpt@ --- */
+
+static endpt *exec_endpt(xdata *x, const char *desc)
+{
+ xept *xe = CREATE(xept);
+ xe->e.ops = &xept_ops;
+ xe->e.other = 0;
+ xe->e.t = 0;
+ xe->e.f = 0;
+ xe->xa = x->xa; xe->xa->ref++;
+ xe->xo = x->xo; xe->xo->ref++;
+ xe->kid = -1;
+ xe->desc = desc;
+ return (&xe->e);
+}
+
+/* --- @exec_destroy@ --- */
+
+static void exec_destroy(xdata *x)
+{
+ x_tidy(x->xa, x->xo);
+}
+
+/*----- Source definition -------------------------------------------------*/
+
+/* --- @option@ --- */
+
+static int xsource_option(source *s, scanner *sc)
+{
+ xsource *xs = (xsource *)s;
+ return (exec_option(xs ? &xs->x : 0, sc));
+}
+
+/* --- @read@ --- */
+
+static source *xsource_read(scanner *sc)
+{
+ xsource *xs;
+
+ if (!conf_prefix(sc, "exec"))
+ return (0);
+ xs = CREATE(xsource);
+ xs->s.ops = &xsource_ops;
+ xs->s.desc = 0;
+ exec_read(&xs->x, sc);
+ return (&xs->s);
+}
+
+/* --- @attach@ --- */
+
+static void xsource_destroy(source */*s*/);
+
+static void xsource_attach(source *s, scanner *sc, target *t)
+{
+ xsource *xs = (xsource *)s;
+ endpt *e, *ee;
+
+ /* --- Set up the source description string --- */
+
+ {
+ dstr d = DSTR_INIT;
+ exec_desc(&xs->x, &d);
+ dstr_puts(&d, " -> ");
+ dstr_puts(&d, t->desc);
+ xs->s.desc = xstrdup(d.buf);
+ dstr_destroy(&d);
+ }
+
+ /* --- Create the endpoints --- */
+
+ if ((ee = t->ops->create(t, xs->s.desc)) == 0)
+ goto tidy;
+ if ((e = exec_endpt(&xs->x, xs->s.desc)) == 0) {
+ ee->ops->close(ee);
+ goto tidy;
+ }
+ endpt_join(e, ee);
+
+ /* --- Dispose of source and target --- */
+
+tidy:
+ t->ops->destroy(t);
+ xsource_destroy(&xs->s);
+}
+
+/* --- @destroy@ --- */
+
+static void xsource_destroy(source *s)
+{
+ xsource *xs = (xsource *)s;
+ exec_destroy(&xs->x);
+ DESTROY(xs);
+}
+
+/* --- Executable source operations --- */
+
+source_ops xsource_ops = {
+ "exec",
+ xsource_option, xsource_read, xsource_attach, xsource_destroy
+};
+
+/*----- Exec target description -------------------------------------------*/
+
+/* --- @option@ --- */
+
+static int xtarget_option(target *t, scanner *sc)
+{
+ xtarget *xt = (xtarget *)t;
+ return (exec_option(xt ? &xt->x : 0, sc));
+}
+
+/* --- @read@ --- */
+
+static target *xtarget_read(scanner *sc)
+{
+ xtarget *xt;
+ dstr d = DSTR_INIT;
+
+ if (!conf_prefix(sc, "exec"))
+ return (0);
+ xt = CREATE(xtarget);
+ xt->t.ops = &xtarget_ops;
+ exec_read(&xt->x, sc);
+ exec_desc(&xt->x, &d);
+ xt->t.desc = xstrdup(d.buf);
+ dstr_destroy(&d);
+ return (&xt->t);
+}
+
+/* --- @create@ --- */
+
+static endpt *xtarget_create(target *t, const char *desc)
+{
+ xtarget *xt = (xtarget *)t;
+ endpt *e = exec_endpt(&xt->x, desc);
+ return (e);
+}
+
+/* --- @destroy@ --- */
+
+static void xtarget_destroy(target *t)
+{
+ xtarget *xt = (xtarget *)t;
+ exec_destroy(&xt->x);
+ DESTROY(xt);
+}
+
+/* --- Exec target operations --- */
+
+target_ops xtarget_ops = {
+ "exec",
+ xtarget_option, xtarget_read, xtarget_create, xtarget_destroy
+};
+
+/*----- That's all, folks -------------------------------------------------*/