]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.11-20250409
authorWietse Z Venema <wietse@porcupine.org>
Wed, 9 Apr 2025 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <ietf-dane@dukhovni.org>
Thu, 10 Apr 2025 16:12:43 +0000 (02:12 +1000)
26 files changed:
postfix/HISTORY
postfix/html/postconf.5.html
postfix/man/man5/postconf.5
postfix/proto/postconf.proto
postfix/proto/stop
postfix/proto/stop.spell-cc
postfix/src/global/Makefile.in
postfix/src/global/haproxy_srvr.c
postfix/src/global/haproxy_srvr.h
postfix/src/global/haproxy_srvr_test.c [new file with mode: 0644]
postfix/src/global/mail_version.h
postfix/src/postscreen/postscreen_endpt.c
postfix/src/smtp/smtp_tlsrpt.c
postfix/src/smtpd/smtpd_peer.c
postfix/src/util/Makefile.in
postfix/src/util/inet_proto.c
postfix/src/util/myaddrinfo.c
postfix/src/util/myaddrinfo.h
postfix/src/util/myaddrinfo.ref
postfix/src/util/myaddrinfo.ref2
postfix/src/util/myaddrinfo4.ref
postfix/src/util/myaddrinfo4.ref2
postfix/src/util/normalize_v4mapped_addr.c [new file with mode: 0644]
postfix/src/util/normalize_v4mapped_addr.h [new file with mode: 0644]
postfix/src/util/normalize_v4mapped_addr_test.c [new file with mode: 0644]
postfix/src/util/sane_sockaddr_to_hostaddr.c

index 94c94d7b37ba7836e5b83b9bb639c97c3d84f21c..449cbad12ba626157a12d1ad9caa3aa13087e667 100644 (file)
@@ -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
index 4bb3b351c090264c1040b9b58b5b16a2b04a175f..03d029cd0d2e5c1d53a833df1a065e04aa209c10 100644 (file)
@@ -8878,6 +8878,10 @@ clients,
 and <a href="postscreen.8.html">postscreen(8)</a> will update an SMTP client's DNSBL score with
 each non-error reply as described below. </p>
 
+<p> NOTE: Always respect the usage policies of reputation services.
+Avoid public or ISP resolvers, unless the queries use your unique
+API key. </p>
+
 <p> Caution: when postscreen rejects mail, its SMTP response contains
 the DNSBL
 domain name. Use the <a href="postconf.5.html#postscreen_dnsbl_reply_map">postscreen_dnsbl_reply_map</a> feature to hide
@@ -15553,7 +15557,12 @@ The <a href="postconf.5.html#maps_rbl_reject_code">maps_rbl_reject_code</a> para
 rejected requests (default:  554), the <a href="postconf.5.html#default_rbl_reply">default_rbl_reply</a>  parameter
 specifies the default server reply, and the <a href="postconf.5.html#rbl_reply_maps">rbl_reply_maps</a>  parameter
 specifies tables with server replies indexed by <i>rbl_domain</i>.
-This feature is available in Postfix 2.0 and later.  </dd>
+<br>
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see <a href="postconf.5.html#rbl_reply_maps">rbl_reply_maps</a> for how to avoid leaking the key in SMTP server
+responses). <br>
+This feature is available in Postfix 2.0 and later. </dd>
 
 <dt><b><a name="permit_dnswl_client">permit_dnswl_client <i>dnswl_domain=d.d.d.d</i></a></b></dt>
 
@@ -15565,8 +15574,12 @@ If no "<i>=d.d.d.d</i>" is specified, accept the request when the
 reversed client network address is listed with any A record under
 <i>dnswl_domain</i>. <br> For safety, <a href="postconf.5.html#permit_dnswl_client">permit_dnswl_client</a> is silently
 ignored when it would override <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>.  The
