-/* $Cambridge: exim/src/src/lookups/ldap.c,v 1.1 2004/10/07 13:10:01 ph10 Exp $ */
-
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* Copyright (c) University of Cambridge 1995 - 2012 */
/* See the file NOTICE for conditions of use and distribution. */
/* Many thanks to Stuart Lynne for contributing the original code for this
#include "../exim.h"
#include "lf_functions.h"
-#include "ldap.h"
-
-/* We can't just compile this code and allow the library mechanism to omit the
-functions if they are not wanted, because we need to have the LDAP headers
-available for compiling. Therefore, compile these functions only if LOOKUP_LDAP
-is defined. However, some compilers don't like compiling empty modules, so keep
-them happy with a dummy when skipping the rest. Make it reference itself to
-stop picky compilers complaining that it is unused, and put in a dummy argument
-to stop even pickier compilers complaining about infinite loops. */
-#ifndef LOOKUP_LDAP
-static void dummy(int x) { dummy(x-1); }
-#else
+/* Include LDAP headers. The code below uses some "old" LDAP interfaces that
+are deprecated in OpenLDAP. I don't know their status in other LDAP
+implementations. LDAP_DEPRECATED causes their prototypes to be defined in
+ldap.h. */
-
-/* Include LDAP headers */
+#define LDAP_DEPRECATED 1
#include <lber.h>
#include <ldap.h>
#endif
-/* For libraries without TCP connect timeouts */
-
-#ifndef LDAP_X_IO_TIMEOUT_NO_TIMEOUT
-#define LDAP_X_IO_TIMEOUT_NO_TIMEOUT (-1)
-#endif
-
-
/* Four types of LDAP search are implemented */
#define SEARCH_LDAP_MULTIPLE 0 /* Get attributes from multiple entries */
password password for authentication, or NULL
sizelimit max number of entries returned, or 0 for no limit
timelimit max time to wait, or 0 for no limit
- tcplimit max time to connect, or NULL for OS default
+ tcplimit max time for network activity, e.g. connect, or 0 for OS default
deference the dereference option, which is one of
LDAP_DEREF_{NEVER,SEARCHING,FINDING,ALWAYS}
+ referrals the referral option, which is LDAP_OPT_ON or LDAP_OPT_OFF
Returns: OK or FAIL or DEFER
FAIL is given only if a lookup was performed successfully, but
static int
perform_ldap_search(uschar *ldap_url, uschar *server, int s_port, int search_type,
uschar **res, uschar **errmsg, BOOL *defer_break, uschar *user, uschar *password,
- int sizelimit, int timelimit, int tcplimit, int dereference)
+ int sizelimit, int timelimit, int tcplimit, int dereference, void *referrals)
{
LDAPURLDesc *ludp = NULL;
LDAPMessage *result = NULL;
int attr_count = 0;
int error_yield = DEFER;
int msgid;
-int rc;
+int rc, ldap_rc, ldap_parse_rc;
int port;
int ptr = 0;
int rescount = 0;
if (ldapi || port == lcp->port) break;
}
+/* Use this network timeout in any requests. */
+
+if (tcplimit > 0)
+ {
+ timeout.tv_sec = tcplimit;
+ timeout.tv_usec = 0;
+ timeoutptr = &timeout;
+ }
+
/* If no cached connection found, we must open a connection to the server. If
the server name is actually an absolute path, we set ldapi=TRUE above. This
requests connection via a Unix socket. However, as far as I know, only OpenLDAP
in Netscape SDK v4.1; I don't know about other libraries. */
#ifdef LDAP_X_OPT_CONNECT_TIMEOUT
- ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT, (void *)&tcplimit);
+ if (tcplimit > 0)
+ {
+ int timeout1000 = tcplimit*1000;
+ ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT, (void *)&timeout1000);
+ }
+ else
+ {
+ int notimeout = LDAP_X_IO_TIMEOUT_NO_TIMEOUT;
+ ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT, (void *)¬imeout);
+ }
+ #endif
+
+ /* Set the TCP connect timeout. This works with OpenLDAP 2.2.14. */
+
+ #ifdef LDAP_OPT_NETWORK_TIMEOUT
+ if (tcplimit > 0)
+ ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, (void *)timeoutptr);
#endif
/* I could not get TLS to work until I set the version to 3. That version
}
#endif /* LDAP_OPT_X_TLS */
+ #ifdef LDAP_OPT_X_TLS_CACERTFILE
+ if (eldap_ca_cert_file != NULL)
+ {
+ ldap_set_option(ld, LDAP_OPT_X_TLS_CACERTFILE, eldap_ca_cert_file);
+ }
+ #endif
+ #ifdef LDAP_OPT_X_TLS_CACERTDIR
+ if (eldap_ca_cert_dir != NULL)
+ {
+ ldap_set_option(ld, LDAP_OPT_X_TLS_CACERTDIR, eldap_ca_cert_dir);
+ }
+ #endif
+ #ifdef LDAP_OPT_X_TLS_CERTFILE
+ if (eldap_cert_file != NULL)
+ {
+ ldap_set_option(ld, LDAP_OPT_X_TLS_CERTFILE, eldap_cert_file);
+ }
+ #endif
+ #ifdef LDAP_OPT_X_TLS_KEYFILE
+ if (eldap_cert_key != NULL)
+ {
+ ldap_set_option(ld, LDAP_OPT_X_TLS_KEYFILE, eldap_cert_key);
+ }
+ #endif
+ #ifdef LDAP_OPT_X_TLS_CIPHER_SUITE
+ if (eldap_cipher_suite != NULL)
+ {
+ ldap_set_option(ld, LDAP_OPT_X_TLS_CIPHER_SUITE, eldap_cipher_suite);
+ }
+ #endif
+ #ifdef LDAP_OPT_X_TLS_REQUIRE_CERT
+ if (eldap_require_cert != NULL)
+ {
+ int cert_option = LDAP_OPT_X_TLS_NEVER;
+ if (Ustrcmp(eldap_require_cert, "hard") == 0)
+ {
+ cert_option = LDAP_OPT_X_TLS_HARD;
+ }
+ else if (Ustrcmp(eldap_require_cert, "demand") == 0)
+ {
+ cert_option = LDAP_OPT_X_TLS_DEMAND;
+ }
+ else if (Ustrcmp(eldap_require_cert, "allow") == 0)
+ {
+ cert_option = LDAP_OPT_X_TLS_ALLOW;
+ }
+ else if (Ustrcmp(eldap_require_cert, "try") == 0)
+ {
+ cert_option = LDAP_OPT_X_TLS_TRY;
+ }
+ ldap_set_option(ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &cert_option);
+ }
+ #endif
+
/* Now add this connection to the chain of cached connections */
lcp = store_get(sizeof(LDAP_CONNECTION));
{
DEBUG(D_lookup) debug_printf("%sbinding with user=%s password=%s\n",
(lcp->bound)? "re-" : "", user, password);
- if ((rc = ldap_bind_s(lcp->ld, CS user, CS password, LDAP_AUTH_SIMPLE))
- != LDAP_SUCCESS)
+#ifdef LDAP_OPT_X_TLS
+ /* The Oracle LDAP libraries (LDAP_LIB_TYPE=SOLARIS) don't support this: */
+ if (eldap_start_tls)
{
- /* Invalid credentials when just checking credentials returns FAIL. This
- stops any further servers being tried. */
+ if ( (rc = ldap_start_tls_s(lcp->ld, NULL, NULL)) != LDAP_SUCCESS) {
+ *errmsg = string_sprintf("failed to initiate TLS processing on an "
+ "LDAP session to server %s%s - ldap_start_tls_s() returned %d:"
+ " %s", host, porttext, rc, ldap_err2string(rc));
+ goto RETURN_ERROR;
+ }
+ }
+#endif
+ if ((msgid = ldap_bind(lcp->ld, CS user, CS password, LDAP_AUTH_SIMPLE))
+ == -1)
+ {
+ *errmsg = string_sprintf("failed to bind the LDAP connection to server "
+ "%s%s - ldap_bind() returned -1", host, porttext);
+ goto RETURN_ERROR;
+ }
- if (search_type == SEARCH_LDAP_AUTH && rc == LDAP_INVALID_CREDENTIALS)
- {
- DEBUG(D_lookup)
- debug_printf("Invalid credentials: ldapauth returns FAIL\n");
- error_yield = FAIL;
- goto RETURN_ERROR_NOMSG;
- }
+ if ((rc = ldap_result( lcp->ld, msgid, 1, timeoutptr, &result )) <= 0)
+ {
+ *errmsg = string_sprintf("failed to bind the LDAP connection to server "
+ "%s%s - LDAP error: %s", host, porttext,
+ rc == -1 ? "result retrieval failed" : "timeout" );
+ result = NULL;
+ goto RETURN_ERROR;
+ }
+
+ rc = ldap_result2error( lcp->ld, result, 0 );
- /* Otherwise we have a problem that doesn't stop further servers from being
- tried. */
+ /* Invalid credentials when just checking credentials returns FAIL. This
+ stops any further servers being tried. */
+ if (search_type == SEARCH_LDAP_AUTH && rc == LDAP_INVALID_CREDENTIALS)
+ {
+ DEBUG(D_lookup)
+ debug_printf("Invalid credentials: ldapauth returns FAIL\n");
+ error_yield = FAIL;
+ goto RETURN_ERROR_NOMSG;
+ }
+
+ /* Otherwise we have a problem that doesn't stop further servers from being
+ tried. */
+
+ if (rc != LDAP_SUCCESS)
+ {
*errmsg = string_sprintf("failed to bind the LDAP connection to server "
"%s%s - LDAP error %d: %s", host, porttext, rc, ldap_err2string(rc));
goto RETURN_ERROR;
lcp->bound = TRUE;
lcp->user = (user == NULL)? NULL : string_copy(user);
lcp->password = (password == NULL)? NULL : string_copy(password);
+
+ ldap_msgfree(result);
+ result = NULL;
}
/* If we are just checking credentials, return OK. */
ldap_set_option(lcp->ld, LDAP_OPT_DEREF, (void *)&dereference);
#endif
+/* Similarly for the referral setting; should the library follow referrals that
+the LDAP server returns? The conditional is just in case someone uses a library
+without it. */
+
+#if defined(LDAP_OPT_REFERRALS)
+ldap_set_option(lcp->ld, LDAP_OPT_REFERRALS, referrals);
+#endif
+
/* Start the search on the server. */
DEBUG(D_lookup) debug_printf("Start search\n");
if (msgid == -1)
{
- *errmsg = string_sprintf("ldap search initiation failed");
+ #if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2
+ int err;
+ ldap_get_option(lcp->ld, LDAP_OPT_ERROR_NUMBER, &err);
+ *errmsg = string_sprintf("ldap_search failed: %d, %s", err,
+ ldap_err2string(err));
+
+ #else
+ *errmsg = string_sprintf("ldap_search failed");
+ #endif
+
goto RETURN_ERROR;
}
/* Loop to pick up results as they come in, setting a timeout if one was
given. */
-if (timelimit > 0)
- {
- timeout.tv_sec = timelimit;
- timeout.tv_usec = 0;
- timeoutptr = &timeout;
- }
-
while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) ==
LDAP_RES_SEARCH_ENTRY)
{
DEBUG(D_lookup) debug_printf("LDAP attr loop %s:%s\n", attr, value);
if (values != firstval)
- data = string_cat(data, &size, &ptr, US", ", 2);
+ data = string_cat(data, &size, &ptr, US",", 1);
/* For multiple attributes, the data is in quotes. We must escape
- internal quotes, backslashes, newlines. */
+ internal quotes, backslashes, newlines, and must double commas. */
if (attr_count != 1)
{
{
if (value[j] == '\n')
data = string_cat(data, &size, &ptr, US"\\n", 2);
+ else if (value[j] == ',')
+ data = string_cat(data, &size, &ptr, US",,", 2);
else
{
if (value[j] == '\"' || value[j] == '\\')
}
}
- /* For single attributes, copy the value verbatim */
+ /* For single attributes, just double commas */
+
+ else
+ {
+ int j;
+ for (j = 0; j < len; j++)
+ {
+ if (value[j] == ',')
+ data = string_cat(data, &size, &ptr, US",,", 2);
+ else
+ data = string_cat(data, &size, &ptr, value+j, 1);
+ }
+ }
- else data = string_cat(data, &size, &ptr, value, len);
/* Move on to the next value */
}
/* A return code that isn't -1 doesn't necessarily mean there were no problems
-with the search. The message must be an LDAP_RES_SEARCH_RESULT or else it's
-something we can't handle. */
-
-if (rc != LDAP_RES_SEARCH_RESULT)
+with the search. The message must be an LDAP_RES_SEARCH_RESULT or
+LDAP_RES_SEARCH_REFERENCE or else it's something we can't handle. Some versions
+of LDAP do not define LDAP_RES_SEARCH_REFERENCE (LDAP v1 is one, it seems). So
+we don't provide that functionality when we can't. :-) */
+
+if (rc != LDAP_RES_SEARCH_RESULT
+#ifdef LDAP_RES_SEARCH_REFERENCE
+ && rc != LDAP_RES_SEARCH_REFERENCE
+#endif
+ )
{
*errmsg = string_sprintf("ldap_result returned unexpected code %d", rc);
goto RETURN_ERROR;
/* We have a result message from the server. This doesn't yet mean all is well.
We need to parse the message to find out exactly what's happened. */
- #if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2
- if (ldap_parse_result(lcp->ld, result, &rc, CSS &matched, CSS &error2, NULL,
- NULL, 0) < 0)
+#if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2
+ ldap_rc = rc;
+ ldap_parse_rc = ldap_parse_result(lcp->ld, result, &rc, CSS &matched,
+ CSS &error2, NULL, NULL, 0);
+ DEBUG(D_lookup) debug_printf("ldap_parse_result: %d\n", ldap_parse_rc);
+ if (ldap_parse_rc < 0 &&
+ (ldap_parse_rc != LDAP_NO_RESULTS_RETURNED
+ #ifdef LDAP_RES_SEARCH_REFERENCE
+ || ldap_rc != LDAP_RES_SEARCH_REFERENCE
+ #endif
+ ))
{
- *errmsg = US"ldap_parse_result failed";
+ *errmsg = string_sprintf("ldap_parse_result failed %d", ldap_parse_rc);
goto RETURN_ERROR;
}
error1 = US ldap_err2string(rc);
(1) If we get LDAP_SIZELIMIT_EXCEEDED, just carry on, to return the
truncated result list.
- (2) The range of errors defined by LDAP_NAME_ERROR generally mean "that
+ (2) If we get LDAP_RES_SEARCH_REFERENCE, also just carry on. This was a
+ submitted patch that is reported to "do the right thing" with Solaris
+ LDAP libraries. (The problem it addresses apparently does not occur with
+ Open LDAP.)
+
+ (3) The range of errors defined by LDAP_NAME_ERROR generally mean "that
object does not, or cannot, exist in the database". For those cases we
fail the lookup.
- (3) All other non-successes here are treated as some kind of problem with
+ (4) All other non-successes here are treated as some kind of problem with
the lookup, so return DEFER (which is the default in error_yield).
*/
DEBUG(D_lookup) debug_printf("ldap_parse_result yielded %d: %s\n",
rc, ldap_err2string(rc));
-if (rc != LDAP_SUCCESS && rc != LDAP_SIZELIMIT_EXCEEDED)
+if (rc != LDAP_SUCCESS && rc != LDAP_SIZELIMIT_EXCEEDED
+ #ifdef LDAP_RES_SEARCH_REFERENCE
+ && rc != LDAP_RES_SEARCH_REFERENCE
+ #endif
+ )
{
*errmsg = string_sprintf("LDAP search failed - error %d: %s%s%s%s%s",
rc,
BOOL defer_break = FALSE;
int timelimit = LDAP_NO_LIMIT;
int sizelimit = LDAP_NO_LIMIT;
-int tcplimit = LDAP_X_IO_TIMEOUT_NO_TIMEOUT;
-int dereference = LDAP_DEREF_NEVER;
+int tcplimit = 0;
int sep = 0;
+int dereference = LDAP_DEREF_NEVER;
+void* referrals = LDAP_OPT_ON;
uschar *url = ldap_url;
uschar *p;
uschar *user = NULL;
else if (strncmpic(name, US"PASS=", namelen) == 0) password = value;
else if (strncmpic(name, US"SIZE=", namelen) == 0) sizelimit = Uatoi(value);
else if (strncmpic(name, US"TIME=", namelen) == 0) timelimit = Uatoi(value);
- else if (strncmpic(name, US"CONNECT=", namelen) == 0) tcplimit = Uatoi(value) * 1000;
+ else if (strncmpic(name, US"CONNECT=", namelen) == 0) tcplimit = Uatoi(value);
+ else if (strncmpic(name, US"NETTIME=", namelen) == 0) tcplimit = Uatoi(value);
/* Don't know if all LDAP libraries have LDAP_OPT_DEREF */
DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
return DEFER;
}
+ #endif
+ #ifdef LDAP_OPT_REFERRALS
+ else if (strncmpic(name, US"REFERRALS=", namelen) == 0)
+ {
+ if (strcmpic(value, US"follow") == 0) referrals = LDAP_OPT_ON;
+ else if (strcmpic(value, US"nofollow") == 0) referrals = LDAP_OPT_OFF;
+ else
+ {
+ *errmsg = string_sprintf("LDAP option REFERRALS is not \"follow\" "
+ "or \"nofollow\"");
+ DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
+ return DEFER;
+ }
+ }
+ #else
+ else if (strncmpic(name, US"REFERRALS=", namelen) == 0)
+ {
+ *errmsg = string_sprintf("LDAP_OP_REFERRALS not defined in this LDAP "
+ "library - cannot use \"referrals\"");
+ DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
+ return DEFER;
+ }
#endif
else
DEBUG(D_lookup)
debug_printf("LDAP parameters: user=%s pass=%s size=%d time=%d connect=%d "
- "dereference=%d\n", user, password, sizelimit, timelimit, tcplimit,
- dereference);
+ "dereference=%d referrals=%s\n", user, password, sizelimit, timelimit,
+ tcplimit, dereference, (referrals == LDAP_OPT_ON)? "on" : "off");
/* If the request is just to check authentication, some credentials must
be given. The password must not be empty because LDAP binds with an empty
if (eldap_default_servers == NULL || p[3] != '/')
{
return perform_ldap_search(url, NULL, 0, search_type, res, errmsg,
- &defer_break, user, password, sizelimit, timelimit, tcplimit, dereference);
+ &defer_break, user, password, sizelimit, timelimit, tcplimit, dereference,
+ referrals);
}
/* Loop through the default servers until OK or FAIL */
port = Uatoi(colon+1);
}
rc = perform_ldap_search(url, server, port, search_type, res, errmsg,
- &defer_break, user, password, sizelimit, timelimit, tcplimit, dereference);
+ &defer_break, user, password, sizelimit, timelimit, tcplimit, dereference,
+ referrals);
if (rc != DEFER || defer_break) return rc;
}
are handled by a common function, with a flag to differentiate between them.
The handle and filename arguments are not used. */
-int
+static int
eldap_find(void *handle, uschar *filename, uschar *ldap_url, int length,
uschar **result, uschar **errmsg, BOOL *do_cache)
{
return(control_ldap_search(ldap_url, SEARCH_LDAP_SINGLE, result, errmsg));
}
-int
+static int
eldapm_find(void *handle, uschar *filename, uschar *ldap_url, int length,
uschar **result, uschar **errmsg, BOOL *do_cache)
{
return(control_ldap_search(ldap_url, SEARCH_LDAP_MULTIPLE, result, errmsg));
}
-int
+static int
eldapdn_find(void *handle, uschar *filename, uschar *ldap_url, int length,
uschar **result, uschar **errmsg, BOOL *do_cache)
{
/* See local README for interface description. */
-void *
+static void *
eldap_open(uschar *filename, uschar **errmsg)
{
return (void *)(1); /* Just return something non-null */
/* See local README for interface description.
Make sure that eldap_dn does not refer to reclaimed or worse, freed store */
-void
+static void
eldap_tidy(void)
{
LDAP_CONNECTION *lcp = NULL;
-uschar *
+static uschar *
eldap_quote(uschar *s, uschar *opt)
{
register int c;
return quoted;
}
-#endif /* LOOKUP_LDAP */
+
+
+/*************************************************
+* Version reporting entry point *
+*************************************************/
+
+/* See local README for interface description. */
+
+#include "../version.h"
+
+void
+ldap_version_report(FILE *f)
+{
+#ifdef DYNLOOKUP
+fprintf(f, "Library version: LDAP: Exim version %s\n", EXIM_VERSION_STR);
+#endif
+}
+
+
+static lookup_info ldap_lookup_info = {
+ US"ldap", /* lookup name */
+ lookup_querystyle, /* query-style lookup */
+ eldap_open, /* open function */
+ NULL, /* check function */
+ eldap_find, /* find function */
+ NULL, /* no close function */
+ eldap_tidy, /* tidy function */
+ eldap_quote, /* quoting function */
+ ldap_version_report /* version reporting */
+};
+
+static lookup_info ldapdn_lookup_info = {
+ US"ldapdn", /* lookup name */
+ lookup_querystyle, /* query-style lookup */
+ eldap_open, /* sic */ /* open function */
+ NULL, /* check function */
+ eldapdn_find, /* find function */
+ NULL, /* no close function */
+ eldap_tidy, /* sic */ /* tidy function */
+ eldap_quote, /* sic */ /* quoting function */
+ NULL /* no version reporting (redundant) */
+};
+
+static lookup_info ldapm_lookup_info = {
+ US"ldapm", /* lookup name */
+ lookup_querystyle, /* query-style lookup */
+ eldap_open, /* sic */ /* open function */
+ NULL, /* check function */
+ eldapm_find, /* find function */
+ NULL, /* no close function */
+ eldap_tidy, /* sic */ /* tidy function */
+ eldap_quote, /* sic */ /* quoting function */
+ NULL /* no version reporting (redundant) */
+};
+
+#ifdef DYNLOOKUP
+#define ldap_lookup_module_info _lookup_module_info
+#endif
+
+static lookup_info *_lookup_list[] = { &ldap_lookup_info, &ldapdn_lookup_info, &ldapm_lookup_info };
+lookup_module_info ldap_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 3 };
/* End of lookups/ldap.c */