Pass a threadsafe ctx into fr_connection_pool create callback
[freeradius.git] / src / modules / rlm_ldap / ldap.c
index eaee9b6..6fe2473 100644 (file)
  * @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
  *
@@ -341,8 +339,7 @@ static ldap_rcode_t rlm_ldap_result(ldap_instance_t const *inst, ldap_handle_t c
                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)) {
@@ -489,9 +486,7 @@ static ldap_rcode_t rlm_ldap_result(ldap_instance_t const *inst, ldap_handle_t c
                ldap_memfree(part_dn);
        }
 
-       if (our_err) {
-               talloc_free(our_err);
-       }
+       talloc_free(our_err);
 
        if ((lib_errno || srv_errno) && *result) {
                ldap_msgfree(*result);
@@ -514,93 +509,106 @@ static ldap_rcode_t rlm_ldap_result(ldap_instance_t const *inst, ldap_handle_t c
  * @return one of the LDAP_PROC_* values.
  */
 ldap_rcode_t rlm_ldap_bind(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn, char const *dn,
-                          char const *password, int retry)
+                          char const *password, bool retry)
 {
-       ldap_rcode_t    status;
+       ldap_rcode_t    status = LDAP_PROC_ERROR;
 
        int             msgid;
 
        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:
-               LDAP_DBG_REQ("Bind successful");
-               break;
-       case LDAP_PROC_NOT_PERMITTED:
-               LDAP_ERR_REQ("Bind was not permitted: %s", error);
-               LDAP_EXT_REQ();
+               status = rlm_ldap_result(inst, *pconn, msgid, dn, NULL, &error, &extra);
+               switch (status) {
+               case LDAP_PROC_SUCCESS:
+                       LDAP_DBG_REQ("Bind successful");
+                       break;
 
-               break;
+               case LDAP_PROC_NOT_PERMITTED:
+                       LDAP_ERR_REQ("Bind was not permitted: %s", error);
+                       LDAP_EXT_REQ();
 
-       case LDAP_PROC_REJECT:
-               LDAP_ERR_REQ("Bind credentials incorrect: %s", error);
-               LDAP_EXT_REQ();
+                       break;
 
-               break;
+               case LDAP_PROC_REJECT:
+                       LDAP_ERR_REQ("Bind credentials incorrect: %s", error);
+                       LDAP_EXT_REQ();
 
-       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);
+                       break;
 
-                               talloc_free(extra); /* don't leak debug info */
+               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);
 
-                               goto retry;
-                       }
-               };
+                                       talloc_free(extra); /* don't leak debug info */
 
-               status = LDAP_PROC_ERROR;
+                                       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:
+                       /*
+                        *      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.
@@ -633,6 +641,9 @@ ldap_rcode_t rlm_ldap_search(ldap_instance_t const *inst, REQUEST *request, ldap
        char const      *error = NULL;
        char            *extra = NULL;
 
+       int             i;
+
+
        rad_assert(*pconn && (*pconn)->handle);
 
        /*
@@ -670,14 +681,21 @@ ldap_rcode_t rlm_ldap_search(ldap_instance_t const *inst, REQUEST *request, ldap
         */
        memset(&tv, 0, sizeof(tv));
        tv.tv_sec = inst->res_timeout;
-retry:
-       (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) {
+       /*
+        *      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) {
@@ -685,7 +703,7 @@ retry:
 
                                talloc_free(extra); /* don't leak debug info */
 
-                               goto retry;
+                               continue;
                        }
 
                        status = LDAP_PROC_ERROR;
@@ -696,6 +714,16 @@ retry:
                        if (extra) LDAP_ERR_REQ("%s", extra);
 
                        goto finish;
+               }
+
+               break;
+       }
+
+       if (i < 0) {
+               LDAP_ERR_REQ("Hit reconnection limit");
+               status = LDAP_PROC_ERROR;
+
+               goto finish;
        }
 
        count = ldap_count_entries((*pconn)->handle, our_result);
@@ -713,10 +741,8 @@ retry:
                our_result = NULL;
        }
 