-result is DEFER_IF_REJECT when allowlist lookup fails.  This feature
-is available in Postfix 2.8 and later.  </dd>
+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 <a href="postconf.5.html#rbl_reply_maps">rbl_reply_maps</a> for how to avoid leaking an API key in SMTP
+server responses). <br>
+This feature is available in Postfix 2.8 and later. </dd>
 
 <dt><b><a name="reject_rhsbl_client">reject_rhsbl_client <i>rbl_domain=d.d.d.d</i></a></b></dt>
 
@@ -15579,9 +15592,14 @@ number..number ranges (Postfix version 2.8 and later).  If no
 hostname is listed with
 any A record under <i>rbl_domain</i>. See the <a href="postconf.5.html#reject_rbl_client">reject_rbl_client</a>
 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 <a href="postconf.5.html#rbl_reply_maps">rbl_reply_maps</a> 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, <a href="postconf.5.html#reject_rhsbl_reverse_client">reject_rhsbl_reverse_client</a> will usually
-produce better results.  </dd>
+produce better results. </dd>
 
 <dt><b><a name="permit_rhswl_client">permit_rhswl_client <i>rhswl_domain=d.d.d.d</i></a></b></dt>
 
@@ -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.
 <br> For safety, <a href="postconf.5.html#permit_rhswl_client">permit_rhswl_client</a> is silently ignored when it
 would override <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>.  The result is DEFER_IF_REJECT
-when allowlist lookup fails.  This feature is available in Postfix
-2.8 and later.  </dd>
+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 <a href="postconf.5.html#rbl_reply_maps">rbl_reply_maps</a> for how to avoid leaking an API key in SMTP
+server responses). <br>
+This feature is available in Postfix 2.8 and later.</dd>
 
 <dt><b><a name="reject_rhsbl_reverse_client">reject_rhsbl_reverse_client <i>rbl_domain=d.d.d.d</i></a></b></dt>
 
@@ -15609,8 +15631,12 @@ one or more ";"-separated numbers or number..number ranges.
 If no "<i>=d.d.d.d</i>" is specified, reject the request when the
 unverified reverse client hostname is listed with any A record under
 <i>rbl_domain</i>. See the <a href="postconf.5.html#reject_rbl_client">reject_rbl_client</a> description above for
-additional RBL related configuration parameters.  This feature is
-available in Postfix 2.8 and later.  </dd>
+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 <a href="postconf.5.html#rbl_reply_maps">rbl_reply_maps</a> for how to avoid leaking an API key in SMTP
+server responses). <br>
+This feature is available in Postfix 2.8 and later. </dd>
 
 <dt><b><a name="reject_unknown_client_hostname">reject_unknown_client_hostname</a></b> (with Postfix &lt; 2.3: reject_unknown_client)</dt>
 
@@ -16512,8 +16538,12 @@ listed with any A record under <i>rbl_domain</i>. See the
 parameters.  Note: specify "<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a> = yes" to fully
 enforce this restriction (without "<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a> = yes", a
 client can simply skip <a href="postconf.5.html#reject_rhsbl_helo">reject_rhsbl_helo</a> by not sending HELO or
-EHLO). This feature is available in Postfix 2.0
-and later.  </dd>
+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 <a href="postconf.5.html#rbl_reply_maps">rbl_reply_maps</a> for how to avoid leaking an API key in SMTP
+server responses). <br>
+This feature is available in Postfix 2.0 and later. </dd>
 
 <dt><b><a name="reject_unknown_helo_hostname">reject_unknown_helo_hostname</a></b> (with Postfix &lt; 2.3: reject_unknown_hostname)</dt>
 
@@ -17231,8 +17261,12 @@ any A record under <i>rbl_domain</i>. <br> The <a href="postconf.5.html#maps_rbl
 parameter specifies the response code for rejected requests (default:
 554); the <a href="postconf.5.html#default_rbl_reply">default_rbl_reply</a> parameter specifies the default server
 reply; and the <a href="postconf.5.html#rbl_reply_maps">rbl_reply_maps</a> parameter specifies tables with server
