diff --git a/postfix/HISTORY b/postfix/HISTORY
index 94c94d7b3..449cbad12 100644
--- a/postfix/HISTORY
+++ b/postfix/HISTORY
@@ -29044,3 +29044,40 @@ Apologies for any names omitted.
quotes. Oscar Bataille reported that the non-recommended
form is not protected against SQL injection. Files:
global/dict_sqlite.c, global/dict_sqlite_test.c.
+
+20250326
+
+ Updated myaddrinfo tests to also cover service/port conversion.
+ Files: util/myaddrinfo4.ref, util/myaddrinfo4.ref2,
+ util/myaddrinfo.c, util/myaddrinfo.ref, util/myaddrinfo.ref2.
+
+20260402
+
+ Documentation: updated guidance for using DNS-based reputation
+ services. File: proto/postconf.proto.
+
+20250404
+
+ Code health: simplified the conversions from IPv4 mapped
+ IPv6 addresses (::ffff:d.d.d.d) to their IPv4 form (d.d.d.d),
+ for both the binary form and human-readable form. Added
+ unit tests to show that the conversions work as expected.
+ Files: util/normalize_v4mapped_addr.[hc],
+ util/normalize_v4mapped_addr_test.c.
+
+20260406
+
+ Code health: overhauled the haproxy adapter to simplify
+ code and to avoid unnecessary conversions between binary
+ and human-readable forms. Added more unit tests for the v1
+ and v2 proxy protocols. A separate update will overhaul
+ the smtpd 'peer' lookups. Files: global/haproxy_srvr.c,
+ global/haproxy_srvr_test.c.
+
+20250408
+
+ Code health: replace explicit code with normalize_v4mapped_xxx()
+ call. File: util/sane_sockaddr_to_hostaddr.c.
+
+ Bit rot: sane_sockaddr_to_hostaddr() may modify its inputs.
+ smtp/smtp_tlsrpt.c, postscreen/postscreen_endpt.c
diff --git a/postfix/html/postconf.5.html b/postfix/html/postconf.5.html
index 4bb3b351c..03d029cd0 100644
--- a/postfix/html/postconf.5.html
+++ b/postfix/html/postconf.5.html
@@ -8878,6 +8878,10 @@ clients,
and postscreen(8) will update an SMTP client's DNSBL score with
each non-error reply as described below.
+ NOTE: Always respect the usage policies of reputation services.
+Avoid public or ISP resolvers, unless the queries use your unique
+API key.
+
Caution: when postscreen rejects mail, its SMTP response contains
the DNSBL
domain name. Use the postscreen_dnsbl_reply_map feature to hide
@@ -15553,7 +15557,12 @@ The maps_rbl_reject_code para
rejected requests (default: 554), the default_rbl_reply parameter
specifies the default server reply, and the rbl_reply_maps parameter
specifies tables with server replies indexed by rbl_domain.
-This feature is available in Postfix 2.0 and later.
+
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking the key in SMTP server
+responses).
+This feature is available in Postfix 2.0 and later.
permit_dnswl_client dnswl_domain=d.d.d.d
@@ -15565,8 +15574,12 @@ If no "=d.d.d.d" is specified, accept the request when the
reversed client network address is listed with any A record under
dnswl_domain.
For safety, permit_dnswl_client is silently
ignored when it would override reject_unauth_destination. The
-result is DEFER_IF_REJECT when allowlist lookup fails. This feature
-is available in Postfix 2.8 and later.
+result is DEFER_IF_REJECT when allowlist lookup fails.
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking an API key in SMTP
+server responses).
+This feature is available in Postfix 2.8 and later.
reject_rhsbl_client rbl_domain=d.d.d.d
@@ -15579,9 +15592,14 @@ number..number ranges (Postfix version 2.8 and later). If no
hostname is listed with
any A record under rbl_domain. See the reject_rbl_client
description above for additional RBL related configuration parameters.
+
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking an API key in SMTP
+server responses).
This feature is available in Postfix 2.0 and later; with Postfix
version 2.8 and later, reject_rhsbl_reverse_client will usually
-produce better results.
+produce better results.
permit_rhswl_client rhswl_domain=d.d.d.d
@@ -15597,8 +15615,12 @@ allowlisting should be used only to reduce false positives in e.g.
DNS-based blocklists, and not for making access rule exceptions.
For safety, permit_rhswl_client is silently ignored when it
would override reject_unauth_destination. The result is DEFER_IF_REJECT
-when allowlist lookup fails. This feature is available in Postfix
-2.8 and later.
+when allowlist lookup fails.
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking an API key in SMTP
+server responses).
+This feature is available in Postfix 2.8 and later.
reject_rhsbl_reverse_client rbl_domain=d.d.d.d
@@ -15609,8 +15631,12 @@ one or more ";"-separated numbers or number..number ranges.
If no "=d.d.d.d" is specified, reject the request when the
unverified reverse client hostname is listed with any A record under
rbl_domain. See the reject_rbl_client description above for
-additional RBL related configuration parameters. This feature is
-available in Postfix 2.8 and later.
+additional RBL related configuration parameters.
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking an API key in SMTP
+server responses).
+This feature is available in Postfix 2.8 and later.
reject_unknown_client_hostname (with Postfix < 2.3: reject_unknown_client)
@@ -16512,8 +16538,12 @@ listed with any A record under rbl_domain. See the
parameters. Note: specify "smtpd_helo_required = yes" to fully
enforce this restriction (without "smtpd_helo_required = yes", a
client can simply skip reject_rhsbl_helo by not sending HELO or
-EHLO). This feature is available in Postfix 2.0
-and later.
+EHLO).
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking an API key in SMTP
+server responses).
+This feature is available in Postfix 2.0 and later.
reject_unknown_helo_hostname (with Postfix < 2.3: reject_unknown_hostname)
@@ -17231,8 +17261,12 @@ any A record under rbl_domain.
The default_rbl_reply parameter specifies the default server
reply; and the rbl_reply_maps parameter specifies tables with server
-replies indexed by rbl_domain. This feature is available
-in Postfix version 2.0 and later.
+replies indexed by rbl_domain.
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking an API key in SMTP
+server responses).
+This feature is available in Postfix version 2.0 and later.
reject_unauth_destination
@@ -18082,6 +18116,11 @@ listed with any A record under rbl_domain.
The
rejected requests (default: 554); the default_rbl_reply parameter
specifies the default server reply; and the rbl_reply_maps parameter
specifies tables with server replies indexed by rbl_domain.
+
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking an API key in SMTP
+server responses).
This feature is available in Postfix 2.0 and later.
reject_sender_login_mismatch
diff --git a/postfix/man/man5/postconf.5 b/postfix/man/man5/postconf.5
index 207b99fad..afb9cc97b 100644
--- a/postfix/man/man5/postconf.5
+++ b/postfix/man/man5/postconf.5
@@ -5505,6 +5505,10 @@ clients,
and \fBpostscreen\fR(8) will update an SMTP client's DNSBL score with
each non\-error reply as described below.
.PP
+NOTE: Always respect the usage policies of reputation services.
+Avoid public or ISP resolvers, unless the queries use your unique
+API key.
+.PP
Caution: when postscreen rejects mail, its SMTP response contains
the DNSBL
domain name. Use the postscreen_dnsbl_reply_map feature to hide
@@ -10381,6 +10385,12 @@ The maps_rbl_reject_code parameter specifies the response code for
rejected requests (default: 554), the default_rbl_reply parameter
specifies the default server reply, and the rbl_reply_maps parameter
specifies tables with server replies indexed by \fIrbl_domain\fR.
+.br
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking the key in SMTP server
+responses).
+.br
This feature is available in Postfix 2.0 and later.
.br
.IP "\fBpermit_dnswl_client \fIdnswl_domain=d.d.d.d\fR\fR"
@@ -10394,8 +10404,14 @@ reversed client network address is listed with any A record under
.br
For safety, permit_dnswl_client is silently
ignored when it would override reject_unauth_destination. The
-result is DEFER_IF_REJECT when allowlist lookup fails. This feature
-is available in Postfix 2.8 and later.
+result is DEFER_IF_REJECT when allowlist lookup fails.
+.br
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking an API key in SMTP
+server responses).
+.br
+This feature is available in Postfix 2.8 and later.
.br
.IP "\fBreject_rhsbl_client \fIrbl_domain=d.d.d.d\fR\fR"
Reject the request when the client hostname is listed with the
@@ -10407,6 +10423,12 @@ number..number ranges (Postfix version 2.8 and later). If no
hostname is listed with
any A record under \fIrbl_domain\fR. See the reject_rbl_client
description above for additional RBL related configuration parameters.
+.br
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking an API key in SMTP
+server responses).
+.br
This feature is available in Postfix 2.0 and later; with Postfix
version 2.8 and later, reject_rhsbl_reverse_client will usually
produce better results.
@@ -10426,8 +10448,14 @@ DNS\-based blocklists, and not for making access rule exceptions.
.br
For safety, permit_rhswl_client is silently ignored when it
would override reject_unauth_destination. The result is DEFER_IF_REJECT
-when allowlist lookup fails. This feature is available in Postfix
-2.8 and later.
+when allowlist lookup fails.
+.br
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking an API key in SMTP
+server responses).
+.br
+This feature is available in Postfix 2.8 and later.
.br
.IP "\fBreject_rhsbl_reverse_client \fIrbl_domain=d.d.d.d\fR\fR"
Reject the request when the unverified reverse client hostname
@@ -10437,8 +10465,14 @@ one or more ";"\-separated numbers or number..number ranges.
If no "\fI=d.d.d.d\fR" is specified, reject the request when the
unverified reverse client hostname is listed with any A record under
\fIrbl_domain\fR. See the reject_rbl_client description above for
-additional RBL related configuration parameters. This feature is
-available in Postfix 2.8 and later.
+additional RBL related configuration parameters.
+.br
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking an API key in SMTP
+server responses).
+.br
+This feature is available in Postfix 2.8 and later.
.br
.IP "\fBreject_unknown_client_hostname\fR (with Postfix < 2.3: reject_unknown_client)"
Reject the request when 1) the client IP address\->name mapping
@@ -11155,8 +11189,14 @@ reject_rbl_client description for additional RBL related configuration
parameters. Note: specify "smtpd_helo_required = yes" to fully
enforce this restriction (without "smtpd_helo_required = yes", a
client can simply skip reject_rhsbl_helo by not sending HELO or
-EHLO). This feature is available in Postfix 2.0
-and later.
+EHLO).
+.br
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking an API key in SMTP
+server responses).
+.br
+This feature is available in Postfix 2.0 and later.
.br
.IP "\fBreject_unknown_helo_hostname\fR (with Postfix < 2.3: reject_unknown_hostname)"
Reject the request when the HELO or EHLO hostname has no DNS A
@@ -11653,8 +11693,14 @@ The maps_rbl_reject_code
parameter specifies the response code for rejected requests (default:
554); the default_rbl_reply parameter specifies the default server
reply; and the rbl_reply_maps parameter specifies tables with server
-replies indexed by \fIrbl_domain\fR. This feature is available
-in Postfix version 2.0 and later.
+replies indexed by \fIrbl_domain\fR.
+.br
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking an API key in SMTP
+server responses).
+.br
+This feature is available in Postfix version 2.0 and later.
.br
.IP "\fBreject_unauth_destination\fR"
Reject the request unless one of the following is true:
@@ -12298,6 +12344,12 @@ maps_rbl_reject_code parameter specifies the response code for
rejected requests (default: 554); the default_rbl_reply parameter
specifies the default server reply; and the rbl_reply_maps parameter
specifies tables with server replies indexed by \fIrbl_domain\fR.
+.br
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking an API key in SMTP
+server responses).
+.br
This feature is available in Postfix 2.0 and later.
.br
.IP "\fBreject_sender_login_mismatch\fR"
diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto
index f754c961c..4aef0b2d3 100644
--- a/postfix/proto/postconf.proto
+++ b/postfix/proto/postconf.proto
@@ -5471,7 +5471,12 @@ The maps_rbl_reject_code parameter specifies the response code for
rejected requests (default: 554), the default_rbl_reply parameter
specifies the default server reply, and the rbl_reply_maps parameter
specifies tables with server replies indexed by rbl_domain.
-This feature is available in Postfix 2.0 and later.
+
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking the key in SMTP server
+responses).
+This feature is available in Postfix 2.0 and later.
permit_dnswl_client dnswl_domain=d.d.d.d
@@ -5483,8 +5488,12 @@ If no "=d.d.d.d" is specified, accept the request when the
reversed client network address is listed with any A record under
dnswl_domain.
For safety, permit_dnswl_client is silently
ignored when it would override reject_unauth_destination. The
-result is DEFER_IF_REJECT when allowlist lookup fails. This feature
-is available in Postfix 2.8 and later.
+result is DEFER_IF_REJECT when allowlist lookup fails.
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking an API key in SMTP
+server responses).
+This feature is available in Postfix 2.8 and later.
reject_rhsbl_client rbl_domain=d.d.d.d
@@ -5497,9 +5506,14 @@ number..number ranges (Postfix version 2.8 and later). If no
hostname is listed with
any A record under rbl_domain. See the reject_rbl_client
description above for additional RBL related configuration parameters.
+
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking an API key in SMTP
+server responses).
This feature is available in Postfix 2.0 and later; with Postfix
version 2.8 and later, reject_rhsbl_reverse_client will usually
-produce better results.
+produce better results.
permit_rhswl_client rhswl_domain=d.d.d.d
@@ -5515,8 +5529,12 @@ allowlisting should be used only to reduce false positives in e.g.
DNS-based blocklists, and not for making access rule exceptions.
For safety, permit_rhswl_client is silently ignored when it
would override reject_unauth_destination. The result is DEFER_IF_REJECT
-when allowlist lookup fails. This feature is available in Postfix
-2.8 and later.
+when allowlist lookup fails.
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking an API key in SMTP
+server responses).
+This feature is available in Postfix 2.8 and later.
reject_rhsbl_reverse_client rbl_domain=d.d.d.d
@@ -5527,8 +5545,12 @@ one or more ";"-separated numbers or number..number ranges.
If no "=d.d.d.d" is specified, reject the request when the
unverified reverse client hostname is listed with any A record under
rbl_domain. See the reject_rbl_client description above for
-additional RBL related configuration parameters. This feature is
-available in Postfix 2.8 and later.
+additional RBL related configuration parameters.
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking an API key in SMTP
+server responses).
+This feature is available in Postfix 2.8 and later.
reject_unknown_client_hostname (with Postfix < 2.3: reject_unknown_client)
@@ -6034,8 +6056,12 @@ reject_rbl_client description for additional RBL related configuration
parameters. Note: specify "smtpd_helo_required = yes" to fully
enforce this restriction (without "smtpd_helo_required = yes", a
client can simply skip reject_rhsbl_helo by not sending HELO or
-EHLO). This feature is available in Postfix 2.0
-and later.
+EHLO).
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking an API key in SMTP
+server responses).
+This feature is available in Postfix 2.0 and later.
reject_unknown_helo_hostname (with Postfix < 2.3: reject_unknown_hostname)
@@ -6343,8 +6369,12 @@ any A record under rbl_domain.
The maps_rbl_reject_code
parameter specifies the response code for rejected requests (default:
554); the default_rbl_reply parameter specifies the default server
reply; and the rbl_reply_maps parameter specifies tables with server
-replies indexed by rbl_domain. This feature is available
-in Postfix version 2.0 and later.
+replies indexed by rbl_domain.
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking an API key in SMTP
+server responses).
+This feature is available in Postfix version 2.0 and later.
reject_unauth_destination
@@ -6861,6 +6891,11 @@ maps_rbl_reject_code parameter specifies the response code for
rejected requests (default: 554); the default_rbl_reply parameter
specifies the default server reply; and the rbl_reply_maps parameter
specifies tables with server replies indexed by rbl_domain.
+
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see rbl_reply_maps for how to avoid leaking an API key in SMTP
+server responses).
This feature is available in Postfix 2.0 and later.
reject_sender_login_mismatch
@@ -14699,6 +14734,10 @@ clients,
and postscreen(8) will update an SMTP client's DNSBL score with
each non-error reply as described below.
+ NOTE: Always respect the usage policies of reputation services.
+Avoid public or ISP resolvers, unless the queries use your unique
+API key.
+
Caution: when postscreen rejects mail, its SMTP response contains
the DNSBL
domain name. Use the postscreen_dnsbl_reply_map feature to hide
diff --git a/postfix/proto/stop b/postfix/proto/stop
index a4a6b63ab..3ac8e4bb8 100644
--- a/postfix/proto/stop
+++ b/postfix/proto/stop
@@ -1672,3 +1672,4 @@ URIs
bugfix
MLKEM
cleartext
+redacted
diff --git a/postfix/proto/stop.spell-cc b/postfix/proto/stop.spell-cc
index 34e439196..3fa8e9528 100644
--- a/postfix/proto/stop.spell-cc
+++ b/postfix/proto/stop.spell-cc
@@ -1858,3 +1858,5 @@ TINYCDB
getdata
XXXSENDOPTS
xtra
+HAPROXY
+SRVR
diff --git a/postfix/src/global/Makefile.in b/postfix/src/global/Makefile.in
index 6ed62d412..8a2e26ca0 100644
--- a/postfix/src/global/Makefile.in
+++ b/postfix/src/global/Makefile.in
@@ -129,7 +129,7 @@ TESTPROG= domain_list dot_lockfile mail_addr_crunch mail_addr_find \
data_redirect addr_match_list safe_ultostr verify_sender_addr \
mail_version mail_dict server_acl uxtext mail_parm_split \
fold_addr smtp_reply_footer mail_addr_map normalize_mailhost_addr \
- haproxy_srvr map_search delivered_hdr login_sender_match \
+ haproxy_srvr_test map_search delivered_hdr login_sender_match \
compat_level config_known_tcp_ports hfrom_format rfc2047_code \
ascii_header_text sendopts_test dict_sqlite_test
@@ -381,7 +381,7 @@ smtp_reply_footer: smtp_reply_footer.c $(LIB) $(LIBS)
normalize_mailhost_addr: normalize_mailhost_addr.c $(LIB) $(LIBS)
$(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
-haproxy_srvr: haproxy_srvr.c $(LIB) $(LIBS)
+haproxy_srvr_test: haproxy_srvr_test.c $(LIB) $(LIBS)
$(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
map_search: map_search.c $(LIB) $(LIBS)
@@ -422,7 +422,7 @@ tests: tok822_test mime_tests strip_addr_test tok822_limit_test \
safe_ultostr_test mail_parm_split_test fold_addr_test \
smtp_reply_footer_test off_cvt_test mail_addr_crunch_test \
mail_addr_find_test mail_addr_map_test quote_822_local_test \
- normalize_mailhost_addr_test haproxy_srvr_test map_search_test \
+ normalize_mailhost_addr_test test_haproxy_srvr map_search_test \
delivered_hdr_test login_sender_match_test compat_level_test \
config_known_tcp_ports_test hfrom_format_test rfc2047_code_test \
ascii_header_text_test test_sendopts test_dict_sqlite
@@ -743,10 +743,8 @@ normalize_mailhost_addr_test: update normalize_mailhost_addr
diff /dev/null normalize_mailhost_addr.tmp
rm -f normalize_mailhost_addr.tmp
-haproxy_srvr_test: update haproxy_srvr
- -$(SHLIB_ENV) $(VALGRIND) ./haproxy_srvr >haproxy_srvr.tmp 2>&1
- diff /dev/null haproxy_srvr.tmp
- rm -f haproxy_srvr.tmp
+test_haproxy_srvr: update haproxy_srvr_test
+ $(SHLIB_ENV) $(VALGRIND) ./haproxy_srvr_test
map_search_test: update map_search map_search.ref
-$(SHLIB_ENV) $(VALGRIND) ./map_search >map_search.tmp 2>&1
@@ -1532,6 +1530,7 @@ haproxy_srvr.o: ../../include/inet_proto.h
haproxy_srvr.o: ../../include/msg.h
haproxy_srvr.o: ../../include/myaddrinfo.h
haproxy_srvr.o: ../../include/mymalloc.h
+haproxy_srvr.o: ../../include/normalize_v4mapped_addr.h
haproxy_srvr.o: ../../include/sock_addr.h
haproxy_srvr.o: ../../include/split_at.h
haproxy_srvr.o: ../../include/stringops.h
@@ -1541,6 +1540,18 @@ haproxy_srvr.o: ../../include/vbuf.h
haproxy_srvr.o: ../../include/vstring.h
haproxy_srvr.o: haproxy_srvr.c
haproxy_srvr.o: haproxy_srvr.h
+haproxy_srvr_test.o: ../../include/check_arg.h
+haproxy_srvr_test.o: ../../include/msg.h
+haproxy_srvr_test.o: ../../include/msg_vstream.h
+haproxy_srvr_test.o: ../../include/myaddrinfo.h
+haproxy_srvr_test.o: ../../include/sock_addr.h
+haproxy_srvr_test.o: ../../include/stringops.h
+haproxy_srvr_test.o: ../../include/sys_defs.h
+haproxy_srvr_test.o: ../../include/vbuf.h
+haproxy_srvr_test.o: ../../include/vstream.h
+haproxy_srvr_test.o: ../../include/vstring.h
+haproxy_srvr_test.o: haproxy_srvr.h
+haproxy_srvr_test.o: haproxy_srvr_test.c
header_body_checks.o: ../../include/argv.h
header_body_checks.o: ../../include/check_arg.h
header_body_checks.o: ../../include/dict.h
diff --git a/postfix/src/global/haproxy_srvr.c b/postfix/src/global/haproxy_srvr.c
index 211cf1d1a..9c8456ad9 100644
--- a/postfix/src/global/haproxy_srvr.c
+++ b/postfix/src/global/haproxy_srvr.c
@@ -6,6 +6,31 @@
/* SYNOPSIS
/* #include
/*
+/* const char *haproxy_srvr_parse_sa(
+/* const char *str,
+/* ssize_t *str_len,
+/* int *non_proxy,
+/* MAI_HOSTADDR_STR *smtp_client_addr,
+/* MAI_SERVPORT_STR *smtp_client_port,
+/* MAI_HOSTADDR_STR *smtp_server_addr,
+/* MAI_SERVPORT_STR *smtp_server_port,
+/* struct sockaddr *client_sa,
+/* SOCKADDR_SIZE *client_sa_len,
+/* struct sockaddr *server_sa,
+/* SOCKADDR_SIZE *server_sa_len)
+/*
+/* const char *haproxy_srvr_receive_sa(
+/* int fd,
+/* int *non_proxy,
+/* MAI_HOSTADDR_STR *smtp_client_addr,
+/* MAI_SERVPORT_STR *smtp_client_port,
+/* MAI_HOSTADDR_STR *smtp_server_addr,
+/* MAI_SERVPORT_STR *smtp_server_port,
+/* struct sockaddr *client_sa,
+/* SOCKADDR_SIZE *client_sa_len,
+/* struct sockaddr *server_sa,
+/* SOCKADDR_SIZE *server_sa_len)
+/* ABI COMPATIBILITY
/* const char *haproxy_srvr_parse(str, str_len, non_proxy,
/* smtp_client_addr, smtp_client_port,
/* smtp_server_addr, smtp_server_port)
@@ -27,7 +52,7 @@
/* MAI_HOSTADDR_STR *smtp_server_addr,
/* MAI_SERVPORT_STR *smtp_server_port;
/* DESCRIPTION
-/* haproxy_srvr_parse() parses a haproxy v1 or v2 protocol
+/* haproxy_srvr_parse_sa() parses a haproxy v1 or v2 protocol
/* message. The result is null in case of success, a pointer
/* to text (with the error type) in case of error. If both
/* IPv6 and IPv4 support are enabled, IPV4_IN_IPV6 address
@@ -36,7 +61,7 @@
/* of bytes parsed, and the non_proxy argument is true or false
/* if the haproxy message specifies a non-proxied connection.
/*
-/* haproxy_srvr_receive() receives and parses a haproxy protocol
+/* haproxy_srvr_receive_sa() receives and parses a haproxy protocol
/* handshake. This must be called before any I/O is done on
/* the specified file descriptor. The result is 0 in case of
/* success, -1 in case of error. All errors are logged.
@@ -45,6 +70,13 @@
/* TCP over IPv6, and non-proxied connections. In the latter
/* case, the caller is responsible for any local or remote
/* address/port lookup.
+/*
+/* The client or server sockaddr and length storage are updated
+/* when their pointers are non-null.
+/*
+/* haproxy_srvr_parse() and haproxy_srvr_receive() provide ABI
+/* backwards compatibility, passing null pointers for the sockaddr
+/* and length storage arguments.
/* LICENSE
/* .ad
/* .fi
@@ -59,6 +91,9 @@
/* Google, Inc.
/* 111 8th Avenue
/* New York, NY 10011, USA
+/*
+/* Wietse Venema
+/* porcupine.org
/*--*/
/* System library. */
@@ -82,89 +117,13 @@
#include
#include
#include
+#include
/* Global library. */
+#define _HAPROXY_SRVR_INTERNAL_
#include
-/* Application-specific. */
-
- /*
- * The haproxy protocol assumes that a haproxy header will normally not
- * exceed the default IPv4 TCP MSS, i.e. 576-40=536 bytes (the IPv6 default
- * is larger: 1280-60=1220). With a proxy header that contains IPv6
- * addresses, that leaves room for 536-52=484 bytes of TLVs. The Postfix
- * implementation does not support headers with UNIX-domain addresses.
- */
-#define HAPROXY_HEADER_MAX_LEN 536
-
- /*
- * Begin protocol v2 definitions from haproxy/include/types/connection.h.
- */
-#define PP2_SIGNATURE "\r\n\r\n\0\r\nQUIT\n"
-#define PP2_SIGNATURE_LEN 12
-#define PP2_HEADER_LEN 16
-
-/* ver_cmd byte */
-#define PP2_CMD_LOCAL 0x00
-#define PP2_CMD_PROXY 0x01
-#define PP2_CMD_MASK 0x0F
-
-#define PP2_VERSION 0x20
-#define PP2_VERSION_MASK 0xF0
-
-/* fam byte */
-#define PP2_TRANS_UNSPEC 0x00
-#define PP2_TRANS_STREAM 0x01
-#define PP2_TRANS_DGRAM 0x02
-#define PP2_TRANS_MASK 0x0F
-
-#define PP2_FAM_UNSPEC 0x00
-#define PP2_FAM_INET 0x10
-#define PP2_FAM_INET6 0x20
-#define PP2_FAM_UNIX 0x30
-#define PP2_FAM_MASK 0xF0
-
-/* len field (2 bytes) */
-#define PP2_ADDR_LEN_UNSPEC (0)
-#define PP2_ADDR_LEN_INET (4 + 4 + 2 + 2)
-#define PP2_ADDR_LEN_INET6 (16 + 16 + 2 + 2)
-#define PP2_ADDR_LEN_UNIX (108 + 108)
-
-#define PP2_HDR_LEN_UNSPEC (PP2_HEADER_LEN + PP2_ADDR_LEN_UNSPEC)
-#define PP2_HDR_LEN_INET (PP2_HEADER_LEN + PP2_ADDR_LEN_INET)
-#define PP2_HDR_LEN_INET6 (PP2_HEADER_LEN + PP2_ADDR_LEN_INET6)
-#define PP2_HDR_LEN_UNIX (PP2_HEADER_LEN + PP2_ADDR_LEN_UNIX)
-
-struct proxy_hdr_v2 {
- uint8_t sig[PP2_SIGNATURE_LEN]; /* PP2_SIGNATURE */
- uint8_t ver_cmd; /* protocol version | command */
- uint8_t fam; /* protocol family and transport */
- uint16_t len; /* length of remainder */
- union {
- struct { /* for TCP/UDP over IPv4, len = 12 */
- uint32_t src_addr;
- uint32_t dst_addr;
- uint16_t src_port;
- uint16_t dst_port;
- } ip4;
- struct { /* for TCP/UDP over IPv6, len = 36 */
- uint8_t src_addr[16];
- uint8_t dst_addr[16];
- uint16_t src_port;
- uint16_t dst_port;
- } ip6;
- struct { /* for AF_UNIX sockets, len = 216 */
- uint8_t src_addr[108];
- uint8_t dst_addr[108];
- } unx;
- } addr;
-};
-
- /*
- * End protocol v2 definitions from haproxy/include/types/connection.h.
- */
-
static const INET_PROTO_INFO *proto_info;
#define STR_OR_NULL(str) ((str) ? (str) : "(null)")
@@ -223,10 +182,14 @@ static int haproxy_srvr_parse_proto(const char *str, int *addr_family)
/* haproxy_srvr_parse_addr - extract and validate IP address */
static int haproxy_srvr_parse_addr(const char *str, MAI_HOSTADDR_STR *addr,
- int addr_family)
+ int addr_family,
+ struct sockaddr *sa,
+ SOCKADDR_SIZE *sa_len)
{
- struct addrinfo *res = 0;
+ struct addrinfo *res;
int err;
+ struct sockaddr_storage ss;
+ SOCKADDR_SIZE ss_len;
if (msg_verbose)
msg_info("haproxy_srvr_parse: addr=%s proto=%d",
@@ -238,30 +201,60 @@ static int haproxy_srvr_parse_addr(const char *str, MAI_HOSTADDR_STR *addr,
switch (addr_family) {
#ifdef AF_INET6
case AF_INET6:
- err = !valid_ipv6_hostaddr(str, DONT_GRIPE);
+ if (!valid_ipv6_hostaddr(str, DONT_GRIPE))
+ return (-1);
break;
#endif
case AF_INET:
- err = !valid_ipv4_hostaddr(str, DONT_GRIPE);
+ if (!valid_ipv4_hostaddr(str, DONT_GRIPE))
+ return (-1);
break;
default:
msg_panic("haproxy_srvr_parse: unexpected address family: %d",
addr_family);
}
- if (err == 0)
- err = (hostaddr_to_sockaddr(str, (char *) 0, 0, &res)
- || sane_sockaddr_to_hostaddr(res->ai_addr, res->ai_addrlen,
- addr, (MAI_SERVPORT_STR *) 0, 0));
- if (res)
- freeaddrinfo(res);
- if (err)
+
+ /*
+ * Convert the printable address to canonical form. Don't rely on the
+ * proxy. This requires a conversion to binary form and back, even if a
+ * caller such as postscreen does not need the binary form.
+ */
+ if ((err = hostaddr_to_sockaddr(str, (char *) 0, 0, &res)) != 0) {
+ msg_warn("haproxy_srvr_parse: hostaddr_to_sockaddr(\"%s\") failed: %s",
+ str, MAI_STRERROR(err));
return (-1);
+ }
+ if (sa == 0) {
+ sa = (struct sockaddr *) &ss;
+ ss_len = sizeof(ss);
+ sa_len = &ss_len;
+ } else {
+ if (sa_len == 0)
+ msg_panic("haproxy_srvr_parse: sockaddr length not specified");
+ }
+ if (*sa_len < res->ai_addrlen)
+ msg_panic("haproxy_srvr_parse: sockaddr size %d too small",
+ (int) *sa_len);
+ *sa_len = res->ai_addrlen;
+ memcpy((void *) sa, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+#ifdef AF_INET6
+ if (sa->sa_family == AF_INET6)
+ normalize_v4mapped_sockaddr(sa, sa_len);
+#endif
+ if ((err = sockaddr_to_hostaddr(sa, *sa_len,
+ addr, (MAI_SERVPORT_STR *) 0, 0)) != 0) {
+ msg_warn("haproxy_srvr_parse: sockaddr_to_hostaddr() failed: %s",
+ MAI_STRERROR(err));
+ return (-1);
+ }
return (0);
}
/* haproxy_srvr_parse_port - extract and validate TCP port */
-static int haproxy_srvr_parse_port(const char *str, MAI_SERVPORT_STR *port)
+static int haproxy_srvr_parse_port(const char *str, MAI_SERVPORT_STR *port,
+ struct sockaddr *sa)
{
if (msg_verbose)
msg_info("haproxy_srvr_parse: port=%s", STR_OR_NULL(str));
@@ -270,6 +263,21 @@ static int haproxy_srvr_parse_port(const char *str, MAI_SERVPORT_STR *port)
return (-1);
} else {
memcpy(port->buf, str, strlen(str) + 1);
+ if (sa != 0) {
+ switch (sa->sa_family) {
+#ifdef AF_INET6
+ case AF_INET6:
+ SOCK_ADDR_IN6_PORT(sa) = htons(atoi(str));
+ break;
+#endif
+ case AF_INET:
+ SOCK_ADDR_IN_PORT(sa) = htons(atoi(str));
+ break;
+ default:
+ msg_panic("haproxy_srvr_parse: unexpected address family: %d",
+ sa->sa_family);
+ }
+ }
return (0);
}
}
@@ -279,16 +287,33 @@ static int haproxy_srvr_parse_port(const char *str, MAI_SERVPORT_STR *port)
static int haproxy_srvr_parse_v2_addr_v4(uint32_t sin_addr,
unsigned sin_port,
MAI_HOSTADDR_STR *addr,
- MAI_SERVPORT_STR *port)
+ MAI_SERVPORT_STR *port,
+ struct sockaddr *sa,
+ SOCKADDR_SIZE *sa_len)
{
struct sockaddr_in sin;
+ SOCKADDR_SIZE sin_len;
- memset((void *) &sin, 0, sizeof(sin));
- sin.sin_family = AF_INET;
- sin.sin_addr.s_addr = sin_addr;
- sin.sin_port = sin_port;
- if (sockaddr_to_hostaddr((struct sockaddr *) &sin, sizeof(sin),
- addr, port, 0) < 0)
+ /*
+ * Convert the binary address and port to printable form.
+ */
+ if (sa == 0) {
+ sa = (struct sockaddr *) &sin;
+ sin_len = sizeof(sin);
+ sa_len = &sin_len;
+ } else {
+ if (sa_len == 0)
+ msg_panic("haproxy_srvr_parse: sockaddr length not specified");
+ if (*sa_len < sizeof(sin))
+ msg_panic("haproxy_srvr_parse: sockaddr size %d too small",
+ (int) *sa_len);
+ *sa_len = sizeof(sin);
+ }
+ memset((void *) sa, 0, *sa_len);
+ SOCK_ADDR_IN_FAMILY(sa) = AF_INET;
+ SOCK_ADDR_IN_ADDR(sa).s_addr = sin_addr;
+ SOCK_ADDR_IN_PORT(sa) = sin_port;
+ if (sockaddr_to_hostaddr(sa, *sa_len, addr, port, 0) < 0)
return (-1);
return (0);
}
@@ -300,22 +325,35 @@ static int haproxy_srvr_parse_v2_addr_v4(uint32_t sin_addr,
static int haproxy_srvr_parse_v2_addr_v6(uint8_t *sin6_addr,
unsigned sin6_port,
MAI_HOSTADDR_STR *addr,
- MAI_SERVPORT_STR *port)
+ MAI_SERVPORT_STR *port,
+ struct sockaddr *sa,
+ SOCKADDR_SIZE *sa_len)
{
struct sockaddr_in6 sin6;
+ SOCKADDR_SIZE sin6_len;
- memset((void *) &sin6, 0, sizeof(sin6));
- sin6.sin6_family = AF_INET6;
- memcpy(&sin6.sin6_addr, sin6_addr, 16);
- sin6.sin6_port = sin6_port;
- if (sockaddr_to_hostaddr((struct sockaddr *) &sin6,
- sizeof(sin6), addr, port, 0) < 0)
+ /*
+ * Convert the binary address and port to printable form.
+ */
+ if (sa == 0) {
+ sa = (struct sockaddr *) &sin6;
+ sin6_len = sizeof(sin6);
+ sa_len = &sin6_len;
+ } else {
+ if (sa_len == 0)
+ msg_panic("haproxy_srvr_parse: sockaddr length not specified");
+ if (*sa_len < sizeof(sin6))
+ msg_panic("haproxy_srvr_parse: sockaddr size %d too small",
+ (int) *sa_len);
+ *sa_len = sizeof(sin6);
+ }
+ memset((void *) sa, 0, *sa_len);
+ SOCK_ADDR_IN6_FAMILY(sa) = AF_INET6;
+ memcpy(&SOCK_ADDR_IN6_ADDR(sa), sin6_addr, sizeof(SOCK_ADDR_IN6_ADDR(sa)));
+ SOCK_ADDR_IN6_PORT(sa) = sin6_port;
+ normalize_v4mapped_sockaddr(sa, sa_len);
+ if (sockaddr_to_hostaddr(sa, *sa_len, addr, port, 0) < 0)
return (-1);
- if (addr->buf[0] == ':'
- && strncasecmp("::ffff:", addr->buf, 7) == 0
- && strchr((char *) proto_info->sa_family_list, AF_INET) != 0)
- memmove(addr->buf, addr->buf + 7,
- strlen(addr->buf) + 1 - 7);
return (0);
}
@@ -328,7 +366,11 @@ static const char *haproxy_srvr_parse_v2_hdr(const char *str, ssize_t *str_len,
MAI_HOSTADDR_STR *smtp_client_addr,
MAI_SERVPORT_STR *smtp_client_port,
MAI_HOSTADDR_STR *smtp_server_addr,
- MAI_SERVPORT_STR *smtp_server_port)
+ MAI_SERVPORT_STR *smtp_server_port,
+ struct sockaddr *client_sa,
+ SOCKADDR_SIZE *client_sa_len,
+ struct sockaddr *server_sa,
+ SOCKADDR_SIZE *server_sa_len)
{
const char myname[] = "haproxy_srvr_parse_v2_hdr";
struct proxy_hdr_v2 *hdr_v2;
@@ -357,14 +399,18 @@ static const char *haproxy_srvr_parse_v2_hdr(const char *str, ssize_t *str_len,
return ("short address field");
if (haproxy_srvr_parse_v2_addr_v4(hdr_v2->addr.ip4.src_addr,
hdr_v2->addr.ip4.src_port,
- smtp_client_addr, smtp_client_port) < 0)
+ smtp_client_addr, smtp_client_port,
+ client_sa, client_sa_len) < 0)
return ("client network address conversion error");
if (msg_verbose)
msg_info("%s: smtp_client_addr=%s smtp_client_port=%s",
myname, smtp_client_addr->buf, smtp_client_port->buf);
if (haproxy_srvr_parse_v2_addr_v4(hdr_v2->addr.ip4.dst_addr,
hdr_v2->addr.ip4.dst_port,
- smtp_server_addr, smtp_server_port) < 0)
+ smtp_server_addr,
+ smtp_server_port,
+ server_sa,
+ server_sa_len) < 0)
return ("server network address conversion error");
if (msg_verbose)
msg_info("%s: smtp_server_addr=%s smtp_server_port=%s",
@@ -380,7 +426,9 @@ static const char *haproxy_srvr_parse_v2_hdr(const char *str, ssize_t *str_len,
if (haproxy_srvr_parse_v2_addr_v6(hdr_v2->addr.ip6.src_addr,
hdr_v2->addr.ip6.src_port,
smtp_client_addr,
- smtp_client_port) < 0)
+ smtp_client_port,
+ client_sa,
+ client_sa_len) < 0)
return ("client network address conversion error");
if (msg_verbose)
msg_info("%s: smtp_client_addr=%s smtp_client_port=%s",
@@ -388,7 +436,9 @@ static const char *haproxy_srvr_parse_v2_hdr(const char *str, ssize_t *str_len,
if (haproxy_srvr_parse_v2_addr_v6(hdr_v2->addr.ip6.dst_addr,
hdr_v2->addr.ip6.dst_port,
smtp_server_addr,
- smtp_server_port) < 0)
+ smtp_server_port,
+ server_sa,
+ server_sa_len) < 0)
return ("server network address conversion error");
if (msg_verbose)
msg_info("%s: smtp_server_addr=%s smtp_server_port=%s",
@@ -418,14 +468,18 @@ static const char *haproxy_srvr_parse_v2_hdr(const char *str, ssize_t *str_len,
}
}
-/* haproxy_srvr_parse - parse haproxy line */
+/* haproxy_srvr_parse_sa - parse haproxy line */
-const char *haproxy_srvr_parse(const char *str, ssize_t *str_len,
- int *non_proxy,
- MAI_HOSTADDR_STR *smtp_client_addr,
- MAI_SERVPORT_STR *smtp_client_port,
- MAI_HOSTADDR_STR *smtp_server_addr,
- MAI_SERVPORT_STR *smtp_server_port)
+const char *haproxy_srvr_parse_sa(const char *str, ssize_t *str_len,
+ int *non_proxy,
+ MAI_HOSTADDR_STR *smtp_client_addr,
+ MAI_SERVPORT_STR *smtp_client_port,
+ MAI_HOSTADDR_STR *smtp_server_addr,
+ MAI_SERVPORT_STR *smtp_server_port,
+ struct sockaddr *client_sa,
+ SOCKADDR_SIZE *client_sa_len,
+ struct sockaddr *server_sa,
+ SOCKADDR_SIZE *server_sa_len)
{
const char *err;
@@ -456,14 +510,18 @@ const char *haproxy_srvr_parse(const char *str, ssize_t *str_len,
else if (haproxy_srvr_parse_proto(NEXT_TOKEN, &addr_family) < 0)
err = "bad or missing protocol type";
else if (haproxy_srvr_parse_addr(NEXT_TOKEN, smtp_client_addr,
- addr_family) < 0)
+ addr_family, client_sa,
+ client_sa_len) < 0)
err = "bad or missing client address";
else if (haproxy_srvr_parse_addr(NEXT_TOKEN, smtp_server_addr,
- addr_family) < 0)
+ addr_family, server_sa,
+ server_sa_len) < 0)
err = "bad or missing server address";
- else if (haproxy_srvr_parse_port(NEXT_TOKEN, smtp_client_port) < 0)
+ else if (haproxy_srvr_parse_port(NEXT_TOKEN, smtp_client_port,
+ client_sa) < 0)
err = "bad or missing client port";
- else if (haproxy_srvr_parse_port(NEXT_TOKEN, smtp_server_port) < 0)
+ else if (haproxy_srvr_parse_port(NEXT_TOKEN, smtp_server_port,
+ server_sa) < 0)
err = "bad or missing server port";
else {
err = 0;
@@ -480,17 +538,48 @@ const char *haproxy_srvr_parse(const char *str, ssize_t *str_len,
else {
return (haproxy_srvr_parse_v2_hdr(str, str_len, non_proxy,
smtp_client_addr, smtp_client_port,
- smtp_server_addr, smtp_server_port));
+ smtp_server_addr, smtp_server_port,
+ client_sa, client_sa_len,
+ server_sa, server_sa_len));
}
}
-/* haproxy_srvr_receive - receive and parse haproxy protocol handshake */
+/* haproxy_srvr_parse - ABI compatibility */
-int haproxy_srvr_receive(int fd, int *non_proxy,
- MAI_HOSTADDR_STR *smtp_client_addr,
- MAI_SERVPORT_STR *smtp_client_port,
- MAI_HOSTADDR_STR *smtp_server_addr,
- MAI_SERVPORT_STR *smtp_server_port)
+#undef haproxy_srvr_parse
+
+const char *haproxy_srvr_parse(const char *str, ssize_t *str_len,
+ int *non_proxy,
+ MAI_HOSTADDR_STR *smtp_client_addr,
+ MAI_SERVPORT_STR *smtp_client_port,
+ MAI_HOSTADDR_STR *smtp_server_addr,
+ MAI_SERVPORT_STR *smtp_server_port);
+
+const char *haproxy_srvr_parse(const char *str, ssize_t *str_len,
+ int *non_proxy,
+ MAI_HOSTADDR_STR *smtp_client_addr,
+ MAI_SERVPORT_STR *smtp_client_port,
+ MAI_HOSTADDR_STR *smtp_server_addr,
+ MAI_SERVPORT_STR *smtp_server_port)
+{
+ return (haproxy_srvr_parse_sa(str, str_len, non_proxy,
+ smtp_client_addr, smtp_client_port,
+ smtp_server_addr, smtp_server_port,
+ (struct sockaddr *) 0, (SOCKADDR_SIZE *) 0,
+ (struct sockaddr *) 0, (SOCKADDR_SIZE *) 0));
+}
+
+/* haproxy_srvr_receive_sa - receive and parse haproxy protocol handshake */
+
+int haproxy_srvr_receive_sa(int fd, int *non_proxy,
+ MAI_HOSTADDR_STR *smtp_client_addr,
+ MAI_SERVPORT_STR *smtp_client_port,
+ MAI_HOSTADDR_STR *smtp_server_addr,
+ MAI_SERVPORT_STR *smtp_server_port,
+ struct sockaddr *client_sa,
+ SOCKADDR_SIZE *client_sa_len,
+ struct sockaddr *server_sa,
+ SOCKADDR_SIZE *server_sa_len)
{
const char *err;
VSTRING *escape_buf;
@@ -513,9 +602,11 @@ int haproxy_srvr_receive(int fd, int *non_proxy,
*/
read_buf[read_len] = 0;
- if ((err = haproxy_srvr_parse(read_buf, &read_len, non_proxy,
- smtp_client_addr, smtp_client_port,
- smtp_server_addr, smtp_server_port)) != 0) {
+ if ((err = haproxy_srvr_parse_sa(read_buf, &read_len, non_proxy,
+ smtp_client_addr, smtp_client_port,
+ smtp_server_addr, smtp_server_port,
+ client_sa, client_sa_len,
+ server_sa, server_sa_len)) != 0) {
escape_buf = vstring_alloc(read_len * 2);
escape(escape_buf, read_buf, read_len);
msg_warn("haproxy read: %s: %s", err, vstring_str(escape_buf));
@@ -533,356 +624,27 @@ int haproxy_srvr_receive(int fd, int *non_proxy,
return (0);
}
- /*
- * Test program.
- */
-#ifdef TEST
+/* haproxy_srvr_receive - ABI compatibility */
- /*
- * Test cases with inputs and expected outputs. A request may contain
- * trailing garbage, and it may be too short. A v1 request may also contain
- * malformed address or port information.
- */
-typedef struct TEST_CASE {
- const char *haproxy_request; /* v1 or v2 request including thrash */
- ssize_t haproxy_req_len; /* request length including thrash */
- ssize_t exp_req_len; /* parsed request length */
- int exp_non_proxy; /* request is not proxied */
- const char *exp_return; /* expected error string */
- const char *exp_client_addr; /* expected client address string */
- const char *exp_server_addr; /* expected client port string */
- const char *exp_client_port; /* expected client address string */
- const char *exp_server_port; /* expected server port string */
-} TEST_CASE;
-static TEST_CASE v1_test_cases[] = {
- /* IPv6. */
- {"PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1 123 321\n", 0, 0, 0, 0, "fc::1:2:3:4", "fc::4:3:2:1", "123", "321"},
- {"PROXY TCP6 FC:00:00:00:1:2:3:4 FC:00:00:00:4:3:2:1 123 321\n", 0, 0, 0, 0, "fc::1:2:3:4", "fc::4:3:2:1", "123", "321"},
- {"PROXY TCP6 1.2.3.4 4.3.2.1 123 321\n", 0, 0, 0, "bad or missing client address"},
- {"PROXY TCP6 fc:00:00:00:1:2:3:4 4.3.2.1 123 321\n", 0, 0, 0, "bad or missing server address"},
- /* IPv4 in IPv6. */
- {"PROXY TCP6 ::ffff:1.2.3.4 ::ffff:4.3.2.1 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
- {"PROXY TCP6 ::FFFF:1.2.3.4 ::FFFF:4.3.2.1 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
- {"PROXY TCP4 ::ffff:1.2.3.4 ::ffff:4.3.2.1 123 321\n", 0, 0, 0, "bad or missing client address"},
- {"PROXY TCP4 1.2.3.4 ::ffff:4.3.2.1 123 321\n", 0, 0, 0, "bad or missing server address"},
- /* IPv4. */
- {"PROXY TCP4 1.2.3.4 4.3.2.1 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
- {"PROXY TCP4 01.02.03.04 04.03.02.01 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
- {"PROXY TCP4 1.2.3.4 4.3.2.1 123456 321\n", 0, 0, 0, "bad or missing client port"},
- {"PROXY TCP4 1.2.3.4 4.3.2.1 123 654321\n", 0, 0, 0, "bad or missing server port"},
- {"PROXY TCP4 1.2.3.4 4.3.2.1 0123 321\n", 0, 0, 0, "bad or missing client port"},
- {"PROXY TCP4 1.2.3.4 4.3.2.1 123 0321\n", 0, 0, 0, "bad or missing server port"},
- /* Missing fields. */
- {"PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1 123\n", 0, 0, 0, "bad or missing server port"},
- {"PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1\n", 0, 0, 0, "bad or missing client port"},
- {"PROXY TCP6 fc:00:00:00:1:2:3:4\n", 0, 0, 0, "bad or missing server address"},
- {"PROXY TCP6\n", 0, 0, 0, "bad or missing client address"},
- {"PROXY TCP4 1.2.3.4 4.3.2.1 123\n", 0, 0, 0, "bad or missing server port"},
- {"PROXY TCP4 1.2.3.4 4.3.2.1\n", 0, 0, 0, "bad or missing client port"},
- {"PROXY TCP4 1.2.3.4\n", 0, 0, 0, "bad or missing server address"},
- {"PROXY TCP4\n", 0, 0, 0, "bad or missing client address"},
- /* Other. */
- {"PROXY BLAH\n", 0, 0, 0, "bad or missing protocol type"},
- {"PROXY\n", 0, 0, 0, "short protocol header"},
- {"BLAH\n", 0, 0, 0, "short protocol header"},
- {"\n", 0, 0, 0, "short protocol header"},
- {"", 0, 0, 0, "short protocol header"},
- 0,
-};
+#undef haproxy_srvr_receive
-static struct proxy_hdr_v2 v2_local_request = {
- PP2_SIGNATURE, PP2_VERSION | PP2_CMD_LOCAL,
-};
-static TEST_CASE v2_non_proxy_test = {
- (char *) &v2_local_request, PP2_HEADER_LEN, PP2_HEADER_LEN, 1,
-};
+int haproxy_srvr_receive(int fd, int *non_proxy,
+ MAI_HOSTADDR_STR *smtp_client_addr,
+ MAI_SERVPORT_STR *smtp_client_port,
+ MAI_HOSTADDR_STR *smtp_server_addr,
+ MAI_SERVPORT_STR *smtp_server_port);
-#define STR(x) vstring_str(x)
-#define LEN(x) VSTRING_LEN(x)
-
-/* evaluate_test_case - evaluate one test case */
-
-static int evaluate_test_case(const char *test_label,
- const TEST_CASE *test_case)
+int haproxy_srvr_receive(int fd, int *non_proxy,
+ MAI_HOSTADDR_STR *smtp_client_addr,
+ MAI_SERVPORT_STR *smtp_client_port,
+ MAI_HOSTADDR_STR *smtp_server_addr,
+ MAI_SERVPORT_STR *smtp_server_port)
{
- /* Actual results. */
- const char *act_return;
- ssize_t act_req_len;
- int act_non_proxy;
- MAI_HOSTADDR_STR act_smtp_client_addr;
- MAI_HOSTADDR_STR act_smtp_server_addr;
- MAI_SERVPORT_STR act_smtp_client_port;
- MAI_SERVPORT_STR act_smtp_server_port;
- int test_failed;
-
- if (msg_verbose)
- msg_info("test case=%s exp_client_addr=%s exp_server_addr=%s "
- "exp_client_port=%s exp_server_port=%s",
- test_label, STR_OR_NULL(test_case->exp_client_addr),
- STR_OR_NULL(test_case->exp_server_addr),
- STR_OR_NULL(test_case->exp_client_port),
- STR_OR_NULL(test_case->exp_server_port));
-
- /*
- * Start the test.
- */
- test_failed = 0;
- act_req_len = test_case->haproxy_req_len;
- act_return =
- haproxy_srvr_parse(test_case->haproxy_request, &act_req_len,
- &act_non_proxy,
- &act_smtp_client_addr, &act_smtp_client_port,
- &act_smtp_server_addr, &act_smtp_server_port);
- if (act_return != test_case->exp_return) {
- msg_warn("test case %s return expected=%s actual=%s",
- test_label, STR_OR_NULL(test_case->exp_return),
- STR_OR_NULL(act_return));
- test_failed = 1;
- return (test_failed);
- }
- if (act_req_len != test_case->exp_req_len) {
- msg_warn("test case %s str_len expected=%ld actual=%ld",
- test_label,
- (long) test_case->exp_req_len, (long) act_req_len);
- test_failed = 1;
- return (test_failed);
- }
- if (act_non_proxy != test_case->exp_non_proxy) {
- msg_warn("test case %s non_proxy expected=%d actual=%d",
- test_label,
- test_case->exp_non_proxy, act_non_proxy);
- test_failed = 1;
- return (test_failed);
- }
- if (test_case->exp_non_proxy || test_case->exp_return != 0)
- /* No expected address/port results. */
- return (test_failed);
-
- /*
- * Compare address/port results against expected results.
- */
- if (strcmp(test_case->exp_client_addr, act_smtp_client_addr.buf)) {
- msg_warn("test case %s client_addr expected=%s actual=%s",
- test_label,
- test_case->exp_client_addr, act_smtp_client_addr.buf);
- test_failed = 1;
- }
- if (strcmp(test_case->exp_server_addr, act_smtp_server_addr.buf)) {
- msg_warn("test case %s server_addr expected=%s actual=%s",
- test_label,
- test_case->exp_server_addr, act_smtp_server_addr.buf);
- test_failed = 1;
- }
- if (strcmp(test_case->exp_client_port, act_smtp_client_port.buf)) {
- msg_warn("test case %s client_port expected=%s actual=%s",
- test_label,
- test_case->exp_client_port, act_smtp_client_port.buf);
- test_failed = 1;
- }
- if (strcmp(test_case->exp_server_port, act_smtp_server_port.buf)) {
- msg_warn("test case %s server_port expected=%s actual=%s",
- test_label,
- test_case->exp_server_port, act_smtp_server_port.buf);
- test_failed = 1;
- }
- return (test_failed);
+ return (haproxy_srvr_receive_sa(fd, non_proxy,
+ smtp_client_addr, smtp_client_port,
+ smtp_server_addr, smtp_server_port,
+ (struct sockaddr *) 0,
+ (SOCKADDR_SIZE *) 0,
+ (struct sockaddr *) 0,
+ (SOCKADDR_SIZE *) 0));
}
-
-/* convert_v1_proxy_req_to_v2 - convert well-formed v1 proxy request to v2 */
-
-static void convert_v1_proxy_req_to_v2(VSTRING *buf, const char *req,
- ssize_t req_len)
-{
- const char myname[] = "convert_v1_proxy_req_to_v2";
- const char *err;
- int non_proxy;
- MAI_HOSTADDR_STR smtp_client_addr;
- MAI_SERVPORT_STR smtp_client_port;
- MAI_HOSTADDR_STR smtp_server_addr;
- MAI_SERVPORT_STR smtp_server_port;
- struct proxy_hdr_v2 *hdr_v2;
- struct addrinfo *src_res;
- struct addrinfo *dst_res;
-
- /*
- * Allocate buffer space for the largest possible protocol header, so we
- * don't have to worry about hidden realloc() calls.
- */
- VSTRING_RESET(buf);
- VSTRING_SPACE(buf, sizeof(struct proxy_hdr_v2));
- hdr_v2 = (struct proxy_hdr_v2 *) STR(buf);
-
- /*
- * Fill in the header,
- */
- memcpy(hdr_v2->sig, PP2_SIGNATURE, PP2_SIGNATURE_LEN);
- hdr_v2->ver_cmd = PP2_VERSION | PP2_CMD_PROXY;
- if ((err = haproxy_srvr_parse(req, &req_len, &non_proxy, &smtp_client_addr,
- &smtp_client_port, &smtp_server_addr,
- &smtp_server_port)) != 0 || non_proxy)
- msg_fatal("%s: malformed or non-proxy request: %s",
- myname, req);
-
- if (hostaddr_to_sockaddr(smtp_client_addr.buf, smtp_client_port.buf, 0,
- &src_res) != 0)
- msg_fatal("%s: unable to convert source address %s port %s",
- myname, smtp_client_addr.buf, smtp_client_port.buf);
- if (hostaddr_to_sockaddr(smtp_server_addr.buf, smtp_server_port.buf, 0,
- &dst_res) != 0)
- msg_fatal("%s: unable to convert destination address %s port %s",
- myname, smtp_server_addr.buf, smtp_server_port.buf);
- if (src_res->ai_family != dst_res->ai_family)
- msg_fatal("%s: mixed source/destination address families", myname);
-#ifdef AF_INET6
- if (src_res->ai_family == PF_INET6) {
- hdr_v2->fam = PP2_FAM_INET6 | PP2_TRANS_STREAM;
- hdr_v2->len = htons(PP2_ADDR_LEN_INET6);
- memcpy(hdr_v2->addr.ip6.src_addr,
- &SOCK_ADDR_IN6_ADDR(src_res->ai_addr),
- sizeof(hdr_v2->addr.ip6.src_addr));
- hdr_v2->addr.ip6.src_port = SOCK_ADDR_IN6_PORT(src_res->ai_addr);
- memcpy(hdr_v2->addr.ip6.dst_addr,
- &SOCK_ADDR_IN6_ADDR(dst_res->ai_addr),
- sizeof(hdr_v2->addr.ip6.dst_addr));
- hdr_v2->addr.ip6.dst_port = SOCK_ADDR_IN6_PORT(dst_res->ai_addr);
- } else
-#endif
- if (src_res->ai_family == PF_INET) {
- hdr_v2->fam = PP2_FAM_INET | PP2_TRANS_STREAM;
- hdr_v2->len = htons(PP2_ADDR_LEN_INET);
- hdr_v2->addr.ip4.src_addr = SOCK_ADDR_IN_ADDR(src_res->ai_addr).s_addr;
- hdr_v2->addr.ip4.src_port = SOCK_ADDR_IN_PORT(src_res->ai_addr);
- hdr_v2->addr.ip4.dst_addr = SOCK_ADDR_IN_ADDR(dst_res->ai_addr).s_addr;
- hdr_v2->addr.ip4.dst_port = SOCK_ADDR_IN_PORT(dst_res->ai_addr);
- } else {
- msg_panic("unknown address family 0x%x", src_res->ai_family);
- }
- vstring_set_payload_size(buf, PP2_SIGNATURE_LEN + ntohs(hdr_v2->len));
- freeaddrinfo(src_res);
- freeaddrinfo(dst_res);
-}
-
-int main(int argc, char **argv)
-{
- VSTRING *test_label;
- TEST_CASE *v1_test_case;
- TEST_CASE v2_test_case;
- TEST_CASE mutated_test_case;
- VSTRING *v2_request_buf;
- VSTRING *mutated_request_buf;
-
- /* Findings. */
- int tests_failed = 0;
- int test_failed;
-
- test_label = vstring_alloc(100);
- v2_request_buf = vstring_alloc(100);
- mutated_request_buf = vstring_alloc(100);
-
- for (tests_failed = 0, v1_test_case = v1_test_cases;
- v1_test_case->haproxy_request != 0;
- tests_failed += test_failed, v1_test_case++) {
-
- /*
- * Fill in missing string length info in v1 test data.
- */
- if (v1_test_case->haproxy_req_len == 0)
- v1_test_case->haproxy_req_len =
- strlen(v1_test_case->haproxy_request);
- if (v1_test_case->exp_req_len == 0)
- v1_test_case->exp_req_len = v1_test_case->haproxy_req_len;
-
- /*
- * Evaluate each v1 test case.
- */
- vstring_sprintf(test_label, "%d", (int) (v1_test_case - v1_test_cases));
- test_failed = evaluate_test_case(STR(test_label), v1_test_case);
-
- /*
- * If the v1 test input is malformed, skip the mutation tests.
- */
- if (v1_test_case->exp_return != 0)
- continue;
-
- /*
- * Mutation test: a well-formed v1 test case should still pass after
- * appending a byte, and should return the actual parsed header
- * length. The test uses the implicit VSTRING null safety byte.
- */
- vstring_sprintf(test_label, "%d (one byte appended)",
- (int) (v1_test_case - v1_test_cases));
- mutated_test_case = *v1_test_case;
- mutated_test_case.haproxy_req_len += 1;
- /* reuse v1_test_case->exp_req_len */
- test_failed += evaluate_test_case(STR(test_label), &mutated_test_case);
-
- /*
- * Mutation test: a well-formed v1 test case should fail after
- * stripping the terminator.
- */
- vstring_sprintf(test_label, "%d (last byte stripped)",
- (int) (v1_test_case - v1_test_cases));
- mutated_test_case = *v1_test_case;
- mutated_test_case.exp_return = "missing protocol header terminator";
- mutated_test_case.haproxy_req_len -= 1;
- mutated_test_case.exp_req_len = mutated_test_case.haproxy_req_len;
- test_failed += evaluate_test_case(STR(test_label), &mutated_test_case);
-
- /*
- * A 'well-formed' v1 test case should pass after conversion to v2.
- */
- vstring_sprintf(test_label, "%d (converted to v2)",
- (int) (v1_test_case - v1_test_cases));
- v2_test_case = *v1_test_case;
- convert_v1_proxy_req_to_v2(v2_request_buf,
- v1_test_case->haproxy_request,
- v1_test_case->haproxy_req_len);
- v2_test_case.haproxy_request = STR(v2_request_buf);
- v2_test_case.haproxy_req_len = PP2_HEADER_LEN
- + ntohs(((struct proxy_hdr_v2 *) STR(v2_request_buf))->len);
- v2_test_case.exp_req_len = v2_test_case.haproxy_req_len;
- test_failed += evaluate_test_case(STR(test_label), &v2_test_case);
-
- /*
- * Mutation test: a well-formed v2 test case should still pass after
- * appending a byte, and should return the actual parsed header
- * length. The test uses the implicit VSTRING null safety byte.
- */
- vstring_sprintf(test_label, "%d (converted to v2, one byte appended)",
- (int) (v1_test_case - v1_test_cases));
- mutated_test_case = v2_test_case;
- mutated_test_case.haproxy_req_len += 1;
- /* reuse v2_test_case->exp_req_len */
- test_failed += evaluate_test_case(STR(test_label), &mutated_test_case);
-
- /*
- * Mutation test: a well-formed v2 test case should fail after
- * stripping one byte
- */
- vstring_sprintf(test_label, "%d (converted to v2, last byte stripped)",
- (int) (v1_test_case - v1_test_cases));
- mutated_test_case = v2_test_case;
- mutated_test_case.haproxy_req_len -= 1;
- mutated_test_case.exp_req_len = mutated_test_case.haproxy_req_len;
- mutated_test_case.exp_return = "short version 2 protocol header";
- test_failed += evaluate_test_case(STR(test_label), &mutated_test_case);
- }
-
- /*
- * Additional V2-only tests.
- */
- test_failed +=
- evaluate_test_case("v2 non-proxy request", &v2_non_proxy_test);
-
- /*
- * Clean up.
- */
- vstring_free(v2_request_buf);
- vstring_free(mutated_request_buf);
- vstring_free(test_label);
- if (tests_failed)
- msg_info("tests failed: %d", tests_failed);
- exit(tests_failed != 0);
-}
-
-#endif
diff --git a/postfix/src/global/haproxy_srvr.h b/postfix/src/global/haproxy_srvr.h
index 4a801f1d8..8a21f7960 100644
--- a/postfix/src/global/haproxy_srvr.h
+++ b/postfix/src/global/haproxy_srvr.h
@@ -19,12 +19,32 @@
/*
* External interface.
*/
-extern const char *haproxy_srvr_parse(const char *, ssize_t *, int *,
+extern const char *haproxy_srvr_parse_sa(const char *, ssize_t *, int *,
MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *,
- MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *);
-extern int haproxy_srvr_receive(int, int *,
MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *,
- MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *);
+ struct sockaddr *, SOCKADDR_SIZE *,
+ struct sockaddr *, SOCKADDR_SIZE *);
+
+#define haproxy_srvr_parse(str, len, non_proxy, client_addr, client_port, \
+ server_addr, server_port) \
+ haproxy_srvr_parse_sa((str), (len), (non_proxy), \
+ (client_addr), (client_port), \
+ (server_addr), (server_port), \
+ (struct sockaddr *) 0, (SOCKADDR_SIZE *) 0, \
+ (struct sockaddr *) 0, (SOCKADDR_SIZE *) 0)
+extern int haproxy_srvr_receive_sa(int, int *,
+ MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *,
+ MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *,
+ struct sockaddr *, SOCKADDR_SIZE *,
+ struct sockaddr *, SOCKADDR_SIZE *);
+
+#define haproxy_srvr_receive(fd, non_proxy, client_addr, client_port, \
+ server_addr, server_port) \
+ haproxy_srvr_receive_sa((fd), (non_proxy), \
+ (client_addr), (client_port), \
+ (server_addr), (server_port), \
+ (struct sockaddr *) 0, (SOCKADDR_SIZE *) 0, \
+ (struct sockaddr *) 0, (SOCKADDR_SIZE *) 0)
#define HAPROXY_PROTO_NAME "haproxy"
@@ -33,6 +53,86 @@ extern int haproxy_srvr_receive(int, int *,
#define DONT_GRIPE 0
#endif
+#ifdef _HAPROXY_SRVR_INTERNAL_
+
+ /*
+ * The haproxy protocol assumes that a haproxy header will normally not
+ * exceed the default IPv4 TCP MSS, i.e. 576-40=536 bytes (the IPv6 default
+ * is larger: 1280-60=1220). With a proxy header that contains IPv6
+ * addresses, that leaves room for 536-52=484 bytes of TLVs. The Postfix
+ * implementation does not support headers with UNIX-domain addresses.
+ */
+#define HAPROXY_HEADER_MAX_LEN 536
+
+ /*
+ * Begin protocol v2 definitions from haproxy/include/types/connection.h.
+ */
+#define PP2_SIGNATURE "\r\n\r\n\0\r\nQUIT\n"
+#define PP2_SIGNATURE_LEN 12
+#define PP2_HEADER_LEN 16
+
+/* ver_cmd byte */
+#define PP2_CMD_LOCAL 0x00
+#define PP2_CMD_PROXY 0x01
+#define PP2_CMD_MASK 0x0F
+
+#define PP2_VERSION 0x20
+#define PP2_VERSION_MASK 0xF0
+
+/* fam byte */
+#define PP2_TRANS_UNSPEC 0x00
+#define PP2_TRANS_STREAM 0x01
+#define PP2_TRANS_DGRAM 0x02
+#define PP2_TRANS_MASK 0x0F
+
+#define PP2_FAM_UNSPEC 0x00
+#define PP2_FAM_INET 0x10
+#define PP2_FAM_INET6 0x20
+#define PP2_FAM_UNIX 0x30
+#define PP2_FAM_MASK 0xF0
+
+/* len field (2 bytes) */
+#define PP2_ADDR_LEN_UNSPEC (0)
+#define PP2_ADDR_LEN_INET (4 + 4 + 2 + 2)
+#define PP2_ADDR_LEN_INET6 (16 + 16 + 2 + 2)
+#define PP2_ADDR_LEN_UNIX (108 + 108)
+
+#define PP2_HDR_LEN_UNSPEC (PP2_HEADER_LEN + PP2_ADDR_LEN_UNSPEC)
+#define PP2_HDR_LEN_INET (PP2_HEADER_LEN + PP2_ADDR_LEN_INET)
+#define PP2_HDR_LEN_INET6 (PP2_HEADER_LEN + PP2_ADDR_LEN_INET6)
+#define PP2_HDR_LEN_UNIX (PP2_HEADER_LEN + PP2_ADDR_LEN_UNIX)
+
+struct proxy_hdr_v2 {
+ uint8_t sig[PP2_SIGNATURE_LEN]; /* PP2_SIGNATURE */
+ uint8_t ver_cmd; /* protocol version | command */
+ uint8_t fam; /* protocol family and transport */
+ uint16_t len; /* length of remainder */
+ union {
+ struct { /* for TCP/UDP over IPv4, len = 12 */
+ uint32_t src_addr;
+ uint32_t dst_addr;
+ uint16_t src_port;
+ uint16_t dst_port;
+ } ip4;
+ struct { /* for TCP/UDP over IPv6, len = 36 */
+ uint8_t src_addr[16];
+ uint8_t dst_addr[16];
+ uint16_t src_port;
+ uint16_t dst_port;
+ } ip6;
+ struct { /* for AF_UNIX sockets, len = 216 */
+ uint8_t src_addr[108];
+ uint8_t dst_addr[108];
+ } unx;
+ } addr;
+};
+
+ /*
+ * End protocol v2 definitions from haproxy/include/types/connection.h.
+ */
+
+#endif /* _HAPROXY_SRVR_INTERNAL_ */
+
/* LICENSE
/* .ad
/* .fi
diff --git a/postfix/src/global/haproxy_srvr_test.c b/postfix/src/global/haproxy_srvr_test.c
new file mode 100644
index 000000000..8dcdd8b58
--- /dev/null
+++ b/postfix/src/global/haproxy_srvr_test.c
@@ -0,0 +1,534 @@
+ /*
+ * System library.
+ */
+#include
+#include
+
+ /*
+ * Utility library.
+ */
+#include
+#include
+#include
+#include
+#include
+#include
+
+ /*
+ * Global library.
+ */
+#define _HAPROXY_SRVR_INTERNAL_
+#include
+
+ /*
+ * Application-specific.
+ */
+#define STR_OR_NULL(str) ((str) ? (str) : "(null)")
+
+ /*
+ * Test cases with inputs and expected outputs. A request may contain
+ * trailing garbage, and it may be too short. A v1 request may also contain
+ * malformed address or port information. TODO(wietse) add unit test with
+ * different inet_protocols setting. See normalize_v4mapped_addr_test.c.
+ */
+typedef struct TEST_CASE {
+ const char *haproxy_request; /* v1 or v2 request including thrash */
+ ssize_t haproxy_req_len; /* request length including thrash */
+ ssize_t exp_req_len; /* parsed request length */
+ int exp_non_proxy; /* request is not proxied */
+ const char *exp_return; /* expected error string */
+ const char *exp_client_addr; /* expected client address string */
+ const char *exp_server_addr; /* expected client port string */
+ const char *exp_client_port; /* expected client address string */
+ const char *exp_server_port; /* expected server port string */
+} TEST_CASE;
+
+static TEST_CASE v1_test_cases[] = {
+ /* IPv6. */
+ {"PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1 123 321\n", 0, 0, 0, 0, "fc::1:2:3:4", "fc::4:3:2:1", "123", "321"},
+ {"PROXY TCP6 FC:00:00:00:1:2:3:4 FC:00:00:00:4:3:2:1 123 321\n", 0, 0, 0, 0, "fc::1:2:3:4", "fc::4:3:2:1", "123", "321"},
+ {"PROXY TCP6 1.2.3.4 4.3.2.1 123 321\n", 0, 0, 0, "bad or missing client address"},
+ {"PROXY TCP6 fc:00:00:00:1:2:3:4 4.3.2.1 123 321\n", 0, 0, 0, "bad or missing server address"},
+ /* IPv4 in IPv6. */
+ {"PROXY TCP6 ::ffff:1.2.3.4 ::ffff:4.3.2.1 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
+ {"PROXY TCP6 ::FFFF:1.2.3.4 ::FFFF:4.3.2.1 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
+ {"PROXY TCP4 ::ffff:1.2.3.4 ::ffff:4.3.2.1 123 321\n", 0, 0, 0, "bad or missing client address"},
+ {"PROXY TCP4 1.2.3.4 ::ffff:4.3.2.1 123 321\n", 0, 0, 0, "bad or missing server address"},
+ /* IPv4. */
+ {"PROXY TCP4 1.2.3.4 4.3.2.1 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
+ {"PROXY TCP4 01.02.03.04 04.03.02.01 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
+ {"PROXY TCP4 1.2.3.4 4.3.2.1 123456 321\n", 0, 0, 0, "bad or missing client port"},
+ {"PROXY TCP4 1.2.3.4 4.3.2.1 123 654321\n", 0, 0, 0, "bad or missing server port"},
+ {"PROXY TCP4 1.2.3.4 4.3.2.1 0123 321\n", 0, 0, 0, "bad or missing client port"},
+ {"PROXY TCP4 1.2.3.4 4.3.2.1 123 0321\n", 0, 0, 0, "bad or missing server port"},
+ /* Missing fields. */
+ {"PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1 123\n", 0, 0, 0, "bad or missing server port"},
+ {"PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1\n", 0, 0, 0, "bad or missing client port"},
+ {"PROXY TCP6 fc:00:00:00:1:2:3:4\n", 0, 0, 0, "bad or missing server address"},
+ {"PROXY TCP6\n", 0, 0, 0, "bad or missing client address"},
+ {"PROXY TCP4 1.2.3.4 4.3.2.1 123\n", 0, 0, 0, "bad or missing server port"},
+ {"PROXY TCP4 1.2.3.4 4.3.2.1\n", 0, 0, 0, "bad or missing client port"},
+ {"PROXY TCP4 1.2.3.4\n", 0, 0, 0, "bad or missing server address"},
+ {"PROXY TCP4\n", 0, 0, 0, "bad or missing client address"},
+ /* Other. */
+ {"PROXY BLAH\n", 0, 0, 0, "bad or missing protocol type"},
+ {"PROXY\n", 0, 0, 0, "short protocol header"},
+ {"BLAH\n", 0, 0, 0, "short protocol header"},
+ {"\n", 0, 0, 0, "short protocol header"},
+ {"", 0, 0, 0, "short protocol header"},
+ 0,
+};
+
+static struct proxy_hdr_v2 v2_local_request = {
+ PP2_SIGNATURE, PP2_VERSION | PP2_CMD_LOCAL,
+};
+static TEST_CASE v2_non_proxy_test = {
+ (char *) &v2_local_request, PP2_HEADER_LEN, PP2_HEADER_LEN, 1,
+};
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+#define DO_SOCKADDR_OUTPUT 1
+#define NO_SOCKADDR_OUTPUT 0
+
+static int evaluate_sockaddr(const char *which, struct sockaddr *sa,
+ SOCKADDR_SIZE sa_len, const char *want_addr,
+ const char *want_port)
+{
+ MAI_HOSTADDR_STR act_addr;
+ MAI_SERVPORT_STR act_port;
+ int err;
+ int failed = 0;
+
+ if ((err = sockaddr_to_hostaddr(sa, sa_len, &act_addr, &act_port, 0)) != 0) {
+ msg_warn("sockaddr_to_hostaddr: %s", MAI_STRERROR(err));
+ return (1);
+ }
+ if (strcmp(act_addr.buf, want_addr) != 0) {
+ msg_warn("got %s address '%s', want '%s'",
+ which, act_addr.buf, want_addr);
+ failed = 1;
+ }
+ if (strcmp(act_port.buf, want_port) != 0) {
+ msg_warn("got %s port '%s', want '%s'",
+ which, act_port.buf, want_port);
+ failed = 1;
+ }
+ return (failed);
+}
+
+/* evaluate_test_case - evaluate one base or mutated test case */
+
+static int evaluate_test_case(const char *test_label,
+ const TEST_CASE *test_case,
+ int want_sockaddr_output)
+{
+ /* Actual results. */
+ const char *act_return;
+ ssize_t act_req_len;
+ int act_non_proxy;
+ MAI_HOSTADDR_STR act_smtp_client_addr;
+ MAI_HOSTADDR_STR act_smtp_server_addr;
+ MAI_SERVPORT_STR act_smtp_client_port;
+ MAI_SERVPORT_STR act_smtp_server_port;
+ int test_failed;
+
+ struct sockaddr_storage client_ss;
+ struct sockaddr *client_sa;
+ SOCKADDR_SIZE client_sa_len;
+
+ struct sockaddr_storage server_ss;
+ struct sockaddr *server_sa;
+ SOCKADDR_SIZE server_sa_len;
+
+ if (msg_verbose)
+ msg_info("test case=%s exp_client_addr=%s exp_server_addr=%s "
+ "exp_client_port=%s exp_server_port=%s",
+ test_label, STR_OR_NULL(test_case->exp_client_addr),
+ STR_OR_NULL(test_case->exp_server_addr),
+ STR_OR_NULL(test_case->exp_client_port),
+ STR_OR_NULL(test_case->exp_server_port));
+
+ if (want_sockaddr_output) {
+ client_sa = (struct sockaddr *) &client_ss;
+ client_sa_len = sizeof(client_ss);
+ server_sa = (struct sockaddr *) &server_ss;
+ server_sa_len = sizeof(server_ss);
+ } else {
+ client_sa = 0;
+ client_sa_len = 0;
+ server_sa = 0;
+ server_sa_len = 0;
+ }
+
+ /*
+ * Start the test.
+ */
+ test_failed = 0;
+ act_req_len = test_case->haproxy_req_len;
+ act_return =
+ haproxy_srvr_parse_sa(test_case->haproxy_request, &act_req_len,
+ &act_non_proxy,
+ &act_smtp_client_addr, &act_smtp_client_port,
+ &act_smtp_server_addr, &act_smtp_server_port,
+ client_sa, &client_sa_len,
+ server_sa, &server_sa_len);
+ if (act_return != test_case->exp_return
+ && strcmp(STR_OR_NULL(act_return), STR_OR_NULL(test_case->exp_return))) {
+ msg_warn("test case %s return expected=>%s< actual=>%s<",
+ test_label, STR_OR_NULL(test_case->exp_return),
+ STR_OR_NULL(act_return));
+ test_failed = 1;
+ return (test_failed);
+ }
+ if (act_req_len != test_case->exp_req_len) {
+ msg_warn("test case %s str_len expected=%ld actual=%ld",
+ test_label,
+ (long) test_case->exp_req_len, (long) act_req_len);
+ test_failed = 1;
+ return (test_failed);
+ }
+ if (act_non_proxy != test_case->exp_non_proxy) {
+ msg_warn("test case %s non_proxy expected=%d actual=%d",
+ test_label,
+ test_case->exp_non_proxy, act_non_proxy);
+ test_failed = 1;
+ return (test_failed);
+ }
+ if (test_case->exp_non_proxy || test_case->exp_return != 0)
+ /* No expected address/port results. */
+ return (test_failed);
+
+ /*
+ * Compare address/port results against expected results.
+ */
+ if (strcmp(test_case->exp_client_addr, act_smtp_client_addr.buf)) {
+ msg_warn("test case %s client_addr expected=%s actual=%s",
+ test_label,
+ test_case->exp_client_addr, act_smtp_client_addr.buf);
+ test_failed = 1;
+ }
+ if (strcmp(test_case->exp_server_addr, act_smtp_server_addr.buf)) {
+ msg_warn("test case %s server_addr expected=%s actual=%s",
+ test_label,
+ test_case->exp_server_addr, act_smtp_server_addr.buf);
+ test_failed = 1;
+ }
+ if (strcmp(test_case->exp_client_port, act_smtp_client_port.buf)) {
+ msg_warn("test case %s client_port expected=%s actual=%s",
+ test_label,
+ test_case->exp_client_port, act_smtp_client_port.buf);
+ test_failed = 1;
+ }
+ if (strcmp(test_case->exp_server_port, act_smtp_server_port.buf)) {
+ msg_warn("test case %s server_port expected=%s actual=%s",
+ test_label,
+ test_case->exp_server_port, act_smtp_server_port.buf);
+ test_failed = 1;
+ }
+ if (want_sockaddr_output) {
+ if (evaluate_sockaddr("client", client_sa, client_sa_len,
+ test_case->exp_client_addr,
+ test_case->exp_client_port) != 0
+ || evaluate_sockaddr("server", server_sa, server_sa_len,
+ test_case->exp_server_addr,
+ test_case->exp_server_port) != 0)
+ test_failed = 1;
+ }
+ return (test_failed);
+}
+
+/* convert_v1_proxy_req_to_v2 - convert well-formed v1 proxy request to v2 */
+
+static void convert_v1_proxy_req_to_v2(VSTRING *buf, const char *req,
+ ssize_t req_len)
+{
+ const char myname[] = "convert_v1_proxy_req_to_v2";
+ const char *err;
+ int non_proxy;
+ MAI_HOSTADDR_STR smtp_client_addr;
+ MAI_SERVPORT_STR smtp_client_port;
+ MAI_HOSTADDR_STR smtp_server_addr;
+ MAI_SERVPORT_STR smtp_server_port;
+ struct proxy_hdr_v2 *hdr_v2;
+ struct addrinfo *src_res;
+ struct addrinfo *dst_res;
+
+ /*
+ * Allocate buffer space for the largest possible protocol header, so we
+ * don't have to worry about hidden realloc() calls.
+ */
+ VSTRING_RESET(buf);
+ VSTRING_SPACE(buf, sizeof(struct proxy_hdr_v2));
+ hdr_v2 = (struct proxy_hdr_v2 *) STR(buf);
+
+ /*
+ * Fill in the header,
+ */
+ memcpy(hdr_v2->sig, PP2_SIGNATURE, PP2_SIGNATURE_LEN);
+ hdr_v2->ver_cmd = PP2_VERSION | PP2_CMD_PROXY;
+ if ((err = haproxy_srvr_parse(req, &req_len, &non_proxy, &smtp_client_addr,
+ &smtp_client_port, &smtp_server_addr,
+ &smtp_server_port)) != 0 || non_proxy)
+ msg_fatal("%s: malformed or non-proxy request: %s",
+ myname, req);
+
+ if (hostaddr_to_sockaddr(smtp_client_addr.buf, smtp_client_port.buf, 0,
+ &src_res) != 0)
+ msg_fatal("%s: unable to convert source address %s port %s",
+ myname, smtp_client_addr.buf, smtp_client_port.buf);
+ if (hostaddr_to_sockaddr(smtp_server_addr.buf, smtp_server_port.buf, 0,
+ &dst_res) != 0)
+ msg_fatal("%s: unable to convert destination address %s port %s",
+ myname, smtp_server_addr.buf, smtp_server_port.buf);
+ if (src_res->ai_family != dst_res->ai_family)
+ msg_fatal("%s: mixed source/destination address families", myname);
+#ifdef AF_INET6
+ if (src_res->ai_family == PF_INET6) {
+ hdr_v2->fam = PP2_FAM_INET6 | PP2_TRANS_STREAM;
+ hdr_v2->len = htons(PP2_ADDR_LEN_INET6);
+ memcpy(hdr_v2->addr.ip6.src_addr,
+ &SOCK_ADDR_IN6_ADDR(src_res->ai_addr),
+ sizeof(hdr_v2->addr.ip6.src_addr));
+ hdr_v2->addr.ip6.src_port = SOCK_ADDR_IN6_PORT(src_res->ai_addr);
+ memcpy(hdr_v2->addr.ip6.dst_addr,
+ &SOCK_ADDR_IN6_ADDR(dst_res->ai_addr),
+ sizeof(hdr_v2->addr.ip6.dst_addr));
+ hdr_v2->addr.ip6.dst_port = SOCK_ADDR_IN6_PORT(dst_res->ai_addr);
+ } else
+#endif
+ if (src_res->ai_family == PF_INET) {
+ hdr_v2->fam = PP2_FAM_INET | PP2_TRANS_STREAM;
+ hdr_v2->len = htons(PP2_ADDR_LEN_INET);
+ hdr_v2->addr.ip4.src_addr = SOCK_ADDR_IN_ADDR(src_res->ai_addr).s_addr;
+ hdr_v2->addr.ip4.src_port = SOCK_ADDR_IN_PORT(src_res->ai_addr);
+ hdr_v2->addr.ip4.dst_addr = SOCK_ADDR_IN_ADDR(dst_res->ai_addr).s_addr;
+ hdr_v2->addr.ip4.dst_port = SOCK_ADDR_IN_PORT(dst_res->ai_addr);
+ } else {
+ msg_panic("unknown address family 0x%x", src_res->ai_family);
+ }
+ vstring_set_payload_size(buf, PP2_SIGNATURE_LEN + ntohs(hdr_v2->len));
+ freeaddrinfo(src_res);
+ freeaddrinfo(dst_res);
+}
+
+int main(int argc, char **argv)
+{
+ VSTRING *test_label;
+ TEST_CASE *v1_test_case;
+ TEST_CASE v2_test_case;
+ TEST_CASE mutated_test_case;
+ VSTRING *v2_request_buf;
+ VSTRING *mutated_request_buf;
+
+ msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
+
+ /* Findings. */
+ int tests_failed = 0;
+ int tests_passed = 0;
+
+ test_label = vstring_alloc(100);
+ v2_request_buf = vstring_alloc(100);
+ mutated_request_buf = vstring_alloc(100);
+
+ /*
+ * Evaluate each case in the test case list. If the test input is
+ * well-formed, also test a number of mutations based on that test,
+ * before proceeding with the next test case in the list.
+ */
+ for (tests_failed = 0, tests_passed = 0, v1_test_case = v1_test_cases;
+ v1_test_case->haproxy_request != 0; v1_test_case++) {
+
+ /*
+ * Fill in missing string length info in v1 test data.
+ */
+ if (v1_test_case->haproxy_req_len == 0)
+ v1_test_case->haproxy_req_len =
+ strlen(v1_test_case->haproxy_request);
+ if (v1_test_case->exp_req_len == 0)
+ v1_test_case->exp_req_len = v1_test_case->haproxy_req_len;
+
+ /*
+ * Evaluate each v1 test case.
+ */
+ vstring_sprintf(test_label, "%d (%s%.5s input)",
+ (int) (v1_test_case - v1_test_cases),
+ v1_test_case->exp_return ? "malformed" : "well-formed",
+ v1_test_case->exp_return ? "" : v1_test_case->haproxy_request + 5);
+ msg_info("RUN %s", STR(test_label));
+ if (evaluate_test_case(STR(test_label), v1_test_case,
+ NO_SOCKADDR_OUTPUT)) {
+ msg_info("FAIL %s", STR(test_label));
+ tests_failed += 1;
+ } else {
+ msg_info("PASS %s", STR(test_label));
+ tests_passed += 1;
+ }
+
+ /*
+ * If the v1 test input is malformed, skip the mutation tests.
+ */
+ if (v1_test_case->exp_return != 0)
+ continue;
+
+ /*
+ * Mutation test: a well-formed v1 test case should also pass with
+ * output to sockaddr arguments.
+ */
+ vstring_sprintf(test_label, "%d (with sockaddr output)",
+ (int) (v1_test_case - v1_test_cases));
+ msg_info("RUN %s", STR(test_label));
+ if (evaluate_test_case(STR(test_label), v1_test_case,
+ DO_SOCKADDR_OUTPUT)) {
+ msg_info("FAIL %s", STR(test_label));
+ tests_failed += 1;
+ } else {
+ msg_info("PASS %s", STR(test_label));
+ tests_passed += 1;
+ }
+
+ /*
+ * Mutation test: a well-formed v1 test case should still pass after
+ * appending a byte, and should return the actual parsed header
+ * length. The test uses the implicit VSTRING null safety byte.
+ */
+ vstring_sprintf(test_label, "%d (one byte appended)",
+ (int) (v1_test_case - v1_test_cases));
+ mutated_test_case = *v1_test_case;
+ mutated_test_case.haproxy_req_len += 1;
+ msg_info("RUN %s", STR(test_label));
+ /* reuse v1_test_case->exp_req_len */
+ if (evaluate_test_case(STR(test_label), &mutated_test_case,
+ NO_SOCKADDR_OUTPUT)) {
+ msg_info("FAIL %s", STR(test_label));
+ tests_failed += 1;
+ } else {
+ msg_info("PASS %s", STR(test_label));
+ tests_passed += 1;
+ }
+
+ /*
+ * Mutation test: a well-formed v1 test case should fail after
+ * stripping the terminator.
+ */
+ vstring_sprintf(test_label, "%d (last byte stripped)",
+ (int) (v1_test_case - v1_test_cases));
+ mutated_test_case = *v1_test_case;
+ mutated_test_case.exp_return = "missing protocol header terminator";
+ mutated_test_case.haproxy_req_len -= 1;
+ mutated_test_case.exp_req_len = mutated_test_case.haproxy_req_len;
+ msg_info("RUN %s", STR(test_label));
+ if (evaluate_test_case(STR(test_label), &mutated_test_case,
+ NO_SOCKADDR_OUTPUT)) {
+ msg_info("FAIL %s", STR(test_label));
+ tests_failed += 1;
+ } else {
+ msg_info("PASS %s", STR(test_label));
+ tests_passed += 1;
+ }
+
+ /*
+ * A 'well-formed' v1 test case should pass after conversion to v2.
+ */
+ vstring_sprintf(test_label, "%d (converted to v2)",
+ (int) (v1_test_case - v1_test_cases));
+ v2_test_case = *v1_test_case;
+ convert_v1_proxy_req_to_v2(v2_request_buf,
+ v1_test_case->haproxy_request,
+ v1_test_case->haproxy_req_len);
+ v2_test_case.haproxy_request = STR(v2_request_buf);
+ v2_test_case.haproxy_req_len = PP2_HEADER_LEN
+ + ntohs(((struct proxy_hdr_v2 *) STR(v2_request_buf))->len);
+ v2_test_case.exp_req_len = v2_test_case.haproxy_req_len;
+ msg_info("RUN %s", STR(test_label));
+ if (evaluate_test_case(STR(test_label), &v2_test_case,
+ NO_SOCKADDR_OUTPUT)) {
+ msg_info("FAIL %s", STR(test_label));
+ tests_failed += 1;
+ } else {
+ msg_info("PASS %s", STR(test_label));
+ tests_passed += 1;
+ }
+
+ /*
+ * Mutation test: a well-formed v2 test case should also pass with
+ * output to sockaddr arguments.
+ */
+ vstring_sprintf(test_label,
+ "%d (converted to v2, with sockaddr output)",
+ (int) (v1_test_case - v1_test_cases));
+ msg_info("RUN %s", STR(test_label));
+ if (evaluate_test_case(STR(test_label), &v2_test_case,
+ DO_SOCKADDR_OUTPUT)) {
+ msg_info("FAIL %s", STR(test_label));
+ tests_failed += 1;
+ } else {
+ msg_info("PASS %s", STR(test_label));
+ tests_passed += 1;
+ }
+
+ /*
+ * Mutation test: a well-formed v2 test case should still pass after
+ * appending a byte, and should return the actual parsed header
+ * length. The test uses the implicit VSTRING null safety byte.
+ */
+ vstring_sprintf(test_label, "%d (converted to v2, one byte appended)",
+ (int) (v1_test_case - v1_test_cases));
+ mutated_test_case = v2_test_case;
+ mutated_test_case.haproxy_req_len += 1;
+ /* reuse v2_test_case->exp_req_len */
+ msg_info("RUN %s", STR(test_label));
+ if (evaluate_test_case(STR(test_label), &mutated_test_case,
+ NO_SOCKADDR_OUTPUT)) {
+ msg_info("FAIL %s", STR(test_label));
+ tests_failed += 1;
+ } else {
+ msg_info("PASS %s", STR(test_label));
+ tests_passed += 1;
+ }
+
+ /*
+ * Mutation test: a well-formed v2 test case should fail after
+ * stripping one byte
+ */
+ vstring_sprintf(test_label, "%d (converted to v2, last byte stripped)",
+ (int) (v1_test_case - v1_test_cases));
+ mutated_test_case = v2_test_case;
+ mutated_test_case.haproxy_req_len -= 1;
+ mutated_test_case.exp_req_len = mutated_test_case.haproxy_req_len;
+ mutated_test_case.exp_return = "short version 2 protocol header";
+ msg_info("RUN %s", STR(test_label));
+ if (evaluate_test_case(STR(test_label), &mutated_test_case,
+ NO_SOCKADDR_OUTPUT)) {
+ msg_info("FAIL %s", STR(test_label));
+ tests_failed += 1;
+ } else {
+ msg_info("PASS %s", STR(test_label));
+ tests_passed += 1;
+ }
+ }
+
+ /*
+ * Additional V2-only tests.
+ */
+ vstring_strcpy(test_label, "v2 non-proxy request");
+ msg_info("RUN %s", STR(test_label));
+ if (evaluate_test_case("v2 non-proxy request", &v2_non_proxy_test,
+ NO_SOCKADDR_OUTPUT)) {
+ msg_info("FAIL %s", STR(test_label));
+ tests_failed += 1;
+ } else {
+ msg_info("PASS %s", STR(test_label));
+ tests_passed += 1;
+ }
+
+ /*
+ * Clean up.
+ */
+ vstring_free(v2_request_buf);
+ vstring_free(mutated_request_buf);
+ vstring_free(test_label);
+ msg_info("PASS=%d FAIL=%d", tests_passed, tests_failed);
+ exit(tests_failed != 0);
+}
diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h
index 87fd6ced4..5e4813491 100644
--- a/postfix/src/global/mail_version.h
+++ b/postfix/src/global/mail_version.h
@@ -20,7 +20,7 @@
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20250323"
+#define MAIL_RELEASE_DATE "20250409"
#define MAIL_VERSION_NUMBER "3.11"
#ifdef SNAPSHOT
diff --git a/postfix/src/postscreen/postscreen_endpt.c b/postfix/src/postscreen/postscreen_endpt.c
index 46e657994..728fabc57 100644
--- a/postfix/src/postscreen/postscreen_endpt.c
+++ b/postfix/src/postscreen/postscreen_endpt.c
@@ -116,7 +116,7 @@ void psc_endpt_local_lookup(VSTREAM *smtp_client_stream,
PSC_ENDPT_LOOKUP_FN lookup_done)
{
struct sockaddr_storage addr_storage;
- SOCKADDR_SIZE addr_storage_len = sizeof(addr_storage);
+ SOCKADDR_SIZE addr_storage_len;
int status;
MAI_HOSTADDR_STR smtp_client_addr;
MAI_SERVPORT_STR smtp_client_port;
@@ -124,10 +124,13 @@ void psc_endpt_local_lookup(VSTREAM *smtp_client_stream,
MAI_SERVPORT_STR smtp_server_port;
int aierr;
+#define RESET_ADDR_STORAGE_LEN() (addr_storage_len = sizeof(addr_storage))
+
/*
* Look up the remote SMTP client address and port.
*/
- if (getpeername(vstream_fileno(smtp_client_stream), (struct sockaddr *)
+ if (RESET_ADDR_STORAGE_LEN(),
+ getpeername(vstream_fileno(smtp_client_stream), (struct sockaddr *)
&addr_storage, &addr_storage_len) < 0) {
msg_warn("getpeername: %m -- dropping this connection");
status = -1;
@@ -135,11 +138,12 @@ void psc_endpt_local_lookup(VSTREAM *smtp_client_stream,
/*
* Convert the remote SMTP client address and port to printable form for
- * logging and access control.
+ * logging and access control. Note: this may change addr_storage and
+ * addr_storage_len.
*/
else if ((aierr = sane_sockaddr_to_hostaddr(
(struct sockaddr *) &addr_storage,
- addr_storage_len, &smtp_client_addr,
+ &addr_storage_len, &smtp_client_addr,
&smtp_client_port, SOCK_STREAM)) != 0) {
msg_warn("cannot convert client address/port to string: %s"
" -- dropping this connection",
@@ -148,9 +152,11 @@ void psc_endpt_local_lookup(VSTREAM *smtp_client_stream,
}
/*
- * Look up the local SMTP server address and port.
+ * Look up the local SMTP server address and port. Be sure to reset the
+ * addr_storage_len value.
*/
- else if (getsockname(vstream_fileno(smtp_client_stream),
+ else if (RESET_ADDR_STORAGE_LEN(),
+ getsockname(vstream_fileno(smtp_client_stream),
(struct sockaddr *) &addr_storage,
&addr_storage_len) < 0) {
msg_warn("getsockname: %m -- dropping this connection");
@@ -159,11 +165,12 @@ void psc_endpt_local_lookup(VSTREAM *smtp_client_stream,
/*
* Convert the local SMTP server address and port to printable form for
- * logging.
+ * logging. This may also change addr_storage and addr_storage_len, but
+ * those variables are dead.
*/
else if ((aierr = sane_sockaddr_to_hostaddr(
(struct sockaddr *) &addr_storage,
- addr_storage_len, &smtp_server_addr,
+ &addr_storage_len, &smtp_server_addr,
&smtp_server_port, SOCK_STREAM)) != 0) {
msg_warn("cannot convert server address/port to string: %s"
" -- dropping this connection",
diff --git a/postfix/src/smtp/smtp_tlsrpt.c b/postfix/src/smtp/smtp_tlsrpt.c
index a371f4c19..cef46d5cd 100644
--- a/postfix/src/smtp/smtp_tlsrpt.c
+++ b/postfix/src/smtp/smtp_tlsrpt.c
@@ -372,7 +372,7 @@ void smtp_tlsrpt_set_tcp_connection(SMTP_STATE *state)
client_addr.buf[0] = 0;
} else if ((aierr = sane_sockaddr_to_hostaddr(
(struct sockaddr *) &addr_storage,
- addr_storage_len, &client_addr,
+ &addr_storage_len, &client_addr,
(MAI_SERVPORT_STR *) 0,
SOCK_STREAM)) != 0) {
msg_warn("%s: cannot convert IP address to string (%s)"
diff --git a/postfix/src/smtpd/smtpd_peer.c b/postfix/src/smtpd/smtpd_peer.c
index 468732d30..b77d5e2a1 100644
--- a/postfix/src/smtpd/smtpd_peer.c
+++ b/postfix/src/smtpd/smtpd_peer.c
@@ -58,6 +58,10 @@
/* .IP dest_port
/* Server port, available as Milter {daemon_port} macro, and
/* as server_port policy delegation attribute.
+/* .IP sockaddr_len
+/* .IP dest_sockaddr_len
+/* These are initialized to zero, to indicate that the corresponding
+/* sockaddr_storage members are not set.
/* .IP name_status
/* The name_status result field specifies how the name
/* information should be interpreted:
@@ -117,6 +121,9 @@
/* Google, Inc.
/* 111 8th Avenue
/* New York, NY 10011, USA
+/*
+/* Wietse Venema
+/* porcupine.org
/*--*/
/* System library. */
@@ -617,6 +624,7 @@ void smtpd_peer_init(SMTPD_STATE *state)
state->anvil_range = 0;
state->dest_addr = 0;
state->dest_port = 0;
+ state->dest_sockaddr_len = 0;
/*
* Determine the remote SMTP client address and port.
diff --git a/postfix/src/util/Makefile.in b/postfix/src/util/Makefile.in
index 32ad7fa34..e749eb0db 100644
--- a/postfix/src/util/Makefile.in
+++ b/postfix/src/util/Makefile.in
@@ -47,7 +47,7 @@ SRCS = alldig.c allprint.c argv.c argv_split.c attr_clnt.c attr_print0.c \
mkmap_fail.c mkmap_lmdb.c mkmap_open.c mkmap_sdbm.c inet_prefix_top.c \
inet_addr_sizes.c quote_for_json.c mystrerror.c \
sane_sockaddr_to_hostaddr.c normalize_ws.c valid_uri_scheme.c \
- clean_ascii_cntrl_space.c
+ clean_ascii_cntrl_space.c normalize_v4mapped_addr.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 \
@@ -95,7 +95,8 @@ OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.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 \
quote_for_json.o mystrerror.o sane_sockaddr_to_hostaddr.o \
- normalize_ws.o valid_uri_scheme.o clean_ascii_cntrl_space.o
+ normalize_ws.o valid_uri_scheme.o clean_ascii_cntrl_space.o \
+ normalize_v4mapped_addr.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.
@@ -128,7 +129,7 @@ HDRS = argv.h attr.h attr_clnt.h auto_clnt.h base64_code.h binhash.h \
check_arg.h argv_attr.h msg_logger.h logwriter.h byte_mask.h \
known_tcp_ports.h sane_strtol.h hash_fnv.h ldseed.h mkmap.h \
inet_prefix_top.h inet_addr_sizes.h valid_uri_scheme.h \
- clean_ascii_cntrl_space.h
+ clean_ascii_cntrl_space.h normalize_v4mapped_addr.h
TESTSRC = fifo_open.c fifo_rdwr_bug.c fifo_rdonly_bug.c select_bug.c \
stream_test.c dup2_pass_on_exec.c
DEFS = -I. -D$(SYSTYPE)
@@ -151,7 +152,8 @@ TESTPROG= dict_open dup2_pass_on_exec events exec_command fifo_open \
vbuf_print split_qnameval vstream msg_logger byte_mask \
known_tcp_ports dict_stream find_inet binhash hash_fnv argv \
clean_env inet_prefix_top printable readlline quote_for_json \
- normalize_ws valid_uri_scheme clean_ascii_cntrl_space
+ normalize_ws valid_uri_scheme clean_ascii_cntrl_space \
+ normalize_v4mapped_addr_test
PLUGIN_MAP_SO = $(LIB_PREFIX)pcre$(LIB_SUFFIX) $(LIB_PREFIX)lmdb$(LIB_SUFFIX) \
$(LIB_PREFIX)cdb$(LIB_SUFFIX) $(LIB_PREFIX)sdbm$(LIB_SUFFIX)
HTABLE_FIX = NORANDOMIZE=1
@@ -630,6 +632,9 @@ clean_ascii_cntrl_space: $(LIB)
$(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
mv junk $@.o
+normalize_v4mapped_addr_test: normalize_v4mapped_addr_test.c $(LIB)
+ $(CC) $(CFLAGS) -o $@ normalize_v4mapped_addr_test.c $(LIB) $(SYSLIBS)
+
tests: all valid_hostname_test mac_expand_test dict_test unescape_test \
hex_quote_test ctable_test inet_addr_list_test base64_code_test \
attr_scan64_test attr_scan0_test host_port_test dict_tests \
@@ -641,7 +646,8 @@ tests: all valid_hostname_test mac_expand_test dict_test unescape_test \
vstream_test byte_mask_tests mystrtok_test known_tcp_ports_test \
binhash_test argv_test inet_prefix_top_test printable_test \
valid_utf8_string_test readlline_test quote_for_json_test \
- normalize_ws_test valid_uri_scheme_test clean_ascii_cntrl_space_test
+ normalize_ws_test valid_uri_scheme_test clean_ascii_cntrl_space_test \
+ test_normalize_v4mapped_addr
dict_tests: all dict_test \
dict_pcre_tests dict_cidr_test dict_thash_test dict_static_test \
@@ -1127,6 +1133,9 @@ valid_uri_scheme_test: valid_uri_scheme
clean_ascii_cntrl_space_test: clean_ascii_cntrl_space
$(SHLIB_ENV) ${VALGRIND} ./clean_ascii_cntrl_space
+test_normalize_v4mapped_addr: update normalize_v4mapped_addr_test
+ $(SHLIB_ENV) ${VALGRIND} ./normalize_v4mapped_addr_test
+
depend: $(MAKES)
(sed '1,/^# do not edit/!d' Makefile.in; \
set -e; for i in [a-z][a-z0-9]*.c; do \
@@ -2532,6 +2541,24 @@ non_blocking.o: iostuff.h
non_blocking.o: msg.h
non_blocking.o: non_blocking.c
non_blocking.o: sys_defs.h
+normalize_v4mapped_addr.o: inet_proto.h
+normalize_v4mapped_addr.o: myaddrinfo.h
+normalize_v4mapped_addr.o: normalize_v4mapped_addr.c
+normalize_v4mapped_addr.o: normalize_v4mapped_addr.h
+normalize_v4mapped_addr.o: sock_addr.h
+normalize_v4mapped_addr.o: sys_defs.h
+normalize_v4mapped_addr_test.o: check_arg.h
+normalize_v4mapped_addr_test.o: inet_proto.h
+normalize_v4mapped_addr_test.o: msg.h
+normalize_v4mapped_addr_test.o: msg_vstream.h
+normalize_v4mapped_addr_test.o: myaddrinfo.h
+normalize_v4mapped_addr_test.o: normalize_v4mapped_addr.h
+normalize_v4mapped_addr_test.o: normalize_v4mapped_addr_test.c
+normalize_v4mapped_addr_test.o: stringops.h
+normalize_v4mapped_addr_test.o: sys_defs.h
+normalize_v4mapped_addr_test.o: vbuf.h
+normalize_v4mapped_addr_test.o: vstream.h
+normalize_v4mapped_addr_test.o: vstring.h
normalize_ws.o: check_arg.h
normalize_ws.o: msg.h
normalize_ws.o: msg_vstream.h
@@ -2673,8 +2700,8 @@ sane_rename.o: sane_fsops.h
sane_rename.o: sane_rename.c
sane_rename.o: sys_defs.h
sane_rename.o: warn_stat.h
-sane_sockaddr_to_hostaddr.o: inet_proto.h
sane_sockaddr_to_hostaddr.o: myaddrinfo.h
+sane_sockaddr_to_hostaddr.o: normalize_v4mapped_addr.h
sane_sockaddr_to_hostaddr.o: sane_sockaddr_to_hostaddr.c
sane_sockaddr_to_hostaddr.o: sys_defs.h
sane_socketpair.o: msg.h
diff --git a/postfix/src/util/inet_proto.c b/postfix/src/util/inet_proto.c
index fedf7610d..1e76caa46 100644
--- a/postfix/src/util/inet_proto.c
+++ b/postfix/src/util/inet_proto.c
@@ -15,7 +15,9 @@
/* .in -4
/* } INET_PROTO_INFO;
/*
-/* const INET_PROTO_INFO *inet_proto_init(context, protocols)
+/* const INET_PROTO_INFO *inet_proto_init(
+/* const char *context,
+/* const char *protocols)
/*
/* const INET_PROTO_INFO *inet_proto_info()
/* DESCRIPTION
diff --git a/postfix/src/util/myaddrinfo.c b/postfix/src/util/myaddrinfo.c
index 5edafde8d..192506e19 100644
--- a/postfix/src/util/myaddrinfo.c
+++ b/postfix/src/util/myaddrinfo.c
@@ -833,8 +833,11 @@ int main(int argc, char **argv)
struct addrinfo **resv;
MAI_HOSTNAME_STR host;
MAI_HOSTADDR_STR addr;
+ MAI_SERVNAME_STR serv;
+ MAI_SERVPORT_STR port;
size_t len, n;
int err;
+ char *aport;
msg_vstream_init(argv[0], VSTREAM_ERR);
@@ -845,9 +848,13 @@ int main(int argc, char **argv)
msg_info("=== hostname %s ===", argv[2]);
- if ((err = hostname_to_sockaddr(argv[2], (char *) 0, 0, &info)) != 0) {
- msg_info("hostname_to_sockaddr(%s): %s",
- argv[2], err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
+#define STR_OR_NULL(s) ((s) ? (s) : "(null)")
+
+ aport = split_at(argv[2], ':');
+ if ((err = hostname_to_sockaddr(argv[2], aport, 0, &info)) != 0) {
+ msg_warn("hostname_to_sockaddr(%s:%s): %s",
+ argv[2], STR_OR_NULL(aport), err == EAI_SYSTEM ?
+ strerror(errno) : gai_strerror(err));
} else {
for (len = 0, ip = info; ip != 0; ip = ip->ai_next)
len += 1;
@@ -858,20 +865,21 @@ int main(int argc, char **argv)
for (n = 0; n < len; n++) {
ip = resv[n];
if ((err = sockaddr_to_hostaddr(ip->ai_addr, ip->ai_addrlen, &addr,
- (MAI_SERVPORT_STR *) 0, 0)) != 0) {
- msg_info("sockaddr_to_hostaddr: %s",
+ &port, 0)) != 0) {
+ msg_warn("sockaddr_to_hostaddr: %s",
err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
continue;
}
- msg_info("%s -> family=%d sock=%d proto=%d %s", argv[2],
- ip->ai_family, ip->ai_socktype, ip->ai_protocol, addr.buf);
+ msg_info("%s:%s -> family=%d sock=%d proto=%d %s:%s",
+ argv[2], STR_OR_NULL(aport), ip->ai_family,
+ ip->ai_socktype, ip->ai_protocol, addr.buf, port.buf);
if ((err = sockaddr_to_hostname(ip->ai_addr, ip->ai_addrlen, &host,
- (MAI_SERVNAME_STR *) 0, 0)) != 0) {
- msg_info("sockaddr_to_hostname: %s",
+ &serv, 0)) != 0) {
+ msg_warn("sockaddr_to_hostname: %s",
err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
continue;
}
- msg_info("%s -> %s", addr.buf, host.buf);
+ msg_info("%s:%s -> %s:%s", addr.buf, port.buf, host.buf, serv.buf);
}
freeaddrinfo(info);
myfree((void *) resv);
@@ -879,23 +887,25 @@ int main(int argc, char **argv)
msg_info("=== host address %s ===", argv[3]);
- if ((err = hostaddr_to_sockaddr(argv[3], (char *) 0, 0, &ip)) != 0) {
- msg_info("hostaddr_to_sockaddr(%s): %s",
- argv[3], err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
+ aport = split_at(argv[3], ':');
+ if ((err = hostaddr_to_sockaddr(argv[3], aport, 0, &ip)) != 0) {
+ msg_warn("hostaddr_to_sockaddr(%s:%s): %s",
+ argv[3], STR_OR_NULL(aport), err == EAI_SYSTEM ?
+ strerror(errno) : gai_strerror(err));
} else {
if ((err = sockaddr_to_hostaddr(ip->ai_addr, ip->ai_addrlen, &addr,
- (MAI_SERVPORT_STR *) 0, 0)) != 0) {
- msg_info("sockaddr_to_hostaddr: %s",
+ &port, 0)) != 0) {
+ msg_warn("sockaddr_to_hostaddr: %s",
err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
} else {
- msg_info("%s -> family=%d sock=%d proto=%d %s", argv[3],
- ip->ai_family, ip->ai_socktype, ip->ai_protocol, addr.buf);
+ msg_info("%s:%s -> family=%d sock=%d proto=%d %s:%s", argv[3], STR_OR_NULL(aport),
+ ip->ai_family, ip->ai_socktype, ip->ai_protocol, addr.buf, port.buf);
if ((err = sockaddr_to_hostname(ip->ai_addr, ip->ai_addrlen, &host,
- (MAI_SERVNAME_STR *) 0, 0)) != 0) {
- msg_info("sockaddr_to_hostname: %s",
+ &serv, 0)) != 0) {
+ msg_warn("sockaddr_to_hostname: %s",
err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
} else
- msg_info("%s -> %s", addr.buf, host.buf);
+ msg_info("%s:%s -> %s:%s", addr.buf, port.buf, host.buf, serv.buf);
freeaddrinfo(ip);
}
}
diff --git a/postfix/src/util/myaddrinfo.h b/postfix/src/util/myaddrinfo.h
index 0d6e0e005..14a6baece 100644
--- a/postfix/src/util/myaddrinfo.h
+++ b/postfix/src/util/myaddrinfo.h
@@ -213,8 +213,8 @@ extern void myaddrinfo_control(int,...);
/*
* sane_sockaddr_to_hostaddr.c
*/
-extern int WARN_UNUSED_RESULT sane_sockaddr_to_hostaddr(const struct sockaddr *,
- SOCKADDR_SIZE, MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *, int);
+extern int WARN_UNUSED_RESULT sane_sockaddr_to_hostaddr(struct sockaddr *,
+ SOCKADDR_SIZE *, MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *, int);
/* LICENSE
/* .ad
diff --git a/postfix/src/util/myaddrinfo.ref b/postfix/src/util/myaddrinfo.ref
index 7dccdb0bf..a1aa8ca02 100644
--- a/postfix/src/util/myaddrinfo.ref
+++ b/postfix/src/util/myaddrinfo.ref
@@ -1,8 +1,8 @@
./myaddrinfo: === hostname belly.porcupine.org ===
-./myaddrinfo: belly.porcupine.org -> family=2 sock=1 proto=6 168.100.3.6
-./myaddrinfo: 168.100.3.6 -> belly.porcupine.org
-./myaddrinfo: belly.porcupine.org -> family=28 sock=1 proto=6 2604:8d00:189::6
-./myaddrinfo: 2604:8d00:189::6 -> belly.porcupine.org
+./myaddrinfo: belly.porcupine.org:(null) -> family=2 sock=1 proto=6 168.100.3.6:0
+./myaddrinfo: 168.100.3.6:0 -> belly.porcupine.org:0
+./myaddrinfo: belly.porcupine.org:(null) -> family=28 sock=1 proto=6 2604:8d00:189::6:0
+./myaddrinfo: 2604:8d00:189::6:0 -> belly.porcupine.org:0
./myaddrinfo: === host address 168.100.3.2 ===
-./myaddrinfo: 168.100.3.2 -> family=2 sock=1 proto=6 168.100.3.2
-./myaddrinfo: 168.100.3.2 -> spike.porcupine.org
+./myaddrinfo: 168.100.3.2:(null) -> family=2 sock=1 proto=6 168.100.3.2:0
+./myaddrinfo: 168.100.3.2:0 -> spike.porcupine.org:0
diff --git a/postfix/src/util/myaddrinfo.ref2 b/postfix/src/util/myaddrinfo.ref2
index f2305dceb..d7b83d008 100644
--- a/postfix/src/util/myaddrinfo.ref2
+++ b/postfix/src/util/myaddrinfo.ref2
@@ -1,5 +1,5 @@
./myaddrinfo: === hostname null.porcupine.org ===
-./myaddrinfo: hostname_to_sockaddr(null.porcupine.org): hostname nor servname provided, or not known
+./myaddrinfo: warning: hostname_to_sockaddr(null.porcupine.org:(null)): hostname nor servname provided, or not known
./myaddrinfo: === host address 10.0.0.0 ===
-./myaddrinfo: 10.0.0.0 -> family=2 sock=1 proto=6 10.0.0.0
-./myaddrinfo: sockaddr_to_hostname: hostname nor servname provided, or not known
+./myaddrinfo: 10.0.0.0:(null) -> family=2 sock=1 proto=6 10.0.0.0:0
+./myaddrinfo: warning: sockaddr_to_hostname: hostname nor servname provided, or not known
diff --git a/postfix/src/util/myaddrinfo4.ref b/postfix/src/util/myaddrinfo4.ref
index 33c128422..50dafe9da 100644
--- a/postfix/src/util/myaddrinfo4.ref
+++ b/postfix/src/util/myaddrinfo4.ref
@@ -1,6 +1,6 @@
./myaddrinfo4: === hostname belly.porcupine.org ===
-./myaddrinfo4: belly.porcupine.org -> family=2 sock=1 proto=6 168.100.3.6
-./myaddrinfo4: 168.100.3.6 -> belly.porcupine.org
+./myaddrinfo4: belly.porcupine.org:(null) -> family=2 sock=1 proto=0 168.100.3.6:0
+./myaddrinfo4: warning: sockaddr_to_hostname: hostname nor servname provided, or not known
./myaddrinfo4: === host address 168.100.3.2 ===
-./myaddrinfo4: 168.100.3.2 -> family=2 sock=1 proto=6 168.100.3.2
-./myaddrinfo4: 168.100.3.2 -> spike.porcupine.org
+./myaddrinfo4: 168.100.3.2:(null) -> family=2 sock=1 proto=0 168.100.3.2:0
+./myaddrinfo4: warning: sockaddr_to_hostname: hostname nor servname provided, or not known
diff --git a/postfix/src/util/myaddrinfo4.ref2 b/postfix/src/util/myaddrinfo4.ref2
index f73560bf3..1f6177d3c 100644
--- a/postfix/src/util/myaddrinfo4.ref2
+++ b/postfix/src/util/myaddrinfo4.ref2
@@ -1,5 +1,5 @@
./myaddrinfo4: === hostname null.porcupine.org ===
-./myaddrinfo4: hostname2sockaddr(null.porcupine.org): No address associated with hostname
+./myaddrinfo4: warning: hostname_to_sockaddr(null.porcupine.org:(null)): No address associated with hostname
./myaddrinfo4: === host address 10.0.0.0 ===
-./myaddrinfo4: 10.0.0.0 -> family=2 sock=1 proto=6 10.0.0.0
-./myaddrinfo4: sockaddr2hostname: hostname nor servname provided, or not known
+./myaddrinfo4: 10.0.0.0:(null) -> family=2 sock=1 proto=0 10.0.0.0:0
+./myaddrinfo4: warning: sockaddr_to_hostname: hostname nor servname provided, or not known
diff --git a/postfix/src/util/normalize_v4mapped_addr.c b/postfix/src/util/normalize_v4mapped_addr.c
new file mode 100644
index 000000000..40f864a45
--- /dev/null
+++ b/postfix/src/util/normalize_v4mapped_addr.c
@@ -0,0 +1,90 @@
+/*++
+/* NAME
+/* normalize_v4mapped_addr 3
+/* SUMMARY
+/* normalize v4mapped IPv6 address
+/* SYNOPSIS
+/* #include
+/*
+/* int normalize_v4mapped_sockaddr(
+/* struct sockaddr *sa,
+/* SOCKADDR_SIZE *sa_len)
+/*
+/* int normalize_v4mapped_hostaddr(
+/* MAI_HOSTADDR_STR *addr)
+/* DESCRIPTION
+/* The functions in this module convert IPV6 addresses containing a
+/* mapped IPv4 address to the IPv4 form, but only if IPv4 support
+/* is enabled.
+/*
+/* normalize_v4mapped_sockaddr converts a V4mapped IPv6 sockaddr
+/* structure (struct sockaddr_in6) to the IPv4 form (struct
+/* sockaddr_in) and reduces *sa_len to (sizeof(struct sockaddr_in)).
+/*
+/* normalize_v4mapped_hostaddr converts a V4mapped IPv6 printable
+/* address (:ffff:d.d.d.d) to the IPv4 form (d.d.d.d).
+/*
+/* Both functions return 0 (false) when no input was converted,
+/* and return 1 (true) when input was converted.
+/* BUGS
+/* normalize_v4mapped_hostaddr() converts only canonical address
+/* forms, not all equivalent forms.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* porcupine.org
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include
+
+ /*
+ * Utility library.
+ */
+#include
+#include
+#include
+
+/* normalize_v4mapped_sockaddr - normalize V4mapped IPv6 sockaddr */
+
+int normalize_v4mapped_sockaddr(struct sockaddr *sa, SOCKADDR_SIZE *sa_len)
+{
+#ifdef AF_INET6
+ struct sockaddr_in sin;
+
+ if (sa->sa_family == AF_INET6
+ && IN6_IS_ADDR_V4MAPPED(&SOCK_ADDR_IN6_ADDR(sa))
+ && strchr((char *) inet_proto_info()->sa_family_list, AF_INET) != 0) {
+ memset((void *) &sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_port = SOCK_ADDR_IN6_PORT(sa);
+ memcpy((void *) &sin.sin_addr, ((void *) &SOCK_ADDR_IN6_ADDR(sa)) + 12,
+ sizeof(sin.sin_addr));
+ *(struct sockaddr_in *) sa = sin;
+ *sa_len = sizeof(sin);
+ return (1);
+ }
+#endif
+ return (0);
+}
+
+/* normalize_v4mapped_hostaddr - normalize V4mapped IPv6 printable address */
+
+int normalize_v4mapped_hostaddr(MAI_HOSTADDR_STR *addr)
+{
+#ifdef AF_INET6
+ if (addr->buf[0] == ':'
+ && strncasecmp("::ffff:", addr->buf, 7) == 0
+ && strchr((char *) inet_proto_info()->sa_family_list, AF_INET) != 0) {
+ memmove(addr->buf, addr->buf + 7, strlen(addr->buf) + 1 - 7);
+ return (1);
+ }
+#endif
+ return (0);
+}
diff --git a/postfix/src/util/normalize_v4mapped_addr.h b/postfix/src/util/normalize_v4mapped_addr.h
new file mode 100644
index 000000000..fca3476b7
--- /dev/null
+++ b/postfix/src/util/normalize_v4mapped_addr.h
@@ -0,0 +1,40 @@
+#ifndef _NORMALIZE_V4MAPPED_ADDR_H_INCLUDED_
+#define _NORMALIZE_V4MAPPED_ADDR_H_INCLUDED_
+
+/*++
+/* NAME
+/* normalize_v4mapped_addr 3h
+/* SUMMARY
+/* normalize v4mapped IPv6 address
+/* SYNOPSIS
+/* #include
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include
+#include
+
+ /*
+ * Utility library.
+ */
+#include
+
+ /*
+ * External interface.
+ */
+extern int normalize_v4mapped_sockaddr(struct sockaddr *, SOCKADDR_SIZE *);
+extern int normalize_v4mapped_hostaddr(MAI_HOSTADDR_STR *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* porcupine.org
+/*--*/
+
+#endif
diff --git a/postfix/src/util/normalize_v4mapped_addr_test.c b/postfix/src/util/normalize_v4mapped_addr_test.c
new file mode 100644
index 000000000..1a438f468
--- /dev/null
+++ b/postfix/src/util/normalize_v4mapped_addr_test.c
@@ -0,0 +1,189 @@
+ /*
+ * System library.
+ */
+#include
+#include
+
+ /*
+ * Utility library.
+ */
+#include
+#include
+#include
+#include
+#include
+
+ /*
+ * Test cases are used twice, first to test normalize_v4mapped_hostaddr(), and
+ * then normalize_v4mapped_sockaddr().
+ */
+typedef struct TEST_CASE {
+ const char *label;
+ const char *inet_protocols;
+ const char *in_hostaddr;
+ int want_return;
+ const char *want_hostaddr;
+} TEST_CASE;
+
+#define PASS 1
+#define FAIL 2
+
+static int test_normalize_v4mapped_hostaddr(const TEST_CASE *tp)
+{
+ int got_return;
+ MAI_HOSTADDR_STR hostaddr;
+
+ /*
+ * Prepare inputs.
+ */
+ if (strlen(tp->in_hostaddr) >= sizeof(hostaddr.buf)) {
+ msg_warn("test_normalize_v4mapped_hostaddr: input too large");
+ return (FAIL);
+ }
+ memcpy(hostaddr.buf, tp->in_hostaddr, strlen(tp->in_hostaddr) + 1);
+ inet_proto_init("test_normalize_v4mapped_hostaddr", tp->inet_protocols);
+
+ /*
+ * Exercise the function under test: normalize_v4mapped_hostaddr().
+ */
+ got_return = normalize_v4mapped_hostaddr(&hostaddr);
+
+ /*
+ * Verify the results.
+ */
+ if (got_return != tp->want_return) {
+ msg_warn("got return value %d, want %d", got_return, tp->want_return);
+ return (FAIL);
+ }
+ if (strcmp(hostaddr.buf, tp->want_hostaddr) != 0) {
+ msg_warn("got hostaddr '%s', want '%s'",
+ hostaddr.buf, tp->want_hostaddr);
+ return (FAIL);
+ }
+ return (PASS);
+}
+
+static int test_normalize_v4mapped_sockaddr(const TEST_CASE *tp)
+{
+ int got_return;
+ MAI_HOSTADDR_STR hostaddr;
+ int err;
+ struct addrinfo *res = 0;
+ struct sockaddr_storage ss;
+ SOCKADDR_SIZE ss_len;
+
+ /*
+ * Prepare inputs.
+ */
+ if (strlen(tp->in_hostaddr) >= sizeof(hostaddr.buf)) {
+ msg_warn("test_normalize_v4mapped_hostaddr: input too large");
+ return (FAIL);
+ }
+ memcpy(hostaddr.buf, tp->in_hostaddr, strlen(tp->in_hostaddr) + 1);
+ inet_proto_init("test_normalize_v4mapped_sockaddr", tp->inet_protocols);
+
+ /*
+ * Convert the input hostaddr to sockaddr.
+ */
+ if ((err = hostaddr_to_sockaddr(tp->in_hostaddr, (char *) 0, 0, &res)) != 0) {
+ msg_warn("hostaddr_to_sockaddr(\"%s\"...): %s",
+ tp->in_hostaddr, MAI_STRERROR(err));
+ return (FAIL);
+ }
+ memcpy((void *) &ss, res->ai_addr, res->ai_addrlen);
+ ss_len = res->ai_addrlen;
+ freeaddrinfo(res);
+
+ /*
+ * Exercise the function under test: normalize_v4mapped_sockaddr().
+ */
+ got_return = normalize_v4mapped_sockaddr((struct sockaddr *) &ss, &ss_len);
+
+ /*
+ * Convert the output sockaddr to hostaddr.
+ */
+ if ((err = sockaddr_to_hostaddr((struct sockaddr *) &ss, ss_len,
+ &hostaddr, (MAI_SERVPORT_STR *) 0, 0)) != 0) {
+ msg_warn("cannot convert address to string: %s", MAI_STRERROR(err));
+ return (FAIL);
+ }
+
+ /*
+ * Verify the results.
+ */
+ if (got_return != tp->want_return) {
+ msg_warn("got return value %d, want %d", got_return, tp->want_return);
+ return (FAIL);
+ }
+ if (strcmp(hostaddr.buf, tp->want_hostaddr) != 0) {
+ msg_warn("got hostaddr '%s', want '%s'",
+ hostaddr.buf, tp->want_hostaddr);
+ return (FAIL);
+ }
+ return (PASS);
+}
+
+static const TEST_CASE test_cases[] = {
+ {.label = "does not convert v4 address, ipv4 enabled",
+ .inet_protocols = "ipv6, ipv4",
+ .in_hostaddr = "192.168.1.1",
+ .want_return = 0,
+ .want_hostaddr = "192.168.1.1",
+ },
+#if 0
+ /* Can't test IPv4 forms with ipv4 disabled. */
+ {.label = "does not convert v4 address, ipv4 disabled",
+ .inet_protocols = "ipv6",
+ .in_hostaddr = "192.168.1.1",
+ .want_return = 0,
+ .want_hostaddr = "192.168.1.1",
+ },
+#endif
+ {.label = "does not convert v4inv6 address, ipv4 disabled",
+ .inet_protocols = "ipv6",
+ .in_hostaddr = "::ffff:192.168.1.1",
+ .want_return = 0,
+ .want_hostaddr = "::ffff:192.168.1.1",
+ },
+ {.label = "converts v4inv6 address, ipv4 enabled",
+ .inet_protocols = "ipv6, ipv4",
+ .in_hostaddr = "::ffff:192.168.1.1",
+ .want_return = 1,
+ .want_hostaddr = "192.168.1.1",
+ },
+ 0,
+};
+
+int main(int argc, char **argv)
+{
+ const TEST_CASE *tp;
+ int pass = 0;
+ int fail = 0;
+ struct test_action {
+ int (*act_fn) (const TEST_CASE *);
+ const char *act_name;
+ };
+ static struct test_action actions[] = {
+ test_normalize_v4mapped_hostaddr, "test_normalize_v4mapped_hostaddr",
+ test_normalize_v4mapped_sockaddr, "test_normalize_v4mapped_sockaddr",
+ 0
+ };
+ struct test_action *ap;
+
+ msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
+
+ for (ap = actions; ap->act_fn; ap++) {
+ for (tp = test_cases; tp->label != 0; tp++) {
+ msg_info("RUN %s/%s", ap->act_name, tp->label);
+ if (ap->act_fn(tp) != PASS) {
+ fail++;
+ msg_info("FAIL %s/%s", ap->act_name, tp->label);
+ } else {
+ msg_info("PASS %s/%s", ap->act_name, tp->label);
+ pass++;
+ }
+ }
+ }
+ msg_info("PASS=%d FAIL=%d", pass, fail);
+ exit(fail != 0);
+}
diff --git a/postfix/src/util/sane_sockaddr_to_hostaddr.c b/postfix/src/util/sane_sockaddr_to_hostaddr.c
index 919054c0e..f3d0d3042 100644
--- a/postfix/src/util/sane_sockaddr_to_hostaddr.c
+++ b/postfix/src/util/sane_sockaddr_to_hostaddr.c
@@ -13,14 +13,13 @@
/* MAI_SERVPORT_STR *port_buf,
/* int socktype)
/* DESCRIPTION
-/* sane_sockaddr_to_hostaddr() wraps sockaddr_to_hostaddr() and
-/* converts an IPv4 in IPv6 address to IPv4 form, but only if IPv4
-/* support is available.
-/* HISTORY
-/* .ad
-/* .fi
-/* This implementation was taken from postscreen, and consolidates
-/* multiple instances of similar code across the Postfix code base.
+/* sane_sockaddr_to_hostaddr() converts a V4mapped IPv6 address to
+/* IPv4 form, but only if IPv4 support is available. It then invokes
+/* sockaddr_to_hostaddr() to convert the result to human-readable
+/* form.
+/*
+/* NOTE: The V4mapped IPv6 conversion to IPv4 applies to both inputs
+/* and output.
/* LICENSE
/* .ad
/* .fi
@@ -49,28 +48,20 @@
* Utility library.
*/
#include
-#include
-
-static const INET_PROTO_INFO *proto_info;
+#include
/* sane_sockaddr_to_hostaddr - sanitize IPV4 in IPV6 address */
-int sane_sockaddr_to_hostaddr(const struct sockaddr *addr_storage,
- SOCKADDR_SIZE addr_storage_len,
+int sane_sockaddr_to_hostaddr(struct sockaddr *addr_storage,
+ SOCKADDR_SIZE *addr_storage_len,
MAI_HOSTADDR_STR *addr_buf,
MAI_SERVPORT_STR *port_buf,
int socktype)
{
- int aierr;
-
- if (proto_info == 0)
- proto_info = inet_proto_info();
-
- if ((aierr = sockaddr_to_hostaddr(addr_storage, addr_storage_len,
- addr_buf, port_buf, socktype)) == 0
- && strncasecmp("::ffff:", addr_buf->buf, 7) == 0
- && strchr((char *) proto_info->sa_family_list, AF_INET) != 0)
- memmove(addr_buf->buf, addr_buf->buf + 7,
- sizeof(addr_buf->buf) - 7);
- return (aierr);
+#ifdef AF_INET6
+ if (addr_storage->sa_family == AF_INET6)
+ normalize_v4mapped_sockaddr(addr_storage, addr_storage_len);
+#endif
+ return (sockaddr_to_hostaddr(addr_storage, *addr_storage_len,
+ addr_buf, port_buf, socktype));
}