diff --git a/postfix/HISTORY b/postfix/HISTORY index 94c94d7b3..449cbad12 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -29044,3 +29044,40 @@ Apologies for any names omitted. quotes. Oscar Bataille reported that the non-recommended form is not protected against SQL injection. Files: global/dict_sqlite.c, global/dict_sqlite_test.c. + +20250326 + + Updated myaddrinfo tests to also cover service/port conversion. + Files: util/myaddrinfo4.ref, util/myaddrinfo4.ref2, + util/myaddrinfo.c, util/myaddrinfo.ref, util/myaddrinfo.ref2. + +20260402 + + Documentation: updated guidance for using DNS-based reputation + services. File: proto/postconf.proto. + +20250404 + + Code health: simplified the conversions from IPv4 mapped + IPv6 addresses (::ffff:d.d.d.d) to their IPv4 form (d.d.d.d), + for both the binary form and human-readable form. Added + unit tests to show that the conversions work as expected. + Files: util/normalize_v4mapped_addr.[hc], + util/normalize_v4mapped_addr_test.c. + +20260406 + + Code health: overhauled the haproxy adapter to simplify + code and to avoid unnecessary conversions between binary + and human-readable forms. Added more unit tests for the v1 + and v2 proxy protocols. A separate update will overhaul + the smtpd 'peer' lookups. Files: global/haproxy_srvr.c, + global/haproxy_srvr_test.c. + +20250408 + + Code health: replace explicit code with normalize_v4mapped_xxx() + call. File: util/sane_sockaddr_to_hostaddr.c. + + Bit rot: sane_sockaddr_to_hostaddr() may modify its inputs. + smtp/smtp_tlsrpt.c, postscreen/postscreen_endpt.c diff --git a/postfix/html/postconf.5.html b/postfix/html/postconf.5.html index 4bb3b351c..03d029cd0 100644 --- a/postfix/html/postconf.5.html +++ b/postfix/html/postconf.5.html @@ -8878,6 +8878,10 @@ clients, and postscreen(8) will update an SMTP client's DNSBL score with each non-error reply as described below.

+

NOTE: Always respect the usage policies of reputation services. +Avoid public or ISP resolvers, unless the queries use your unique +API key.

+

Caution: when postscreen rejects mail, its SMTP response contains the DNSBL domain name. Use the postscreen_dnsbl_reply_map feature to hide @@ -15553,7 +15557,12 @@ The maps_rbl_reject_code para rejected requests (default: 554), the default_rbl_reply parameter specifies the default server reply, and the rbl_reply_maps parameter specifies tables with server replies indexed by rbl_domain. -This feature is available in Postfix 2.0 and later. +
+NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking the key in SMTP server +responses).
+This feature is available in Postfix 2.0 and later.

