Remove fr_connection_delete
[freeradius.git] / src / modules / rlm_ldap / rlm_ldap.c
index 856f28a..6593303 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 rlm_ldap.c
@@ -38,11 +38,34 @@ RCSID("$Id$")
 /*
  *     Scopes
  */
-const FR_NAME_NUMBER ldap_scope[] = {
+FR_NAME_NUMBER const ldap_scope[] = {
        { "sub",        LDAP_SCOPE_SUB  },
        { "one",        LDAP_SCOPE_ONE  },
        { "base",       LDAP_SCOPE_BASE },
-       
+#ifdef LDAP_SCOPE_CHILDREN
+       { "children",   LDAP_SCOPE_CHILDREN },
+#endif
+       {  NULL , -1 }
+};
+
+#ifdef LDAP_OPT_X_TLS_NEVER
+FR_NAME_NUMBER const ldap_tls_require_cert[] = {
+       { "never",      LDAP_OPT_X_TLS_NEVER    },
+       { "demand",     LDAP_OPT_X_TLS_DEMAND   },
+       { "allow",      LDAP_OPT_X_TLS_ALLOW    },
+       { "try",        LDAP_OPT_X_TLS_TRY      },
+       { "hard",       LDAP_OPT_X_TLS_HARD     },      /* oh yes, just like that */
+
+       {  NULL , -1 }
+};
+#endif
+
+FR_NAME_NUMBER const ldap_dereference[] = {
+       { "never",      LDAP_DEREF_NEVER        },
+       { "searching",  LDAP_DEREF_SEARCHING    },
+       { "finding",    LDAP_DEREF_FINDING      },
+       { "always",     LDAP_DEREF_ALWAYS       },
+
        {  NULL , -1 }
 };
 
@@ -50,22 +73,40 @@ const FR_NAME_NUMBER ldap_scope[] = {
  *     TLS Configuration
  */
 static CONF_PARSER tls_config[] = {
-       {"start_tls", PW_TYPE_BOOLEAN, offsetof(ldap_instance_t, start_tls), NULL, "no"},
-       {"cacertfile", PW_TYPE_FILENAME, offsetof(ldap_instance_t, tls_cacertfile), NULL, NULL},
-       {"cacertdir", PW_TYPE_FILENAME, offsetof(ldap_instance_t, tls_cacertdir), NULL, NULL},
-       {"certfile", PW_TYPE_FILENAME, offsetof(ldap_instance_t, tls_certfile), NULL, NULL},
-       {"keyfile", PW_TYPE_FILENAME, offsetof(ldap_instance_t, tls_keyfile), NULL, NULL}, // OK if it changes on HUP
-       {"randfile", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t, tls_randfile), NULL, NULL},
-       {"require_cert",PW_TYPE_STRING_PTR, offsetof(ldap_instance_t, tls_require_cert), NULL, TLS_DEFAULT_VERIFY},
+       /*
+        *      Deprecated attributes
+        */
+       { "cacertfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT | PW_TYPE_DEPRECATED, ldap_instance_t, tls_ca_file), NULL },
+       { "ca_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, ldap_instance_t, tls_ca_file), NULL },
+
+       { "cacertdir", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT | PW_TYPE_DEPRECATED, ldap_instance_t, tls_ca_path), NULL },
+       { "ca_path", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, ldap_instance_t, tls_ca_path), NULL },
+
+       { "certfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT | PW_TYPE_DEPRECATED, ldap_instance_t, tls_certificate_file), NULL },
+       { "certificate_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, ldap_instance_t, tls_certificate_file), NULL },
+
+       { "keyfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT | PW_TYPE_DEPRECATED, ldap_instance_t, tls_private_key_file), NULL }, // OK if it changes on HUP
+       { "private_key_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, ldap_instance_t, tls_private_key_file), NULL }, // OK if it changes on HUP
+
+       { "randfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT | PW_TYPE_DEPRECATED, ldap_instance_t, tls_random_file), NULL },
+       { "random_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, ldap_instance_t, tls_random_file), NULL },
+
+       /*
+        *      LDAP Specific TLS attributes
+        */
+       { "start_tls", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, ldap_instance_t, start_tls), "no" },
+       { "require_cert", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, tls_require_cert_str), NULL },
 
        { NULL, -1, 0, NULL, NULL }
 };
 
 
 static CONF_PARSER profile_config[] = {
-       {"profile_attribute", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t, profile_attr), NULL, NULL},
-       {"default_profile", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t, default_profile), NULL, NULL},
-       {"filter", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t, profile_filter), NULL, NULL},
+       { "filter", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, profile_filter), "(&)" },   //!< Correct filter for
+                                                                                               //!< when the DN is
+                                                                                               //!< known.
+       { "attribute", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, profile_attr), NULL },
+       { "default", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, default_profile), NULL },
 
        { NULL, -1, 0, NULL, NULL }
 };
@@ -74,12 +115,12 @@ static CONF_PARSER profile_config[] = {
  *     User configuration
  */
 static CONF_PARSER user_config[] = {
-       {"filter", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t, userobj_filter), NULL, "(uid=%u)"},
-       {"scope", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t, userobj_scope_str), NULL, "sub"},
-       {"basedn", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,userobj_base_dn), NULL, NULL},
-       
-       {"access_attribute", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t, userobj_access_attr), NULL, NULL},
-       {"access_positive", PW_TYPE_BOOLEAN, offsetof(ldap_instance_t, access_positive), NULL, "yes"},
+       { "filter", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, userobj_filter), "(uid=%u)" },
+       { "scope", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, userobj_scope_str), "sub" },
+       { "base_dn", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, userobj_base_dn), "" },
+
+       { "access_attribute", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, userobj_access_attr), NULL },
+       { "access_positive", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, ldap_instance_t, access_positive), "yes" },
 
        { NULL, -1, 0, NULL, NULL }
 };
