]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.6-20201101
authorWietse Venema <wietse@porcupine.org>
Sun, 1 Nov 2020 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <postfix-users@dukhovni.org>
Sun, 8 Nov 2020 03:01:10 +0000 (01:01 -0200)
postfix/HISTORY
postfix/src/global/login_sender_match.c
postfix/src/global/login_sender_match.ref
postfix/src/global/mail_version.h
postfix/src/postdrop/postdrop.c
postfix/src/util/Makefile.in
postfix/src/util/mystrtok.c
postfix/src/util/mystrtok.ref [new file with mode: 0644]
postfix/src/util/stringops.h

index 62eca85a531ec26ceed086948a440a3d74006cc6..e06a1efc769401b61bc0602e331e09f785e976b1 100644 (file)
@@ -25251,4 +25251,10 @@ Apologies for any names omitted.
 
        Cleanup: changed the postdrop numerical UID prefix from "#"
        to "uid:", and tweaked some local_login_sender_maps
-       documentatin. Files: proto/postconf.proto, postdrop/postdrop.c.
+       documentation. Files: proto/postconf.proto, postdrop/postdrop.c.
+
+20201031
+
+       Cleanup: don't split a space-comma separated address list
+       on on space or comma inside a quoted string. Files:
+       util/mystrtok.c, util/mystetok.ref, global/login_sender_match.c.
index ded638d9b3698ce2ca722770b41357c9263494ec..e263762ef42f77f0b6309861583b59cd1817eba7 100644 (file)
@@ -53,7 +53,7 @@ r*    The lookup table(s) with (login name, sender patterns) entries.
 /* .IP ext_delimiters
 /*     The set of address extension delimiters.
 /* .IP null_sender
-/*     If a sender pattern equals the null_sender pattern, then 
+/*     If a sender pattern equals the null_sender pattern, then
 /*     the empty address is matched.
 /* .IP wildcard
 /*     Null pointer, or non-empty string with a wildcard pattern.
@@ -196,16 +196,16 @@ int     login_sender_match(LOGIN_SENDER_MATCH *lsm, const char *login_name,
                                      /* flags= */ 0)) != 0) {
 
        /*
-        * Match the sender. TODO: don't break a sender pattern on a
-        * comma/space inside a quoted localpart.
+        * Match the sender. Don't break a sender pattern between double
+        * quotes.
         */
        cp = saved_sender_patterns = mystrdup(sender_patterns);
        while (found_or_error == LSM_STAT_NOTFOUND
-              && (sender_pattern = mystrtok(&cp, CHARS_COMMA_SP)) != 0) {
+              && (sender_pattern = mystrtokdq(&cp, CHARS_COMMA_SP)) != 0) {
            /* Special pattern: @domain. */
            if (*sender_pattern == '@') {
                if ((at_sender_domain = strrchr(sender_addr, '@')) != 0
-                   && strcasecmp_utf8(sender_pattern, at_sender_domain) == 0)
+                 && strcasecmp_utf8(sender_pattern, at_sender_domain) == 0)
                    found_or_error = LSM_STAT_FOUND;
            }
            /* Special pattern: wildcard. */
@@ -218,13 +218,15 @@ int     login_sender_match(LOGIN_SENDER_MATCH *lsm, const char *login_name,
                    found_or_error = LSM_STAT_FOUND;
            }
            /* Literal pattern: match the stripped and externalized sender. */
-           if (ext_stripped_sender == 0)
-               ext_stripped_sender =
-                   STR(strip_externalize_addr(lsm->ext_stripped_sender,
-                                              sender_addr,
-                                              lsm->ext_delimiters));
-           if (strcasecmp_utf8(sender_pattern, ext_stripped_sender) == 0)
-               found_or_error = LSM_STAT_FOUND;
+           else {
+               if (ext_stripped_sender == 0)
+                   ext_stripped_sender =
+                       STR(strip_externalize_addr(lsm->ext_stripped_sender,
+                                                  sender_addr,
+                                                  lsm->ext_delimiters));
+               if (strcasecmp_utf8(sender_pattern, ext_stripped_sender) == 0)
+                   found_or_error = LSM_STAT_FOUND;
+           }
        }
        myfree(saved_sender_patterns);
     } else {
@@ -304,6 +306,18 @@ int     main(int argc, char **argv)
            "inline:{root=*, {uid:12345 = foo,foo@example.com}, bar=<>}",
            "+-", "<>", "*", "uid:12345", "foo", LSM_STAT_FOUND
        },
