Pass a threadsafe ctx into fr_connection_pool create callback
[freeradius.git] / src / modules / rlm_ldap / ldap.c
index d262c35..6fe2473 100644 (file)
@@ -12,7 +12,7 @@
  *   along with this program; if not, write to the Free Software
  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  */
+
 /**
  * $Id$
  * @file ldap.c
  * @copyright 2013 Network RADIUS SARL <info@networkradius.com>
  * @copyright 2013 The FreeRADIUS Server Project.
  */
-#include       <freeradius-devel/radiusd.h>
-#include       <freeradius-devel/modules.h>
-#include       <freeradius-devel/rad_assert.h>
-
-#include       <stdarg.h>
-#include       <ctype.h>
-
-#include       <lber.h>
-#include       <ldap.h>
-#include       "ldap.h"
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/modules.h>
+#include <freeradius-devel/rad_assert.h>
 
+#include <stdarg.h>
+#include <ctype.h>
 
+#include <lber.h>
+#include <ldap.h>
+#include "ldap.h"
 
 /** Converts "bad" strings into ones which are safe for LDAP
  *
  * @param in Raw unescaped string.
  * @param arg Any additional arguments (unused).
  */
-size_t rlm_ldap_escape_func(UNUSED REQUEST *request, char *out, size_t outlen, const char *in, UNUSED void *arg)
+size_t rlm_ldap_escape_func(UNUSED REQUEST *request, char *out, size_t outlen, char const *in, UNUSED void *arg)
 {
-       static const char encode[] = ",+\"\\<>;*=()";
-       static const char hextab[] = "0123456789abcdef";
+       static char const encode[] = ",+\"\\<>;*=()";
+       static char const hextab[] = "0123456789abcdef";
        size_t left = outlen;
-       
+
        if (*in && ((*in == ' ') || (*in == '#'))) {
                goto encode;
        }
-       
+
        while (*in) {
                /*
                 *      Encode unsafe characters.
@@ -87,9 +85,9 @@ size_t rlm_ldap_escape_func(UNUSED REQUEST *request, char *out, size_t outlen, c
                *out++ = *in++;
                left--;
        }
-       
+
        *out = '\0';
-       
+
        return outlen - left;
 }
 
@@ -98,58 +96,149 @@ size_t rlm_ldap_escape_func(UNUSED REQUEST *request, char *out, size_t outlen, c
  * @param str to check.
  * @return true if string is a DN, else false.
  */
-int rlm_ldap_is_dn(const char *str)
+int rlm_ldap_is_dn(char const *str)
 {
-       return strrchr(str, ',') == NULL ? FALSE : TRUE;
+       return strrchr(str, ',') == NULL ? false : true;
 }
 
 /** Find the place at which the two DN strings diverge
- * 
+ *
  * Returns the length of the non matching string in full.
  *
  * @param full DN.
  * @param part Partial DN as returned by ldap_parse_result.
  * @return the length of the portion of full which wasn't matched or -1 on error.
  */
-static size_t rlm_ldap_common_dn(const char *full, const char *part)
+static size_t rlm_ldap_common_dn(char const *full, char const *part)
 {
        size_t f_len, p_len, i;
-       
+
        if (!full) {
                return -1;
        }
-       
+
        f_len = strlen(full);
-       
+
        if (!part) {
-               return f_len;
+               return -1;
        }
-       
+
        p_len = strlen(part);
        if (!p_len) {
                return f_len;
        }
-       
+
        if ((f_len < p_len) || !f_len) {
-               return -1; 
+               return -1;
        }
 
-
        for (i = 0; i < p_len; i++) {
                if (part[p_len - i] != full[f_len - i]) {
-                       return -1; 
+                       return -1;
                }
        }
 
        return f_len - p_len;
 }
 
+/** Combine and expand filters
+ *
+ * @param request Current request.
+ * @param out Where to write the expanded string.
+ * @param outlen Length of output buffer.
+ * @param sub Array of subfilters (may contain NULLs).
+ * @param sublen Number of potential subfilters in array.
+ * @return length of expanded data.
+ */
+ssize_t rlm_ldap_xlat_filter(REQUEST *request, char const **sub, size_t sublen, char *out, size_t outlen)
+{
+       char buffer[LDAP_MAX_FILTER_STR_LEN + 1];
+       char const *in = NULL;
+       char *p = buffer;
+
+       ssize_t len = 0;
+
+       unsigned int i;
+       int cnt = 0;
+
+       /*
+        *      Figure out how many filter elements we need to integrate
+        */
+       for (i = 0; i < sublen; i++) {
+               if (sub[i] && *sub[i]) {
+                       in = sub[i];
+                       cnt++;
+               }
+       }
+
+       if (!cnt) {
+               out[0] = '\0';
+               return 0;
+       }
+
+       if (cnt > 1) {
+               if (outlen < 3) {
+                       goto oob;
+               }
+
+               p[len++] = '(';
+               p[len++] = '&';
+
+               for (i = 0; i < sublen; i++) {
+                       if (sub[i] && (*sub[i] != '\0')) {
+                               len += strlcpy(p + len, sub[i], outlen - len);
+
+                               if ((size_t) len >= outlen) {
+                                       oob:
+                                       REDEBUG("Out of buffer space creating filter");
+
+                                       return -1;
+                               }
+                       }
+               }
+
+               if ((outlen - len) < 2) {
+                       goto oob;
+               }
+
+               p[len++] = ')';
+               p[len] = '\0';
+
+               in = buffer;
+       }
+
+       len = radius_xlat(out, outlen, request, in, rlm_ldap_escape_func, NULL);
+       if (len < 0) {
+               REDEBUG("Failed creating filter");
+
+               return -1;
+       }
+
+       return len;
+}
+
+/** Return the error string associated with a handle
+ *
+ * @param conn to retrieve error from.
+ * @return error string.
+ */
+char const *rlm_ldap_error_str(ldap_handle_t const *conn)
+{
+       int lib_errno;
+       ldap_get_option(conn->handle, LDAP_OPT_ERROR_NUMBER, &lib_errno);
+       if (lib_errno == LDAP_SUCCESS) {
+               return "unknown";
+       }
+
+       return ldap_err2string(lib_errno);
+}
+
 /** Parse response from LDAP server dealing with any errors
  *
- * Should be called after an LDAP operation. Will check result of operation and if it was successful, then attempt 
+ * Should be called after an LDAP operation. Will check result of operation and if it was successful, then attempt
  * to retrieve and parse the result.
  *
- * Will also produce extended error output including any messages the server sent, and information about partial 
+ * Will also produce extended error output including any messages the server sent, and information about partial
  * DN matches.
  *
  * @param[in] inst of LDAP module.
@@ -158,49 +247,51 @@ static size_t rlm_ldap_common_dn(const char *full, const char *part)
  * @param[in] dn Last search or bind DN.
  * @param[out] result Where to write result, if NULL result will be freed.
  * @param[out] error Where to write the error string, may be NULL, must not be freed.
- * @param[out] extra Where to write additional error string to, may be NULL (faster) or must be freed 
+ * @param[out] extra Where to write additional error string to, may be NULL (faster) or must be freed
  *     (with talloc_free).
  * @return One of the LDAP_PROC_* codes.
  */
-static ldap_rcode_t rlm_ldap_result(const ldap_instance_t *inst, const ldap_handle_t *conn, int msgid, const char *dn,
-                                   LDAPMessage **result, const char **error, char **extra)
+static ldap_rcode_t rlm_ldap_result(ldap_instance_t const *inst, ldap_handle_t const *conn, int msgid, char const *dn,
+                                   LDAPMessage **result, char const **error, char **extra)
 {
        ldap_rcode_t status = LDAP_PROC_SUCCESS;
 
        int lib_errno = LDAP_SUCCESS;   // errno returned by the library.
        int srv_errno = LDAP_SUCCESS;   // errno in the result message.
-       
+
        char *part_dn = NULL;           // Partial DN match.
        char *our_err = NULL;           // Our extended error message.
        char *srv_err = NULL;           // Server's extended error message.
        char *p, *a;
 
-       int freeit = FALSE;             // Whether the message should be freed after being processed.
+       bool freeit = false;            // Whether the message should be freed after being processed.
        int len;
-       
+
        struct timeval tv;              // Holds timeout values.
-       
+
        LDAPMessage *tmp_msg;           // Temporary message pointer storage if we weren't provided with one.
-       
-       const char *tmp_err;            // Temporary error pointer storage if we weren't provided with one.
-       
+
+       char const *tmp_err;            // Temporary error pointer storage if we weren't provided with one.
+
        if (!error) {
                error = &tmp_err;
        }
        *error = NULL;
-       
+
        if (extra) {
                *extra = NULL;
        }
-       
+
        /*
         *      We always need the result, but our caller may not
         */
        if (!result) {
                result = &tmp_msg;
-               freeit = TRUE;
+               freeit = true;
        }
-       
+
+       *result = NULL;
+
        /*
         *      Check if there was an error sending the request
         */
@@ -209,9 +300,9 @@ static ldap_rcode_t rlm_ldap_result(const ldap_instance_t *inst, const ldap_hand
        if (lib_errno != LDAP_SUCCESS) {
                goto process_error;
        }
-       
-       tv.tv_sec = inst->timeout;
-       tv.tv_usec = 0;
+
+       memset(&tv, 0, sizeof(tv));
+       tv.tv_sec = inst->res_timeout;
 
        /*
         *      Now retrieve the result and check for errors
@@ -220,16 +311,16 @@ static ldap_rcode_t rlm_ldap_result(const ldap_instance_t *inst, const ldap_hand
        lib_errno = ldap_result(conn->handle, msgid, 1, &tv, result);
        if (lib_errno == 0) {
                lib_errno = LDAP_TIMEOUT;
-               
+
                goto process_error;
        }
-       
+
        if (lib_errno == -1) {
                ldap_get_option(conn->handle, LDAP_OPT_ERROR_NUMBER,
                                &lib_errno);
                goto process_error;
        }
-       
+
        /*
         *      Parse the result and check for errors sent by the server
         */
@@ -238,73 +329,75 @@ static ldap_rcode_t rlm_ldap_result(const ldap_instance_t *inst, const ldap_hand
                                      extra ? &part_dn : NULL,
                                      extra ? &srv_err : NULL,
                                      NULL, NULL, freeit);
-                                     
+       if (freeit) {
+               *result = NULL;
+       }
+
        if (lib_errno != LDAP_SUCCESS) {
                ldap_get_option(conn->handle, LDAP_OPT_ERROR_NUMBER,
                                &lib_errno);
                goto process_error;
        }
-       
-       process_error:
-       
+
+process_error:
        if ((lib_errno == LDAP_SUCCESS) && (srv_errno != LDAP_SUCCESS)) {
                lib_errno = srv_errno;
        } else if ((lib_errno != LDAP_SUCCESS) && (srv_errno == LDAP_SUCCESS)) {
                srv_errno = lib_errno;
        }
-       
+
        switch (lib_errno) {
        case LDAP_SUCCESS:
                *error = "Success";
-               
+
                break;
 
        case LDAP_NO_SUCH_OBJECT:
-               *error = "The specified object wasn't found, check basedn and admin dn";
-               
+               *error = "The specified DN wasn't found, check base_dn and identity";
+
                status = LDAP_PROC_BAD_DN;
-               
+
                if (!extra) break;
-               
-               /* 
+
+               /*
                 *      Build our own internal diagnostic string
                 */
                len = rlm_ldap_common_dn(dn, part_dn);
                if (len < 0) break;
-               
-               our_err = talloc_asprintf(conn, "Match stopped here: [%.*s]%s", len, part_dn, part_dn ? part_dn : "");
+
+               our_err = talloc_typed_asprintf(conn, "Match stopped here: [%.*s]%s", len, dn, part_dn ? part_dn : "");
 
                goto error_string;
 
        case LDAP_INSUFFICIENT_ACCESS:
                *error = "Insufficient access. Check the identity and password configuration directives";
-               
+
                status = LDAP_PROC_NOT_PERMITTED;
                break;
-               
+
        case LDAP_UNWILLING_TO_PERFORM:
                *error = "Server was unwilling to perform";
-       
+
                status = LDAP_PROC_NOT_PERMITTED;
                break;
-               
+
        case LDAP_TIMEOUT:
-               exec_trigger(NULL, inst->cs, "modules.ldap.timeout", TRUE);
-               
+               exec_trigger(NULL, inst->cs, "modules.ldap.timeout", true);
+
                *error = "Timed out while waiting for server to respond";
-                      
+
                status = LDAP_PROC_ERROR;
                break;
-               
+
        case LDAP_FILTER_ERROR:
                *error = "Bad search filter";
 
                status = LDAP_PROC_ERROR;
                break;
-               
+
        case LDAP_TIMELIMIT_EXCEEDED:
-               exec_trigger(NULL, inst->cs, "modules.ldap.timeout", TRUE);
-               
+               exec_trigger(NULL, inst->cs, "modules.ldap.timeout", true);
+
                *error = "Time limit exceeded";
                /* FALL-THROUGH */
 
@@ -312,96 +405,94 @@ static ldap_rcode_t rlm_ldap_result(const ldap_instance_t *inst, const ldap_hand
        case LDAP_UNAVAILABLE:
        case LDAP_SERVER_DOWN:
                status = LDAP_PROC_RETRY;
-               
+
                goto error_string;
-               
+
        case LDAP_INVALID_CREDENTIALS:
        case LDAP_CONSTRAINT_VIOLATION:
                status = LDAP_PROC_REJECT;
-               
+
                goto error_string;
-               
+
        case LDAP_OPERATIONS_ERROR:
                *error = "Please set 'chase_referrals=yes' and 'rebind=yes'. See the ldap module configuration "
                         "for details.";
-                        
+
                /* FALL-THROUGH */
        default:
                status = LDAP_PROC_ERROR;
-               
+
                error_string:
-               
+
                if (!*error) {
                        *error = ldap_err2string(lib_errno);
                }
-               
+
                if (!extra || ((lib_errno == srv_errno) && !our_err && !srv_err)) {
                        break;
                }
-               
+
                /*
                 *      Output the error codes from the library and server
                 */
-               p = talloc_strdup(conn, "");
+               p = talloc_zero_array(conn, char, 1);
                if (!p) break;
 
                if (lib_errno != srv_errno) {
-                       a = talloc_asprintf_append(p, "LDAP lib error: %s (%u), srv error: %s (%u)", 
-                                                  ldap_err2string(lib_errno), lib_errno,
+                       a = talloc_asprintf_append(p, "LDAP lib error: %s (%u), srv error: %s (%u). ",
+                                                  ldap_err2string(lib_errno), lib_errno,
                                                   ldap_err2string(srv_errno), srv_errno);
                        if (!a) {
                                talloc_free(p);
                                break;
                        }
-                       
+
                        p = a;
                }
 
                if (our_err) {
-                       a = talloc_asprintf_append_buffer(p,". %s", our_err);
+                       a = talloc_asprintf_append_buffer(p, "%s. ", our_err);
                        if (!a) {
                                talloc_free(p);
                                break;
                        }
-                       
+
                        p = a;
                }
-               
+
                if (srv_err) {
-                       a = talloc_asprintf_append_buffer(p, ". Server said: %s", srv_err);
+                       a = talloc_asprintf_append_buffer(p, "Server said: %s. ", srv_err);
                        if (!a) {
                                talloc_free(p);
                                break;
                        }
-                       
+
                        p = a;
                }
-               
+
                *extra = p;
-               
+
                break;
        }
-       
+
        /*
         *      Cleanup memory
         */
        if (srv_err) {
                ldap_memfree(srv_err);
        }
-       
+
        if (part_dn) {
                ldap_memfree(part_dn);
        }
-       
-       if (our_err) {
-               talloc_free(our_err);
-       }
-       
+
+       talloc_free(our_err);
+
        if ((lib_errno || srv_errno) && *result) {
                ldap_msgfree(*result);
                *result = NULL;
        }
-       
+
        return status;
 }
 
@@ -417,93 +508,107 @@ static ldap_rcode_t rlm_ldap_result(const ldap_instance_t *inst, const ldap_hand
  * @param[in] retry if the server is down.
  * @return one of the LDAP_PROC_* values.
  */
-ldap_rcode_t rlm_ldap_bind(const ldap_instance_t *inst, REQUEST *request, ldap_handle_t **pconn, const char *dn,
-                          const char *password, int retry)
+ldap_rcode_t rlm_ldap_bind(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn, char const *dn,
+                          char const *password, bool retry)
 {
-       ldap_rcode_t    status;
-       
+       ldap_rcode_t    status = LDAP_PROC_ERROR;
+
        int             msgid;
-       
-       const char      *error = NULL;
+
+       char const      *error = NULL;
        char            *extra = NULL;
 
+       int             i, num;
+
        rad_assert(*pconn && (*pconn)->handle);
-       
+       rad_assert(!retry || inst->pool);
+
        /*
         *      Bind as anonymous user
         */
        if (!dn) dn = "";
 
-retry:
-       msgid = ldap_bind((*pconn)->handle, dn, password, LDAP_AUTH_SIMPLE);
-       /* We got a valid message ID */
-       if (msgid >= 0) {
-               if (request) {
-                       RDEBUG2("Waiting for bind result...");
-               } else {
-                       DEBUG2("rlm_ldap (%s): Waiting for bind result...", inst->xlat_name);
+       /*
+        *      For sanity, for when no connections are viable,
+        *      and we can't make a new one.
+        */
+       num = retry ? fr_connection_get_num(inst->pool) : 0;
+       for (i = num; i >= 0; i--) {
+               msgid = ldap_bind((*pconn)->handle, dn, password, LDAP_AUTH_SIMPLE);
+               /* We got a valid message ID */
+               if (msgid >= 0) {
+                       if (request) {
+                               RDEBUG2("Waiting for bind result...");
+                       } else {
+                               DEBUG2("rlm_ldap (%s): Waiting for bind result...", inst->xlat_name);
+                       }
                }
-       }
 
-       status = rlm_ldap_result(inst, *pconn, msgid, dn, NULL, &error, &extra);
-       switch (status) {
-       case LDAP_PROC_SUCCESS:
-               break;
-       case LDAP_PROC_NOT_PERMITTED:
-               LDAP_ERR_REQ("Bind was not permitted: %s", error);
-               LDAP_EXT_REQ();
-               
-               break;
+               status = rlm_ldap_result(inst, *pconn, msgid, dn, NULL, &error, &extra);
+               switch (status) {
+               case LDAP_PROC_SUCCESS:
+                       LDAP_DBG_REQ("Bind successful");
+                       break;
 
-       case LDAP_PROC_REJECT:
-               LDAP_ERR_REQ("Bind credentials incorrect: %s", error);
-               LDAP_EXT_REQ();
+               case LDAP_PROC_NOT_PERMITTED:
+                       LDAP_ERR_REQ("Bind was not permitted: %s", error);
+                       LDAP_EXT_REQ();
 
-               break;
+                       break;
 
-       case LDAP_PROC_RETRY:
-               if (retry) {
-                       *pconn = fr_connection_reconnect(inst->pool, *pconn);
-                       if (*pconn) {
-                               LDAP_DBGW_REQ("Bind with %s to %s:%d failed: %s. Got new socket, retrying...",
-                                             dn, inst->server, inst->port, error);
-                               
-                               talloc_free(extra); /* don't leak debug info */
-                               
-                               goto retry;
-                       }
-               };
-               
-               status = LDAP_PROC_ERROR;
-               
-               /*
-                *      Were not allowed to retry, or there are no more
-                *      sockets, treat this as a hard failure.
-                */
-               /* FALL-THROUGH */
-       default:
+               case LDAP_PROC_REJECT:
+                       LDAP_ERR_REQ("Bind credentials incorrect: %s", error);
+                       LDAP_EXT_REQ();
+
+                       break;
+
+               case LDAP_PROC_RETRY:
+                       if (retry) {
+                               *pconn = fr_connection_reconnect(inst->pool, *pconn);
+                               if (*pconn) {
+                                       LDAP_DBGW_REQ("Bind with %s to %s:%d failed: %s. Got new socket, retrying...",
+                                                     dn, inst->server, inst->port, error);
+
+                                       talloc_free(extra); /* don't leak debug info */
+
+                                       continue;
+                               }
+                       };
+                       status = LDAP_PROC_ERROR;
+
+                       /*
+                        *      Were not allowed to retry, or there are no more
+                        *      sockets, treat this as a hard failure.
+                        */
+                       /* FALL-THROUGH */
+               default:
 #ifdef HAVE_LDAP_INITIALIZE
-               if (inst->is_url) {
-                       LDAP_ERR_REQ("Bind with %s to %s failed: %s", dn, inst->server, error);
-               } else
+                       if (inst->is_url) {
+                               LDAP_ERR_REQ("Bind with %s to %s failed: %s", dn, inst->server, error);
+                       } else
 #endif
-               {
-                       LDAP_ERR_REQ("Bind with %s to %s:%d failed: %s", dn, inst->server,
-                                    inst->port, error);
+                       {
+                               LDAP_ERR_REQ("Bind with %s to %s:%d failed: %s", dn, inst->server,
+                                            inst->port, error);
+                       }
+                       LDAP_EXT_REQ();
+
+                       break;
                }
-               LDAP_EXT_REQ();
-               
+
                break;
        }
 
-       if (extra) {
-               talloc_free(extra);
+       if (retry && (i < 0)) {
+               LDAP_ERR_REQ("Hit reconnection limit");
+               status = LDAP_PROC_ERROR;
        }
-       
+
+       talloc_free(extra);
+
        return status; /* caller closes the connection */
 }
 
-
 /** Search for something in the LDAP directory
  *
  * Binds as the administrative user and performs a search, dealing with any errors.
@@ -519,24 +624,28 @@ retry:
  *     May be NULL in which case result will be automatically freed after use.
  * @return One of the LDAP_PROC_* values.
  */
-ldap_rcode_t rlm_ldap_search(const ldap_instance_t *inst, REQUEST *request, ldap_handle_t **pconn,
-                            const char *dn, int scope, const char *filter, const char * const *attrs,
+ldap_rcode_t rlm_ldap_search(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn,
+                            char const *dn, int scope, char const *filter, char const * const *attrs,
                             LDAPMessage **result)
 {
        ldap_rcode_t    status;
-       
+       LDAPMessage     *our_result = NULL;
+
        int             msgid;          // Message id returned by
                                        // ldap_search_ext.
-                               
+
        int             count = 0;      // Number of results we got.
-       
+
        struct timeval  tv;             // Holds timeout values.
-       
-       const char      *error = NULL;
+
+       char const      *error = NULL;
        char            *extra = NULL;
 
+       int             i;
+
+
        rad_assert(*pconn && (*pconn)->handle);
-       
+
        /*
         *      OpenLDAP library doesn't declare attrs array as const, but
         *      it really should be *sigh*.
@@ -548,70 +657,106 @@ ldap_rcode_t rlm_ldap_search(const ldap_instance_t *inst, REQUEST *request, ldap
         *      Do all searches as the admin user.
         */
        if ((*pconn)->rebound) {
-               status = rlm_ldap_bind(inst, request, pconn, inst->login, inst->password, TRUE);
+               status = rlm_ldap_bind(inst, request, pconn, inst->admin_dn, inst->password, true);
                if (status != LDAP_PROC_SUCCESS) {
                        return LDAP_PROC_ERROR;
                }
 
                rad_assert(*pconn);
-               
-               (*pconn)->rebound = FALSE;
-       }
 
-       RDEBUG2("Performing search in '%s' with filter '%s'", dn, filter);
+               (*pconn)->rebound = false;
+       }
 
+       if (filter) {
+               LDAP_DBG_REQ("Performing search in '%s' with filter '%s', scope '%s'", dn, filter,
+                            fr_int2str(ldap_scope, scope, "<INVALID>"));
+       } else {
+               LDAP_DBG_REQ("Performing unfiltered search in '%s', scope '%s'", dn,
+                            fr_int2str(ldap_scope, scope, "<INVALID>"));
+       }
        /*
         *      If LDAP search produced an error it should also be logged
         *      to the ld. result should pick it up without us
         *      having to pass it explicitly.
         */
-       tv.tv_sec = inst->timeout;
-       tv.tv_usec = 0;
-retry: 
-       (void) ldap_search_ext((*pconn)->handle, dn, scope, filter, search_attrs, 0, NULL, NULL, &tv, 0, &msgid);
+       memset(&tv, 0, sizeof(tv));
+       tv.tv_sec = inst->res_timeout;
 
-       RDEBUG2("Waiting for search result...");               
-       status = rlm_ldap_result(inst, *pconn, msgid, dn, result, &error, &extra);                     
-       switch (status) {
+       /*
+        *      For sanity, for when no connections are viable,
+        *      and we can't make a new one.
+        */
+       for (i = fr_connection_get_num(inst->pool); i >= 0; i--) {
+               (void) ldap_search_ext((*pconn)->handle, dn, scope, filter, search_attrs,
+                                      0, NULL, NULL, &tv, 0, &msgid);
+
+               LDAP_DBG_REQ("Waiting for search result...");
+               status = rlm_ldap_result(inst, *pconn, msgid, dn, &our_result, &error, &extra);
+               switch (status) {
                case LDAP_PROC_SUCCESS:
                        break;
+
                case LDAP_PROC_RETRY:
                        *pconn = fr_connection_reconnect(inst->pool, *pconn);
                        if (*pconn) {
-                               RDEBUGW("Search failed: %s. Got new socket, retrying...", error);
-                               
+                               LDAP_DBGW_REQ("Search failed: %s. Got new socket, retrying...", error);
+
                                talloc_free(extra); /* don't leak debug info */
-                               
-                               goto retry;
+
+                               continue;
                        }
-                       
+
                        status = LDAP_PROC_ERROR;
-                       
+
                        /* FALL-THROUGH */
                default:
-                       RDEBUGE("Failed performing search: %s", error);
-                       RDEBUGE("%s", extra);
+                       LDAP_ERR_REQ("Failed performing search: %s", error);
+                       if (extra) LDAP_ERR_REQ("%s", extra);
 
                        goto finish;
-       }
-       
-       if (result) {   
-               count = ldap_count_entries((*pconn)->handle, *result);
-               if (count == 0) {
-                       ldap_msgfree(*result);
-                       *result = NULL;
-               
-                       RDEBUG("Search returned no results");
-               
-                       status = LDAP_PROC_NO_RESULT;
                }
+
+               break;
        }
-       
-       finish:
-       if (extra) {
-               talloc_free(extra);
+
+       if (i < 0) {
+               LDAP_ERR_REQ("Hit reconnection limit");
+               status = LDAP_PROC_ERROR;
+
+               goto finish;
        }
-       
+
+       count = ldap_count_entries((*pconn)->handle, our_result);
+       if (count < 0) {
+               LDAP_ERR_REQ("Error counting results: %s", rlm_ldap_error_str(*pconn));
+               status = LDAP_PROC_ERROR;
+
+               ldap_msgfree(our_result);
+               our_result = NULL;
+       } else if (count == 0) {
+               LDAP_DBG_REQ("Search returned no results");
+               status = LDAP_PROC_NO_RESULT;
+
+               ldap_msgfree(our_result);
+               our_result = NULL;
+       }
+
+finish:
+       talloc_free(extra);
+
+       /*
+        *      We always need to get the result to count entries, but the caller
+        *      may not of requested one. If that's the case, free it, else write
+        *      it to where our caller said.
+        */
+       if (!result) {
+               if (our_result) {
+                       ldap_msgfree(our_result);
+               }
+       } else {
+               *result = our_result;
+       }
+
        return status;
 }
 
@@ -626,66 +771,79 @@ retry:
  * @param[in] mods to make, see 'man ldap_modify' for more information.
  * @return One of the LDAP_PROC_* values.
  */
-ldap_rcode_t rlm_ldap_modify(const ldap_instance_t *inst, REQUEST *request, ldap_handle_t **pconn,
-                            const char *dn, LDAPMod *mods[])
+ldap_rcode_t rlm_ldap_modify(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn,
+                            char const *dn, LDAPMod *mods[])
 {
        ldap_rcode_t    status;
-       
+
        int             msgid;          // Message id returned by ldap_search_ext.
-       
-       const char      *error = NULL;
-       char            *extra = NULL;                     
+
+       char const      *error = NULL;
+       char            *extra = NULL;
+
+       int             i;
 
        rad_assert(*pconn && (*pconn)->handle);
-               
+
        /*
         *      Perform all modifications as the admin user.
         */
        if ((*pconn)->rebound) {
-               status = rlm_ldap_bind(inst, request, pconn, inst->login, inst->password, TRUE);
+               status = rlm_ldap_bind(inst, request, pconn, inst->admin_dn, inst->password, true);
                if (status != LDAP_PROC_SUCCESS) {
                        return LDAP_PROC_ERROR;
                }
 
                rad_assert(*pconn);
-               
-               (*pconn)->rebound = FALSE;
+
+               (*pconn)->rebound = false;
        }
-       
-       RDEBUG2("Modifying object with DN \"%s\"", dn);
-       retry:
-       (void) ldap_modify_ext((*pconn)->handle, dn, mods, NULL, NULL, &msgid);
-       
-       RDEBUG2("Waiting for modify result...");
-       status = rlm_ldap_result(inst, *pconn, msgid, dn, NULL, &error, &extra);
-       switch (status) {
-               case LDAP_PROC_SUCCESS:
-                       break;
-               case LDAP_PROC_RETRY:
-                       *pconn = fr_connection_reconnect(inst->pool, *pconn);
-                       if (*pconn) {
-                               RDEBUGW("Modify failed: %s. Got new socket, retrying...", error);
-                               
-                               talloc_free(extra); /* don't leak debug info */
-                               
-                               goto retry;
-                       }
-                       
-                       status = LDAP_PROC_ERROR;
-                       
-                       /* FALL-THROUGH */
-               default:
-                       RDEBUGE("Failed modifying object: %s", error);
-                       RDEBUGE("%s", extra);
-                       
-                       goto finish;
-       }                    
-       
-       finish:
-       if (extra) {
-               talloc_free(extra);
+
+       /*
+        *      For sanity, for when no connections are viable,
+        *      and we can't make a new one.
+        */
+       for (i = fr_connection_get_num(inst->pool); i >= 0; i--) {
+               RDEBUG2("Modifying object with DN \"%s\"", dn);
+               (void) ldap_modify_ext((*pconn)->handle, dn, mods, NULL, NULL, &msgid);
+
+               RDEBUG2("Waiting for modify result...");
+               status = rlm_ldap_result(inst, *pconn, msgid, dn, NULL, &error, &extra);
+               switch (status) {
+                       case LDAP_PROC_SUCCESS:
+                               break;
+
+                       case LDAP_PROC_RETRY:
+                               *pconn = fr_connection_reconnect(inst->pool, *pconn);
+                               if (*pconn) {
+                                       RWDEBUG("Modify failed: %s. Got new socket, retrying...", error);
+
+                                       talloc_free(extra); /* don't leak debug info */
+
+                                       continue;
+                               }
+
+                               status = LDAP_PROC_ERROR;
+
+                               /* FALL-THROUGH */
+                       default:
+                               REDEBUG("Failed modifying object: %s", error);
+                               REDEBUG("%s", extra);
+
+                               goto finish;
+               }
+
+               break;
        }
-       
+
+       if (i < 0) {
+               LDAP_ERR_REQ("Hit reconnection limit");
+               status = LDAP_PROC_ERROR;
+       }
+
+finish:
+       talloc_free(extra);
+
        return status;
 }
 
@@ -696,7 +854,7 @@ ldap_rcode_t rlm_ldap_modify(const ldap_instance_t *inst, REQUEST *request, ldap
  *
  * This potentially allows for all authorization and authentication checks to be performed in one ldap search
  * operation, which is a big bonus given the number of crappy, slow *cough*AD*cough* LDAP directory servers out there.
- * 
+ *
  * @param[in] inst rlm_ldap configuration.
  * @param[in] request Current request.
  * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect.
@@ -706,34 +864,34 @@ ldap_rcode_t rlm_ldap_modify(const ldap_instance_t *inst, REQUEST *request, ldap
  * @param[out] rcode The status of the operation, one of the RLM_MODULE_* codes.
  * @return The user's DN or NULL on error.
  */
-const char *rlm_ldap_find_user(const ldap_instance_t *inst, REQUEST *request, ldap_handle_t **pconn,
-                              const char *attrs[], int force, LDAPMessage **result, rlm_rcode_t *rcode)
+char const *rlm_ldap_find_user(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn,
+                              char const *attrs[], int force, LDAPMessage **result, rlm_rcode_t *rcode)
 {
-       static const char *tmp_attrs[] = { NULL };
-       
+       static char const *tmp_attrs[] = { NULL };
+
        ldap_rcode_t    status;
        VALUE_PAIR      *vp = NULL;
        LDAPMessage     *tmp_msg = NULL, *entry = NULL;
        int             ldap_errno;
        char            *dn = NULL;
-       char            filter[LDAP_MAX_FILTER_STR_LEN];        
-       char            basedn[LDAP_MAX_FILTER_STR_LEN];
-       
-       int freeit = FALSE;                                     //!< Whether the message should
+       char            filter[LDAP_MAX_FILTER_STR_LEN];
+       char            base_dn[LDAP_MAX_DN_STR_LEN];
+
+       bool freeit = false;                                    //!< Whether the message should
                                                                //!< be freed after being processed.
 
        *rcode = RLM_MODULE_FAIL;
 
        if (!result) {
                result = &tmp_msg;
-               freeit = TRUE;
+               freeit = true;
        }
        *result = NULL;
-       
+
        if (!attrs) {
                memset(&attrs, 0, sizeof(tmp_attrs));
        }
-       
+
        /*
         *      If the caller isn't looking for the result we can just return the current userdn value.
         */
@@ -745,79 +903,80 @@ const char *rlm_ldap_find_user(const ldap_instance_t *inst, REQUEST *request, ld
                        return vp->vp_strvalue;
                }
        }
-       
+
        /*
         *      Perform all searches as the admin user.
         */
        if ((*pconn)->rebound) {
-               status = rlm_ldap_bind(inst, request, pconn, inst->login, inst->password, TRUE);
+               status = rlm_ldap_bind(inst, request, pconn, inst->admin_dn, inst->password, true);
                if (status != LDAP_PROC_SUCCESS) {
                        *rcode = RLM_MODULE_FAIL;
                        return NULL;
                }
 
                rad_assert(*pconn);
-               
-               (*pconn)->rebound = FALSE;
+
+               (*pconn)->rebound = false;
        }
 
-       
-       if (!radius_xlat(filter, sizeof(filter), inst->userobj_filter, request, rlm_ldap_escape_func, NULL)) {
-               RDEBUGE("Unable to create filter");
-               
+       if (radius_xlat(filter, sizeof(filter), request, inst->userobj_filter, rlm_ldap_escape_func, NULL) < 0) {
+               REDEBUG("Unable to create filter");
                *rcode = RLM_MODULE_INVALID;
+
                return NULL;
        }
 
-       if (!radius_xlat(basedn, sizeof(basedn), inst->basedn, request, rlm_ldap_escape_func, NULL)) {
-               RDEBUGE("Unable to create basedn");
-               
+       if (radius_xlat(base_dn, sizeof(base_dn), request, inst->userobj_base_dn, rlm_ldap_escape_func, NULL) < 0) {
+               REDEBUG("Unable to create base_dn");
                *rcode = RLM_MODULE_INVALID;
+
                return NULL;
        }
 
-       status = rlm_ldap_search(inst, request, pconn, basedn, LDAP_SCOPE_SUBTREE, filter, attrs, result);
+       status = rlm_ldap_search(inst, request, pconn, base_dn, inst->userobj_scope, filter, attrs, result);
        switch (status) {
                case LDAP_PROC_SUCCESS:
                        break;
+
                case LDAP_PROC_NO_RESULT:
                        *rcode = RLM_MODULE_NOTFOUND;
                        return NULL;
+
                default:
                        *rcode = RLM_MODULE_FAIL;
                        return NULL;
        }
-       
+
        rad_assert(*pconn);
 
        entry = ldap_first_entry((*pconn)->handle, *result);
        if (!entry) {
                ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
-               RDEBUGE("Failed retrieving entry: %s", 
+               REDEBUG("Failed retrieving entry: %s",
                        ldap_err2string(ldap_errno));
-                        
+
                goto finish;
        }
 
        dn = ldap_get_dn((*pconn)->handle, entry);
        if (!dn) {
                ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
-                               
-               RDEBUGE("Retrieving object DN from entry failed: %s",
+
+               REDEBUG("Retrieving object DN from entry failed: %s",
                        ldap_err2string(ldap_errno));
-                      
+
                goto finish;
        }
-       
+
        RDEBUG("User object found at DN \"%s\"", dn);
        vp = pairmake(request, &request->config_items, "LDAP-UserDN", dn, T_OP_EQ);
-       if (vp) {       
+       if (vp) {
                *rcode = RLM_MODULE_OK;
        }
-       
+
        finish:
        ldap_memfree(dn);
-       
+
        if ((freeit || (*rcode != RLM_MODULE_OK)) && *result) {
                ldap_msgfree(*result);
                *result = NULL;
@@ -826,316 +985,6 @@ const char *rlm_ldap_find_user(const ldap_instance_t *inst, REQUEST *request, ld
        return vp ? vp->vp_strvalue : NULL;
 }
 
-/** Convert multiple group names into a DNs
- * 
- * Given an array of group names, builds a filter matching all names, then retrieves all group objects
- * and stores the DN associated with each group object.
- *
- * @param[in] inst rlm_ldap configuration.
- * @param[in] request Current request.
- * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect.
- * @param[in] names to covert to DNs (NULL terminated).
- * @param[out] out Where to write the DNs. DNs must be freed with ldap_memfree(). Will be NULL terminated.
- * @param[in] outlen Size of out.
- * @return One of the RLM_MODULE_* values.
- */
-rlm_rcode_t rlm_ldap_group_name2dn(const ldap_instance_t *inst, REQUEST *request,
-                                  ldap_handle_t **pconn, char **names, char **out,
-                                  size_t outlen)
-{
-       rlm_rcode_t rcode;
-       ldap_rcode_t status;
-       int ldap_errno;
-       
-       unsigned int name_cnt = 0;
-       unsigned int entry_cnt;
-       const char *attrs[] = { NULL };
-
-       LDAPMessage *result = NULL, *entry;
-
-       char **name = names;
-       char **dn = out;
-       char buffer[LDAP_MAX_GROUP_NAME_LEN + 1];
-       
-       char *filter;
-       
-       *dn = NULL;
-       
-       if (!*names) {
-               return RLM_MODULE_OK;
-       }
-       
-       if (!inst->groupobj_name_attr) {
-               RDEBUGE("Told to convert group names to DNs but missing 'group.name_attribute' directive");
-               
-               return RLM_MODULE_INVALID;
-       }
-
-       /*
-        *      It'll probably only save a few ms in network latency, but it means we can send a query
-        *      for the entire group list at once.
-        */
-       filter = talloc_asprintf(request, "(&(%s)(|(", inst->base_filter);
-       while (*name) {
-               rlm_ldap_escape_func(request, buffer, sizeof(buffer), *++name, NULL);
-               filter = talloc_asprintf_append_buffer(filter, "(%s=%s)", inst->groupobj_name_attr, buffer);
-               
-               entry_cnt++;
-       }
-       filter = talloc_strdup_append_buffer(filter, "))");
-       
-       status = rlm_ldap_search(inst, request, pconn, inst->basedn, LDAP_SCOPE_SUB, filter, attrs, &result);
-       switch (status) {
-               case LDAP_PROC_SUCCESS:
-                       break;
-               case LDAP_PROC_NO_RESULT:
-                       rcode = RLM_MODULE_INVALID;
-                       goto finish;
-               default:
-                       rcode = RLM_MODULE_FAIL;
-                       goto finish;
-       }
-       
-       entry_cnt = ldap_count_entries((*pconn)->handle, result);
-       if (entry_cnt > name_cnt) {
-               RDEBUGE("Number of DNs exceeds number of names, base_dn or base_filter should be more restrictive");
-               rcode = RLM_MODULE_INVALID;
-               
-               goto finish;
-       }
-       
-       if (entry_cnt > (outlen - 1)) {
-               RDEBUGE("Number of DNs exceeds limit (%i)", outlen - 1);
-               rcode = RLM_MODULE_INVALID;
-               
-               goto finish;
-       }
-       
-       if (entry_cnt < name_cnt) {
-               RDEBUGW("Got partial mapping of group names to DNs, membership information may be incomplete");
-       }
-       
-       entry = ldap_first_entry((*pconn)->handle, result);
-       if (!entry) {
-               ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
-               RDEBUGE("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
-                       
-               rcode = RLM_MODULE_INVALID;      
-               goto finish;
-       }
-       
-       do {
-               *dn = ldap_get_dn((*pconn)->handle, entry);     
-       } while((entry = ldap_next_entry((*pconn)->handle, entry)));
-       
-       *dn = NULL;
-       
-       finish:
-       talloc_free(filter);
-       if (result) {
-               ldap_msgfree(result);
-       }
-       
-       /*
-        *      Be nice and cleanup the output array if we error out.
-        */
-       if (rcode != RLM_MODULE_OK) {
-               dn = out;
-               while(*dn) ldap_memfree(*dn++);
-               *dn = NULL;
-       }
-       
-       return status;
-}
-
-/** Convert a single group name into a DN
- *
- * Unlike the inverse conversion of a name to a DN, most LDAP directories don't allow filtering by DN,
- * so we need to search for each DN individually.
- *
- * @param[in] inst rlm_ldap configuration.
- * @param[in] request Current request.
- * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect.
- * @param[in] dn to resolve.
- * @param[out] out Where to write group name (must be freed with ldap_memfree()).
- * @return One of the RLM_MODULE_* values.
- */
-rlm_rcode_t rlm_ldap_group_dn2name(const ldap_instance_t *inst, REQUEST *request, ldap_handle_t **pconn,
-                                  const char *dn, char **out)
-{
-       rlm_rcode_t rcode;
-       ldap_rcode_t status;
-       int ldap_errno;
-       
-       char **vals;
-       const char *attrs[] = { inst->groupobj_name_attr, NULL };
-       LDAPMessage *result = NULL, *entry;
-       
-       *out = NULL;
-       
-       if (!inst->groupobj_name_attr) {
-               RDEBUGE("Told to convert group DN to name but missing 'group.name_attribute' directive");
-               
-               return RLM_MODULE_INVALID;
-       }
-       
-       status = rlm_ldap_search(inst, request, pconn, dn, LDAP_SCOPE_BASE, inst->base_filter, attrs,
-                                &result); 
-       switch (status) {
-               case LDAP_PROC_SUCCESS:
-                       break;
-               case LDAP_PROC_NO_RESULT:
-                       return RLM_MODULE_INVALID;
-               default:
-                       return RLM_MODULE_FAIL;
-       }
-       
-       entry = ldap_first_entry((*pconn)->handle, result);
-       if (!entry) {
-               ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
-               RDEBUGE("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
-                       
-               rcode = RLM_MODULE_INVALID;      
-               goto finish;
-       }
-
-       vals = ldap_get_values((*pconn)->handle, entry, inst->groupobj_name_attr);
-       if (!vals) {
-               rcode = RLM_MODULE_INVALID;
-               goto finish;
-       }
-       
-       *out = *vals;
-       
-       finish:
-       if (result) {
-               ldap_msgfree(result);
-       }
-       
-       if (vals) {
-               ldap_value_free(vals);        
-       }
-       
-       return rcode;
-}
-
-/** Convert group membership information into attributes
- *
- * @param[in] inst rlm_ldap configuration.
- * @param[in] request Current request.
- * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect.
- * @param[in] entry retrieved by rlm_ldap_find_user or rlm_ldap_search.
- * @return One of the RLM_MODULE_* values.
- */
-rlm_rcode_t rlm_ldap_cacheable_membership(const ldap_instance_t *inst, REQUEST *request, ldap_handle_t **pconn,
-                                         LDAPMessage *entry)
-{
-       rlm_rcode_t rcode;
-       char **vals;
-
-       char *group_name[LDAP_MAX_CACHEABLE + 1];
-       char **name_p = group_name;
-
-       char *group_dn[LDAP_MAX_CACHEABLE + 1];
-       char **dn_p;
-       
-       char *name;
-       
-       int is_dn;
-       int i;
-
-       if (!inst->cacheable_group_dn && !inst->cacheable_group_name) {
-               return RLM_MODULE_OK;
-       }
-       
-       /*
-        *      Group membership apparently isn't stored in user objects, so jump straight to resolving groups
-        *      with the group membership filter.
-        */
-       if (!inst->userobj_membership_attr) {
-               goto skip_userobj;
-       }
-       
-       /*
-        *      Parse the membership information we got in the initial user query.
-        */
-       vals = ldap_get_values((*pconn)->handle, entry, inst->userobj_membership_attr);
-       if (!vals) {
-               goto skip_userobj;
-       }
-
-       for (i = 0; (vals[i] != NULL) && (i < LDAP_MAX_CACHEABLE); i++) {
-               is_dn = rlm_ldap_is_dn(vals[i]);
-               
-               if (inst->cacheable_group_dn) {
-                       /*
-                        *      The easy case, were caching DNs and we got a DN.
-                        */
-                       if (is_dn) {
-                               pairmake(request, &request->config_items, "LDAP-GroupDN", vals[i], T_OP_ADD);
-                               RDEBUG3("Added LDAP-GroupDN with value \"%s\" to control list", vals[i]);
-                               
-                       /*
-                        *      We were told to cache DNs but we got a name, we now need to resolve this to a DN.
-                        *      Store all the group names in an array so we can do one query.
-                        */
-                       } else {
-                               *name_p++ = vals[i];
-                       }
-               }
-               
-               if (inst->cacheable_group_name) {
-                       /*
-                        *      The easy case, were caching names and we got a name.
-                        */
-                       if (!is_dn) {
-                               pairmake(request, &request->config_items, "LDAP-Group", vals[i], T_OP_ADD);
-                               RDEBUG3("Added LDAP-Group with value \"%s\" to control list", vals[i]);
-                       /*
-                        *      We were told to cache names but we got a DN, we now need to resolve this to a name.
-                        *      Only Active Directory supports filtering on DN, so we have to search for each
-                        *      individual group.
-                        */
-                       } else {
-                               rcode = rlm_ldap_group_dn2name(inst, request, pconn, vals[i], &name);
-                               if (rcode != RLM_MODULE_OK) {
-                                       ldap_value_free(vals);
-                                       
-                                       return rcode;
-                               }
-                               
-                               pairmake(request, &request->config_items, "LDAP-Group", name, T_OP_ADD);
-                               RDEBUG3("Added LDAP-Group with value \"%s\" to control list", name);
-                               ldap_memfree(name);
-                       }
-               }
-       }
-       *name_p = NULL;
-       
-       rcode = rlm_ldap_group_name2dn(inst, request, pconn, group_name, group_dn, sizeof(group_dn));
-       
-       ldap_value_free(vals);
-       
-       if (rcode != RLM_MODULE_OK) {
-               return rcode;
-       }
-       
-       dn_p = group_dn;
-       while(*dn_p) {
-               pairmake(request, &request->config_items, "LDAP-GroupDN", *dn_p, T_OP_ADD);
-               RDEBUG3("Added LDAP-GroupDN with value \"%s\" to control list", *dn_p);
-               ldap_memfree(*dn_p);
-               
-               dn_p++;
-       }
-
-       skip_userobj:
-       
-       /* @todo add code to search for groups with this user as a member and add them to control list */
-       
-       return rcode;
-}              
-
 /** Check for presence of access attribute in result
  *
  * @param[in] inst rlm_ldap configuration.
@@ -1144,18 +993,22 @@ rlm_rcode_t rlm_ldap_cacheable_membership(const ldap_instance_t *inst, REQUEST *
  * @param[in] entry retrieved by rlm_ldap_find_user or rlm_ldap_search.
  * @return RLM_MODULE_USERLOCK if the user was denied access, else RLM_MODULE_OK.
  */
-rlm_rcode_t rlm_ldap_check_access(const ldap_instance_t *inst, REQUEST *request,
-                                 const ldap_handle_t *conn, LDAPMessage *entry)
+rlm_rcode_t rlm_ldap_check_access(ldap_instance_t const *inst, REQUEST *request,
+                                 ldap_handle_t const *conn, LDAPMessage *entry)
 {
        rlm_rcode_t rcode = RLM_MODULE_OK;
        char **vals = NULL;
 
        vals = ldap_get_values(conn->handle, entry, inst->userobj_access_attr);
        if (vals) {
-               if (inst->access_positive && (strncmp(vals[0], "FALSE", 5) == 0)) {
-                       RDEBUG("\"%s\" attribute exists but is set to 'false' - user locked out");
-                       rcode = RLM_MODULE_USERLOCK;
-               } else {
+               if (inst->access_positive) {
+                       if (strncasecmp(vals[0], "false", 5) == 0) {
+                               RDEBUG("\"%s\" attribute exists but is set to 'false' - user locked out",
+                                      inst->userobj_access_attr);
+                               rcode = RLM_MODULE_USERLOCK;
+                       }
+                       /* RLM_MODULE_OK set above... */
+               } else if (strncasecmp(vals[0], "false", 5) != 0) {
                        RDEBUG("\"%s\" attribute exists - user locked out", inst->userobj_access_attr);
                        rcode = RLM_MODULE_USERLOCK;
                }
@@ -1176,7 +1029,7 @@ rlm_rcode_t rlm_ldap_check_access(const ldap_instance_t *inst, REQUEST *request,
  * @param inst rlm_ldap configuration.
  * @param request Current request.
  */
-void rlm_ldap_check_reply(const ldap_instance_t *inst, REQUEST *request)
+void rlm_ldap_check_reply(ldap_instance_t const *inst, REQUEST *request)
 {
        /*
        *       More warning messages for people who can't be bothered to read the documentation.
@@ -1190,9 +1043,9 @@ void rlm_ldap_check_reply(const ldap_instance_t *inst, REQUEST *request)
                    !pairfind(request->config_items, PW_USER_PASSWORD, 0, TAG_ANY) &&
                    !pairfind(request->config_items, PW_PASSWORD_WITH_HEADER, 0, TAG_ANY) &&
                    !pairfind(request->config_items, PW_CRYPT_PASSWORD, 0, TAG_ANY)) {
-                       RDEBUGW("No \"reference\" password added. Ensure the admin user has permission to "
+                       RWDEBUG("No \"known good\" password added. Ensure the admin user has permission to "
                                "read the password attribute");
-                       RDEBUGW("PAP authentication will *NOT* work with Active Directory (if that is what you "
+                       RWDEBUG("PAP authentication will *NOT* work with Active Directory (if that is what you "
                                "were trying to configure)");
                }
        }
@@ -1213,51 +1066,79 @@ static int rlm_ldap_rebind(LDAP *handle, LDAP_CONST char *url, UNUSED ber_tag_t
                           void *ctx)
 {
        ldap_rcode_t status;
-       ldap_handle_t *conn = ctx;
-       
+       ldap_handle_t *conn = talloc_get_type_abort(ctx, ldap_handle_t);
+
        int ldap_errno;
 
-       conn->referred = TRUE;
-       conn->rebound = TRUE;   /* not really, but oh well... */
+       conn->referred = true;
+       conn->rebound = true;   /* not really, but oh well... */
        rad_assert(handle == conn->handle);
 
        DEBUG("rlm_ldap (%s): Rebinding to URL %s", conn->inst->xlat_name, url);
 
-       status = rlm_ldap_bind(conn->inst, NULL, &conn, conn->inst->login, conn->inst->password, FALSE);
+       status = rlm_ldap_bind(conn->inst, NULL, &conn, conn->inst->admin_dn, conn->inst->password, false);
        if (status != LDAP_PROC_SUCCESS) {
                ldap_get_option(handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno);
-                       
+
                return ldap_errno;
        }
-       
+
 
        return LDAP_SUCCESS;
 }
 #endif
 
+/** Close and delete a connection
+ *
+ * Unbinds the LDAP connection, informing the server and freeing any memory, then releases the memory used by the
+ * connection handle.
+ *
+ * @param conn to destroy.
+ * @return always indicates success.
+ */
+static int _mod_conn_free(ldap_handle_t *conn)
+{
+       DEBUG3("rlm_ldap: Closing libldap handle %p", conn->handle);
+
+       if (conn->handle) ldap_unbind_s(conn->handle);
+
+       return 0;
+}
+
 /** Create and return a new connection
  *
  * Create a new ldap connection and allocate memory for a new rlm_handle_t
  *
- * @param ctx rlm_ldap instance.
+ * @param ctx to allocate connection handle memory in.
+ * @param instance rlm_ldap instance.
  * @return A new connection handle or NULL on error.
  */
-void *rlm_ldap_conn_create(void *ctx)
+void *mod_conn_create(TALLOC_CTX *ctx, void *instance)
 {
        ldap_rcode_t status;
-       
+
        int ldap_errno, ldap_version;
        struct timeval tv;
-       
-       ldap_instance_t *inst = ctx;
-       LDAP *handle = NULL;
-       ldap_handle_t *conn = NULL;
+
+       ldap_instance_t *inst = instance;
+       ldap_handle_t *conn;
+
+       /*
+        *      Allocate memory for the handle.
+        */
+       conn = talloc_zero(ctx, ldap_handle_t);
+       if (!conn) return NULL;
+       talloc_set_destructor(conn, _mod_conn_free);
+
+       conn->inst = inst;
+       conn->rebound = false;
+       conn->referred = false;
 
 #ifdef HAVE_LDAP_INITIALIZE
        if (inst->is_url) {
                DEBUG("rlm_ldap (%s): Connecting to %s", inst->xlat_name, inst->server);
-               
-               ldap_errno = ldap_initialize(&handle, inst->server);
+
+               ldap_errno = ldap_initialize(&conn->handle, inst->server);
                if (ldap_errno != LDAP_SUCCESS) {
                        LDAP_ERR("ldap_initialize failed: %s", ldap_err2string(ldap_errno));
                        goto error;
@@ -1267,12 +1148,13 @@ void *rlm_ldap_conn_create(void *ctx)
        {
                DEBUG("rlm_ldap (%s): Connecting to %s:%d", inst->xlat_name, inst->server, inst->port);
 
-               handle = ldap_init(inst->server, inst->port);
-               if (!handle) {
+               conn->handle = ldap_init(inst->server, inst->port);
+               if (!conn->handle) {
                        LDAP_ERR("ldap_init() failed");
                        goto error;
                }
        }
+       DEBUG3("rlm_ldap: New libldap handle %p", conn->handle);
 
        /*
         *      We now have a connection structure, but no actual TCP connection.
@@ -1280,28 +1162,39 @@ void *rlm_ldap_conn_create(void *ctx)
         *      Set a bunch of LDAP options, using common code.
         */
 #define do_ldap_option(_option, _name, _value) \
-       if (ldap_set_option(handle, _option, _value) != LDAP_OPT_SUCCESS) { \
-               ldap_get_option(handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno); \
+       if (ldap_set_option(conn->handle, _option, _value) != LDAP_OPT_SUCCESS) { \
+               ldap_get_option(conn->handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno); \
                LDAP_ERR("Could not set %s: %s", _name, ldap_err2string(ldap_errno)); \
        }
-               
+
+#define do_ldap_global_option(_option, _name, _value) \
+       if (ldap_set_option(NULL, _option, _value) != LDAP_OPT_SUCCESS) { \
+               ldap_get_option(conn->handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno); \
+               LDAP_ERR("Could not set %s: %s", _name, ldap_err2string(ldap_errno)); \
+       }
+
+
        if (inst->ldap_debug) {
-               do_ldap_option(LDAP_OPT_DEBUG_LEVEL, "ldap_debug", &(inst->ldap_debug));
+               do_ldap_global_option(LDAP_OPT_DEBUG_LEVEL, "ldap_debug", &(inst->ldap_debug));
+       }
+
+       /*
+        *      Leave "dereference" unset to use the OpenLDAP default.
+        */
+       if (inst->dereference_str) {
+               do_ldap_option(LDAP_OPT_DEREF, "dereference", &(inst->dereference));
        }
 
        /*
         *      Leave "chase_referrals" unset to use the OpenLDAP default.
         */
-       if (inst->chase_referrals != 2) {
+       if (!inst->chase_referrals_unset) {
                if (inst->chase_referrals) {
                        do_ldap_option(LDAP_OPT_REFERRALS, "chase_referrals", LDAP_OPT_ON);
-                       
-                       if (inst->rebind == 1) {
+
+                       if (inst->rebind == true) {
 #if LDAP_SET_REBIND_PROC_ARGS == 3
-                               ldap_set_rebind_proc(handle, rlm_ldap_rebind, inst);
-#else
-                               DEBUGW("The flag 'rebind = yes' is not supported by the system LDAP library. "
-                                      "Ignoring.");
+                               ldap_set_rebind_proc(conn->handle, rlm_ldap_rebind, conn);
 #endif
                        }
                } else {
@@ -1309,11 +1202,16 @@ void *rlm_ldap_conn_create(void *ctx)
                }
        }
 
-       tv.tv_sec = inst->net_timeout;
-       tv.tv_usec = 0;
-       do_ldap_option(LDAP_OPT_NETWORK_TIMEOUT, "net_timeout", &tv);
+#ifdef LDAP_OPT_NETWORK_TIMEOUT
+       if (inst->net_timeout) {
+               memset(&tv, 0, sizeof(tv));
+               tv.tv_sec = inst->net_timeout;
+
+               do_ldap_option(LDAP_OPT_NETWORK_TIMEOUT, "net_timeout", &tv);
+       }
+#endif
 
-       do_ldap_option(LDAP_OPT_TIMELIMIT, "timelimit", &(inst->timelimit));
+       do_ldap_option(LDAP_OPT_TIMELIMIT, "srv_timelimit", &(inst->srv_timelimit));
 
        ldap_version = LDAP_VERSION3;
        do_ldap_option(LDAP_OPT_PROTOCOL_VERSION, "ldap_version", &ldap_version);
@@ -1341,36 +1239,47 @@ void *rlm_ldap_conn_create(void *ctx)
 #  define maybe_ldap_option(_option, _name, _value) \
        if (_value) do_ldap_option(_option, _name, _value)
 
-       maybe_ldap_option(LDAP_OPT_X_TLS_CACERTFILE, "cacertfile", inst->tls_cacertfile);
-       maybe_ldap_option(LDAP_OPT_X_TLS_CACERTDIR, "cacertdir", inst->tls_cacertdir);
+       maybe_ldap_option(LDAP_OPT_X_TLS_CACERTFILE, "ca_file", inst->tls_ca_file);
+       maybe_ldap_option(LDAP_OPT_X_TLS_CACERTDIR, "ca_path", inst->tls_ca_path);
 
-#  ifdef HAVE_LDAP_INT_TLS_CONFIG
-       if (ldap_int_tls_config(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, inst->tls_require_cert) != LDAP_OPT_SUCCESS) {
-               ldap_get_option(handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno);
-               
-               LDAP_ERR("Could not set LDAP_OPT_X_TLS_REQUIRE_CERT option to %s: %s", inst->tls_require_cert,
-                        ldap_err2string(ldap_errno));
+
+       /*
+        *      Set certificate options
+        */
+       maybe_ldap_option(LDAP_OPT_X_TLS_CERTFILE, "certificate_file", inst->tls_certificate_file);
+       maybe_ldap_option(LDAP_OPT_X_TLS_KEYFILE, "private_key_file", inst->tls_private_key_file);
+       maybe_ldap_option(LDAP_OPT_X_TLS_RANDOM_FILE, "random_file", inst->tls_random_file);
+
+#  ifdef LDAP_OPT_X_TLS_NEVER
+       if (inst->tls_require_cert_str) {
+               do_ldap_option(LDAP_OPT_X_TLS_REQUIRE_CERT, "require_cert", &inst->tls_require_cert);
        }
 #  endif
 
        /*
-        *      Set certificate options
+        *      Counter intuitively the TLS context appears to need to be initialised
+        *      after all the TLS options are set on the handle.
         */
-       maybe_ldap_option(LDAP_OPT_X_TLS_CERTFILE, "certfile", inst->tls_certfile);
-       maybe_ldap_option(LDAP_OPT_X_TLS_KEYFILE, "keyfile", inst->tls_keyfile);
-       maybe_ldap_option(LDAP_OPT_X_TLS_RANDOM_FILE, "randfile", inst->tls_randfile);
+#  ifdef LDAP_OPT_X_TLS_NEWCTX
+       {
+               /* Always use the new TLS configuration context */
+               int is_server = 0;
+               do_ldap_option(LDAP_OPT_X_TLS_NEWCTX, "new TLS context", &is_server);
+
+       }
+#  endif
 
        /*
         *      And finally start the TLS code.
         */
        if (inst->start_tls) {
                if (inst->port == 636) {
-                       DEBUGW("Told to Start TLS on LDAPS port this will probably fail, please correct the "
+                       WARN("Told to Start TLS on LDAPS port this will probably fail, please correct the "
                               "configuration");
                }
-               
-               if (ldap_start_tls_s(handle, NULL, NULL) != LDAP_SUCCESS) {
-                       ldap_get_option(handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno);
+
+               if (ldap_start_tls_s(conn->handle, NULL, NULL) != LDAP_SUCCESS) {
+                       ldap_get_option(conn->handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno);
 
                        LDAP_ERR("Could not start TLS: %s", ldap_err2string(ldap_errno));
                        goto error;
@@ -1378,69 +1287,29 @@ void *rlm_ldap_conn_create(void *ctx)
        }
 #endif /* HAVE_LDAP_START_TLS */
 
-       /*
-        *      Allocate memory for the handle.
-        */
-       conn = talloc_zero(ctx, ldap_handle_t);
-       conn->inst = inst;
-       conn->handle = handle;
-       conn->rebound = FALSE;
-       conn->referred = FALSE;
-
-       status = rlm_ldap_bind(inst, NULL, &conn, inst->login, inst->password, FALSE);
+       status = rlm_ldap_bind(inst, NULL, &conn, inst->admin_dn, inst->password, false);
        if (status != LDAP_PROC_SUCCESS) {
                goto error;
        }
 
        return conn;
-       
-       error:
-       if (handle) ldap_unbind_s(handle);
-       if (conn) talloc_free(conn);
-       
-       return NULL;
-}
-
 
-/** Close and delete a connection
- *
- * Unbinds the LDAP connection, informing the server and freeing any memory, then releases the memory used by the 
- * connection handle.
- *
- * @param ctx unused.
- * @param connection to destroy.
- * @return always indicates success.
- */
-int rlm_ldap_conn_delete(UNUSED void *ctx, void *connection)
-{
-       ldap_handle_t *conn = connection;
-
-       ldap_unbind_s(conn->handle);
+error:
        talloc_free(conn);
 
-       return 0;
+       return NULL;
 }
 
-
 /** Gets an LDAP socket from the connection pool
  *
  * Retrieve a socket from the connection pool, or NULL on error (of if no sockets are available).
  *
  * @param inst rlm_ldap configuration.
- * @param request Current request.
+ * @param request Current request (may be NULL).
  */
-ldap_handle_t *rlm_ldap_get_socket(const ldap_instance_t *inst, REQUEST *request)
+ldap_handle_t *rlm_ldap_get_socket(ldap_instance_t const *inst, UNUSED REQUEST *request)
 {
-       ldap_handle_t *conn;
-
-       conn = fr_connection_get(inst->pool);
-       if (!conn) {
-               RDEBUGE("All ldap connections are in use");
-               
-               return NULL;
-       }
-
-       return conn;
+       return fr_connection_get(inst->pool);
 }
 
 /** Frees an LDAP socket back to the connection pool
@@ -1451,7 +1320,7 @@ ldap_handle_t *rlm_ldap_get_socket(const ldap_instance_t *inst, REQUEST *request
  * @param inst rlm_ldap configuration.
  * @param conn to release.
  */
-void rlm_ldap_release_socket(const ldap_instance_t *inst, ldap_handle_t *conn)
+void rlm_ldap_release_socket(ldap_instance_t const *inst, ldap_handle_t *conn)
 {
        /*
         *      Could have already been free'd due to a previous error.