X-Git-Url: https://vcs.fsf.org/?p=exim.git;a=blobdiff_plain;f=src%2Fsrc%2Flookups%2Fldap.c;h=3528075b059a7090d4dd0d72a3b84227761585b9;hp=55761977c5ad929e889fe8abf6e33dc354053dd6;hb=9494140a9fbaed32259a60af2b59e6f61f06589c;hpb=6ec97b1bb5ba11ef3febc5ba8f9bcb4365984189 diff --git a/src/src/lookups/ldap.c b/src/src/lookups/ldap.c index 55761977c..3528075b0 100644 --- a/src/src/lookups/ldap.c +++ b/src/src/lookups/ldap.c @@ -1,10 +1,8 @@ -/* $Cambridge: exim/src/src/lookups/ldap.c,v 1.12 2006/07/17 09:18:09 ph10 Exp $ */ - /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2006 */ +/* Copyright (c) University of Cambridge 1995 - 2015 */ /* See the file NOTICE for conditions of use and distribution. */ /* Many thanks to Stuart Lynne for contributing the original code for this @@ -15,20 +13,6 @@ researching how to handle the different kinds of error. */ #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 @@ -97,6 +81,7 @@ typedef struct ldap_connection { uschar *password; BOOL bound; int port; + BOOL is_start_tls_called; LDAP *ld; } LDAP_CONNECTION; @@ -145,9 +130,10 @@ Returns: OK or FAIL or DEFER */ 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, void *referrals) +perform_ldap_search(const 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, void *referrals) { LDAPURLDesc *ludp = NULL; LDAPMessage *result = NULL; @@ -170,7 +156,7 @@ uschar *error1 = NULL; /* string representation of errcode (static) */ uschar *error2 = NULL; /* error message from the server */ uschar *matched = NULL; /* partially matched DN */ -int attr_count = 0; +int attrs_requested = 0; int error_yield = DEFER; int msgid; int rc, ldap_rc, ldap_parse_rc; @@ -262,7 +248,7 @@ if (host != NULL) /* Count the attributes; we need this later to tell us how to format results */ for (attrp = USS ludp->lud_attrs; attrp != NULL && *attrp != NULL; attrp++) - attr_count++; + attrs_requested++; /* See if we can find a cached connection to this host. The port is not relevant for ldapi. The host name pointer is set to NULL if no host was given @@ -295,6 +281,13 @@ if (lcp == NULL) { LDAP *ld; + #ifdef LDAP_OPT_X_TLS_NEWCTX + int am_server = 0; + LDAP *ldsetctx; + #else + LDAP *ldsetctx = NULL; + #endif + /* --------------------------- OpenLDAP ------------------------ */ @@ -380,6 +373,10 @@ if (lcp == NULL) goto RETURN_ERROR; } + #ifdef LDAP_OPT_X_TLS_NEWCTX + ldsetctx = ld; + #endif + /* Set the TCP connect time limit if available. This is something that is in Netscape SDK v4.1; I don't know about other libraries. */ @@ -431,20 +428,121 @@ if (lcp == NULL) if (!ldapi) { int tls_option; + #ifdef LDAP_OPT_X_TLS_REQUIRE_CERT + if (eldap_require_cert != NULL) + { + tls_option = LDAP_OPT_X_TLS_NEVER; + if (Ustrcmp(eldap_require_cert, "hard") == 0) + { + tls_option = LDAP_OPT_X_TLS_HARD; + } + else if (Ustrcmp(eldap_require_cert, "demand") == 0) + { + tls_option = LDAP_OPT_X_TLS_DEMAND; + } + else if (Ustrcmp(eldap_require_cert, "allow") == 0) + { + tls_option = LDAP_OPT_X_TLS_ALLOW; + } + else if (Ustrcmp(eldap_require_cert, "try") == 0) + { + tls_option = LDAP_OPT_X_TLS_TRY; + } + DEBUG(D_lookup) + debug_printf("Require certificate overrides LDAP_OPT_X_TLS option (%d)\n", + tls_option); + } + else + #endif /* LDAP_OPT_X_TLS_REQUIRE_CERT */ if (strncmp(ludp->lud_scheme, "ldaps", 5) == 0) { tls_option = LDAP_OPT_X_TLS_HARD; - DEBUG(D_lookup) debug_printf("LDAP_OPT_X_TLS_HARD set\n"); + DEBUG(D_lookup) + debug_printf("LDAP_OPT_X_TLS_HARD set due to ldaps:// URI\n"); } else { tls_option = LDAP_OPT_X_TLS_TRY; - DEBUG(D_lookup) debug_printf("LDAP_OPT_X_TLS_TRY set\n"); + DEBUG(D_lookup) + debug_printf("LDAP_OPT_X_TLS_TRY set due to ldap:// URI\n"); } ldap_set_option(ld, LDAP_OPT_X_TLS, (void *)&tls_option); } #endif /* LDAP_OPT_X_TLS */ + #ifdef LDAP_OPT_X_TLS_CACERTFILE + if (eldap_ca_cert_file != NULL) + { + ldap_set_option(ldsetctx, 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(ldsetctx, LDAP_OPT_X_TLS_CACERTDIR, eldap_ca_cert_dir); + } + #endif + #ifdef LDAP_OPT_X_TLS_CERTFILE + if (eldap_cert_file != NULL) + { + ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CERTFILE, eldap_cert_file); + } + #endif + #ifdef LDAP_OPT_X_TLS_KEYFILE + if (eldap_cert_key != NULL) + { + ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_KEYFILE, eldap_cert_key); + } + #endif + #ifdef LDAP_OPT_X_TLS_CIPHER_SUITE + if (eldap_cipher_suite != NULL) + { + ldap_set_option(ldsetctx, 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; + } + /* This ldap handle is set at compile time based on client libs. Older + * versions want it to be global and newer versions can force a reload + * of the TLS context (to reload these settings we are changing from the + * default that loaded at instantiation). */ + rc = ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_REQUIRE_CERT, &cert_option); + if (rc) + { + DEBUG(D_lookup) + debug_printf("Unable to set TLS require cert_option(%d) globally: %s\n", + cert_option, ldap_err2string(rc)); + } + } + #endif + #ifdef LDAP_OPT_X_TLS_NEWCTX + rc = ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_NEWCTX, &am_server); + if (rc) + { + DEBUG(D_lookup) + debug_printf("Unable to reload TLS context %d: %s\n", + rc, ldap_err2string(rc)); + } + #endif + /* Now add this connection to the chain of cached connections */ lcp = store_get(sizeof(LDAP_CONNECTION)); @@ -455,6 +553,7 @@ if (lcp == NULL) lcp->port = port; lcp->ld = ld; lcp->next = ldap_connections; + lcp->is_start_tls_called = FALSE; ldap_connections = lcp; } @@ -481,6 +580,26 @@ if (!lcp->bound || { DEBUG(D_lookup) debug_printf("%sbinding with user=%s password=%s\n", (lcp->bound)? "re-" : "", user, password); + if (eldap_start_tls && !lcp->is_start_tls_called) + { +#if defined(LDAP_OPT_X_TLS) && !defined(LDAP_LIB_SOLARIS) + /* The Oracle LDAP libraries (LDAP_LIB_TYPE=SOLARIS) don't support this. + * Note: moreover, they appear to now define LDAP_OPT_X_TLS and still not + * export an ldap_start_tls_s symbol. + */ + 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; + } + lcp->is_start_tls_called = TRUE; +#else + DEBUG(D_lookup) + debug_printf("TLS initiation not supported with this Exim and your LDAP library.\n"); +#endif + } if ((msgid = ldap_bind(lcp->ld, CS user, CS password, LDAP_AUTH_SIMPLE)) == -1) { @@ -646,10 +765,10 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) == /* Otherwise, loop through the entry, grabbing attribute values. If there's only one attribute being retrieved, no attribute name is given, and the - result is not quoted. Multiple values are separated by (comma, space). + result is not quoted. Multiple values are separated by (comma). If more than one attribute is being retrieved, the data is given as a - sequence of name=value pairs, with the value always in quotes. If there are - multiple values, they are given within the quotes, comma separated. */ + sequence of name=value pairs, separated by (space), with the value always in quotes. + If there are multiple values, they are given within the quotes, comma separated. */ else for (attr = US ldap_first_attribute(lcp->ld, e, &ber); attr != NULL; @@ -662,7 +781,8 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) == if ((firstval = values = USS ldap_get_values(lcp->ld, e, CS attr)) != NULL) { - if (attr_count != 1) + + if (attrs_requested != 1) { if (insert_space) data = string_cat(data, &size, &ptr, US" ", 1); @@ -679,19 +799,27 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) == DEBUG(D_lookup) debug_printf("LDAP attr loop %s:%s\n", attr, value); - if (values != firstval) - data = string_cat(data, &size, &ptr, US", ", 2); + /* In case we requested one attribute only but got + * several times into that attr loop, we need to append + * the additional values. (This may happen if you derive + * attributeTypes B and C from A and then query for A.) + * In all other cases we detect the different attribute + * and append only every non first value. */ + if ((attr_count == 1 && data) || (values != firstval)) + 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 (attrs_requested != 1) { int j; for (j = 0; j < len; j++) { 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] == '\\') @@ -701,9 +829,20 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) == } } - /* 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 */ @@ -713,7 +852,7 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) == /* Closing quote at the end of the data for a named attribute. */ - if (attr_count != 1) + if (attrs_requested != 1) data = string_cat(data, &size, &ptr, US"\"", 1); /* Free the values */ @@ -853,18 +992,27 @@ We need to parse the message to find out exactly what's happened. */ (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, @@ -979,7 +1127,7 @@ Returns: OK or FAIL or DEFER */ static int -control_ldap_search(uschar *ldap_url, int search_type, uschar **res, +control_ldap_search(const uschar *ldap_url, int search_type, uschar **res, uschar **errmsg) { BOOL defer_break = FALSE; @@ -989,11 +1137,13 @@ int tcplimit = 0; int sep = 0; int dereference = LDAP_DEREF_NEVER; void* referrals = LDAP_OPT_ON; -uschar *url = ldap_url; -uschar *p; +const uschar *url = ldap_url; +const uschar *p; uschar *user = NULL; uschar *password = NULL; -uschar *server, *list; +uschar *local_servers = NULL; +uschar *server; +const uschar *list; uschar buffer[512]; while (isspace(*url)) url++; @@ -1005,7 +1155,7 @@ NAME has the value "ldap". */ while (strncmpic(url, US"ldap", 4) != 0) { - uschar *name = url; + const uschar *name = url; while (*url != 0 && *url != '=') url++; if (*url == '=') { @@ -1021,6 +1171,7 @@ while (strncmpic(url, US"ldap", 4) != 0) else if (strncmpic(name, US"TIME=", namelen) == 0) timelimit = Uatoi(value); else if (strncmpic(name, US"CONNECT=", namelen) == 0) tcplimit = Uatoi(value); else if (strncmpic(name, US"NETTIME=", namelen) == 0) tcplimit = Uatoi(value); + else if (strncmpic(name, US"SERVERS=", namelen) == 0) local_servers = value; /* Don't know if all LDAP libraries have LDAP_OPT_DEREF */ @@ -1148,16 +1299,16 @@ if (Ustrncmp(p, "://", 3) != 0) /* No default servers, or URL contains a server name: just one attempt */ -if (eldap_default_servers == NULL || p[3] != '/') +if ((eldap_default_servers == NULL && local_servers == NULL) || p[3] != '/') { return perform_ldap_search(url, NULL, 0, search_type, res, errmsg, &defer_break, user, password, sizelimit, timelimit, tcplimit, dereference, referrals); } -/* Loop through the default servers until OK or FAIL */ - -list = eldap_default_servers; +/* Loop through the default servers until OK or FAIL. Use local_servers list + * if defined in the lookup, otherwise use the global default list */ +list = (local_servers == NULL) ? eldap_default_servers : local_servers; while ((server = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL) { int rc; @@ -1187,27 +1338,27 @@ return DEFER; are handled by a common function, with a flag to differentiate between them. The handle and filename arguments are not used. */ -int -eldap_find(void *handle, uschar *filename, uschar *ldap_url, int length, - uschar **result, uschar **errmsg, BOOL *do_cache) +static int +eldap_find(void *handle, uschar *filename, const uschar *ldap_url, int length, + uschar **result, uschar **errmsg, uint *do_cache) { /* Keep picky compilers happy */ do_cache = do_cache; return(control_ldap_search(ldap_url, SEARCH_LDAP_SINGLE, result, errmsg)); } -int -eldapm_find(void *handle, uschar *filename, uschar *ldap_url, int length, - uschar **result, uschar **errmsg, BOOL *do_cache) +static int +eldapm_find(void *handle, uschar *filename, const uschar *ldap_url, int length, + uschar **result, uschar **errmsg, uint *do_cache) { /* Keep picky compilers happy */ do_cache = do_cache; return(control_ldap_search(ldap_url, SEARCH_LDAP_MULTIPLE, result, errmsg)); } -int -eldapdn_find(void *handle, uschar *filename, uschar *ldap_url, int length, - uschar **result, uschar **errmsg, BOOL *do_cache) +static int +eldapdn_find(void *handle, uschar *filename, const uschar *ldap_url, int length, + uschar **result, uschar **errmsg, uint *do_cache) { /* Keep picky compilers happy */ do_cache = do_cache; @@ -1215,8 +1366,8 @@ return(control_ldap_search(ldap_url, SEARCH_LDAP_DN, result, errmsg)); } int -eldapauth_find(void *handle, uschar *filename, uschar *ldap_url, int length, - uschar **result, uschar **errmsg, BOOL *do_cache) +eldapauth_find(void *handle, uschar *filename, const uschar *ldap_url, int length, + uschar **result, uschar **errmsg, uint *do_cache) { /* Keep picky compilers happy */ do_cache = do_cache; @@ -1231,7 +1382,7 @@ return(control_ldap_search(ldap_url, SEARCH_LDAP_AUTH, result, errmsg)); /* See local README for interface description. */ -void * +static void * eldap_open(uschar *filename, uschar **errmsg) { return (void *)(1); /* Just return something non-null */ @@ -1246,7 +1397,7 @@ 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; @@ -1256,7 +1407,8 @@ while ((lcp = ldap_connections) != NULL) { DEBUG(D_lookup) debug_printf("unbind LDAP connection to %s:%d\n", lcp->host, lcp->port); - ldap_unbind(lcp->ld); + if(lcp->bound == TRUE) + ldap_unbind(lcp->ld); ldap_connections = lcp->next; } } @@ -1342,7 +1494,7 @@ quote_ldap_dn, respectively. */ -uschar * +static uschar * eldap_quote(uschar *s, uschar *opt) { register int c; @@ -1461,6 +1613,66 @@ else 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 */