Hi,
The current static (file) table parser is a bit clumsy. It tries to
determine the type (mapping or list entry) of each line independently
by splitting on allowed separators (" \t:") without using any context,
then fails if the type is not what's expected. It's impossible to define
a list of ipv6 addresses for instance, since ':' is a valid separator.
This diff changes the parser logic. If the table is untyped, determine
its type by examining the first entry: if it contains a separator, type
is "mapping", otherwise type is "list". All entries are then parsed
according to the table type. The "list" type can also be forced by using
the "@list" directive in a comment. This allows to define list of entries
containing a separator.
Existing table files should still be working as expected.
As a bonus, parse errors are now logged with line number.
Eric.
Index: table_static.c
===================================================================
RCS file: /cvs/src/usr.sbin/smtpd/table_static.c,v
retrieving revision 1.16
diff -u -p -r1.16 table_static.c
--- table_static.c 14 Aug 2017 08:01:14 -0000 1.16
+++ table_static.c 16 Aug 2017 15:27:22 -0000
@@ -73,7 +73,8 @@ static int
table_static_config(struct table *t)
{
FILE *fp;
- char *buf = NULL;
+ char *buf = NULL, *p;
+ int lineno = 0;
size_t sz = 0;
ssize_t flen;
char *keyp;
@@ -90,14 +91,47 @@ table_static_config(struct table *t)
}
while ((flen = getline(&buf, &sz, fp)) != -1) {
+ lineno++;
if (buf[flen - 1] == '\n')
- buf[flen - 1] = '\0';
+ buf[--flen] = '\0';
keyp = buf;
- while (isspace((unsigned char)*keyp))
+ while (isspace((unsigned char)*keyp)) {
++keyp;
- if (*keyp == '\0' || *keyp == '#')
+ --flen;
+ }
+ if (*keyp == '\0')
+ continue;
+ while (isspace((unsigned char)keyp[flen - 1]))
+ keyp[--flen] = '\0';
+ if (*keyp == '#') {
+ if (t->t_type == T_NONE) {
+ keyp++;
+ while (isspace((unsigned char)*keyp))
+ ++keyp;
+ if (!strcmp(keyp, "@list"))
+ t->t_type = T_LIST;
+ }
continue;
+ }
+
+ if (t->t_type == T_NONE) {
+ for (p = keyp; *p; p++) {
+ if (*p == ' ' || *p == '\t' || *p == ':') {
+ t->t_type = T_HASH;
+ break;
+ }
+ }
+ if (t->t_type == T_NONE)
+ t->t_type = T_LIST;
+ }
+
+ if (t->t_type == T_LIST) {
+ table_add(t, keyp, NULL);
+ continue;
+ }
+
+ /* T_HASH */
valp = keyp;
strsep(&valp, " \t:");
if (valp) {
@@ -111,18 +145,20 @@ table_static_config(struct table *t)
if (*valp == '\0')
valp = NULL;
}
+ if (valp == NULL) {
+ log_warnx("%s: invalid map entry line %d", t->t_config,
+ lineno);
+ goto end;
+ }
- if (t->t_type == 0)
- t->t_type = (valp == keyp || valp == NULL) ? T_LIST :
- T_HASH;
+ table_add(t, keyp, valp);
+ }
- if ((valp == keyp || valp == NULL) && t->t_type == T_LIST)
- table_add(t, keyp, NULL);
- else if ((valp != keyp && valp != NULL) && t->t_type == T_HASH)
- table_add(t, keyp, valp);
- else
- goto end;
+ if (ferror(fp)) {
+ log_warn("%s: getline", t->t_config);
+ goto end;
}
+
/* Accept empty alias files; treat them as hashes */
if (t->t_type == T_NONE && t->t_backend->services & K_ALIAS)
t->t_type = T_HASH;