New sources and targets.
authormdw <mdw>
Mon, 26 Jul 1999 23:33:32 +0000 (23:33 +0000)
committermdw <mdw>
Mon, 26 Jul 1999 23:33:32 +0000 (23:33 +0000)
exec.c [new file with mode: 0644]
exec.h [new file with mode: 0644]
file.c [new file with mode: 0644]
file.h [new file with mode: 0644]
socket.c [new file with mode: 0644]
socket.h [new file with mode: 0644]

diff --git a/exec.c b/exec.c
new file mode 100644 (file)
index 0000000..1900017
--- /dev/null
+++ b/exec.c
@@ -0,0 +1,1152 @@
+/* -*-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 -------------------------------------------------*/
diff --git a/exec.h b/exec.h
new file mode 100644 (file)
index 0000000..7faeecf
--- /dev/null
+++ b/exec.h
@@ -0,0 +1,78 @@
+/* -*-c-*-
+ *
+ * $Id: exec.h,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.h,v $
+ * Revision 1.1  1999/07/26 23:33:32  mdw
+ * New sources and targets.
+ *
+ */
+
+#ifndef EXEC_H
+#define EXEC_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifndef SOURCE_H
+#  include "source.h"
+#endif
+
+#ifndef TARGET_H
+#  include "target.h"
+#endif
+
+/*----- Source and target -------------------------------------------------*/
+
+extern source_ops xsource_ops;
+extern target_ops xtarget_ops;
+
+/*----- Other functions ---------------------------------------------------*/
+
+/* --- @exec_init@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes the executable problem source and target.
+ */
+
+extern void exec_init(void);   
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/file.c b/file.c
new file mode 100644 (file)
index 0000000..300ac04
--- /dev/null
+++ b/file.c
@@ -0,0 +1,610 @@
+/* -*-c-*-
+ *
+ * $Id: file.c,v 1.1 1999/07/26 23:33:32 mdw Exp $
+ *
+ * File source and target
+ *
+ * (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: file.c,v $
+ * Revision 1.1  1999/07/26 23:33:32  mdw
+ * New sources and targets.
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "config.h"
+
+#include <ctype.h>
+#include <errno.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 <mLib/alloc.h>
+#include <mLib/dstr.h>
+#include <mLib/fdflags.h>
+#include <mLib/sub.h>
+
+#include "conf.h"
+#include "endpt.h"
+#include "fattr.h"
+#include "file.h"
+#include "fw.h"
+#include "reffd.h"
+#include "scan.h"
+#include "source.h"
+#include "target.h"
+
+/*----- Data structures ---------------------------------------------------*/
+
+/* --- File specification --- */
+
+typedef struct fspec {
+  unsigned type;
+  union {
+    reffd *fd;
+    char *name;
+  } u;
+} fspec;
+
+#define FTYPE_GUESS 0u
+#define FTYPE_FD 1u
+#define FTYPE_NAME 2u
+#define FTYPE_NULL 3u
+
+/* --- File options --- */
+
+typedef struct fopts {
+  unsigned of;
+} fopts;
+
+/* --- File data block --- */
+
+typedef struct fdata {
+  fspec in, out;
+  fattr fa;
+  fopts fo;
+} fdata;
+
+/* --- File source block --- */
+
+typedef struct fsource {
+  source s;
+  fdata f;
+} fsource;  
+
+/* --- File target block --- */
+
+typedef struct ftarget {
+  target t;
+  fdata f;
+} ftarget;
+
+/*----- Static variables --------------------------------------------------*/
+
+static fopts file_opts = { O_TRUNC };
+
+static reffd *rstdin, *rstdout;
+static reffd *rnull;
+
+/*----- File endpoint operations ------------------------------------------*/
+
+/* --- @wclose@ --- */
+
+static void fept_wclose(endpt *e)
+{
+  if (e->out) {
+    REFFD_DEC(e->out);
+    e->out = 0;
+  }
+}
+
+/* --- @close@ --- */
+
+static void fept_close(endpt *e)
+{
+  if (e->in)
+    REFFD_DEC(e->in);
+  if (e->out)
+    REFFD_DEC(e->out);
+  fw_dec();
+  DESTROY(e);
+}
+
+/* --- Endpoint operations table --- */
+
+static endpt_ops fept_ops = { 0, fept_wclose, fept_close };
+
+/*----- General operations on sources and targets -------------------------*/
+
+/* --- @file_null@ --- *
+ *
+ * Arguments:  @void *p@ = an uninteresting argument
+ *
+ * Returns:    ---
+ *
+ * Use:                Removes the reference to @rnull@ when the file closes.
+ */
+
+static void file_null(void *p)
+{
+  rnull = 0;
+}
+
+/* --- @file_nullref@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    A reference to a file descriptor open on /dev/null.
+ *
+ * Use:                Obtains a file descriptor for /dev/null.  The reference
+ *             @rnull@ is `weak', so that I only have a descriptor open when
+ *             I actually need one.
+ */
+
+static reffd *file_nullref(void)
+{
+  if (rnull)
+    REFFD_INC(rnull);
+  else {
+    int n;
+    if ((n = open("/dev/null", O_RDWR)) < 0) {
+      fw_log(-1, "couldn't open `/dev/null': %s", strerror(errno));
+      return (0);
+    }
+    fdflags(n, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
+    rnull = reffd_init(n);
+    reffd_handler(rnull, file_null, 0);
+  }
+  return (rnull);
+}  
+
+/* --- @file_fspec@ --- *
+ *
+ * Arguments:  @fspec *f@ = pointer to filespec to fill in
+ *             @scanner *sc@ = pointer to scanner to read from
+ *
+ * Returns:    ---
+ *
+ * Use:                Reads in a file spec.
+ */
+
+static void file_fspec(fspec *f, scanner *sc)
+{
+  int c = 0;
+  unsigned type = FTYPE_GUESS;
+  reffd *fd = 0;
+
+  /* --- Set up references to @stdin@ and @stdout@ --- */
+
+  if (!rstdin) {
+    fdflags(STDIN_FILENO, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
+    fdflags(STDOUT_FILENO, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
+    rstdin = reffd_init(STDIN_FILENO);
+    rstdout = reffd_init(STDOUT_FILENO);
+  }
+
+  /* --- Try to get an access type --- */
+
+  if (sc->t == ':') {
+    c = 1;
+    token(sc);
+  }
+
+  if (sc->t == CTOK_WORD) {
+    int i = conf_enum(sc, "fd,name,null", c ? ENUM_ABBREV : ENUM_NONE,
+                     "file access type");
+    type = i + 1;
+    if (type && sc->t == ':')
+      token(sc);
+  }
+
+  /* --- Check for a valid file descriptor name --- */
+
+  if (sc->t == CTOK_WORD && type != FTYPE_NAME && type != FTYPE_NULL) {
+    if (strcmp(sc->d.buf, "stdin") == 0) {
+      fd = rstdin;
+      REFFD_INC(rstdin);
+    } else if (strcmp(sc->d.buf, "stdout") == 0) {
+      fd = rstdout;
+      REFFD_INC(rstdout);
+    }
+    if (fd)
+      REFFD_INC(fd);
+    else if (isdigit((unsigned char)*sc->d.buf)) {
+      int nfd = atoi(sc->d.buf);
+      switch (nfd) {
+       case STDIN_FILENO: fd = rstdin; REFFD_INC(rstdin); break;
+       case STDOUT_FILENO: fd = rstdout; REFFD_INC(rstdout); break;
+       default:
+         fd = reffd_init(nfd);
+         fdflags(nfd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
+         break;
+      }
+    }
+  }
+
+  /* --- Sort out what to do as a result --- */
+
+  if (type == FTYPE_NULL)
+    f->type = FTYPE_NULL;
+  else if (fd) {
+    f->type = FTYPE_FD;
+    f->u.fd = fd;
+    token(sc);  
+  } else if (type == FTYPE_FD) {
+    if (sc->t == CTOK_WORD)
+      error(sc, "bad file descriptor `%s'", sc->d.buf);
+    else
+      error(sc, "parse error, expected file descriptor");
+  } else {
+    dstr d = DSTR_INIT;
+    conf_name(sc, '/', &d);
+    f->type = FTYPE_NAME;
+    f->u.name = xstrdup(d.buf);
+    dstr_destroy(&d);
+  }
+}
+
+/* --- @read@ --- */
+
+static void file_read(fdata *f, scanner *sc)
+{
+  file_fspec(&f->in, sc);
+  if (sc->t != ',') {
+    f->out = f->in;
+    if (f->out.type == FTYPE_FD && f->out.u.fd == rstdin)
+      f->out.u.fd = rstdout;
+  } else {
+    token(sc);
+    file_fspec(&f->out, sc);
+  }
+  f->fa = fattr_global;
+  f->fo = file_opts;
+}
+
+/* --- @print@ --- */
+
+static void file_pfspec(fspec *f, dstr *d)
+{
+  switch (f->type) {
+    case FTYPE_FD:
+      switch (f->u.fd->fd) {
+       case STDIN_FILENO:
+         dstr_puts(d, "stdin");
+         break;
+       case STDOUT_FILENO:
+         dstr_puts(d, "stdout");
+         break;
+       default:
+         dstr_putf(d, "fd:%i", f->u.fd->fd);
+         break;
+      }
+      break;
+    case FTYPE_NAME:
+      dstr_putf(d, "name:%s", f->u.name);
+      break;
+    case FTYPE_NULL:
+      dstr_puts(d, "null");
+      break;
+  }      
+}
+
+static void file_desc(fdata *f, dstr *d)
+{
+  dstr_puts(d, "file ");
+  file_pfspec(&f->in, d);
+  dstr_puts(d, ", ");
+  file_pfspec(&f->out, d);
+}
+
+/* --- @option@ --- */
+
+static int file_option(fdata *f, scanner *sc)
+{
+  fopts *fo = f ? &f->fo : &file_opts;
+
+  CONF_BEGIN(sc, "file", "file")
+
+  /* --- Handle file-specific options --- */
+
+  if (strcmp(sc->d.buf, "create") == 0) {
+    token(sc);
+    if (sc->t == '=')
+      token(sc);
+    switch (conf_enum(sc, "no,yes", ENUM_ABBREV, "create status")) {
+      case 0: fo->of &= ~O_CREAT; break;
+      case 1: fo->of |= O_CREAT; break;
+    }
+    CONF_ACCEPT;
+  }
+
+  else if (strcmp(sc->d.buf, "open") == 0) {
+    token(sc);
+    if (sc->t == '=')
+      token(sc);
+    fo->of &= ~(O_TRUNC | O_APPEND | O_EXCL);
+    switch (conf_enum(sc, "no,truncate,append",
+                     ENUM_ABBREV, "open status")) {
+      case 0: fo->of |= O_EXCL; break;
+      case 1: fo->of |= O_TRUNC; break;
+      case 2: fo->of |= O_APPEND; break;
+    }
+    CONF_ACCEPT;
+  }
+
+  /* --- See if it's a file attribute --- */
+
+  {
+    fattr *fa = f ? &f->fa : &fattr_global;
+    if (fattr_option(sc, fa))
+      CONF_ACCEPT;
+  }
+
+  /* --- Nobody understood it --- */
+
+  CONF_END;
+}
+
+/* --- @endpt@ --- */
+
+static endpt *file_endpt(fdata *f, const char *desc)
+{
+  reffd *in = 0, *out = 0;
+  endpt *e;
+
+  /* --- Make the input file first --- */
+
+  switch (f->in.type) {
+    case FTYPE_NULL:
+      in = file_nullref();
+      break;
+    case FTYPE_FD:
+      in = f->in.u.fd;
+      REFFD_INC(in);
+      break;
+    case FTYPE_NAME: {
+      int fd;
+      if ((fd = open(f->in.u.name, O_RDONLY | O_NONBLOCK)) < 0) {
+       fw_log(-1, "[%s] couldn't open `%s' for reading: %s",
+              desc, f->in.u.name, strerror(errno));
+       return (0);
+      }
+      in = reffd_init(fd);
+    } break;
+  }
+
+  /* --- Make the output file second --- */
+
+  switch (f->out.type) {
+    case FTYPE_NULL:
+      out = file_nullref();
+      break;
+    case FTYPE_FD:
+      out = f->out.u.fd;
+      REFFD_INC(out);
+      break;
+    case FTYPE_NAME: {
+      int m = f->fo.of | O_WRONLY | O_NONBLOCK;
+      int fd = -1;
+
+      /* --- First try creating the file --- *
+       *
+       * This lets me know whether to set the file attributes or not.  It
+       * also stands a chance of noticing people playing silly beggars with
+       * dangling symlinks.
+       */
+
+      if ((m & O_CREAT) &&
+         (fd = open(f->out.u.name, m | O_EXCL, f->fa.mode)) < 0 &&
+         (errno != EEXIST || (m & O_EXCL))) {
+       fw_log(-1, "[%s] couldn't create `%s': %s",
+              desc, f->out.u.name, strerror(errno));
+       REFFD_DEC(in);
+       return (0);
+      }
+
+      if (fd != -1) {
+       if (fattr_apply(f->out.u.name, &f->fa)) {
+         fw_log(-1, "[%s] couldn't apply file attributes: %s",
+                desc, f->out.u.name, strerror(errno));
+       }
+      } else if ((fd = open(f->out.u.name, m & ~O_CREAT)) < 0) {
+       fw_log(-1, "[%s] couldn't open `%s': %s",
+              desc, f->out.u.name, strerror(errno));
+       REFFD_DEC(in);
+       return (0);
+      }
+
+      fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
+      out = reffd_init(fd);
+    } break;
+  }
+
+  /* --- Set up the endpoint --- */
+
+  e = CREATE(endpt);
+  e->ops = &fept_ops;
+  e->other = 0;
+  e->f = EPF_FILE;
+  e->t = 0;
+  e->in = in;
+  e->out = out;
+  fw_inc();
+  return (e);
+}
+
+/* --- @destroy@ --- */
+
+static void file_destroy(fdata *f)
+{
+  if (f->in.type == FTYPE_NAME)
+    free(f->in.u.name);
+  if (f->out.type == FTYPE_NAME)
+    free(f->out.u.name);
+}
+
+/*----- File source description -------------------------------------------*/
+
+/* --- @option@ --- */
+
+static int fsource_option(source *s, scanner *sc)
+{
+  fsource *fs = (fsource *)s;
+  return (file_option(fs ? &fs->f : 0, sc));
+}
+
+/* --- @read@ --- */
+
+static source *fsource_read(scanner *sc)
+{
+  fsource *fs;
+
+  if (!conf_prefix(sc, "file"))
+    return (0);
+  fs = CREATE(fsource);
+  fs->s.ops = &fsource_ops;
+  fs->s.desc = 0;
+  file_read(&fs->f, sc);
+  return (&fs->s);
+}
+
+/* --- @attach@ --- */
+
+static void fsource_destroy(source */*s*/);
+
+static void fsource_attach(source *s, scanner *sc, target *t)
+{
+  fsource *fs = (fsource *)s;
+  endpt *e, *ee;
+
+  /* --- Set up the source description string --- */
+
+  {
+    dstr d = DSTR_INIT;
+    file_desc(&fs->f, &d);
+    dstr_puts(&d, " -> ");
+    dstr_puts(&d, t->desc);
+    fs->s.desc = xstrdup(d.buf);
+    dstr_destroy(&d);
+  }
+
+  /* --- And then create the endpoints --- */
+
+  if ((ee = t->ops->create(t, fs->s.desc)) == 0)
+    goto tidy;
+  if ((e = file_endpt(&fs->f, fs->s.desc)) == 0) {
+    ee->ops->close(ee);
+    goto tidy;
+  }
+  endpt_join(e, ee);
+
+  /* --- Dispose of the source and target now --- */
+
+tidy:
+  t->ops->destroy(t);
+  fsource_destroy(&fs->s);
+}
+
+/* --- @destroy@ --- */
+
+static void fsource_destroy(source *s)
+{
+  fsource *fs = (fsource *)s;
+
+  /* free(fs->s.desc); */
+  file_destroy(&fs->f);
+  DESTROY(fs);
+}
+
+/* --- File source operations --- */
+
+source_ops fsource_ops = {
+  "file",
+  fsource_option, fsource_read, fsource_attach, fsource_destroy
+};
+
+/*----- File target description -------------------------------------------*/
+
+/* --- @option@ --- */
+
+static int ftarget_option(target *t, scanner *sc)
+{
+  ftarget *ft = (ftarget *)t;
+  return (file_option(ft ? &ft->f : 0, sc));
+}
+
+/* --- @read@ --- */
+
+static target *ftarget_read(scanner *sc)
+{
+  ftarget *ft;
+  dstr d = DSTR_INIT;
+
+  if (!conf_prefix(sc, "file"))
+    return (0);
+  ft = CREATE(ftarget);
+  ft->t.ops = &ftarget_ops;
+  file_read(&ft->f, sc);
+  file_desc(&ft->f, &d);
+  ft->t.desc = xstrdup(d.buf);
+  dstr_destroy(&d);
+  return (&ft->t);
+}
+
+/* --- @create@ --- */
+
+static endpt *ftarget_create(target *t, const char *desc)
+{
+  ftarget *ft = (ftarget *)t;
+  endpt *e = file_endpt(&ft->f, desc);
+  return (e);
+}
+
+/* --- @destroy@ --- */
+
+static void ftarget_destroy(target *t)
+{
+  ftarget *ft = (ftarget *)t;
+  file_destroy(&ft->f);
+  /* free(ft->t.desc); */
+  DESTROY(ft);
+}
+
+/* --- File target operations --- */
+
+target_ops ftarget_ops = {
+  "file",
+  ftarget_option, ftarget_read, ftarget_create, ftarget_destroy
+};
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/file.h b/file.h
new file mode 100644 (file)
index 0000000..6b0e53d
--- /dev/null
+++ b/file.h
@@ -0,0 +1,65 @@
+/* -*-c-*-
+ *
+ * $Id: file.h,v 1.1 1999/07/26 23:33:32 mdw Exp $
+ *
+ * File source and target
+ *
+ * (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: file.h,v $
+ * Revision 1.1  1999/07/26 23:33:32  mdw
+ * New sources and targets.
+ *
+ */
+
+#ifndef FILE_H
+#define FILE_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifndef SOURCE_H
+#  include "source.h"
+#endif
+
+#ifndef TARGET_H
+#  include "target.h"
+#endif
+
+/*----- Sources and targets -----------------------------------------------*/
+
+extern source_ops fsource_ops;
+extern target_ops ftarget_ops;
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/socket.c b/socket.c
new file mode 100644 (file)
index 0000000..941bcd5
--- /dev/null
+++ b/socket.c
@@ -0,0 +1,698 @@
+/* -*-c-*-
+ *
+ * $Id: socket.c,v 1.1 1999/07/26 23:33:32 mdw Exp $
+ *
+ * Socket source and target definitions
+ *
+ * (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: socket.c,v $
+ * Revision 1.1  1999/07/26 23:33:32  mdw
+ * New sources and targets.
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "config.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <mLib/alloc.h>
+#include <mLib/conn.h>
+#include <mLib/dstr.h>
+#include <mLib/fdflags.h>
+#include <mLib/sel.h>
+#include <mLib/sub.h>
+
+#include "addr.h"
+#include "conf.h"
+#include "endpt.h"
+#include "fw.h"
+#include "scan.h"
+#include "socket.h"
+#include "target.h"
+
+#include "inet.h"
+#include "un.h"
+
+/*----- Data structures ---------------------------------------------------*/
+
+/* --- Socket source options --- */
+
+typedef struct ssource_opts {
+  unsigned conn;
+} ssource_opts;
+
+static ssource_opts ssgo = { 256 };
+
+/* --- Socket source --- */
+
+typedef struct ssource {
+  source s;
+  addr *a;
+  target *t;
+  addr_opts *ao;
+  ssource_opts o;
+  sel_file r;
+} ssource;
+
+/* --- Socket target --- */
+
+typedef struct starget {
+  target t;
+  addr *a;
+  addr_opts *ao;
+} starget;
+
+/* --- Socket target endpoint --- */
+
+typedef struct stept {
+  endpt e;
+  conn c;
+  const char *desc;
+} stept;
+
+/* --- Socket source endpoint --- */
+
+typedef struct ssept {
+  endpt e;
+  ssource *s;
+} ssept;
+
+#define SKF_CONN 16u
+#define SKF_BROKEN 32u
+
+/*----- Protocol table ----------------------------------------------------*/
+
+static addr_ops *addrs[] = { &inet_ops, &un_ops, 0 };
+
+/*----- Other persistent variables ----------------------------------------*/
+
+static addr_opts gao = { 0 };
+
+/*----- Parsing address types ---------------------------------------------*/
+
+/* --- @getaddrtype@ --- *
+ *
+ * Arguments:  @scanner *sc@ = pointer to scanner (for error reporting)
+ *             @const char *p@ = pointer to protocol name
+ *             @int abbrev@ = nonzero to allow abbreviations
+ *
+ * Returns:    Pointer to address operations table or null.
+ *
+ * Use:                Looks up a protocol name.  Handy when parsing addresses and
+ *             other bits of configuration.  Returns null if no matching
+ *             address was found.
+ */
+
+static addr_ops *getaddrtype(scanner *sc, const char *p, int abbrev)
+{
+  addr_ops **ops;
+  addr_ops *chosen = 0;
+  size_t sz = strlen(p);
+
+  for (ops = addrs; *ops; ops++) {
+    if (strncmp((*ops)->name, p, sz) == 0) {
+      if ((*ops)->name[sz] == 0)
+       return (*ops);
+      else if (chosen && abbrev)
+       error(sc, "ambiguous socket address type `%s'", p);
+      chosen = *ops;
+    }
+  }
+  if (!abbrev)
+    return (0);
+  return (chosen);
+}
+
+/* --- @getaddr@ --- *
+ *
+ * Arguments:  @scanner *sc@ = pointer to scanner to read from
+ *             @unsigned type@ = address type (@ADDR_SRC@ or @ADDR_DEST@)
+ *
+ * Returns:    Pointer to an address successfully read.
+ *
+ * Use:                Reads an optionally qualified address.
+ */
+
+static addr *getaddr(scanner *sc, unsigned type)
+{
+  addr_ops *ops = 0;
+  int abbrev = 0;
+
+  if (sc->t == ':') {
+    token(sc);
+    abbrev = 1;
+  }
+  if (sc->t == CTOK_WORD)
+    ops = getaddrtype(sc, sc->d.buf, abbrev);
+  if (ops)
+    token(sc);
+  else if (abbrev)
+    error(sc, "unknown socket address type `%s'", sc->d.buf);
+  else
+    ops = &inet_ops;
+  if (sc->t == ':')
+    token(sc);
+
+  return (ops->read(sc, type));
+}
+
+/*----- Socket endpoints --------------------------------------------------*/
+
+/* --- @wclose@ --- */
+
+static void sept_wclose(endpt *e)
+{
+  shutdown(e->out->fd, 1);
+}
+
+/* --- @close@ (source) --- */
+
+static void ss_listen(ssource */*ss*/);
+
+static void ssept_close(endpt *e)
+{
+  ssept *ee = (ssept *)e;
+
+  if (!ee->s->o.conn) {
+    ee->s->o.conn++;
+    ss_listen(ee->s);
+  } else
+    ee->s->o.conn++;
+  REFFD_DEC(ee->e.in);
+  REFFD_DEC(ee->e.out);
+  fw_dec();
+  DESTROY(ee);
+}
+
+/* --- @close@ (target) --- */
+
+static void stept_close(endpt *e)
+{
+  stept *ee = (stept *)e;
+
+  if (ee->e.f & EPF_PENDING) {
+    if (ee->e.f & SKF_CONN)
+      conn_kill(&ee->c);
+  } else {
+    REFFD_DEC(ee->e.in);
+    REFFD_DEC(ee->e.out);
+  }
+
+  fw_dec();
+  DESTROY(ee);
+}
+
+/* --- @stept_go@ --- *
+ *
+ * Arguments:  @int fd@ = file descriptor now ready for use
+ *             @void *p@ = pointer to an endpoint structure
+ *
+ * Returns:    ---
+ *
+ * Use:                Handles successful connection of the target endpoint.
+ */
+
+static void stept_go(int fd, void *p)
+{
+  stept *e = p;
+
+  /* --- Complicated and subtle --- *
+   *
+   * This code interacts quite closely with @starget_create@, mainly through
+   * flags in the endpoint block.
+   *
+   * If the connection failed, I log a message (that's easy enough).  The
+   * behaviour then depends on whether the endpoints have been joined yet.
+   * If not, I set @SKF_BROKEN@ and return, so that @starget_create@ can
+   * clean up the mess and return an immediate failure.  If they have, I kill
+   * the connection and everything ought to work.
+   *
+   * If the connection worked, I clear @EPF_PENDING@ (as expected, because
+   * my endpoint is now ready), and @SKF_CONN@ (to let @starget_create@ know
+   * that the connection is already going).  Then, only if this isn't the
+   * first attempt, I rejoin this endpoint to its partner.
+   */
+
+  if (fd == -1) {
+    fw_log(-1, "[%s] connection failed: %s", e->desc, strerror(errno));
+    e->e.f &= ~SKF_CONN;
+    if (e->e.f & EPF_PENDING)
+      endpt_kill(&e->e);
+    else
+      e->e.f |= SKF_BROKEN;
+  } else {
+    reffd *r = reffd_init(fd);
+    REFFD_INC(r);
+    e->e.in = e->e.out = r;
+    e->e.f &= ~(EPF_PENDING | SKF_CONN);
+    if (e->e.other)
+      endpt_join(&e->e, e->e.other);
+  }
+}
+
+/* --- Socket endpoint definition --- */
+
+static endpt_ops ssept_ops = {
+  0, sept_wclose, ssept_close
+};
+
+static endpt_ops stept_ops = {
+  0, sept_wclose, stept_close
+};
+
+/*----- Source definition -------------------------------------------------*/
+
+/* --- @option@ --- */
+
+static int ssource_option(source *s, scanner *sc)
+{
+  ssource *ss = (ssource *)s;
+  ssource_opts *sso = ss ? &ss->o : &ssgo;
+
+  CONF_BEGIN(sc, "socket", "socket")
+
+  /* --- Make sure the next token is a word --- */
+
+  if (sc->t != CTOK_WORD)
+    error(sc, "parse error, option keyword expected");
+
+  /* --- Handle options at this level --- */
+
+  if (strcmp(sc->d.buf, "conn") == 0) {
+    token(sc);
+    if (sc->t == '=')
+      token(sc);
+    if (sc->t != CTOK_WORD || !isdigit((unsigned char)sc->d.buf[0]))
+      error(sc, "parse error, argument of `conn' must be a number");
+    sso->conn = atoi(sc->d.buf);
+    if (sso->conn == 0)
+      error(sc, "argument of `conn' must be positive");
+    token(sc);
+    CONF_ACCEPT;
+  }
+
+  if (strcmp(sc->d.buf, "logging") == 0 ||
+      strcmp(sc->d.buf, "log") == 0) {
+    addr_opts *ao = ss ? ss->ao : &gao;
+    token(sc);
+    if (sc->t == '=')
+      token(sc);
+    if (conf_enum(sc, "no,yes", ENUM_ABBREV, "logging status"))
+      ao->f &= ~ADDRF_NOLOG;
+    else
+      ao->f |= ADDRF_NOLOG;
+    CONF_ACCEPT;
+  }
+
+  /* --- Pass the option around the various address types --- */
+
+  if (ss) {
+    if (ss->a->ops->option && ss->a->ops->option(sc, ss ? ss->ao : 0))
+      CONF_ACCEPT;
+  } else {
+    addr_ops **a;
+    for (a = addrs; *a; a++) {
+      if ((*a)->option && (*a)->option(sc, 0))
+       CONF_ACCEPT;
+    }
+  }
+
+  /* --- Nobody understood the option --- */
+
+  CONF_END;
+}
+
+/* --- @read@ --- */
+
+static source *ssource_read(scanner *sc)
+{
+  ssource *ss;
+
+  (void)(conf_prefix(sc, "socket") || conf_prefix(sc, "sk"));
+  ss = CREATE(ssource);
+  ss->s.ops = &ssource_ops;
+  ss->s.desc = 0;
+  ss->t = 0;
+  ss->a = getaddr(sc, ADDR_SRC);
+  if (ss->a->ops->initopts)
+    ss->ao = ss->a->ops->initopts();
+  else
+    ss->ao = CREATE(addr_opts);
+  *ss->ao = gao;
+  ss->o = ssgo;
+  return (&ss->s);
+}
+
+/* --- @ss_accept@ --- *
+ *
+ * Arguments:  @int fd@ = file descriptor to accept from
+ *             @unsigned mode@ = what's ready with the descriptor
+ *             @void *p@ = pointer to the source definition
+ *
+ * Returns:    ---
+ *
+ * Use:                Accepts an incoming connection and attaches it to a target
+ *             endpoint.
+ */
+
+static void ss_accept(int fd, unsigned mode, void *p)
+{
+  ssource *ss = p;
+  ssept *e;
+  endpt *ee;
+  reffd *r;
+
+  /* --- Make the file descriptor --- */
+
+  {
+    int opt = 1;
+    if ((r = ss->a->ops->accept(fd, ss->ao, ss->s.desc)) == 0)
+      return;
+    setsockopt(r->fd, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(opt));
+    fdflags(r->fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
+  }
+
+  /* --- Make an endpoint --- */
+
+  e = CREATE(ssept);
+  e->e.ops = &ssept_ops;
+  e->e.other = 0;
+  e->e.f = EPF_FILE;
+  e->e.t = 0;
+  e->e.in = e->e.out = r;
+  e->s = ss;
+  REFFD_INC(r);
+
+  /* --- Obtain the target endpoint and let rip --- */
+
+  if ((ee = ss->t->ops->create(ss->t, ss->s.desc)) == 0) {
+    REFFD_DEC(r);
+    REFFD_DEC(r);
+    DESTROY(e);
+    return;
+  }
+
+  /* --- Remove the listening socket if necessary --- */
+
+  ss->o.conn--;
+  if (!ss->o.conn) {
+    fw_log(-1, "[%s] maximum connections reached", ss->s.desc);
+    sel_rmfile(&ss->r);
+    close(ss->r.fd);
+    if (ss->a->ops->unbind)
+      ss->a->ops->unbind(ss->a);
+  }
+
+  /* --- Let everything else happen --- */
+
+  fw_inc();
+  endpt_join(&e->e, ee);
+}
+
+/* --- @ss_listen@ --- *
+ *
+ * Arguments:  @ssource *ss@ = source to listen on
+ *
+ * Returns:    ---
+ *
+ * Use:                Sets the socket to listen again, if it stopped for some
+ *             reason.  This is a copy of the code in the @read@ function,
+ *             because it has different (wildly different) error handling
+ *             behaviour.
+ */
+
+static void ssource_destroy(source */*s*/);
+
+static void ss_listen(ssource *ss)
+{
+  gen_addr *ga = (gen_addr *)ss->a;
+  int fd;
+
+  fw_log(-1, "[%s] reattaching listener", ss->s.desc);
+
+  /* --- Make the socket --- */
+
+  if ((fd = socket(ga->a.ops->pf, SOCK_STREAM, 0)) < 0) {
+    fw_log(-1, "[%s] couldn't create socket: %s",
+          ss->s.desc, strerror(errno));
+    goto fail_0;
+  }
+
+  /* --- Set it to allow address reuse --- */
+
+  {
+    int opt = 1;
+    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+    fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
+  }
+
+  /* --- Bind it to the right port --- */
+
+  if (bind(fd, &ga->sa, ga->a.sz)) {
+    fw_log(-1, "[%s] couldn't bind to %s: %s", ss->s.desc, strerror(errno));
+    goto fail_1;
+  }
+  if (ga->a.ops->bound)
+    ga->a.ops->bound(&ga->a, ss->ao);
+
+  /* --- Set it to listen for connections --- */
+
+  if (listen(fd, 5)) {
+    fw_log(-1, "[%s] couldn't listen on socket: %s",
+          ss->s.desc, strerror(errno));
+    goto fail_1;
+  }
+
+  /* --- Set the listener up again --- */
+
+  ss->r.fd = fd;
+  sel_addfile(&ss->r);
+  return;
+
+  /* --- Tidy up if it failed --- *
+   *
+   * I'll just remove the entire source.
+   */
+
+fail_1:
+  close(fd);
+fail_0:
+  ss->o.conn = 0;
+  ssource_destroy(&ss->s);
+}
+
+/* --- @attach@ --- */
+
+static void ssource_attach(source *s, scanner *sc, target *t)
+{
+  ssource *ss = (ssource *)s;
+  int fd;
+
+  ss->t = t;
+
+  /* --- Initialize the description string --- */
+
+  {
+    dstr d = DSTR_INIT;
+    dstr_puts(&d, "socket.");
+    ss->a->ops->print(ss->a, ADDR_SRC, &d);
+    dstr_puts(&d, " -> ");
+    dstr_puts(&d, ss->t->desc);
+    ss->s.desc = xstrdup(d.buf);
+    dstr_destroy(&d);
+  }
+
+  /* --- Initialize the socket for listening --- */
+
+  {
+    gen_addr *ga = (gen_addr *)ss->a;
+
+    /* --- Make the socket --- */
+
+    if ((fd = socket(ga->a.ops->pf, SOCK_STREAM, 0)) < 0)
+      error(sc, "couldn't create socket: %s", strerror(errno));
+
+    /* --- Set it to allow address reuse --- */
+
+    {
+      int opt = 1;
+      setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+      fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
+    }
+
+    /* --- Bind it to the right port --- */
+
+    if (bind(fd, &ga->sa, ga->a.sz))
+      error(sc, "couldn't bind to %s: %s", ss->s.desc, strerror(errno));
+    if (ga->a.ops->bound)
+      ga->a.ops->bound(&ga->a, ss->ao);
+
+    /* --- Set it to listen for connections --- */
+
+    if (listen(fd, 5))
+      error(sc, "couldn't listen on socket: %s", strerror(errno));
+  }
+
+  /* --- We're ready to go now --- */
+
+  sel_initfile(sel, &ss->r, fd, SEL_READ, ss_accept, ss);
+  sel_addfile(&ss->r);
+  source_add(&ss->s);
+  fw_inc();
+}
+
+/* --- @destroy@ --- */
+
+static void ssource_destroy(source *s)
+{
+  ssource *ss = (ssource *)s;
+
+  if (ss->o.conn) {
+    sel_rmfile(&ss->r);
+    close(ss->r.fd);
+    if (ss->a->ops->unbind)
+      ss->a->ops->unbind(ss->a);
+  }
+  if (ss->a->ops->freeopts)
+    ss->a->ops->freeopts(ss->ao);
+  else
+    DESTROY(ss->ao);
+  /* free(ss->s.desc); */
+  ss->a->ops->destroy(ss->a);
+  ss->t->ops->destroy(ss->t);
+  source_remove(&ss->s);
+  DESTROY(ss);
+  fw_dec();
+}
+
+/* --- Source definition block --- */
+
+source_ops ssource_ops = {
+  "socket",
+  ssource_option, ssource_read, ssource_attach, ssource_destroy
+};
+
+/*----- Target definition -------------------------------------------------*/
+
+/* --- @read@ --- */
+
+static target *starget_read(scanner *sc)
+{
+  starget *st;
+  dstr d = DSTR_INIT;
+
+  (void)(conf_prefix(sc, "socket") || conf_prefix(sc, "sk"));
+  st = CREATE(starget);
+  st->t.ops = &starget_ops;
+  st->a = getaddr(sc, ADDR_DEST);
+  dstr_puts(&d, "socket.");
+  st->a->ops->print(st->a, ADDR_DEST, &d);
+  st->t.desc = xstrdup(d.buf);
+  dstr_destroy(&d);
+  return (&st->t);
+}
+
+/* --- @create@ --- *
+ *
+ * Arguments:  @target *t@ = pointer to target
+ *             @const char *desc@ = description of connection
+ *
+ * Returns:    Pointer to a created endpoint.
+ *
+ * Use:                Generates a target endpoint for communication.
+ */
+
+static endpt *starget_create(target *t, const char *desc)
+{
+  starget *st = (starget *)t;
+  stept *e = CREATE(stept);
+  int fd;
+  gen_addr *ga = (gen_addr *)st->a;
+  int opt;
+
+  if ((fd = socket(st->a->ops->pf, SOCK_STREAM, 0)) < 0)
+    return (0);
+  setsockopt(fd, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(opt));
+  fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
+  e->e.ops = &stept_ops;
+  e->e.other = 0;
+  e->e.f = EPF_FILE | SKF_CONN;
+  e->e.t = 0;
+  e->desc = desc;
+
+  /* --- Pay attention --- *
+   *
+   * This bit is quite subtle.  The connect can succeed or fail later: that's
+   * fine.  The problem comes if it makes its mind up right now.  The flag
+   * @SKF_CONN@ signifies that I'm trying to connect.  I set it up to begin
+   * with and @stept_go@ turns it off when it's done: @stept_close@ uses it
+   * to decide whether to kill the connection.  The flag @EPF_PENDING@ is
+   * only set after @conn_init@ returns and @SKF_CONN@ is still set (meaning
+   * that the connection is still in progress).  That's used to let
+   * @stept_go@ know whether to kill the other endpoint.  The flag
+   * @SKF_BROKEN@ is used to signify an immediate failure.
+   */
+
+  conn_init(&e->c, sel, fd, &ga->sa, ga->a.sz, stept_go, e);
+  if (e->e.f & SKF_BROKEN) {
+    DESTROY(e);
+    return (0);
+  }
+  if (e->e.f & SKF_CONN)
+    e->e.f |= EPF_PENDING;
+  fw_inc();
+  return (&e->e);
+}
+
+/* --- @destroy@ --- */
+
+static void starget_destroy(target *t)
+{
+  starget *st = (starget *)t;
+  st->a->ops->destroy(st->a);
+  /* free(st->t.desc); */
+  DESTROY(st);
+}
+
+/* --- Socket target definition block --- */
+
+target_ops starget_ops = {
+  "socket",
+  0, starget_read, starget_create, starget_destroy
+};
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/socket.h b/socket.h
new file mode 100644 (file)
index 0000000..b64bd5e
--- /dev/null
+++ b/socket.h
@@ -0,0 +1,65 @@
+/* -*-c-*-
+ *
+ * $Id: socket.h,v 1.1 1999/07/26 23:33:32 mdw Exp $
+ *
+ * Socket source and target definitions
+ *
+ * (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: socket.h,v $
+ * Revision 1.1  1999/07/26 23:33:32  mdw
+ * New sources and targets.
+ *
+ */
+
+#ifndef SOCKET_H
+#define SOCKET_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifndef SOURCE_H
+#  include "source.h"
+#endif
+
+#ifndef TARGET_H
+#  include "target.h"
+#endif
+
+/*----- Sources and targets -----------------------------------------------*/
+
+extern source_ops ssource_ops;
+extern target_ops starget_ops;
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif