/** Modify something in the LDAP directory * * Binds as the administrative user and attempts to modify an LDAP object. * * @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 of the object to modify. * @param[in] mods to make, see 'man ldap_modify' for more information. * @param[in] serverctrls Search controls to pass to the server. May be NULL. * @param[in] clientctrls Search controls for ldap_modify. May be NULL. * @return One of the LDAP_PROC_* (#fr_ldap_rcode_t) values. */ fr_ldap_rcode_t fr_ldap_modify(REQUEST *request, fr_ldap_connection_t **pconn, char const *dn, LDAPMod *mods[], LDAPControl **serverctrls, LDAPControl **clientctrls) { fr_ldap_rcode_t status = LDAP_PROC_ERROR; int msgid; // Message id returned by ldap_search_ext. LDAPControl *our_serverctrls[LDAP_MAX_CONTROLS]; LDAPControl *our_clientctrls[LDAP_MAX_CONTROLS]; fr_ldap_control_merge(our_serverctrls, our_clientctrls, sizeof(our_serverctrls) / sizeof(*our_serverctrls), sizeof(our_clientctrls) / sizeof(*our_clientctrls), *pconn, serverctrls, clientctrls); rad_assert(*pconn && (*pconn)->handle); if (RDEBUG_ENABLED4) fr_ldap_timeout_debug(request, *pconn, NULL, __FUNCTION__); /* * Perform all modifications as the admin user. */ if ((*pconn)->rebound) { status = fr_ldap_bind(request, pconn, (*pconn)->config->admin_identity, (*pconn)->config->admin_password, &(*pconn)->config->admin_sasl, NULL, NULL, NULL); if (status != LDAP_PROC_SUCCESS) { return LDAP_PROC_ERROR; } rad_assert(*pconn); (*pconn)->rebound = false; } RDEBUG2("Modifying object with DN \"%s\"", dn); (void) ldap_modify_ext((*pconn)->handle, dn, mods, our_serverctrls, our_clientctrls, &msgid); RDEBUG2("Waiting for modify result..."); status = fr_ldap_result(NULL, NULL, *pconn, msgid, 0, dn, NULL); switch (status) { case LDAP_PROC_SUCCESS: break; case LDAP_PROC_BAD_CONN: break; /* FALL-THROUGH */ default: ROPTIONAL(RPEDEBUG, RPERROR, "Failed modifying object"); goto finish; } finish: return status; }
/** Search for something in the LDAP directory * * Binds as the administrative user and performs a search, dealing with any errors. * * @param[out] result Where to store the result. Must be freed with ldap_msgfree * if LDAP_PROC_SUCCESS is returned. * May be NULL in which case result will be automatically freed after use. * @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 use as base for the search. * @param[in] scope to use (LDAP_SCOPE_BASE, LDAP_SCOPE_ONE, LDAP_SCOPE_SUB). * @param[in] filter to use, should be pre-escaped. * @param[in] attrs to retrieve. * @param[in] serverctrls Search controls to pass to the server. May be NULL. * @param[in] clientctrls Search controls for ldap_search. May be NULL. * @return One of the LDAP_PROC_* (#fr_ldap_rcode_t) values. */ fr_ldap_rcode_t fr_ldap_search(LDAPMessage **result, REQUEST *request, fr_ldap_connection_t **pconn, char const *dn, int scope, char const *filter, char const * const *attrs, LDAPControl **serverctrls, LDAPControl **clientctrls) { fr_ldap_rcode_t status = LDAP_PROC_ERROR; LDAPMessage *our_result = NULL; fr_ldap_config_t const *handle_config = (*pconn)->config; int msgid; // Message id returned by // ldap_search_ext. int count = 0; // Number of results we got. struct timeval tv; // Holds timeout values. LDAPControl *our_serverctrls[LDAP_MAX_CONTROLS]; LDAPControl *our_clientctrls[LDAP_MAX_CONTROLS]; fr_ldap_control_merge(our_serverctrls, our_clientctrls, sizeof(our_serverctrls) / sizeof(*our_serverctrls), sizeof(our_clientctrls) / sizeof(*our_clientctrls), *pconn, serverctrls, clientctrls); rad_assert(*pconn && (*pconn)->handle); if (DEBUG_ENABLED4 || (request && RDEBUG_ENABLED4)) { fr_ldap_timeout_debug(request, *pconn, NULL, __FUNCTION__); } /* * OpenLDAP library doesn't declare attrs array as const, but * it really should be *sigh*. */ char **search_attrs; memcpy(&search_attrs, &attrs, sizeof(attrs)); /* * Do all searches as the admin user. */ if ((*pconn)->rebound) { status = fr_ldap_bind(request, pconn, (*pconn)->config->admin_identity, (*pconn)->config->admin_password, &(*pconn)->config->admin_sasl, NULL, NULL, NULL); if (status != LDAP_PROC_SUCCESS) return LDAP_PROC_ERROR; rad_assert(*pconn); (*pconn)->rebound = false; } if (filter) { ROPTIONAL(RDEBUG2, DEBUG2, "Performing search in \"%s\" with filter \"%s\", scope \"%s\"", dn, filter, fr_int2str(fr_ldap_scope, scope, "<INVALID>")); } else { ROPTIONAL(RDEBUG2, DEBUG2, "Performing unfiltered search in \"%s\", scope \"%s\"", dn, fr_int2str(fr_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. */ memset(&tv, 0, sizeof(tv)); (void) ldap_search_ext((*pconn)->handle, dn, scope, filter, search_attrs, 0, our_serverctrls, our_clientctrls, NULL, 0, &msgid); ROPTIONAL(RDEBUG2, DEBUG2, "Waiting for search result..."); status = fr_ldap_result(&our_result, NULL, *pconn, msgid, 1, dn, NULL); switch (status) { case LDAP_PROC_SUCCESS: break; default: ROPTIONAL(RPEDEBUG, PERROR, "Failed performing search"); goto finish; } count = ldap_count_entries((*pconn)->handle, our_result); if (count < 0) { ROPTIONAL(REDEBUG, ERROR, "Error counting results: %s", fr_ldap_error_str(*pconn)); status = LDAP_PROC_ERROR; ldap_msgfree(our_result); our_result = NULL; } else if (count == 0) { ROPTIONAL(RDEBUG2, DEBUG2, "Search returned no results"); status = LDAP_PROC_NO_RESULT; ldap_msgfree(our_result); our_result = NULL; } finish: /* * 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; }
/** Search for something in the LDAP directory * * Binds as the administrative user and performs a search, dealing with any errors. * * @param[out] msgid to match response to request. * @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 use as base for the search. * @param[in] scope to use (LDAP_SCOPE_BASE, LDAP_SCOPE_ONE, LDAP_SCOPE_SUB). * @param[in] filter to use, should be pre-escaped. * @param[in] attrs to retrieve. * @param[in] serverctrls Search controls to pass to the server. May be NULL. * @param[in] clientctrls Search controls for ldap_search. May be NULL. * @return One of the LDAP_PROC_* (#fr_ldap_rcode_t) values. */ fr_ldap_rcode_t fr_ldap_search_async(int *msgid, REQUEST *request, fr_ldap_connection_t **pconn, char const *dn, int scope, char const *filter, char const * const *attrs, LDAPControl **serverctrls, LDAPControl **clientctrls) { fr_ldap_rcode_t status = LDAP_PROC_ERROR; fr_ldap_config_t const *handle_config = (*pconn)->config; struct timeval tv; // Holds timeout values. LDAPControl *our_serverctrls[LDAP_MAX_CONTROLS]; LDAPControl *our_clientctrls[LDAP_MAX_CONTROLS]; fr_ldap_control_merge(our_serverctrls, our_clientctrls, sizeof(our_serverctrls) / sizeof(*our_serverctrls), sizeof(our_clientctrls) / sizeof(*our_clientctrls), *pconn, serverctrls, clientctrls); rad_assert(*pconn && (*pconn)->handle); if (DEBUG_ENABLED4 || (request && RDEBUG_ENABLED4)) fr_ldap_timeout_debug(request, *pconn, NULL, __FUNCTION__); /* * OpenLDAP library doesn't declare attrs array as const, but * it really should be *sigh*. */ char **search_attrs; memcpy(&search_attrs, &attrs, sizeof(attrs)); /* * Do all searches as the admin user. */ if ((*pconn)->rebound) { status = fr_ldap_bind(request, pconn, (*pconn)->config->admin_identity, (*pconn)->config->admin_password, &(*pconn)->config->admin_sasl, NULL, NULL, NULL); if (status != LDAP_PROC_SUCCESS) return LDAP_PROC_ERROR; rad_assert(*pconn); (*pconn)->rebound = false; } if (filter) { ROPTIONAL(RDEBUG2, DEBUG2, "Performing search in \"%s\" with filter \"%s\", scope \"%s\"", dn, filter, fr_int2str(fr_ldap_scope, scope, "<INVALID>")); } else { ROPTIONAL(RDEBUG2, DEBUG2, "Performing unfiltered search in \"%s\", scope \"%s\"", dn, fr_int2str(fr_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. */ memset(&tv, 0, sizeof(tv)); if (ldap_search_ext((*pconn)->handle, dn, scope, filter, search_attrs, 0, our_serverctrls, our_clientctrls, NULL, 0, msgid) != LDAP_SUCCESS) { int ldap_errno; ldap_get_option((*pconn)->handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno); ERROR("Failed performing search: %s", ldap_err2string(ldap_errno)); return LDAP_PROC_ERROR; } return LDAP_PROC_SUCCESS; }
/** Send a bind request to a aserver * * @param[in] el the event occurred in. * @param[in] fd the event occurred on. * @param[in] flags from kevent. * @param[in] uctx bind_ctx containing credentials, and connection config/handle. */ static void _ldap_bind_io_write(fr_event_list_t *el, int fd, UNUSED int flags, void *uctx) { fr_ldap_bind_ctx_t *bind_ctx = talloc_get_type_abort(uctx, fr_ldap_bind_ctx_t); fr_ldap_connection_t *c = bind_ctx->c; LDAPControl *our_serverctrls[LDAP_MAX_CONTROLS]; LDAPControl *our_clientctrls[LDAP_MAX_CONTROLS]; struct timeval tv = { 0, 0 }; int ret; struct berval cred; fr_ldap_control_merge(our_serverctrls, our_clientctrls, sizeof(our_serverctrls) / sizeof(*our_serverctrls), sizeof(our_clientctrls) / sizeof(*our_clientctrls), c, bind_ctx->serverctrls, bind_ctx->clientctrls); /* * Set timeout to be 0.0, which is the magic * non-blocking value. */ (void) ldap_set_option(c->handle, LDAP_OPT_NETWORK_TIMEOUT, &tv); if (bind_ctx->password) { memcpy(&cred.bv_val, &bind_ctx->password, sizeof(cred.bv_val)); cred.bv_len = talloc_array_length(bind_ctx->password) - 1; } else { cred.bv_val = NULL; cred.bv_len = 0; } /* * Yes, confusingly named. This is the simple version * of the SASL bind function that should always be * available. */ ret = ldap_sasl_bind(c->handle, bind_ctx->bind_dn, LDAP_SASL_SIMPLE, &cred, our_serverctrls, our_clientctrls, &bind_ctx->msgid); switch (ret) { /* * If the handle was not connected, this operation * can return either LDAP_X_CONNECTING or LDAP_SUCCESS * depending on how fast the connection came up * and whether it was connectionless. */ case LDAP_X_CONNECTING: /* Connection in progress - retry later */ ret = ldap_get_option(c->handle, LDAP_OPT_DESC, &fd); if (!fr_cond_assert(ret == LDAP_OPT_SUCCESS)) { error: talloc_free(bind_ctx); fr_ldap_connection_timeout_reset(c); fr_ldap_state_error(c); /* Restart the connection state machine */ return; } ret = fr_event_fd_insert(bind_ctx, el, fd, NULL, _ldap_bind_io_write, /* We'll be called again when the conn is open */ _ldap_bind_io_error, bind_ctx); if (!fr_cond_assert(ret == 0)) goto error; break; case LDAP_SUCCESS: ret = fr_event_fd_insert(bind_ctx, el, fd, _ldap_bind_io_read, NULL, _ldap_bind_io_error, bind_ctx); if (!fr_cond_assert(ret == 0)) goto error; break; default: ERROR("Bind failed: %s", ldap_err2string(ret)); goto error; } fr_ldap_connection_timeout_reset(c); }