Prereq: "2.4.11"
diff -cr --new-file /var/tmp/postfix-2.4.11/src/global/mail_version.h ./src/global/mail_version.h
*** /var/tmp/postfix-2.4.11/src/global/mail_version.h	Tue May 12 13:11:13 2009
--- ./src/global/mail_version.h	Wed Aug 26 20:40:19 2009
***************
*** 20,27 ****
    * Patches change both the patchlevel and the release date. Snapshots have no
    * patchlevel; they change the release date only.
    */
! #define MAIL_RELEASE_DATE	"20090511"
! #define MAIL_VERSION_NUMBER	"2.4.11"
  
  #ifdef SNAPSHOT
  # define MAIL_VERSION_DATE	"-" MAIL_RELEASE_DATE
--- 20,27 ----
    * Patches change both the patchlevel and the release date. Snapshots have no
    * patchlevel; they change the release date only.
    */
! #define MAIL_RELEASE_DATE	"20090826"
! #define MAIL_VERSION_NUMBER	"2.4.12"
  
  #ifdef SNAPSHOT
  # define MAIL_VERSION_DATE	"-" MAIL_RELEASE_DATE
diff -cr --new-file /var/tmp/postfix-2.4.11/HISTORY ./HISTORY
*** /var/tmp/postfix-2.4.11/HISTORY	Tue May 12 13:10:38 2009
--- ./HISTORY	Tue Aug 25 20:28:13 2009
***************
*** 13674,13676 ****
--- 13674,13720 ----
  
  	Robustness: don't re-enable SIGTERM if it is ignored in the
  	parent. Files: postsuper/postsuper.c, postdrop/postdrop.c.
+ 
+ 20090710
+ 
+ 	Bugfix (introduced Postfix 2.3): Postfix got out of sync
+ 	with a Milter application after the application sent a
+ 	"quarantine" request at end-of-message time. The milter
+ 	application would still be in the end-of-message state,
+ 	while Postfix would already be working on the next SMTP
+ 	event (typically, QUIT or MAIL FROM).  Problem diagnosed
+ 	with help from Alban Deniz. File: milter/milter8.c.
+ 
+ 20090805
+ 
+ 	Bugfix: don't panic when an unexpected smtpd access map is
+ 	specified. File: smtpd/smtpd_check.c.
+ 
+ 20090807
+ 
+ 	Workaround: NS record lookups for certain domains always
+ 	fail, while other queries for those domains always succeed
+ 	(and even return replies with NS records as additional
+ 	information).
+ 
+ 	This inconsistency in DNS lookup results would allow spammers
+ 	to circumvent 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 inconsistency, check_{client,etc}_ns_access
+ 	now require that a known-in-DNS domain name (or parent
+ 	thereof) always resolves to at least one name server IP
+ 	address.
+ 
+ 	For consistency, check_{client,etc}_mx_access now require
+ 	that a known-in-DNS domain name always resolves to at least
+ 	one mail server IP address.
+ 
+ 	These measures merely raise the difficulty level for spammers.
+ 	The IP address information thus obtained is not necessarily
+ 	"correct".  There is little to stop an uncooperative DNS
+ 	server from lying, especially when the owner of the domain
+ 	has no desire to receive email.  File: smtpd/smtpd_check.c.
+ 
+ 	Problem reported by MXTools.com.
diff -cr --new-file /var/tmp/postfix-2.4.11/RELEASE_NOTES ./RELEASE_NOTES
*** /var/tmp/postfix-2.4.11/RELEASE_NOTES	Fri Jul 20 11:27:38 2007
--- ./RELEASE_NOTES	Tue Aug 25 20:24:30 2009
***************
*** 11,16 ****
--- 11,38 ----
  The mail_release_date configuration parameter (format: yyyymmdd)
  specifies the release date of a stable release or snapshot release.
  
+ Incompatibility with Postfix 2.4.12
+ ===================================
+ 
+ With some domain names, NS record lookups always fail while other
+ lookups always succeed (and may even return NS records as additional
+ information).  This anomaly could be used by evil elements to skip
+ Postfix check_{client,helo,sender,recipient}_ns_access checks,
+ because these apply only to NS records that are found in the DNS.
+ 
+ To address this specific problem, check_{client,etc}_ns_access now
+ requires that a known-in-DNS domain name (or parent thereof) always
+ resolves to at least one name server IP address.
+ 
+ For consistency, check_{client,etc}_mx_access now requires that a
+ known-in-DNS domain name always resolves to at least one mail server
+ IP address.
+ 
+ These measures provide no hard assurances that the IP address
+ information thus obtained is correct. There is little to stop an
+ uncooperative DNS server from lying, especially when the owner of
+ the domain has no desire to receive email.
+ 
  Incompatibility with Postfix 2.4.4
  ==================================
  