-replies indexed by <i>rbl_domain</i>.  This feature is available
-in Postfix version 2.0 and later.</dd>
+replies indexed by <i>rbl_domain</i>. <br>
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see <a href="postconf.5.html#rbl_reply_maps">rbl_reply_maps</a> for how to avoid leaking an API key in SMTP
+server responses). <br>
+This feature is available in Postfix version 2.0 and later.</dd>
 
 <dt><b><a name="reject_unauth_destination">reject_unauth_destination</a></b></dt>
 
@@ -18082,6 +18116,11 @@ listed with any A record under <i>rbl_domain</i>. <br> The
 rejected requests (default:  554); the <a href="postconf.5.html#default_rbl_reply">default_rbl_reply</a> parameter
 specifies the default server reply; and the <a href="postconf.5.html#rbl_reply_maps">rbl_reply_maps</a> parameter
 specifies tables with server replies indexed by <i>rbl_domain</i>.
+<br>
+NOTE: Always respect the usage policies of reputation services. Avoid
+public or ISP resolvers, unless the queries use your unique API key
+(see <a href="postconf.5.html#rbl_reply_maps">rbl_reply_maps</a> for how to avoid leaking an API key in SMTP
+server responses). <br>
 This feature is available in Postfix 2.0 and later.</dd>
 
 <dt><b><a name="reject_sender_login_mismatch">reject_sender_login_mismatch</a></b></dt>
index 207b99fadcfcb08232743c78e61e1e1f4abe7287..afb9cc97b5b7b870eb99e4fabcc318dd0a3c4e72 100644 (file)
@@ -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"
index f754c961c78ee69f81bf4f1dc7eb9434ebb3c1cc..4aef0b2d39a826301474c67bf43265d3b1ca36a7 100644 (file)
@@ -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 <i>rbl_domain</i>.
-This feature is available in Postfix 2.0 and later.  </dd>
+<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. </dd>
 
 <dt><b><a name="permit_dnswl_client">permit_dnswl_client <i>dnswl_domain=d.d.d.d</i></a></b></dt>
 
@@ -5483,8 +5488,12 @@ If no "<i>=d.d.d.d</i>" is specified, accept the request when the
 reversed client network address is listed with any A record under
 <i>dnswl_domain</i>. <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.  </dd>
+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. </dd>
 
 <dt><b><a name="reject_rhsbl_client">reject_rhsbl_client <i>rbl_domain=d.d.d.d</i></a></b></dt>
 
@@ -5497,9 +5506,14 @@ number..number ranges (Postfix version 2.8 and later).  If no
 hostname is listed with
 any A record under <i>rbl_domain</i>. 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.  </dd>
+produce better results. </dd>
 
 <dt><b><a name="permit_rhswl_client">permit_rhswl_client <i>rhswl_domain=d.d.d.d</i></a></b></dt>
 
@@ -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.
 <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.  </dd>
+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.</dd>
 
 <dt><b><a name="reject_rhsbl_reverse_client">reject_rhsbl_reverse_client <i>rbl_domain=d.d.d.d</i></a></b></dt>
 
@@ -5527,8 +5545,12 @@ one or more ";"-separated numbers or number..number ranges.
 If no "<i>=d.d.d.d</i>" is specified, reject the request when the
 unverified reverse client hostname is listed with any A record under
 <i>rbl_domain</i>. See the reject_rbl_client description above for
-additional RBL related configuration parameters.  This feature is
-available in Postfix 2.8 and later.  </dd>
+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. </dd>
 
 <dt><b><a name="reject_unknown_client_hostname">reject_unknown_client_hostname</a></b> (with Postfix &lt; 2.3: reject_unknown_client)</dt>
 
@@ -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.  </dd>
+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. </dd>
 
 <dt><b><a name="reject_unknown_helo_hostname">reject_unknown_helo_hostname</a></b> (with Postfix &lt; 2.3: reject_unknown_hostname)</dt>
 