@@ -88,15 +129,39 @@ static CONF_PARSER user_config[] = {
  *     Group configuration
  */
 static CONF_PARSER group_config[] = {
-       {"filter", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t, groupobj_filter), NULL, NULL},
-       {"scope", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t, groupobj_scope_str), NULL, "sub"},
-       {"basedn", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t, groupobj_base_dn), NULL, NULL},
-       
-       {"name_attribute", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t, groupobj_name_attr), NULL, "cn"},
-       {"membership_attribute", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t, userobj_membership_attr), NULL, NULL},
-       {"membership_filter", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t, groupobj_membership_filter), NULL, NULL},
-       {"cacheable_name", PW_TYPE_BOOLEAN, offsetof(ldap_instance_t, cacheable_group_name), NULL, "no"},
-       {"cacheable_dn", PW_TYPE_BOOLEAN, offsetof(ldap_instance_t, cacheable_group_dn), NULL, "no"},
+       { "filter", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, groupobj_filter), NULL },
+       { "scope", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, groupobj_scope_str), "sub" },
+       { "base_dn", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, groupobj_base_dn), "" },
+
+       { "name_attribute", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, groupobj_name_attr), "cn" },
+       { "membership_attribute", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, userobj_membership_attr), NULL },
+       { "membership_filter", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, groupobj_membership_filter), NULL },
+       { "cacheable_name", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, ldap_instance_t, cacheable_group_name), "no" },
+       { "cacheable_dn", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, ldap_instance_t, cacheable_group_dn), "no" },
+       { "cache_attribute", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, cache_attribute), NULL },
+
+       { NULL, -1, 0, NULL, NULL }
+};
+
+/*
+ *     Client configuration
+ */
+static CONF_PARSER client_attribute[] = {
+       { "identifier", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, clientobj_identifier), "host" },
+       { "shortname", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, clientobj_shortname), "cn" },
+       { "nas_type", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, clientobj_type), NULL },
+       { "secret", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, clientobj_secret), NULL },
+       { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, clientobj_server), NULL },
+       { "require_message_authenticator", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, clientobj_require_ma), NULL },
+
+       { NULL, -1, 0, NULL, NULL }
+};
+
+static CONF_PARSER client_config[] = {
+       { "filter", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, clientobj_filter), NULL },
+       { "scope", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, clientobj_scope_str), "sub" },
+       { "base_dn", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, clientobj_base_dn), "" },
+       { "attribute", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) client_attribute },
 
        { NULL, -1, 0, NULL, NULL }
 };
@@ -105,7 +170,7 @@ static CONF_PARSER group_config[] = {
  *     Reference for accounting updates
  */
 static const CONF_PARSER acct_section_config[] = {
-       {"reference", PW_TYPE_STRING_PTR, offsetof(ldap_acct_section_t, reference), NULL, "."},
+       { "reference", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_acct_section_t, reference), "." },
 
        {NULL, -1, 0, NULL, NULL}
 };
@@ -119,29 +184,33 @@ static CONF_PARSER option_config[] = {
        /*
         *      Debugging flags to the server
         */
-       {"ldap_debug", PW_TYPE_INTEGER, offsetof(ldap_instance_t,ldap_debug), NULL, "0x0000"},
+       { "ldap_debug", FR_CONF_OFFSET(PW_TYPE_INTEGER, ldap_instance_t, ldap_debug), "0x0000" },
 
-       {"chase_referrals", PW_TYPE_BOOLEAN, offsetof(ldap_instance_t,chase_referrals), NULL, NULL},
+       { "dereference", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, dereference_str), NULL },
 
-       {"rebind", PW_TYPE_BOOLEAN,offsetof(ldap_instance_t,rebind), NULL, NULL},
+       { "chase_referrals", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, ldap_instance_t, chase_referrals), NULL },
 
+       { "rebind", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, ldap_instance_t, rebind), NULL },
+
+#ifdef LDAP_OPT_NETWORK_TIMEOUT
        /* timeout on network activity */
-       {"net_timeout", PW_TYPE_INTEGER, offsetof(ldap_instance_t,net_timeout), NULL, "10"},
+       { "net_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, ldap_instance_t, net_timeout), "10" },
+#endif
 
        /* timeout for search results */
-       {"res_timeout", PW_TYPE_INTEGER, offsetof(ldap_instance_t,res_timeout), NULL, "20"},
+       { "res_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, ldap_instance_t, res_timeout), "20" },
 
        /* allow server unlimited time for search (server-side limit) */
-       {"srv_timelimit", PW_TYPE_INTEGER, offsetof(ldap_instance_t,srv_timelimit), NULL, "20"},
+       { "srv_timelimit", FR_CONF_OFFSET(PW_TYPE_INTEGER, ldap_instance_t, srv_timelimit), "20" },
 
 #ifdef LDAP_OPT_X_KEEPALIVE_IDLE
-       {"idle", PW_TYPE_INTEGER, offsetof(ldap_instance_t,keepalive_idle), NULL, "60"},
+       { "idle", FR_CONF_OFFSET(PW_TYPE_INTEGER, ldap_instance_t, keepalive_idle), "60" },
 #endif
 #ifdef LDAP_OPT_X_KEEPALIVE_PROBES
-       {"probes", PW_TYPE_INTEGER, offsetof(ldap_instance_t,keepalive_probes), NULL, "3"},
+       { "probes", FR_CONF_OFFSET(PW_TYPE_INTEGER, ldap_instance_t, keepalive_probes), "3" },
 #endif
 #ifdef LDAP_OPT_X_KEEPALIVE_INTERVAL
-       {"interval", PW_TYPE_INTEGER,  offsetof(ldap_instance_t,keepalive_interval), NULL, "30"},
+       { "interval", FR_CONF_OFFSET(PW_TYPE_INTEGER, ldap_instance_t, keepalive_interval), "30" },
 #endif
 
        { NULL, -1, 0, NULL, NULL }
@@ -149,36 +218,38 @@ static CONF_PARSER option_config[] = {
 
 
 static const CONF_PARSER module_config[] = {
-       {"server", PW_TYPE_STRING_PTR | PW_TYPE_REQUIRED, offsetof(ldap_instance_t,server), NULL, "localhost"},
-       {"port", PW_TYPE_INTEGER, offsetof(ldap_instance_t,port), NULL, "389"},
+       { "server", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, ldap_instance_t, server), "localhost" },
+       { "port", FR_CONF_OFFSET(PW_TYPE_SHORT, ldap_instance_t, port), "389" },
 
-       {"password", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,password), NULL, ""},
-       {"identity", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,admin_dn), NULL, ""},
-       
-       {"basedn", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,base_dn), NULL, ""},
-       
-       {"valuepair_attr", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t, base_dn), NULL, NULL},
+       { "password", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, ldap_instance_t, password), "" },
+       { "identity", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, admin_dn), "" },
+
+       { "valuepair_attribute", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, valuepair_attr), NULL },
 
 #ifdef WITH_EDIR
        /* support for eDirectory Universal Password */
-       {"edir", PW_TYPE_BOOLEAN, offsetof(ldap_instance_t,edir), NULL, NULL}, /* NULL defaults to "no" */
+       { "edir", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, ldap_instance_t, edir), NULL }, /* NULL defaults to "no" */
 
        /*
-        *      Attempt to bind with the Cleartext password we got from eDirectory
+        *      Attempt to bind with the cleartext password we got from eDirectory
         *      Universal password for additional authorization checks.
         */
-       {"edir_autz", PW_TYPE_BOOLEAN, offsetof(ldap_instance_t,edir_autz), NULL, NULL}, /* NULL defaults to "no" */
+       { "edir_autz", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, ldap_instance_t, edir_autz), NULL }, /* NULL defaults to "no" */
 #endif
 
-       { "user", PW_TYPE_SUBSECTION, 0, NULL, (void const *) user_config },
+       { "read_clients", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, ldap_instance_t, do_clients), NULL }, /* NULL defaults to "no" */
+
+       { "user", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) user_config },
 
-       { "group", PW_TYPE_SUBSECTION, 0, NULL, (void const *) group_config },
-       
-       { "profiles", PW_TYPE_SUBSECTION, 0, NULL, (void const *) profile_config },
+       { "group", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) group_config },
 
-       { "options", PW_TYPE_SUBSECTION, 0, NULL, (void const *) option_config },
+       { "client", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) client_config },
 
-       { "tls", PW_TYPE_SUBSECTION, 0, NULL, (void const *) tls_config },
+       { "profile", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) profile_config },
+
+       { "options", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) option_config },
+
+       { "tls", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) tls_config },
 
        {NULL, -1, 0, NULL, NULL}
 };
@@ -186,11 +257,10 @@ static const CONF_PARSER module_config[] = {
 /** Expand an LDAP URL into a query, and return a string result from that query.
  *
  */
-static size_t ldap_xlat(void *instance, REQUEST *request, char const *fmt,
-                       char *out, size_t freespace)
+static ssize_t ldap_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace)
 {
        ldap_rcode_t status;
-       size_t length = 0;
+       size_t len = 0;
        ldap_instance_t *inst = instance;
        LDAPURLDesc *ldap_url;
        LDAPMessage *result = NULL;
@@ -205,12 +275,12 @@ static size_t ldap_xlat(void *instance, REQUEST *request, char const *fmt,
 
        if (!ldap_is_ldap_url(url)) {
                REDEBUG("String passed does not look like an LDAP URL");
-               return 0;
+               return -1;
        }
 
        if (ldap_url_parse(url, &ldap_url)){
                REDEBUG("Parsing LDAP URL failed");
-               return 0;
+               return -1;
        }
 
        /*
@@ -221,15 +291,15 @@ static size_t ldap_xlat(void *instance, REQUEST *request, char const *fmt,
            (strcmp(ldap_url->lud_attrs[0], "*") == 0) ||
            ldap_url->lud_attrs[1]) {
                REDEBUG("Bad attributes list in LDAP URL. URL must specify exactly one attribute to retrieve");
-                      
+
                goto free_urldesc;
        }
 
-       if (ldap_url->lud_host && 
+       if (ldap_url->lud_host &&
            ((strncmp(inst->server, ldap_url->lud_host, strlen(inst->server)) != 0) ||
-            (ldap_url->lud_port != inst->port))) {
+            ((uint32_t) ldap_url->lud_port != inst->port))) {
                RDEBUG("Requested server/port is \"%s:%i\"", ldap_url->lud_host, inst->port);
-               
+
                goto free_urldesc;
        }
 
@@ -237,7 +307,7 @@ static size_t ldap_xlat(void *instance, REQUEST *request, char const *fmt,
        if (!conn) goto free_urldesc;
 
        memcpy(&attrs, &ldap_url->lud_attrs, sizeof(attrs));
-       
+
        status = rlm_ldap_search(inst, request, &conn, ldap_url->lud_dn, ldap_url->lud_scope, ldap_url->lud_filter,
                                 attrs, &result);
        switch (status) {
@@ -256,6 +326,7 @@ static size_t ldap_xlat(void *instance, REQUEST *request, char const *fmt,
        if (!entry) {
                ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
                REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
+               len = -1;
                goto free_result;
        }
 
@@ -265,9 +336,8 @@ static size_t ldap_xlat(void *instance, REQUEST *request, char const *fmt,
                goto free_result;
        }
 
-       length = strlen(vals[0]);
-       if (length >= freespace){
-
+       len = strlen(vals[0]);
+       if (len >= freespace){
                goto free_vals;
        }
 
@@ -282,7 +352,7 @@ free_socket:
 free_urldesc:
        ldap_free_urldesc(ldap_url);
 
-       return length;
+       return len;
 }
 
 /** Perform LDAP-Group comparison checking
@@ -302,13 +372,15 @@ static int rlm_ldap_groupcmp(void *instance, REQUEST *request, UNUSED VALUE_PAIR
 {
        ldap_instance_t *inst = instance;
        rlm_rcode_t     rcode;
-       
-       int             found = false;
-       int             check_is_dn;
+
+       bool            found = false;
+       bool            check_is_dn;
 
        ldap_handle_t   *conn = NULL;
        char const      *user_dn;
-       
+
+       rad_assert(inst->groupobj_base_dn);
+
        RDEBUG("Searching for user in group \"%s\"", check->vp_strvalue);
 
        if (check->length == 0) {
@@ -323,11 +395,18 @@ static int rlm_ldap_groupcmp(void *instance, REQUEST *request, UNUSED VALUE_PAIR
        if ((check_is_dn && inst->cacheable_group_dn) || (!check_is_dn && inst->cacheable_group_name)) {
                switch(rlm_ldap_check_cached(inst, request, check)) {
                        case RLM_MODULE_NOTFOUND:
-                               break;
+                               found = false;
+                               goto finish;
                        case RLM_MODULE_OK:
                                found = true;
-                       default:
                                goto finish;
+                       /*
+                        *      Fallback to dynamic search on failure
+                        */
+                       case RLM_MODULE_FAIL:
+                       case RLM_MODULE_INVALID:
+                       default:
+                               break;
                }
        }
 
@@ -358,7 +437,7 @@ static int rlm_ldap_groupcmp(void *instance, REQUEST *request, UNUSED VALUE_PAIR
                                goto finish;
                }
        }
