From 225d645d4ed08e3a529aa34b50ac8de7da2b88d8 Mon Sep 17 00:00:00 2001 From: Wietse Venema Date: Sun, 1 Nov 2020 00:00:00 -0500 Subject: [PATCH] postfix-3.6-20201101 --- postfix/HISTORY | 8 +- postfix/src/global/login_sender_match.c | 38 ++++-- postfix/src/global/login_sender_match.ref | 6 + postfix/src/global/mail_version.h | 2 +- postfix/src/postdrop/postdrop.c | 2 + postfix/src/util/Makefile.in | 8 +- postfix/src/util/mystrtok.c | 150 +++++++++++++++++++--- postfix/src/util/mystrtok.ref | 30 +++++ postfix/src/util/stringops.h | 1 + 9 files changed, 210 insertions(+), 35 deletions(-) create mode 100644 postfix/src/util/mystrtok.ref diff --git a/postfix/HISTORY b/postfix/HISTORY index 62eca85a5..e06a1efc7 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -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. diff --git a/postfix/src/global/login_sender_match.c b/postfix/src/global/login_sender_match.c index ded638d9b..e263762ef 100644 --- a/postfix/src/global/login_sender_match.c +++ b/postfix/src/global/login_sender_match.c @@ -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; diff --git a/postfix/src/global/login_sender_match.ref b/postfix/src/global/login_sender_match.ref index 8dbb262f6..20ea4832f 100644 --- a/postfix/src/global/login_sender_match.ref +++ b/postfix/src/global/login_sender_match.ref @@ -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 diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 588e5237c..acce9f9b3 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -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 diff --git a/postfix/src/postdrop/postdrop.c b/postfix/src/postdrop/postdrop.c index 0ebd92a9b..16657abe4 100644 --- a/postfix/src/postdrop/postdrop.c +++ b/postfix/src/postdrop/postdrop.c @@ -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. diff --git a/postfix/src/util/Makefile.in b/postfix/src/util/Makefile.in index 4a5bee332..b66a6e628 100644 --- a/postfix/src/util/Makefile.in +++ b/postfix/src/util/Makefile.in @@ -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 \ diff --git a/postfix/src/util/mystrtok.c b/postfix/src/util/mystrtok.c index 91e3c472e..963588737 100644 --- a/postfix/src/util/mystrtok.c +++ b/postfix/src/util/mystrtok.c @@ -14,6 +14,10 @@ /* 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 @@ -24,6 +28,11 @@ /* 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; + const struct testcase *tp; + char *actual; + int pass; + int fail; + int match; + int n; - 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); +#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 index 000000000..4f920f994 --- /dev/null +++ b/postfix/src/util/mystrtok.ref @@ -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 diff --git a/postfix/src/util/stringops.h b/postfix/src/util/stringops.h index c54a5268b..abd2641e8 100644 --- a/postfix/src/util/stringops.h +++ b/postfix/src/util/stringops.h @@ -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) \