/* SSSD Async LDAP Helper routines Copyright (C) Simo Sorce - 2009 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include "util/util.h" #include "util/strtonum.h" #include "util/probes.h" #include "util/sss_chain_id.h" #include "providers/ldap/sdap_async_private.h" #define REPLY_REALLOC_INCREMENT 10 struct sdap_op { struct sdap_op *prev, *next; struct sdap_handle *sh; uint64_t chain_id; int msgid; char *stat_info; uint64_t start_time; int timeout; bool done; sdap_op_callback_t *callback; void *data; struct tevent_context *ev; struct sdap_msg *list; struct sdap_msg *last; }; int sdap_op_get_msgid(struct sdap_op *op) { return op != NULL ? op->msgid : 0; } /* ==LDAP-Memory-Handling================================================= */ static int lmsg_destructor(void *mem) { ldap_msgfree((LDAPMessage *)mem); return 0; } /* ==sdap-handle-utility-functions======================================== */ static inline void sdap_handle_release(struct sdap_handle *sh); static int sdap_handle_destructor(void *mem); struct sdap_handle *sdap_handle_create(TALLOC_CTX *memctx) { struct sdap_handle *sh; sh = talloc_zero(memctx, struct sdap_handle); if (!sh) return NULL; talloc_set_destructor((TALLOC_CTX *)sh, sdap_handle_destructor); return sh; } static int sdap_handle_destructor(void *mem) { struct sdap_handle *sh = talloc_get_type(mem, struct sdap_handle); /* if the structure is currently locked, then mark it to be released * and prevent talloc from freeing the memory */ if (sh->destructor_lock) { sh->release_memory = true; return -1; } sdap_handle_release(sh); return 0; } static void sdap_call_op_callback(struct sdap_op *op, struct sdap_msg *reply, int error) { uint64_t time_spend; const char *info = (op->stat_info == NULL ? "-" : op->stat_info); if (op->start_time != 0) { time_spend = get_spend_time_us(op->start_time); DEBUG(SSSDBG_PERF_STAT, "Handling LDAP operation [%d][%s] took %s.\n", op->msgid, info, sss_format_time(time_spend)); /* time_spend is in us and timeout in s */ if (op->timeout != 0 && (time_spend / op->timeout) >= (80 * 10000)) { DEBUG(SSSDBG_IMPORTANT_INFO, "LDAP operation [%d][%s] seems slow, " "took more than 80%% of timeout [%d].\n", op->msgid, info, op->timeout); } /* Avoid multiple outputs for the same operation if multiple results * are returned */ op->start_time = 0; } op->callback(op, reply, error, op->data); } static void sdap_handle_release(struct sdap_handle *sh) { struct sdap_op *op; DEBUG(SSSDBG_TRACE_INTERNAL, "Trace: sh[%p], connected[%d], ops[%p], ldap[%p], " "destructor_lock[%d], release_memory[%d]\n", sh, (int)sh->connected, sh->ops, sh->ldap, (int)sh->destructor_lock, (int)sh->release_memory); if (sh->destructor_lock) return; sh->destructor_lock = true; /* make sure nobody tries to reuse this connection from now on */ sh->connected = false; remove_ldap_connection_callbacks(sh); while (sh->ops) { op = sh->ops; sdap_call_op_callback(op, NULL, EIO); /* calling the callback may result in freeing the op */ /* check if it is still the same or avoid freeing */ if (op == sh->ops) talloc_free(op); } if (sh->ldap) { ldap_unbind_ext(sh->ldap, NULL, NULL); sh->ldap = NULL; } /* ok, we have done the job, unlock now */ sh->destructor_lock = false; /* finally if a destructor was ever called, free sh before * exiting */ if (sh->release_memory) { /* neutralize the destructor as we already handled * all was needed to be released */ talloc_set_destructor((TALLOC_CTX *)sh, NULL); talloc_free(sh); } } /* ==Parse-Results-And-Handle-Disconnections============================== */ static void sdap_process_message(struct tevent_context *ev, struct sdap_handle *sh, LDAPMessage *msg); static void sdap_process_result(struct tevent_context *ev, void *pvt); static void sdap_process_next_reply(struct tevent_context *ev, struct tevent_timer *te, struct timeval tv, void *pvt); void sdap_ldap_result(struct tevent_context *ev, struct tevent_fd *fde, uint16_t flags, void *pvt) { sdap_process_result(ev, pvt); } static void sdap_ldap_next_result(struct tevent_context *ev, struct tevent_timer *te, struct timeval tv, void *pvt) { sdap_process_result(ev, pvt); } static struct sdap_op *sdap_get_message_op(struct sdap_handle *sh, LDAPMessage *msg) { struct sdap_op *op; int msgid; msgid = ldap_msgid(msg); if (msgid == -1) { DEBUG(SSSDBG_OP_FAILURE, "Invalid message id!\n"); return NULL; } for (op = sh->ops; op; op = op->next) { if (op->msgid == msgid) { return op; } } return NULL; } static void sdap_process_result(struct tevent_context *ev, void *pvt) { struct sdap_handle *sh = talloc_get_type(pvt, struct sdap_handle); uint64_t old_chain_id; struct timeval no_timeout = {0, 0}; struct tevent_timer *te; struct sdap_op *op; LDAPMessage *msg; int ret; /* This is a top level event, always use chain id 0. We set a proper id * later in this function once we can match the reply with an operation. */ old_chain_id = sss_chain_id_set(0); DEBUG(SSSDBG_TRACE_INTERNAL, "Trace: sh[%p], connected[%d], ops[%p], ldap[%p]\n", sh, (int)sh->connected, sh->ops, sh->ldap); if (!sh->connected || !sh->ldap) { DEBUG(SSSDBG_OP_FAILURE, "ERROR: LDAP connection is not connected!\n"); sdap_handle_release(sh); return; } ret = ldap_result(sh->ldap, LDAP_RES_ANY, 0, &no_timeout, &msg); if (ret == 0) { /* this almost always means we have reached the end of * the list of received messages */ DEBUG(SSSDBG_TRACE_INTERNAL, "Trace: end of ldap_result list\n"); return; } if (ret == -1) { ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &ret); DEBUG(SSSDBG_OP_FAILURE, "ldap_result error: [%s]\n", ldap_err2string(ret)); sdap_handle_release(sh); return; } /* We don't know if this will be the last result. * * important: we must do this before actually processing the message * because the message processing might even free the sdap_handler * so it must be the last operation. * FIXME: use tevent_immediate/tevent_queues, when available */ memset(&no_timeout, 0, sizeof(struct timeval)); te = tevent_add_timer(ev, sh, no_timeout, sdap_ldap_next_result, sh); if (!te) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to add critical timer to fetch next result!\n"); } /* Set the chain id if we can match the operation. */ op = sdap_get_message_op(sh, msg); if (op != NULL) { sss_chain_id_set(op->chain_id); } /* now process this message */ sdap_process_message(ev, sh, msg); /* Restore the chain id. */ sss_chain_id_set(old_chain_id); } static const char *sdap_ldap_result_str(int msgtype) { switch (msgtype) { case LDAP_RES_BIND: return "LDAP_RES_BIND"; case LDAP_RES_SEARCH_ENTRY: return "LDAP_RES_SEARCH_ENTRY"; case LDAP_RES_SEARCH_REFERENCE: return "LDAP_RES_SEARCH_REFERENCE"; case LDAP_RES_SEARCH_RESULT: return "LDAP_RES_SEARCH_RESULT"; case LDAP_RES_MODIFY: return "LDAP_RES_MODIFY"; case LDAP_RES_ADD: return "LDAP_RES_ADD"; case LDAP_RES_DELETE: return "LDAP_RES_DELETE"; case LDAP_RES_MODDN: /* These are the same result case LDAP_RES_MODRDN: case LDAP_RES_RENAME: */ return "LDAP_RES_RENAME"; case LDAP_RES_COMPARE: return "LDAP_RES_COMPARE"; case LDAP_RES_EXTENDED: return "LDAP_RES_EXTENDED"; case LDAP_RES_INTERMEDIATE: return "LDAP_RES_INTERMEDIATE"; case LDAP_RES_ANY: return "LDAP_RES_ANY"; case LDAP_RES_UNSOLICITED: return "LDAP_RES_UNSOLICITED"; default: /* Unmatched, fall through */ break; } /* Unknown result type */ return "Unknown result type!"; } /* process a message calling the right operation callback. * msg is completely taken care of (including freeing it) * NOTE: this function may even end up freeing the sdap_handle * so sdap_handle must not be used after this function is called */ static void sdap_process_message(struct tevent_context *ev, struct sdap_handle *sh, LDAPMessage *msg) { struct sdap_msg *reply; struct sdap_op *op; int msgtype; int ret; msgtype = ldap_msgtype(msg); op = sdap_get_message_op(sh, msg); if (op == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Unmatched msgid, discarding message (type: %0x)\n", msgtype); ldap_msgfree(msg); return; } /* shouldn't happen */ if (op->done) { DEBUG(SSSDBG_OP_FAILURE, "Operation [%p] already handled (type: %0x)\n", op, msgtype); ldap_msgfree(msg); return; } DEBUG(SSSDBG_TRACE_ALL, "Message type: [%s]\n", sdap_ldap_result_str(msgtype)); switch (msgtype) { case LDAP_RES_SEARCH_ENTRY: case LDAP_RES_SEARCH_REFERENCE: /* go and process entry */ break; case LDAP_RES_BIND: case LDAP_RES_SEARCH_RESULT: case LDAP_RES_MODIFY: case LDAP_RES_ADD: case LDAP_RES_DELETE: case LDAP_RES_MODDN: case LDAP_RES_COMPARE: case LDAP_RES_EXTENDED: case LDAP_RES_INTERMEDIATE: /* no more results expected with this msgid */ op->done = true; break; default: /* unknown msg type?? */ DEBUG(SSSDBG_CRIT_FAILURE, "Couldn't figure out the msg type! [%0x]\n", msgtype); ldap_msgfree(msg); return; } reply = talloc_zero(op, struct sdap_msg); if (!reply) { ldap_msgfree(msg); ret = ENOMEM; } else { reply->msg = msg; ret = sss_mem_attach(reply, msg, lmsg_destructor); if (ret != EOK) { ldap_msgfree(msg); talloc_zfree(reply); } } if (op->list) { /* list exist, queue it */ op->last->next = reply; op->last = reply; } else { /* create list, then call callback */ op->list = op->last = reply; /* must be the last operation as it may end up freeing all memory * including all ops handlers */ sdap_call_op_callback(op, reply, ret); } } static void sdap_unlock_next_reply(struct sdap_op *op) { struct timeval tv; struct tevent_timer *te; struct sdap_msg *next_reply; if (op->list) { next_reply = op->list->next; /* get rid of the previous reply, it has been processed already */ talloc_zfree(op->list); op->list = next_reply; } /* if there are still replies to parse, queue a new operation */ if (op->list) { /* use a very small timeout, so that fd operations have a chance to be * served while processing a long reply */ tv = tevent_timeval_current(); /* wait 5 microsecond */ tv.tv_usec += 5; tv.tv_sec += tv.tv_usec / 1000000; tv.tv_usec = tv.tv_usec % 1000000; te = tevent_add_timer(op->ev, op, tv, sdap_process_next_reply, op); if (!te) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to add critical timer for next reply!\n"); sdap_call_op_callback(op, NULL, EFAULT); } } } static void sdap_process_next_reply(struct tevent_context *ev, struct tevent_timer *te, struct timeval tv, void *pvt) { struct sdap_op *op = talloc_get_type(pvt, struct sdap_op); sdap_call_op_callback(op, op->list, EOK); } /* ==LDAP-Operations-Helpers============================================== */ static int sdap_op_destructor(void *mem) { struct sdap_op *op = (struct sdap_op *)mem; DLIST_REMOVE(op->sh->ops, op); if (op->done) { DEBUG(SSSDBG_TRACE_INTERNAL, "Operation %d finished\n", op->msgid); return 0; } /* we don't check the result here, if a message was really abandoned, * hopefully the server will get an abandon. * If the operation was already fully completed, this is going to be * just a noop */ DEBUG(SSSDBG_TRACE_LIBS, "Abandoning operation %d\n", op->msgid); ldap_abandon_ext(op->sh->ldap, op->msgid, NULL, NULL); return 0; } static void sdap_op_timeout(struct tevent_req *req) { struct sdap_op *op = tevent_req_callback_data(req, struct sdap_op); /* should never happen, but just in case */ if (op->done) { DEBUG(SSSDBG_OP_FAILURE, "Timeout happened after op was finished !?\n"); return; } /* signal the caller that we have a timeout */ DEBUG(SSSDBG_TRACE_LIBS, "Issuing timeout [ldap_opt_timeout] for message id %d\n", op->msgid); sdap_call_op_callback(op, NULL, ETIMEDOUT); } int sdap_op_add(TALLOC_CTX *memctx, struct tevent_context *ev, struct sdap_handle *sh, int msgid, const char *stat_info, sdap_op_callback_t *callback, void *data, int timeout, struct sdap_op **_op) { struct sdap_op *op; op = talloc_zero(memctx, struct sdap_op); if (!op) return ENOMEM; op->start_time = get_start_time(); op->timeout = timeout; op->sh = sh; op->msgid = msgid; if (stat_info != NULL) { op->stat_info = talloc_strdup(op, stat_info); if (op->stat_info == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Failed to copy stat_info, ignored.\n"); } } op->callback = callback; op->data = data; op->ev = ev; op->chain_id = sss_chain_id_get(); DEBUG(SSSDBG_TRACE_INTERNAL, "New operation %d timeout %d\n", op->msgid, timeout); /* check if we need to set a timeout */ if (timeout) { struct tevent_req *req; struct timeval tv; tv = tevent_timeval_current(); tv = tevent_timeval_add(&tv, timeout, 0); /* allocate on op, so when it get freed the timeout is removed */ req = tevent_wakeup_send(op, ev, tv); if (!req) { talloc_zfree(op); return ENOMEM; } tevent_req_set_callback(req, sdap_op_timeout, op); } DLIST_ADD(sh->ops, op); talloc_set_destructor((TALLOC_CTX *)op, sdap_op_destructor); *_op = op; return EOK; } /* ==Modify-Password====================================================== */ static errno_t sdap_chpass_result(TALLOC_CTX *mem_ctx, int ldap_result, const char *ldap_msg, char **_user_msg) { errno_t ret; switch (ldap_result) { case LDAP_SUCCESS: /* There's no need to set _user_msg here. */ return EOK; case LDAP_CONSTRAINT_VIOLATION: if (ldap_msg == NULL || *ldap_msg == '\0') { ldap_msg = "Please make sure the password " "meets the complexity constraints."; } ret = ERR_CHPASS_DENIED; break; default: ret = ERR_NETWORK_IO; } if (ldap_msg != NULL) { *_user_msg = talloc_strdup(mem_ctx, ldap_msg); if (*_user_msg == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n"); return ENOMEM; } } return ret; } struct sdap_exop_modify_passwd_state { struct sdap_handle *sh; struct sdap_op *op; char *user_error_message; }; static void sdap_exop_modify_passwd_done(struct sdap_op *op, struct sdap_msg *reply, int error, void *pvt); struct tevent_req *sdap_exop_modify_passwd_send(TALLOC_CTX *memctx, struct tevent_context *ev, struct sdap_handle *sh, char *user_dn, const char *password, const char *new_password, int timeout) { struct tevent_req *req = NULL; struct sdap_exop_modify_passwd_state *state; int ret; BerElement *ber = NULL; struct berval *bv = NULL; int msgid; LDAPControl **request_controls = NULL; LDAPControl *ctrls[2] = { NULL, NULL }; char *stat_info; req = tevent_req_create(memctx, &state, struct sdap_exop_modify_passwd_state); if (!req) return NULL; state->sh = sh; state->user_error_message = NULL; ber = ber_alloc_t( LBER_USE_DER ); if (ber == NULL) { DEBUG(SSSDBG_TRACE_LIBS, "ber_alloc_t failed.\n"); talloc_zfree(req); return NULL; } ret = ber_printf( ber, "{tststs}", LDAP_TAG_EXOP_MODIFY_PASSWD_ID, user_dn, LDAP_TAG_EXOP_MODIFY_PASSWD_OLD, password, LDAP_TAG_EXOP_MODIFY_PASSWD_NEW, new_password); if (ret == -1) { DEBUG(SSSDBG_CRIT_FAILURE, "ber_printf failed.\n"); ber_free(ber, 1); talloc_zfree(req); return NULL; } ret = ber_flatten(ber, &bv); ber_free(ber, 1); if (ret == -1) { DEBUG(SSSDBG_CRIT_FAILURE, "ber_flatten failed.\n"); talloc_zfree(req); return NULL; } ret = sdap_control_create(state->sh, LDAP_CONTROL_PASSWORDPOLICYREQUEST, 0, NULL, 0, &ctrls[0]); if (ret != LDAP_SUCCESS && ret != LDAP_NOT_SUPPORTED) { DEBUG(SSSDBG_CRIT_FAILURE, "sdap_control_create failed to create " "Password Policy control.\n"); ret = ERR_INTERNAL; goto fail; } request_controls = ctrls; DEBUG(SSSDBG_CONF_SETTINGS, "Executing extended operation\n"); ret = ldap_extended_operation(state->sh->ldap, LDAP_EXOP_MODIFY_PASSWD, bv, request_controls, NULL, &msgid); ber_bvfree(bv); if (ctrls[0]) ldap_control_free(ctrls[0]); if (ret == -1 || msgid == -1) { DEBUG(SSSDBG_CRIT_FAILURE, "ldap_extended_operation failed\n"); ret = ERR_NETWORK_IO; goto fail; } DEBUG(SSSDBG_TRACE_INTERNAL, "ldap_extended_operation sent, msgid = %d\n", msgid); stat_info = talloc_asprintf(state, "server: [%s] modify passwd dn: [%s]", sdap_get_server_peer_str_safe(state->sh), user_dn); if (stat_info == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Failed to create info string, ignored.\n"); } ret = sdap_op_add(state, ev, state->sh, msgid, stat_info, sdap_exop_modify_passwd_done, req, timeout, &state->op); if (ret) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set up operation!\n"); ret = ERR_INTERNAL; goto fail; } return req; fail: tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } static void sdap_exop_modify_passwd_done(struct sdap_op *op, struct sdap_msg *reply, int error, void *pvt) { struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); struct sdap_exop_modify_passwd_state *state = tevent_req_data(req, struct sdap_exop_modify_passwd_state); char *errmsg = NULL; int ret; LDAPControl **response_controls = NULL; int c; ber_int_t pp_grace; ber_int_t pp_expire; LDAPPasswordPolicyError pp_error; int result; if (error) { tevent_req_error(req, error); return; } ret = ldap_parse_result(state->sh->ldap, reply->msg, &result, NULL, &errmsg, NULL, &response_controls, 0); if (ret != LDAP_SUCCESS) { DEBUG(SSSDBG_OP_FAILURE, "ldap_parse_result failed (%d)\n", state->op->msgid); ret = ERR_INTERNAL; goto done; } if (response_controls == NULL) { DEBUG(SSSDBG_FUNC_DATA, "Server returned no controls.\n"); } else { for (c = 0; response_controls[c] != NULL; c++) { DEBUG(SSSDBG_TRACE_ALL, "Server returned control [%s].\n", response_controls[c]->ldctl_oid); if (strcmp(response_controls[c]->ldctl_oid, LDAP_CONTROL_PASSWORDPOLICYRESPONSE) == 0) { ret = ldap_parse_passwordpolicy_control(state->sh->ldap, response_controls[c], &pp_expire, &pp_grace, &pp_error); if (ret != LDAP_SUCCESS) { DEBUG(SSSDBG_CRIT_FAILURE, "ldap_parse_passwordpolicy_control failed.\n"); ret = ERR_NETWORK_IO; goto done; } DEBUG(SSSDBG_TRACE_LIBS, "Password Policy Response: expire [%d] grace [%d] " "error [%s].\n", pp_expire, pp_grace, ldap_passwordpolicy_err2txt(pp_error)); } } } DEBUG(SSSDBG_MINOR_FAILURE, "ldap_extended_operation result: %s(%d), %s\n", sss_ldap_err2string(result), result, errmsg); ret = sdap_chpass_result(state, result, errmsg, &state->user_error_message); done: ldap_controls_free(response_controls); ldap_memfree(errmsg); if (ret == EOK) { tevent_req_done(req); } else { tevent_req_error(req, ret); } } errno_t sdap_exop_modify_passwd_recv(struct tevent_req *req, TALLOC_CTX * mem_ctx, char **user_error_message) { struct sdap_exop_modify_passwd_state *state = tevent_req_data(req, struct sdap_exop_modify_passwd_state); /* We want to return the error message even on failure */ *user_error_message = talloc_steal(mem_ctx, state->user_error_message); TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } struct sdap_modify_state { struct tevent_context *ev; struct sdap_handle *sh; struct sdap_op *op; int ldap_result; char *ldap_msg; }; static void sdap_modify_done(struct sdap_op *op, struct sdap_msg *reply, int error, void *pvt); static struct tevent_req * sdap_modify_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_handle *sh, int timeout, const char *dn, char *attr, char **values) { struct tevent_req *req; struct sdap_modify_state *state; LDAPMod **mods; errno_t ret; int msgid; char *stat_info; req = tevent_req_create(mem_ctx, &state, struct sdap_modify_state); if (req == NULL) { return NULL; } state->ev = ev; state->sh = sh; mods = talloc_zero_array(state, LDAPMod *, 2); if (mods == NULL) { ret = ENOMEM; goto done; } mods[0] = talloc_zero(mods, LDAPMod); if (mods[0] == NULL) { ret = ENOMEM; goto done; } mods[0]->mod_op = LDAP_MOD_REPLACE; mods[0]->mod_type = attr; mods[0]->mod_vals.modv_strvals = values; mods[1] = NULL; ret = ldap_modify_ext(state->sh->ldap, dn, mods, NULL, NULL, &msgid); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "ldap_modify_ext() failed [%d]\n", ret); goto done; } stat_info = talloc_asprintf(state, "server: [%s] modify dn: [%s] attr: [%s]", sdap_get_server_peer_str_safe(state->sh), dn, attr); if (stat_info == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Failed to create info string, ignored.\n"); } ret = sdap_op_add(state, state->ev, state->sh, msgid, stat_info, sdap_modify_done, req, timeout, &state->op); if (ret) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set up operation!\n"); goto done; } ret = EOK; done: if (ret != EOK) { tevent_req_error(req, ret); tevent_req_post(req, ev); } return req; } static void sdap_modify_done(struct sdap_op *op, struct sdap_msg *reply, int error, void *pvt) { struct tevent_req *req; struct sdap_modify_state *state; char *errmsg; errno_t ret; int result; int lret; req = talloc_get_type(pvt, struct tevent_req); state = tevent_req_data(req, struct sdap_modify_state); if (error) { tevent_req_error(req, error); return; } lret = ldap_parse_result(state->sh->ldap, reply->msg, &result, NULL, &errmsg, NULL, NULL, 0); if (lret != LDAP_SUCCESS) { DEBUG(SSSDBG_OP_FAILURE, "ldap_parse_result failed (%d)\n", state->op->msgid); ret = EIO; goto done; } DEBUG(SSSDBG_TRACE_LIBS, "ldap_modify result: %s(%d), %s\n", sss_ldap_err2string(result), result, errmsg); state->ldap_result = result; if (errmsg != NULL) { state->ldap_msg = talloc_strdup(state, errmsg); if (state->ldap_msg == NULL) { ret = ENOMEM; goto done; } } ret = EOK; done: ldap_memfree(errmsg); if (ret == EOK) { tevent_req_done(req); } else { tevent_req_error(req, ret); } } static errno_t sdap_modify_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, int *_ldap_result, char **_ldap_msg) { struct sdap_modify_state *state; state = tevent_req_data(req, struct sdap_modify_state); TEVENT_REQ_RETURN_ON_ERROR(req); if (_ldap_result != NULL) { *_ldap_result = state->ldap_result; } if (_ldap_msg != NULL) { *_ldap_msg = talloc_steal(mem_ctx, state->ldap_msg); } return EOK; } struct sdap_modify_passwd_state { const char *dn; char *user_msg; }; static void sdap_modify_passwd_done(struct tevent_req *subreq); struct tevent_req * sdap_modify_passwd_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_handle *sh, int timeout, char *attr, const char *user_dn, const char *new_password) { struct tevent_req *req; struct tevent_req *subreq; struct sdap_modify_passwd_state *state; char **values; errno_t ret; req = tevent_req_create(mem_ctx, &state, struct sdap_modify_passwd_state); if (req == NULL) { return NULL; } state->dn = user_dn; values = talloc_zero_array(state, char *, 2); if (values == NULL) { ret = ENOMEM; goto done; } values[0] = talloc_strdup(values, new_password); if (values[0] == NULL) { ret = ENOMEM; goto done; } subreq = sdap_modify_send(state, ev, sh, timeout, user_dn, attr, values); if (subreq == NULL) { ret = ENOMEM; goto done; } tevent_req_set_callback(subreq, sdap_modify_passwd_done, req); ret = EOK; done: if (ret != EOK) { tevent_req_error(req, ret); tevent_req_post(req, ev); } return req; } static void sdap_modify_passwd_done(struct tevent_req *subreq) { struct tevent_req *req; struct sdap_modify_passwd_state *state; int ldap_result; char *ldap_msg; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct sdap_modify_passwd_state); ret = sdap_modify_recv(state, subreq, &ldap_result, &ldap_msg); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Password change for [%s] failed [%d]: %s\n", state->dn, ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } ret = sdap_chpass_result(state, ldap_result, ldap_msg, &state->user_msg); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Password change for [%s] failed [%d]: %s\n", state->dn, ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } DEBUG(SSSDBG_TRACE_FUNC, "Password change for [%s] was successful\n", state->dn); tevent_req_done(req); } errno_t sdap_modify_passwd_recv(struct tevent_req *req, TALLOC_CTX * mem_ctx, char **_user_error_message) { struct sdap_modify_passwd_state *state; state = tevent_req_data(req, struct sdap_modify_passwd_state); /* We want to return the error message even on failure */ *_user_error_message = talloc_steal(mem_ctx, state->user_msg); TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } /* ==Update-passwordLastChanged-attribute====================== */ struct sdap_modify_shadow_lastchange_state { const char *dn; }; static void sdap_modify_shadow_lastchange_done(struct tevent_req *subreq); struct tevent_req * sdap_modify_shadow_lastchange_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sdap_handle *sh, const char *dn, char *attr) { struct tevent_req *req; struct tevent_req *subreq; struct sdap_modify_shadow_lastchange_state *state; char **values; errno_t ret; req = tevent_req_create(mem_ctx, &state, struct sdap_modify_shadow_lastchange_state); if (req == NULL) { return NULL; } state->dn = dn; values = talloc_zero_array(state, char *, 2); if (values == NULL) { ret = ENOMEM; goto done; } /* The attribute contains number of days since the epoch */ values[0] = talloc_asprintf(values, "%"SPRItime, time(NULL)/86400); if (values[0] == NULL) { ret = ENOMEM; goto done; } subreq = sdap_modify_send(state, ev, sh, 5, dn, attr, values); if (subreq == NULL) { ret = ENOMEM; goto done; } tevent_req_set_callback(subreq, sdap_modify_shadow_lastchange_done, req); ret = EOK; done: if (ret != EOK) { tevent_req_error(req, ret); tevent_req_post(req, ev); } return req; } static void sdap_modify_shadow_lastchange_done(struct tevent_req *subreq) { struct tevent_req *req; struct sdap_modify_shadow_lastchange_state *state; errno_t ret; req = tevent_req_callback_data(subreq, struct tevent_req); state = tevent_req_data(req, struct sdap_modify_shadow_lastchange_state); ret = sdap_modify_recv(state, subreq, NULL, NULL); talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "shadowLastChange change for [%s] failed [%d]: %s\n", state->dn, ret, sss_strerror(ret)); tevent_req_error(req, ret); return; } DEBUG(SSSDBG_TRACE_FUNC, "shadowLastChange change for [%s] was successful\n", state->dn); tevent_req_done(req); } errno_t sdap_modify_shadow_lastchange_recv(struct tevent_req *req) { TEVENT_REQ_RETURN_ON_ERROR(req); return EOK; } /* ==Fetch-RootDSE============================================= */ struct sdap_get_rootdse_state { struct tevent_context *ev; struct sdap_options *opts; struct sdap_handle *sh; struct sysdb_attrs *rootdse; }; static void sdap_get_rootdse_done(struct tevent_req *subreq); struct tevent_req *sdap_get_rootdse_send(TALLOC_CTX *memctx, struct tevent_context *ev, struct sdap_options *opts, struct sdap_handle *sh) { struct tevent_req *req, *subreq; struct sdap_get_rootdse_state *state; const char *attrs[] = { "*", "altServer", SDAP_ROOTDSE_ATTR_NAMING_CONTEXTS, "supportedControl", "supportedExtension", "supportedFeatures", "supportedLDAPVersion", "supportedSASLMechanisms", SDAP_ROOTDSE_ATTR_AD_VERSION, SDAP_ROOTDSE_ATTR_DEFAULT_NAMING_CONTEXT, SDAP_IPA_LAST_USN, SDAP_AD_LAST_USN, NULL }; DEBUG(SSSDBG_TRACE_ALL, "Getting rootdse\n"); req = tevent_req_create(memctx, &state, struct sdap_get_rootdse_state); if (!req) return NULL; state->ev = ev; state->opts = opts; state->sh = sh; state->rootdse = NULL; subreq = sdap_get_generic_send(state, ev, opts, sh, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, NULL, 0, dp_opt_get_int(state->opts->basic, SDAP_SEARCH_TIMEOUT), false); if (!subreq) { talloc_zfree(req); return NULL; } tevent_req_set_callback(subreq, sdap_get_rootdse_done, req); return req; } /* This is not a real attribute, it's just there to avoid * actually pulling real data down, to save bandwidth */ static void sdap_get_rootdse_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct sdap_get_rootdse_state *state = tevent_req_data(req, struct sdap_get_rootdse_state); struct sysdb_attrs **results; size_t num_results; int ret; ret = sdap_get_generic_recv(subreq, state, &num_results, &results); talloc_zfree(subreq); if (ret) { tevent_req_error(req, ret); return; } if (num_results == 0 || !results) { DEBUG(SSSDBG_OP_FAILURE, "RootDSE could not be retrieved. " "Please check that anonymous access to RootDSE is allowed\n" ); tevent_req_error(req, ENOENT); return; } if (num_results > 1) { DEBUG(SSSDBG_OP_FAILURE, "Multiple replies when searching for RootDSE??\n"); tevent_req_error(req, EIO); return; } state->rootdse = talloc_steal(state, results[0]); talloc_zfree(results); DEBUG(SSSDBG_TRACE_INTERNAL, "Got rootdse\n"); tevent_req_done(req); return; } int sdap_get_rootdse_recv(struct tevent_req *req, TALLOC_CTX *memctx, struct sysdb_attrs **rootdse) { struct sdap_get_rootdse_state *state = tevent_req_data(req, struct sdap_get_rootdse_state); TEVENT_REQ_RETURN_ON_ERROR(req); *rootdse = talloc_steal(memctx, state->rootdse); return EOK; } /* ==Helpers for parsing replies============================== */ struct sdap_reply { size_t reply_max; size_t reply_count; struct sysdb_attrs **reply; }; static errno_t add_to_reply(TALLOC_CTX *mem_ctx, struct sdap_reply *sreply, struct sysdb_attrs *msg) { if (sreply->reply == NULL || sreply->reply_max == sreply->reply_count) { sreply->reply_max += REPLY_REALLOC_INCREMENT; sreply->reply = talloc_realloc(mem_ctx, sreply->reply, struct sysdb_attrs *, sreply->reply_max); if (sreply->reply == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "talloc_realloc failed.\n"); return ENOMEM; } } sreply->reply[sreply->reply_count++] = talloc_steal(sreply->reply, msg); return EOK; } struct sdap_deref_reply { size_t reply_max; size_t reply_count; struct sdap_deref_attrs **reply; }; static errno_t add_to_deref_reply(TALLOC_CTX *mem_ctx, int num_maps, struct sdap_deref_reply *dreply, struct sdap_deref_attrs **res) { int i; if (res == NULL) { /* Nothing to add, probably ACIs prevented us from dereferencing * the attribute */ return EOK; } for (i=0; i < num_maps; i++) { if (res[i]->attrs == NULL) continue; /* Nothing in this map */ if (dreply->reply == NULL || dreply->reply_max == dreply->reply_count) { dreply->reply_max += REPLY_REALLOC_INCREMENT; dreply->reply = talloc_realloc(mem_ctx, dreply->reply, struct sdap_deref_attrs *, dreply->reply_max); if (dreply->reply == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "talloc_realloc failed.\n"); return ENOMEM; } } dreply->reply[dreply->reply_count++] = talloc_steal(dreply->reply, res[i]); } return EOK; } const char *sdap_get_server_peer_str(struct sdap_handle *sh) { int ret; int fd; struct sockaddr sa; socklen_t sa_len = sizeof(sa); char ip[NI_MAXHOST]; static char out[NI_MAXHOST + 8]; int port; ret = get_fd_from_ldap(sh->ldap, &fd); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "cannot get sdap fd\n"); return NULL; } ret = getpeername(fd, &sa, &sa_len); if (ret == -1) { DEBUG(SSSDBG_MINOR_FAILURE, "getpeername failed\n"); return NULL; } switch (sa.sa_family) { case AF_INET: { struct sockaddr_in in; socklen_t in_len = sizeof(in); ret = getpeername(fd, (struct sockaddr *)(&in), &in_len); if (ret == -1) { DEBUG(SSSDBG_MINOR_FAILURE, "getpeername failed\n"); return NULL; } ret = getnameinfo((struct sockaddr *)(&in), in_len, ip, sizeof(ip), NULL, 0, NI_NUMERICHOST); if (ret != 0) { DEBUG(SSSDBG_MINOR_FAILURE, "getnameinfo failed\n"); return NULL; } port = ntohs(in.sin_port); ret = snprintf(out, sizeof(out), "%s:%d", ip, port); break; } case AF_INET6: { struct sockaddr_in6 in6; socklen_t in6_len = sizeof(in6); ret = getpeername(fd, (struct sockaddr *)(&in6), &in6_len); if (ret == -1) { DEBUG(SSSDBG_MINOR_FAILURE, "getpeername failed\n"); return NULL; } ret = getnameinfo((struct sockaddr *)(&in6), in6_len, ip, sizeof(ip), NULL, 0, NI_NUMERICHOST); if (ret != 0) { DEBUG(SSSDBG_MINOR_FAILURE, "getnameinfo failed\n"); return NULL; } port = ntohs(in6.sin6_port); ret = snprintf(out, sizeof(out), "[%s]:%d", ip, port); break; } case AF_UNIX: { struct sockaddr_un un; socklen_t un_len = sizeof(un); ret = getpeername(fd, (struct sockaddr *)(&un), &un_len); if (ret == -1) { DEBUG(SSSDBG_MINOR_FAILURE, "getpeername failed\n"); return NULL; } ret = snprintf(out, sizeof(out), "%.*s", (int)strnlen(un.sun_path, un_len - offsetof(struct sockaddr_un, sun_path)), un.sun_path); break; } default: return NULL; } if (ret < 0 || ret >= sizeof(out)) { return NULL; } return out; } const char *sdap_get_server_peer_str_safe(struct sdap_handle *sh) { const char *ip = sdap_get_server_peer_str(sh); return ip != NULL ? ip : "- IP not available -"; } static void sdap_print_server(struct sdap_handle *sh) { const char *ip; /* The purpose of the call is to add the server IP to the debug output if * debug_level is SSSDBG_TRACE_INTERNAL or higher */ if (DEBUG_IS_SET(SSSDBG_TRACE_INTERNAL)) { ip = sdap_get_server_peer_str(sh); if (ip != NULL) { DEBUG(SSSDBG_TRACE_INTERNAL, "Searching %s\n", ip); } else { DEBUG(SSSDBG_OP_FAILURE, "sdap_get_server_peer_str failed.\n"); } } } /* ==Generic Search exposing all options======================= */ typedef errno_t (*sdap_parse_cb)(struct sdap_handle *sh, struct sdap_msg *msg, void *pvt); struct sdap_get_generic_ext_state { struct tevent_context *ev; struct sdap_options *opts; struct sdap_handle *sh; const char *search_base; int scope; const char *filter; const char **attrs; int timeout; int sizelimit; struct sdap_op *op; struct berval cookie; LDAPControl **serverctrls; int nserverctrls; LDAPControl **clientctrls; size_t ref_count; char **refs; sdap_parse_cb parse_cb; void *cb_data; unsigned int flags; }; static errno_t sdap_get_generic_ext_step(struct tevent_req *req); static void sdap_get_generic_op_finished(struct sdap_op *op, struct sdap_msg *reply, int error, void *pvt); enum { /* Be silent about exceeded size limit */ SDAP_SRCH_FLG_SIZELIMIT_SILENT = 1 << 0, /* Allow paging */ SDAP_SRCH_FLG_PAGING = 1 << 1, /* Only attribute descriptions are requested */ SDAP_SRCH_FLG_ATTRS_ONLY = 1 << 2, }; static struct tevent_req * sdap_get_generic_ext_send(TALLOC_CTX *memctx, struct tevent_context *ev, struct sdap_options *opts, struct sdap_handle *sh, const char *search_base, int scope, const char *filter, const char **attrs, LDAPControl **serverctrls, LDAPControl **clientctrls, int sizelimit, int timeout, sdap_parse_cb parse_cb, void *cb_data, unsigned int flags) { errno_t ret; struct sdap_get_generic_ext_state *state; struct tevent_req *req; int i; LDAPControl *control; req = tevent_req_create(memctx, &state, struct sdap_get_generic_ext_state); if (!req) return NULL; state->ev = ev; state->opts = opts; state->sh = sh; state->search_base = search_base; state->scope = scope; state->filter = filter; state->attrs = attrs; state->op = NULL; state->sizelimit = sizelimit; state->timeout = timeout; state->cookie.bv_len = 0; state->cookie.bv_val = NULL; state->parse_cb = parse_cb; state->cb_data = cb_data; state->clientctrls = clientctrls; state->flags = flags; if (state->sh == NULL || state->sh->ldap == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Trying LDAP search while not connected.\n"); tevent_req_error(req, EIO); tevent_req_post(req, ev); return req; } sdap_print_server(sh); /* Be extra careful and never allow paging for BASE searches, * even if requested. */ if (scope == LDAP_SCOPE_BASE && (flags & SDAP_SRCH_FLG_PAGING)) { /* Disable paging */ state->flags &= ~SDAP_SRCH_FLG_PAGING; DEBUG(SSSDBG_TRACE_FUNC, "WARNING: Disabling paging because scope is set to base.\n"); } /* Also check for deref/asq requests and force * paging on for those requests */ /* X-DEREF */ control = ldap_control_find(LDAP_CONTROL_X_DEREF, serverctrls, NULL); if (control) { state->flags |= SDAP_SRCH_FLG_PAGING; } /* ASQ */ control = ldap_control_find(LDAP_SERVER_ASQ_OID, serverctrls, NULL); if (control) { state->flags |= SDAP_SRCH_FLG_PAGING; } for (state->nserverctrls=0; serverctrls && serverctrls[state->nserverctrls]; state->nserverctrls++) ; /* One extra space for NULL, one for page control */ state->serverctrls = talloc_array(state, LDAPControl *, state->nserverctrls+2); if (!state->serverctrls) { tevent_req_error(req, ENOMEM); tevent_req_post(req, ev); return req; } for (i=0; i < state->nserverctrls; i++) { state->serverctrls[i] = serverctrls[i]; } state->serverctrls[i] = NULL; PROBE(SDAP_GET_GENERIC_EXT_SEND, state->search_base, state->scope, state->filter, state->attrs); ret = sdap_get_generic_ext_step(req); if (ret != EOK) { tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } return req; } static errno_t sdap_get_generic_ext_step(struct tevent_req *req) { struct sdap_get_generic_ext_state *state = tevent_req_data(req, struct sdap_get_generic_ext_state); char *errmsg; int lret; int optret; errno_t ret; int msgid; bool disable_paging; char *stat_info; LDAPControl *page_control = NULL; /* Make sure to free any previous operations so * if we are handling a large number of pages we * don't waste memory. */ talloc_zfree(state->op); DEBUG(SSSDBG_TRACE_FUNC, "calling ldap_search_ext with [%s][%s].\n", state->filter ? state->filter : "no filter", state->search_base); if (state->attrs) { for (int i = 0; state->attrs[i]; i++) { DEBUG(SSSDBG_TRACE_LIBS, "Requesting attrs: [%s]\n", state->attrs[i]); } } disable_paging = dp_opt_get_bool(state->opts->basic, SDAP_DISABLE_PAGING); if (!disable_paging && (state->flags & SDAP_SRCH_FLG_PAGING) && sdap_is_control_supported(state->sh, LDAP_CONTROL_PAGEDRESULTS)) { lret = ldap_create_page_control(state->sh->ldap, state->sh->page_size, state->cookie.bv_val ? &state->cookie : NULL, false, &page_control); if (lret != LDAP_SUCCESS) { ret = EIO; goto done; } state->serverctrls[state->nserverctrls] = page_control; state->serverctrls[state->nserverctrls+1] = NULL; } lret = ldap_search_ext(state->sh->ldap, state->search_base, state->scope, state->filter, discard_const(state->attrs), (state->flags & SDAP_SRCH_FLG_ATTRS_ONLY), state->serverctrls, state->clientctrls, NULL, state->sizelimit, &msgid); ldap_control_free(page_control); state->serverctrls[state->nserverctrls] = NULL; if (lret != LDAP_SUCCESS) { DEBUG(SSSDBG_MINOR_FAILURE, "ldap_search_ext failed: %s\n", sss_ldap_err2string(lret)); if (lret == LDAP_SERVER_DOWN) { ret = ETIMEDOUT; optret = sss_ldap_get_diagnostic_msg(state, state->sh->ldap, &errmsg); if (optret == LDAP_SUCCESS) { DEBUG(SSSDBG_MINOR_FAILURE, "Connection error: %s\n", errmsg); sss_log(SSS_LOG_ERR, "LDAP connection error: %s", errmsg); } else { sss_log(SSS_LOG_ERR, "LDAP connection error, %s", sss_ldap_err2string(lret)); } } else if (lret == LDAP_FILTER_ERROR) { ret = ERR_INVALID_FILTER; } else { ret = EIO; } goto done; } DEBUG(SSSDBG_TRACE_INTERNAL, "ldap_search_ext called, msgid = %d\n", msgid); stat_info = talloc_asprintf(state, "server: [%s] filter: [%s] base: [%s]", sdap_get_server_peer_str_safe(state->sh), state->filter, state->search_base); if (stat_info == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Failed to create info string, ignored.\n"); } ret = sdap_op_add(state, state->ev, state->sh, msgid, stat_info, sdap_get_generic_op_finished, req, state->timeout, &state->op); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set up operation!\n"); goto done; } done: return ret; } static errno_t sdap_get_generic_ext_add_references(struct sdap_get_generic_ext_state *state, char **refs) { int i; if (refs == NULL) { /* Rare, but it's possible that we might get a reference result with * no references attached. */ return EOK; } for (i = 0; refs[i]; i++) { DEBUG(SSSDBG_TRACE_LIBS, "Additional References: %s\n", refs[i]); } /* Extend the size of the ref array */ state->refs = talloc_realloc(state, state->refs, char *, state->ref_count + i); if (state->refs == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "talloc_realloc failed extending ref_array.\n"); return ENOMEM; } /* Copy in all the references */ for (i = 0; refs[i]; i++) { state->refs[state->ref_count + i] = talloc_strdup(state->refs, refs[i]); if (state->refs[state->ref_count + i] == NULL) { return ENOMEM; } } state->ref_count += i; return EOK; } static void sdap_get_generic_op_finished(struct sdap_op *op, struct sdap_msg *reply, int error, void *pvt) { struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); struct sdap_get_generic_ext_state *state = tevent_req_data(req, struct sdap_get_generic_ext_state); char *errmsg = NULL; char **refs = NULL; int result; int ret; int lret; ber_int_t total_count; struct berval cookie; LDAPControl **returned_controls = NULL; LDAPControl *page_control; if (error) { tevent_req_error(req, error); return; } switch (ldap_msgtype(reply->msg)) { case LDAP_RES_SEARCH_REFERENCE: ret = ldap_parse_reference(state->sh->ldap, reply->msg, &refs, NULL, 0); if (ret != LDAP_SUCCESS) { DEBUG(SSSDBG_OP_FAILURE, "ldap_parse_reference failed (%d)\n", state->op->msgid); tevent_req_error(req, EIO); return; } ret = sdap_get_generic_ext_add_references(state, refs); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sdap_get_generic_ext_add_references failed: %s(%d)\n", sss_strerror(ret), ret); ldap_memvfree((void **)refs); tevent_req_error(req, ret); return; } /* Remove the original strings */ ldap_memvfree((void **)refs); /* unlock the operation so that we can proceed with the next result */ sdap_unlock_next_reply(state->op); break; case LDAP_RES_SEARCH_ENTRY: ret = state->parse_cb(state->sh, reply, state->cb_data); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "reply parsing callback failed.\n"); tevent_req_error(req, ret); return; } sdap_unlock_next_reply(state->op); break; case LDAP_RES_SEARCH_RESULT: ret = ldap_parse_result(state->sh->ldap, reply->msg, &result, NULL, &errmsg, &refs, &returned_controls, 0); if (ret != LDAP_SUCCESS) { DEBUG(SSSDBG_OP_FAILURE, "ldap_parse_result failed (%d)\n", state->op->msgid); tevent_req_error(req, EIO); return; } DEBUG(SSSDBG_TRACE_FUNC, "Search result: %s(%d), %s\n", sss_ldap_err2string(result), result, errmsg ? errmsg : "no errmsg set"); if (result == LDAP_SIZELIMIT_EXCEEDED || result == LDAP_ADMINLIMIT_EXCEEDED) { /* Try to return what we've got */ if ( ! (state->flags & SDAP_SRCH_FLG_SIZELIMIT_SILENT)) { DEBUG(SSSDBG_MINOR_FAILURE, "LDAP sizelimit was exceeded, " "returning incomplete data\n"); } } else if (result == LDAP_INAPPROPRIATE_MATCHING) { /* This error should only occur when we're testing for * specialized functionality like the LDAP matching rule * filter for Active Directory. Warn at a higher log * level and return EIO. */ DEBUG(SSSDBG_TRACE_INTERNAL, "LDAP_INAPPROPRIATE_MATCHING: %s\n", errmsg ? errmsg : "no errmsg set"); ldap_memfree(errmsg); tevent_req_error(req, EIO); return; } else if (result == LDAP_UNAVAILABLE_CRITICAL_EXTENSION) { ldap_memfree(errmsg); tevent_req_error(req, ENOTSUP); return; } else if (result == LDAP_REFERRAL) { ret = sdap_get_generic_ext_add_references(state, refs); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sdap_get_generic_ext_add_references failed: %s(%d)\n", sss_strerror(ret), ret); tevent_req_error(req, ret); } /* For referrals, we need to fall through as if it was LDAP_SUCCESS */ } else if (result != LDAP_SUCCESS && result != LDAP_NO_SUCH_OBJECT) { DEBUG(SSSDBG_OP_FAILURE, "Unexpected result from ldap: %s(%d), %s\n", sss_ldap_err2string(result), result, errmsg ? errmsg : "no errmsg set"); ldap_memfree(errmsg); tevent_req_error(req, EIO); return; } ldap_memfree(errmsg); /* Determine if there are more pages to retrieve */ page_control = ldap_control_find(LDAP_CONTROL_PAGEDRESULTS, returned_controls, NULL ); if (!page_control) { /* No paging support. We are done */ tevent_req_done(req); return; } lret = ldap_parse_pageresponse_control(state->sh->ldap, page_control, &total_count, &cookie); ldap_controls_free(returned_controls); if (lret != LDAP_SUCCESS) { DEBUG(SSSDBG_CRIT_FAILURE, "Could not determine page control\n"); tevent_req_error(req, EIO); return; } DEBUG(SSSDBG_TRACE_INTERNAL, "Total count [%d]\n", total_count); if (cookie.bv_val != NULL && cookie.bv_len > 0) { /* Cookie contains data, which means there are more requests * to be processed. */ talloc_zfree(state->cookie.bv_val); state->cookie.bv_len = cookie.bv_len; state->cookie.bv_val = talloc_memdup(state, cookie.bv_val, cookie.bv_len); if (!state->cookie.bv_val) { tevent_req_error(req, ENOMEM); return; } ber_memfree(cookie.bv_val); ret = sdap_get_generic_ext_step(req); if (ret != EOK) { tevent_req_error(req, ENOMEM); return; } return; } /* The cookie must be freed even if len == 0 */ ber_memfree(cookie.bv_val); /* This was the last page. We're done */ tevent_req_done(req); return; default: /* what is going on here !? */ tevent_req_error(req, EIO); return; } } static int sdap_get_generic_ext_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, size_t *ref_count, char ***refs) { struct sdap_get_generic_ext_state *state = tevent_req_data(req, struct sdap_get_generic_ext_state); PROBE(SDAP_GET_GENERIC_EXT_RECV, state->search_base, state->scope, state->filter); TEVENT_REQ_RETURN_ON_ERROR(req); if (ref_count) { *ref_count = state->ref_count; } if (refs) { *refs = talloc_steal(mem_ctx, state->refs); } return EOK; } /* This search handler can be used by most calls */ static void generic_ext_search_handler(struct tevent_req *subreq, struct sdap_options *opts) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); int ret; size_t ref_count, i; char **refs; ret = sdap_get_generic_ext_recv(subreq, req, &ref_count, &refs); talloc_zfree(subreq); if (ret != EOK) { if (ret == ETIMEDOUT) { DEBUG(SSSDBG_CRIT_FAILURE, "sdap_get_generic_ext_recv failed: [%d]: %s " "[ldap_search_timeout]\n", ret, sss_strerror(ret)); } else { DEBUG(SSSDBG_CRIT_FAILURE, "sdap_get_generic_ext_recv request failed: [%d]: %s\n", ret, sss_strerror(ret)); } tevent_req_error(req, ret); return; } if (ref_count > 0) { /* We will ignore referrals in the generic handler */ DEBUG(SSSDBG_TRACE_ALL, "Request included referrals which were ignored.\n"); if (debug_level & SSSDBG_TRACE_ALL) { for(i = 0; i < ref_count; i++) { DEBUG(SSSDBG_TRACE_ALL, " Ref: %s\n", refs[i]); } } } talloc_free(refs); tevent_req_done(req); } /* ==Generic Search exposing all options======================= */ struct sdap_get_and_parse_generic_state { struct sdap_attr_map *map; int map_num_attrs; struct sdap_reply sreply; struct sdap_options *opts; }; static void sdap_get_and_parse_generic_done(struct tevent_req *subreq); static errno_t sdap_get_and_parse_generic_parse_entry(struct sdap_handle *sh, struct sdap_msg *msg, void *pvt); struct tevent_req *sdap_get_and_parse_generic_send(TALLOC_CTX *memctx, struct tevent_context *ev, struct sdap_options *opts, struct sdap_handle *sh, const char *search_base, int scope, const char *filter, const char **attrs, struct sdap_attr_map *map, int map_num_attrs, int attrsonly, LDAPControl **serverctrls, LDAPControl **clientctrls, int sizelimit, int timeout, bool allow_paging) { struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct sdap_get_and_parse_generic_state *state = NULL; unsigned int flags = 0; req = tevent_req_create(memctx, &state, struct sdap_get_and_parse_generic_state); if (!req) return NULL; state->map = map; state->map_num_attrs = map_num_attrs; state->opts = opts; if (allow_paging) { flags |= SDAP_SRCH_FLG_PAGING; } if (attrsonly) { flags |= SDAP_SRCH_FLG_ATTRS_ONLY; } subreq = sdap_get_generic_ext_send(state, ev, opts, sh, search_base, scope, filter, attrs, serverctrls, clientctrls, sizelimit, timeout, sdap_get_and_parse_generic_parse_entry, state, flags); if (!subreq) { talloc_zfree(req); return NULL; } tevent_req_set_callback(subreq, sdap_get_and_parse_generic_done, req); return req; } static errno_t sdap_get_and_parse_generic_parse_entry(struct sdap_handle *sh, struct sdap_msg *msg, void *pvt) { errno_t ret; struct sysdb_attrs *attrs; struct sdap_get_and_parse_generic_state *state = talloc_get_type(pvt, struct sdap_get_and_parse_generic_state); bool disable_range_rtrvl = dp_opt_get_bool(state->opts->basic, SDAP_DISABLE_RANGE_RETRIEVAL); ret = sdap_parse_entry(state, sh, msg, state->map, state->map_num_attrs, &attrs, disable_range_rtrvl); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "sdap_parse_entry failed [%d]: %s\n", ret, strerror(ret)); return ret; } ret = add_to_reply(state, &state->sreply, attrs); if (ret != EOK) { talloc_free(attrs); DEBUG(SSSDBG_CRIT_FAILURE, "add_to_reply failed.\n"); return ret; } /* add_to_reply steals attrs, no need to free them here */ return EOK; } static void sdap_get_and_parse_generic_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct sdap_get_and_parse_generic_state *state = tevent_req_data(req, struct sdap_get_and_parse_generic_state); return generic_ext_search_handler(subreq, state->opts); } int sdap_get_and_parse_generic_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, size_t *reply_count, struct sysdb_attrs ***reply) { struct sdap_get_and_parse_generic_state *state = tevent_req_data(req, struct sdap_get_and_parse_generic_state); TEVENT_REQ_RETURN_ON_ERROR(req); *reply_count = state->sreply.reply_count; *reply = talloc_steal(mem_ctx, state->sreply.reply); return EOK; } /* ==Simple generic search============================================== */ struct sdap_get_generic_state { size_t reply_count; struct sysdb_attrs **reply; }; static void sdap_get_generic_done(struct tevent_req *subreq); struct tevent_req *sdap_get_generic_send(TALLOC_CTX *memctx, struct tevent_context *ev, struct sdap_options *opts, struct sdap_handle *sh, const char *search_base, int scope, const char *filter, const char **attrs, struct sdap_attr_map *map, int map_num_attrs, int timeout, bool allow_paging) { struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct sdap_get_generic_state *state = NULL; req = tevent_req_create(memctx, &state, struct sdap_get_generic_state); if (!req) return NULL; subreq = sdap_get_and_parse_generic_send(memctx, ev, opts, sh, search_base, scope, filter, attrs, map, map_num_attrs, false, NULL, NULL, 0, timeout, allow_paging); if (subreq == NULL) { talloc_zfree(req); return NULL; } tevent_req_set_callback(subreq, sdap_get_generic_done, req); return req; } static void sdap_get_generic_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct sdap_get_generic_state *state = tevent_req_data(req, struct sdap_get_generic_state); errno_t ret; ret = sdap_get_and_parse_generic_recv(subreq, state, &state->reply_count, &state->reply); if (ret != EOK) { tevent_req_error(req, ret); return; } tevent_req_done(req); } int sdap_get_generic_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, size_t *reply_count, struct sysdb_attrs ***reply) { struct sdap_get_generic_state *state = tevent_req_data(req, struct sdap_get_generic_state); TEVENT_REQ_RETURN_ON_ERROR(req); *reply_count = state->reply_count; *reply = talloc_steal(mem_ctx, state->reply); return EOK; } /* ==OpenLDAP deref search============================================== */ static int sdap_x_deref_create_control(struct sdap_handle *sh, const char *deref_attr, const char **attrs, LDAPControl **ctrl); static void sdap_x_deref_search_done(struct tevent_req *subreq); static int sdap_x_deref_search_ctrls_destructor(void *ptr); static errno_t sdap_x_deref_parse_entry(struct sdap_handle *sh, struct sdap_msg *msg, void *pvt); struct sdap_x_deref_search_state { struct sdap_handle *sh; struct sdap_op *op; struct sdap_attr_map_info *maps; LDAPControl **ctrls; struct sdap_options *opts; bool ldap_ignore_unreadable_references; struct sdap_deref_reply dreply; int num_maps; }; static struct tevent_req * sdap_x_deref_search_send(TALLOC_CTX *memctx, struct tevent_context *ev, struct sdap_options *opts, struct sdap_handle *sh, const char *base_dn, const char *filter, const char *deref_attr, const char **attrs, struct sdap_attr_map_info *maps, int num_maps, int timeout) { struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct sdap_x_deref_search_state *state; int ret; req = tevent_req_create(memctx, &state, struct sdap_x_deref_search_state); if (!req) return NULL; state->sh = sh; state->maps = maps; state->op = NULL; state->opts = opts; state->num_maps = num_maps; state->ctrls = talloc_zero_array(state, LDAPControl *, 2); if (state->ctrls == NULL) { talloc_zfree(req); return NULL; } talloc_set_destructor((TALLOC_CTX *) state->ctrls, sdap_x_deref_search_ctrls_destructor); state->ldap_ignore_unreadable_references = dp_opt_get_bool(opts->basic, SDAP_IGNORE_UNREADABLE_REFERENCES); ret = sdap_x_deref_create_control(sh, deref_attr, attrs, &state->ctrls[0]); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Could not create OpenLDAP deref control\n"); talloc_zfree(req); return NULL; } DEBUG(SSSDBG_TRACE_FUNC, "Dereferencing entry [%s] using OpenLDAP deref\n", base_dn); subreq = sdap_get_generic_ext_send(state, ev, opts, sh, base_dn, filter == NULL ? LDAP_SCOPE_BASE : LDAP_SCOPE_SUBTREE, filter, attrs, state->ctrls, NULL, 0, timeout, sdap_x_deref_parse_entry, state, SDAP_SRCH_FLG_PAGING); if (!subreq) { talloc_zfree(req); return NULL; } tevent_req_set_callback(subreq, sdap_x_deref_search_done, req); return req; } static int sdap_x_deref_create_control(struct sdap_handle *sh, const char *deref_attr, const char **attrs, LDAPControl **ctrl) { struct berval derefval; int ret; struct LDAPDerefSpec ds[2]; ds[0].derefAttr = discard_const(deref_attr); ds[0].attributes = discard_const(attrs); ds[1].derefAttr = NULL; /* sentinel */ ret = ldap_create_deref_control_value(sh->ldap, ds, &derefval); if (ret != LDAP_SUCCESS) { DEBUG(SSSDBG_CRIT_FAILURE, "ldap_create_deref_control_value failed: %s\n", ldap_err2string(ret)); return ret; } ret = sdap_control_create(sh, LDAP_CONTROL_X_DEREF, 1, &derefval, 1, ctrl); ldap_memfree(derefval.bv_val); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "sdap_control_create failed %d\n", ret); return ret; } return EOK; } static errno_t sdap_x_deref_parse_entry(struct sdap_handle *sh, struct sdap_msg *msg, void *pvt) { errno_t ret; LDAPControl **ctrls = NULL; LDAPControl *derefctrl = NULL; LDAPDerefRes *deref_res = NULL; LDAPDerefRes *dref; struct sdap_deref_attrs **res; TALLOC_CTX *tmp_ctx; struct sdap_x_deref_search_state *state = talloc_get_type(pvt, struct sdap_x_deref_search_state); tmp_ctx = talloc_new(NULL); if (!tmp_ctx) return ENOMEM; ret = ldap_get_entry_controls(state->sh->ldap, msg->msg, &ctrls); if (ret != LDAP_SUCCESS) { DEBUG(SSSDBG_OP_FAILURE, "ldap_parse_result failed\n"); goto done; } if (!ctrls) { /* When we attempt to request attributes that are not present in * the dereferenced links, some serves might not send the dereference * control back at all. Be permissive and treat the search as if * it didn't find anything. */ DEBUG(SSSDBG_MINOR_FAILURE, "No controls found for entry\n"); ret = EOK; goto done; } res = NULL; derefctrl = ldap_control_find(LDAP_CONTROL_X_DEREF, ctrls, NULL); if (!derefctrl) { DEBUG(SSSDBG_FUNC_DATA, "No deref controls found\n"); ret = EOK; goto done; } DEBUG(SSSDBG_TRACE_FUNC, "Got deref control\n"); ret = ldap_parse_derefresponse_control(state->sh->ldap, derefctrl, &deref_res); if (ret != LDAP_SUCCESS) { DEBUG(SSSDBG_OP_FAILURE, "ldap_parse_derefresponse_control failed: %s\n", ldap_err2string(ret)); goto done; } for (dref = deref_res; dref; dref=dref->next) { ret = sdap_parse_deref(tmp_ctx, state->maps, state->num_maps, dref, &res); if (ret) { DEBUG(SSSDBG_OP_FAILURE, "sdap_parse_deref failed [%d]: %s\n", ret, strerror(ret)); goto done; } ret = add_to_deref_reply(state, state->num_maps, &state->dreply, res); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "add_to_deref_reply failed.\n"); goto done; } } DEBUG(SSSDBG_TRACE_FUNC, "All deref results from a single control parsed\n"); ldap_derefresponse_free(deref_res); deref_res = NULL; ret = EOK; done: if (ret != EOK && ret != ENOMEM) { if (state->ldap_ignore_unreadable_references) { DEBUG(SSSDBG_TRACE_FUNC, "Ignoring unreadable reference\n"); ret = EOK; } } talloc_zfree(tmp_ctx); ldap_controls_free(ctrls); ldap_derefresponse_free(deref_res); return ret; } static void sdap_x_deref_search_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct sdap_x_deref_search_state *state = tevent_req_data(req, struct sdap_x_deref_search_state); return generic_ext_search_handler(subreq, state->opts); } static int sdap_x_deref_search_ctrls_destructor(void *ptr) { LDAPControl **ctrls = talloc_get_type(ptr, LDAPControl *); if (ctrls && ctrls[0]) { ldap_control_free(ctrls[0]); } return 0; } static int sdap_x_deref_search_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, size_t *reply_count, struct sdap_deref_attrs ***reply) { struct sdap_x_deref_search_state *state = tevent_req_data(req, struct sdap_x_deref_search_state); TEVENT_REQ_RETURN_ON_ERROR(req); *reply_count = state->dreply.reply_count; *reply = talloc_steal(mem_ctx, state->dreply.reply); return EOK; } /* ==Security Descriptor (ACL) search=================================== */ struct sdap_sd_search_state { LDAPControl **ctrls; struct sdap_options *opts; size_t reply_count; struct sysdb_attrs **reply; struct sdap_reply sreply; /* Referrals returned by the search */ size_t ref_count; char **refs; }; static int sdap_sd_search_create_control(struct sdap_handle *sh, int val, LDAPControl **ctrl); static int sdap_sd_search_ctrls_destructor(void *ptr); static errno_t sdap_sd_search_parse_entry(struct sdap_handle *sh, struct sdap_msg *msg, void *pvt); static void sdap_sd_search_done(struct tevent_req *subreq); struct tevent_req * sdap_sd_search_send(TALLOC_CTX *memctx, struct tevent_context *ev, struct sdap_options *opts, struct sdap_handle *sh, const char *base_dn, int sd_flags, const char **attrs, int timeout) { struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct sdap_sd_search_state *state; int ret; req = tevent_req_create(memctx, &state, struct sdap_sd_search_state); if (!req) return NULL; state->ctrls = talloc_zero_array(state, LDAPControl *, 2); state->opts = opts; if (state->ctrls == NULL) { ret = EIO; goto fail; } talloc_set_destructor((TALLOC_CTX *) state->ctrls, sdap_sd_search_ctrls_destructor); ret = sdap_sd_search_create_control(sh, sd_flags, &state->ctrls[0]); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "Could not create SD control\n"); ret = EIO; goto fail; } DEBUG(SSSDBG_TRACE_FUNC, "Searching entry [%s] using SD\n", base_dn); subreq = sdap_get_generic_ext_send(state, ev, opts, sh, base_dn, LDAP_SCOPE_BASE, "(objectclass=*)", attrs, state->ctrls, NULL, 0, timeout, sdap_sd_search_parse_entry, state, SDAP_SRCH_FLG_PAGING); if (!subreq) { ret = EIO; goto fail; } tevent_req_set_callback(subreq, sdap_sd_search_done, req); return req; fail: tevent_req_error(req, ret); tevent_req_post(req, ev); return req; } static int sdap_sd_search_create_control(struct sdap_handle *sh, int val, LDAPControl **ctrl) { struct berval *sdval; int ret; BerElement *ber = NULL; ber = ber_alloc_t(LBER_USE_DER); if (ber == NULL) { DEBUG(SSSDBG_OP_FAILURE, "ber_alloc_t failed.\n"); return ENOMEM; } ret = ber_printf(ber, "{i}", val); if (ret == -1) { DEBUG(SSSDBG_OP_FAILURE, "ber_printf failed.\n"); ber_free(ber, 1); return EIO; } ret = ber_flatten(ber, &sdval); ber_free(ber, 1); if (ret == -1) { DEBUG(SSSDBG_CRIT_FAILURE, "ber_flatten failed.\n"); return EIO; } ret = sdap_control_create(sh, LDAP_SERVER_SD_OID, 1, sdval, 1, ctrl); ber_bvfree(sdval); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "sdap_control_create failed\n"); return ret; } return EOK; } static errno_t sdap_sd_search_parse_entry(struct sdap_handle *sh, struct sdap_msg *msg, void *pvt) { errno_t ret; struct sysdb_attrs *attrs; struct sdap_sd_search_state *state = talloc_get_type(pvt, struct sdap_sd_search_state); bool disable_range_rtrvl = dp_opt_get_bool(state->opts->basic, SDAP_DISABLE_RANGE_RETRIEVAL); ret = sdap_parse_entry(state, sh, msg, NULL, 0, &attrs, disable_range_rtrvl); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "sdap_parse_entry failed [%d]: %s\n", ret, strerror(ret)); return ret; } ret = add_to_reply(state, &state->sreply, attrs); if (ret != EOK) { talloc_free(attrs); DEBUG(SSSDBG_CRIT_FAILURE, "add_to_reply failed.\n"); return ret; } /* add_to_reply steals attrs, no need to free them here */ return EOK; } static void sdap_sd_search_done(struct tevent_req *subreq) { int ret; struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct sdap_sd_search_state *state = tevent_req_data(req, struct sdap_sd_search_state); ret = sdap_get_generic_ext_recv(subreq, state, &state->ref_count, &state->refs); talloc_zfree(subreq); if (ret != EOK) { if (ret == ETIMEDOUT) { DEBUG(SSSDBG_CRIT_FAILURE, "sdap_get_generic_ext_recv request failed: [%d]: %s " "[ldap_network_timeout]\n", ret, sss_strerror(ret)); } else { DEBUG(SSSDBG_CRIT_FAILURE, "sdap_get_generic_ext_recv request failed: [%d]: %s\n", ret, sss_strerror(ret)); } tevent_req_error(req, ret); return; } tevent_req_done(req); } static int sdap_sd_search_ctrls_destructor(void *ptr) { LDAPControl **ctrls = talloc_get_type(ptr, LDAPControl *); if (ctrls && ctrls[0]) { ldap_control_free(ctrls[0]); } return 0; } int sdap_sd_search_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, size_t *_reply_count, struct sysdb_attrs ***_reply, size_t *_ref_count, char ***_refs) { struct sdap_sd_search_state *state = tevent_req_data(req, struct sdap_sd_search_state); TEVENT_REQ_RETURN_ON_ERROR(req); *_reply_count = state->sreply.reply_count; *_reply = talloc_steal(mem_ctx, state->sreply.reply); if(_ref_count) { *_ref_count = state->ref_count; } if (_refs) { *_refs = talloc_steal(mem_ctx, state->refs); } return EOK; } /* ==Attribute scoped search============================================ */ struct sdap_asq_search_state { struct sdap_attr_map_info *maps; int num_maps; LDAPControl **ctrls; struct sdap_options *opts; bool ldap_ignore_unreadable_references; struct sdap_deref_reply dreply; }; static int sdap_asq_search_create_control(struct sdap_handle *sh, const char *attr, LDAPControl **ctrl); static int sdap_asq_search_ctrls_destructor(void *ptr); static errno_t sdap_asq_search_parse_entry(struct sdap_handle *sh, struct sdap_msg *msg, void *pvt); static void sdap_asq_search_done(struct tevent_req *subreq); static struct tevent_req * sdap_asq_search_send(TALLOC_CTX *memctx, struct tevent_context *ev, struct sdap_options *opts, struct sdap_handle *sh, const char *base_dn, const char *deref_attr, const char **attrs, struct sdap_attr_map_info *maps, int num_maps, int timeout) { struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct sdap_asq_search_state *state; int ret; req = tevent_req_create(memctx, &state, struct sdap_asq_search_state); if (!req) return NULL; state->maps = maps; state->num_maps = num_maps; state->ctrls = talloc_zero_array(state, LDAPControl *, 2); state->opts = opts; if (state->ctrls == NULL) { talloc_zfree(req); return NULL; } talloc_set_destructor((TALLOC_CTX *) state->ctrls, sdap_asq_search_ctrls_destructor); state->ldap_ignore_unreadable_references = dp_opt_get_bool(opts->basic, SDAP_IGNORE_UNREADABLE_REFERENCES); ret = sdap_asq_search_create_control(sh, deref_attr, &state->ctrls[0]); if (ret != EOK) { talloc_zfree(req); DEBUG(SSSDBG_CRIT_FAILURE, "Could not create ASQ control\n"); return NULL; } DEBUG(SSSDBG_TRACE_FUNC, "Dereferencing entry [%s] using ASQ\n", base_dn); subreq = sdap_get_generic_ext_send(state, ev, opts, sh, base_dn, LDAP_SCOPE_BASE, NULL, attrs, state->ctrls, NULL, 0, timeout, sdap_asq_search_parse_entry, state, SDAP_SRCH_FLG_PAGING); if (!subreq) { talloc_zfree(req); return NULL; } tevent_req_set_callback(subreq, sdap_asq_search_done, req); return req; } static int sdap_asq_search_create_control(struct sdap_handle *sh, const char *attr, LDAPControl **ctrl) { struct berval *asqval; int ret; BerElement *ber = NULL; ber = ber_alloc_t(LBER_USE_DER); if (ber == NULL) { DEBUG(SSSDBG_OP_FAILURE, "ber_alloc_t failed.\n"); return ENOMEM; } ret = ber_printf(ber, "{s}", attr); if (ret == -1) { DEBUG(SSSDBG_OP_FAILURE, "ber_printf failed.\n"); ber_free(ber, 1); return EIO; } ret = ber_flatten(ber, &asqval); ber_free(ber, 1); if (ret == -1) { DEBUG(SSSDBG_CRIT_FAILURE, "ber_flatten failed.\n"); return EIO; } ret = sdap_control_create(sh, LDAP_SERVER_ASQ_OID, 1, asqval, 1, ctrl); ber_bvfree(asqval); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "sdap_control_create failed\n"); return ret; } return EOK; } static errno_t sdap_asq_search_parse_entry(struct sdap_handle *sh, struct sdap_msg *msg, void *pvt) { errno_t ret; struct sdap_asq_search_state *state = talloc_get_type(pvt, struct sdap_asq_search_state); struct berval **vals; int i, mi; struct sdap_attr_map *map; int num_attrs = 0; struct sdap_deref_attrs **res; char *tmp; char *dn = NULL; TALLOC_CTX *tmp_ctx; bool disable_range_rtrvl; tmp_ctx = talloc_new(NULL); if (!tmp_ctx) return ENOMEM; res = talloc_array(tmp_ctx, struct sdap_deref_attrs *, state->num_maps); if (!res) { ret = ENOMEM; goto done; } for (mi =0; mi < state->num_maps; mi++) { res[mi] = talloc_zero(res, struct sdap_deref_attrs); if (!res[mi]) { ret = ENOMEM; goto done; } res[mi]->map = state->maps[mi].map; res[mi]->attrs = NULL; } tmp = ldap_get_dn(sh->ldap, msg->msg); if (!tmp) { ret = EINVAL; goto done; } dn = talloc_strdup(tmp_ctx, tmp); ldap_memfree(tmp); if (!dn) { ret = ENOMEM; goto done; } /* Find all suitable maps in the list */ vals = ldap_get_values_len(sh->ldap, msg->msg, "objectClass"); if (!vals) { DEBUG(SSSDBG_OP_FAILURE, "Unknown entry type, no objectClass found for DN [%s]!\n", dn); ret = EINVAL; goto done; } for (mi =0; mi < state->num_maps; mi++) { map = NULL; for (i = 0; vals[i]; i++) { /* the objectclass is always the first name in the map */ if (strncasecmp(state->maps[mi].map[0].name, vals[i]->bv_val, vals[i]->bv_len) == 0) { /* it's an entry of the right type */ DEBUG(SSSDBG_TRACE_INTERNAL, "Matched objectclass [%s] on DN [%s], will use associated map\n", state->maps[mi].map[0].name, dn); map = state->maps[mi].map; num_attrs = state->maps[mi].num_attrs; break; } } if (!map) { DEBUG(SSSDBG_TRACE_INTERNAL, "DN [%s] did not match the objectClass [%s]\n", dn, state->maps[mi].map[0].name); continue; } disable_range_rtrvl = dp_opt_get_bool(state->opts->basic, SDAP_DISABLE_RANGE_RETRIEVAL); ret = sdap_parse_entry(res[mi], sh, msg, map, num_attrs, &res[mi]->attrs, disable_range_rtrvl); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "sdap_parse_entry failed [%d]: %s\n", ret, strerror(ret)); goto done; } } ldap_value_free_len(vals); ret = add_to_deref_reply(state, state->num_maps, &state->dreply, res); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "add_to_deref_reply failed.\n"); goto done; } ret = EOK; done: if (ret != EOK && ret != ENOMEM) { if (state->ldap_ignore_unreadable_references) { DEBUG(SSSDBG_TRACE_FUNC, "Ignoring unreadable reference [%s]\n", dn != NULL ? dn : "(null)"); ret = EOK; } } talloc_zfree(tmp_ctx); return ret; } static void sdap_asq_search_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct sdap_asq_search_state *state = tevent_req_data(req, struct sdap_asq_search_state); return generic_ext_search_handler(subreq, state->opts); } static int sdap_asq_search_ctrls_destructor(void *ptr) { LDAPControl **ctrls = talloc_get_type(ptr, LDAPControl *); if (ctrls && ctrls[0]) { ldap_control_free(ctrls[0]); } return 0; } int sdap_asq_search_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, size_t *reply_count, struct sdap_deref_attrs ***reply) { struct sdap_asq_search_state *state = tevent_req_data(req, struct sdap_asq_search_state); TEVENT_REQ_RETURN_ON_ERROR(req); *reply_count = state->dreply.reply_count; *reply = talloc_steal(mem_ctx, state->dreply.reply); return EOK; } /* ==Generic Deref Search============================================ */ enum sdap_deref_type { SDAP_DEREF_OPENLDAP, SDAP_DEREF_ASQ }; struct sdap_deref_search_state { struct sdap_handle *sh; const char *base_dn; const char *deref_attr; size_t reply_count; struct sdap_deref_attrs **reply; enum sdap_deref_type deref_type; unsigned flags; }; static void sdap_deref_search_done(struct tevent_req *subreq); static void sdap_deref_search_with_filter_done(struct tevent_req *subreq); struct tevent_req * sdap_deref_search_with_filter_send(TALLOC_CTX *memctx, struct tevent_context *ev, struct sdap_options *opts, struct sdap_handle *sh, const char *search_base, const char *filter, const char *deref_attr, const char **attrs, int num_maps, struct sdap_attr_map_info *maps, int timeout, unsigned flags) { struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct sdap_deref_search_state *state; req = tevent_req_create(memctx, &state, struct sdap_deref_search_state); if (!req) return NULL; state->sh = sh; state->reply_count = 0; state->reply = NULL; state->flags = flags; if (sdap_is_control_supported(sh, LDAP_CONTROL_X_DEREF)) { DEBUG(SSSDBG_TRACE_INTERNAL, "Server supports OpenLDAP deref\n"); state->deref_type = SDAP_DEREF_OPENLDAP; subreq = sdap_x_deref_search_send(state, ev, opts, sh, search_base, filter, deref_attr, attrs, maps, num_maps, timeout); if (!subreq) { DEBUG(SSSDBG_OP_FAILURE, "Cannot start OpenLDAP deref search\n"); goto fail; } } else { DEBUG(SSSDBG_OP_FAILURE, "Server does not support any known deref method!\n"); goto fail; } tevent_req_set_callback(subreq, sdap_deref_search_with_filter_done, req); return req; fail: talloc_zfree(req); return NULL; } static void sdap_deref_search_with_filter_done(struct tevent_req *subreq) { sdap_deref_search_done(subreq); } int sdap_deref_search_with_filter_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, size_t *reply_count, struct sdap_deref_attrs ***reply) { return sdap_deref_search_recv(req, mem_ctx, reply_count, reply); } struct tevent_req * sdap_deref_search_send(TALLOC_CTX *memctx, struct tevent_context *ev, struct sdap_options *opts, struct sdap_handle *sh, const char *base_dn, const char *deref_attr, const char **attrs, int num_maps, struct sdap_attr_map_info *maps, int timeout) { struct tevent_req *req = NULL; struct tevent_req *subreq = NULL; struct sdap_deref_search_state *state; req = tevent_req_create(memctx, &state, struct sdap_deref_search_state); if (!req) return NULL; state->sh = sh; state->reply_count = 0; state->reply = NULL; state->base_dn = base_dn; state->deref_attr = deref_attr; PROBE(SDAP_DEREF_SEARCH_SEND, state->base_dn, state->deref_attr); if (sdap_is_control_supported(sh, LDAP_SERVER_ASQ_OID)) { DEBUG(SSSDBG_TRACE_INTERNAL, "Server supports ASQ\n"); state->deref_type = SDAP_DEREF_ASQ; subreq = sdap_asq_search_send(state, ev, opts, sh, base_dn, deref_attr, attrs, maps, num_maps, timeout); if (!subreq) { DEBUG(SSSDBG_OP_FAILURE, "Cannot start ASQ search\n"); goto fail; } } else if (sdap_is_control_supported(sh, LDAP_CONTROL_X_DEREF)) { DEBUG(SSSDBG_TRACE_INTERNAL, "Server supports OpenLDAP deref\n"); state->deref_type = SDAP_DEREF_OPENLDAP; subreq = sdap_x_deref_search_send(state, ev, opts, sh, base_dn, NULL, deref_attr, attrs, maps, num_maps, timeout); if (!subreq) { DEBUG(SSSDBG_OP_FAILURE, "Cannot start OpenLDAP deref search\n"); goto fail; } } else { DEBUG(SSSDBG_OP_FAILURE, "Server does not support any known deref method!\n"); goto fail; } tevent_req_set_callback(subreq, sdap_deref_search_done, req); return req; fail: talloc_zfree(req); return NULL; } static void sdap_deref_search_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct sdap_deref_search_state *state = tevent_req_data(req, struct sdap_deref_search_state); int ret; switch (state->deref_type) { case SDAP_DEREF_OPENLDAP: ret = sdap_x_deref_search_recv(subreq, state, &state->reply_count, &state->reply); break; case SDAP_DEREF_ASQ: ret = sdap_asq_search_recv(subreq, state, &state->reply_count, &state->reply); break; default: DEBUG(SSSDBG_CRIT_FAILURE, "Unknown deref method %d\n", state->deref_type); tevent_req_error(req, EINVAL); return; } talloc_zfree(subreq); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "dereference processing failed [%d]: %s\n", ret, strerror(ret)); if (ret == ENOTSUP) { state->sh->disable_deref = true; } if (!(state->flags & SDAP_DEREF_FLG_SILENT)) { if (ret == ENOTSUP) { sss_log(SSS_LOG_WARNING, "LDAP server claims to support deref, but deref search " "failed. Disabling deref for further requests. You can " "permanently disable deref by setting " "ldap_deref_threshold to 0 in domain configuration."); } else { sss_log(SSS_LOG_WARNING, "dereference processing failed : %s", strerror(ret)); } } tevent_req_error(req, ret); return; } tevent_req_done(req); } int sdap_deref_search_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, size_t *reply_count, struct sdap_deref_attrs ***reply) { struct sdap_deref_search_state *state = tevent_req_data(req, struct sdap_deref_search_state); PROBE(SDAP_DEREF_SEARCH_RECV, state->base_dn, state->deref_attr); TEVENT_REQ_RETURN_ON_ERROR(req); *reply_count = state->reply_count; *reply = talloc_steal(mem_ctx, state->reply); return EOK; } bool sdap_has_deref_support_ex(struct sdap_handle *sh, struct sdap_options *opts, bool ignore_client) { const char *deref_oids[][2] = { { LDAP_SERVER_ASQ_OID, "ASQ" }, { LDAP_CONTROL_X_DEREF, "OpenLDAP" }, { NULL, NULL } }; int i; int deref_threshold; if (sh->disable_deref) { return false; } if (ignore_client == false) { deref_threshold = dp_opt_get_int(opts->basic, SDAP_DEREF_THRESHOLD); if (deref_threshold == 0) { return false; } } for (i=0; deref_oids[i][0]; i++) { if (sdap_is_control_supported(sh, deref_oids[i][0])) { DEBUG(SSSDBG_TRACE_FUNC, "The server supports deref method %s\n", deref_oids[i][1]); return true; } } return false; } bool sdap_has_deref_support(struct sdap_handle *sh, struct sdap_options *opts) { return sdap_has_deref_support_ex(sh, opts, false); }