+++ /dev/null
-/* -*-c-*-
- *
- * Port forwarding thingy
- *
- * (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.
- */
-
-#include "fw.h"
-
-/*----- Global variables --------------------------------------------------*/
-
-sel_state *sel; /* Multiplexor for nonblocking I/O */
-
-/*----- Static variables --------------------------------------------------*/
-
-typedef struct conffile {
- struct conffile *next;
- char *name;
-} conffile;
-
-static unsigned flags = 0; /* Global state flags */
-static unsigned active = 0; /* Number of active things */
-static conffile *conffiles = 0; /* List of configuration files */
-
-#define FW_SYSLOG 1u
-#define FW_QUIET 2u
-#define FW_SET 4u
-
-/*----- Configuration parsing ---------------------------------------------*/
-
-/* --- @parse@ --- *
- *
- * Arguments: @scanner *sc@ = pointer to scanner definition
- *
- * Returns: ---
- *
- * Use: Parses a configuration file from the scanner.
- */
-
-static source_ops *sources[] =
- { &xsource_ops, &fsource_ops, &ssource_ops, 0 };
-static target_ops *targets[] =
- { &xtarget_ops, &ftarget_ops, &starget_ops, 0 };
-
-void parse(scanner *sc)
-{
- token(sc);
-
- for (;;) {
- if (sc->t == CTOK_EOF)
- break;
- if (sc->t != CTOK_WORD)
- error(sc, "parse error, keyword expected");
-
- /* --- Handle a forwarding request --- */
-
- if (strcmp(sc->d.buf, "forward") == 0 ||
- strcmp(sc->d.buf, "fw") == 0 ||
- strcmp(sc->d.buf, "from") == 0) {
- source *s;
- target *t;
-
- token(sc);
-
- /* --- Read a source description --- */
-
- {
- source_ops **sops;
-
- /* --- Try to find a source type which understands --- */
-
- s = 0;
- for (sops = sources; *sops; sops++) {
- if ((s = (*sops)->read(sc)) != 0)
- goto found_source;
- }
- error(sc, "unknown source name `%s'", sc->d.buf);
-
- /* --- Read any source-specific options --- */
-
- found_source:
- if (sc->t == '{') {
- token(sc);
- while (sc->t == CTOK_WORD) {
- if (!s->ops->option || !s->ops->option(s, sc)) {
- error(sc, "unknown %s source option `%s'",
- s->ops->name, sc->d.buf);
- }
- if (sc->t == ';')
- token(sc);
- }
- if (sc->t != '}')
- error(sc, "parse error, missing `}'");
- token(sc);
- }
- }
-
- /* --- Read a destination description --- */
-
- if (sc->t == CTOK_WORD && (strcmp(sc->d.buf, "to") == 0 ||
- strcmp(sc->d.buf, "->") == 0))
- token(sc);
-
- {
- target_ops **tops;
-
- /* --- Try to find a target which understands --- */
-
- t = 0;
- for (tops = targets; *tops; tops++) {
- if ((t = (*tops)->read(sc)) != 0)
- goto found_target;
- }
- error(sc, "unknown target name `%s'", sc->d.buf);
-
- /* --- Read any target-specific options --- */
-
- found_target:
- if (sc->t == '{') {
- token(sc);
- while (sc->t == CTOK_WORD) {
- if (!t->ops->option || !t->ops->option(t, sc)) {
- error(sc, "unknown %s target option `%s'",
- t->ops->name, sc->d.buf);
- }
- if (sc->t == ';')
- token(sc);
- }
- if (sc->t != '}')
- error(sc, "parse error, `}' expected");
- token(sc);
- }
- }
-
- /* --- Combine the source and target --- */
-
- s->ops->attach(s, sc, t);
- if (t->ops->confirm)
- t->ops->confirm(t);
- }
-
- /* --- Include configuration from a file --- *
- *
- * Slightly tricky. Scan the optional semicolon from the including
- * stream, not the included one.
- */
-
- else if (strcmp(sc->d.buf, "include") == 0) {
- FILE *fp;
- dstr d = DSTR_INIT;
-
- token(sc);
- conf_name(sc, '/', &d);
- if ((fp = fopen(d.buf, "r")) == 0)
- error(sc, "can't include `%s': %s", d.buf, strerror(errno));
- if (sc->t == ';')
- token(sc);
- pushback(sc);
- scan_push(sc, scan_file(fp, d.buf, 0));
- token(sc);
- dstr_destroy(&d);
- continue; /* Don't parse a trailing `;' */
- }
-
- /* --- Other configuration is handled elsewhere --- */
-
- else {
-
- /* --- First try among the sources --- */
-
- {
- source_ops **sops;
-
- for (sops = sources; *sops; sops++) {
- if ((*sops)->option && (*sops)->option(0, sc))
- goto found_option;
- }
- }
-
- /* --- Then try among the targets --- */
-
- {
- target_ops **tops;
-
- for (tops = targets; *tops; tops++) {
- if ((*tops)->option && (*tops)->option(0, sc))
- goto found_option;
- }
- }
-
- /* --- Nobody wants the option --- */
-
- error(sc, "unknown global option or prefix `%s'", sc->d.buf);
-
- found_option:;
- }
-
- if (sc->t == ';')
- token(sc);
- }
-}
-
-/*----- General utility functions -----------------------------------------*/
-
-/* --- @fw_log@ --- *
- *
- * Arguments: @time_t t@ = when the connection occurred or (@-1@)
- * @const char *fmt@ = format string to fill in
- * @...@ = other arguments
- *
- * Returns: ---
- *
- * Use: Logs a connection.
- */
-
-void fw_log(time_t t, const char *fmt, ...)
-{
- struct tm *tm;
- dstr d = DSTR_INIT;
- va_list ap;
-
- if (flags & FW_QUIET)
- return;
-
- if (t == -1)
- t = time(0);
- tm = localtime(&t);
- DENSURE(&d, 64);
- d.len += strftime(d.buf, d.sz, "%Y-%m-%d %H:%M:%S ", tm);
- va_start(ap, fmt);
- dstr_vputf(&d, fmt, &ap);
- va_end(ap);
- if (flags & FW_SYSLOG)
- syslog(LOG_NOTICE, "%s", d.buf);
- else {
- DPUTC(&d, '\n');
- dstr_write(&d, stderr);
- }
- DDESTROY(&d);
-}
-
-/* --- @fw_inc@, @fw_dec@ --- *
- *
- * Arguments: ---
- *
- * Returns: ---
- *
- * Use: Increments or decrements the active thing count. `fw' won't
- * quit while there are active things.
- */
-
-void fw_inc(void) { flags |= FW_SET; active++; }
-void fw_dec(void) { if (active) active--; }
-
-/* --- @fw_exit@ --- *
- *
- * Arguments: ---
- *
- * Returns: ---
- *
- * Use: Exits when appropriate.
- */
-
-static void fw_exit(void)
-{
- endpt_killall();
- source_killall();
-}
-
-/* --- @fw_tidy@ --- *
- *
- * Arguments: @int n@ = signal number
- * @void *p@ = an uninteresting argument
- *
- * Returns: ---
- *
- * Use: Handles various signals and causes a clean tidy-up.
- */
-
-static void fw_tidy(int n, void *p)
-{
- const char *sn = 0;
- switch (n) {
- case SIGTERM: sn = "SIGTERM"; break;
- case SIGINT: sn = "SIGINT"; break;
- default: abort();
- }
-
- fw_log(-1, "closing down gracefully on %s", sn);
- source_killall();
-}
-
-/* --- @fw_die@ --- *
- *
- * Arguments: @int n@ = signal number
- * @void *p@ = an uninteresting argument
- *
- * Returns: ---
- *
- * Use: Handles various signals and causes an abrupt shutdown.
- */
-
-static void fw_die(int n, void *p)
-{
- const char *sn = 0;
- switch (n) {
- case SIGQUIT: sn = "SIGQUIT"; break;
- default: abort();
- }
-
- fw_log(-1, "closing down abruptly on %s", sn);
- source_killall();
- endpt_killall();
-}
-
-/* --- @fw_reload@ --- *
- *
- * Arguments: @int n@ = a signal number
- * @void *p@ = an uninteresting argument
- *
- * Returns: ---
- *
- * Use: Handles a hangup signal by re-reading configuration files.
- */
-
-static void fw_reload(int n, void *p)
-{
- FILE *fp;
- scanner sc;
- conffile *cf;
-
- assert(n == SIGHUP);
- if (!conffiles) {
- fw_log(-1, "no configuration files to reload: ignoring SIGHUP");
- return;
- }
- fw_log(-1, "reloading configuration files...");
- source_killall();
- scan_create(&sc);
- for (cf = conffiles; cf; cf = cf->next) {
- if ((fp = fopen(cf->name, "r")) == 0)
- fw_log(-1, "error loading `%s': %s", cf->name, strerror(errno));
- else
- scan_add(&sc, scan_file(fp, cf->name, 0));
- }
- parse(&sc);
- fw_log(-1, "... reload completed OK");
-}
-
-/*----- Startup and options parsing ---------------------------------------*/
-
-/* --- Standard GNU help options --- */
-
-static void version(FILE *fp)
-{
- pquis(fp, "$ version " VERSION "\n");
-}
-
-static void usage(FILE *fp)
-{
- pquis(fp, "Usage: $ [-dql] [-p PIDFILE] [-f FILE] [CONFIG-STMTS...]\n");
-}
-
-static void help(FILE *fp)
-{
- version(fp);
- fputc('\n', fp);
- usage(fp);
- pquis(fp, "\n\
-An excessively full-featured port-forwarder, which subsumes large chunks\n\
-of the functionality of inetd, netcat, and normal cat.\n\
-\n\
-Configuration may be supplied in one or more configuration files, or on\n\
-the command line (or both). If no `-f' option is present, and no\n\
-configuration is given on the command line, the standard input stream is\n\
-read.\n\
-\n\
-Configuration is free-form. Comments begin with a `#' character and\n\
-continue to the end of the line. Each command line argument is considered\n\
-to be a separate line.\n\
-\n\
-The grammar is fairly complicated. For a summary, run `$ --grammar'.\n\
-For a summary of the various options, run `$ --options'.\n\
-\n\
-Options available are:\n\
-\n\
-Help options:\n\
- -h, --help Display this help message.\n\
- -v, --version Display the program's version number.\n\
- -u, --usage Display a terse usage summary.\n\
-\n\
-Configuration summary:\n\
- -G, --grammar Show a summary of the configuration language.\n\
- -O, --options Show a summary of the source and target options.\n\
-\n\
-Other options:\n\
- -f, --file=FILE Read configuration from a file.\n\
- -q, --quiet Don't emit any logging information.\n\
- -d, --daemon Fork into background after initializing.\n\
- -p, --pidfile=FILE Write process-id to the named FILE.\n\
- -l, --syslog Send log output to the system logger.\n\
- -s, --setuid=USER Change uid to USER after initializing sources.\n\
- -g, --setgid=GRP Change gid to GRP after initializing sources.\n\
-");
-}
-
-/* --- Other helpful options --- */
-
-static void grammar(FILE *fp)
-{
- version(fp);
- fputs("\nGrammar summary\n\n", fp);
- fputs(grammar_text, fp);
-}
-
-static void options(FILE *fp)
-{
- version(fp);
- fputs("\nOption summary\n\n", fp);
- fputs(option_text, fp);
-}
-
-/* --- @main@ --- *
- *
- * Arguments: @int argc@ = number of command line arguments
- * @char *argv[]@ = vector of argument strings
- *
- * Returns: ---
- *
- * Use: Simple port-forwarding server.
- */
-
-int main(int argc, char *argv[])
-{
- unsigned f = 0;
- sel_state sst;
- sig s_term, s_quit, s_int, s_hup;
- scanner sc;
- uid_t drop = -1;
- gid_t dropg = -1;
- const char *pidfile = 0;
- conffile *cf, **cff = &conffiles;
-
-#define f_bogus 1u
-#define f_file 2u
-#define f_syslog 4u
-#define f_fork 8u
-
- /* --- Initialize things --- */
-
- ego(argv[0]);
- sel = &sst;
- sel_init(sel);
- sub_init();
- sig_init(sel);
- bres_init(sel);
- bres_exec(0);
- exec_init();
- fattr_init(&fattr_global);
- scan_create(&sc);
-
- atexit(fw_exit);
-
- /* --- Parse command line options --- */
-
- for (;;) {
- static struct option opts[] = {
-
- /* --- Standard GNU help options --- */
-
- { "help", 0, 0, 'h' },
- { "version", 0, 0, 'v' },
- { "usage", 0, 0, 'u' },
-
- /* --- Other help options --- */
-
- { "grammar", 0, 0, 'G' },
- { "options", 0, 0, 'O' },
-
- /* --- Other useful arguments --- */
-
- { "file", OPTF_ARGREQ, 0, 'f' },
- { "fork", 0, 0, 'd' },
- { "daemon", 0, 0, 'd' },
- { "pidfile", OPTF_ARGREQ, 0, 'p' },
- { "syslog", 0, 0, 'l' },
- { "log", 0, 0, 'l' },
- { "quiet", 0, 0, 'q' },
- { "setuid", OPTF_ARGREQ, 0, 's' },
- { "uid", OPTF_ARGREQ, 0, 's' },
- { "setgid", OPTF_ARGREQ, 0, 'g' },
- { "gid", OPTF_ARGREQ, 0, 'g' },
-
- /* --- Magic terminator --- */
-
- { 0, 0, 0, 0 }
- };
- int i = mdwopt(argc, argv, "+hvu" "GO" "f:dp:ls:g:", opts, 0, 0, 0);
-
- if (i < 0)
- break;
- switch (i) {
- case 'h':
- help(stdout);
- exit(0);
- break;
- case 'v':
- version(stdout);
- exit(0);
- break;
- case 'u':
- usage(stdout);
- exit(0);
- break;
- case 'G':
- grammar(stdout);
- exit(0);
- break;
- case 'O':
- options(stdout);
- exit(0);
- break;
- case 'f':
- if (strcmp(optarg, "-") == 0)
- scan_add(&sc, scan_file(stdin, "<stdin>", SCF_NOCLOSE));
- else {
- FILE *fp;
- if ((fp = fopen(optarg, "r")) == 0)
- die(1, "couldn't open file `%s': %s", optarg, strerror(errno));
- cf = CREATE(conffile);
- cf->name = optarg;
- *cff = cf;
- cff = &cf->next;
- scan_add(&sc, scan_file(fp, optarg, 0));
- }
- f |= f_file;
- break;
- case 'd':
- f |= f_fork;
- break;
- case 'p':
- pidfile = optarg;
- break;
- case 'l':
- f |= f_syslog;
- break;
- case 'q':
- flags |= FW_QUIET;
- break;
- case 's':
- if (isdigit((unsigned char )optarg[0])) {
- char *q;
- drop = strtol(optarg, &q, 0);
- if (*q)
- die(1, "bad uid `%s'", optarg);
- } else {
- struct passwd *pw = getpwnam(optarg);
- if (!pw)
- die(1, "unknown user `%s'", optarg);
- drop = pw->pw_uid;
- }
- break;
- case 'g':
- if (isdigit((unsigned char )optarg[0])) {
- char *q;
- dropg = strtol(optarg, &q, 0);
- if (*q)
- die(1, "bad gid `%s'", optarg);
- } else {
- struct group *gr = getgrnam(optarg);
- if (!gr)
- die(1, "unknown group `%s'", optarg);
- dropg = gr->gr_gid;
- }
- break;
- default:
- f |= f_bogus;
- break;
- }
- }
-
- if (f & f_bogus) {
- usage(stderr);
- exit(1);
- }
- *cff = 0;
-
- /* --- Deal with the remaining arguments --- */
-
- if (optind < argc)
- scan_add(&sc, scan_argv(argv + optind));
- else if (f & f_file)
- /* Cool */;
- else if (!isatty(STDIN_FILENO))
- scan_add(&sc, scan_file(stdin, "<stdin>", SCF_NOCLOSE));
- else {
- moan("no configuration given and stdin is a terminal.");
- moan("type `%s --help' for usage information.", QUIS);
- exit(1);
- }
-
- /* --- Parse the configuration now gathered --- */
-
- parse(&sc);
-
- /* --- Set up some signal handlers --- *
- *
- * Don't enable @SIGINT@ if the caller already disabled it.
- */
-
- {
- struct sigaction sa;
-
- sig_add(&s_term, SIGTERM, fw_tidy, 0);
- sig_add(&s_quit, SIGQUIT, fw_die, 0);
- sigaction(SIGINT, 0, &sa);
- if (sa.sa_handler != SIG_IGN)
- sig_add(&s_int, SIGINT, fw_tidy, 0);
- sig_add(&s_hup, SIGHUP, fw_reload, 0);
- }
-
- /* --- Fork into the background --- */
-
- if (f & f_fork) {
- pid_t kid;
-
- kid = fork();
- if (kid == -1)
- die(1, "couldn't fork: %s", strerror(errno));
- if (kid != 0)
- _exit(0);
-
- close(0); close(1); close(2);
- chdir("/");
- setsid();
-
- kid = fork();
- if (kid != 0)
- _exit(0);
- }
- if (pidfile) {
- FILE *fp = fopen(pidfile, "w");
- if (!fp) {
- die(EXIT_FAILURE, "couldn't create pidfile `%s': %s",
- pidfile, strerror(errno));
- }
- fprintf(fp, "%lu\n", (unsigned long)getpid());
- if (fflush(fp) || ferror(fp) || fclose(fp)) {
- die(EXIT_FAILURE, "couldn't write pidfile `%s': %s",
- pidfile, strerror(errno));
- }
- }
-
- if (f & f_syslog) {
- flags |= FW_SYSLOG;
- openlog(QUIS, 0, LOG_DAEMON);
- }
-
- /* --- Drop privileges --- */
-
- if (drop != (uid_t)-1)
- privconn_split(sel);
-#ifdef HAVE_SETGROUPS
- if ((dropg != (gid_t)-1 && (setgid(dropg) || setgroups(1, &dropg))) ||
- (drop != (uid_t)-1 && setuid(drop)))
- die(1, "couldn't drop privileges: %s", strerror(errno));
-#else
- if ((dropg != (gid_t)-1 && setgid(dropg)) ||
- (drop != (uid_t)-1 && setuid(drop)))
- die(1, "couldn't drop privileges: %s", strerror(errno));
-#endif
-
- /* --- Let rip --- */
-
- if (!(flags & FW_SET))
- moan("nothing to do!");
- signal(SIGPIPE, SIG_IGN);
-
- {
- int selerr = 0;
- while (active) {
- if (!sel_select(sel))
- selerr = 0;
- else if (errno != EINTR && errno != EAGAIN) {
- fw_log(-1, "error from select: %s", strerror(errno));
- selerr++;
- if (selerr > 8) {
- fw_log(-1, "too many consecutive select errors: bailing out");
- exit(EXIT_FAILURE);
- }
- }
- }
- }
-
- return (0);
-}
-
-/*----- That's all, folks -------------------------------------------------*/