/* -*-c-*-
*
- * $Id: conf.c,v 1.1 1999/07/01 08:56:23 mdw Exp $
- *
* Configuration parsing
*
- * (c) 1999 Mark Wooding
+ * (c) 1999 Straylight/Edgeware
*/
-/*----- Licensing notice --------------------------------------------------*
+/*----- Licensing notice --------------------------------------------------*
*
- * This file is part of the `fw' port forwarder.
+ * This file is part of the `fwd' port forwarder.
*
- * `fw' is free software; you can redistribute it and/or modify
+ * `fwd' 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,
+ *
+ * `fwd' 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,
+ * along with `fwd'; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
-/*----- Revision history --------------------------------------------------*
+#include "fwd.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @conf_undelim@ --- *
+ *
+ * Arguments: @scanner *sc@ = pointer to scanner definition
+ * @const char *d, *dd@ = pointer to characters to escape
*
- * $Log: conf.c,v $
- * Revision 1.1 1999/07/01 08:56:23 mdw
- * Initial revision
+ * 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.
*/
-/*----- Header files ------------------------------------------------------*/
-
-#include "config.h"
-
-#include <ctype.h>
-#include <errno.h>
-#include <stdarg.h>
-#include <stdio.h>
-#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 "scan.h"
-
-/*----- Magic numbers -----------------------------------------------------*/
-
-#define CTOK_EOF (-1)
-#define CTOK_WORD 256
-
-/*----- Main code ---------------------------------------------------------*/
+void conf_undelim(scanner *sc, const char *d, const char *dd)
+{
+ sc->wbegin = d;
+ sc->wcont = dd;
+}
/* --- @token@ --- *
*
* 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++;
- else if (isspace((unsigned char)ch))
+ ch = scan(sc);
+
+ /* --- Deal with pushed-back tokens --- */
+
+ if (sc->head->tok) {
+ dstr_puts(&sc->d, sc->head->tok);
+ xfree(sc->head->tok);
+ sc->head->tok = 0;
+ sc->t = sc->head->t;
+ goto done;
+ }
+
+ else if (isspace(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 (!sc->wbegin || strchr(sc->wbegin, 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 || (sc->wcont && strchr(sc->wcont, ch)))
+ goto insert;
+ goto word;
+ default:
+ if (!q && isspace(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:
+ 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.
+ */
+
+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 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
*
- * Arguments: @scanner *sc@ = pointer to scanner (for error reporting)
- * @const char *p@ = pointer to port name
+ * Returns: Index into list, zero-based, or @-1@.
*
- * Returns: Port number (network byte order)
+ * Use: Checks whether the current token is a string which matches
+ * one of the comma-separated items given. The return value is
+ * the index (zero-based) of the matched string in the list.
*
- * Use: Converts a textual port name or number into a usable thing.
+ * The flags control the behaviour if no exact match is found.
+ * If @ENUM_ABBREV@ is set, and the current token is a left
+ * substring of exactly one of the possibilities, then that one
+ * is chosen. If @ENUM_NONE@ is set, the value @-1@ is
+ * returned; otherwise an error is reported and the program is
+ * terminated.
*/
-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: 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.
*
- * Use: Reads a local or global configuration statement.
+ * 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;
-
- /* --- Access control limitations --- */
-
- 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;
-
- /* --- Find the host or network address --- */
-
+ if (sc->t == CTOK_WORD && strcmp(p, sc->d.buf) == 0) {
token(sc);
- if (sc->t == CTOK_WORD && strcmp(sc->d.buf, "from") == 0)
+ if (sc->t == '.')
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);
-
- /* --- Find the netmask, if any --- */
-
- if (sc->t != '/')
- mask.s_addr = ~0ul;
- else {
- 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
- }
- token(sc);
- }
-
- /* --- Add the access control entry --- */
-
- acl_add(a, act, addr, mask);
+ return (1);
}
-
- /* --- Anything unrecognized --- */
-
- else
- error(sc, "parse error, unknown configuration keyword `%s'", sc->d.buf);
+ return (0);
}
-/* --- @conf_parse@ --- *
+/* --- @conf_name@ --- *
*
- * Arguments: @void *scp@ = pointer to scanner definition
+ * Arguments: @scanner *sc@ = pointer to scanner
+ * @char delim@ = delimiter character to look for
+ * @dstr *d@ = pointer to dynamic string for output
*
* Returns: ---
*
- * Use: Parses a configuration file from the scanner.
+ * 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 `.').
*/
-void conf_parse(void *scp)
+void conf_name(scanner *sc, char delim, dstr *d)
{
- scanner *sc = scp;
-
- token(sc);
+ unsigned f = 0;
- for (;;) {
- if (sc->t == CTOK_EOF)
- break;
- if (sc->t != CTOK_WORD)
- error(sc, "parse error, keyword expected");
-
- /* --- Handle a forwarding request --- */
+#define f_ok 1u
+#define f_bra 2u
- 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 an optional opening bracket --- */
- /* --- Read the source port --- */
-
- 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);
+ if (sc->t == '[') {
+ token(sc);
+ f |= f_bra | f_ok;
+ }
- /* --- Read the destination address --- */
+ /* --- Do the main reading sequence --- */
+ do {
+ if (sc->t == delim) {
+ DPUTC(d, delim);
+ f |= f_ok;
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);
-
+ }
+ if (sc->t == CTOK_WORD) {
+ DPUTD(d, &sc->d);
+ f |= f_ok;
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);
-
- /* --- Make the socket --- */
-
- if ((fd = socket(PF_INET, 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));
- }
-
- /* --- Bind it to the right port --- */
-
- 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));
- }
-
- /* --- Set it to listen for connections --- */
-
- if (listen(fd, 5))
- error(sc, "couldn't listen on socket: %s", strerror(errno));
+ }
+ } while (sc->t == delim);
- /* --- Fill in a new listener --- */
+ /* --- Check that the string was OK --- */
- memcpy(&sin.sin_addr, h->h_addr, sizeof(struct in_addr));
- sin.sin_port = dp;
- l = listener_add(fd, sp, &sin);
+ if (!(f & f_ok))
+ error(sc, "parse error, name expected");
- /* --- Snarf access controls and other attributes --- */
+ /* --- Read a closing bracket --- */
+ if (f & f_bra) {
+ if (sc->t == ']')
token(sc);
- if (sc->t == '{') {
- acl_entry **a = &l->acl;
- 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);
- }
- }
-
- /* --- Other configuration is handled elsewhere --- */
-
else
- getconf(sc, 0, 0);
-
- if (sc->t == ';')
- token(sc);
+ error(sc, "parse error, missing `]'");
}
+ DPUTZ(d);
+
+#undef f_ok
+#undef f_bra
}
/*----- That's all, folks -------------------------------------------------*/