-       
+
        rad_assert(conn);
 
        /*
@@ -374,17 +453,17 @@ static int rlm_ldap_groupcmp(void *instance, REQUEST *request, UNUSED VALUE_PAIR
                                goto finish;
                }
        }
-       
+
        rad_assert(conn);
-       
+
        finish:
        if (conn) {
                rlm_ldap_release_socket(inst, conn);
        }
-       
+
        if (!found) {
                RDEBUG("User is not a member of specified group");
-               
+
                return 1;
        }
 
@@ -397,7 +476,7 @@ static int rlm_ldap_groupcmp(void *instance, REQUEST *request, UNUSED VALUE_PAIR
 static int mod_detach(void *instance)
 {
        ldap_instance_t *inst = instance;
-       
+
        fr_connection_pool_delete(inst->pool);
 
        if (inst->user_map) {
@@ -418,34 +497,34 @@ static int mod_detach(void *instance)
  * @return 0 on success, else < 0 on failure.
  */
 static int parse_sub_section(ldap_instance_t *inst, CONF_SECTION *parent, ldap_acct_section_t **config,
-                            rlm_components_t comp)
+                            rlm_components_t comp)
 {
        CONF_SECTION *cs;
 
        char const *name = section_type_value[comp].section;
-       
+
        cs = cf_section_sub_find(parent, name);
        if (!cs) {
                INFO("rlm_ldap (%s): Couldn't find configuration for %s, will return NOOP for calls "
                       "from this section", inst->xlat_name, name);
-               
+
                return 0;
        }
-       
+
        *config = talloc_zero(inst, ldap_acct_section_t);
        if (cf_section_parse(cs, *config, acct_section_config) < 0) {
                LDAP_ERR("Failed parsing configuration for section %s", name);
-               
+
                return -1;
        }
-               
+
        (*config)->cs = cs;
 
        return 0;
 }
 
 /** Instantiate the module
- * 
+ *
  * Creates a new instance of the module reading parameters from a configuration section.
  *
  * @param conf to parse.
@@ -454,60 +533,77 @@ static int parse_sub_section(ldap_instance_t *inst, CONF_SECTION *parent, ldap_a
  */
 static int mod_instantiate(CONF_SECTION *conf, void *instance)
 {
+       static bool version_done;
+
+       CONF_SECTION *options;
        ldap_instance_t *inst = instance;
 
        inst->cs = conf;
 
-       inst->chase_referrals = 2; /* use OpenLDAP defaults */
-       inst->rebind = 2;
-       
+       options = cf_section_sub_find(conf, "options");
+       if (!options || !cf_pair_find(options, "chase_referrals")) {
+               inst->chase_referrals_unset = true;      /* use OpenLDAP defaults */
+       }
+
        inst->xlat_name = cf_section_name2(conf);
        if (!inst->xlat_name) {
                inst->xlat_name = cf_section_name1(conf);
        }
 
        /*
+        *      Get version info from the LDAP API.
+        */
+       if (!version_done) {
+               int ldap_errno;
+               LDAPAPIInfo info;
+
+               version_done = true;
+
+               ldap_errno = ldap_get_option(NULL, LDAP_OPT_API_INFO, &info);
+               if (ldap_errno == LDAP_OPT_SUCCESS) {
+                       if (strcmp(info.ldapai_vendor_name, LDAP_VENDOR_NAME) != 0) {
+                               WARN("rlm_ldap: libldap vendor changed since the server was built");
+                               WARN("rlm_ldap: linked: %s built: %s", info.ldapai_vendor_name, LDAP_VENDOR_NAME);
+                       }
+
+                       if (info.ldapai_vendor_version != LDAP_VENDOR_VERSION) {
+                               WARN("rlm_ldap: libldap version changed since the server was built");
+                               WARN("rlm_ldap: linked: %i built: %i",
+                                    info.ldapai_vendor_version, LDAP_VENDOR_VERSION);
+                       }
+
+                       INFO("rlm_ldap: libldap vendor: %s version: %i", info.ldapai_vendor_name,
+                            info.ldapai_vendor_version);
+                       ldap_memfree(info.ldapai_vendor_name);
+                       ldap_memfree(info.ldapai_extensions);
+               } else {
+                       WARN("rlm_ldap: Falling back to build time libldap version info.  Query for LDAP_OPT_API_INFO "
+                            "returned: %i", ldap_errno);
+                       INFO("rlm_ldap: libldap vendor: %s version: %i", LDAP_VENDOR_NAME, LDAP_VENDOR_VERSION);
+               }
+       }
+
+       /*
         *      If the configuration parameters can't be parsed, then fail.
         */
        if ((parse_sub_section(inst, conf, &inst->accounting, RLM_COMPONENT_ACCT) < 0) ||
            (parse_sub_section(inst, conf, &inst->postauth, RLM_COMPONENT_POST_AUTH) < 0)) {
                LDAP_ERR("Failed parsing configuration");
-               
+
                goto error;
        }
 
        /*
         *      Sanity checks for cacheable groups code.
         */
-       if (inst->cacheable_group_name && inst->groupobj_membership_filter && !inst->groupobj_name_attr) {
-               LDAP_ERR("Directive 'group.name_attribute' must be set if cacheable group names are enabled");
-               
-               goto error;
-       }
+       if (inst->cacheable_group_name && inst->groupobj_membership_filter) {
+               if (!inst->groupobj_name_attr) {
+                       LDAP_ERR("Directive 'group.name_attribute' must be set if cacheable group names are enabled");
 
-       /*
-        *      Copy across values from base_dn to the object specific base_dn.
-        */
-       if (!inst->groupobj_base_dn) {
-               if (!inst->base_dn) {
-                       LDAP_ERR("Must set 'base_dn' if there is no 'group_base_dn'");
-                       
                        goto error;
                }
-               
-               inst->groupobj_base_dn = inst->base_dn;
        }
 
-       if (!inst->userobj_base_dn) {
-               if (!inst->base_dn) {
-                       LDAP_ERR("Must set 'base_dn' if there is no 'userobj_base_dn'");
-                       
-                       goto error;
-               }
-               
-               inst->userobj_base_dn = inst->base_dn;
-       }
-       
        /*
         *      Check for URLs.  If they're used and the library doesn't support them, then complain.
         */
@@ -517,11 +613,12 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance)
                inst->is_url = 1;
                inst->port = 0;
 #else
-               LDAP_ERR("'server' directive is in URL form but ldap_initialize() is not available");
+               LDAP_ERR("Directive 'server' is in URL form but ldap_initialize() is not available");
                goto error;
 #endif
        }
 
+#ifdef LDAP_OPT_X_TLS_NEVER
        /*
         *      Workaround for servers which support LDAPS but not START TLS
         */
@@ -530,37 +627,90 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance)
        } else {
                inst->tls_mode = 0;
        }