@@ -6343,8 +6369,12 @@ any A record under <i>rbl_domain</i>. <br> 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 <i>rbl_domain</i>.  This feature is available
-in Postfix version 2.0 and later.</dd>
+replies indexed by <i>rbl_domain</i>. <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.</dd>
 
 <dt><b><a name="reject_unauth_destination">reject_unauth_destination</a></b></dt>
 
@@ -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 <i>rbl_domain</i>.
+<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.</dd>
 
 <dt><b><a name="reject_sender_login_mismatch">reject_sender_login_mismatch</a></b></dt>
@@ -14699,6 +14734,10 @@ clients,
 and postscreen(8) will update an SMTP client's DNSBL score with
 each non-error reply as described below. </p>
 
+<p> NOTE: Always respect the usage policies of reputation services.
+Avoid public or ISP resolvers, unless the queries use your unique
+API key. </p>
+
 <p> Caution: when postscreen rejects mail, its SMTP response contains
 the DNSBL
 domain name. Use the postscreen_dnsbl_reply_map feature to hide
index a4a6b63ab2d703050371c158dc188c62ae44da81..3ac8e4bb8d2cd6b6914439d037cbb8cd1ad6d341 100644 (file)
@@ -1672,3 +1672,4 @@ URIs
 bugfix
 MLKEM
 cleartext
+redacted
index 34e4391962ad737d64daabfa4254d678163e5805..3fa8e952895133858ff035edd3dbc22c98c78de4 100644 (file)
@@ -1858,3 +1858,5 @@ TINYCDB
 getdata
 XXXSENDOPTS
 xtra
+HAPROXY
+SRVR
index 6ed62d412af687d3e10fe3cc5758237aa76b6c89..8a2e26ca071449a7d072801c358f66008d080b02 100644 (file)
@@ -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
index 211cf1d1aeb377d71beb1d5e2460e1398a892947..9c8456ad92e97b826f77d50ec6ee4d4243de3f9c 100644 (file)
@@ -6,6 +6,31 @@
 /* SYNOPSIS
 /*     #include <haproxy_srvr.h>
 /*
+/*     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.
 /*     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. */
 #include <inet_proto.h>
 #include <split_at.h>
 #include <sock_addr.h>
+#include <normalize_v4mapped_addr.h>
 
 /* Global library. */
 
+#define _HAPROXY_SRVR_INTERNAL_
 #include <haproxy_srvr.h>
 
-/* 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 */
-
-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)
+/* haproxy_srvr_parse_sa - parse haproxy line */
+
+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
-
- /*
-  * 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,
-};
-
-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)
-
-/* evaluate_test_case - evaluate one test case */
-
-static int evaluate_test_case(const char *test_label,
-                                     const TEST_CASE *test_case)
-{
-    /* 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);
-}
-
-/* convert_v1_proxy_req_to_v2 - convert well-formed v1 proxy request to v2 */
+/* haproxy_srvr_receive - ABI compatibility */
 
-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);
+#undef haproxy_srvr_receive
 
