diff --git a/postfix/.indent.pro b/postfix/.indent.pro
index ebb7b7270..f1671a6ec 100644
--- a/postfix/.indent.pro
+++ b/postfix/.indent.pro
@@ -260,6 +260,10 @@
-TPCF_SERVICE_DEF
-TPCF_SERVICE_PATTERN
-TPCF_STRING_NV
+-TPEER_FROM_HAPROXY_CASE
+-TPEER_FROM_NON_SOCKET_CASE
+-TPEER_FROM_PASS_ATTR_CASE
+-TPEER_FROM_UNCONN_SOCKET_CASE
-TPEER_NAME
-TPGSQL_NAME
-TPICKUP_INFO
@@ -358,6 +362,7 @@
-TSTRING_LIST
-TSTRING_TABLE
-TSYS_EXITS_DETAIL
+-TTEST_BASE
-TTEST_CASE
-TTLSMGR_SCACHE
-TTLSP_STATE
diff --git a/postfix/HISTORY b/postfix/HISTORY
index 449cbad12..489e0f9e9 100644
--- a/postfix/HISTORY
+++ b/postfix/HISTORY
@@ -29081,3 +29081,24 @@ Apologies for any names omitted.
Bit rot: sane_sockaddr_to_hostaddr() may modify its inputs.
smtp/smtp_tlsrpt.c, postscreen/postscreen_endpt.c
+
+20250411
+
+ Code health: simplified the Postfix SMTP server code to
+ find out the client and server IP addresses for an SMTP
+ connection. This takes advantage of the improved support
+ for address normalization and for haproxy load balancers.
+ Files: smtpd/smtpd_peer.c, smtpd/smtpd_haproxy.c.
+
+ Documentation: XCLIENT attribute availability. File:
+ proto/XCLIENT.
+
+20250418
+
+ Code health: added unit tests for connection address and
+ port information received through haproxy or postscreen,
+ and improved error handling. Files: smtpd/smtpd_peer.c,
+ smtpd/smtpd_haproxy.c, smtpd/smtpd_peer_test.c.
+
+ Unit tests for 'direct' connections are deferred pending
+ support to mock or intercept system library function calls.
diff --git a/postfix/Makefile.in b/postfix/Makefile.in
index a37f89f3b..12789cad7 100644
--- a/postfix/Makefile.in
+++ b/postfix/Makefile.in
@@ -115,7 +115,8 @@ manpages:
done
-
Note 4: Some Postfix implementations do not implement the PORT
-or LOGIN attributes.
+ Note 4: The PORT attribute is implemented in Postfix 2.5 and
+later; the LOGIN attribute in Postfix 2.9 and later.
XCLIENT Server response
diff --git a/postfix/proto/XCLIENT_README.html b/postfix/proto/XCLIENT_README.html
index 36a42bc91..ed228f49f 100644
--- a/postfix/proto/XCLIENT_README.html
+++ b/postfix/proto/XCLIENT_README.html
@@ -145,8 +145,8 @@ xtext encode attribute values. Servers that wish to interoperate
with these older implementations should be prepared to receive
unencoded information.
- Note 4: Some Postfix implementations do not implement the PORT
-or LOGIN attributes.
+ Note 4: The PORT attribute is implemented in Postfix 2.5 and
+later; the LOGIN attribute in Postfix 2.9 and later.
XCLIENT Server response
diff --git a/postfix/proto/stop b/postfix/proto/stop
index 3ac8e4bb8..7c06d7ee4 100644
--- a/postfix/proto/stop
+++ b/postfix/proto/stop
@@ -1673,3 +1673,4 @@ bugfix
MLKEM
cleartext
redacted
+subclassed
diff --git a/postfix/proto/stop.spell-history b/postfix/proto/stop.spell-history
index 1a66bdb7f..da067ab97 100644
--- a/postfix/proto/stop.spell-history
+++ b/postfix/proto/stop.spell-history
@@ -105,3 +105,4 @@ Oemer
Kozmenko
Oleksandr
Bataille
+balancers
diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h
index 5e4813491..1d3ab0d81 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 "20250409"
+#define MAIL_RELEASE_DATE "20250418"
#define MAIL_VERSION_NUMBER "3.11"
#ifdef SNAPSHOT
diff --git a/postfix/src/smtpd/Makefile.in b/postfix/src/smtpd/Makefile.in
index 139918f41..5bd1a78f6 100644
--- a/postfix/src/smtpd/Makefile.in
+++ b/postfix/src/smtpd/Makefile.in
@@ -13,7 +13,7 @@ HDRS = smtpd_token.h smtpd_check.h smtpd_chat.h smtpd_sasl_proto.h \
TESTSRC = smtpd_token_test.c
DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
CFLAGS = $(DEBUG) $(OPT) $(DEFS)
-TESTPROG= smtpd_token smtpd_check
+TESTPROG= smtpd_token smtpd_check smtpd_peer_test
PROG = smtpd
INC_DIR = ../../include
LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
@@ -53,18 +53,24 @@ smtpd_check: smtpd_check.o smtpd_check.c $(SMTPD_CHECK_OBJ) $(LIBS)
$(LIBS) $(SYSLIBS)
mv junk $@.o
+SMTPD_PEER_OBJ = smtpd_state.o smtpd_peer.o smtpd_haproxy.o
+
+smtpd_peer_test: smtpd_peer_test.c $(SMTPD_PEER_OBJ) $(LIBS)
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(SMTPD_PEER_OBJ) \
+ $(LIBS) $(SYSLIBS)
+
clean:
rm -f *.o *core $(PROG) $(TESTPROG) junk *.db *.out *.tmp
tidy: clean
-broken-tests: smtpd_check_test smtpd_check_test2
+broken-tests: smtpd_check_test smtpd_check_test2
tests: smtpd_acl_test smtpd_addr_valid_test smtpd_exp_test \
smtpd_token_test smtpd_check_test4 smtpd_check_dsn_test \
smtpd_check_backup_test smtpd_dnswl_test smtpd_error_test \
smtpd_server_test smtpd_nullmx_test smtpd_dns_filter_test \
- smtpd_deprecated_test
+ smtpd_deprecated_test test_smtpd_peer
root_tests:
@@ -165,6 +171,9 @@ smtpd_deprecated_test: smtpd_check smtpd_deprecated.in smtpd_deprecated.ref
diff smtpd_deprecated.ref smtpd_check.tmp
rm -f smtpd_check.tmp
+test_smtpd_peer: smtpd_peer_test
+ $(SHLIB_ENV) $(VALGRIND) ./smtpd_peer_test
+
depend: $(MAKES)
(sed '1,/^# do not edit/!d' Makefile.in; \
set -e; for i in [a-z][a-z0-9]*.c; do \
@@ -503,6 +512,36 @@ smtpd_peer.o: ../../include/vstream.h
smtpd_peer.o: ../../include/vstring.h
smtpd_peer.o: smtpd.h
smtpd_peer.o: smtpd_peer.c
+smtpd_peer_test.o: ../../include/argv.h
+smtpd_peer_test.o: ../../include/attr.h
+smtpd_peer_test.o: ../../include/check_arg.h
+smtpd_peer_test.o: ../../include/dns.h
+smtpd_peer_test.o: ../../include/haproxy_srvr.h
+smtpd_peer_test.o: ../../include/htable.h
+smtpd_peer_test.o: ../../include/inet_proto.h
+smtpd_peer_test.o: ../../include/iostuff.h
+smtpd_peer_test.o: ../../include/mail_params.h
+smtpd_peer_test.o: ../../include/mail_proto.h
+smtpd_peer_test.o: ../../include/mail_stream.h
+smtpd_peer_test.o: ../../include/milter.h
+smtpd_peer_test.o: ../../include/msg.h
+smtpd_peer_test.o: ../../include/msg_vstream.h
+smtpd_peer_test.o: ../../include/myaddrinfo.h
+smtpd_peer_test.o: ../../include/mymalloc.h
+smtpd_peer_test.o: ../../include/name_code.h
+smtpd_peer_test.o: ../../include/name_mask.h
+smtpd_peer_test.o: ../../include/nvtable.h
+smtpd_peer_test.o: ../../include/sock_addr.h
+smtpd_peer_test.o: ../../include/stringops.h
+smtpd_peer_test.o: ../../include/sys_defs.h
+smtpd_peer_test.o: ../../include/tls.h
+smtpd_peer_test.o: ../../include/vbuf.h
+smtpd_peer_test.o: ../../include/vstream.h
+smtpd_peer_test.o: ../../include/vstring.h
+smtpd_peer_test.o: smtpd.h
+smtpd_peer_test.o: smtpd_chat.h
+smtpd_peer_test.o: smtpd_peer_test.c
+smtpd_peer_test.o: smtpd_sasl_glue.h
smtpd_proxy.o: ../../include/argv.h
smtpd_proxy.o: ../../include/attr.h
smtpd_proxy.o: ../../include/check_arg.h
diff --git a/postfix/src/smtpd/smtpd_haproxy.c b/postfix/src/smtpd/smtpd_haproxy.c
index 542c3fe3d..2b1d26915 100644
--- a/postfix/src/smtpd/smtpd_haproxy.c
+++ b/postfix/src/smtpd/smtpd_haproxy.c
@@ -14,11 +14,16 @@
/*
/* The following summarizes what the Postfix SMTP server expects
/* from an up-stream proxy adapter.
+/*
+/* .IP
+/* Return -1 in case of error, zero otherwise. In case of error,
+/* the caller will clean up any incomplete endpoint info.
/* .IP \(bu
/* Call smtpd_peer_from_default() if the up-stream proxy
/* indicates that the connection is not proxied. In that case,
-/* a proxy adapter MUST NOT update any STATE fields: the
-/* smtpd_peer_from_default() function will do that instead.
+/* a proxy adapter MUST NOT update dynamically-allocated STATE
+/* text fields: the smtpd_peer_from_default() function will do
+/* that instead.
/* .IP \(bu
/* Validate protocol, address and port syntax. Permit only
/* protocols that are configured with the main.cf:inet_protocols
@@ -27,13 +32,17 @@
/* Convert IPv4-in-IPv6 address syntax to IPv4 syntax when
/* both IPv6 and IPv4 support are enabled with main.cf:inet_protocols.
/* .IP \(bu
-/* Update the following session context fields: addr, port,
-/* rfc_addr, addr_family, dest_addr, dest_port. The addr_family
-/* field applies to the client address.
+/* Update the following STATE fields: addr, port, rfc_addr,
+/* addr_family, dest_addr, dest_port. The addr_family field applies
+/* to the client address.
+/* .IP \(bu
+/* Either update the STATE binary fields sockaddr and dest_sockaddr,
+/* or call smtpd_peer_hostaddr_to_sockaddr() after updating the
+/* STATE text fields addr, port, dest_addr, and dest_port.
/* .IP \(bu
/* Dynamically allocate storage for string information with
/* mystrdup(). In case of error, leave unassigned string fields
-/* at their initial zero value.
+/* at their initial zero value. The caller will clean up.
/* .IP \(bu
/* Log a clear warning message that explains why a request
/* fails.
@@ -62,6 +71,9 @@
/* Google, Inc.
/* 111 8th Avenue
/* New York, NY 10011, USA
+/*
+/* Wietse Venema
+/* porcupine.org
/*--*/
/* System library. */
@@ -107,9 +119,15 @@ int smtpd_peer_from_haproxy(SMTPD_STATE *state)
msg_warn("haproxy read: timeout error");
return (-1);
}
- if (haproxy_srvr_receive(vstream_fileno(state->client), &non_proxy,
- &smtp_client_addr, &smtp_client_port,
- &smtp_server_addr, &smtp_server_port) < 0) {
+ state->sockaddr_len = sizeof(state->sockaddr);
+ state->dest_sockaddr_len = sizeof(state->dest_sockaddr);
+ if (haproxy_srvr_receive_sa(vstream_fileno(state->client), &non_proxy,
+ &smtp_client_addr, &smtp_client_port,
+ &smtp_server_addr, &smtp_server_port,
+ (struct sockaddr *) &state->sockaddr,
+ &state->sockaddr_len,
+ (struct sockaddr *) &state->dest_sockaddr,
+ &state->dest_sockaddr_len) <0) {
return (-1);
}
if (non_proxy) {
diff --git a/postfix/src/smtpd/smtpd_peer.c b/postfix/src/smtpd/smtpd_peer.c
index b77d5e2a1..3ca3ce595 100644
--- a/postfix/src/smtpd/smtpd_peer.c
+++ b/postfix/src/smtpd/smtpd_peer.c
@@ -21,12 +21,16 @@
/* are set to "unknown".
/*
/* Alternatively, the peer address and port may be obtained
-/* from a proxy server.
+/* from a proxy server or from attributes that postscreen(8)
+/* passes to smtpd(8) over local IPC.
/*
/* This module uses the local name service via getaddrinfo()
/* and getnameinfo(). It does not query the DNS directly.
/*
/* smtpd_peer_init() updates the following fields:
+/* .IP flags.SMTPD_FLAG_HANGUP
+/* This flag is raised when the program should hang up
+/* without reading client input.
/* .IP name
/* The verified client hostname. This name is represented by
/* the string "unknown" when 1) the address->name lookup failed,
@@ -36,16 +40,12 @@
/* The unverified client hostname as found with address->name
/* lookup; it is not verified for consistency with the client
/* IP address result from name->address lookup.
-/* .IP forward_name
-/* The unverified client hostname as found with address->name
-/* lookup followed by name->address lookup; it is not verified
-/* for consistency with the result from address->name lookup.
-/* For example, when the address->name lookup produces as
-/* hostname an alias, the name->address lookup will produce
-/* as hostname the expansion of that alias, so that the two
-/* lookups produce different names.
/* .IP addr
/* Printable representation of the client address.
+/* .IP addr_family
+/* AF_INET or AF_INET6 in case of an open TCP connection.
+/* AF_UNSPEC in all other cases, including an open non-socket
+/* connection, or a closed connection.
/* .IP namaddr
/* String of the form: "name[addr]:port".
/* .IP rfc_addr
@@ -88,19 +88,6 @@
/* .IP 5
/* The address->name lookup failed with an unrecoverable error.
/* .RE
-/* .IP forward_name_status
-/* The forward_name_status result field specifies how the
-/* forward_name information should be interpreted:
-/* .RS
-/* .IP 2
-/* The address->name and name->address lookup succeeded.
-/* .IP 4
-/* The address->name lookup or name->address failed with a
-/* recoverable error.
-/* .IP 5
-/* The address->name lookup or name->address failed with an
-/* unrecoverable error.
-/* .RE
/* .PP
/* smtpd_peer_reset() releases memory allocated by smtpd_peer_init().
/*
@@ -159,8 +146,6 @@
#include "smtpd.h"
-static const INET_PROTO_INFO *proto_info;
-
/*
* XXX If we make local port information available via logging, then we must
* also support these attributes with the XFORWARD command.
@@ -177,7 +162,7 @@ static int smtpd_peer_sockaddr_to_hostaddr(SMTPD_STATE *state)
{
const char *myname = "smtpd_peer_sockaddr_to_hostaddr";
struct sockaddr *sa = (struct sockaddr *) &(state->sockaddr);
- SOCKADDR_SIZE sa_length = state->sockaddr_len;
+ SOCKADDR_SIZE *sa_length = &state->sockaddr_len;
/*
* XXX If we're given an IPv6 (or IPv4) connection from, e.g., inetd,
@@ -195,12 +180,11 @@ static int smtpd_peer_sockaddr_to_hostaddr(SMTPD_STATE *state)
MAI_HOSTADDR_STR server_addr;
MAI_SERVPORT_STR server_port;
int aierr;
- char *colonp;
/*
* Sanity check: we can't use sockets that we're not configured for.
*/
- if (strchr((char *) proto_info->sa_family_list, sa->sa_family) == 0)
+ if (strchr((char *) inet_proto_info()->sa_family_list, sa->sa_family) == 0)
msg_fatal("cannot handle socket type %s with \"%s = %s\"",
#ifdef AF_INET6
sa->sa_family == AF_INET6 ? "AF_INET6" :
@@ -222,15 +206,15 @@ static int smtpd_peer_sockaddr_to_hostaddr(SMTPD_STATE *state)
/*
* Convert the client address to printable form.
*/
- if ((aierr = sockaddr_to_hostaddr(sa, sa_length, &client_addr,
- &client_port, 0)) != 0)
+ if ((aierr = sane_sockaddr_to_hostaddr(sa, sa_length, &client_addr,
+ &client_port, 0)) != 0)
msg_fatal("%s: cannot convert client sockaddr type %s length %ld "
"to string: %s", myname,
#ifdef AF_INET6
sa->sa_family == AF_INET6 ? "AF_INET6" :
#endif
sa->sa_family == AF_INET ? "AF_INET" : "other",
- (long) sa_length, MAI_STRERROR(aierr));
+ (long) *sa_length, MAI_STRERROR(aierr));
state->port = mystrdup(client_port.buf);
/*
@@ -241,55 +225,20 @@ static int smtpd_peer_sockaddr_to_hostaddr(SMTPD_STATE *state)
if (strchr(client_addr.buf, '%') != 0)
msg_panic("%s: address %s has datalink suffix",
myname, client_addr.buf);
-#endif
/*
- * We convert IPv4-in-IPv6 address to 'true' IPv4 address early on,
- * but only if IPv4 support is enabled (why would anyone want to turn
- * it off)? With IPv4 support enabled we have no need for the IPv6
- * form in logging, hostname verification and access checks.
+ * Following RFC 2821 section 4.1.3, an IPv6 address literal gets a
+ * prefix of 'IPv6:'. We do this consistently for all IPv6 addresses
+ * that appear in headers or envelopes. The fact that
+ * valid_mailhost_addr() enforces the form helps of course. We use
+ * the form without IPV6: prefix when doing access control, or when
+ * accessing the connection cache.
*/
-#ifdef HAS_IPV6
if (sa->sa_family == AF_INET6) {
- if (strchr((char *) proto_info->sa_family_list, AF_INET) != 0
- && IN6_IS_ADDR_V4MAPPED(&SOCK_ADDR_IN6_ADDR(sa))
- && (colonp = strrchr(client_addr.buf, ':')) != 0) {
- struct addrinfo *res0;
-
- if (msg_verbose > 1)
- msg_info("%s: rewriting V4-mapped address \"%s\" to \"%s\"",
- myname, client_addr.buf, colonp + 1);
-
- state->addr = mystrdup(colonp + 1);
- state->rfc_addr = mystrdup(colonp + 1);
- state->addr_family = AF_INET;
- aierr =
- hostaddr_to_sockaddr(state->addr, state->port, 0, &res0);
- if (aierr)
- msg_fatal("%s: cannot convert [%s]:%s to binary: %s",
- myname, state->addr, state->port,
- MAI_STRERROR(aierr));
- sa_length = res0->ai_addrlen;
- if (sa_length > sizeof(state->sockaddr))
- sa_length = sizeof(state->sockaddr);
- memcpy((void *) sa, res0->ai_addr, sa_length);
- freeaddrinfo(res0); /* 200412 */
- }
-
- /*
- * Following RFC 2821 section 4.1.3, an IPv6 address literal gets
- * a prefix of 'IPv6:'. We do this consistently for all IPv6
- * addresses that appear in headers or envelopes. The fact that
- * valid_mailhost_addr() enforces the form helps of course. We
- * use the form without IPV6: prefix when doing access control,
- * or when accessing the connection cache.
- */
- else {
- state->addr = mystrdup(client_addr.buf);
- state->rfc_addr =
- concatenate(IPV6_COL, client_addr.buf, (char *) 0);
- state->addr_family = sa->sa_family;
- }
+ state->addr = mystrdup(client_addr.buf);
+ state->rfc_addr =
+ concatenate(IPV6_COL, client_addr.buf, (char *) 0);
+ state->addr_family = sa->sa_family;
}
/*
@@ -306,14 +255,13 @@ static int smtpd_peer_sockaddr_to_hostaddr(SMTPD_STATE *state)
/*
* Convert the server address/port to printable form.
*/
- if ((aierr = sockaddr_to_hostaddr((struct sockaddr *)
- &state->dest_sockaddr,
- state->dest_sockaddr_len,
- &server_addr,
- &server_port, 0)) != 0)
- /* TODO: convert IPv4-in-IPv6 to IPv4 form. */
+ if ((aierr = sane_sockaddr_to_hostaddr((struct sockaddr *)
+ &state->dest_sockaddr,
+ &state->dest_sockaddr_len,
+ &server_addr,
+ &server_port, 0)) != 0)
msg_fatal("%s: cannot convert server sockaddr type %s length %ld "
- "to string: %s", myname,
+ "to string: %s", myname,
#ifdef AF_INET6
state->dest_sockaddr.ss_family == AF_INET6 ? "AF_INET6" :
#endif
@@ -404,7 +352,7 @@ static void smtpd_peer_sockaddr_to_hostname(SMTPD_STATE *state)
REJECT_PEER_NAME(state, SMTPD_PEER_CODE_FORGED);
break;
}
- if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) {
+ if (strchr((char *) inet_proto_info()->sa_family_list, res->ai_family) == 0) {
msg_info("skipping address family %d for host %s",
res->ai_family, state->name);
continue;
@@ -425,6 +373,11 @@ static void smtpd_peer_hostaddr_to_sockaddr(SMTPD_STATE *state)
struct addrinfo *res;
int aierr;
+ /*
+ * The client binary address value is provided by non-error peer lookup
+ * methods. It is used to compute the anvil aggregation prefix for client
+ * IP addresses.
+ */
if ((aierr = hostaddr_to_sockaddr(state->addr, state->port,
SOCK_STREAM, &res)) != 0)
msg_fatal("%s: cannot convert client address '%s' port '%s' to binary: %s",
@@ -434,11 +387,26 @@ static void smtpd_peer_hostaddr_to_sockaddr(SMTPD_STATE *state)
memcpy((void *) &(state->sockaddr), res->ai_addr, res->ai_addrlen);
state->sockaddr_len = res->ai_addrlen;
freeaddrinfo(res);
+
+ /*
+ * The server binary address is provided by non-error peer lookup
+ * methods. It is currently unused, but it is the result of a hermetic
+ * conversion, therefore low-risk.
+ */
+ if ((aierr = hostaddr_to_sockaddr(state->dest_addr, state->dest_port,
+ SOCK_STREAM, &res)) != 0)
+ msg_fatal("%s: cannot convert server address '%s' port '%s' to binary: %s",
+ myname, state->dest_addr, state->dest_port, MAI_STRERROR(aierr));
+ if (res->ai_addrlen > sizeof(state->dest_sockaddr))
+ msg_panic("%s: address length > struct sockaddr_storage", myname);
+ memcpy((void *) &(state->dest_sockaddr), res->ai_addr, res->ai_addrlen);
+ state->dest_sockaddr_len = res->ai_addrlen;
+ freeaddrinfo(res);
}
-/* smtpd_peer_not_inet - non-socket or non-Internet endpoint */
+/* smtpd_peer_assume_local_client - non-Internet endpoint */
-static void smtpd_peer_not_inet(SMTPD_STATE *state)
+static void smtpd_peer_assume_local_client(SMTPD_STATE *state)
{
/*
@@ -448,7 +416,7 @@ static void smtpd_peer_not_inet(SMTPD_STATE *state)
state->name = mystrdup("localhost");
state->reverse_name = mystrdup("localhost");
#ifdef AF_INET6
- if (proto_info->sa_family_list[0] == PF_INET6) {
+ if (inet_proto_info()->sa_family_list[0] == PF_INET6) {
state->addr = mystrdup("::1"); /* XXX bogus. */
state->rfc_addr = mystrdup(IPV6_COL "::1"); /* XXX bogus. */
} else
@@ -464,13 +432,15 @@ static void smtpd_peer_not_inet(SMTPD_STATE *state)
state->dest_addr = mystrdup(state->addr); /* XXX bogus. */
state->dest_port = mystrdup(state->port); /* XXX bogus. */
+
+ state->sockaddr_len = 0;
+ state->dest_sockaddr_len = 0;
}
-/* smtpd_peer_no_client - peer went away, or peer info unavailable */
+/* smtpd_peer_assume_unknown_client - peer went away, or peer info unavailable */
-static void smtpd_peer_no_client(SMTPD_STATE *state)
+static void smtpd_peer_assume_unknown_client(SMTPD_STATE *state)
{
- smtpd_peer_reset(state);
state->name = mystrdup(CLIENT_NAME_UNKNOWN);
state->reverse_name = mystrdup(CLIENT_NAME_UNKNOWN);
state->addr = mystrdup(CLIENT_ADDR_UNKNOWN);
@@ -482,58 +452,67 @@ static void smtpd_peer_no_client(SMTPD_STATE *state)
state->dest_addr = mystrdup(SERVER_ADDR_UNKNOWN);
state->dest_port = mystrdup(SERVER_PORT_UNKNOWN);
+
+ state->sockaddr_len = 0;
+ state->dest_sockaddr_len = 0;
}
/* smtpd_peer_from_pass_attr - initialize from attribute hash */
-static void smtpd_peer_from_pass_attr(SMTPD_STATE *state)
+static int smtpd_peer_from_pass_attr(SMTPD_STATE *state)
{
HTABLE *attr = (HTABLE *) vstream_context(state->client);
const char *cp;
+#define BAD_PASS_ATTR(...) do { \
+ msg_warn(__VA_ARGS__); \
+ return (-1); \
+ } while (0)
+
/*
* Extract the client endpoint information from the attribute hash.
*/
if ((cp = htable_find(attr, MAIL_ATTR_ACT_CLIENT_ADDR)) == 0)
- msg_fatal("missing client address from proxy");
+ BAD_PASS_ATTR("missing client address from proxy");
if (strrchr(cp, ':') != 0) {
if (valid_ipv6_hostaddr(cp, DO_GRIPE) == 0)
- msg_fatal("bad IPv6 client address syntax from proxy: %s", cp);
+ BAD_PASS_ATTR("bad IPv6 client address syntax from proxy: %s", cp);
state->addr = mystrdup(cp);
state->rfc_addr = concatenate(IPV6_COL, cp, (char *) 0);
state->addr_family = AF_INET6;
} else {
if (valid_ipv4_hostaddr(cp, DO_GRIPE) == 0)
- msg_fatal("bad IPv4 client address syntax from proxy: %s", cp);
+ BAD_PASS_ATTR("bad IPv4 client address syntax from proxy: %s", cp);
state->addr = mystrdup(cp);
state->rfc_addr = mystrdup(cp);
state->addr_family = AF_INET;
}
if ((cp = htable_find(attr, MAIL_ATTR_ACT_CLIENT_PORT)) == 0)
- msg_fatal("missing client port from proxy");
+ BAD_PASS_ATTR("missing client port from proxy");
if (valid_hostport(cp, DO_GRIPE) == 0)
- msg_fatal("bad TCP client port number syntax from proxy: %s", cp);
+ BAD_PASS_ATTR("bad TCP client port number syntax from proxy: %s", cp);
state->port = mystrdup(cp);
/*
* The Dovecot authentication server needs the server IP address.
*/
if ((cp = htable_find(attr, MAIL_ATTR_ACT_SERVER_ADDR)) == 0)
- msg_fatal("missing server address from proxy");
+ BAD_PASS_ATTR("missing server address from proxy");
if (valid_hostaddr(cp, DO_GRIPE) == 0)
- msg_fatal("bad IPv6 server address syntax from proxy: %s", cp);
+ BAD_PASS_ATTR("bad IPv6 server address syntax from proxy: %s", cp);
state->dest_addr = mystrdup(cp);
if ((cp = htable_find(attr, MAIL_ATTR_ACT_SERVER_PORT)) == 0)
- msg_fatal("missing server port from proxy");
+ BAD_PASS_ATTR("missing server port from proxy");
if (valid_hostport(cp, DO_GRIPE) == 0)
- msg_fatal("bad TCP server port number syntax from proxy: %s", cp);
+ BAD_PASS_ATTR("bad TCP server port number syntax from proxy: %s", cp);
state->dest_port = mystrdup(cp);
/*
* Convert the client address from string to binary form.
*/
smtpd_peer_hostaddr_to_sockaddr(state);
+ return (0);
}
/* smtpd_peer_from_default - try to initialize peer information from socket */
@@ -544,8 +523,8 @@ void smtpd_peer_from_default(SMTPD_STATE *state)
/*
* The "no client" routine provides surrogate information so that the
* application can produce sensible logging when a client disconnects
- * before the server wakes up. The "not inet" routine provides surrogate
- * state for (presumably) local IPC channels.
+ * before the server wakes up. The "assume local" routine provides
+ * surrogate state for open, presumably, local, IPC channels.
*/
state->sockaddr_len = sizeof(state->sockaddr);
state->dest_sockaddr_len = sizeof(state->dest_sockaddr);
@@ -556,18 +535,34 @@ void smtpd_peer_from_default(SMTPD_STATE *state)
(struct sockaddr *) &state->dest_sockaddr,
&state->dest_sockaddr_len) < 0) {
if (errno == ENOTSOCK)
- smtpd_peer_not_inet(state);
+ smtpd_peer_assume_local_client(state);
else
- smtpd_peer_no_client(state);
+ smtpd_peer_assume_unknown_client(state);
} else {
if (smtpd_peer_sockaddr_to_hostaddr(state) < 0)
- smtpd_peer_not_inet(state);
+ smtpd_peer_assume_local_client(state);
}
}
+/* smtpd_peer_fall_back_and_hangup - recover after incomplete peer info */
+
+static void smtpd_peer_fall_back_and_hangup(SMTPD_STATE *state)
+{
+
+ /*
+ * Clear incomplete endpoint info. Populate the SMTPD_STATE with default
+ * endpoint info, so that the caller won't trip over a null pointer. Hang
+ * up before accepting input: we don't know what we're talking to and
+ * what rights they might have.
+ */
+ smtpd_peer_reset(state);
+ smtpd_peer_assume_unknown_client(state);
+ state->flags |= SMTPD_FLAG_HANGUP;
+}
+
/* smtpd_peer_from_proxy - get endpoint info from proxy agent */
-static void smtpd_peer_from_proxy(SMTPD_STATE *state)
+static int smtpd_peer_from_proxy(SMTPD_STATE *state)
{
typedef struct {
const char *name;
@@ -575,6 +570,11 @@ static void smtpd_peer_from_proxy(SMTPD_STATE *state)
} SMTPD_ENDPT_LOOKUP_INFO;
static const SMTPD_ENDPT_LOOKUP_INFO smtpd_endpt_lookup_info[] = {
HAPROXY_PROTO_NAME, smtpd_peer_from_haproxy,
+
+ /*
+ * See smtpd_haproxy.c for the a summary of the information that a
+ * proxy endpoint lookup function is expected to provide.
+ */
0,
};
const SMTPD_ENDPT_LOOKUP_INFO *pp;
@@ -588,13 +588,7 @@ static void smtpd_peer_from_proxy(SMTPD_STATE *state)
msg_fatal("unsupported %s value: %s",
VAR_SMTPD_UPROXY_PROTO, var_smtpd_uproxy_proto);
if (strcmp(var_smtpd_uproxy_proto, pp->name) == 0)
- break;
- }
- if (pp->endpt_lookup(state) < 0) {
- smtpd_peer_from_default(state);
- state->flags |= SMTPD_FLAG_HANGUP;
- } else {
- smtpd_peer_hostaddr_to_sockaddr(state);
+ return (pp->endpt_lookup(state));
}
}
@@ -602,13 +596,6 @@ static void smtpd_peer_from_proxy(SMTPD_STATE *state)
void smtpd_peer_init(SMTPD_STATE *state)
{
- int af;
-
- /*
- * Initialize.
- */
- if (proto_info == 0)
- proto_info = inet_proto_info();
/*
* Prepare for partial initialization after error.
@@ -629,19 +616,25 @@ void smtpd_peer_init(SMTPD_STATE *state)
/*
* Determine the remote SMTP client address and port.
*
+ * If we can't process the connection hand-off info from postscreen or
+ * proxy, fall back to some default endpoint info for logging and force a
+ * hangup. We can't determine what rights the peer should have.
+ *
* XXX In stand-alone mode, don't assume that the peer will be a local
* process. That could introduce a gaping hole when the SMTP daemon is
* hooked up to the network via inetd or some other super-server.
*/
if (vstream_context(state->client) != 0) {
- smtpd_peer_from_pass_attr(state);
+ if (smtpd_peer_from_pass_attr(state) < 0)
+ smtpd_peer_fall_back_and_hangup(state);
if (*var_smtpd_uproxy_proto != 0)
msg_warn("ignoring non-empty %s setting behind postscreen",
VAR_SMTPD_UPROXY_PROTO);
} else if (SMTPD_STAND_ALONE(state) || *var_smtpd_uproxy_proto == 0) {
smtpd_peer_from_default(state);
} else {
- smtpd_peer_from_proxy(state);
+ if (smtpd_peer_from_proxy(state) < 0)
+ smtpd_peer_fall_back_and_hangup(state);
}
/*
@@ -659,14 +652,14 @@ void smtpd_peer_init(SMTPD_STATE *state)
state->port);
/*
- * Generate 'address' or 'net/mask' index for anvil event aggregation.
- * Don't do this for non-socket input. See smtpd_peer_not_inet().
+ * Generate the 'address' or 'net/mask' index for anvil event
+ * aggregation.
*/
+
if (state->addr_family != AF_UNSPEC) {
- af = SOCK_ADDR_FAMILY(&(state->sockaddr));
- state->anvil_range = inet_prefix_top(af,
+ state->anvil_range = inet_prefix_top(state->addr_family,
SOCK_ADDR_ADDRP(&(state->sockaddr)),
- af == AF_INET ?
+ state->addr_family == AF_INET ?
var_smtpd_cipv4_prefix :
var_smtpd_cipv6_prefix);
}
@@ -676,22 +669,24 @@ void smtpd_peer_init(SMTPD_STATE *state)
void smtpd_peer_reset(SMTPD_STATE *state)
{
+#define MYFREE_AND_ZERO(e) do { myfree(e); (e) = 0; } while (0);
+
if (state->name)
- myfree(state->name);
+ MYFREE_AND_ZERO(state->name);
if (state->reverse_name)
- myfree(state->reverse_name);
+ MYFREE_AND_ZERO(state->reverse_name);
if (state->addr)
- myfree(state->addr);
+ MYFREE_AND_ZERO(state->addr);
if (state->namaddr)
- myfree(state->namaddr);
+ MYFREE_AND_ZERO(state->namaddr);
if (state->rfc_addr)
- myfree(state->rfc_addr);
+ MYFREE_AND_ZERO(state->rfc_addr);
if (state->port)
- myfree(state->port);
+ MYFREE_AND_ZERO(state->port);
if (state->dest_addr)
- myfree(state->dest_addr);
+ MYFREE_AND_ZERO(state->dest_addr);
if (state->dest_port)
- myfree(state->dest_port);
+ MYFREE_AND_ZERO(state->dest_port);
if (state->anvil_range)
- myfree(state->anvil_range);
+ MYFREE_AND_ZERO(state->anvil_range);
}
diff --git a/postfix/src/smtpd/smtpd_peer_test.c b/postfix/src/smtpd/smtpd_peer_test.c
new file mode 100644
index 000000000..d1e42cc48
--- /dev/null
+++ b/postfix/src/smtpd/smtpd_peer_test.c
@@ -0,0 +1,715 @@
+/*++
+/* NAME
+/* smtpd_peer_test 1t
+/* SUMMARY
+/* smtpd_peer_init() unit tests
+/* SYNOPSIS
+/* ./smtpd_peer_test
+/* DESCRIPTION
+/* Verifies that smtpd_peer_init() will update the SMTPD_STATE
+/* structure with the expected error or endpoint information for
+/* different input sources.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* porcupine.org
+/*--*/
+
+ /*
+ * System library.
+ */
+#include
+
+ /*
+ * Utility library.
+ */
+#include
+#include
+#include
+#include
+#include
+
+ /*
+ * Global library.
+ */
+#include
+#include
+#include
+#include
+
+ /*
+ * TODO(wietse) make this a proper VSTREAM interface or test helper API.
+ */
+
+/* vstream_swap - capture output for testing */
+
+static void vstream_swap(VSTREAM *one, VSTREAM *two)
+{
+ VSTREAM save;
+
+ save = *one;
+ *one = *two;
+ *two = save;
+}
+
+ /*
+ * Application-specific.
+ */
+#include
+
+int tests_failed = 0;
+int tests_passed = 0;
+
+ /*
+ * Fakes to satisfy some dependencies.
+ */
+#include
+#include
+
+#define TEST_TIMEOUT 10
+char *var_smtpd_uproxy_proto = "";
+int var_smtpd_uproxy_tmout = TEST_TIMEOUT;
+bool var_smtpd_peername_lookup;
+bool var_smtpd_client_port_log;
+bool var_smtpd_sasl_enable;
+int var_smtpd_cipv4_prefix = DEF_SMTPD_CIPV4_PREFIX;
+int var_smtpd_cipv6_prefix = DEF_SMTPD_CIPV6_PREFIX;
+char *var_notify_classes = DEF_NOTIFY_CLASSES;
+void smtpd_chat_reset(SMTPD_STATE *state)
+{
+}
+void smtpd_sasl_state_init(SMTPD_STATE *state)
+{
+}
+void smtpd_xforward_init(SMTPD_STATE *state)
+{
+}
+
+/* Reset globals that may be tweaked by individual tests */
+
+static void reset_global_variables(void)
+{
+ var_smtpd_uproxy_proto = "";
+ inet_proto_init("reset_global_variables", "all");
+}
+
+ /*
+ * Basic tests that smtpd_peer_init() will update the SMTPD_STATE structure
+ * with the expected error info or endpoint info. This needs to be subclassed
+ * to support different input sources (local client, no open connection,
+ * HaProxy, postscreen, etc.).
+ */
+typedef struct TEST_BASE {
+ const char *label;
+ int want_hangup;
+ const char *want_warning;
+ const char *want_client_name;
+ int want_client_name_status;
+ const char *want_client_reverse_name;
+ int want_client_reverse_name_status;
+ const char *want_client_addr;
+ const char *want_client_rfc_addr;
+ const char *want_client_port;
+ int want_client_addr_family;
+ const char *want_server_addr;
+ const char *want_server_port;
+ int want_sockaddr_len;
+ int want_dest_sockaddr_len;
+} TEST_BASE;
+
+/* test_smtpd_peer_init - enforce smtpd_peer_init() expectations */
+
+static int test_smtpd_peer_init(const TEST_BASE *tp, VSTREAM *fp,
+ SMTPD_STATE *state)
+{
+ VSTRING *msg_buf = vstring_alloc(100);
+ VSTREAM *memory_stream;
+ int test_passed = 1;
+ int aierr;
+ MAI_HOSTADDR_STR got_addr;
+ MAI_SERVPORT_STR got_port;
+
+ /*
+ * Detonate smtpd_state_init() in a little sandbox.
+ */
+ VSTRING_RESET(msg_buf);
+ VSTRING_TERMINATE(msg_buf);
+ if ((memory_stream = vstream_memopen(msg_buf, O_WRONLY)) == 0)
+ msg_fatal("open memory stream: %m");
+ vstream_swap(VSTREAM_ERR, memory_stream);
+ smtpd_state_init(state, fp, "something");
+ vstream_swap(memory_stream, VSTREAM_ERR);
+ (void) vstream_fclose(memory_stream);
+
+ /* Verify the results. */
+ if (tp->want_hangup != (state->flags & SMTPD_FLAG_HANGUP)) {
+ msg_warn("got hangup flag '0x%x', want '0x%x'",
+ state->flags & SMTPD_FLAG_HANGUP, tp->want_hangup);
+ test_passed = 0;
+ } else if (tp->want_warning == 0 && VSTRING_LEN(msg_buf) > 0) {
+ msg_warn("got warning ``%s'', want ``null''", vstring_str(msg_buf));
+ test_passed = 0;
+ } else if (tp->want_warning != 0) {
+ if (strstr(vstring_str(msg_buf), tp->want_warning) == 0) {
+ msg_warn("got warning ``%s'', want ``%s''",
+ vstring_str(msg_buf), tp->want_warning);
+ test_passed = 0;
+ }
+ }
+ if (test_passed == 0) {
+ /* void */ ;
+ } else if (tp->want_client_name
+ && strcmp(state->name, tp->want_client_name) != 0) {
+ msg_warn("got client name '%s', want '%s'",
+ state->name, tp->want_client_name);
+ test_passed = 0;
+ } else if (tp->want_client_name_status != 0
+ && state->name_status != tp->want_client_name_status) {
+ msg_warn("got client name status '%d', want '%d'",
+ state->name_status, tp->want_client_name_status);
+ test_passed = 0;
+ } else if (tp->want_client_reverse_name
+ && strcmp(state->reverse_name, tp->want_client_reverse_name) != 0) {
+ msg_warn("got client reverse name '%s', want '%s'",
+ state->reverse_name, tp->want_client_reverse_name);
+ test_passed = 0;
+ } else if (tp->want_client_reverse_name_status != 0
+ && state->reverse_name_status != tp->want_client_reverse_name_status) {
+ msg_warn("got client reverse name status '%d', want '%d'",
+ state->reverse_name_status, tp->want_client_reverse_name_status);
+ test_passed = 0;
+ } else if (tp->want_client_addr
+ && strcmp(state->addr, tp->want_client_addr) != 0) {
+ msg_warn("got text client address '%s', want '%s'",
+ state->addr, tp->want_client_addr);
+ test_passed = 0;
+ } if (tp->want_client_rfc_addr
+ && strcmp(tp->want_client_rfc_addr, state->rfc_addr) != 0) {
+ msg_warn("got client rfc_addr '%s', want '%s'",
+ state->rfc_addr, tp->want_client_rfc_addr);
+ test_passed = 0;
+ } else if (tp->want_client_port
+ && strcmp(state->port, tp->want_client_port) != 0) {
+ msg_warn("got text client port '%s', want '%s'",
+ state->port, tp->want_client_port);
+ test_passed = 0;
+ } else if (!state->sockaddr_len != !tp->want_sockaddr_len) {
+ msg_warn("got sockaddr_len '%d', want '%d'",
+ (int) state->sockaddr_len, (int) tp->want_sockaddr_len);
+ test_passed = 0;
+ } else if (tp->want_server_addr
+ && strcmp(state->dest_addr, tp->want_server_addr) != 0) {
+ msg_warn("got text server address '%s', want '%s'",
+ state->dest_addr, tp->want_server_addr);
+ test_passed = 0;
+ } else if (tp->want_server_port
+ && strcmp(state->dest_port, tp->want_server_port) != 0) {
+ msg_warn("got text server port '%s', want '%s'",
+ state->dest_port, tp->want_server_port);
+ test_passed = 0;
+ } else if (!state->dest_sockaddr_len != !tp->want_dest_sockaddr_len) {
+ msg_warn("got dest_sockaddr_len '%d', want '%d'",
+ (int) state->dest_sockaddr_len, (int) tp->want_dest_sockaddr_len);
+ test_passed = 0;
+ } else {
+ if (state->sockaddr_len > 0) {
+ if ((aierr =
+ sockaddr_to_hostaddr((struct sockaddr *) &state->sockaddr,
+ state->sockaddr_len, &got_addr, &got_port, 0)) != 0) {
+ msg_warn("sockaddr_to_hostaddr: %s", MAI_STRERROR(aierr));
+ test_passed = 0;
+ } else if (tp->want_client_addr
+ && strcmp(got_addr.buf, tp->want_client_addr) != 0) {
+ msg_warn("got binary client address '%s', want '%s'",
+ got_addr.buf, tp->want_client_addr);
+ test_passed = 0;
+ } else if (tp->want_client_port
+ && strcmp(got_port.buf, tp->want_client_port) != 0) {
+ msg_warn("got binary client port '%s', want '%s'",
+ got_port.buf, tp->want_client_port);
+ test_passed = 0;
+ }
+ }
+ if (state->dest_sockaddr_len > 0) {
+ if ((aierr =
+ sockaddr_to_hostaddr((struct sockaddr *) &state->dest_sockaddr,
+ state->dest_sockaddr_len, &got_addr, &got_port, 0)) != 0) {
+ msg_warn("sockaddr_to_hostaddr: %s", MAI_STRERROR(aierr));
+ test_passed = 0;
+ } else if (tp->want_server_addr
+ && strcmp(got_addr.buf, tp->want_server_addr) != 0) {
+ msg_warn("got binary server address '%s', want '%s'",
+ got_addr.buf, tp->want_server_addr);
+ test_passed = 0;
+ } else if (tp->want_server_port
+ && strcmp(got_port.buf, tp->want_server_port) != 0) {
+ msg_warn("got binary server port '%s', want '%s'",
+ got_port.buf, tp->want_server_port);
+ test_passed = 0;
+ }
+ }
+ }
+ (void) vstring_free(msg_buf);
+ smtpd_state_reset(state);
+ return (test_passed);
+}
+
+ /*
+ * Tests that a non-socket client results in fake localhost info.
+ */
+typedef struct PEER_FROM_NON_SOCKET_CASE {
+ TEST_BASE base;
+ const char *inet_protocols;
+} PEER_FROM_NON_SOCKET_CASE;
+
+static const PEER_FROM_NON_SOCKET_CASE peer_from_non_socket_cases[] = {
+ {
+ .base = {
+ .label = "prefer_ipv4",
+ .want_client_name = "localhost",
+ .want_client_name_status = SMTPD_PEER_CODE_OK,
+ .want_client_addr = "127.0.0.1",
+ .want_client_addr_family = AF_UNSPEC,
+ .want_client_rfc_addr = "127.0.0.1",
+ .want_client_reverse_name_status = SMTPD_PEER_CODE_OK,
+ .want_client_port = "0",
+ .want_server_addr = "127.0.0.1",
+ .want_server_port = "0",
+ },
+ .inet_protocols = "ipv4",
+ },
+ {
+ .base = {
+ .label = "prefer_ipv6",
+ .want_client_name = "localhost",
+ .want_client_name_status = SMTPD_PEER_CODE_OK,
+ .want_client_reverse_name = "localhost",
+ .want_client_reverse_name_status = SMTPD_PEER_CODE_OK,
+ .want_client_addr = "::1",
+ .want_client_addr_family = AF_UNSPEC,
+ .want_client_rfc_addr = "IPv6:::1",
+ .want_client_port = "0",
+ .want_server_addr = "::1",
+ .want_server_port = "0",
+ },
+ .inet_protocols = "ipv6",
+ },
+ {0},
+};
+
+static void test_peer_from_non_socket(void)
+{
+ int test_passed;
+ const PEER_FROM_NON_SOCKET_CASE *tp;
+
+ reset_global_variables();
+
+ for (tp = peer_from_non_socket_cases; tp->base.label != 0; tp++) {
+ msg_info("RUN test_peer_from_non_socket/%s", tp->base.label);
+ {
+ SMTPD_STATE state;
+ VSTREAM *fp;
+ int pair[2];
+
+ if (pipe(pair) < 0)
+ msg_fatal("pipe: %m");
+ if ((fp = vstream_fdopen(pair[0], O_RDONLY)) == 0)
+ msg_fatal("vstream_fdopen: %m");
+ (void) inet_proto_init("test_peer_from_non_socket",
+ tp->inet_protocols);
+
+ test_passed = test_smtpd_peer_init((TEST_BASE *) tp, fp, &state);
+
+ (void) vstream_fdclose(fp);
+ (void) close(pair[0]);
+ (void) close(pair[1]);
+ }
+ if (test_passed) {
+ msg_info("PASS test_peer_from_non_socket/%s", tp->base.label);
+ tests_passed += 1;
+ } else {
+ msg_info("FAIL test_peer_from_non_socket/%s", tp->base.label);
+ tests_failed += 1;
+ }
+ }
+}
+
+ /*
+ * Tests that a non-connected socket results in 'unknown' endpoint info.
+ */
+typedef struct PEER_FROM_UNCONN_SOCKET_CASE {
+ TEST_BASE base;
+ int proto_family;
+} PEER_FROM_UNCONN_SOCKET_CASE;
+
+static const PEER_FROM_UNCONN_SOCKET_CASE peer_from_unconn_socket_cases[] = {
+ {
+ .base = {
+ .label = "tcp4",
+ .want_client_name = CLIENT_NAME_UNKNOWN,
+ .want_client_name_status = SMTPD_PEER_CODE_PERM,
+ .want_client_addr = CLIENT_ADDR_UNKNOWN,
+ .want_client_addr_family = AF_UNSPEC,
+ .want_client_rfc_addr = CLIENT_ADDR_UNKNOWN,
+ .want_client_reverse_name = CLIENT_NAME_UNKNOWN,
+ .want_client_reverse_name_status = SMTPD_PEER_CODE_PERM,
+ .want_client_port = CLIENT_PORT_UNKNOWN,
+ .want_server_addr = SERVER_ADDR_UNKNOWN,
+ .want_server_port = SERVER_PORT_UNKNOWN,
+ },
+ .proto_family = PF_INET,
+ },
+ {
+ .base = {
+ .label = "tcp6",
+ .want_client_name = CLIENT_NAME_UNKNOWN,
+ .want_client_name_status = SMTPD_PEER_CODE_PERM,
+ .want_client_addr = CLIENT_ADDR_UNKNOWN,
+ .want_client_addr_family = AF_UNSPEC,
+ .want_client_rfc_addr = CLIENT_ADDR_UNKNOWN,
+ .want_client_reverse_name = CLIENT_NAME_UNKNOWN,
+ .want_client_reverse_name_status = SMTPD_PEER_CODE_PERM,
+ .want_client_port = CLIENT_PORT_UNKNOWN,
+ .want_server_addr = SERVER_ADDR_UNKNOWN,
+ .want_server_port = SERVER_PORT_UNKNOWN,
+ },
+ .proto_family = PF_INET6,
+ },
+ {
+ .base = {
+ .label = "unix",
+ .want_client_name = CLIENT_NAME_UNKNOWN,
+ .want_client_name_status = SMTPD_PEER_CODE_PERM,
+ .want_client_addr = CLIENT_ADDR_UNKNOWN,
+ .want_client_addr_family = AF_UNSPEC,
+ .want_client_rfc_addr = CLIENT_ADDR_UNKNOWN,
+ .want_client_reverse_name = CLIENT_NAME_UNKNOWN,
+ .want_client_reverse_name_status = SMTPD_PEER_CODE_PERM,
+ .want_client_port = CLIENT_PORT_UNKNOWN,
+ .want_server_addr = SERVER_ADDR_UNKNOWN,
+ .want_server_port = SERVER_PORT_UNKNOWN,
+ },
+ .proto_family = PF_UNIX,
+ },
+ {0},
+};
+
+static void test_peer_from_unconn_socket(void)
+{
+ int test_passed;
+ const PEER_FROM_UNCONN_SOCKET_CASE *tp;
+
+ reset_global_variables();
+
+ for (tp = peer_from_unconn_socket_cases; tp->base.label != 0; tp++) {
+ msg_info("RUN test_peer_from_unconn_socket/%s", tp->base.label);
+ {
+ SMTPD_STATE state;
+ VSTREAM *fp;
+ int sock;
+
+ if ((sock = socket(tp->proto_family, SOCK_STREAM, 0)) < 0)
+ msg_fatal("socketpair: %m");
+ if ((fp = vstream_fdopen(sock, O_RDONLY)) == 0)
+ msg_fatal("vstream_fdopen: %m");
+
+ test_passed = test_smtpd_peer_init((TEST_BASE *) tp, fp, &state);
+
+ (void) vstream_fclose(fp);
+ }
+ if (test_passed) {
+ msg_info("PASS test_peer_from_unconn_socket/%s", tp->base.label);
+ tests_passed += 1;
+ } else {
+ msg_info("FAIL test_peer_from_unconn_socket/%s", tp->base.label);
+ tests_failed += 1;
+ }
+ }
+}
+
+ /*
+ * Tests that smtpd_peer_from_pass_attr() updates the SMTPD_STATE structure
+ * with the expected error or endpoint information.
+ */
+#define PASS_ATTR_COUNT 5
+struct PASS_ATTR {
+ const char *key;
+ const char *value;
+};
+typedef struct PEER_FROM_PASS_ATTR_CASE {
+ const TEST_BASE base; /* parent class */
+ struct PASS_ATTR attrs[PASS_ATTR_COUNT];
+} PEER_FROM_PASS_ATTR_CASE;
+
+ /*
+ * We need one test for every constraint in smtpd_peer_from_pass(), to
+ * demonstrate that smtpd_pass_attr.c propagates errors and endpoint info.
+ */
+static const PEER_FROM_PASS_ATTR_CASE peer_from_pass_attr_cases[] = {
+ {
+ .base = {
+ .label = "propagates_endpoint_info_from_good_pass_attr",
+ .want_client_addr = "1.2.3.4",
+ .want_client_port = "123",
+ .want_client_addr_family = AF_INET,
+ .want_server_addr = "4.3.2.1",
+ .want_server_port = "321",
+ .want_sockaddr_len = 1,
+ .want_dest_sockaddr_len = 1,
+ },
+ .attrs = {
+ MAIL_ATTR_ACT_CLIENT_ADDR, "1.2.3.4",
+ MAIL_ATTR_ACT_CLIENT_PORT, "123",
+ MAIL_ATTR_ACT_SERVER_ADDR, "4.3.2.1",
+ MAIL_ATTR_ACT_SERVER_PORT, "321",
+ },
+ },
+ {
+ .base = {
+ .label = "propagates_error_from_bad_IPv4_client_addr",
+ .want_hangup = 1,
+ .want_warning = "bad IPv4 client address",
+ /* TODO(wietse) Should we verify the surrogate endpoint info? */
+ },
+ .attrs = {
+ MAIL_ATTR_ACT_CLIENT_ADDR, "1.1.2.3.4",
+ MAIL_ATTR_ACT_CLIENT_PORT, "123",
+ MAIL_ATTR_ACT_SERVER_ADDR, "4.3.2.1",
+ MAIL_ATTR_ACT_SERVER_PORT, "321",
+ },
+ },
+ {
+ .base = {
+ .label = "propagates_error_from_missing_client_addr",
+ .want_hangup = 1,
+ .want_warning = "missing client address",
+ },
+ .attrs = {
+ MAIL_ATTR_ACT_CLIENT_PORT, "123",
+ MAIL_ATTR_ACT_SERVER_ADDR, "4.3.2.1",
+ MAIL_ATTR_ACT_SERVER_PORT, "321",
+ },
+ },
+ {
+ .base = {
+ .label = "propagates_error_from_bad_TCP_client_port",
+ .want_hangup = 1,
+ .want_warning = "bad TCP client port",
+ },
+ .attrs = {
+ MAIL_ATTR_ACT_CLIENT_ADDR, "1.2.3.4",
+ MAIL_ATTR_ACT_CLIENT_PORT, "A23",
+ MAIL_ATTR_ACT_SERVER_ADDR, "4.3.2.1",
+ MAIL_ATTR_ACT_SERVER_PORT, "321",
+ },
+ },
+ {
+ .base = {
+ .label = "propagates_error_from_missing_client_port",
+ .want_hangup = 1,
+ .want_warning = "missing client port",
+ },
+ .attrs = {
+ MAIL_ATTR_ACT_CLIENT_ADDR, "1.2.3.4",
+ MAIL_ATTR_ACT_SERVER_ADDR, "4.3.2.1",
+ MAIL_ATTR_ACT_SERVER_PORT, "321",
+ },
+ },
+ {
+ .base = {
+ .label = "propagates_error_from_bad_IPv6_server_addr",
+ .want_hangup = 1,
+ .want_warning = "bad IPv6 server address",
+ },
+ .attrs = {
+ MAIL_ATTR_ACT_CLIENT_ADDR, "1.2.3.4",
+ MAIL_ATTR_ACT_CLIENT_PORT, "123",
+ MAIL_ATTR_ACT_SERVER_ADDR, ":::4.3.2.1",
+ MAIL_ATTR_ACT_SERVER_PORT, "321",
+ },
+ },
+ {
+ .base = {
+ .label = "propagates_error_from_missing_server_addr",
+ .want_hangup = 1,
+ .want_warning = "missing server address",
+ },
+ .attrs = {
+ MAIL_ATTR_ACT_CLIENT_ADDR, "1.2.3.4",
+ MAIL_ATTR_ACT_CLIENT_PORT, "123",
+ MAIL_ATTR_ACT_SERVER_PORT, "321",
+ },
+ },
+ {
+ .base = {
+ .label = "propagates_error_from_bad_TCP_server_port",
+ .want_hangup = 1,
+ .want_warning = "bad TCP server port",
+ },
+ .attrs = {
+ MAIL_ATTR_ACT_CLIENT_ADDR, "1.2.3.4",
+ MAIL_ATTR_ACT_CLIENT_PORT, "123",
+ MAIL_ATTR_ACT_SERVER_ADDR, "4.3.2.1",
+ MAIL_ATTR_ACT_SERVER_PORT, "A21",
+ },
+ },
+ {
+ .base = {
+ .label = "propagates_error_from_missing_server_port",
+ .want_hangup = 1,
+ .want_warning = "missing server port",
+ },
+ .attrs = {
+ MAIL_ATTR_ACT_CLIENT_ADDR, "1.2.3.4",
+ MAIL_ATTR_ACT_CLIENT_PORT, "123",
+ MAIL_ATTR_ACT_SERVER_ADDR, "4.3.2.1",
+ },
+ },
+ 0,
+};
+
+static void test_peer_from_pass_attr(void)
+{
+ int test_passed;
+ const PEER_FROM_PASS_ATTR_CASE *tp;
+
+ reset_global_variables();
+
+ for (tp = peer_from_pass_attr_cases; tp->base.label != 0; tp++) {
+ msg_info("RUN test_peer_from_pass_attr/%s", tp->base.label);
+ {
+ SMTPD_STATE state;
+ VSTREAM *fp;
+ int sock;
+ HTABLE *attr_table = htable_create(PASS_ATTR_COUNT);
+ const struct PASS_ATTR *p;
+
+ if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+ msg_fatal("socket: %m");
+ if ((fp = vstream_fdopen(sock, O_RDWR)) == 0)
+ msg_fatal("vstream_fdopen: %m");
+ for (p = tp->attrs; p < tp->attrs + PASS_ATTR_COUNT && p->key != 0; p++)
+ htable_enter(attr_table, p->key, (void *) p->value);
+ vstream_control(fp,
+ VSTREAM_CTL_CONTEXT, (void *) attr_table,
+ VSTREAM_CTL_END);
+
+ test_passed = test_smtpd_peer_init((TEST_BASE *) tp, fp, &state);
+
+ htable_free(attr_table, (void (*) (void *)) 0);
+ (void) vstream_fclose(fp);
+ }
+ if (test_passed) {
+ msg_info("PASS test_peer_from_pass_attr/%s", tp->base.label);
+ tests_passed += 1;
+ } else {
+ msg_info("FAIL test_peer_from_pass_attr/%s", tp->base.label);
+ tests_failed += 1;
+ }
+ }
+}
+
+ /*
+ * Tests that smtpd_peer_from_haproxy() updates the SMTPD_STATE structure
+ * with the expected error or endpoint information.
+ */
+typedef struct PEER_FROM_HAPROXY_CASE {
+ const TEST_BASE base;
+ const char *proxy_header;
+} PEER_FROM_HAPROXY_CASE;
+
+ /*
+ * We need only two tests to show that smtpd_haproxy.c propagates errors and
+ * non-error endpoint info. We don't need to duplicate each individual test in
+ * haproxy_srvr_test.c for different IP protocols, HaProxy protocol
+ * versions, and error modes.
+ */
+static const PEER_FROM_HAPROXY_CASE peer_from_haproxy_caes[] = {
+ {
+ .base = {
+ .label = "propagates_endpoint_info_from_good_proxy_header",
+ .want_client_addr = "1.2.3.4",
+ .want_client_port = "123",
+ .want_client_addr_family = AF_INET,
+ .want_server_addr = "4.3.2.1",
+ .want_server_port = "321",
+ .want_sockaddr_len = 1,
+ .want_dest_sockaddr_len = 1,
+ },
+ .proxy_header = "PROXY TCP4 1.2.3.4 4.3.2.1 123 321\n",
+ },
+ {
+ .base = {
+ .label = "propagates_error_from_bad_proxy_header",
+ .want_hangup = 1,
+ .want_warning = "short protocol header",
+ /* TODO(wietse) Should we verify the surrogate endpoint info? */
+ },
+ .proxy_header = "bad",
+ },
+ 0,
+};
+
+static void test_peer_from_haproxy(void)
+{
+ int test_passed;
+ const PEER_FROM_HAPROXY_CASE *tp;
+
+ reset_global_variables();
+ var_smtpd_uproxy_proto = HAPROXY_PROTO_NAME;
+
+ for (tp = peer_from_haproxy_caes; tp->proxy_header != 0; tp++) {
+ msg_info("RUN test_peer_from_haproxy/%s", tp->base.label);
+ {
+ SMTPD_STATE state;
+ VSTREAM *fp;
+ int pair[2];
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) < 0)
+ msg_fatal("socketpair: %m");
+ if (write_buf(pair[1], tp->proxy_header, strlen(tp->proxy_header),
+ TEST_TIMEOUT) < 0)
+ msg_fatal("write_buf: %m");
+ if ((fp = vstream_fdopen(pair[0], O_RDONLY)) == 0)
+ msg_fatal("vstream_fdopen: %m");
+
+ test_passed = test_smtpd_peer_init((TEST_BASE *) tp, fp, &state);
+
+ (void) vstream_fdclose(fp);
+ (void) close(pair[0]);
+ (void) close(pair[1]);
+ }
+ if (test_passed) {
+ msg_info("PASS test_peer_from_haproxy/%s", tp->base.label);
+ tests_passed += 1;
+ } else {
+ msg_info("FAIL test_peer_from_haproxy/%s", tp->base.label);
+ tests_failed += 1;
+ }
+ }
+}
+
+int main(int argc, char **argv)
+{
+ msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
+
+ test_peer_from_non_socket();
+
+ test_peer_from_unconn_socket();
+
+ test_peer_from_pass_attr();
+
+ test_peer_from_haproxy();
+
+ /*
+ * TODO(wietse) tests for a connected socket. This will require mock
+ * get_peername/get/sockname() and getnameinfo/getaddrinfo()
+ * infrastructure.
+ */
+
+ msg_info("PASS=%d FAIL=%d", tests_passed, tests_failed);
+ exit(tests_failed != 0);
+}