+#endif
+
+       /*
+        *      Convert dereference strings to enumerated constants
+        */
+       if (inst->dereference_str) {
+               inst->dereference = fr_str2int(ldap_dereference, inst->dereference_str, -1);
+               if (inst->dereference < 0) {
+                       LDAP_ERR("Invalid 'dereference' value \"%s\", expected 'never', 'searching', "
+                                "'finding' or 'always'", inst->dereference_str);
+                       goto error;
+               }
+       }
 
 #if LDAP_SET_REBIND_PROC_ARGS != 3
        /*
         *      The 2-argument rebind doesn't take an instance variable.  Our rebind function needs the instance
         *      variable for the username, password, etc.
         */
-       if (inst->rebind == 1) {
+       if (inst->rebind == true) {
                LDAP_ERR("Cannot use 'rebind' directive as this version of libldap does not support the API "
                         "that we need");
-                        
+
                goto error;
        }
 #endif
 
        /*
-        *      Convert scope strings to integers
+        *      Convert scope strings to enumerated constants
         */
        inst->userobj_scope = fr_str2int(ldap_scope, inst->userobj_scope_str, -1);
        if (inst->userobj_scope < 0) {
-               LDAP_ERR("Invalid 'user.scope' value '%s', expected 'sub', 'one' or 'base'",
-                        inst->userobj_scope_str);
+               LDAP_ERR("Invalid 'user.scope' value \"%s\", expected 'sub', 'one'"
+#ifdef LDAP_SCOPE_CHILDREN
+                        ", 'base' or 'children'"
+#else
+                        " or 'base'"
+#endif
+                        , inst->userobj_scope_str);
                goto error;
        }
-       
+
        inst->groupobj_scope = fr_str2int(ldap_scope, inst->groupobj_scope_str, -1);
        if (inst->groupobj_scope < 0) {
-               LDAP_ERR("Invalid 'group.scope' value '%s', expected 'sub', 'one' or 'base'",
-                        inst->groupobj_scope_str);
+               LDAP_ERR("Invalid 'group.scope' value \"%s\", expected 'sub', 'one'"
+#ifdef LDAP_SCOPE_CHILDREN
+                        ", 'base' or 'children'"
+#else
+                        " or 'base'"
+#endif
+                        , inst->groupobj_scope_str);
+               goto error;
+       }
+
+       inst->clientobj_scope = fr_str2int(ldap_scope, inst->clientobj_scope_str, -1);
+       if (inst->clientobj_scope < 0) {
+               LDAP_ERR("Invalid 'client.scope' value \"%s\", expected 'sub', 'one'"
+#ifdef LDAP_SCOPE_CHILDREN
+                        ", 'base' or 'children'"
+#else
+                        " or 'base'"
+#endif
+                        , inst->clientobj_scope_str);
                goto error;
        }
 
+       if (inst->tls_require_cert_str) {
+#ifdef LDAP_OPT_X_TLS_NEVER
+               /*
+                *      Convert cert strictness to enumerated constants
+                */
+               inst->tls_require_cert = fr_str2int(ldap_tls_require_cert, inst->tls_require_cert_str, -1);
+               if (inst->tls_require_cert < 0) {
+                       LDAP_ERR("Invalid 'tls.require_cert' value \"%s\", expected 'never', 'demand', 'allow', "
+                                "'try' or 'hard'", inst->tls_require_cert_str);
+                       goto error;
+               }
+#else
+               LDAP_ERR("Modifying 'tls.require_cert' is not supported by current version of libldap. "
+                        "Please upgrade or substitute current libldap and rebuild this module");
+
+               goto error;
+#endif
+       }
        /*
         *      Build the attribute map
         */
@@ -571,51 +721,77 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance)
        /*
         *      Group comparison checks.
         */
-       inst->group_da = dict_attrbyvalue(PW_LDAP_GROUP, 0);
-       paircompare_register(PW_LDAP_GROUP, PW_USER_NAME, rlm_ldap_groupcmp, inst);     
        if (cf_section_name2(conf)) {
-               ATTR_FLAGS flags;
+               static ATTR_FLAGS flags;
                char buffer[256];
 
-               snprintf(buffer, sizeof(buffer), "%s-Ldap-Group",
-                        inst->xlat_name);
-               memset(&flags, 0, sizeof(flags));
+               snprintf(buffer, sizeof(buffer), "%s-Ldap-Group", inst->xlat_name);
+               if (dict_addattr(buffer, -1, 0, PW_TYPE_STRING, flags) < 0) {
+                       LDAP_ERR("Error creating group attribute: %s", fr_strerror());
 
-               dict_addattr(buffer, -1, 0, PW_TYPE_STRING, flags);
+                       return -1;
+               }
                inst->group_da = dict_attrbyname(buffer);
                if (!inst->group_da) {
                        LDAP_ERR("Failed creating attribute %s", buffer);
-                       
+
                        goto error;
                }
-               
 
-               paircompare_register(inst->group_da->attr, PW_USER_NAME, rlm_ldap_groupcmp, inst);
+               paircompare_register(inst->group_da, dict_attrbyvalue(PW_USER_NAME, 0), false, rlm_ldap_groupcmp, inst);
+       /*
+        *      Were the default instance
+        */
+       } else {
+               inst->group_da = dict_attrbyvalue(PW_LDAP_GROUP, 0);
+               paircompare_register(dict_attrbyvalue(PW_LDAP_GROUP, 0), dict_attrbyvalue(PW_USER_NAME, 0),
+                               false, rlm_ldap_groupcmp, inst);
        }
 
        xlat_register(inst->xlat_name, ldap_xlat, rlm_ldap_escape_func, inst);
 
        /*
+        *      Setup the cache attribute
+        */
+       if (inst->cache_attribute) {
+               static ATTR_FLAGS flags;
+
+               if (dict_addattr(inst->cache_attribute, -1, 0, PW_TYPE_STRING, flags) < 0) {
+                       LDAP_ERR("Error creating cache attribute: %s", fr_strerror());
+
+                       return -1;
+               }
+               inst->cache_da = dict_attrbyname(inst->cache_attribute);
+       } else {
+               inst->cache_da = inst->group_da;        /* Default to the group_da */
+       }
+
+       /*
         *      Initialize the socket pool.
         */
