From 48bb1f76fd805b19aa32a374a8a9d8106ab7f420 Mon Sep 17 00:00:00 2001 From: mdw Date: Mon, 26 Jul 1999 23:33:32 +0000 Subject: [PATCH] New sources and targets. --- exec.c | 1152 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ exec.h | 78 +++++ file.c | 610 +++++++++++++++++++++++++++++++++ file.h | 65 ++++ socket.c | 698 +++++++++++++++++++++++++++++++++++++ socket.h | 65 ++++ 6 files changed, 2668 insertions(+) create mode 100644 exec.c create mode 100644 exec.h create mode 100644 file.c create mode 100644 file.h create mode 100644 socket.c create mode 100644 socket.h diff --git a/exec.c b/exec.c new file mode 100644 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef HAVE_SETRLIMIT +# include +#endif + +#ifndef DECL_ENVIRON + extern char **environ; +#endif + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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 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 +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 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 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 +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 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 -- 2.11.0