]*host\b;$&;g;
s;\brelocated_maps\b;$&;g;
+ s;\brelocated_prefix_enable\b;$&;g;
s;\brequire_home_directory\b;$&;g;
s;\bresolve_dequoted_address\b;$&;g;
s;\brewrite_service_name\b;$&;g;
diff --git a/postfix/proto/DEPRECATION_README.html b/postfix/proto/DEPRECATION_README.html
index 356d23230..6f9f247bf 100644
--- a/postfix/proto/DEPRECATION_README.html
+++ b/postfix/proto/DEPRECATION_README.html
@@ -104,11 +104,16 @@ detailed description.
Removed in version | Replacement |
+ |
+xxx_tls_enforce_peername | 3.11
+ | - | xxx_tls_security_level
+ |
+
| disable_dns_lookups
| 3.9 | - |
smtp_dns_support_level |
- | xxx_use_tls |
+
| xxx_use_tls |
3.9 | - |
xxx_tls_security_level |
@@ -149,6 +154,45 @@ reject_rbl_client
+
+
+ The postconf(1) command logs one of the following:
+
+
+
+- support for parameter "lmtp_tls_enforce_peername" will be
+removed; instead, specify "lmtp_tls_security_level"
+
+
- support for parameter "smtp_tls_enforce_peername" will be
+removed; instead, specify "smtp_tls_security_level"
+
+
+
+ There are similarly-named parameters and warnings for postscreen(8)
+and tlsproxy(8), but those parameters should rarely be specified
+by hand.
+
+ Replace obsolete configuration with its replacement:
+
+
+
+
+
+ | Goal | Obsolete configuration | Replacement configuration |
+
+ | Enforce peer name match with server certificate |
+ xxx_enforce_peername = yes | xxx_security_level
+= verify xxx_security_level = secure |
+
+ | Disable peer name match with server certificate |
+ xxx_enforce_peername = no | xxx_security_level
+= may xxx_security_level = encrypt |
+
+
+
+
+
diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto
index b1130e134..860f1aec9 100644
--- a/postfix/proto/postconf.proto
+++ b/postfix/proto/postconf.proto
@@ -4012,6 +4012,26 @@ relocated_maps = dbm:/etc/postfix/relocated
relocated_maps = hash:/etc/postfix/relocated
+%PARAM relocated_prefix_enable yes
+
+ Prepend the prefix "5.1.6 User has moved to " to all
+relocated_maps lookup results. With "relocated_prefix_enable =
+no", all lookup results must contain a valid RFC 3463 compliant
+enhanced status code and text (format: "[45].number.number text...").
+
+
+Example:
+
+
+
+/etc/postfix/main.cf:
+ relocated_maps = hash:/etc/postfix/relocated
+ relocated_prefix_enable = no
+
+hash:/etc/postfix/relocated:
+ user@example.com 5.2.1 User account is disabled
+
+
%PARAM require_home_directory no
diff --git a/postfix/proto/relocated b/postfix/proto/relocated
index b517b354f..f48b53093 100644
--- a/postfix/proto/relocated
+++ b/postfix/proto/relocated
@@ -36,9 +36,10 @@
# TABLE FORMAT
# .ad
# .fi
-# The input format for the \fBpostmap\fR(1) command is as follows:
# .IP \(bu
-# An entry has one of the following form:
+# By default, Postfix will prepend a hard-coded prefix "5.1.6 User
+# has moved to " to a table lookup result, and the format for a
+# table entry is as follows:
#
# .nf
# \fIpattern new_location\fR
@@ -47,6 +48,16 @@
# Where \fInew_location\fR specifies contact information such as
# an email address, or perhaps a street address or telephone number.
# .IP \(bu
+# Postfix 3.11 and later can optionally disable the hard-coded
+# prefix. Specify "relocated_prefix_enable = no" in main.cf, and
+# specify relocated_maps entries with your own RFC 3463-compliant
+# enhanced status code and text, for example:
+#
+# .nf
+# \fIpattern\fR 5.2.0 Mailbox is unavailable
+# \fIpattern\fR 5.2.1 Mailbox is disabled
+# .fi
+# .IP \(bu
# Empty lines and whitespace-only lines are ignored, as
# are lines whose first non-whitespace character is a `#'.
# .IP \(bu
@@ -123,6 +134,11 @@
# Optional lookup tables with new contact information for users or
# domains that no longer exist.
# .PP
+# Available with Postfix version 3.11 and later:
+# .IP "\fBrelocated_prefix_enable (yes)\fR"
+# Prepend the prefix "\fB5.1.6 User has moved to \fR" to all
+# relocated_maps lookup results.
+# .PP
# Other parameters of interest:
# .IP "\fBinet_interfaces (all)\fR"
# The local network interface addresses that this mail system
diff --git a/postfix/proto/stop.double-install-proto-text b/postfix/proto/stop.double-install-proto-text
index 4e3f43dca..7721d87d7 100644
--- a/postfix/proto/stop.double-install-proto-text
+++ b/postfix/proto/stop.double-install-proto-text
@@ -43,3 +43,6 @@ virtual virtual alias domain anything right hand content does not matter
Inbound SMTP smuggling strip extra CR in CR LF CR CR LF
Inbound SMTP smuggling don t strip extra CR in CR LF CR CR LF
CR LF CR CR LF to silence false alarms from test tools
+ Prepend the prefix 5 1 6 User has moved to to all
+ pattern number number number text
+to to the lookup result With Postfix 3 11 and later specify
diff --git a/postfix/proto/stop.double-proto-html b/postfix/proto/stop.double-proto-html
index 911ed5145..bed72b65e 100644
--- a/postfix/proto/stop.double-proto-html
+++ b/postfix/proto/stop.double-proto-html
@@ -364,3 +364,4 @@ Postfix Postfix legacy TLS Support
with cipher ECDHE RSA AES256 GCM SHA384 256 256 bits
TLSv1 2 with cipher ECDHE RSA AES256 GCM SHA384 256 256 bits
The recommended socket location is still to be determined A good socket location would be under the Postfix queue directory for example smtp_tlsrpt_socket_name run tlsrpt tlsrpt sock The advantage of using a relative name is that it
+enhanced status code and text format 45 number number text
diff --git a/postfix/src/global/mail_params.h b/postfix/src/global/mail_params.h
index 40aa5c2ba..15cc2469e 100644
--- a/postfix/src/global/mail_params.h
+++ b/postfix/src/global/mail_params.h
@@ -763,6 +763,10 @@ extern int var_tls_prng_upd_period;
#define DEF_RELOCATED_MAPS ""
extern char *var_relocated_maps;
+#define VAR_ENB_RELOCATED_PFX "relocated_prefix_enable"
+#define DEF_ENB_RELOCATED_PFX "yes"
+extern bool var_enb_relocated_pfx;
+
/*
* Queue manager: after each failed attempt the backoff time (how long we
* won't try this host in seconds) is doubled until it reaches the maximum.
diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h
index 102c9cd24..0c6018de1 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 "20250606"
+#define MAIL_RELEASE_DATE "20250621"
#define MAIL_VERSION_NUMBER "3.11"
#ifdef SNAPSHOT
diff --git a/postfix/src/global/maps.c b/postfix/src/global/maps.c
index d2370029d..592ec91d9 100644
--- a/postfix/src/global/maps.c
+++ b/postfix/src/global/maps.c
@@ -154,9 +154,8 @@ MAPS *maps_create(const char *title, const char *map_names, int dict_flags)
#define OPEN_FLAGS O_RDONLY
while ((map_type_name = mystrtokq(&bufp, sep, parens)) != 0) {
- vstring_sprintf(map_type_name_flags, "%s(%o,%s)",
- map_type_name, OPEN_FLAGS,
- dict_flags_str(dict_flags));
+ dict_make_registered_name(map_type_name_flags, map_type_name,
+ OPEN_FLAGS, dict_flags);
if ((dict = dict_handle(vstring_str(map_type_name_flags))) == 0)
dict = dict_open(map_type_name, OPEN_FLAGS, dict_flags);
if ((dict->flags & dict_flags) != dict_flags)
diff --git a/postfix/src/postconf/Makefile.in b/postfix/src/postconf/Makefile.in
index 9e5e3b966..163900bb7 100644
--- a/postfix/src/postconf/Makefile.in
+++ b/postfix/src/postconf/Makefile.in
@@ -56,7 +56,7 @@ tests: test1 test2 test3 test4 test5 test6 test7 test8 test9 test10 test11 \
test42 test43 test44 test45 test46 test47 test48 test49 test50 test51 \
test52 test53 test54 test55 test56 test57 test58 test59 test60 test61 \
test62 test63 test64 test65 test66 test67 test68 test69 test70 test71 \
- test72 test73 test74 test75 test76
+ test72 test73 test74 test75 test76 test78
root_tests:
@@ -1065,6 +1065,26 @@ test76: $(PROG) test76.ref
diff /dev/null test76.tmp
rm -f main.cf master.cf test76.tmp
+# Warn about unused, deprecated, or deleted parameters.
+test78: $(PROG) test78.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c. \
+ config_directory=. \
+ smtp_tls_enforce_peername=yes \
+ lmtp_tls_enforce_peername=yes \
+ >test78.tmp 2>&1
+ touch -t 197601010000 main.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo ' -o lmtp_tls_enforce_peername=no' >> master.cf
+ echo ' -o smtp_tls_enforce_peername=no' >> master.cf
+ touch -t 197601010000 master.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc. >>test78.tmp 2>&1
+ diff test78.ref test78.tmp
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -qnc. >/dev/null 2>test78.tmp
+ diff /dev/null test78.tmp
+ rm -f main.cf master.cf test78.tmp
+
clean:
rm -f *.o *core $(PROG) $(TESTPROG) junk $(MAKES) $(AUTOS) $(DUMMIES) \
$(TEST_TMP) $(DB_MAKES)
diff --git a/postfix/src/postconf/postconf_unused.c b/postfix/src/postconf/postconf_unused.c
index 717d1a53a..0b8d14646 100644
--- a/postfix/src/postconf/postconf_unused.c
+++ b/postfix/src/postconf/postconf_unused.c
@@ -94,8 +94,15 @@ static const PCF_DEPR_PARAM_INFO pcf_depr_param_info[] = {
"smtpd_tls_dh1024_param_file", "do not specify (leave at default)",
"smtpd_tls_eecdh_grade", "do not specify (leave at default)",
"deleted-test-only", "do not specify", /* For testing */
+
+ /*
+ * Deprecated as of Postfix 3.11.
+ */
+ "lmtp_tls_enforce_peername", "specify \"lmtp_tls_security_level\"",
+ "smtp_tls_enforce_peername", "specify \"smtp_tls_security_level\"",
0,
};
+
static HTABLE *pcf_depr_param_table;
/* pcf_init_depr_params - initialize lookup table */
diff --git a/postfix/src/postconf/test78.ref b/postfix/src/postconf/test78.ref
new file mode 100644
index 000000000..59d171b4a
--- /dev/null
+++ b/postfix/src/postconf/test78.ref
@@ -0,0 +1,7 @@
+config_directory = .
+lmtp_tls_enforce_peername = yes
+smtp_tls_enforce_peername = yes
+./postconf: warning: ./main.cf: support for parameter "smtp_tls_enforce_peername" will be removed; instead, specify "smtp_tls_security_level"
+./postconf: warning: ./main.cf: support for parameter "lmtp_tls_enforce_peername" will be removed; instead, specify "lmtp_tls_security_level"
+./postconf: warning: ./master.cf: support for parameter "smtp_tls_enforce_peername" will be removed; instead, specify "smtp_tls_security_level"
+./postconf: warning: ./master.cf: support for parameter "lmtp_tls_enforce_peername" will be removed; instead, specify "lmtp_tls_security_level"
diff --git a/postfix/src/smtp/smtp.h b/postfix/src/smtp/smtp.h
index 8c5ee0a0f..7214661a6 100644
--- a/postfix/src/smtp/smtp.h
+++ b/postfix/src/smtp/smtp.h
@@ -59,6 +59,9 @@ typedef struct SMTP_ITERATOR {
VSTRING *host; /* hostname or empty */
VSTRING *addr; /* printable address or empty */
unsigned port; /* network byte order or null */
+#ifdef USE_TLS
+ int tlsreqno; /* "TLS-Required: no" */
+#endif
struct DNS_RR *rr; /* DNS resource record or null */
struct DNS_RR *mx; /* DNS resource record or null */
/* Private members. */
@@ -66,11 +69,18 @@ typedef struct SMTP_ITERATOR {
struct SMTP_STATE *parent; /* parent linkage */
} SMTP_ITERATOR;
+#ifdef USE_TLS
+#define IF_USE_TLS(...) (__VA_ARGS__)
+#else
+#define IF_USE_TLS(...)
+#endif
+
#define SMTP_ITER_INIT(iter, _dest, _host, _addr, _port, state) do { \
vstring_strcpy((iter)->dest, (_dest)); \
vstring_strcpy((iter)->host, (_host)); \
vstring_strcpy((iter)->addr, (_addr)); \
(iter)->port = (_port); \
+ IF_USE_TLS((iter)->tlsreqno = 0); \
(iter)->mx = (iter)->rr = 0; \
vstring_strcpy((iter)->saved_dest, ""); \
(iter)->parent = (state); \
@@ -728,7 +738,7 @@ char *smtp_key_prefix(VSTRING *, const char *, SMTP_ITERATOR *, int);
*/
#define SMTP_KEY_MASK_SCACHE_DEST_LABEL \
(SMTP_KEY_FLAG_SERVICE | COND_SASL_SMTP_KEY_FLAG_SENDER \
- | SMTP_KEY_FLAG_REQ_NEXTHOP)
+ | SMTP_KEY_FLAG_REQ_NEXTHOP | SMTP_KEY_FLAG_TLS_LEVEL)
/*
* Connection-cache endpoint lookup key. The SENDER, CUR_NEXTHOP, HOSTNAME,
diff --git a/postfix/src/smtp/smtp_connect.c b/postfix/src/smtp/smtp_connect.c
index 2bfff1c93..e4b60791a 100644
--- a/postfix/src/smtp/smtp_connect.c
+++ b/postfix/src/smtp/smtp_connect.c
@@ -507,6 +507,24 @@ static int smtp_get_effective_tls_level(DSN_BUF *why, SMTP_STATE *state)
SMTP_ITERATOR *iter = state->iterator;
SMTP_TLS_POLICY *tls = state->tls;
+ /*
+ * If the message contains a "TLS-Required: no" header, update the
+ * iterator to limit the policy at TLS_LEV_MAY.
+ *
+ * We must do this early to avoid possible failure if TLSA record lookups
+ * fail, or if TLSA records are found, but can't be activated because the
+ * security level has been reset to "may".
+ *
+ * Note that the REQUIRETLS verb in ESMTP overrides the "TLS-Required: no"
+ * header.
+ */
+#ifdef USE_TLS
+ if (var_tls_required_enable
+ && (state->request->sendopts & SOPT_REQUIRETLS_HEADER)) {
+ iter->tlsreqno = 1;
+ }
+#endif
+
/*
* Determine the TLS level for this destination.
*/
@@ -529,16 +547,6 @@ static int smtp_get_effective_tls_level(DSN_BUF *why, SMTP_STATE *state)
}
#endif
- /*
- * Otherwise, if the TLS level is not TLS_LEV_NONE or some non-level, and
- * the message contains a "TLS-Required: no" header, limit the level to
- * TLS_LEV_MAY.
- */
- else if (var_tls_required_enable && tls->level > TLS_LEV_NONE
- && (state->request->sendopts & SOPT_REQUIRETLS_HEADER)) {
- tls->level = TLS_LEV_MAY;
- }
-
/*
* Success.
*/
diff --git a/postfix/src/smtp/smtp_proto.c b/postfix/src/smtp/smtp_proto.c
index df2f74391..b89686378 100644
--- a/postfix/src/smtp/smtp_proto.c
+++ b/postfix/src/smtp/smtp_proto.c
@@ -926,13 +926,16 @@ static int smtp_start_tls(SMTP_STATE *state)
* XXX: The TLS library will salt the serverid with further details of the
* protocol and cipher requirements including the server ehlo response.
* Deferring the helo to the digested suffix results in more predictable
- * SSL session lookup key lengths.
+ * SSL session lookup key lengths. Add the current TLS security level to
+ * account for TLS level overrides based on message content or envelope
+ * metadata.
*/
serverid = vstring_alloc(10);
smtp_key_prefix(serverid, "&", state->iterator, SMTP_KEY_FLAG_SERVICE
| SMTP_KEY_FLAG_CUR_NEXTHOP /* With port */
| SMTP_KEY_FLAG_HOSTNAME
- | SMTP_KEY_FLAG_ADDR);
+ | SMTP_KEY_FLAG_ADDR
+ | SMTP_KEY_FLAG_TLS_LEVEL);
if (state->tls->conn_reuse) {
TLS_CLIENT_PARAMS tls_params;
diff --git a/postfix/src/smtp/smtp_tls_policy.c b/postfix/src/smtp/smtp_tls_policy.c
index 6122c2d78..6b9ee66e6 100644
--- a/postfix/src/smtp/smtp_tls_policy.c
+++ b/postfix/src/smtp/smtp_tls_policy.c
@@ -651,7 +651,12 @@ static void *policy_create(const char *unused_key, void *context)
tls->level = global_tls_level();
site_level = TLS_LEV_NOTFOUND;
- if (tls_policy) {
+ if (iter->tlsreqno) {
+ if (msg_verbose)
+ msg_info("%s: no tls policy lookup", __func__);
+ if (tls->level > TLS_LEV_MAY)
+ tls->level = TLS_LEV_MAY;
+ } else if (tls_policy) {
tls_policy_lookup(tls, &site_level, dest, "next-hop destination");
} else if (tls_per_site) {
tls_site_lookup(tls, &site_level, dest, "next-hop destination");
diff --git a/postfix/src/testing/Makefile.in b/postfix/src/testing/Makefile.in
index ad624de78..16375e2d0 100644
--- a/postfix/src/testing/Makefile.in
+++ b/postfix/src/testing/Makefile.in
@@ -1,22 +1,63 @@
+SHELL = /bin/sh
+SRCS = nosleep.c dict_test_helper.c
+LIB_OBJ = dict_test_helper.o
+MOCK_OBJ=
+TEST_OBJ=
+HDRS = dict_test_helper.h
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+INCL =
+LIB = libtesting.a
LIB_SO = nosleep.so
+TESTPROG=
+
+LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
LIB_DIR = ../../lib
INC_DIR = ../../include
+MAKES =
+
+.c.o:; $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c
+
+all: $(LIB_SO) $(LIB) $(MOCK_OBJ)
-all: $(LIB_SO)
+$(LIB_OBJ) $(MOCK_OBJ) $(TEST_OBJ): ../../conf/makedefs.out
Makefile: Makefile.in
cat ../../conf/makedefs.out $? >$@
+test: $(TESTPROG)
+
+$(LIB): $(LIB_OBJ)
+ $(_AR) $(ARFL) $(LIB) $?
+ $(_RANLIB) $(LIB)
+
+$(LIB_DIR)/$(LIB): $(LIB)
+ cp $(LIB) $(LIB_DIR)
+ $(_RANLIB) $(LIB_DIR)/$(LIB)
+
nosleep.so: nosleep.c
$(CC) $(CFLAGS) -fPIC -shared -o $@ $?
-update: lib_so_update
+update: lib_so_update lib_update
lib_so_update: $(LIB_SO)
for i in $(LIB_SO); do \
cmp -s $$i $(LIB_DIR)/$$i 2>/dev/null || cp $$i $(LIB_DIR); \
done
+lib_update: $(LIB_DIR)/$(LIB) $(HDRS) $(MOCK_OBJ)
+ -for i in $(HDRS); \
+ do \
+ cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \
+ done
+ (cd $(INC_DIR); chmod 644 $(HDRS))
+ -for i in $(MOCK_OBJ); \
+ do \
+ cmp -s $$i $(LIB_DIR)/$$i 2>/dev/null || cp $$i $(LIB_DIR); \
+ done
+
clean:
rm -f $(LIB_SO) *.o
@@ -32,4 +73,16 @@ depend: $(MAKES)
@$(EXPORT) make -f Makefile.in Makefile 1>&2
# do not edit below this line - it is generated by 'make depend'
+dict_test_helper.o: ../../include/argv.h
+dict_test_helper.o: ../../include/check_arg.h
+dict_test_helper.o: ../../include/dict.h
+dict_test_helper.o: ../../include/msg.h
+dict_test_helper.o: ../../include/myflock.h
+dict_test_helper.o: ../../include/stringops.h
+dict_test_helper.o: ../../include/sys_defs.h
+dict_test_helper.o: ../../include/vbuf.h
+dict_test_helper.o: ../../include/vstream.h
+dict_test_helper.o: ../../include/vstring.h
+dict_test_helper.o: dict_test_helper.c
+dict_test_helper.o: dict_test_helper.h
nosleep.o: nosleep.c
diff --git a/postfix/src/testing/dict_test_helper.c b/postfix/src/testing/dict_test_helper.c
new file mode 100644
index 000000000..22474a25e
--- /dev/null
+++ b/postfix/src/testing/dict_test_helper.c
@@ -0,0 +1,266 @@
+/*++
+/* NAME
+/* dict_test_helper 3
+/* SUMMARY
+/* dictionary test helpers
+/* SYNOPSIS
+/* #include
+/*
+/* DICT *dict_open_and_capture_msg(
+/* const char *type_name,
+/* int open_flags,
+/* int dict_flags,
+/* VSTRING *out_msg_buf)
+/*
+/* char *dict_compose_spec(
+/* const char *dict_type,
+/* const char **component_specs,
+/* int open_flags,
+/* int dict_flags,
+/* VSTRING *out_composite_spec,
+/* ARGV *out_reg_component_specs)
+/*
+/* chat char *dict_get_and_capture_msg(
+/* DICT *dict,
+/* const char *key,
+/* VSTRING *out_msg_buf)
+/*
+/* struct dict_get_verify_data {
+/* .in +4
+/* const char *key;
+/* const char *want_value;
+/* int want_error;
+/* const char *want_msg;
+/* .in -4
+/* }
+/*
+/* int dict_get_and_verify(
+/* DICT *dict,
+/* const char *key,
+/* const char *want_value,
+/* int want_error,
+/* const char *want_msg)
+/*
+/* int dict_get_and_verify_bulk(
+/* DICT *dict,
+/* const struct dict_get_verify_data *data)
+/* DESCRIPTION
+/* This module contains common code for dictionary tests.
+/*
+/* All functions that capture msg(3) output clear the output
+/* VSTRING buffer first.
+/*
+/* dict_open_and_capture_msg() invokes dict_open() while
+/* capturing msg(3) output to a VSTRING buffer.
+/*
+/* dict_compose_spec() constructs a composite dictionary spec with
+/* the form "\fIdict_type:{component_specs[0],...}\fR". It records in
+/* out_reg_component_specs the names under which the component_specs
+/* dictionaries will be registered with dict_register(), with each
+/* name having the form "\fItype:name(open_flags,dict_flags). The
+/* result value is the out_composite_spec string value.
+/*
+/* dict_get_and_capture_msg() invokes dict_get() while capturing
+/* msg(3) output to a VSTRING buffer.
+/*
+/* dict_get_and_verify() invokes dict_get() and verifies that
+/* expectations are met. The want_value argument requires an exact
+/* match; specify null if the expected lookup result is "not found".
+/* The want_error argument requires an exact match; specify zero
+/* (DICT_ERR_NONE) or one of the other expected DICT_ERR_*
+/* values. The want_msg argument requires a substring match;
+/* specify null if no msg(3) output is expected. The result value
+/* is PASS or FAIL.
+/*
+/* dict_get_and_verify_bulk() provides a convenient interface for
+/* multiple lookup tests.
+/* LICENSE
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* porcupine.org
+/*--*/
+
+ /*
+ * System library.
+ */
+#include
+#include
+#include
+
+ /*
+ * Utility library.
+ */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define LEN(x) VSTRING_LEN(x)
+#define STR(x) vstring_str(x)
+
+ /*
+ * TODO(wietse) factor this out to common testing header file.
+ */
+#define PASS 1
+#define FAIL 0
+
+ /*
+ * TODO(wietse) make this a proper VSTREAM interface or test helper API.
+ */
+
+/* vstream_swap - capture msg(3) output output for testing */
+
+static void vstream_swap(VSTREAM *one, VSTREAM *two)
+{
+ VSTREAM save;
+
+ save = *one;
+ *one = *two;
+ *two = save;
+}
+
+/* dict_open_and_capture_msg - open dictionary and capture msg(3) output */
+
+DICT *dict_open_and_capture_msg(const char *type_name, int open_flags,
+ int dict_flags, VSTRING *out_msg_buf)
+{
+ VSTREAM *memory_stream;
+ DICT *dict;
+
+ VSTRING_RESET(out_msg_buf);
+ VSTRING_TERMINATE(out_msg_buf);
+ if ((memory_stream = vstream_memopen(out_msg_buf, O_WRONLY)) == 0)
+ msg_fatal("open memory stream: %m");
+ vstream_swap(VSTREAM_ERR, memory_stream);
+ dict = dict_open(type_name, open_flags, dict_flags);
+ vstream_swap(memory_stream, VSTREAM_ERR);
+ (void) vstream_fclose(memory_stream);
+ return (dict);
+}
+
+/* dict_compose_spec - compose aggregate spec and component registered specs */
+
+char *dict_compose_spec(const char *dict_type,
+ const char **component_specs,
+ int open_flags, int dict_flags,
+ VSTRING *out_composite_spec,
+ ARGV *out_reg_component_specs)
+{
+ VSTRING *reg_spec = vstring_alloc(100);
+ const char **cpp;
+
+ /*
+ * A dictionary spec is formatted as "type:name", and a dictionary is
+ * registered with dict_register() as "type:name(open_flags,dict_flags)".
+ * The latter form is used to share dictionary instances that have the
+ * exact same same properties.
+ *
+ * Build the composite dictionary spec from the dict_type and component
+ * dictionary specs, and build the list of component specs decorated with
+ * open_flags and initial dict_flags such as locking.
+ *
+ * Normally, these decorated specs are used for registering tables with
+ * dict_register() and for looking them up with dict_handle(). For
+ * testing, we need those names to determine whether a component
+ * dictionary is registered.
+ *
+ * The dict_flags in a registered component spec may differ from actual
+ * dictionary flags: when a dictionary is opened, it may add dict_flags
+ * that describe its own properties such as whether the table's left-hand
+ * side is a fixed string or a pattern.
+ */
+ argv_truncate(out_reg_component_specs, 0);
+ vstring_strcpy(out_composite_spec, dict_type);
+ vstring_strcat(out_composite_spec, ":{");
+ for (cpp = component_specs; *cpp; cpp++) {
+ vstring_strcat(out_composite_spec, *cpp);
+ if (cpp[1])
+ vstring_strcat(out_composite_spec, ",");
+ dict_make_registered_name(reg_spec, *cpp, open_flags, dict_flags);
+ argv_add(out_reg_component_specs, STR(reg_spec), (char *) 0);
+ }
+ vstring_strcat(out_composite_spec, "}");
+ vstring_free(reg_spec);
+ return (STR(out_composite_spec));
+}
+
+/* dict_get_and_capture_msg - deploy dict_get() and capture msg(3) output */
+
+const char *dict_get_and_capture_msg(DICT *dict, const char *key,
+ VSTRING *out_msg_buf)
+{
+ VSTREAM *memory_stream;
+ const char *value;
+
+ VSTRING_RESET(out_msg_buf);
+ VSTRING_TERMINATE(out_msg_buf);
+ if ((memory_stream = vstream_memopen(out_msg_buf, O_WRONLY)) == 0)
+ msg_fatal("open memory stream: %m");
+ vstream_swap(VSTREAM_ERR, memory_stream);
+ value = dict_get(dict, key);
+ vstream_swap(memory_stream, VSTREAM_ERR);
+ (void) vstream_fclose(memory_stream);
+ return (value);
+}
+
+/* dict_get_and_verify - deploy dict_get() and verify results */
+
+int dict_get_and_verify(DICT *dict, const char *key, const char *want_value,
+ int want_error, const char *want_msg)
+{
+ VSTRING *msg_buf = vstring_alloc(100);
+ int ret = PASS;
+ const char *got;
+
+ got = dict_get_and_capture_msg(dict, key, msg_buf);
+ if (LEN(msg_buf) > 0 && want_msg == 0) {
+ msg_warn("unexpected error message: '%s'", STR(msg_buf));
+ ret = FAIL;
+ } else if (want_msg != 0 && strstr(STR(msg_buf), want_msg) == 0) {
+ msg_warn("unexpected error message: got '%s', want '%s'",
+ STR(msg_buf), want_msg);
+ ret = FAIL;
+ } else if (dict->error != want_error) {
+ msg_warn("unexpected lookup error for '%s': got '%d', want '%d",
+ key, dict->error, want_error);
+ ret = FAIL;
+ } else if (got == 0) {
+ if (want_value != 0) {
+ msg_warn("unexpected lookup result for '%s': got '%s', want '%s'",
+ key, "NOTFOUND", want_value);
+ ret = FAIL;
+ }
+ } else {
+ if (want_value == 0) {
+ msg_warn("unexpected lookup result for '%s': got '%s', want '%s'",
+ key, got, "NOTFOUND");
+ ret = FAIL;
+ } else if (strcmp(got, want_value) != 0) {
+ msg_warn("unexpected lookup result for '%s': got '%s', want '%s'",
+ key, got, want_value);
+ ret = FAIL;
+ }
+ }
+ vstring_free(msg_buf);
+ return (ret);
+}
+
+/* dict_get_and_verify_bulk - dict_get_and_verify() wrapper for bulk usage */
+
+int dict_get_and_verify_bulk(DICT *dict,
+ const struct dict_get_verify_data * data)
+{
+ int ret = PASS;
+ const struct dict_get_verify_data *dp;
+
+ for (dp = data; dp->key; dp++) {
+ if (dict_get_and_verify(dict, dp->key, dp->want_value,
+ dp->want_error, dp->want_msg) == FAIL)
+ ret = FAIL;
+ }
+ return (ret);
+}
diff --git a/postfix/src/testing/dict_test_helper.h b/postfix/src/testing/dict_test_helper.h
new file mode 100644
index 000000000..044dec05a
--- /dev/null
+++ b/postfix/src/testing/dict_test_helper.h
@@ -0,0 +1,45 @@
+#ifndef _DICT_TEST_HELPER_H_INCLUDED_
+#define _DICT_TEST_HELPER_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_test_helper 3h
+/* SUMMARY
+/* dictionary test helpers
+/* SYNOPSIS
+/* #include
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include
+#include
+#include
+
+ /*
+ * External interface.
+ */
+extern DICT *dict_open_and_capture_msg(const char *, int, int, VSTRING *);
+extern char *dict_compose_spec(const char *, const char **, int, int, VSTRING *, ARGV *);
+extern const char *dict_get_and_capture_msg(DICT *, const char *, VSTRING *);
+
+struct dict_get_verify_data {
+ const char *key;
+ const char *want_value;
+ int want_error;
+ const char *want_msg;
+};
+
+extern int dict_get_and_verify(DICT *, const char *, const char *, int, const char *);
+extern int dict_get_and_verify_bulk(DICT *, const struct dict_get_verify_data *);
+
+/* LICENSE
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* porcupine.org
+/*--*/
+
+#endif
diff --git a/postfix/src/trivial-rewrite/Makefile.in b/postfix/src/trivial-rewrite/Makefile.in
index ffe9ee71b..72ed83636 100644
--- a/postfix/src/trivial-rewrite/Makefile.in
+++ b/postfix/src/trivial-rewrite/Makefile.in
@@ -68,6 +68,7 @@ resolve.o: ../../include/attr.h
resolve.o: ../../include/check_arg.h
resolve.o: ../../include/dict.h
resolve.o: ../../include/domain_list.h
+resolve.o: ../../include/dsn_util.h
resolve.o: ../../include/htable.h
resolve.o: ../../include/iostuff.h
resolve.o: ../../include/mail_addr_find.h
diff --git a/postfix/src/trivial-rewrite/resolve.c b/postfix/src/trivial-rewrite/resolve.c
index df761e76c..5ca1f791a 100644
--- a/postfix/src/trivial-rewrite/resolve.c
+++ b/postfix/src/trivial-rewrite/resolve.c
@@ -87,6 +87,7 @@
#include
#include
#include
+#include
/* Application-specific. */
@@ -715,13 +716,23 @@ static void resolve_addr(RES_CONTEXT *rp, char *sender, char *addr,
#define IGNORE_ADDR_EXTENSION ((char **) 0)
if (relocated_maps != 0) {
- const char *newloc;
+ const char *reply;
+ DSN_SPLIT dp;
- if ((newloc = mail_addr_find(relocated_maps, STR(nextrcpt),
- IGNORE_ADDR_EXTENSION)) != 0) {
+ if ((reply = mail_addr_find(relocated_maps, STR(nextrcpt),
+ IGNORE_ADDR_EXTENSION)) != 0) {
vstring_strcpy(channel, MAIL_SERVICE_ERROR);
- /* 5.1.6 is the closest match, but not perfect. */
- vstring_sprintf(nexthop, "5.1.6 User has moved to %s", newloc);
+ if (var_enb_relocated_pfx) {
+ /* 5.1.6 is the closest match, but not perfect. */
+ vstring_sprintf(nexthop, "5.1.6 User has moved to %s", reply);
+ } else if (!dsn_valid(reply)
+ || dsn_split(&dp, "5.2.0", reply)->text[0] == 0) {
+ msg_warn("%s result must contain RFC 3463 status and text: '%.100s'",
+ VAR_RELOCATED_MAPS, reply);
+ vstring_sprintf(nexthop, "5.2.0 Mailbox is unavailable");
+ } else {
+ vstring_sprintf(nexthop, "%s %s", DSN_STATUS(dp.dsn), dp.text);
+ }
} else if (relocated_maps->error != 0) {
msg_warn("%s lookup failure", VAR_RELOCATED_MAPS);
*flags |= RESOLVE_FLAG_FAIL;
diff --git a/postfix/src/trivial-rewrite/trivial-rewrite.c b/postfix/src/trivial-rewrite/trivial-rewrite.c
index bb8da091d..8c896749c 100644
--- a/postfix/src/trivial-rewrite/trivial-rewrite.c
+++ b/postfix/src/trivial-rewrite/trivial-rewrite.c
@@ -352,6 +352,7 @@ char *var_virt_mailbox_maps; /* XXX virtual_mailbox_domains */
char *var_virt_alias_doms;
char *var_virt_mailbox_doms;
char *var_relocated_maps;
+bool var_enb_relocated_pfx;
char *var_def_transport;
char *var_snd_def_xport_maps;
char *var_empty_addr;
@@ -648,6 +649,7 @@ int main(int argc, char **argv)
};
static const CONFIG_NBOOL_TABLE nbool_table[] = {
VAR_APP_DOT_MYDOMAIN, DEF_APP_DOT_MYDOMAIN, &var_append_dot_mydomain,
+ VAR_ENB_RELOCATED_PFX, DEF_ENB_RELOCATED_PFX, &var_enb_relocated_pfx,
0,
};
diff --git a/postfix/src/util/Makefile.in b/postfix/src/util/Makefile.in
index 7c99ae23a..c6573320f 100644
--- a/postfix/src/util/Makefile.in
+++ b/postfix/src/util/Makefile.in
@@ -153,12 +153,14 @@ TESTPROG= dict_open dup2_pass_on_exec events exec_command fifo_open \
known_tcp_ports dict_stream find_inet binhash hash_fnv argv \
clean_env inet_prefix_top printable readlline quote_for_json \
normalize_ws valid_uri_scheme clean_ascii_cntrl_space \
- normalize_v4mapped_addr_test ossl_digest_test
+ normalize_v4mapped_addr_test ossl_digest_test dict_pipe_test \
+ dict_union_test
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
LIB_DIR = ../../lib
INC_DIR = ../../include
+TESTLIB = $(LIB_DIR)/libtesting.a
.c.o:; $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c
@@ -638,6 +640,12 @@ normalize_v4mapped_addr_test: normalize_v4mapped_addr_test.c $(LIB)
ossl_digest_test: ossl_digest_test.c $(LIB)
$(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS)
+dict_pipe_test: dict_pipe_test.c $(TESTLIB) $(LIB)
+ $(CC) $(CFLAGS) -o $@ $@.c $(TESTLIB) $(LIB) $(SYSLIBS)
+
+dict_union_test: dict_union_test.c $(TESTLIB) $(LIB)
+ $(CC) $(CFLAGS) -o $@ $@.c $(TESTLIB) $(LIB) $(SYSLIBS)
+
tests: all valid_hostname_test mac_expand_test dict_test unescape_test \
hex_quote_test ctable_test inet_addr_list_test base64_code_test \
attr_scan64_test attr_scan0_test host_port_test dict_tests \
@@ -650,12 +658,13 @@ tests: all valid_hostname_test mac_expand_test dict_test unescape_test \
binhash_test argv_test inet_prefix_top_test printable_test \
valid_utf8_string_test readlline_test quote_for_json_test \
normalize_ws_test valid_uri_scheme_test clean_ascii_cntrl_space_test \
- test_normalize_v4mapped_addr test_ossl_digest
+ test_normalize_v4mapped_addr test_ossl_digest test_dict_pipe
+ test_dict_union
dict_tests: all dict_test \
dict_pcre_tests dict_cidr_test dict_thash_test dict_static_test \
- dict_inline_test dict_utf8_test dict_regexp_test dict_union_test \
- dict_pipe_test dict_regexp_file_test dict_cidr_file_test \
+ dict_inline_test dict_utf8_test dict_regexp_test \
+ dict_regexp_file_test dict_cidr_file_test \
dict_static_file_test dict_random_test dict_random_file_test \
dict_inline_file_test dict_stream_test dict_inline_regexp_test \
dict_inline_cidr_test
@@ -1056,16 +1065,6 @@ vbuf_print_test: vbuf_print vbuf_print_test.in vbuf_print_test.ref
diff vbuf_print_test.ref vbuf_print_test.tmp
rm -f vbuf_print_test.tmp
-dict_union_test: dict_open dict_union_test.in dict_union_test.ref
- $(SHLIB_ENV) ${VALGRIND} sh -x dict_union_test.in >dict_union_test.tmp 2>&1
- diff dict_union_test.ref dict_union_test.tmp
- rm -f dict_union_test.tmp
-
-dict_pipe_test: dict_open dict_pipe_test.in dict_pipe_test.ref
- $(SHLIB_ENV) ${VALGRIND} sh -x dict_pipe_test.in >dict_pipe_test.tmp 2>&1
- diff dict_pipe_test.ref dict_pipe_test.tmp
- rm -f dict_pipe_test.tmp
-
vstring_test: dict_open vstring vstring_test.ref
$(SHLIB_ENV) ${VALGRIND} ./vstring one two three >vstring_test.tmp 2>&1
diff vstring_test.ref vstring_test.tmp
@@ -1142,6 +1141,12 @@ test_normalize_v4mapped_addr: update normalize_v4mapped_addr_test
test_ossl_digest: update ossl_digest_test
$(SHLIB_ENV) ${VALGRIND} ./ossl_digest_test
+test_dict_pipe: update dict_pipe_test
+ $(SHLIB_ENV) ${VALGRIND} ./dict_pipe_test
+
+test_dict_union: update dict_union_test
+ $(SHLIB_ENV) ${VALGRIND} ./dict_union_test
+
depend: $(MAKES)
(sed '1,/^# do not edit/!d' Makefile.in; \
set -e; for i in [a-z][a-z0-9]*.c; do \
@@ -1735,6 +1740,20 @@ dict_pipe.o: sys_defs.h
dict_pipe.o: vbuf.h
dict_pipe.o: vstream.h
dict_pipe.o: vstring.h
+dict_pipe_test.o: ../../include/dict_test_helper.h
+dict_pipe_test.o: argv.h
+dict_pipe_test.o: check_arg.h
+dict_pipe_test.o: dict.h
+dict_pipe_test.o: dict_pipe.h
+dict_pipe_test.o: dict_pipe_test.c
+dict_pipe_test.o: msg.h
+dict_pipe_test.o: msg_vstream.h
+dict_pipe_test.o: myflock.h
+dict_pipe_test.o: stringops.h
+dict_pipe_test.o: sys_defs.h
+dict_pipe_test.o: vbuf.h
+dict_pipe_test.o: vstream.h
+dict_pipe_test.o: vstring.h
dict_random.o: argv.h
dict_random.o: check_arg.h
dict_random.o: dict.h
@@ -1902,6 +1921,20 @@ dict_union.o: sys_defs.h
dict_union.o: vbuf.h
dict_union.o: vstream.h
dict_union.o: vstring.h
+dict_union_test.o: ../../include/dict_test_helper.h
+dict_union_test.o: argv.h
+dict_union_test.o: check_arg.h
+dict_union_test.o: dict.h
+dict_union_test.o: dict_union.h
+dict_union_test.o: dict_union_test.c
+dict_union_test.o: msg.h
+dict_union_test.o: msg_vstream.h
+dict_union_test.o: myflock.h
+dict_union_test.o: stringops.h
+dict_union_test.o: sys_defs.h
+dict_union_test.o: vbuf.h
+dict_union_test.o: vstream.h
+dict_union_test.o: vstring.h
dict_unix.o: argv.h
dict_unix.o: check_arg.h
dict_unix.o: dict.h
@@ -2602,6 +2635,26 @@ open_lock.o: sys_defs.h
open_lock.o: vbuf.h
open_lock.o: vstream.h
open_lock.o: vstring.h
+ossl_digest.o: argv.h
+ossl_digest.o: check_arg.h
+ossl_digest.o: msg.h
+ossl_digest.o: mymalloc.h
+ossl_digest.o: ossl_digest.c
+ossl_digest.o: ossl_digest.h
+ossl_digest.o: sys_defs.h
+ossl_digest.o: vbuf.h
+ossl_digest.o: vstring.h
+ossl_digest_test.o: argv.h
+ossl_digest_test.o: check_arg.h
+ossl_digest_test.o: hex_code.h
+ossl_digest_test.o: msg.h
+ossl_digest_test.o: mymalloc.h
+ossl_digest_test.o: ossl_digest.h
+ossl_digest_test.o: ossl_digest_test.c
+ossl_digest_test.o: stringops.h
+ossl_digest_test.o: sys_defs.h
+ossl_digest_test.o: vbuf.h
+ossl_digest_test.o: vstring.h
pass_accept.o: attr.h
pass_accept.o: check_arg.h
pass_accept.o: htable.h
diff --git a/postfix/src/util/dict.c b/postfix/src/util/dict.c
index 35c02db05..1fe169d2d 100644
--- a/postfix/src/util/dict.c
+++ b/postfix/src/util/dict.c
@@ -69,6 +69,12 @@
/*
/* int dict_flags_mask(names)
/* const char *names;
+/*
+/* char *dict_make_registered_name(
+/* VSTRING *out,
+/* const char *type_name,
+/* int open_flags,
+/* int dict_flags)
/* DESCRIPTION
/* This module maintains a collection of name-value dictionaries.
/* Each dictionary has its own name and has its own methods to read
@@ -166,6 +172,12 @@
/*
/* dict_flags_mask() returns the bitmask for the specified
/* comma/space-separated dictionary flag names.
+/*
+/* dict_make_registered_name() formats a dictionary type:name and
+/* (initial) flag values for use in dict_register() calls.
+/* This encourages consistent sharing of dictionary instances that
+/* have the exact same type:name and (initial) flags. The result
+/* value is the string value of the \fIout\fR VSTRING buffer.
/* TRUST AND PROVENANCE
/* .ad
/* .fi
@@ -668,3 +680,13 @@ int dict_flags_mask(const char *names)
{
return (name_mask("dictionary flags", dict_mask, names));
}
+
+/* dict_make_registered_name - format registry name for consistent sharing */
+
+char *dict_make_registered_name(VSTRING *out, const char *type_name,
+ int open_flags, int dict_flags)
+{
+ return (STR(vstring_sprintf(out, "%s(%o,%s)",
+ type_name, open_flags,
+ dict_flags_str(dict_flags))));
+}
diff --git a/postfix/src/util/dict.h b/postfix/src/util/dict.h
index ef0e83343..a6badac26 100644
--- a/postfix/src/util/dict.h
+++ b/postfix/src/util/dict.h
@@ -280,6 +280,13 @@ void dict_test(int, char **);
extern int dict_allow_surrogate;
extern DICT *PRINTFLIKE(5, 6) dict_surrogate(const char *, const char *, int, int, const char *,...);
+ /*
+ * Support for consistent sharing and collision avoidance when tables are
+ * registered with dict_register(). Only share instances that have the same
+ * type, name, open_flags, and (initial) dict_flags.
+ */
+extern char *dict_make_registered_name(VSTRING *, const char *, int, int);
+
/*
* This name is reserved for matchlist error handling.
*/
diff --git a/postfix/src/util/dict_pipe.c b/postfix/src/util/dict_pipe.c
index 8ce0faad7..9002bd369 100644
--- a/postfix/src/util/dict_pipe.c
+++ b/postfix/src/util/dict_pipe.c
@@ -115,6 +115,8 @@ DICT *dict_pipe_open(const char *name, int open_flags, int dict_flags)
int match_flags = 0;
struct DICT_OWNER aggr_owner;
size_t len;
+ VSTRING *reg_name_buf = vstring_alloc(100);
+ char *reg_name;
/*
* Clarity first. Let the optimizer worry about redundant code.
@@ -124,6 +126,8 @@ DICT *dict_pipe_open(const char *name, int open_flags, int dict_flags)
myfree(saved_name); \
if (argv != 0) \
argv_free(argv); \
+ if (reg_name_buf != 0) \
+ vstring_free(reg_name_buf); \
return (x); \
} while (0)
@@ -151,13 +155,10 @@ DICT *dict_pipe_open(const char *name, int open_flags, int dict_flags)
DICT_TYPE_PIPE));
/*
- * The least-trusted table in the pipeline determines the over-all trust
- * level. The first table determines the pattern-matching flags.
+ * Check all underlying table specs before registering any of them to
+ * avoid leaking refcounts if one of them is bad.
*/
- DICT_OWNER_AGGREGATE_INIT(aggr_owner);
for (cpp = argv->argv; (dict_type_name = *cpp) != 0; cpp++) {
- if (msg_verbose)
- msg_info("%s: %s", myname, dict_type_name);
if (strchr(dict_type_name, ':') == 0)
DICT_PIPE_RETURN(dict_surrogate(DICT_TYPE_PIPE, name,
open_flags, dict_flags,
@@ -165,9 +166,22 @@ DICT *dict_pipe_open(const char *name, int open_flags, int dict_flags)
"need \"%s:{type:name...}\"",
DICT_TYPE_PIPE, name,
DICT_TYPE_PIPE));
- if ((dict = dict_handle(dict_type_name)) == 0)
+ }
+
+ /*
+ * The least-trusted table in the pipeline determines the over-all trust
+ * level. The first table determines the pattern-matching flags.
+ */
+ DICT_OWNER_AGGREGATE_INIT(aggr_owner);
+ for (cpp = argv->argv; (dict_type_name = *cpp) != 0; cpp++) {
+ if (msg_verbose)
+ msg_info("%s: %s", myname, dict_type_name);
+ reg_name = dict_make_registered_name(reg_name_buf, dict_type_name,
+ open_flags, dict_flags);
+ if ((dict = dict_handle(reg_name)) == 0)
dict = dict_open(dict_type_name, open_flags, dict_flags);
- dict_register(dict_type_name, dict);
+ dict_register(reg_name, dict);
+ argv_replace_one(argv, cpp - argv->argv, reg_name);
DICT_OWNER_AGGREGATE_UPDATE(aggr_owner, dict->owner);
if (cpp == argv->argv)
match_flags = dict->flags & (DICT_FLAG_FIXED | DICT_FLAG_PATTERN);
@@ -182,7 +196,8 @@ DICT *dict_pipe_open(const char *name, int open_flags, int dict_flags)
dict_pipe->dict.close = dict_pipe_close;
dict_pipe->dict.flags = dict_flags | match_flags;
dict_pipe->dict.owner = aggr_owner;
- dict_pipe->qr_buf = vstring_alloc(100);
+ dict_pipe->qr_buf = reg_name_buf;
+ reg_name_buf = 0;
dict_pipe->map_pipe = argv;
argv = 0;
DICT_PIPE_RETURN(DICT_DEBUG (&dict_pipe->dict));
diff --git a/postfix/src/util/dict_pipe_test.c b/postfix/src/util/dict_pipe_test.c
new file mode 100644
index 000000000..79bf3fa55
--- /dev/null
+++ b/postfix/src/util/dict_pipe_test.c
@@ -0,0 +1,226 @@
+/*++
+/* AUTHOR(S)
+/* Wietse Venema
+/* porcupine.org
+/*--*/
+
+ /*
+ * System library.
+ */
+#include
+#include
+#include
+
+ /*
+ * Utility library.
+ */
+#include
+#include
+#include
+#include
+#include
+#include
+
+ /*
+ * Testing library.
+ */
+#include
+
+#define LEN(x) VSTRING_LEN(x)
+#define STR(x) vstring_str(x)
+
+ /*
+ * TODO(wietse) move these to common testing header file.
+ */
+#define PASS 1
+#define FAIL 0
+
+static VSTRING *msg_buf;
+
+static int valid_refcounts_for_good_composite_syntax(void)
+{
+ DICT *dict;
+ int open_flags = O_RDONLY;
+ int dict_flags = DICT_FLAG_LOCK;
+ VSTRING *composite_spec = vstring_alloc(100);
+ ARGV *reg_component_specs = argv_alloc(3);
+ const char *component_specs[] = {
+ "static:foo",
+ "inline:{xx=yy}",
+ "unix:passwd.byname",
+ 0,
+ };
+ char **cpp;
+
+ dict_compose_spec(DICT_TYPE_PIPE, component_specs, open_flags, dict_flags,
+ composite_spec, reg_component_specs);
+ dict = dict_open_and_capture_msg(STR(composite_spec), open_flags,
+ dict_flags, msg_buf);
+
+#undef RETURN
+#define RETURN(x) do { \
+ if (dict) dict_close(dict); \
+ vstring_free(composite_spec); \
+ argv_free(reg_component_specs); \
+ return (x); \
+ } while (0);
+
+ if (LEN(msg_buf) > 0) {
+ msg_warn("unexpected dict_open() warning: got %s", STR(msg_buf));
+ RETURN(FAIL);
+ }
+ for (cpp = reg_component_specs->argv; *cpp; cpp++) {
+ if (dict_handle(*cpp) == 0) {
+ msg_warn("table '%s' is not registered after dict_open()", *cpp);
+ RETURN(FAIL);
+ }
+ }
+ dict_close(dict);
+ dict = 0;
+ for (cpp = reg_component_specs->argv; *cpp; cpp++) {
+ if (dict_handle(*cpp) != 0) {
+ msg_warn("table '%s' is still registered after dict_close()", *cpp);
+ RETURN(FAIL);
+ }
+ }
+ RETURN(PASS);
+}
+
+static int valid_refcounts_for_bad_composite_syntax(void)
+{
+ DICT *dict;
+ int open_flags = O_RDONLY;
+ int dict_flags = DICT_FLAG_LOCK;
+ VSTRING *composite_spec = vstring_alloc(100);
+ ARGV *reg_component_specs = argv_alloc(3);
+ const char *component_specs[] = {
+ "static:foo",
+ "inline{xx=yy}",
+ "unix:passwd.byname",
+ 0,
+ };
+ char **cpp;
+ const char *want_msg = "bad syntax:";
+
+ dict_compose_spec(DICT_TYPE_PIPE, component_specs, open_flags, dict_flags,
+ composite_spec, reg_component_specs);
+ dict = dict_open_and_capture_msg(STR(composite_spec), open_flags, dict_flags,
+ msg_buf);
+
+#undef RETURN
+#define RETURN(x) do { \
+ dict_close(dict); \
+ vstring_free(composite_spec); \
+ argv_free(reg_component_specs); \
+ return (x); \
+ } while (0);
+
+ if (LEN(msg_buf) == 0) {
+ msg_warn("missing dict_open() warning: want '%s'", want_msg);
+ RETURN(FAIL);
+ }
+ if (strstr(STR(msg_buf), want_msg) == 0) {
+ msg_warn("unexpected warning message: got '%s', want '%s'",
+ STR(msg_buf), want_msg);
+ RETURN(FAIL);
+ }
+ for (cpp = reg_component_specs->argv; *cpp; cpp++) {
+ if (dict_handle(*cpp) != 0) {
+ msg_warn("table '%s' is registered after failed dict_open()",
+ *cpp);
+ RETURN(FAIL);
+ }
+ }
+ RETURN(PASS);
+}
+
+static int propagates_notfound_and_found(void)
+{
+ DICT *dict;
+ int open_flags = O_RDONLY;
+ int dict_flags = DICT_FLAG_LOCK;
+ const char *dict_spec = "pipemap:{inline:{k1=v1,k2=v2},inline:{v2=v3}}";
+ static struct dict_get_verify_data expectations[] = {
+ {.key = "k0",.want_value = 0},
+ {.key = "k1",.want_value = 0},
+ {.key = "k2",.want_value = "v3"},
+ {0},
+ };
+ int ret;
+
+ dict = dict_open_and_capture_msg(dict_spec, open_flags, dict_flags,
+ msg_buf);
+ if (LEN(msg_buf) > 0) {
+ msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf));
+ ret = FAIL;
+ } else {
+ ret = dict_get_and_verify_bulk(dict, expectations);
+ }
+ dict_close(dict);
+ return (ret);
+}
+
+static int propagates_notfound_and_error(void)
+{
+ DICT *dict;
+ int open_flags = O_RDONLY;
+ int dict_flags = DICT_FLAG_LOCK;
+ const char *dict_spec = "pipemap:{inline:{k1=v1},fail:fail}";
+ static struct dict_get_verify_data expectations[] = {
+ {.key = "k0",.want_value = 0,.want_error = DICT_ERR_NONE},
+ {.key = "k1",.want_value = 0,.want_error = DICT_ERR_RETRY},
+ {0},
+ };
+ int ret;
+
+ dict = dict_open_and_capture_msg(dict_spec, open_flags, dict_flags,
+ msg_buf);
+ if (LEN(msg_buf) > 0) {
+ msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf));
+ ret = FAIL;
+ } else {
+ ret = dict_get_and_verify_bulk(dict, expectations);
+ }
+ dict_close(dict);
+ return (ret);
+}
+
+struct TEST_CASE {
+ const char *label;
+ int (*action) (void);
+};
+
+static const struct TEST_CASE test_cases[] = {
+ {"valid_refcounts_for_good_composite_syntax", valid_refcounts_for_good_composite_syntax,},
+ {"valid_refcounts_for_bad_composite_syntax", valid_refcounts_for_bad_composite_syntax,},
+ {"propagates_notfound_and_found", propagates_notfound_and_found,},
+ {"propagates_notfound_and_error", propagates_notfound_and_error,},
+ {0},
+};
+
+int main(int argc, char **argv)
+{
+ static int tests_passed = 0;
+ static int tests_failed = 0;
+ const struct TEST_CASE *tp;
+
+ msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
+
+ msg_buf = vstring_alloc(100);
+ dict_allow_surrogate = 1;
+
+ for (tp = test_cases; tp->label; tp++) {
+ msg_info("RUN %s", tp->label);
+ if (tp->action() == PASS) {
+ msg_info("PASS %s", tp->label);
+ tests_passed += 1;
+ } else {
+ msg_info("FAIL %s", tp->label);
+ tests_failed += 1;
+ }
+ }
+ vstring_free(msg_buf);
+
+ msg_info("PASS=%d FAIL=%d", tests_passed, tests_failed);
+ exit(tests_failed != 0);
+}
diff --git a/postfix/src/util/dict_pipe_test.in b/postfix/src/util/dict_pipe_test.in
deleted file mode 100644
index 9626dcd3f..000000000
--- a/postfix/src/util/dict_pipe_test.in
+++ /dev/null
@@ -1,9 +0,0 @@
-${VALGRIND} ./dict_open 'pipemap:{inline:{k1=v1,k2=v2},inline:{v2=v3}}' read < get k0
-k0: not found
-> get k1
-k1: not found
-> get k2
-k2=v3
-+ ./dict_open pipemap:{inline:{k1=v1},fail:fail} read
-owner=trusted (uid=2147483647)
-> get k0
-k0: not found
-> get k1
-k1: error
diff --git a/postfix/src/util/dict_union.c b/postfix/src/util/dict_union.c
index 80df03b61..cda3fe177 100644
--- a/postfix/src/util/dict_union.c
+++ b/postfix/src/util/dict_union.c
@@ -128,15 +128,19 @@ DICT *dict_union_open(const char *name, int open_flags, int dict_flags)
int match_flags = 0;
struct DICT_OWNER aggr_owner;
size_t len;
+ VSTRING *reg_name_buf = vstring_alloc(100);
+ char *reg_name;
/*
* Clarity first. Let the optimizer worry about redundant code.
*/
#define DICT_UNION_RETURN(x) do { \
if (saved_name != 0) \
- myfree(saved_name); \
+ myfree(saved_name); \
if (argv != 0) \
- argv_free(argv); \
+ argv_free(argv); \
+ if (reg_name_buf != 0) \
+ vstring_free(reg_name_buf); \
return (x); \
} while (0)
@@ -164,13 +168,10 @@ DICT *dict_union_open(const char *name, int open_flags, int dict_flags)
DICT_TYPE_UNION));
/*
- * The least-trusted table in the set determines the over-all trust
- * level. The first table determines the pattern-matching flags.
+ * Check all underlying table specs before registering any of them to
+ * avoid leaking refcounts if one of them is bad.
*/
- DICT_OWNER_AGGREGATE_INIT(aggr_owner);
for (cpp = argv->argv; (dict_type_name = *cpp) != 0; cpp++) {
- if (msg_verbose)
- msg_info("%s: %s", myname, dict_type_name);
if (strchr(dict_type_name, ':') == 0)
DICT_UNION_RETURN(dict_surrogate(DICT_TYPE_UNION, name,
open_flags, dict_flags,
@@ -178,9 +179,22 @@ DICT *dict_union_open(const char *name, int open_flags, int dict_flags)
"need \"%s:{type:name...}\"",
DICT_TYPE_UNION, name,
DICT_TYPE_UNION));
- if ((dict = dict_handle(dict_type_name)) == 0)
+ }
+
+ /*
+ * The least-trusted table in the set determines the over-all trust
+ * level. The first table determines the pattern-matching flags.
+ */
+ DICT_OWNER_AGGREGATE_INIT(aggr_owner);
+ for (cpp = argv->argv; (dict_type_name = *cpp) != 0; cpp++) {
+ if (msg_verbose)
+ msg_info("%s: %s", myname, dict_type_name);
+ reg_name = dict_make_registered_name(reg_name_buf, dict_type_name,
+ open_flags, dict_flags);
+ if ((dict = dict_handle(reg_name)) == 0)
dict = dict_open(dict_type_name, open_flags, dict_flags);
- dict_register(dict_type_name, dict);
+ dict_register(reg_name, dict);
+ argv_replace_one(argv, cpp - argv->argv, reg_name);
DICT_OWNER_AGGREGATE_UPDATE(aggr_owner, dict->owner);
if (cpp == argv->argv)
match_flags = dict->flags & (DICT_FLAG_FIXED | DICT_FLAG_PATTERN);
@@ -195,7 +209,8 @@ DICT *dict_union_open(const char *name, int open_flags, int dict_flags)
dict_union->dict.close = dict_union_close;
dict_union->dict.flags = dict_flags | match_flags;
dict_union->dict.owner = aggr_owner;
- dict_union->re_buf = vstring_alloc(100);
+ dict_union->re_buf = reg_name_buf;
+ reg_name_buf = 0;
dict_union->map_union = argv;
argv = 0;
DICT_UNION_RETURN(DICT_DEBUG (&dict_union->dict));
diff --git a/postfix/src/util/dict_union_test.c b/postfix/src/util/dict_union_test.c
new file mode 100644
index 000000000..0ed87b892
--- /dev/null
+++ b/postfix/src/util/dict_union_test.c
@@ -0,0 +1,253 @@
+/*++
+/* AUTHOR(S)
+/* Wietse Venema
+/* porcupine.org
+/*--*/
+
+ /*
+ * System library.
+ */
+#include
+#include
+#include
+
+ /*
+ * Utility library.
+ */
+#include
+#include
+#include
+#include
+#include
+#include
+
+ /*
+ * Testing library.
+ */
+#include
+
+#define LEN(x) VSTRING_LEN(x)
+#define STR(x) vstring_str(x)
+
+ /*
+ * TODO(wietse) move these to common testing header file.
+ */
+#define PASS 1
+#define FAIL 0
+
+static VSTRING *msg_buf;
+
+static int valid_refcounts_for_good_composite_syntax(void)
+{
+ DICT *dict;
+ int open_flags = O_RDONLY;
+ int dict_flags = DICT_FLAG_LOCK;
+ VSTRING *composite_spec = vstring_alloc(100);
+ ARGV *reg_component_specs = argv_alloc(3);
+ const char *component_specs[] = {
+ "static:one",
+ "static:two",
+ "inline:{foo=three}",
+ 0,
+ };
+ char **cpp;
+
+ dict_compose_spec(DICT_TYPE_UNION, component_specs, open_flags, dict_flags,
+ composite_spec, reg_component_specs);
+ dict = dict_open_and_capture_msg(STR(composite_spec), open_flags, dict_flags,
+ msg_buf);
+
+#undef RETURN
+#define RETURN(x) do { \
+ if (dict) dict_close(dict); \
+ vstring_free(composite_spec); \
+ argv_free(reg_component_specs); \
+ return (x); \
+ } while (0);
+
+ if (LEN(msg_buf) > 0) {
+ msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf));
+ RETURN(FAIL);
+ }
+ for (cpp = reg_component_specs->argv; *cpp; cpp++) {
+ if (dict_handle(*cpp) == 0) {
+ msg_warn("table '%s' is not registered after dict_open()", *cpp);
+ RETURN(FAIL);
+ }
+ }
+ dict_close(dict);
+ dict = 0;
+ for (cpp = reg_component_specs->argv; *cpp; cpp++) {
+ if (dict_handle(*cpp) != 0) {
+ msg_warn("table '%s' is still registered after dict_close()", *cpp);
+ RETURN(FAIL);
+ }
+ }
+ RETURN(PASS);
+}
+
+static int valid_refcounts_for_bad_composite_syntax(void)
+{
+ DICT *dict;
+ int open_flags = O_RDONLY;
+ int dict_flags = DICT_FLAG_LOCK;
+ VSTRING *composite_spec = vstring_alloc(100);
+ ARGV *reg_component_specs = argv_alloc(3);
+ const char *component_specs[] = {
+ "static:one",
+ "static:two",
+ "inline{foo=three}",
+ 0,
+ };
+ char **cpp;
+ const char *want_msg = "bad syntax:";
+
+ dict_compose_spec(DICT_TYPE_UNION, component_specs, open_flags, dict_flags,
+ composite_spec, reg_component_specs);
+ dict = dict_open_and_capture_msg(STR(composite_spec), open_flags,
+ dict_flags, msg_buf);
+
+#undef RETURN
+#define RETURN(x) do { \
+ dict_close(dict); \
+ vstring_free(composite_spec); \
+ argv_free(reg_component_specs); \
+ return (x); \
+ } while (0);
+
+ if (LEN(msg_buf) == 0) {
+ msg_warn("missing dict_open() warning: want '%s'", want_msg);
+ RETURN(FAIL);
+ }
+ if (strstr(STR(msg_buf), want_msg) == 0) {
+ msg_warn("unexpected warning message: got '%s', want '%s'",
+ STR(msg_buf), want_msg);
+ RETURN(FAIL);
+ }
+ for (cpp = reg_component_specs->argv; *cpp; cpp++) {
+ if (dict_handle(*cpp) != 0) {
+ msg_warn("table '%s' is registered after failed dict_open()",
+ *cpp);
+ RETURN(FAIL);
+ }
+ }
+ RETURN(PASS);
+}
+
+static int propagates_notfound_and_found(void)
+{
+ DICT *dict;
+ int open_flags = O_RDONLY;
+ int dict_flags = DICT_FLAG_LOCK;
+ const char *dict_spec = ("unionmap:{static:one,static:two,"
+ "inline:{foo=three}}");
+ static struct dict_get_verify_data expectations[] = {
+ {.key = "foo",.want_value = "one,two,three"},
+ {.key = "bar",.want_value = "one,two"},
+ {0},
+ };
+ int ret;
+
+ dict = dict_open_and_capture_msg(dict_spec, open_flags, dict_flags,
+ msg_buf);
+ if (LEN(msg_buf) > 0) {
+ msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf));
+ ret = FAIL;
+ } else {
+ ret = dict_get_and_verify_bulk(dict, expectations);
+ }
+ dict_close(dict);
+ return (ret);
+}
+
+static int propagates_error(void)
+{
+ DICT *dict;
+ int open_flags = O_RDONLY;
+ int dict_flags = DICT_FLAG_LOCK;
+ const char *dict_spec = "unionmap:{static:one,fail:fail}";
+ static struct dict_get_verify_data expectations[] = {
+ {.key = "foo",.want_value = 0,.want_error = DICT_ERR_RETRY},
+ {0},
+ };
+ int ret;
+
+ dict = dict_open_and_capture_msg(dict_spec, open_flags, dict_flags,
+ msg_buf);
+ if (LEN(msg_buf) > 0) {
+ msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf));
+ ret = FAIL;
+ } else {
+ ret = dict_get_and_verify_bulk(dict, expectations);
+ }
+ dict_close(dict);
+ return (ret);
+}
+
+static int no_comma_for_not_found(void)
+{
+ DICT *dict;
+ int open_flags = O_RDONLY;
+ int dict_flags = DICT_FLAG_LOCK;
+ const char *dict_spec = "unionmap:{regexp:{{/a|c/ 1}},regexp:{{/b|c/ 2}}}";
+ static struct dict_get_verify_data expectations[] = {
+ {.key = "x",.want_value = 0},
+ {.key = "a",.want_value = "1"},
+ {.key = "b",.want_value = "2"},
+ {.key = "c",.want_value = "1,2"},
+ {0},
+ };
+ int ret;
+
+ dict = dict_open_and_capture_msg(dict_spec, open_flags, dict_flags,
+ msg_buf);
+ if (LEN(msg_buf) > 0) {
+ msg_warn("unexpected dict_open() warning: got '%s'", STR(msg_buf));
+ ret = FAIL;
+ } else {
+ ret = dict_get_and_verify_bulk(dict, expectations);
+ }
+ dict_close(dict);
+ return (ret);
+}
+
+struct TEST_CASE {
+ const char *label;
+ int (*action) (void);
+};
+
+static const struct TEST_CASE test_cases[] = {
+ {"valid_refcounts_for_good_composite_syntax", valid_refcounts_for_good_composite_syntax,},
+ {"valid_refcounts_for_bad_composite_syntax", valid_refcounts_for_bad_composite_syntax,},
+ {"propagates_notfound_and_found", propagates_notfound_and_found,},
+ {"propagates_error", propagates_error,},
+ {"no_comma_for_not_found", no_comma_for_not_found,},
+ {0},
+};
+
+int main(int argc, char **argv)
+{
+ static int tests_passed = 0;
+ static int tests_failed = 0;
+ const struct TEST_CASE *tp;
+
+ msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
+
+ msg_buf = vstring_alloc(100);
+ dict_allow_surrogate = 1;
+
+ for (tp = test_cases; tp->label; tp++) {
+ msg_info("RUN %s", tp->label);
+ if (tp->action() == PASS) {
+ msg_info("PASS %s", tp->label);
+ tests_passed += 1;
+ } else {
+ msg_info("FAIL %s", tp->label);
+ tests_failed += 1;
+ }
+ }
+ vstring_free(msg_buf);
+
+ msg_info("PASS=%d FAIL=%d", tests_passed, tests_failed);
+ exit(tests_failed != 0);
+}
diff --git a/postfix/src/util/dict_union_test.in b/postfix/src/util/dict_union_test.in
deleted file mode 100644
index 9d111d43e..000000000
--- a/postfix/src/util/dict_union_test.in
+++ /dev/null
@@ -1,7 +0,0 @@
-${VALGRIND} ./dict_open 'unionmap:{static:one,static:two,inline:{foo=three}}' read < get foo
-foo=one,two,three
-> get bar
-bar=one,two
-+ ./dict_open unionmap:{static:one,fail:fail} read
-owner=trusted (uid=2147483647)
-> get foo
-foo: error