2
0
mirror of https://github.com/vdukhovni/postfix synced 2025-09-02 07:05:27 +00:00

postfix-2.7-20090805

This commit is contained in:
Wietse Venema
2009-08-05 00:00:00 -05:00
committed by Viktor Dukhovni
parent d42f0b6b3f
commit 24a96550cb
4 changed files with 165 additions and 69 deletions

View File

@@ -15331,13 +15331,27 @@ Apologies for any names omitted.
feature can be used meaningfully at any protocol stage. feature can be used meaningfully at any protocol stage.
File: proto/postconf.proto. File: proto/postconf.proto.
20090803 20090805
Workaround: with some local DNS servers including BIND, it Bugfix: don't panic when an unexpected smtpd access map is
is possible that A or MX lookups succeed, while NS lookups specified. File: smtpd/smtpd_check.c.
for the same domains time out. Spammers use this to avoid
access restrictions. To deal with future variations of Workaround: NS record lookups for certain domains always
this, check_{client,helo,sender,etc}_{mx,ns,etc}_access no fail, while other queries for those domains always succeed
longer tolerate any lookup failures. Instead, they reply (and even return replies with NS records as additional
with $access_map_defer_code or $access_map_reject_code as information).
appropriate. File: smtpd/smtpd_check.c.
This specific inconsistency would allow spammers to avoid
the Postfix check_{client,helo,sender,etc}_ns_access
restrictions, because those restrictions have effect only
for NS records that can be looked up in the DNS.
To address this specific inconsistency, the Postfix
reject_unknown_helo_hostname, reject_unknown_sender_domain
and reject_unknown_recipient_domain restrictions now require
that a domain has NS records that resolve to at least one
IP address, and they now accept only MX records that resolve
to at least one IP address; those addresses may or may not
be "correct". Postfix has no code to determine whether the
SMTP client name has a resolvable NS or MX record. File:
smtpd/smtpd_check.c.

View File

@@ -14,21 +14,22 @@ specifies the release date of a stable release or snapshot release.
If you upgrade from Postfix 2.5 or earlier, read RELEASE_NOTES-2.6 If you upgrade from Postfix 2.5 or earlier, read RELEASE_NOTES-2.6
before proceeding. before proceeding.
Incompatibility with snapshot 20090803-nonprod Incompatibility with snapshot 20090805
============================================== ======================================
The check_{client,helo,sender,etc}_{mx,ns,etc}_access features no With some domain names, NS record lookups always fail while other
longer tolerate any lookup failures. Instead, they now reply with lookups always succeed (and often return NS records as additional
$access_map_defer_code or $access_map_reject_code as appropriate. information). This anomaly could be used by evil elements to skip
Postfix check_{client,helo,sender,recipient}_ns_access checks,
because those apply only domains with an NS record in the DNS.
The reason for this change is that spammers are using tricks where To address this specific problem, the reject_unknown_helo_hostname,
A or MX lookups succeed while NS lookups for the same domains fail, reject_unknown_sender_domain and reject_unknown_recipient_domain
depending local DNS infrastructure details. The change deals with features now require that a domain name has NS records that resolve
future variants of this anomalous behavior. to at least one IP address, and they now accept only MX records
that resolve to at least one IP address; those addresses may or may
As a side effect, non-existent domain names in HELO commands will not be "correct". Postfix has no code to determine whether the
now trigger a REJECT action with check_helo_{mx,ns}_access, where SMTP client name has a resolvable NS or MX record.
previously such commands were silently permitted.
Incompatibility with snapshot 20090606 Incompatibility with snapshot 20090606
====================================== ======================================

View File

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

View File

