/* -*-c-*-
*
- * $Id: conf.c,v 1.1 1999/07/01 08:56:23 mdw Exp $
+ * $Id: conf.c,v 1.3 1999/08/19 18:32:48 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.3 1999/08/19 18:32:48 mdw
+ * Improve lexical analysis. In particular, `chmod' patterns don't have to
+ * be quoted any more.
+ *
+ * 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"
+
+#include "exec.h"
+#include "file.h"
+#include "socket.h"
-/*----- Magic numbers -----------------------------------------------------*/
+/*----- Source and target tables ------------------------------------------*/
-#define CTOK_EOF (-1)
-#define CTOK_WORD 256
+static source_ops *sources[] = { &xsource_ops, &fsource_ops, &ssource_ops };
+static target_ops *targets[] = { &xtarget_ops, &ftarget_ops, &starget_ops };
+
+static const char *notword = 0;
+static const char *notdelim = 0;
/*----- Main code ---------------------------------------------------------*/
+/* --- @undelim@ --- *
+ *
+ * Arguments: @const char *d, dd@ = pointer to characters to escape
+ *
+ * Returns: ---
+ *
+ * Use: Modifies the tokenizer. Characters in the first list will
+ * always be considered to begin a word. Characters in the
+ * second list will always be allowed to continue a word.
+ */
+
+void undelim(const char *d, const char *dd) { notword = d; notdelim = dd; }
+
/* --- @token@ --- *
*
* Arguments: @scanner *sc@ = pointer to scanner definition
* 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:
+ if (!notword || strchr(notword, ch) == 0) {
+ 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 || (notdelim && strchr(notdelim, ch)))
+ 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 --- */
-
- if (listen(fd, 5))
- error(sc, "couldn't listen on socket: %s", strerror(errno));
+ /* --- Combine the source and target --- */
- /* --- 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);