-       inst->pool = fr_connection_pool_init(inst->cs, inst, mod_conn_create, NULL, mod_conn_delete, NULL);
+       inst->pool = fr_connection_pool_init(inst->cs, inst, mod_conn_create, NULL, NULL);
        if (!inst->pool) {
                return -1;
        }
-       
+
+       /*
+        *      Bulk load dynamic clients.
+        */
+       if (inst->do_clients) {
+               if (rlm_ldap_load_clients(inst) < 0) {
+                       LDAP_ERR("Error loading clients");
+
+                       return -1;
+               }
+       }
+
        return 0;
 
 error:
        return -1;
 }
 
-/** Check the user's password against ldap directory
- * 
- * @param instance rlm_ldap configuration.
- * @param request Current request.
- * @return one of the RLM_MODULE_* values.
- */
-static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request)
+static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
 {
        rlm_rcode_t     rcode;
        ldap_rcode_t    status;
@@ -636,20 +812,20 @@ static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request)
 
        if (!request->password ||
            (request->password->da->attr != PW_USER_PASSWORD)) {
-               RWDEBUG("You have set \"Auth-Type := LDAP\" somewhere.");
+               RWDEBUG("You have set \"Auth-Type := LDAP\" somewhere");
                RWDEBUG("*********************************************");
                RWDEBUG("* THAT CONFIGURATION IS WRONG.  DELETE IT.   ");
-               RWDEBUG("* YOU ARE PREVENTING THE SERVER FROM WORKING.");
+               RWDEBUG("* YOU ARE PREVENTING THE SERVER FROM WORKING");
                RWDEBUG("*********************************************");
-               
-               REDEBUG("Attribute \"User-Password\" is required for authentication.");
-               
+
+               REDEBUG("Attribute \"User-Password\" is required for authentication");
+
                return RLM_MODULE_INVALID;
        }
 
        if (request->password->length == 0) {
                REDEBUG("Empty password supplied");
-               
+
                return RLM_MODULE_INVALID;
        }
 
@@ -664,7 +840,7 @@ static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request)
        dn = rlm_ldap_find_user(inst, request, &conn, NULL, false, NULL, &rcode);
        if (!dn) {
                rlm_ldap_release_socket(inst, conn);
-               
+
                return rcode;
        }
 
@@ -677,38 +853,35 @@ static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request)
        case LDAP_PROC_SUCCESS:
                rcode = RLM_MODULE_OK;
                RDEBUG("Bind as user \"%s\" was successful", dn);
-               
                break;
+
        case LDAP_PROC_NOT_PERMITTED:
                rcode = RLM_MODULE_USERLOCK;
-               
                break;
+
        case LDAP_PROC_REJECT:
                rcode = RLM_MODULE_REJECT;
-               
                break;
+
        case LDAP_PROC_BAD_DN:
                rcode = RLM_MODULE_INVALID;
-               
                break;
+
        case LDAP_PROC_NO_RESULT:
                rcode = RLM_MODULE_NOTFOUND;
-               
                break;
+
        default:
                rcode = RLM_MODULE_FAIL;
                break;
        };
 
        rlm_ldap_release_socket(inst, conn);
-       
+
        return rcode;
 }
 
-/** Check if user is authorized for remote access
- *
- */
-static rlm_rcode_t mod_authorize(void *instance, REQUEST *request)
+static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
 {
        rlm_rcode_t     rcode = RLM_MODULE_OK;
        ldap_rcode_t    status;
@@ -720,11 +893,11 @@ static rlm_rcode_t mod_authorize(void *instance, REQUEST *request)
        ldap_handle_t   *conn;
        LDAPMessage     *result, *entry;
        char const      *dn = NULL;
-       rlm_ldap_map_xlat_t     expanded; /* faster that mallocing every time */
-       
+       rlm_ldap_map_xlat_t     expanded; /* faster than mallocing every time */
+
        if (!request->username) {
-               RDEBUG2("Attribute \"User-Name\" is required for authorization.");
-               
+               RDEBUG2("Attribute \"User-Name\" is required for authorization");
+
                return RLM_MODULE_NOOP;
        }
 
@@ -733,17 +906,17 @@ static rlm_rcode_t mod_authorize(void *instance, REQUEST *request)
         */
        if (request->username->length == 0) {
                RDEBUG2("Zero length username not permitted");
-               
+
                return RLM_MODULE_INVALID;
        }
 
        if (rlm_ldap_map_xlat(request, inst->user_map, &expanded) < 0) {
                return RLM_MODULE_FAIL;
        }
-       
+
        conn = rlm_ldap_get_socket(inst, request);
        if (!conn) return RLM_MODULE_FAIL;
-       
+
        /*
         *      Add any additional attributes we need for checking access, memberships, and profiles
         */
@@ -754,27 +927,27 @@ static rlm_rcode_t mod_authorize(void *instance, REQUEST *request)
        if (inst->userobj_membership_attr && (inst->cacheable_group_dn || inst->cacheable_group_name)) {
                expanded.attrs[expanded.count++] = inst->userobj_membership_attr;
        }
-       
+
        if (inst->profile_attr) {
                expanded.attrs[expanded.count++] = inst->profile_attr;
        }
-       
+
        if (inst->valuepair_attr) {
                expanded.attrs[expanded.count++] = inst->valuepair_attr;
        }
-       
+
        expanded.attrs[expanded.count] = NULL;
-       
+
        dn = rlm_ldap_find_user(inst, request, &conn, expanded.attrs, true, &result, &rcode);
        if (!dn) {
-               goto finish;                    
+               goto finish;
        }
 
        entry = ldap_first_entry(conn->handle, result);
        if (!entry) {
                ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
                REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
-                        
+
                goto finish;
        }
 
@@ -787,16 +960,18 @@ static rlm_rcode_t mod_authorize(void *instance, REQUEST *request)
                        goto finish;
                }
        }
