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;