* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
/* 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. */
uschar *password;
BOOL bound;
int port;
+ BOOL is_start_tls_called;
LDAP *ld;
} LDAP_CONNECTION;
*/
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;
uschar *attr;
uschar **attrp;
-uschar *data = NULL;
+gstring * data = NULL;
uschar *dn = NULL;
uschar *host;
uschar **values;
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;
int port;
-int ptr = 0;
int rescount = 0;
-int size = 0;
BOOL attribute_found = FALSE;
BOOL ldapi = FALSE;
/* 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
{
LDAP *ld;
+ #ifdef LDAP_OPT_X_TLS_NEWCTX
+ int am_server = 0;
+ LDAP *ldsetctx;
+ #else
+ LDAP *ldsetctx = NULL;
+ #endif
+
/* --------------------------- OpenLDAP ------------------------ */
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. */
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);
}
#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
{
cert_option = LDAP_OPT_X_TLS_TRY;
}
- ldap_set_option(ld, 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
lcp->port = port;
lcp->ld = ld;
lcp->next = ldap_connections;
+ lcp->is_start_tls_called = FALSE;
ldap_connections = lcp;
}
{
DEBUG(D_lookup) debug_printf("%sbinding with user=%s password=%s\n",
(lcp->bound)? "re-" : "", user, password);
-#ifdef LDAP_OPT_X_TLS
- /* The Oracle LDAP libraries (LDAP_LIB_TYPE=SOLARIS) don't support this: */
- if (eldap_start_tls)
+ if (eldap_start_tls && !lcp->is_start_tls_called && !ldapi)
{
- 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;
- }
- }
+#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)
{
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);
- e != NULL;
+ for(e = ldap_first_entry(lcp->ld, result), valuecount = 0;
+ e;
e = ldap_next_entry(lcp->ld, e))
{
uschar *new_dn;
/* Results for multiple entries values are separated by newlines. */
- if (data != NULL) data = string_cat(data, &size, &ptr, US"\n", 1);
+ if (data) data = string_catn(data, US"\n", 1);
/* Get the DN from the last 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[ptr] = 0;
+ data = string_cat(data, new_dn);
+ (void) string_from_gstring(data);
attribute_found = TRUE;
}
}
/* 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))
+ attr; 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. */
- if ((firstval = values = USS ldap_get_values(lcp->ld, e, CS attr))
- != NULL)
+ if ((firstval = values = USS ldap_get_values(lcp->ld, e, CS attr)))
{
- if (attr_count != 1)
+
+ if (attrs_requested != 1)
{
if (insert_space)
- data = string_cat(data, &size, &ptr, US" ", 1);
+ data = string_catn(data, 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, attr);
+ data = string_catn(data, US"=\"", 2);
}
- while (*values != NULL)
+ while (*values)
{
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, 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, US"\\n", 2);
else if (value[j] == ',')
- data = string_cat(data, &size, &ptr, US",,", 2);
+ data = string_catn(data, 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, US"\\", 1);
+ data = string_catn(data, value+j, 1);
}
}
}
{
int j;
for (j = 0; j < len; j++)
- {
if (value[j] == ',')
- data = string_cat(data, &size, &ptr, US",,", 2);
+ data = string_catn(data, US",,", 2);
else
- data = string_cat(data, &size, &ptr, value+j, 1);
- }
+ data = string_catn(data, value+j, 1);
}
/* 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, US"\"", 1);
/* Free the values */
/* Terminate the dynamic string that we have built and reclaim unused store */
-if (data != NULL)
+if (data)
{
- data[ptr] = 0;
- store_reset(data + ptr + 1);
+ (void) string_from_gstring(data);
+ store_reset(data->s + data->ptr + 1);
}
/* Copy the last dn into eldap_dn */
-if (dn != NULL)
+if (dn)
{
eldap_dn = string_copy(dn);
#if defined LDAP_LIB_NETSCAPE || defined LDAP_LIB_OPENLDAP2
/* Otherwise, it's all worked */
-DEBUG(D_lookup) debug_printf("LDAP search: returning: %s\n", data);
-*res = data;
+DEBUG(D_lookup) debug_printf("LDAP search: returning: %s\n", data->s);
+*res = data->s;
RETURN_OK:
if (result != NULL) ldap_msgfree(result);
*/
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;
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++;
while (strncmpic(url, US"ldap", 4) != 0)
{
- uschar *name = url;
+ const uschar *name = url;
while (*url != 0 && *url != '=') url++;
if (*url == '=')
{
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 */
/* 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;
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;
}
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;
}
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;
}
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;
{
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;
}
}