]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.9-20231024
authorWietse Venema <wietse@porcupine.org>
Tue, 24 Oct 2023 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <ietf-dane@dukhovni.org>
Sat, 28 Oct 2023 15:22:30 +0000 (11:22 -0400)
postfix/HISTORY
postfix/src/global/mail_version.h
postfix/src/postscreen/postscreen_smtpd.c
postfix/src/smtpd/smtpd_sasl_glue.c
postfix/src/util/Makefile.in
postfix/src/util/printable.c
postfix/src/util/readlline.c
postfix/src/util/valid_utf8_string.c

index fbd6ea037f8ccdce1a56785cc1addae98d9a1b89..5f1d4937dc9a008e4e3d662e22d776d4de26498e 100644 (file)
@@ -27491,3 +27491,13 @@ Apologies for any names omitted.
        src/util/dict_utf8.c, src/util/midna_domain.c,
        src/util/printable.c, src/util/stringops.h,
        src/util/valid_utf8_string.c.
+
+       Cleanup: added unit tests to the readlline module, with
+       multiline input that contains embedded comments, input that
+       contains a null byte, text not ending in newline. File:
+       readlline.c.
+
+20231024
+
+       Cleanup: emit place holder text when no SASL authentication
+       failure reason is available. File: smtpd/smtpd_sasl_glue.c.
index 1a5567833f3c0e3a983ebc19fd18c1775b4fc90f..94d0b412556517f066a9bcb1f9a870850136dcc4 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      "20231012"
+#define MAIL_RELEASE_DATE      "20231024"
 #define MAIL_VERSION_NUMBER    "3.9"
 
 #ifdef SNAPSHOT
index 1e859acc9fc49f6b81be059f3eb524f287fd768e..6b72626a751c93f6068691416769cd46c44d4050 100644 (file)
@@ -930,7 +930,9 @@ static void psc_smtpd_read_event(int event, void *context)
        }
 
        /*
-        * Avoid complaints from Postfix maps about malformed content.
+        * Avoid complaints from Postfix maps about malformed content. Note:
+        * this will stop at the first null byte, just like the code that
+        * parses the command name or command arguments.
         */
 #define PSC_BAD_UTF8(str) \
        (var_smtputf8_enable && !valid_utf8_stringz(str))
