X-Git-Url: https://git.exim.org/users/jgh/exim.git/blobdiff_plain/33382dd9537a16c676e07632e122c0112855d5c3..4c04137d73637107669e02b21f890387aaa2ef34:/src/src/lookups/ldap.c diff --git a/src/src/lookups/ldap.c b/src/src/lookups/ldap.c index bb29b43af..b52ef2221 100644 --- a/src/src/lookups/ldap.c +++ b/src/src/lookups/ldap.c @@ -2,11 +2,11 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2016 */ /* See the file NOTICE for conditions of use and distribution. */ /* Many thanks to Stuart Lynne for contributing the original code for this -driver. Further contibutions from Michael Haardt, Brian Candler, Barry +driver. Further contributions from Michael Haardt, Brian Candler, Barry Pederson, Peter Savitch and Christian Kellner. Particular thanks to Brian for researching how to handle the different kinds of error. */ @@ -130,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; @@ -155,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; @@ -247,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 @@ -280,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 ------------------------ */ @@ -365,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. */ @@ -461,31 +473,31 @@ if (lcp == NULL) #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); + 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(ld, LDAP_OPT_X_TLS_CACERTDIR, eldap_ca_cert_dir); + 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(ld, LDAP_OPT_X_TLS_CERTFILE, eldap_cert_file); + 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(ld, LDAP_OPT_X_TLS_KEYFILE, eldap_cert_key); + 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(ld, LDAP_OPT_X_TLS_CIPHER_SUITE, eldap_cipher_suite); + ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CIPHER_SUITE, eldap_cipher_suite); } #endif #ifdef LDAP_OPT_X_TLS_REQUIRE_CERT @@ -508,8 +520,26 @@ if (lcp == NULL) { cert_option = LDAP_OPT_X_TLS_TRY; } - /* Use NULL ldap handle because is a global option */ - ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &cert_option); + /* 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 @@ -550,7 +580,7 @@ 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 (eldap_start_tls && !lcp->is_start_tls_called && !ldapi) { #if defined(LDAP_OPT_X_TLS) && !defined(LDAP_LIB_SOLARIS) /* The Oracle LDAP libraries (LDAP_LIB_TYPE=SOLARIS) don't support this. @@ -683,10 +713,15 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) == LDAP_RES_SEARCH_ENTRY) { LDAPMessage *e; + int valuecount; /* We can see an attr spread across several + entries. If B is derived from A and we request + A and the directory contains both, A and B, + then we get two entries, one for A and one for B. + Here we just count the values per entry */ - DEBUG(D_lookup) debug_printf("ldap_result loop\n"); + DEBUG(D_lookup) debug_printf("LDAP result loop\n"); - for(e = ldap_first_entry(lcp->ld, result); + for(e = ldap_first_entry(lcp->ld, result), valuecount = 0; e != NULL; e = ldap_next_entry(lcp->ld, e)) { @@ -699,7 +734,7 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) == /* Results for multiple entries values are separated by newlines. */ - if (data != NULL) data = string_cat(data, &size, &ptr, US"\n", 1); + if (data != NULL) data = string_catn(data, &size, &ptr, US"\n", 1); /* Get the DN from the last result. */ @@ -727,7 +762,7 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) == { /* condition, because of the else */ if (new_dn != NULL) /* below, that's for the first only */ { - data = string_cat(data, &size, &ptr, new_dn, Ustrlen(new_dn)); + data = string_cat(data, &size, &ptr, new_dn); data[ptr] = 0; attribute_found = TRUE; } @@ -735,15 +770,21 @@ 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; attr = US ldap_next_attribute(lcp->ld, e, ber)) { + DEBUG(D_lookup) debug_printf("LDAP attr loop\n"); + + /* In case of attrs_requested == 1 we just count the values, in all other cases + (0, >1) we count the values per attribute */ + if (attrs_requested != 1) valuecount = 0; + if (attr[0] != 0) { /* Get array of values for this attribute. */ @@ -751,43 +792,51 @@ 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); + data = string_catn(data, &size, &ptr, US" ", 1); else insert_space = TRUE; - data = string_cat(data, &size, &ptr, attr, Ustrlen(attr)); - data = string_cat(data, &size, &ptr, US"=\"", 2); + data = string_cat(data, &size, &ptr, attr); + data = string_catn(data, &size, &ptr, US"=\"", 2); } while (*values != NULL) { uschar *value = *values; int len = Ustrlen(value); + ++valuecount; - DEBUG(D_lookup) debug_printf("LDAP attr loop %s:%s\n", attr, value); + DEBUG(D_lookup) debug_printf("LDAP value loop %s:%s\n", attr, value); - if (values != firstval) - data = string_cat(data, &size, &ptr, US",", 1); + /* 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 (data && valuecount > 1) + data = string_catn(data, &size, &ptr, US",", 1); /* For multiple attributes, the data is in quotes. We must escape 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); + data = string_catn(data, &size, &ptr, US"\\n", 2); else if (value[j] == ',') - data = string_cat(data, &size, &ptr, US",,", 2); + data = string_catn(data, &size, &ptr, US",,", 2); else { if (value[j] == '\"' || value[j] == '\\') - data = string_cat(data, &size, &ptr, US"\\", 1); - data = string_cat(data, &size, &ptr, value+j, 1); + data = string_catn(data, &size, &ptr, US"\\", 1); + data = string_catn(data, &size, &ptr, value+j, 1); } } } @@ -798,12 +847,10 @@ while ((rc = ldap_result(lcp->ld, msgid, 0, timeoutptr, &result)) == { int j; for (j = 0; j < len; j++) - { if (value[j] == ',') - data = string_cat(data, &size, &ptr, US",,", 2); + data = string_catn(data, &size, &ptr, US",,", 2); else - data = string_cat(data, &size, &ptr, value+j, 1); - } + data = string_catn(data, &size, &ptr, value+j, 1); } @@ -815,8 +862,8 @@ 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) - data = string_cat(data, &size, &ptr, US"\"", 1); + if (attrs_requested != 1) + data = string_catn(data, &size, &ptr, US"\"", 1); /* Free the values */ @@ -1090,7 +1137,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; @@ -1100,11 +1147,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++; @@ -1116,7 +1165,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 == '=') { @@ -1132,6 +1181,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 */ @@ -1259,16 +1309,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; @@ -1299,8 +1349,8 @@ are handled by a common function, with a flag to differentiate between them. The handle and filename arguments are not used. */ static int -eldap_find(void *handle, uschar *filename, uschar *ldap_url, int length, - uschar **result, uschar **errmsg, BOOL *do_cache) +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; @@ -1308,8 +1358,8 @@ return(control_ldap_search(ldap_url, SEARCH_LDAP_SINGLE, result, errmsg)); } static int -eldapm_find(void *handle, uschar *filename, uschar *ldap_url, int length, - uschar **result, uschar **errmsg, BOOL *do_cache) +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; @@ -1317,8 +1367,8 @@ return(control_ldap_search(ldap_url, SEARCH_LDAP_MULTIPLE, result, errmsg)); } static int -eldapdn_find(void *handle, uschar *filename, uschar *ldap_url, int length, - uschar **result, uschar **errmsg, BOOL *do_cache) +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; @@ -1326,8 +1376,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; @@ -1367,7 +1417,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; } }