-    /*
-     * 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     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);
 
-int     main(int argc, char **argv)
+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)
 {
-    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);
+    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));
 }
-
-#endif
index 4a801f1d82bddf0855721be5b769006819fb9625..8a21f79606af8dcb3194833c2d84ec2f4df90e19 100644 (file)
  /*
   * 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 (file)
index 0000000..8dcdd8b
--- /dev/null
@@ -0,0 +1,534 @@
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <stdlib.h>
+
+ /*
+  * Utility library.
+  */
+#include <msg.h>
+#include <msg_vstream.h>
+#include <myaddrinfo.h>
+#include <sock_addr.h>
+#include <stringops.h>
+#include <vstring.h>
+
+ /*
+  * Global library.
+  */
+#define _HAPROXY_SRVR_INTERNAL_
+#include <haproxy_srvr.h>
+
+ /*
+  * 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);
+}
index 87fd6ced41c5882144bc33eb618bdc650d0e782d..5e48134915070cbd6a5f3d03eb6c026f2986ff0b 100644 (file)
@@ -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
index 46e6579946733a626881e2aeede1871c1c1ba8d3..728fabc574e12f2da444539acbafe9ed235cf527 100644 (file)
@@ -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",
index a371f4c19d7e8150ac02c8da27060ceb0186e1b6..cef46d5cd1601bf50bf671316c3b722597b58619 100644 (file)
@@ -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)"
index 468732d30f9d9cb44e9e6ff600a9a00d610dbcdc..b77d5e2a18ed379d53eae2b766ba428e398a59a3 100644 (file)
 /* .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:
 /*     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.
index 32ad7fa34ce6824c988e1322f3b7a45943c37f23..e749eb0db7a3f825753b72c6b3ff0a3d8f982e5a 100644 (file)
@@ -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
index fedf7610de45ee27b343880eb00e5181faf51833..1e76caa4665d83aa87f221902c96465deb199ca1 100644 (file)
@@ -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
index 5edafde8d11d5c803a493a588b173d95a4261367..192506e19e3225baf189e7a1143fbf6082401b7e 100644 (file)
@@ -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);
        }
     }
index 0d6e0e005d6da80732913de03f26ae83c58078a7..14a6baecedcb17bdedda4a56eb336c76af286ec8 100644 (file)
@@ -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
index 7dccdb0bf77d3206df7d95ca0cf7e0933903c60c..a1aa8ca0211eb87eadf4b8310f88d6143d600d27 100644 (file)
@@ -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
index f2305dcebfe103ab5945556bb4f841bd784d917e..d7b83d00830eed34649ecf9988b03cc091ba888e 100644 (file)
@@ -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
index 33c1284225704775c21c410296661855e566be08..50dafe9da3e35f880c03593c417a2547347f3b4d 100644 (file)
@@ -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
index f73560bf342a957e980fdb8160bd2172c599096c..1f6177d3c576e33e277045a2100b4619f7f04241 100644 (file)
@@ -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 (file)
index 0000000..40f864a
--- /dev/null
@@ -0,0 +1,90 @@
+/*++
+/* NAME
+/*     normalize_v4mapped_addr 3
+/* SUMMARY
+/*     normalize v4mapped IPv6 address
+/* SYNOPSIS
+/*     #include <stringops.h>
+/*
+/*     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 <sys_defs.h>
+
+ /*
+  * Utility library.
+  */
+#include <inet_proto.h>
+#include <sock_addr.h>
+#include <normalize_v4mapped_addr.h>
+
+/* 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 (file)
index 0000000..fca3476
--- /dev/null
@@ -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 <normalize_v4mapped_addr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <sys/socket.h>
+
+ /*
+  * Utility library.
+  */
+#include <myaddrinfo.h>
+
+ /*
+  * 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 (file)
index 0000000..1a438f4
--- /dev/null
@@ -0,0 +1,189 @@
+ /*
+  * System library.
+  */
+#include <sys_defs.h>
+#include <stdlib.h>
+
+ /*
+  * Utility library.
+  */
+#include <normalize_v4mapped_addr.h>
+#include <msg.h>
+#include <msg_vstream.h>
+#include <inet_proto.h>
+#include <stringops.h>
+
+ /*
+  * 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);
+}
index 919054c0ee6bfef9d323b1ed806153f10dbe6e12..f3d0d3042a3bc82fb5449f4117e89c323ac660da 100644 (file)
 /*     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
   * Utility library.
   */
 #include <myaddrinfo.h>
-#include <inet_proto.h>
-
-static const INET_PROTO_INFO *proto_info;
+#include <normalize_v4mapped_addr.h>
 
 /* 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));
 }