From: mdw Date: Mon, 26 Jul 1999 23:28:39 +0000 (+0000) Subject: Major reconstruction work for new design. X-Git-Tag: 1.1.0~15 X-Git-Url: https://git.distorted.org.uk/~mdw/fwd/commitdiff_plain/61e3dbdf67c571ec4973ab19475ced6a438ab8df Major reconstruction work for new design. --- diff --git a/conf.c b/conf.c index 049af46..020df7a 100644 --- a/conf.c +++ b/conf.c @@ -1,10 +1,10 @@ /* -*-c-*- * - * $Id: conf.c,v 1.1 1999/07/01 08:56:23 mdw Exp $ + * $Id: conf.c,v 1.2 1999/07/26 23:28:39 mdw Exp $ * * Configuration parsing * - * (c) 1999 Mark Wooding + * (c) 1999 Straylight/Edgeware */ /*----- Licensing notice --------------------------------------------------* @@ -29,8 +29,11 @@ /*----- Revision history --------------------------------------------------* * * $Log: conf.c,v $ - * Revision 1.1 1999/07/01 08:56:23 mdw - * Initial revision + * Revision 1.2 1999/07/26 23:28:39 mdw + * Major reconstruction work for new design. + * + * Revision 1.1.1.1 1999/07/01 08:56:23 mdw + * Initial revision. * */ @@ -45,23 +48,23 @@ #include #include -#include -#include -#include -#include - #include #include #include -#include "acl.h" -#include "listener.h" +#include "conf.h" #include "scan.h" +#include "source.h" +#include "target.h" -/*----- Magic numbers -----------------------------------------------------*/ +#include "exec.h" +#include "file.h" +#include "socket.h" -#define CTOK_EOF (-1) -#define CTOK_WORD 256 +/*----- Source and target tables ------------------------------------------*/ + +static source_ops *sources[] = { &xsource_ops, &fsource_ops, &ssource_ops }; +static target_ops *targets[] = { &xtarget_ops, &ftarget_ops, &starget_ops }; /*----- Main code ---------------------------------------------------------*/ @@ -74,49 +77,119 @@ * Use: Reads the next token from the character scanner. */ -static int token(scanner *sc) +int token(scanner *sc) { -#define SCAN(sc) (sc)->ops->scan((sc)) -#define UNSCAN(sc, x) (sc)->ops->unscan((x), (sc)) +#define SELFDELIM \ + '{': case '}': case '/': case ',': \ + case '=': case ':': case ';': \ + case '.': case '[': case ']' int ch; + DRESET(&sc->d); + + /* --- Main tokenization --- */ + for (;;) { - ch = SCAN(sc); - if (ch == '\n') - sc->line++; + ch = scan(sc); + + /* --- Deal with pushed-back tokens --- */ + + if (sc->head->tok) { + dstr_puts(&sc->d, sc->head->tok); + free(sc->head->tok); + sc->head->tok = 0; + sc->t = sc->head->t; + goto done; + } + else if (isspace((unsigned char)ch)) ; else switch (ch) { + + /* --- End of file --- */ + case EOF: - return (sc->t = CTOK_EOF); + sc->t = CTOK_EOF; + goto done; + + /* --- Comment character --- */ + case '#': - do ch = SCAN(sc); while (ch != EOF && ch != '\n'); - if (ch == '\n') - sc->line++; + do ch = scan(sc); while (ch != EOF && ch != '\n'); break; - case '{': - case '}': - case ':': - case '/': - case ';': - return (sc->t = ch); - default: - DRESET(&sc->d); - do { - DPUTC(&sc->d, ch); - ch = SCAN(sc); - } while (ch != EOF && ch != '{' && ch != ';' && - ch != '}' && ch != ':' && ch != '/' && - !isspace((unsigned char)(ch))); - UNSCAN(sc, ch); + + /* --- Various self-delimiting characters --- */ + + case SELFDELIM: + dstr_putc(&sc->d, ch); + dstr_putz(&sc->d); + sc->t = ch; + goto done; + + /* --- Bare words --- * + * + * These aren't as bare any more. You can now backslash-escape + * individual characters, and enclose sections in double-quotes. + */ + + default: { + int q = 0; + + for (;;) { + switch (ch) { + case EOF: + goto word; + case '\\': + ch = scan(sc); + if (ch == EOF) + goto word; + DPUTC(&sc->d, ch); + break; + case '\"': + q = !q; + break; + case SELFDELIM: + if (q) + goto insert; + goto word; + default: + if (!q && isspace((unsigned char)(ch))) + goto word; + insert: + DPUTC(&sc->d, ch); + break; + } + ch = scan(sc); + } + word: + unscan(sc, ch); DPUTZ(&sc->d); - return (sc->t = CTOK_WORD); + sc->t = CTOK_WORD; + goto done; + } } } -#undef SCAN -#undef UNSCAN +done: +/* printf("token `%s'\n", sc->d.buf); */ + return (sc->t); +} + +/* --- @pushback@ --- * + * + * Arguments: @scanner *sc@ = pointer to scanner definition + * + * Returns: --- + * + * Use: Pushes the current token back. This is normally a precursor + * to pushing a new scanner source. + */ + +static void pushback(scanner *sc) +{ + sc->head->tok = xstrdup(sc->d.buf); + sc->head->t = sc->t; } /* --- @error@ --- * @@ -130,119 +203,196 @@ static int token(scanner *sc) * Use: Reports an error at the current scanner location. */ -static void error(scanner *sc, const char *msg, ...) +void error(scanner *sc, const char *msg, ...) { va_list ap; va_start(ap, msg); - fprintf(stderr, "%s: %s:%i: ", QUIS, sc->src, sc->line); + fprintf(stderr, "%s: %s:%i: ", QUIS, sc->head->src, sc->head->line); vfprintf(stderr, msg, ap); fputc('\n', stderr); exit(1); } -/* --- @portnum@ --- * +/* --- @conf_enum@ --- * * - * Arguments: @scanner *sc@ = pointer to scanner (for error reporting) - * @const char *p@ = pointer to port name + * Arguments: @scanner *sc@ = pointer to a scanner object + * @const char *list@ = comma-separated things to allow + * @unsigned f@ = flags for the search + * @const char *err@ = error message if not found * - * Returns: Port number (network byte order) + * Returns: Index into list, zero-based, or @-1@. * - * Use: Converts a textual port name or number into a usable thing. + * Use: Checks whether the current token is a string which matches + * one of the comma-separated items given. If not, an error is + * reported; otherwise the index of the matched item is + * returned. */ -static unsigned short portnum(scanner *sc, const char *p) +int conf_enum(scanner *sc, const char *list, unsigned f, const char *err) { - struct servent *s; - if (isdigit((unsigned char)*p)) - return (htons(atoi(p))); - if ((s = getservbyname(p, "tcp")) == 0) - error(sc, "unknown tcp service `%s'", p); - return (s->s_port); + const char *p, *q; + int chosen = -1; + int ok; + int index; + + /* --- Make sure it's a string --- */ + + if (sc->t != CTOK_WORD) + error(sc, "parse error, expected %s", err); + + /* --- Grind through the list --- */ + + q = sc->d.buf; + ok = 1; + index = 0; + p = list; + for (;;) { + switch (*p) { + case 0: + if (ok && !*q) { + token(sc); + return (index); + } else if (chosen != -1) { + token(sc); + return (chosen); + } + else if (f & ENUM_NONE) + return (-1); + else + error(sc, "unknown %s `%s'", err, sc->d.buf); + break; + case ',': + if (ok && !*q) { + token(sc); + return (index); + } + ok = 1; + q = sc->d.buf; + index++; + break; + default: + if (!ok) + break; + if ((f & ENUM_ABBREV) && !*q) { + if (chosen != -1) + error(sc, "ambiguous %s `%s'", err, sc->d.buf); + chosen = index; + ok = 0; + } + if (*p == *q) + q++; + else + ok = 0; + break; + } + p++; + } } -/* --- @getconf@ --- * +/* --- @conf_prefix@ --- * * - * Arguments: @scanner *sc@ = pointer to scanner to read from - * @listener *l@ = listener to configure (or zero) - * @acl_entry ***a@ = pointer to tail of ACL (or zero) + * Arguments: @scanner *sc@ = pointer to a scanner object + * @const char *p@ = pointer to prefix string to check * - * Returns: --- + * Returns: Nonzero if the prefix matches. * - * Use: Reads a local or global configuration statement. + * Use: If the current token is a word matching the given prefix + * string, then it and an optional `.' character are removed and + * a nonzero result is returned. Otherwise the current token is + * left as it is, and zero is returned. + * + * Typical options parsing code would remove an expected prefix, + * scan an option anyway (since qualifying prefixes are + * optional) and if a match is found, claim the option. If no + * match is found, and a prefix was stripped, then an error + * should be reported. */ -static void getconf(scanner *sc, listener *l, acl_entry ***a) +int conf_prefix(scanner *sc, const char *p) { - unsigned act; + if (sc->t == CTOK_WORD && strcmp(p, sc->d.buf) == 0) { + token(sc); + if (sc->t == '.') + token(sc); + return (1); + } + return (0); +} - /* --- Access control limitations --- */ +/* --- @conf_name@ --- * + * + * Arguments: @scanner *sc@ = pointer to scanner + * @char delim@ = delimiter character to look for + * @dstr *d@ = pointer to dynamic string for output + * + * Returns: --- + * + * Use: Reads in a compound name consisting of words separated by + * delimiters. Leading and trailing delimiters are permitted, + * although they'll probably cause confusion if used. The name + * may be enclosed in square brackets if that helps at all. + * + * Examples of compound names are filenames (delimited by `/') + * and IP addresses (delimited by `.'). + */ - if ((strcmp(sc->d.buf, "allow") == 0 && (act = ACL_ALLOW, 1)) || - (strcmp(sc->d.buf, "deny") == 0 && (act = ACL_DENY, 1))) { - struct hostent *h; - struct netent *n; - struct in_addr addr, mask; +void conf_name(scanner *sc, char delim, dstr *d) +{ + unsigned f = 0; + enum { + f_ok = 1, + f_bra = 2 + }; - /* --- Find the host or network address --- */ + /* --- Read an optional opening bracket --- */ + if (sc->t == '[') { token(sc); - if (sc->t == CTOK_WORD && strcmp(sc->d.buf, "from") == 0) - token(sc); - if (sc->t != CTOK_WORD) - error(sc, "parse error, address expected"); - if ((n = getnetbyname(sc->d.buf)) != 0) - addr.s_addr = htonl(n->n_net); - else if ((h = gethostbyname(sc->d.buf)) == 0) - error(sc, "couldn't resolve address `%s'", sc->d.buf); - else - memcpy(&addr, h->h_addr, sizeof(struct in_addr)); - token(sc); + f |= f_bra; + } - /* --- Find the netmask, if any --- */ + /* --- Do the main reading sequence --- */ - if (sc->t != '/') - mask.s_addr = ~0ul; - else { + do { + if (sc->t == delim) { + DPUTC(d, delim); + f |= f_ok; token(sc); - if (sc->t != CTOK_WORD) - error(sc, "parse error, netmask expected"); - if (strchr(sc->d.buf, '.') == 0) - mask.s_addr = htonl((~0ul << (32 - atoi(sc->d.buf))) & 0xffffffff); - else { -#ifdef HAVE_INET_ATON - if (!inet_aton(sc->d.buf, &mask)) - error(sc, "bad netmask `%s'", sc->d.buf); -#else - mask.s_addr = inet_addr(sc->d.buf); -#endif - } + } + if (sc->t == CTOK_WORD) { + DPUTD(d, &sc->d); + f |= f_ok; token(sc); } + } while (sc->t == delim); - /* --- Add the access control entry --- */ + /* --- Check that the string was OK --- */ - acl_add(a, act, addr, mask); - } + if (!(f & f_ok)) + error(sc, "parse error, name expected"); - /* --- Anything unrecognized --- */ + /* --- Read a closing bracket --- */ - else - error(sc, "parse error, unknown configuration keyword `%s'", sc->d.buf); + if (f & f_bra) { + if (sc->t == ']') + token(sc); + else + error(sc, "parse error, missing `]'"); + } + DPUTZ(d); } /* --- @conf_parse@ --- * * - * Arguments: @void *scp@ = pointer to scanner definition + * Arguments: @scanner *sc@ = pointer to scanner definition * * Returns: --- * * Use: Parses a configuration file from the scanner. */ -void conf_parse(void *scp) +void conf_parse(scanner *sc) { - scanner *sc = scp; - token(sc); for (;;) { @@ -254,94 +404,143 @@ void conf_parse(void *scp) /* --- Handle a forwarding request --- */ if (strcmp(sc->d.buf, "forward") == 0 || - strcmp(sc->d.buf, "fw") == 0) { - unsigned short sp, dp; - struct sockaddr_in sin; - struct hostent *h; - int fd; - listener *l; - - /* --- Read the source port --- */ + strcmp(sc->d.buf, "fw") == 0 || + strcmp(sc->d.buf, "from") == 0) { + source *s; + target *t; token(sc); - if (sc->t == CTOK_WORD && strcmp(sc->d.buf, "port") == 0) - token(sc); - if (sc->t != CTOK_WORD) - error(sc, "parse error, source port expected"); - sp = portnum(sc, sc->d.buf); - /* --- Read the destination address --- */ + /* --- Read a source description --- */ - token(sc); - if (sc->t == CTOK_WORD && strcmp(sc->d.buf, "to") == 0) - token(sc); - if (sc->t != CTOK_WORD) - error(sc, "parse error, destination address expected"); - if ((h = gethostbyname(sc->d.buf)) == 0) - error(sc, "couldn't resolve address `%s'", sc->d.buf); + { + source_ops **sops; - token(sc); - if (sc->t == ':') - token(sc); - if (sc->t != CTOK_WORD) - error(sc, "parse error, destination port expected"); - dp = portnum(sc, sc->d.buf); + /* --- Try to find a source type which understands --- */ - /* --- Make the socket --- */ + 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); + } + } - if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) - error(sc, "couldn't create socket: %s", strerror(errno)); + /* --- Read a destination description --- */ - /* --- Set it to allow address reuse --- */ + if (sc->t == CTOK_WORD && (strcmp(sc->d.buf, "to") == 0 || + strcmp(sc->d.buf, "->") == 0)) + token(sc); { - int opt = 1; - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); - } + target_ops **tops; - /* --- Bind it to the right port --- */ + /* --- Try to find a target which understands --- */ - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = htonl(INADDR_ANY); - sin.sin_port = sp; - if (bind(fd, (struct sockaddr *)&sin, sizeof(sin))) { - error(sc, "couldn't bind to port %i: %s", - ntohs(sp), strerror(errno)); + 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); + } } - /* --- Set it to listen for connections --- */ + /* --- Combine the source and target --- */ - if (listen(fd, 5)) - error(sc, "couldn't listen on socket: %s", strerror(errno)); - - /* --- Fill in a new listener --- */ + s->ops->attach(s, sc, t); + } - memcpy(&sin.sin_addr, h->h_addr, sizeof(struct in_addr)); - sin.sin_port = dp; - l = listener_add(fd, sp, &sin); + /* --- Include configuration from a file --- * + * + * Slightly tricky. Scan the optional semicolon from the including + * stream, not the included one. + */ - /* --- Snarf access controls and other attributes --- */ + else if (strcmp(sc->d.buf, "include") == 0) { + FILE *fp; + dstr d = DSTR_INIT; token(sc); - if (sc->t == '{') { - acl_entry **a = &l->acl; + 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); - while (sc->t != '}') { - if (sc->t != CTOK_WORD) - error(sc, "parse error, keyword or `}' expected"); - getconf(sc, l, &a); - if (sc->t == ';') - token(sc); - } - *a = 0; - token(sc); - } + pushback(sc); + scan_push(sc, scan_file(fp, xstrdup(d.buf), SCF_FREENAME)); + token(sc); + dstr_destroy(&d); + continue; /* Don't parse a trailing `;' */ } /* --- Other configuration is handled elsewhere --- */ - else - getconf(sc, 0, 0); + 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); diff --git a/conf.h b/conf.h index e1713ee..1704587 100644 --- a/conf.h +++ b/conf.h @@ -1,10 +1,10 @@ /* -*-c-*- * - * $Id: conf.h,v 1.1 1999/07/01 08:56:23 mdw Exp $ + * $Id: conf.h,v 1.2 1999/07/26 23:28:39 mdw Exp $ * * Configuration parsing * - * (c) 1999 Mark Wooding + * (c) 1999 Straylight/Edgeware */ /*----- Licensing notice --------------------------------------------------* @@ -29,8 +29,11 @@ /*----- Revision history --------------------------------------------------* * * $Log: conf.h,v $ - * Revision 1.1 1999/07/01 08:56:23 mdw - * Initial revision + * Revision 1.2 1999/07/26 23:28:39 mdw + * Major reconstruction work for new design. + * + * Revision 1.1.1.1 1999/07/01 08:56:23 mdw + * Initial revision. * */ @@ -43,22 +46,172 @@ /*----- Header files ------------------------------------------------------*/ +#include + #ifndef SCAN_H # include "scan.h" #endif +/*----- Magic numbers -----------------------------------------------------*/ + +#define CTOK_EOF (-1) +#define CTOK_WORD 256 + /*----- Functions provided ------------------------------------------------*/ +/* --- @token@ --- * + * + * Arguments: @scanner *sc@ = pointer to scanner definition + * + * Returns: Type of token scanned. + * + * Use: Reads the next token from the character scanner. + */ + +extern int token(scanner */*sc*/); + +/* --- @error@ --- * + * + * Arguments: @scanner *sc@ = pointer to scanner definition + * @const char *msg@ = message skeleton string + * @...@ = extra arguments for the skeleton + * + * Returns: Doesn't + * + * Use: Reports an error at the current scanner location. + */ + +extern void error(scanner */*sc*/, const char */*msg*/, ...); + +/* --- @conf_enum@ --- * + * + * Arguments: @scanner *sc@ = pointer to a scanner object + * @const char *list@ = comma-separated things to allow + * @unsigned @f = flags for the search + * @const char *err@ = error message if not found + * + * Returns: Index into list, zero-based, or @-1@. + * + * Use: Checks whether the current token is a string which matches + * one of the comma-separated items given. If not, an error is + * reported; otherwise the index of the matched item is + * returned. + */ + +#define ENUM_ABBREV 1u +#define ENUM_NONE 2u + +extern int conf_enum(scanner */*sc*/, const char */*list*/, + unsigned /*flags*/, const char */*err*/); + +/* --- @conf_prefix@ --- * + * + * Arguments: @scanner *sc@ = pointer to a scanner object + * @const char *p@ = pointer to prefix string to check + * + * Returns: Nonzero if the prefix matches. + * + * Use: If the current token is a word matching the given prefix + * string, then it and an optional `.' character are removed and + * a nonzero result is returned. Otherwise the current token is + * left as it is, and zero is returned. + * + * Typical options parsing code would remove an expected prefix, + * scan an option anyway (since qualifying prefixes are + * optional) and if a match is found, claim the option. If no + * match is found, and a prefix was stripped, then an error + * should be reported. + */ + +extern int conf_prefix(scanner */*sc*/, const char */*p*/); + +/* --- @CONF_BEGIN@, @CONF_ACCEPT@, @CONF_END@ --- * + * + * Arguments: @sc@ = scanner to read from + * @prefix@ = prefix to scan for + * @desc@ = description of what we're parsing + * + * Use: Bracket an options parsing routine. + */ + +#define CS_PLAIN 0 +#define CS_PREFIX 1 +#define CS_BRACE 2 +#define CS_UNKNOWN 3 + +#define CONF_BEGIN(sc, prefix, desc) do { \ + scanner *_conf_sc = (sc); \ + const char *_conf_desc = (desc); \ + int _conf_state = CS_PLAIN; \ + if (_conf_sc->t == CTOK_WORD && \ + strcmp(_conf_sc->d.buf, (prefix)) == 0) { \ + token(_conf_sc); \ + _conf_state = CS_PREFIX; \ + if (_conf_sc->t == '.') \ + token(_conf_sc); \ + else if (_conf_sc->t == '{') { \ + token(_conf_sc); \ + _conf_state = CS_BRACE; \ + } \ + } \ + if (_conf_sc->t != CTOK_WORD) \ + error(_conf_sc, "parse error, expected option keyword"); \ + do { + +#define CONF_ACCEPT goto _conf_accept +#define CONF_REJECT goto _conf_reject +#define CONF_QUAL (_conf_state != CS_PLAIN) + +#define CONF_END \ + _conf_reject: \ + if (_conf_state == CS_PLAIN) \ + _conf_state = CS_UNKNOWN; \ + else { \ + error(_conf_sc, "unknown %s option `%s'", \ + _conf_desc, _conf_sc->d.buf); \ + } \ + _conf_accept: \ + if (_conf_state == CS_BRACE && _conf_sc->t == ';') \ + token(_conf_sc); \ + } while (_conf_state == CS_BRACE && _conf_sc->t == CTOK_WORD); \ + if (_conf_state == CS_BRACE) { \ + if (_conf_sc->t == '}') \ + token(_conf_sc); \ + else \ + error(_conf_sc, "parse error, expected `}'"); \ + } \ + return (_conf_state != CS_UNKNOWN); \ +} while (0) + +/* --- @conf_name@ --- * + * + * Arguments: @scanner *sc@ = pointer to scanner + * @char delim@ = delimiter character to look for + * @dstr *d@ = pointer to dynamic string for output + * + * Returns: --- + * + * Use: Reads in a compound name consisting of words separated by + * delimiters. Leading and trailing delimiters are permitted, + * although they'll probably cause confusion if used. The name + * may be enclosed in square brackets if that helps at all. + * + * Examples of compound names are filenames (delimited by `/') + * and IP addresses (delimited by `.'). + */ + +extern void conf_name(scanner */*sc*/, char /*delim*/, dstr */*d*/); + /* --- @conf_parse@ --- * * - * Arguments: @void *scp@ = pointer to a scanner structure + * Arguments: @scanner *sc@ = pointer to a scanner structure * * Returns: --- * * Use: Parses a configuration file fragment from the scanner */ -extern void conf_parse(void */*scp*/); +extern void conf_parse(scanner *sc); /*----- That's all, folks -------------------------------------------------*/ diff --git a/fw.c b/fw.c index 04235d4..202c3e7 100644 --- a/fw.c +++ b/fw.c @@ -1,10 +1,10 @@ /* -*-c-*- * - * $Id: fw.c,v 1.2 1999/07/03 13:55:17 mdw Exp $ + * $Id: fw.c,v 1.3 1999/07/26 23:30:42 mdw Exp $ * * Port forwarding thingy * - * (c) 1999 Mark Wooding + * (c) 1999 Straylight/Edgeware */ /*----- Licensing notice --------------------------------------------------* @@ -29,6 +29,9 @@ /*----- Revision history --------------------------------------------------* * * $Log: fw.c,v $ + * Revision 1.3 1999/07/26 23:30:42 mdw + * Major reconstruction work for new design. + * * Revision 1.2 1999/07/03 13:55:17 mdw * Various changes. Add configuration grammar to help text. Change to * root directory and open syslog when forking into background. @@ -44,33 +47,136 @@ #include #include +#include +#include #include #include #include +#include #include #include +#include #include #include #include #include +#include #include -#include "acl.h" #include "bres.h" #include "conf.h" +#include "endpt.h" +#include "exec.h" +#include "fattr.h" #include "fw.h" -#include "listener.h" #include "scan.h" +#include "source.h" /*----- Global variables --------------------------------------------------*/ sel_state *sel; /* Multiplexor for nonblocking I/O */ -unsigned flags = 0; /* Global state flags */ + +/*----- Static variables --------------------------------------------------*/ + +static unsigned flags = 0; /* Global state flags */ +static unsigned active = 0; /* Number of active things */ + +#define FW_SYSLOG 1u +#define FW_QUIET 2u +#define FW_SET 4u /*----- Main code ---------------------------------------------------------*/ +/* --- @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); + DPUTC(&d, ' '); + 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 = "unexpected signal (bug!)"; + if (n == SIGTERM) + sn = "SIGTERM"; + else if (n == SIGINT) + sn = "SIGINT"; + + fw_log(-1, "closing down on %s", sn); + fw_exit(); +} + /* --- Standard GNU help options --- */ static void version(FILE *fp) @@ -90,15 +196,16 @@ static void help(FILE *fp) fputc('\n', fp); usage(fp); fputs("\n\ -A fairly full-featured port-forwarder. Options available are:\n\ +An excessively full-featured port-forwarder, which subsumes large chunks\n\ +of the functionality of inetd, netcat, and normal cat. Options available\n\ +are:\n\ \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\ -f, --file=FILE Read configuration from a file.\n\ --d, --dump Dump the configuration to standard output.\n\ --b, --background Fork into background after initializing.\n\ +-d, --daemon Fork into background after initializing.\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\ @@ -107,13 +214,9 @@ 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. The syntax is as follows:\n\ +to be a separate line.\n\ \n\ -config : stmt...\n\ -stmt : fwd-stmt | acl-stmt\n\ -fwd-stmt : `forward' [`port'] port [`to'] addr [`:'] port [fwd-attr] [`;']\n\ -fwd-attr : `{' acl-stmt... `}'\n\ -acl-stmt : (`allow' | `deny') [`from'] addr [`/' mask] [`;']\n\ +The syntax is too complicated to describe here. Read the manual.\n\ ", fp); } @@ -131,11 +234,12 @@ int main(int argc, char *argv[]) { unsigned f = 0; sel_state sst; + sig s_term, s_int; + scanner sc; enum { f_bogus = 1, f_file = 2, - f_dump = 4, f_fork = 8 }; @@ -145,7 +249,17 @@ int main(int argc, char *argv[]) sel = &sst; sel_init(sel); sub_init(); + sig_init(sel); bres_init(sel); + exec_init(); + fattr_init(&fattr_global); + scan_create(&sc); + + /* --- Set up some signal handlers --- */ + + sig_add(&s_term, SIGTERM, fw_tidy, 0); + sig_add(&s_int, SIGINT, fw_tidy, 0); + atexit(fw_exit); /* --- Parse command line options --- */ @@ -161,15 +275,15 @@ int main(int argc, char *argv[]) /* --- Other useful arguments --- */ { "file", OPTF_ARGREQ, 0, 'f' }, - { "dump", 0, 0, 'd' }, - { "fork", 0, 0, 'b' }, - { "background", 0, 0, 'b' }, + { "fork", 0, 0, 'd' }, + { "daemon", 0, 0, 'd' }, + { "quiet", 0, 0, 'q' }, /* --- Magic terminator --- */ { 0, 0, 0, 0 } }; - int i = mdwopt(argc, argv, "hvu f:db", opts, 0, 0, 0); + int i = mdwopt(argc, argv, "+hvu f:d", opts, 0, 0, 0); if (i < 0) break; @@ -186,22 +300,23 @@ int main(int argc, char *argv[]) usage(stdout); exit(0); break; - case 'f': { - scan_filectx ctx; - FILE *fp; - if ((fp = fopen(optarg, "r")) == 0) - die(1, "couldn't open file `%s': %s", optarg, strerror(errno)); - scan_fileinit(&ctx, fp, optarg); - conf_parse(&ctx); - fclose(fp); + case 'f': + if (strcmp(optarg, "-") == 0) + scan_add(&sc, scan_file(stdin, "", SCF_NOCLOSE)); + else { + FILE *fp; + if ((fp = fopen(optarg, "r")) == 0) + die(1, "couldn't open file `%s': %s", optarg, strerror(errno)); + scan_add(&sc, scan_file(fp, optarg, 0)); + } f |= f_file; - } break; - case 'd': - f |= f_dump; break; - case 'b': + case 'd': f |= f_fork; break; + case 'q': + flags |= FW_QUIET; + break; default: f |= f_bogus; break; @@ -215,34 +330,21 @@ int main(int argc, char *argv[]) /* --- Deal with the remaining arguments --- */ - if (optind == argc) { - if (f & f_file) - /* Cool */; - else if (isatty(STDIN_FILENO)) { - moan("no configuration given and stdin is a terminal."); - moan("type `%s --help' for usage information.", QUIS); - exit(1); - } else { - scan_filectx ctx; - scan_fileinit(&ctx, stdin, ""); - conf_parse(&ctx); - } - } else { - scan_argvctx ctx; - scan_argvinit(&ctx, argv + optind); - conf_parse(&ctx); + 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, "", SCF_NOCLOSE)); + else { + moan("no configuration given and stdin is a terminal."); + moan("type `%s --help' for usage information.", QUIS); + exit(1); } - /* --- Dump out the state --- */ + /* --- Parse the configuration now gathered --- */ - if (f & f_dump) { - sel_file *s; - fputs("global acl:\n", stdout); - acl_dump(0, stdout); - for (s = sel->files; s; s = s->next) - listener_dump((listener *)s, stdout); - exit(0); - } + conf_parse(&sc); /* --- Fork into the background --- */ @@ -269,7 +371,10 @@ int main(int argc, char *argv[]) /* --- Let rip --- */ - for (;;) + if (!(flags & FW_SET)) + moan("nothing to do!"); + signal(SIGPIPE, SIG_IGN); + while (active) sel_select(sel); return (0); } diff --git a/fw.h b/fw.h index 6a5750e..d9a04ad 100644 --- a/fw.h +++ b/fw.h @@ -1,10 +1,10 @@ /* -*-c-*- * - * $Id: fw.h,v 1.2 1999/07/03 13:56:33 mdw Exp $ + * $Id: fw.h,v 1.3 1999/07/26 23:30:42 mdw Exp $ * * Main header file for port forwarder * - * (c) 1999 Mark Wooding + * (c) 1999 Straylight/Edgeware */ /*----- Licensing notice --------------------------------------------------* @@ -29,6 +29,9 @@ /*----- Revision history --------------------------------------------------* * * $Log: fw.h,v $ + * Revision 1.3 1999/07/26 23:30:42 mdw + * Major reconstruction work for new design. + * * Revision 1.2 1999/07/03 13:56:33 mdw * Add flag saying whether to log messages to syslog or stderr. * @@ -46,14 +49,41 @@ /*----- Header files ------------------------------------------------------*/ +#include +#include #include /*----- Global variables --------------------------------------------------*/ extern sel_state *sel; -extern unsigned flags; -#define FW_SYSLOG 1u +/*----- Functions provided ------------------------------------------------*/ + +/* --- @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. + */ + +extern void fw_log(time_t /*t*/, const char */*fmt*/, ...); + +/* --- @fw_inc@, @fw_dec@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Increments or decrements the active thing count. `fw' won't + * quit while there are active things. + */ + +extern void fw_inc(void); +extern void fw_dec(void); /*----- That's all, folks -------------------------------------------------*/