@@ -354,6 +354,19 @@ static int unk_addr_tf_act;
static int unv_rcpt_tf_act; static int unv_rcpt_tf_act;
static int unv_from_tf_act; static int unv_from_tf_act;
/*
* What address types to query for when we determine domain existence.
*
* XXX Should we accept mail from a domain that has IPv6 addresses only, when
* the local system has IPv4 only? This can happen when the receiving system
* uses an IPv6-enabled relayhost.
*/
#ifdef T_AAAA
#define CHECK_RR_ADDR_TYPES T_A, T_AAAA
#else
#define CHECK_RR_ADDR_TYPES T_A
#endif
/* /*
* YASLM. * YASLM.
*/ */
@@ -1135,29 +1148,92 @@ static int reject_non_fqdn_hostname(SMTPD_STATE *state, char *name,
return (stat); return (stat);
} }
/* reject_unknown_hostname - fail if name has no A, AAAA or MX record */ /* resolve_server_list - check if at least some name resolves */
static int resolve_server_list(const char *name, int type,
const char *reply_name,
const char *reply_class)
{
const char *myname = "resolve_server_list";
const char *domain = name;
DNS_RR *server_list;
DNS_RR *server;
int dns_status;
int soft_err = 0;
/*
* Require that at least one server record exists.
*/
dns_status = dns_lookup(domain, type, 0, &server_list,
(VSTRING *) 0, (VSTRING *) 0);
if (dns_status == DNS_NOTFOUND && h_errno == NO_DATA) {
if (type == T_MX) {
server_list = dns_rr_create(domain, domain, type, C_IN, 0, 0,
domain, strlen(domain) + 1);
dns_status = DNS_OK;
} else if (type == T_NS) {
while ((domain = strchr(domain, '.')) != 0 && domain[1]) {
domain += 1;
dns_status = dns_lookup(domain, type, 0, &server_list,
(VSTRING *) 0, (VSTRING *) 0);
if (dns_status != DNS_NOTFOUND || h_errno != NO_DATA)
break;
}
}
}
if (dns_status != DNS_OK) {
msg_warn("Unable to look up %s host for %s: %s", dns_strtype(type),
domain && domain[1] ? domain : name, dns_strerror(h_errno));
return (dns_status);
}
/*
* Require that at least one server record resolves to an IP address.
*/
for (server = server_list; server != 0; server = server->next) {
if (msg_verbose)
msg_info("%s: %s hostname check: %s",
myname, dns_strtype(type), (char *) server->data);
if (valid_hostaddr((char *) server->data, DONT_GRIPE)) {
soft_err = 0;
break;
}
dns_status = dns_lookup_l((char *) server->data, 0, (DNS_RR **) 0,
(VSTRING *) 0, (VSTRING *) 0,
DNS_REQ_FLAG_STOP_OK,
CHECK_RR_ADDR_TYPES, 0);
if (dns_status == DNS_OK) {
soft_err = 0;
break;
}
msg_warn("Unable to look up %s host %s for %s %s: %s",
dns_strtype(type), (char *) server->data,
reply_class, reply_name, dns_strerror(h_errno));
if (dns_status == DNS_RETRY)
soft_err = 1;
}
dns_rr_free(server_list);
return (soft_err ? DNS_RETRY : dns_status);
}
/* reject_unknown_hostname - fail if name has no NS, A, AAAA or MX record */
static int reject_unknown_hostname(SMTPD_STATE *state, char *name, static int reject_unknown_hostname(SMTPD_STATE *state, char *name,
char *reply_name, char *reply_class) char *reply_name, char *reply_class)
{ {
const char *myname = "reject_unknown_hostname"; const char *myname = "reject_unknown_hostname";
int dns_status; int dns_status;
DNS_RR *dummy;
if (msg_verbose) if (msg_verbose)
msg_info("%s: %s", myname, name); msg_info("%s: %s", myname, name);
#ifdef T_AAAA /*
#define RR_ADDR_TYPES T_A, T_AAAA * Require that at least one record exists of each type that
#else * check_server_access() wants to check.
#define RR_ADDR_TYPES T_A */
#endif dns_status = resolve_server_list(name, T_MX, reply_name, reply_class);
if (dns_status == DNS_OK)
dns_status = dns_lookup_l(name, 0, &dummy, (VSTRING *) 0, dns_status = resolve_server_list(name, T_NS, reply_name, reply_class);
(VSTRING *) 0, DNS_REQ_FLAG_STOP_OK,
RR_ADDR_TYPES, T_MX, 0);
if (dummy)
dns_rr_free(dummy);
if (dns_status != DNS_OK) { /* incl. DNS_INVAL */ if (dns_status != DNS_OK) { /* incl. DNS_INVAL */
if (dns_status != DNS_RETRY) if (dns_status != DNS_RETRY)
return (smtpd_check_reject(state, MAIL_ERROR_POLICY, return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
@@ -1176,25 +1252,24 @@ static int reject_unknown_hostname(SMTPD_STATE *state, char *name,
return (SMTPD_CHECK_DUNNO); return (SMTPD_CHECK_DUNNO);
} }
/* reject_unknown_mailhost - fail if name has no A, AAAA or MX record */ /* reject_unknown_mailhost - fail if name has no NS, A, AAAA or MX record */
static int reject_unknown_mailhost(SMTPD_STATE *state, const char *name, static int reject_unknown_mailhost(SMTPD_STATE *state, const char *name,
const char *reply_name, const char *reply_class) const char *reply_name, const char *reply_class)
{ {
const char *myname = "reject_unknown_mailhost"; const char *myname = "reject_unknown_mailhost";
int dns_status; int dns_status;
DNS_RR *dummy;
if (msg_verbose) if (msg_verbose)
msg_info("%s: %s", myname, name); msg_info("%s: %s", myname, name);
#define MAILHOST_LOOKUP_FLAGS (DNS_REQ_FLAG_STOP_OK | DNS_REQ_FLAG_STOP_INVAL) /*
* Require that at least one record exists of each type that
dns_status = dns_lookup_l(name, 0, &dummy, (VSTRING *) 0, * check_server_access() wants to check.
(VSTRING *) 0, MAILHOST_LOOKUP_FLAGS, */
T_MX, RR_ADDR_TYPES, 0); dns_status = resolve_server_list(name, T_MX, reply_name, reply_class);
if (dummy) if (dns_status == DNS_OK)
dns_rr_free(dummy); dns_status = resolve_server_list(name, T_NS, reply_name, reply_class);
if (dns_status != DNS_OK) { /* incl. DNS_INVAL */ if (dns_status != DNS_OK) { /* incl. DNS_INVAL */
if (dns_status != DNS_RETRY) if (dns_status != DNS_RETRY)
return (smtpd_check_reject(state, MAIL_ERROR_POLICY, return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
@@ -2315,8 +2390,13 @@ static int check_access(SMTPD_STATE *state, const char *table, const char *name,
if (msg_verbose) if (msg_verbose)
msg_info("%s: %s", myname, name); msg_info("%s: %s", myname, name);
if ((dict = dict_handle(table)) == 0) if ((dict = dict_handle(table)) == 0) {
msg_panic("%s: dictionary not found: %s", myname, table); msg_warn("%s: unexpected dictionary: %s", myname, table);
value = "451 4.3.5 Server configuration error";
CHK_ACCESS_RETURN(check_table_result(state, table, value, name,
reply_name, reply_class,
def_acl), FOUND);
}
if (flags == 0 || (flags & dict->flags) != 0) { if (flags == 0 || (flags & dict->flags) != 0) {
if ((value = dict_get(dict, name)) != 0) if ((value = dict_get(dict, name)) != 0)
CHK_ACCESS_RETURN(check_table_result(state, table, value, name, CHK_ACCESS_RETURN(check_table_result(state, table, value, name,
@@ -2360,8 +2440,13 @@ static int check_domain_access(SMTPD_STATE *state, const char *table,
*/ */
#define CHK_DOMAIN_RETURN(x,y) { *found = y; return(x); } #define CHK_DOMAIN_RETURN(x,y) { *found = y; return(x); }
if ((dict = dict_handle(table)) == 0) if ((dict = dict_handle(table)) == 0) {
msg_panic("%s: dictionary not found: %s", myname, table); msg_warn("%s: unexpected dictionary: %s", myname, table);
value = "451 4.3.5 Server configuration error";
CHK_DOMAIN_RETURN(check_table_result(state, table, value,
domain, reply_name, reply_class,
def_acl), FOUND);
}
for (name = domain; *name != 0; name = next) { for (name = domain; *name != 0; name = next) {
if (flags == 0 || (flags & dict->flags) != 0) { if (flags == 0 || (flags & dict->flags) != 0) {
if ((value = dict_get(dict, name)) != 0) if ((value = dict_get(dict, name)) != 0)
@@ -2419,8 +2504,13 @@ static int check_addr_access(SMTPD_STATE *state, const char *table,
#endif #endif
delim = '.'; delim = '.';
if ((dict = dict_handle(table)) == 0) if ((dict = dict_handle(table)) == 0) {
msg_panic("%s: dictionary not found: %s", myname, table); msg_warn("%s: unexpected dictionary: %s", myname, table);
value = "451 4.3.5 Server configuration error";
CHK_ADDR_RETURN(check_table_result(state, table, value, address,
reply_name, reply_class,
def_acl), FOUND);
}
do { do {
if (flags == 0 || (flags & dict->flags) != 0) { if (flags == 0 || (flags & dict->flags) != 0) {
if ((value = dict_get(dict, addr)) != 0) if ((value = dict_get(dict, addr)) != 0)
@@ -2575,14 +2665,7 @@ static int check_server_access(SMTPD_STATE *state, const char *table,
if (dns_status != DNS_OK) { if (dns_status != DNS_OK) {
msg_warn("Unable to look up %s host for %s: %s", dns_strtype(type), msg_warn("Unable to look up %s host for %s: %s", dns_strtype(type),
domain && domain[1] ? domain : name, dns_strerror(h_errno)); domain && domain[1] ? domain : name, dns_strerror(h_errno));
/* No mercy for DNS failure. */ return (SMTPD_CHECK_DUNNO);
return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
dns_status == DNS_NOTFOUND ?
var_map_reject_code : var_map_defer_code,
smtpd_dsn_fix("4.1.8", reply_class),
"<%s>: %s rejected: %s",
reply_name, reply_class,
"Domain not found"));
} }
/* /*
@@ -2598,6 +2681,13 @@ static int check_server_access(SMTPD_STATE *state, const char *table,
if (msg_verbose) if (msg_verbose)
msg_info("%s: %s hostname check: %s", msg_info("%s: %s hostname check: %s",
myname, dns_strtype(type), (char *) server->data); myname, dns_strtype(type), (char *) server->data);
if (valid_hostaddr((char *) server->data, DONT_GRIPE)) {
if ((status = check_addr_access(state, table, (char *) server->data,
FULL, &found, reply_name, reply_class,
def_acl)) != 0 || found)
CHECK_SERVER_RETURN(status);
continue;
}
if ((status = check_domain_access(state, table, (char *) server->data, if ((status = check_domain_access(state, table, (char *) server->data,
FULL, &found, reply_name, reply_class, FULL, &found, reply_name, reply_class,
def_acl)) != 0 || found) def_acl)) != 0 || found)
@@ -2607,16 +2697,7 @@ static int check_server_access(SMTPD_STATE *state, const char *table,
msg_warn("Unable to look up %s host %s for %s %s: %s", msg_warn("Unable to look up %s host %s for %s %s: %s",
dns_strtype(type), (char *) server->data, dns_strtype(type), (char *) server->data,
reply_class, reply_name, MAI_STRERROR(aierr)); reply_class, reply_name, MAI_STRERROR(aierr));
/* No mercy for DNS failure. */ continue;
status = smtpd_check_reject(state,
MAIL_ERROR_POLICY,
aierr == EAI_NONAME ?
var_map_reject_code : var_map_defer_code,
smtpd_dsn_fix("4.1.8", reply_class),
"<%s>: %s rejected: %s",
reply_name, reply_class,
"Domain not found");
CHECK_SERVER_RETURN(status);
} }
/* Now we must also free the addrinfo result. */ /* Now we must also free the addrinfo result. */
if (msg_verbose) if (msg_verbose)