summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bounce/bounce_notify_util.c13
-rw-r--r--src/bounce/with-msgid-with-filter-no-thread.ref2
-rw-r--r--src/bounce/with-msgid-with-filter-with-thread.ref2
-rw-r--r--src/cleanup/Makefile.in44
-rw-r--r--src/cleanup/cleanup.c24
-rw-r--r--src/cleanup/cleanup_addr.c6
-rw-r--r--src/cleanup/cleanup_init.c2
-rw-r--r--src/cleanup/cleanup_message.c5
-rw-r--r--src/cleanup/cleanup_milter.c9
-rw-r--r--src/cleanup/cleanup_milter.in18a8
-rw-r--r--src/cleanup/cleanup_milter.in18b8
-rw-r--r--src/cleanup/cleanup_milter.in18c9
-rw-r--r--src/cleanup/cleanup_milter.in18d8
-rw-r--r--src/cleanup/cleanup_milter.ref18a11
-rw-r--r--src/cleanup/cleanup_milter.ref18a229
-rw-r--r--src/cleanup/cleanup_milter.ref18b11
-rw-r--r--src/cleanup/cleanup_milter.ref18b227
-rw-r--r--src/cleanup/cleanup_milter.ref18c11
-rw-r--r--src/cleanup/cleanup_milter.ref18c229
-rw-r--r--src/cleanup/cleanup_milter.ref18d11
-rw-r--r--src/cleanup/cleanup_milter.ref18d227
-rw-r--r--src/cleanup/test-queue-file18bin0 -> 653 bytes
-rw-r--r--src/dns/Makefile.in26
-rw-r--r--src/dns/dns_lookup.c8
-rw-r--r--src/dns/dns_rr_test.c433
-rw-r--r--src/dns/mxonly_test.ref2
-rw-r--r--src/dns/no-mx.ref6
-rw-r--r--src/dns/test_dns_lookup.c5
-rw-r--r--src/dnsblog/dnsblog.c3
-rw-r--r--src/global/Makefile.in31
-rw-r--r--src/global/dict_ldap.c4
-rw-r--r--src/global/dict_mongodb.c570
-rwxr-xr-xsrc/global/dict_mongodb.h43
-rw-r--r--src/global/dict_mysql.c87
-rw-r--r--src/global/dict_pgsql.c73
-rw-r--r--src/global/dict_sqlite.c2
-rw-r--r--src/global/mail_addr_find.c4
-rw-r--r--src/global/mail_date.c9
-rw-r--r--src/global/mail_dict.c4
-rw-r--r--src/global/mail_params.c19
-rw-r--r--src/global/mail_params.h32
-rw-r--r--src/global/mail_proto.h7
-rw-r--r--src/global/mail_version.h4
-rw-r--r--src/global/maillog_client.c2
-rw-r--r--src/global/maps.c12
-rw-r--r--src/global/wildcard_inet_addr.c2
-rw-r--r--src/local/command.c5
-rw-r--r--src/local/local.c31
-rw-r--r--src/master/master.c12
-rw-r--r--src/master/master_ent.c8
-rw-r--r--src/oqmgr/qmgr_deliver.c4
-rw-r--r--src/pipe/pipe.c9
-rw-r--r--src/postalias/postalias.c2
-rw-r--r--src/postcat/postcat.c6
-rw-r--r--src/postconf/Makefile.in85
-rw-r--r--src/postconf/postconf.c40
-rw-r--r--src/postconf/postconf.h7
-rw-r--r--src/postconf/postconf_dbms.c49
-rw-r--r--src/postconf/postconf_unused.c112
-rw-r--r--src/postconf/postconf_user.c16
-rw-r--r--src/postconf/test29.ref3
-rw-r--r--src/postconf/test72.ref3
-rw-r--r--src/postconf/test73.ref3
-rw-r--r--src/postconf/test74.ref3
-rw-r--r--src/postconf/test75.ref3
-rw-r--r--src/postconf/test76.ref9
-rw-r--r--src/postdrop/postdrop.c2
-rw-r--r--src/postfix/postfix.c3
-rw-r--r--src/postkick/postkick.c2
-rw-r--r--src/postlock/postlock.c2
-rw-r--r--src/postlog/postlog.c6
-rw-r--r--src/postlogd/postlogd.c14
-rw-r--r--src/postmap/postmap.c2
-rw-r--r--src/postqueue/showq_json.c79
-rw-r--r--src/postscreen/postscreen.c32
-rw-r--r--src/postscreen/postscreen_smtpd.c16
-rw-r--r--src/postsuper/postsuper.c2
-rw-r--r--src/posttls-finger/posttls-finger.c64
-rw-r--r--src/proxymap/proxymap.c38
-rw-r--r--src/qmgr/qmgr.c2
-rw-r--r--src/qmgr/qmgr_deliver.c4
-rw-r--r--src/qmqpd/qmqpd.c2
-rw-r--r--src/sendmail/sendmail.c7
-rw-r--r--src/smtp/lmtp_params.c2
-rw-r--r--src/smtp/smtp.c136
-rw-r--r--src/smtp/smtp.h2
-rw-r--r--src/smtp/smtp_addr.c2
-rw-r--r--src/smtp/smtp_params.c2
-rw-r--r--src/smtp/smtp_proto.c2
-rw-r--r--src/smtp/smtp_sasl_glue.c4
-rw-r--r--src/smtp/smtp_tls_policy.c56
-rw-r--r--src/smtpd/Makefile.in11
-rw-r--r--src/smtpd/smtpd.c117
-rw-r--r--src/smtpd/smtpd.h4
-rw-r--r--src/smtpd/smtpd_check.c177
-rw-r--r--src/smtpd/smtpd_check_backup.ref1
-rw-r--r--src/smtpd/smtpd_deprecated.in20
-rw-r--r--src/smtpd/smtpd_deprecated.ref35
-rw-r--r--src/smtpd/smtpd_exp.ref10
-rw-r--r--src/smtpd/smtpd_sasl_glue.c4
-rw-r--r--src/smtpd/smtpd_state.c1
-rw-r--r--src/smtpstone/smtp-source.c94
-rw-r--r--src/tls/tls.h31
-rw-r--r--src/tls/tls_client.c157
-rw-r--r--src/tls/tls_dane.c77
-rw-r--r--src/tls/tls_fprint.c53
-rw-r--r--src/tls/tls_misc.c129
-rw-r--r--src/tls/tls_proxy.h13
-rw-r--r--src/tls/tls_proxy_client_print.c1
-rw-r--r--src/tls/tls_proxy_client_scan.c3
-rw-r--r--src/tls/tls_proxy_context_print.c4
-rw-r--r--src/tls/tls_proxy_context_scan.c4
-rw-r--r--src/tls/tls_server.c73
-rw-r--r--src/tls/tls_verify.c18
-rw-r--r--src/tlsproxy/tlsproxy.c11
-rw-r--r--src/trivial-rewrite/resolve.c2
-rw-r--r--src/trivial-rewrite/trivial-rewrite.c17
-rw-r--r--src/util/Makefile.in47
-rw-r--r--src/util/argv.c57
-rw-r--r--src/util/argv.h2
-rw-r--r--src/util/casefold.c2
-rw-r--r--src/util/dict_inline.c2
-rw-r--r--src/util/dict_thash.c6
-rw-r--r--src/util/dict_utf8.c4
-rw-r--r--src/util/inet_prefix_top.c1
-rw-r--r--src/util/logwriter.c34
-rw-r--r--src/util/logwriter.h1
-rw-r--r--src/util/midna_domain.c4
-rw-r--r--src/util/parse_utf8_char.h122
-rw-r--r--src/util/printable.c162
-rw-r--r--src/util/quote_for_json.c218
-rw-r--r--src/util/readlline.c231
-rw-r--r--src/util/stringops.h3
-rw-r--r--src/util/sys_defs.h7
-rw-r--r--src/util/valid_hostname.c13
-rw-r--r--src/util/valid_hostname.in6
-rw-r--r--src/util/valid_hostname.ref10
-rw-r--r--src/util/valid_utf8_string.c247
-rw-r--r--src/util/vstream.c34
-rw-r--r--src/xsasl/xsasl_cyrus_server.c4
-rw-r--r--src/xsasl/xsasl_server.c9
141 files changed, 4042 insertions, 788 deletions
diff --git a/src/bounce/bounce_notify_util.c b/src/bounce/bounce_notify_util.c
index 781a525..2482359 100644
--- a/src/bounce/bounce_notify_util.c
+++ b/src/bounce/bounce_notify_util.c
@@ -773,15 +773,14 @@ int bounce_header_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
post_mail_fprintf(bounce, "X-%s-Queue-ID: %s",
bounce_info->mail_name, bounce_info->queue_id);
-#define IS_UTF8_ADDRESS(str, len) \
- ((str)[0] != 0 && !allascii(str) && valid_utf8_string((str), (len)))
+#define IS_UTF8_ADDRESS(str) \
+ ((str)[0] != 0 && !allascii(str) && valid_utf8_stringz(str))
/* Fix 20140708: use "utf-8" or "rfc822" as appropriate. */
if (VSTRING_LEN(bounce_info->sender) > 0)
post_mail_fprintf(bounce, "X-%s-Sender: %s; %s",
bounce_info->mail_name, bounce_info->smtputf8
- && IS_UTF8_ADDRESS(STR(bounce_info->sender),
- VSTRING_LEN(bounce_info->sender)) ?
+ && IS_UTF8_ADDRESS(STR(bounce_info->sender)) ?
"utf-8" : "rfc822", STR(bounce_info->sender));
if (bounce_info->arrival_time > 0)
post_mail_fprintf(bounce, "Arrival-Date: %s",
@@ -800,8 +799,7 @@ int bounce_recipient_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
/* Fix 20140708: Don't send "utf-8" type with non-UTF8 address. */
post_mail_fprintf(bounce, "Final-Recipient: %s; %s",
bounce_info->smtputf8
- && IS_UTF8_ADDRESS(rcpt->address,
- strlen(rcpt->address)) ?
+ && IS_UTF8_ADDRESS(rcpt->address) ?
"utf-8" : "rfc822", rcpt->address);
/*
@@ -829,8 +827,7 @@ int bounce_recipient_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info)
/* Fix 20140708: Don't send "utf-8" type with non-UTF8 address. */
post_mail_fprintf(bounce, "Original-Recipient: %s; %s",
bounce_info->smtputf8
- && IS_UTF8_ADDRESS(rcpt->orig_addr,
- strlen(rcpt->orig_addr)) ?
+ && IS_UTF8_ADDRESS(rcpt->orig_addr) ?
"utf-8" : "rfc822", rcpt->orig_addr);
}
post_mail_fprintf(bounce, "Action: %s",
diff --git a/src/bounce/with-msgid-with-filter-no-thread.ref b/src/bounce/with-msgid-with-filter-no-thread.ref
index fa30ddf..adcd507 100644
--- a/src/bounce/with-msgid-with-filter-no-thread.ref
+++ b/src/bounce/with-msgid-with-filter-no-thread.ref
@@ -24,7 +24,7 @@ Reporting-MTA: dns; mail.example
Original-Envelope-Id: TEST-ENVID
X-Postfix-Queue-ID: msgid
X-Postfix-Sender: rfc822; sender@sender.example
-Arrival-Date: Sat, 5 Dec 2020 13:31:48 -0500 (EST)
+Arrival-Date: Sat, 05 Dec 2020 13:31:48 -0500 (EST)
Final-Recipient: rfc822; rcpt-address
Original-Recipient: rfc822; rcpt-orig_addr
diff --git a/src/bounce/with-msgid-with-filter-with-thread.ref b/src/bounce/with-msgid-with-filter-with-thread.ref
index 14d3373..ec3de87 100644
--- a/src/bounce/with-msgid-with-filter-with-thread.ref
+++ b/src/bounce/with-msgid-with-filter-with-thread.ref
@@ -26,7 +26,7 @@ Reporting-MTA: dns; mail.example
Original-Envelope-Id: TEST-ENVID
X-Postfix-Queue-ID: msgid
X-Postfix-Sender: rfc822; sender@sender.example
-Arrival-Date: Sat, 5 Dec 2020 13:31:48 -0500 (EST)
+Arrival-Date: Sat, 05 Dec 2020 13:31:48 -0500 (EST)
Final-Recipient: rfc822; rcpt-address
Original-Recipient: rfc822; rcpt-orig_addr
diff --git a/src/cleanup/Makefile.in b/src/cleanup/Makefile.in
index 8a3c18c..b74fe0a 100644
--- a/src/cleanup/Makefile.in
+++ b/src/cleanup/Makefile.in
@@ -88,7 +88,9 @@ milter_tests: cleanup_milter_test bug_tests \
cleanup_milter_test15g cleanup_milter_test15h cleanup_milter_test15i \
cleanup_milter_test16a cleanup_milter_test16b cleanup_milter_test17a \
cleanup_milter_test17b cleanup_milter_test17c cleanup_milter_test17d \
- cleanup_milter_test17e cleanup_milter_test17f cleanup_milter_test17g
+ cleanup_milter_test17e cleanup_milter_test17f cleanup_milter_test17g \
+ cleanup_milter_test18a cleanup_milter_test18b cleanup_milter_test18c \
+ cleanup_milter_test18d
root_tests:
@@ -671,6 +673,46 @@ cleanup_milter_test17g: cleanup_milter test-queue-file17 cleanup_milter.in17g \
diff cleanup_milter.ref17g2 cleanup_milter.tmp2
rm -f test-queue-file17g.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+cleanup_milter_test18a: cleanup_milter test-queue-file18 cleanup_milter.in18a \
+ cleanup_milter.ref18a1 ../postcat/postcat cleanup_milter.ref18a2
+ cp test-queue-file18 test-queue-file18a.tmp
+ chmod u+w test-queue-file18a.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter <cleanup_milter.in18a 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref18a1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file18a.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref18a2 cleanup_milter.tmp2
+ rm -f test-queue-file18a.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test18b: cleanup_milter test-queue-file18 cleanup_milter.in18b \
+ cleanup_milter.ref18b1 ../postcat/postcat cleanup_milter.ref18b2
+ cp test-queue-file18 test-queue-file18b.tmp
+ chmod u+w test-queue-file18b.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter <cleanup_milter.in18b 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref18b1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file18b.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref18b2 cleanup_milter.tmp2
+ rm -f test-queue-file18b.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test18c: cleanup_milter test-queue-file18 cleanup_milter.in18c \
+ cleanup_milter.ref18c1 ../postcat/postcat cleanup_milter.ref18c2
+ cp test-queue-file18 test-queue-file18c.tmp
+ chmod u+w test-queue-file18c.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter <cleanup_milter.in18c 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref18c1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file18c.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref18c2 cleanup_milter.tmp2
+ rm -f test-queue-file18c.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
+cleanup_milter_test18d: cleanup_milter test-queue-file18 cleanup_milter.in18d \
+ cleanup_milter.ref18d1 ../postcat/postcat cleanup_milter.ref18d2
+ cp test-queue-file18 test-queue-file18d.tmp
+ chmod u+w test-queue-file18d.tmp
+ $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter <cleanup_milter.in18d 2>cleanup_milter.tmp1
+ diff cleanup_milter.ref18d1 cleanup_milter.tmp1
+ $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file18d.tmp 2>/dev/null >cleanup_milter.tmp2
+ diff cleanup_milter.ref18d2 cleanup_milter.tmp2
+ rm -f test-queue-file18d.tmp cleanup_milter.tmp1 cleanup_milter.tmp2
+
depend: $(MAKES)
(sed '1,/^# do not edit/!d' Makefile.in; \
set -e; for i in [a-z][a-z0-9]*.c; do \
diff --git a/src/cleanup/cleanup.c b/src/cleanup/cleanup.c
index 6fe61f8..5db42bd 100644
--- a/src/cleanup/cleanup.c
+++ b/src/cleanup/cleanup.c
@@ -262,10 +262,10 @@
/* Available in Postfix version 2.1 and later:
/* .IP "\fBsender_bcc_maps (empty)\fR"
/* Optional BCC (blind carbon-copy) address lookup tables, indexed
-/* by sender address.
+/* by envelope sender address.
/* .IP "\fBrecipient_bcc_maps (empty)\fR"
/* Optional BCC (blind carbon-copy) address lookup tables, indexed by
-/* recipient address.
+/* envelope recipient address.
/* ADDRESS TRANSFORMATION CONTROLS
/* .ad
/* .fi
@@ -303,8 +303,9 @@
/* .PP
/* Available in Postfix version 2.0 and later:
/* .IP "\fBvirtual_alias_maps ($virtual_maps)\fR"
-/* Optional lookup tables that alias specific mail addresses or domains
-/* to other local or remote address.
+/* Optional lookup tables with aliases that apply to all recipients:
+/* \fBlocal\fR(8), virtual, and remote; this is unlike alias_maps that apply
+/* only to \fBlocal\fR(8) recipients.
/* .PP
/* Available in Postfix version 2.2 and later:
/* .IP "\fBcanonical_classes (envelope_sender, envelope_recipient, header_sender, header_recipient)\fR"
@@ -316,9 +317,10 @@
/* What addresses are subject to sender_canonical_maps address
/* mapping.
/* .IP "\fBremote_header_rewrite_domain (empty)\fR"
-/* Don't rewrite message headers from remote clients at all when
-/* this parameter is empty; otherwise, rewrite message headers and
-/* append the specified domain name to incomplete addresses.
+/* Rewrite or add message headers in mail from remote clients if
+/* the remote_header_rewrite_domain parameter value is non-empty,
+/* updating incomplete addresses with the domain specified in the
+/* remote_header_rewrite_domain parameter, and adding missing headers.
/* RESOURCE AND RATE CONTROLS
/* .ad
/* .fi
@@ -365,7 +367,7 @@
/* Preliminary SMTPUTF8 support is introduced with Postfix 3.0.
/* .IP "\fBsmtputf8_enable (yes)\fR"
/* Enable preliminary SMTPUTF8 support for the protocols described
-/* in RFC 6531..6533.
+/* in RFC 6531, RFC 6532, and RFC 6533.
/* .IP "\fBsmtputf8_autodetect_classes (sendmail, verify)\fR"
/* Detect that a message requires SMTPUTF8 support for the specified
/* mail origin classes.
@@ -433,6 +435,12 @@
/* .IP "\fBinfo_log_address_format (external)\fR"
/* The email address form that will be used in non-debug logging
/* (info, warning, etc.).
+/* .PP
+/* Available in Postfix 3.9 and later:
+/* .IP "\fBforce_mime_input_conversion (no)\fR"
+/* Convert body content that claims to be 8-bit into quoted-printable,
+/* before header_checks, body_checks, Milters, and before after-queue
+/* content filters.
/* FILES
/* /etc/postfix/canonical*, canonical mapping table
/* /etc/postfix/virtual*, virtual mapping table
diff --git a/src/cleanup/cleanup_addr.c b/src/cleanup/cleanup_addr.c
index fd8a511..f5e7d91 100644
--- a/src/cleanup/cleanup_addr.c
+++ b/src/cleanup/cleanup_addr.c
@@ -148,7 +148,7 @@ off_t cleanup_addr_sender(CLEANUP_STATE *state, const char *buf)
}
/* Fix 20140711: Auto-detect an UTF8 sender. */
if (var_smtputf8_enable && *STR(clean_addr) && !allascii(STR(clean_addr))
- && valid_utf8_string(STR(clean_addr), LEN(clean_addr))) {
+ && valid_utf8_stringz(STR(clean_addr))) {
state->smtputf8 |= SMTPUTF8_FLAG_SENDER;
/* Fix 20140713: request SMTPUTF8 support selectively. */
if (state->flags & CLEANUP_FLAG_AUTOUTF8)
@@ -216,7 +216,7 @@ void cleanup_addr_recipient(CLEANUP_STATE *state, const char *buf)
}
/* Fix 20140711: Auto-detect an UTF8 recipient. */
if (var_smtputf8_enable && *STR(clean_addr) && !allascii(STR(clean_addr))
- && valid_utf8_string(STR(clean_addr), LEN(clean_addr))) {
+ && valid_utf8_stringz(STR(clean_addr))) {
/* Fix 20140713: request SMTPUTF8 support selectively. */
if (state->flags & CLEANUP_FLAG_AUTOUTF8)
state->smtputf8 |= SMTPUTF8_FLAG_REQUESTED;
@@ -275,7 +275,7 @@ void cleanup_addr_bcc_dsn(CLEANUP_STATE *state, const char *bcc,
}
/* Fix 20140711: Auto-detect an UTF8 recipient. */
if (var_smtputf8_enable && *STR(clean_addr) && !allascii(STR(clean_addr))
- && valid_utf8_string(STR(clean_addr), LEN(clean_addr))) {
+ && valid_utf8_stringz(STR(clean_addr))) {
/* Fix 20140713: request SMTPUTF8 support selectively. */
if (state->flags & CLEANUP_FLAG_AUTOUTF8)
state->smtputf8 |= SMTPUTF8_FLAG_REQUESTED;
diff --git a/src/cleanup/cleanup_init.c b/src/cleanup/cleanup_init.c
index 369a019..446ddf2 100644
--- a/src/cleanup/cleanup_init.c
+++ b/src/cleanup/cleanup_init.c
@@ -174,6 +174,7 @@ int var_auto_8bit_enc_hdr; /* auto-detect 8bit encoding header */
int var_always_add_hdrs; /* always add missing headers */
int var_virt_addrlen_limit; /* stop exponential growth */
char *var_hfrom_format; /* header_from_format */
+int var_force_mime_iconv; /* force mime downgrade on input */
int var_cleanup_mask_stray_cr_lf; /* replace stray CR or LF with space */
const CONFIG_INT_TABLE cleanup_int_table[] = {
@@ -191,6 +192,7 @@ const CONFIG_BOOL_TABLE cleanup_bool_table[] = {
VAR_VERP_BOUNCE_OFF, DEF_VERP_BOUNCE_OFF, &var_verp_bounce_off,
VAR_AUTO_8BIT_ENC_HDR, DEF_AUTO_8BIT_ENC_HDR, &var_auto_8bit_enc_hdr,
VAR_ALWAYS_ADD_HDRS, DEF_ALWAYS_ADD_HDRS, &var_always_add_hdrs,
+ VAR_FORCE_MIME_ICONV, DEF_FORCE_MIME_ICONV, &var_force_mime_iconv,
VAR_CLEANUP_MASK_STRAY_CR_LF, DEF_CLEANUP_MASK_STRAY_CR_LF, &var_cleanup_mask_stray_cr_lf,
0,
};
diff --git a/src/cleanup/cleanup_message.c b/src/cleanup/cleanup_message.c
index 1ee0a52..0d31598 100644
--- a/src/cleanup/cleanup_message.c
+++ b/src/cleanup/cleanup_message.c
@@ -1069,6 +1069,9 @@ void cleanup_message(CLEANUP_STATE *state, int type, const char *buf, ssize_t
*/
mime_options = 0;
if (var_disable_mime_input) {
+ if (var_force_mime_iconv)
+ msg_fatal("do not specify both %s=yes and %s=yes",
+ VAR_DISABLE_MIME_INPUT, VAR_FORCE_MIME_ICONV);
mime_options |= MIME_OPT_DISABLE_MIME;
} else {
/* Turn off content checks if bouncing or forwarding mail. */
@@ -1085,6 +1088,8 @@ void cleanup_message(CLEANUP_STATE *state, int type, const char *buf, ssize_t
|| *var_nesthdr_checks)
mime_options |= MIME_OPT_REPORT_NESTING;
}
+ if (var_force_mime_iconv)
+ mime_options |= MIME_OPT_DOWNGRADE;
}
state->mime_state = mime_state_alloc(mime_options,
cleanup_header_callback,
diff --git a/src/cleanup/cleanup_milter.c b/src/cleanup/cleanup_milter.c
index 491de25..a35344c 100644
--- a/src/cleanup/cleanup_milter.c
+++ b/src/cleanup/cleanup_milter.c
@@ -2444,6 +2444,7 @@ static void open_queue_file(CLEANUP_STATE *state, const char *path)
long data_offset;
long rcpt_count;
long qmgr_opts;
+ const HEADER_OPTS *opts;
if (state->dst != 0) {
msg_warn("closing %s", cleanup_path);
@@ -2455,6 +2456,7 @@ static void open_queue_file(CLEANUP_STATE *state, const char *path)
if ((state->dst = vstream_fopen(path, O_RDWR, 0)) == 0) {
msg_warn("open %s: %m", path);
} else {
+ var_drop_hdrs = "";
cleanup_path = mystrdup(path);
for (;;) {
if ((curr_offset = vstream_ftell(state->dst)) < 0)
@@ -2511,9 +2513,16 @@ static void open_queue_file(CLEANUP_STATE *state, const char *path)
msg_fatal("file %s: vstream_ftell: %m", cleanup_path);
}
}
+ } else if (rec_type == REC_TYPE_NORM && state->hop_count == 0
+ && (opts = header_opts_find(STR(buf))) != 0
+ && opts->type == HDR_RECEIVED) {
+ state->hop_count += 1;
+ /* XXX Only the first line of the first Received: header. */
+ argv_add(state->auto_hdrs, STR(buf), ARGV_END);
}
if (state->append_rcpt_pt_offset > 0
&& state->append_hdr_pt_offset > 0
+ && state->hop_count > 0
&& (rec_type == REC_TYPE_END
|| state->append_meta_pt_offset > 0))
break;
diff --git a/src/cleanup/cleanup_milter.in18a b/src/cleanup/cleanup_milter.in18a
new file mode 100644
index 0000000..4e60fe4
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in18a
@@ -0,0 +1,8 @@
+#verbose on
+open test-queue-file18a.tmp
+#
+# Update a prepended header.
+#
+upd_header 1 Header-Label new-header-value
+
+close
diff --git a/src/cleanup/cleanup_milter.in18b b/src/cleanup/cleanup_milter.in18b
new file mode 100644
index 0000000..5f8c12d
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in18b
@@ -0,0 +1,8 @@
+#verbose on
+open test-queue-file18b.tmp
+#
+# Delete a prepended header.
+#
+del_header 1 Header-Label
+
+close
diff --git a/src/cleanup/cleanup_milter.in18c b/src/cleanup/cleanup_milter.in18c
new file mode 100644
index 0000000..bb78c94
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in18c
@@ -0,0 +1,9 @@
+#verbose on
+open test-queue-file18c.tmp
+#
+# Update the first Received: header. This adds a new header, because
+# there is no header that was exposed to the Milter.
+#
+upd_header 1 Received whatever
+
+close
diff --git a/src/cleanup/cleanup_milter.in18d b/src/cleanup/cleanup_milter.in18d
new file mode 100644
index 0000000..607014d
--- /dev/null
+++ b/src/cleanup/cleanup_milter.in18d
@@ -0,0 +1,8 @@
+#verbose on
+open test-queue-file18d.tmp
+#
+# Delete our Received: header. This should do nothing.
+#
+del_header 1 Received
+
+close
diff --git a/src/cleanup/cleanup_milter.ref18a1 b/src/cleanup/cleanup_milter.ref18a1
new file mode 100644
index 0000000..eab5a83
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref18a1
@@ -0,0 +1 @@
+./cleanup_milter: flags = enable_header_body_filter enable_milters
diff --git a/src/cleanup/cleanup_milter.ref18a2 b/src/cleanup/cleanup_milter.ref18a2
new file mode 100644
index 0000000..b5c0477
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref18a2
@@ -0,0 +1,29 @@
+*** ENVELOPE RECORDS test-queue-file18a.tmp ***
+ 0 message_size: 342 290 1 0 342 0
+ 97 message_arrival_time: Tue Dec 12 14:29:04 2023
+ 116 create_time: Tue Dec 12 14:29:04 2023
+ 140 named_attribute: rewrite_context=local
+ 163 sender_fullname: Wietse Venema
+ 178 sender: user@example.com
+ 196 named_attribute: dsn_orig_rcpt=rfc822;user@example.com
+ 235 original_recipient: user@example.com
+ 253 recipient: user@example.com
+ 271 pointer_record: 0
+ 288 *** MESSAGE CONTENTS test-queue-file18a.tmp ***
+ 290 pointer_record: 653
+ 653 regular_text: Header-Label: new-header-value
+ 685 pointer_record: 318
+ 318 regular_text: Received: by wzv.porcupine.org (Postfix, from userid 1000)
+ 378 regular_text: id 4SqTFD6TVpz4w4n; Tue, 12 Dec 2023 14:29:04 -0500 (EST)
+ 438 regular_text: Subject: test
+ 453 padding: 0
+ 456 regular_text: Message-Id: <4SqTFD6TVpz4w4n@wzv.porcupine.org>
+ 505 regular_text: Date: Tue, 12 Dec 2023 14:29:04 -0500 (EST)
+ 550 regular_text: From: Wietse Venema <user@example.com>
+ 590 pointer_record: 0
+ 607 regular_text:
+ 609 regular_text: test
+ 615 pointer_record: 0
+ 632 *** HEADER EXTRACTED test-queue-file18a.tmp ***
+ 634 pointer_record: 0
+ 651 *** MESSAGE FILE END test-queue-file18a.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref18b1 b/src/cleanup/cleanup_milter.ref18b1
new file mode 100644
index 0000000..eab5a83
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref18b1
@@ -0,0 +1 @@
+./cleanup_milter: flags = enable_header_body_filter enable_milters
diff --git a/src/cleanup/cleanup_milter.ref18b2 b/src/cleanup/cleanup_milter.ref18b2
new file mode 100644
index 0000000..d3b4ed3
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref18b2
@@ -0,0 +1,27 @@
+*** ENVELOPE RECORDS test-queue-file18b.tmp ***
+ 0 message_size: 342 290 1 0 342 0
+ 97 message_arrival_time: Tue Dec 12 14:29:04 2023
+ 116 create_time: Tue Dec 12 14:29:04 2023
+ 140 named_attribute: rewrite_context=local
+ 163 sender_fullname: Wietse Venema
+ 178 sender: user@example.com
+ 196 named_attribute: dsn_orig_rcpt=rfc822;user@example.com
+ 235 original_recipient: user@example.com
+ 253 recipient: user@example.com
+ 271 pointer_record: 0
+ 288 *** MESSAGE CONTENTS test-queue-file18b.tmp ***
+ 290 pointer_record: 318
+ 318 regular_text: Received: by wzv.porcupine.org (Postfix, from userid 1000)
+ 378 regular_text: id 4SqTFD6TVpz4w4n; Tue, 12 Dec 2023 14:29:04 -0500 (EST)
+ 438 regular_text: Subject: test
+ 453 padding: 0
+ 456 regular_text: Message-Id: <4SqTFD6TVpz4w4n@wzv.porcupine.org>
+ 505 regular_text: Date: Tue, 12 Dec 2023 14:29:04 -0500 (EST)
+ 550 regular_text: From: Wietse Venema <user@example.com>
+ 590 pointer_record: 0
+ 607 regular_text:
+ 609 regular_text: test
+ 615 pointer_record: 0
+ 632 *** HEADER EXTRACTED test-queue-file18b.tmp ***
+ 634 pointer_record: 0
+ 651 *** MESSAGE FILE END test-queue-file18b.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref18c1 b/src/cleanup/cleanup_milter.ref18c1
new file mode 100644
index 0000000..eab5a83
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref18c1
@@ -0,0 +1 @@
+./cleanup_milter: flags = enable_header_body_filter enable_milters
diff --git a/src/cleanup/cleanup_milter.ref18c2 b/src/cleanup/cleanup_milter.ref18c2
new file mode 100644
index 0000000..84a3449
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref18c2
@@ -0,0 +1,29 @@
+*** ENVELOPE RECORDS test-queue-file18c.tmp ***
+ 0 message_size: 342 290 1 0 342 0
+ 97 message_arrival_time: Tue Dec 12 14:29:04 2023
+ 116 create_time: Tue Dec 12 14:29:04 2023
+ 140 named_attribute: rewrite_context=local
+ 163 sender_fullname: Wietse Venema
+ 178 sender: user@example.com
+ 196 named_attribute: dsn_orig_rcpt=rfc822;user@example.com
+ 235 original_recipient: user@example.com
+ 253 recipient: user@example.com
+ 271 pointer_record: 0
+ 288 *** MESSAGE CONTENTS test-queue-file18c.tmp ***
+ 290 regular_text: Header-Label: header-value
+ 318 regular_text: Received: by wzv.porcupine.org (Postfix, from userid 1000)
+ 378 regular_text: id 4SqTFD6TVpz4w4n; Tue, 12 Dec 2023 14:29:04 -0500 (EST)
+ 438 regular_text: Subject: test
+ 453 padding: 0
+ 456 regular_text: Message-Id: <4SqTFD6TVpz4w4n@wzv.porcupine.org>
+ 505 regular_text: Date: Tue, 12 Dec 2023 14:29:04 -0500 (EST)
+ 550 regular_text: From: Wietse Venema <user@example.com>
+ 590 pointer_record: 653
+ 653 regular_text: Received: whatever
+ 673 pointer_record: 607
+ 607 regular_text:
+ 609 regular_text: test
+ 615 pointer_record: 0
+ 632 *** HEADER EXTRACTED test-queue-file18c.tmp ***
+ 634 pointer_record: 0
+ 651 *** MESSAGE FILE END test-queue-file18c.tmp ***
diff --git a/src/cleanup/cleanup_milter.ref18d1 b/src/cleanup/cleanup_milter.ref18d1
new file mode 100644
index 0000000..eab5a83
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref18d1
@@ -0,0 +1 @@
+./cleanup_milter: flags = enable_header_body_filter enable_milters
diff --git a/src/cleanup/cleanup_milter.ref18d2 b/src/cleanup/cleanup_milter.ref18d2
new file mode 100644
index 0000000..b436ba2
--- /dev/null
+++ b/src/cleanup/cleanup_milter.ref18d2
@@ -0,0 +1,27 @@
+*** ENVELOPE RECORDS test-queue-file18d.tmp ***
+ 0 message_size: 342 290 1 0 342 0
+ 97 message_arrival_time: Tue Dec 12 14:29:04 2023
+ 116 create_time: Tue Dec 12 14:29:04 2023
+ 140 named_attribute: rewrite_context=local
+ 163 sender_fullname: Wietse Venema
+ 178 sender: user@example.com
+ 196 named_attribute: dsn_orig_rcpt=rfc822;user@example.com
+ 235 original_recipient: user@example.com
+ 253 recipient: user@example.com
+ 271 pointer_record: 0
+ 288 *** MESSAGE CONTENTS test-queue-file18d.tmp ***
+ 290 regular_text: Header-Label: header-value
+ 318 regular_text: Received: by wzv.porcupine.org (Postfix, from userid 1000)
+ 378 regular_text: id 4SqTFD6TVpz4w4n; Tue, 12 Dec 2023 14:29:04 -0500 (EST)
+ 438 regular_text: Subject: test
+ 453 padding: 0
+ 456 regular_text: Message-Id: <4SqTFD6TVpz4w4n@wzv.porcupine.org>
+ 505 regular_text: Date: Tue, 12 Dec 2023 14:29:04 -0500 (EST)
+ 550 regular_text: From: Wietse Venema <user@example.com>
+ 590 pointer_record: 0
+ 607 regular_text:
+ 609 regular_text: test
+ 615 pointer_record: 0
+ 632 *** HEADER EXTRACTED test-queue-file18d.tmp ***
+ 634 pointer_record: 0
+ 651 *** MESSAGE FILE END test-queue-file18d.tmp ***
diff --git a/src/cleanup/test-queue-file18 b/src/cleanup/test-queue-file18
new file mode 100644
index 0000000..42c46f1
--- /dev/null
+++ b/src/cleanup/test-queue-file18
Binary files differ
diff --git a/src/dns/Makefile.in b/src/dns/Makefile.in
index 3ebf75f..5ea09cf 100644
--- a/src/dns/Makefile.in
+++ b/src/dns/Makefile.in
@@ -11,7 +11,8 @@ DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
CFLAGS = $(DEBUG) $(OPT) $(DEFS)
INCL =
LIB = lib$(LIB_PREFIX)dns$(LIB_SUFFIX)
-TESTPROG= test_dns_lookup dns_rr_to_pa dns_rr_to_sa dns_sa_to_rr dns_rr_eq_sa
+TESTPROG= test_dns_lookup dns_rr_to_pa dns_rr_to_sa dns_sa_to_rr dns_rr_eq_sa \
+ dns_rr_test
LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
LIB_DIR = ../../lib
@@ -31,7 +32,7 @@ test: $(TESTPROG)
tests: test dns_rr_to_pa_test dns_rr_to_sa_test dns_sa_to_rr_test \
dns_rr_eq_sa_test no-a-test no-aaaa-test no-mx-test \
error-filter-test nullmx_test nxdomain_test mxonly_test \
- dnsbl_tests
+ dnsbl_tests dns_rr_tests
dnsbl_tests: \
dnsbl_ttl_127.0.0.2_bind_plain_test \
@@ -57,7 +58,7 @@ DNSBL_EXIST_REPLY_FIX = \
-e 's/ [0-9]* [0-9]* [0-9]* [0-9]* [0-9]*/ D D D D D/' \
-e 's/127.0.0.[0-9]*$$/127.0.0.D/' \
| uniq
-
+
root_tests:
$(LIB): $(OBJS)
@@ -240,6 +241,12 @@ dnsbl_ttl_127.0.0.2_priv_ncache_test: test_dns_lookup dnsbl_ttl_127.0.0.2_bind_p
diff dnsbl_ttl_127.0.0.2_bind_plain.ref dnsbl_ttl_127.0.0.2_priv_ncache.tmp
rm -f dnsbl_ttl_127.0.0.2_priv_ncache.tmp
+dns_rr_tests: dns_rr_test
+ $(SHLIB_ENV) $(VALGRIND) ./dns_rr_test
+
+dns_rr_test: dns_rr_test.o $(LIB) $(LIBS)
+ $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+
printfck: $(OBJS) $(PROG)
rm -rf printfck
mkdir printfck
@@ -319,6 +326,19 @@ dns_rr_filter.o: ../../include/vstream.h
dns_rr_filter.o: ../../include/vstring.h
dns_rr_filter.o: dns.h
dns_rr_filter.o: dns_rr_filter.c
+dns_rr_test.o: ../../include/check_arg.h
+dns_rr_test.o: ../../include/msg.h
+dns_rr_test.o: ../../include/msg_vstream.h
+dns_rr_test.o: ../../include/myaddrinfo.h
+dns_rr_test.o: ../../include/mymalloc.h
+dns_rr_test.o: ../../include/sock_addr.h
+dns_rr_test.o: ../../include/stringops.h
+dns_rr_test.o: ../../include/sys_defs.h
+dns_rr_test.o: ../../include/vbuf.h
+dns_rr_test.o: ../../include/vstream.h
+dns_rr_test.o: ../../include/vstring.h
+dns_rr_test.o: dns.h
+dns_rr_test.o: dns_rr_test.c
dns_rr_to_pa.o: ../../include/check_arg.h
dns_rr_to_pa.o: ../../include/msg.h
dns_rr_to_pa.o: ../../include/myaddrinfo.h
diff --git a/src/dns/dns_lookup.c b/src/dns/dns_lookup.c
index c21b619..4cf9a5f 100644
--- a/src/dns/dns_lookup.c
+++ b/src/dns/dns_lookup.c
@@ -85,6 +85,12 @@
/* an invalid name is reported as a DNS_INVAL result, while
/* malformed replies are reported as transient errors.
/*
+/* Note: in dns_lookup*() results and queries, a name may start
+/* with a "*" label, which is valid according to RFC 1034
+/* section 4.3.3. Such a name will not pass valid_hostname()
+/* checks in the rest of Postfix, because it is not a valid
+/* host or domain name.
+/*
/* dns_get_h_errno() returns the last error. This deprecates
/* usage of the global h_errno variable. We should not rely
/* on that being updated.
@@ -300,7 +306,7 @@ typedef struct DNS_REPLY {
/*
* Use the threadsafe resolver API if available, not because it is
- * theadsafe, but because it has more functionality.
+ * threadsafe, but because it has more functionality.
*/
#ifdef USE_RES_NCALLS
static struct __res_state dns_res_state;
diff --git a/src/dns/dns_rr_test.c b/src/dns/dns_rr_test.c
new file mode 100644
index 0000000..7bbe769
--- /dev/null
+++ b/src/dns/dns_rr_test.c
@@ -0,0 +1,433 @@
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <stdlib.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <msg_vstream.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <vstring.h>
+
+ /*
+ * DNS library.
+ */
+#include <dns.h>
+
+#define STR(x) vstring_str(x)
+
+ /*
+ * Test helpers. TODO: move eq_dns_rr() to testing/dns_rr_testers.c; need to
+ * verify that the expected difference is reported, or use a GTEST matcher.
+ */
+
+/* print_dns_rr - format as { qname, reply, flags } */
+
+static char *print_dns_rr(VSTRING *buf, DNS_RR *rr)
+{
+ static VSTRING *tmp;
+
+ if (tmp == 0)
+ tmp = vstring_alloc(100);
+ vstring_sprintf(buf, "{qname=%s, reply='%s', flags=0x%x}",
+ rr->qname, dns_strrecord(tmp, rr), rr->flags);
+ return (STR(buf));
+}
+
+/* eq_dns_rr - predicate that two lists are equivalent */
+
+static int eq_dns_rr(DNS_RR *got, DNS_RR *want)
+{
+ VSTRING *got_buf = 0;
+ VSTRING *want_buf = 0;
+
+#define EQ_DNS_RR_RETURN(val) do { \
+ if (got_buf) \
+ vstring_free(got_buf); \
+ if (want_buf) \
+ vstring_free(want_buf); \
+ return (val); \
+ } while (0)
+
+ /* Same length. */
+ if (got == 0 && want == 0)
+ EQ_DNS_RR_RETURN(1);
+ if (want == 0) {
+ msg_warn("got %s, want null",
+ print_dns_rr(got_buf = vstring_alloc(100), got));
+ }
+ if (got == 0) {
+ msg_warn("got null, want %s",
+ print_dns_rr(want_buf = vstring_alloc(100), want));
+ EQ_DNS_RR_RETURN(0);
+ }
+ /* Same query name, resource record, flags. */
+ if (strcmp(print_dns_rr(got_buf = vstring_alloc(100), got),
+ print_dns_rr(want_buf = vstring_alloc(100), want)) != 0) {
+ msg_warn("got %s, want %s", STR(want_buf), STR(got_buf));
+ EQ_DNS_RR_RETURN(0);
+ }
+ /* Same children. */
+ EQ_DNS_RR_RETURN(eq_dns_rr(got->next, want->next));
+}
+
+static int eq_dns_rr_free(DNS_RR *got, DNS_RR *want)
+{
+ int res = eq_dns_rr(got, want);
+
+ dns_rr_free(got);
+ dns_rr_free(want);
+ return (res);
+}
+
+ /*
+ * Tests and test cases.
+ */
+typedef struct TEST_CASE {
+ const char *label; /* identifies test case */
+ int (*fn) (void);
+} TEST_CASE;
+
+#define PASS (0)
+#define FAIL (1)
+
+ /*
+ * Begin helper tests. TODO: move these to testing/dns_rr_testers_test.c.
+ */
+
+static int eq_dns_rr_qname_differ(void)
+{
+ DNS_RR *got = dns_rr_create("qa", "ra", T_SRV, C_IN, 3600, 1, 25, 1, "mxa", 4);
+ DNS_RR *want = dns_rr_copy(got);
+
+ myfree(want->qname);
+ want->qname = mystrdup("qb");
+ return (!eq_dns_rr_free(got, want));
+}
+
+static int eq_dns_rr_reply_differ(void)
+{
+ DNS_RR *got = dns_rr_create("qa", "ra", T_SRV, C_IN, 3600, 1, 25, 1, "mxa", 4);
+ DNS_RR *want = dns_rr_copy(got);
+
+ want->port += 1;
+ return (!eq_dns_rr_free(got, want));
+}
+
+ /*
+ * End helper tests.
+ */
+
+ /*
+ * Begin DNS_RR tests.
+ */
+
+static int eq_dns_rr_flags_differ(void)
+{
+ DNS_RR *got = dns_rr_create_noport("qa", "ra", T_MX, C_IN, 3600, 1, "mxa", 4);
+ DNS_RR *want = dns_rr_copy(got);
+
+ want->flags |= DNS_RR_FLAG_TRUNCATED;
+ return (!eq_dns_rr_free(got, want));
+}
+
+static int append_to_null_from_null(void)
+{
+ DNS_RR *got = dns_rr_append((DNS_RR *) 0, (DNS_RR *) 0);
+ DNS_RR *want = 0;
+
+ return (eq_dns_rr_free(got, want));
+}
+
+static int append_to_elem_from_null(void)
+{
+ DNS_RR *a = dns_rr_create_noport("qa", "ra", T_MX, C_IN, 3600, 1, "mxa", 4);
+ DNS_RR *got, *want;
+
+ got = dns_rr_append(dns_rr_copy(a), (DNS_RR *) 0);
+
+ want = a;
+
+ return (eq_dns_rr_free(got, want));
+}
+
+static int appent_to_null_from_elem(void)
+{
+ DNS_RR *a = dns_rr_create_noport("qa", "ra", T_MX, C_IN, 3600, 1, "mxa", 4);
+ DNS_RR *got, *want;
+
+ got = dns_rr_append((DNS_RR *) 0, dns_rr_copy(a));
+
+ want = a;
+
+ return (eq_dns_rr_free(got, want));
+}
+
+static int append_to_elem_from_elem(void)
+{
+ DNS_RR *a = dns_rr_create_noport("qa", "ra", T_MX, C_IN, 3600, 1, "mxa", 4);
+ DNS_RR *b = dns_rr_create_noport("qb", "rb", T_MX, C_IN, 3600, 1, "mxb", 4);
+ DNS_RR *got, *want;
+
+ got = dns_rr_append(dns_rr_copy(a), dns_rr_copy(b));
+
+ (want = a)->next = b;
+
+ return (eq_dns_rr_free(got, want));
+}
+
+static int append_to_elem_from_list(void)
+{
+ DNS_RR *a = dns_rr_create_noport("qa", "ra", T_MX, C_IN, 3600, 1, "mxa", 4);
+ DNS_RR *b = dns_rr_create_noport("qb", "rb", T_MX, C_IN, 3600, 1, "mxb", 4);
+ DNS_RR *c = dns_rr_create_noport("qc", "rc", T_MX, C_IN, 3600, 1, "mxc", 4);
+ DNS_RR *got, *want;
+
+ got = dns_rr_append(dns_rr_copy(a),
+ dns_rr_append(dns_rr_copy(b),
+ dns_rr_copy(c)));
+
+ ((want = a)->next = b)->next = c;
+
+ return (eq_dns_rr_free(got, want));
+}
+
+static int append_to_list_from_elem(void)
+{
+ DNS_RR *a = dns_rr_create_noport("qa", "ra", T_MX, C_IN, 3600, 1, "mxa", 4);
+ DNS_RR *b = dns_rr_create_noport("qb", "rb", T_MX, C_IN, 3600, 1, "mxb", 4);
+ DNS_RR *c = dns_rr_create_noport("qc", "rc", T_MX, C_IN, 3600, 1, "mxc", 4);
+ DNS_RR *got, *want;
+
+ got = dns_rr_append(dns_rr_append(dns_rr_copy(a),
+ dns_rr_copy(b)),
+ dns_rr_copy(c));
+
+ ((want = a)->next = b)->next = c;
+
+ return (eq_dns_rr_free(got, want));
+}
+
+static int append_to_list_from_list(void)
+{
+ DNS_RR *a = dns_rr_create_noport("qa", "ra", T_MX, C_IN, 3600, 1, "mxa", 4);
+ DNS_RR *b = dns_rr_create_noport("qb", "rb", T_MX, C_IN, 3600, 1, "mxb", 4);
+ DNS_RR *c = dns_rr_create_noport("qc", "rc", T_MX, C_IN, 3600, 1, "mxc", 4);
+ DNS_RR *d = dns_rr_create_noport("qd", "rd", T_MX, C_IN, 3600, 1, "mxd", 4);
+ DNS_RR *got, *want;
+
+ got = dns_rr_append(dns_rr_append(dns_rr_copy(a),
+ dns_rr_copy(b)),
+ dns_rr_append(dns_rr_copy(c),
+ dns_rr_copy(d)));
+
+ (((want = a)->next = b)->next = c)->next = d;
+
+ return (eq_dns_rr_free(got, want));
+}
+
+static int append_propagates_flags(void)
+{
+ DNS_RR *a = dns_rr_create_noport("qa", "ra", T_MX, C_IN, 3600, 1, "mxa", 4);
+ DNS_RR *b = dns_rr_create_noport("qb", "rb", T_MX, C_IN, 3600, 1, "mxb", 4);
+ DNS_RR *c = dns_rr_create_noport("qc", "rc", T_MX, C_IN, 3600, 1, "mxc", 4);
+ DNS_RR *d = dns_rr_create_noport("qd", "rd", T_MX, C_IN, 3600, 1, "mxd", 4);
+ DNS_RR *left = dns_rr_append(dns_rr_copy(a), dns_rr_copy(b));
+ DNS_RR *rite = dns_rr_append(dns_rr_copy(c), dns_rr_copy(d));
+ DNS_RR *got, *want, *rr;
+
+ for (rr = rite; rr; rr = rr->next)
+ rr->flags |= DNS_RR_FLAG_TRUNCATED;
+
+ got = dns_rr_append(left, rite);
+
+ (((want = a)->next = b)->next = c)->next = d;
+ for (rr = want; rr; rr = rr->next)
+ rr->flags |= DNS_RR_FLAG_TRUNCATED;
+
+ return (eq_dns_rr_free(got, want));
+}
+
+static int append_to_list_from_list_truncate(void)
+{
+ DNS_RR *a = dns_rr_create_noport("qa", "ra", T_MX, C_IN, 3600, 1, "mxa", 4);
+ DNS_RR *b = dns_rr_create_noport("qb", "rb", T_MX, C_IN, 3600, 1, "mxb", 4);
+ DNS_RR *c = dns_rr_create_noport("qc", "rc", T_MX, C_IN, 3600, 1, "mxc", 4);
+ DNS_RR *d = dns_rr_create_noport("qd", "rd", T_MX, C_IN, 3600, 1, "mxd", 4);
+ DNS_RR *got, *want, *rr;
+
+ var_dns_rr_list_limit = 3;
+
+ ((want = dns_rr_copy(a))->next = dns_rr_copy(b))->next = dns_rr_copy(c);
+ for (rr = want; rr; rr = rr->next)
+ rr->flags |= DNS_RR_FLAG_TRUNCATED;
+
+ got = dns_rr_append(dns_rr_append(a, b),
+ dns_rr_append(c, d));
+
+ return (eq_dns_rr_free(got, want));
+}
+
+static int append_to_list_from_elem_elem_truncate(void)
+{
+ DNS_RR *a = dns_rr_create_noport("qa", "ra", T_MX, C_IN, 3600, 1, "mxa", 4);
+ DNS_RR *b = dns_rr_create_noport("qb", "rb", T_MX, C_IN, 3600, 1, "mxb", 4);
+ DNS_RR *c = dns_rr_create_noport("qc", "rc", T_MX, C_IN, 3600, 1, "mxc", 4);
+ DNS_RR *d = dns_rr_create_noport("qd", "rd", T_MX, C_IN, 3600, 1, "mxd", 4);
+ DNS_RR *got, *want, *rr;
+
+ var_dns_rr_list_limit = 2;
+
+ (want = dns_rr_copy(a))->next = dns_rr_copy(b);
+ for (rr = want; rr; rr = rr->next)
+ rr->flags |= DNS_RR_FLAG_TRUNCATED;
+
+ got = dns_rr_append(a, b);
+ got = dns_rr_append(got, c); /* should be logged */
+ got = dns_rr_append(got, d); /* should be silent */
+
+ return (eq_dns_rr_free(got, want));
+}
+
+static int append_to_list_from_elem_truncate(void)
+{
+ DNS_RR *a = dns_rr_create_noport("qa", "ra", T_MX, C_IN, 3600, 1, "mxa", 4);
+ DNS_RR *b = dns_rr_create_noport("qb", "rb", T_MX, C_IN, 3600, 1, "mxb", 4);
+ DNS_RR *c = dns_rr_create_noport("qc", "rc", T_MX, C_IN, 3600, 1, "mxc", 4);
+ DNS_RR *got, *want, *rr;
+
+ var_dns_rr_list_limit = 2;
+
+ (want = dns_rr_copy(a))->next = dns_rr_copy(b);
+ for (rr = want; rr; rr = rr->next)
+ rr->flags |= DNS_RR_FLAG_TRUNCATED;
+
+ got = dns_rr_append(dns_rr_append(a, b), c);
+
+ return (eq_dns_rr_free(got, want));
+}
+
+static int append_to_elem_from_list_truncate(void)
+{
+ DNS_RR *a = dns_rr_create_noport("qa", "ra", T_MX, C_IN, 3600, 1, "mxa", 4);
+ DNS_RR *b = dns_rr_create_noport("qb", "rb", T_MX, C_IN, 3600, 1, "mxb", 4);
+ DNS_RR *c = dns_rr_create_noport("qc", "rc", T_MX, C_IN, 3600, 1, "mxc", 4);
+ DNS_RR *got, *want, *rr;
+
+ var_dns_rr_list_limit = 2;
+
+ (want = dns_rr_copy(a))->next = dns_rr_copy(b);
+ for (rr = want; rr; rr = rr->next)
+ rr->flags |= DNS_RR_FLAG_TRUNCATED;
+
+ got = dns_rr_append(a, dns_rr_append(b, c));
+
+ return (eq_dns_rr_free(got, want));
+}
+
+static int append_to_list_from_elem_exact_fit(void)
+{
+ DNS_RR *a = dns_rr_create_noport("qa", "ra", T_MX, C_IN, 3600, 1, "mxa", 4);
+ DNS_RR *b = dns_rr_create_noport("qb", "rb", T_MX, C_IN, 3600, 1, "mxb", 4);
+ DNS_RR *c = dns_rr_create_noport("qc", "rc", T_MX, C_IN, 3600, 1, "mxc", 4);
+ DNS_RR *got, *want;
+
+ var_dns_rr_list_limit = 3;
+
+ ((want = dns_rr_copy(a))->next = dns_rr_copy(b))->next = dns_rr_copy(c);
+
+ got = dns_rr_append(dns_rr_append(a, b), c);
+
+ return (eq_dns_rr_free(got, want));
+}
+
+static int append_to_elem_from_list_exact_fit(void)
+{
+ DNS_RR *a = dns_rr_create_noport("qa", "ra", T_MX, C_IN, 3600, 1, "mxa", 4);
+ DNS_RR *b = dns_rr_create_noport("qb", "rb", T_MX, C_IN, 3600, 1, "mxb", 4);
+ DNS_RR *c = dns_rr_create_noport("qc", "rc", T_MX, C_IN, 3600, 1, "mxc", 4);
+ DNS_RR *got, *want;
+
+ var_dns_rr_list_limit = 3;
+
+ ((want = dns_rr_copy(a))->next = dns_rr_copy(b))->next = dns_rr_copy(c);
+
+ got = dns_rr_append(a, dns_rr_append(b, c));
+
+ return (eq_dns_rr_free(got, want));
+}
+
+ /*
+ * The test cases.
+ */
+static const TEST_CASE test_cases[] = {
+
+ /*
+ * Test eq_dns_rr; TODO: move to testing/dns_rr_testers_test.c
+ */
+ "eq_dns_rr qname differ", eq_dns_rr_qname_differ,
+ "eq_dns_rr reply differ", eq_dns_rr_reply_differ,
+ "eq_dns_rr flags differ", eq_dns_rr_flags_differ,
+
+ /*
+ * Test dns_rr_append() without truncation.
+ */
+ "append to null from null", append_to_null_from_null,
+ "append to null from element", appent_to_null_from_elem,
+ "append to element from null", append_to_elem_from_null,
+ "append to element from element", append_to_elem_from_elem,
+ "append to element from list", append_to_elem_from_list,
+ "append to list from element", append_to_list_from_elem,
+ "append to list from list", append_to_list_from_list,
+
+ /*
+ * Test dns_rr_append() flag propagation.
+ */
+ "append propagates flags", append_propagates_flags,
+
+ /*
+ * Test dns_rr_append() with truncation.
+ */
+ "append to list from list truncate", append_to_list_from_list_truncate,
+ "append to list from element element truncate", append_to_list_from_elem_elem_truncate,
+ "append to list from element truncate", append_to_list_from_elem_truncate,
+ "append to element from list truncate", append_to_elem_from_list_truncate,
+ "append to list from element exact fit", append_to_list_from_elem_exact_fit,
+ "append to element from list exact fit", append_to_elem_from_list_exact_fit,
+
+ /*
+ * TODO: tests dns_rr_sort(), dns_rr_srv_sort(), dns_rr_remove(),
+ * dns_rr_shuffle(), etc.
+ */
+ 0,
+};
+
+int main(int argc, char **argv)
+{
+ const TEST_CASE *tp;
+ int pass = 0;
+ int fail = 0;
+ VSTRING *res_buf = vstring_alloc(100);
+ int saved_limit = var_dns_rr_list_limit;
+
+ msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
+
+ for (tp = test_cases; tp->label != 0; tp++) {
+ msg_info("RUN %s", tp->label);
+ if (tp->fn() == 0) {
+ fail++;
+ msg_info("FAIL %s", tp->label);
+ } else {
+ msg_info("PASS %s", tp->label);
+ pass++;
+ }
+ var_dns_rr_list_limit = saved_limit;
+ }
+ msg_info("PASS=%d FAIL=%d", pass, fail);
+ vstring_free(res_buf);
+ exit(fail != 0);
+}
diff --git a/src/dns/mxonly_test.ref b/src/dns/mxonly_test.ref
index 44f22d6..e1e4bad 100644
--- a/src/dns/mxonly_test.ref
+++ b/src/dns/mxonly_test.ref
@@ -6,6 +6,6 @@
./test_dns_lookup: lookup porcupine.org type A flags RES_USE_DNSSEC
./test_dns_lookup: dns_query: porcupine.org (A): Host found but no data record of requested type
ad: 0, rr: porcupine.org. 3600 IN MX 10 spike.porcupine.org.
-ad: 0, rr: porcupine.org. 3600 IN MX 30 m1.porcupine.org.
ad: 0, rr: porcupine.org. 3600 IN MX 30 vz.porcupine.org.
+ad: 0, rr: porcupine.org. 3600 IN MX 40 m1.porcupine.org.
porcupine.org: fqdn: porcupine.org
diff --git a/src/dns/no-mx.ref b/src/dns/no-mx.ref
index 5adc7bf..527e0b8 100644
--- a/src/dns/no-mx.ref
+++ b/src/dns/no-mx.ref
@@ -1,15 +1,15 @@
./test_dns_lookup: dict_regexp_lookup: no-mx.reg: porcupine.org. 3600 IN MX 10 spike.porcupine.org.
-./test_dns_lookup: dict_regexp_lookup: no-mx.reg: porcupine.org. 3600 IN MX 30 m1.porcupine.org.
./test_dns_lookup: dict_regexp_lookup: no-mx.reg: porcupine.org. 3600 IN MX 30 vz.porcupine.org.
+./test_dns_lookup: dict_regexp_lookup: no-mx.reg: porcupine.org. 3600 IN MX 40 m1.porcupine.org.
./test_dns_lookup: dns_get_answer: type MX for porcupine.org
./test_dns_lookup: dns_get_answer: type MX for porcupine.org
./test_dns_lookup: dns_get_answer: type MX for porcupine.org
./test_dns_lookup: dns_query: porcupine.org (MX): OK
./test_dns_lookup: ignoring DNS RR: porcupine.org. 3600 IN MX 10 spike.porcupine.org.
-./test_dns_lookup: ignoring DNS RR: porcupine.org. 3600 IN MX 30 m1.porcupine.org.
./test_dns_lookup: ignoring DNS RR: porcupine.org. 3600 IN MX 30 vz.porcupine.org.
+./test_dns_lookup: ignoring DNS RR: porcupine.org. 3600 IN MX 40 m1.porcupine.org.
./test_dns_lookup: lookup porcupine.org type MX flags RES_USE_DNSSEC
./test_dns_lookup: maps_find: DNS reply filter: regexp:no-mx.reg(0,lock|fold_fix): porcupine.org. 3600 IN MX 10 spike.porcupine.org. = ignore
-./test_dns_lookup: maps_find: DNS reply filter: regexp:no-mx.reg(0,lock|fold_fix): porcupine.org. 3600 IN MX 30 m1.porcupine.org. = ignore
./test_dns_lookup: maps_find: DNS reply filter: regexp:no-mx.reg(0,lock|fold_fix): porcupine.org. 3600 IN MX 30 vz.porcupine.org. = ignore
+./test_dns_lookup: maps_find: DNS reply filter: regexp:no-mx.reg(0,lock|fold_fix): porcupine.org. 3600 IN MX 40 m1.porcupine.org. = ignore
./test_dns_lookup: warning: Error looking up name=porcupine.org type=MX: DNS reply filter drops all results (rcode=0)
diff --git a/src/dns/test_dns_lookup.c b/src/dns/test_dns_lookup.c
index f07c3ef..6970124 100644
--- a/src/dns/test_dns_lookup.c
+++ b/src/dns/test_dns_lookup.c
@@ -80,7 +80,7 @@ int main(int argc, char **argv)
var_dnssec_probe = "";
msg_vstream_init(argv[0], VSTREAM_ERR);
- while ((ch = GETOPT(argc, argv, "f:npvs")) > 0) {
+ while ((ch = GETOPT(argc, argv, "f:l:npvs")) > 0) {
switch (ch) {
case 'v':
msg_verbose++;
@@ -88,6 +88,9 @@ int main(int argc, char **argv)
case 'f':
dns_rr_filter_compile("DNS reply filter", optarg);
break;
+ case 'l':
+ var_dns_rr_list_limit = atoi(optarg);
+ break;
case 'n':
lflags |= DNS_REQ_FLAG_NCACHE_TTL;
break;
diff --git a/src/dnsblog/dnsblog.c b/src/dnsblog/dnsblog.c
index bc87c4b..7a4a446 100644
--- a/src/dnsblog/dnsblog.c
+++ b/src/dnsblog/dnsblog.c
@@ -43,7 +43,8 @@
/* How much time a Postfix daemon process may take to handle a
/* request before it is terminated by a built-in watchdog timer.
/* .IP "\fBpostscreen_dnsbl_sites (empty)\fR"
-/* Optional list of DNS allow/denylist domains, filters and weight
+/* Optional list of patterns with DNS allow/denylist domains, filters
+/* and weight
/* factors.
/* .IP "\fBipc_timeout (3600s)\fR"
/* The time limit for sending or receiving information over an internal
diff --git a/src/global/Makefile.in b/src/global/Makefile.in
index 86390ed..c7a1d36 100644
--- a/src/global/Makefile.in
+++ b/src/global/Makefile.in
@@ -3,7 +3,7 @@ SRCS = abounce.c anvil_clnt.c been_here.c bounce.c bounce_log.c \
canon_addr.c cfg_parser.c cleanup_strerror.c cleanup_strflags.c \
clnt_stream.c conv_time.c db_common.c debug_peer.c debug_process.c \
defer.c deliver_completed.c deliver_flock.c deliver_pass.c \
- deliver_request.c dict_ldap.c dict_mysql.c dict_pgsql.c \
+ deliver_request.c dict_ldap.c dict_mongodb.c dict_mysql.c dict_pgsql.c \
dict_proxy.c dict_sqlite.c domain_list.c dot_lockfile.c dot_lockfile_as.c \
dsb_scan.c dsn.c dsn_buf.c dsn_mask.c dsn_print.c dsn_util.c \
ehlo_mask.c ext_prop.c file_id.c flush_clnt.c header_opts.c \
@@ -80,13 +80,13 @@ OBJS = abounce.o anvil_clnt.o been_here.o bounce.o bounce_log.o \
# MAP_OBJ is for maps that may be dynamically loaded with dynamicmaps.cf.
# When hard-linking these maps, makedefs sets NON_PLUGIN_MAP_OBJ=$(MAP_OBJ),
# otherwise it sets the PLUGIN_* macros.
-MAP_OBJ = dict_ldap.o dict_mysql.o dict_pgsql.o dict_sqlite.o
+MAP_OBJ = dict_ldap.o dict_mysql.o dict_pgsql.o dict_sqlite.o dict_mongodb.o
HDRS = abounce.h anvil_clnt.h been_here.h bounce.h bounce_log.h \
canon_addr.h cfg_parser.h cleanup_user.h clnt_stream.h config.h \
conv_time.h db_common.h debug_peer.h debug_process.h defer.h \
deliver_completed.h deliver_flock.h deliver_pass.h deliver_request.h \
- dict_ldap.h dict_mysql.h dict_pgsql.h dict_proxy.h dict_sqlite.h domain_list.h \
+ dict_ldap.h dict_mysql.h dict_pgsql.h dict_mongodb.h dict_proxy.h dict_sqlite.h domain_list.h \
dot_lockfile.h dot_lockfile_as.h dsb_scan.h dsn.h dsn_buf.h \
dsn_mask.h dsn_print.h dsn_util.h ehlo_mask.h ext_prop.h \
file_id.h flush_clnt.h header_opts.h header_token.h input_transp.h \
@@ -136,7 +136,7 @@ LIBS = ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
LIB_DIR = ../../lib
INC_DIR = ../../include
PLUGIN_MAP_SO = $(LIB_PREFIX)ldap$(LIB_SUFFIX) $(LIB_PREFIX)mysql$(LIB_SUFFIX) \
- $(LIB_PREFIX)pgsql$(LIB_SUFFIX) $(LIB_PREFIX)sqlite$(LIB_SUFFIX)
+ $(LIB_PREFIX)pgsql$(LIB_SUFFIX) $(LIB_PREFIX)sqlite$(LIB_SUFFIX) $(LIB_PREFIX)mongodb$(LIB_SUFFIX)
MAKES =
.c.o:; $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c
@@ -173,6 +173,9 @@ $(LIB_PREFIX)pgsql$(LIB_SUFFIX): dict_pgsql.o
$(LIB_PREFIX)sqlite$(LIB_SUFFIX): dict_sqlite.o
$(PLUGIN_LD) $(SHLIB_RPATH) -o $@ dict_sqlite.o $(AUXLIBS_SQLITE)
+$(LIB_PREFIX)mongodb$(LIB_SUFFIX): dict_mongodb.o
+ $(PLUGIN_LD) $(SHLIB_RPATH) -o $@ dict_mongodb.o $(AUXLIBS_MONGODB)
+
update: $(LIB_DIR)/$(LIB) $(HDRS) $(PLUGIN_MAP_SO_UPDATE)
-for i in $(HDRS); \
do \
@@ -1210,6 +1213,24 @@ dict_memcache.o: dict_memcache.c
dict_memcache.o: dict_memcache.h
dict_memcache.o: memcache_proto.h
dict_memcache.o: string_list.h
+dict_mongodb.o: ../../include/argv.h
+dict_mongodb.o: ../../include/auto_clnt.h
+dict_mongodb.o: ../../include/check_arg.h
+dict_mongodb.o: ../../include/dict.h
+dict_mongodb.o: ../../include/match_list.h
+dict_mongodb.o: ../../include/msg.h
+dict_mongodb.o: ../../include/myflock.h
+dict_mongodb.o: ../../include/mymalloc.h
+dict_mongodb.o: ../../include/stringops.h
+dict_mongodb.o: ../../include/sys_defs.h
+dict_mongodb.o: ../../include/vbuf.h
+dict_mongodb.o: ../../include/vstream.h
+dict_mongodb.o: ../../include/vstring.h
+dict_mongodb.o: cfg_parser.h
+dict_mongodb.o: db_common.h
+dict_mongodb.o: dict_mongodb.c
+dict_mongodb.o: dict_mongodb.h
+dict_mongodb.o: string_list.h
dict_mysql.o: ../../include/argv.h
dict_mysql.o: ../../include/check_arg.h
dict_mysql.o: ../../include/dict.h
@@ -1861,6 +1882,7 @@ mail_dict.o: ../../include/vstream.h
mail_dict.o: ../../include/vstring.h
mail_dict.o: dict_ldap.h
mail_dict.o: dict_memcache.h
+mail_dict.o: dict_mongodb.h
mail_dict.o: dict_mysql.h
mail_dict.o: dict_pgsql.h
mail_dict.o: dict_proxy.h
@@ -1911,6 +1933,7 @@ mail_params.o: ../../include/htable.h
mail_params.o: ../../include/inet_addr_list.h
mail_params.o: ../../include/inet_proto.h
mail_params.o: ../../include/iostuff.h
+mail_params.o: ../../include/logwriter.h
mail_params.o: ../../include/midna_domain.h
mail_params.o: ../../include/mkmap.h
mail_params.o: ../../include/msg.h
diff --git a/src/global/dict_ldap.c b/src/global/dict_ldap.c
index a078721..7310a96 100644
--- a/src/global/dict_ldap.c
+++ b/src/global/dict_ldap.c
@@ -904,7 +904,7 @@ static int attrdesc_subtype(const char *a1, const char *a2)
/* url_attrs - attributes we want from LDAP URL */
-static char **url_attrs(DICT_LDAP *dict_ldap, LDAPURLDesc * url)
+static char **url_attrs(DICT_LDAP *dict_ldap, LDAPURLDesc *url)
{
static ARGV *attrs;
char **a1;
@@ -1234,7 +1234,7 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name)
* Don't frustrate future attempts to make Postfix UTF-8 transparent.
*/
if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0
- && !valid_utf8_string(name, strlen(name))) {
+ && !valid_utf8_stringz(name)) {
if (msg_verbose)
msg_info("%s: %s: Skipping lookup of non-UTF-8 key '%s'",
myname, dict_ldap->parser->name, name);
diff --git a/src/global/dict_mongodb.c b/src/global/dict_mongodb.c
new file mode 100644
index 0000000..18144b7
--- /dev/null
+++ b/src/global/dict_mongodb.c
@@ -0,0 +1,570 @@
+/*++
+/* NAME
+/* dict_mongodb 3
+/* SUMMARY
+/* dictionary interface to mongodb, compatible with libmongoc-1.0
+/* SYNOPSIS
+/* #include <dict_mongodb.h>
+/*
+/* DICT *dict_mongodb_open(name, open_flags, dict_flags)
+/* const char *name;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_mongodb_open() opens a MongoDB database, providing a
+/* dictionary interface for Postfix mappings. The result is a
+/* pointer to the installed dictionary.
+/*
+/* Configuration parameters are described in mongodb_table(5).
+/*
+/* Arguments:
+/* .IP name
+/* Either the path to the MongoDB configuration file (if it
+/* starts with '/' or '.'), or the prefix which will be used
+/* to obtain main.cf configuration parameters for this search.
+/*
+/* In the first case, configuration parameters are specified
+/* in the file as \fIname\fR=\fIvalue\fR pairs.
+/*
+/* In the second case, the configuration parameters are prefixed
+/* with the value of \fIname\fR and an underscore, and they
+/* are specified in main.cf. For example, if this value is
+/* \fImongodbconf\fR, the parameters would look like
+/* \fImongodbconf_uri\fR, \fImongodbconf_collection\fR, and
+/* so on.
+/* .IP open_flags
+/* Must be O_RDONLY
+/* .IP dict_flags
+/* See dict_open(3).
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* HISTORY
+/* .ad
+/* .fi
+/* MongoDB support was added in Postfix 3.9.
+/* AUTHOR(S)
+/* Hamid Maadani (hamid@dexo.tech)
+/* Dextrous Technologies, LLC
+/*
+/* Edited by:
+/* Wietse Venema
+/* porcupine.org
+/*
+/* Based on prior work by:
+/* Stephan Ferraro
+/* Aionda GmbH
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#ifdef HAS_MONGODB
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <ctype.h>
+#include <inttypes.h> /* C99 PRId64 */
+
+#include <bson/bson.h>
+#include <mongoc/mongoc.h>
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <auto_clnt.h>
+#include <vstream.h>
+
+ /*
+ * Global library.
+ */
+#include <cfg_parser.h>
+#include <db_common.h>
+
+ /*
+ * Application-specific.
+ */
+#include <dict_mongodb.h>
+
+ /*
+ * Initial size for dynamically-allocated buffers.
+ */
+#ifndef BUFFER_SIZE
+#define BUFFER_SIZE 1024
+#endif
+
+#define INIT_VSTR(buf, len) do { \
+ if (buf == 0) \
+ buf = vstring_alloc(len); \
+ VSTRING_RESET(buf); \
+ VSTRING_TERMINATE(buf); \
+ } while (0)
+
+/* Structure of one mongodb dictionary handle. */
+typedef struct {
+ /* Initialized by dict_mongodb_open(). */
+ DICT dict; /* Parent class */
+ CFG_PARSER *parser; /* Configuration file parser */
+ mongoc_client_t *client; /* Mongo C client handle */
+ /* Initialized by mongodb_parse_config(). */
+ char *uri; /* mongodb+srv:/*localhost:27017 */
+ char *dbname; /* Database name */
+ char *collection; /* Collection name */
+ char *query_filter; /* db_common_expand() query template */
+ char *projection; /* Advanced MongoDB projection */
+ char *result_attribute; /* The key(s) to return the data for */
+ char *result_format; /* db_common_expand() result_template */
+ int expansion_limit; /* Result expansion limit */
+ void *ctx; /* db_common handle */
+} DICT_MONGODB;
+
+/* Per-process initialization. */
+static bool init_done = false;
+
+/* itoa - int64_t to string */
+
+static char *itoa(int64_t val)
+{
+ static char buf[21] = {0};
+ int ret;
+
+ /*
+ * XXX(Wietse) replaced custom code with standard library calls that
+ * handle zero, and negative values.
+ */
+#define PRId64_FORMAT "%" PRId64
+
+ ret = snprintf(buf, sizeof(buf), PRId64_FORMAT, val);
+ if (ret < 0)
+ msg_panic("itoa: output error for '%s'", PRId64_FORMAT);
+ if (ret >= sizeof(buf))
+ msg_panic("itoa: output for '%s' exceeds space %ld",
+ PRId64_FORMAT, sizeof(buf));
+ return (buf);
+}
+
+/* mongodb_parse_config - parse mongodb configuration file */
+
+static void mongodb_parse_config(DICT_MONGODB *dict_mongodb,
+ const char *mongodbcf)
+{
+ CFG_PARSER *p = dict_mongodb->parser;
+
+ /*
+ * Parse the configuration file.
+ */
+ dict_mongodb->uri = cfg_get_str(p, "uri", NULL, 1, 0);
+ dict_mongodb->dbname = cfg_get_str(p, "dbname", NULL, 1, 0);
+ dict_mongodb->collection = cfg_get_str(p, "collection", NULL, 1, 0);
+ dict_mongodb->query_filter = cfg_get_str(p, "query_filter", NULL, 1, 0);
+
+ /*
+ * One of projection and result_attribute must be specified. That is
+ * enforced in the caller.
+ */
+ dict_mongodb->projection = cfg_get_str(p, "projection", NULL, 0, 0);
+ dict_mongodb->result_attribute
+ = cfg_get_str(p, "result_attribute", NULL, 0, 0);
+ dict_mongodb->result_format
+ = cfg_get_str(dict_mongodb->parser, "result_format", "%s", 1, 0);
+ dict_mongodb->expansion_limit
+ = cfg_get_int(dict_mongodb->parser, "expansion_limit", 10, 0, 100);
+
+ /*
+ * db_common query parsing and domain pattern lookup.
+ */
+ dict_mongodb->ctx = 0;
+ (void) db_common_parse(&dict_mongodb->dict, &dict_mongodb->ctx,
+ dict_mongodb->query_filter, 1);
+ db_common_parse_domain(dict_mongodb->parser, dict_mongodb->ctx);
+}
+
+/* expand_value - expand lookup result value */
+
+static bool expand_value(DICT_MONGODB *dict_mongodb, const char *p,
+ const char *lookup_name,
+ VSTRING *resultString,
+ int *expansion, const char *key)
+{
+
+ /*
+ * If a lookup result cannot be processed due to an expansion limit
+ * error, return a DICT_ERR_RETRY error code and a 'false' result value.
+ * As documented for many dict_xxx() implementations, and expansion limit
+ * error is considered a temporary error.
+ */
+ if (dict_mongodb->expansion_limit > 0
+ && ++(*expansion) > dict_mongodb->expansion_limit) {
+ msg_warn("%s:%s: expansion limit exceeded for key: '%s'",
+ dict_mongodb->dict.type, dict_mongodb->dict.name, key);
+ dict_mongodb->dict.error = DICT_ERR_RETRY;
+ return (false);
+ }
+
+ /*
+ * XXX(Wietse) Added the dict_mongodb_lookup() lookup_name argument,
+ * because it selects code paths inside db_common_expand() that are
+ * specifically for lookup results instead of lookup keys, including
+ * %[SUD] substitution.
+ */
+ db_common_expand(dict_mongodb->ctx, dict_mongodb->result_format, p,
+ lookup_name, resultString, 0);
+ return (true);
+}
+
+/* get_result_string - convert lookup result to string, or set dict.error */
+
+static char *get_result_string(DICT_MONGODB *dict_mongodb,
+ VSTRING *resultString,
+ bson_iter_t *iter,
+ const char *lookup_name,
+ int *expansion,
+ const char *key)
+{
+ char *p = NULL;
+ bool got_one_result = false;
+
+ /*
+ * If a lookup result cannot be processed due to an error, return a
+ * non-zero error code and a NULL result value.
+ */
+ INIT_VSTR(resultString, BUFFER_SIZE);
+ while (dict_mongodb->dict.error == DICT_ERR_NONE && bson_iter_next(iter)) {
+ switch (bson_iter_type(iter)) {
+ case BSON_TYPE_UTF8:
+ p = (char *) bson_iter_utf8(iter, NULL);
+ if (!bson_utf8_validate(p, strlen(p), true)) {
+ msg_warn("%s:%s: invalid UTF-8 in lookup result '%s'",
+ dict_mongodb->dict.type, dict_mongodb->dict.name, p);
+ dict_mongodb->dict.error = DICT_ERR_RETRY;
+ break;
+ }
+ got_one_result |= expand_value(dict_mongodb, p, lookup_name,
+ resultString, expansion, key);
+ break;
+ case BSON_TYPE_INT64:
+ case BSON_TYPE_INT32:
+ p = itoa(bson_iter_as_int64(iter));
+ got_one_result |= expand_value(dict_mongodb, p, lookup_name,
+ resultString, expansion, key);
+ break;
+ case BSON_TYPE_ARRAY:
+ ; /* For pre-C23 Clang. */
+ const uint8_t *dataBuffer = NULL;
+ unsigned int len = 0;
+ bson_iter_t dataIter;
+ bson_t *data = NULL;
+
+ /*
+ * XXX(Wietse) are there any non-error cases, such as a valid but
+ * empty array, where bson_new_from_data() or bson_iter_init()
+ * would return null or false? If there are no such cases then we
+ * must handle null/false as an error.
+ */
+ bson_iter_array(iter, &len, &dataBuffer);
+ if ((data = bson_new_from_data(dataBuffer, len)) != 0
+ && bson_iter_init(&dataIter, data)) {
+ VSTRING *iterResult = vstring_alloc(BUFFER_SIZE);
+
+ if ((p = get_result_string(dict_mongodb, iterResult, &dataIter,
+ lookup_name, expansion, key)) != 0) {
+ vstring_sprintf_append(resultString, (got_one_result) ?
+ ",%s" : "%s", p);
+ got_one_result |= true;
+ }
+ vstring_free(iterResult);
+ }
+ bson_destroy(data);
+ break;
+ default:
+ /* Unexpected field type. As documented, warn and ignore. */
+ msg_warn("%s:%s: failed to retrieve value of '%s', "
+ "Unknown result type %d.", dict_mongodb->dict.type,
+ dict_mongodb->dict.name, bson_iter_key(iter),
+ bson_iter_type(iter));
+ break;
+ }
+ }
+ if (dict_mongodb->dict.error != DICT_ERR_NONE || !got_one_result)
+ return (0);
+ return (vstring_str(resultString));
+}
+
+/* dict_mongdb_quote - quote json string */
+
+static void dict_mongdb_quote(DICT *dict, const char *name, VSTRING *result)
+{
+ /* quote_for_json_append() will resize the result buffer as needed. */
+ (void) quote_for_json_append(result, name, -1);
+}
+
+/* dict_mongdb_append_result_attributes - projection builder */
+
+static int dict_mongdb_append_result_attribute(bson_t * projection,
+ const char *result_attribute)
+{
+ char *ra = mystrdup(result_attribute);
+ char *pp = ra;
+ char *cp;
+ int ok = 1;
+
+ while (ok && (cp = mystrtok(&pp, CHARS_COMMA_SP)) != 0)
+ ok = BSON_APPEND_INT32(projection, cp, 1);
+ myfree(ra);
+ return (ok);
+}
+
+/* dict_mongodb_lookup - find database entry using mongo query language */
+
+static const char *dict_mongodb_lookup(DICT *dict, const char *name)
+{
+ DICT_MONGODB *dict_mongodb = (DICT_MONGODB *) dict;
+ mongoc_collection_t *coll = NULL;
+ mongoc_cursor_t *cursor = NULL;
+ bson_iter_t iter;
+ const bson_t *doc = NULL;
+ bson_t *query = NULL;
+ bson_t *options = NULL;
+ bson_t *projection = NULL;
+ bson_error_t error;
+ char *result = NULL;
+ static VSTRING *queryString = NULL;
+ static VSTRING *resultString = NULL;
+ int domain_rc;
+ int expansion = 0;
+
+ dict_mongodb->dict.error = DICT_ERR_NONE;
+
+ /*
+ * If they specified a domain list for this map, then only search for
+ * addresses in domains on the list. This can significantly reduce the
+ * load on the database.
+ */
+ if ((domain_rc = db_common_check_domain(dict_mongodb->ctx, name)) == 0) {
+ if (msg_verbose)
+ msg_info("%s:%s: skipping lookup of '%s': domain mismatch",
+ dict_mongodb->dict.type, dict_mongodb->dict.name, name);
+ return (0);
+ } else if (domain_rc < 0) {
+ DICT_ERR_VAL_RETURN(dict, domain_rc, (char *) 0);
+ }
+
+ /*
+ * Ugly macros to make error and non-error handling code more readable.
+ * If code size is a concern, them an optimizing compiler can eliminate
+ * dead code or duplicated code.
+ */
+
+ /* Set an error code, and return null. */
+#define DICT_MONGODB_LOOKUP_ERR_RETURN(err) do { \
+ dict_mongodb->dict.error = (err); \
+ DICT_MONGODB_LOOKUP_RETURN((char *) 0); \
+} while (0);
+
+ /* Pass through any error, and return the specified value. */
+#define DICT_MONGODB_LOOKUP_RETURN(val) do { \
+ if (coll) mongoc_collection_destroy(coll); \
+ if (cursor) mongoc_cursor_destroy(cursor); \
+ if (query) bson_destroy(query); \
+ if (options) bson_destroy(options); \
+ if (projection) bson_destroy(projection); \
+ return (val); \
+ } while (0)
+
+ coll = mongoc_client_get_collection(dict_mongodb->client,
+ dict_mongodb->dbname,
+ dict_mongodb->collection);
+ if (!coll) {
+ msg_warn("%s:%s: failed to get collection [%s] from [%s]",
+ dict_mongodb->dict.type, dict_mongodb->dict.name,
+ dict_mongodb->collection, dict_mongodb->dbname);
+ DICT_MONGODB_LOOKUP_ERR_RETURN(DICT_ERR_RETRY);
+ }
+
+ /*
+ * Use the specified result projection, or craft one from the
+ * result_attribute. Exclude the _id field from the result.
+ */
+ options = bson_new();
+ if (dict_mongodb->projection) {
+ projection = bson_new_from_json((uint8_t *) dict_mongodb->projection,
+ -1, &error);
+ if (!projection) {
+ msg_warn("%s:%s: failed to create a projection from '%s': %s",
+ dict_mongodb->dict.type, dict_mongodb->dict.name,
+ dict_mongodb->projection, error.message);
+ DICT_MONGODB_LOOKUP_ERR_RETURN(DICT_ERR_RETRY);
+ }
+ if (!BSON_APPEND_INT32(projection, "_id", 0)
+ || !BSON_APPEND_DOCUMENT(options, "projection", projection)) {
+ msg_warn("%s:%s: failed to append a projection from '%s'",
+ dict_mongodb->dict.type, dict_mongodb->dict.name,
+ dict_mongodb->projection);
+ DICT_MONGODB_LOOKUP_ERR_RETURN(DICT_ERR_RETRY);
+ }
+ } else if (dict_mongodb->result_attribute) {
+ bson_t res_attr;
+
+ if (!BSON_APPEND_DOCUMENT_BEGIN(options, "projection", &res_attr)
+ || !BSON_APPEND_INT32(&res_attr, "_id", 0)
+ || !dict_mongdb_append_result_attribute(&res_attr,
+ dict_mongodb->result_attribute)
+ || !bson_append_document_end(options, &res_attr)) {
+ msg_warn("%s:%s: failed to append a projection from '%s'",
+ dict_mongodb->dict.type, dict_mongodb->dict.name,
+ dict_mongodb->result_attribute);
+ DICT_MONGODB_LOOKUP_ERR_RETURN(DICT_ERR_RETRY);
+ }
+ } else {
+ /* Can't happen. The configuration parser should reject this. */
+ msg_panic("%s:%s: empty 'projection' and 'result_attribute'",
+ dict_mongodb->dict.type, dict_mongodb->dict.name);
+ }
+
+ /*
+ * Expand filter template. This uses a quoting function to prevent
+ * metacharacter injection with parts from a crafted email address.
+ */
+ INIT_VSTR(queryString, BUFFER_SIZE);
+ if (!db_common_expand(dict_mongodb->ctx, dict_mongodb->query_filter,
+ name, 0, queryString, dict_mongdb_quote))
+ /* Suppress the actual lookup if the expansion is empty. */
+ DICT_MONGODB_LOOKUP_RETURN(0);
+
+ /* Create the query from the expanded query template. */
+ query = bson_new_from_json((uint8_t *) vstring_str(queryString),
+ -1, &error);
+ if (!query) {
+ msg_warn("%s:%s: failed to create a query from '%s': %s",
+ dict_mongodb->dict.type, dict_mongodb->dict.name,
+ vstring_str(queryString), error.message);
+ DICT_MONGODB_LOOKUP_ERR_RETURN(DICT_ERR_RETRY);
+ }
+ /* Run the query. */
+ cursor = mongoc_collection_find_with_opts(coll, query, options, NULL);
+ if (mongoc_cursor_error(cursor, &error)) {
+ msg_warn("%s:%s: cursor error for '%s': %s",
+ dict_mongodb->dict.type, dict_mongodb->dict.name,
+ vstring_str(queryString), error.message);
+ DICT_MONGODB_LOOKUP_ERR_RETURN(DICT_ERR_RETRY);
+ }
+ /* Convert the lookup result to C string. */
+ INIT_VSTR(resultString, BUFFER_SIZE);
+ while (mongoc_cursor_next(cursor, &doc)) {
+ if (bson_iter_init(&iter, doc)) {
+ result = get_result_string(dict_mongodb, resultString, &iter,
+ name, &expansion, name);
+ }
+ }
+ DICT_MONGODB_LOOKUP_RETURN(result);
+}
+
+/* dict_mongodb_close - close MongoDB database */
+
+static void dict_mongodb_close(DICT *dict)
+{
+ DICT_MONGODB *dict_mongodb = (DICT_MONGODB *) dict;
+
+ cfg_parser_free(dict_mongodb->parser);
+ if (dict_mongodb->ctx) {
+ db_common_free_ctx(dict_mongodb->ctx);
+ }
+ myfree(dict_mongodb->uri);
+ myfree(dict_mongodb->dbname);
+ myfree(dict_mongodb->collection);
+ myfree(dict_mongodb->query_filter);
+
+ if (dict_mongodb->result_attribute) {
+ myfree(dict_mongodb->result_attribute);
+ }
+ if (dict_mongodb->result_format) {
+ myfree(dict_mongodb->result_format);
+ }
+ if (dict_mongodb->projection) {
+ myfree(dict_mongodb->projection);
+ }
+ if (dict_mongodb->client) {
+ mongoc_client_destroy(dict_mongodb->client);
+ }
+ dict_free(dict);
+}
+
+/* dict_mongodb_open - open MongoDB database connection */
+
+DICT *dict_mongodb_open(const char *name, int open_flags, int dict_flags)
+{
+ DICT_MONGODB *dict_mongodb;
+ CFG_PARSER *parser;
+ mongoc_uri_t *uri = 0;
+ bson_error_t error;
+
+ /* Sanity checks. */
+ if (open_flags != O_RDONLY) {
+ return (dict_surrogate(DICT_TYPE_MONGODB, name, open_flags, dict_flags,
+ "%s:%s: map requires O_RDONLY access mode",
+ DICT_TYPE_MONGODB, name));
+ }
+ /* Open the configuration file. */
+ if ((parser = cfg_parser_alloc(name)) == 0) {
+ return (dict_surrogate(DICT_TYPE_MONGODB, name, open_flags, dict_flags,
+ "open %s: %m", name));
+ }
+ /* Create the dictionary object. */
+ dict_mongodb = (DICT_MONGODB *) dict_alloc(DICT_TYPE_MONGODB, name,
+ sizeof(*dict_mongodb));
+ dict_mongodb->dict.lookup = dict_mongodb_lookup;
+ dict_mongodb->dict.close = dict_mongodb_close;
+ dict_mongodb->dict.flags = dict_flags;
+ dict_mongodb->parser = parser;
+ dict_mongodb->dict.owner = cfg_get_owner(dict_mongodb->parser);
+ dict_mongodb->client = NULL;
+
+ /* Parse config. */
+ mongodb_parse_config(dict_mongodb, name);
+ if (!dict_mongodb->projection == !dict_mongodb->result_attribute) {
+ dict_mongodb_close(&dict_mongodb->dict);
+ return (dict_surrogate(DICT_TYPE_MONGODB, name, open_flags, dict_flags,
+ "%s:%s: specify exactly one of 'projection' or 'result_attribute'",
+ DICT_TYPE_MONGODB, name));
+ }
+ /* One-time initialization of libmongoc 's internals. */
+ if (!init_done) {
+ mongoc_init();
+ init_done = true;
+ }
+#define DICT_MONGODB_OPEN_ERR_RETURN(d) do { \
+ DICT *_d = (d); \
+ if (uri) mongoc_uri_destroy(uri); \
+ dict_mongodb_close(&dict_mongodb->dict); \
+ return (_d); \
+ } while (0);
+
+ uri = mongoc_uri_new_with_error(dict_mongodb->uri, &error);
+ if (!uri)
+ DICT_MONGODB_OPEN_ERR_RETURN(dict_surrogate(DICT_TYPE_MONGODB, name,
+ open_flags, dict_flags,
+ "%s:%s: failed to parse URI '%s': %s",
+ DICT_TYPE_MONGODB, name,
+ dict_mongodb->uri, error.message));
+
+ dict_mongodb->client = mongoc_client_new_from_uri_with_error(uri, &error);
+ if (!dict_mongodb->client)
+ DICT_MONGODB_OPEN_ERR_RETURN(dict_surrogate(DICT_TYPE_MONGODB, name,
+ open_flags, dict_flags,
+ "%s:%s: failed to create client for '%s': %s",
+ DICT_TYPE_MONGODB, name,
+ dict_mongodb->uri,
+ error.message));
+
+ mongoc_uri_destroy(uri);
+ mongoc_client_set_error_api(dict_mongodb->client, MONGOC_ERROR_API_VERSION_2);
+ return (DICT_DEBUG (&dict_mongodb->dict));
+}
+
+#endif
diff --git a/src/global/dict_mongodb.h b/src/global/dict_mongodb.h
new file mode 100755
index 0000000..d5120cb
--- /dev/null
+++ b/src/global/dict_mongodb.h
@@ -0,0 +1,43 @@
+#ifndef _DICT_MONGODB_INCLUDED_
+#define _DICT_MONGODB_INCLUDED_
+
+/*++
+/* NAME
+/* dict_mongodb 3h
+/* SUMMARY
+/* dictionary interface to mongodb databases
+/* SYNOPSIS
+/* #include <dict_mongodb.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_MONGODB "mongodb"
+
+extern DICT *dict_mongodb_open(const char *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Hamid Maadani (hamid@dexo.tech)
+/* Dextrous Technologies, LLC
+/*
+/* Edited by:
+/* Wietse Venema
+/* porcupine.org
+/*
+/* Based on prior work by:
+/* Stephan Ferraro
+/* Aionda GmbH
+/*--*/
+
+#endif
diff --git a/src/global/dict_mysql.c b/src/global/dict_mysql.c
index 3c8fe4f..133cc0d 100644
--- a/src/global/dict_mysql.c
+++ b/src/global/dict_mysql.c
@@ -83,6 +83,10 @@
#include <limits.h>
#include <errno.h>
+#if !defined(MYSQL_VERSION_ID) || MYSQL_VERSION_ID < 40000
+#error "MySQL versions <4 are no longer supported"
+#endif
+
#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#endif
@@ -147,9 +151,11 @@ typedef struct {
char *username;
char *password;
char *dbname;
+ char *charset;
+ int retry_interval;
+ int idle_interval;
ARGV *hosts;
PLMYSQL *pldb;
-#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
HOST *active_host;
char *tls_cert_file;
char *tls_key_file;
@@ -159,7 +165,6 @@ typedef struct {
#if defined(DICT_MYSQL_SSL_VERIFY_SERVER_CERT)
int tls_verify_cert;
#endif
-#endif
int require_result_set;
} DICT_MYSQL;
@@ -171,15 +176,15 @@ typedef struct {
#define TYPEINET (1<<1)
#define RETRY_CONN_MAX 100
-#define RETRY_CONN_INTV 60 /* 1 minute */
-#define IDLE_CONN_INTV 60 /* 1 minute */
+#define DEF_RETRY_INTV 60 /* 1 minute */
+#define DEF_IDLE_INTV 60 /* 1 minute */
/* internal function declarations */
static PLMYSQL *plmysql_init(ARGV *);
static int plmysql_query(DICT_MYSQL *, const char *, VSTRING *, MYSQL_RES **);
static void plmysql_dealloc(PLMYSQL *);
static void plmysql_close_host(HOST *);
-static void plmysql_down_host(HOST *);
+static void plmysql_down_host(HOST *, int);
static void plmysql_connect_single(DICT_MYSQL *, HOST *);
static const char *dict_mysql_lookup(DICT *, const char *);
DICT *dict_mysql_open(const char *, int, int);
@@ -205,13 +210,21 @@ static void dict_mysql_quote(DICT *dict, const char *name, VSTRING *result)
buflen = 2 * len + 1;
VSTRING_SPACE(result, buflen);
-#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
- if (dict_mysql->active_host)
- mysql_real_escape_string(dict_mysql->active_host->db,
- vstring_end(result), name, len);
- else
+ if (dict_mysql->active_host == 0)
+ msg_panic("dict_mysql_quote: no active host");
+#if MYSQL_VERSION_ID >= 50706 && !defined(MARIADB_VERSION_ID)
+ mysql_real_escape_string_quote(dict_mysql->active_host->db,
+ vstring_end(result), name, len, '\'');
+#else
+ if (mysql_real_escape_string(dict_mysql->active_host->db,
+ vstring_end(result), name, len) ==
+ (unsigned long) -1) {
+ msg_warn("dict_mysql: host (%s) cannot escape input string: >%s<",
+ dict_mysql->active_host->hostname,
+ mysql_error(dict_mysql->active_host->db));
+ dict_mysql->active_host->stat = STATFAIL;
+ }
#endif
- mysql_escape_string(vstring_end(result), name, len);
VSTRING_SKIP(result);
}
@@ -231,7 +244,6 @@ static const char *dict_mysql_lookup(DICT *dict, const char *name)
int numrows;
int expansion;
const char *r;
- db_quote_callback_t quote_func = dict_mysql_quote;
int domain_rc;
dict->error = 0;
@@ -241,7 +253,7 @@ static const char *dict_mysql_lookup(DICT *dict, const char *name)
*/
#ifdef SNAPSHOT
if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0
- && !valid_utf8_string(name, strlen(name))) {
+ && !valid_utf8_stringz(name)) {
if (msg_verbose)
msg_info("%s: %s: Skipping lookup of non-UTF-8 key '%s'",
myname, dict_mysql->parser->name, name);
@@ -291,11 +303,8 @@ static const char *dict_mysql_lookup(DICT *dict, const char *name)
* quoting happens separately for each connection, we don't bother with
* quoting...
*/
-#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
- quote_func = 0;
-#endif
if (!db_common_expand(dict_mysql->ctx, dict_mysql->query,
- name, 0, query, quote_func))
+ name, 0, query, (db_quote_callback_t) 0))
return (0);
/* do the query - set dict->error & cleanup if there's an error */
@@ -439,8 +448,12 @@ static int plmysql_query(DICT_MYSQL *dict_mysql,
{
HOST *host;
MYSQL_RES *first_result = 0;
+
+ /* In case all hosts are down. */
int query_error = 1;
+ errno = ENOTSUP;
+
/*
* Helper to avoid spamming the log with warnings.
*/
@@ -454,8 +467,6 @@ static int plmysql_query(DICT_MYSQL *dict_mysql,
while ((host = dict_mysql_get_active(dict_mysql)) != NULL) {
-#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
-
/*
* The active host is used to escape strings in the context of the
* active connection's character encoding.
@@ -465,8 +476,14 @@ static int plmysql_query(DICT_MYSQL *dict_mysql,
VSTRING_TERMINATE(query);
db_common_expand(dict_mysql->ctx, dict_mysql->query,
name, 0, query, dict_mysql_quote);
+ /* Check for potential dict_mysql_quote() failure. */
+ if (host->stat == STATFAIL) {
+ plmysql_down_host(host, dict_mysql->retry_interval);
+ continue;
+ }
+ if (msg_verbose)
+ msg_info("expanded and quoted query: >%s<", vstring_str(query));
dict_mysql->active_host = 0;
-#endif
query_error = 0;
errno = 0;
@@ -546,7 +563,7 @@ static int plmysql_query(DICT_MYSQL *dict_mysql,
* See what we got.
*/
if (query_error) {
- plmysql_down_host(host);
+ plmysql_down_host(host, dict_mysql->retry_interval);
if (errno == 0)
errno = ENOTSUP;
if (first_result) {
@@ -559,7 +576,7 @@ static int plmysql_query(DICT_MYSQL *dict_mysql,
dict_mysql->dict.type, dict_mysql->dict.name,
host->hostname);
event_request_timer(dict_mysql_event, (void *) host,
- IDLE_CONN_INTV);
+ dict_mysql->idle_interval);
break;
}
}
@@ -581,7 +598,6 @@ static void plmysql_connect_single(DICT_MYSQL *dict_mysql, HOST *host)
mysql_options(host->db, MYSQL_READ_DEFAULT_FILE, dict_mysql->option_file);
if (dict_mysql->option_group && dict_mysql->option_group[0])
mysql_options(host->db, MYSQL_READ_DEFAULT_GROUP, dict_mysql->option_group);
-#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
if (dict_mysql->tls_key_file || dict_mysql->tls_cert_file ||
dict_mysql->tls_CAfile || dict_mysql->tls_CApath || dict_mysql->tls_ciphers)
mysql_ssl_set(host->db,
@@ -593,7 +609,6 @@ static void plmysql_connect_single(DICT_MYSQL *dict_mysql, HOST *host)
mysql_options(host->db, DICT_MYSQL_SSL_VERIFY_SERVER_CERT,
&dict_mysql->tls_verify_cert);
#endif
-#endif
if (mysql_real_connect(host->db,
(host->type == TYPEINET ? host->name : 0),
dict_mysql->username,
@@ -602,6 +617,12 @@ static void plmysql_connect_single(DICT_MYSQL *dict_mysql, HOST *host)
host->port,
(host->type == TYPEUNIX ? host->name : 0),
CLIENT_MULTI_RESULTS)) {
+ if (mysql_set_character_set(host->db, dict_mysql->charset) != 0) {
+ msg_warn("dict_mysql: mysql_set_character_set '%s' failed: %s",
+ dict_mysql->charset, mysql_error(host->db));
+ plmysql_down_host(host, dict_mysql->retry_interval);
+ return;
+ }
if (msg_verbose)
msg_info("dict_mysql: successful connection to host %s",
host->hostname);
@@ -609,7 +630,7 @@ static void plmysql_connect_single(DICT_MYSQL *dict_mysql, HOST *host)
} else {
msg_warn("connect to mysql server %s: %s",
host->hostname, mysql_error(host->db));
- plmysql_down_host(host);
+ plmysql_down_host(host, dict_mysql->retry_interval);
}
}
@@ -625,11 +646,11 @@ static void plmysql_close_host(HOST *host)
* plmysql_down_host - close a failed connection AND set a "stay away from
* this host" timer
*/
-static void plmysql_down_host(HOST *host)
+static void plmysql_down_host(HOST *host, int retry_interval)
{
mysql_close(host->db);
host->db = 0;
- host->ts = time((time_t *) 0) + RETRY_CONN_INTV;
+ host->ts = time((time_t *) 0) + retry_interval;
host->stat = STATFAIL;
event_cancel_timer(dict_mysql_event, (void *) host);
}
@@ -646,10 +667,14 @@ static void mysql_parse_config(DICT_MYSQL *dict_mysql, const char *mysqlcf)
dict_mysql->username = cfg_get_str(p, "user", "", 0, 0);
dict_mysql->password = cfg_get_str(p, "password", "", 0, 0);
dict_mysql->dbname = cfg_get_str(p, "dbname", "", 1, 0);
+ dict_mysql->charset = cfg_get_str(p, "charset", "utf8mb4", 1, 0);
+ dict_mysql->retry_interval = cfg_get_int(p, "retry_interval",
+ DEF_RETRY_INTV, 1, 0);
+ dict_mysql->idle_interval = cfg_get_int(p, "idle_interval",
+ DEF_IDLE_INTV, 1, 0);
dict_mysql->result_format = cfg_get_str(p, "result_format", "%s", 1, 0);
dict_mysql->option_file = cfg_get_str(p, "option_file", NULL, 0, 0);
dict_mysql->option_group = cfg_get_str(p, "option_group", "client", 0, 0);
-#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
dict_mysql->tls_key_file = cfg_get_str(p, "tls_key_file", NULL, 0, 0);
dict_mysql->tls_cert_file = cfg_get_str(p, "tls_cert_file", NULL, 0, 0);
dict_mysql->tls_CAfile = cfg_get_str(p, "tls_CAfile", NULL, 0, 0);
@@ -658,7 +683,6 @@ static void mysql_parse_config(DICT_MYSQL *dict_mysql, const char *mysqlcf)
#if defined(DICT_MYSQL_SSL_VERIFY_SERVER_CERT)
dict_mysql->tls_verify_cert = cfg_get_bool(p, "tls_verify_cert", -1);
#endif
-#endif
dict_mysql->require_result_set = cfg_get_bool(p, "require_result_set", 1);
/*
@@ -741,9 +765,7 @@ DICT *dict_mysql_open(const char *name, int open_flags, int dict_flags)
dict_mysql->dict.flags = dict_flags;
dict_mysql->parser = parser;
mysql_parse_config(dict_mysql, name);
-#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
dict_mysql->active_host = 0;
-#endif
dict_mysql->pldb = plmysql_init(dict_mysql->hosts);
if (dict_mysql->pldb == NULL)
msg_fatal("couldn't initialize pldb!\n");
@@ -826,13 +848,13 @@ static void dict_mysql_close(DICT *dict)
myfree(dict_mysql->username);
myfree(dict_mysql->password);
myfree(dict_mysql->dbname);
+ myfree(dict_mysql->charset);
myfree(dict_mysql->query);
myfree(dict_mysql->result_format);
if (dict_mysql->option_file)
myfree(dict_mysql->option_file);
if (dict_mysql->option_group)
myfree(dict_mysql->option_group);
-#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
if (dict_mysql->tls_key_file)
myfree(dict_mysql->tls_key_file);
if (dict_mysql->tls_cert_file)
@@ -843,7 +865,6 @@ static void dict_mysql_close(DICT *dict)
myfree(dict_mysql->tls_CApath);
if (dict_mysql->tls_ciphers)
myfree(dict_mysql->tls_ciphers);
-#endif
if (dict_mysql->hosts)
argv_free(dict_mysql->hosts);
if (dict_mysql->ctx)
diff --git a/src/global/dict_pgsql.c b/src/global/dict_pgsql.c
index 5992135..c626854 100644
--- a/src/global/dict_pgsql.c
+++ b/src/global/dict_pgsql.c
@@ -108,18 +108,18 @@
#define TYPEUNIX (1<<0)
#define TYPEINET (1<<1)
-#define TYPECONNSTRING (1<<2)
+#define TYPECONNSTR (1<<2)
#define RETRY_CONN_MAX 100
-#define RETRY_CONN_INTV 60 /* 1 minute */
-#define IDLE_CONN_INTV 60 /* 1 minute */
+#define DEF_RETRY_INTV 60 /* 1 minute */
+#define DEF_IDLE_INTV 60 /* 1 minute */
typedef struct {
PGconn *db;
char *hostname;
char *name;
char *port;
- unsigned type; /* TYPEUNIX | TYPEINET | TYPECONNSTRING */
+ unsigned type; /* TYPEUNIX | TYPEINET | TYPECONNSTR */
unsigned stat; /* STATUNTRIED | STATFAIL | STATCUR */
time_t ts; /* used for attempting reconnection */
} HOST;
@@ -140,6 +140,8 @@ typedef struct {
char *password;
char *dbname;
char *encoding;
+ int retry_interval;
+ int idle_interval;
char *table;
ARGV *hosts;
PLPGSQL *pldb;
@@ -152,12 +154,11 @@ typedef struct {
/* internal function declarations */
static PLPGSQL *plpgsql_init(ARGV *);
-static PGSQL_RES *plpgsql_query(DICT_PGSQL *, const char *, VSTRING *, char *,
- char *, char *, char *);
+static PGSQL_RES *plpgsql_query(DICT_PGSQL *, const char *, VSTRING *);
static void plpgsql_dealloc(PLPGSQL *);
static void plpgsql_close_host(HOST *);
-static void plpgsql_down_host(HOST *);
-static void plpgsql_connect_single(HOST *, char *, char *, char *, char *);
+static void plpgsql_down_host(HOST *, int);
+static void plpgsql_connect_single(DICT_PGSQL *, HOST *);
static const char *dict_pgsql_lookup(DICT *, const char *);
DICT *dict_pgsql_open(const char *, int, int);
static void dict_pgsql_close(DICT *);
@@ -280,7 +281,7 @@ static const char *dict_pgsql_lookup(DICT *dict, const char *name)
*/
#ifdef SNAPSHOT
if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0
- && !valid_utf8_string(name, strlen(name))) {
+ && !valid_utf8_stringz(name)) {
if (msg_verbose)
msg_info("%s: %s: Skipping lookup of non-UTF-8 key '%s'",
myname, dict_pgsql->parser->name, name);
@@ -324,11 +325,7 @@ static const char *dict_pgsql_lookup(DICT *dict, const char *name)
return (0);
/* do the query - set dict->error & cleanup if there's an error */
- if ((query_res = plpgsql_query(dict_pgsql, name, query,
- dict_pgsql->dbname,
- dict_pgsql->encoding,
- dict_pgsql->username,
- dict_pgsql->password)) == 0) {
+ if ((query_res = plpgsql_query(dict_pgsql, name, query)) == 0) {
dict->error = DICT_ERR_RETRY;
return 0;
}
@@ -404,8 +401,7 @@ static HOST *dict_pgsql_find_host(PLPGSQL *PLDB, unsigned stat, unsigned type)
/* dict_pgsql_get_active - get an active connection */
-static HOST *dict_pgsql_get_active(PLPGSQL *PLDB, char *dbname, char *encoding,
- char *username, char *password)
+static HOST *dict_pgsql_get_active(DICT_PGSQL *dict_pgsql, PLPGSQL *PLDB)
{
const char *myname = "dict_pgsql_get_active";
HOST *host;
@@ -414,7 +410,7 @@ static HOST *dict_pgsql_get_active(PLPGSQL *PLDB, char *dbname, char *encoding,
/* try the active connections first; prefer the ones to UNIX sockets */
if ((host = dict_pgsql_find_host(PLDB, STATACTIVE, TYPEUNIX)) != NULL ||
(host = dict_pgsql_find_host(PLDB, STATACTIVE, TYPEINET)) != NULL ||
- (host = dict_pgsql_find_host(PLDB, STATACTIVE, TYPECONNSTRING)) != NULL) {
+ (host = dict_pgsql_find_host(PLDB, STATACTIVE, TYPECONNSTR)) != NULL) {
if (msg_verbose)
msg_info("%s: found active connection to host %s", myname,
host->hostname);
@@ -432,11 +428,11 @@ static HOST *dict_pgsql_get_active(PLPGSQL *PLDB, char *dbname, char *encoding,
(host = dict_pgsql_find_host(PLDB, STATUNTRIED | STATFAIL,
TYPEINET)) != NULL ||
(host = dict_pgsql_find_host(PLDB, STATUNTRIED | STATFAIL,
- TYPECONNSTRING)) != NULL)) {
+ TYPECONNSTR)) != NULL)) {
if (msg_verbose)
msg_info("%s: attempting to connect to host %s", myname,
host->hostname);
- plpgsql_connect_single(host, dbname, encoding, username, password);
+ plpgsql_connect_single(dict_pgsql, host);
if (host->stat == STATACTIVE)
return host;
}
@@ -464,18 +460,14 @@ static void dict_pgsql_event(int unused_event, void *context)
static PGSQL_RES *plpgsql_query(DICT_PGSQL *dict_pgsql,
const char *name,
- VSTRING *query,
- char *dbname,
- char *encoding,
- char *username,
- char *password)
+ VSTRING *query)
{
PLPGSQL *PLDB = dict_pgsql->pldb;
HOST *host;
PGSQL_RES *res = 0;
ExecStatusType status;
- while ((host = dict_pgsql_get_active(PLDB, dbname, encoding, username, password)) != NULL) {
+ while ((host = dict_pgsql_get_active(dict_pgsql, PLDB)) != NULL) {
/*
* The active host is used to escape strings in the context of the
@@ -490,7 +482,7 @@ static PGSQL_RES *plpgsql_query(DICT_PGSQL *dict_pgsql,
/* Check for potential dict_pgsql_quote() failure. */
if (host->stat == STATFAIL) {
- plpgsql_down_host(host);
+ plpgsql_down_host(host, dict_pgsql->retry_interval);
continue;
}
@@ -528,7 +520,7 @@ static PGSQL_RES *plpgsql_query(DICT_PGSQL *dict_pgsql,
msg_info("dict_pgsql: successful query from host %s",
host->hostname);
event_request_timer(dict_pgsql_event, (void *) host,
- IDLE_CONN_INTV);
+ dict_pgsql->idle_interval);
return (res);
case PGRES_FATAL_ERROR:
msg_warn("pgsql query failed: fatal error from host %s: %s",
@@ -559,7 +551,7 @@ static PGSQL_RES *plpgsql_query(DICT_PGSQL *dict_pgsql,
*/
if (res != 0)
PQclear(res);
- plpgsql_down_host(host);
+ plpgsql_down_host(host, dict_pgsql->retry_interval);
}
return (0);
@@ -570,24 +562,25 @@ static PGSQL_RES *plpgsql_query(DICT_PGSQL *dict_pgsql,
* used to reconnect to a single database when one is down or none is
* connected yet. Log all errors and set the stat field of host accordingly
*/
-static void plpgsql_connect_single(HOST *host, char *dbname, char *encoding, char *username, char *password)
+static void plpgsql_connect_single(DICT_PGSQL *dict_pgsql, HOST *host)
{
- if (host->type == TYPECONNSTRING) {
+ if (host->type == TYPECONNSTR) {
host->db = PQconnectdb(host->name);
} else {
host->db = PQsetdbLogin(host->name, host->port, NULL, NULL,
- dbname, username, password);
+ dict_pgsql->dbname, dict_pgsql->username,
+ dict_pgsql->password);
}
if (host->db == NULL || PQstatus(host->db) != CONNECTION_OK) {
msg_warn("connect to pgsql server %s: %s",
host->hostname, PQerrorMessage(host->db));
- plpgsql_down_host(host);
+ plpgsql_down_host(host, dict_pgsql->retry_interval);
return;
}
- if (PQsetClientEncoding(host->db, encoding) != 0) {
+ if (PQsetClientEncoding(host->db, dict_pgsql->encoding) != 0) {
msg_warn("dict_pgsql: cannot set the encoding to %s, skipping %s",
- encoding, host->hostname);
- plpgsql_down_host(host);
+ dict_pgsql->encoding, host->hostname);
+ plpgsql_down_host(host, dict_pgsql->retry_interval);
return;
}
if (msg_verbose)
@@ -611,12 +604,12 @@ static void plpgsql_close_host(HOST *host)
* plpgsql_down_host - close a failed connection AND set a "stay away from
* this host" timer.
*/
-static void plpgsql_down_host(HOST *host)
+static void plpgsql_down_host(HOST *host, int retry_interval)
{
if (host->db)
PQfinish(host->db);
host->db = 0;
- host->ts = time((time_t *) 0) + RETRY_CONN_INTV;
+ host->ts = time((time_t *) 0) + retry_interval;
host->stat = STATFAIL;
event_cancel_timer(dict_pgsql_event, (void *) host);
}
@@ -635,6 +628,10 @@ static void pgsql_parse_config(DICT_PGSQL *dict_pgsql, const char *pgsqlcf)
dict_pgsql->password = cfg_get_str(p, "password", "", 0, 0);
dict_pgsql->dbname = cfg_get_str(p, "dbname", "", 1, 0);
dict_pgsql->encoding = cfg_get_str(p, "encoding", "UTF8", 1, 0);
+ dict_pgsql->retry_interval = cfg_get_int(p, "retry_interval",
+ DEF_RETRY_INTV, 1, 0);
+ dict_pgsql->idle_interval = cfg_get_int(p, "idle_interval",
+ DEF_IDLE_INTV, 1, 0);
dict_pgsql->result_format = cfg_get_str(p, "result_format", "%s", 1, 0);
/*
@@ -764,7 +761,7 @@ static HOST *host_init(const char *hostname)
* Modern syntax: "postgresql://connection-info".
*/
if (strncmp(d, "postgresql:", 11) == 0) {
- host->type = TYPECONNSTRING;
+ host->type = TYPECONNSTR;
host->name = mystrdup(d);
host->port = 0;
}
diff --git a/src/global/dict_sqlite.c b/src/global/dict_sqlite.c
index 677d05a..7d6608a 100644
--- a/src/global/dict_sqlite.c
+++ b/src/global/dict_sqlite.c
@@ -149,7 +149,7 @@ static const char *dict_sqlite_lookup(DICT *dict, const char *name)
* Don't frustrate future attempts to make Postfix UTF-8 transparent.
*/
if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0
- && !valid_utf8_string(name, strlen(name))) {
+ && !valid_utf8_stringz(name)) {
if (msg_verbose)
msg_info("%s: %s: Skipping lookup of non-UTF-8 key '%s'",
myname, dict_sqlite->parser->name, name);
diff --git a/src/global/mail_addr_find.c b/src/global/mail_addr_find.c
index afbccd5..c7e5545 100644
--- a/src/global/mail_addr_find.c
+++ b/src/global/mail_addr_find.c
@@ -442,8 +442,8 @@ const char *mail_addr_find_opt(MAPS *path, const char *address, char **extp,
/*
* Try localpart@ even if the domain is not local.
*/
- if ((strategy & MA_FIND_LOCALPART_AT) != 0 \
- &&result == 0 && path->error == 0)
+ if ((strategy & MA_FIND_LOCALPART_AT) != 0
+ && result == 0 && path->error == 0)
result = find_local(path, ratsign, 1, int_full_key,
int_bare_key, query_form, extp, &saved_ext,
ext_addr_buf);
diff --git a/src/global/mail_date.c b/src/global/mail_date.c
index 55d8907..439a0ea 100644
--- a/src/global/mail_date.c
+++ b/src/global/mail_date.c
@@ -10,7 +10,7 @@
/* time_t when;
/* DESCRIPTION
/* mail_date() converts the time specified in \fIwhen\fR to the
-/* form: "Mon, 9 Dec 1996 05:38:26 -0500 (EST)" and returns
+/* form: "Mon, 09 Dec 1996 05:38:26 -0500 (EST)" and returns
/* a pointer to the result. The result is overwritten upon
/* each call.
/* DIAGNOSTICS
@@ -98,8 +98,13 @@ const char *mail_date(time_t when)
* First, format the date and wall-clock time. XXX The %e format (day of
* month, leading zero replaced by blank) isn't in my POSIX book, but
* many vendors seem to support it.
+ *
+ * The RFC 5322 Date and Time Specification recommends (i.e., should) "that
+ * a single space be used in each place that FWS appears". To avoid a
+ * potentially breaking change, we prefer the %d (two-digit day) format,
+ * i.e. days 1-9 now have a leading zero instead of a leading space.
*/
-#ifdef MISSING_STRFTIME_E
+#if defined(MISSING_STRFTIME_E) || defined(TWO_DIGIT_DAY_IN_DATE_TIME)
#define STRFTIME_FMT "%a, %d %b %Y %H:%M:%S "
#else
#define STRFTIME_FMT "%a, %e %b %Y %H:%M:%S "
diff --git a/src/global/mail_dict.c b/src/global/mail_dict.c
index c640a80..55ac5dc 100644
--- a/src/global/mail_dict.c
+++ b/src/global/mail_dict.c
@@ -52,6 +52,7 @@
#include <dict_pgsql.h>
#include <dict_sqlite.h>
#include <dict_memcache.h>
+#include <dict_mongodb.h>
#include <mail_dict.h>
#include <mail_params.h>
#include <mail_dict.h>
@@ -71,6 +72,9 @@ static const DICT_OPEN_INFO dict_open_info[] = {
#ifdef HAS_SQLITE
DICT_TYPE_SQLITE, dict_sqlite_open, 0,
#endif
+#ifdef HAS_MONGODB
+ DICT_TYPE_MONGODB, dict_mongodb_open, 0,
+#endif
#endif /* !USE_DYNAMIC_MAPS */
DICT_TYPE_MEMCACHE, dict_memcache_open, 0,
0,
diff --git a/src/global/mail_params.c b/src/global/mail_params.c
index 81aee73..2a7f84c 100644
--- a/src/global/mail_params.c
+++ b/src/global/mail_params.c
@@ -159,6 +159,7 @@
/* char *var_maillog_file_pfxs;
/* char *var_maillog_file_comp;
/* char *var_maillog_file_stamp;
+/* char *var_maillog_file_perms;
/* char *var_postlog_service;
/*
/* char *var_dnssec_probe;
@@ -226,6 +227,7 @@
#include <vstring_vstream.h>
#include <iostuff.h>
#include <midna_domain.h>
+#include <logwriter.h>
/* Global library. */
@@ -375,6 +377,7 @@ char *var_maillog_file;
char *var_maillog_file_pfxs;
char *var_maillog_file_comp;
char *var_maillog_file_stamp;
+char *var_maillog_file_perms;
char *var_postlog_service;
char *var_dnssec_probe;
@@ -515,9 +518,11 @@ static void check_mail_owner(void)
*/
if ((pwd = getpwuid(var_owner_uid)) != 0
&& strcmp(pwd->pw_name, var_mail_owner) != 0)
- msg_fatal("file %s/%s: parameter %s: user %s has same user ID as %s",
+ msg_fatal("file %s/%s: parameter %s: user %s has the same"
+ " user ID %ld as user %s",
var_config_dir, MAIN_CONF_FILE,
- VAR_MAIL_OWNER, var_mail_owner, pwd->pw_name);
+ VAR_MAIL_OWNER, var_mail_owner,
+ (long) var_owner_uid, pwd->pw_name);
}
/* check_sgid_group - lookup setgid group attributes and validate */
@@ -542,9 +547,11 @@ static void check_sgid_group(void)
*/
if ((grp = getgrgid(var_sgid_gid)) != 0
&& strcmp(grp->gr_name, var_sgid_group) != 0)
- msg_fatal("file %s/%s: parameter %s: group %s has same group ID as %s",
+ msg_fatal("file %s/%s: parameter %s: group %s has the same"
+ " group ID %ld as group %s",
var_config_dir, MAIN_CONF_FILE,
- VAR_SGID_GROUP, var_sgid_group, grp->gr_name);
+ VAR_SGID_GROUP, var_sgid_group,
+ (long) var_sgid_gid, grp->gr_name);
}
/* check_overlap - disallow UID or GID sharing */
@@ -729,6 +736,7 @@ void mail_params_init()
VAR_MAILLOG_FILE_PFXS, DEF_MAILLOG_FILE_PFXS, &var_maillog_file_pfxs, 1, 0,
VAR_MAILLOG_FILE_COMP, DEF_MAILLOG_FILE_COMP, &var_maillog_file_comp, 1, 0,
VAR_MAILLOG_FILE_STAMP, DEF_MAILLOG_FILE_STAMP, &var_maillog_file_stamp, 1, 0,
+ VAR_MAILLOG_FILE_PERMS, DEF_MAILLOG_FILE_PERMS, &var_maillog_file_perms, 1, 0,
VAR_POSTLOG_SERVICE, DEF_POSTLOG_SERVICE, &var_postlog_service, 1, 0,
VAR_DNSSEC_PROBE, DEF_DNSSEC_PROBE, &var_dnssec_probe, 0, 0,
VAR_KNOWN_TCP_PORTS, DEF_KNOWN_TCP_PORTS, &var_known_tcp_ports, 0, 0,
@@ -979,6 +987,9 @@ void mail_params_init()
dict_db_cache_size = var_db_read_buf;
dict_lmdb_map_size = var_lmdb_map_size;
inet_windowsize = var_inet_windowsize;
+ if (set_logwriter_create_perms(var_maillog_file_perms) < 0)
+ msg_warn("ignoring bad permissions: %s = %s",
+ VAR_MAILLOG_FILE_PERMS, var_maillog_file_perms);
/*
* Variables whose defaults are determined at runtime, after other
diff --git a/src/global/mail_params.h b/src/global/mail_params.h
index 3064b01..1f03b0b 100644
--- a/src/global/mail_params.h
+++ b/src/global/mail_params.h
@@ -1321,6 +1321,10 @@ extern bool var_smtpd_tls_ask_ccert;
#define DEF_SMTPD_TLS_RCERT 0
extern bool var_smtpd_tls_req_ccert;
+#define VAR_SMTPD_TLS_ENABLE_RPK "smtpd_tls_enable_rpk"
+#define DEF_SMTPD_TLS_ENABLE_RPK 0
+extern bool var_smtpd_tls_enable_rpk;
+
#define VAR_SMTPD_TLS_CCERT_VD "smtpd_tls_ccert_verifydepth"
#define DEF_SMTPD_TLS_CCERT_VD 9
extern int var_smtpd_tls_ccert_vd;
@@ -1555,6 +1559,12 @@ extern char *var_smtp_tls_mand_excl;
"{md5} : {sha256}}"
extern char *var_smtp_tls_fpt_dgst;
+#define VAR_SMTP_TLS_ENABLE_RPK "smtp_tls_enable_rpk"
+#define DEF_SMTP_TLS_ENABLE_RPK 0
+#define VAR_LMTP_TLS_ENABLE_RPK "lmtp_tls_enable_rpk"
+#define DEF_LMTP_TLS_ENABLE_RPK 0
+extern bool var_smtp_tls_enable_rpk;
+
#define VAR_SMTP_TLS_TAFILE "smtp_tls_trust_anchor_file"
#define DEF_SMTP_TLS_TAFILE ""
#define VAR_LMTP_TLS_TAFILE "lmtp_tls_trust_anchor_file"
@@ -1745,6 +1755,12 @@ extern bool var_smtp_sasl_enable;
#define DEF_SMTP_SASL_PASSWD ""
extern char *var_smtp_sasl_passwd;
+#define VAR_SMTP_SASL_PASSWD_RES_DELIM "smtp_sasl_password_result_delimiter"
+#define DEF_SMTP_SASL_PASSWD_RES_DELIM ":"
+#define VAR_LMTP_SASL_PASSWD_RES_DELIM "lmtp_sasl_password_result_delimiter"
+#define DEF_LMTP_SASL_PASSWD_RES_DELIM DEF_SMTP_SASL_PASSWD_RES_DELIM
+extern char *var_smtp_sasl_passwd_res_delim;
+
#define VAR_SMTP_SASL_OPTS "smtp_sasl_security_options"
#define DEF_SMTP_SASL_OPTS "noplaintext, noanonymous"
extern char *var_smtp_sasl_opts;
@@ -2437,7 +2453,7 @@ extern char *var_smtpd_exp_filter;
extern bool var_smtpd_peername_lookup;
#define VAR_SMTPD_FORBID_UNAUTH_PIPE "smtpd_forbid_unauth_pipelining"
-#define DEF_SMTPD_FORBID_UNAUTH_PIPE 0
+#define DEF_SMTPD_FORBID_UNAUTH_PIPE 1
extern bool var_smtpd_forbid_unauth_pipe;
/*
@@ -3072,6 +3088,10 @@ extern bool var_disable_mime_input;
#define DEF_DISABLE_MIME_OCONV 0
extern bool var_disable_mime_oconv;
+#define VAR_FORCE_MIME_ICONV "force_mime_input_conversion"
+#define DEF_FORCE_MIME_ICONV 0
+extern bool var_force_mime_iconv;
+
#define VAR_STRICT_8BITMIME "strict_8bitmime"
#define DEF_STRICT_8BITMIME 0
extern bool var_strict_8bitmime;
@@ -3982,6 +4002,10 @@ extern bool var_tlsp_tls_ask_ccert;
#define DEF_TLSP_TLS_RCERT "$" VAR_SMTPD_TLS_RCERT
extern bool var_tlsp_tls_req_ccert;
+#define VAR_TLSP_TLS_ENABLE_RPK "tlsproxy_tls_enable_rpk"
+#define DEF_TLSP_TLS_ENABLE_RPK "$" VAR_SMTPD_TLS_ENABLE_RPK
+extern bool var_tlsp_tls_enable_rpk;
+
#define VAR_TLSP_TLS_CCERT_VD "tlsproxy_tls_ccert_verifydepth"
#define DEF_TLSP_TLS_CCERT_VD "$" VAR_SMTPD_TLS_CCERT_VD
extern int var_tlsp_tls_ccert_vd;
@@ -4282,7 +4306,7 @@ extern char *var_smtpd_dns_re_filter;
* Backwards compatibility.
*/
#define VAR_SMTPD_FORBID_BARE_LF "smtpd_forbid_bare_newline"
-#define DEF_SMTPD_FORBID_BARE_LF "no"
+#define DEF_SMTPD_FORBID_BARE_LF "normalize"
#define VAR_SMTPD_FORBID_BARE_LF_EXCL "smtpd_forbid_bare_newline_exclusions"
#define DEF_SMTPD_FORBID_BARE_LF_EXCL "$" VAR_MYNETWORKS
@@ -4379,6 +4403,10 @@ extern char *var_maillog_file_comp;
#define DEF_MAILLOG_FILE_STAMP "%Y%m%d-%H%M%S"
extern char *var_maillog_file_stamp;
+#define VAR_MAILLOG_FILE_PERMS "maillog_file_permissions"
+#define DEF_MAILLOG_FILE_PERMS "0600"
+extern char *var_maillog_file_perms;
+
#define VAR_POSTLOG_SERVICE "postlog_service_name"
#define DEF_POSTLOG_SERVICE MAIL_SERVICE_POSTLOG
extern char *var_postlog_service;
diff --git a/src/global/mail_proto.h b/src/global/mail_proto.h
index 315a2e1..bea0886 100644
--- a/src/global/mail_proto.h
+++ b/src/global/mail_proto.h
@@ -63,6 +63,13 @@
#define MAIL_SERVICE_POSTLOG "postlog"
/*
+ * Process names: convention is to use the basename of an executable file,
+ * but there is nothing to enforce that.
+ */
+#define MAIL_PROC_NAME_SMTP "smtp"
+#define MAIL_PROC_NAME_LMTP "lmtp"
+
+ /*
* Mail source classes. Used to specify policy decisions for content
* inspection and SMTPUTF8 detection.
*/
diff --git a/src/global/mail_version.h b/src/global/mail_version.h
index 9eda667..9e08896 100644
--- a/src/global/mail_version.h
+++ b/src/global/mail_version.h
@@ -20,8 +20,8 @@
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20240304"
-#define MAIL_VERSION_NUMBER "3.8.6"
+#define MAIL_RELEASE_DATE "20240306"
+#define MAIL_VERSION_NUMBER "3.9"
#ifdef SNAPSHOT
#define MAIL_VERSION_DATE "-" MAIL_RELEASE_DATE
diff --git a/src/global/maillog_client.c b/src/global/maillog_client.c
index 7f79a1f..34952ef 100644
--- a/src/global/maillog_client.c
+++ b/src/global/maillog_client.c
@@ -58,7 +58,7 @@
/* unitialized and the process environment does not specify
/* POSTLOG_SERVICE, the program will log to the syslog service
/* instead.
-/* .IP "myhostname (default: see postconf -d output)"
+/* .IP "myhostname (default: see 'postconf -d' output)"
/* The internet hostname of this mail system.
/* .IP "postlog_service_name (postlog)"
/* The name of the internal postlog logging service.
diff --git a/src/global/maps.c b/src/global/maps.c
index 790396b..d237002 100644
--- a/src/global/maps.c
+++ b/src/global/maps.c
@@ -195,8 +195,12 @@ const char *maps_find(MAPS *maps, const char *name, int flags)
for (map_name = maps->argv->argv; *map_name; map_name++) {
if ((dict = dict_handle(*map_name)) == 0)
msg_panic("%s: dictionary not found: %s", myname, *map_name);
- if (flags != 0 && (dict->flags & flags) == 0)
+ if (flags != 0 && (dict->flags & flags) == 0) {
+ if (msg_verbose)
+ msg_info("%s: %s: skipping %s lookup for %s",
+ myname, maps->title, *map_name, name);
continue;
+ }
if ((expansion = dict_get(dict, name)) != 0) {
if (*expansion == 0) {
msg_warn("%s lookup of %s returns an empty string result",
@@ -252,8 +256,12 @@ const char *maps_file_find(MAPS *maps, const char *name, int flags)
if ((dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) == 0)
msg_panic("%s: %s: opened without DICT_FLAG_SRC_RHS_IS_FILE",
myname, maps->title);
- if (flags != 0 && (dict->flags & flags) == 0)
+ if (flags != 0 && (dict->flags & flags) == 0) {
+ if (msg_verbose)
+ msg_info("%s: %s: skipping %s lookup for %s",
+ myname, maps->title, *map_name, name);
continue;
+ }
if ((expansion = dict_get(dict, name)) != 0) {
if (*expansion == 0) {
msg_warn("%s lookup of %s returns an empty string result",
diff --git a/src/global/wildcard_inet_addr.c b/src/global/wildcard_inet_addr.c
index 97f6c46..0a3c37a 100644
--- a/src/global/wildcard_inet_addr.c
+++ b/src/global/wildcard_inet_addr.c
@@ -11,7 +11,7 @@
/* wildcard_inet_addr() determines all wild-card addresses
/* for all supported address families.
/* DIAGNOSTICS
-/* Fatal errors: out of memory.
+/* Fatal errors: out of memory; no wildcard addresses.
/* SEE ALSO
/* inet_addr_list(3) address list management
/* LICENSE
diff --git a/src/local/command.c b/src/local/command.c
index 4781daf..368307d 100644
--- a/src/local/command.c
+++ b/src/local/command.c
@@ -17,7 +17,8 @@
/* Duplicate commands for the same recipient are suppressed.
/* A limited amount of information is exported via the environment:
/* HOME, SHELL, LOGNAME, USER, EXTENSION, DOMAIN, RECIPIENT (entire
-/* address) LOCAL (just the local part) and SENDER. The exported
+/* address) LOCAL (just the local part), SENDER, and ENVID
+/* (see RFC 3461). The exported
/* information is censored with var_cmd_filter.
/*
/* Arguments:
@@ -169,6 +170,8 @@ int deliver_command(LOCAL_STATE state, USER_ATTR usr_attr, const char *comma
if (state.msg_attr.rcpt.orig_addr && state.msg_attr.rcpt.orig_addr[0])
argv_add(env, "ORIGINAL_RECIPIENT", state.msg_attr.rcpt.orig_addr,
ARGV_END);
+ if (state.request->dsn_envid[0])
+ argv_add(env, "ENVID", state.request->dsn_envid, ARGV_END);
#define EXPORT_REQUEST(name, value) \
if ((value)[0]) argv_add(env, (name), (value), ARGV_END);
diff --git a/src/local/local.c b/src/local/local.c
index 32bdea7..10b8082 100644
--- a/src/local/local.c
+++ b/src/local/local.c
@@ -207,27 +207,30 @@
/* is specified with the \fBcommand_expansion_filter\fR configuration
/* parameter.
/* .IP \fBSHELL\fR
-/* The recipient user's login shell.
+/* The envelope recipient user's login shell.
/* .IP \fBHOME\fR
-/* The recipient user's home directory.
+/* The envelope recipient user's home directory.
/* .IP \fBUSER\fR
-/* The bare recipient name.
+/* The bare envelope recipient name.
/* .IP \fBEXTENSION\fR
-/* The optional recipient address extension.
+/* The optional envelope recipient address extension.
/* .IP \fBDOMAIN\fR
-/* The recipient address domain part.
+/* The envelope recipient address domain part.
/* .IP \fBLOGNAME\fR
-/* The bare recipient name.
+/* The bare envelope recipient name.
/* .IP \fBLOCAL\fR
-/* The entire recipient address localpart (text to the left of the
-/* rightmost @ character).
+/* The entire envelope recipient address localpart (text to
+/* the left of the rightmost @ character).
/* .IP \fBORIGINAL_RECIPIENT\fR
-/* The entire recipient address, before any address rewriting
-/* or aliasing (Postfix 2.5 and later).
+/* The entire envelope recipient address, before any address
+/* rewriting or aliasing (Postfix 2.5 and later).
/* .IP \fBRECIPIENT\fR
-/* The entire recipient address.
+/* The entire envelope recipient address.
/* .IP \fBSENDER\fR
-/* The entire sender address.
+/* The entire envelope sender address.
+/* .IP \fBENVID\fR
+/* The optional RFC 3461 envelope ID. Available as of Postfix
+/* 3.9.
/* .PP
/* Additional remote client information is made available via
/* the following environment variables:
@@ -413,7 +416,9 @@
/* home_mailbox, mail_spool_directory, fallback_transport_maps,
/* fallback_transport, and luser_relay.
/* .IP "\fBalias_maps (see 'postconf -d' output)\fR"
-/* The alias databases that are used for \fBlocal\fR(8) delivery.
+/* Optional lookup tables with aliases that apply only to \fBlocal\fR(8)
+/* recipients; this is unlike virtual_alias_maps that apply to all
+/* recipients: \fBlocal\fR(8), virtual, and remote.
/* .IP "\fBforward_path (see 'postconf -d' output)\fR"
/* The \fBlocal\fR(8) delivery agent search list for finding a .forward
/* file with user-specified delivery methods.
diff --git a/src/master/master.c b/src/master/master.c
index 1fc3fe9..b6afe3f 100644
--- a/src/master/master.c
+++ b/src/master/master.c
@@ -135,13 +135,13 @@
/* The external command to execute when a Postfix daemon program is
/* invoked with the -D option.
/* .IP "\fBinet_interfaces (all)\fR"
-/* The network interface addresses that this mail system receives
-/* mail on.
-/* .IP "\fBinet_protocols (see 'postconf -d output')\fR"
+/* The local network interface addresses that this mail system
+/* receives mail on.
+/* .IP "\fBinet_protocols (see 'postconf -d' output)\fR"
/* The Internet protocols Postfix will attempt to use when making
/* or accepting connections.
/* .IP "\fBimport_environment (see 'postconf -d' output)\fR"
-/* The list of environment parameters that a privileged Postfix
+/* The list of environment variables that a privileged Postfix
/* process will import from a non-Postfix parent process, or name=value
/* environment overrides.
/* .IP "\fBmail_owner (postfix)\fR"
@@ -495,7 +495,7 @@ int main(int argc, char **argv)
vstring_sprintf(lock_path, "%s/%s.pid", DEF_PID_DIR, var_procname);
if (test_lock && access(vstring_str(lock_path), F_OK) < 0)
exit(0);
- lock_fp = open_lock(vstring_str(lock_path), O_RDWR | O_CREAT, 0644, why);
+ lock_fp = open_lock(vstring_str(lock_path), O_RDWR | O_CREAT, 0600, why);
if (test_lock)
exit(lock_fp ? 0 : 1);
if (lock_fp == 0)
@@ -513,7 +513,7 @@ int main(int argc, char **argv)
vstring_sprintf(data_lock_path, "%s/%s.lock", var_data_dir, var_procname);
set_eugid(var_owner_uid, var_owner_gid);
data_lock_fp =
- open_lock(vstring_str(data_lock_path), O_RDWR | O_CREAT, 0644, why);
+ open_lock(vstring_str(data_lock_path), O_RDWR | O_CREAT, 0600, why);
set_ugid(getuid(), getgid());
if (data_lock_fp == 0)
msg_fatal("open lock file %s: %s",
diff --git a/src/master/master_ent.c b/src/master/master_ent.c
index 5edc308..98f8404 100644
--- a/src/master/master_ent.c
+++ b/src/master/master_ent.c
@@ -369,8 +369,12 @@ MASTER_SERV *get_master_ent()
} else {
MASTER_INET_ADDRLIST(serv) =
strcasecmp(saved_interfaces, INET_INTERFACES_ALL) ?
- own_inet_addr_list() : /* virtual */
- wildcard_inet_addr_list(); /* wild-card */
+ own_inet_addr_list() : /* result can be empty */
+ wildcard_inet_addr_list(); /* result can't be empty */
+ if (MASTER_INET_ADDRLIST(serv)->used == 0)
+ fatal_with_context("service definition requires valid"
+ " host name or address, or non-empty"
+ " %s setting", VAR_INET_INTERFACES);
inet_addr_list_uniq(MASTER_INET_ADDRLIST(serv));
serv->listen_fd_count = MASTER_INET_ADDRLIST(serv)->used;
}
diff --git a/src/oqmgr/qmgr_deliver.c b/src/oqmgr/qmgr_deliver.c
index 03e0340..6c09350 100644
--- a/src/oqmgr/qmgr_deliver.c
+++ b/src/oqmgr/qmgr_deliver.c
@@ -155,7 +155,7 @@ static int qmgr_deliver_send_request(QMGR_ENTRY *entry, VSTREAM *stream)
*/
for (recipient = list.info; recipient < list.info + list.len; recipient++)
if (var_smtputf8_enable && (addr = recipient->address)[0]
- && !allascii(addr) && valid_utf8_string(addr, strlen(addr))) {
+ && !allascii(addr) && valid_utf8_stringz(addr)) {
smtputf8 |= SMTPUTF8_FLAG_RECIPIENT;
if (message->verp_delims)
smtputf8 |= SMTPUTF8_FLAG_SENDER;
@@ -334,7 +334,7 @@ static void qmgr_deliver_update(int unused_event, void *context)
#define SUSPENDED "delivery temporarily suspended: "
if (status == DELIVER_STAT_CRASH)
- DSN_SIMPLE(&dsb->dsn, "4.3.0", "unknown mail transport error");
+ (void) DSN_SIMPLE(&dsb->dsn, "4.3.0", "unknown mail transport error");
if (status == DELIVER_STAT_CRASH || status == DELIVER_STAT_DEFER) {
message->flags |= DELIVER_STAT_DEFER;
if (VSTRING_LEN(dsb->status)) {
diff --git a/src/pipe/pipe.c b/src/pipe/pipe.c
index 8a99430..3017937 100644
--- a/src/pipe/pipe.c
+++ b/src/pipe/pipe.c
@@ -230,6 +230,11 @@
/* This information is modified by the \fBh\fR flag for case folding.
/* .sp
/* This feature is available as of Postfix 2.5.
+/* .IP \fB${envid}\fR
+/* This macro expands to the RFC 3461 envelope ID if available,
+/* otherwise the empty string.
+/* .sp
+/* This feature is available as of Postfix 3.9.
/* .IP \fB${extension}\fR
/* This macro expands to the extension part of a recipient address.
/* For example, with an address \fIuser+foo@domain\fR the extension is
@@ -544,6 +549,7 @@
#define PIPE_DICT_SASL_USERNAME "sasl_username" /* key */
#define PIPE_DICT_SASL_SENDER "sasl_sender" /* key */
#define PIPE_DICT_QUEUE_ID "queue_id" /* key */
+#define PIPE_DICT_ENVID "envid" /* key */
/*
* Flags used to pass back the type of special parameter found by
@@ -649,6 +655,7 @@ static int parse_callback(int type, VSTRING *buf, void *context)
PIPE_DICT_SASL_USERNAME, 0,
PIPE_DICT_SASL_SENDER, 0,
PIPE_DICT_QUEUE_ID, 0,
+ PIPE_DICT_ENVID, 0,
0, 0,
};
struct cmd_flags *p;
@@ -1278,6 +1285,8 @@ static int deliver_message(DELIVER_REQUEST *request, char *service, char **argv)
request->sasl_sender);
dict_update(PIPE_DICT_TABLE, PIPE_DICT_QUEUE_ID,
request->queue_id);
+ dict_update(PIPE_DICT_TABLE, PIPE_DICT_ENVID,
+ request->dsn_envid);
vstring_free(buf);
if ((expanded_argv = expand_argv(service, attr.command,
diff --git a/src/postalias/postalias.c b/src/postalias/postalias.c
index d17e397..72ca729 100644
--- a/src/postalias/postalias.c
+++ b/src/postalias/postalias.c
@@ -393,7 +393,7 @@ static void postalias(char *map_type, char *path_name, int postalias_flags,
*/
if ((mkmap->dict->flags & DICT_FLAG_UTF8_ACTIVE)
&& !allascii(STR(line_buffer))
- && !valid_utf8_string(STR(line_buffer), LEN(line_buffer))) {
+ && !valid_utf8_stringz(STR(line_buffer))) {
msg_warn("%s, line %d: non-UTF-8 input \"%s\""
" -- ignoring this line",
VSTREAM_PATH(source_fp), lineno, STR(line_buffer));
diff --git a/src/postcat/postcat.c b/src/postcat/postcat.c
index 36f2740..26b46a9 100644
--- a/src/postcat/postcat.c
+++ b/src/postcat/postcat.c
@@ -76,7 +76,7 @@
/* The default location of the Postfix main.cf and master.cf
/* configuration files.
/* .IP "\fBimport_environment (see 'postconf -d' output)\fR"
-/* The list of environment parameters that a privileged Postfix
+/* The list of environment variables that a privileged Postfix
/* process will import from a non-Postfix parent process, or name=value
/* environment overrides.
/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
@@ -274,7 +274,7 @@ static void postcat(VSTREAM *fp, VSTRING *buffer, int flags)
break;
/* Optimization: skip to extracted segment marker. */
if (do_print == 0 && (flags & PC_FLAG_PRINT_ENV)
- && data_offset >= 0 && data_size >= 0
+ && data_offset > 0 && data_size >= 0
&& vstream_fseek(fp, data_offset + data_size, SEEK_SET) < 0)
msg_fatal("seek error: %m");
}
@@ -289,7 +289,7 @@ static void postcat(VSTREAM *fp, VSTRING *buffer, int flags)
PRINT_MARKER(flags, fp, offset, rec_type, "MESSAGE CONTENTS");
/* Optimization: skip to extracted segment marker. */
if ((flags & PC_MASK_PRINT_TEXT) == 0
- && data_offset >= 0 && data_size >= 0
+ && data_offset > 0 && data_size >= 0
&& vstream_fseek(fp, data_offset + data_size, SEEK_SET) < 0)
msg_fatal("seek error: %m");
/* Update the state machine, even when skipping. */
diff --git a/src/postconf/Makefile.in b/src/postconf/Makefile.in
index 6aff794..6df6dfa 100644
--- a/src/postconf/Makefile.in
+++ b/src/postconf/Makefile.in
@@ -17,7 +17,7 @@ MAKES = bool_table.h bool_vars.h int_table.h int_vars.h str_table.h \
nint_table.h nint_vars.h nbool_table.h nbool_vars.h long_table.h \
long_vars.h str_fn_table.h str_fn_vars.h
DB_MAKES= pcf_ldap_suffixes.h pcf_memcache_suffixes.h pcf_mysql_suffixes.h \
- pcf_pgsql_suffixes.h pcf_sqlite_suffixes.h
+ pcf_pgsql_suffixes.h pcf_sqlite_suffixes.h pcf_mongodb_suffixes.h
TEST_TMP= main.cf master.cf test*.tmp
DUMMIES = makes_dummy # for "make -j"
PROG = postconf
@@ -55,7 +55,8 @@ tests: test1 test2 test3 test4 test5 test6 test7 test8 test9 test10 test11 \
test31 test32 test33 test34 test35 test36 test37 test39 test40 test41 \
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
+ test62 test63 test64 test65 test66 test67 test68 test69 test70 test71 \
+ test72 test73 test74 test75 test76
root_tests:
@@ -78,6 +79,9 @@ pcf_ldap_suffixes.h: ../global/dict_ldap.c
pcf_memcache_suffixes.h: ../global/dict_memcache.c
sh extract_cfg.sh -d ../global/dict_memcache.c > $@
+pcf_mongodb_suffixes.h: ../global/dict_mongodb.c
+ sh extract_cfg.sh -d ../global/dict_mongodb.c > $@
+
pcf_mysql_suffixes.h: ../global/dict_mysql.c
sh extract_cfg.sh -d -s ../global/dict_mysql.c > $@
@@ -465,6 +469,9 @@ test29: $(PROG) test29.ref
echo 'memcachexx = proxy:memcache:memcachefoo' >> main.cf
echo 'memcachefoo_domain = bar' >> main.cf
echo 'memcachefoo_domainx = bar' >> main.cf
+ echo 'mongodbxx = proxy:mongodb:mongodbfoo' >> main.cf
+ echo 'mongodbfoo_domain = bar' >> main.cf
+ echo 'mongodbfoo_domainx = bar' >> main.cf
touch -t 197101010000 main.cf
$(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test29.tmp 2>&1
diff test29.ref test29.tmp
@@ -807,7 +814,7 @@ test58: $(PROG) test58.ref
echo 'yy_backup = bbb' >> main.cf
echo 'yy_bogus = bbb' >> main.cf
touch -t 197101010000 main.cf
- $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./postconf -nc. >test58.tmp 2>&1 || true
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc. >test58.tmp 2>&1 || true
diff test58.ref test58.tmp
rm -f main.cf master.cf test58.tmp
@@ -989,6 +996,75 @@ test71: $(PROG) test71.ref
diff test71.ref test71.tmp
rm -f main.cf master.cf test71.tmp
+# Different requests to add lines to master.cf.
+test72: $(PROG) test72.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mc. smtp/unix='smtp unix - n n - 0 other'
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mc. smtp/abcd='smtp fifo - n n - 0 other'
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mc. smtp/abcd='smtp inet - n n - 0 other'
+ touch -t 197201010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mc. >test72.tmp 2>&1
+ diff test72.ref test72.tmp
+ rm -f main.cf master.cf test72.tmp
+
+# Replace one entry based on the name+type in the request's service entry.
+test73: $(PROG) test73.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mc. smtp/unix='smtp unix - n n - 0 other'
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mc. smtp/abcd='smtp fifo - n n - 0 other'
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mc. smtp/abcd='smtp inet - n n - 0 other'
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mc. smtp/abcd='smtp unix - n n - 0 otherx'
+ touch -t 197301010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mc. >test73.tmp 2>&1
+ diff test73.ref test73.tmp
+ rm -f main.cf master.cf test73.tmp
+
+# Replace one entry based on the name+type in the request's service pattern.
+test74: $(PROG) test74.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mc. smtp/unix='smtp unix - n n - 0 other'
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mc. smtp/abcd='smtp fifo - n n - 0 other'
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mc. smtp/abcd='smtp inet - n n - 0 other'
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mc. smtp/fifo='lmtp unix - n n - 0 otherx'
+ touch -t 197401010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mc. >test74.tmp 2>&1
+ diff test74.ref test74.tmp
+ rm -f main.cf master.cf test74.tmp
+
+# Warn about skipping redundant name=value update.
+test75: $(PROG) test75.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c. mail_version=x mail_version=y >test75.tmp 2>&1
+ touch -t 197501010000 main.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc. >>test75.tmp 2>&1
+ diff test75.ref test75.tmp
+ rm -f main.cf master.cf test75.tmp
+
+# Warn about unused, deprecated, or deleted parameters.
+test76: $(PROG) test76.ref
+ rm -f main.cf master.cf
+ touch main.cf master.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c. \
+ config_directory=. \
+ deleted-test-only=whatever \
+ disable_dns_lookups=no \
+ lmtp_use_tls=no \
+ smtpd_tls_dh1024_param_file=auto >test76.tmp 2>&1
+ touch -t 197601010000 main.cf
+ echo foo unix - n n - 0 other >> master.cf
+ echo ' -o alias_maps=foo' >> master.cf
+ echo ' -o smtp_enforce_tls=yes' >> master.cf
+ touch -t 197601010000 master.cf
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc. >>test76.tmp 2>&1
+ diff test76.ref test76.tmp
+ $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -qnc. >/dev/null 2>test76.tmp
+ diff /dev/null test76.tmp
+ rm -f main.cf master.cf test76.tmp
+
printfck: $(OBJS) $(PROG)
rm -rf printfck
mkdir printfck
@@ -1094,6 +1170,7 @@ postconf_dbms.o: ../../include/dict.h
postconf_dbms.o: ../../include/dict_ht.h
postconf_dbms.o: ../../include/dict_ldap.h
postconf_dbms.o: ../../include/dict_memcache.h
+postconf_dbms.o: ../../include/dict_mongodb.h
postconf_dbms.o: ../../include/dict_mysql.h
postconf_dbms.o: ../../include/dict_pcre.h
postconf_dbms.o: ../../include/dict_pgsql.h
@@ -1118,6 +1195,7 @@ postconf_dbms.o: ../../include/vstream.h
postconf_dbms.o: ../../include/vstring.h
postconf_dbms.o: pcf_ldap_suffixes.h
postconf_dbms.o: pcf_memcache_suffixes.h
+postconf_dbms.o: pcf_mongodb_suffixes.h
postconf_dbms.o: pcf_mysql_suffixes.h
postconf_dbms.o: pcf_pgsql_suffixes.h
postconf_dbms.o: pcf_sqlite_suffixes.h
@@ -1184,6 +1262,7 @@ postconf_main.o: postconf_main.c
postconf_master.o: ../../include/argv.h
postconf_master.o: ../../include/check_arg.h
postconf_master.o: ../../include/dict.h
+postconf_master.o: ../../include/dict_ht.h
postconf_master.o: ../../include/htable.h
postconf_master.o: ../../include/mail_params.h
postconf_master.o: ../../include/master_proto.h
diff --git a/src/postconf/postconf.c b/src/postconf/postconf.c
index f598a5b..74f13b2 100644
--- a/src/postconf/postconf.c
+++ b/src/postconf/postconf.c
@@ -8,7 +8,7 @@
/* .ti -4
/* \fBManaging main.cf:\fR
/*
-/* \fBpostconf\fR [\fB-dfhHnopvx\fR] [\fB-c \fIconfig_dir\fR]
+/* \fBpostconf\fR [\fB-dfhHnopqvx\fR] [\fB-c \fIconfig_dir\fR]
/* [\fB-C \fIclass,...\fR] [\fIparameter ...\fR]
/*
/* \fBpostconf\fR [\fB-epv\fR] [\fB-c \fIconfig_dir\fR]
@@ -23,7 +23,7 @@
/* .ti -4
/* \fBManaging master.cf service entries:\fR
/*
-/* \fBpostconf\fR \fB-M\fR [\fB-fovx\fR] [\fB-c \fIconfig_dir\fR]
+/* \fBpostconf\fR \fB-M\fR [\fB-foqvx\fR] [\fB-c \fIconfig_dir\fR]
/* [\fIservice\fR[\fB/\fItype\fR]\fI ...\fR]
/*
/* \fBpostconf\fR \fB-M\fR [\fB-ev\fR] [\fB-c \fIconfig_dir\fR]
@@ -38,7 +38,7 @@
/* .ti -4
/* \fBManaging master.cf service fields:\fR
/*
-/* \fBpostconf\fR \fB-F\fR [\fB-fhHovx\fR] [\fB-c \fIconfig_dir\fR]
+/* \fBpostconf\fR \fB-F\fR [\fB-fhHoqvx\fR] [\fB-c \fIconfig_dir\fR]
/* [\fIservice\fR[\fB/\fItype\fR[\fB/\fIfield\fR]]\fI ...\fR]
/*
/* \fBpostconf\fR \fB-F\fR [\fB-ev\fR] [\fB-c \fIconfig_dir\fR]
@@ -47,7 +47,7 @@
/* .ti -4
/* \fBManaging master.cf service parameters:\fR
/*
-/* \fBpostconf\fR \fB-P\fR [\fB-fhHovx\fR] [\fB-c \fIconfig_dir\fR]
+/* \fBpostconf\fR \fB-P\fR [\fB-fhHoqvx\fR] [\fB-c \fIconfig_dir\fR]
/* [\fIservice\fR[\fB/\fItype\fR[\fB/\fIparameter\fR]]\fI ...\fR]
/*
/* \fBpostconf\fR \fB-P\fR [\fB-ev\fR] [\fB-c \fIconfig_dir\fR]
@@ -293,6 +293,11 @@
/* \fBmemcache_table\fR(5).
/*
/* This feature is available with Postfix 2.9 and later.
+/* .IP "\fBmongodb\fR"
+/* MongoDB database client. This is described in
+/* \fBmongodb_table\fR(5).
+/*
+/* This feature is available with Postfix 3.9 and later.
/* .IP "\fBmysql\fR (read-only)"
/* MySQL database client. Available on systems with support
/* for MySQL databases. This is described in \fBmysql_table\fR(5).
@@ -452,6 +457,10 @@
/* wildcard fields.
/*
/* This feature is available with Postfix 2.11 and later.
+/* .IP \fB-q\fR
+/* Do not log warnings for deprecated or unused parameters.
+/*
+/* This feature is available with Postfix 3.9 and later.
/* .IP "\fB-t\fR [\fItemplate_file\fR]"
/* Display the templates for text that appears at the beginning
/* of delivery status notification (DSN) messages, without
@@ -779,6 +788,8 @@ static void pcf_check_compat_options(int optval)
const int (*op)[2];
int excess;
+ optval &= ~PCF_DEF_MODE;
+
for (op = pcf_compat_options; op[0][0] != 0; op++) {
if ((optval & *op[0]) != 0
&& (excess = (optval & ~((*op)[0] | (*op)[1]))) != 0)
@@ -844,7 +855,7 @@ int main(int argc, char **argv)
/*
* Parse JCL.
*/
- while ((ch = GETOPT(argc, argv, "aAbc:C:deEfFhHlmMno:pPtT:vxX#")) > 0) {
+ while ((ch = GETOPT(argc, argv, "aAbc:C:deEfFhHlmMno:pPqtT:vxX#")) > 0) {
switch (ch) {
case 'a':
pcf_cmd_mode |= PCF_SHOW_SASL_SERV;
@@ -912,6 +923,9 @@ int main(int argc, char **argv)
case 'P':
pcf_cmd_mode |= PCF_MASTER_PARAM;
break;
+ case 'q':
+ pcf_cmd_mode &= ~(PCF_WARN_UNUSED_DEPRECATED);
+ break;
case 't':
pcf_cmd_mode |= PCF_DUMP_DSN_TEMPL;
if (ext_argv)
@@ -1028,7 +1042,7 @@ int main(int argc, char **argv)
pcf_set_parameters(override_params->argv);
pcf_register_builtin_parameters(basename(argv[0]), getpid());
pcf_register_service_parameters();
- pcf_register_user_parameters();
+ pcf_register_user_parameters(pcf_cmd_mode);
if (pcf_cmd_mode & PCF_MASTER_FLD)
pcf_show_master_fields(VSTREAM_OUT, pcf_cmd_mode, argc - optind,
argv + optind);
@@ -1038,7 +1052,8 @@ int main(int argc, char **argv)
else
pcf_show_master_entries(VSTREAM_OUT, pcf_cmd_mode, argc - optind,
argv + optind);
- pcf_flag_unused_master_parameters();
+ if (pcf_cmd_mode & PCF_WARN_UNUSED_DEPRECATED)
+ pcf_flag_unused_master_parameters();
}
/*
@@ -1090,7 +1105,7 @@ int main(int argc, char **argv)
pcf_read_master(PCF_WARN_ON_OPEN_ERROR);
pcf_register_service_parameters();
if ((pcf_cmd_mode & PCF_SHOW_DEFS) == 0)
- pcf_register_user_parameters();
+ pcf_register_user_parameters(pcf_cmd_mode);
/*
* Show the requested values.
@@ -1099,11 +1114,12 @@ int main(int argc, char **argv)
argv + optind);
/*
- * Flag unused parameters. This makes no sense with "postconf -d",
- * because that ignores all the user-specified parameters and
- * user-specified macro expansions in main.cf.
+ * Flag unused or deprecated parameters. This makes no sense with
+ * "postconf -d", because that ignores all the user-specified
+ * parameters and user-specified macro expansions in main.cf.
*/
- if ((pcf_cmd_mode & PCF_SHOW_DEFS) == 0) {
+ if ((pcf_cmd_mode & PCF_SHOW_DEFS) == 0
+ && (pcf_cmd_mode & PCF_WARN_UNUSED_DEPRECATED) != 0) {
pcf_flag_unused_main_parameters();
pcf_flag_unused_master_parameters();
}
diff --git a/src/postconf/postconf.h b/src/postconf/postconf.h
index 24a1ed7..b42245c 100644
--- a/src/postconf/postconf.h
+++ b/src/postconf/postconf.h
@@ -46,8 +46,9 @@
#define PCF_MASTER_PARAM (1<<19) /* manage master.cf -o name=value */
#define PCF_HIDE_VALUE (1<<20) /* hide main.cf/master.cf =value */
#define PCF_SHOW_TLS (1<<21) /* TLS support introspection */
+#define PCF_WARN_UNUSED_DEPRECATED (1<<22) /* As the name says */
-#define PCF_DEF_MODE 0
+#define PCF_DEF_MODE (PCF_WARN_UNUSED_DEPRECATED)
/*
* Structure for one "valid parameter" (built-in, service-defined or valid
@@ -274,12 +275,12 @@ typedef struct {
/*
* postconf_user.c.
*/
-extern void pcf_register_user_parameters(void);
+extern void pcf_register_user_parameters(int);
/*
* postconf_dbms.c
*/
-extern void pcf_register_dbms_parameters(const char *,
+extern void pcf_register_dbms_parameters(int, const char *,
const char *(*) (const char *, int, PCF_MASTER_ENT *),
PCF_MASTER_ENT *);
diff --git a/src/postconf/postconf_dbms.c b/src/postconf/postconf_dbms.c
index 0ed5b53..105ae85 100644
--- a/src/postconf/postconf_dbms.c
+++ b/src/postconf/postconf_dbms.c
@@ -6,8 +6,9 @@
/* SYNOPSIS
/* #include <postconf.h>
/*
-/* void pcf_register_dbms_parameters(param_value, flag_parameter,
+/* void pcf_register_dbms_parameters(mode, param_value, flag_parameter,
/* local_scope)
+/* int mode;
/* const char *param_value;
/* const char *(flag_parameter) (const char *, int, PCF_MASTER_ENT *);
/* PCF_MASTER_ENT *local_scope;
@@ -17,6 +18,9 @@
/* the database name to a database-defined suffix.
/*
/* Arguments:
+/* .IP mode
+/* If PCF_WARN_UNUSED_DEPRECATED is set, warn about unused
+/* database settings.
/* .IP param_value
/* A parameter value to be searched for "type:table" strings.
/* When a database type is found that supports legacy-style
@@ -77,6 +81,7 @@
#include <dict_pgsql.h>
#include <dict_sqlite.h>
#include <dict_memcache.h>
+#include <dict_mongodb.h>
#include <dict_regexp.h>
#include <dict_pcre.h>
@@ -131,6 +136,13 @@ static const char *pcf_memcache_suffixes[] = {
0,
};
+/* See mongodb_table(5). */
+
+static const char *pcf_mongodb_suffixes[] = {
+#include "pcf_mongodb_suffixes.h"
+ 0,
+};
+
/*
* Bundle up the database types and their suffix lists.
*/
@@ -149,6 +161,7 @@ static const PCF_DBMS_INFO pcf_dbms_info[] = {
{DICT_TYPE_PGSQL, PCF_DBMS_CLASS_CLIENT, pcf_pgsql_suffixes},
{DICT_TYPE_SQLITE, PCF_DBMS_CLASS_CLIENT, pcf_sqlite_suffixes},
{DICT_TYPE_MEMCACHE, PCF_DBMS_CLASS_CLIENT, pcf_memcache_suffixes},
+ {DICT_TYPE_MONGODB, PCF_DBMS_CLASS_CLIENT, pcf_mongodb_suffixes},
{DICT_TYPE_REGEXP, PCF_DBMS_CLASS_REGEX},
{DICT_TYPE_PCRE, PCF_DBMS_CLASS_REGEX},
{0},
@@ -163,7 +176,8 @@ static const PCF_DBMS_INFO pcf_dbms_info[] = {
/* pcf_check_dbms_client - look for unused names in client configuration */
-static void pcf_check_dbms_client(const PCF_DBMS_INFO *dp, const char *cf_file)
+static void pcf_check_dbms_client(int mode, const PCF_DBMS_INFO *dp,
+ const char *cf_file)
{
DICT *dict;
VSTREAM *fp;
@@ -217,19 +231,21 @@ static void pcf_check_dbms_client(const PCF_DBMS_INFO *dp, const char *cf_file)
* code, because a database client parameter namespace is unlike the
* parameter namespaces in main.cf or master.cf.
*/
- for (cpp = dp->db_suffixes; *cpp; cpp++)
- (void) dict_del(dict, *cpp);
- for (dir = DICT_SEQ_FUN_FIRST;
- dict->sequence(dict, dir, &name, &value) == DICT_STAT_SUCCESS;
- dir = DICT_SEQ_FUN_NEXT)
- msg_warn("%s: unused parameter: %s=%s", dict_spec, name, value);
+ if (mode & PCF_WARN_UNUSED_DEPRECATED) {
+ for (cpp = dp->db_suffixes; *cpp; cpp++)
+ (void) dict_del(dict, *cpp);
+ for (dir = DICT_SEQ_FUN_FIRST;
+ dict->sequence(dict, dir, &name, &value) == DICT_STAT_SUCCESS;
+ dir = DICT_SEQ_FUN_NEXT)
+ msg_warn("%s: unused parameter: %s=%s", dict_spec, name, value);
+ }
}
myfree(dict_spec);
}
/* pcf_register_dbms_helper - parse one possible database type:name */
-static void pcf_register_dbms_helper(char *str_value,
+static void pcf_register_dbms_helper(int mode, char *str_value,
const char *(flag_parameter) (const char *, int, PCF_MASTER_ENT *),
PCF_MASTER_ENT *local_scope,
int recurse)
@@ -258,8 +274,8 @@ static void pcf_register_dbms_helper(char *str_value,
myfree(err);
}
if (recurse)
- pcf_register_dbms_helper(db_type, flag_parameter, local_scope,
- recurse);
+ pcf_register_dbms_helper(mode, db_type, flag_parameter,
+ local_scope, recurse);
continue;
}
@@ -287,7 +303,7 @@ static void pcf_register_dbms_helper(char *str_value,
for (dp = pcf_dbms_info; dp->db_type != 0; dp++) {
if (strcmp(db_type, dp->db_type) == 0) {
if (dp->db_class == PCF_DBMS_CLASS_CLIENT)
- pcf_check_dbms_client(dp, prefix);
+ pcf_check_dbms_client(mode, dp, prefix);
break;
}
}
@@ -321,8 +337,8 @@ static void pcf_register_dbms_helper(char *str_value,
break;
}
}
- pcf_register_dbms_helper(prefix, flag_parameter, local_scope,
- next_recurse);
+ pcf_register_dbms_helper(mode, prefix, flag_parameter,
+ local_scope, next_recurse);
continue;
} else {
for (dp = pcf_dbms_info; dp->db_type != 0; dp++) {
@@ -347,7 +363,7 @@ static void pcf_register_dbms_helper(char *str_value,
/* pcf_register_dbms_parameters - look for database_type:prefix_name */
-void pcf_register_dbms_parameters(const char *param_value,
+void pcf_register_dbms_parameters(int mode, const char *param_value,
const char *(flag_parameter) (const char *, int, PCF_MASTER_ENT *),
PCF_MASTER_ENT *local_scope)
{
@@ -363,7 +379,8 @@ void pcf_register_dbms_parameters(const char *param_value,
buffer = vstring_alloc(100);
bufp = pcf_expand_parameter_value(buffer, PCF_SHOW_EVAL, param_value,
local_scope);
- pcf_register_dbms_helper(bufp, flag_parameter, local_scope, PCF_DBMS_RECURSE);
+ pcf_register_dbms_helper(mode, bufp, flag_parameter, local_scope,
+ PCF_DBMS_RECURSE);
}
#endif
diff --git a/src/postconf/postconf_unused.c b/src/postconf/postconf_unused.c
index d4416f8..717d1a5 100644
--- a/src/postconf/postconf_unused.c
+++ b/src/postconf/postconf_unused.c
@@ -2,7 +2,7 @@
/* NAME
/* postconf_unused 3
/* SUMMARY
-/* report unused parameters
+/* report unused or deprecated parameters
/* SYNOPSIS
/* #include <postconf.h>
/*
@@ -15,11 +15,11 @@
/* In other words, don't call these functions with "postconf
/* -d" which ignores user-defined main.cf settings.
/*
-/* pcf_flag_unused_main_parameters() reports unused "name=value"
-/* entries in main.cf.
+/* pcf_flag_unused_main_parameters() reports unused or deprecated
+/* "name=value" entries in main.cf.
/*
-/* pcf_flag_unused_master_parameters() reports unused "-o
-/* name=value" entries in master.cf.
+/* pcf_flag_unused_master_parameters() reports unused or
+/* deprecated "-o name=value" entries in master.cf.
/* DIAGNOSTICS
/* Problems are reported to the standard error stream.
/* LICENSE
@@ -31,6 +31,10 @@
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* porcupine.org
+/* Amawalk, NY 10501, USA
/*--*/
/* System library. */
@@ -52,12 +56,66 @@
#include <postconf.h>
+ /*
+ * Deprecated parameter names and suggested alternatives. If we keep deleted
+ * parameter names in the table, a warning can still suggest alternatives.
+ * The downside of keeping deleted names in the table is that we may falsely
+ * warn about a user-defined parameter whose name matches that of a deleted
+ * parameter.
+ */
+typedef struct {
+ char *name;
+ char *alternative;
+} PCF_DEPR_PARAM_INFO;
+
+static const PCF_DEPR_PARAM_INFO pcf_depr_param_info[] = {
+
+ /*
+ * Parameters with deprecation warnings as of Postfix 3.9. The
+ * disable_dns_lookups parameter was documented as deprecated since
+ * Postfix 2.11 but nothing was logged.
+ */
+ "disable_dns_lookups", "specify \"smtp_dns_support_level\"",
+ "lmtp_use_tls", "specify \"lmtp_tls_security_level\"",
+ "postscreen_use_tls", "specify \"postscreen_tls_security_level\"",
+ "smtp_use_tls", "specify \"smtp_tls_security_level\"",
+ "smtpd_use_tls", "specify \"smtpd_tls_security_level\"",
+ "tlsproxy_client_use_tls", "specify \"tlsproxy_client_security_level\"",
+ "tlsproxy_use_tls", "specify \"tlsproxy_tls_security_level\"",
+ "lmtp_enforce_tls", "lmtp_tls_security_level",
+ "postscreen_enforce_tls", "specify \"postscreen_tls_security_level\"",
+ "smtp_enforce_tls", "specify \"smtp_tls_security_level\"",
+ "smtpd_enforce_tls", "specify \"smtpd_tls_security_level\"",
+ "tlsproxy_client_enforce_tls", "specify \"tlsproxy_client_security_level\"",
+ "tlsproxy_enforce_tls", "specify \"tlsproxy_tls_security_level\"",
+ "lmtp_tls_per_site", "specify \"lmtp_tls_policy_maps\"",
+ "smtp_tls_per_site", "specify \"smtp_tls_policy_maps\"",
+ "tlsproxy_client_per_site", "specify \"tlsproxy_client_policy_maps\"",
+ "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 */
+ 0,
+};
+static HTABLE *pcf_depr_param_table;
+
+/* pcf_init_depr_params - initialize lookup table */
+
+static void pcf_init_depr_params(void)
+{
+ const PCF_DEPR_PARAM_INFO *dp;
+
+ pcf_depr_param_table = htable_create(30);
+ for (dp = pcf_depr_param_info; dp->name; dp++)
+ (void) htable_enter(pcf_depr_param_table, dp->name, (void *) dp);
+}
+
/* pcf_flag_unused_parameters - warn about unused parameters */
static void pcf_flag_unused_parameters(DICT *dict, const char *conf_name,
PCF_MASTER_ENT *local_scope)
{
const char *myname = "pcf_flag_unused_parameters";
+ const PCF_DEPR_PARAM_INFO *dp;
const char *param_name;
const char *param_value;
int how;
@@ -67,23 +125,55 @@ static void pcf_flag_unused_parameters(DICT *dict, const char *conf_name,
*/
if (pcf_param_table == 0)
msg_panic("%s: global parameter table is not initialized", myname);
+ if (dict->sequence == 0)
+ msg_panic("%s: parameter dictionary %s has no iterator",
+ myname, conf_name);
+
+ /*
+ * One-time initialization.
+ */
+ if (pcf_depr_param_table == 0)
+ pcf_init_depr_params();
/*
* Iterate over all entries, and flag parameter names that aren't used
- * anywhere. Show the warning message at the end of the output.
+ * anywhere, or that are deprecated. Show the warning message(s) after
+ * the end of the stdout output.
*/
- if (dict->sequence == 0)
- msg_panic("%s: parameter dictionary %s has no iterator",
- myname, conf_name);
for (how = DICT_SEQ_FUN_FIRST;
dict->sequence(dict, how, &param_name, &param_value) == 0;
how = DICT_SEQ_FUN_NEXT) {
+
+ /*
+ * Flag a parameter that is not used (deleted name, or incorrect
+ * name).
+ */
if (PCF_PARAM_TABLE_LOCATE(pcf_param_table, param_name) == 0
&& (local_scope == 0
|| PCF_PARAM_TABLE_LOCATE(local_scope->valid_names, param_name) == 0)) {
vstream_fflush(VSTREAM_OUT);
- msg_warn("%s/%s: unused parameter: %s=%s",
- var_config_dir, conf_name, param_name, param_value);
+ if ((dp = (const PCF_DEPR_PARAM_INFO *)
+ htable_find(pcf_depr_param_table, param_name)) != 0) {
+ msg_warn("%s/%s: support for parameter %s has been removed;"
+ " instead, %s", var_config_dir, conf_name,
+ param_name, dp->alternative);
+ } else {
+ msg_warn("%s/%s: unused parameter: %s=%s",
+ var_config_dir, conf_name, param_name, param_value);
+ }
+ }
+
+ /*
+ * Flag a parameter that is used but deprecated. Note that this may
+ * falsely complain about a user-defined parameter whose name matches
+ * that of a deleted parameter.
+ */
+ else if ((dp = (const PCF_DEPR_PARAM_INFO *)
+ htable_find(pcf_depr_param_table, param_name)) != 0) {
+ vstream_fflush(VSTREAM_OUT);
+ msg_warn("%s/%s: support for parameter \"%s\" will be removed;"
+ " instead, %s", var_config_dir, conf_name,
+ param_name, dp->alternative);
}
}
}
diff --git a/src/postconf/postconf_user.c b/src/postconf/postconf_user.c
index 5942ec0..46f4d61 100644
--- a/src/postconf/postconf_user.c
+++ b/src/postconf/postconf_user.c
@@ -6,7 +6,7 @@
/* SYNOPSIS
/* #include <postconf.h>
/*
-/* void pcf_register_user_parameters()
+/* void pcf_register_user_parameters(int mode)
/* DESCRIPTION
/* Postfix has multiple parameter name spaces: the global
/* main.cf parameter name space, and the local parameter name
@@ -40,6 +40,10 @@
/* to instantiate legacy per-dbms parameters, and to examine
/* per-dbms configuration files. This is limited to the content
/* of global and local, built-in and per-service, parameters.
+/*
+/* Arguments:
+/* .IP mode
+/* Passed on to pcf_register_dbms_parameters().
/* DIAGNOSTICS
/* Problems are reported to the standard error stream.
/* LICENSE
@@ -224,7 +228,7 @@ static const char *pcf_lookup_eval(const char *dict_name, const char *name)
/* pcf_scan_user_parameter_namespace - scan parameters in name space */
-static void pcf_scan_user_parameter_namespace(const char *dict_name,
+static void pcf_scan_user_parameter_namespace(int mode, const char *dict_name,
PCF_MASTER_ENT *local_scope)
{
const char *myname = "pcf_scan_user_parameter_namespace";
@@ -308,7 +312,7 @@ static void pcf_scan_user_parameter_namespace(const char *dict_name,
*/
if (node != 0
&& (PCF_BUILTIN_PARAMETER(node) || PCF_SERVICE_PARAMETER(node)))
- pcf_register_dbms_parameters(cparam_value, pcf_flag_user_parameter,
+ pcf_register_dbms_parameters(mode, cparam_value, pcf_flag_user_parameter,
local_scope);
#endif
}
@@ -345,7 +349,7 @@ static void pcf_scan_default_parameter_values(HTABLE *valid_params,
/* pcf_register_user_parameters - add parameters with user-defined names */
-void pcf_register_user_parameters(void)
+void pcf_register_user_parameters(int mode)
{
const char *myname = "pcf_register_user_parameters";
PCF_MASTER_ENT *masterp;
@@ -403,7 +407,7 @@ void pcf_register_user_parameters(void)
*/
for (masterp = pcf_master_table; masterp->argv != 0; masterp++)
if (masterp->all_params != 0)
- pcf_scan_user_parameter_namespace(masterp->name_space, masterp);
+ pcf_scan_user_parameter_namespace(mode, masterp->name_space, masterp);
/*
* Scan parameter values that are left at their defaults in the global
@@ -418,5 +422,5 @@ void pcf_register_user_parameters(void)
/*
* Scan the explicit name=value entries in the global name space.
*/
- pcf_scan_user_parameter_namespace(CONFIG_DICT, (PCF_MASTER_ENT *) 0);
+ pcf_scan_user_parameter_namespace(mode, CONFIG_DICT, (PCF_MASTER_ENT *) 0);
}
diff --git a/src/postconf/test29.ref b/src/postconf/test29.ref
index 646890a..c3bbaec 100644
--- a/src/postconf/test29.ref
+++ b/src/postconf/test29.ref
@@ -2,15 +2,18 @@ config_directory = .
./postconf: warning: ./main.cf: unused parameter: pgsqlfoo_domain=bar
./postconf: warning: ./main.cf: unused parameter: sqlitefoo_domain=bar
./postconf: warning: ./main.cf: unused parameter: ldapxx=proxy:ldap:ldapfoo
+./postconf: warning: ./main.cf: unused parameter: mongodbfoo_domain=bar
./postconf: warning: ./main.cf: unused parameter: sqlitexx=proxy:sqlite:sqlitefoo
./postconf: warning: ./main.cf: unused parameter: mysqlfoo_domain=bar
./postconf: warning: ./main.cf: unused parameter: sqlitefoo_domainx=bar
./postconf: warning: ./main.cf: unused parameter: memcachefoo_domain=bar
./postconf: warning: ./main.cf: unused parameter: pgsqlfoo_domainx=bar
+./postconf: warning: ./main.cf: unused parameter: mongodbfoo_domainx=bar
./postconf: warning: ./main.cf: unused parameter: ldapfoo_domainx=bar
./postconf: warning: ./main.cf: unused parameter: ldapfoo_domain=bar
./postconf: warning: ./main.cf: unused parameter: memcachexx=proxy:memcache:memcachefoo
./postconf: warning: ./main.cf: unused parameter: memcachefoo_domainx=bar
./postconf: warning: ./main.cf: unused parameter: mysqlfoo_domainx=bar
./postconf: warning: ./main.cf: unused parameter: mysqlxx=proxy:mysql:mysqlfoo
+./postconf: warning: ./main.cf: unused parameter: mongodbxx=proxy:mongodb:mongodbfoo
./postconf: warning: ./main.cf: unused parameter: pgsqlxx=proxy:pgsql:pgsqlfoo
diff --git a/src/postconf/test72.ref b/src/postconf/test72.ref
new file mode 100644
index 0000000..6b13cdc
--- /dev/null
+++ b/src/postconf/test72.ref
@@ -0,0 +1,3 @@
+smtp unix - n n - 0 other
+smtp fifo - n n - 0 other
+smtp inet - n n - 0 other
diff --git a/src/postconf/test73.ref b/src/postconf/test73.ref
new file mode 100644
index 0000000..9554cc0
--- /dev/null
+++ b/src/postconf/test73.ref
@@ -0,0 +1,3 @@
+smtp unix - n n - 0 otherx
+smtp fifo - n n - 0 other
+smtp inet - n n - 0 other
diff --git a/src/postconf/test74.ref b/src/postconf/test74.ref
new file mode 100644
index 0000000..2886334
--- /dev/null
+++ b/src/postconf/test74.ref
@@ -0,0 +1,3 @@
+smtp unix - n n - 0 other
+lmtp unix - n n - 0 otherx
+smtp inet - n n - 0 other
diff --git a/src/postconf/test75.ref b/src/postconf/test75.ref
new file mode 100644
index 0000000..b8c54ab
--- /dev/null
+++ b/src/postconf/test75.ref
@@ -0,0 +1,3 @@
+./postconf: warning: ignoring earlier request: 'mail_version = x'
+config_directory = .
+mail_version = y
diff --git a/src/postconf/test76.ref b/src/postconf/test76.ref
new file mode 100644
index 0000000..3e4cd26
--- /dev/null
+++ b/src/postconf/test76.ref
@@ -0,0 +1,9 @@
+config_directory = .
+disable_dns_lookups = no
+lmtp_use_tls = no
+smtpd_tls_dh1024_param_file = auto
+./postconf: warning: ./main.cf: support for parameter "disable_dns_lookups" will be removed; instead, specify "smtp_dns_support_level"
+./postconf: warning: ./main.cf: support for parameter "lmtp_use_tls" will be removed; instead, specify "lmtp_tls_security_level"
+./postconf: warning: ./main.cf: support for parameter "smtpd_tls_dh1024_param_file" will be removed; instead, do not specify (leave at default)
+./postconf: warning: ./main.cf: support for parameter deleted-test-only has been removed; instead, do not specify
+./postconf: warning: ./master.cf: support for parameter "smtp_enforce_tls" will be removed; instead, specify "smtp_tls_security_level"
diff --git a/src/postdrop/postdrop.c b/src/postdrop/postdrop.c
index e9335e9..66c9ea5 100644
--- a/src/postdrop/postdrop.c
+++ b/src/postdrop/postdrop.c
@@ -64,7 +64,7 @@
/* The default location of the Postfix main.cf and master.cf
/* configuration files.
/* .IP "\fBimport_environment (see 'postconf -d' output)\fR"
-/* The list of environment parameters that a privileged Postfix
+/* The list of environment variables that a privileged Postfix
/* process will import from a non-Postfix parent process, or name=value
/* environment overrides.
/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
diff --git a/src/postfix/postfix.c b/src/postfix/postfix.c
index c7f8b40..93bcecc 100644
--- a/src/postfix/postfix.c
+++ b/src/postfix/postfix.c
@@ -303,7 +303,7 @@
/*
/* Table-driven mechanisms:
/* access(5), Postfix SMTP access control table
-/* aliases(5), Postfix alias database
+/* aliases(5), Postfix local aliasing
/* canonical(5), Postfix input address rewriting
/* generic(5), Postfix output address rewriting
/* header_checks(5), body_checks(5), Postfix content inspection
@@ -316,6 +316,7 @@
/* ldap_table(5), Postfix LDAP client
/* lmdb_table(5), Postfix LMDB database driver
/* memcache_table(5), Postfix memcache client
+/* mongodb_table(5), Postfix MongoDB client
/* mysql_table(5), Postfix MYSQL client
/* nisplus_table(5), Postfix NIS+ client
/* pcre_table(5), Associate PCRE pattern with value
diff --git a/src/postkick/postkick.c b/src/postkick/postkick.c
index 6bf9245..a1ce55c 100644
--- a/src/postkick/postkick.c
+++ b/src/postkick/postkick.c
@@ -54,7 +54,7 @@
/* How long the \fBpostkick\fR(1) command waits for a request to enter the
/* Postfix daemon process input buffer before giving up.
/* .IP "\fBimport_environment (see 'postconf -d' output)\fR"
-/* The list of environment parameters that a privileged Postfix
+/* The list of environment variables that a privileged Postfix
/* process will import from a non-Postfix parent process, or name=value
/* environment overrides.
/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
diff --git a/src/postlock/postlock.c b/src/postlock/postlock.c
index a05d11e..972577b 100644
--- a/src/postlock/postlock.c
+++ b/src/postlock/postlock.c
@@ -79,7 +79,7 @@
/* The default location of the Postfix main.cf and master.cf
/* configuration files.
/* .IP "\fBimport_environment (see 'postconf -d' output)\fR"
-/* The list of environment parameters that a privileged Postfix
+/* The list of environment variables that a privileged Postfix
/* process will import from a non-Postfix parent process, or name=value
/* environment overrides.
/* SEE ALSO
diff --git a/src/postlog/postlog.c b/src/postlog/postlog.c
index 7175f42..195ebd9 100644
--- a/src/postlog/postlog.c
+++ b/src/postlog/postlog.c
@@ -82,6 +82,12 @@
/* \fBpostlogd\fR(8) service.
/* .IP "\fBpostlog_service_name (postlog)\fR"
/* The name of the \fBpostlogd\fR(8) service entry in master.cf.
+/* .PP
+/* Available in Postfix 3.9 and later:
+/* .IP "\fBmaillog_file_permissions (0600)\fR"
+/* The file access permissions that will be set when the file
+/* $maillog_file is created for the first time, or when the file is
+/* created after an existing file is rotated.
/* SEE ALSO
/* postconf(5), configuration parameters
/* postlogd(8), Postfix logging
diff --git a/src/postlogd/postlogd.c b/src/postlogd/postlogd.c
index 902cbe5..13f996f 100644
--- a/src/postlogd/postlogd.c
+++ b/src/postlogd/postlogd.c
@@ -31,10 +31,10 @@
/* CONFIGURATION PARAMETERS
/* .ad
/* .fi
-/* Changes to \fBmain.cf\fR are picked up automatically, as
-/* \fBpostlogd\fR(8) processes run for only a limited amount
-/* of time. Use the command "\fBpostfix reload\fR" to speed
-/* up a change.
+/* Changes to \fBmain.cf\fR are not picked up automatically,
+/* because \fBpostlogd\fR(8) terminates only after reaching
+/* the \fBmax_idle\fR time limit.
+/* Use the command "\fBpostfix reload\fR" to speed up a change.
/*
/* The text below provides only a parameter summary. See
/* \fBpostconf\fR(5) for more details including examples.
@@ -56,6 +56,12 @@
/* .IP "\fBpostlogd_watchdog_timeout (10s)\fR"
/* How much time a \fBpostlogd\fR(8) process may take to process a request
/* before it is terminated by a built-in watchdog timer.
+/* .PP
+/* Available in Postfix 3.9 and later:
+/* .IP "\fBmaillog_file_permissions (0600)\fR"
+/* The file access permissions that will be set when the file
+/* $maillog_file is created for the first time, or when the file is
+/* created after an existing file is rotated.
/* SEE ALSO
/* postconf(5), configuration parameters
/* syslogd(8), system logging
diff --git a/src/postmap/postmap.c b/src/postmap/postmap.c
index 2826fc6..7390450 100644
--- a/src/postmap/postmap.c
+++ b/src/postmap/postmap.c
@@ -489,7 +489,7 @@ static void postmap(char *map_type, char *path_name, int postmap_flags,
*/
if ((mkmap->dict->flags & DICT_FLAG_UTF8_ACTIVE)
&& !allascii(STR(line_buffer))
- && !valid_utf8_string(STR(line_buffer), LEN(line_buffer))) {
+ && !valid_utf8_stringz(STR(line_buffer))) {
msg_warn("%s, line %d: non-UTF-8 input \"%s\""
" -- ignoring this line",
VSTREAM_PATH(source_fp), lineno, STR(line_buffer));
diff --git a/src/postqueue/showq_json.c b/src/postqueue/showq_json.c
index db79404..a2820dd 100644
--- a/src/postqueue/showq_json.c
+++ b/src/postqueue/showq_json.c
@@ -58,69 +58,6 @@
#define STR(x) vstring_str(x)
#define LEN(x) VSTRING_LEN(x)
-/* json_quote - quote JSON string */
-
-static char *json_quote(VSTRING *result, const char *text)
-{
- unsigned char *cp;
- int ch;
-
- /*
- * We use short escape sequences for common control characters. Note that
- * RFC 4627 allows "/" (0x2F) to be sent without quoting. Differences
- * with RFC 4627: we send DEL (0x7f) as \u007F; the result remains RFC
- * 4627 complaint.
- */
- VSTRING_RESET(result);
- for (cp = (unsigned char *) text; (ch = *cp) != 0; cp++) {
- if (UNEXPECTED(ISCNTRL(ch))) {
- switch (ch) {
- case '\b':
- VSTRING_ADDCH(result, '\\');
- VSTRING_ADDCH(result, 'b');
- break;
- case '\f':
- VSTRING_ADDCH(result, '\\');
- VSTRING_ADDCH(result, 'f');
- break;
- case '\n':
- VSTRING_ADDCH(result, '\\');
- VSTRING_ADDCH(result, 'n');
- break;
- case '\r':
- VSTRING_ADDCH(result, '\\');
- VSTRING_ADDCH(result, 'r');
- break;
- case '\t':
- VSTRING_ADDCH(result, '\\');
- VSTRING_ADDCH(result, 't');
- break;
- default:
- vstring_sprintf_append(result, "\\u%04X", ch);
- break;
- }
- } else {
- switch (ch) {
- case '\\':
- case '"':
- VSTRING_ADDCH(result, '\\');
- /* FALLTHROUGH */
- default:
- VSTRING_ADDCH(result, ch);
- break;
- }
- }
- }
- VSTRING_TERMINATE(result);
-
- /*
- * Force the result to be UTF-8 (with SMTPUTF8 enabled) or ASCII (with
- * SMTPUTF8 disabled).
- */
- printable(STR(result), '?');
- return (STR(result));
-}
-
/* json_message - report status for one message */
static void format_json(VSTREAM *showq_stream)
@@ -148,6 +85,12 @@ static void format_json(VSTREAM *showq_stream)
}
/*
+ * Force JSON values to UTF-8 (with SMTPUTF8 enabled) or ASCII (with
+ * SMTPUTF8 disabled).
+ */
+#define QUOTE_JSON(res, src) printable(quote_for_json((res), (src), -1), '?')
+
+ /*
* Read the message properties and sender address.
*/
if (attr_scan(showq_stream, ATTR_FLAG_MORE | ATTR_FLAG_STRICT
@@ -162,14 +105,14 @@ static void format_json(VSTREAM *showq_stream)
msg_fatal_status(EX_SOFTWARE, "malformed showq server response");
vstream_printf("{");
vstream_printf("\"queue_name\": \"%s\", ",
- json_quote(quote_buf, STR(queue_name)));
+ QUOTE_JSON(quote_buf, STR(queue_name)));
vstream_printf("\"queue_id\": \"%s\", ",
- json_quote(quote_buf, STR(queue_id)));
+ QUOTE_JSON(quote_buf, STR(queue_id)));
vstream_printf("\"arrival_time\": %ld, ", arrival_time);
vstream_printf("\"message_size\": %ld, ", message_size);
vstream_printf("\"forced_expire\": %s, ", forced_expire ? "true" : "false");
vstream_printf("\"sender\": \"%s\", ",
- json_quote(quote_buf, STR(addr)));
+ QUOTE_JSON(quote_buf, STR(addr)));
/*
* Read zero or more (recipient, reason) pair(s) until attr_scan_more()
@@ -188,10 +131,10 @@ static void format_json(VSTREAM *showq_stream)
ATTR_TYPE_END) != 2)
msg_fatal_status(EX_SOFTWARE, "malformed showq server response");
vstream_printf("\"address\": \"%s\"",
- json_quote(quote_buf, STR(addr)));
+ QUOTE_JSON(quote_buf, STR(addr)));
if (LEN(why) > 0)
vstream_printf(", \"delay_reason\": \"%s\"",
- json_quote(quote_buf, STR(why)));
+ QUOTE_JSON(quote_buf, STR(why)));
vstream_printf("}");
}
vstream_printf("]");
diff --git a/src/postscreen/postscreen.c b/src/postscreen/postscreen.c
index 192c2e9..ebb680c 100644
--- a/src/postscreen/postscreen.c
+++ b/src/postscreen/postscreen.c
@@ -291,25 +291,29 @@
/* The amount of time that \fBpostscreen\fR(8) will cache an expired
/* temporary allowlist entry before it is removed.
/* .IP "\fBpostscreen_bare_newline_ttl (30d)\fR"
-/* The amount of time that \fBpostscreen\fR(8) will use the result from
-/* a successful "bare newline" SMTP protocol test.
+/* The amount of time that \fBpostscreen\fR(8) remembers that a client
+/* IP address passed a "bare newline" SMTP protocol test, before it
+/* address is required to pass that test again.
/* .IP "\fBpostscreen_dnsbl_max_ttl (${postscreen_dnsbl_ttl?{$postscreen_dnsbl_ttl}:{1}}h)\fR"
-/* The maximum amount of time that \fBpostscreen\fR(8) will use the
-/* result from a successful DNS-based reputation test before a
-/* client IP address is required to pass that test again.
+/* The maximum amount of time that \fBpostscreen\fR(8) remembers that a
+/* client IP address passed a DNS-based reputation test, before it is
+/* required to pass that test again.
/* .IP "\fBpostscreen_dnsbl_min_ttl (60s)\fR"
-/* The minimum amount of time that \fBpostscreen\fR(8) will use the
-/* result from a successful DNS-based reputation test before a
-/* client IP address is required to pass that test again.
+/* The minimum amount of time that \fBpostscreen\fR(8) remembers that a
+/* client IP address passed a DNS-based reputation test, before it
+/* is required to pass that test again.
/* .IP "\fBpostscreen_greet_ttl (1d)\fR"
-/* The amount of time that \fBpostscreen\fR(8) will use the result from
-/* a successful PREGREET test.
+/* The amount of time that \fBpostscreen\fR(8) remembers that a client
+/* IP address passed a PREGREET test, before it is required to pass
+/* that test again.
/* .IP "\fBpostscreen_non_smtp_command_ttl (30d)\fR"
-/* The amount of time that \fBpostscreen\fR(8) will use the result from
-/* a successful "non_smtp_command" SMTP protocol test.
+/* The amount of time that \fBpostscreen\fR(8) remembers that a client
+/* IP address passed a "non_smtp_command" SMTP protocol test, before
+/* it is required to pass that test again.
/* .IP "\fBpostscreen_pipelining_ttl (30d)\fR"
-/* The amount of time that \fBpostscreen\fR(8) will use the result from
-/* a successful "pipelining" SMTP protocol test.
+/* The amount of time that \fBpostscreen\fR(8) remembers that a client
+/* IP address passed a "pipelining" SMTP protocol test, before it is
+/* required to pass that test again.
/* RESOURCE CONTROLS
/* .ad
/* .fi
diff --git a/src/postscreen/postscreen_smtpd.c b/src/postscreen/postscreen_smtpd.c
index dfc5d54..6b72626 100644
--- a/src/postscreen/postscreen_smtpd.c
+++ b/src/postscreen/postscreen_smtpd.c
@@ -874,7 +874,8 @@ static void psc_smtpd_read_event(int event, void *context)
}
/*
- * Bare newline test.
+ * Bare newline test. Note: at this point, state->cmd_buffer is
+ * not null-terminated and may contain embedded null bytes.
*/
if (ch == '\n') {
if ((state->flags & PSC_STATE_MASK_BARLF_TODO_SKIP)
@@ -929,18 +930,19 @@ 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, len) \
- (var_smtputf8_enable && !valid_utf8_string((str), (len)))
+#define PSC_BAD_UTF8(str) \
+ (var_smtputf8_enable && !valid_utf8_stringz(str))
/*
* Terminate the command buffer, and apply the last-resort command
* editing workaround.
*/
VSTRING_TERMINATE(state->cmd_buffer);
- if (psc_cmd_filter != 0 && !PSC_BAD_UTF8(STR(state->cmd_buffer),
- LEN(state->cmd_buffer))) {
+ if (psc_cmd_filter != 0 && !PSC_BAD_UTF8(STR(state->cmd_buffer))) {
const char *cp;
for (cp = STR(state->cmd_buffer); *cp && IS_SPACE_TAB(*cp); cp++)
@@ -1007,7 +1009,7 @@ static void psc_smtpd_read_event(int event, void *context)
if ((state->flags & PSC_STATE_MASK_NSMTP_TODO_SKIP)
== PSC_STATE_FLAG_NSMTP_TODO && cmdp->name == 0
&& (is_header(command)
- || PSC_BAD_UTF8(command, strlen(command))
+ || PSC_BAD_UTF8(command)
/* Ignore forbid_cmds lookup errors. Non-critical feature. */
|| (*var_psc_forbid_cmds
&& string_list_match(psc_forbid_cmds, command)))) {
diff --git a/src/postsuper/postsuper.c b/src/postsuper/postsuper.c
index d3f2d5b..c9b8e38 100644
--- a/src/postsuper/postsuper.c
+++ b/src/postsuper/postsuper.c
@@ -288,7 +288,7 @@
/* The names of queue directories that are split across multiple
/* subdirectory levels.
/* .IP "\fBimport_environment (see 'postconf -d' output)\fR"
-/* The list of environment parameters that a privileged Postfix
+/* The list of environment variables that a privileged Postfix
/* process will import from a non-Postfix parent process, or name=value
/* environment overrides.
/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
diff --git a/src/posttls-finger/posttls-finger.c b/src/posttls-finger/posttls-finger.c
index d64c355..b474a40 100644
--- a/src/posttls-finger/posttls-finger.c
+++ b/src/posttls-finger/posttls-finger.c
@@ -103,7 +103,7 @@
/* in the DNS). In Postfix versions prior to 3.6, the default value
/* was "md5".
/* .IP "\fB-f\fR"
-/* Lookup the associated DANE TLSA RRset even when a hostname is not an
+/* Look up the associated DANE TLSA RRset even when a hostname is not an
/* alias and its address records lie in an unsigned zone. See
/* smtp_tls_force_insecure_host_tlsa_lookup for details.
/* .IP "\fB-F \fICAfile.pem\fR (default: none)"
@@ -264,6 +264,15 @@
/* the SMTP-in-SSL protocol, rather than the STARTTLS protocol.
/* The destination \fIdomain\fR:\fIport\fR must of course provide such
/* a service.
+/* .IP "\fB-x\fR"
+/* Prefer RFC7250 non-X.509 raw public key (RPK) server credentials. By
+/* default only X.509 certificates are accepted. This is analogous to
+/* setting \fBsmtp_tls_enable_rpk = yes\fR in the smtp(8) client. At the
+/* fingerprint security level, when raw public keys are enabled, only
+/* public key (and not certificate) fingerprints will be compared against
+/* the specified list of \fImatch\fR arguments. Certificate fingerprints
+/* are fragile when raw public keys are solicited, the server may at some
+/* point in time start returning only the public key.
/* .IP "\fB-X\fR"
/* Enable \fBtlsproxy\fR(8) mode. This is an unsupported mode,
/* for program development only.
@@ -441,6 +450,9 @@ typedef struct OPTIONS {
ARGV *tas;
char *host_lookup;
char *addr_pref;
+#ifdef USE_TLS
+ int enable_rpk;
+#endif
} OPTIONS;
/*
@@ -696,7 +708,7 @@ static void print_stack(STATE *state, x509_stack_t *sk, int trustout)
BIO_printf(state->tls_bio, " cert digest=%s\n", digest);
myfree(digest);
- digest = tls_pkey_fprint(cert, state->mdalg);
+ digest = tls_pkey_fprint(X509_get0_pubkey(cert), state->mdalg);
BIO_printf(state->tls_bio, " pkey digest=%s\n", digest);
myfree(digest);
@@ -809,6 +821,7 @@ static int starttls(STATE *state)
mdalg = state->mdalg);
TLS_PROXY_CLIENT_START_PROPS(&start_props,
timeout = smtp_tmout,
+ enable_rpk = state->options.enable_rpk,
tls_level = state->level,
nexthop = state->nexthop,
host = state->hostname,
@@ -826,7 +839,7 @@ static int starttls(STATE *state)
state->ddane : state->dane);
#define PROXY_OPEN_FLAGS \
- (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_SEND_CONTEXT)
+ (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_SEND_CONTEXT)
#define var_tlsproxy_service
if ((cwd_fd = open(".", O_RDONLY)) < 0)
@@ -886,13 +899,19 @@ static int starttls(STATE *state)
state->tls_context = tls_proxy_context_receive(state->stream);
if (state->tls_context) {
if (state->log_mask &
- (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT))
- msg_info("%s: subject_CN=%s, issuer_CN=%s, "
- "fingerprint=%s, pkey_fingerprint=%s",
- state->namaddrport, state->tls_context->peer_CN,
- state->tls_context->issuer_CN,
- state->tls_context->peer_cert_fprint,
- state->tls_context->peer_pkey_fprint);
+ (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT)) {
+ if (state->tls_context->stoc_rpk)
+ msg_info("%s: pkey_fingerprint=%s", state->namaddrport,
+ state->tls_context->peer_pkey_fprint);
+ else
+ msg_info("%s: subject_CN=%s, issuer_CN=%s, "
+ "fingerprint=%s, pkey_fingerprint=%s",
+ state->namaddrport,
+ state->tls_context->peer_CN,
+ state->tls_context->issuer_CN,
+ state->tls_context->peer_cert_fprint,
+ state->tls_context->peer_pkey_fprint);
+ }
tls_log_summary(TLS_ROLE_CLIENT, TLS_USAGE_NEW,
state->tls_context);
} else {
@@ -906,6 +925,7 @@ static int starttls(STATE *state)
stream = stream,
fd = -1,
timeout = smtp_tmout,
+ enable_rpk = state->options.enable_rpk,
tls_level = state->level,
nexthop = state->nexthop,
host = state->hostname,
@@ -938,7 +958,7 @@ static int starttls(STATE *state)
if (state->pass == 1) {
ehlo(state);
- if (!TLS_CERT_IS_PRESENT(state->tls_context))
+ if (!TLS_CRED_IS_PRESENT(state->tls_context))
msg_info("Server is anonymous");
else if (state->tlsproxy_mode == 0) {
if (state->print_trust)
@@ -1232,7 +1252,7 @@ static DNS_RR *addr_one(STATE *state, DNS_RR *addr_list, const char *host,
* should not clobber a soft error text and status code.
*/
#define RETRY_AI_ERROR(e) \
- ((e) == EAI_AGAIN || (e) == EAI_MEMORY || (e) == EAI_SYSTEM)
+ ((e) == EAI_AGAIN || (e) == EAI_MEMORY || (e) == EAI_SYSTEM)
#ifdef EAI_NODATA
#define DSN_NOHOST(e) \
((e) == EAI_AGAIN || (e) == EAI_NODATA || (e) == EAI_NONAME)
@@ -1833,14 +1853,14 @@ static void usage(void)
#ifdef USE_TLS
fprintf(stderr, "usage: %s %s \\\n\t%s \\\n\t%s \\\n\t%s \\\n\t%s"
" destination [match ...]\n", var_procname,
- "[-acCfSvw] [-t conn_tmout] [-T cmd_tmout] [-L logopts]",
+ "[-acCfRSvwx] [-t conn_tmout] [-T cmd_tmout] [-L logopts]",
"[-h host_lookup] [-l level] [-d mdalg] [-g grade] [-p protocols]",
"[-A tafile] [-F CAfile.pem] [-P CApath/] [-s servername]",
"[ [-H chainfiles] | [-k certfile [-K keyfile]] ]",
"[-m count] [-r delay] [-o name=value]");
#else
- fprintf(stderr, "usage: %s [-acStTv] [-h host_lookup] [-o name=value] destination\n",
- var_procname);
+ fprintf(stderr, "usage: %s [-acRStTv] [-h host_lookup] [-o name=value]"
+ " destination\n", var_procname);
#endif
exit(1);
}
@@ -1910,7 +1930,7 @@ static void parse_options(STATE *state, int argc, char *argv[])
#define OPTS "a:ch:o:RSt:T:v"
#ifdef USE_TLS
-#define TLSOPTS "A:Cd:fF:g:H:k:K:l:L:m:M:p:P:r:s:wX"
+#define TLSOPTS "A:Cd:fF:g:H:k:K:l:L:m:M:p:P:r:s:wxX"
state->mdalg = 0;
state->CApath = mystrdup("");
@@ -1921,6 +1941,7 @@ static void parse_options(STATE *state, int argc, char *argv[])
state->sni = mystrdup("");
state->options.tas = argv_alloc(1);
state->options.logopts = 0;
+ state->options.enable_rpk = 0;
state->level = TLS_LEV_DANE;
state->mxinsec_level = TLS_LEV_DANE;
state->tlsproxy_mode = 0;
@@ -2054,6 +2075,9 @@ static void parse_options(STATE *state, int argc, char *argv[])
case 'w':
state->wrapper_mode = 1;
break;
+ case 'x':
+ state->options.enable_rpk = 1;
+ break;
case 'X':
state->tlsproxy_mode = 1;
break;
@@ -2119,8 +2143,8 @@ static void parse_match(STATE *state, int argc, char *argv[])
int smtp_mode = 1;
/*
- * DANE match names are configured late, once the TLSA records are in
- * hand. For now, prepare to fall back to "secure".
+ * DANE match names are configured late, once the TLSA records are in hand.
+ * For now, prepare to fall back to "secure".
*/
switch (state->level) {
default:
@@ -2148,8 +2172,8 @@ static void parse_match(STATE *state, int argc, char *argv[])
case TLS_LEV_FPRINT:
state->dane = tls_dane_alloc();
while (*argv)
- tls_dane_add_fpt_digests((TLS_DANE *) state->dane, *argv++, "",
- smtp_mode);
+ tls_dane_add_fpt_digests(state->dane, state->options.enable_rpk,
+ *argv++, "", smtp_mode);
break;
}
#endif
diff --git a/src/proxymap/proxymap.c b/src/proxymap/proxymap.c
index abdcf3a..c0af411 100644
--- a/src/proxymap/proxymap.c
+++ b/src/proxymap/proxymap.c
@@ -37,7 +37,7 @@
/* .IP \(bu
/* To provide single-updater functionality for lookup tables
/* that do not reliably support multiple writers (i.e. all
-/* file-based tables).
+/* file-based tables that are not based on \fBlmdb\fR).
/* .PP
/* The \fBproxymap\fR(8) server implements the following requests:
/* .IP "\fBopen\fR \fImaptype:mapname flags\fR"
@@ -752,8 +752,10 @@ static void post_jail_init(char *service_name, char **unused_argv)
if (strcmp(service_name, MAIL_SERVICE_PROXYWRITE) == 0)
proxy_writer = 1;
else if (strcmp(service_name, MAIL_SERVICE_PROXYMAP) != 0)
- msg_fatal("service name must be one of %s or %s",
- MAIL_SERVICE_PROXYMAP, MAIL_SERVICE_PROXYMAP);
+ msg_fatal("invalid service name: \"%s\" - "
+ "service name must be \"%s\" or \"%s\"",
+ service_name, MAIL_SERVICE_PROXYWRITE,
+ MAIL_SERVICE_PROXYMAP);
/*
* Pre-allocate buffers.
@@ -841,6 +843,36 @@ int main(int argc, char **argv)
*/
MAIL_VERSION_STAMP_ALLOCATE;
+ /*
+ * XXX When invoked with the master.cf service name "proxywrite", the
+ * proxymap daemon will allow update requests. To update a table that is
+ * not multi-writer safe (for example, some versions of Berkeley DB), the
+ * "proxywrite" service should run as a single updater (i.e. a process
+ * limit of 1, which could be enforced below by requesting
+ * CA_MAIL_SERVER_SOLITARY).
+ *
+ * In the default master.cf file, the "proxywrite" service has a process
+ * limit of 1. Assuming that updates will be rare, this process limit
+ * will suffice. Latency-sensitive services such as postscreen must not
+ * use the proxywrite service (in fact, postscreen has a latency check
+ * built-in).
+ *
+ * Optimizing for multi-writer operation would suffer from all kinds of
+ * complexity that would make it hard to use:
+ *
+ * - The master daemon specifies the "proxywrite" service name with the -n
+ * command-line option. This information is not known here, before the
+ * multi_server_main() call. The multi_server_main() function could
+ * reveal process limit information to its call-back functions, and leave
+ * single-updater enforcement to its call-back functions.
+ *
+ * - If we really want multi-writer update support, the "proxywrite" service
+ * would have to parse the $proxy_write_maps value, and permit
+ * multi-writer operation only if all tables are multi-writer safe. That
+ * would require a new dict(3) method, to query each lookup table
+ * implementation if it is multi-writer safe, without instantiating a
+ * lookup table client.
+ */
multi_server_main(argc, argv, proxymap_service,
CA_MAIL_SERVER_STR_TABLE(str_table),
CA_MAIL_SERVER_POST_INIT(post_jail_init),
diff --git a/src/qmgr/qmgr.c b/src/qmgr/qmgr.c
index 9d90a6e..25168e4 100644
--- a/src/qmgr/qmgr.c
+++ b/src/qmgr/qmgr.c
@@ -206,7 +206,7 @@
/* parameter value, where \fItransport\fR is the master.cf name of
/* the message delivery transport.
/* .IP "\fBdefault_recipient_refill_delay (5s)\fR"
-/* The default per-transport maximum delay between recipients refills.
+/* The default per-transport maximum delay between refilling recipients.
/* .IP "\fBtransport_recipient_refill_delay ($default_recipient_refill_delay)\fR"
/* A transport-specific override for the default_recipient_refill_delay
/* parameter value, where \fItransport\fR is the master.cf name of
diff --git a/src/qmgr/qmgr_deliver.c b/src/qmgr/qmgr_deliver.c
index 577bb98..6c880ce 100644
--- a/src/qmgr/qmgr_deliver.c
+++ b/src/qmgr/qmgr_deliver.c
@@ -160,7 +160,7 @@ static int qmgr_deliver_send_request(QMGR_ENTRY *entry, VSTREAM *stream)
*/
for (recipient = list.info; recipient < list.info + list.len; recipient++)
if (var_smtputf8_enable && (addr = recipient->address)[0]
- && !allascii(addr) && valid_utf8_string(addr, strlen(addr))) {
+ && !allascii(addr) && valid_utf8_stringz(addr)) {
smtputf8 |= SMTPUTF8_FLAG_RECIPIENT;
if (message->verp_delims)
smtputf8 |= SMTPUTF8_FLAG_SENDER;
@@ -339,7 +339,7 @@ static void qmgr_deliver_update(int unused_event, void *context)
#define SUSPENDED "delivery temporarily suspended: "
if (status == DELIVER_STAT_CRASH)
- DSN_SIMPLE(&dsb->dsn, "4.3.0", "unknown mail transport error");
+ (void) DSN_SIMPLE(&dsb->dsn, "4.3.0", "unknown mail transport error");
if (status == DELIVER_STAT_CRASH || status == DELIVER_STAT_DEFER) {
message->flags |= DELIVER_STAT_DEFER;
if (VSTRING_LEN(dsb->status)) {
diff --git a/src/qmqpd/qmqpd.c b/src/qmqpd/qmqpd.c
index d94d33d..138ac64 100644
--- a/src/qmqpd/qmqpd.c
+++ b/src/qmqpd/qmqpd.c
@@ -57,7 +57,7 @@
/* Preliminary SMTPUTF8 support is introduced with Postfix 3.0.
/* .IP "\fBsmtputf8_enable (yes)\fR"
/* Enable preliminary SMTPUTF8 support for the protocols described
-/* in RFC 6531..6533.
+/* in RFC 6531, RFC 6532, and RFC 6533.
/* .IP "\fBsmtputf8_autodetect_classes (sendmail, verify)\fR"
/* Detect that a message requires SMTPUTF8 support for the specified
/* mail origin classes.
diff --git a/src/sendmail/sendmail.c b/src/sendmail/sendmail.c
index 27b3543..df052c5 100644
--- a/src/sendmail/sendmail.c
+++ b/src/sendmail/sendmail.c
@@ -412,9 +412,10 @@
/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
/* The location of the Postfix top-level queue directory.
/* .IP "\fBremote_header_rewrite_domain (empty)\fR"
-/* Don't rewrite message headers from remote clients at all when
-/* this parameter is empty; otherwise, rewrite message headers and
-/* append the specified domain name to incomplete addresses.
+/* Rewrite or add message headers in mail from remote clients if
+/* the remote_header_rewrite_domain parameter value is non-empty,
+/* updating incomplete addresses with the domain specified in the
+/* remote_header_rewrite_domain parameter, and adding missing headers.
/* .IP "\fBsyslog_facility (mail)\fR"
/* The syslog facility of Postfix logging.
/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
diff --git a/src/smtp/lmtp_params.c b/src/smtp/lmtp_params.c
index bca7cd4..385c81f 100644
--- a/src/smtp/lmtp_params.c
+++ b/src/smtp/lmtp_params.c
@@ -4,6 +4,7 @@
VAR_BESTMX_TRANSP, DEF_BESTMX_TRANSP, &var_bestmx_transp, 0, 0,
VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0,
VAR_LMTP_SASL_PASSWD, DEF_LMTP_SASL_PASSWD, &var_smtp_sasl_passwd, 0, 0,
+ VAR_LMTP_SASL_PASSWD_RES_DELIM, DEF_LMTP_SASL_PASSWD_RES_DELIM, &var_smtp_sasl_passwd_res_delim, 1, 1,
VAR_LMTP_SASL_OPTS, DEF_LMTP_SASL_OPTS, &var_smtp_sasl_opts, 0, 0,
VAR_LMTP_SASL_PATH, DEF_LMTP_SASL_PATH, &var_smtp_sasl_path, 0, 0,
#ifdef USE_TLS
@@ -120,6 +121,7 @@
VAR_LMTP_TLS_NOTEOFFER, DEF_LMTP_TLS_NOTEOFFER, &var_smtp_tls_note_starttls_offer,
VAR_LMTP_TLS_BLK_EARLY_MAIL_REPLY, DEF_LMTP_TLS_BLK_EARLY_MAIL_REPLY, &var_smtp_tls_blk_early_mail_reply,
VAR_LMTP_TLS_FORCE_TLSA, DEF_LMTP_TLS_FORCE_TLSA, &var_smtp_tls_force_tlsa,
+ VAR_LMTP_TLS_ENABLE_RPK, DEF_LMTP_TLS_ENABLE_RPK, &var_smtp_tls_enable_rpk,
#endif
VAR_LMTP_TLS_WRAPPER, DEF_LMTP_TLS_WRAPPER, &var_smtp_tls_wrappermode,
VAR_LMTP_SENDER_AUTH, DEF_LMTP_SENDER_AUTH, &var_smtp_sender_auth,
diff --git a/src/smtp/smtp.c b/src/smtp/smtp.c
index f7f2fc1..51b2e6d 100644
--- a/src/smtp/smtp.c
+++ b/src/smtp/smtp.c
@@ -1,17 +1,21 @@
/*++
/* NAME
-/* smtp 8
+/* smtp, lmtp 8
/* SUMMARY
/* Postfix SMTP+LMTP client
/* SYNOPSIS
/* \fBsmtp\fR [generic Postfix daemon options] [flags=DORX]
+/*
+/* \fBlmtp\fR [generic Postfix daemon options] [flags=DORX]
/* DESCRIPTION
/* The Postfix SMTP+LMTP client implements the SMTP and LMTP mail
/* delivery protocols. It processes message delivery requests from
/* the queue manager. Each request specifies a queue file, a sender
/* address, a domain or host to deliver to, and recipient information.
/* This program expects to be run from the \fBmaster\fR(8) process
-/* manager.
+/* manager. The process name, \fBsmtp\fR or \fBlmtp\fR, controls
+/* the protocol, and the names of the configuration parameters
+/* that will be used.
/*
/* The SMTP+LMTP client updates the queue file and marks recipients
/* as finished, or it informs the queue manager that delivery should
@@ -19,13 +23,9 @@
/* to the \fBbounce\fR(8), \fBdefer\fR(8) or \fBtrace\fR(8) daemon as
/* appropriate.
/*
-/* The SMTP+LMTP client looks up a list of mail exchanger addresses for
-/* the destination host, sorts the list by preference, and connects
-/* to each listed address until it finds a server that responds.
-/*
-/* When a server is not reachable, or when mail delivery fails due
-/* to a recoverable error condition, the SMTP+LMTP client will try to
-/* deliver the mail to an alternate host.
+/* The server lookup strategy is different for SMTP and LMTP,
+/* as described in the sections "SMTP SERVER LOOKUP" and "LMTP
+/* SERVER LOOKUP".
/*
/* After a successful mail transaction, a connection may be saved
/* to the \fBscache\fR(8) connection cache server, so that it
@@ -35,44 +35,58 @@
/* destinations that have a high volume of mail in the active
/* queue. Connection caching can be enabled permanently for
/* specific destinations.
-/* SMTP DESTINATION SYNTAX
+/* SMTP SERVER LOOKUP
/* .ad
/* .fi
-/* The Postfix SMTP+LMTP client supports multiple destinations
+/* The Postfix SMTP client supports multiple destinations
/* separated by comma or whitespace (Postfix 3.5 and later).
+/* Each destination is tried in the specified order.
+/*
/* SMTP destinations have the following form:
/* .IP \fIdomainname\fR
-/* .IP \fIdomainname\fR:\fIport\fR
+/* .IP \fIdomainname\fR:\fIservice\fR
/* Look up the mail exchangers for the specified domain, and
-/* connect to the specified port (default: \fBsmtp\fR).
+/* connect to the specified service (default: \fBsmtp\fR).
+/* Optionally, mail exchangers may be looked up with SRV queries
+/* instead of MX; this requires that \fIservice\fR is given
+/* in symbolic form.
/* .IP [\fIhostname\fR]
-/* .IP [\fIhostname\fR]:\fIport\fR
-/* Look up the address(es) of the specified host, and connect to
-/* the specified port (default: \fBsmtp\fR).
+/* .IP [\fIhostname\fR]:\fIservice\fR
+/* Look up the address(es) for the specified host, and connect to
+/* the specified service (default: \fBsmtp\fR).
/* .IP [\fIaddress\fR]
-/* .IP [\fIaddress\fR]:\fIport\fR
+/* .IP [\fIaddress\fR]:\fIservice\fR
/* Connect to the host at the specified address, and connect
-/* to the specified port (default: \fBsmtp\fR). An IPv6 address
+/* to the specified service (default: \fBsmtp\fR). An IPv6 address
/* must be formatted as [\fBipv6\fR:\fIaddress\fR].
-/* LMTP DESTINATION SYNTAX
+/* LMTP SERVER LOOKUP
/* .ad
/* .fi
-/* The Postfix SMTP+LMTP client supports multiple destinations
+/* The Postfix LMTP client supports multiple destinations
/* separated by comma or whitespace (Postfix 3.5 and later).
+/* Each destination is tried in the specified order.
+/*
/* LMTP destinations have the following form:
/* .IP \fBunix\fR:\fIpathname\fR
/* Connect to the local UNIX-domain server that is bound to the specified
/* \fIpathname\fR. If the process runs chrooted, an absolute pathname
/* is interpreted relative to the Postfix queue directory.
+/* .IP \fBinet\fR:\fIdomainname\fR
+/* .IP \fBinet\fR:\fIdomainname\fR:\fIservice\fR
+/* Look up the LMTP servers for the specified domain and service
+/* (default: \fBlmtp\fR).
+/* This form is supported when SRV lookups are enabled, and
+/* requires that \fIservice\fR is in symbolic form.
/* .IP \fBinet\fR:\fIhostname\fR
-/* .IP \fBinet\fR:\fIhostname\fR:\fIport\fR
+/* .IP \fBinet\fR:\fIhostname\fR:\fIservice\fR
+/* Look up the address(es) for the specified host, and connect to
+/* the specified service (default: \fBlmtp\fR). When SRV lookups
+/* are enabled, use the form \fB[\fIhostname\fB]\fR to force
+/* address lookups.
/* .IP \fBinet\fR:[\fIaddress\fR]
-/* .IP \fBinet\fR:[\fIaddress\fR]:\fIport\fR
-/* Connect to the specified TCP port on the specified local or
-/* remote host. If no port is specified, connect to the port defined as
-/* \fBlmtp\fR in \fBservices\fR(4).
-/* If no such service is found, the \fBlmtp_tcp_port\fR configuration
-/* parameter (default value of 24) will be used.
+/* .IP \fBinet\fR:[\fIaddress\fR]:\fIservice\fR
+/* Connect to the specified local or remote host and service
+/* (default: \fBlmtp\fR).
/* An IPv6 address must be formatted as [\fBipv6\fR:\fIaddress\fR].
/* SINGLE-RECIPIENT DELIVERY
/* .ad
@@ -130,6 +144,8 @@
/* This feature is available as of Postfix 3.5.
/* .RE
/* SECURITY
+/* .ad
+/* .fi
/* The SMTP+LMTP client is moderately security-sensitive. It
/* talks to SMTP or LMTP servers and to DNS servers on the
/* network. The SMTP+LMTP client can be run chrooted at fixed
@@ -175,11 +191,10 @@
/* CONFIGURATION PARAMETERS
/* .ad
/* .fi
-/* Before Postfix version 2.3, the LMTP client is a separate
-/* program that implements only a subset of the functionality
-/* available with SMTP: there is no support for TLS, and
-/* connections are cached in-process, making it ineffective
-/* when the client is used for multiple domains.
+/* Postfix versions 2.3 and later implement the SMTP and LMTP
+/* client with the same program, and choose the protocol and
+/* configuration parameters based on the process name, \fBsmtp\fR
+/* or \fBlmtp\fR.
/*
/* Most smtp_\fIxxx\fR configuration parameters have an
/* lmtp_\fIxxx\fR "mirror" parameter for the equivalent LMTP
@@ -432,6 +447,11 @@
/* .IP "\fBsmtp_send_dummy_mail_auth (no)\fR"
/* Whether or not to append the "AUTH=<>" option to the MAIL
/* FROM command in SASL-authenticated SMTP sessions.
+/* .PP
+/* Available in Postfix version 3.9 and later:
+/* .IP "\fBsmtp_sasl_password_result_delimiter (:)\fR"
+/* The delimiter between username and password in sasl_passwd_maps lookup
+/* results.
/* STARTTLS SUPPORT CONTROLS
/* .ad
/* .fi
@@ -532,7 +552,7 @@
/* certificate fingerprints.
/* .PP
/* Available in Postfix version 2.6 and later:
-/* .IP "\fBsmtp_tls_protocols (see postconf -d output)\fR"
+/* .IP "\fBsmtp_tls_protocols (see 'postconf -d' output)\fR"
/* TLS protocols that the Postfix SMTP client will use with
/* opportunistic TLS encryption.
/* .IP "\fBsmtp_tls_ciphers (medium)\fR"
@@ -613,6 +633,11 @@
/* .IP "\fBtls_config_name (empty)\fR"
/* The application name passed by Postfix to OpenSSL library
/* initialization functions.
+/* .PP
+/* Available in Postfix version 3.9 and later:
+/* .IP "\fBsmtp_tls_enable_rpk (no)\fR"
+/* Request that remote SMTP servers send an RFC7250 raw public key
+/* instead of an X.509 certificate.
/* OBSOLETE STARTTLS CONTROLS
/* .ad
/* .fi
@@ -799,9 +824,9 @@
/* .IP "\fBdisable_dns_lookups (no)\fR"
/* Disable DNS lookups in the Postfix SMTP and LMTP clients.
/* .IP "\fBinet_interfaces (all)\fR"
-/* The local network interface addresses that this mail system receives
-/* mail on.
-/* .IP "\fBinet_protocols (see 'postconf -d output')\fR"
+/* The local network interface addresses that this mail system
+/* receives mail on.
+/* .IP "\fBinet_protocols (see 'postconf -d' output)\fR"
/* The Internet protocols Postfix will attempt to use when making
/* or accepting connections.
/* .IP "\fBipc_timeout (3600s)\fR"
@@ -1020,6 +1045,7 @@ int var_smtp_never_ehlo;
char *var_smtp_sasl_opts;
char *var_smtp_sasl_path;
char *var_smtp_sasl_passwd;
+char *var_smtp_sasl_passwd_res_delim;
bool var_smtp_sasl_enable;
char *var_smtp_sasl_mechs;
char *var_smtp_sasl_type;
@@ -1090,6 +1116,7 @@ char *var_smtp_tls_sni;
bool var_smtp_tls_blk_early_mail_reply;
bool var_smtp_tls_force_tlsa;
char *var_smtp_tls_insecure_mx_policy;
+bool var_smtp_tls_enable_rpk;
#endif
@@ -1117,8 +1144,8 @@ bool var_smtp_balance_inet_proto;
bool var_smtp_req_deadline;
int var_smtp_min_data_rate;
char *var_use_srv_lookup;
-bool var_ign_srv_lookup_err;
-bool var_allow_srv_fallback;
+bool var_ign_srv_lookup_err;
+bool var_allow_srv_fallback;
/* Special handling of 535 AUTH errors. */
char *var_smtp_sasl_auth_cache_name;
@@ -1126,7 +1153,7 @@ int var_smtp_sasl_auth_cache_time;
bool var_smtp_sasl_auth_soft_bounce;
char *var_hfrom_format;
-bool var_smtp_bind_addr_enforce;
+bool var_smtp_bind_addr_enforce;
/*
* Global variables.
@@ -1459,6 +1486,19 @@ static void pre_init(char *unused_name, char **unused_argv)
};
/*
+ * The process name, "smtp" or "lmtp", determines the configuration
+ * parameters to use, protocol, DSN server reply type, SASL service
+ * information lookup, and more. We peeked at the name in the main()
+ * function before logging was initialized. Here, we detect and report an
+ * invalid process name.
+ */
+ if (strcmp(var_procname, MAIL_PROC_NAME_SMTP) != 0
+ && strcmp(var_procname, MAIL_PROC_NAME_LMTP) != 0)
+ msg_fatal("unexpected process name \"%s\" - "
+ "specify \"%s\" or \"%s\"", var_procname,
+ MAIL_PROC_NAME_SMTP, MAIL_PROC_NAME_LMTP);
+
+ /*
* Turn on per-peer debugging.
*/
debug_peer_init();
@@ -1649,21 +1689,15 @@ int main(int argc, char **argv)
MAIL_VERSION_STAMP_ALLOCATE;
/*
- * XXX At this point, var_procname etc. are not initialized.
- *
- * The process name, "smtp" or "lmtp", determines the protocol, the DSN
- * server reply type, SASL service information lookup, and more. Prepare
- * for the possibility there may be another personality.
+ * XXX The process name, "smtp" or "lmtp", determines what configuration
+ * parameter settings to use, and more. However, at this point, logging
+ * and var_procname are not initialized. Here, we peek at the process
+ * name to determine what configuration parameter settings to use. Later,
+ * we detect and report an invalid process name.
*/
sane_procname = sane_basename((VSTRING *) 0, argv[0]);
- if (strcmp(sane_procname, "smtp") == 0)
+ if (strcmp(sane_procname, MAIL_PROC_NAME_SMTP) == 0)
smtp_mode = 1;
- else if (strcmp(sane_procname, "lmtp") == 0)
- smtp_mode = 0;
- else
- /* TODO: logging is not initialized. */
- msg_fatal("unexpected process name \"%s\" - "
- "specify \"smtp\" or \"lmtp\"", var_procname);
/*
* Initialize with the LMTP or SMTP parameter name space.
diff --git a/src/smtp/smtp.h b/src/smtp/smtp.h
index f8c8f58..60c68f8 100644
--- a/src/smtp/smtp.h
+++ b/src/smtp/smtp.h
@@ -107,6 +107,7 @@ typedef struct SMTP_TLS_POLICY {
TLS_DANE *dane; /* DANE TLSA digests */
char *sni; /* Optional SNI name when not DANE */
int conn_reuse; /* enable connection reuse */
+ int enable_rpk; /* Enable server->client RPK */
} SMTP_TLS_POLICY;
/*
@@ -142,6 +143,7 @@ extern void smtp_tls_policy_cache_flush(void);
_tls_policy_init_tmp->dane = 0; \
_tls_policy_init_tmp->sni = 0; \
_tls_policy_init_tmp->conn_reuse = 0; \
+ _tls_policy_init_tmp->enable_rpk = 0; \
} while (0)
#endif
diff --git a/src/smtp/smtp_addr.c b/src/smtp/smtp_addr.c
index f30ce73..8c384fc 100644
--- a/src/smtp/smtp_addr.c
+++ b/src/smtp/smtp_addr.c
@@ -262,7 +262,7 @@ static DNS_RR *smtp_addr_one(DNS_RR *addr_list, const char *host, int res_opt,
msg_fatal("host %s: conversion error for address family "
"%d: %m", host, res0->ai_addr->sa_family);
addr_list = dns_rr_append(addr_list, addr);
- if (DNS_RR_IS_TRUNCATED(addr_list))
+ if (DNS_RR_IS_TRUNCATED(addr_list))
break;
if (msg_verbose) {
MAI_HOSTADDR_STR hostaddr_str;
diff --git a/src/smtp/smtp_params.c b/src/smtp/smtp_params.c
index 22f4709..cebff93 100644
--- a/src/smtp/smtp_params.c
+++ b/src/smtp/smtp_params.c
@@ -4,6 +4,7 @@
VAR_BESTMX_TRANSP, DEF_BESTMX_TRANSP, &var_bestmx_transp, 0, 0,
VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0,
VAR_SMTP_SASL_PASSWD, DEF_SMTP_SASL_PASSWD, &var_smtp_sasl_passwd, 0, 0,
+ VAR_SMTP_SASL_PASSWD_RES_DELIM, DEF_SMTP_SASL_PASSWD_RES_DELIM, &var_smtp_sasl_passwd_res_delim, 1, 1,
VAR_SMTP_SASL_OPTS, DEF_SMTP_SASL_OPTS, &var_smtp_sasl_opts, 0, 0,
VAR_SMTP_SASL_PATH, DEF_SMTP_SASL_PATH, &var_smtp_sasl_path, 0, 0,
#ifdef USE_TLS
@@ -124,6 +125,7 @@
VAR_SMTP_TLS_NOTEOFFER, DEF_SMTP_TLS_NOTEOFFER, &var_smtp_tls_note_starttls_offer,
VAR_SMTP_TLS_BLK_EARLY_MAIL_REPLY, DEF_SMTP_TLS_BLK_EARLY_MAIL_REPLY, &var_smtp_tls_blk_early_mail_reply,
VAR_SMTP_TLS_FORCE_TLSA, DEF_SMTP_TLS_FORCE_TLSA, &var_smtp_tls_force_tlsa,
+ VAR_SMTP_TLS_ENABLE_RPK, DEF_SMTP_TLS_ENABLE_RPK, &var_smtp_tls_enable_rpk,
#endif
VAR_SMTP_TLS_WRAPPER, DEF_SMTP_TLS_WRAPPER, &var_smtp_tls_wrappermode,
VAR_SMTP_SENDER_AUTH, DEF_SMTP_SENDER_AUTH, &var_smtp_sender_auth,
diff --git a/src/smtp/smtp_proto.c b/src/smtp/smtp_proto.c
index 097d518..e022bc2 100644
--- a/src/smtp/smtp_proto.c
+++ b/src/smtp/smtp_proto.c
@@ -929,6 +929,7 @@ static int smtp_start_tls(SMTP_STATE *state)
TLS_PROXY_CLIENT_START_PROPS(&start_props,
timeout = var_smtp_starttls_tmout,
tls_level = state->tls->level,
+ enable_rpk = state->tls->enable_rpk,
nexthop = session->tls_nexthop,
host = STR(iter->host),
namaddr = session->namaddrport,
@@ -1051,6 +1052,7 @@ static int smtp_start_tls(SMTP_STATE *state)
fd = -1,
timeout = var_smtp_starttls_tmout,
tls_level = state->tls->level,
+ enable_rpk = state->tls->enable_rpk,
nexthop = session->tls_nexthop,
host = STR(iter->host),
namaddr = session->namaddrport,
diff --git a/src/smtp/smtp_sasl_glue.c b/src/smtp/smtp_sasl_glue.c
index ef8e8c4..cce5ef7 100644
--- a/src/smtp/smtp_sasl_glue.c
+++ b/src/smtp/smtp_sasl_glue.c
@@ -200,7 +200,9 @@ int smtp_sasl_passwd_lookup(SMTP_SESSION *session)
if (session->sasl_username)
myfree(session->sasl_username);
session->sasl_username = mystrdup(value);
- passwd = split_at(session->sasl_username, ':');
+ /* Historically, the delimiter may appear in the password. */
+ passwd = split_at(session->sasl_username,
+ *var_smtp_sasl_passwd_res_delim);
if (session->sasl_passwd)
myfree(session->sasl_passwd);
session->sasl_passwd = mystrdup(passwd ? passwd : "");
diff --git a/src/smtp/smtp_tls_policy.c b/src/smtp/smtp_tls_policy.c
index 92a231d..f407d65 100644
--- a/src/smtp/smtp_tls_policy.c
+++ b/src/smtp/smtp_tls_policy.c
@@ -334,9 +334,10 @@ static void tls_policy_lookup_one(SMTP_TLS_POLICY *tls, int *site_level,
INVALID_RETURN(tls->why, site_level);
break;
case TLS_LEV_FPRINT:
- if (!tls->dane)
- tls->dane = tls_dane_alloc();
- tls_dane_add_fpt_digests(tls->dane, val, "|", smtp_mode);
+ if (tls->matchargv == 0)
+ tls->matchargv = argv_split(val, "|");
+ else
+ argv_split_append(tls->matchargv, val, "|");
break;
case TLS_LEV_VERIFY:
case TLS_LEV_SECURE:
@@ -390,6 +391,19 @@ static void tls_policy_lookup_one(SMTP_TLS_POLICY *tls, int *site_level,
}
continue;
}
+ if (!strcasecmp(name, "enable_rpk")) {
+ /* Ultimately ignored at some security levels */
+ if (strcasecmp(val, "yes") == 0) {
+ tls->enable_rpk = 1;
+ } else if (strcasecmp(val, "no") == 0) {
+ tls->enable_rpk = 0;
+ } else {
+ msg_warn("%s: attribute \"%s\" has bad value: \"%s\"",
+ WHERE, name, val);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ continue;
+ }
msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name);
INVALID_RETURN(tls->why, site_level);
}
@@ -518,6 +532,7 @@ static void *policy_create(const char *unused_key, void *context)
smtp_tls_policy_init(tls, dsb_create());
tls->conn_reuse = var_smtp_tls_conn_reuse;
+ tls->enable_rpk = var_smtp_tls_enable_rpk;
/*
* Compute the per-site TLS enforcement level. For compatibility with the
@@ -602,6 +617,13 @@ static void *policy_create(const char *unused_key, void *context)
*/
set_cipher_grade(tls);
+/*
+ * Even when soliciting raw public keys, synthesize TLSA RRs that also match
+ * certificates. Though this is fragile, it maintains compatibility with
+ * servers that never return RPKs.
+ */
+#define DONT_SUPPRESS_CERT_MATCH 0
+
/*
* Use main.cf cert_match setting if not set in per-destination table.
*/
@@ -617,16 +639,26 @@ static void *policy_create(const char *unused_key, void *context)
case TLS_LEV_FPRINT:
if (tls->dane == 0)
tls->dane = tls_dane_alloc();
- if (tls->dane->tlsa == 0) {
- tls_dane_add_fpt_digests(tls->dane, var_smtp_tls_fpt_cmatch,
- CHARS_COMMA_SP, smtp_mode);
- if (tls->dane->tlsa == 0) {
- msg_warn("nexthop domain %s: configured at fingerprint "
- "security level, but with no fingerprints to match.",
- dest);
- MARK_INVALID(tls->why, &tls->level);
- return ((void *) tls);
+ /* Process the specified fingerprint match patterns */
+ if (tls->matchargv) {
+ int i;
+
+ for (i = 0; i < tls->matchargv->argc; ++i) {
+ tls_dane_add_fpt_digests(tls->dane, DONT_SUPPRESS_CERT_MATCH,
+ tls->matchargv->argv[i], "",
+ smtp_mode);
}
+ } else {
+ tls_dane_add_fpt_digests(tls->dane, DONT_SUPPRESS_CERT_MATCH,
+ var_smtp_tls_fpt_cmatch, CHARS_COMMA_SP,
+ smtp_mode);
+ }
+ if (tls->dane->tlsa == 0) {
+ msg_warn("nexthop domain %s: configured at fingerprint "
+ "security level, but with no fingerprints to match.",
+ dest);
+ MARK_INVALID(tls->why, &tls->level);
+ return ((void *) tls);
}
break;
case TLS_LEV_VERIFY:
diff --git a/src/smtpd/Makefile.in b/src/smtpd/Makefile.in
index 7fdfe12..c8837fe 100644
--- a/src/smtpd/Makefile.in
+++ b/src/smtpd/Makefile.in
@@ -75,7 +75,8 @@ broken-tests: smtpd_check_test smtpd_check_test2
tests: smtpd_acl_test smtpd_addr_valid_test smtpd_exp_test \
smtpd_token_test smtpd_check_test4 smtpd_check_dsn_test \
smtpd_check_backup_test smtpd_dnswl_test smtpd_error_test \
- smtpd_server_test smtpd_nullmx_test smtpd_dns_filter_test
+ smtpd_server_test smtpd_nullmx_test smtpd_dns_filter_test \
+ smtpd_deprecated_test
root_tests:
@@ -114,7 +115,8 @@ smtpd_addr_valid_test: smtpd_check smtpd_addr_valid.in smtpd_addr_valid.ref
# This requires that the DNS server can query porcupine.org.
-ADDRINFO_FIX = sed 's/No address associated with hostname/hostname nor servname provided, or not known/'
+ADDRINFO_FIX = sed -e 's/No address associated with hostname/hostname nor servname provided, or not known/' \
+ -e 's/Name or service not known/hostname nor servname provided, or not known/'
smtpd_exp_test: smtpd_check smtpd_exp.in smtpd_exp.ref
$(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access
@@ -170,6 +172,11 @@ smtpd_error_test: smtpd_check smtpd_error.in smtpd_error.ref
diff smtpd_error.ref smtpd_check.tmp
rm -f smtpd_check.tmp
+smtpd_deprecated_test: smtpd_check smtpd_deprecated.in smtpd_deprecated.ref
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_check <smtpd_deprecated.in >smtpd_check.tmp 2>&1
+ diff smtpd_deprecated.ref smtpd_check.tmp
+ rm -f smtpd_check.tmp
+
depend: $(MAKES)
(sed '1,/^# do not edit/!d' Makefile.in; \
set -e; for i in [a-z][a-z0-9]*.c; do \
diff --git a/src/smtpd/smtpd.c b/src/smtpd/smtpd.c
index 6a2cf01..bce0d43 100644
--- a/src/smtpd/smtpd.c
+++ b/src/smtpd/smtpd.c
@@ -468,7 +468,7 @@
/* \fBcheck_ccert_access\fR and \fBpermit_tls_clientcerts\fR.
/* .PP
/* Available in Postfix version 2.6 and later:
-/* .IP "\fBsmtpd_tls_protocols (see postconf -d output)\fR"
+/* .IP "\fBsmtpd_tls_protocols (see 'postconf -d' output)\fR"
/* TLS protocols accepted by the Postfix SMTP server with opportunistic
/* TLS encryption.
/* .IP "\fBsmtpd_tls_ciphers (medium)\fR"
@@ -537,6 +537,12 @@
/* .IP "\fBtls_config_name (empty)\fR"
/* The application name passed by Postfix to OpenSSL library
/* initialization functions.
+/* .PP
+/* Available in Postfix version 3.9 and later:
+/* .IP "\fBsmtpd_tls_enable_rpk (no)\fR"
+/* Request that remote SMTP clients send an RFC7250 raw public key
+/* instead of an X.509 certificate, when asking for or requiring client
+/* authentication.
/* OBSOLETE STARTTLS CONTROLS
/* .ad
/* .fi
@@ -662,12 +668,12 @@
/* The list of domains that are delivered via the $local_transport
/* mail delivery transport.
/* .IP "\fBinet_interfaces (all)\fR"
-/* The local network interface addresses that this mail system receives
-/* mail on.
+/* The local network interface addresses that this mail system
+/* receives mail on.
/* .IP "\fBproxy_interfaces (empty)\fR"
/* The remote network interface addresses that this mail system receives mail
/* on by way of a proxy or network address translation unit.
-/* .IP "\fBinet_protocols (see 'postconf -d output')\fR"
+/* .IP "\fBinet_protocols (see 'postconf -d' output)\fR"
/* The Internet protocols Postfix will attempt to use when making
/* or accepting connections.
/* .IP "\fBlocal_recipient_maps (proxy:unix:passwd.byname $alias_maps)\fR"
@@ -698,8 +704,9 @@
/* alias domains, that is, domains for which all addresses are aliased
/* to addresses in other local or remote domains.
/* .IP "\fBvirtual_alias_maps ($virtual_maps)\fR"
-/* Optional lookup tables that alias specific mail addresses or domains
-/* to other local or remote addresses.
+/* Optional lookup tables with aliases that apply to all recipients:
+/* \fBlocal\fR(8), virtual, and remote; this is unlike alias_maps that apply
+/* only to \fBlocal\fR(8) recipients.
/* .IP "\fBunknown_virtual_alias_reject_code (550)\fR"
/* The Postfix SMTP server reply code when a recipient address matches
/* $virtual_alias_domains, and $virtual_alias_maps specifies a list
@@ -817,7 +824,7 @@
/* command pipelining constraints.
/* .PP
/* Available in Postfix 3.9, 3.8.4, 3.7.9, 3.6.13, 3.5.23 and later:
-/* .IP "\fBsmtpd_forbid_bare_newline (Postfix < 3.9: no)\fR"
+/* .IP "\fBsmtpd_forbid_bare_newline (Postfix >= 3.9: normalize)\fR"
/* Reject or restrict input lines from an SMTP client that end in
/* <LF> instead of the standard <CR><LF>.
/* .IP "\fBsmtpd_forbid_bare_newline_exclusions ($mynetworks)\fR"
@@ -1492,6 +1499,7 @@ char *var_smtpd_tls_eecdh;
char *var_smtpd_tls_eccert_file;
char *var_smtpd_tls_eckey_file;
char *var_smtpd_tls_chain_files;
+int var_smtpd_tls_enable_rpk;
#endif
@@ -1664,13 +1672,16 @@ int smtpd_hfrom_format;
*/
#define BARE_LF_FLAG_WANT_STD_EOD (1<<0) /* Require CRLF.CRLF */
#define BARE_LF_FLAG_REPLY_REJECT (1<<1) /* Reject bare newline */
+#define BARE_LF_FLAG_NOTE_LOG (1<<2) /* Note bare newline */
#define IS_BARE_LF_WANT_STD_EOD(m) ((m) & BARE_LF_FLAG_WANT_STD_EOD)
#define IS_BARE_LF_REPLY_REJECT(m) ((m) & BARE_LF_FLAG_REPLY_REJECT)
+#define IS_BARE_LF_NOTE_LOG(m) ((m) & BARE_LF_FLAG_NOTE_LOG)
static const NAME_CODE bare_lf_mask_table[] = {
"normalize", BARE_LF_FLAG_WANT_STD_EOD, /* Default */
"yes", BARE_LF_FLAG_WANT_STD_EOD, /* Migration aid */
+ "note", BARE_LF_FLAG_WANT_STD_EOD | BARE_LF_FLAG_NOTE_LOG,
"reject", BARE_LF_FLAG_WANT_STD_EOD | BARE_LF_FLAG_REPLY_REJECT,
"no", 0,
0, -1, /* error */
@@ -3504,11 +3515,15 @@ static void common_pre_message_handling(SMTPD_STATE *state,
}
if (state->tls_context->srvr_sig_curve
&& *state->tls_context->srvr_sig_curve)
- vstring_sprintf_append(state->buffer, " (%s)",
- state->tls_context->srvr_sig_curve);
+ vstring_sprintf_append(state->buffer, " (%s%s)",
+ state->tls_context->srvr_sig_curve,
+ state->tls_context->stoc_rpk ?
+ " raw public key" : "");
else if (state->tls_context->srvr_sig_bits > 0)
- vstring_sprintf_append(state->buffer, " (%d bits)",
- state->tls_context->srvr_sig_bits);
+ vstring_sprintf_append(state->buffer, " (%d bit%s)",
+ state->tls_context->srvr_sig_bits,
+ state->tls_context->stoc_rpk ?
+ " raw public key" : "s");
if (state->tls_context->srvr_sig_dgst
&& *state->tls_context->srvr_sig_dgst)
vstring_sprintf_append(state->buffer, " server-digest %s",
@@ -3522,11 +3537,15 @@ static void common_pre_message_handling(SMTPD_STATE *state,
state->tls_context->clnt_sig_name);
if (state->tls_context->clnt_sig_curve
&& *state->tls_context->clnt_sig_curve)
- vstring_sprintf_append(state->buffer, " (%s)",
- state->tls_context->clnt_sig_curve);
+ vstring_sprintf_append(state->buffer, " (%s%s)",
+ state->tls_context->clnt_sig_curve,
+ state->tls_context->ctos_rpk ?
+ " raw public key" : "");
else if (state->tls_context->clnt_sig_bits > 0)
- vstring_sprintf_append(state->buffer, " (%d bits)",
- state->tls_context->clnt_sig_bits);
+ vstring_sprintf_append(state->buffer, " (%d bit%s)",
+ state->tls_context->clnt_sig_bits,
+ state->tls_context->ctos_rpk ?
+ " raw public key" : "s");
if (state->tls_context->clnt_sig_dgst
&& *state->tls_context->clnt_sig_dgst)
vstring_sprintf_append(state->buffer, " client-digest %s",
@@ -3546,6 +3565,11 @@ static void common_pre_message_handling(SMTPD_STATE *state,
"verified OK" : "not verified");
vstring_free(issuer_CN);
vstring_free(peer_CN);
+ } else if (TLS_RPK_IS_PRESENT(state->tls_context)) {
+ out_fprintf(out_stream, REC_TYPE_NORM,
+ "\t(Client RPK %s digest %s)",
+ var_smtpd_tls_fpt_dgst,
+ state->tls_context->peer_pkey_fprint);
} else if (var_smtpd_tls_ask_ccert)
out_fprintf(out_stream, REC_TYPE_NORM,
"\t(Client did not present a certificate)");
@@ -3648,6 +3672,8 @@ static void receive_data_message(SMTPD_STATE *state,
curr_rec_type = REC_TYPE_CONT;
if (IS_BARE_LF_REPLY_REJECT(smtp_got_bare_lf))
state->err |= CLEANUP_STAT_BARE_LF;
+ else if (IS_BARE_LF_NOTE_LOG(smtp_got_bare_lf))
+ state->notes |= SMTPD_NOTE_BARE_LF;
start = vstring_str(state->buffer);
len = VSTRING_LEN(state->buffer);
if (first) {
@@ -4168,6 +4194,8 @@ static int bdat_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
}
if (IS_BARE_LF_REPLY_REJECT(smtp_got_bare_lf))
state->err |= CLEANUP_STAT_BARE_LF;
+ else if (IS_BARE_LF_NOTE_LOG(smtp_got_bare_lf))
+ state->notes |= SMTPD_NOTE_BARE_LF;
start = vstring_str(state->bdat_get_buffer);
len = VSTRING_LEN(state->bdat_get_buffer);
if (state->err == CLEANUP_STAT_OK) {
@@ -5231,6 +5259,7 @@ static void smtpd_start_tls(SMTPD_STATE *state)
stream = state->client,
fd = -1,
timeout = var_smtpd_starttls_tmout,
+ enable_rpk = var_smtpd_tls_enable_rpk,
requirecert = requirecert,
serverid = state->service,
namaddr = state->namaddr,
@@ -5469,8 +5498,6 @@ static void tls_reset(SMTPD_STATE *state)
#endif
-#if !defined(USE_TLS) || !defined(USE_SASL_AUTH)
-
/* unimpl_cmd - dummy for functionality that is not compiled in */
static int unimpl_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
@@ -5487,8 +5514,6 @@ static int unimpl_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
return (-1);
}
-#endif
-
/*
* The table of all SMTP commands that we know. Set the junk limit flag on
* any command that can be repeated an arbitrary number of times without
@@ -5513,6 +5538,8 @@ typedef struct SMTPD_CMD {
#define SMTPD_CMD_FLAG_PRE_TLS (1<<1) /* allow before STARTTLS */
#define SMTPD_CMD_FLAG_LAST (1<<2) /* last in PIPELINING command group */
+static int help_cmd(SMTPD_STATE *, int, SMTPD_TOKEN *);
+
static SMTPD_CMD smtpd_cmd_table[] = {
{SMTPD_CMD_HELO, helo_cmd, SMTPD_CMD_FLAG_LIMIT | SMTPD_CMD_FLAG_PRE_TLS | SMTPD_CMD_FLAG_LAST,},
{SMTPD_CMD_EHLO, ehlo_cmd, SMTPD_CMD_FLAG_LIMIT | SMTPD_CMD_FLAG_PRE_TLS | SMTPD_CMD_FLAG_LAST,},
@@ -5537,12 +5564,44 @@ static SMTPD_CMD smtpd_cmd_table[] = {
{SMTPD_CMD_VRFY, vrfy_cmd, SMTPD_CMD_FLAG_LIMIT | SMTPD_CMD_FLAG_LAST,},
{SMTPD_CMD_ETRN, etrn_cmd, SMTPD_CMD_FLAG_LIMIT,},
{SMTPD_CMD_QUIT, quit_cmd, SMTPD_CMD_FLAG_PRE_TLS,},
+ {SMTPD_CMD_HELP, help_cmd, SMTPD_CMD_FLAG_PRE_TLS,},
{0,},
};
static STRING_LIST *smtpd_noop_cmds;
static STRING_LIST *smtpd_forbid_cmds;
+/* help_cmd - process HELP command */
+
+static int help_cmd(SMTPD_STATE *state, int unused_argc, SMTPD_TOKEN *unused_argv)
+{
+ ARGV *argv = argv_alloc(sizeof(smtpd_cmd_table)
+ / sizeof(*smtpd_cmd_table));
+ VSTRING *buf = vstring_alloc(100);
+ SMTPD_CMD *cmdp;
+
+ /*
+ * Return a list of implemented commands.
+ *
+ * The HELP command does not suppress commands that can be dynamically
+ * disabled in the EHLO response or through access control. That would
+ * require refactoring the EHLO feature-suppression and per-feature
+ * access control, so that they can be reused (not duplicated) here.
+ *
+ * The HELP command does not provide information that makes Postfix easier
+ * to fingerprint, such as software name, version, or build information.
+ */
+ for (cmdp = smtpd_cmd_table; cmdp->name != 0; cmdp++)
+ if (cmdp->action != unimpl_cmd)
+ argv_add(argv, cmdp->name, ARGV_END);
+ argv_sort(argv);
+ smtpd_chat_reply(state, "214 2.0.0 Commands: %s",
+ argv_join(buf, argv, ' '));
+ vstring_free(buf);
+ argv_free(argv);
+ return (0);
+}
+
/* smtpd_flag_ill_pipelining - flag pipelining protocol violation */
static int smtpd_flag_ill_pipelining(SMTPD_STATE *state)
@@ -5869,9 +5928,11 @@ static void smtpd_proto(SMTPD_STATE *state)
var_smtpd_forbid_bare_lf_code, var_myhostname);
break;
}
+ if (IS_BARE_LF_NOTE_LOG(smtp_got_bare_lf))
+ state->notes |= SMTPD_NOTE_BARE_LF;
/* Safety: protect internal interfaces against malformed UTF-8. */
- if (var_smtputf8_enable && valid_utf8_string(STR(state->buffer),
- LEN(state->buffer)) == 0) {
+ if (var_smtputf8_enable
+ && valid_utf8_stringz(STR(state->buffer)) == 0) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "500 5.5.2 Error: bad UTF-8 syntax");
state->error_count++;
@@ -6055,11 +6116,12 @@ static void smtpd_proto(SMTPD_STATE *state)
/* smtpd_format_cmd_stats - format per-command statistics */
-static char *smtpd_format_cmd_stats(VSTRING *buf)
+static char *smtpd_format_cmd_stats(SMTPD_STATE *state)
{
SMTPD_CMD *cmdp;
int all_success = 0;
int all_total = 0;
+ VSTRING *buf = state->buffer;
/*
* Log the statistics. Note that this loop produces no output when no
@@ -6103,6 +6165,13 @@ static char *smtpd_format_cmd_stats(VSTRING *buf)
vstring_sprintf_append(buf, " commands=%d", all_success);
if (all_success != all_total || all_total == 0)
vstring_sprintf_append(buf, "/%d", all_total);
+
+ /*
+ * Log aggregated warnings.
+ */
+ if (state->notes & SMTPD_NOTE_BARE_LF)
+ vstring_sprintf_append(buf, " notes=bare_lf");
+
return (lowercase(STR(buf)));
}
@@ -6244,7 +6313,7 @@ static void smtpd_service(VSTREAM *stream, char *service, char **argv)
* connection time.
*/
msg_info("disconnect from %s%s", state.namaddr,
- smtpd_format_cmd_stats(state.buffer));
+ smtpd_format_cmd_stats(&state));
teardown_milters(&state); /* duplicates xclient_cmd */
smtpd_state_reset(&state);
debug_peer_restore();
@@ -6403,7 +6472,6 @@ static void pre_jail_init(char *unused_name, char **unused_argv)
no_server_cert_ok = 0;
cert_file = var_smtpd_tls_cert_file;
}
-
have_server_cert = *cert_file != 0;
have_server_cert |= *var_smtpd_tls_eccert_file != 0;
have_server_cert |= *var_smtpd_tls_dcert_file != 0;
@@ -6653,6 +6721,7 @@ int main(int argc, char **argv)
#ifdef USE_TLS
VAR_SMTPD_TLS_ACERT, DEF_SMTPD_TLS_ACERT, &var_smtpd_tls_ask_ccert,
VAR_SMTPD_TLS_RCERT, DEF_SMTPD_TLS_RCERT, &var_smtpd_tls_req_ccert,
+ VAR_SMTPD_TLS_ENABLE_RPK, DEF_SMTPD_TLS_ENABLE_RPK, &var_smtpd_tls_enable_rpk,
VAR_SMTPD_TLS_RECHEAD, DEF_SMTPD_TLS_RECHEAD, &var_smtpd_tls_received_header,
VAR_SMTPD_TLS_SET_SESSID, DEF_SMTPD_TLS_SET_SESSID, &var_smtpd_tls_set_sessid,
#endif
diff --git a/src/smtpd/smtpd.h b/src/smtpd/smtpd.h
index 56ebc07..c049194 100644
--- a/src/smtpd/smtpd.h
+++ b/src/smtpd/smtpd.h
@@ -114,6 +114,7 @@ typedef struct {
int junk_cmds; /* counter */
int rcpt_overshoot; /* counter */
char *rewrite_context; /* address rewriting context */
+ int notes; /* notes aggregator */
/*
* SASL specific.
@@ -209,6 +210,8 @@ typedef struct {
#define SMTPD_FLAG_SMTPUTF8 (1<<3) /* RFC 6531/2 transaction */
#define SMTPD_FLAG_NEED_MILTER_ABORT (1<<4) /* undo milter_mail_event() */
+#define SMTPD_NOTE_BARE_LF (1<<0) /* saw at least one bare LF */
+
/* Security: don't reset SMTPD_FLAG_AUTH_USED. */
#define SMTPD_MASK_MAIL_KEEP \
~(SMTPD_FLAG_SMTPUTF8) /* Fix 20140706 */
@@ -260,6 +263,7 @@ extern void smtpd_state_reset(SMTPD_STATE *);
#define SMTPD_CMD_XCLIENT "XCLIENT"
#define SMTPD_CMD_XFORWARD "XFORWARD"
#define SMTPD_CMD_UNKNOWN "UNKNOWN"
+#define SMTPD_CMD_HELP "HELP"
/*
* Representation of unknown and non-existent client information. Throughout
diff --git a/src/smtpd/smtpd_check.c b/src/smtpd/smtpd_check.c
index 093aa06..6aeda74 100644
--- a/src/smtpd/smtpd_check.c
+++ b/src/smtpd/smtpd_check.c
@@ -1598,6 +1598,7 @@ static int permit_auth_destination(SMTPD_STATE *state, char *recipient);
static int permit_tls_clientcerts(SMTPD_STATE *state, int permit_all_certs)
{
#ifdef USE_TLS
+ const char *myname = "permit_tls_clientcerts";
const char *found = 0;
if (!state->tls_context)
@@ -1612,9 +1613,9 @@ static int permit_tls_clientcerts(SMTPD_STATE *state, int permit_all_certs)
/*
* When directly checking the fingerprint, it is OK if the issuing CA is
- * not trusted.
+ * not trusted. Raw public keys are also acceptable.
*/
- if (TLS_CERT_IS_PRESENT(state->tls_context)) {
+ if (TLS_CRED_IS_PRESENT(state->tls_context)) {
int i;
char *prints[2];
@@ -1623,12 +1624,24 @@ static int permit_tls_clientcerts(SMTPD_STATE *state, int permit_all_certs)
VAR_SMTPD_TLS_FPT_DGST "=md5 to compute certificate "
"fingerprints");
- prints[0] = state->tls_context->peer_cert_fprint;
- prints[1] = state->tls_context->peer_pkey_fprint;
+ prints[0] = state->tls_context->peer_pkey_fprint;
+ prints[1] = state->tls_context->peer_cert_fprint;
/* After lookup error, leave relay_ccerts->error at non-zero value. */
for (i = 0; i < 2; ++i) {
+ /* With RFC7250 RPK, no certificate may be available */
+ if (!*prints[i])
+ continue;
found = maps_find(relay_ccerts, prints[i], DICT_FLAG_NONE);
+ if (var_smtpd_tls_enable_rpk && i > 0 && found) {
+ msg_warn("%s: %s: %s: Fragile access policy: %s=yes, but"
+ " public key fingerprint \"%s\" not matched, while"
+ " certificate fingerprint \"%s\" matched",
+ myname, state->namaddr, relay_ccerts->title,
+ VAR_SMTPD_TLS_ENABLE_RPK,
+ state->tls_context->peer_cert_fprint,
+ state->tls_context->peer_pkey_fprint);
+ }
if (found != 0) {
if (msg_verbose)
msg_info("Relaying allowed for certified client: %s", found);
@@ -1659,44 +1672,16 @@ static int check_relay_domains(SMTPD_STATE *state, char *recipient,
{
const char *myname = "check_relay_domains";
-#if 1
- static int once;
-
- if (once == 0) {
- once = 1;
- msg_warn("support for restriction \"%s\" will be removed from %s; "
- "use \"%s\" instead",
- CHECK_RELAY_DOMAINS, var_mail_name, REJECT_UNAUTH_DEST);
- }
-#endif
-
- if (msg_verbose)
- msg_info("%s: %s", myname, recipient);
-
/*
- * Permit if the client matches the relay_domains list.
+ * Restriction check_relay_domains is deprecated as of Postfix 2.2.
*/
- if (domain_list_match(relay_domains, state->name)) {
- if (warn_compat_break_relay_domains)
- msg_info("using backwards-compatible default setting "
- VAR_RELAY_DOMAINS "=$mydestination to permit "
- "request from client \"%s\"", state->name);
- return (SMTPD_CHECK_OK);
- }
-
- /*
- * Permit authorized destinations.
- */
- if (permit_auth_destination(state, recipient) == SMTPD_CHECK_OK)
- return (SMTPD_CHECK_OK);
+ if (msg_verbose)
+ msg_info("%s: %s", myname, recipient);
- /*
- * Deny relaying between sites that both are not in relay_domains.
- */
- return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
- var_relay_code, "5.7.1",
- "<%s>: %s rejected: Relay access denied",
- reply_name, reply_class));
+ msg_warn("support for restriction \"%s\" has been removed in %s 3.9; "
+ "instead, specify \"%s\"",
+ CHECK_RELAY_DOMAINS, var_mail_name, REJECT_UNAUTH_DEST);
+ reject_server_error(state);
}
/* permit_auth_destination - OK for message relaying */
@@ -2002,11 +1987,22 @@ static int permit_mx_backup(SMTPD_STATE *state, const char *recipient,
DNS_RR *middle;
DNS_RR *rest;
int dns_status;
+ static int once;
if (msg_verbose)
msg_info("%s: %s", myname, recipient);
/*
+ * Restriction permit_mx_backup is deprecated as of Postfix 3.9.
+ */
+ if (once == 0) {
+ once = 1;
+ msg_warn("support for restriction \"%s\" will be removed from %s; "
+ "instead, specify \"%s\"",
+ PERMIT_MX_BACKUP, var_mail_name, VAR_RELAY_DOMAINS);
+ }
+
+ /*
* Resolve the address.
*/
reply = smtpd_resolve_addr(state->sender, recipient);
@@ -3185,6 +3181,9 @@ static int check_ccert_access(SMTPD_STATE *state, const char *acl_spec,
#ifdef USE_TLS
const char *myname = "check_ccert_access";
+ int cert_result = SMTPD_CHECK_DUNNO;
+ int pkey_result = SMTPD_CHECK_DUNNO;
+ int *respt;
int found;
const MAP_SEARCH *acl;
const char default_search[] = {
@@ -3211,9 +3210,9 @@ static int check_ccert_access(SMTPD_STATE *state, const char *acl_spec,
/*
* When directly checking the fingerprint, it is OK if the issuing CA is
- * not trusted.
+ * not trusted. Raw public keys are also acceptable.
*/
- if (TLS_CERT_IS_PRESENT(state->tls_context)) {
+ if (TLS_CRED_IS_PRESENT(state->tls_context)) {
const char *action;
const char *match_this;
const char *known_action;
@@ -3222,17 +3221,19 @@ static int check_ccert_access(SMTPD_STATE *state, const char *acl_spec,
switch (*action) {
case SMTPD_ACL_SEARCH_CODE_CERT_FPRINT:
match_this = state->tls_context->peer_cert_fprint;
- if (warn_compat_break_smtpd_tls_fpt_dgst)
+ if (*match_this && warn_compat_break_smtpd_tls_fpt_dgst)
msg_info("using backwards-compatible default setting "
VAR_SMTPD_TLS_FPT_DGST "=md5 to compute "
"certificate fingerprints");
+ respt = &cert_result;
break;
case SMTPD_ACL_SEARCH_CODE_PKEY_FPRINT:
match_this = state->tls_context->peer_pkey_fprint;
- if (warn_compat_break_smtpd_tls_fpt_dgst)
+ if (*match_this && warn_compat_break_smtpd_tls_fpt_dgst)
msg_info("using backwards-compatible default setting "
VAR_SMTPD_TLS_FPT_DGST "=md5 to compute "
- "certificate fingerprints");
+ "public key fingerprints");
+ respt = &pkey_result;
break;
default:
known_action = str_name_code(search_actions, *action);
@@ -3245,6 +3246,9 @@ static int check_ccert_access(SMTPD_STATE *state, const char *acl_spec,
451, "4.3.5",
"Server configuration error"));
}
+ /* With RFC7250 RPK, no certificate may be available */
+ if (!*match_this)
+ continue;
if (msg_verbose)
msg_info("%s: look up %s %s",
myname, str_name_code(search_actions, *action),
@@ -3257,11 +3261,16 @@ static int check_ccert_access(SMTPD_STATE *state, const char *acl_spec,
* "reject" event. XXX Should log the thing that is rejected
* (fingerprint etc.) or would that give away too much?
*/
- result = check_access(state, acl->map_type_name, match_this,
+ *respt = check_access(state, acl->map_type_name, match_this,
DICT_FLAG_NONE, &found,
state->tls_context->peer_CN,
SMTPD_NAME_CCERT, def_acl);
- if (result != SMTPD_CHECK_DUNNO)
+ if (*respt == SMTPD_CHECK_DUNNO)
+ continue;
+ if (result == SMTPD_CHECK_DUNNO)
+ result = *respt;
+ if (!var_smtpd_tls_enable_rpk
+ || *action == SMTPD_ACL_SEARCH_CODE_PKEY_FPRINT)
break;
}
} else if (!var_smtpd_tls_ask_ccert) {
@@ -3271,6 +3280,17 @@ static int check_ccert_access(SMTPD_STATE *state, const char *acl_spec,
if (msg_verbose)
msg_info("%s: no client certificate", myname);
}
+ if (var_smtpd_tls_enable_rpk
+ && cert_result != SMTPD_CHECK_DUNNO
+ && cert_result != pkey_result) {
+ msg_warn("%s: %s: %s: Fragile access policy: %s=yes, but"
+ " the action for certificate fingerprint \"%s\" !="
+ " the action for public key fingerprint \"%s\"",
+ myname, state->namaddr, acl->map_type_name,
+ VAR_SMTPD_TLS_ENABLE_RPK,
+ state->tls_context->peer_cert_fprint,
+ state->tls_context->peer_pkey_fprint);
+ }
#endif
return (result);
}
@@ -3877,34 +3897,18 @@ static int permit_dnswl_domain(SMTPD_STATE *state, const char *dnswl_domain,
static int reject_maps_rbl(SMTPD_STATE *state)
{
const char *myname = "reject_maps_rbl";
- char *saved_domains = mystrdup(var_maps_rbl_domains);
- char *bp = saved_domains;
- char *rbl_domain;
- int result = SMTPD_CHECK_DUNNO;
- static int warned;
if (msg_verbose)
msg_info("%s: %s", myname, state->addr);
- if (warned == 0) {
- warned++;
- msg_warn("support for restriction \"%s\" will be removed from %s; "
- "use \"%s domain-name\" instead",
- REJECT_MAPS_RBL, var_mail_name, REJECT_RBL_CLIENT);
- }
- while ((rbl_domain = mystrtok(&bp, CHARS_COMMA_SP)) != 0) {
- result = reject_rbl_addr(state, rbl_domain, state->addr,
- SMTPD_NAME_CLIENT);
- if (result != SMTPD_CHECK_DUNNO)
- break;
- }
-
/*
- * Clean up.
+ * Restriction reject_maps_rbl is deprecated as of Postfix 2.1.
*/
- myfree(saved_domains);
+ msg_warn("support for restriction \"%s\" has been removed in %s 3.9; "
+ "instead, specify \"%s domain-name\"",
+ REJECT_MAPS_RBL, var_mail_name, REJECT_RBL_CLIENT);
- return (result);
+ reject_server_error(state);
}
#ifdef USE_SASL_AUTH
@@ -3980,7 +3984,7 @@ static int valid_utf8_action(const char *server, const char *action)
{
int retval;
- if ((retval = valid_utf8_string(action, strlen(action))) == 0)
+ if ((retval = valid_utf8_stringz(action)) == 0)
msg_warn("malformed UTF-8 in policy server %s response: \"%s\"",
server, action);
return (retval);
@@ -4035,6 +4039,8 @@ static int check_policy_service(SMTPD_STATE *state, const char *server,
ENCODE_CN(subject, subject_buf, state->tls_context->peer_CN);
ENCODE_CN(issuer, issuer_buf, state->tls_context->issuer_CN);
+#define NONEMPTY(x) ((x) != 0 && (*x) != 0)
+
/*
* XXX: Too noisy to warn for each policy lookup, especially because we
* don't even know whether the policy server will use the fingerprint. So
@@ -4044,12 +4050,12 @@ static int check_policy_service(SMTPD_STATE *state, const char *server,
if (!warned
&& warn_compat_break_smtpd_tls_fpt_dgst
&& state->tls_context
- && state->tls_context->peer_cert_fprint
- && *state->tls_context->peer_cert_fprint) {
+ && (NONEMPTY(state->tls_context->peer_cert_fprint)
+ || NONEMPTY(state->tls_context->peer_pkey_fprint))) {
warned = 1;
msg_info("using backwards-compatible default setting "
VAR_SMTPD_TLS_FPT_DGST "=md5 to compute certificate "
- "fingerprints");
+ "and public key fingerprints");
}
#endif
@@ -4480,15 +4486,12 @@ static int generic_checks(SMTPD_STATE *state, ARGV *restrictions,
state->helo_name, SMTPD_NAME_HELO);
}
} else if (strcasecmp(name, PERMIT_NAKED_IP_ADDR) == 0) {
- msg_warn("restriction %s is deprecated. Use %s or %s instead",
- PERMIT_NAKED_IP_ADDR, PERMIT_MYNETWORKS, PERMIT_SASL_AUTH);
- if (state->helo_name) {
- if (state->helo_name[strspn(state->helo_name, "0123456789.:")] == 0
- && (status = reject_invalid_hostaddr(state, state->helo_name,
- state->helo_name, SMTPD_NAME_HELO)) == 0)
- status = smtpd_acl_permit(state, name, SMTPD_NAME_HELO,
- state->helo_name, NO_PRINT_ARGS);
- }
+ /* permit_naked_ip_addr is deprecated as of Postfix 2.0. */
+ msg_warn("support for restriction \"%s\" has been removed in %s"
+ " 3.9; instead, specify \"%s\" or \"%s\"",
+ PERMIT_NAKED_IP_ADDR, var_mail_name,
+ PERMIT_MYNETWORKS, PERMIT_SASL_AUTH);
+ reject_server_error(state);
} else if (is_map_command(state, name, CHECK_HELO_NS_ACL, &cpp)) {
if (state->helo_name) {
status = check_server_access(state, *cpp, state->helo_name,
@@ -5255,8 +5258,9 @@ static int check_recipient_rcpt_maps(SMTPD_STATE *state, const char *recipient)
{
/*
- * Duplicate suppression. There's an implicit check_recipient_maps
- * restriction at the end of all recipient restrictions.
+ * Duplicate suppression. With "smtpd_reject_unlisted_recipient = yes",
+ * there's an implicit reject_unlisted_recipient restriction at the end
+ * of all recipient restrictions.
*/
if (smtpd_input_transp_mask & INPUT_TRANSP_UNKNOWN_RCPT)
return (0);
@@ -5275,8 +5279,9 @@ static int check_sender_rcpt_maps(SMTPD_STATE *state, const char *sender)
{
/*
- * Duplicate suppression. There's an implicit check_sender_maps
- * restriction at the end of all sender restrictions.
+ * Duplicate suppression. With "smtpd_reject_unlisted_sender = yes",
+ * there's an implicit reject_unlisted_sender restriction at the end of
+ * all sender restrictions.
*/
if (smtpd_input_transp_mask & INPUT_TRANSP_UNKNOWN_RCPT)
return (0);
@@ -5832,6 +5837,7 @@ char *var_smtpd_dns_re_filter;
bool var_smtpd_tls_ask_ccert;
int var_smtpd_cipv4_prefix;
int var_smtpd_cipv6_prefix;
+bool var_smtpd_tls_enable_rpk;
#define int_table test_int_table
@@ -5869,6 +5875,7 @@ static const INT_TABLE int_table[] = {
VAR_SMTPD_TLS_ACERT, DEF_SMTPD_TLS_ACERT, &var_smtpd_tls_ask_ccert,
VAR_SMTPD_CIPV4_PREFIX, DEF_SMTPD_CIPV4_PREFIX, &var_smtpd_cipv4_prefix,
VAR_SMTPD_CIPV6_PREFIX, DEF_SMTPD_CIPV6_PREFIX, &var_smtpd_cipv6_prefix,
+ VAR_SMTPD_TLS_ENABLE_RPK, DEF_SMTPD_TLS_ENABLE_RPK, &var_smtpd_tls_enable_rpk,
0,
};
@@ -6406,7 +6413,7 @@ int main(int argc, char **argv)
state.tls_context->peer_cert_fprint =
state.tls_context->peer_pkey_fprint = 0;
}
- state.tls_context->peer_status |= TLS_CERT_FLAG_PRESENT;
+ state.tls_context->peer_status |= TLS_CRED_FLAG_CERT;
UPDATE_STRING(state.tls_context->peer_cert_fprint,
args->argv[1]);
state.tls_context->peer_pkey_fprint =
diff --git a/src/smtpd/smtpd_check_backup.ref b/src/smtpd/smtpd_check_backup.ref
index 8f4a0f2..c15be35 100644
--- a/src/smtpd/smtpd_check_backup.ref
+++ b/src/smtpd/smtpd_check_backup.ref
@@ -17,6 +17,7 @@ OK
>>> recipient_restrictions permit_mx_backup,reject
OK
>>> rcpt wietse@wzv.porcupine.org
+./smtpd_check: warning: support for restriction "permit_mx_backup" will be removed from Postfix; instead, use "relay_domains"
OK
>>> rcpt wietse@backup.porcupine.org
OK
diff --git a/src/smtpd/smtpd_deprecated.in b/src/smtpd/smtpd_deprecated.in
new file mode 100644
index 0000000..345ee71
--- /dev/null
+++ b/src/smtpd/smtpd_deprecated.in
@@ -0,0 +1,20 @@
+#
+# permit_naked_ip_address
+#
+client foo 127.0.0.2
+recipient_restrictions permit_naked_ip_address
+helo 127.0.0.2
+mail sname@sdomain.example
+rcpt rname@rdomain.example
+#
+# check_relay_domains
+#
+client foo 127.0.0.2
+recipient_restrictions check_relay_domains
+relay_domains foo
+helo 127.0.0.2
+mail sname@sdomain.example
+rcpt rname@rdomain.example
+#
+# reject_maps_rbl is already covered elsewhere.
+#
diff --git a/src/smtpd/smtpd_deprecated.ref b/src/smtpd/smtpd_deprecated.ref
new file mode 100644
index 0000000..d64f1b3
--- /dev/null
+++ b/src/smtpd/smtpd_deprecated.ref
@@ -0,0 +1,35 @@
+>>> #
+>>> # permit_naked_ip_address
+>>> #
+>>> client foo 127.0.0.2
+OK
+>>> recipient_restrictions permit_naked_ip_address
+OK
+>>> helo 127.0.0.2
+OK
+>>> mail sname@sdomain.example
+OK
+>>> rcpt rname@rdomain.example
+./smtpd_check: warning: restriction permit_naked_ip_address has been removed in Postfix 3.9; use permit_mynetworks or permit_sasl_authenticated instead
+./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 451 4.3.5 Server configuration error; from=<sname@sdomain.example> to=<rname@rdomain.example> proto=SMTP helo=<127.0.0.2>
+451 4.3.5 Server configuration error
+>>> #
+>>> # check_relay_domains
+>>> #
+>>> client foo 127.0.0.2
+OK
+>>> recipient_restrictions check_relay_domains
+OK
+>>> relay_domains foo
+OK
+>>> helo 127.0.0.2
+OK
+>>> mail sname@sdomain.example
+OK
+>>> rcpt rname@rdomain.example
+./smtpd_check: warning: support for restriction "check_relay_domains" has been removed in Postfix 3.9; use "reject_unauth_destination" instead
+./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 451 4.3.5 Server configuration error; from=<sname@sdomain.example> to=<rname@rdomain.example> proto=SMTP helo=<127.0.0.2>
+451 4.3.5 Server configuration error
+>>> #
+>>> # reject_maps_rbl is already covered elsewhere.
+>>> #
diff --git a/src/smtpd/smtpd_exp.ref b/src/smtpd/smtpd_exp.ref
index 22c027e..00848a5 100644
--- a/src/smtpd/smtpd_exp.ref
+++ b/src/smtpd/smtpd_exp.ref
@@ -25,13 +25,15 @@ OK
>>> client spike.porcupine.org 168.100.3.2
OK
>>> rcpt rname@rdomain
-./smtpd_check: warning: support for restriction "reject_maps_rbl" will be removed from Postfix; use "reject_rbl_client domain-name" instead
-OK
+./smtpd_check: warning: support for restriction "reject_maps_rbl" has been removed in Postfix 3.9; use "reject_rbl_client domain-name" instead
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.3.2]: 451 4.3.5 Server configuration error; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<foobar>
+451 4.3.5 Server configuration error
>>> client foo 127.0.0.2
OK
>>> rcpt rname@rdomain
-./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<foobar>
-554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test
+./smtpd_check: warning: support for restriction "reject_maps_rbl" has been removed in Postfix 3.9; use "reject_rbl_client domain-name" instead
+./smtpd_check: <queue id>: reject: RCPT from foo[127.0.0.2]: 451 4.3.5 Server configuration error; from=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<foobar>
+451 4.3.5 Server configuration error
>>> #
>>> recipient_restrictions reject_rbl_client,dnsbltest.porcupine.org
OK
diff --git a/src/smtpd/smtpd_sasl_glue.c b/src/smtpd/smtpd_sasl_glue.c
index d9db7b0..1163366 100644
--- a/src/smtpd/smtpd_sasl_glue.c
+++ b/src/smtpd/smtpd_sasl_glue.c
@@ -120,6 +120,10 @@
/* Google, Inc.
/* 111 8th Avenue
/* New York, NY 10011, USA
+/*
+/* Wietse Venema
+/* porcupine.org
+/* Amawalk, NY 10501, USA
/*--*/
/* System library. */
diff --git a/src/smtpd/smtpd_state.c b/src/smtpd/smtpd_state.c
index f2f5f89..fefc543 100644
--- a/src/smtpd/smtpd_state.c
+++ b/src/smtpd/smtpd_state.c
@@ -135,6 +135,7 @@ void smtpd_state_init(SMTPD_STATE *state, VSTREAM *stream,
state->instance = vstring_alloc(10);
state->seqno = 0;
state->rewrite_context = 0;
+ state->notes = 0;
#if 0
state->ehlo_discard_mask = ~0;
#else
diff --git a/src/smtpstone/smtp-source.c b/src/smtpstone/smtp-source.c
index be388d6..f9fa64f 100644
--- a/src/smtpstone/smtp-source.c
+++ b/src/smtpstone/smtp-source.c
@@ -42,7 +42,7 @@
/* Don't disconnect after sending a message; send the next
/* message over the same connection.
/* .IP "\fB-f \fIfrom\fR"
-/* Use the specified sender address (default: <foo@myhostname>).
+/* Use the specified sender address (default: <foo@my-hostname>).
/* .IP "\fB-F \fIfile\fR"
/* Send the pre-formatted message header and body in the
/* specified \fIfile\fR, while prepending '.' before lines that
@@ -54,31 +54,49 @@
/* Speak LMTP rather than SMTP.
/* .IP "\fB-m \fImessage_count\fR"
/* Send the specified number of messages (default: 1).
-/* .IP "\fB-M \fImyhostname\fR"
+/* .IP "\fB-M \fImy-hostname\fR"
/* Use the specified hostname or [address] in the HELO command
/* and in the default sender and recipient addresses, instead
/* of the machine hostname.
/* .IP "\fB-N\fR"
-/* Prepend a non-repeating sequence number to each recipient
-/* address. This avoids the artificial 100% hit rate in the
-/* resolve and rewrite client caches and exercises the
-/* trivial-rewrite daemon, better approximating Postfix
-/* performance under real-life work-loads.
+/* Generate each recipient address by appending a number (a
+/* per-process recipient counter) to the recipient address
+/* localpart specified with the \fB-t\fR option.
+/*
+/* Note: to use the number as an address extension, specify
+/* an explicit address delimiter at the end of the recipient
+/* localpart, as in "\fB-t localpart+@domain\fR" or "\fB-t
+/* localpart+\fR", where "\fB+\fR" is a Postfix recipient
+/* address delimiter.
+/*
+/* Benefits:
+/* .RS
+/* .IP \(bu
+/* A non-constant recipient address avoids an unrealistic 100%
+/* cache hit rate in clients of the Postfix trivial-rewrite
+/* service, better approximating performance under real-life
+/* work-loads.
+/* .IP \(bu
+/* A fixed recipient address local-part with a non-constant
+/* address extension avoids the need to configure a large
+/* number of valid recipient addresses in the receiving Postfix
+/* server.
+/* .RE
/* .IP \fB-o\fR
/* Old mode: don't send HELO, and don't send message headers.
/* .IP "\fB-r \fIrecipient_count\fR"
-/* Send the specified number of recipients per transaction (default: 1).
-/* Recipient names are generated by prepending a number to the
-/* recipient address.
+/* Send the specified number of recipients per transaction
+/* (default: 1), and generate recipient addresses as described
+/* under the \fB-N\fR option.
/* .IP "\fB-R \fIinterval\fR"
-/* Wait for a random period of time 0 <= n <= interval between messages.
+/* Wait a random time (0 <= n <= \fIinterval\fR) between messages.
/* Suspending one thread does not affect other delivery threads.
/* .IP "\fB-s \fIsession_count\fR"
/* Run the specified number of SMTP sessions in parallel (default: 1).
/* .IP "\fB-S \fIsubject\fR"
/* Send mail with the named subject line (default: none).
/* .IP "\fB-t \fIto\fR"
-/* Use the specified recipient address (default: <foo@myhostname>).
+/* Use the specified recipient address (default: <foo@my-hostname>).
/* .IP "\fB-T \fIwindowsize\fR"
/* Override the default TCP window size. To work around
/* broken TCP window scaling implementations, specify a
@@ -172,6 +190,7 @@ typedef struct SESSION {
int rcpt_done; /* # of recipients done */
int rcpt_count; /* # of recipients to go */
int rcpt_accepted; /* # of recipients accepted */
+ int rcpt_sample; /* Sample recipient # for To: header */
VSTREAM *stream; /* open connection */
int connect_count; /* # of connect()s to retry */
struct SESSION *next; /* connect() queue linkage */
@@ -202,7 +221,11 @@ static struct sockaddr *sa;
static int sa_length;
static int recipients = 1;
static char *defaddr;
-static char *recipient;
+typedef struct {
+ char *local;
+ char *at_domain;
+} RECIPIENT;
+static RECIPIENT *recipient;
static char *sender;
static char *message_data;
static int message_length;
@@ -216,7 +239,8 @@ static int random_delay = 0;
static int fixed_delay = 0;
static int talk_lmtp = 0;
static char *subject = 0;
-static int number_rcpts = 0;
+static int global_rcpt_suffix = 0;
+static int global_rcpt_done = 0;
static int allow_reject = 0;
static void enqueue_connect(SESSION *);
@@ -238,6 +262,20 @@ static void send_quit(SESSION *);
static void quit_done(int, void *);
static void close_session(SESSION *);
+/* make_recipient - parse recipient into localpart and at_domain */
+
+static RECIPIENT *make_recipient(const char *address)
+{
+ RECIPIENT *rp = (RECIPIENT *) mymalloc(sizeof(*rp));
+ const char *at;
+
+ if ((at = strrchr(address, '@')) == 0)
+ at = address + strlen(address);
+ rp->local = mystrndup(address, at - address);
+ rp->at_domain = mystrdup(at);
+ return (rp);
+}
+
/* random_interval - generate a random value in 0 .. (small) interval */
static int random_interval(int interval)
@@ -655,12 +693,13 @@ static void send_rcpt(int unused_event, void *context)
if ((except = vstream_setjmp(session->stream)) != 0)
msg_fatal("%s while sending recipient", exception_text(except));
- if (session->rcpt_count > 1 || number_rcpts > 0)
- command(session->stream, "RCPT TO:<%d%s>",
- number_rcpts ? number_rcpts++ : session->rcpt_count,
- recipient);
+ if (global_rcpt_suffix)
+ command(session->stream, "RCPT TO:<%s%d%s>",
+ recipient->local, session->rcpt_sample = global_rcpt_done++,
+ recipient->at_domain);
else
- command(session->stream, "RCPT TO:<%s>", recipient);
+ command(session->stream, "RCPT TO:<%s%s>",
+ recipient->local, recipient->at_domain);
session->rcpt_count--;
session->rcpt_done++;
@@ -765,10 +804,16 @@ static void data_done(int unused, void *context)
mypid = getpid();
}
smtp_printf(session->stream, "From: <%s>", sender);
- smtp_printf(session->stream, "To: <%s>", recipient);
+ if (global_rcpt_suffix)
+ smtp_printf(session->stream, "To: <%s%d%s>", recipient->local,
+ session->rcpt_sample, recipient->at_domain);
+ else
+ smtp_printf(session->stream, "To: <%s%s>",
+ recipient->local, recipient->at_domain);
smtp_printf(session->stream, "Date: %s", mydate);
smtp_printf(session->stream, "Message-Id: <%04x.%04x.%04x@%s>",
- mypid, vstream_fileno(session->stream), message_count, var_myhostname);
+ mypid, vstream_fileno(session->stream), message_count,
+ var_myhostname);
if (subject)
smtp_printf(session->stream, "Subject: %s", subject);
smtp_fputs("", 0, session->stream);
@@ -1021,7 +1066,7 @@ int main(int argc, char **argv)
var_myhostname = optarg;
break;
case 'N':
- number_rcpts = 1;
+ global_rcpt_suffix = 1;
break;
case 'o':
send_helo_first = 0;
@@ -1030,6 +1075,7 @@ int main(int argc, char **argv)
case 'r':
if ((recipients = atoi(optarg)) <= 0)
msg_fatal("bad recipient count: %s", optarg);
+ global_rcpt_suffix = 1;
break;
case 'R':
if (fixed_delay > 0)
@@ -1045,7 +1091,7 @@ int main(int argc, char **argv)
subject = optarg;
break;
case 't':
- recipient = optarg;
+ recipient = make_recipient(optarg);
break;
case 'T':
if ((inet_windowsize = atoi(optarg)) <= 0)
@@ -1160,7 +1206,7 @@ int main(int argc, char **argv)
if (sender == 0)
sender = defaddr;
if (recipient == 0)
- recipient = defaddr;
+ recipient = make_recipient(defaddr);
}
/*
diff --git a/src/tls/tls.h b/src/tls/tls.h
index 73eebae..3ec41ba 100644
--- a/src/tls/tls.h
+++ b/src/tls/tls.h
@@ -78,6 +78,7 @@ extern const char *str_tls_level(int);
#include <openssl/opensslv.h> /* OPENSSL_VERSION_NUMBER */
#include <openssl/ssl.h>
#include <openssl/conf.h>
+#include <openssl/tls1.h> /* TLS extensions */
/* Appease indent(1) */
#define x509_stack_t STACK_OF(X509)
@@ -203,8 +204,8 @@ extern void tls_dane_flush(void);
extern TLS_DANE *tls_dane_alloc(void);
extern void tls_tlsa_free(TLS_TLSA *);
extern void tls_dane_free(TLS_DANE *);
-extern void tls_dane_add_fpt_digests(TLS_DANE *, const char *, const char *,
- int);
+extern void tls_dane_add_fpt_digests(TLS_DANE *, int, const char *,
+ const char *, int);
extern TLS_DANE *tls_dane_resolve(unsigned, const char *, DNS_RR *, int);
extern int tls_dane_load_trustfile(TLS_DANE *, const char *);
@@ -232,6 +233,8 @@ typedef struct {
const char *kex_name; /* shared key-exchange algorithm */
const char *kex_curve; /* shared key-exchange ECDHE curve */
int kex_bits; /* shared FFDHE key exchange bits */
+ int ctos_rpk; /* Did the client send an RPK? */
+ int stoc_rpk; /* Did the server send an RPK? */
const char *clnt_sig_name; /* client's signature key algorithm */
const char *clnt_sig_curve; /* client's ECDSA curve name */
int clnt_sig_bits; /* client's RSA signature key bits */
@@ -264,13 +267,17 @@ typedef struct {
* Peer status bits. TLS_CERT_FLAG_MATCHED implies TLS_CERT_FLAG_TRUSTED
* only in the case of a hostname match.
*/
-#define TLS_CERT_FLAG_PRESENT (1<<0)
+#define TLS_CRED_FLAG_CERT (1<<0)
#define TLS_CERT_FLAG_ALTNAME (1<<1)
#define TLS_CERT_FLAG_TRUSTED (1<<2)
#define TLS_CERT_FLAG_MATCHED (1<<3)
#define TLS_CERT_FLAG_SECURED (1<<4)
+#define TLS_CRED_FLAG_RPK (1<<5)
+#define TLS_CRED_FLAG_ANY (TLS_CRED_FLAG_CERT|TLS_CRED_FLAG_RPK)
-#define TLS_CERT_IS_PRESENT(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_PRESENT))
+#define TLS_CRED_IS_PRESENT(c) ((c) && ((c)->peer_status&TLS_CRED_FLAG_ANY))
+#define TLS_CERT_IS_PRESENT(c) ((c) && ((c)->peer_status&TLS_CRED_FLAG_CERT))
+#define TLS_RPK_IS_PRESENT(c) ((c) && ((c)->peer_status&TLS_CRED_FLAG_RPK))
#define TLS_CERT_IS_ALTNAME(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_ALTNAME))
#define TLS_CERT_IS_TRUSTED(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_TRUSTED))
#define TLS_CERT_IS_MATCHED(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_MATCHED))
@@ -472,6 +479,7 @@ typedef struct {
VSTREAM *stream;
int fd; /* Event-driven file descriptor */
int timeout;
+ int enable_rpk; /* Solicit server raw public keys */
int tls_level; /* Security level */
const char *nexthop; /* destination domain */
const char *host; /* MX hostname */
@@ -508,12 +516,12 @@ extern TLS_SESS_STATE *tls_client_post_connect(TLS_SESS_STATE *,
a6, a7, a8, a9, a10, a11, a12, a13, a14))
#define TLS_CLIENT_START(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \
- a10, a11, a12, a13, a14, a15, a16, a17) \
+ a10, a11, a12, a13, a14, a15, a16, a17, a18) \
tls_client_start((((props)->a1), ((props)->a2), ((props)->a3), \
((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \
((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \
((props)->a12), ((props)->a13), ((props)->a14), ((props)->a15), \
- ((props)->a16), ((props)->a17), (props)))
+ ((props)->a16), ((props)->a17), ((props)->a18), (props)))
/*
* tls_server.c
@@ -546,6 +554,7 @@ typedef struct {
VSTREAM *stream; /* Client stream */
int fd; /* Event-driven file descriptor */
int timeout; /* TLS handshake timeout */
+ int enable_rpk; /* Solicit client raw public keys */
int requirecert; /* Insist on client cert? */
const char *serverid; /* Server instance (salt cache key) */
const char *namaddr; /* Client nam[addr] for logging */
@@ -570,10 +579,12 @@ extern TLS_SESS_STATE *tls_server_post_accept(TLS_SESS_STATE *);
((props)->a16), ((props)->a17), ((props)->a18), ((props)->a19), \
((props)->a20), (props)))
-#define TLS_SERVER_START(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) \
+#define TLS_SERVER_START(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \
+ a10, a11) \
tls_server_start((((props)->a1), ((props)->a2), ((props)->a3), \
((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \
- ((props)->a8), ((props)->a9), ((props)->a10), (props)))
+ ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \
+ (props)))
/*
* tls_session.c
@@ -660,7 +671,7 @@ extern TLS_TLSA *tlsa_prepend(TLS_TLSA *, uint8_t, uint8_t, uint8_t,
extern const EVP_MD *tls_digest_byname(const char *, EVP_MD_CTX **);
extern char *tls_digest_encode(const unsigned char *, int);
extern char *tls_cert_fprint(X509 *, const char *);
-extern char *tls_pkey_fprint(X509 *, const char *);
+extern char *tls_pkey_fprint(EVP_PKEY *, const char *);
extern char *tls_serverid_digest(TLS_SESS_STATE *,
const TLS_CLIENT_START_PROPS *, const char *);
@@ -696,6 +707,8 @@ extern long tls_bio_dump_cb(BIO *, int, const char *, int, long, long);
#endif
extern const EVP_MD *tls_validate_digest(const char *);
+extern void tls_enable_client_rpk(SSL_CTX *, SSL *);
+extern void tls_enable_server_rpk(SSL_CTX *, SSL *);
/*
* tls_seed.c
diff --git a/src/tls/tls_client.c b/src/tls/tls_client.c
index e0dfe15..3eda859 100644
--- a/src/tls/tls_client.c
+++ b/src/tls/tls_client.c
@@ -86,8 +86,9 @@
/* available as:
/* .IP TLScontext->peer_status
/* A bitmask field that records the status of the peer certificate
-/* verification. This consists of one or more of TLS_CERT_FLAG_PRESENT,
-/* TLS_CERT_FLAG_TRUSTED, TLS_CERT_FLAG_MATCHED and TLS_CERT_FLAG_SECURED.
+/* verification. This consists of one or more of TLS_CRED_FLAG_CERT,
+/* TLS_CRED_FLAG_RPK, TLS_CERT_FLAG_TRUSTED, TLS_CERT_FLAG_MATCHED and
+/* TLS_CERT_FLAG_SECURED.
/* .IP TLScontext->peer_CN
/* Extracted CommonName of the peer, or zero-length string if the
/* information could not be extracted.
@@ -303,15 +304,11 @@ static void uncache_session(SSL_CTX *ctx, TLS_SESS_STATE *TLScontext)
tls_mgr_delete(TLScontext->cache_type, TLScontext->serverid);
}
-/* verify_extract_name - verify peer name and extract peer information */
+/* verify_x509 - process X.509 certificate verification status */
-static void verify_extract_name(TLS_SESS_STATE *TLScontext, X509 *peercert,
- const TLS_CLIENT_START_PROPS *props)
+static void verify_x509(TLS_SESS_STATE *TLScontext, X509 *peercert,
+ const TLS_CLIENT_START_PROPS *props)
{
- int verbose;
-
- verbose = TLScontext->log_mask &
- (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT);
/*
* On exit both peer_CN and issuer_CN should be set.
@@ -345,7 +342,8 @@ static void verify_extract_name(TLS_SESS_STATE *TLScontext, X509 *peercert,
TLScontext->peer_status |= TLS_CERT_FLAG_SECURED;
TLScontext->peer_status |= TLS_CERT_FLAG_MATCHED;
- if (verbose) {
+ if (TLScontext->log_mask &
+ (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT)) {
const char *peername = SSL_get0_peername(TLScontext->con);
if (peername)
@@ -367,6 +365,50 @@ static void verify_extract_name(TLS_SESS_STATE *TLScontext, X509 *peercert,
if (TLScontext->session_reused == 0)
tls_log_verify_error(TLScontext);
else
+ msg_info("%s: re-using session with untrusted peer credential, "
+ "look for details earlier in the log", props->namaddr);
+ }
+}
+
+/* verify_rpk - process RFC7250 raw public key verification status */
+
+static void verify_rpk(TLS_SESS_STATE *TLScontext, EVP_PKEY *peerpkey,
+ const TLS_CLIENT_START_PROPS *props)
+{
+ /* Was the raw public key (type of cert) matched? */
+ if (SSL_get_verify_result(TLScontext->con) == X509_V_OK) {
+ TLScontext->peer_status |= TLS_CERT_FLAG_TRUSTED;
+ if (TLScontext->must_fail) {
+ msg_panic("%s: raw public key valid despite trust init failure",
+ TLScontext->namaddr);
+ } else if (TLS_MUST_MATCH(TLScontext->level)) {
+
+ /*
+ * Fully secured only if not insecure like half-dane. We use
+ * TLS_CERT_FLAG_MATCHED to satisfy policy, but
+ * TLS_CERT_FLAG_SECURED to log the effective security.
+ */
+ if (!TLS_NEVER_SECURED(TLScontext->level))
+ TLScontext->peer_status |= TLS_CERT_FLAG_SECURED;
+ TLScontext->peer_status |= TLS_CERT_FLAG_MATCHED;
+
+ if (TLScontext->log_mask &
+ (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT))
+ tls_dane_log(TLScontext);
+ }
+ }
+
+ /*
+ * Give them a clue. Problems with trust chain verification are logged
+ * when the session is first negotiated, before the session is stored
+ * into the cache. We don't want mystery failures, so log the fact the
+ * real problem is to be found in the past.
+ */
+ if (!TLS_CERT_IS_MATCHED(TLScontext)
+ && (TLScontext->log_mask & TLS_LOG_UNTRUSTED)) {
+ if (TLScontext->session_reused == 0)
+ tls_log_verify_error(TLScontext);
+ else
msg_info("%s: re-using session with untrusted certificate, "
"look for details earlier in the log", props->namaddr);
}
@@ -793,6 +835,30 @@ TLS_APPL_STATE *tls_client_init(const TLS_CLIENT_INIT_PROPS *props)
}
/*
+ * Enable support for client->server raw public keys, provided we actually
+ * have keys to send. They'll only be used if the server also enables
+ * client RPKs.
+ *
+ * XXX: When the server requests client auth, the TLS 1.2 protocol does not
+ * provide an unambiguous mechanism for the client to not send an RPK (as
+ * it can with client X.509 certs or TLS 1.3). This is why we don't just
+ * enable client RPK also with no keys in hand.
+ *
+ * A very unlikely scenario is that the server allows clients to not send
+ * keys, but only accepts keys for a set of algorithms we don't have. Then
+ * we still can't send a key, but have agreed to RPK. OpenSSL will attempt
+ * to send an empty RPK even with TLS 1.2 (and will accept such a message),
+ * but other implementations may be more strict.
+ *
+ * We could limit client RPK support to connections that support only TLS
+ * 1.3 and up, but that's practical only decades in the future, and the
+ * risk scenario is contrived and very unlikely.
+ */
+ if (SSL_CTX_get0_certificate(client_ctx) != NULL &&
+ SSL_CTX_get0_privatekey(client_ctx) != NULL)
+ tls_enable_client_rpk(client_ctx, NULL);
+
+ /*
* With OpenSSL 1.0.2 and later the client EECDH curve list becomes
* configurable with the preferred curve negotiated via the supported
* curves extension. With OpenSSL 3.0 and TLS 1.3, the same applies
@@ -1008,6 +1074,24 @@ TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *props)
}
/*
+ * Possibly enable RFC7250 raw public keys in non-DANE/non-PKI levels
+ * when the fingerprint mask includes only public keys. For "may" and
+ * "encrypt" this is a heuristic, since we don't use the fingerprints
+ * beyond reporting them in verbose logging. If you always want certs
+ * with "may" and "encrypt" you'll have to tolerate them with
+ * "fingerprint", or use a separate transport.
+ */
+ switch (props->tls_level) {
+ case TLS_LEV_MAY:
+ case TLS_LEV_ENCRYPT:
+ case TLS_LEV_FPRINT:
+ if (props->enable_rpk)
+ tls_enable_server_rpk(NULL, TLScontext->con);
+ default:
+ break;
+ }
+
+ /*
* Try to convey the configured TLSA records for this connection to the
* OpenSSL library. If none are "usable", we'll fall back to "encrypt"
* when authentication is not mandatory, otherwise we must arrange to
@@ -1175,6 +1259,7 @@ TLS_SESS_STATE *tls_client_post_connect(TLS_SESS_STATE *TLScontext,
{
const SSL_CIPHER *cipher;
X509 *peercert;
+ EVP_PKEY *peerpkey = 0;
/* Turn off packet dump if only dumping the handshake */
if ((TLScontext->log_mask & TLS_LOG_ALLPKTS) == 0)
@@ -1192,31 +1277,61 @@ TLS_SESS_STATE *tls_client_post_connect(TLS_SESS_STATE *TLScontext,
* Do peername verification if requested and extract useful information
* from the certificate for later use.
*/
- if ((peercert = TLS_PEEK_PEER_CERT(TLScontext->con)) != 0) {
- TLScontext->peer_status |= TLS_CERT_FLAG_PRESENT;
+ peercert = TLS_PEEK_PEER_CERT(TLScontext->con);
+ if (peercert != 0) {
+ peerpkey = X509_get0_pubkey(peercert);
+ }
+#if OPENSSL_VERSION_PREREQ(3,2)
+ else {
+ peerpkey = SSL_get0_peer_rpk(TLScontext->con);
+ }
+#endif
+
+ if (peercert != 0) {
+ TLScontext->peer_status |= TLS_CRED_FLAG_CERT;
/*
* Peer name or fingerprint verification as requested.
* Unconditionally set peer_CN, issuer_CN and peer_cert_fprint. Check
* fingerprint first, and avoid logging verified as untrusted in the
- * call to verify_extract_name().
+ * call to verify_x509().
*/
- TLScontext->peer_cert_fprint = tls_cert_fprint(peercert, props->mdalg);
- TLScontext->peer_pkey_fprint = tls_pkey_fprint(peercert, props->mdalg);
- verify_extract_name(TLScontext, peercert, props);
+ TLScontext->peer_cert_fprint =
+ tls_cert_fprint(peercert, props->mdalg);
+ TLScontext->peer_pkey_fprint =
+ tls_pkey_fprint(peerpkey, props->mdalg);
+ verify_x509(TLScontext, peercert, props);
if (TLScontext->log_mask &
(TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT))
- msg_info("%s: subject_CN=%s, issuer_CN=%s, "
- "fingerprint=%s, pkey_fingerprint=%s", props->namaddr,
+ msg_info("%s: subject_CN=%s, issuer=%s%s%s%s%s",
+ TLScontext->namaddr,
TLScontext->peer_CN, TLScontext->issuer_CN,
- TLScontext->peer_cert_fprint,
- TLScontext->peer_pkey_fprint);
+ *TLScontext->peer_cert_fprint ?
+ ", cert fingerprint=" : "",
+ *TLScontext->peer_cert_fprint ?
+ TLScontext->peer_cert_fprint : "",
+ *TLScontext->peer_pkey_fprint ?
+ ", pkey fingerprint=" : "",
+ *TLScontext->peer_pkey_fprint ?
+ TLScontext->peer_pkey_fprint : "");
} else {
TLScontext->issuer_CN = mystrdup("");
TLScontext->peer_CN = mystrdup("");
TLScontext->peer_cert_fprint = mystrdup("");
- TLScontext->peer_pkey_fprint = mystrdup("");
+
+ if (!peerpkey) {
+ TLScontext->peer_pkey_fprint = mystrdup("");
+ } else {
+ TLScontext->peer_status |= TLS_CRED_FLAG_RPK;
+ TLScontext->peer_pkey_fprint =
+ tls_pkey_fprint(peerpkey, props->mdalg);
+ if (TLScontext->log_mask &
+ (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT))
+ msg_info("%s: raw public key fingerprint=%s", props->namaddr,
+ TLScontext->peer_pkey_fprint);
+ verify_rpk(TLScontext, peerpkey, props);
+ }
}
/*
diff --git a/src/tls/tls_dane.c b/src/tls/tls_dane.c
index a2b9b80..ac7f05f 100644
--- a/src/tls/tls_dane.c
+++ b/src/tls/tls_dane.c
@@ -22,8 +22,9 @@
/* void tls_dane_free(dane)
/* TLS_DANE *dane;
/*
-/* void tls_dane_add_fpt_digests(dane, digest, delim, smtp_mode)
+/* void tls_dane_add_fpt_digests(dane, pkey_only, digest, delim, smtp_mode)
/* TLS_DANE *dane;
+/* int pkey_only;
/* const char *digest;
/* const char *delim;
/* int smtp_mode;
@@ -130,6 +131,9 @@
/* SSL context to be configured with the chosen digest algorithms.
/* .IP fpt_alg
/* The OpenSSL EVP digest algorithm handle for the fingerprint digest.
+/* .IP pkey_only
+/* When true, generate "fingerprint" TLSA records for just the public
+/* keys. Otherwise, for both certificates and public keys.
/* .IP tlsa
/* TLSA record linked list head, initially NULL.
/* .IP usage
@@ -415,8 +419,9 @@ static void dane_free(void *dane, void *unused_context)
/* tls_dane_add_fpt_digests - map fingerprint list to DANE TLSA RRset */
-void tls_dane_add_fpt_digests(TLS_DANE *dane, const char *digest,
- const char *delim, int smtp_mode)
+void tls_dane_add_fpt_digests(TLS_DANE *dane, int pkey_only,
+ const char *digest, const char *delim,
+ int smtp_mode)
{
ARGV *values = argv_split(digest, delim);
ssize_t i;
@@ -455,31 +460,41 @@ void tls_dane_add_fpt_digests(TLS_DANE *dane, const char *digest,
continue;
}
+#define USTR_LEN(raw) (unsigned char *) STR(raw), VSTRING_LEN(raw)
+
/*
* At the "fingerprint" security level certificate digests and public
- * key digests are interchangeable. Each leaf certificate is matched
- * via either the public key digest or full certificate digest. The
- * DER encoding of a certificate is not a valid public key, and
- * conversely, the DER encoding of a public key is not a valid
- * certificate. An attacker would need a 2nd-preimage that is
+ * key digests are by default interchangeable. Each leaf certificate
+ * is matched via either the public key digest or full certificate
+ * digest. The DER encoding of a certificate is not a valid public
+ * key, and conversely, the DER encoding of a public key is not a
+ * valid certificate. An attacker would need a 2nd-preimage that is
* feasible across types (given cert digest == some pkey digest) and
* yet presumably difficult within a type (e.g. given cert digest ==
* some other cert digest). No such attacks are known at this time,
* and it is expected that if any are found they would work within as
* well as across the cert/pkey data types.
+ *
+ * That said, when `pkey_only` is true, we match only public keys.
*
* The private-use matching type "255" is mapped to the configured
* fingerprint digest, which may (harmlessly) coincide with one of
* the standard DANE digest algorithms. The private code point is
* however unconditionally enabled.
*/
+ if (!pkey_only) {
+ dane->tlsa = tlsa_prepend(dane->tlsa, 3, 0, 255, USTR_LEN(raw));
+ if (log_mask & (TLS_LOG_VERBOSE | TLS_LOG_DANE))
+ tlsa_info("fingerprint", "digest as private-use TLSA record",
+ 3, 0, 255, USTR_LEN(raw));
+ }
+
+ /* The public key match is unconditional */
+ dane->tlsa = tlsa_prepend(dane->tlsa, 3, 1, 255, USTR_LEN(raw));
if (log_mask & (TLS_LOG_VERBOSE | TLS_LOG_DANE))
tlsa_info("fingerprint", "digest as private-use TLSA record",
- 3, 0, 255, (unsigned char *) STR(raw), VSTRING_LEN(raw));
- dane->tlsa = tlsa_prepend(dane->tlsa, 3, 0, 255,
- (unsigned char *) STR(raw), VSTRING_LEN(raw));
- dane->tlsa = tlsa_prepend(dane->tlsa, 3, 1, 255,
- (unsigned char *) STR(raw), VSTRING_LEN(raw));
+ 3, 1, 255, USTR_LEN(raw));
+
vstring_free(raw);
}
argv_free(values);
@@ -798,12 +813,21 @@ int tls_dane_enable(TLS_SESS_STATE *TLScontext)
SSL *ssl = TLScontext->con;
int usable = 0;
int ret;
+ int rpk_compat = 1;
for (tp = dane->tlsa; tp != 0; tp = tp->next) {
ret = SSL_dane_tlsa_add(ssl, tp->usage, tp->selector,
tp->mtype, tp->data, tp->length);
if (ret > 0) {
++usable;
+ /*
+ * Disable use of RFC7250 raw public keys if any TLSA record
+ * depends on X.509 certificates. Only DANE-EE(3) SPKI(1) records
+ * can get by with just a public key.
+ */
+ if (tp->usage != DNS_TLSA_USAGE_DOMAIN_ISSUED_CERTIFICATE
+ || tp->selector != DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO)
+ rpk_compat = 0;
continue;
}
if (ret == 0) {
@@ -818,6 +842,9 @@ int tls_dane_enable(TLS_SESS_STATE *TLScontext)
tls_print_errors();
return (-1);
}
+ if (rpk_compat)
+ tls_enable_server_rpk(NULL, ssl);
+
return (usable);
}
@@ -964,8 +991,9 @@ void tls_dane_log(TLS_SESS_STATE *TLScontext)
{
static VSTRING *top;
static VSTRING *bot;
+ X509 *mcert = 0;
EVP_PKEY *mspki = 0;
- int depth = SSL_get0_dane_authority(TLScontext->con, NULL, &mspki);
+ int depth = SSL_get0_dane_authority(TLScontext->con, &mcert, &mspki);
uint8_t u, s, m;
unsigned const char *data;
size_t dlen;
@@ -994,22 +1022,27 @@ void tls_dane_log(TLS_SESS_STATE *TLScontext)
hex_encode(top, (char *) data, dlen);
}
- switch (TLScontext->level) {
- case TLS_LEV_FPRINT:
+ if (TLScontext->level == TLS_LEV_FPRINT) {
msg_info("%s: Matched fingerprint: %s%s%s", TLScontext->namaddr,
STR(top), dlen > MAX_DUMP_BYTES ? "..." : "",
dlen > MAX_DUMP_BYTES ? STR(bot) : "");
return;
-
- default:
- msg_info("%s: Matched DANE %s at depth %d: %u %u %u %s%s%s",
- TLScontext->namaddr, mspki ?
- "TA public key verified certificate" : depth ?
- "TA certificate" : "EE certificate", depth, u, s, m,
+ }
+#if OPENSSL_VERSION_PREREQ(3,2)
+ if (SSL_get0_peer_rpk(TLScontext->con) != NULL) {
+ msg_info("%s: Matched DANE raw public key: %u %u %u %s%s%s",
+ TLScontext->namaddr, u, s, m,
STR(top), dlen > MAX_DUMP_BYTES ? "..." : "",
dlen > MAX_DUMP_BYTES ? STR(bot) : "");
return;
}
+#endif
+ msg_info("%s: Matched DANE %s at depth %d: %u %u %u %s%s%s",
+ TLScontext->namaddr, mspki ?
+ "TA public key verified certificate" : depth ?
+ "TA certificate" : "EE certificate", depth, u, s, m,
+ STR(top), dlen > MAX_DUMP_BYTES ? "..." : "",
+ dlen > MAX_DUMP_BYTES ? STR(bot) : "");
}
#ifdef TEST
diff --git a/src/tls/tls_fprint.c b/src/tls/tls_fprint.c
index 39b5a52..dc3f99e 100644
--- a/src/tls/tls_fprint.c
+++ b/src/tls/tls_fprint.c
@@ -24,7 +24,7 @@
/* const char *mdalg;
/*
/* char *tls_pkey_fprint(peercert, mdalg)
-/* X509 *peercert;
+/* EVP_PKEY *peerpkey;
/* const char *mdalg;
/* DESCRIPTION
/* tls_digest_byname() constructs, and optionally returns, an EVP_MD_CTX
@@ -48,8 +48,6 @@
/*
/* tls_pkey_fprint() returns a public-key fingerprint; in all
/* other respects the function behaves as tls_cert_fprint().
-/* The var_tls_bc_pkey_fprint variable enables an incorrect
-/* algorithm that was used in Postfix versions 2.9.[0-5].
/* The return value is dynamically allocated with mymalloc(),
/* and the caller must eventually free it with myfree().
/*
@@ -274,6 +272,9 @@ char *tls_serverid_digest(TLS_SESS_STATE *TLScontext,
CHECK_OK_AND_DIGEST_CHARS(mdctx, props->protocols);
CHECK_OK_AND_DIGEST_CHARS(mdctx, ciphers);
+ /* Just in case we make this destination-policy specific */
+ CHECK_OK_AND_DIGEST_OBJECT(mdctx, &props->enable_rpk);
+
/*
* Ensure separation of caches for sessions where DANE trust
* configuration succeeded from those where it did not. The latter
@@ -398,38 +399,24 @@ char *tls_cert_fprint(X509 *peercert, const char *mdalg)
return (result);
}
-/* tls_pkey_fprint - extract public key fingerprint from certificate */
+/* tls_pkey_fprint - extract public key fingerprint */
-char *tls_pkey_fprint(X509 *peercert, const char *mdalg)
+char *tls_pkey_fprint(EVP_PKEY *peerpkey, const char *mdalg)
{
- if (var_tls_bc_pkey_fprint) {
- const char *myname = "tls_pkey_fprint";
- ASN1_BIT_STRING *key;
- char *result;
-
- key = X509_get0_pubkey_bitstr(peercert);
- if (key == 0)
- msg_fatal("%s: error extracting legacy public-key fingerprint: %m",
- myname);
-
- result = tls_data_fprint(key->data, key->length, mdalg);
- return (result);
- } else {
- int len;
- unsigned char *buf;
- unsigned char *buf2;
- char *result;
-
- len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(peercert), NULL);
- buf2 = buf = mymalloc(len);
- i2d_X509_PUBKEY(X509_get_X509_PUBKEY(peercert), &buf2);
- if (buf2 - buf != len)
- msg_panic("i2d_X509_PUBKEY invalid result length");
-
- result = tls_data_fprint(buf, len, mdalg);
- myfree(buf);
- return (result);
- }
+ int len;
+ unsigned char *buf;
+ unsigned char *buf2;
+ char *result;
+
+ len = i2d_PUBKEY(peerpkey, NULL);
+ buf2 = buf = mymalloc(len);
+ i2d_PUBKEY(peerpkey, &buf2);
+ if (buf2 - buf != len)
+ msg_panic("i2d_PUBKEY invalid result length");
+
+ result = tls_data_fprint(buf, len, mdalg);
+ myfree(buf);
+ return (result);
}
#endif
diff --git a/src/tls/tls_misc.c b/src/tls/tls_misc.c
index b7acd1d..cf8f6fa 100644
--- a/src/tls/tls_misc.c
+++ b/src/tls/tls_misc.c
@@ -118,6 +118,14 @@
/*
/* const EVP_MD *tls_validate_digest(dgst)
/* const char *dgst;
+/*
+/* void tls_enable_client_rpk(ctx, ssl)
+/* SSL_CTX *ctx;
+/* SSL *ssl;
+/*
+/* void tls_enable_server_rpk(ctx, ssl)
+/* SSL_CTX *ctx;
+/* SSL *ssl;
/* DESCRIPTION
/* This module implements public and internal routines that
/* support the TLS client and server.
@@ -215,6 +223,12 @@
/*
/* tls_validate_digest() returns a static handle for the named
/* digest algorithm, or NULL on error.
+/*
+/* tls_enable_client_rpk() enables the use of raw public keys in the
+/* client to server direction, if supported by the OpenSSL library.
+/*
+/* tls_enable_server_rpk() enables the use of raw public keys in the
+/* server to client direction, if supported by the OpenSSL library.
/* LICENSE
/* .ad
/* .fi
@@ -762,7 +776,7 @@ int tls_library_init(void)
/*
* The default global config file is optional. With "default"
- * initialisation we don't insist on a match for the requested
+ * initialization we don't insist on a match for the requested
* application name, allowing fallback to the default application
* name, even when a non-default application name is specified.
* Errors in loading the default configuration are ignored.
@@ -1028,7 +1042,6 @@ void tls_get_signature_params(TLS_SESS_STATE *TLScontext)
SSL *ssl = TLScontext->con;
int srvr = SSL_is_server(ssl);
EVP_PKEY *dh_pkey = 0;
- X509 *local_cert;
EVP_PKEY *local_pkey = 0;
X509 *peer_cert;
EVP_PKEY *peer_pkey = 0;
@@ -1060,18 +1073,23 @@ void tls_get_signature_params(TLS_SESS_STATE *TLScontext)
}
/*
- * On the client end, the certificate may be preset, but not used, so we
+ * On the client end, the certificate may be present, but not used, so we
* check via SSL_get_signature_nid(). This means that local signature
* data on clients requires at least 1.1.1a.
*/
- if (srvr || SSL_get_signature_nid(ssl, &nid))
- local_cert = SSL_get_certificate(ssl);
- else
- local_cert = 0;
-
+ if (srvr || SSL_get_signature_nid(ssl, &nid)) {
+ local_pkey = SSL_get_privatekey(ssl);
+ }
/* Signature algorithms for the local end of the connection */
- if (local_cert) {
- local_pkey = X509_get0_pubkey(local_cert);
+ if (local_pkey) {
+#if OPENSSL_VERSION_PREREQ(3,2)
+ if (srvr)
+ TLScontext->stoc_rpk = TLSEXT_cert_type_rpk ==
+ SSL_get_negotiated_server_cert_type(ssl);
+ else
+ TLScontext->ctos_rpk = TLSEXT_cert_type_rpk ==
+ SSL_get_negotiated_client_cert_type(ssl);
+#endif
/*
* Override the built-in name for the "ECDSA" algorithms OID, with
@@ -1097,7 +1115,6 @@ void tls_get_signature_params(TLS_SESS_STATE *TLScontext)
break;
#endif
}
- /* No X509_free(local_cert) */
}
/*
@@ -1107,9 +1124,26 @@ void tls_get_signature_params(TLS_SESS_STATE *TLScontext)
if (SSL_get_signature_nid(ssl, &nid) && nid != NID_undef)
locl_sig_dgst = OBJ_nid2sn(nid);
}
- /* Signature algorithms for the peer end of the connection */
- if ((peer_cert = TLS_PEEK_PEER_CERT(ssl)) != 0) {
+ peer_cert = TLS_PEEK_PEER_CERT(ssl);
+ if (peer_cert != 0) {
peer_pkey = X509_get0_pubkey(peer_cert);
+ }
+#if OPENSSL_VERSION_PREREQ(3,2)
+ else {
+ peer_pkey = SSL_get0_peer_rpk(ssl);
+ }
+#endif
+
+ /* Signature algorithms for the peer end of the connection */
+ if (peer_pkey != 0) {
+#if OPENSSL_VERSION_PREREQ(3,2)
+ if (srvr)
+ TLScontext->ctos_rpk = TLSEXT_cert_type_rpk ==
+ SSL_get_negotiated_client_cert_type(ssl);
+ else
+ TLScontext->stoc_rpk = TLSEXT_cert_type_rpk ==
+ SSL_get_negotiated_server_cert_type(ssl);
+#endif
/*
* Override the built-in name for the "ECDSA" algorithms OID, with
@@ -1144,8 +1178,9 @@ void tls_get_signature_params(TLS_SESS_STATE *TLScontext)
if (SSL_get_peer_signature_nid(ssl, &nid) && nid != NID_undef)
peer_sig_dgst = OBJ_nid2sn(nid);
- TLS_FREE_PEER_CERT(peer_cert);
}
+ TLS_FREE_PEER_CERT(peer_cert);
+
if (kex_name) {
TLScontext->kex_name = mystrdup(kex_name);
TLScontext->kex_curve = kex_curve;
@@ -1180,7 +1215,7 @@ void tls_log_summary(TLS_ROLE role, TLS_USAGE usage, TLS_SESS_STATE *ctx)
*/
vstring_sprintf(msg, "%s TLS connection %s %s %s%s%s: %s"
" with cipher %s (%d/%d bits)",
- !TLS_CERT_IS_PRESENT(ctx) ? "Anonymous" :
+ !TLS_CRED_IS_PRESENT(ctx) ? "Anonymous" :
TLS_CERT_IS_SECURED(ctx) ? "Verified" :
TLS_CERT_IS_TRUSTED(ctx) ? "Trusted" : "Untrusted",
usage == TLS_USAGE_NEW ? "established" : "reused",
@@ -1199,9 +1234,13 @@ void tls_log_summary(TLS_ROLE role, TLS_USAGE usage, TLS_SESS_STATE *ctx)
vstring_sprintf_append(msg, " server-signature %s",
ctx->srvr_sig_name);
if (ctx->srvr_sig_curve && *ctx->srvr_sig_curve)
- vstring_sprintf_append(msg, " (%s)", ctx->srvr_sig_curve);
+ vstring_sprintf_append(msg, " (%s%s)", ctx->srvr_sig_curve,
+ ctx->stoc_rpk ? " raw public key" : "");
else if (ctx->srvr_sig_bits > 0)
- vstring_sprintf_append(msg, " (%d bits)", ctx->srvr_sig_bits);
+ vstring_sprintf_append(msg, " (%d bit%s)", ctx->srvr_sig_bits,
+ ctx->stoc_rpk ? " raw public key" : "s");
+ else if (ctx->stoc_rpk)
+ vstring_sprintf_append(msg, " (raw public key)");
if (ctx->srvr_sig_dgst && *ctx->srvr_sig_dgst)
vstring_sprintf_append(msg, " server-digest %s",
ctx->srvr_sig_dgst);
@@ -1210,9 +1249,13 @@ void tls_log_summary(TLS_ROLE role, TLS_USAGE usage, TLS_SESS_STATE *ctx)
vstring_sprintf_append(msg, " client-signature %s",
ctx->clnt_sig_name);
if (ctx->clnt_sig_curve && *ctx->clnt_sig_curve)
- vstring_sprintf_append(msg, " (%s)", ctx->clnt_sig_curve);
+ vstring_sprintf_append(msg, " (%s%s)", ctx->clnt_sig_curve,
+ ctx->ctos_rpk ? " raw public key" : "");
else if (ctx->clnt_sig_bits > 0)
- vstring_sprintf_append(msg, " (%d bits)", ctx->clnt_sig_bits);
+ vstring_sprintf_append(msg, " (%d bit%s)", ctx->clnt_sig_bits,
+ ctx->ctos_rpk ? " raw public key" : "s");
+ else if (ctx->ctos_rpk)
+ vstring_sprintf_append(msg, " (raw public key)");
if (ctx->clnt_sig_dgst && *ctx->clnt_sig_dgst)
vstring_sprintf_append(msg, " client-digest %s",
ctx->clnt_sig_dgst);
@@ -1288,6 +1331,8 @@ TLS_SESS_STATE *tls_alloc_sess_context(int log_mask, const char *namaddr)
TLScontext->cipher_name = 0;
TLScontext->kex_name = 0;
TLScontext->kex_curve = 0;
+ TLScontext->ctos_rpk = 0;
+ TLScontext->stoc_rpk = 0;
TLScontext->clnt_sig_name = 0;
TLScontext->clnt_sig_curve = 0;
TLScontext->clnt_sig_dgst = 0;
@@ -1702,6 +1747,52 @@ const EVP_MD *tls_validate_digest(const char *dgst)
return md_alg;
}
+void tls_enable_client_rpk(SSL_CTX *ctx, SSL *ssl)
+{
+#if OPENSSL_VERSION_PREREQ(3,2)
+ static int warned = 0;
+ static const unsigned char cert_types_rpk[] = {
+ TLSEXT_cert_type_rpk,
+ TLSEXT_cert_type_x509
+ };
+
+ if ((ctx && !SSL_CTX_set1_client_cert_type(ctx, cert_types_rpk,
+ sizeof(cert_types_rpk))) ||
+ (ssl && !SSL_set1_client_cert_type(ssl, cert_types_rpk,
+ sizeof(cert_types_rpk)))) {
+ if (warned++) {
+ ERR_clear_error();
+ return;
+ }
+ msg_warn("Failed to enable client to server raw public key support");
+ tls_print_errors();
+ }
+#endif
+}
+
+void tls_enable_server_rpk(SSL_CTX *ctx, SSL *ssl)
+{
+#if OPENSSL_VERSION_PREREQ(3,2)
+ static int warned = 0;
+ static const unsigned char cert_types_rpk[] = {
+ TLSEXT_cert_type_rpk,
+ TLSEXT_cert_type_x509
+ };
+
+ if ((ctx && !SSL_CTX_set1_server_cert_type(ctx, cert_types_rpk,
+ sizeof(cert_types_rpk))) ||
+ (ssl && !SSL_set1_server_cert_type(ssl, cert_types_rpk,
+ sizeof(cert_types_rpk)))) {
+ if (warned++) {
+ ERR_clear_error();
+ return;
+ }
+ msg_warn("Failed to enable server to client raw public key support");
+ tls_print_errors();
+ }
+#endif
+}
+
#else
/*
diff --git a/src/tls/tls_proxy.h b/src/tls/tls_proxy.h
index ca664c6..6528639 100644
--- a/src/tls/tls_proxy.h
+++ b/src/tls/tls_proxy.h
@@ -32,8 +32,10 @@
#ifdef USE_TLS
/*
- * TLS_CLIENT_PARAMS structure. If this changes, update all
- * TLS_CLIENT_PARAMS related functions in tls_proxy_client_*.c.
+ * TLS_CLIENT_PARAMS structure, to communicate global TLS library settings
+ * that are the same for all TLS client contexts. This information is used
+ * in tlsproxy(8) to detect inconsistencies. If this structure is changed,
+ * update all TLS_CLIENT_PARAMS related functions in tls_proxy_client_*.c.
*
* In the serialization these attributes are identified by their configuration
* parameter names.
@@ -106,11 +108,11 @@ extern VSTREAM *tls_proxy_open(const char *, int, VSTREAM *, const char *,
((props)->a12), ((props)->a13), ((props)->a14))
#define TLS_PROXY_CLIENT_START_PROPS(props, a1, a2, a3, a4, a5, a6, a7, a8, \
- a9, a10, a11, a12, a13, a14) \
+ a9, a10, a11, a12, a13, a14, a15) \
(((props)->a1), ((props)->a2), ((props)->a3), \
((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \
((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \
- ((props)->a12), ((props)->a13), ((props)->a14))
+ ((props)->a12), ((props)->a13), ((props)->a14), ((props)->a15))
extern TLS_SESS_STATE *tls_proxy_context_receive(VSTREAM *);
extern void tls_proxy_context_free(TLS_SESS_STATE *);
@@ -168,6 +170,8 @@ extern void tls_proxy_server_start_free(TLS_SERVER_START_PROPS *);
#define TLS_ATTR_KEX_NAME "key_exchange"
#define TLS_ATTR_KEX_CURVE "key_exchange_curve"
#define TLS_ATTR_KEX_BITS "key_exchange_bits"
+#define TLS_ATTR_CTOS_RPK "ctos_rpk"
+#define TLS_ATTR_STOC_RPK "stoc_rpk"
#define TLS_ATTR_CLNT_SIG_NAME "clnt_signature"
#define TLS_ATTR_CLNT_SIG_CURVE "clnt_signature_curve"
#define TLS_ATTR_CLNT_SIG_BITS "clnt_signature_bits"
@@ -237,6 +241,7 @@ extern void tls_proxy_server_start_free(TLS_SERVER_START_PROPS *);
* TLS_CLIENT_START_PROPS attributes.
*/
#define TLS_ATTR_TIMEOUT "timeout"
+#define TLS_ATTR_ENABLE_RPK "enable_rpk"
#define TLS_ATTR_TLS_LEVEL "tls_level"
#define TLS_ATTR_NEXTHOP "nexthop"
#define TLS_ATTR_HOST "host"
diff --git a/src/tls/tls_proxy_client_print.c b/src/tls/tls_proxy_client_print.c
index 1cc5778..81e50b9 100644
--- a/src/tls/tls_proxy_client_print.c
+++ b/src/tls/tls_proxy_client_print.c
@@ -257,6 +257,7 @@ int tls_proxy_client_start_print(ATTR_PRINT_COMMON_FN print_fn,
ret = print_fn(fp, flags | ATTR_FLAG_MORE,
SEND_ATTR_INT(TLS_ATTR_TIMEOUT, props->timeout),
+ SEND_ATTR_INT(TLS_ATTR_ENABLE_RPK, props->enable_rpk),
SEND_ATTR_INT(TLS_ATTR_TLS_LEVEL, props->tls_level),
SEND_ATTR_STR(TLS_ATTR_NEXTHOP,
STRING_OR_EMPTY(props->nexthop)),
diff --git a/src/tls/tls_proxy_client_scan.c b/src/tls/tls_proxy_client_scan.c
index a69388c..d36cf4d 100644
--- a/src/tls/tls_proxy_client_scan.c
+++ b/src/tls/tls_proxy_client_scan.c
@@ -451,6 +451,7 @@ int tls_proxy_client_start_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
props->dane = 0; /* scan_fn may return early */
ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
RECV_ATTR_INT(TLS_ATTR_TIMEOUT, &props->timeout),
+ RECV_ATTR_INT(TLS_ATTR_ENABLE_RPK, &props->enable_rpk),
RECV_ATTR_INT(TLS_ATTR_TLS_LEVEL, &props->tls_level),
RECV_ATTR_STR(TLS_ATTR_NEXTHOP, nexthop),
RECV_ATTR_STR(TLS_ATTR_HOST, host),
@@ -478,7 +479,7 @@ int tls_proxy_client_start_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
props->cipher_grade = vstring_export(cipher_grade);
props->cipher_exclusions = vstring_export(cipher_exclusions);
props->mdalg = vstring_export(mdalg);
- ret = (ret == 14 ? 1 : -1);
+ ret = (ret == 15 ? 1 : -1);
if (ret != 1) {
tls_proxy_client_start_free(props);
props = 0;
diff --git a/src/tls/tls_proxy_context_print.c b/src/tls/tls_proxy_context_print.c
index 04123cb..930410a 100644
--- a/src/tls/tls_proxy_context_print.c
+++ b/src/tls/tls_proxy_context_print.c
@@ -88,6 +88,10 @@ int tls_proxy_context_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp,
STRING_OR_EMPTY(tp->kex_curve)),
SEND_ATTR_INT(TLS_ATTR_KEX_BITS,
tp->kex_bits),
+ SEND_ATTR_INT(TLS_ATTR_CTOS_RPK,
+ tp->ctos_rpk),
+ SEND_ATTR_INT(TLS_ATTR_STOC_RPK,
+ tp->stoc_rpk),
SEND_ATTR_STR(TLS_ATTR_CLNT_SIG_NAME,
STRING_OR_EMPTY(tp->clnt_sig_name)),
SEND_ATTR_STR(TLS_ATTR_CLNT_SIG_CURVE,
diff --git a/src/tls/tls_proxy_context_scan.c b/src/tls/tls_proxy_context_scan.c
index 1d463ad..48aaff6 100644
--- a/src/tls/tls_proxy_context_scan.c
+++ b/src/tls/tls_proxy_context_scan.c
@@ -113,6 +113,8 @@ int tls_proxy_context_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
RECV_ATTR_STR(TLS_ATTR_KEX_NAME, kex_name),
RECV_ATTR_STR(TLS_ATTR_KEX_CURVE, kex_curve),
RECV_ATTR_INT(TLS_ATTR_KEX_BITS, &tls_context->kex_bits),
+ RECV_ATTR_INT(TLS_ATTR_CTOS_RPK, &tls_context->ctos_rpk),
+ RECV_ATTR_INT(TLS_ATTR_STOC_RPK, &tls_context->stoc_rpk),
RECV_ATTR_STR(TLS_ATTR_CLNT_SIG_NAME, clnt_sig_name),
RECV_ATTR_STR(TLS_ATTR_CLNT_SIG_CURVE, clnt_sig_curve),
RECV_ATTR_INT(TLS_ATTR_CLNT_SIG_BITS, &tls_context->clnt_sig_bits),
@@ -139,7 +141,7 @@ int tls_proxy_context_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
tls_context->srvr_sig_curve = vstring_export(srvr_sig_curve);
tls_context->srvr_sig_dgst = vstring_export(srvr_sig_dgst);
tls_context->namaddr = vstring_export(namaddr);
- ret = (ret == 22 ? 1 : -1);
+ ret = (ret == 24 ? 1 : -1);
if (ret != 1) {
tls_proxy_context_free(tls_context);
tls_context = 0;
diff --git a/src/tls/tls_server.c b/src/tls/tls_server.c
index 262cda9..88b3326 100644
--- a/src/tls/tls_server.c
+++ b/src/tls/tls_server.c
@@ -62,8 +62,8 @@
/* available as:
/* .IP TLScontext->peer_status
/* A bitmask field that records the status of the peer certificate
-/* verification. One or more of TLS_CERT_FLAG_PRESENT and
-/* TLS_CERT_FLAG_TRUSTED.
+/* verification. One or more of TLS_CRED_FLAG_CERT, TLS_CRED_FLAG_RPK
+/* and TLS_CERT_FLAG_TRUSTED.
/* .IP TLScontext->peer_CN
/* Extracted CommonName of the peer, or zero-length string
/* when information could not be extracted.
@@ -637,6 +637,13 @@ TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *props)
}
/*
+ * Always support server->client raw public keys, if they're good enough
+ * for the client, they're good enough for us.
+ */
+ tls_enable_server_rpk(server_ctx, NULL);
+ tls_enable_server_rpk(sni_ctx, NULL);
+
+ /*
* Upref and share the cert store. Sadly we can't yet use
* SSL_CTX_set1_cert_store(3) which was added in OpenSSL 1.1.0.
*/
@@ -865,11 +872,19 @@ TLS_SESS_STATE *tls_server_start(const TLS_SERVER_START_PROPS *props)
tls_free_context(TLScontext);
return (0);
}
-#ifdef SSL_SECOP_PEER
- /* When authenticating the peer, use 80-bit plus OpenSSL security level */
+
+ /*
+ * When encryption is mandatory use the 80-bit plus OpenSSL security level.
+ */
if (props->requirecert)
SSL_set_security_level(TLScontext->con, 1);
-#endif
+
+ /*
+ * Also enable client->server raw public keys, provided we're not
+ * interested in client certificate fingerprints.
+ */
+ if (props->enable_rpk)
+ tls_enable_client_rpk(NULL, TLScontext->con);
/*
* Before really starting anything, try to seed the PRNG a little bit
@@ -946,6 +961,7 @@ TLS_SESS_STATE *tls_server_post_accept(TLS_SESS_STATE *TLScontext)
{
const SSL_CIPHER *cipher;
X509 *peer;
+ EVP_PKEY *pkey = 0;
char buf[CCERT_BUFSIZ];
/* Turn off packet dump if only dumping the handshake */
@@ -966,8 +982,17 @@ TLS_SESS_STATE *tls_server_post_accept(TLS_SESS_STATE *TLScontext)
* actual information. We want to save it for later use.
*/
peer = TLS_PEEK_PEER_CERT(TLScontext->con);
+ if (peer) {
+ pkey = X509_get0_pubkey(peer);
+ }
+#if OPENSSL_VERSION_PREREQ(3,2)
+ else {
+ pkey = SSL_get0_peer_rpk(TLScontext->con);
+ }
+#endif
+
if (peer != NULL) {
- TLScontext->peer_status |= TLS_CERT_FLAG_PRESENT;
+ TLScontext->peer_status |= TLS_CRED_FLAG_CERT;
if (SSL_get_verify_result(TLScontext->con) == X509_V_OK)
TLScontext->peer_status |= TLS_CERT_FLAG_TRUSTED;
@@ -981,16 +1006,23 @@ TLS_SESS_STATE *tls_server_post_accept(TLS_SESS_STATE *TLScontext)
}
TLScontext->peer_CN = tls_peer_CN(peer, TLScontext);
TLScontext->issuer_CN = tls_issuer_CN(peer, TLScontext);
- TLScontext->peer_cert_fprint = tls_cert_fprint(peer, TLScontext->mdalg);
- TLScontext->peer_pkey_fprint = tls_pkey_fprint(peer, TLScontext->mdalg);
+ TLScontext->peer_cert_fprint =
+ tls_cert_fprint(peer, TLScontext->mdalg);
+ TLScontext->peer_pkey_fprint =
+ tls_pkey_fprint(pkey, TLScontext->mdalg);
if (TLScontext->log_mask & (TLS_LOG_VERBOSE | TLS_LOG_PEERCERT)) {
- msg_info("%s: subject_CN=%s, issuer=%s, fingerprint=%s"
- ", pkey_fingerprint=%s",
+ msg_info("%s: subject_CN=%s, issuer=%s%s%s%s%s",
TLScontext->namaddr,
TLScontext->peer_CN, TLScontext->issuer_CN,
- TLScontext->peer_cert_fprint,
- TLScontext->peer_pkey_fprint);
+ *TLScontext->peer_cert_fprint ?
+ ", cert fingerprint=" : "",
+ *TLScontext->peer_cert_fprint ?
+ TLScontext->peer_cert_fprint : "",
+ *TLScontext->peer_pkey_fprint ?
+ ", pkey fingerprint=" : "",
+ *TLScontext->peer_pkey_fprint ?
+ TLScontext->peer_pkey_fprint : "");
}
TLS_FREE_PEER_CERT(peer);
@@ -1013,7 +1045,22 @@ TLS_SESS_STATE *tls_server_post_accept(TLS_SESS_STATE *TLScontext)
TLScontext->peer_CN = mystrdup("");
TLScontext->issuer_CN = mystrdup("");
TLScontext->peer_cert_fprint = mystrdup("");
- TLScontext->peer_pkey_fprint = mystrdup("");
+ if (!pkey) {
+ TLScontext->peer_pkey_fprint = mystrdup("");
+ } else {
+
+ /*
+ * Raw public keys don't involve CA trust, and we don't have a
+ * way to associate DANE TLSA RRs with clients just yet, we just
+ * make the fingerprint available to the access(5) layer.
+ */
+ TLScontext->peer_status |= TLS_CRED_FLAG_RPK;
+ TLScontext->peer_pkey_fprint =
+ tls_pkey_fprint(pkey, TLScontext->mdalg);
+ if (TLScontext->log_mask & (TLS_LOG_VERBOSE | TLS_LOG_PEERCERT))
+ msg_info("%s: raw public key fingerprint=%s",
+ TLScontext->namaddr, TLScontext->peer_pkey_fprint);
+ }
}
/*
diff --git a/src/tls/tls_verify.c b/src/tls/tls_verify.c
index f32f32b..c643f18 100644
--- a/src/tls/tls_verify.c
+++ b/src/tls/tls_verify.c
@@ -144,6 +144,7 @@ int tls_verify_certificate_callback(int ok, X509_STORE_CTX *ctx)
int depth;
SSL *con;
TLS_SESS_STATE *TLScontext;
+ EVP_PKEY *rpk = 0;
/* May be NULL as of OpenSSL 1.0, thanks for the API change! */
cert = X509_STORE_CTX_get_current_cert(ctx);
@@ -151,6 +152,10 @@ int tls_verify_certificate_callback(int ok, X509_STORE_CTX *ctx)
con = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
TLScontext = SSL_get_ex_data(con, TLScontext_index);
depth = X509_STORE_CTX_get_error_depth(ctx);
+#if OPENSSL_VERSION_PREREQ(3,2)
+ if (cert == 0)
+ rpk = X509_STORE_CTX_get0_rpk(ctx);
+#endif
/*
* Transient failures to load the (DNS or synthetic TLSA) trust settings
@@ -174,12 +179,15 @@ int tls_verify_certificate_callback(int ok, X509_STORE_CTX *ctx)
update_error_state(TLScontext, depth, cert, err);
if (TLScontext->log_mask & TLS_LOG_VERBOSE) {
- if (cert)
+ if (cert) {
X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf));
- else
- strcpy(buf, "<unknown>");
- msg_info("%s: depth=%d verify=%d subject=%s",
- TLScontext->namaddr, depth, ok, printable(buf, '?'));
+ msg_info("%s: depth=%d verify=%d subject=%s",
+ TLScontext->namaddr, depth, ok, printable(buf, '?'));
+ } else if (rpk) {
+ msg_info("%s: verify=%d raw public key", TLScontext->namaddr, ok);
+ } else {
+ msg_info("%s: depth=%d verify=%d", TLScontext->namaddr, depth, ok);
+ }
}
return (1);
}
diff --git a/src/tlsproxy/tlsproxy.c b/src/tlsproxy/tlsproxy.c
index 7c0d814..0ebf52c 100644
--- a/src/tlsproxy/tlsproxy.c
+++ b/src/tlsproxy/tlsproxy.c
@@ -237,6 +237,12 @@
/* .IP "\fBtlsproxy_tls_chain_files ($smtpd_tls_chain_files)\fR"
/* Files with the Postfix \fBtlsproxy\fR(8) server keys and certificate
/* chains in PEM format.
+/* .PP
+/* Available in Postfix version 3.9 and later:
+/* .IP "\fBtlsproxy_tls_enable_rpk ($smtpd_tls_enable_rpk)\fR"
+/* Request that remote SMTP clients send an RFC7250 raw public key
+/* instead of an X.509 certificate, when asking or requiring client
+/* authentication.
/* STARTTLS CLIENT CONTROLS
/* .ad
/* .fi
@@ -436,6 +442,7 @@ bool var_smtpd_use_tls;
bool var_smtpd_enforce_tls;
bool var_smtpd_tls_ask_ccert;
bool var_smtpd_tls_req_ccert;
+bool var_smtpd_tls_enable_rpk;
bool var_smtpd_tls_set_sessid;
char *var_smtpd_relay_ccerts;
char *var_smtpd_tls_chain_files;
@@ -465,6 +472,7 @@ bool var_tlsp_use_tls;
bool var_tlsp_enforce_tls;
bool var_tlsp_tls_ask_ccert;
bool var_tlsp_tls_req_ccert;
+bool var_tlsp_tls_enable_rpk;
bool var_tlsp_tls_set_sessid;
char *var_tlsp_tls_chain_files;
char *var_tlsp_tls_cert_file;
@@ -1081,6 +1089,7 @@ static int tlsp_server_start_pre_handshake(TLSP_STATE *state)
timeout = 0, /* unused */
requirecert = (var_tlsp_tls_req_ccert
&& var_tlsp_enforce_tls),
+ enable_rpk = var_tlsp_tls_enable_rpk,
serverid = state->server_id,
namaddr = state->remote_endpt,
cipher_grade = cipher_grade,
@@ -1827,6 +1836,7 @@ int main(int argc, char **argv)
VAR_SMTPD_ENFORCE_TLS, DEF_SMTPD_ENFORCE_TLS, &var_smtpd_enforce_tls,
VAR_SMTPD_TLS_ACERT, DEF_SMTPD_TLS_ACERT, &var_smtpd_tls_ask_ccert,
VAR_SMTPD_TLS_RCERT, DEF_SMTPD_TLS_RCERT, &var_smtpd_tls_req_ccert,
+ VAR_SMTPD_TLS_ENABLE_RPK, DEF_SMTPD_TLS_ENABLE_RPK, &var_smtpd_tls_enable_rpk,
VAR_SMTPD_TLS_SET_SESSID, DEF_SMTPD_TLS_SET_SESSID, &var_smtpd_tls_set_sessid,
VAR_SMTP_USE_TLS, DEF_SMTP_USE_TLS, &var_smtp_use_tls,
VAR_SMTP_ENFORCE_TLS, DEF_SMTP_ENFORCE_TLS, &var_smtp_enforce_tls,
@@ -1837,6 +1847,7 @@ int main(int argc, char **argv)
VAR_TLSP_ENFORCE_TLS, DEF_TLSP_ENFORCE_TLS, &var_tlsp_enforce_tls,
VAR_TLSP_TLS_ACERT, DEF_TLSP_TLS_ACERT, &var_tlsp_tls_ask_ccert,
VAR_TLSP_TLS_RCERT, DEF_TLSP_TLS_RCERT, &var_tlsp_tls_req_ccert,
+ VAR_TLSP_TLS_ENABLE_RPK, DEF_TLSP_TLS_ENABLE_RPK, &var_tlsp_tls_enable_rpk,
VAR_TLSP_TLS_SET_SESSID, DEF_TLSP_TLS_SET_SESSID, &var_tlsp_tls_set_sessid,
VAR_TLSP_CLNT_USE_TLS, DEF_TLSP_CLNT_USE_TLS, &var_tlsp_clnt_use_tls,
VAR_TLSP_CLNT_ENFORCE_TLS, DEF_TLSP_CLNT_ENFORCE_TLS, &var_tlsp_clnt_enforce_tls,
diff --git a/src/trivial-rewrite/resolve.c b/src/trivial-rewrite/resolve.c
index 40e6aa5..df761e7 100644
--- a/src/trivial-rewrite/resolve.c
+++ b/src/trivial-rewrite/resolve.c
@@ -427,7 +427,7 @@ static void resolve_addr(RES_CONTEXT *rp, char *sender, char *addr,
if (!valid_mailhost_literal(rcpt_domain, DONT_GRIPE))
*flags |= RESOLVE_FLAG_ERROR;
} else if (var_smtputf8_enable
- && valid_utf8_string(STR(nextrcpt), LEN(nextrcpt)) == 0) {
+ && valid_utf8_stringz(STR(nextrcpt)) == 0) {
*flags |= RESOLVE_FLAG_ERROR;
} else if (!valid_utf8_hostname(var_smtputf8_enable, rcpt_domain,
DONT_GRIPE)) {
diff --git a/src/trivial-rewrite/trivial-rewrite.c b/src/trivial-rewrite/trivial-rewrite.c
index 675af80..bb8da09 100644
--- a/src/trivial-rewrite/trivial-rewrite.c
+++ b/src/trivial-rewrite/trivial-rewrite.c
@@ -122,9 +122,10 @@
/* .PP
/* Available in Postfix 2.2 and later:
/* .IP "\fBremote_header_rewrite_domain (empty)\fR"
-/* Don't rewrite message headers from remote clients at all when
-/* this parameter is empty; otherwise, rewrite message headers and
-/* append the specified domain name to incomplete addresses.
+/* Rewrite or add message headers in mail from remote clients if
+/* the remote_header_rewrite_domain parameter value is non-empty,
+/* updating incomplete addresses with the domain specified in the
+/* remote_header_rewrite_domain parameter, and adding missing headers.
/* ROUTING CONTROLS
/* .ad
/* .fi
@@ -141,10 +142,12 @@
/* final delivery to domains listed with $virtual_mailbox_domains.
/* .IP "\fBrelay_transport (relay)\fR"
/* The default mail delivery transport and next-hop destination for
-/* remote delivery to domains listed with $relay_domains.
+/* the relay domain address class: recipient domains that match
+/* $relay_domains.
/* .IP "\fBdefault_transport (smtp)\fR"
/* The default mail delivery transport and next-hop destination for
-/* destinations that do not match $mydestination, $inet_interfaces,
+/* the default domain class: recipient domains that do not match
+/* $mydestination, $inet_interfaces,
/* $proxy_interfaces, $virtual_alias_domains, $virtual_mailbox_domains,
/* or $relay_domains.
/* .IP "\fBparent_domain_matches_subdomains (see 'postconf -d' output)\fR"
@@ -152,8 +155,8 @@
/* matches subdomains of example.com,
/* instead of requiring an explicit ".example.com" pattern.
/* .IP "\fBrelayhost (empty)\fR"
-/* The next-hop destination(s) for non-local mail; overrides non-local
-/* domains in recipient addresses.
+/* The next-hop destination(s) for non-local mail; takes precedence
+/* over non-local domains in recipient addresses.
/* .IP "\fBtransport_maps (empty)\fR"
/* Optional lookup tables with mappings from recipient address to
/* (message delivery transport, next-hop destination).
diff --git a/src/util/Makefile.in b/src/util/Makefile.in
index f69dec5..01211fb 100644
--- a/src/util/Makefile.in
+++ b/src/util/Makefile.in
@@ -45,7 +45,7 @@ SRCS = alldig.c allprint.c argv.c argv_split.c attr_clnt.c attr_print0.c \
byte_mask.c known_tcp_ports.c argv_split_at.c dict_stream.c \
sane_strtol.c hash_fnv.c ldseed.c mkmap_cdb.c mkmap_db.c mkmap_dbm.c \
mkmap_fail.c mkmap_lmdb.c mkmap_open.c mkmap_sdbm.c inet_prefix_top.c \
- inet_addr_sizes.c
+ inet_addr_sizes.c quote_for_json.c
OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \
attr_print64.o attr_print_plain.o attr_scan0.o attr_scan64.o \
attr_scan_plain.o auto_clnt.o base64_code.o basename.o binhash.o \
@@ -91,7 +91,8 @@ OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \
msg_logger.o logwriter.o unix_dgram_connect.o unix_dgram_listen.o \
byte_mask.o known_tcp_ports.o argv_split_at.o dict_stream.o \
sane_strtol.o hash_fnv.o ldseed.o mkmap_db.o mkmap_dbm.o \
- mkmap_fail.o mkmap_open.o inet_prefix_top.o inet_addr_sizes.o
+ mkmap_fail.o mkmap_open.o inet_prefix_top.o inet_addr_sizes.o \
+ quote_for_json.o
# MAP_OBJ is for maps that may be dynamically loaded with dynamicmaps.cf.
# When hard-linking these, makedefs sets NON_PLUGIN_MAP_OBJ=$(MAP_OBJ),
# otherwise it sets the PLUGIN_* macros.
@@ -145,7 +146,7 @@ TESTPROG= dict_open dup2_pass_on_exec events exec_command fifo_open \
vstream timecmp dict_cache midna_domain casefold strcasecmp_utf8 \
vbuf_print split_qnameval vstream msg_logger byte_mask \
known_tcp_ports dict_stream find_inet binhash hash_fnv argv \
- clean_env inet_prefix_top
+ clean_env inet_prefix_top printable readlline quote_for_json
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
@@ -365,6 +366,16 @@ unescape: $(LIB)
$(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
mv junk $@.o
+printable: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+readlline: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
hex_quote: $(LIB)
mv $@.o junk
$(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
@@ -609,6 +620,11 @@ inet_prefix_top: $(LIB)
$(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
mv junk $@.o
+quote_for_json: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
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 \
@@ -618,7 +634,8 @@ tests: all valid_hostname_test mac_expand_test dict_test unescape_test \
strcasecmp_utf8_test vbuf_print_test miss_endif_cidr_test \
miss_endif_regexp_test split_qnameval_test vstring_test \
vstream_test byte_mask_tests mystrtok_test known_tcp_ports_test \
- binhash_test argv_test inet_prefix_top_test
+ binhash_test argv_test inet_prefix_top_test printable_test \
+ valid_utf8_string_test readlline_test quote_for_json_test
dict_tests: all dict_test \
dict_pcre_tests dict_cidr_test dict_thash_test dict_static_test \
@@ -650,6 +667,15 @@ unescape_test: unescape unescape.in unescape.ref
# diff unescape.in unescape.tmp
rm -f unescape.tmp
+printable_test: printable
+ $(SHLIB_ENV) ${VALGRIND} ./printable
+
+readlline_test: readlline
+ $(SHLIB_ENV) ${VALGRIND} ./readlline
+
+valid_utf8_string_test: valid_utf8_string
+ $(SHLIB_ENV) ${VALGRIND} ./valid_utf8_string
+
hex_quote_test: hex_quote
$(SHLIB_ENV) ${VALGRIND} ./hex_quote <hex_quote.c | od -cb >hex_quote.tmp
od -cb <hex_quote.c >hex_quote.ref
@@ -1083,6 +1109,9 @@ argv_test: argv
inet_prefix_top_test: inet_prefix_top
$(SHLIB_ENV) ${VALGRIND} ./inet_prefix_top
+quote_for_json_test: quote_for_json
+ $(SHLIB_ENV) ${VALGRIND} ./quote_for_json
+
depend: $(MAKES)
(sed '1,/^# do not edit/!d' Makefile.in; \
set -e; for i in [a-z][a-z0-9]*.c; do \
@@ -1119,9 +1148,12 @@ allspace.o: vbuf.h
allspace.o: vstring.h
argv.o: argv.c
argv.o: argv.h
+argv.o: check_arg.h
argv.o: msg.h
argv.o: mymalloc.h
argv.o: sys_defs.h
+argv.o: vbuf.h
+argv.o: vstring.h
argv_attr_print.o: argv.h
argv_attr_print.o: argv_attr.h
argv_attr_print.o: argv_attr_print.c
@@ -2157,6 +2189,7 @@ logwriter.o: logwriter.c
logwriter.o: logwriter.h
logwriter.o: msg.h
logwriter.o: mymalloc.h
+logwriter.o: name_code.h
logwriter.o: safe_open.h
logwriter.o: sys_defs.h
logwriter.o: vbuf.h
@@ -2525,11 +2558,16 @@ posix_signals.o: posix_signals.c
posix_signals.o: posix_signals.h
posix_signals.o: sys_defs.h
printable.o: check_arg.h
+printable.o: parse_utf8_char.h
printable.o: printable.c
printable.o: stringops.h
printable.o: sys_defs.h
printable.o: vbuf.h
printable.o: vstring.h
+quote_for_json.o: quote_for_json.c
+quote_for_json.o: stringops.h
+quote_for_json.o: sys_defs.h
+quote_for_json.o: vstring.h
rand_sleep.o: iostuff.h
rand_sleep.o: msg.h
rand_sleep.o: myrand.h
@@ -2848,6 +2886,7 @@ valid_utf8_hostname.o: valid_utf8_hostname.h
valid_utf8_hostname.o: vbuf.h
valid_utf8_hostname.o: vstring.h
valid_utf8_string.o: check_arg.h
+valid_utf8_string.o: parse_utf8_char.h
valid_utf8_string.o: stringops.h
valid_utf8_string.o: sys_defs.h
valid_utf8_string.o: valid_utf8_string.c
diff --git a/src/util/argv.c b/src/util/argv.c
index 4e05fd0..332426e 100644
--- a/src/util/argv.c
+++ b/src/util/argv.c
@@ -53,6 +53,11 @@
/* ssize_t pos;
/* ssize_t how_many;
/*
+/* char *argv_join(buf, argvp, delim)
+/* VSTRING *buf;
+/* ARGV *argvp;
+/* int delim;
+/*
/* void ARGV_FAKE_BEGIN(argv, arg)
/* const char *arg;
/*
@@ -109,6 +114,10 @@
/* starting at the specified array position. The result is
/* null-terminated.
/*
+/* argv_join() joins all elements in an array using the
+/* specified delimiter value, and appends the result to the
+/* specified buffer.
+/*
/* ARGV_FAKE_BEGIN/END are an optimization for the case where
/* a single string needs to be passed into an ARGV-based
/* interface. ARGV_FAKE_BEGIN() opens a statement block and
@@ -148,6 +157,7 @@
#include "mymalloc.h"
#include "msg.h"
+#include "vstring.h"
#include "argv.h"
#ifdef TEST
@@ -379,6 +389,20 @@ void argv_delete(ARGV *argvp, ssize_t first, ssize_t how_many)
argvp->argc -= how_many;
}
+/* argv_join - concatenate array elements with delimiter */
+
+char *argv_join(VSTRING *buf, ARGV *argv, int delim)
+{
+ char **cpp;
+
+ for (cpp = argv->argv; *cpp; cpp++) {
+ vstring_strcat(buf, *cpp);
+ if (cpp[1])
+ VSTRING_ADDCH(buf, delim);
+ }
+ return (vstring_str(buf));
+}
+
#ifdef TEST
/*
@@ -402,6 +426,7 @@ typedef struct TEST_CASE {
const char *exp_panic_msg; /* expected panic */
int exp_argc; /* expected array length */
const char *exp_argv[ARRAY_LEN]; /* expected array content */
+ int join_delim; /* argv_join() delimiter */
} TEST_CASE;
#define TERMINATE_ARRAY (1)
@@ -559,6 +584,24 @@ static ARGV *test_argv_bad_delete3(const TEST_CASE *tp, ARGV *argvp)
return (argvp);
}
+/* test_argv_join - populate, join, and overwrite */
+
+static ARGV *test_argv_join(const TEST_CASE *tp, ARGV *argvp)
+{
+ VSTRING *buf = vstring_alloc(100);
+
+ /*
+ * Impedance mismatch: argv_join() produces output to VSTRING, but the
+ * test fixture wants output to ARGV.
+ */
+ test_argv_populate(tp, argvp);
+ argv_join(buf, argvp, tp->join_delim);
+ argv_delete(argvp, 0, argvp->argc);
+ argv_add(argvp, vstring_str(buf), ARGV_END);
+ vstring_free(buf);
+ return (argvp);
+}
+
/* test_argv_verify - verify result */
static int test_argv_verify(const TEST_CASE *tp, ARGV *argvp)
@@ -573,7 +616,7 @@ static int test_argv_verify(const TEST_CASE *tp, ARGV *argvp)
}
if (strcmp(vstring_str(test_panic_str), tp->exp_panic_msg) != 0) {
msg_warn("test case '%s': got '%s', want: '%s'",
- tp->label, vstring_str(test_panic_str), tp->exp_panic_msg);
+ tp->label, vstring_str(test_panic_str), tp->exp_panic_msg);
return (FAIL);
}
return (PASS);
@@ -682,6 +725,18 @@ static const TEST_CASE test_cases[] = {
{"foo", "baz", "bar", 0}, 0, test_argv_bad_delete3,
"argv_delete bad range: (start=100 count=1)"
},
+ {"argv_join, multiple strings",
+ {"foo", "baz", "bar", 0}, 0, test_argv_join,
+ 0, 1, {"foo:baz:bar", 0}, ':'
+ },
+ {"argv_join, one string",
+ {"foo", 0}, 0, test_argv_join,
+ 0, 1, {"foo", 0}, ':'
+ },
+ {"argv_join, empty",
+ {0}, 0, test_argv_join,
+ 0, 1, {"", 0}, ':'
+ },
0,
};
diff --git a/src/util/argv.h b/src/util/argv.h
index b0098ce..f1e746a 100644
--- a/src/util/argv.h
+++ b/src/util/argv.h
@@ -33,6 +33,8 @@ extern void argv_truncate(ARGV *, ssize_t);
extern void argv_insert_one(ARGV *, ssize_t, const char *);
extern void argv_replace_one(ARGV *, ssize_t, const char *);
extern void argv_delete(ARGV *, ssize_t, ssize_t);
+struct VSTRING;
+extern char *argv_join(struct VSTRING *buf, ARGV *, int);
extern ARGV *argv_free(ARGV *);
extern ARGV *argv_split(const char *, const char *);
diff --git a/src/util/casefold.c b/src/util/casefold.c
index d3ebd4b..94860b8 100644
--- a/src/util/casefold.c
+++ b/src/util/casefold.c
@@ -300,7 +300,7 @@ int main(int argc, char **argv)
encode_utf8(buffer, codepoint);
if (msg_verbose)
vstream_printf("U+%X -> %s\n", codepoint, STR(buffer));
- if (valid_utf8_string(STR(buffer), LEN(buffer)) == 0)
+ if (valid_utf8_stringz(STR(buffer)) == 0)
msg_fatal("bad utf-8 encoding for U+%X\n", codepoint);
casefold(dest, STR(buffer));
}
diff --git a/src/util/dict_inline.c b/src/util/dict_inline.c
index 72339b2..d7f9344 100644
--- a/src/util/dict_inline.c
+++ b/src/util/dict_inline.c
@@ -87,7 +87,7 @@ DICT *dict_inline_open(const char *name, int open_flags, int dict_flags)
*/
if (DICT_NEED_UTF8_ACTIVATION(util_utf8_enable, dict_flags)
&& allascii(name) == 0
- && valid_utf8_string(name, strlen(name)) == 0)
+ && valid_utf8_stringz(name) == 0)
DICT_INLINE_RETURN(dict_surrogate(DICT_TYPE_INLINE, name,
open_flags, dict_flags,
"bad UTF-8 syntax: \"%s:%s\"; "
diff --git a/src/util/dict_thash.c b/src/util/dict_thash.c
index 69eb17b..bae4a63 100644
--- a/src/util/dict_thash.c
+++ b/src/util/dict_thash.c
@@ -127,7 +127,7 @@ DICT *dict_thash_open(const char *path, int open_flags, int dict_flags)
*/
if ((dict->flags & DICT_FLAG_UTF8_ACTIVE)
&& allascii(STR(line_buffer)) == 0
- && valid_utf8_string(STR(line_buffer), LEN(line_buffer)) == 0) {
+ && valid_utf8_stringz(STR(line_buffer)) == 0) {
msg_warn("%s, line %d: non-UTF-8 input \"%s\""
" -- ignoring this line",
VSTREAM_PATH(fp), lineno, STR(line_buffer));
@@ -181,8 +181,8 @@ DICT *dict_thash_open(const char *path, int open_flags, int dict_flags)
" is this an alias file?", path, lineno);
/*
- * Optionally treat the value as a filename, and replace the value
- * with the BASE64-encoded content of the named file.
+ * Optionally treat the value as a filename, and replace the
+ * value with the BASE64-encoded content of the named file.
*/
if (dict_flags & DICT_FLAG_SRC_RHS_IS_FILE) {
VSTRING *base64_buf;
diff --git a/src/util/dict_utf8.c b/src/util/dict_utf8.c
index f1fc65a..9bb6b7b 100644
--- a/src/util/dict_utf8.c
+++ b/src/util/dict_utf8.c
@@ -100,7 +100,7 @@ static char *dict_utf8_check_fold(DICT *dict, const char *string,
/*
* Validate UTF-8 without casefolding.
*/
- if (!allascii(string) && valid_utf8_string(string, strlen(string)) == 0) {
+ if (!allascii(string) && valid_utf8_stringz(string) == 0) {
if (err)
*err = "malformed UTF-8 or invalid codepoint";
return (0);
@@ -123,7 +123,7 @@ static char *dict_utf8_check_fold(DICT *dict, const char *string,
static int dict_utf8_check(const char *string, CONST_CHAR_STAR *err)
{
- if (!allascii(string) && valid_utf8_string(string, strlen(string)) == 0) {
+ if (!allascii(string) && valid_utf8_stringz(string) == 0) {
if (err)
*err = "malformed UTF-8 or invalid codepoint";
return (0);
diff --git a/src/util/inet_prefix_top.c b/src/util/inet_prefix_top.c
index 8d5af00..f35d5f0 100644
--- a/src/util/inet_prefix_top.c
+++ b/src/util/inet_prefix_top.c
@@ -164,6 +164,7 @@ int main(int argc, char **argv)
msg_info("PASS %s/%d", str_name_code(af_map, tp->in_af),
tp->in_prefix_len);
}
+ myfree(act_prefix);
}
msg_info("PASS=%d FAIL=%d", pass, fail);
return (fail > 0);
diff --git a/src/util/logwriter.c b/src/util/logwriter.c
index aea2767..4a18be3 100644
--- a/src/util/logwriter.c
+++ b/src/util/logwriter.c
@@ -21,6 +21,9 @@
/* const char *path,
/* const char *buffer,
/* ssize_t buflen)
+/*
+/* int set_logwriter_create_perms(
+/* const char *mode)
/* DESCRIPTION
/* This module manages a logfile writer.
/*
@@ -38,6 +41,15 @@
/* logwriter_one_shot() combines all the above operations. The
/* result is zero if successful, VSTREAM_EOF if any operation
/* failed.
+/*
+/* set_logwriter_create_perms() sets the file permissions that
+/* will be used when creating a logfile. Valid inputs are
+/* "644", "640", and "600". Leading zeros are allowed and
+/* ignored.
+/* DIAGNOSTICS
+/* Fatal error: logfile create error; warning: logfile permission
+/* change error. set_logwriter_create_perms() returns the file
+/* create permission if the request is valid, -1 otherwise.
/* LICENSE
/* .ad
/* .fi
@@ -66,10 +78,12 @@
#include <mymalloc.h>
#include <safe_open.h>
#include <vstream.h>
+#include <name_code.h>
/*
* Application-specific.
*/
+static int logwriter_perms = 0600;
/* logwriter_open_or_die - open logfile */
@@ -82,7 +96,7 @@ VSTREAM *logwriter_open_or_die(const char *path)
#define NO_CHOWN (-1)
#define NO_CHGRP (-1)
- fp = safe_open(path, O_CREAT | O_WRONLY | O_APPEND, 0644,
+ fp = safe_open(path, O_CREAT | O_WRONLY | O_APPEND, logwriter_perms,
NO_STATP, NO_CHOWN, NO_CHGRP, why);
if (fp == 0)
msg_fatal("open logfile '%s': %s", path, vstring_str(why));
@@ -122,3 +136,21 @@ int logwriter_one_shot(const char *path, const char *buf, ssize_t len)
err |= logwriter_close(fp);
return (err ? VSTREAM_EOF : 0);
}
+
+/* set_logwriter_create_perms - logfile permission control */
+
+int set_logwriter_create_perms(const char *mode_str)
+{
+ static const NAME_CODE sane_perms[] = {
+ "644", 0644,
+ "640", 0640,
+ "600", 0600,
+ 0, -1,
+ };
+ int perms;
+
+ if ((perms = name_code(sane_perms, NAME_CODE_FLAG_NONE,
+ mode_str + strspn(mode_str, "0"))) != -1)
+ logwriter_perms = perms;
+ return (perms);
+}
diff --git a/src/util/logwriter.h b/src/util/logwriter.h
index f5266e4..c827d25 100644
--- a/src/util/logwriter.h
+++ b/src/util/logwriter.h
@@ -23,6 +23,7 @@ extern VSTREAM *logwriter_open_or_die(const char *);
extern int logwriter_write(VSTREAM *, const char *, ssize_t);
extern int logwriter_close(VSTREAM *);
extern int logwriter_one_shot(const char *, const char *, ssize_t);
+extern int set_logwriter_create_perms(const char *);
/* LICENSE
/* .ad
diff --git a/src/util/midna_domain.c b/src/util/midna_domain.c
index 333a5c9..bc016b6 100644
--- a/src/util/midna_domain.c
+++ b/src/util/midna_domain.c
@@ -178,7 +178,7 @@ static void *midna_domain_to_ascii_create(const char *name, void *unused_context
/*
* Paranoia: do not expose uidna_*() to unfiltered network data.
*/
- if (allascii(name) == 0 && valid_utf8_string(name, strlen(name)) == 0) {
+ if (allascii(name) == 0 && valid_utf8_stringz(name) == 0) {
msg_warn("%s: Problem translating domain \"%.100s\" to ASCII form: %s",
myname, name, "malformed UTF-8");
return (0);
@@ -232,7 +232,7 @@ static void *midna_domain_to_utf8_create(const char *name, void *unused_context)
/*
* Paranoia: do not expose uidna_*() to unfiltered network data.
*/
- if (allascii(name) == 0 && valid_utf8_string(name, strlen(name)) == 0) {
+ if (allascii(name) == 0 && valid_utf8_stringz(name) == 0) {
msg_warn("%s: Problem translating domain \"%.100s\" to UTF-8 form: %s",
myname, name, "malformed UTF-8");
return (0);
diff --git a/src/util/parse_utf8_char.h b/src/util/parse_utf8_char.h
new file mode 100644
index 0000000..b00a1c2
--- /dev/null
+++ b/src/util/parse_utf8_char.h
@@ -0,0 +1,122 @@
+/*++
+/* NAME
+/* parse_utf8_char 3h
+/* SUMMARY
+/* parse one UTF-8 multibyte character
+/* SYNOPSIS
+/* #include <parse_utf8_char.h>
+/*
+/* char *parse_utf8_char(str, end)
+/* const char *str;
+/* const char *end;
+/* DESCRIPTION
+/* parse_utf8_char() determines if the byte sequence starting
+/* at \fBstr\fR begins with a complete UTF-8 character as
+/* defined in RFC 3629. That is, a proper encoding of code
+/* points U+0000..U+10FFFF, excluding over-long encodings and
+/* excluding U+D800..U+DFFF surrogates.
+/*
+/* When the byte sequence starting at \fBstr\fR begins with a
+/* complete UTF-8 character, this function returns a pointer
+/* to the last byte in that character. Otherwise, it returns
+/* a null pointer.
+/*
+/* The \fBend\fR argument is either null (the byte sequence
+/* starting at \fBstr\fR must be null terminated), or \fBend
+/* - str\fR specifies the length of the byte sequence.
+/* BUGS
+/* Code points in the range U+FDD0..U+FDEF and ending in FFFE
+/* or FFFF are non-characters in UNICODE. This function does
+/* not reject these.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* porcupine.org
+/* Amawalk, NY 10501, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+
+#ifdef NO_INLINE
+#define inline /* */
+#endif
+
+/* parse_utf8_char - parse and validate one UTF8 multibyte sequence */
+
+static inline char *parse_utf8_char(const char *str, const char *end)
+{
+ const unsigned char *cp = (const unsigned char *) str;
+ const unsigned char *ep = (const unsigned char *) end;
+ unsigned char c0, ch;
+
+ /*
+ * Optimized for correct input, time, space, and for CPUs that have a
+ * decent number of registers. Other implementation considerations:
+ *
+ * - In the UTF-8 encoding, a non-leading byte is never null. Therefore,
+ * this function will correctly reject a partial UTF-8 character at the
+ * end of a null-terminated string.
+ *
+ * - If the "end" argument is a null constant, and if this function is
+ * inlined, then an optimizing compiler should propagate the constant
+ * through the "ep" variable, and eliminate any code branches that
+ * require ep != 0.
+ */
+ /* Single-byte encodings. */
+ if (EXPECTED((c0 = *cp) <= 0x7f) /* we know that c0 >= 0x0 */ ) {
+ return ((char *) cp);
+ }
+ /* Two-byte encodings. */
+ else if (EXPECTED(c0 <= 0xdf) /* we know that c0 >= 0x80 */ ) {
+ /* Exclude over-long encodings. */
+ if (UNEXPECTED(c0 < 0xc2)
+ || UNEXPECTED(ep && cp + 1 >= ep)
+ /* Require UTF-8 tail byte. */
+ || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80))
+ return (0);
+ return ((char *) cp);
+ }
+ /* Three-byte encodings. */
+ else if (EXPECTED(c0 <= 0xef) /* we know that c0 >= 0xe0 */ ) {
+ if (UNEXPECTED(ep && cp + 2 >= ep)
+ /* Exclude over-long encodings. */
+ || UNEXPECTED((ch = *++cp) < (c0 == 0xe0 ? 0xa0 : 0x80))
+ /* Exclude U+D800..U+DFFF. */
+ || UNEXPECTED(ch > (c0 == 0xed ? 0x9f : 0xbf))
+ /* Require UTF-8 tail byte. */
+ || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80))
+ return (0);
+ return ((char *) cp);
+ }
+ /* Four-byte encodings. */
+ else if (EXPECTED(c0 <= 0xf4) /* we know that c0 >= 0xf0 */ ) {
+ if (UNEXPECTED(ep && cp + 3 >= ep)
+ /* Exclude over-long encodings. */
+ || UNEXPECTED((ch = *++cp) < (c0 == 0xf0 ? 0x90 : 0x80))
+ /* Exclude code points above U+10FFFF. */
+ || UNEXPECTED(ch > (c0 == 0xf4 ? 0x8f : 0xbf))
+ /* Require UTF-8 tail byte. */
+ || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80)
+ /* Require UTF-8 tail byte. */
+ || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80))
+ return (0);
+ return ((char *) cp);
+ }
+ /* Invalid: c0 >= 0xf5 */
+ else {
+ return (0);
+ }
+}
+
+#undef inline
diff --git a/src/util/printable.c b/src/util/printable.c
index 6c148fd..0e1ae19 100644
--- a/src/util/printable.c
+++ b/src/util/printable.c
@@ -45,6 +45,10 @@
/* Google, Inc.
/* 111 8th Avenue
/* New York, NY 10011, USA
+/*
+/* Wietse Venema
+/* porcupine.org
+/* Amawalk, NY 10501, USA
/*--*/
/* System library. */
@@ -56,8 +60,9 @@
/* Utility library. */
#include "stringops.h"
+#include "parse_utf8_char.h"
-int util_utf8_enable = 0;
+int util_utf8_enable = 0;
/* printable - binary compatibility */
@@ -74,27 +79,150 @@ char *printable(char *string, int replacement)
char *printable_except(char *string, int replacement, const char *except)
{
- unsigned char *cp;
+ char *cp;
+ char *last;
int ch;
/*
- * XXX Replace invalid UTF8 sequences (too short, over-long encodings,
- * out-of-range code points, etc). See valid_utf8_string.c.
+ * In case of a non-UTF8 sequence (bad leader byte, bad non-leader byte,
+ * over-long encodings, out-of-range code points, etc), replace the first
+ * byte, and try to resynchronize at the next byte.
*/
- cp = (unsigned char *) string;
- while ((ch = *cp) != 0) {
- if (ISASCII(ch) && (ISPRINT(ch) || (except && strchr(except, ch)))) {
- /* ok */
- } else if (util_utf8_enable && ch >= 194 && ch <= 254
- && cp[1] >= 128 && cp[1] < 192) {
- /* UTF8; skip the rest of the bytes in the character. */
- while (cp[1] >= 128 && cp[1] < 192)
- cp++;
- } else {
- /* Not ASCII and not UTF8. */
- *cp = replacement;
+#define PRINT_OR_EXCEPT(ch) (ISPRINT(ch) || (except && strchr(except, ch)))
+
+ for (cp = string; (ch = *(unsigned char *) cp) != 0; cp++) {
+ if (util_utf8_enable == 0) {
+ if (ISASCII(ch) && PRINT_OR_EXCEPT(ch))
+ continue;
+ } else if ((last = parse_utf8_char(cp, 0)) == cp) { /* ASCII */
+ if (PRINT_OR_EXCEPT(ch))
+ continue;
+ } else if (last != 0) { /* Other UTF8 */
+ cp = last;
+ continue;
}
- cp++;
+ *cp = replacement;
}
return (string);
}
+
+#ifdef TEST
+
+#include <stdlib.h>
+#include <string.h>
+#include <msg.h>
+#include <msg_vstream.h>
+#include <mymalloc.h>
+#include <vstream.h>
+
+ /*
+ * Test cases for 1-, 2-, and 3-byte encodings. Originally contributed by
+ * Viktor Dukhovni, and annotated using translate.google.com.
+ *
+ * See valid_utf8_string.c for single-error tests.
+ *
+ * XXX Need a test for 4-byte encodings, preferably with strings that can be
+ * displayed.
+ */
+struct testcase {
+ const char *name;
+ const char *input;
+ const char *expected;;
+};
+static const struct testcase testcases[] = {
+ {"Printable ASCII",
+ "printable", "printable"
+ },
+ {"ASCII with control character",
+ "non\bn-printable", "non?n-printable"
+ },
+ {"Latin accented text, no error",
+ "na\303\257ve", "na\303\257ve"
+ },
+ {"Latin text, with error",
+ "na\303ve", "na?ve"
+ },
+ {"Viktor, Cyrillic, no error",
+ "\320\262\320\270\320\272\321\202\320\276\321\200",
+ "\320\262\320\270\320\272\321\202\320\276\321\200"
+ },
+ {"Viktor, Cyrillic, two errors",
+ "\320\262\320\320\272\272\321\202\320\276\321\200",
+ "\320\262?\320\272?\321\202\320\276\321\200"
+ },
+ {"Viktor, Hebrew, no error",
+ "\327\225\327\231\327\247\327\230\327\225\326\274\327\250",
+ "\327\225\327\231\327\247\327\230\327\225\326\274\327\250"
+ },
+ {"Viktor, Hebrew, with error",
+ "\327\225\231\327\247\327\230\327\225\326\274\327\250",
+ "\327\225?\327\247\327\230\327\225\326\274\327\250"
+ },
+ {"Chinese (Simplified), no error",
+ "\344\270\255\345\233\275\344\272\222\350\201\224\347\275\221\347"
+ "\273\234\345\217\221\345\261\225\347\212\266\345\206\265\347\273"
+ "\237\350\256\241\346\212\245\345\221\212",
+ "\344\270\255\345\233\275\344\272\222\350\201\224\347\275\221\347"
+ "\273\234\345\217\221\345\261\225\347\212\266\345\206\265\347\273"
+ "\237\350\256\241\346\212\245\345\221\212"
+ },
+ {"Chinese (Simplified), with errors",
+ "\344\270\255\345\344\272\222\350\224\347\275\221\347"
+ "\273\234\345\217\221\345\261\225\347\212\266\345\206\265\347\273"
+ "\237\350\256\241\346\212\245\345",
+ "\344\270\255?\344\272\222??\347\275\221\347"
+ "\273\234\345\217\221\345\261\225\347\212\266\345\206\265\347\273"
+ "\237\350\256\241\346\212\245?"
+ },
+};
+
+int main(int argc, char **argv)
+{
+ const struct testcase *tp;
+ 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++) {
+ char *input;
+ char *actual;
+ int ok = 0;
+
+ /*
+ * Notes:
+ *
+ * - The input is modified, therefore it must be copied.
+ *
+ * - The msg(3) functions use printable() which interferes when logging
+ * inputs and outputs. Use vstream_fprintf() instead.
+ */
+ vstream_fprintf(VSTREAM_ERR, "RUN %s\n", tp->name);
+ input = mystrdup(tp->input);
+ actual = printable(input, '?');
+
+ if (strcmp(actual, tp->expected) != 0) {
+ vstream_fprintf(VSTREAM_ERR, "input: >%s<, got: >%s<, want: >%s<\n",
+ tp->input, actual, tp->expected);
+ } else {
+ vstream_fprintf(VSTREAM_ERR, "input: >%s<, got and want: >%s<\n",
+ tp->input, actual);
+ ok = 1;
+ }
+ if (ok) {
+ vstream_fprintf(VSTREAM_ERR, "PASS %s\n", tp->name);
+ pass++;
+ } else {
+ vstream_fprintf(VSTREAM_ERR, "FAIL %s\n", tp->name);
+ fail++;
+ }
+ myfree(input);
+ }
+ msg_info("PASS=%d FAIL=%d", pass, fail);
+ return (fail > 0);
+}
+
+#endif
diff --git a/src/util/quote_for_json.c b/src/util/quote_for_json.c
new file mode 100644
index 0000000..f54af3f
--- /dev/null
+++ b/src/util/quote_for_json.c
@@ -0,0 +1,218 @@
+/*++
+/* NAME
+/* quote_for_json 3
+/* SUMMARY
+/* quote UTF-8 string value for JSON
+/* SYNOPSIS
+/* #include <quote_for_json.h>
+/*
+/* char *quote_for_json(
+/* VSTRING *result,
+/* const char *in,
+/* ssize_t len)
+/*
+/* char *quote_for_json_append(
+/* VSTRING *result,
+/* const char *in,
+/* ssize_t len)
+/* DESCRIPTION
+/* quote_for_json() takes well-formed UTF-8 encoded text,
+/* quotes that text compliant with RFC 4627, and returns a
+/* pointer to the resulting text. The input may contain null
+/* bytes, but the output will not.
+/*
+/* quote_for_json() produces short (two-letter) escape sequences
+/* for common control characters, double quote and backslash.
+/* It will not quote "/" (0x2F), and will quote DEL (0x7f) as
+/* \u007F to make it printable. The input byte sequence "\uXXXX"
+/* is quoted like any other text (the "\" is escaped as "\\").
+/*
+/* quote_for_json() does not perform UTF-8 validation. The caller
+/* should use valid_utf8_string() or printable() as appropriate.
+/*
+/* quote_for_json_append() appends the output to the result buffer.
+/*
+/* Arguments:
+/* .IP result
+/* Storage for the result, resized automatically.
+/* .IP in
+/* Pointer to the input byte sequence.
+/* .IP len
+/* The length of the input byte sequence, or a negative number
+/* when the byte sequence is null-terminated.
+/* DIAGNOSTICS
+/* Fatal error: memory allocation error.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*
+/* Wietse Venema
+/* porcupine.org
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <ctype.h>
+#include <string.h>
+
+ /*
+ * Utility library.
+ */
+#include <stringops.h>
+#include <vstring.h>
+
+#define STR(x) vstring_str(x)
+
+/* quote_for_json_append - quote JSON string, append result */
+
+char *quote_for_json_append(VSTRING *result, const char *text, ssize_t len)
+{
+ const char *cp;
+ int ch;
+
+ if (len < 0)
+ len = strlen(text);
+
+ for (cp = text; len > 0; len--, cp++) {
+ ch = *(const unsigned char *) cp;
+ if (UNEXPECTED(ISCNTRL(ch))) {
+ switch (ch) {
+ case '\b':
+ VSTRING_ADDCH(result, '\\');
+ VSTRING_ADDCH(result, 'b');
+ break;
+ case '\f':
+ VSTRING_ADDCH(result, '\\');
+ VSTRING_ADDCH(result, 'f');
+ break;
+ case '\n':
+ VSTRING_ADDCH(result, '\\');
+ VSTRING_ADDCH(result, 'n');
+ break;
+ case '\r':
+ VSTRING_ADDCH(result, '\\');
+ VSTRING_ADDCH(result, 'r');
+ break;
+ case '\t':
+ VSTRING_ADDCH(result, '\\');
+ VSTRING_ADDCH(result, 't');
+ break;
+ default:
+ /* All other controls including DEL and NUL. */
+ vstring_sprintf_append(result, "\\u%04X", ch);
+ break;
+ }
+ } else {
+ switch (ch) {
+ case '\\':
+ case '"':
+ VSTRING_ADDCH(result, '\\');
+ /* FALLTHROUGH */
+ default:
+ /* Includes malformed UTF-8. */
+ VSTRING_ADDCH(result, ch);
+ break;
+ }
+ }
+ }
+ VSTRING_TERMINATE(result);
+ return (STR(result));
+}
+
+/* quote_for_json - quote JSON string */
+
+char *quote_for_json(VSTRING *result, const char *text, ssize_t len)
+{
+ VSTRING_RESET(result);
+ return (quote_for_json_append(result, text, len));
+}
+
+#ifdef TEST
+
+ /*
+ * System library.
+ */
+#include <stdlib.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <msg_vstream.h>
+
+typedef struct TEST_CASE {
+ const char *label; /* identifies test case */
+ char *(*fn) (VSTRING *, const char *, ssize_t);
+ const char *input; /* input string */
+ ssize_t input_len; /* -1 or input length */
+ const char *exp_res; /* expected result */
+} TEST_CASE;
+
+#define PASS (0)
+#define FAIL (1)
+
+ /*
+ * The test cases.
+ */
+static const TEST_CASE test_cases[] = {
+ {"ordinary ASCII text", quote_for_json,
+ " abcABC012.,[]{}/", -1, " abcABC012.,[]{}/",
+ },
+ {"quote_for_json_append", quote_for_json_append,
+ "foo", -1, " abcABC012.,[]{}/foo",
+ },
+ {"common control characters", quote_for_json,
+ "\b\f\r\n\t", -1, "\\b\\f\\r\\n\\t",
+ },
+ {"uncommon control characters and DEL", quote_for_json,
+ "\0\01\037\040\176\177", 6, "\\u0000\\u0001\\u001F ~\\u007F",
+ },
+ {"malformed UTF-8", quote_for_json,
+ "\\*\\uasd\\u007F\x80", -1, "\\\\*\\\\uasd\\\\u007F\x80",
+ },
+ 0,
+};
+
+int main(int argc, char **argv)
+{
+ const TEST_CASE *tp;
+ int pass = 0;
+ int fail = 0;
+ VSTRING *res_buf = vstring_alloc(100);
+
+ msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
+
+ for (tp = test_cases; tp->label != 0; tp++) {
+ int test_fail = 0;
+ char *res;
+
+ msg_info("RUN %s", tp->label);
+ res = tp->fn(res_buf, tp->input, tp->input_len);
+ if (strcmp(res, tp->exp_res) != 0) {
+ msg_warn("test case '%s': got '%s', want '%s'",
+ tp->label, res, tp->exp_res);
+ test_fail = 1;
+ }
+ if (test_fail) {
+ fail++;
+ msg_info("FAIL %s", tp->label);
+ test_fail = 1;
+ } else {
+ msg_info("PASS %s", tp->label);
+ pass++;
+ }
+ }
+ msg_info("PASS=%d FAIL=%d", pass, fail);
+ vstring_free(res_buf);
+ exit(fail != 0);
+}
+
+#endif
diff --git a/src/util/readlline.c b/src/util/readlline.c
index 015877a..721b75f 100644
--- a/src/util/readlline.c
+++ b/src/util/readlline.c
@@ -85,9 +85,15 @@ VSTRING *readllines(VSTRING *buf, VSTREAM *fp, int *lineno, int *first_line)
int next;
ssize_t start;
char *cp;
+ int my_lineno = 0, my_first_line, got_null = 0;
VSTRING_RESET(buf);
+ if (lineno == 0)
+ lineno = &my_lineno;
+ if (first_line == 0)
+ first_line = &my_first_line;
+
/*
* Ignore comment lines, all whitespace lines, and empty lines. Terminate
* at EOF or at the beginning of the next logical line.
@@ -95,16 +101,19 @@ VSTRING *readllines(VSTRING *buf, VSTREAM *fp, int *lineno, int *first_line)
for (;;) {
/* Read one line, possibly not newline terminated. */
start = LEN(buf);
- while ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF && ch != '\n')
+ while ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF && ch != '\n') {
VSTRING_ADDCH(buf, ch);
- if (lineno != 0 && (ch == '\n' || LEN(buf) > start))
+ if (ch == 0)
+ got_null = 1;
+ }
+ if (ch == '\n' || LEN(buf) > start)
*lineno += 1;
/* Ignore comment line, all whitespace line, or empty line. */
for (cp = STR(buf) + start; cp < END(buf) && ISSPACE(*cp); cp++)
/* void */ ;
if (cp == END(buf) || *cp == '#')
vstring_truncate(buf, start);
- else if (start == 0 && lineno != 0 && first_line != 0)
+ if (start == 0)
*first_line = *lineno;
/* Terminate at EOF or at the beginning of the next logical line. */
if (ch == VSTREAM_EOF)
@@ -119,6 +128,20 @@ VSTRING *readllines(VSTRING *buf, VSTREAM *fp, int *lineno, int *first_line)
VSTRING_TERMINATE(buf);
/*
+ * This code does not care about embedded null bytes, but callers do.
+ */
+ if (got_null) {
+ const char *why = "text after null byte may be ignored";
+
+ if (*first_line == *lineno)
+ msg_warn("%s, line %d: %s",
+ VSTREAM_PATH(fp), *lineno, why);
+ else
+ msg_warn("%s, line %d-%d: %s",
+ VSTREAM_PATH(fp), *first_line, *lineno, why);
+ }
+
+ /*
* Invalid input: continuing text without preceding text. Allowing this
* would complicate "postconf -e", which implements its own multi-line
* parsing routine. Do not abort, just warn, so that critical programs
@@ -136,3 +159,205 @@ VSTRING *readllines(VSTRING *buf, VSTREAM *fp, int *lineno, int *first_line)
*/
return (LEN(buf) > 0 ? buf : 0);
}
+
+ /*
+ * Stand-alone test program.
+ */
+#ifdef TEST
+#include <stdlib.h>
+#include <string.h>
+#include <msg.h>
+#include <msg_vstream.h>
+#include <stringops.h>
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * Test cases. Note: the input and exp_output fields are converted with
+ * unescape(). Embedded null bytes must be specified as \\0.
+ */
+struct testcase {
+ const char *name;
+ const char *input;
+ const char *exp_output;
+ int exp_first_line;
+ int exp_last_line;
+};
+
+static const struct testcase testcases[] = {
+ {"leading space before non-comment",
+ " abcde\nfghij\n",
+ "fghij",
+ 2, 2
+ /* Expect "logical line must not start with whitespace" */
+ },
+ {"leading space before leading comment",
+ " #abcde\nfghij\n",
+ "fghij",
+ 2, 2
+ },
+ {"leading #comment at beginning of line",
+ "#abc\ndef",
+ "def",
+ 2, 2,
+ },
+ {"empty line before non-comment",
+ "\nabc\n",
+ "abc",
+ 2, 2,
+ },
+ {"whitespace line before non-comment",
+ " \nabc\n",
+ "abc",
+ 2, 2,
+ },
+ {"missing newline at end of non-comment",
+ "abc def",
+ "abc def",
+ 1, 1,
+ },
+ {"missing newline at end of comment",
+ "#abc def",
+ "",
+ 1, 1,
+ },
+ {"embedded null, single-line",
+ "abc\\0def",
+ "abc\\0def",
+ 1, 1,
+ /* Expect "line 1: text after null byte may be ignored" */
+ },
+ {"embedded null, multiline",
+ "abc\\0\n def",
+ "abc\\0 def",
+ 1, 2,
+ /* Expect "line 1-2: text after null byte may be ignored" */
+ },
+ {"embedded null in comment",
+ "#abc\\0\ndef",
+ "def",
+ 2, 2,
+ /* Expect "line 2: text after null byte may be ignored" */
+ },
+ {"multiline input",
+ "abc\n def\n",
+ "abc def",
+ 1, 2,
+ },
+ {"multiline input with embedded #comment after space",
+ "abc\n #def\n ghi",
+ "abc ghi",
+ 1, 3,
+ },
+ {"multiline input with embedded #comment flush left",
+ "abc\n#def\n ghi",
+ "abc ghi",
+ 1, 3,
+ },
+ {"multiline input with embedded whitespace line",
+ "abc\n \n ghi",
+ "abc ghi",
+ 1, 3,
+ },
+ {"multiline input with embedded empty line",
+ "abc\n\n ghi",
+ "abc ghi",
+ 1, 3,
+ },
+ {"multiline input with embedded #comment after space",
+ "abc\n #def\n",
+ "abc",
+ 1, 2,
+ },
+ {"multiline input with embedded #comment flush left",
+ "abc\n#def\n",
+ "abc",
+ 1, 2,
+ },
+ {"empty line at end of file",
+ "\n",
+ "",
+ 1, 1,
+ },
+ {"whitespace line at end of file",
+ "\n \n",
+ "",
+ 2, 2,
+ },
+ {"whitespace at end of file",
+ "abc\n ",
+ "abc",
+ 1, 2,
+ },
+};
+
+int main(int argc, char **argv)
+{
+ const struct testcase *tp;
+ VSTRING *inp_buf = vstring_alloc(100);
+ VSTRING *exp_buf = vstring_alloc(100);
+ VSTRING *out_buf = vstring_alloc(100);
+ VSTRING *esc_buf = vstring_alloc(100);
+ VSTREAM *fp;
+ int last_line;
+ int first_line;
+ int pass;
+ int fail;
+
+#define NUM_TESTS sizeof(testcases)/sizeof(testcases[0])
+
+ msg_vstream_init(basename(argv[0]), VSTREAM_ERR);
+ util_utf8_enable = 1;
+
+ for (pass = fail = 0, tp = testcases; tp < testcases + NUM_TESTS; tp++) {
+ int ok = 0;
+
+ vstream_fprintf(VSTREAM_ERR, "RUN %s\n", tp->name);
+ unescape(inp_buf, tp->input);
+ unescape(exp_buf, tp->exp_output);
+ if ((fp = vstream_memopen(inp_buf, O_RDONLY)) == 0)
+ msg_panic("open memory stream for reading: %m");
+ vstream_control(fp, CA_VSTREAM_CTL_PATH("memory buffer"),
+ CA_VSTREAM_CTL_END);
+ last_line = 0;
+ if (readllines(out_buf, fp, &last_line, &first_line) == 0) {
+ VSTRING_RESET(out_buf);
+ VSTRING_TERMINATE(out_buf);
+ }
+ if (LEN(out_buf) != LEN(exp_buf)) {
+ msg_warn("unexpected output length, got: %ld, want: %ld",
+ (long) LEN(out_buf), (long) LEN(exp_buf));
+ } else if (memcmp(STR(out_buf), STR(exp_buf), LEN(out_buf)) != 0) {
+ msg_warn("unexpected output: got: >%s<, want: >%s<",
+ STR(escape(esc_buf, STR(out_buf), LEN(out_buf))),
+ tp->exp_output);
+ } else if (first_line != tp->exp_first_line) {
+ msg_warn("unexpected first_line: got: %d, want: %d",
+ first_line, tp->exp_first_line);
+ } else if (last_line != tp->exp_last_line) {
+ msg_warn("unexpected last_line: got: %d, want: %d",
+ last_line, tp->exp_last_line);
+ } else {
+ vstream_fprintf(VSTREAM_ERR, "got and want: >%s<\n",
+ tp->exp_output);
+ ok = 1;
+ }
+ if (ok) {
+ vstream_fprintf(VSTREAM_ERR, "PASS %s\n", tp->name);
+ pass++;
+ } else {
+ vstream_fprintf(VSTREAM_ERR, "FAIL %s\n", tp->name);
+ fail++;
+ }
+ vstream_fclose(fp);
+ }
+ vstring_free(inp_buf);
+ vstring_free(exp_buf);
+ vstring_free(out_buf);
+ vstring_free(esc_buf);
+
+ msg_info("PASS=%d FAIL=%d", pass, fail);
+ return (fail > 0);
+}
+
+#endif
diff --git a/src/util/stringops.h b/src/util/stringops.h
index 97aa597..db56f23 100644
--- a/src/util/stringops.h
+++ b/src/util/stringops.h
@@ -60,10 +60,13 @@ extern int allascii_len(const char *, ssize_t);
extern const char *WARN_UNUSED_RESULT split_nameval(char *, char **, char **);
extern const char *WARN_UNUSED_RESULT split_qnameval(char *, char **, char **);
extern int valid_utf8_string(const char *, ssize_t);
+extern int valid_utf8_stringz(const char *);
extern size_t balpar(const char *, const char *);
extern char *WARN_UNUSED_RESULT extpar(char **, const char *, int);
extern int strcasecmp_utf8x(int, const char *, const char *);
extern int strncasecmp_utf8x(int, const char *, const char *, ssize_t);
+extern char *quote_for_json(VSTRING *, const char *, ssize_t);
+extern char *quote_for_json_append(VSTRING *, const char *, ssize_t);
#define EXTPAR_FLAG_NONE (0)
#define EXTPAR_FLAG_STRIP (1<<0) /* "{ text }" -> "text" */
diff --git a/src/util/sys_defs.h b/src/util/sys_defs.h
index 9247185..62749ab 100644
--- a/src/util/sys_defs.h
+++ b/src/util/sys_defs.h
@@ -1332,6 +1332,13 @@ extern int dup2_pass_on_exec(int oldd, int newd);
#endif
/*
+ * The RFC 5322 Date and Time Specification recommends single space between
+ * date-time tokens. To avoid breaking change, format all numerical days as
+ * two-digit days (i.e. days 1-9 now have a leading zero instead of space).
+ */
+#define TWO_DIGIT_DAY_IN_DATE_TIME
+
+ /*
* Check for required but missing definitions.
*/
#if !defined(HAS_FCNTL_LOCK) && !defined(HAS_FLOCK_LOCK)
diff --git a/src/util/valid_hostname.c b/src/util/valid_hostname.c
index 8b234c4..457d1f1 100644
--- a/src/util/valid_hostname.c
+++ b/src/util/valid_hostname.c
@@ -6,9 +6,9 @@
/* SYNOPSIS
/* #include <valid_hostname.h>
/*
-/* int valid_hostname(name, gripe)
+/* int valid_hostname(name, flags)
/* const char *name;
-/* int gripe;
+/* int flags;
/*
/* int valid_hostaddr(addr, gripe)
/* const char *addr;
@@ -32,6 +32,10 @@
/* dots, no leading or trailing dots or hyphens, no labels
/* longer than VALID_LABEL_LEN characters, and it should not
/* be all numeric.
+/* The flags argument is the bit-wise or of zero or more of
+/* DO_GRIPE or DO_WILDCARD (the latter allows the "*." name
+/* prefix, which is rare but valid in some DNS responses and
+/* queries).
/*
/* valid_hostaddr() requires that the input is a valid string
/* representation of an IPv4 or IPv6 network address as
@@ -403,8 +407,9 @@ int main(int unused_argc, char **argv)
while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
msg_info("testing: \"%s\"", vstring_str(buffer));
- valid_hostname(vstring_str(buffer), DO_GRIPE);
- valid_hostaddr(vstring_str(buffer), DO_GRIPE);
+ valid_hostname(vstring_str(buffer), DO_GRIPE | DO_WILDCARD);
+ if (strchr(vstring_str(buffer), '*') == 0)
+ valid_hostaddr(vstring_str(buffer), DO_GRIPE);
}
exit(0);
}
diff --git a/src/util/valid_hostname.in b/src/util/valid_hostname.in
index 608c0d1..4cdf019 100644
--- a/src/util/valid_hostname.in
+++ b/src/util/valid_hostname.in
@@ -53,3 +53,9 @@ g:a:a:a:a:a:a:a
a::b
:a::b
a::b:
+*.foo.bar
+*foo.bar
+foo.*.bar
+foo*bar
+foo.bar*
+*
diff --git a/src/util/valid_hostname.ref b/src/util/valid_hostname.ref
index 08b23b8..eccc558 100644
--- a/src/util/valid_hostname.ref
+++ b/src/util/valid_hostname.ref
@@ -141,3 +141,13 @@
./valid_hostname: testing: "a::b:"
./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a::b:
./valid_hostname: warning: valid_ipv6_hostaddr: bad null last field in IPv6 address: a::b:
+./valid_hostname: testing: "*.foo.bar"
+./valid_hostname: testing: "*foo.bar"
+./valid_hostname: warning: valid_hostname: '*' can be the first label only: *foo.bar
+./valid_hostname: testing: "foo.*.bar"
+./valid_hostname: warning: valid_hostname: '*' can be the first label only: foo.*.bar
+./valid_hostname: testing: "foo*bar"
+./valid_hostname: warning: valid_hostname: '*' can be the first label only: foo*bar
+./valid_hostname: testing: "foo.bar*"
+./valid_hostname: warning: valid_hostname: '*' can be the first label only: foo.bar*
+./valid_hostname: testing: "*"
diff --git a/src/util/valid_utf8_string.c b/src/util/valid_utf8_string.c
index 96b5b4d..f5b4ff4 100644
--- a/src/util/valid_utf8_string.c
+++ b/src/util/valid_utf8_string.c
@@ -9,24 +9,24 @@
/* int valid_utf8_string(str, len)
/* const char *str;
/* ssize_t len;
+/*
+/* int valid_utf8_stringz(str)
+/* const char *str;
+/* ssize_t len;
/* DESCRIPTION
-/* valid_utf8_string() determines if a string satisfies the UTF-8
-/* definition in RFC 3629. That is, it contains proper encodings
-/* of code points U+0000..U+10FFFF, excluding over-long encodings
-/* and excluding U+D800..U+DFFF surrogates.
+/* valid_utf8_string() determines if all bytes in a string
+/* satisfy parse_utf8_char(3h) checks. See there for any
+/* implementation limitations.
+/*
+/* valid_utf8_stringz() determines the same for zero-terminated
+/* strings.
/*
/* A zero-length string is considered valid.
/* DIAGNOSTICS
/* The result value is zero when the caller specifies a negative
-/* length, or a string that violates RFC 3629, for example a
-/* string that is truncated in the middle of a multi-byte
-/* sequence.
-/* BUGS
-/* But wait, there is more. Code points in the range U+FDD0..U+FDEF
-/* and ending in FFFE or FFFF are non-characters in UNICODE. This
-/* function does not block these.
+/* length, or a string that does not pass parse_utf8_char(3h) checks.
/* SEE ALSO
-/* RFC 3629
+/* parse_utf8_char(3h), parse one UTF-8 multibyte character
/* LICENSE
/* .ad
/* .fi
@@ -36,6 +36,10 @@
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* porcupine.org
+/* Amawalk, NY 10501, USA
/*--*/
/* System library. */
@@ -45,66 +49,50 @@
/* Utility library. */
#include <stringops.h>
+#include <parse_utf8_char.h>
/* valid_utf8_string - validate string according to RFC 3629 */
int valid_utf8_string(const char *str, ssize_t len)
{
- const unsigned char *end = (const unsigned char *) str + len;
- const unsigned char *cp;
- unsigned char c0, ch;
+ const char *ep = str + len;
+ const char *cp;
+ const char *last;
if (len < 0)
return (0);
- if (len <= 0)
+ if (len == 0)
return (1);
/*
- * Optimized for correct input, time, space, and for CPUs that have a
- * decent number of registers.
+ * Ideally, the compiler will inline parse_utf8_char().
*/
- for (cp = (const unsigned char *) str; cp < end; cp++) {
- /* Single-byte encodings. */
- if (EXPECTED((c0 = *cp) <= 0x7f) /* we know that c0 >= 0x0 */ ) {
- /* void */ ;
- }
- /* Two-byte encodings. */
- else if (EXPECTED(c0 <= 0xdf) /* we know that c0 >= 0x80 */ ) {
- /* Exclude over-long encodings. */
- if (UNEXPECTED(c0 < 0xc2)
- || UNEXPECTED(cp + 1 >= end)
- /* Require UTF-8 tail byte. */
- || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80))
- return (0);
- }
- /* Three-byte encodings. */
- else if (EXPECTED(c0 <= 0xef) /* we know that c0 >= 0xe0 */ ) {
- if (UNEXPECTED(cp + 2 >= end)
- /* Exclude over-long encodings. */
- || UNEXPECTED((ch = *++cp) < (c0 == 0xe0 ? 0xa0 : 0x80))
- /* Exclude U+D800..U+DFFF. */
- || UNEXPECTED(ch > (c0 == 0xed ? 0x9f : 0xbf))
- /* Require UTF-8 tail byte. */
- || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80))
- return (0);
- }
- /* Four-byte encodings. */
- else if (EXPECTED(c0 <= 0xf4) /* we know that c0 >= 0xf0 */ ) {
- if (UNEXPECTED(cp + 3 >= end)
- /* Exclude over-long encodings. */
- || UNEXPECTED((ch = *++cp) < (c0 == 0xf0 ? 0x90 : 0x80))
- /* Exclude code points above U+10FFFF. */
- || UNEXPECTED(ch > (c0 == 0xf4 ? 0x8f : 0xbf))
- /* Require UTF-8 tail byte. */
- || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80)
- /* Require UTF-8 tail byte. */
- || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80))
- return (0);
- }
- /* Invalid: c0 >= 0xf5 */
- else {
+ for (cp = str; cp < ep; cp++) {
+ if ((last = parse_utf8_char(cp, ep)) != 0)
+ cp = last;
+ else
+ return (0);
+ }
+ return (1);
+}
+
+/* valid_utf8_stringz - validate string according to RFC 3629 */
+
+int valid_utf8_stringz(const char *str)
+{
+ const char *cp;
+ const char *last;
+
+ /*
+ * Ideally, the compiler will inline parse_utf8_char(), propagate the
+ * null pointer constant value, and eliminate code branches that test
+ * whether 0 != 0.
+ */
+ for (cp = str; *cp; cp++) {
+ if ((last = parse_utf8_char(cp, 0)) != 0)
+ cp = last;
+ else
return (0);
- }
}
return (1);
}
@@ -114,26 +102,139 @@ int valid_utf8_string(const char *str, ssize_t len)
*/
#ifdef TEST
#include <stdlib.h>
+#include <string.h>
+#include <msg.h>
#include <vstream.h>
-#include <vstring.h>
-#include <vstring_vstream.h>
+#include <msg_vstream.h>
+
+ /*
+ * Test cases for 1-, 2-, and 3-byte encodings. See printable.c for UTF8
+ * parser resychronization tests.
+ *
+ * XXX Need a test for 4-byte encodings, preferably with strings that can be
+ * displayed.
+ *
+ * XXX Need tests with hand-crafted over-long encodings and surrogates.
+ */
+struct testcase {
+ const char *name;
+ const char *input;
+ int expected;
+};
-#define STR(x) vstring_str(x)
-#define LEN(x) VSTRING_LEN(x)
+#define T_VALID (1)
+#define T_INVALID (0)
+#define valid_to_str(v) ((v) ? "VALID" : "INVALID")
-int main(void)
+static const struct testcase testcases[] = {
+ {"Printable ASCII",
+ "printable", T_VALID,
+ },
+ {"Latin script, accented, no error",
+ "na\303\257ve", T_VALID,
+ },
+ {"Latin script, accented, missing non-leading byte",
+ "na\303ve", T_INVALID,
+ },
+ {"Latin script, accented, missing leading byte",
+ "na\257ve", T_INVALID,
+ },
+ {"Viktor, Cyrillic, no error",
+ "\320\262\320\270\320\272\321\202\320\276\321\200", T_VALID,
+ },
+ {"Viktor, Cyrillic, missing non-leading byte",
+ "\320\262\320\320\272\321\202\320\276\321\200", T_INVALID,
+ },
+ {"Viktor, Cyrillic, missing leading byte",
+ "\320\262\270\320\272\321\202\320\276\321\200", T_INVALID,
+ },
+ {"Viktor, Cyrillic, truncated",
+ "\320\262\320\270\320\272\321\202\320\276\321", T_INVALID,
+ },
+ {"Viktor, Hebrew, no error",
+ "\327\225\327\231\327\247\327\230\327\225\326\274\327\250", T_VALID,
+ },
+ {"Viktor, Hebrew, missing leading byte",
+ "\327\225\231\327\247\327\230\327\225\326\274\327\250", T_INVALID,
+ },
+ {"Chinese (Simplified), no error",
+ "\344\270\255\345\233\275\344\272\222\350\201\224\347\275\221\347"
+ "\273\234\345\217\221\345\261\225\347\212\266\345\206\265\347\273"
+ "\237\350\256\241\346\212\245\345\221\212", T_VALID,
+ },
+ {"Chinese (Simplified), missing leading byte",
+ "\344\270\255\345\233\275\344\272\222\350\201\224\275\221\347"
+ "\273\234\345\217\221\345\261\225\347\212\266\345\206\265\347\273"
+ "\237\350\256\241\346\212\245\345\221\212", T_INVALID,
+ },
+ {"Chinese (Simplified), missing first non-leading byte",
+ "\344\270\255\345\233\275\344\272\222\350\201\224\347\221\347"
+ "\273\234\345\217\221\345\261\225\347\212\266\345\206\265\347\273"
+ "\237\350\256\241\346\212\245\345\221\212", T_INVALID,
+ },
+ {"Chinese (Simplified), missing second non-leading byte",
+ "\344\270\255\345\233\275\344\272\222\350\201\224\347\275\347"
+ "\273\234\345\217\221\345\261\225\347\212\266\345\206\265\347\273"
+ "\237\350\256\241\346\212\245\345\221\212", T_INVALID,
+ },
+ {"Chinese (Simplified), truncated",
+ "\344\270\255\345\233\275\344\272\222\350\201\224\347\275\221\347"
+ "\273\234\345\217\221\345\261\225\347\212\266\345\206\265\347\273"
+ "\237\350\256\241\346\212\245\345", T_INVALID,
+ },
+};
+
+int main(int argc, char **argv)
{
- VSTRING *buf = vstring_alloc(1);
+ const struct testcase *tp;
+ 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 actual_l;
+ int actual_z;
+ int ok = 0;
- while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
- vstream_printf("%c", (LEN(buf) && !valid_utf8_string(STR(buf), LEN(buf))) ?
- '!' : ' ');
- vstream_fwrite(VSTREAM_OUT, STR(buf), LEN(buf));
- vstream_printf("\n");
+ /*
+ * Notes:
+ *
+ * - The msg(3) functions use printable() which interferes when logging
+ * inputs and outputs. Use vstream_fprintf() instead.
+ */
+ vstream_fprintf(VSTREAM_ERR, "RUN %s\n", tp->name);
+ actual_l = valid_utf8_string(tp->input, strlen(tp->input));
+ actual_z = valid_utf8_stringz(tp->input);
+
+ if (actual_l != tp->expected) {
+ vstream_fprintf(VSTREAM_ERR,
+ "input: >%s<, 'actual_l' got: >%s<, want: >%s<\n",
+ tp->input, valid_to_str(actual_l),
+ valid_to_str(tp->expected));
+ } else if (actual_z != tp->expected) {
+ vstream_fprintf(VSTREAM_ERR,
+ "input: >%s<, 'actual_z' got: >%s<, want: >%s<\n",
+ tp->input, valid_to_str(actual_z),
+ valid_to_str(tp->expected));
+ } else {
+ vstream_fprintf(VSTREAM_ERR, "input: >%s<, got and want: >%s<\n",
+ tp->input, valid_to_str(actual_l));
+ ok = 1;
+ }
+ if (ok) {
+ vstream_fprintf(VSTREAM_ERR, "PASS %s\n", tp->name);
+ pass++;
+ } else {
+ vstream_fprintf(VSTREAM_ERR, "FAIL %s\n", tp->name);
+ fail++;
+ }
}
- vstream_fflush(VSTREAM_OUT);
- vstring_free(buf);
- exit(0);
+ msg_info("PASS=%d FAIL=%d", pass, fail);
+ return (fail > 0);
}
#endif
diff --git a/src/util/vstream.c b/src/util/vstream.c
index b4f9fbb..affbcc0 100644
--- a/src/util/vstream.c
+++ b/src/util/vstream.c
@@ -522,6 +522,7 @@
/* System library. */
#include <sys_defs.h>
+#include <sys/stat.h>
#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
#include <stdarg.h>
#include <stddef.h>
@@ -1386,7 +1387,38 @@ VSTREAM *vstream_fopen(const char *path, int flags, mode_t mode)
VSTREAM *stream;
int fd;
- if ((fd = open(path, flags, mode)) < 0) {
+ /*
+ * To set permissions on new files only, we need to distinguish between
+ * creating a new file and opening an existing one.
+ */
+#define open_create(path, flags, mode) \
+ open((path), (flags) | (O_CREAT | O_EXCL), (mode))
+#define open_exist(path, flags, mode) \
+ open((path), (flags) & ~(O_CREAT | O_EXCL), (mode))
+
+ switch (flags & (O_CREAT | O_EXCL)) {
+ case O_CREAT:
+ fd = open_exist(path, flags, mode);
+ if (fd < 0 && errno == ENOENT) {
+ fd = open_create(path, flags, mode);
+ if (fd >= 0) {
+ if (fchmod(fd, mode) < 0) /* can't uncreate */
+ msg_warn("fchmod %s 0%o: %m", path, (unsigned) mode);
+ } else if ( /* fd < 0 && */ errno == EEXIST)
+ fd = open_exist(path, flags, mode);
+ }
+ break;
+ case O_CREAT | O_EXCL:
+ fd = open(path, flags, mode);
+ if (fd >= 0)
+ if (fchmod(fd, mode) < 0) /* can't uncreate */
+ msg_warn("fchmod %s 0%o: %m", path, (unsigned) mode);
+ break;
+ default:
+ fd = open(path, flags, mode);
+ break;
+ }
+ if (fd < 0) {
return (0);
} else {
stream = vstream_fdopen(fd, flags);
diff --git a/src/xsasl/xsasl_cyrus_server.c b/src/xsasl/xsasl_cyrus_server.c
index 4bf2ed2..e903289 100644
--- a/src/xsasl/xsasl_cyrus_server.c
+++ b/src/xsasl/xsasl_cyrus_server.c
@@ -52,6 +52,10 @@
/* Google, Inc.
/* 111 8th Avenue
/* New York, NY 10011, USA
+/*
+/* Wietse Venema
+/* porcupine.org
+/* Amawalk, NY 10501, USA
/*--*/
/* System library. */
diff --git a/src/xsasl/xsasl_server.c b/src/xsasl/xsasl_server.c
index e8d7e16..c504864 100644
--- a/src/xsasl/xsasl_server.c
+++ b/src/xsasl/xsasl_server.c
@@ -123,7 +123,10 @@
/* reply.
/*
/* xsasl_server_get_username() returns the stored username
-/* after successful authentication.
+/* after successful authentication. The username may be null
+/* after authentication failure, depending on the kind of
+/* failure and on authentication backend implementation
+/* details. A non-null result is converted to printable text.
/*
/* Arguments:
/* .IP addr_family
@@ -207,6 +210,10 @@
/* Google, Inc.
/* 111 8th Avenue
/* New York, NY 10011, USA
+/*
+/* Wietse Venema
+/* porcupine.org
+/* Amawalk, NY 10501, USA
/*--*/
/* System library. */