diff -cr --new-file /var/tmp/postfix-2.4.11/src/milter/milter8.c ./src/milter/milter8.c
*** /var/tmp/postfix-2.4.11/src/milter/milter8.c	Thu Jan 31 20:10:50 2008
--- ./src/milter/milter8.c	Sat Jul 11 20:29:05 2009
***************
*** 1217,1223 ****
  	    /*
  	     * Decision: quarantine. In Sendmail 8.13 this does not imply a
  	     * transition in the receiver state (reply, reject, tempfail,
! 	     * accept, discard).
  	     */
  #ifdef SMFIR_QUARANTINE
  	case SMFIR_QUARANTINE:
--- 1217,1224 ----
  	    /*
  	     * Decision: quarantine. In Sendmail 8.13 this does not imply a
  	     * transition in the receiver state (reply, reject, tempfail,
! 	     * accept, discard). We should not transition, either, otherwise
! 	     * we get out of sync.
  	     */
  #ifdef SMFIR_QUARANTINE
  	case SMFIR_QUARANTINE:
***************
*** 1226,1232 ****
  				  MILTER8_DATA_BUFFER, milter->buf,
  				  MILTER8_DATA_END) != 0)
  		MILTER8_EVENT_BREAK(milter->def_reply);
! 	    MILTER8_EVENT_BREAK("H");
  #endif
  
  	    /*
--- 1227,1234 ----
  				  MILTER8_DATA_BUFFER, milter->buf,
  				  MILTER8_DATA_END) != 0)
  		MILTER8_EVENT_BREAK(milter->def_reply);
! 	    milter8_def_reply(milter, "H");
! 	    continue;
  #endif
  
  	    /*
diff -cr --new-file /var/tmp/postfix-2.4.11/src/smtpd/smtpd_check.c ./src/smtpd/smtpd_check.c
*** /var/tmp/postfix-2.4.11/src/smtpd/smtpd_check.c	Fri May  9 09:36:03 2008
--- ./src/smtpd/smtpd_check.c	Wed Aug 26 20:33:38 2009
***************
*** 2269,2276 ****
      if (msg_verbose)
  	msg_info("%s: %s", myname, name);
  
!     if ((dict = dict_handle(table)) == 0)
! 	msg_panic("%s: dictionary not found: %s", myname, table);
      if (flags == 0 || (flags & dict->flags) != 0) {
  	if ((value = dict_get(dict, name)) != 0)
  	    CHK_ACCESS_RETURN(check_table_result(state, table, value, name,
--- 2269,2281 ----
      if (msg_verbose)
  	msg_info("%s: %s", myname, name);
  
!     if ((dict = dict_handle(table)) == 0) {
! 	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 ((value = dict_get(dict, name)) != 0)
  	    CHK_ACCESS_RETURN(check_table_result(state, table, value, name,
***************
*** 2314,2321 ****
       */
  #define CHK_DOMAIN_RETURN(x,y) { *found = y; return(x); }
  
