diff --git a/postfix/.indent.pro b/postfix/.indent.pro index 35ef0bc9f..d092777b4 100644 --- a/postfix/.indent.pro +++ b/postfix/.indent.pro @@ -77,6 +77,7 @@ -TINTV -TINT_TABLE -TJMP_BUF_WRAPPER +-TLDAP_CONN -TLMTP_ATTR -TLMTP_RESP -TLMTP_SESSION diff --git a/postfix/HISTORY b/postfix/HISTORY index 07311ca92..e3008675f 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -8558,6 +8558,38 @@ Apologies for any names omitted. Cleanup: postcat is now null-byte transparent. File: postcat/postcat.c. +20030916 + + Feature: ``check_{sender,recipient}_mx_access maptype:mapname'' + applies the named Postfix access table to the MX host name + and IP addresses for the sender or recipient address. If + no MX record is found, the A record is used instead. File: + smtpd/smtpd_check.c. + + Experimental feature: ``check_{sender,recipient}_ns_access + maptype:mapname'' applies the named Postfix access table + to the DNS server hostname and IP addresses for the sender + or recipient address. If no NS record is found, the parent + domain is used instead. File: smtpd/smtpd_check.c. + +20030917 + + Feature: ``check_helo_{ns,mx}_access maptype:mapname'', + same semantics as sender and recipient. + + Multiple LDAP lookup tables in the one Postfix process now + share one LDAP connection. Code by Victor Duchovni, Morgan + Stanley. File: util/dict_ldap.c. + + Performance: with prefix_domain specified for an LDAP lookup + table, lookups of @domain are skipped. Code by Victor + Duchovni, Morgan Stanley. File: util/dict_ldap.c. + + Safety: check_mumble_{mx,ns}_access refuses to be used for + whitelisting. The Postfix SMTP server will reject the + request with "451 server configuration error" and will log + a warning explaining why. File: smtpd/smtpd_check.c. + Open problems: High: when virtual aliasing is turned off after content diff --git a/postfix/README_FILES/LDAP_README b/postfix/README_FILES/LDAP_README index e41eb6672..98d176188 100644 --- a/postfix/README_FILES/LDAP_README +++ b/postfix/README_FILES/LDAP_README @@ -423,6 +423,12 @@ NOTES AND THINGS TO THINK ABOUT they won't be a bottleneck, but it's a good idea to know how to tune your directory service. +- Multiple LDAP maps share the same LDAP connection if they differ + only in their query related parameters: base, scope, query_filter, and + so on. To take advantage of this avoid spurious differences in the + definitions of LDAP maps: host selection order, version, bind, tls + parameters, ... should be the same for multiple maps whenever possible. + FEEDBACK ======== @@ -450,9 +456,10 @@ Samuel Tardieu: Noticed that searches could include wildcards, prompting Sami Haahtinen: Referral chasing and v3 support. Victor Duchovni: ldap_bind() timeout. With fixes from LaMont Jones: OpenLDAP cache deprecation. Limits on recursion, expansion - and query results size. + and query results size. LDAP connection sharing for maps + differing only in the query parameters. Liviu Daia: Support for SSL/STARTTLS. Support for storing map definitions in - external files (ldap:/path/ldap.cf) need to securely store + external files (ldap:/path/ldap.cf) needed to securely store passwords for plain auth. And of course Wietse. diff --git a/postfix/RELEASE_NOTES b/postfix/RELEASE_NOTES index 5824fc4ff..65de58f6b 100644 --- a/postfix/RELEASE_NOTES +++ b/postfix/RELEASE_NOTES @@ -22,14 +22,42 @@ snapshot release). Patches change the patchlevel and the release date. Snapshots change only the release date, unless they include the same bugfixes as a patch release. -Incompatible changes with Postfix snapshot 2.0.13-20030914 +Major changes with Postfix snapshot 2.0.16-20030917 +=================================================== + +New check_{helo,sender,recipient}_{ns,mx}_access maptype:mapname +restriction that 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. + +This can be used to block mail from so-called spammer havens, or +from sender addresses that resolve to Verisign's wild-card mail +responder, currently at IP address 64.94.110.11. + + /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 verisign wild-card domain + +Note: OK actions are not allowed for security reasons. Instead of +OK, use DUNNO in order to exclude specific hosts from blacklists. +If an OK result is found for an NS or MX host, Postfix rejects the +SMTP command with "451 Server configuration error". + +Incompatible changes with Postfix snapshot 2.0.16-20030915 ========================================================== In header/body_checks actions, the OK action is being phased out, and the DUNNO action is being phased in. Both actions still work and do the same thing, but hopefully DUNNO causes less confusion. -Major changes with Postfix snapshot 2.0.13-20030914 +Major changes with Postfix snapshot 2.0.16-20030915 =================================================== LDAP parameters can now be defined in external files. Specify the diff --git a/postfix/conf/sample-smtpd.cf b/postfix/conf/sample-smtpd.cf index a3fdb46b0..7e07cec12 100644 --- a/postfix/conf/sample-smtpd.cf +++ b/postfix/conf/sample-smtpd.cf @@ -358,6 +358,11 @@ smtpd_helo_required = no # 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. # check_policy_service transport:endpoint: delegate the decision to # an external policy server. See SMTPD_POLICY_README for details. # reject_rhsbl_helo domain.tld: reject if the helo argument is listed @@ -396,6 +401,11 @@ smtpd_helo_restrictions = # 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 @@ -467,6 +477,11 @@ smtpd_sender_restrictions = # 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 # check_policy_service transport:endpoint: delegate the decision to # an external policy server. See SMTPD_POLICY_README for details. diff --git a/postfix/html/uce.html b/postfix/html/uce.html index f75848d09..4392efa21 100644 --- a/postfix/html/uce.html +++ b/postfix/html/uce.html @@ -599,6 +599,22 @@ or parent domains.