-       
+
        /*
         *      Check if we need to cache group memberships
         */
        if (inst->cacheable_group_dn || inst->cacheable_group_name) {
-               rcode = rlm_ldap_cacheable_userobj(inst, request, &conn, entry);
-               if (rcode != RLM_MODULE_OK) {
-                       goto finish;
+               if (inst->userobj_membership_attr) {
+                       rcode = rlm_ldap_cacheable_userobj(inst, request, &conn, entry, inst->userobj_membership_attr);
+                       if (rcode != RLM_MODULE_OK) {
+                               goto finish;
+                       }
                }
-               
+
                rcode = rlm_ldap_cacheable_groupobj(inst, request, &conn);
                if (rcode != RLM_MODULE_OK) {
                        goto finish;
@@ -824,8 +999,8 @@ static rlm_rcode_t mod_authorize(void *instance, REQUEST *request)
                 */
                res = nmasldap_get_password(conn->handle, dn, password, &pass_size);
                if (res != 0) {
-                       RWDEBUG("Failed to retrieve eDirectory password");
-                       rcode = RLM_MODULE_NOOP;
+                       REDEBUG("Failed to retrieve eDirectory password: (%i) %s", res, edir_errstr(res));
+                       rcode = RLM_MODULE_FAIL;
 
                        goto finish;
                }
@@ -836,9 +1011,13 @@ static rlm_rcode_t mod_authorize(void *instance, REQUEST *request)
                vp = radius_paircreate(request, &request->config_items, PW_CLEARTEXT_PASSWORD, 0);
                pairstrcpy(vp, password);
                vp->length = pass_size;
-               
-               RDEBUG2("Added eDirectory password in check items as %s = %s", vp->da->name, vp->vp_strvalue);
-                       
+
+               if (RDEBUG_ENABLED3) {
+                       RDEBUG3("Added eDirectory password.  control:%s += '%s'", vp->da->name, vp->vp_strvalue);
+               } else {
+                       RDEBUG2("Added eDirectory password");
+               }
+
                if (inst->edir_autz) {
                        RDEBUG2("Binding as user for eDirectory authorization checks");
                        /*
@@ -849,28 +1028,27 @@ static rlm_rcode_t mod_authorize(void *instance, REQUEST *request)
                        switch (status) {
                        case LDAP_PROC_SUCCESS:
                                rcode = RLM_MODULE_OK;
-                               RDEBUG("Bind as user \"%s\" was successful", dn);
-                               
+                               RDEBUG("Bind as user '%s' was successful", dn);
                                break;
+
                        case LDAP_PROC_NOT_PERMITTED:
                                rcode = RLM_MODULE_USERLOCK;
-                               
                                goto finish;
+
                        case LDAP_PROC_REJECT:
                                rcode = RLM_MODULE_REJECT;
-                               
                                goto finish;
+
                        case LDAP_PROC_BAD_DN:
                                rcode = RLM_MODULE_INVALID;
-                               
                                goto finish;
+
                        case LDAP_PROC_NO_RESULT:
                                rcode = RLM_MODULE_NOTFOUND;
-                               
                                goto finish;
+
                        default:
                                rcode = RLM_MODULE_FAIL;
-                               
                                goto finish;
                        };
                }
@@ -882,11 +1060,15 @@ skip_edir:
        /*
         *      Apply ONE user profile, or a default user profile.
         */
-       vp = pairfind(request->config_items, PW_USER_PROFILE, 0, TAG_ANY);
-       if (vp || inst->default_profile) {
-               char const *profile = inst->default_profile;
+       if (inst->default_profile) {
+               char profile[1024];
+
+               if (radius_xlat(profile, sizeof(profile), request, inst->default_profile, NULL, NULL) < 0) {
+                       REDEBUG("Failed creating default profile string");
 
-               if (vp) profile = vp->vp_strvalue;
+                       rcode = RLM_MODULE_INVALID;
+                       goto finish;
+               }
 
                rlm_ldap_map_profile(inst, request, &conn, profile, &expanded);
        }
@@ -900,16 +1082,17 @@ skip_edir:
                        for (i = 0; vals[i] != NULL; i++) {
                                rlm_ldap_map_profile(inst, request, &conn, vals[i], &expanded);
                        }
-       
+
                        ldap_value_free(vals);
                }
        }
 
-       if (inst->user_map) {
+       if (inst->user_map || inst->valuepair_attr) {
+               RDEBUG("Processing user attributes");
                rlm_ldap_map_do(inst, request, conn->handle, &expanded, entry);
                rlm_ldap_check_reply(inst, request);
        }
-       
+
 finish:
        rlm_ldap_map_xlat_free(&expanded);
        if (result) {
@@ -932,21 +1115,22 @@ finish:
 static rlm_rcode_t user_modify(ldap_instance_t *inst, REQUEST *request, ldap_acct_section_t *section)
 {
        rlm_rcode_t     rcode = RLM_MODULE_OK;
-       
+       ldap_rcode_t    status;
+
        ldap_handle_t   *conn = NULL;
-       
+
        LDAPMod         *mod_p[LDAP_MAX_ATTRMAP + 1], mod_s[LDAP_MAX_ATTRMAP];
        LDAPMod         **modify = mod_p;
-       
+
        char            *passed[LDAP_MAX_ATTRMAP * 2];
        int             i, total = 0, last_pass = 0;
-       
+
        char            *expanded[LDAP_MAX_ATTRMAP];
        int             last_exp = 0;
-       
+
        char const      *attr;
        char const      *value;
-       
+
        char const      *dn;
        /*
         *      Build our set of modifications using the update sections in
@@ -957,58 +1141,58 @@ static rlm_rcode_t user_modify(ldap_instance_t *inst, REQUEST *request, ldap_acc
        CONF_SECTION    *cs;
        FR_TOKEN        op;
        char            path[MAX_STRING_LEN];
-       
+
        char            *p = path;
 
        rad_assert(section);
-       
+
        /*
         *      Locate the update section were going to be using
         */
        if (section->reference[0] != '.') {
                *p++ = '.';
        }
-       
+
        if (radius_xlat(p, (sizeof(path) - (p - path)) - 1, request, section->reference, NULL, NULL) < 0) {
-               goto error;     
+               goto error;
        }
 
        ci = cf_reference_item(NULL, section->cs, path);
        if (!ci) {
-               goto error;     
+               goto error;
        }
-       
+
        if (!cf_item_is_section(ci)){
                REDEBUG("Reference must resolve to a section");
-               
-               goto error;     
+
+               goto error;
        }
-       
+
        cs = cf_section_sub_find(cf_itemtosection(ci), "update");
        if (!cs) {
                REDEBUG("Section must contain 'update' subsection");
-               
+
                goto error;
        }
-       
+
        /*
         *      Iterate over all the pairs, building our mods array
         */
        for (ci = cf_item_find_next(cs, NULL); ci != NULL; ci = cf_item_find_next(cs, ci)) {
-               int do_xlat = false;
-               
-               if (total == LDAP_MAX_ATTRMAP) {
-                       REDEBUG("Modify map size exceeded");
-       
-                       goto error;
-               }
-               
+               bool do_xlat = false;
+
+               if (total == LDAP_MAX_ATTRMAP) {
+                       REDEBUG("Modify map size exceeded");
+
+                       goto error;
+               }
+
                if (!cf_item_is_pair(ci)) {
                        REDEBUG("Entry is not in \"ldap-attribute = value\" format");
-                              
+
                        goto error;
                }
-       
+
                /*
                 *      Retrieve all the information we need about the pair
                 */
@@ -1016,57 +1200,57 @@ static rlm_rcode_t user_modify(ldap_instance_t *inst, REQUEST *request, ldap_acc
                value = cf_pair_value(cp);
                attr = cf_pair_attr(cp);
                op = cf_pair_operator(cp);
-               
+
                if (!value || (*value == '\0')) {
                        RDEBUG("Empty value string, skipping attribute \"%s\"", attr);
-                       
+
                        continue;
                }
 
-               switch (cf_pair_value_type(cp))
-               {
-                       case T_BARE_WORD:
-                       case T_SINGLE_QUOTED_STRING:
+               switch (cf_pair_value_type(cp)) {
+               case T_BARE_WORD:
+               case T_SINGLE_QUOTED_STRING:
                        break;
-                       case T_BACK_QUOTED_STRING:
-                       case T_DOUBLE_QUOTED_STRING:
-                               do_xlat = true;         
+
+               case T_BACK_QUOTED_STRING:
+               case T_DOUBLE_QUOTED_STRING:
+                       do_xlat = true;
                        break;
-                       default:
-                               rad_assert(0);
-                               goto error;
+
+               default:
+                       rad_assert(0);
+                       goto error;
                }
-               
+
                if (op == T_OP_CMP_FALSE) {
                        passed[last_pass] = NULL;
                } else if (do_xlat) {
                        char *exp = NULL;
-                       
-                       if (radius_xlat(exp, 0, request, value, NULL, NULL) <= 0) {
+
+                       if (radius_axlat(&exp, request, value, NULL, NULL) <= 0) {
                                RDEBUG("Skipping attribute \"%s\"", attr);
-                                      
+
                                talloc_free(exp);
-                               
+
                                continue;
                        }
-                       
+
                        expanded[last_exp++] = exp;
                        passed[last_pass] = exp;
-               /* 
+               /*
                 *      Static strings
                 */
                } else {
                        memcpy(&(passed[last_pass]), &value, sizeof(passed[last_pass]));
                }
-               
+
                passed[last_pass + 1] = NULL;
-               
+
                mod_s[total].mod_values = &(passed[last_pass]);
-                                       
+
                last_pass += 2;
-               
-               switch (op)
-               {
+
+               switch (op) {
                /*
                 *  T_OP_EQ is *NOT* supported, it is impossible to
                 *  support because of the lack of transactions in LDAP
@@ -1091,28 +1275,28 @@ static rlm_rcode_t user_modify(ldap_instance_t *inst, REQUEST *request, ldap_acc
 #endif
                default:
                        REDEBUG("Operator '%s' is not supported for LDAP modify operations",
-                               fr_int2str(fr_tokens, op, "¿unknown?"));
-                              
+                               fr_int2str(fr_tokens, op, "<INVALID>"));
+
                        goto error;
                }
-               
+
                /*
                 *      Now we know the value is ok, copy the pointers into
                 *      the ldapmod struct.
                 */
-               memcpy(&(mod_s[total].mod_type), &(attr), sizeof(mod_s[total].mod_type));
-               
+               memcpy(&(mod_s[total].mod_type), &attr, sizeof(mod_s[total].mod_type));
+
                mod_p[total] = &(mod_s[total]);
                total++;
        }
-       
+
        if (total == 0) {
                rcode = RLM_MODULE_NOOP;
                goto release;
        }
-       
+
        mod_p[total] = NULL;
-       
+
        conn = rlm_ldap_get_socket(inst, request);
        if (!conn) return RLM_MODULE_FAIL;
 
@@ -1121,39 +1305,52 @@ static rlm_rcode_t user_modify(ldap_instance_t *inst, REQUEST *request, ldap_acc
        if (!dn || (rcode != RLM_MODULE_OK)) {
                goto error;
        }
-       
-       rcode = rlm_ldap_modify(inst, request, &conn, dn, modify);
-       
+
+       status = rlm_ldap_modify(inst, request, &conn, dn, modify);
+       switch (status) {
+       case LDAP_PROC_SUCCESS:
+               break;
+
+       case LDAP_PROC_REJECT:
+       case LDAP_PROC_BAD_DN:
+               rcode = RLM_MODULE_INVALID;
+               break;
+
+       default:
+               rcode = RLM_MODULE_FAIL;
+               break;
+       };
+
        release:
        error:
        /*
         *      Free up any buffers we allocated for xlat expansion
-        */     
+        */
        for (i = 0; i < last_exp; i++) {
                talloc_free(expanded[i]);
        }
 
        rlm_ldap_release_socket(inst, conn);
-       
+
        return rcode;
 }
 
-static rlm_rcode_t mod_accounting(void *instance, REQUEST * request) {
-       ldap_instance_t *inst = instance;               
+static rlm_rcode_t CC_HINT(nonnull) mod_accounting(void *instance, REQUEST * request) {
+       ldap_instance_t *inst = instance;
 
        if (inst->accounting) {
-               return user_modify(inst, request, inst->accounting); 
+               return user_modify(inst, request, inst->accounting);
        }
-       
+
        return RLM_MODULE_NOOP;
 }
 
-static rlm_rcode_t mod_post_auth(void *instance, REQUEST * request)
+static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST * request)
 {
        ldap_instance_t *inst = instance;
 
        if (inst->postauth) {
-               return user_modify(inst, request, inst->postauth); 
+               return user_modify(inst, request, inst->postauth);
        }
 
        return RLM_MODULE_NOOP;