+       {"unknown \"other last\"",
+           "inline:{root=*, {foo = \"first last\",\"first last\"@example.com}, bar=<>}",
+           "+-", "<>", "*", "foo", "other last", LSM_STAT_NOTFOUND
+       },
+       {"bare \"first last\"",
+           "inline:{root=*, {foo = \"first last\",\"first last\"@example.com}, bar=<>}",
+           "+-", "<>", "*", "foo", "first last", LSM_STAT_FOUND
+       },
+       {"\"first last\"@domain",
+           "inline:{root=*, {foo = \"first last\",\"first last\"@example.com}, bar=<>}",
+           "+-", "<>", "*", "foo", "first last@example.com", LSM_STAT_FOUND
+       },
     };
     struct testcase *tp;
     int     act_return;
index 8dbb262f62c7c056d865b8434467576ffeb87659..20ea4832f392245bdf3225aad3f68abc4a6af280 100644 (file)
@@ -27,3 +27,9 @@ unknown: RUN test case 12 unknown uid:number
 unknown: PASS test 12
 unknown: RUN test case 13 known uid:number
 unknown: PASS test 13
+unknown: RUN test case 14 unknown "other last"
+unknown: PASS test 14
+unknown: RUN test case 15 bare "first last"
+unknown: PASS test 15
+unknown: RUN test case 16 "first last"@domain
+unknown: PASS test 16
index 588e5237cdb25c9324e31ab5e9ecf4e86bff4433..acce9f9b33fab5ef1c9df15bb8577cf2acd37f81 100644 (file)
@@ -20,7 +20,7 @@
   * Patches change both the patchlevel and the release date. Snapshots have no
   * patchlevel; they change the release date only.
   */
-#define MAIL_RELEASE_DATE      "20201026"
+#define MAIL_RELEASE_DATE      "20201101"
 #define MAIL_VERSION_NUMBER    "3.6"
 
 #ifdef SNAPSHOT
