-TINTV
-TINT_TABLE
-TJMP_BUF_WRAPPER
+-TLDAP_CONN
-TLMTP_ATTR
-TLMTP_RESP
-TLMTP_SESSION
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
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
========
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.
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
# 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
# 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
# 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.
<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>
+
<a name="reject_rhsbl_helo">
<dt> <b>reject_rhsbl_helo</b> <i>domain.tld=127.0.0.2</i>
<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
<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>
+
<a name="check_recipient_maps">
<dt> <b>check_recipient_maps</b> <dd> Reject the request
vstring_sprintf(why,
"Name service error for %s: invalid host or domain name",
name);
+ h_errno = HOST_NOT_FOUND;
return (DNS_NOTFOUND);
}
vstring_sprintf(why,
"Name service error for %s: invalid host or domain name",
name);
+ h_errno = HOST_NOT_FOUND;
return (DNS_NOTFOUND);
}
#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 */
* 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;
/*
/* .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.
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.
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,
}
}
+/* 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,
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 != '[')
} 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);
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);
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
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
>>> rcpt rname@rdomain
./smtpd_check: <queue id>: 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=<sname@sdomain> to=<rname@rdomain> proto=SMTP helo=<example.tld>
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: <queue id>: reject: HELO from spike.porcupine.org[168.100.189.2]: 554 <verisign-wildcard.com>: Helo command rejected: Verisign wild-card; from=<sname@sdomain> proto=SMTP helo=<verisign-wildcard.com>
+554 <verisign-wildcard.com>: 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: <queue id>: reject: MAIL from spike.porcupine.org[168.100.189.2]: 554 <foo@verisign-wildcard.com>: Sender address rejected: Verisign wild-card; from=<foo@verisign-wildcard.com> proto=SMTP helo=<example.tld>
+554 <foo@verisign-wildcard.com>: 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: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 <foo@verisign-wildcard.com>: Recipient address rejected: Verisign wild-card; from=<foo@verisign.com> to=<foo@verisign-wildcard.com> proto=SMTP helo=<example.tld>
+554 <foo@verisign-wildcard.com>: 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: <queue id>: reject: HELO from spike.porcupine.org[168.100.189.2]: 554 <email-publisher.com>: Helo command rejected: Access denied; from=<foo@verisign.com> proto=SMTP helo=<email-publisher.com>
+554 <email-publisher.com>: Helo command rejected: Access denied
+>>> helo ns1.topica.com
+./smtpd_check: <queue id>: reject: HELO from spike.porcupine.org[168.100.189.2]: 554 <ns1.topica.com>: Helo command rejected: Access denied; from=<foo@verisign.com> proto=SMTP helo=<ns1.topica.com>
+554 <ns1.topica.com>: 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: <queue id>: reject: MAIL from spike.porcupine.org[168.100.189.2]: 554 <foo@email-publisher.com>: Sender address rejected: Access denied; from=<foo@email-publisher.com> proto=SMTP helo=<example.tld>
+554 <foo@email-publisher.com>: Sender address rejected: Access denied
+>>> mail foo@ns1.topica.com
+./smtpd_check: <queue id>: reject: MAIL from spike.porcupine.org[168.100.189.2]: 554 <foo@ns1.topica.com>: Sender address rejected: Access denied; from=<foo@ns1.topica.com> proto=SMTP helo=<example.tld>
+554 <foo@ns1.topica.com>: 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: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 <foo@email-publisher.com>: Recipient address rejected: Access denied; from=<foo@verisign-wildcard.com> to=<foo@email-publisher.com> proto=SMTP helo=<example.tld>
+554 <foo@email-publisher.com>: Recipient address rejected: Access denied
+>>> rcpt foo@ns1.topica.com
+./smtpd_check: <queue id>: reject: RCPT from spike.porcupine.org[168.100.189.2]: 554 <foo@ns1.topica.com>: Recipient address rejected: Access denied; from=<foo@verisign-wildcard.com> to=<foo@ns1.topica.com> proto=SMTP helo=<example.tld>
+554 <foo@ns1.topica.com>: Recipient address rejected: Access denied
+>>> rcpt foo@verisign-wildcard.com
+OK
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",
#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.
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);
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);
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)
*/
VSTRING *escaped_name = 0,
*filter_buf = 0;
int rc = 0;
+ int sizelimit;
char *sub,
*end;
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);
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.
*/
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.
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);
/*
* 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.
{
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);
}
}
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);
dict_ldap->debuglevel);
#endif
+ /*
+ * Find or allocate shared LDAP connection container.
+ */
+ dict_ldap_conn_find(dict_ldap);
+
/*
* Return the new dict_ldap structure.
*/