Re: smtpd: tweak static table parser

2017-08-28 Thread Gilles Chehade
On Mon, Aug 28, 2017 at 05:27:03PM +0200, Eric Faurot wrote:
> 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.
> 

as discussed, i think it's a neat idea

the diff is ok gilles@ too


> 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.c14 Aug 2017 08:01:14 -  1.16
> +++ table_static.c16 Aug 2017 15:27:22 -
> @@ -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(, , 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(, " \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;
> 

-- 
Gilles Chehade

https://www.poolp.org  @poolpOrg



smtpd: tweak static table parser

2017-08-28 Thread Eric Faurot
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 -  1.16
+++ table_static.c  16 Aug 2017 15:27:22 -
@@ -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(, , 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(, " \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;