/* -*-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 --------------------------------------------------*
/*----- 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.
*
*/
#include <stdlib.h>
#include <string.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <netdb.h>
-
#include <mLib/dstr.h>
#include <mLib/quis.h>
#include <mLib/report.h>
-#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 ---------------------------------------------------------*/
* 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@ --- *
* 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 (;;) {
/* --- 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);
/* -*-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 --------------------------------------------------*
/*----- 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.
*
*/
/*----- Header files ------------------------------------------------------*/
+#include <mLib/dstr.h>
+
#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 -------------------------------------------------*/
/* -*-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 --------------------------------------------------*
/*----- 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.
#include <ctype.h>
#include <errno.h>
+#include <signal.h>
+#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <time.h>
#include <unistd.h>
#include <syslog.h>
+#include <mLib/dstr.h>
#include <mLib/mdwopt.h>
#include <mLib/quis.h>
#include <mLib/report.h>
#include <mLib/sel.h>
+#include <mLib/sig.h>
#include <mLib/sub.h>
-#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)
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\
\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);
}
{
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
};
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 --- */
/* --- 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;
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, "<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;
/* --- 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, "<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, "<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 --- */
/* --- Let rip --- */
- for (;;)
+ if (!(flags & FW_SET))
+ moan("nothing to do!");
+ signal(SIGPIPE, SIG_IGN);
+ while (active)
sel_select(sel);
return (0);
}