2
0
mirror of https://github.com/vdukhovni/postfix synced 2025-08-22 09:57:34 +00:00

postfix-3.11-20250418

This commit is contained in:
Wietse Z Venema 2025-04-18 00:00:00 -05:00 committed by Viktor Dukhovni
parent 1b9f8ac2fb
commit 6a87331d09
13 changed files with 951 additions and 155 deletions

5
postfix/.indent.pro vendored
View File

@ -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

View File

@ -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.

View File

@ -115,7 +115,8 @@ manpages:
done </dev/null
# Some checks require a bin/postconf executable.
pre-release-checks: typo-check double-check missing-proxy-read-maps-check \
pre-release-checks: update typo-check double-check \
missing-proxy-read-maps-check \
postlink-check postfix-files-check \
postconf-unimplemented-check postconf-undocumented-check \
check-table-proto check-see-postconf-d-output \

View File

@ -100,8 +100,8 @@ Note 3: Postfix implementations prior to version 2.3 do not 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.
XXCCLLIIEENNTT SSeerrvveerr rreessppoonnssee

View File

@ -145,8 +145,8 @@ xtext encode attribute values. Servers that wish to interoperate
with these older implementations should be prepared to receive
unencoded information. </p>
<p> Note 4: Some Postfix implementations do not implement the PORT
or LOGIN attributes. </p>
<p> Note 4: The PORT attribute is implemented in Postfix 2.5 and
later; the LOGIN attribute in Postfix 2.9 and later. </p>
<h2>XCLIENT Server response</h2>

View File

@ -145,8 +145,8 @@ xtext encode attribute values. Servers that wish to interoperate
with these older implementations should be prepared to receive
unencoded information. </p>
<p> Note 4: Some Postfix implementations do not implement the PORT
or LOGIN attributes. </p>
<p> Note 4: The PORT attribute is implemented in Postfix 2.5 and
later; the LOGIN attribute in Postfix 2.9 and later. </p>
<h2>XCLIENT Server response</h2>

View File

@ -1673,3 +1673,4 @@ bugfix
MLKEM
cleartext
redacted
subclassed

View File

@ -105,3 +105,4 @@ Oemer
Kozmenko
Oleksandr
Bataille
balancers

View File

@ -20,7 +20,7 @@
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
#define MAIL_RELEASE_DATE "20250409"
#define MAIL_RELEASE_DATE "20250418"
#define MAIL_VERSION_NUMBER "3.11"
#ifdef SNAPSHOT

View File

@ -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,6 +53,12 @@ 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
@ -64,7 +70,7 @@ 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

View File

@ -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) {

View File

@ -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);
}

View File

@ -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 <sys_defs.h>
/*
* Utility library.
*/
#include <htable.h>
#include <msg.h>
#include <msg_vstream.h>
#include <stringops.h>
#include <vstream.h>
/*
* Global library.
*/
#include <haproxy_srvr.h>
#include <inet_proto.h>
#include <mail_params.h>
#include <mail_proto.h>
/*
* 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 <smtpd.h>
int tests_failed = 0;
int tests_passed = 0;
/*
* Fakes to satisfy some dependencies.
*/
#include <smtpd_chat.h>
#include <smtpd_sasl_glue.h>
#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);
}