-       finish:
-       if (extra) {
-               talloc_free(extra);
-       }
+finish:
+       talloc_free(extra);
 
        /*
         *      We always need to get the result to count entries, but the caller
@@ -755,6 +781,8 @@ ldap_rcode_t rlm_ldap_modify(ldap_instance_t const *inst, REQUEST *request, ldap
        char const      *error = NULL;
        char            *extra = NULL;
 
+       int             i;
+
        rad_assert(*pconn && (*pconn)->handle);
 
        /*
@@ -771,40 +799,51 @@ ldap_rcode_t rlm_ldap_modify(ldap_instance_t const *inst, REQUEST *request, ldap
                (*pconn)->rebound = false;
        }
 
-       RDEBUG2("Modifying object with DN \"%s\"", dn);
-       retry:
-       (void) ldap_modify_ext((*pconn)->handle, dn, mods, NULL, NULL, &msgid);
+       /*
+        *      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;
 
-       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);
+                       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 */
+                                       talloc_free(extra); /* don't leak debug info */
 
-                               goto retry;
-                       }
+                                       continue;
+                               }
 
-                       status = LDAP_PROC_ERROR;
+                               status = LDAP_PROC_ERROR;
 
-                       /* FALL-THROUGH */
-               default:
-                       REDEBUG("Failed modifying object: %s", error);
-                       REDEBUG("%s", extra);
+                               /* FALL-THROUGH */
+                       default:
+                               REDEBUG("Failed modifying object: %s", error);
+                               REDEBUG("%s", extra);
 
-                       goto finish;
+                               goto finish;
+               }
+
+               break;
        }
 
-       finish:
-       if (extra) {
-               talloc_free(extra);
+       if (i < 0) {
+               LDAP_ERR_REQ("Hit reconnection limit");
+               status = LDAP_PROC_ERROR;
        }
 
+finish:
+       talloc_free(extra);
+
        return status;
 }
 
@@ -882,15 +921,15 @@ char const *rlm_ldap_find_user(ldap_instance_t const *inst, REQUEST *request, ld
 
        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(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;
        }
 
@@ -898,9 +937,11 @@ char const *rlm_ldap_find_user(ldap_instance_t const *inst, REQUEST *request, ld
        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;
@@ -1047,14 +1088,32 @@ static int rlm_ldap_rebind(LDAP *handle, LDAP_CONST char *url, UNUSED ber_tag_t
 }
 #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 to allocate connection handle memory in.
  * @param instance rlm_ldap instance.
  * @return A new connection handle or NULL on error.
  */
-void *mod_conn_create(void *instance)
+void *mod_conn_create(TALLOC_CTX *ctx, void *instance)
 {
        ldap_rcode_t status;
 
@@ -1067,8 +1126,9 @@ void *mod_conn_create(void *instance)
        /*
         *      Allocate memory for the handle.
         */
-       conn = talloc_zero(instance, ldap_handle_t);
+       conn = talloc_zero(ctx, ldap_handle_t);
        if (!conn) return NULL;
+       talloc_set_destructor(conn, _mod_conn_free);
 
        conn->inst = inst;
        conn->rebound = false;
@@ -1094,6 +1154,7 @@ void *mod_conn_create(void *instance)
                        goto error;
                }
        }
+       DEBUG3("rlm_ldap: New libldap handle %p", conn->handle);
 
        /*
         *      We now have a connection structure, but no actual TCP connection.
@@ -1118,6 +1179,13 @@ void *mod_conn_create(void *instance)
        }
 
        /*
+        *      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_unset) {
@@ -1134,10 +1202,14 @@ void *mod_conn_create(void *instance)
                }
        }
 
-       memset(&tv, 0, sizeof(tv));
-       tv.tv_sec = inst->net_timeout;
+#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);
+               do_ldap_option(LDAP_OPT_NETWORK_TIMEOUT, "net_timeout", &tv);
+       }
+#endif
 
        do_ldap_option(LDAP_OPT_TIMELIMIT, "srv_timelimit", &(inst->srv_timelimit));
 
@@ -1222,34 +1294,12 @@ void *mod_conn_create(void *instance)
 
        return conn;
 
-       error:
-       if (conn->handle) ldap_unbind_s(conn->handle);
+error:
        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 instance rlm_ldap instance.
- * @param handle to destroy.
- * @return always indicates success.
- */
-int mod_conn_delete(UNUSED void *instance, void *handle)
-{
-       ldap_handle_t *conn = handle;
-
-       ldap_unbind_s(conn->handle);
-       talloc_free(conn);
-
-       return 0;
-}
-
-
 /** 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).
@@ -1262,7 +1312,6 @@ ldap_handle_t *rlm_ldap_get_socket(ldap_instance_t const *inst, UNUSED REQUEST *
        return fr_connection_get(inst->pool);
 }
 
-
 /** Frees an LDAP socket back to the connection pool
  *
  * If the socket was rebound chasing a referral onto another server then we destroy it.