diff options
Diffstat (limited to 'src/smtpd/smtpd_peer.c')
-rw-r--r-- | src/smtpd/smtpd_peer.c | 652 |
1 files changed, 652 insertions, 0 deletions
diff --git a/src/smtpd/smtpd_peer.c b/src/smtpd/smtpd_peer.c new file mode 100644 index 0000000..073310a --- /dev/null +++ b/src/smtpd/smtpd_peer.c @@ -0,0 +1,652 @@ +/*++ +/* NAME +/* smtpd_peer 3 +/* SUMMARY +/* look up peer name/address information +/* SYNOPSIS +/* #include "smtpd.h" +/* +/* void smtpd_peer_init(state) +/* SMTPD_STATE *state; +/* +/* void smtpd_peer_reset(state) +/* SMTPD_STATE *state; +/* DESCRIPTION +/* The smtpd_peer_init() routine attempts to produce a printable +/* version of the peer name and address of the specified socket. +/* Where information is unavailable, the name and/or address +/* are set to "unknown". +/* +/* Alternatively, the peer address and port may be obtained +/* from a proxy server. +/* +/* 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 name +/* The verified client hostname. This name is represented by +/* the string "unknown" when 1) the address->name lookup failed, +/* 2) the name->address mapping fails, or 3) the name->address +/* mapping does not produce the client IP address. +/* .IP reverse_name +/* 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 namaddr +/* String of the form: "name[addr]:port". +/* .IP rfc_addr +/* String of the form "ipv4addr" or "ipv6:ipv6addr" for use +/* in Received: message headers. +/* .IP dest_addr +/* Server address, used by the Dovecot authentication server, +/* available as Milter {daemon_addr} macro, and as server_address +/* policy delegation attribute. +/* .IP dest_port +/* Server port, available as Milter {daemon_port} macro, and +/* as server_port policy delegation attribute. +/* .IP name_status +/* The name_status result field specifies how the name +/* information should be interpreted: +/* .RS +/* .IP 2 +/* The address->name lookup and name->address lookup produced +/* the client IP address. +/* .IP 4 +/* The address->name lookup or name->address lookup failed +/* with a recoverable error. +/* .IP 5 +/* The address->name lookup or name->address lookup failed +/* with an unrecoverable error, or the result did not match +/* the client IP address. +/* .RE +/* .IP reverse_name_status +/* The reverse_name_status result field specifies how the +/* reverse_name information should be interpreted: +/* .RS +/* .IP 2 +/* The address->name lookup succeeded. +/* .IP 4 +/* The address->name lookup failed with a recoverable error. +/* .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(). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdio.h> /* strerror() */ +#include <errno.h> +#include <netdb.h> +#include <string.h> +#include <htable.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <stringops.h> +#include <myaddrinfo.h> +#include <sock_addr.h> +#include <inet_proto.h> +#include <split_at.h> + +/* Global library. */ + +#include <mail_proto.h> +#include <valid_mailhost_addr.h> +#include <mail_params.h> +#include <haproxy_srvr.h> + +/* Application-specific. */ + +#include "smtpd.h" + +static 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. + * + * XXX If support were to be added for Milter applications in down-stream MTAs, + * then consistency demands that we propagate a lot of Sendmail macro + * information via the XFORWARD command. Otherwise we could end up with a + * very confusing situation. + */ + +/* smtpd_peer_sockaddr_to_hostaddr - client address/port to printable form */ + +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; + + /* + * XXX If we're given an IPv6 (or IPv4) connection from, e.g., inetd, + * while Postfix IPv6 (or IPv4) support is turned off, don't (skip to the + * final else clause, pretend the origin is localhost[127.0.0.1], and + * become an open relay). + */ + if (sa->sa_family == AF_INET +#ifdef AF_INET6 + || sa->sa_family == AF_INET6 +#endif + ) { + MAI_HOSTADDR_STR client_addr; + MAI_SERVPORT_STR client_port; + 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) + msg_fatal("cannot handle socket type %s with \"%s = %s\"", +#ifdef AF_INET6 + sa->sa_family == AF_INET6 ? "AF_INET6" : +#endif + sa->sa_family == AF_INET ? "AF_INET" : + "other", VAR_INET_PROTOCOLS, var_inet_protocols); + + /* + * Sorry, but there are some things that we just cannot do while + * connected to the network. + */ + if (geteuid() != var_owner_uid || getuid() != var_owner_uid) { + msg_error("incorrect SMTP server privileges: uid=%lu euid=%lu", + (unsigned long) getuid(), (unsigned long) geteuid()); + msg_fatal("the Postfix SMTP server must run with $%s privileges", + VAR_MAIL_OWNER); + } + + /* + * Convert the client address to printable form. + */ + if ((aierr = sockaddr_to_hostaddr(sa, sa_length, &client_addr, + &client_port, 0)) != 0) + msg_fatal("%s: cannot convert client address/port to string: %s", + myname, MAI_STRERROR(aierr)); + state->port = mystrdup(client_port.buf); + + /* + * XXX Require that the infrastructure strips off the IPv6 datalink + * suffix to avoid false alarms with strict address syntax checks. + */ +#ifdef HAS_IPV6 + 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. + */ +#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, (char *) 0, 0, &res0); + if (aierr) + msg_fatal("%s: cannot convert %s from string to binary: %s", + myname, state->addr, 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 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; + } + } + + /* + * An IPv4 address is in dotted quad decimal form. + */ + else +#endif + { + state->addr = mystrdup(client_addr.buf); + state->rfc_addr = mystrdup(client_addr.buf); + state->addr_family = sa->sa_family; + } + + /* + * 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) + msg_fatal("%s: cannot convert server address/port to string: %s", + myname, MAI_STRERROR(aierr)); + /* TODO: convert IPv4-in-IPv6 to IPv4 form. */ + state->dest_addr = mystrdup(server_addr.buf); + state->dest_port = mystrdup(server_port.buf); + + return (0); + } + + /* + * It's not Internet. + */ + else { + return (-1); + } +} + +/* smtpd_peer_sockaddr_to_hostname - client hostname lookup */ + +static void smtpd_peer_sockaddr_to_hostname(SMTPD_STATE *state) +{ + struct sockaddr *sa = (struct sockaddr *) &(state->sockaddr); + SOCKADDR_SIZE sa_length = state->sockaddr_len; + MAI_HOSTNAME_STR client_name; + int aierr; + + /* + * Look up and sanity check the client hostname. + * + * It is unsafe to allow numeric hostnames, especially because there exists + * pressure to turn off the name->addr double check. In that case an + * attacker could trivally bypass access restrictions. + * + * sockaddr_to_hostname() already rejects malformed or numeric names. + */ +#define TEMP_AI_ERROR(e) \ + ((e) == EAI_AGAIN || (e) == EAI_MEMORY || (e) == EAI_SYSTEM) + +#define REJECT_PEER_NAME(state, code) { \ + myfree(state->name); \ + state->name = mystrdup(CLIENT_NAME_UNKNOWN); \ + state->name_status = code; \ + } + + if (var_smtpd_peername_lookup == 0) { + state->name = mystrdup(CLIENT_NAME_UNKNOWN); + state->reverse_name = mystrdup(CLIENT_NAME_UNKNOWN); + state->name_status = SMTPD_PEER_CODE_PERM; + state->reverse_name_status = SMTPD_PEER_CODE_PERM; + } else if ((aierr = sockaddr_to_hostname(sa, sa_length, &client_name, + (MAI_SERVNAME_STR *) 0, 0)) != 0) { + state->name = mystrdup(CLIENT_NAME_UNKNOWN); + state->reverse_name = mystrdup(CLIENT_NAME_UNKNOWN); + state->name_status = (TEMP_AI_ERROR(aierr) ? + SMTPD_PEER_CODE_TEMP : SMTPD_PEER_CODE_PERM); + state->reverse_name_status = (TEMP_AI_ERROR(aierr) ? + SMTPD_PEER_CODE_TEMP : SMTPD_PEER_CODE_PERM); + } else { + struct addrinfo *res0; + struct addrinfo *res; + + state->name = mystrdup(client_name.buf); + state->reverse_name = mystrdup(client_name.buf); + state->name_status = SMTPD_PEER_CODE_OK; + state->reverse_name_status = SMTPD_PEER_CODE_OK; + + /* + * Reject the hostname if it does not list the peer address. Without + * further validation or qualification, such information must not be + * allowed to enter the audit trail, as people would draw false + * conclusions. + */ + aierr = hostname_to_sockaddr_pf(state->name, state->addr_family, + (char *) 0, 0, &res0); + if (aierr) { + msg_warn("hostname %s does not resolve to address %s: %s", + state->name, state->addr, MAI_STRERROR(aierr)); + REJECT_PEER_NAME(state, (TEMP_AI_ERROR(aierr) ? + SMTPD_PEER_CODE_TEMP : SMTPD_PEER_CODE_FORGED)); + } else { + for (res = res0; /* void */ ; res = res->ai_next) { + if (res == 0) { + msg_warn("hostname %s does not resolve to address %s", + state->name, state->addr); + REJECT_PEER_NAME(state, SMTPD_PEER_CODE_FORGED); + break; + } + if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) { + msg_info("skipping address family %d for host %s", + res->ai_family, state->name); + continue; + } + if (sock_addr_cmp_addr(res->ai_addr, sa) == 0) + break; /* keep peer name */ + } + freeaddrinfo(res0); + } + } +} + +/* smtpd_peer_hostaddr_to_sockaddr - convert numeric string to binary */ + +static void smtpd_peer_hostaddr_to_sockaddr(SMTPD_STATE *state) +{ + const char *myname = "smtpd_peer_hostaddr_to_sockaddr"; + struct addrinfo *res; + int aierr; + + if ((aierr = hostaddr_to_sockaddr(state->addr, state->port, + SOCK_STREAM, &res)) != 0) + msg_fatal("%s: cannot convert client address/port to string: %s", + myname, MAI_STRERROR(aierr)); + if (res->ai_addrlen > sizeof(state->sockaddr)) + msg_panic("%s: address length > struct sockaddr_storage", myname); + memcpy((void *) &(state->sockaddr), res->ai_addr, res->ai_addrlen); + state->sockaddr_len = res->ai_addrlen; + freeaddrinfo(res); +} + +/* smtpd_peer_not_inet - non-socket or non-Internet endpoint */ + +static void smtpd_peer_not_inet(SMTPD_STATE *state) +{ + + /* + * If it's not Internet, assume the client is local, and avoid using the + * naming service because that can hang when the machine is disconnected. + */ + state->name = mystrdup("localhost"); + state->reverse_name = mystrdup("localhost"); +#ifdef AF_INET6 + if (proto_info->sa_family_list[0] == PF_INET6) { + state->addr = mystrdup("::1"); /* XXX bogus. */ + state->rfc_addr = mystrdup(IPV6_COL "::1"); /* XXX bogus. */ + } else +#endif + { + state->addr = mystrdup("127.0.0.1"); /* XXX bogus. */ + state->rfc_addr = mystrdup("127.0.0.1");/* XXX bogus. */ + } + state->addr_family = AF_UNSPEC; + state->name_status = SMTPD_PEER_CODE_OK; + state->reverse_name_status = SMTPD_PEER_CODE_OK; + state->port = mystrdup("0"); /* XXX bogus. */ + + state->dest_addr = mystrdup(state->addr); /* XXX bogus. */ + state->dest_port = mystrdup(state->port); /* XXX bogus. */ +} + +/* smtpd_peer_no_client - peer went away, or peer info unavailable */ + +static void smtpd_peer_no_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); + state->rfc_addr = mystrdup(CLIENT_ADDR_UNKNOWN); + state->addr_family = AF_UNSPEC; + state->name_status = SMTPD_PEER_CODE_PERM; + state->reverse_name_status = SMTPD_PEER_CODE_PERM; + state->port = mystrdup(CLIENT_PORT_UNKNOWN); + + state->dest_addr = mystrdup(SERVER_ADDR_UNKNOWN); + state->dest_port = mystrdup(SERVER_PORT_UNKNOWN); +} + +/* smtpd_peer_from_pass_attr - initialize from attribute hash */ + +static void smtpd_peer_from_pass_attr(SMTPD_STATE *state) +{ + HTABLE *attr = (HTABLE *) vstream_context(state->client); + const char *cp; + + /* + * 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"); + if (strrchr(cp, ':') != 0) { + if (valid_ipv6_hostaddr(cp, DO_GRIPE) == 0) + msg_fatal("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); + 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"); + if (valid_hostport(cp, DO_GRIPE) == 0) + msg_fatal("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"); + if (valid_hostaddr(cp, DO_GRIPE) == 0) + msg_fatal("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"); + if (valid_hostport(cp, DO_GRIPE) == 0) + msg_fatal("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); +} + +/* smtpd_peer_from_default - try to initialize peer information from socket */ + +static 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. + */ + state->sockaddr_len = sizeof(state->sockaddr); + state->dest_sockaddr_len = sizeof(state->dest_sockaddr); + if (getpeername(vstream_fileno(state->client), + (struct sockaddr *) &state->sockaddr, + &state->sockaddr_len) <0 + || getsockname(vstream_fileno(state->client), + (struct sockaddr *) &state->dest_sockaddr, + &state->dest_sockaddr_len) < 0) { + if (errno == ENOTSOCK) + smtpd_peer_not_inet(state); + else + smtpd_peer_no_client(state); + } else { + if (smtpd_peer_sockaddr_to_hostaddr(state) < 0) + smtpd_peer_not_inet(state); + } +} + +/* smtpd_peer_from_proxy - get endpoint info from proxy agent */ + +static void smtpd_peer_from_proxy(SMTPD_STATE *state) +{ + typedef struct { + const char *name; + int (*endpt_lookup) (SMTPD_STATE *); + } SMTPD_ENDPT_LOOKUP_INFO; + static const SMTPD_ENDPT_LOOKUP_INFO smtpd_endpt_lookup_info[] = { + HAPROXY_PROTO_NAME, smtpd_peer_from_haproxy, + 0, + }; + const SMTPD_ENDPT_LOOKUP_INFO *pp; + + /* + * When the proxy information is unavailable, we can't maintain an audit + * trail or enforce access control, therefore we forcibly hang up. + */ + for (pp = smtpd_endpt_lookup_info; /* see below */ ; pp++) { + if (pp->name == 0) + 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_no_client(state); + state->flags |= SMTPD_FLAG_HANGUP; + } else { + smtpd_peer_hostaddr_to_sockaddr(state); + } +} + +/* smtpd_peer_init - initialize peer information */ + +void smtpd_peer_init(SMTPD_STATE *state) +{ + + /* + * Initialize. + */ + if (proto_info == 0) + proto_info = inet_proto_info(); + + /* + * Prepare for partial initialization after error. + */ + memset((void *) &(state->sockaddr), 0, sizeof(state->sockaddr)); + state->sockaddr_len = 0; + state->name = 0; + state->reverse_name = 0; + state->addr = 0; + state->namaddr = 0; + state->rfc_addr = 0; + state->port = 0; + state->dest_addr = 0; + state->dest_port = 0; + + /* + * Determine the remote SMTP client address and port. + * + * 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 (*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); + } + + /* + * Determine the remote SMTP client hostname. Note: some of the handlers + * above provide surrogate endpoint information in case of error. In that + * case, leave the surrogate information alone. + */ + if (state->name == 0) + smtpd_peer_sockaddr_to_hostname(state); + + /* + * Do the name[addr]:port formatting for pretty reports. + */ + state->namaddr = SMTPD_BUILD_NAMADDRPORT(state->name, state->addr, + state->port); +} + +/* smtpd_peer_reset - destroy peer information */ + +void smtpd_peer_reset(SMTPD_STATE *state) +{ + if (state->name) + myfree(state->name); + if (state->reverse_name) + myfree(state->reverse_name); + if (state->addr) + myfree(state->addr); + if (state->namaddr) + myfree(state->namaddr); + if (state->rfc_addr) + myfree(state->rfc_addr); + if (state->port) + myfree(state->port); + if (state->dest_addr) + myfree(state->dest_addr); + if (state->dest_port) + myfree(state->dest_port); +} |