--- /dev/null
+This is an unofficial patch for Postfix stable release 2.0 that
+back-ports a new feature from the after-2.0 snapshot releases.
+
+This feature can be used to block mail from so-called spammer
+havens, from sender addresses that resolve to Verisign's wild-card
+mail responder, or from domains that claim to have mail servers in
+reserved networks such as 127.0.0.1.
+
+This patch is a revised version; the first implementation of the
+check_{helo,sender,recipient}_ns_access feature did not correctly
+look up name server records and caused mail to be deferred with a
+450 status code; the second revision no longer defers mail when
+some NS or MX host lookup fails.
+
+-----------------
+
+The check_{helo,sender,recipient}_{ns,mx}_access maptype:mapname
+restriction applies the specified access table to the NS or MX
+hosts of the host/domain given in HELO, EHLO, MAIL FROM or RCPT TO
+commands.
+
+ /etc/postfix/main.cf:
+ smtpd_mumble_restrictions =
+ ...
+ reject_unknown_sender_domain
+ check_sender_mx_access hash:/etc/postfix/mx_access
+ ...
+
+ /etc/postfix/mx_access:
+ spammer.haven.tld reject spammer mx host
+ 64.94.110.11 reject mail server in verisign wild-card domain
+ 127 reject mail server in loopback network
+ 0 reject mail server in broadcast network
+
+The following entries block mail from domains that claim to have
+have mail servers or domain servers in reserved networks.
+
+ 10 reject mail server in RFC 1918 private network
+ 169.254 reject mail server in link local network
+ 172.16 reject mail server in RFC 1918 private network
+ ...similar entries for 172.17...172.31
+ 192.0.2 reject mail server in TEST-NET network
+ 192.168 reject mail server in RFC 1918 private network
+ 224 reject mail server in class D multicast network
+ ...similar entries for 225...239
+ 240 reject mail server in class E reserved network
+ ...similar entries for 241...247
+ 248 reject mail server in reserved network
+ ...similar entries for 249...255
+
+diff -cr /tmp/postfix-2.0.16/conf/sample-smtpd.cf ./conf/sample-smtpd.cf
+*** /tmp/postfix-2.0.16/conf/sample-smtpd.cf Tue Aug 12 12:28:46 2003
+--- ./conf/sample-smtpd.cf Fri Sep 19 11:17:34 2003
+***************
+*** 311,316 ****
+--- 311,321 ----
+ # check_helo_access maptype:mapname
+ # look up HELO hostname or parent domains.
+ # see access(5) for possible lookup results.
++ # check_helo_mx_access maptype:mapname
++ # check_helo_ns_access maptype:mapname
++ # look up the helo hostname MX hosts (or name servers) and apply the
++ # specified access table to those hosts.
++ # Note: the OK result is not allowed here for security reasons.
+ # reject: reject the request. Place this at the end of a restriction.
+ # permit: permit the request. Place this at the end of a restriction.
+ # warn_if_reject: next restriction logs a warning instead of rejecting.
+***************
+*** 343,348 ****
+--- 348,358 ----
+ # check_sender_access maptype:mapname
+ # look up sender address, parent domain, or localpart@.
+ # see access(5) for possible lookup results.
++ # check_sender_mx_access maptype:mapname
++ # check_sender_ns_access maptype:mapname
++ # look up sender address MX hosts (or name servers) and apply the
++ # specified access table to those hosts.
++ # Note: the OK result is not allowed here for security reasons.
+ # reject_sender_login_mismatch: reject if $smtpd_sender_login_maps specifies
+ # a MAIL FROM address owner, but the client is not (SASL) logged in as
+ # that MAIL FROM address owner; or if the client is (SASL) logged in, but
+***************
+*** 409,414 ****
+--- 419,429 ----
+ # check_recipient_access maptype:mapname
+ # look up recipient address, parent domain, or localpart@.
+ # see access(5) for possible lookup results.
++ # check_recipient_mx_access maptype:mapname
++ # check_recipient_ns_access maptype:mapname
++ # look up the recipient address MX hosts (or name servers) and apply the
++ # specified access table to those hosts.
++ # Note: the OK result is not allowed here for security reasons.
+ # reject_non_fqdn_recipient: reject recipient address that is not in FQDN form
+ # reject: reject the request. Place this at the end of a restriction.
+ # permit: permit the request. Place this at the end of a restriction.
+diff -cr /tmp/postfix-2.0.16/html/uce.html ./html/uce.html
+*** /tmp/postfix-2.0.16/html/uce.html Mon Aug 25 09:54:11 2003
+--- ./html/uce.html Fri Sep 19 11:17:34 2003
+***************
+*** 567,572 ****
+--- 567,588 ----
+
+ <p>
+
++ <a name="check_helo_ns_access">
++
++ <dt> <b>check_helo_ns_access</b> <i>maptype</i>:<i>mapname</i>
++
++ <a name="check_helo_mx_access">
++
++ <dt> <b>check_helo_mx_access</b> <i>maptype</i>:<i>mapname</i>
++
++ <dd> Apply the specified <a href="access.5.html">access database</a>
++ to the DNS (or MX) servers for the host or domain name given with
++ the HELO (or EHLO) command.
++
++ <dd> Note: an OK result is not allowed for safety reasons.
++
++ <p>
++
+ <dt> <b><a href="#permit">permit</a></b>
+
+ <dt> <b><a href="#defer">defer</a></b>
+***************
+*** 714,719 ****
+--- 730,751 ----
+
+ <p>
+
++ <a name="check_sender_ns_access">
++
++ <dt> <b>check_sender_ns_access</b> <i>maptype</i>:<i>mapname</i>
++
++ <a name="check_sender_mx_access">
++
++ <dt> <b>check_sender_mx_access</b> <i>maptype</i>:<i>mapname</i>
++
++ <dd> Apply the specified <a href="access.5.html">access database</a>
++ to the DNS (or MX) servers for the host or domain name given with
++ the MAIL FROM command.
++
++ <dd> Note: an OK result is not allowed for safety reasons.
++
++ <p>
++
+ <a name="reject_non_fqdn_sender">
+
+ <dt> <b>reject_non_fqdn_sender</b> <dd> Reject the request when
+***************
+*** 921,926 ****
+--- 953,974 ----
+ <dt> <i>maptype</i>:<i>mapname</i> <dd> Search the named <a
+ href="access.5.html">access database</a> for the resolved destination
+ address, recipient domain or parent domain, or <i>localpart</i>@.
++
++ <p>
++
++ <a name="check_recipient_ns_access">
++
++ <dt> <b>check_recipient_ns_access</b> <i>maptype</i>:<i>mapname</i>
++
++ <a name="check_recipient_mx_access">
++
++ <dt> <b>check_recipient_mx_access</b> <i>maptype</i>:<i>mapname</i>
++
++ <dd> Apply the specified <a href="access.5.html">access database</a>
++ to the DNS servers (or MX hosts) for the host or domain name given
++ with the RCPT TO command.
++
++ <dd> Note: an OK result is not allowed for safety reasons.
+
+ <p>
+
+diff -cr /tmp/postfix-2.0.16/src/dns/dns_lookup.c ./src/dns/dns_lookup.c
+*** /tmp/postfix-2.0.16/src/dns/dns_lookup.c Sun Dec 8 09:09:11 2002
+--- ./src/dns/dns_lookup.c Fri Sep 19 11:17:34 2003
+***************
+*** 509,514 ****
+--- 509,515 ----
+ vstring_sprintf(why,
+ "Name service error for %s: invalid host or domain name",
+ name);
++ h_errno = HOST_NOT_FOUND;
+ return (DNS_NOTFOUND);
+ }
+
+***************
+*** 520,525 ****
+--- 521,527 ----
+ vstring_sprintf(why,
+ "Name service error for %s: invalid host or domain name",
+ name);
++ h_errno = HOST_NOT_FOUND;
+ return (DNS_NOTFOUND);
+ }
+
+diff -cr /tmp/postfix-2.0.16/src/global/mail_params.h ./src/global/mail_params.h
+*** /tmp/postfix-2.0.16/src/global/mail_params.h Mon Mar 3 17:07:03 2003
+--- ./src/global/mail_params.h Fri Sep 19 11:17:35 2003
+***************
+*** 1249,1254 ****
+--- 1249,1261 ----
+ #define CHECK_RECIP_ACL "check_recipient_access"
+ #define CHECK_ETRN_ACL "check_etrn_access"
+
++ #define CHECK_HELO_MX_ACL "check_helo_mx_access"
++ #define CHECK_SENDER_MX_ACL "check_sender_mx_access"
++ #define CHECK_RECIP_MX_ACL "check_recipient_mx_access"
++ #define CHECK_HELO_NS_ACL "check_helo_ns_access"
++ #define CHECK_SENDER_NS_ACL "check_sender_ns_access"
++ #define CHECK_RECIP_NS_ACL "check_recipient_ns_access"
++
+ #define WARN_IF_REJECT "warn_if_reject"
+
+ #define REJECT_RBL "reject_rbl" /* LaMont compatibility */
+diff -cr /tmp/postfix-2.0.16/src/smtpd/smtpd_check.c ./src/smtpd/smtpd_check.c
+*** /tmp/postfix-2.0.16/src/smtpd/smtpd_check.c Tue Aug 12 10:53:25 2003
+--- ./src/smtpd/smtpd_check.c Fri Sep 19 11:17:52 2003
+***************
+*** 86,91 ****
+--- 86,103 ----
+ /* .IP "check_recipient_access maptype:mapname"
+ /* Look up the resolved recipient address in the named access table,
+ /* any parent domains of the recipient domain, and the localpart@.
++ /* .IP "check_helo_mx_access maptype:mapname"
++ /* .IP "check_sender_mx_access maptype:mapname"
++ /* .IP "check_recipient_mx_access maptype:mapname"
++ /* Apply the specified access table to the MX server host name and IP
++ /* addresses for the helo hostname, sender, or recipient, respectively.
++ /* If no MX record is found the A record is used instead.
++ /* .IP "check_helo_ns_access maptype:mapname"
++ /* .IP "check_sender_ns_access maptype:mapname"
++ /* .IP "check_recipient_ns_access maptype:mapname"
++ /* Apply the specified access table to the DNS server host name and IP
++ /* addresses for the helo hostname, sender, or recipient, respectively.
++ /* If no NS record is found, the parent domain is used instead.
+ /* .IP "check_recipient_maps"
+ /* Reject recipients not listed as valid local, virtual or relay
+ /* recipients.
+***************
+*** 455,460 ****
+--- 467,484 ----
+ else \
+ (void) smtpd_check_reject((state), (class), (fmt), (a1), (a2)); \
+ } while (0)
++ #define DEFER_IF_PERMIT3(state, class, fmt, a1, a2, a3) do { \
++ if ((state)->warn_if_reject == 0) \
++ defer_if(&(state)->defer_if_permit, (class), (fmt), (a1), (a2), (a3)); \
++ else \
++ (void) smtpd_check_reject((state), (class), (fmt), (a1), (a2), (a3)); \
++ } while (0)
++ #define DEFER_IF_PERMIT4(state, class, fmt, a1, a2, a3, a4) do { \
++ if ((state)->warn_if_reject == 0) \
++ defer_if(&(state)->defer_if_permit, (class), (fmt), (a1), (a2), (a3), (a4)); \
++ else \
++ (void) smtpd_check_reject((state), (class), (fmt), (a1), (a2), (a3), (a4)); \
++ } while (0)
+
+ /*
+ * Cached RBL lookup state.
+***************
+*** 2005,2010 ****
+--- 2029,2151 ----
+ return (SMTPD_CHECK_DUNNO);
+ }
+
++ /* check_server_access - access control by server host name or address */
++
++ static int check_server_access(SMTPD_STATE *state, const char *table,
++ const char *name,
++ int type,
++ const char *reply_name,
++ const char *reply_class,
++ const char *def_acl)
++ {
++ const char *myname = "check_server_access";
++ const char *domain;
++ int dns_status;
++ DNS_RR *server_list;
++ DNS_RR *server;
++ int found = 0;
++ struct in_addr addr;
++ struct hostent *hp;
++ char *addr_string;
++ int status;
++ char **cpp;
++ static DNS_FIXED fixed;
++
++ /*
++ * Sanity check.
++ */
++ if (type != T_MX && type != T_NS)
++ msg_panic("%s: unexpected resource type \"%s\" in request",
++ myname, dns_strtype(type));
++
++ if (msg_verbose)
++ msg_info("%s: %s %s", myname, dns_strtype(type), name);
++
++ /*
++ * Skip over local-part.
++ */
++ if ((domain = strrchr(name, '@')) != 0)
++ domain += 1;
++ else
++ domain = name;
++
++ /*
++ * If the domain name does not exist then we apply no restriction.
++ *
++ * If the domain name exists but no MX record exists, fabricate an MX record
++ * that points to the domain name itself.
++ *
++ * If the domain name exists but no NS record exists, look up parent domain
++ * NS records.
++ */
++ 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, &fixed, 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", dns_strtype(type),
++ domain && domain[1] ? domain : reply_name);
++ return (SMTPD_CHECK_DUNNO);
++ }
++
++ /*
++ * No bare returns after this point or we have a memory leak.
++ */
++ #define CHECK_SERVER_RETURN(x) { dns_rr_free(server_list); return(x); }
++
++ /*
++ * Check the hostnames first, then the addresses.
++ */
++ for (server = server_list; server != 0; server = server->next) {
++ if ((hp = gethostbyname((char *) server->data)) == 0) {
++ msg_warn("Unable to look up %s host %s for %s %s",
++ dns_strtype(type), (char *) server->data,
++ reply_class, reply_name);
++ continue;
++ }
++ if (hp->h_addrtype != AF_INET || hp->h_length != sizeof(addr)) {
++ if (msg_verbose)
++ msg_warn("address type %d length %d for %s",
++ hp->h_addrtype, hp->h_length, (char *) server->data);
++ continue; /* XXX */
++ }
++ if (msg_verbose)
++ msg_info("%s: %s hostname check: %s",
++ myname, dns_strtype(type), (char *) server->data);
++ if ((status = check_domain_access(state, table, (char *) server->data,
++ FULL, &found, reply_name, reply_class,
++ def_acl)) != 0 || found)
++ CHECK_SERVER_RETURN(status);
++ if (msg_verbose)
++ msg_info("%s: %s host address check: %s",
++ myname, dns_strtype(type), (char *) server->data);
++ for (cpp = hp->h_addr_list; *cpp; cpp++) {
++ memcpy((char *) &addr, *cpp, sizeof(addr));
++ addr_string = mystrdup(inet_ntoa(addr));
++ status = check_addr_access(state, table, addr_string, FULL,
++ &found, reply_name, reply_class,
++ def_acl);
++ myfree(addr_string);
++ if (status != 0 || found)
++ CHECK_SERVER_RETURN(status);
++ }
++ }
++ CHECK_SERVER_RETURN(SMTPD_CHECK_DUNNO);
++ }
++
+ /* check_mail_access - OK/FAIL based on mail address lookup */
+
+ static int check_mail_access(SMTPD_STATE *state, const char *table,
+***************
+*** 2568,2573 ****
+--- 2709,2728 ----
+ }
+ }
+
++ /* forbid_whitelist - disallow whitelisting */
++
++ static void forbid_whitelist(SMTPD_STATE *state, const char *name,
++ int status, const char *target)
++ {
++ if (status == SMTPD_CHECK_OK) {
++ msg_warn("restriction %s returns OK for %s", name, target);
++ msg_warn("this is not allowed for security reasons");
++ msg_warn("use DUNNO instead of OK if you want to make an exception");
++ longjmp(smtpd_check_buf, smtpd_check_reject(state, MAIL_ERROR_SOFTWARE,
++ "451 Server configuration error"));
++ }
++ }
++
+ /* generic_checks - generic restrictions */
+
+ static int generic_checks(SMTPD_STATE *state, ARGV *restrictions,
+***************
+*** 2722,2727 ****
+--- 2877,2896 ----
+ state->helo_name, SMTPD_NAME_HELO)) == 0)
+ status = SMTPD_CHECK_OK;
+ }
++ } else if (is_map_command(state, name, CHECK_HELO_NS_ACL, &cpp)) {
++ if (state->helo_name) {
++ status = check_server_access(state, *cpp, state->helo_name,
++ T_NS, state->helo_name,
++ SMTPD_NAME_HELO, def_acl);
++ forbid_whitelist(state, name, status, state->helo_name);
++ }
++ } else if (is_map_command(state, name, CHECK_HELO_MX_ACL, &cpp)) {
++ if (state->helo_name) {
++ status = check_server_access(state, *cpp, state->helo_name,
++ T_MX, state->helo_name,
++ SMTPD_NAME_HELO, def_acl);
++ forbid_whitelist(state, name, status, state->helo_name);
++ }
+ } else if (strcasecmp(name, REJECT_NON_FQDN_HOSTNAME) == 0) {
+ if (state->helo_name) {
+ if (*state->helo_name != '[')
+***************
+*** 2760,2765 ****
+--- 2929,2948 ----
+ } else if (strcasecmp(name, REJECT_SENDER_LOGIN_MISMATCH) == 0) {
+ if (state->sender && *state->sender)
+ status = reject_sender_login_mismatch(state, state->sender);
++ } else if (is_map_command(state, name, CHECK_SENDER_NS_ACL, &cpp)) {
++ if (state->sender && *state->sender) {
++ status = check_server_access(state, *cpp, state->sender,
++ T_NS, state->sender,
++ SMTPD_NAME_SENDER, def_acl);
++ forbid_whitelist(state, name, status, state->sender);
++ }
++ } else if (is_map_command(state, name, CHECK_SENDER_MX_ACL, &cpp)) {
++ if (state->sender && *state->sender) {
++ status = check_server_access(state, *cpp, state->sender,
++ T_MX, state->sender,
++ SMTPD_NAME_SENDER, def_acl);
++ forbid_whitelist(state, name, status, state->sender);
++ }
+ } else if (strcasecmp(name, REJECT_RHSBL_SENDER) == 0) {
+ if (cpp[1] == 0)
+ msg_warn("restriction %s requires domain name argument", name);
+***************
+*** 2812,2817 ****
+--- 2995,3014 ----
+ if (state->recipient)
+ status = reject_non_fqdn_address(state, state->recipient,
+ state->recipient, SMTPD_NAME_RECIPIENT);
++ } else if (is_map_command(state, name, CHECK_RECIP_NS_ACL, &cpp)) {
++ if (state->recipient && *state->recipient) {
++ status = check_server_access(state, *cpp, state->recipient,
++ T_NS, state->recipient,
++ SMTPD_NAME_RECIPIENT, def_acl);
++ forbid_whitelist(state, name, status, state->recipient);
++ }
++ } else if (is_map_command(state, name, CHECK_RECIP_MX_ACL, &cpp)) {
++ if (state->recipient && *state->recipient) {
++ status = check_server_access(state, *cpp, state->recipient,
++ T_MX, state->recipient,
++ SMTPD_NAME_RECIPIENT, def_acl);
++ forbid_whitelist(state, name, status, state->recipient);
++ }
+ } else if (strcasecmp(name, REJECT_RHSBL_RECIPIENT) == 0) {
+ if (cpp[1] == 0)
+ msg_warn("restriction %s requires domain name argument", name);