!     if ((dict = dict_handle(table)) == 0)
! 	msg_panic("%s: dictionary not found: %s", myname, table);
      for (name = domain; *name != 0; name = next) {
  	if (flags == 0 || (flags & dict->flags) != 0) {
  	    if ((value = dict_get(dict, name)) != 0)
--- 2319,2331 ----
       */
  #define CHK_DOMAIN_RETURN(x,y) { *found = y; return(x); }
  
!     if ((dict = dict_handle(table)) == 0) {
! 	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) {
  	if (flags == 0 || (flags & dict->flags) != 0) {
  	    if ((value = dict_get(dict, name)) != 0)
***************
*** 2373,2380 ****
  #endif
  	delim = '.';
  
!     if ((dict = dict_handle(table)) == 0)
! 	msg_panic("%s: dictionary not found: %s", myname, table);
      do {
  	if (flags == 0 || (flags & dict->flags) != 0) {
  	    if ((value = dict_get(dict, addr)) != 0)
--- 2383,2395 ----
  #endif
  	delim = '.';
  
!     if ((dict = dict_handle(table)) == 0) {
! 	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 {
  	if (flags == 0 || (flags & dict->flags) != 0) {
  	    if ((value = dict_get(dict, addr)) != 0)
***************
*** 2454,2459 ****
--- 2469,2478 ----
      struct addrinfo *res;
      int     status;
      INET_PROTO_INFO *proto_info;
+     const char *saved_domain;
+     int     non_err, soft_err;
+     int     known_name_in_dns;
+     int     ping_status;
  
      /*
       * Sanity check.
***************
*** 2508,2522 ****
       * 
       * 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, 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,
--- 2527,2552 ----
       * 
       * If the domain name exists but no NS record exists, look up parent domain
       * NS records.
+      * 
+      * After the initial lookup fails, do one final DNS sanity check. Reject
+      * mail when the name exists, but MX lookup produces no valid response or
+      * NS lookup fails for any reason. Beware, this sanity check provides no
+      * hard assurance. An uncooperative DNS server may lie about everything,
+      * including non-existence.
       */
+ #define SOME_DNS_RR_EXISTS(stat, herr) \
+ 	((stat) == DNS_OK || (stat) == DNS_INVAL || (herr) == NO_DATA)
+ 
+     saved_domain = domain;
      dns_status = dns_lookup(domain, type, 0, &server_list,
  			    (VSTRING *) 0, (VSTRING *) 0);
!     known_name_in_dns = SOME_DNS_RR_EXISTS(dns_status, h_errno);
!     if (dns_status == DNS_NOTFOUND /* Not: 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 && h_errno == NO_DATA) {
  	    while ((domain = strchr(domain, '.')) != 0 && domain[1]) {
  		domain += 1;
  		dns_status = dns_lookup(domain, type, 0, &server_list,
***************
*** 2526,2534 ****
--- 2556,2586 ----
  	    }
  	}
      }
+ 
+ #ifndef VAR_MAP_DEFER_CODE
+ #define var_map_reject_code var_reject_code
+ #define var_map_defer_code var_defer_code
+ #endif
+ 
      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));
+ 	if (known_name_in_dns == 0) {
+ 	    /* With hostile DNS, an address query is more likely to work. */
+ 	    ping_status = dns_lookup_l(saved_domain, 0, (DNS_RR **) 0,
+ 				       (VSTRING *) 0, (VSTRING *) 0,
+ 				       DNS_REQ_FLAG_STOP_OK,
+ 				       RR_ADDR_TYPES, 0);
+ 	    known_name_in_dns = SOME_DNS_RR_EXISTS(ping_status, h_errno);
+ 	}
+ 	if (known_name_in_dns)
+ 	    return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
+ 				       dns_status == DNS_RETRY ?
+ 				   var_map_defer_code : var_map_reject_code,
+ 				       smtpd_dsn_fix("4.1.8", reply_class),
+ 				       "<%s>: %s rejected: %s",
+ 				       reply_name, reply_class,
+ 				       "Domain not found"));
  	return (SMTPD_CHECK_DUNNO);
      }
  
***************
*** 2541,2550 ****
--- 2593,2611 ----
       * Check the hostnames first, then the addresses.
       */
      proto_info = inet_proto_info();
+     non_err = soft_err = 0;
      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)) {
+ 	    non_err = 1;
+ 	    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,
  				      FULL, &found, reply_name, reply_class,
  					  def_acl)) != 0 || found)
***************
*** 2554,2561 ****
--- 2615,2625 ----
  	    msg_warn("Unable to look up %s host %s for %s %s: %s",
  		     dns_strtype(type), (char *) server->data,
  		     reply_class, reply_name, MAI_STRERROR(aierr));
+ 	    if (aierr == EAI_AGAIN || aierr == EAI_SYSTEM)
+ 		soft_err = 1;
  	    continue;
  	}
+ 	non_err = 1;
  	/* Now we must also free the addrinfo result. */
  	if (msg_verbose)
  	    msg_info("%s: %s host address check: %s",
***************
*** 2579,2585 ****
  	}
  	freeaddrinfo(res0);			/* 200412 */
      }
!     CHECK_SERVER_RETURN(SMTPD_CHECK_DUNNO);
  }
  
  /* check_ccert_access - access for TLS clients by certificate fingerprint */
--- 2643,2657 ----
  	}
  	freeaddrinfo(res0);			/* 200412 */
      }
!     status = non_err ? SMTPD_CHECK_DUNNO :
! 	smtpd_check_reject(state, MAIL_ERROR_POLICY,
! 			   soft_err ? var_map_defer_code :
! 			   var_map_reject_code,
! 			   smtpd_dsn_fix("4.1.8", reply_class),
! 			   "<%s>: %s rejected: %s",
! 			   reply_name, reply_class,
! 			   "Domain not found");
!     CHECK_SERVER_RETURN(status);
  }
  
  /* check_ccert_access - access for TLS clients by certificate fingerprint */