+ + +

check_helo_ns_access maptype:mapname + + + +
check_helo_mx_access maptype:mapname + +
Apply the specified access database +to the DNS (or MX) servers for the host or domain name given with +the HELO (or EHLO) command. + +
Note: an OK result is not allowed for safety reasons. + +

+

reject_rhsbl_helo domain.tld=127.0.0.2 @@ -783,6 +799,22 @@ sender domain and parent domain, or localpart@.

+ + +

check_sender_ns_access maptype:mapname + + + +
check_sender_mx_access maptype:mapname + +
Apply the specified access database +to the DNS (or MX) servers for the host or domain name given with +the MAIL FROM command. + +
Note: an OK result is not allowed for safety reasons. + +

+

reject_non_fqdn_sender
Reject the request when @@ -995,6 +1027,22 @@ address, recipient domain or parent domain, or localpart@.

+ + +

check_recipient_ns_access maptype:mapname + + + +
check_recipient_mx_access maptype:mapname + +
Apply the specified access database +to the DNS servers (or MX hosts) for the host or domain name given +with the RCPT TO command. + +
Note: an OK result is not allowed for safety reasons. + +

+

check_recipient_maps
Reject the request diff --git a/postfix/src/dns/dns_lookup.c b/postfix/src/dns/dns_lookup.c index c35338159..e7816e442 100644 --- a/postfix/src/dns/dns_lookup.c +++ b/postfix/src/dns/dns_lookup.c @@ -509,6 +509,7 @@ int dns_lookup(const char *name, unsigned type, unsigned flags, vstring_sprintf(why, "Name service error for %s: invalid host or domain name", name); + h_errno = HOST_NOT_FOUND; return (DNS_NOTFOUND); } @@ -520,6 +521,7 @@ int dns_lookup(const char *name, unsigned type, unsigned flags, vstring_sprintf(why, "Name service error for %s: invalid host or domain name", name); + h_errno = HOST_NOT_FOUND; return (DNS_NOTFOUND); } diff --git a/postfix/src/global/mail_params.h b/postfix/src/global/mail_params.h index 0d16455bf..b35f5617b 100644 --- a/postfix/src/global/mail_params.h +++ b/postfix/src/global/mail_params.h @@ -1314,6 +1314,13 @@ extern int var_access_map_code; #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 --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index f64592d5b..0b394b79b 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -20,10 +20,10 @@ * Patches change the patchlevel and the release date. Snapshots change the * release date only, unless they include the same bugfix as a patch release. */ -#define MAIL_RELEASE_DATE "20030915" +#define MAIL_RELEASE_DATE "20030917" #define VAR_MAIL_VERSION "mail_version" -#define DEF_MAIL_VERSION "2.0.14-" MAIL_RELEASE_DATE +#define DEF_MAIL_VERSION "2.0.16-" MAIL_RELEASE_DATE extern char *var_mail_version; /* diff --git a/postfix/src/smtpd/smtpd_check.c b/postfix/src/smtpd/smtpd_check.c index 387ffa65d..2f0f0cc20 100644 --- a/postfix/src/smtpd/smtpd_check.c +++ b/postfix/src/smtpd/smtpd_check.c @@ -89,6 +89,18 @@ /* .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. @@ -482,6 +494,12 @@ static void PRINTFLIKE(3, 4) defer_if(SMTPD_DEFER *, int, const char *,...); 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. @@ -2153,6 +2171,127 @@ static int check_namadr_access(SMTPD_STATE *state, const char *table, 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 does not exist then we apply no restriction. In all + * other cases, DNS lookup failure results in a "try again" status. + * + * If the domain name exists but MX lookup fails, fabricate an MX record + * that points to the domain name itself. + * + * If the domain name exists but NS lookup fails, look up the parent domain + * NS record. + */ + dns_status = dns_lookup(domain, type, 0, &server_list, + (VSTRING *) 0, (VSTRING *) 0); + if (dns_status == DNS_NOTFOUND && h_errno != HOST_NOT_FOUND) { + 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 && (domain = strchr(domain, '.')) != 0 + && strchr(++domain, '.') != 0) { + dns_status = dns_lookup(domain, T_NS, 0, &server_list, + (VSTRING *) 0, (VSTRING *) 0); + if (dns_status != DNS_OK) + dns_status = DNS_RETRY; + } + } + if (dns_status == DNS_NOTFOUND) + return (SMTPD_CHECK_DUNNO); + if (dns_status != DNS_OK) { + DEFER_IF_PERMIT3(state, MAIL_ERROR_POLICY, + "450 <%s>: %s rejected: unable to look up %s host", + reply_name, reply_class, dns_strtype(type)); + 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) { + DEFER_IF_PERMIT4(state, MAIL_ERROR_POLICY, + "450 <%s>: %s rejected: " + "Unable to look up %s host %s", + reply_name, reply_class, + dns_strtype(type), (char *) server->data); + CHECK_SERVER_RETURN(SMTPD_CHECK_DUNNO); + } + 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, @@ -2818,6 +2957,20 @@ static int is_map_command(SMTPD_STATE *state, const char *name, } } +/* 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, @@ -2979,6 +3132,20 @@ static int generic_checks(SMTPD_STATE *state, ARGV *restrictions, 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 != '[') @@ -3032,6 +3199,20 @@ static int generic_checks(SMTPD_STATE *state, ARGV *restrictions, } 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); @@ -3084,6 +3265,20 @@ static int generic_checks(SMTPD_STATE *state, ARGV *restrictions, 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); diff --git a/postfix/src/smtpd/smtpd_check_access b/postfix/src/smtpd/smtpd_check_access index bfcb3d35f..1ee24aa48 100644 --- a/postfix/src/smtpd/smtpd_check_access +++ b/postfix/src/smtpd/smtpd_check_access @@ -56,3 +56,5 @@ holdtext@hold.domain hold text discard@hold.domain discard discardtext@hold.domain discard text dunnotext@dunno.domain dunno text +64.94.110.11 reject Verisign wild-card +topica.com reject diff --git a/postfix/src/smtpd/smtpd_exp.in b/postfix/src/smtpd/smtpd_exp.in index 8c05d1069..71b188ef3 100644 --- a/postfix/src/smtpd/smtpd_exp.in +++ b/postfix/src/smtpd/smtpd_exp.in @@ -60,3 +60,32 @@ recipient_restrictions reject_rhsbl_helo,abuse.rfc-ignorant.org helo example.tld mail sname@sdomain rcpt rname@rdomain +# +# Check MX access +# +helo_restrictions check_helo_mx_access,hash:smtpd_check_access +helo verisign-wildcard.com +helo verisign.com +helo example.tld +sender_restrictions check_sender_mx_access,hash:smtpd_check_access +mail foo@verisign-wildcard.com +mail foo@verisign.com +recipient_restrictions check_recipient_mx_access,hash:smtpd_check_access +rcpt foo@verisign-wildcard.com +rcpt foo@verisign.com +# +# Check NS access +# +helo_restrictions check_helo_ns_access,hash:smtpd_check_access +helo email-publisher.com +helo ns1.topica.com +helo verisign-wildcard.com +helo example.tld +sender_restrictions check_sender_ns_access,hash:smtpd_check_access +mail foo@email-publisher.com +mail foo@ns1.topica.com +mail foo@verisign-wildcard.com +recipient_restrictions check_recipient_ns_access,hash:smtpd_check_access +rcpt foo@email-publisher.com +rcpt foo@ns1.topica.com +rcpt foo@verisign-wildcard.com diff --git a/postfix/src/smtpd/smtpd_exp.ref b/postfix/src/smtpd/smtpd_exp.ref index 950349874..2495de5f0 100644 --- a/postfix/src/smtpd/smtpd_exp.ref +++ b/postfix/src/smtpd/smtpd_exp.ref @@ -109,3 +109,64 @@ OK >>> rcpt rname@rdomain ./smtpd_check: : reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 Service unavailable; Helo command [example.tld] blocked using abuse.rfc-ignorant.org; Not supporting abuse@domain; from= to= proto=SMTP helo= 554 Service unavailable; Helo command [example.tld] blocked using abuse.rfc-ignorant.org; Not supporting abuse@domain +>>> # +>>> # Check MX access +>>> # +>>> helo_restrictions check_helo_mx_access,hash:smtpd_check_access +OK +>>> helo verisign-wildcard.com +./smtpd_check: : reject: HELO from spike.porcupine.org[168.100.189.2]: 554 : Helo command rejected: Verisign wild-card; from= proto=SMTP helo= +554 : Helo command rejected: Verisign wild-card +>>> helo verisign.com +OK +>>> helo example.tld +OK +>>> sender_restrictions check_sender_mx_access,hash:smtpd_check_access +OK +>>> mail foo@verisign-wildcard.com +./smtpd_check: : reject: MAIL from spike.porcupine.org[168.100.189.2]: 554 : Sender address rejected: Verisign wild-card; from= proto=SMTP helo= +554 : Sender address rejected: Verisign wild-card +>>> mail foo@verisign.com +OK +>>> recipient_restrictions check_recipient_mx_access,hash:smtpd_check_access +OK +>>> rcpt foo@verisign-wildcard.com +./smtpd_check: : reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 : Recipient address rejected: Verisign wild-card; from= to= proto=SMTP helo= +554 : Recipient address rejected: Verisign wild-card +>>> rcpt foo@verisign.com +OK +>>> # +>>> # Check NS access +>>> # +>>> helo_restrictions check_helo_ns_access,hash:smtpd_check_access +OK +>>> helo email-publisher.com +./smtpd_check: : reject: HELO from spike.porcupine.org[168.100.189.2]: 554 : Helo command rejected: Access denied; from= proto=SMTP helo= +554 : Helo command rejected: Access denied +>>> helo ns1.topica.com +./smtpd_check: : reject: HELO from spike.porcupine.org[168.100.189.2]: 554 : Helo command rejected: Access denied; from= proto=SMTP helo= +554 : Helo command rejected: Access denied +>>> helo verisign-wildcard.com +OK +>>> helo example.tld +OK +>>> sender_restrictions check_sender_ns_access,hash:smtpd_check_access +OK +>>> mail foo@email-publisher.com +./smtpd_check: : reject: MAIL from spike.porcupine.org[168.100.189.2]: 554 : Sender address rejected: Access denied; from= proto=SMTP helo= +554 : Sender address rejected: Access denied +>>> mail foo@ns1.topica.com +./smtpd_check: : reject: MAIL from spike.porcupine.org[168.100.189.2]: 554 : Sender address rejected: Access denied; from= proto=SMTP helo= +554 : Sender address rejected: Access denied +>>> mail foo@verisign-wildcard.com +OK +>>> recipient_restrictions check_recipient_ns_access,hash:smtpd_check_access +OK +>>> rcpt foo@email-publisher.com +./smtpd_check: : reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 : Recipient address rejected: Access denied; from= to= proto=SMTP helo= +554 : Recipient address rejected: Access denied +>>> rcpt foo@ns1.topica.com +./smtpd_check: : reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 : Recipient address rejected: Access denied; from= to= proto=SMTP helo= +554 : Recipient address rejected: Access denied +>>> rcpt foo@verisign-wildcard.com +OK diff --git a/postfix/src/trivial-rewrite/resolve.c b/postfix/src/trivial-rewrite/resolve.c index 0ea2b3698..90c3d7850 100644 --- a/postfix/src/trivial-rewrite/resolve.c +++ b/postfix/src/trivial-rewrite/resolve.c @@ -409,9 +409,11 @@ static void resolve_addr(RES_CONTEXT *rp, char *addr, msg_warn("do not list domain %s in BOTH %s and %s", rcpt_domain, VAR_VIRT_ALIAS_DOMS, VAR_RELAY_DOMAINS); +#if 0 if (strcasecmp(rcpt_domain, var_myorigin) == 0) msg_warn("do not list $%s (%s) in %s", VAR_MYORIGIN, var_myorigin, VAR_VIRT_ALIAS_DOMS); +#endif } vstring_strcpy(channel, MAIL_SERVICE_ERROR); vstring_sprintf(nexthop, "User unknown%s", diff --git a/postfix/src/util/dict_ldap.c b/postfix/src/util/dict_ldap.c index 1523c3397..e1c4bca74 100644 --- a/postfix/src/util/dict_ldap.c +++ b/postfix/src/util/dict_ldap.c @@ -180,11 +180,17 @@ #include "dict.h" #include "dict_ldap.h" #include "stringops.h" +#include "binhash.h" /* AAARGH!! */ #include "../global/mail_conf.h" +typedef struct { + LDAP *conn_ld; + int conn_refcount; +} LDAP_CONN; + /* * Structure containing all the configuration parameters for a given * LDAP source, plus its connection handle. @@ -223,9 +229,14 @@ typedef struct { char *tls_random_file; char *tls_cipher_suite; #endif - LDAP *ld; + BINHASH_INFO *ht; /* hash entry for LDAP connection */ + LDAP *ld; /* duplicated from conn->conn_ld */ } DICT_LDAP; +#define DICT_LDAP_CONN(d) ((LDAP_CONN *)((d)->ht->value)) + +static BINHASH *conn_hash = 0; + typedef struct { char *(*get_str) (const char *, const char *, const char *, int, int); int (*get_int) (const char *, const char *, int, int, int); @@ -641,6 +652,9 @@ static int dict_ldap_connect(DICT_LDAP *dict_ldap) msg_info("%s: Successful bind to server %s as %s ", myname, dict_ldap->server_host, dict_ldap->bind_dn); } + /* Save connection handle in shared container */ + DICT_LDAP_CONN(dict_ldap)->conn_ld = dict_ldap->ld; + if (msg_verbose) msg_info("%s: Cached connection handle for LDAP source %s", myname, dict_ldap->ldapsource); @@ -648,6 +662,58 @@ static int dict_ldap_connect(DICT_LDAP *dict_ldap) return (0); } +/* + * Locate or allocate connection cache entry. + */ +static void dict_ldap_conn_find(DICT_LDAP *dict_ldap) +{ + VSTRING *keybuf = vstring_alloc(10); + char *key; + int len; + int sslon = dict_ldap->start_tls || dict_ldap->ldap_ssl; + LDAP_CONN *conn; + +#define ADDSTR(vp, s) vstring_memcat((vp), (s), strlen((s))+1) +#define ADDINT(vp, i) vstring_sprintf_append((vp), "%lu", (unsigned long)(i)) + + ADDSTR(keybuf, dict_ldap->server_host); + ADDINT(keybuf, dict_ldap->server_port); + ADDINT(keybuf, dict_ldap->bind); + ADDSTR(keybuf, dict_ldap->bind ? dict_ldap->bind_dn : ""); + ADDSTR(keybuf, dict_ldap->bind ? dict_ldap->bind_pw : ""); + ADDINT(keybuf, dict_ldap->dereference); + ADDINT(keybuf, dict_ldap->chase_referrals); + ADDINT(keybuf, dict_ldap->debuglevel); + ADDINT(keybuf, dict_ldap->version); +#ifdef LDAP_API_FEATURE_X_OPENLDAP + ADDINT(keybuf, dict_ldap->ldap_ssl); + ADDINT(keybuf, dict_ldap->start_tls); + ADDINT(keybuf, sslon ? dict_ldap->tls_require_cert : 0); + ADDSTR(keybuf, sslon ? dict_ldap->tls_ca_cert_file : ""); + ADDSTR(keybuf, sslon ? dict_ldap->tls_ca_cert_dir : ""); + ADDSTR(keybuf, sslon ? dict_ldap->tls_cert : ""); + ADDSTR(keybuf, sslon ? dict_ldap->tls_key : ""); + ADDSTR(keybuf, sslon ? dict_ldap->tls_random_file : ""); + ADDSTR(keybuf, sslon ? dict_ldap->tls_cipher_suite : ""); +#endif + + key = vstring_str(keybuf); + len = VSTRING_LEN(keybuf); + + if (conn_hash == 0) + conn_hash = binhash_create(0); + + if ((dict_ldap->ht = binhash_locate(conn_hash, key, len)) == 0) { + conn = (LDAP_CONN *) mymalloc(sizeof(LDAP_CONN)); + conn->conn_ld = 0; + conn->conn_refcount = 0; + dict_ldap->ht = binhash_enter(conn_hash, key, len, (char *) conn); + } + ++DICT_LDAP_CONN(dict_ldap)->conn_refcount; + + vstring_free(keybuf); +} + /* * expand a filter (lookup or result) */ @@ -907,6 +973,7 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) VSTRING *escaped_name = 0, *filter_buf = 0; int rc = 0; + int sizelimit; char *sub, *end; @@ -923,11 +990,8 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) if (dict_ldap->domain) { const char *p = strrchr(name, '@'); - if (p != 0) - p = p + 1; - else - p = name; - if (match_list_match(dict_ldap->domain, p) == 0) { + if (p == 0 || p == name || + match_list_match(dict_ldap->domain, ++p) == 0) { if (msg_verbose) msg_info("%s: domain of %s not found in domain list", myname, name); @@ -942,6 +1006,13 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) result = vstring_alloc(2); vstring_strcpy(result, ""); + /* + * Because the connection may be shared and invalidated via queries for + * another map, update private copy of "ld" from shared connection + * container. + */ + dict_ldap->ld = DICT_LDAP_CONN(dict_ldap)->conn_ld; + /* * Connect to the LDAP server, if necessary. */ @@ -962,6 +1033,18 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) msg_info("%s: Using existing connection for LDAP source %s", myname, dict_ldap->ldapsource); + /* + * Connection caching, means that the connection handle may have the + * wrong size limit. Re-adjust before each query. This is cheap, just + * sets a field in the ldap connection handle. We also do this in the + * connect code, because we sometimes reconnect (below) in the middle of + * a query. + */ + sizelimit = dict_ldap->size_limit ? dict_ldap->size_limit : LDAP_NO_LIMIT; + if (ldap_set_option(dict_ldap->ld, LDAP_OPT_SIZELIMIT, &sizelimit) + != LDAP_OPT_SUCCESS) + msg_warn("%s: %s: Unable to set query result size limit to %ld.", + myname, dict_ldap->ldapsource, dict_ldap->size_limit); /* * Prepare the query. @@ -1046,7 +1129,7 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) myname, dict_ldap->ldapsource); ldap_unbind(dict_ldap->ld); - dict_ldap->ld = NULL; + dict_ldap->ld = DICT_LDAP_CONN(dict_ldap)->conn_ld = 0; dict_ldap_connect(dict_ldap); /* @@ -1098,7 +1181,7 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) * next lookup. */ ldap_unbind(dict_ldap->ld); - dict_ldap->ld = NULL; + dict_ldap->ld = DICT_LDAP_CONN(dict_ldap)->conn_ld = 0; /* * And tell the caller to try again later. @@ -1129,10 +1212,18 @@ static void dict_ldap_close(DICT *dict) { char *myname = "dict_ldap_close"; DICT_LDAP *dict_ldap = (DICT_LDAP *) dict; + LDAP_CONN *conn = DICT_LDAP_CONN(dict_ldap); + BINHASH_INFO *ht = dict_ldap->ht; - if (dict_ldap->ld) - ldap_unbind(dict_ldap->ld); - + if (--conn->conn_refcount == 0) { + if (conn->conn_ld) { + if (msg_verbose) + msg_info("%s: Closed connection handle for LDAP source %s", + myname, dict_ldap->ldapsource); + ldap_unbind(conn->conn_ld); + } + binhash_delete(conn_hash, ht->key, ht->key_len, myfree); + } myfree(dict_ldap->ldapsource); myfree(dict_ldap->server_host); myfree(dict_ldap->search_base); @@ -1272,6 +1363,11 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags) } } dict_ldap->server_host = mystrdup(vstring_str(url_list) + 1); + + /* + * With URL scheme, clear port to normalize connection cache key + */ + dict_ldap->server_port = 0; if (msg_verbose) msg_info("%s: %s server_host URL is %s", myname, ldapsource, dict_ldap->server_host); @@ -1534,6 +1630,11 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags) dict_ldap->debuglevel); #endif + /* + * Find or allocate shared LDAP connection container. + */ + dict_ldap_conn_find(dict_ldap); + /* * Return the new dict_ldap structure. */