index 0ebd92a9ba5a4f98991c5da909e7de3ee876c2b7..16657abe402dfff79b39b1b120f6d6429c824dca 100644 (file)
@@ -261,8 +261,10 @@ static int check_login_sender_acl(uid_t uid, VSTRING *sender_buf,
     /*
      * Optimization.
      */
+#ifndef SNAPSHOT
     if (strcmp(var_local_login_snd_maps, DEF_LOCAL_LOGIN_SND_MAPS) == 0)
        return (CLEANUP_STAT_OK);
+#endif
 
     /*
      * Get the username.
index 4a5bee332b9afd2af5a37c4b9abd22266bcc653e..b66a6e62895e4d0bf05710510d79726601e33159 100644 (file)
@@ -559,7 +559,8 @@ tests: all valid_hostname_test mac_expand_test dict_test unescape_test \
        miss_endif_pcre_test miss_endif_regexp_test split_qnameval_test \
        vstring_test vstream_test dict_pcre_file_test dict_regexp_file_test \
        dict_cidr_file_test dict_static_file_test dict_random_test \
-       dict_random_file_test dict_inline_file_test byte_mask_tests
+       dict_random_file_test dict_inline_file_test byte_mask_tests \
+       mystrtok_test
 
 root_tests:
 
@@ -957,6 +958,11 @@ vstream_test: vstream vstream_test.in vstream_test.ref
        diff vstream_test.ref vstream_test.tmp
        rm -f vstream_test.tmp
 
+mystrtok_test: mystrtok mystrtok.ref
+       $(SHLIB_ENV) ${VALGRIND} ./mystrtok >mystrtok.tmp 2>&1
+       diff mystrtok.ref mystrtok.tmp
+       rm -f mystrtok.tmp
+
 depend: $(MAKES)
        (sed '1,/^# do not edit/!d' Makefile.in; \
        set -e; for i in [a-z][a-z0-9]*.c; do \
index 91e3c472e0f059ef31d7e21ba2a8ff303eb5ab68..9635887379be40111801a59dd2e24d80085e399b 100644 (file)
 /*     char    **bufp;
 /*     const char *delimiters;
 /*     const char *parens;
+/*
+/*     char    *mystrtokdq(bufp, delimiters)
+/*     char    **bufp;
+/*     const char *delimiters;
 /* DESCRIPTION
 /*     mystrtok() splits a buffer on the specified \fIdelimiters\fR.
 /*     Tokens are delimited by runs of delimiters, so this routine
 /*     opening and closing parenthesis (one of each).  The set of
 /*     \fIparens\fR must be distinct from the set of \fIdelimiters\fR.
 /*
+/*     mystrtokdq() is like mystrtok() but will not split text
+/*     between double quotes. The backslash character may be used
+/*     to escape characters. The double quote and backslash
+/*     character must not appear in the set of \fIdelimiters\fR.
+/*
 /*     The \fIbufp\fR argument specifies the start of the search; it
 /*     is updated with each call. The input is destroyed.
 /*
@@ -80,7 +89,7 @@ char   *mystrtok(char **src, const char *sep)
 char   *mystrtokq(char **src, const char *sep, const char *parens)
 {
     char   *start = *src;
-    static char   *cp;
+    static char *cp;
     int     ch;
     int     level;
 
@@ -99,7 +108,7 @@ char   *mystrtokq(char **src, const char *sep, const char *parens)
     for (level = 0, cp = start; (ch = *(unsigned char *) cp) != 0; cp++) {
        if (ch == parens[0]) {
            level++;
-        } else if (level > 0 && ch == parens[1]) {
+       } else if (level > 0 && ch == parens[1]) {
            level--;
        } else if (level == 0 && strchr(sep, ch) != 0) {
            *cp++ = 0;
@@ -110,34 +119,135 @@ char   *mystrtokq(char **src, const char *sep, const char *parens)
     return (start);
 }
 
+/* mystrtokdq - safe tokenizer, double quote and backslash support */
+
+char   *mystrtokdq(char **src, const char *sep)
+{
+    char   *cp = *src;
+    char   *start;
+
+    /*
+     * Skip leading delimiters.
+     */
+    cp += strspn(cp, sep);
+
+    /*
+     * Skip to next unquoted space or comma.
+     */
+    if (*cp == 0) {
+       start = 0;
+    } else {
+       int     in_quotes;
+
+       for (in_quotes = 0, start = cp; *cp; cp++) {
+           if (*cp == '\\') {
+               if (*++cp == 0)
+                   break;
+           } else if (*cp == '"') {
+               in_quotes = !in_quotes;
+           } else if (!in_quotes && strchr(sep, *(unsigned char *) cp) != 0) {
+               *cp++ = 0;
+               break;
+           }
+       }
+    }
+    *src = cp;
+    return (start);
+}
+
 #ifdef TEST
 
  /*
-  * Test program: read lines from stdin, split on whitespace.
+  * Test program.
   */
-#include "vstring.h"
-#include "vstream.h"
-#include "vstring_vstream.h"
+#include "msg.h"
+#include "mymalloc.h"
+
+ /*
+  * The following needs to be large enough to include a null terminator in
+  * every testcase.expected field.
+  */
+#define EXPECT_SIZE    5
+
+struct testcase {
+    const char *action;
+    const char *input;
+    const char *expected[EXPECT_SIZE];
+};
+static const struct testcase testcases[] = {
+    {"mystrtok", ""},
+    {"mystrtok", "  foo  ", {"foo"}},
+    {"mystrtok", "  foo  bar  ", {"foo", "bar"}},
+    {"mystrtokq", ""},
+    {"mystrtokq", "foo bar", {"foo", "bar"}},
+    {"mystrtokq", "{ bar }  ", {"{ bar }"}},
+    {"mystrtokq", "foo { bar } baz", {"foo", "{ bar }", "baz"}},
+    {"mystrtokq", "foo{ bar } baz", {"foo{ bar }", "baz"}},
+    {"mystrtokq", "foo { bar }baz", {"foo", "{ bar }baz"}},
+    {"mystrtokdq", ""},
+    {"mystrtokdq", "  foo  ", {"foo"}},
+    {"mystrtokdq", "  foo  bar  ", {"foo", "bar"}},
+    {"mystrtokdq", "  foo\\ bar  ", {"foo\\ bar"}},
+    {"mystrtokdq", "  foo \\\" bar", {"foo", "\\\"", "bar"}},
+    {"mystrtokdq", "  foo \" bar baz\"  ", {"foo", "\" bar baz\""}},
+};
 
 int     main(void)
 {
-    VSTRING *vp = vstring_alloc(100);
-    char   *start;
-    char   *str;
-
-    while (vstring_fgets(vp, VSTREAM_IN) && VSTRING_LEN(vp) > 0) {
-       start = vstring_str(vp);
-       if (strchr(start, CHARS_BRACE[0]) == 0) {
-           while ((str = mystrtok(&start, CHARS_SPACE)) != 0)
-               vstream_printf(">%s<\n", str);
-       } else {
-           while ((str = mystrtokq(&start, CHARS_SPACE, CHARS_BRACE)) != 0)
-               vstream_printf(">%s<\n", str);
+    const struct testcase *tp;
+    char   *actual;
+    int     pass;
+    int     fail;
+    int     match;
+    int     n;
+
+#define NUM_TESTS       sizeof(testcases)/sizeof(testcases[0])
+#define STR_OR_NULL(s) ((s) ? (s) : "null")
+
+    for (pass = fail = 0, tp = testcases; tp < testcases + NUM_TESTS; tp++) {
+       char   *saved_input = mystrdup(tp->input);
+       char   *cp = saved_input;
+
+       msg_info("RUN test case %ld %s >%s<",
+                (long) (tp - testcases), tp->action, tp->input);
+#if 0
+       msg_info("action=%s", tp->action);
+       msg_info("input=%s", tp->input);
+       for (n = 0; tp->expected[n]; tp++)
+           msg_info("expected[%d]=%s", n, tp->expected[n]);
+#endif
+
+       for (n = 0; n < EXPECT_SIZE; n++) {
+           if (strcmp(tp->action, "mystrtok") == 0) {
+               actual = mystrtok(&cp, CHARS_SPACE);
+           } else if (strcmp(tp->action, "mystrtokq") == 0) {
+               actual = mystrtokq(&cp, CHARS_SPACE, CHARS_BRACE);
+           } else if (strcmp(tp->action, "mystrtokdq") == 0) {
+               actual = mystrtokdq(&cp, CHARS_SPACE);
+           } else {
+               msg_panic("invalid command: %s", tp->action);
+           }
+           if ((match = (actual && tp->expected[n]) ?
+                (strcmp(actual, tp->expected[n]) == 0) :
+                (actual == tp->expected[n])) != 0) {
+               if (actual == 0) {
+                   msg_info("PASS test %ld", (long) (tp - testcases));
+                   pass++;
+                   break;
+               }
+           } else {
+               msg_warn("expected: >%s<, got: >%s<",
+                        STR_OR_NULL(tp->expected[n]), STR_OR_NULL(actual));
+               msg_info("FAIL test %ld", (long) (tp - testcases));
+               fail++;
+               break;
+           }
        }
-       vstream_fflush(VSTREAM_OUT);
+       if (n >= EXPECT_SIZE)
+           msg_panic("need to increase EXPECT_SIZE");
+       myfree(saved_input);
     }
-    vstring_free(vp);
-    return (0);
+    return (fail > 0);
 }
 
 #endif
diff --git a/postfix/src/util/mystrtok.ref b/postfix/src/util/mystrtok.ref
new file mode 100644 (file)
index 0000000..4f920f9
--- /dev/null
@@ -0,0 +1,30 @@
+unknown: RUN test case 0 mystrtok ><
+unknown: PASS test 0
+unknown: RUN test case 1 mystrtok >  foo  <
+unknown: PASS test 1
+unknown: RUN test case 2 mystrtok >  foo  bar  <
+unknown: PASS test 2
+unknown: RUN test case 3 mystrtokq ><
+unknown: PASS test 3
+unknown: RUN test case 4 mystrtokq >foo bar<
+unknown: PASS test 4
+unknown: RUN test case 5 mystrtokq >{ bar }  <
+unknown: PASS test 5
+unknown: RUN test case 6 mystrtokq >foo { bar } baz<
+unknown: PASS test 6
+unknown: RUN test case 7 mystrtokq >foo{ bar } baz<
+unknown: PASS test 7
+unknown: RUN test case 8 mystrtokq >foo { bar }baz<
+unknown: PASS test 8
+unknown: RUN test case 9 mystrtokdq ><
+unknown: PASS test 9
+unknown: RUN test case 10 mystrtokdq >  foo  <
+unknown: PASS test 10
+unknown: RUN test case 11 mystrtokdq >  foo  bar  <
+unknown: PASS test 11
+unknown: RUN test case 12 mystrtokdq >  foo\ bar  <
+unknown: PASS test 12
+unknown: RUN test case 13 mystrtokdq >  foo \" bar<
+unknown: PASS test 13
+unknown: RUN test case 14 mystrtokdq >  foo " bar baz"  <
+unknown: PASS test 14
index c54a5268b82ee0480902e205586fb054b4acf735..abd2641e87b52644d1a33c1f43fd6ae9141b946f 100644 (file)
@@ -30,6 +30,7 @@ extern char *trimblanks(char *, ssize_t);
 extern char *concatenate(const char *,...);
 extern char *mystrtok(char **, const char *);
 extern char *mystrtokq(char **, const char *, const char *);
+extern char *mystrtokdq(char **, const char *);
 extern char *translit(char *, const char *, const char *);
 
 #define printable(string, replacement) \