mirror of
https://github.com/vdukhovni/postfix
synced 2025-08-22 09:57:34 +00:00
postfix-3.11-20250323
This commit is contained in:
parent
f8a6a51f22
commit
ba7dbdd88a
@ -29015,3 +29015,32 @@ Apologies for any names omitted.
|
||||
Dovecot auth client did not attempt to create a new connection
|
||||
after an I/O error on an existing connection. Reported by
|
||||
Oleksandr Kozmenko. File: xsasl/xsasl_dovecot_server.c.
|
||||
|
||||
20240315
|
||||
|
||||
Code health: two typos canceled each other's effect. Fix
|
||||
by Michael Tokarev. No change in compiler output. File:
|
||||
util/vstring_vstream.c.
|
||||
|
||||
20250316
|
||||
|
||||
Bugfix (defect introduced: date 19991116): when appending a
|
||||
setting to a main.cf or master.cf file that did not end in
|
||||
a newline character, the "postconf -e" command did not add
|
||||
an extra newline character before appending the new setting,
|
||||
causing information to become garbled. Fix by Michael
|
||||
Tokarev. File: postconf/postconf_edit.c.
|
||||
|
||||
20259317
|
||||
|
||||
Documentation: added text to clarify the difference between
|
||||
SMTP connection reuse and TLS session resumption, and that
|
||||
these can be combined together. File: proto/TLSRPT_README.html.
|
||||
|
||||
20250321
|
||||
|
||||
Safety: the SQLite client now logs a warning when a query
|
||||
uses double quotes instead of the Postfix-recommended single
|
||||
quotes. Oscar Bataille reported that the non-recommended
|
||||
form is not protected against SQL injection. Files:
|
||||
global/dict_sqlite.c, global/dict_sqlite_test.c.
|
||||
|
@ -7,6 +7,7 @@ TTaabbllee ooff CCoonntteennttss
|
||||
* Introduction
|
||||
* Building Postfix with TLSRPT support
|
||||
* Turning on TLSRPT
|
||||
* Connection reuse versus session resumption
|
||||
* TLSRPT Status logging
|
||||
* Delivering TLSRPT summaries via email
|
||||
* MTA-STS Support via smtp_tls_policy_maps
|
||||
@ -27,7 +28,7 @@ A policy for domain example.com could look like this:
|
||||
|
||||
Instead of mailto:, a policy may specify an https: destination.
|
||||
|
||||
The diagram below shows how Postfix TLS handshake success and failure events
|
||||
The diagram below shows how successful or failed Postfix TLS handshake events
|
||||
are collected and processed into daily summary reports.
|
||||
|
||||
Postfix SMTP and TLSRPT client TLSRPT collector, Email or HTTP
|
||||
@ -124,6 +125,36 @@ Notes:
|
||||
For details on how to run the TLSRPT collection and reporting infrastructure,
|
||||
see the documentation at https://github.com/sys4/tlsrpt-reporter.
|
||||
|
||||
CCoonnnneeccttiioonn rreeuussee vveerrssuuss sseessssiioonn rreessuummppttiioonn
|
||||
|
||||
The Postfix SMTP client implements two kinds of reuse:
|
||||
|
||||
* SSMMTTPP CCoonnnneeccttiioonn rreeuussee:: a Postfix SMTP client creates a new SMTP connection,
|
||||
sends one email message, and saves the connection instead of closing it.
|
||||
Later, some SMTP client reuses that connection, sends an email message, and
|
||||
saves or closes the connection depending on whether it has reached some
|
||||
reuse limit. Each connection can be used by only one Postfix SMTP client at
|
||||
a time.
|
||||
|
||||
* TTLLSS SSeessssiioonn rreessuummppttiioonn:: a Postfix SMTP client saves the result from a "new"
|
||||
TLS handshake. Later, one or more SMTP clients create a new SMTP connection
|
||||
and resume the saved TLS session on their new connection.
|
||||
|
||||
Of course there is a third case:
|
||||
|
||||
* CCoommbbiinneedd rreeuussee aanndd rreessuummppttiioonn:: a Postfix SMTP client creates a new SMTP
|
||||
connection, sends one email message, saves the result from a "new" TLS
|
||||
handshake, and also saves the connection instead of closing it. Later, one
|
||||
SMTP client reuses (and saves) that connection, one client at a time, and
|
||||
one or more clients create a new SMTP connection and resume the saved TLS
|
||||
session on their new connection.
|
||||
|
||||
In all cases, there is no TLS handshake when a saved SMTP connection is reused,
|
||||
and there is no "new" TLS handshake when a saved TLS session is resumed.
|
||||
|
||||
As described next, Postfix will by default log and generate only a TLSRPT event
|
||||
for a "new" TLS handshake.
|
||||
|
||||
TTLLSSRRPPTT SSttaattuuss llooggggiinngg
|
||||
|
||||
With TLSRPT support turned on, the Postfix TLSRPT client will not only report
|
||||
|
@ -25,6 +25,7 @@
|
||||
<li> <a href="#intro"> Introduction </a> </li>
|
||||
<li> <a href="#building"> Building Postfix with TLSRPT support </a>
|
||||
<li> <a href="#using"> Turning on TLSRPT </a> </li>
|
||||
<li> <a href="#reusing"> Connection reuse versus session resumption </a> </li>
|
||||
<li> <a href="#logging"> TLSRPT Status logging </a> </li>
|
||||
<li> <a href="#delivering"> Delivering TLSRPT summaries via email</a> </li>
|
||||
<li> <a href="#mta-sts"> MTA-STS Support via smtp_tls_policy_maps </a> </li>
|
||||
@ -52,8 +53,8 @@ _smtp._tls.example.com. IN TXT "v=TLSRPTv1; rua=mailto:smtp-tls-report@example.c
|
||||
<p> Instead of <tt>mailto:</tt>, a policy may specify an <tt>https:</tt>
|
||||
destination. </p>
|
||||
|
||||
<p> The diagram below shows how Postfix TLS handshake success and
|
||||
failure events are collected and processed into daily summary
|
||||
<p> The diagram below shows how successful or failed Postfix TLS
|
||||
handshake events are collected and processed into daily summary
|
||||
reports. </p>
|
||||
|
||||
<blockquote>
|
||||
@ -203,6 +204,49 @@ programs should create sockets there. </p>
|
||||
infrastructure, see the documentation at
|
||||
<a href="https://github.com/sys4/tlsrpt-reporter">https://github.com/sys4/tlsrpt-reporter</a>.
|
||||
|
||||
<h2> <a name="reusing"> Connection reuse versus session resumption
|
||||
</a> </h2>
|
||||
|
||||
<p> The Postfix SMTP client implements two kinds of reuse: </p>
|
||||
|
||||
<ul>
|
||||
|
||||
<li> <p> <b> SMTP Connection reuse: </b> a Postfix SMTP client
|
||||
creates a new SMTP connection, sends one email message, and saves
|
||||
the connection instead of closing it. Later, some SMTP client reuses
|
||||
that connection, sends an email message, and saves or closes the
|
||||
connection depending on whether it has reached some reuse limit.
|
||||
Each connection can be used by only one Postfix SMTP client at a
|
||||
time. </p>
|
||||
|
||||
<li> <p> <b> TLS Session resumption: </b> a Postfix SMTP client
|
||||
saves the result from a "new" TLS handshake. Later, one or more
|
||||
SMTP clients create a new SMTP connection and resume the saved TLS
|
||||
session on their new connection. <p>
|
||||
|
||||
</ul>
|
||||
|
||||
<p> Of course there is a third case: </p>
|
||||
|
||||
<ul>
|
||||
|
||||
<li> <p> <b> Combined reuse and resumption: </b> a Postfix SMTP
|
||||
client creates a new SMTP connection, sends one email message, saves
|
||||
the result from a "new" TLS handshake, and also saves the connection
|
||||
instead of closing it. Later, one SMTP client reuses (and saves)
|
||||
that connection, one client at a time, and one or more clients
|
||||
create a new SMTP connection and resume the saved TLS session on
|
||||
their new connection. <p>
|
||||
|
||||
</ul>
|
||||
|
||||
<p> In all cases, there is no TLS handshake when a saved SMTP connection
|
||||
is reused, and there is no "new" TLS handshake when a saved TLS session
|
||||
is resumed. </p>
|
||||
|
||||
<p> As described next, Postfix will by default log and generate only a
|
||||
TLSRPT event for a "new" TLS handshake. </p>
|
||||
|
||||
<h2> <a name="logging"> TLSRPT Status logging </a> </h2>
|
||||
|
||||
<p> With TLSRPT support turned on, the Postfix TLSRPT client will
|
||||
|
@ -25,6 +25,7 @@
|
||||
<li> <a href="#intro"> Introduction </a> </li>
|
||||
<li> <a href="#building"> Building Postfix with TLSRPT support </a>
|
||||
<li> <a href="#using"> Turning on TLSRPT </a> </li>
|
||||
<li> <a href="#reusing"> Connection reuse versus session resumption </a> </li>
|
||||
<li> <a href="#logging"> TLSRPT Status logging </a> </li>
|
||||
<li> <a href="#delivering"> Delivering TLSRPT summaries via email</a> </li>
|
||||
<li> <a href="#mta-sts"> MTA-STS Support via smtp_tls_policy_maps </a> </li>
|
||||
@ -52,8 +53,8 @@ _smtp._tls.example.com. IN TXT "v=TLSRPTv1; rua=mailto:smtp-tls-report@example.c
|
||||
<p> Instead of <tt>mailto:</tt>, a policy may specify an <tt>https:</tt>
|
||||
destination. </p>
|
||||
|
||||
<p> The diagram below shows how Postfix TLS handshake success and
|
||||
failure events are collected and processed into daily summary
|
||||
<p> The diagram below shows how successful or failed Postfix TLS
|
||||
handshake events are collected and processed into daily summary
|
||||
reports. </p>
|
||||
|
||||
<blockquote>
|
||||
@ -203,6 +204,49 @@ programs should create sockets there. </p>
|
||||
infrastructure, see the documentation at
|
||||
https://github.com/sys4/tlsrpt-reporter.
|
||||
|
||||
<h2> <a name="reusing"> Connection reuse versus session resumption
|
||||
</a> </h2>
|
||||
|
||||
<p> The Postfix SMTP client implements two kinds of reuse: </p>
|
||||
|
||||
<ul>
|
||||
|
||||
<li> <p> <b> SMTP Connection reuse: </b> a Postfix SMTP client
|
||||
creates a new SMTP connection, sends one email message, and saves
|
||||
the connection instead of closing it. Later, some SMTP client reuses
|
||||
that connection, sends an email message, and saves or closes the
|
||||
connection depending on whether it has reached some reuse limit.
|
||||
Each connection can be used by only one Postfix SMTP client at a
|
||||
time. </p>
|
||||
|
||||
<li> <p> <b> TLS Session resumption: </b> a Postfix SMTP client
|
||||
saves the result from a "new" TLS handshake. Later, one or more
|
||||
SMTP clients create a new SMTP connection and resume the saved TLS
|
||||
session on their new connection. <p>
|
||||
|
||||
</ul>
|
||||
|
||||
<p> Of course there is a third case: </p>
|
||||
|
||||
<ul>
|
||||
|
||||
<li> <p> <b> Combined reuse and resumption: </b> a Postfix SMTP
|
||||
client creates a new SMTP connection, sends one email message, saves
|
||||
the result from a "new" TLS handshake, and also saves the connection
|
||||
instead of closing it. Later, one SMTP client reuses (and saves)
|
||||
that connection, one client at a time, and one or more clients
|
||||
create a new SMTP connection and resume the saved TLS session on
|
||||
their new connection. <p>
|
||||
|
||||
</ul>
|
||||
|
||||
<p> In all cases, there is no TLS handshake when a saved SMTP connection
|
||||
is reused, and there is no "new" TLS handshake when a saved TLS session
|
||||
is resumed. </p>
|
||||
|
||||
<p> As described next, Postfix will by default log and generate only a
|
||||
TLSRPT event for a "new" TLS handshake. </p>
|
||||
|
||||
<h2> <a name="logging"> TLSRPT Status logging </a> </h2>
|
||||
|
||||
<p> With TLSRPT support turned on, the Postfix TLSRPT client will
|
||||
|
@ -104,3 +104,4 @@ Gueven
|
||||
Oemer
|
||||
Kozmenko
|
||||
Oleksandr
|
||||
Bataille
|
||||
|
@ -131,7 +131,7 @@ TESTPROG= domain_list dot_lockfile mail_addr_crunch mail_addr_find \
|
||||
fold_addr smtp_reply_footer mail_addr_map normalize_mailhost_addr \
|
||||
haproxy_srvr map_search delivered_hdr login_sender_match \
|
||||
compat_level config_known_tcp_ports hfrom_format rfc2047_code \
|
||||
ascii_header_text sendopts_test
|
||||
ascii_header_text sendopts_test dict_sqlite_test
|
||||
|
||||
LIBS = ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
|
||||
LIB_DIR = ../../lib
|
||||
@ -408,6 +408,10 @@ ascii_header_text: ascii_header_text.c $(LIB) $(LIBS)
|
||||
sendopts_test: sendopts_test.c $(LIB) $(LIBS)
|
||||
$(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
|
||||
|
||||
dict_sqlite_test: dict_sqlite_test.c dict_sqlite.o $(LIB) $(LIBS)
|
||||
$(CC) $(CFLAGS) -DTEST -o $@ $@.c dict_sqlite.o $(LIB) $(LIBS) \
|
||||
$(SYSLIBS) $(AUXLIBS_SQLITE)
|
||||
|
||||
config_known_tcp_ports: config_known_tcp_ports.c $(LIB) $(LIBS)
|
||||
$(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
|
||||
|
||||
@ -421,7 +425,7 @@ tests: tok822_test mime_tests strip_addr_test tok822_limit_test \
|
||||
normalize_mailhost_addr_test haproxy_srvr_test map_search_test \
|
||||
delivered_hdr_test login_sender_match_test compat_level_test \
|
||||
config_known_tcp_ports_test hfrom_format_test rfc2047_code_test \
|
||||
ascii_header_text_test test_sendopts
|
||||
ascii_header_text_test test_sendopts test_dict_sqlite
|
||||
|
||||
mime_tests: mime_test mime_nest mime_8bit mime_dom mime_trunc mime_cvt \
|
||||
mime_cvt2 mime_cvt3 mime_garb1 mime_garb2 mime_garb3 mime_garb4
|
||||
@ -794,7 +798,10 @@ ascii_header_text_test: update ascii_header_text
|
||||
$(SHLIB_ENV) $(VALGRIND) ./ascii_header_text
|
||||
|
||||
test_sendopts: update sendopts_test
|
||||
-$(SHLIB_ENV) $(VALGRIND) ./sendopts_test
|
||||
$(SHLIB_ENV) $(VALGRIND) ./sendopts_test
|
||||
|
||||
test_dict_sqlite: update dict_sqlite_test
|
||||
$(SHLIB_ENV) $(VALGRIND) ./dict_sqlite_test
|
||||
|
||||
clean:
|
||||
rm -f *.o $(LIB) *core $(TESTPROG) junk $(MAPS)
|
||||
@ -1331,6 +1338,19 @@ dict_sqlite.o: db_common.h
|
||||
dict_sqlite.o: dict_sqlite.c
|
||||
dict_sqlite.o: dict_sqlite.h
|
||||
dict_sqlite.o: string_list.h
|
||||
dict_sqlite_test.o: ../../include/argv.h
|
||||
dict_sqlite_test.o: ../../include/check_arg.h
|
||||
dict_sqlite_test.o: ../../include/dict.h
|
||||
dict_sqlite_test.o: ../../include/msg.h
|
||||
dict_sqlite_test.o: ../../include/msg_vstream.h
|
||||
dict_sqlite_test.o: ../../include/myflock.h
|
||||
dict_sqlite_test.o: ../../include/stringops.h
|
||||
dict_sqlite_test.o: ../../include/sys_defs.h
|
||||
dict_sqlite_test.o: ../../include/vbuf.h
|
||||
dict_sqlite_test.o: ../../include/vstream.h
|
||||
dict_sqlite_test.o: ../../include/vstring.h
|
||||
dict_sqlite_test.o: dict_sqlite.h
|
||||
dict_sqlite_test.o: dict_sqlite_test.c
|
||||
domain_list.o: ../../include/argv.h
|
||||
domain_list.o: ../../include/check_arg.h
|
||||
domain_list.o: ../../include/match_list.h
|
||||
|
@ -34,9 +34,13 @@
|
||||
/* Must be O_RDONLY.
|
||||
/* .IP dict_flags
|
||||
/* See dict_open(3).
|
||||
/* DIAGNOSTICS
|
||||
/* dict_sqlite_open() logs a warning when the query parameter value
|
||||
/* does not use the recommended '' quotes to protect against SQL
|
||||
/* injection (bad examples; no quotes or "" quotes).
|
||||
/* SEE ALSO
|
||||
/* dict(3) generic dictionary manager
|
||||
/* sqlite_table(5) sqlite client configuration
|
||||
/* sqlite_table(5) Postfix sqlite client configuration
|
||||
/* AUTHOR(S)
|
||||
/* Axel Steiner
|
||||
/* ast@treibsand.com
|
||||
@ -46,12 +50,16 @@
|
||||
/* IBM T.J. Watson Research
|
||||
/* P.O. Box 704
|
||||
/* Yorktown Heights, NY 10598, USA
|
||||
/*
|
||||
/* Wietse Venema
|
||||
/* porcupine.org
|
||||
/*--*/
|
||||
|
||||
/* System library. */
|
||||
|
||||
#include <sys_defs.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#ifdef HAS_SQLITE
|
||||
#include <sqlite3.h>
|
||||
@ -250,6 +258,38 @@ static const char *dict_sqlite_lookup(DICT *dict, const char *name)
|
||||
retval : 0);
|
||||
}
|
||||
|
||||
/* flag_non_recommended_query - as the name says. */
|
||||
|
||||
static void flag_non_recommended_query(const char *query,
|
||||
const char *sqlitecf)
|
||||
{
|
||||
const char *cp;
|
||||
int in_quote;
|
||||
const int squote = '\'';
|
||||
const int dquote = '"';
|
||||
|
||||
for (in_quote = 0, cp = query; *cp != 0; cp++) {
|
||||
if (in_quote == 0) {
|
||||
if (*cp == squote || *cp == dquote)
|
||||
in_quote = *cp;
|
||||
} else if (*cp == in_quote) {
|
||||
in_quote = 0;
|
||||
}
|
||||
if (in_quote == squote)
|
||||
continue;
|
||||
if (*cp == '%') {
|
||||
if (cp[1] == '%') {
|
||||
cp += 1;
|
||||
} else if (ISALNUM(cp[1])) {
|
||||
msg_warn("%s:%s: query >%s< contains >%.2s< without the "
|
||||
"recommended '' quotes", DICT_TYPE_SQLITE, sqlitecf,
|
||||
query, cp);
|
||||
cp += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* sqlite_parse_config - parse sqlite configuration file */
|
||||
|
||||
static void sqlite_parse_config(DICT_SQLITE *dict_sqlite, const char *sqlitecf)
|
||||
@ -268,6 +308,8 @@ static void sqlite_parse_config(DICT_SQLITE *dict_sqlite, const char *sqlitecf)
|
||||
db_common_sql_build_query(buf, dict_sqlite->parser);
|
||||
dict_sqlite->query = vstring_export(buf);
|
||||
}
|
||||
/* Flag %[a-zA-Z0-9] if not protected with ''. */
|
||||
flag_non_recommended_query(dict_sqlite->query, sqlitecf);
|
||||
dict_sqlite->result_format =
|
||||
cfg_get_str(dict_sqlite->parser, "result_format", "%s", 1, 0);
|
||||
dict_sqlite->expansion_limit =
|
||||
|
@ -23,7 +23,6 @@
|
||||
|
||||
extern DICT *dict_sqlite_open(const char *, int, int);
|
||||
|
||||
|
||||
/* AUTHOR(S)
|
||||
/* Axel Steiner
|
||||
/* ast@treibsand.com
|
||||
|
247
postfix/src/global/dict_sqlite_test.c
Normal file
247
postfix/src/global/dict_sqlite_test.c
Normal file
@ -0,0 +1,247 @@
|
||||
/*++
|
||||
/* NAME
|
||||
/* dict_sqlite_test 1t
|
||||
/* SUMMARY
|
||||
/* dict_sqlite unit test
|
||||
/* SYNOPSIS
|
||||
/* ./dict_sqlite_test
|
||||
/* DESCRIPTION
|
||||
/* dict_sqlite_test runs and logs each configured test, reports if
|
||||
/* a test is a PASS or FAIL, and returns an exit status of zero if
|
||||
/* all tests are a PASS.
|
||||
/*
|
||||
/* Each test creates a temporary test database and a corresponding
|
||||
/* Postfix sqlite client configuration file, both having unique
|
||||
/* names. Otherwise, each test is hermetic.
|
||||
/* LICENSE
|
||||
/* .ad
|
||||
/* .fi
|
||||
/* The Secure Mailer license must be distributed with this software.
|
||||
/* AUTHOR(S)
|
||||
/* Wietse Venema porcupine.org
|
||||
/*--*/
|
||||
|
||||
/*
|
||||
/*
|
||||
* System library.
|
||||
*/
|
||||
#include <sys_defs.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* Utility library.
|
||||
*/
|
||||
#include <msg.h>
|
||||
#include <msg_vstream.h>
|
||||
#include <stringops.h>
|
||||
|
||||
/*
|
||||
* Global library.
|
||||
*/
|
||||
#include <dict_sqlite.h>
|
||||
|
||||
/*
|
||||
* TODO(wietse) make this a proper VSTREAM interface or test helper API.
|
||||
*/
|
||||
|
||||
/* vstream_swap - capture output for testing */
|
||||
|
||||
static void vstream_swap(VSTREAM *one, VSTREAM *two)
|
||||
{
|
||||
VSTREAM save;
|
||||
|
||||
save = *one;
|
||||
*one = *two;
|
||||
*two = save;
|
||||
}
|
||||
|
||||
/*
|
||||
* Override the printable.c module because it may break some tests.
|
||||
*
|
||||
* TODO(wietse) move this to a fake_printable.c module that can override all
|
||||
* printable.c global symbols.
|
||||
*/
|
||||
int util_utf8_enable;
|
||||
|
||||
char *printable_except(char *string, int replacement, const char *except)
|
||||
{
|
||||
return (string);
|
||||
}
|
||||
|
||||
/*
|
||||
* Scaffolding for dict_sqlite(3) tests.
|
||||
*/
|
||||
|
||||
/* create_and_populate_db - create an empty database and optionally populate */
|
||||
|
||||
static void create_and_populate_db(char *dbpath, const char *commands)
|
||||
{
|
||||
int fd;
|
||||
|
||||
/*
|
||||
* Create an empty database file with a unique name. Assume that an
|
||||
* adversary cannot rename or remove the file.
|
||||
*/
|
||||
if ((fd = mkstemp(dbpath)) < 0)
|
||||
msg_fatal("mkstemp(\"%s\"): %m", dbpath);
|
||||
if (close(fd) < 0)
|
||||
msg_fatal("close %s: %m", dbpath);
|
||||
|
||||
/*
|
||||
* TODO(wietse) Open the database file, prepare and execute commands
|
||||
* to populate the database, and close the database.
|
||||
*/
|
||||
if (commands) {
|
||||
msg_fatal("commands are not yet supported");
|
||||
}
|
||||
}
|
||||
|
||||
/* create_and_populate_cf - create sqlite_table(5) configuration file */
|
||||
|
||||
static void create_and_populate_cf(char *cfpath, const char *dbpath,
|
||||
const char *cftext)
|
||||
{
|
||||
int fd;
|
||||
VSTREAM *fp;
|
||||
|
||||
/*
|
||||
* Create an empty sqlite_table(5) configuration file with a unique name.
|
||||
* Assume that an adversary cannot rename or remove the file.
|
||||
*/
|
||||
if ((fd = mkstemp(cfpath)) < 0)
|
||||
msg_fatal("mkstemp(\"%s\"): %m", cfpath);
|
||||
if ((fp = vstream_fdopen(fd, O_WRONLY)) == 0)
|
||||
msg_fatal("vstream_fdopen: %m");
|
||||
(void) vstream_fprintf(fp, "%s\ndbpath = %s\n", cftext, dbpath);
|
||||
if (vstream_fclose(fp) != 0)
|
||||
msg_fatal("vstream_fdclose: %m");
|
||||
}
|
||||
|
||||
/*
|
||||
* Test structure. Some tests may come their own.
|
||||
*/
|
||||
typedef struct TEST_CASE {
|
||||
const char *label;
|
||||
int (*action) (const struct TEST_CASE *);
|
||||
const char *commands; /* commands or null */
|
||||
const char *settings; /* sqlite_table(5) */
|
||||
const char *exp_warning; /* substring match or null */
|
||||
} TEST_CASE;
|
||||
|
||||
#define PASS (0)
|
||||
#define FAIL (1)
|
||||
|
||||
#define PATH_TEMPLATE "/tmp/test-XXXXXXX"
|
||||
|
||||
/* test_flag_non_recommended_query - flag non-recommended query payloads */
|
||||
|
||||
static int test_flag_non_recommended_query(const TEST_CASE *tp)
|
||||
{
|
||||
static VSTRING *msg_buf;
|
||||
VSTREAM *memory_stream;
|
||||
const char template[] = PATH_TEMPLATE;
|
||||
char dbpath[sizeof(template)];
|
||||
char cfpath[sizeof(template)];
|
||||
DICT *dict;
|
||||
|
||||
if (msg_buf == 0)
|
||||
msg_buf = vstring_alloc(100);
|
||||
|
||||
/* Prepare scaffolding database and configuration files. */
|
||||
memcpy(dbpath, template, sizeof(dbpath));
|
||||
create_and_populate_db(dbpath, tp->commands);
|
||||
memcpy(cfpath, template, sizeof(cfpath));
|
||||
create_and_populate_cf(cfpath, dbpath, tp->settings);
|
||||
|
||||
/* Run the test with custom STDERR stream. */
|
||||
VSTRING_RESET(msg_buf);
|
||||
VSTRING_TERMINATE(msg_buf);
|
||||
if ((memory_stream = vstream_memopen(msg_buf, O_WRONLY)) == 0)
|
||||
msg_fatal("open memory stream: %m");
|
||||
vstream_swap(VSTREAM_ERR, memory_stream);
|
||||
if ((dict = dict_sqlite_open(cfpath, O_RDONLY, DICT_FLAG_UTF8_REQUEST)) != 0)
|
||||
dict_close(dict);
|
||||
vstream_swap(memory_stream, VSTREAM_ERR);
|
||||
if (vstream_fclose(memory_stream))
|
||||
msg_fatal("close memory stream: %m");
|
||||
|
||||
/* Cleanup scaffolding database and configuration files. */
|
||||
if (unlink(dbpath) < 0)
|
||||
msg_fatal("unlink %s: %m", dbpath);
|
||||
if (unlink(cfpath) < 0)
|
||||
msg_fatal("unlink %s: %m", cfpath);
|
||||
|
||||
/* Verify the results. */
|
||||
if (tp->exp_warning == 0 && VSTRING_LEN(msg_buf) > 0) {
|
||||
msg_warn("got warning ``%s'', want ``null''", vstring_str(msg_buf));
|
||||
return (FAIL);
|
||||
}
|
||||
if (tp->exp_warning != 0
|
||||
&& strstr(vstring_str(msg_buf), tp->exp_warning) == 0) {
|
||||
msg_warn("got warning ``%s'', want ``%s''",
|
||||
vstring_str(msg_buf), tp->exp_warning);
|
||||
return (FAIL);
|
||||
}
|
||||
return (PASS);
|
||||
}
|
||||
|
||||
/*
|
||||
* The list of test cases.
|
||||
*/
|
||||
static const TEST_CASE test_cases[] = {
|
||||
|
||||
/*
|
||||
* Tests to flag non-recommended query forms. These create an empty test
|
||||
* database, and open it with the dict_sqlite client without querying it.
|
||||
*/
|
||||
{.label = "no_dynamic_payload",
|
||||
.action = test_flag_non_recommended_query,
|
||||
.settings = "query = select a from b where c = 5",
|
||||
},
|
||||
{.label = "dynamic_payload_inside_recommended_quotes",
|
||||
.action = test_flag_non_recommended_query,
|
||||
.settings = "query = select a from b where c = 'xx%syy'",
|
||||
},
|
||||
{.label = "dynamic_payload_without_quotes",
|
||||
.action = test_flag_non_recommended_query,
|
||||
.settings = "query = select s from b where c = xx%syy",
|
||||
.exp_warning = "contains >%s< without the recommended '' quotes",
|
||||
},
|
||||
{.label = "payload_inside_double_quotes",
|
||||
.action = test_flag_non_recommended_query,
|
||||
.settings = "query = select s from b where c = \"xx%syy\"",
|
||||
.exp_warning = "contains >%s< without the recommended '' quotes",
|
||||
},
|
||||
|
||||
/*
|
||||
* TODO: Tests that actually populate a test database, and that query it
|
||||
* with the dict_sqlite client.
|
||||
*/
|
||||
{0},
|
||||
};
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const TEST_CASE *tp;
|
||||
int pass = 0;
|
||||
int fail = 0;
|
||||
|
||||
msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
|
||||
|
||||
for (tp = test_cases; tp->label != 0; tp++) {
|
||||
int test_failed;
|
||||
|
||||
msg_info("RUN %s", tp->label);
|
||||
test_failed = tp->action(tp);
|
||||
if (test_failed) {
|
||||
msg_info("FAIL %s", tp->label);
|
||||
fail++;
|
||||
} else {
|
||||
msg_info("PASS %s", tp->label);
|
||||
pass++;
|
||||
}
|
||||
}
|
||||
msg_info("PASS=%d FAIL=%d", pass, fail);
|
||||
exit(fail != 0);
|
||||
}
|
@ -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 "20250304"
|
||||
#define MAIL_RELEASE_DATE "20250323"
|
||||
#define MAIL_VERSION_NUMBER "3.11"
|
||||
|
||||
#ifdef SNAPSHOT
|
||||
|
@ -113,8 +113,13 @@ static char *pcf_find_cf_info(VSTRING *buf, VSTREAM *dst)
|
||||
static char *pcf_next_cf_line(VSTRING *buf, VSTREAM *src, VSTREAM *dst, int *lineno)
|
||||
{
|
||||
char *cp;
|
||||
int last_char;
|
||||
|
||||
while (vstring_get(buf, src) != VSTREAM_EOF) {
|
||||
while ((last_char = vstring_get(buf, src)) != VSTREAM_EOF) {
|
||||
if (last_char != '\n') {
|
||||
VSTRING_ADDCH(buf, '\n');
|
||||
VSTRING_TERMINATE(buf);
|
||||
}
|
||||
if (lineno)
|
||||
*lineno += 1;
|
||||
if ((cp = pcf_find_cf_info(buf, dst)) != 0)
|
||||
|
@ -123,7 +123,7 @@
|
||||
/*
|
||||
* Macro to return the last character added to a VSTRING, for consistency.
|
||||
*/
|
||||
#define VSTRING_GET_RESULT(vp, baselen) \
|
||||
#define VSTRING_GET_RESULT(vp, base_len) \
|
||||
(VSTRING_LEN(vp) > (base_len) ? vstring_end(vp)[-1] : VSTREAM_EOF)
|
||||
|
||||
/* vstring_get_flags - read line from file, keep newline */
|
||||
@ -142,7 +142,7 @@ int vstring_get_flags(VSTRING *vp, VSTREAM *fp, int flags)
|
||||
break;
|
||||
}
|
||||
VSTRING_TERMINATE(vp);
|
||||
return (VSTRING_GET_RESULT(vp, baselen));
|
||||
return (VSTRING_GET_RESULT(vp, base_len));
|
||||
}
|
||||
|
||||
/* vstring_get_flags_nonl - read line from file, strip newline */
|
||||
@ -158,7 +158,7 @@ int vstring_get_flags_nonl(VSTRING *vp, VSTREAM *fp, int flags)
|
||||
while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != '\n')
|
||||
VSTRING_ADDCH(vp, c);
|
||||
VSTRING_TERMINATE(vp);
|
||||
return (c == '\n' ? c : VSTRING_GET_RESULT(vp, baselen));
|
||||
return (c == '\n' ? c : VSTRING_GET_RESULT(vp, base_len));
|
||||
}
|
||||
|
||||
/* vstring_get_flags_null - read null-terminated string from file */
|
||||
@ -174,7 +174,7 @@ int vstring_get_flags_null(VSTRING *vp, VSTREAM *fp, int flags)
|
||||
while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != 0)
|
||||
VSTRING_ADDCH(vp, c);
|
||||
VSTRING_TERMINATE(vp);
|
||||
return (c == 0 ? c : VSTRING_GET_RESULT(vp, baselen));
|
||||
return (c == 0 ? c : VSTRING_GET_RESULT(vp, base_len));
|
||||
}
|
||||
|
||||
/* vstring_get_flags_bound - read line from file, keep newline, up to bound */
|
||||
@ -197,7 +197,7 @@ int vstring_get_flags_bound(VSTRING *vp, VSTREAM *fp, int flags,
|
||||
break;
|
||||
}
|
||||
VSTRING_TERMINATE(vp);
|
||||
return (VSTRING_GET_RESULT(vp, baselen));
|
||||
return (VSTRING_GET_RESULT(vp, base_len));
|
||||
}
|
||||
|
||||
/* vstring_get_flags_nonl_bound - read line from file, strip newline, up to bound */
|
||||
@ -217,7 +217,7 @@ int vstring_get_flags_nonl_bound(VSTRING *vp, VSTREAM *fp, int flags,
|
||||
while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != '\n')
|
||||
VSTRING_ADDCH(vp, c);
|
||||
VSTRING_TERMINATE(vp);
|
||||
return (c == '\n' ? c : VSTRING_GET_RESULT(vp, baselen));
|
||||
return (c == '\n' ? c : VSTRING_GET_RESULT(vp, base_len));
|
||||
}
|
||||
|
||||
/* vstring_get_flags_null_bound - read null-terminated string from file */
|
||||
@ -237,7 +237,7 @@ int vstring_get_flags_null_bound(VSTRING *vp, VSTREAM *fp, int flags,
|
||||
while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != 0)
|
||||
VSTRING_ADDCH(vp, c);
|
||||
VSTRING_TERMINATE(vp);
|
||||
return (c == 0 ? c : VSTRING_GET_RESULT(vp, baselen));
|
||||
return (c == 0 ? c : VSTRING_GET_RESULT(vp, base_len));
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
|
Loading…
x
Reference in New Issue
Block a user