index 6586e005b6270e1843a8892ac8ed94c4f42a87b2..7103d452365b20df88656286938e511c0923e47b 100644 (file)
@@ -346,8 +346,8 @@ int     smtpd_sasl_authenticate(SMTPD_STATE *state,
     if (status != XSASL_AUTH_DONE) {
        sasl_username = xsasl_server_get_username(state->sasl_server);
        msg_warn("%s: SASL %s authentication failed: %s, sasl_username=%s",
-                state->namaddr, sasl_method,
-                STR(state->sasl_reply),
+                state->namaddr, sasl_method, *STR(state->sasl_reply) ?
+                STR(state->sasl_reply) : "(reason unavailable)",
                 sasl_username ? sasl_username : "(unavailable)");
        /* RFC 4954 Section 6. */
        if (status == XSASL_AUTH_TEMP)
index 43894246ee143787da8cd5a6d6e338bc02b9ef14..f4c8e6e5835113474a0977d090511c4a3e061bb0 100644 (file)
@@ -145,7 +145,7 @@ TESTPROG= dict_open dup2_pass_on_exec events exec_command fifo_open \
        vstream timecmp dict_cache midna_domain casefold strcasecmp_utf8 \
        vbuf_print split_qnameval vstream msg_logger byte_mask \
        known_tcp_ports dict_stream find_inet binhash hash_fnv argv \
-       clean_env inet_prefix_top printable
+       clean_env inet_prefix_top printable readlline
 PLUGIN_MAP_SO = $(LIB_PREFIX)pcre$(LIB_SUFFIX) $(LIB_PREFIX)lmdb$(LIB_SUFFIX) \
        $(LIB_PREFIX)cdb$(LIB_SUFFIX) $(LIB_PREFIX)sdbm$(LIB_SUFFIX)
 HTABLE_FIX = NORANDOMIZE=1
@@ -370,6 +370,11 @@ printable: $(LIB)
        $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
        mv junk $@.o
 
+readlline: $(LIB)
+       mv $@.o junk
+       $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+       mv junk $@.o
+
 hex_quote: $(LIB)
        mv $@.o junk
        $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
@@ -624,7 +629,7 @@ tests: all valid_hostname_test mac_expand_test dict_test unescape_test \
        miss_endif_regexp_test split_qnameval_test vstring_test \
        vstream_test byte_mask_tests mystrtok_test known_tcp_ports_test \
        binhash_test argv_test inet_prefix_top_test printable_test \
-       valid_utf8_string_test
+       valid_utf8_string_test readlline_test
  
 dict_tests: all dict_test \
        dict_pcre_tests dict_cidr_test dict_thash_test dict_static_test \
@@ -659,6 +664,9 @@ unescape_test: unescape unescape.in unescape.ref
 printable_test: printable
        $(SHLIB_ENV) ${VALGRIND} ./printable 
 
+readlline_test: readlline
+       $(SHLIB_ENV) ${VALGRIND} ./readlline 
+
 valid_utf8_string_test: valid_utf8_string
        $(SHLIB_ENV) ${VALGRIND} ./valid_utf8_string 
 
index b944daa13b7ef7a9d233c8e01157e5b70d325161..0e1ae19543b76d97b97544e77d7c4ba0036cbc40 100644 (file)
@@ -190,6 +190,7 @@ int     main(int argc, char **argv)
     for (pass = fail = 0, tp = testcases; tp < testcases + NUM_TESTS; tp++) {
        char   *input;
        char   *actual;
+       int     ok = 0;
 
        /*
         * Notes:
@@ -206,13 +207,17 @@ int     main(int argc, char **argv)
        if (strcmp(actual, tp->expected) != 0) {
            vstream_fprintf(VSTREAM_ERR, "input: >%s<, got: >%s<, want: >%s<\n",
                            tp->input, actual, tp->expected);
-           vstream_fprintf(VSTREAM_ERR, "FAIL %s\n", tp->name);
-           fail++;
        } else {
            vstream_fprintf(VSTREAM_ERR, "input: >%s<, got and want: >%s<\n",
                            tp->input, actual);
+           ok = 1;
+       }
+       if (ok) {
            vstream_fprintf(VSTREAM_ERR, "PASS %s\n", tp->name);
            pass++;
+       } else {
+           vstream_fprintf(VSTREAM_ERR, "FAIL %s\n", tp->name);
+           fail++;
        }
        myfree(input);
     }
index 015877a2f28c220b2ad41b8ac3423daa0ba92ac8..721b75f222e6b2204634f2339d62f9e9b26315d9 100644 (file)
@@ -85,9 +85,15 @@ VSTRING *readllines(VSTRING *buf, VSTREAM *fp, int *lineno, int *first_line)
     int     next;
     ssize_t start;
     char   *cp;
+    int     my_lineno = 0, my_first_line, got_null = 0;
 
     VSTRING_RESET(buf);
 
+    if (lineno == 0)
+       lineno = &my_lineno;
+    if (first_line == 0)
+       first_line = &my_first_line;
+
     /*
      * Ignore comment lines, all whitespace lines, and empty lines. Terminate
      * at EOF or at the beginning of the next logical line.
@@ -95,16 +101,19 @@ VSTRING *readllines(VSTRING *buf, VSTREAM *fp, int *lineno, int *first_line)
     for (;;) {
        /* Read one line, possibly not newline terminated. */
        start = LEN(buf);
-       while ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF && ch != '\n')
+       while ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF && ch != '\n') {
            VSTRING_ADDCH(buf, ch);
-       if (lineno != 0 && (ch == '\n' || LEN(buf) > start))
+           if (ch == 0)
+               got_null = 1;
+       }
+       if (ch == '\n' || LEN(buf) > start)
            *lineno += 1;
        /* Ignore comment line, all whitespace line, or empty line. */
        for (cp = STR(buf) + start; cp < END(buf) && ISSPACE(*cp); cp++)
             /* void */ ;
        if (cp == END(buf) || *cp == '#')
            vstring_truncate(buf, start);
-       else if (start == 0 && lineno != 0 && first_line != 0)
+       if (start == 0)
            *first_line = *lineno;
        /* Terminate at EOF or at the beginning of the next logical line. */
        if (ch == VSTREAM_EOF)
@@ -118,6 +127,20 @@ VSTRING *readllines(VSTRING *buf, VSTREAM *fp, int *lineno, int *first_line)
     }
     VSTRING_TERMINATE(buf);
 