permit_dnswl_client dnswl_domain=d.d.d.d
@@ -15565,8 +15574,12 @@ If no "=d.d.d.d" is specified, accept the request when the reversed client network address is listed with any A record under dnswl_domain.
For safety, permit_dnswl_client is silently ignored when it would override reject_unauth_destination. The -result is DEFER_IF_REJECT when allowlist lookup fails. This feature -is available in Postfix 2.8 and later. +result is DEFER_IF_REJECT when allowlist lookup fails.
+NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking an API key in SMTP +server responses).
+This feature is available in Postfix 2.8 and later.
reject_rhsbl_client rbl_domain=d.d.d.d
@@ -15579,9 +15592,14 @@ number..number ranges (Postfix version 2.8 and later). If no hostname is listed with any A record under rbl_domain. See the reject_rbl_client description above for additional RBL related configuration parameters. +
+NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking an API key in SMTP +server responses).
This feature is available in Postfix 2.0 and later; with Postfix version 2.8 and later, reject_rhsbl_reverse_client will usually -produce better results. +produce better results.
permit_rhswl_client rhswl_domain=d.d.d.d
@@ -15597,8 +15615,12 @@ allowlisting should be used only to reduce false positives in e.g. DNS-based blocklists, and not for making access rule exceptions.
For safety, permit_rhswl_client is silently ignored when it would override reject_unauth_destination. The result is DEFER_IF_REJECT -when allowlist lookup fails. This feature is available in Postfix -2.8 and later. +when allowlist lookup fails.
+NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking an API key in SMTP +server responses).
+This feature is available in Postfix 2.8 and later.
reject_rhsbl_reverse_client rbl_domain=d.d.d.d
@@ -15609,8 +15631,12 @@ one or more ";"-separated numbers or number..number ranges. If no "=d.d.d.d" is specified, reject the request when the unverified reverse client hostname is listed with any A record under rbl_domain. See the reject_rbl_client description above for -additional RBL related configuration parameters. This feature is -available in Postfix 2.8 and later. +additional RBL related configuration parameters.
+NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking an API key in SMTP +server responses).
+This feature is available in Postfix 2.8 and later.
reject_unknown_client_hostname (with Postfix < 2.3: reject_unknown_client)
@@ -16512,8 +16538,12 @@ listed with any A record under rbl_domain. See the parameters. Note: specify "smtpd_helo_required = yes" to fully enforce this restriction (without "smtpd_helo_required = yes", a client can simply skip reject_rhsbl_helo by not sending HELO or -EHLO). This feature is available in Postfix 2.0 -and later. +EHLO).
+NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking an API key in SMTP +server responses).
+This feature is available in Postfix 2.0 and later.
reject_unknown_helo_hostname (with Postfix < 2.3: reject_unknown_hostname)
@@ -17231,8 +17261,12 @@ any A record under rbl_domain.
The default_rbl_reply parameter specifies the default server reply; and the rbl_reply_maps parameter specifies tables with server -replies indexed by rbl_domain. This feature is available -in Postfix version 2.0 and later. +replies indexed by rbl_domain.
+NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking an API key in SMTP +server responses).
+This feature is available in Postfix version 2.0 and later.
reject_unauth_destination
@@ -18082,6 +18116,11 @@ listed with any A record under rbl_domain.
The rejected requests (default: 554); the default_rbl_reply parameter specifies the default server reply; and the rbl_reply_maps parameter specifies tables with server replies indexed by rbl_domain. +
+NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking an API key in SMTP +server responses).
This feature is available in Postfix 2.0 and later.
reject_sender_login_mismatch
diff --git a/postfix/man/man5/postconf.5 b/postfix/man/man5/postconf.5 index 207b99fad..afb9cc97b 100644 --- a/postfix/man/man5/postconf.5 +++ b/postfix/man/man5/postconf.5 @@ -5505,6 +5505,10 @@ clients, and \fBpostscreen\fR(8) will update an SMTP client's DNSBL score with each non\-error reply as described below. .PP +NOTE: Always respect the usage policies of reputation services. +Avoid public or ISP resolvers, unless the queries use your unique +API key. +.PP Caution: when postscreen rejects mail, its SMTP response contains the DNSBL domain name. Use the postscreen_dnsbl_reply_map feature to hide @@ -10381,6 +10385,12 @@ The maps_rbl_reject_code parameter specifies the response code for rejected requests (default: 554), the default_rbl_reply parameter specifies the default server reply, and the rbl_reply_maps parameter specifies tables with server replies indexed by \fIrbl_domain\fR. +.br +NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking the key in SMTP server +responses). +.br This feature is available in Postfix 2.0 and later. .br .IP "\fBpermit_dnswl_client \fIdnswl_domain=d.d.d.d\fR\fR" @@ -10394,8 +10404,14 @@ reversed client network address is listed with any A record under .br For safety, permit_dnswl_client is silently ignored when it would override reject_unauth_destination. The -result is DEFER_IF_REJECT when allowlist lookup fails. This feature -is available in Postfix 2.8 and later. +result is DEFER_IF_REJECT when allowlist lookup fails. +.br +NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking an API key in SMTP +server responses). +.br +This feature is available in Postfix 2.8 and later. .br .IP "\fBreject_rhsbl_client \fIrbl_domain=d.d.d.d\fR\fR" Reject the request when the client hostname is listed with the @@ -10407,6 +10423,12 @@ number..number ranges (Postfix version 2.8 and later). If no hostname is listed with any A record under \fIrbl_domain\fR. See the reject_rbl_client description above for additional RBL related configuration parameters. +.br +NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking an API key in SMTP +server responses). +.br This feature is available in Postfix 2.0 and later; with Postfix version 2.8 and later, reject_rhsbl_reverse_client will usually produce better results. @@ -10426,8 +10448,14 @@ DNS\-based blocklists, and not for making access rule exceptions. .br For safety, permit_rhswl_client is silently ignored when it would override reject_unauth_destination. The result is DEFER_IF_REJECT -when allowlist lookup fails. This feature is available in Postfix -2.8 and later. +when allowlist lookup fails. +.br +NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking an API key in SMTP +server responses). +.br +This feature is available in Postfix 2.8 and later. .br .IP "\fBreject_rhsbl_reverse_client \fIrbl_domain=d.d.d.d\fR\fR" Reject the request when the unverified reverse client hostname @@ -10437,8 +10465,14 @@ one or more ";"\-separated numbers or number..number ranges. If no "\fI=d.d.d.d\fR" is specified, reject the request when the unverified reverse client hostname is listed with any A record under \fIrbl_domain\fR. See the reject_rbl_client description above for -additional RBL related configuration parameters. This feature is -available in Postfix 2.8 and later. +additional RBL related configuration parameters. +.br +NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking an API key in SMTP +server responses). +.br +This feature is available in Postfix 2.8 and later. .br .IP "\fBreject_unknown_client_hostname\fR (with Postfix < 2.3: reject_unknown_client)" Reject the request when 1) the client IP address\->name mapping @@ -11155,8 +11189,14 @@ reject_rbl_client description for additional RBL related configuration parameters. Note: specify "smtpd_helo_required = yes" to fully enforce this restriction (without "smtpd_helo_required = yes", a client can simply skip reject_rhsbl_helo by not sending HELO or -EHLO). This feature is available in Postfix 2.0 -and later. +EHLO). +.br +NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking an API key in SMTP +server responses). +.br +This feature is available in Postfix 2.0 and later. .br .IP "\fBreject_unknown_helo_hostname\fR (with Postfix < 2.3: reject_unknown_hostname)" Reject the request when the HELO or EHLO hostname has no DNS A @@ -11653,8 +11693,14 @@ The maps_rbl_reject_code parameter specifies the response code for rejected requests (default: 554); the default_rbl_reply parameter specifies the default server reply; and the rbl_reply_maps parameter specifies tables with server -replies indexed by \fIrbl_domain\fR. This feature is available -in Postfix version 2.0 and later. +replies indexed by \fIrbl_domain\fR. +.br +NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking an API key in SMTP +server responses). +.br +This feature is available in Postfix version 2.0 and later. .br .IP "\fBreject_unauth_destination\fR" Reject the request unless one of the following is true: @@ -12298,6 +12344,12 @@ maps_rbl_reject_code parameter specifies the response code for rejected requests (default: 554); the default_rbl_reply parameter specifies the default server reply; and the rbl_reply_maps parameter specifies tables with server replies indexed by \fIrbl_domain\fR. +.br +NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking an API key in SMTP +server responses). +.br This feature is available in Postfix 2.0 and later. .br .IP "\fBreject_sender_login_mismatch\fR" diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto index f754c961c..4aef0b2d3 100644 --- a/postfix/proto/postconf.proto +++ b/postfix/proto/postconf.proto @@ -5471,7 +5471,12 @@ The maps_rbl_reject_code parameter specifies the response code for rejected requests (default: 554), the default_rbl_reply parameter specifies the default server reply, and the rbl_reply_maps parameter specifies tables with server replies indexed by rbl_domain. -This feature is available in Postfix 2.0 and later. +
+NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking the key in SMTP server +responses).
+This feature is available in Postfix 2.0 and later.
permit_dnswl_client dnswl_domain=d.d.d.d
@@ -5483,8 +5488,12 @@ If no "=d.d.d.d" is specified, accept the request when the reversed client network address is listed with any A record under dnswl_domain.
For safety, permit_dnswl_client is silently ignored when it would override reject_unauth_destination. The -result is DEFER_IF_REJECT when allowlist lookup fails. This feature -is available in Postfix 2.8 and later. +result is DEFER_IF_REJECT when allowlist lookup fails.
+NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking an API key in SMTP +server responses).
+This feature is available in Postfix 2.8 and later.
reject_rhsbl_client rbl_domain=d.d.d.d
@@ -5497,9 +5506,14 @@ number..number ranges (Postfix version 2.8 and later). If no hostname is listed with any A record under rbl_domain. See the reject_rbl_client description above for additional RBL related configuration parameters. +
+NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking an API key in SMTP +server responses).
This feature is available in Postfix 2.0 and later; with Postfix version 2.8 and later, reject_rhsbl_reverse_client will usually -produce better results. +produce better results.
permit_rhswl_client rhswl_domain=d.d.d.d
@@ -5515,8 +5529,12 @@ allowlisting should be used only to reduce false positives in e.g. DNS-based blocklists, and not for making access rule exceptions.
For safety, permit_rhswl_client is silently ignored when it would override reject_unauth_destination. The result is DEFER_IF_REJECT -when allowlist lookup fails. This feature is available in Postfix -2.8 and later. +when allowlist lookup fails.
+NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking an API key in SMTP +server responses).
+This feature is available in Postfix 2.8 and later.
reject_rhsbl_reverse_client rbl_domain=d.d.d.d
@@ -5527,8 +5545,12 @@ one or more ";"-separated numbers or number..number ranges. If no "=d.d.d.d" is specified, reject the request when the unverified reverse client hostname is listed with any A record under rbl_domain. See the reject_rbl_client description above for -additional RBL related configuration parameters. This feature is -available in Postfix 2.8 and later. +additional RBL related configuration parameters.
+NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking an API key in SMTP +server responses).
+This feature is available in Postfix 2.8 and later.
reject_unknown_client_hostname (with Postfix < 2.3: reject_unknown_client)
@@ -6034,8 +6056,12 @@ reject_rbl_client description for additional RBL related configuration parameters. Note: specify "smtpd_helo_required = yes" to fully enforce this restriction (without "smtpd_helo_required = yes", a client can simply skip reject_rhsbl_helo by not sending HELO or -EHLO). This feature is available in Postfix 2.0 -and later. +EHLO).
+NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking an API key in SMTP +server responses).
+This feature is available in Postfix 2.0 and later.
reject_unknown_helo_hostname (with Postfix < 2.3: reject_unknown_hostname)
@@ -6343,8 +6369,12 @@ any A record under rbl_domain.
The maps_rbl_reject_code parameter specifies the response code for rejected requests (default: 554); the default_rbl_reply parameter specifies the default server reply; and the rbl_reply_maps parameter specifies tables with server -replies indexed by rbl_domain. This feature is available -in Postfix version 2.0 and later. +replies indexed by rbl_domain.
+NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking an API key in SMTP +server responses).
+This feature is available in Postfix version 2.0 and later.
reject_unauth_destination
@@ -6861,6 +6891,11 @@ maps_rbl_reject_code parameter specifies the response code for rejected requests (default: 554); the default_rbl_reply parameter specifies the default server reply; and the rbl_reply_maps parameter specifies tables with server replies indexed by rbl_domain. +
+NOTE: Always respect the usage policies of reputation services. Avoid +public or ISP resolvers, unless the queries use your unique API key +(see rbl_reply_maps for how to avoid leaking an API key in SMTP +server responses).
This feature is available in Postfix 2.0 and later.
reject_sender_login_mismatch
@@ -14699,6 +14734,10 @@ clients, and postscreen(8) will update an SMTP client's DNSBL score with each non-error reply as described below.

+

NOTE: Always respect the usage policies of reputation services. +Avoid public or ISP resolvers, unless the queries use your unique +API key.

+

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