2
0
mirror of https://github.com/vdukhovni/postfix synced 2025-08-22 09:57:34 +00:00

postfix-3.9-20231024

This commit is contained in:
Wietse Venema 2023-10-24 00:00:00 -05:00 committed by Viktor Dukhovni
parent 57d8e8fa35
commit d31cde7580
8 changed files with 268 additions and 15 deletions

View File

@ -27491,3 +27491,13 @@ Apologies for any names omitted.
src/util/dict_utf8.c, src/util/midna_domain.c, src/util/dict_utf8.c, src/util/midna_domain.c,
src/util/printable.c, src/util/stringops.h, src/util/printable.c, src/util/stringops.h,
src/util/valid_utf8_string.c. 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.

View File

@ -20,7 +20,7 @@
* Patches change both the patchlevel and the release date. Snapshots have no * Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only. * patchlevel; they change the release date only.
*/ */
#define MAIL_RELEASE_DATE "20231012" #define MAIL_RELEASE_DATE "20231024"
#define MAIL_VERSION_NUMBER "3.9" #define MAIL_VERSION_NUMBER "3.9"
#ifdef SNAPSHOT #ifdef SNAPSHOT

View 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) \ #define PSC_BAD_UTF8(str) \
(var_smtputf8_enable && !valid_utf8_stringz(str)) (var_smtputf8_enable && !valid_utf8_stringz(str))

View File