+    /*
+     * This code does not care about embedded null bytes, but callers do.
+     */
+    if (got_null) {
+       const char *why = "text after null byte may be ignored";
+
+       if (*first_line == *lineno)
+           msg_warn("%s, line %d: %s",
+                    VSTREAM_PATH(fp), *lineno, why);
+       else
+           msg_warn("%s, line %d-%d: %s",
+                    VSTREAM_PATH(fp), *first_line, *lineno, why);
+    }
+
     /*
      * Invalid input: continuing text without preceding text. Allowing this
      * would complicate "postconf -e", which implements its own multi-line
@@ -136,3 +159,205 @@ VSTRING *readllines(VSTRING *buf, VSTREAM *fp, int *lineno, int *first_line)
      */
     return (LEN(buf) > 0 ? buf : 0);
 }
+
+ /*
+  * Stand-alone test program.
+  */
+#ifdef TEST
+#include <stdlib.h>
+#include <string.h>
+#include <msg.h>
+#include <msg_vstream.h>
+#include <stringops.h>
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+  * Test cases. Note: the input and exp_output fields are converted with
+  * unescape(). Embedded null bytes must be specified as \\0.
+  */
+struct testcase {
+    const char *name;
+    const char *input;
+    const char *exp_output;
+    int     exp_first_line;
+    int     exp_last_line;
+};
+
+static const struct testcase testcases[] = {
+    {"leading space before non-comment",
+       " abcde\nfghij\n",
+       "fghij",
+       2, 2
+       /* Expect "logical line must not start with whitespace" */
+    },
+    {"leading space before leading comment",
+       " #abcde\nfghij\n",
+       "fghij",
+       2, 2
+    },
+    {"leading #comment at beginning of line",
+       "#abc\ndef",
+       "def",
+       2, 2,
+    },
+    {"empty line before non-comment",
+       "\nabc\n",
+       "abc",
+       2, 2,
+    },
+    {"whitespace line before non-comment",
+       " \nabc\n",
+       "abc",
+       2, 2,
+    },
+    {"missing newline at end of non-comment",
+       "abc def",
+       "abc def",
+       1, 1,
+    },
+    {"missing newline at end of comment",
+       "#abc def",
+       "",
+       1, 1,
+    },
+    {"embedded null, single-line",
+       "abc\\0def",
+       "abc\\0def",
+       1, 1,
+       /* Expect "line 1: text after null byte may be ignored" */
+    },
+    {"embedded null, multiline",
+       "abc\\0\n def",
+       "abc\\0 def",
+       1, 2,
+       /* Expect "line 1-2: text after null byte may be ignored" */
+    },
+    {"embedded null in comment",
+       "#abc\\0\ndef",
+       "def",
+       2, 2,
+       /* Expect "line 2: text after null byte may be ignored" */
+    },
+    {"multiline input",
+       "abc\n def\n",
+       "abc def",
+       1, 2,
+    },
+    {"multiline input with embedded #comment after space",
+       "abc\n #def\n ghi",
+       "abc ghi",
+       1, 3,
+    },
+    {"multiline input with embedded #comment flush left",
+       "abc\n#def\n ghi",
+       "abc ghi",
+       1, 3,
+    },
+    {"multiline input with embedded whitespace line",
+       "abc\n \n ghi",
+       "abc ghi",
+       1, 3,
+    },
+    {"multiline input with embedded empty line",
+       "abc\n\n ghi",
+       "abc ghi",
+       1, 3,
+    },
+    {"multiline input with embedded #comment after space",
+       "abc\n #def\n",
+       "abc",
+       1, 2,
+    },
+    {"multiline input with embedded #comment flush left",
+       "abc\n#def\n",
+       "abc",
+       1, 2,
+    },
+    {"empty line at end of file",
+       "\n",
+       "",
+       1, 1,
+    },
+    {"whitespace line at end of file",
+       "\n \n",
+       "",
+       2, 2,
+    },
+    {"whitespace at end of file",
+       "abc\n ",
+       "abc",
+       1, 2,
+    },
+};
+
+int     main(int argc, char **argv)
+{
+    const struct testcase *tp;
+    VSTRING *inp_buf = vstring_alloc(100);
+    VSTRING *exp_buf = vstring_alloc(100);
+    VSTRING *out_buf = vstring_alloc(100);
+    VSTRING *esc_buf = vstring_alloc(100);
+    VSTREAM *fp;
+    int     last_line;
+    int     first_line;
+    int     pass;
+    int     fail;
+
+#define NUM_TESTS       sizeof(testcases)/sizeof(testcases[0])
+
+    msg_vstream_init(basename(argv[0]), VSTREAM_ERR);
+    util_utf8_enable = 1;
+
+    for (pass = fail = 0, tp = testcases; tp < testcases + NUM_TESTS; tp++) {
+       int     ok = 0;
+
+       vstream_fprintf(VSTREAM_ERR, "RUN  %s\n", tp->name);
+       unescape(inp_buf, tp->input);
+       unescape(exp_buf, tp->exp_output);
+       if ((fp = vstream_memopen(inp_buf, O_RDONLY)) == 0)
+           msg_panic("open memory stream for reading: %m");
+       vstream_control(fp, CA_VSTREAM_CTL_PATH("memory buffer"),
+                       CA_VSTREAM_CTL_END);
+       last_line = 0;
+       if (readllines(out_buf, fp, &last_line, &first_line) == 0) {
+           VSTRING_RESET(out_buf);
+           VSTRING_TERMINATE(out_buf);
+       }
+       if (LEN(out_buf) != LEN(exp_buf)) {
+           msg_warn("unexpected output length, got: %ld, want: %ld",
+                    (long) LEN(out_buf), (long) LEN(exp_buf));
+       } else if (memcmp(STR(out_buf), STR(exp_buf), LEN(out_buf)) != 0) {
+           msg_warn("unexpected output: got: >%s<, want: >%s<",
+                    STR(escape(esc_buf, STR(out_buf), LEN(out_buf))),
+                    tp->exp_output);
+       } else if (first_line != tp->exp_first_line) {
+           msg_warn("unexpected first_line: got: %d, want: %d",
+                    first_line, tp->exp_first_line);
+       } else if (last_line != tp->exp_last_line) {
+           msg_warn("unexpected last_line: got: %d, want: %d",
+                    last_line, tp->exp_last_line);
+       } else {
+           vstream_fprintf(VSTREAM_ERR, "got and want: >%s<\n",
+                           tp->exp_output);
+           ok = 1;
+       }
+       if (ok) {
+           vstream_fprintf(VSTREAM_ERR, "PASS %s\n", tp->name);
+           pass++;
+       } else {
+           vstream_fprintf(VSTREAM_ERR, "FAIL %s\n", tp->name);
+           fail++;
+       }
+       vstream_fclose(fp);
+    }
+    vstring_free(inp_buf);
+    vstring_free(exp_buf);
+    vstring_free(out_buf);
+    vstring_free(esc_buf);
+
+    msg_info("PASS=%d FAIL=%d", pass, fail);
+    return (fail > 0);
+}
+
+#endif
index 4cea0219b56a0d52b476365c71b72002432f5a5f..f5b4ff4480a2693302e48f540e0daaaaae3de9e3 100644 (file)
@@ -198,6 +198,7 @@ int     main(int argc, char **argv)
     for (pass = fail = 0, tp = testcases; tp < testcases + NUM_TESTS; tp++) {
        int     actual_l;
        int     actual_z;
+       int     ok = 0;
 
        /*
         * Notes:
@@ -214,20 +215,22 @@ int     main(int argc, char **argv)
                          "input: >%s<, 'actual_l' got: >%s<, want: >%s<\n",
                            tp->input, valid_to_str(actual_l),
                            valid_to_str(tp->expected));
-           vstream_fprintf(VSTREAM_ERR, "FAIL %s\n", tp->name);
-           fail++;
        } else if (actual_z != tp->expected) {
            vstream_fprintf(VSTREAM_ERR,
                          "input: >%s<, 'actual_z' got: >%s<, want: >%s<\n",
                            tp->input, valid_to_str(actual_z),
                            valid_to_str(tp->expected));
-           vstream_fprintf(VSTREAM_ERR, "FAIL %s\n", tp->name);
-           fail++;
        } else {
            vstream_fprintf(VSTREAM_ERR, "input: >%s<, got and want: >%s<\n",
                            tp->input, valid_to_str(actual_l));
+           ok = 1;
+       }
+       if (ok) {
            vstream_fprintf(VSTREAM_ERR, "PASS %s\n", tp->name);
            pass++;
+       } else {
+           vstream_fprintf(VSTREAM_ERR, "FAIL %s\n", tp->name);
+           fail++;
        }
     }
     msg_info("PASS=%d FAIL=%d", pass, fail);