@ -346,8 +346,8 @@ int smtpd_sasl_authenticate(SMTPD_STATE *state,
if (status != XSASL_AUTH_DONE) { if (status != XSASL_AUTH_DONE) {
sasl_username = xsasl_server_get_username(state->sasl_server); sasl_username = xsasl_server_get_username(state->sasl_server);
msg_warn("%s: SASL %s authentication failed: %s, sasl_username=%s", msg_warn("%s: SASL %s authentication failed: %s, sasl_username=%s",
state->namaddr, sasl_method, state->namaddr, sasl_method, *STR(state->sasl_reply) ?
STR(state->sasl_reply), STR(state->sasl_reply) : "(reason unavailable)",
sasl_username ? sasl_username : "(unavailable)"); sasl_username ? sasl_username : "(unavailable)");
/* RFC 4954 Section 6. */ /* RFC 4954 Section 6. */
if (status == XSASL_AUTH_TEMP) if (status == XSASL_AUTH_TEMP)

View 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 \ vstream timecmp dict_cache midna_domain casefold strcasecmp_utf8 \
vbuf_print split_qnameval vstream msg_logger byte_mask \ vbuf_print split_qnameval vstream msg_logger byte_mask \
known_tcp_ports dict_stream find_inet binhash hash_fnv argv \ 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) \ PLUGIN_MAP_SO = $(LIB_PREFIX)pcre$(LIB_SUFFIX) $(LIB_PREFIX)lmdb$(LIB_SUFFIX) \
$(LIB_PREFIX)cdb$(LIB_SUFFIX) $(LIB_PREFIX)sdbm$(LIB_SUFFIX) $(LIB_PREFIX)cdb$(LIB_SUFFIX) $(LIB_PREFIX)sdbm$(LIB_SUFFIX)
HTABLE_FIX = NORANDOMIZE=1 HTABLE_FIX = NORANDOMIZE=1
@ -370,6 +370,11 @@ printable: $(LIB)
$(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
mv junk $@.o mv junk $@.o
readlline: $(LIB)
mv $@.o junk
$(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
mv junk $@.o
hex_quote: $(LIB) hex_quote: $(LIB)
mv $@.o junk mv $@.o junk
$(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) $(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 \ miss_endif_regexp_test split_qnameval_test vstring_test \
vstream_test byte_mask_tests mystrtok_test known_tcp_ports_test \ vstream_test byte_mask_tests mystrtok_test known_tcp_ports_test \
binhash_test argv_test inet_prefix_top_test printable_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_tests: all dict_test \
dict_pcre_tests dict_cidr_test dict_thash_test dict_static_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 printable_test: printable
$(SHLIB_ENV) ${VALGRIND} ./printable $(SHLIB_ENV) ${VALGRIND} ./printable
readlline_test: readlline
$(SHLIB_ENV) ${VALGRIND} ./readlline
valid_utf8_string_test: valid_utf8_string valid_utf8_string_test: valid_utf8_string
$(SHLIB_ENV) ${VALGRIND} ./valid_utf8_string $(SHLIB_ENV) ${VALGRIND} ./valid_utf8_string

View File

@ -190,6 +190,7 @@ int main(int argc, char **argv)
for (pass = fail = 0, tp = testcases; tp < testcases + NUM_TESTS; tp++) { for (pass = fail = 0, tp = testcases; tp < testcases + NUM_TESTS; tp++) {
char *input; char *input;
char *actual; char *actual;
int ok = 0;
/* /*
* Notes: * Notes:
@ -206,13 +207,17 @@ int main(int argc, char **argv)
if (strcmp(actual, tp->expected) != 0) { if (strcmp(actual, tp->expected) != 0) {
vstream_fprintf(VSTREAM_ERR, "input: >%s<, got: >%s<, want: >%s<\n", vstream_fprintf(VSTREAM_ERR, "input: >%s<, got: >%s<, want: >%s<\n",
tp->input, actual, tp->expected); tp->input, actual, tp->expected);
vstream_fprintf(VSTREAM_ERR, "FAIL %s\n", tp->name);
fail++;
} else { } else {
vstream_fprintf(VSTREAM_ERR, "input: >%s<, got and want: >%s<\n", vstream_fprintf(VSTREAM_ERR, "input: >%s<, got and want: >%s<\n",
tp->input, actual); tp->input, actual);
ok = 1;
}
if (ok) {
vstream_fprintf(VSTREAM_ERR, "PASS %s\n", tp->name); vstream_fprintf(VSTREAM_ERR, "PASS %s\n", tp->name);
pass++; pass++;
} else {
vstream_fprintf(VSTREAM_ERR, "FAIL %s\n", tp->name);
fail++;
} }
myfree(input); myfree(input);
} }

View File

@ -85,9 +85,15 @@ VSTRING *readllines(VSTRING *buf, VSTREAM *fp, int *lineno, int *first_line)
int next; int next;
ssize_t start; ssize_t start;
char *cp; char *cp;
int my_lineno = 0, my_first_line, got_null = 0;
VSTRING_RESET(buf); 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 * Ignore comment lines, all whitespace lines, and empty lines. Terminate
* at EOF or at the beginning of the next logical line. * 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 (;;) { for (;;) {
/* Read one line, possibly not newline terminated. */ /* Read one line, possibly not newline terminated. */
start = LEN(buf); 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); 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; *lineno += 1;
/* Ignore comment line, all whitespace line, or empty line. */ /* Ignore comment line, all whitespace line, or empty line. */
for (cp = STR(buf) + start; cp < END(buf) && ISSPACE(*cp); cp++) for (cp = STR(buf) + start; cp < END(buf) && ISSPACE(*cp); cp++)
/* void */ ; /* void */ ;
if (cp == END(buf) || *cp == '#') if (cp == END(buf) || *cp == '#')
vstring_truncate(buf, start); vstring_truncate(buf, start);
else if (start == 0 && lineno != 0 && first_line != 0) if (start == 0)
*first_line = *lineno; *first_line = *lineno;
/* Terminate at EOF or at the beginning of the next logical line. */ /* Terminate at EOF or at the beginning of the next logical line. */
if (ch == VSTREAM_EOF) if (ch == VSTREAM_EOF)
@ -118,6 +127,20 @@ VSTRING *readllines(VSTRING *buf, VSTREAM *fp, int *lineno, int *first_line)
} }
VSTRING_TERMINATE(buf); 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 * Invalid input: continuing text without preceding text. Allowing this
* would complicate "postconf -e", which implements its own multi-line * 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); 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

View File

@ -198,6 +198,7 @@ int main(int argc, char **argv)
for (pass = fail = 0, tp = testcases; tp < testcases + NUM_TESTS; tp++) { for (pass = fail = 0, tp = testcases; tp < testcases + NUM_TESTS; tp++) {
int actual_l; int actual_l;
int actual_z; int actual_z;
int ok = 0;
/* /*
* Notes: * Notes:
@ -214,20 +215,22 @@ int main(int argc, char **argv)
"input: >%s<, 'actual_l' got: >%s<, want: >%s<\n", "input: >%s<, 'actual_l' got: >%s<, want: >%s<\n",
tp->input, valid_to_str(actual_l), tp->input, valid_to_str(actual_l),
valid_to_str(tp->expected)); valid_to_str(tp->expected));
vstream_fprintf(VSTREAM_ERR, "FAIL %s\n", tp->name);
fail++;
} else if (actual_z != tp->expected) { } else if (actual_z != tp->expected) {
vstream_fprintf(VSTREAM_ERR, vstream_fprintf(VSTREAM_ERR,
"input: >%s<, 'actual_z' got: >%s<, want: >%s<\n", "input: >%s<, 'actual_z' got: >%s<, want: >%s<\n",
tp->input, valid_to_str(actual_z), tp->input, valid_to_str(actual_z),
valid_to_str(tp->expected)); valid_to_str(tp->expected));
vstream_fprintf(VSTREAM_ERR, "FAIL %s\n", tp->name);
fail++;
} else { } else {
vstream_fprintf(VSTREAM_ERR, "input: >%s<, got and want: >%s<\n", vstream_fprintf(VSTREAM_ERR, "input: >%s<, got and want: >%s<\n",
tp->input, valid_to_str(actual_l)); tp->input, valid_to_str(actual_l));
ok = 1;
}
if (ok) {
vstream_fprintf(VSTREAM_ERR, "PASS %s\n", tp->name); vstream_fprintf(VSTREAM_ERR, "PASS %s\n", tp->name);
pass++; pass++;
} else {
vstream_fprintf(VSTREAM_ERR, "FAIL %s\n", tp->name);
fail++;
} }
} }
msg_info("PASS=%d FAIL=%d", pass, fail); msg_info("PASS=%d FAIL=%d", pass, fail);